From 1af82604b55839275998d6afe7ee51a9ffbfdfe4 Mon Sep 17 00:00:00 2001 From: Monty Marz Date: Sat, 4 Apr 2020 14:39:54 +0000 Subject: [PATCH 001/195] German Translation --- CHANGELOG.md | 1 + .../haxrcorp_4089_cyrillic_altgr_extended.ttf | 4 +- assets/voxygen/i18n/de_DE.ron | 389 ++++++++++++++++++ assets/voxygen/i18n/en.ron | 5 +- 4 files changed, 395 insertions(+), 4 deletions(-) create mode 100644 assets/voxygen/i18n/de_DE.ron diff --git a/CHANGELOG.md b/CHANGELOG.md index ada5e7fbfc..ca2dbe62bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ 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 ### Changed 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..098ad80e20 --- /dev/null +++ b/assets/voxygen/i18n/de_DE.ron @@ -0,0 +1,389 @@ +/// 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! + +/// Localization for "global" English +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. + +Drückt Enter und tippt /waypoint in den Chat um den Wegpunkt hier zu erstellen."#, + + /// 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": "s Inventar", + "hud.bag.stats_title": "s Werte", + "hud.bag.exp": "Erf", + "hud.bag.armor": "Rüstung", + "hud.bag.stats": "Werte", + + // 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": "Freie Sicht", + + "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", + + /// 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 + +Auto Walk + +Sheathe/Draw Weapons + +Put on/Remove Helmet + +Sit + + +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 + + +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.social": "Andere Spieler", + "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": "Online Spieler", + "hud.social.faction": "Fraktion", + + + "hud.spell": "Zauber", + + "hud.free_look_indicator": "Freies Umsehen Aktiv", + /// End HUD 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..987962b814 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( @@ -228,8 +230,7 @@ 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", From d36c2cac99bc22cf1dbe83f8028d6deeef2de2d7 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sat, 4 Apr 2020 12:40:38 -0400 Subject: [PATCH 002/195] Trim newlines from password when pressing enter --- voxygen/src/menu/main/ui.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/voxygen/src/menu/main/ui.rs b/voxygen/src/menu/main/ui.rs index 0ba0007e33..9cf5161021 100644 --- a/voxygen/src/menu/main/ui.rs +++ b/voxygen/src/menu/main/ui.rs @@ -550,6 +550,7 @@ impl MainMenuUi { self.password = password; }, TextBoxEvent::Enter => { + self.password.pop(); login!(); }, } From a21ae27a774cdefcb9b2f1e1b91b0bd12a435547 Mon Sep 17 00:00:00 2001 From: Capucho Date: Sat, 4 Apr 2020 19:36:55 +0000 Subject: [PATCH 003/195] Add player transparency and silhouette --- CHANGELOG.md | 3 +- assets/voxygen/shaders/figure-frag.glsl | 13 +- assets/voxygen/shaders/figure-vert.glsl | 3 + assets/voxygen/shaders/include/globals.glsl | 1 + .../voxygen/shaders/player-shadow-frag.glsl | 33 ++ voxygen/src/render/mod.rs | 2 +- voxygen/src/render/pipelines/figure.rs | 16 +- voxygen/src/render/pipelines/fluid.rs | 7 +- voxygen/src/render/pipelines/mod.rs | 5 + voxygen/src/render/pipelines/skybox.rs | 5 +- voxygen/src/render/pipelines/sprite.rs | 7 +- voxygen/src/render/pipelines/terrain.rs | 5 +- voxygen/src/render/renderer.rs | 150 +++++- voxygen/src/scene/camera.rs | 4 +- voxygen/src/scene/figure/mod.rs | 485 +++++++++++------- voxygen/src/scene/mod.rs | 26 +- voxygen/src/scene/simple.rs | 2 + 17 files changed, 517 insertions(+), 250 deletions(-) create mode 100644 assets/voxygen/shaders/player-shadow-frag.glsl diff --git a/CHANGELOG.md b/CHANGELOG.md index ca2dbe62bf..3c66b44eac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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 ### Changed @@ -56,7 +58,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Asset cleanup to lower client-size - Rewrote the humanoid skeleton to be more ideal for attack animations - ### Removed ## [0.5.0] - 2019-01-31 diff --git a/assets/voxygen/shaders/figure-frag.glsl b/assets/voxygen/shaders/figure-frag.glsl index aba60bf8d5..d02fba8f17 100644 --- a/assets/voxygen/shaders/figure-frag.glsl +++ b/assets/voxygen/shaders/figure-frag.glsl @@ -10,6 +10,9 @@ layout (std140) uniform u_locals { mat4 model_mat; vec4 model_col; + // bit 0 - is player + // bit 1-31 - unused + int flags; }; struct BoneData { @@ -43,5 +46,13 @@ void main() { vec3 fog_color = get_sky_color(normalize(f_pos - cam_pos.xyz), 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); - tgt_color = vec4(color, 1.0); + float opacity = 1.0; + + if ((flags & 1) == 1 && int(cam_mode) == 1) { + float distance = distance(vec3(cam_pos), f_pos) - 1; + + opacity = clamp(distance / 3, 0, 1); + } + + tgt_color = vec4(color, opacity); } diff --git a/assets/voxygen/shaders/figure-vert.glsl b/assets/voxygen/shaders/figure-vert.glsl index eced69e5ea..c42e5d68bc 100644 --- a/assets/voxygen/shaders/figure-vert.glsl +++ b/assets/voxygen/shaders/figure-vert.glsl @@ -11,6 +11,9 @@ layout (std140) uniform u_locals { mat4 model_mat; vec4 model_col; + // bit 0 - is player + // bit 1-31 - unused + int flags; }; struct BoneData { diff --git a/assets/voxygen/shaders/include/globals.glsl b/assets/voxygen/shaders/include/globals.glsl index e0e97949e2..fb80fe9909 100644 --- a/assets/voxygen/shaders/include/globals.glsl +++ b/assets/voxygen/shaders/include/globals.glsl @@ -13,4 +13,5 @@ uniform u_globals { uvec4 medium; ivec4 select_pos; vec4 gamma; + uint cam_mode; }; diff --git a/assets/voxygen/shaders/player-shadow-frag.glsl b/assets/voxygen/shaders/player-shadow-frag.glsl new file mode 100644 index 0000000000..66c0dac893 --- /dev/null +++ b/assets/voxygen/shaders/player-shadow-frag.glsl @@ -0,0 +1,33 @@ +#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() { + tgt_color = vec4(0.0,0.0,0.0, 1.0); +} \ No newline at end of file diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs index e848b80ed0..906493865b 100644 --- a/voxygen/src/render/mod.rs +++ b/voxygen/src/render/mod.rs @@ -30,7 +30,7 @@ pub use self::{ }, Globals, Light, Shadow, }, - renderer::{Renderer, TgtColorFmt, TgtDepthFmt, WinColorFmt, WinDepthFmt}, + renderer::{Renderer, TgtColorFmt, TgtDepthStencilFmt, WinColorFmt, WinDepthFmt}, texture::Texture, }; diff --git a/voxygen/src/render/pipelines/figure.rs b/voxygen/src/render/pipelines/figure.rs index ace2a54533..94a252e68c 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::*; @@ -19,6 +20,7 @@ gfx_defines! { constant Locals { model_mat: [[f32; 4]; 4] = "model_mat", model_col: [f32; 4] = "model_col", + flags: u32 = "flags", } constant BoneData { @@ -36,8 +38,8 @@ 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))), } } @@ -58,16 +60,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 ec13e96df9..dfae0af2df 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::*; @@ -27,7 +28,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))), } } diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs index 4e6fd5b282..a4edd0e598 100644 --- a/voxygen/src/render/pipelines/mod.rs +++ b/voxygen/src/render/pipelines/mod.rs @@ -7,6 +7,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::*; @@ -27,6 +28,7 @@ gfx_defines! { medium: [u32; 4] = "medium", select_pos: [i32; 4] = "select_pos", gamma: [f32; 4] = "gamma", + cam_mode: u32 = "cam_mode", } constant Light { @@ -55,6 +57,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()), @@ -73,6 +76,7 @@ impl Globals { .unwrap_or(Vec4::zero()) .into_array(), gamma: [gamma; 4], + cam_mode: cam_mode as u32, } } } @@ -93,6 +97,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 31178e4b1d..b39ca90a17 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::*; @@ -35,7 +36,7 @@ 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))), } } diff --git a/voxygen/src/render/pipelines/terrain.rs b/voxygen/src/render/pipelines/terrain.rs index 6899a8eb18..8fe5658a6b 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::*; @@ -31,7 +32,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/renderer.rs b/voxygen/src/render/renderer.rs index 3cf3ff7fcb..ab369c7419 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -12,6 +12,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; @@ -20,8 +21,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; @@ -31,7 +32,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; @@ -57,7 +59,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, @@ -70,6 +72,7 @@ pub struct Renderer { sprite_pipeline: GfxPipeline>, ui_pipeline: GfxPipeline>, postprocess_pipeline: GfxPipeline>, + player_shadow_pipeline: GfxPipeline>, shader_reload_indicator: ReloadIndicator, @@ -102,6 +105,7 @@ impl Renderer { sprite_pipeline, ui_pipeline, postprocess_pipeline, + player_shadow_pipeline, ) = create_pipelines( &mut factory, aa_mode, @@ -111,7 +115,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(); @@ -132,7 +136,7 @@ impl Renderer { win_depth_view, tgt_color_view, - tgt_depth_view, + tgt_depth_stencil_view, tgt_color_res, sampler, @@ -144,6 +148,7 @@ impl Renderer { sprite_pipeline, ui_pipeline, postprocess_pipeline, + player_shadow_pipeline, shader_reload_indicator, @@ -158,8 +163,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 @@ -172,8 +177,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 @@ -228,11 +233,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(()) @@ -242,7 +247,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) @@ -279,17 +284,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. @@ -303,7 +309,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); } @@ -336,6 +343,7 @@ impl Renderer { sprite_pipeline, ui_pipeline, postprocess_pipeline, + player_shadow_pipeline, )) => { self.skybox_pipeline = skybox_pipeline; self.figure_pipeline = figure_pipeline; @@ -344,6 +352,7 @@ impl Renderer { self.sprite_pipeline = sprite_pipeline; self.ui_pipeline = ui_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: {:#?}", @@ -502,7 +511,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)), }, ); } @@ -535,7 +544,73 @@ impl Renderer { shadows: shadows.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)), + }, + ); + } + + /// 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, + ) { + 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()), + 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, + ) { + 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()), + tgt_color: self.tgt_color_view.clone(), + tgt_depth_stencil: (self.tgt_depth_stencil_view.clone(), (1, 1)), }, ); } @@ -567,7 +642,7 @@ impl Renderer { shadows: shadows.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)), }, ); } @@ -601,7 +676,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)), }, ); } @@ -633,7 +708,7 @@ impl Renderer { shadows: shadows.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)), }, ); } @@ -721,6 +796,7 @@ fn create_pipelines( GfxPipeline>, GfxPipeline>, GfxPipeline>, + GfxPipeline>, ), RenderError, > { @@ -869,6 +945,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, @@ -877,6 +978,7 @@ fn create_pipelines( sprite_pipeline, ui_pipeline, postprocess_pipeline, + player_shadow_pipeline, )) } diff --git a/voxygen/src/scene/camera.rs b/voxygen/src/scene/camera.rs index bb29781c25..3847b0293e 100644 --- a/voxygen/src/scene/camera.rs +++ b/voxygen/src/scene/camera.rs @@ -13,8 +13,8 @@ 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 { diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 5fa3f1e485..c9ac36634c 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, ); }, } @@ -1386,201 +1399,110 @@ 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, + 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, + 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 loadout_storage = ecs.read_storage::(); + let loadout = loadout_storage.get(player_entity); + + self.render_figure( + renderer, + tick, + globals, + lights, + shadows, + camera, + character_state, + player_entity, + body, + loadout, + true, + ); + } + } + + fn render_figure( + &mut self, + renderer: &mut Renderer, + tick: u64, + globals: &Consts, + lights: &Consts, + shadows: &Consts, + 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(), @@ -1596,11 +1518,175 @@ impl FigureMgr { .0, ) }), - } { - renderer.render_figure(model, globals, locals, bone_consts, lights, shadows); + 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); + renderer.render_player_shadow(model, globals, locals, bone_consts, lights, shadows); } else { - trace!("Body has no saved figure"); + renderer.render_figure(model, globals, locals, bone_consts, lights, shadows); } + } else { + trace!("Body has no saved figure"); } } @@ -1705,6 +1791,7 @@ impl FigureState { state_animation_rate: f32, lpindex: u8, visible: bool, + is_player: bool, ) { self.visible = visible; self.lpindex = lpindex; @@ -1720,7 +1807,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 d9d28d6e0f..bbb4efe64d 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -347,6 +347,7 @@ impl Scene { .unwrap_or(BlockKind::Air), self.select_pos, gamma, + self.camera.get_mode(), )]) .expect("Failed to update global constants"); @@ -381,6 +382,13 @@ impl Scene { tick: u64, ) { // Render terrain and figures. + self.terrain.render( + renderer, + &self.globals, + &self.lights, + &self.shadows, + self.camera.get_focus_pos(), + ); self.figure_mgr.render( renderer, state, @@ -391,13 +399,6 @@ impl Scene { &self.shadows, &self.camera, ); - self.terrain.render( - renderer, - &self.globals, - &self.lights, - &self.shadows, - self.camera.get_focus_pos(), - ); // Render the skybox. renderer.render_skybox(&self.skybox.model, &self.globals, &self.skybox.locals); @@ -410,6 +411,17 @@ impl Scene { self.camera.get_focus_pos(), ); + self.figure_mgr.render_player( + renderer, + state, + player_entity, + tick, + &self.globals, + &self.lights, + &self.shadows, + &self.camera, + ); + renderer.render_post_process( &self.postprocess.model, &self.globals, diff --git a/voxygen/src/scene/simple.rs b/voxygen/src/scene/simple.rs index 34aaec83e4..e663fb841f 100644 --- a/voxygen/src/scene/simple.rs +++ b/voxygen/src/scene/simple.rs @@ -170,6 +170,7 @@ impl Scene { BlockKind::Air, None, scene_data.gamma, + self.camera.get_mode(), )]) { error!("Renderer failed to update: {:?}", err); } @@ -199,6 +200,7 @@ impl Scene { 1.0, 0, true, + false, ); } From 07242644cac44c47adad918f5692d6677b0ab245 Mon Sep 17 00:00:00 2001 From: Monty Marz Date: Sat, 4 Apr 2020 21:51:06 +0000 Subject: [PATCH 004/195] Small UI fixes --- assets/voxygen/i18n/de_DE.ron | 4 ++-- assets/voxygen/i18n/en.ron | 7 +------ voxygen/src/hud/bag.rs | 2 -- voxygen/src/hud/mod.rs | 32 +++++++++++++++++++++----------- voxygen/src/menu/main/ui.rs | 2 +- 5 files changed, 25 insertions(+), 22 deletions(-) diff --git a/assets/voxygen/i18n/de_DE.ron b/assets/voxygen/i18n/de_DE.ron index 098ad80e20..d28ae8fc35 100644 --- a/assets/voxygen/i18n/de_DE.ron +++ b/assets/voxygen/i18n/de_DE.ron @@ -10,7 +10,7 @@ /// To add a new language in Veloren, juste write an additional `.ron` file in /// `assets/voxygen/i18n` and that's it! -/// Localization for "global" English +/// Lokalisation für Deutsch/Deutschland VoxygenLocalization( metadata: ( language_name: "Deutsch", @@ -238,7 +238,7 @@ Viel Spaß in der Welt von Veloren, Abenteurer!"#, "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": "Freie Sicht", + "hud.settings.free_look_behavior": "Freies Umsehen", "hud.settings.view_distance": "Sichtweite", "hud.settings.maximum_fps": "Maximale FPS", diff --git a/assets/voxygen/i18n/en.ron b/assets/voxygen/i18n/en.ron index 987962b814..d1ca9fb2cc 100644 --- a/assets/voxygen/i18n/en.ron +++ b/assets/voxygen/i18n/en.ron @@ -156,17 +156,12 @@ https://account.veloren.net."#, "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 diff --git a/voxygen/src/hud/bag.rs b/voxygen/src/hud/bag.rs index 8bb4a2a7d8..0c9708d9ad 100644 --- a/voxygen/src/hud/bag.rs +++ b/voxygen/src/hud/bag.rs @@ -149,9 +149,7 @@ pub enum Event { Stats, Close, } -/* -*/ impl<'a> Widget for Bag<'a> { type Event = Option; type State = State; diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 706f909c90..fe58075ddb 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -42,6 +42,7 @@ use crate::{ 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, @@ -305,6 +306,7 @@ pub struct Show { impl Show { fn bag(&mut self, open: bool) { self.bag = open; + self.map = false; self.want_grab = !open; } @@ -361,6 +363,8 @@ impl Show { || self.map || self.social || self.spell + || self.help + || self.intro || match self.open_windows { Windows::None => false, _ => true, @@ -368,6 +372,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; @@ -1239,23 +1245,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) @@ -1550,14 +1560,14 @@ impl Hud { 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) @@ -1569,7 +1579,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) diff --git a/voxygen/src/menu/main/ui.rs b/voxygen/src/menu/main/ui.rs index 0ba0007e33..d704d0ab5a 100644 --- a/voxygen/src/menu/main/ui.rs +++ b/voxygen/src/menu/main/ui.rs @@ -534,7 +534,7 @@ 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) + .mid_bottom_with_margin_on(self.ids.password_bg, 17.0) .font_size(self.fonts.cyri.scale(22)) .font_id(self.fonts.cyri.conrod_id) .text_color(TEXT_COLOR) From 84b1abdfcb73205d9a84447ec02d5758b5d7e2a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Korg=C3=B3l?= Date: Mon, 6 Apr 2020 20:35:29 +0200 Subject: [PATCH 005/195] Prevent projectiles from hitting their owners --- CHANGELOG.md | 1 + common/src/sys/projectile.rs | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c66b44eac..89cde4526b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,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/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)); + } } }, _ => {}, From 8f6686adc1907e978824714fbb916146cc3c7183 Mon Sep 17 00:00:00 2001 From: Capucho Date: Tue, 7 Apr 2020 22:50:01 +0000 Subject: [PATCH 006/195] Fix the problems of players walking over water with dithering --- assets/voxygen/shaders/figure-frag.glsl | 12 +++++++----- assets/voxygen/shaders/include/globals.glsl | 12 ++++++++++++ assets/voxygen/shaders/player-shadow-frag.glsl | 12 ++++++++++++ voxygen/src/scene/mod.rs | 16 ++++++++-------- 4 files changed, 39 insertions(+), 13 deletions(-) diff --git a/assets/voxygen/shaders/figure-frag.glsl b/assets/voxygen/shaders/figure-frag.glsl index d02fba8f17..df2439e47d 100644 --- a/assets/voxygen/shaders/figure-frag.glsl +++ b/assets/voxygen/shaders/figure-frag.glsl @@ -46,13 +46,15 @@ void main() { vec3 fog_color = get_sky_color(normalize(f_pos - cam_pos.xyz), 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); - float opacity = 1.0; - if ((flags & 1) == 1 && int(cam_mode) == 1) { - float distance = distance(vec3(cam_pos), f_pos) - 1; + float distance = distance(vec3(cam_pos), vec3(model_mat * vec4(vec3(0), 1))) - 2; + + float opacity = clamp(distance / distance_divider, 0, 1); - opacity = clamp(distance / 3, 0, 1); + if(threshold_matrix[int(gl_FragCoord.x) % 4][int(gl_FragCoord.y) % 4] > opacity) { + discard; + } } - tgt_color = vec4(color, opacity); + tgt_color = vec4(color, 1.0); } diff --git a/assets/voxygen/shaders/include/globals.glsl b/assets/voxygen/shaders/include/globals.glsl index fb80fe9909..b35fab1228 100644 --- a/assets/voxygen/shaders/include/globals.glsl +++ b/assets/voxygen/shaders/include/globals.glsl @@ -13,5 +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/player-shadow-frag.glsl b/assets/voxygen/shaders/player-shadow-frag.glsl index 66c0dac893..25bd05ed0a 100644 --- a/assets/voxygen/shaders/player-shadow-frag.glsl +++ b/assets/voxygen/shaders/player-shadow-frag.glsl @@ -29,5 +29,17 @@ uniform u_bones { 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/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index bbb4efe64d..d8a2fbe300 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -403,14 +403,6 @@ impl Scene { // Render the skybox. renderer.render_skybox(&self.skybox.model, &self.globals, &self.skybox.locals); - self.terrain.render_translucent( - renderer, - &self.globals, - &self.lights, - &self.shadows, - self.camera.get_focus_pos(), - ); - self.figure_mgr.render_player( renderer, state, @@ -422,6 +414,14 @@ impl Scene { &self.camera, ); + self.terrain.render_translucent( + renderer, + &self.globals, + &self.lights, + &self.shadows, + self.camera.get_focus_pos(), + ); + renderer.render_post_process( &self.postprocess.model, &self.globals, From 198c875559dbb1895249ea5d959b61dbff8901bf Mon Sep 17 00:00:00 2001 From: Carbonhell Date: Wed, 8 Apr 2020 17:36:37 +0000 Subject: [PATCH 007/195] Carbonhell/keybindings --- CHANGELOG.md | 1 + assets/voxygen/i18n/en.ron | 127 ++++++--------- voxygen/src/hud/mod.rs | 120 ++++++++------- voxygen/src/hud/settings_window.rs | 227 +++++++++++---------------- voxygen/src/hud/skillbar.rs | 62 ++++---- voxygen/src/session.rs | 3 + voxygen/src/settings.rs | 240 ++++++++++++++++++++--------- voxygen/src/window.rs | 221 +++++++++++--------------- 8 files changed, 493 insertions(+), 508 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89cde4526b..c02676c7ba 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 diff --git a/assets/voxygen/i18n/en.ron b/assets/voxygen/i18n/en.ron index d1ca9fb2cc..368f02145b 100644 --- a/assets/voxygen/i18n/en.ron +++ b/assets/voxygen/i18n/en.ron @@ -250,90 +250,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,6 +265,48 @@ Chat commands: /// End HUD section + /// Start GameInput section + + "gameinput.primary": "Basic Attack", + "gameinput.secondary": "Secondary Attack/Block/Aim", + "gameinput.ability3": "Hotbar Slot 1", + "gameinput.swaploadout": "Swap Loadout", + "gameinput.togglecursor": "Free 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": "Spell Book", + "gameinput.settings": "Settings", + "gameinput.respawn": "Respawn", + "gameinput.charge": "Charge", + "gameinput.togglewield": "Toggle Wield", + "gameinput.interact": "Interact", + "gameinput.freelook": "Free look behavior", + + /// End GameInput section + + /// Start chracter selection section "char_selection.delete_permanently": "Permanently delete this Character?", "char_selection.change_server": "Change Server", diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index fe58075ddb..ebe1d3f163 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -237,6 +237,7 @@ pub enum Event { Logout, Quit, ChangeLanguage(LanguageMetadata), + ChangeBinding(GameInput), ChangeFreeLookBehavior(PressBehavior), } @@ -1494,66 +1495,70 @@ 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 @@ -1822,6 +1827,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)); }, diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs index d8a3db3109..8ead5fb68d 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, @@ -221,6 +225,7 @@ pub enum Event { SctPlayerBatch(bool), SctDamageBatch(bool), ChangeLanguage(LanguageMetadata), + ChangeBinding(GameInput), ChangeFreeLookBehavior(PressBehavior), } @@ -1328,142 +1333,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..036cbc8302 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -5,6 +5,7 @@ use super::{ use crate::{ i18n::{i18n_asset_key, VoxygenLocalization}, ui::fonts::ConrodVoxygenFonts, + window::GameInput, GlobalState, }; use common::{ @@ -300,36 +301,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 { diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 4fa2b2b833..61bfcd3d92 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -713,6 +713,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; }, diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index fddf225fb1..c062ae420f 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,135 @@ 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::Ability3 => KeyMouse::Key(VirtualKeyCode::Key1), + 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::Ability3, + GameInput::SwapLoadout, + ]; + for game_input in game_inputs { + new_settings.insert_binding(game_input, ControlSettings::default_binding(game_input)); } + new_settings } } diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index f54e0eabf1..9b80288f37 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -1,7 +1,7 @@ use crate::{ controller::*, render::{Renderer, WinColorFmt, WinDepthFmt}, - settings::Settings, + settings::{ControlSettings, Settings}, ui, Error, }; use gilrs::{EventType, Gilrs}; @@ -12,7 +12,7 @@ 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, @@ -34,8 +34,6 @@ pub enum GameInput { Escape, Map, Bag, - QuestLog, - CharacterWindow, Social, Spellbook, Settings, @@ -54,6 +52,49 @@ pub enum GameInput { 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::Ability3 => "gameinput.ability3", + 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 { @@ -319,8 +360,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 +396,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 +441,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 +474,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,23 +509,27 @@ 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 Some(game_inputs) = Window::map_input( + KeyMouse::Mouse(button), + controls, + remapping_keybindings, + ) { for game_input in game_inputs { events.push(Event::InputUpdate( *game_input, state == glutin::ElementState::Pressed, )); } + events.push(Event::MouseButton(button, state)); } - 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 +567,9 @@ impl Window { } } } - }, - _ => {}, + } }, + glutin::WindowEvent::Focused(state) => { *focused = state; events.push(Event::Focused(state)); @@ -1000,4 +936,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); + } } From b6078d832ae7dc25fc0e2e6300f2d0c27ba1743b Mon Sep 17 00:00:00 2001 From: Capucho Date: Wed, 8 Apr 2020 21:28:33 +0100 Subject: [PATCH 008/195] Fixed the player being rendered after dying --- voxygen/src/scene/figure/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index c9ac36634c..1686f091e6 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -1435,6 +1435,13 @@ impl FigureMgr { 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); From 20ec77eeb0b2dcbeb2b6e1595e7c0b9b175e7cb9 Mon Sep 17 00:00:00 2001 From: Carbonhell Date: Wed, 8 Apr 2020 23:46:42 +0200 Subject: [PATCH 009/195] Fixes mouseinput event ignoring cursor_grabbed being true --- voxygen/src/window.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index 9b80288f37..7229f007d0 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -509,10 +509,13 @@ impl Window { }, glutin::WindowEvent::ReceivedCharacter(c) => events.push(Event::Char(c)), glutin::WindowEvent::MouseInput { button, state, .. } => { - if let Some(game_inputs) = Window::map_input( - KeyMouse::Mouse(button), - controls, - remapping_keybindings, + 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( From 342e46afcbbc7417efdfd842e5404204d15a6873 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 9 Apr 2020 11:22:34 +0000 Subject: [PATCH 010/195] Install meta package librust-backtrace+libbacktrace-dev in debian, this might fix the problem that we dont have server backtraces. According to its documentation its specifically tailed down to deliver everything for rust backtrace crate: https://packages.debian.org/buster/librust-backtrace+libbacktrace-dev The official requierement would be install `cc` and `ar`, where `ar` is in binutils, and `cc` seems to be in gcc-8 or a subpackage of it. Both would requiere about 100MB additionally for backtraces, while this package should requiere additional 8MB --- server-cli/Dockerfile | 6 ++++++ 1 file changed, 6 insertions(+) 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 From c0c92c425942a9fca41d709c8128106745dc9919 Mon Sep 17 00:00:00 2001 From: Carbonhell Date: Thu, 9 Apr 2020 18:43:12 +0200 Subject: [PATCH 011/195] Fixes mouse_input event not being pushed everytime --- voxygen/src/window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index 7229f007d0..da2ff7f6a9 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -523,8 +523,8 @@ impl Window { state == glutin::ElementState::Pressed, )); } - events.push(Event::MouseButton(button, state)); } + events.push(Event::MouseButton(button, state)); }, glutin::WindowEvent::KeyboardInput { input, .. } => { if let Some(key) = input.virtual_keycode { From 542491c48dd8f25df5bde77a83805934bb0c6d67 Mon Sep 17 00:00:00 2001 From: Treeco <5021038-Treeco@users.noreply.gitlab.com> Date: Thu, 6 Feb 2020 19:09:34 +0000 Subject: [PATCH 012/195] Fixed leaf lerp doing weird things --- world/src/block/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index a994baf712..8f60479481 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -579,9 +579,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, From 7c0a3bafe6adea15887561456b89a19c9cfc3975 Mon Sep 17 00:00:00 2001 From: Olexorus Date: Wed, 8 Apr 2020 14:40:17 +0200 Subject: [PATCH 013/195] Change how fall damage works --- server/src/events/entity_manipulation.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) 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, + }); } } } From 4bd7e5ab676b16ab3103294f26b66b7881d58fc4 Mon Sep 17 00:00:00 2001 From: Olexorus Date: Sun, 12 Apr 2020 13:40:52 +0200 Subject: [PATCH 014/195] Allow passwords with up to 35 characters --- voxygen/src/menu/main/ui.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/voxygen/src/menu/main/ui.rs b/voxygen/src/menu/main/ui.rs index 39f3e32175..da1d67660b 100644 --- a/voxygen/src/menu/main/ui.rs +++ b/voxygen/src/menu/main/ui.rs @@ -535,7 +535,9 @@ impl MainMenuUi { for event in TextBox::new(&self.password) .w_h(290.0, 30.0) .mid_bottom_with_margin_on(self.ids.password_bg, 17.0) - .font_size(self.fonts.cyri.scale(22)) + // 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 From 9c76bdde0e9e9f5ea41283355c7a2f8880fc6578 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sat, 4 Apr 2020 01:40:00 -0400 Subject: [PATCH 015/195] Setup basic generic slot system and used it for the inventory slots --- voxygen/src/hud/bag.rs | 160 +++---------- voxygen/src/hud/item_imgs.rs | 12 +- voxygen/src/hud/mod.rs | 3 +- voxygen/src/hud/slot_kinds.rs | 91 +++++++ voxygen/src/ui/mod.rs | 2 + voxygen/src/ui/widgets/mod.rs | 1 + voxygen/src/ui/widgets/slot.rs | 417 +++++++++++++++++++++++++++++++++ 7 files changed, 562 insertions(+), 124 deletions(-) create mode 100644 voxygen/src/hud/slot_kinds.rs create mode 100644 voxygen/src/ui/widgets/slot.rs diff --git a/voxygen/src/hud/bag.rs b/voxygen/src/hud/bag.rs index 0c9708d9ad..ce71942427 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}, + slot_kinds::{HudSlotManager, InventorySlot}, Event as HudEvent, 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::SlotMaker, ImageFrame, Tooltip, TooltipManager, + Tooltipable, + }, }; use client::Client; -use common::comp::{item::ItemKind, Stats}; +use common::comp::Stats; use conrod_core::{ color, image, widget::{self, Button, Image, Rectangle, Text}, widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, }; -// +use vek::Vec2; widget_ids! { pub struct Ids { @@ -93,7 +97,6 @@ widget_ids! { } #[derive(WidgetCommon)] -#[allow(dead_code)] pub struct Bag<'a> { client: &'a Client, imgs: &'a Imgs, @@ -103,7 +106,7 @@ pub struct Bag<'a> { common: widget::CommonBuilder, rot_imgs: &'a ImgsRot, tooltip_manager: &'a mut TooltipManager, - pulse: f32, + _pulse: f32, localized_strings: &'a std::sync::Arc, stats: &'a Stats, show: &'a Show, @@ -130,7 +133,7 @@ impl<'a> Bag<'a> { common: widget::CommonBuilder::default(), rot_imgs, tooltip_manager, - pulse, + _pulse: pulse, localized_strings, stats, show, @@ -603,57 +606,37 @@ 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 + // TODO: add slot manager + let slot_manager: Option<&mut HudSlotManager> = None; + let mut slot_maker = SlotMaker { + background: self.imgs.inv_slot, + selected_background: self.imgs.inv_slot_sel, + background_color: Some(UI_MAIN), + content_size: Vec2::broadcast(30.0), + selected_content_size: Vec2::broadcast(32.0), + 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, + }; 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)) + .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]); + if let Some(item) = item { slot_widget .with_tooltip( self.tooltip_manager, @@ -664,82 +647,15 @@ 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); - } - } + // if ui.widget_input(ui.window).clicks().left().next().is_some() { + // Stats Button if Button::image(self.imgs.button) .w_h(92.0, 22.0) diff --git a/voxygen/src/hud/item_imgs.rs b/voxygen/src/hud/item_imgs.rs index 84566e45c0..e8a7ca851e 100644 --- a/voxygen/src/hud/item_imgs.rs +++ b/voxygen/src/hud/item_imgs.rs @@ -82,12 +82,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 +99,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 +145,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 diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index ebe1d3f163..13c704e859 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -8,6 +8,7 @@ mod map; mod minimap; mod settings_window; mod skillbar; +mod slot_kinds; mod social; mod spell; @@ -461,7 +462,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, diff --git a/voxygen/src/hud/slot_kinds.rs b/voxygen/src/hud/slot_kinds.rs new file mode 100644 index 0000000000..a7f9b6a479 --- /dev/null +++ b/voxygen/src/hud/slot_kinds.rs @@ -0,0 +1,91 @@ +use super::item_imgs::{ItemImgs, ItemKey}; +use crate::ui::slot::{ContentKey, SlotKinds, SlotManager}; +use common::comp::{item::ItemKind, Inventory}; +use conrod_core::image; +use vek::*; + +#[derive(Clone, Copy, PartialEq)] +pub enum HudSlotKinds { + Inventory(InventorySlot), + Armor(ArmorSlot), + Hotbar(HotbarSlot), +} + +pub type HudSlotManager = SlotManager; + +#[derive(Clone, Copy, PartialEq)] +pub struct InventorySlot(pub usize); + +#[derive(Clone, Copy, PartialEq)] +pub enum ArmorSlot { + Helmet, + Neck, + Shoulders, + Chest, + Hands, + LeftRing, + RightRing, + Back, + Belt, + Legs, + Feet, + Mainhand, + Offhand, + Tabard, +} + +#[derive(Clone, Copy, PartialEq)] +pub enum HotbarSlot { + One, + Two, + Three, + Four, + Five, + Six, + Seven, + Eight, + Nine, + Ten, +} + +impl ContentKey for InventorySlot { + type ContentSource = Inventory; + type ImageKey = ItemKey; + type ImageSource = ItemImgs; + + fn image_key(&self, source: &Self::ContentSource) -> Option { + source.get(self.0).map(Into::into) + } + + fn amount(&self, source: &Self::ContentSource) -> Option { + source + .get(self.0) + .and_then(|item| match item.kind { + ItemKind::Tool { .. } | 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: &Self::ImageSource) -> image::Id { + source.img_id_or_not_found_img(key.clone()) + } + + fn back_icon(&self, _: &Self::ImageSource) -> Option<(image::Id, Vec2)> { None } +} + +impl From for HudSlotKinds { + fn from(inventory: InventorySlot) -> Self { Self::Inventory(inventory) } +} + +impl From for HudSlotKinds { + fn from(armor: ArmorSlot) -> Self { Self::Armor(armor) } +} + +impl From for HudSlotKinds { + fn from(hotbar: HotbarSlot) -> Self { Self::Hotbar(hotbar) } +} + +impl SlotKinds for HudSlotKinds {} 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/mod.rs b/voxygen/src/ui/widgets/mod.rs index 7c26721f56..c58451ee81 100644 --- a/voxygen/src/ui/widgets/mod.rs +++ b/voxygen/src/ui/widgets/mod.rs @@ -2,5 +2,6 @@ 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..996cea3658 --- /dev/null +++ b/voxygen/src/ui/widgets/slot.rs @@ -0,0 +1,417 @@ +//! A widget for selecting a single value along some linear range. +use conrod_core::{ + builder_methods, image, + 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 ContentKey: Copy { + type ContentSource; + type ImageSource; + type ImageKey: PartialEq + Send + 'static; + /// Returns an Option since the slot could be empty + fn image_key(&self, source: &Self::ContentSource) -> Option; + // TODO: is this the right integer type? + fn amount(&self, source: &Self::ContentSource) -> Option; + fn image_id(key: &Self::ImageKey, source: &Self::ImageSource) -> image::Id; + /// Returns slot icon and icon size as fraction of slot size + fn back_icon(&self, source: &Self::ImageSource) -> Option<(image::Id, Vec2)>; +} + +pub trait SlotKinds: Sized + PartialEq + Copy {} + +pub struct SlotMaker<'a, C: ContentKey + Into, K: SlotKinds> { + pub background: image::Id, + pub selected_background: image::Id, + pub background_color: Option, + pub content_size: Vec2, + pub selected_content_size: Vec2, + pub amount_font: font::Id, + pub amount_font_size: u32, + pub amount_margins: Vec2, + pub amount_text_color: Color, + pub content_source: &'a C::ContentSource, + pub image_source: &'a C::ImageSource, + pub slot_manager: Option<&'a mut SlotManager>, +} + +impl<'a, C, K> SlotMaker<'a, C, K> +where + C: ContentKey + Into, + K: SlotKinds, +{ + pub fn fabricate(&mut self, contents: C) -> Slot { + Slot::new( + contents, + self.background, + self.selected_background, + self.content_size, + self.selected_content_size, + self.amount_font, + self.amount_font_size, + self.amount_margins, + self.amount_text_color, + self.content_source, + self.image_source, + ) + .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)) + } +} + +/* +// Note: will probably delete this +// Integrate tooltip adding?? +use super::tooltip::{Tooltip, TooltipManager, Tooltipable}; +pub fn with_tooltips( + self, + tooltip_manager: &'a mut TooltipManager, + tooltip: &'a Tooltip<'a>, +) -> TooltippedSlotMaker<'a, C, K> { + TooltippedSlotMaker { + slot_maker: self, + tooltip_manager, + tooltip, + } +} + + +tooltip: &'a Tooltip<'a>, +slot_widget + .with_tooltip( + self.tooltip_manager, + &item.name(), + &format!( + "{}", + /* item.kind, item.effect(), */ item.description() + ), + &item_tooltip, + ) + +pub struct TooltippedSlotMaker<'a, C: ContentKey + Into, K: SlotKinds> { + pub slot_maker: SlotMaker<'a, C, K>, + pub tooltip_manager: &'a mut TooltipManager, + pub tooltip: &'a Tooltip, +}*/ + +#[derive(Clone, Copy)] +enum ManagerState { + Dragging(widget::Id, K), + Selected(widget::Id, K), + Idle, +} + +enum Interaction { + Selected, + Dragging, + None, +} + +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, + events: Vec>, + // widget id for dragging image +} + +impl SlotManager +where + K: SlotKinds, +{ + fn update(&mut self, widget: widget::Id, slot: K, ui: &conrod_core::Ui) -> Interaction { + // 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 + }, + _ => (), + } + + // TODO: make more robust wrt multiple events in the same frame (eg event order + // may matter) TODO: handle taps as well + // TODO: handle clicks in empty space + // TODO: handle drags + let click_count = ui.widget_input(widget).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 { + ManagerState::Selected(widget, slot) + } else { + // Selected and then deselected with one or more clicks + ManagerState::Idle + } + }; + } + + // 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, + } + } +} + +#[derive(WidgetCommon)] +pub struct Slot<'a, C: ContentKey + Into, K: SlotKinds> { + content: C, + + // Background is also the frame + background: image::Id, + selected_background: image::Id, + background_color: Option, + + // Size of content image + content_size: Vec2, + // TODO: maybe use constant scale factor or move this setting to the slot manager? + selected_content_size: Vec2, + + // 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::ContentSource, + image_source: &'a C::ImageSource, + + #[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, C, K> Slot<'a, C, K> +where + C: ContentKey + Into, + K: SlotKinds, +{ + builder_methods! { + pub with_manager { slot_manager = Some(&'a mut SlotManager) } + pub with_background_color { background_color = Some(Color) } + } + + fn new( + content: C, + background: image::Id, + selected_background: image::Id, + content_size: Vec2, + selected_content_size: Vec2, + amount_font: font::Id, + amount_font_size: u32, + amount_margins: Vec2, + amount_text_color: Color, + content_source: &'a C::ContentSource, + image_source: &'a C::ImageSource, + ) -> Self { + Self { + content, + background, + selected_background, + background_color: None, + content_size, + selected_content_size, + amount_font, + amount_font_size, + amount_margins, + amount_text_color, + slot_manager: None, + content_source, + image_source, + common: widget::CommonBuilder::default(), + } + } +} + +impl<'a, C, K> Widget for Slot<'a, C, K> +where + C: ContentKey + Into, + K: SlotKinds, +{ + 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 { + content, + background, + selected_background, + background_color, + content_size, + selected_content_size, + 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.image_key(content_source); + 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 = C::image_id(&key, &image_source); + (key, image_id) + }); + }); + } + + // Get whether this slot is selected + let interaction = self + .slot_manager + .map_or(Interaction::None, |m| m.update(id, content.into(), ui)); + + // Get image ids + let background_image = if let Interaction::Selected = interaction { + selected_background + } else { + background + }; + let icon = content.back_icon(image_source); + let content_image = state.cached_image.as_ref().map(|c| c.1); + + // Get amount (None => no amount text) + let amount = content.amount(content_source); + + // Get slot widget dimensions and position + let (x, y, w, h) = rect.x_y_w_h(); + + // Draw background + Image::new(background_image) + .x_y(x, y) + .w_h(w, h) + .parent(id) + .graphics_for(id) + .color(background_color) + .set(state.ids.background, ui); + + // Draw icon + if let Some((icon_image, size_frac)) = icon { + let wh = (size_frac.map(|e| e as f64) * Vec2::new(w, h)).into_array(); + Image::new(icon_image) + .x_y(x, y) + .wh(wh) + .parent(id) + .graphics_for(id) + .set(state.ids.icon, ui); + } + + // Draw contents + if let Some(content_image) = content_image { + Image::new(content_image) + .x_y(x, y) + .wh(if let Interaction::Selected = interaction { + content_size + } else { + selected_content_size + } + .map(|e| e as f64) + .into_array()) + .parent(id) + .graphics_for(id) + .set(state.ids.icon, ui); + } + + // Draw amount + if let Some(amount) = amount { + let amount = format!("{}", &amount); + // Text shadow + Text::new(&amount) + .parent(id) + .graphics_for(id) + .font_id(amount_font) + .font_size(amount_font_size) + .top_right_with_margins_on(id, amount_margins.x as f64, amount_margins.y as f64) + .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); + } + } +} From 968c064874b5feeff41eebb39f636e8bd8b9dffe Mon Sep 17 00:00:00 2001 From: Imbris Date: Sat, 4 Apr 2020 03:13:51 -0400 Subject: [PATCH 016/195] Restore slot interaction functionality --- voxygen/src/hud/bag.rs | 18 ++++++---------- voxygen/src/hud/mod.rs | 39 +++++++++++++++++++++++++++++++++- voxygen/src/session.rs | 9 ++++++++ voxygen/src/ui/widgets/slot.rs | 37 ++++++++++++++++++++++++++------ 4 files changed, 84 insertions(+), 19 deletions(-) diff --git a/voxygen/src/hud/bag.rs b/voxygen/src/hud/bag.rs index ce71942427..9b232e1ab0 100644 --- a/voxygen/src/hud/bag.rs +++ b/voxygen/src/hud/bag.rs @@ -1,6 +1,6 @@ use super::{ img_ids::{Imgs, ImgsRot}, - item_imgs::{ItemImgs, ItemKey}, + item_imgs::ItemImgs, slot_kinds::{HudSlotManager, InventorySlot}, Event as HudEvent, Show, CRITICAL_HP_COLOR, LOW_HP_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN, XP_COLOR, @@ -15,7 +15,7 @@ use crate::{ use client::Client; 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, }; @@ -32,10 +32,7 @@ widget_ids! { inv_slots_0, map_title, inv_slots[], - items[], - amounts[], - amounts_bg[], - tooltip[], + //tooltip[], bg, bg_frame, char_ico, @@ -106,6 +103,7 @@ pub struct Bag<'a> { common: widget::CommonBuilder, rot_imgs: &'a ImgsRot, tooltip_manager: &'a mut TooltipManager, + slot_manager: &'a mut HudSlotManager, _pulse: f32, localized_strings: &'a std::sync::Arc, stats: &'a Stats, @@ -120,6 +118,7 @@ impl<'a> Bag<'a> { fonts: &'a ConrodVoxygenFonts, rot_imgs: &'a ImgsRot, tooltip_manager: &'a mut TooltipManager, + slot_manager: &'a mut HudSlotManager, pulse: f32, localized_strings: &'a std::sync::Arc, stats: &'a Stats, @@ -133,6 +132,7 @@ impl<'a> Bag<'a> { common: widget::CommonBuilder::default(), rot_imgs, tooltip_manager, + slot_manager, _pulse: pulse, localized_strings, stats, @@ -143,7 +143,6 @@ impl<'a> Bag<'a> { pub struct State { ids: Ids, - img_id_cache: Vec>, selected_slot: Option, } @@ -161,7 +160,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, } } @@ -607,8 +605,6 @@ impl<'a> Widget for Bag<'a> { }); } // Display inventory contents - // TODO: add slot manager - let slot_manager: Option<&mut HudSlotManager> = None; let mut slot_maker = SlotMaker { background: self.imgs.inv_slot, selected_background: self.imgs.inv_slot_sel, @@ -621,7 +617,7 @@ impl<'a> Widget for Bag<'a> { amount_text_color: TEXT_COLOR, content_source: inventory, image_source: self.item_imgs, - slot_manager, + slot_manager: Some(self.slot_manager), }; for (i, item) in inventory.slots().iter().enumerate() { let x = i % 9; diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 13c704e859..b129d73fc1 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -36,7 +36,7 @@ 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, }; @@ -234,6 +234,8 @@ pub enum Event { CharacterSelection, UseInventorySlot(usize), SwapInventorySlots(usize, usize), + SwapInventoryArmor(usize, slot_kinds::ArmorSlot), + SwapArmorSlots(slot_kinds::ArmorSlot, slot_kinds::ArmorSlot), DropInventorySlot(usize), Logout, Quit, @@ -441,6 +443,7 @@ pub struct Hud { pulse: f32, velocity: f32, voxygen_i18n: std::sync::Arc, + slot_manager: slot_kinds::HudSlotManager, } impl Hud { @@ -509,6 +512,7 @@ impl Hud { pulse: 0.0, velocity: 0.0, voxygen_i18n, + slot_manager: slot_kinds::HudSlotManager::new(), } } @@ -1646,6 +1650,7 @@ impl Hud { &self.fonts, &self.rot_imgs, tooltip_manager, + &mut self.slot_manager, self.pulse, &self.voxygen_i18n, &player_stats, @@ -1952,6 +1957,38 @@ impl Hud { .set(self.ids.free_look_txt, ui_widgets); } + // Maintain slot manager + for event in self.slot_manager.maintain(ui_widgets) { + use slot_kinds::HudSlotKinds; + match event { + slot::Event::Dragged( + HudSlotKinds::Inventory(from), + HudSlotKinds::Inventory(to), + ) => { + // Swap between inventory slots + events.push(Event::SwapInventorySlots(from.0, to.0)); + }, + slot::Event::Dragged(HudSlotKinds::Armor(from), HudSlotKinds::Armor(to)) => { + // Swap between two armor slots + events.push(Event::SwapArmorSlots(from, to)); + }, + slot::Event::Dragged(HudSlotKinds::Inventory(inv), HudSlotKinds::Armor(arm)) + | slot::Event::Dragged(HudSlotKinds::Armor(arm), HudSlotKinds::Inventory(inv)) => { + // Swap between inventory and armor slot + events.push(Event::SwapInventoryArmor(inv.0, arm)); + }, + slot::Event::Dropped(HudSlotKinds::Inventory(from)) => { + // Drop item from inventory + events.push(Event::DropInventorySlot(from.0)); + }, + slot::Event::Used(HudSlotKinds::Inventory(inv)) => { + // Item in inventory used (selected and then clicked again) + events.push(Event::UseInventorySlot(inv.0)); + }, + _ => {}, + } + } + events } diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 61bfcd3d92..5348c6f602 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -647,6 +647,15 @@ impl PlayState for SessionState { HudEvent::SwapInventorySlots(a, b) => { self.client.borrow_mut().swap_inventory_slots(a, b) }, + HudEvent::SwapInventoryArmor(inv_slot, armor_slot) => { + // Swapping between inventory and armor slot + // TODO: don't do this + self.client.borrow_mut().use_inventory_slot(inv_slot) + }, + HudEvent::SwapArmorSlots(from, to) => { + // Only works with rings currently + // TODO: implement + }, HudEvent::DropInventorySlot(x) => { self.client.borrow_mut().drop_inventory_slot(x) }, diff --git a/voxygen/src/ui/widgets/slot.rs b/voxygen/src/ui/widgets/slot.rs index 996cea3658..e0ff566652 100644 --- a/voxygen/src/ui/widgets/slot.rs +++ b/voxygen/src/ui/widgets/slot.rs @@ -111,7 +111,7 @@ enum Interaction { None, } -enum Event { +pub enum Event { // Dragged to another slot Dragged(K, K), // Dragged to open space @@ -130,6 +130,25 @@ impl SlotManager where K: SlotKinds, { + pub fn new() -> Self { + Self { + state: ManagerState::Idle, + events: Vec::new(), + } + } + + pub fn maintain(&mut self, ui: &conrod_core::Ui) -> Vec> { + // 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)) + } + } + + std::mem::replace(&mut self.events, Vec::new()) + } + fn update(&mut self, widget: widget::Id, slot: K, ui: &conrod_core::Ui) -> Interaction { // If this is the selected/dragged widget make sure the slot value is up to date match &mut self.state { @@ -377,15 +396,15 @@ where Image::new(content_image) .x_y(x, y) .wh(if let Interaction::Selected = interaction { - content_size - } else { selected_content_size + } else { + content_size } .map(|e| e as f64) .into_array()) .parent(id) .graphics_for(id) - .set(state.ids.icon, ui); + .set(state.ids.content, ui); } // Draw amount @@ -393,11 +412,15 @@ where let amount = format!("{}", &amount); // Text shadow Text::new(&amount) - .parent(id) - .graphics_for(id) .font_id(amount_font) .font_size(amount_font_size) - .top_right_with_margins_on(id, amount_margins.x as f64, amount_margins.y as f64) + .top_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) From 0b932ae99a6bca17d0f664c7a0f061135a758434 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sat, 4 Apr 2020 13:51:41 -0400 Subject: [PATCH 017/195] Use new slot widget for armor slots --- client/src/lib.rs | 2 + common/src/comp/controller.rs | 18 +- voxygen/src/hud/bag.rs | 362 ++++++++++++++++----------------- voxygen/src/hud/slot_kinds.rs | 31 ++- voxygen/src/ui/widgets/slot.rs | 17 +- 5 files changed, 226 insertions(+), 204 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index 7341cdd916..1cbe9a72ce 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -387,6 +387,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/comp/controller.rs b/common/src/comp/controller.rs index 5852ad56ec..49c5cf8658 100644 --- a/common/src/comp/controller.rs +++ b/common/src/comp/controller.rs @@ -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(usize), + Swap(usize, usize), + Drop(usize), +} + #[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/voxygen/src/hud/bag.rs b/voxygen/src/hud/bag.rs index 9b232e1ab0..c3fcec0c20 100644 --- a/voxygen/src/hud/bag.rs +++ b/voxygen/src/hud/bag.rs @@ -1,7 +1,7 @@ use super::{ img_ids::{Imgs, ImgsRot}, item_imgs::ItemImgs, - slot_kinds::{HudSlotManager, InventorySlot}, + slot_kinds::{ArmorSlot, HudSlotManager, InventorySlot}, Event as HudEvent, Show, CRITICAL_HP_COLOR, LOW_HP_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN, XP_COLOR, }; @@ -47,7 +47,7 @@ widget_ids! { tab_2, tab_3, tab_4, - //Stats + // Stats stats_alignment, level, exp_rectangle, @@ -57,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, + ring_r_slot, + ring_l_slot, + feet_slot, + back_slot, + tabard_slot, + mainhand_slot, + offhand_slot, + // ??? end_ico, fit_ico, wp_ico, @@ -143,7 +130,6 @@ impl<'a> Bag<'a> { pub struct State { ids: Ids, - selected_slot: Option, } pub enum Event { @@ -160,7 +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), - selected_slot: None, } } @@ -173,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!( "{}/{} {}", @@ -322,174 +313,173 @@ 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) + let mut slot_maker = SlotMaker { + background: self.imgs.armor_slot, + selected_background: self.imgs.armor_slot, + background_color: Some(UI_HIGHLIGHT_0), + content_size: Vec2::broadcast(30.0), + selected_content_size: Vec2::broadcast(32.0), + 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) = ("Helmet", ""); + slot_maker + .fabricate(ArmorSlot::Head) .w_h(45.0, 45.0) .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) + .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) = ("Neck", ""); + slot_maker + .fabricate(ArmorSlot::Neck) .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 + .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(("Chest", ""), |item| (item.name(), item.description())); + slot_maker + .fabricate(ArmorSlot::Chest) .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) + .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(("Shoulders", ""), |item| (item.name(), item.description())); + slot_maker + .fabricate(ArmorSlot::Shoulders) .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) + .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(("Hands", ""), |item| (item.name(), item.description())); + slot_maker + .fabricate(ArmorSlot::Hands) .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) + .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(("Belt", ""), |item| (item.name(), item.description())); + slot_maker + .fabricate(ArmorSlot::Belt) .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) + .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 + .belt + .as_ref() + .map_or(("Legs", ""), |item| (item.name(), item.description())); + slot_maker + .fabricate(ArmorSlot::Legs) .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) + .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); + // Ring-L + let (title, desc) = ("Left Ring", ""); + slot_maker + .fabricate(ArmorSlot::LeftRing) .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) + .bottom_right_with_margins_on(state.ids.shoulders_slot, -55.0, 0.0) + .with_icon(self.imgs.ring_l_bg, Vec2::new(36.0, 40.0), Some(UI_MAIN)) + .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .set(state.ids.ring_l_slot, ui); + // Ring-R + let (title, desc) = ("Right Ring", ""); + slot_maker + .fabricate(ArmorSlot::RightRing) .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) + .bottom_left_with_margins_on(state.ids.hands_slot, -55.0, 0.0) + .with_icon(self.imgs.ring_r_bg, Vec2::new(36.0, 40.0), Some(UI_MAIN)) + .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .set(state.ids.ring_r_slot, ui); + // Back + let (title, desc) = ("Back", ""); + slot_maker + .fabricate(ArmorSlot::Back) .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) + .down_from(state.ids.ring_l_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(("Feet", ""), |item| (item.name(), item.description())); + slot_maker + .fabricate(ArmorSlot::Feet) .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) + .down_from(state.ids.ring_r_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) = ("Tabard", ""); + slot_maker + .fabricate(ArmorSlot::Tabard) .w_h(70.0, 70.0) .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) + .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(("Mainhand", ""), |item| (item.name(), item.description())); + slot_maker + .fabricate(ArmorSlot::Mainhand) .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) + .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(("Offhand", ""), |item| (item.name(), item.description())); + slot_maker + .fabricate(ArmorSlot::Offhand) .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); + .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 diff --git a/voxygen/src/hud/slot_kinds.rs b/voxygen/src/hud/slot_kinds.rs index a7f9b6a479..900342e324 100644 --- a/voxygen/src/hud/slot_kinds.rs +++ b/voxygen/src/hud/slot_kinds.rs @@ -1,8 +1,7 @@ use super::item_imgs::{ItemImgs, ItemKey}; use crate::ui::slot::{ContentKey, SlotKinds, SlotManager}; -use common::comp::{item::ItemKind, Inventory}; +use common::comp::{item::ItemKind, Inventory, Loadout}; use conrod_core::image; -use vek::*; #[derive(Clone, Copy, PartialEq)] pub enum HudSlotKinds { @@ -18,7 +17,7 @@ pub struct InventorySlot(pub usize); #[derive(Clone, Copy, PartialEq)] pub enum ArmorSlot { - Helmet, + Head, Neck, Shoulders, Chest, @@ -72,8 +71,32 @@ impl ContentKey for InventorySlot { fn image_id(key: &Self::ImageKey, source: &Self::ImageSource) -> image::Id { source.img_id_or_not_found_img(key.clone()) } +} - fn back_icon(&self, _: &Self::ImageSource) -> Option<(image::Id, Vec2)> { None } +impl ContentKey for ArmorSlot { + type ContentSource = Loadout; + type ImageKey = ItemKey; + type ImageSource = ItemImgs; + + fn image_key(&self, source: &Self::ContentSource) -> Option { + let item = match self { + ArmorSlot::Shoulders => source.shoulder.as_ref(), + ArmorSlot::Chest => source.chest.as_ref(), + ArmorSlot::Belt => source.belt.as_ref(), + ArmorSlot::Hands => source.hand.as_ref(), + ArmorSlot::Legs => source.pants.as_ref(), + ArmorSlot::Feet => source.foot.as_ref(), + _ => None, + }; + + item.map(Into::into) + } + + fn amount(&self, _: &Self::ContentSource) -> Option { None } + + fn image_id(key: &Self::ImageKey, source: &Self::ImageSource) -> image::Id { + source.img_id_or_not_found_img(key.clone()) + } } impl From for HudSlotKinds { diff --git a/voxygen/src/ui/widgets/slot.rs b/voxygen/src/ui/widgets/slot.rs index e0ff566652..25b4a9dce5 100644 --- a/voxygen/src/ui/widgets/slot.rs +++ b/voxygen/src/ui/widgets/slot.rs @@ -18,8 +18,6 @@ pub trait ContentKey: Copy { // TODO: is this the right integer type? fn amount(&self, source: &Self::ContentSource) -> Option; fn image_id(key: &Self::ImageKey, source: &Self::ImageSource) -> image::Id; - /// Returns slot icon and icon size as fraction of slot size - fn back_icon(&self, source: &Self::ImageSource) -> Option<(image::Id, Vec2)>; } pub trait SlotKinds: Sized + PartialEq + Copy {} @@ -223,6 +221,8 @@ pub struct Slot<'a, C: ContentKey + Into, K: SlotKinds> { // TODO: maybe use constant scale factor or move this setting to the slot manager? selected_content_size: Vec2, + icon: Option<(image::Id, Vec2, Option)>, + // Amount styling amount_font: font::Id, amount_font_size: u32, @@ -265,6 +265,11 @@ where pub with_background_color { background_color = Some(Color) } } + pub fn with_icon(mut self, img: image::Id, size: Vec2, color: Option) -> Self { + self.icon = Some((img, size, color)); + self + } + fn new( content: C, background: image::Id, @@ -285,6 +290,7 @@ where background_color: None, content_size, selected_content_size, + icon: None, amount_font, amount_font_size, amount_margins, @@ -331,6 +337,7 @@ where background_color, content_size, selected_content_size, + icon, amount_font, amount_font_size, amount_margins, @@ -362,7 +369,6 @@ where } else { background }; - let icon = content.back_icon(image_source); let content_image = state.cached_image.as_ref().map(|c| c.1); // Get amount (None => no amount text) @@ -381,13 +387,14 @@ where .set(state.ids.background, ui); // Draw icon - if let Some((icon_image, size_frac)) = icon { - let wh = (size_frac.map(|e| e as f64) * Vec2::new(w, h)).into_array(); + if let Some((icon_image, size, color)) = icon { + 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); } From cac2321464d231165a9e1b09f046a6ff0a89c994 Mon Sep 17 00:00:00 2001 From: Pfauenauge Date: Sun, 5 Apr 2020 21:19:59 +0200 Subject: [PATCH 018/195] armor slot visuals --- assets/voxygen/element/buttons/armor_slot.png | 4 ++-- assets/voxygen/element/buttons/armor_slot_empty.png | 3 +++ assets/voxygen/element/buttons/armor_slot_selected.png | 3 +++ voxygen/src/hud/img_ids.rs | 2 ++ 4 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 assets/voxygen/element/buttons/armor_slot_empty.png create mode 100644 assets/voxygen/element/buttons/armor_slot_selected.png 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/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index b62913d453..3cd1bc7e94 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -230,6 +230,8 @@ 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_sel", + 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", From 4c5f668203a16b06a75c651dc205ba63d1751728 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sun, 5 Apr 2020 15:40:05 -0400 Subject: [PATCH 019/195] Don't show icon when slot is filled and have a separate image for filled slot backgrounds --- voxygen/src/hud/bag.rs | 10 ++++--- voxygen/src/hud/img_ids.rs | 2 +- voxygen/src/ui/widgets/slot.rs | 54 ++++++++++++++++++++-------------- 3 files changed, 39 insertions(+), 27 deletions(-) diff --git a/voxygen/src/hud/bag.rs b/voxygen/src/hud/bag.rs index c3fcec0c20..22d92e3898 100644 --- a/voxygen/src/hud/bag.rs +++ b/voxygen/src/hud/bag.rs @@ -314,8 +314,9 @@ impl<'a> Widget for Bag<'a> { .set(state.ids.slots_bg, ui);*/ // Armor Slots let mut slot_maker = SlotMaker { - background: self.imgs.armor_slot, - selected_background: self.imgs.armor_slot, + 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: Vec2::broadcast(30.0), selected_content_size: Vec2::broadcast(32.0), @@ -596,8 +597,9 @@ impl<'a> Widget for Bag<'a> { } // Display inventory contents let mut slot_maker = SlotMaker { - background: self.imgs.inv_slot, - selected_background: self.imgs.inv_slot_sel, + 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: Vec2::broadcast(30.0), selected_content_size: Vec2::broadcast(32.0), diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index 3cd1bc7e94..a49e22e810 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -230,7 +230,7 @@ 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_sel", + 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", diff --git a/voxygen/src/ui/widgets/slot.rs b/voxygen/src/ui/widgets/slot.rs index 25b4a9dce5..69b8ee3bef 100644 --- a/voxygen/src/ui/widgets/slot.rs +++ b/voxygen/src/ui/widgets/slot.rs @@ -23,8 +23,10 @@ pub trait ContentKey: Copy { pub trait SlotKinds: Sized + PartialEq + Copy {} pub struct SlotMaker<'a, C: ContentKey + Into, K: SlotKinds> { - pub background: image::Id, - pub selected_background: image::Id, + 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: Vec2, pub selected_content_size: Vec2, @@ -45,8 +47,9 @@ where pub fn fabricate(&mut self, contents: C) -> Slot { Slot::new( contents, - self.background, - self.selected_background, + self.empty_slot, + self.filled_slot, + self.selected_slot, self.content_size, self.selected_content_size, self.amount_font, @@ -211,9 +214,10 @@ where pub struct Slot<'a, C: ContentKey + Into, K: SlotKinds> { content: C, - // Background is also the frame - background: image::Id, - selected_background: image::Id, + // 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 @@ -272,8 +276,9 @@ where fn new( content: C, - background: image::Id, - selected_background: image::Id, + empty_slot: image::Id, + filled_slot: image::Id, + selected_slot: image::Id, content_size: Vec2, selected_content_size: Vec2, amount_font: font::Id, @@ -285,8 +290,9 @@ where ) -> Self { Self { content, - background, - selected_background, + empty_slot, + filled_slot, + selected_slot, background_color: None, content_size, selected_content_size, @@ -332,8 +338,9 @@ where } = args; let Slot { content, - background, - selected_background, + empty_slot, + filled_slot, + selected_slot, background_color, content_size, selected_content_size, @@ -364,12 +371,14 @@ where .map_or(Interaction::None, |m| m.update(id, content.into(), ui)); // Get image ids - let background_image = if let Interaction::Selected = interaction { - selected_background - } else { - background - }; let content_image = state.cached_image.as_ref().map(|c| c.1); + 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 = content.amount(content_source); @@ -377,8 +386,8 @@ where // Get slot widget dimensions and position let (x, y, w, h) = rect.x_y_w_h(); - // Draw background - Image::new(background_image) + // Draw slot frame/background + Image::new(slot_image) .x_y(x, y) .w_h(w, h) .parent(id) @@ -386,8 +395,9 @@ where .color(background_color) .set(state.ids.background, ui); - // Draw icon - if let Some((icon_image, size, color)) = icon { + // 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) From 77948d27ca67f10441041d6f6f87b3e812e7a13f Mon Sep 17 00:00:00 2001 From: Imbris Date: Sun, 5 Apr 2020 20:03:59 -0400 Subject: [PATCH 020/195] Scale content image based on slot size, fix weapon images not showing, stop selection of empty slots --- voxygen/src/hud/bag.rs | 66 ++++++++++++--------------- voxygen/src/hud/slot_kinds.rs | 2 + voxygen/src/ui/widgets/slot.rs | 81 +++++++++++++++++++++++++--------- 3 files changed, 91 insertions(+), 58 deletions(-) diff --git a/voxygen/src/hud/bag.rs b/voxygen/src/hud/bag.rs index 22d92e3898..60a74ac19f 100644 --- a/voxygen/src/hud/bag.rs +++ b/voxygen/src/hud/bag.rs @@ -8,8 +8,9 @@ use super::{ use crate::{ i18n::VoxygenLocalization, ui::{ - fonts::ConrodVoxygenFonts, slot::SlotMaker, ImageFrame, Tooltip, TooltipManager, - Tooltipable, + fonts::ConrodVoxygenFonts, + slot::{ContentSize, SlotMaker}, + ImageFrame, Tooltip, TooltipManager, Tooltipable, }, }; use client::Client; @@ -318,8 +319,11 @@ impl<'a> Widget for Bag<'a> { filled_slot: self.imgs.armor_slot, selected_slot: self.imgs.armor_slot_sel, background_color: Some(UI_HIGHLIGHT_0), - content_size: Vec2::broadcast(30.0), - selected_content_size: Vec2::broadcast(32.0), + 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), @@ -331,8 +335,7 @@ impl<'a> Widget for Bag<'a> { // Head let (title, desc) = ("Helmet", ""); slot_maker - .fabricate(ArmorSlot::Head) - .w_h(45.0, 45.0) + .fabricate(ArmorSlot::Head, [45.0; 2]) .mid_top_with_margin_on(state.ids.bg_frame, 60.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) @@ -340,8 +343,7 @@ impl<'a> Widget for Bag<'a> { // Necklace let (title, desc) = ("Neck", ""); slot_maker - .fabricate(ArmorSlot::Neck) - .w_h(45.0, 45.0) + .fabricate(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) @@ -353,8 +355,7 @@ impl<'a> Widget for Bag<'a> { .as_ref() .map_or(("Chest", ""), |item| (item.name(), item.description())); slot_maker - .fabricate(ArmorSlot::Chest) - .w_h(85.0, 85.0) + .fabricate(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) @@ -365,8 +366,7 @@ impl<'a> Widget for Bag<'a> { .as_ref() .map_or(("Shoulders", ""), |item| (item.name(), item.description())); slot_maker - .fabricate(ArmorSlot::Shoulders) - .w_h(70.0, 70.0) + .fabricate(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) @@ -377,8 +377,7 @@ impl<'a> Widget for Bag<'a> { .as_ref() .map_or(("Hands", ""), |item| (item.name(), item.description())); slot_maker - .fabricate(ArmorSlot::Hands) - .w_h(70.0, 70.0) + .fabricate(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) @@ -389,8 +388,7 @@ impl<'a> Widget for Bag<'a> { .as_ref() .map_or(("Belt", ""), |item| (item.name(), item.description())); slot_maker - .fabricate(ArmorSlot::Belt) - .w_h(45.0, 45.0) + .fabricate(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) @@ -401,8 +399,7 @@ impl<'a> Widget for Bag<'a> { .as_ref() .map_or(("Legs", ""), |item| (item.name(), item.description())); slot_maker - .fabricate(ArmorSlot::Legs) - .w_h(85.0, 85.0) + .fabricate(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) @@ -410,8 +407,7 @@ impl<'a> Widget for Bag<'a> { // Ring-L let (title, desc) = ("Left Ring", ""); slot_maker - .fabricate(ArmorSlot::LeftRing) - .w_h(45.0, 45.0) + .fabricate(ArmorSlot::LeftRing, [45.0; 2]) .bottom_right_with_margins_on(state.ids.shoulders_slot, -55.0, 0.0) .with_icon(self.imgs.ring_l_bg, Vec2::new(36.0, 40.0), Some(UI_MAIN)) .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) @@ -419,8 +415,7 @@ impl<'a> Widget for Bag<'a> { // Ring-R let (title, desc) = ("Right Ring", ""); slot_maker - .fabricate(ArmorSlot::RightRing) - .w_h(45.0, 45.0) + .fabricate(ArmorSlot::RightRing, [45.0; 2]) .bottom_left_with_margins_on(state.ids.hands_slot, -55.0, 0.0) .with_icon(self.imgs.ring_r_bg, Vec2::new(36.0, 40.0), Some(UI_MAIN)) .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) @@ -428,8 +423,7 @@ impl<'a> Widget for Bag<'a> { // Back let (title, desc) = ("Back", ""); slot_maker - .fabricate(ArmorSlot::Back) - .w_h(45.0, 45.0) + .fabricate(ArmorSlot::Back, [45.0; 2]) .down_from(state.ids.ring_l_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) @@ -440,8 +434,7 @@ impl<'a> Widget for Bag<'a> { .as_ref() .map_or(("Feet", ""), |item| (item.name(), item.description())); slot_maker - .fabricate(ArmorSlot::Feet) - .w_h(45.0, 45.0) + .fabricate(ArmorSlot::Feet, [45.0; 2]) .down_from(state.ids.ring_r_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) @@ -449,8 +442,7 @@ impl<'a> Widget for Bag<'a> { // Tabard let (title, desc) = ("Tabard", ""); slot_maker - .fabricate(ArmorSlot::Tabard) - .w_h(70.0, 70.0) + .fabricate(ArmorSlot::Tabard, [70.0; 2]) .top_right_with_margins_on(state.ids.bg_frame, 80.5, 53.0) .with_icon(self.imgs.tabard_bg, Vec2::new(60.0, 60.0), Some(UI_MAIN)) .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) @@ -462,8 +454,7 @@ impl<'a> Widget for Bag<'a> { .map(|i| &i.item) .map_or(("Mainhand", ""), |item| (item.name(), item.description())); slot_maker - .fabricate(ArmorSlot::Mainhand) - .w_h(85.0, 85.0) + .fabricate(ArmorSlot::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) @@ -475,8 +466,7 @@ impl<'a> Widget for Bag<'a> { .map(|i| &i.item) .map_or(("Offhand", ""), |item| (item.name(), item.description())); slot_maker - .fabricate(ArmorSlot::Offhand) - .w_h(85.0, 85.0) + .fabricate(ArmorSlot::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) @@ -601,8 +591,11 @@ impl<'a> Widget for Bag<'a> { filled_slot: self.imgs.inv_slot, selected_slot: self.imgs.inv_slot_sel, background_color: Some(UI_MAIN), - content_size: Vec2::broadcast(30.0), - selected_content_size: Vec2::broadcast(32.0), + 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), @@ -617,13 +610,12 @@ impl<'a> Widget for Bag<'a> { // Slot let slot_widget = slot_maker - .fabricate(InventorySlot(i)) + .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), - ) - .wh([40.0; 2]); + ); if let Some(item) = item { slot_widget .with_tooltip( diff --git a/voxygen/src/hud/slot_kinds.rs b/voxygen/src/hud/slot_kinds.rs index 900342e324..017c31c3df 100644 --- a/voxygen/src/hud/slot_kinds.rs +++ b/voxygen/src/hud/slot_kinds.rs @@ -86,6 +86,8 @@ impl ContentKey for ArmorSlot { ArmorSlot::Hands => source.hand.as_ref(), ArmorSlot::Legs => source.pants.as_ref(), ArmorSlot::Feet => source.foot.as_ref(), + ArmorSlot::Mainhand => source.active_item.as_ref().map(|i| &i.item), + ArmorSlot::Offhand => source.second_item.as_ref().map(|i| &i.item), _ => None, }; diff --git a/voxygen/src/ui/widgets/slot.rs b/voxygen/src/ui/widgets/slot.rs index 69b8ee3bef..43735d114d 100644 --- a/voxygen/src/ui/widgets/slot.rs +++ b/voxygen/src/ui/widgets/slot.rs @@ -22,14 +22,22 @@ pub trait ContentKey: Copy { pub trait SlotKinds: Sized + PartialEq + Copy {} +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: ContentKey + Into, K: SlotKinds> { 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: Vec2, - pub selected_content_size: Vec2, + 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, @@ -44,14 +52,29 @@ where C: ContentKey + Into, K: SlotKinds, { - pub fn fabricate(&mut self, contents: C) -> Slot { + pub fn fabricate(&mut self, contents: C, 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, - self.content_size, - self.selected_content_size, + content_size, + self.selected_content_scale, self.amount_font, self.amount_font_size, self.amount_margins, @@ -59,6 +82,7 @@ where 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)) } @@ -150,7 +174,23 @@ where std::mem::replace(&mut self.events, Vec::new()) } - fn update(&mut self, widget: widget::Id, slot: K, ui: &conrod_core::Ui) -> Interaction { + fn update( + &mut self, + widget: widget::Id, + slot: K, + ui: &conrod_core::Ui, + filled: bool, + ) -> Interaction { + // 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) @@ -163,7 +203,6 @@ where // TODO: make more robust wrt multiple events in the same frame (eg event order // may matter) TODO: handle taps as well - // TODO: handle clicks in empty space // TODO: handle drags let click_count = ui.widget_input(widget).clicks().left().count(); if click_count > 0 { @@ -192,7 +231,7 @@ where } } else { // No widgets were selected - if odd_num_clicks { + if odd_num_clicks && filled { ManagerState::Selected(widget, slot) } else { // Selected and then deselected with one or more clicks @@ -222,8 +261,7 @@ pub struct Slot<'a, C: ContentKey + Into, K: SlotKinds> { // Size of content image content_size: Vec2, - // TODO: maybe use constant scale factor or move this setting to the slot manager? - selected_content_size: Vec2, + selected_content_scale: f32, icon: Option<(image::Id, Vec2, Option)>, @@ -280,7 +318,7 @@ where filled_slot: image::Id, selected_slot: image::Id, content_size: Vec2, - selected_content_size: Vec2, + selected_content_scale: f32, amount_font: font::Id, amount_font_size: u32, amount_margins: Vec2, @@ -295,7 +333,7 @@ where selected_slot, background_color: None, content_size, - selected_content_size, + selected_content_scale, icon: None, amount_font, amount_font_size, @@ -343,7 +381,7 @@ where selected_slot, background_color, content_size, - selected_content_size, + selected_content_scale, icon, amount_font, amount_font_size, @@ -366,9 +404,9 @@ where } // Get whether this slot is selected - let interaction = self - .slot_manager - .map_or(Interaction::None, |m| m.update(id, content.into(), ui)); + let interaction = self.slot_manager.map_or(Interaction::None, |m| { + m.update(id, content.into(), ui, state.cached_image.is_some()) + }); // Get image ids let content_image = state.cached_image.as_ref().map(|c| c.1); @@ -412,11 +450,12 @@ where if let Some(content_image) = content_image { Image::new(content_image) .x_y(x, y) - .wh(if let Interaction::Selected = interaction { - selected_content_size - } else { - content_size - } + .wh((content_size + * if let Interaction::Selected = interaction { + selected_content_scale + } else { + 1.0 + }) .map(|e| e as f64) .into_array()) .parent(id) From 6c336c015eed445e796d89b292240953a06221e3 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sun, 5 Apr 2020 22:50:27 -0400 Subject: [PATCH 021/195] Make remove skin in armor item images --- voxygen/src/hud/item_imgs.rs | 18 ++++++++++++++++-- voxygen/src/ui/graphic/mod.rs | 9 +++++---- voxygen/src/ui/img_ids.rs | 24 ++++++++++++++++++------ 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/voxygen/src/hud/item_imgs.rs b/voxygen/src/hud/item_imgs.rs index e8a7ca851e..647c91d3ac 100644 --- a/voxygen/src/hud/item_imgs.rs +++ b/voxygen/src/hud/item_imgs.rs @@ -6,6 +6,7 @@ use common::{ tool::{Tool, ToolKind}, Consumable, Ingredient, Item, ItemKind, Utility, }, + figure::Segment, }; use conrod_core::image::Id; use dot_vox::DotVoxData; @@ -49,7 +50,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 +58,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) @@ -179,3 +180,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/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() From 1f31b7f1238608f4a33f1d2d18fbd9bf9d323248 Mon Sep 17 00:00:00 2001 From: Imbris Date: Mon, 6 Apr 2020 02:15:22 -0400 Subject: [PATCH 022/195] Drag things! --- voxygen/src/hud/mod.rs | 5 +- voxygen/src/ui/widgets/ghost_image.rs | 54 ++++++++++ voxygen/src/ui/widgets/mod.rs | 1 + voxygen/src/ui/widgets/slot.rs | 150 +++++++++++++++++--------- 4 files changed, 157 insertions(+), 53 deletions(-) create mode 100644 voxygen/src/ui/widgets/ghost_image.rs diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index b129d73fc1..53cc3e70b7 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -474,6 +474,9 @@ impl Hud { let fonts = ConrodVoxygenFonts::load(&voxygen_i18n.fonts, &mut ui) .expect("Impossible to load fonts!"); + let slot_manager = + slot_kinds::HudSlotManager::new(ui.id_generator(), Vec2::broadcast(40.0)); + Self { ui, imgs, @@ -512,7 +515,7 @@ impl Hud { pulse: 0.0, velocity: 0.0, voxygen_i18n, - slot_manager: slot_kinds::HudSlotManager::new(), + slot_manager, } } 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 c58451ee81..b27ecd39e7 100644 --- a/voxygen/src/ui/widgets/mod.rs +++ b/voxygen/src/ui/widgets/mod.rs @@ -1,3 +1,4 @@ +pub mod ghost_image; pub mod image_frame; pub mod image_slider; pub mod ingame; diff --git a/voxygen/src/ui/widgets/slot.rs b/voxygen/src/ui/widgets/slot.rs index 43735d114d..ff7ab353de 100644 --- a/voxygen/src/ui/widgets/slot.rs +++ b/voxygen/src/ui/widgets/slot.rs @@ -1,6 +1,7 @@ //! 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, @@ -20,7 +21,7 @@ pub trait ContentKey: Copy { fn image_id(key: &Self::ImageKey, source: &Self::ImageSource) -> image::Id; } -pub trait SlotKinds: Sized + PartialEq + Copy {} +pub trait SlotKinds: Sized + PartialEq + Copy + Send + 'static {} pub struct ContentSize { // Width divided by height @@ -88,44 +89,9 @@ where } } -/* -// Note: will probably delete this -// Integrate tooltip adding?? -use super::tooltip::{Tooltip, TooltipManager, Tooltipable}; -pub fn with_tooltips( - self, - tooltip_manager: &'a mut TooltipManager, - tooltip: &'a Tooltip<'a>, -) -> TooltippedSlotMaker<'a, C, K> { - TooltippedSlotMaker { - slot_maker: self, - tooltip_manager, - tooltip, - } -} - - -tooltip: &'a Tooltip<'a>, -slot_widget - .with_tooltip( - self.tooltip_manager, - &item.name(), - &format!( - "{}", - /* item.kind, item.effect(), */ item.description() - ), - &item_tooltip, - ) - -pub struct TooltippedSlotMaker<'a, C: ContentKey + Into, K: SlotKinds> { - pub slot_maker: SlotMaker<'a, C, K>, - pub tooltip_manager: &'a mut TooltipManager, - pub tooltip: &'a Tooltip, -}*/ - #[derive(Clone, Copy)] enum ManagerState { - Dragging(widget::Id, K), + Dragging(widget::Id, K, image::Id), Selected(widget::Id, K), Idle, } @@ -147,30 +113,74 @@ pub enum Event { // Handles interactions with slots pub struct SlotManager { state: ManagerState, + // Rebuilt every frame + slot_ids: Vec, + // Rebuilt every frame + slot_kinds: Vec, events: Vec>, - // widget id for dragging image + // 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 K: SlotKinds, { - pub fn new() -> Self { + pub fn new(mut gen: widget::id::Generator, drag_img_size: Vec2) -> Self { Self { state: ManagerState::Idle, + slot_ids: Vec::new(), + slot_kinds: Vec::new(), events: Vec::new(), + drag_id: gen.next(), + drag_img_size, } } - pub fn maintain(&mut self, ui: &conrod_core::Ui) -> Vec> { + 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 slot_kinds = std::mem::replace(&mut self.slot_kinds, 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)) + self.events.push(Event::Dropped(slot)); } } + // If dragging and mouse if released check if there is a slot widget under the + // mouse + if let ManagerState::Dragging(id, 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, slot_kinds[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()) } @@ -179,11 +189,16 @@ where widget: widget::Id, slot: K, ui: &conrod_core::Ui, - filled: bool, + content_img: Option, ) -> Interaction { + // Add to list of slots + self.slot_ids.push(widget); + self.slot_kinds.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, _) + ManagerState::Selected(id, _) | ManagerState::Dragging(id, _, _) if *id == widget && !filled => { self.state = ManagerState::Idle; @@ -193,7 +208,8 @@ where // 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) + ManagerState::Selected(id, stored_slot) + | ManagerState::Dragging(id, stored_slot, _) if *id == widget => { *stored_slot = slot @@ -203,7 +219,6 @@ where // TODO: make more robust wrt multiple events in the same frame (eg event order // may matter) TODO: handle taps as well - // TODO: handle drags let click_count = ui.widget_input(widget).clicks().left().count(); if click_count > 0 { let odd_num_clicks = click_count % 2 == 1; @@ -240,10 +255,25 @@ where }; } + // If not dragging and the mouse is down and started on this slot start dragging + let input = &ui.global_input().current; + if let mouse::ButtonPosition::Down(_, Some(id)) = input.mouse.buttons.left() { + match self.state { + ManagerState::Selected(_, _) | ManagerState::Idle if widget == *id => { + // Start dragging if widget is filled + if let Some(img) = content_img { + self.state = ManagerState::Dragging(widget, slot, img); + } + }, + // Already dragging or id doesn't match + _ => {}, + } + } + // 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, + ManagerState::Dragging(id, _, _) if id == widget => Interaction::Dragging, _ => Interaction::None, } } @@ -292,9 +322,10 @@ widget_ids! { } /// Represents the state of the Slot widget. -pub struct State { +pub struct State { ids: Ids, cached_image: Option<(K, image::Id)>, + slot_kind: S, } impl<'a, C, K> Slot<'a, C, K> @@ -353,13 +384,14 @@ where K: SlotKinds, { type Event = (); - type State = State; + 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, + slot_kind: self.content.into(), } } @@ -403,13 +435,27 @@ where }); } - // Get whether this slot is selected - let interaction = self.slot_manager.map_or(Interaction::None, |m| { - m.update(id, content.into(), ui, state.cached_image.is_some()) - }); + // If the slot kind value changed update the state + let slot_kind = content.into(); + if slot_kind != state.slot_kind { + state.update(|state| { + state.slot_kind = slot_kind; + }); + } // 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, content.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() { From 771baaaae5dca497178abf53cd6472bf6d1c7fd1 Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Mon, 6 Apr 2020 02:25:52 +0200 Subject: [PATCH 023/195] cape item --- assets/common/items/armor/back/short_0.ron | 8 +++ .../voxel/humanoid_armor_back_manifest.ron | 12 ++++ common/src/comp/inventory/item/armor.rs | 7 +++ server/src/events/inventory_manip.rs | 1 + voxygen/src/scene/figure/load.rs | 59 +++++++++++++++++++ 5 files changed, 87 insertions(+) create mode 100644 assets/common/items/armor/back/short_0.ron create mode 100644 assets/voxygen/voxel/humanoid_armor_back_manifest.ron 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..805b88edd0 --- /dev/null +++ b/assets/common/items/armor/back/short_0.ron @@ -0,0 +1,8 @@ +Item( + name: "Short leather Cape", + description: "WIP", + kind: Armor( + kind: Back(Short0), + stats: (20), + ), +) 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..4d1db6dcff --- /dev/null +++ b/assets/voxygen/voxel/humanoid_armor_back_manifest.ron @@ -0,0 +1,12 @@ +(( + default: ( + vox_spec: ("armor.empty", (0.0, 0.0, 0.0)), + color: None + ), + map: { + Short0: ( + vox_spec: ("armor.back.short-0", (0.0, 0.0, 0.0)), + color: None + ), + }, +)) diff --git a/common/src/comp/inventory/item/armor.rs b/common/src/comp/inventory/item/armor.rs index 88cd537203..339ef7c92a 100644 --- a/common/src/comp/inventory/item/armor.rs +++ b/common/src/comp/inventory/item/armor.rs @@ -163,6 +163,12 @@ 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, +} +pub const ALL_BACKS: [Back; 1] = [Back::Short0]; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum Armor { @@ -172,6 +178,7 @@ pub enum Armor { Hand(Hand), Pants(Pants), Foot(Foot), + Back(Back), } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index f5929ec4f5..57900f7c86 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -138,6 +138,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv Hand(_) => &mut loadout.hand, Pants(_) => &mut loadout.pants, Foot(_) => &mut loadout.foot, + Back(_) => &mut loadout.back, }; // Insert old item into inventory diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs index fcb221726d..bbfb0bb309 100644 --- a/voxygen/src/scene/figure/load.rs +++ b/voxygen/src/scene/figure/load.rs @@ -255,6 +255,8 @@ pub struct HumArmorPantsSpec(ArmorVoxSpecMap); #[derive(Serialize, Deserialize)] pub struct HumArmorFootSpec(ArmorVoxSpecMap); #[derive(Serialize, Deserialize)] +pub struct HumArmorBackSpec(ArmorVoxSpecMap); +#[derive(Serialize, Deserialize)] pub struct HumMainWeaponSpec(HashMap); impl Asset for HumArmorShoulderSpec { @@ -299,6 +301,13 @@ impl Asset for HumArmorFootSpec { 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 HumMainWeaponSpec { const ENDINGS: &'static [&'static str] = &["ron"]; @@ -597,6 +606,56 @@ impl HumArmorFootSpec { self.mesh_foot(body, loadout, false) } } +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-armor specification exists for {:?}", back); + 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_back = graceful_load_mat_segment("armor.empty"); + + let mut back_armor = graceful_load_mat_segment(&spec.vox_spec.0); + + if let Some(color) = spec.color { + let back_color = Vec3::from(color); + back_armor = back_armor.map_rgb(|rgb| recolor_grey(rgb, Rgb::from(back_color))); + } + + let back = DynaUnionizer::new() + .add(color(bare_back), Vec3::new(0, 0, 0)) + .add(color(back_armor), Vec3::new(0, 0, 0)) + .unify() + .0; + + generate_mesh(&back, Vec3::from(spec.vox_spec.1)) + } +} impl HumMainWeaponSpec { pub fn load_watched(indicator: &mut ReloadIndicator) -> Arc { From 7f97fbac0b9d381b07aee6618b1001fec0bdaff0 Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Mon, 6 Apr 2020 18:11:05 +0200 Subject: [PATCH 024/195] cape item --- .../voxel/humanoid_armor_back_manifest.ron | 2 +- common/src/comp/ability.rs | 1 + server/src/state_ext.rs | 1 + server/src/sys/terrain.rs | 4 ++ voxygen/src/menu/char_selection/ui.rs | 1 + voxygen/src/scene/figure/load.rs | 58 +++++++++---------- 6 files changed, 37 insertions(+), 30 deletions(-) diff --git a/assets/voxygen/voxel/humanoid_armor_back_manifest.ron b/assets/voxygen/voxel/humanoid_armor_back_manifest.ron index 4d1db6dcff..142a876de6 100644 --- a/assets/voxygen/voxel/humanoid_armor_back_manifest.ron +++ b/assets/voxygen/voxel/humanoid_armor_back_manifest.ron @@ -5,7 +5,7 @@ ), map: { Short0: ( - vox_spec: ("armor.back.short-0", (0.0, 0.0, 0.0)), + vox_spec: ("armor.back.short-0", (10.0, 10.0, 10.0)), color: None ), }, diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 0b009b270f..aefb5b2538 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -104,6 +104,7 @@ pub struct Loadout { pub hand: Option, pub pants: Option, pub foot: Option, + pub back: Option, } impl From<&CharacterAbility> for CharacterState { diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 458339b690..e8dc2a2487 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -184,6 +184,7 @@ impl StateExt for State { hand: None, pants: None, foot: None, + back: None, } } else { comp::Loadout::default() diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index dbc6bcd513..a45246583a 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -243,6 +243,7 @@ impl<'a> System<'a> for Sys { foot: Some(assets::load_expect_cloned( "common.items.armor.foot.leather_0", )), + back: None, }, comp::Alignment::Enemy => comp::Loadout { active_item, @@ -265,6 +266,7 @@ impl<'a> System<'a> for Sys { foot: Some(assets::load_expect_cloned( "common.items.armor.foot.plate_0", )), + back: None, }, _ => comp::Loadout { active_item, @@ -275,6 +277,7 @@ impl<'a> System<'a> for Sys { hand: None, pants: None, foot: None, + back: None, }, }; @@ -335,6 +338,7 @@ impl<'a> System<'a> for Sys { foot: Some(assets::load_expect_cloned( "common.items.armor.foot.plate_0", )), + back: None, }; stats.level.set_level(rand::thread_rng().gen_range(30, 35)); diff --git a/voxygen/src/menu/char_selection/ui.rs b/voxygen/src/menu/char_selection/ui.rs index 2c8d7ac104..2804c84fcd 100644 --- a/voxygen/src/menu/char_selection/ui.rs +++ b/voxygen/src/menu/char_selection/ui.rs @@ -355,6 +355,7 @@ impl CharSelectionUi { hand: None, pants: None, foot: None, + back: None, }; Some(loadout) }, diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs index bbfb0bb309..f372a309df 100644 --- a/voxygen/src/scene/figure/load.rs +++ b/voxygen/src/scene/figure/load.rs @@ -12,7 +12,7 @@ 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, Pants, Shoulder}, tool::{Tool, ToolKind}, ItemKind, }, @@ -255,9 +255,9 @@ pub struct HumArmorPantsSpec(ArmorVoxSpecMap); #[derive(Serialize, Deserialize)] pub struct HumArmorFootSpec(ArmorVoxSpecMap); #[derive(Serialize, Deserialize)] -pub struct HumArmorBackSpec(ArmorVoxSpecMap); -#[derive(Serialize, Deserialize)] pub struct HumMainWeaponSpec(HashMap); +#[derive(Serialize, Deserialize)] +pub struct HumArmorBackSpec(ArmorVoxSpecMap); impl Asset for HumArmorShoulderSpec { const ENDINGS: &'static [&'static str] = &["ron"]; @@ -606,6 +606,32 @@ impl HumArmorFootSpec { self.mesh_foot(body, loadout, false) } } + +impl HumMainWeaponSpec { + pub fn load_watched(indicator: &mut ReloadIndicator) -> Arc { + assets::load_watched::("voxygen.voxel.humanoid_main_weapon_manifest", indicator) + .unwrap() + } + + pub fn mesh_main_weapon(&self, item_kind: Option<&ItemKind>) -> Mesh { + let tool_kind = if let Some(ItemKind::Tool(Tool { kind, .. })) = item_kind { + kind + } else { + return Mesh::new(); + }; + + let spec = match self.0.get(tool_kind) { + Some(spec) => spec, + None => { + error!("No hand specification exists for {:?}", tool_kind); + return load_mesh("not_found", Vec3::new(-1.5, -1.5, -7.0)); + }, + }; + + let tool_kind_segment = graceful_load_segment(&spec.vox_spec.0); + generate_mesh(&tool_kind_segment, Vec3::from(spec.vox_spec.1)) + } +} impl HumArmorBackSpec { pub fn load_watched(indicator: &mut ReloadIndicator) -> Arc { assets::load_watched::("voxygen.voxel.humanoid_armor_back_manifest", indicator) @@ -657,32 +683,6 @@ impl HumArmorBackSpec { } } -impl HumMainWeaponSpec { - pub fn load_watched(indicator: &mut ReloadIndicator) -> Arc { - assets::load_watched::("voxygen.voxel.humanoid_main_weapon_manifest", indicator) - .unwrap() - } - - pub fn mesh_main_weapon(&self, item_kind: Option<&ItemKind>) -> Mesh { - let tool_kind = if let Some(ItemKind::Tool(Tool { kind, .. })) = item_kind { - kind - } else { - return Mesh::new(); - }; - - let spec = match self.0.get(tool_kind) { - Some(spec) => spec, - None => { - error!("No hand specification exists for {:?}", tool_kind); - return load_mesh("not_found", Vec3::new(-1.5, -1.5, -7.0)); - }, - }; - - let tool_kind_segment = graceful_load_segment(&spec.vox_spec.0); - generate_mesh(&tool_kind_segment, 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)) From f5a768dccfe2d00807f2b3cff9749caa5c4d12f3 Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Mon, 6 Apr 2020 22:03:46 +0200 Subject: [PATCH 025/195] more armour types --- assets/voxygen/element/icons/lantern.png | 3 + .../voxel/humanoid_armor_head_manifest.ron | 12 ++ .../voxel/humanoid_armor_lantern_manifest.ron | 12 ++ .../voxel/humanoid_armor_neck_manifest.ron | 12 ++ .../voxel/humanoid_armor_ring_manifest.ron | 12 ++ .../voxel/humanoid_armor_tabard_manifest.ron | 12 ++ assets/voxygen/voxel/object/lantern0.vox | 3 + common/src/comp/ability.rs | 5 + common/src/comp/inventory/item/armor.rs | 35 ++++ server/src/events/inventory_manip.rs | 5 + server/src/state_ext.rs | 5 + server/src/sys/terrain.rs | 20 ++ voxygen/src/menu/char_selection/ui.rs | 5 + voxygen/src/scene/figure/load.rs | 195 +++++++++++++++++- 14 files changed, 328 insertions(+), 8 deletions(-) create mode 100644 assets/voxygen/element/icons/lantern.png create mode 100644 assets/voxygen/voxel/humanoid_armor_head_manifest.ron create mode 100644 assets/voxygen/voxel/humanoid_armor_lantern_manifest.ron create mode 100644 assets/voxygen/voxel/humanoid_armor_neck_manifest.ron create mode 100644 assets/voxygen/voxel/humanoid_armor_ring_manifest.ron create mode 100644 assets/voxygen/voxel/humanoid_armor_tabard_manifest.ron create mode 100644 assets/voxygen/voxel/object/lantern0.vox 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/voxel/humanoid_armor_head_manifest.ron b/assets/voxygen/voxel/humanoid_armor_head_manifest.ron new file mode 100644 index 0000000000..0e367e48af --- /dev/null +++ b/assets/voxygen/voxel/humanoid_armor_head_manifest.ron @@ -0,0 +1,12 @@ +(( + default: ( + vox_spec: ("armor.empty", (0.0, 0.0, 0.0)), + color: None + ), + map: { + Neck0: ( + vox_spec: ("armor.empty", (0.0, 0.0, 0.0)), + color: None + ), + }, +)) diff --git a/assets/voxygen/voxel/humanoid_armor_lantern_manifest.ron b/assets/voxygen/voxel/humanoid_armor_lantern_manifest.ron new file mode 100644 index 0000000000..7ab66ff84a --- /dev/null +++ b/assets/voxygen/voxel/humanoid_armor_lantern_manifest.ron @@ -0,0 +1,12 @@ +(( + default: ( + vox_spec: ("armor.empty", (0.0, 0.0, 0.0)), + color: None + ), + map: { + Green0: ( + vox_spec: ("armor.empty", (0.0, 0.0, 0.0)), + color: None + ), + }, +)) diff --git a/assets/voxygen/voxel/humanoid_armor_neck_manifest.ron b/assets/voxygen/voxel/humanoid_armor_neck_manifest.ron new file mode 100644 index 0000000000..0e367e48af --- /dev/null +++ b/assets/voxygen/voxel/humanoid_armor_neck_manifest.ron @@ -0,0 +1,12 @@ +(( + default: ( + vox_spec: ("armor.empty", (0.0, 0.0, 0.0)), + color: None + ), + map: { + Neck0: ( + vox_spec: ("armor.empty", (0.0, 0.0, 0.0)), + color: None + ), + }, +)) diff --git a/assets/voxygen/voxel/humanoid_armor_ring_manifest.ron b/assets/voxygen/voxel/humanoid_armor_ring_manifest.ron new file mode 100644 index 0000000000..6dc285312c --- /dev/null +++ b/assets/voxygen/voxel/humanoid_armor_ring_manifest.ron @@ -0,0 +1,12 @@ +(( + default: ( + vox_spec: ("armor.empty", (0.0, 0.0, 0.0)), + color: None + ), + map: { + Ring0: ( + vox_spec: ("armor.empty", (0.0, 0.0, 0.0)), + color: None + ), + }, +)) diff --git a/assets/voxygen/voxel/humanoid_armor_tabard_manifest.ron b/assets/voxygen/voxel/humanoid_armor_tabard_manifest.ron new file mode 100644 index 0000000000..0e367e48af --- /dev/null +++ b/assets/voxygen/voxel/humanoid_armor_tabard_manifest.ron @@ -0,0 +1,12 @@ +(( + default: ( + vox_spec: ("armor.empty", (0.0, 0.0, 0.0)), + color: None + ), + map: { + Neck0: ( + vox_spec: ("armor.empty", (0.0, 0.0, 0.0)), + color: None + ), + }, +)) 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/common/src/comp/ability.rs b/common/src/comp/ability.rs index aefb5b2538..e1f0af4d2b 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -105,6 +105,11 @@ pub struct Loadout { 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/inventory/item/armor.rs b/common/src/comp/inventory/item/armor.rs index 339ef7c92a..002ecb2789 100644 --- a/common/src/comp/inventory/item/armor.rs +++ b/common/src/comp/inventory/item/armor.rs @@ -169,6 +169,36 @@ pub enum Back { Short0 = 1, } pub const ALL_BACKS: [Back; 1] = [Back::Short0]; +#[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 Lantern { + Lantern0 = 1, +} +pub const ALL_LANTERNS: [Lantern; 1] = [Lantern::Lantern0]; +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[repr(u32)] +pub enum Head { + Head0 = 1, +} +pub const ALL_HEADS: [Head; 1] = [Head::Head0]; +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[repr(u32)] +pub enum Tabard { + Tabard0 = 1, +} +pub const ALL_TABARDS: [Tabard; 1] = [Tabard::Tabard0]; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum Armor { @@ -179,6 +209,11 @@ pub enum Armor { Pants(Pants), Foot(Foot), Back(Back), + Ring(Ring), + Neck(Neck), + Lantern(Lantern), + Head(Head), + Tabard(Tabard), } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index 57900f7c86..54fe6554bd 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -139,6 +139,11 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv Pants(_) => &mut loadout.pants, Foot(_) => &mut loadout.foot, Back(_) => &mut loadout.back, + Ring(_) => &mut loadout.back, + Neck(_) => &mut loadout.back, + Lantern(_) => &mut loadout.back, + Head(_) => &mut loadout.back, + Tabard(_) => &mut loadout.back, }; // Insert old item into inventory diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index e8dc2a2487..b5ee99286d 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -185,6 +185,11 @@ impl StateExt for State { pants: None, foot: None, 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 a45246583a..352ed91ecc 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -244,6 +244,11 @@ impl<'a> System<'a> for Sys { "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, @@ -267,6 +272,11 @@ impl<'a> System<'a> for Sys { "common.items.armor.foot.plate_0", )), back: None, + ring: None, + neck: None, + lantern: None, + head: None, + tabard: None, }, _ => comp::Loadout { active_item, @@ -278,6 +288,11 @@ impl<'a> System<'a> for Sys { pants: None, foot: None, back: None, + ring: None, + neck: None, + lantern: None, + head: None, + tabard: None, }, }; @@ -339,6 +354,11 @@ impl<'a> System<'a> for Sys { "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)); diff --git a/voxygen/src/menu/char_selection/ui.rs b/voxygen/src/menu/char_selection/ui.rs index 2804c84fcd..51c46309d7 100644 --- a/voxygen/src/menu/char_selection/ui.rs +++ b/voxygen/src/menu/char_selection/ui.rs @@ -356,6 +356,11 @@ impl CharSelectionUi { pants: None, foot: None, back: None, + ring: None, + neck: None, + lantern: None, + head: None, + tabard: None, }; Some(loadout) }, diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs index f372a309df..c3cc21f428 100644 --- a/voxygen/src/scene/figure/load.rs +++ b/voxygen/src/scene/figure/load.rs @@ -12,7 +12,7 @@ use common::{ dragon, fish_medium, fish_small, humanoid::{Body, BodyType, EyeColor, Eyebrows, Race, Skin}, item::{ - armor::{Armor, Back, Belt, Chest, Foot, Hand, Pants, Shoulder}, + armor::{Armor, Back, Belt, Chest, Foot, Hand, Head, Lantern, Pants, Shoulder, Tabard}, tool::{Tool, ToolKind}, ItemKind, }, @@ -258,6 +258,12 @@ pub struct HumArmorFootSpec(ArmorVoxSpecMap); pub struct HumMainWeaponSpec(HashMap); #[derive(Serialize, Deserialize)] pub struct HumArmorBackSpec(ArmorVoxSpecMap); +#[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"]; @@ -308,6 +314,27 @@ impl Asset for HumArmorBackSpec { 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"]; @@ -315,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) @@ -372,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) @@ -423,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) @@ -475,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) @@ -509,7 +536,7 @@ impl HumArmorBeltSpec { generate_mesh(&belt_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) @@ -560,7 +587,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) @@ -632,6 +659,7 @@ impl HumMainWeaponSpec { generate_mesh(&tool_kind_segment, Vec3::from(spec.vox_spec.1)) } } +// Back impl HumArmorBackSpec { pub fn load_watched(indicator: &mut ReloadIndicator) -> Arc { assets::load_watched::("voxygen.voxel.humanoid_armor_back_manifest", indicator) @@ -682,13 +710,164 @@ impl HumArmorBackSpec { generate_mesh(&back, Vec3::from(spec.vox_spec.1)) } } +// Lantern +impl HumArmorLanternSpec { + pub fn load_watched(indicator: &mut ReloadIndicator) -> Arc { + assets::load_watched::("voxygen.voxel.humanoid_armor_lantern_manifest", indicator) + .unwrap() + } + pub fn mesh_lantern(&self, body: &Body, loadout: &Loadout) -> Mesh { + let spec = if let Some(ItemKind::Armor { + kind: Armor::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(-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_lantern = graceful_load_mat_segment("armor.empty"); + + let mut lantern_armor = graceful_load_mat_segment(&spec.vox_spec.0); + + if let Some(color) = spec.color { + let lantern_color = Vec3::from(color); + lantern_armor = + lantern_armor.map_rgb(|rgb| recolor_grey(rgb, Rgb::from(lantern_color))); + } + + let lantern = DynaUnionizer::new() + .add(color(bare_lantern), Vec3::new(0, 0, 0)) + .add(color(lantern_armor), Vec3::new(0, 0, 0)) + .unify() + .0; + + generate_mesh(&lantern, 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_lantern(&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_lantern(&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)) + load_mesh("object.lantern0", Vec3::new(0.0, 0.0, 0.0)) } ///////// From fa03f9ddb9cef72ddad7e772530bcd4e14e8aad0 Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Mon, 6 Apr 2020 23:16:24 +0200 Subject: [PATCH 026/195] slot kinds --- voxygen/src/hud/bag.rs | 28 ++++++++++++++-------------- voxygen/src/hud/img_ids.rs | 4 ++-- voxygen/src/hud/slot_kinds.rs | 11 +++++++++-- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/voxygen/src/hud/bag.rs b/voxygen/src/hud/bag.rs index 60a74ac19f..e166c8a166 100644 --- a/voxygen/src/hud/bag.rs +++ b/voxygen/src/hud/bag.rs @@ -67,8 +67,8 @@ widget_ids! { hands_slot, legs_slot, belt_slot, - ring_r_slot, - ring_l_slot, + lantern_slot, + ring_slot, feet_slot, back_slot, tabard_slot, @@ -404,27 +404,27 @@ impl<'a> Widget for Bag<'a> { .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); - // Ring-L - let (title, desc) = ("Left Ring", ""); + // Lantern + let (title, desc) = ("Lantern", ""); slot_maker - .fabricate(ArmorSlot::LeftRing, [45.0; 2]) + .fabricate(ArmorSlot::Lantern, [45.0; 2]) .bottom_right_with_margins_on(state.ids.shoulders_slot, -55.0, 0.0) - .with_icon(self.imgs.ring_l_bg, Vec2::new(36.0, 40.0), Some(UI_MAIN)) + .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.ring_l_slot, ui); - // Ring-R - let (title, desc) = ("Right Ring", ""); + .set(state.ids.lantern_slot, ui); + // Ring + let (title, desc) = ("Ring", ""); slot_maker - .fabricate(ArmorSlot::RightRing, [45.0; 2]) + .fabricate(ArmorSlot::Ring, [45.0; 2]) .bottom_left_with_margins_on(state.ids.hands_slot, -55.0, 0.0) - .with_icon(self.imgs.ring_r_bg, Vec2::new(36.0, 40.0), Some(UI_MAIN)) + .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_r_slot, ui); + .set(state.ids.ring_slot, ui); // Back let (title, desc) = ("Back", ""); slot_maker .fabricate(ArmorSlot::Back, [45.0; 2]) - .down_from(state.ids.ring_l_slot, 10.0) + .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); @@ -435,7 +435,7 @@ impl<'a> Widget for Bag<'a> { .map_or(("Feet", ""), |item| (item.name(), item.description())); slot_maker .fabricate(ArmorSlot::Feet, [45.0; 2]) - .down_from(state.ids.ring_r_slot, 10.0) + .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); diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index a49e22e810..719018b21d 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -238,11 +238,11 @@ image_ids! { 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/slot_kinds.rs b/voxygen/src/hud/slot_kinds.rs index 017c31c3df..6dcf3d260d 100644 --- a/voxygen/src/hud/slot_kinds.rs +++ b/voxygen/src/hud/slot_kinds.rs @@ -8,6 +8,7 @@ pub enum HudSlotKinds { Inventory(InventorySlot), Armor(ArmorSlot), Hotbar(HotbarSlot), + //Spellbook(SpellbookSlot), TODO } pub type HudSlotManager = SlotManager; @@ -22,8 +23,8 @@ pub enum ArmorSlot { Shoulders, Chest, Hands, - LeftRing, - RightRing, + Ring, + Lantern, Back, Belt, Legs, @@ -86,6 +87,12 @@ impl ContentKey for ArmorSlot { ArmorSlot::Hands => source.hand.as_ref(), ArmorSlot::Legs => source.pants.as_ref(), ArmorSlot::Feet => source.foot.as_ref(), + ArmorSlot::Back => source.back.as_ref(), + ArmorSlot::Ring => source.ring.as_ref(), + ArmorSlot::Neck => source.neck.as_ref(), + ArmorSlot::Head => source.head.as_ref(), + ArmorSlot::Lantern => source.lantern.as_ref(), + ArmorSlot::Tabard => source.tabard.as_ref(), ArmorSlot::Mainhand => source.active_item.as_ref().map(|i| &i.item), ArmorSlot::Offhand => source.second_item.as_ref().map(|i| &i.item), _ => None, From ea2c646df3c62e1c41f4b50f7a22e0e7499d0fc7 Mon Sep 17 00:00:00 2001 From: jshipsey Date: Mon, 6 Apr 2020 22:55:16 -0400 Subject: [PATCH 027/195] initial cape implementation --- assets/voxygen/voxel/armor/back/short-0.vox | 4 +- .../voxel/humanoid_armor_back_manifest.ron | 2 +- voxygen/src/anim/biped_large/mod.rs | 3 +- voxygen/src/anim/bird_medium/mod.rs | 3 +- voxygen/src/anim/bird_small/mod.rs | 3 +- voxygen/src/anim/character/jump.rs | 4 + voxygen/src/anim/character/mod.rs | 5 +- voxygen/src/anim/character/run.rs | 4 + voxygen/src/anim/character/stand.rs | 6 +- voxygen/src/anim/critter/mod.rs | 3 +- voxygen/src/anim/dragon/mod.rs | 3 +- voxygen/src/anim/fish_medium/mod.rs | 3 +- voxygen/src/anim/fish_small/mod.rs | 3 +- voxygen/src/anim/fixture/mod.rs | 3 +- voxygen/src/anim/mod.rs | 2 +- voxygen/src/anim/object/mod.rs | 3 +- voxygen/src/anim/quadruped_medium/mod.rs | 3 +- voxygen/src/anim/quadruped_small/mod.rs | 3 +- voxygen/src/scene/figure/cache.rs | 11 +- voxygen/src/scene/figure/load.rs | 103 ++++++++---------- 20 files changed, 96 insertions(+), 78 deletions(-) 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/humanoid_armor_back_manifest.ron b/assets/voxygen/voxel/humanoid_armor_back_manifest.ron index 142a876de6..6601f0e9ab 100644 --- a/assets/voxygen/voxel/humanoid_armor_back_manifest.ron +++ b/assets/voxygen/voxel/humanoid_armor_back_manifest.ron @@ -5,7 +5,7 @@ ), map: { Short0: ( - vox_spec: ("armor.back.short-0", (10.0, 10.0, 10.0)), + vox_spec: ("armor.back.short-0", (-5.0, -1.0, -11.0)), color: None ), }, diff --git a/voxygen/src/anim/biped_large/mod.rs b/voxygen/src/anim/biped_large/mod.rs index 282c094876..0a066963a4 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; 19] { 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(); @@ -80,6 +80,7 @@ impl Skeleton for BipedLargeSkeleton { FigureBoneData::default(), FigureBoneData::default(), FigureBoneData::default(), + FigureBoneData::default(), ] } diff --git a/voxygen/src/anim/bird_medium/mod.rs b/voxygen/src/anim/bird_medium/mod.rs index 8e15bdaef2..0d134a31cb 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; 19] { let torso_mat = self.torso.compute_base_matrix(); [ @@ -49,6 +49,7 @@ impl Skeleton for BirdMediumSkeleton { 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..b2989c9cee 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; 19] { let torso_mat = self.torso.compute_base_matrix(); [ @@ -53,6 +53,7 @@ impl Skeleton for BirdSmallSkeleton { FigureBoneData::default(), FigureBoneData::default(), FigureBoneData::default(), + FigureBoneData::default(), ] } diff --git a/voxygen/src/anim/character/jump.rs b/voxygen/src/anim/character/jump.rs index ba90251487..82d09590c3 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(); diff --git a/voxygen/src/anim/character/mod.rs b/voxygen/src/anim/character/mod.rs index 26cf805191..8dc6e083f3 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; 19] { 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(); @@ -77,6 +78,7 @@ impl Skeleton for CharacterSkeleton { 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.back.compute_base_matrix()), FigureBoneData::new(torso_mat * chest_mat * self.shorts.compute_base_matrix()), 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), @@ -99,6 +101,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/run.rs b/voxygen/src/anim/character/run.rs index 27643664a1..594b4634d2 100644 --- a/voxygen/src/anim/character/run.rs +++ b/voxygen/src/anim/character/run.rs @@ -90,6 +90,10 @@ impl Animation for RunAnimation { 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(); diff --git a/voxygen/src/anim/character/stand.rs b/voxygen/src/anim/character/stand.rs index 74b77ff306..97f9660e39 100644 --- a/voxygen/src/anim/character/stand.rs +++ b/voxygen/src/anim/character/stand.rs @@ -50,6 +50,10 @@ impl Animation for StandAnimation { next.belt.ori = Quaternion::rotation_z(head_look.x * -0.1); next.belt.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); //2 next.shorts.ori = Quaternion::rotation_x(head_look.x * -0.2); next.shorts.scale = Vec3::one() + breathe * -0.05; @@ -101,7 +105,7 @@ impl Animation for StandAnimation { 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.scale = Vec3::one() * 5.0; 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/critter/mod.rs b/voxygen/src/anim/critter/mod.rs index 4a2c05ef6d..3c0538b5e4 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; 19] { [ FigureBoneData::new(self.head.compute_base_matrix()), FigureBoneData::new(self.chest.compute_base_matrix()), @@ -52,6 +52,7 @@ impl Skeleton for CritterSkeleton { 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..5121b7f7c0 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; 19] { 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(); @@ -74,6 +74,7 @@ impl Skeleton for DragonSkeleton { 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..919a08918c 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; 19] { let torso_mat = self.torso.compute_base_matrix(); let rear_mat = self.rear.compute_base_matrix(); @@ -58,6 +58,7 @@ impl Skeleton for FishMediumSkeleton { 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..1b652c61c5 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; 19] { let torso_mat = self.torso.compute_base_matrix(); [ @@ -49,6 +49,7 @@ impl Skeleton for FishSmallSkeleton { 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..9b45c2fca8 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; 19] { [ FigureBoneData::new(vek::Mat4::identity()), FigureBoneData::new(vek::Mat4::identity()), @@ -33,6 +33,7 @@ impl Skeleton for FixtureSkeleton { 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..82950e49da 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; 19]; /// Change the current skeleton to be more like `target`. fn interpolate(&mut self, target: &Self, dt: f32); diff --git a/voxygen/src/anim/object/mod.rs b/voxygen/src/anim/object/mod.rs index b9befc2522..e2b587139e 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; 19] { [ FigureBoneData::new(Mat4::scaling_3d(Vec3::broadcast(SCALE))), FigureBoneData::new(Mat4::scaling_3d(Vec3::broadcast(SCALE))), @@ -35,6 +35,7 @@ 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))), ] } diff --git a/voxygen/src/anim/quadruped_medium/mod.rs b/voxygen/src/anim/quadruped_medium/mod.rs index a30eaa0203..3be2ef9599 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; 19] { 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(); @@ -55,6 +55,7 @@ impl Skeleton for QuadrupedMediumSkeleton { 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..c76b1e6a92 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; 19] { [ FigureBoneData::new(self.head.compute_base_matrix()), FigureBoneData::new(self.chest.compute_base_matrix()), @@ -46,6 +46,7 @@ impl Skeleton for QuadrupedSmallSkeleton { FigureBoneData::default(), FigureBoneData::default(), FigureBoneData::default(), + FigureBoneData::default(), ] } diff --git a/voxygen/src/scene/figure/cache.rs b/voxygen/src/scene/figure/cache.rs index 26961df98c..fb12fe9740 100644 --- a/voxygen/src/scene/figure/cache.rs +++ b/voxygen/src/scene/figure/cache.rs @@ -30,6 +30,7 @@ struct CharacterCacheKey { shoulder: Option, chest: Option, belt: Option, + back: Option, hand: Option, pants: Option, foot: Option, @@ -49,6 +50,7 @@ impl CharacterCacheKey { shoulder: loadout.shoulder.clone(), chest: loadout.chest.clone(), belt: loadout.belt.clone(), + back: loadout.back.clone(), hand: loadout.hand.clone(), pants: loadout.pants.clone(), foot: loadout.foot.clone(), @@ -118,6 +120,8 @@ 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_pants_spec = HumArmorPantsSpec::load_watched(&mut self.manifest_indicator); let humanoid_armor_foot_spec = @@ -158,6 +162,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), @@ -227,7 +237,6 @@ impl FigureModelCache { Some(mesh_lantern()), None, None, - None, ] }, Body::QuadrupedSmall(body) => { diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs index c3cc21f428..bcae0fbf7c 100644 --- a/voxygen/src/scene/figure/load.rs +++ b/voxygen/src/scene/figure/load.rs @@ -251,14 +251,14 @@ 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 HumArmorBackSpec(ArmorVoxSpecMap); -#[derive(Serialize, Deserialize)] pub struct HumArmorLanternSpec(ArmorVoxSpecMap); #[derive(Serialize, Deserialize)] pub struct HumArmorHeadSpec(ArmorVoxSpecMap); @@ -293,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"]; @@ -307,13 +314,6 @@ impl Asset for HumArmorFootSpec { 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 HumArmorLanternSpec { const ENDINGS: &'static [&'static str] = &["ron"]; @@ -536,6 +536,40 @@ 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 { @@ -659,57 +693,6 @@ impl HumMainWeaponSpec { generate_mesh(&tool_kind_segment, Vec3::from(spec.vox_spec.1)) } } -// Back -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-armor specification exists for {:?}", back); - 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_back = graceful_load_mat_segment("armor.empty"); - - let mut back_armor = graceful_load_mat_segment(&spec.vox_spec.0); - - if let Some(color) = spec.color { - let back_color = Vec3::from(color); - back_armor = back_armor.map_rgb(|rgb| recolor_grey(rgb, Rgb::from(back_color))); - } - - let back = DynaUnionizer::new() - .add(color(bare_back), Vec3::new(0, 0, 0)) - .add(color(back_armor), Vec3::new(0, 0, 0)) - .unify() - .0; - - generate_mesh(&back, Vec3::from(spec.vox_spec.1)) - } -} // Lantern impl HumArmorLanternSpec { pub fn load_watched(indicator: &mut ReloadIndicator) -> Arc { From 156b554023fd5cbb666d1f6a1fb8a4a847eb8064 Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Tue, 7 Apr 2020 19:21:47 +0200 Subject: [PATCH 028/195] item types --- assets/common/items/armor/back/admin.ron | 9 ++++ .../common/items/armor/head/assa_mask_0.ron | 8 ++++ assets/common/items/armor/head/leather_0.ron | 8 ++++ assets/common/items/armor/lantern/black_0.ron | 8 ++++ assets/common/items/armor/lantern/green_0.ron | 8 ++++ assets/common/items/armor/neck/neck_0.ron | 8 ++++ assets/common/items/armor/ring/ring_0.ron | 8 ++++ assets/common/items/armor/tabard/admin.ron | 9 ++++ assets/voxygen/item_image_manifest.ron | 43 +++++++++++++++++++ assets/voxygen/voxel/armor/back/admin.vox | 3 ++ .../head/{assa_mask.vox => assa_mask-0.vox} | 0 assets/voxygen/voxel/armor/head/leather-0.vox | 3 ++ .../voxygen/voxel/armor/lantern/leather-0.vox | 3 ++ assets/voxygen/voxel/armor/neck/neck-0.vox | 3 ++ assets/voxygen/voxel/armor/ring/ring-0.vox | 3 ++ .../voxygen/voxel/armor/tabard/tabard-0.vox | 3 ++ .../voxel/humanoid_armor_back_manifest.ron | 4 ++ .../voxel/humanoid_armor_head_manifest.ron | 12 ------ .../voxel/humanoid_armor_lantern_manifest.ron | 6 ++- .../voxel/humanoid_armor_neck_manifest.ron | 12 ------ .../voxel/humanoid_armor_ring_manifest.ron | 12 ------ .../voxel/humanoid_armor_tabard_manifest.ron | 12 ------ common/src/comp/inventory/item/armor.rs | 13 +++--- 23 files changed, 144 insertions(+), 54 deletions(-) create mode 100644 assets/common/items/armor/back/admin.ron create mode 100644 assets/common/items/armor/head/assa_mask_0.ron create mode 100644 assets/common/items/armor/head/leather_0.ron create mode 100644 assets/common/items/armor/lantern/black_0.ron create mode 100644 assets/common/items/armor/lantern/green_0.ron create mode 100644 assets/common/items/armor/neck/neck_0.ron create mode 100644 assets/common/items/armor/ring/ring_0.ron create mode 100644 assets/common/items/armor/tabard/admin.ron create mode 100644 assets/voxygen/voxel/armor/back/admin.vox rename assets/voxygen/voxel/armor/head/{assa_mask.vox => assa_mask-0.vox} (100%) create mode 100644 assets/voxygen/voxel/armor/head/leather-0.vox create mode 100644 assets/voxygen/voxel/armor/lantern/leather-0.vox create mode 100644 assets/voxygen/voxel/armor/neck/neck-0.vox create mode 100644 assets/voxygen/voxel/armor/ring/ring-0.vox create mode 100644 assets/voxygen/voxel/armor/tabard/tabard-0.vox delete mode 100644 assets/voxygen/voxel/humanoid_armor_head_manifest.ron delete mode 100644 assets/voxygen/voxel/humanoid_armor_neck_manifest.ron delete mode 100644 assets/voxygen/voxel/humanoid_armor_ring_manifest.ron delete mode 100644 assets/voxygen/voxel/humanoid_armor_tabard_manifest.ron diff --git a/assets/common/items/armor/back/admin.ron b/assets/common/items/armor/back/admin.ron new file mode 100644 index 0000000000..fc8f427533 --- /dev/null +++ b/assets/common/items/armor/back/admin.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/armor/head/assa_mask_0.ron b/assets/common/items/armor/head/assa_mask_0.ron new file mode 100644 index 0000000000..e30bde1424 --- /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(Leather0), + 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/lantern/black_0.ron b/assets/common/items/armor/lantern/black_0.ron new file mode 100644 index 0000000000..d10d4150d9 --- /dev/null +++ b/assets/common/items/armor/lantern/black_0.ron @@ -0,0 +1,8 @@ +Item( + name: "Black Lantern", + description: "WIP", + kind: Armor( + kind: Lantern(Black0), + stats: (20), + ), +) diff --git a/assets/common/items/armor/lantern/green_0.ron b/assets/common/items/armor/lantern/green_0.ron new file mode 100644 index 0000000000..c37daf9b99 --- /dev/null +++ b/assets/common/items/armor/lantern/green_0.ron @@ -0,0 +1,8 @@ +Item( + name: "Lime Zest Lantern", + description: "It has an opening that could fit a ring...", + kind: Armor( + kind: Lantern(Green0), + 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..4dbd32ae6f --- /dev/null +++ b/assets/common/items/armor/neck/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/armor/ring/ring_0.ron b/assets/common/items/armor/ring/ring_0.ron new file mode 100644 index 0000000000..8544765246 --- /dev/null +++ b/assets/common/items/armor/ring/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/armor/tabard/admin.ron b/assets/common/items/armor/tabard/admin.ron new file mode 100644 index 0000000000..9ad427f3ed --- /dev/null +++ b/assets/common/items/armor/tabard/admin.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/voxygen/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index f9c40cf4ec..fcf2045e59 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -222,6 +222,49 @@ "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)): VoxTrans( + "voxel.armor.ring.ring-0", + (0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.0, + ), + // Necks + Armor(Neck(Neck0)): VoxTrans( + "voxel.armor.neck.neck-0", + (0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.0, + ), + + // Lanterns + Armor(Lantern(Black0)): VoxTrans( + "voxel.armor.lantern.lantern_black-0", + (0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.0, + ), + Armor(Lantern(Green0)): VoxTrans( + "voxel.armor.lantern.lantern_green-0", + (0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.0, + ), + // Tabards + Armor(Tabard(Tabard0)): VoxTrans( + "voxel.armor.tabard.tabard-0", + (0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.0, + ), + // 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( "element.icons.item_apple", diff --git a/assets/voxygen/voxel/armor/back/admin.vox b/assets/voxygen/voxel/armor/back/admin.vox new file mode 100644 index 0000000000..783f50d687 --- /dev/null +++ b/assets/voxygen/voxel/armor/back/admin.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9458d297c50bba40dec868ae542fd66b17f3b099173f076413e856360441d4c +size 55979 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..e9a2e55758 --- /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:9b2e1e7b2dc1fbf34d262567d38aa55a4e71c690516d4d895a240b47786c709e +size 1472 diff --git a/assets/voxygen/voxel/armor/lantern/leather-0.vox b/assets/voxygen/voxel/armor/lantern/leather-0.vox new file mode 100644 index 0000000000..e9a2e55758 --- /dev/null +++ b/assets/voxygen/voxel/armor/lantern/leather-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b2e1e7b2dc1fbf34d262567d38aa55a4e71c690516d4d895a240b47786c709e +size 1472 diff --git a/assets/voxygen/voxel/armor/neck/neck-0.vox b/assets/voxygen/voxel/armor/neck/neck-0.vox new file mode 100644 index 0000000000..e9a2e55758 --- /dev/null +++ b/assets/voxygen/voxel/armor/neck/neck-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b2e1e7b2dc1fbf34d262567d38aa55a4e71c690516d4d895a240b47786c709e +size 1472 diff --git a/assets/voxygen/voxel/armor/ring/ring-0.vox b/assets/voxygen/voxel/armor/ring/ring-0.vox new file mode 100644 index 0000000000..e9a2e55758 --- /dev/null +++ b/assets/voxygen/voxel/armor/ring/ring-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b2e1e7b2dc1fbf34d262567d38aa55a4e71c690516d4d895a240b47786c709e +size 1472 diff --git a/assets/voxygen/voxel/armor/tabard/tabard-0.vox b/assets/voxygen/voxel/armor/tabard/tabard-0.vox new file mode 100644 index 0000000000..e9a2e55758 --- /dev/null +++ b/assets/voxygen/voxel/armor/tabard/tabard-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b2e1e7b2dc1fbf34d262567d38aa55a4e71c690516d4d895a240b47786c709e +size 1472 diff --git a/assets/voxygen/voxel/humanoid_armor_back_manifest.ron b/assets/voxygen/voxel/humanoid_armor_back_manifest.ron index 6601f0e9ab..5165820bc9 100644 --- a/assets/voxygen/voxel/humanoid_armor_back_manifest.ron +++ b/assets/voxygen/voxel/humanoid_armor_back_manifest.ron @@ -7,6 +7,10 @@ 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_head_manifest.ron b/assets/voxygen/voxel/humanoid_armor_head_manifest.ron deleted file mode 100644 index 0e367e48af..0000000000 --- a/assets/voxygen/voxel/humanoid_armor_head_manifest.ron +++ /dev/null @@ -1,12 +0,0 @@ -(( - default: ( - vox_spec: ("armor.empty", (0.0, 0.0, 0.0)), - color: None - ), - map: { - Neck0: ( - vox_spec: ("armor.empty", (0.0, 0.0, 0.0)), - color: None - ), - }, -)) diff --git a/assets/voxygen/voxel/humanoid_armor_lantern_manifest.ron b/assets/voxygen/voxel/humanoid_armor_lantern_manifest.ron index 7ab66ff84a..8f4a831912 100644 --- a/assets/voxygen/voxel/humanoid_armor_lantern_manifest.ron +++ b/assets/voxygen/voxel/humanoid_armor_lantern_manifest.ron @@ -5,7 +5,11 @@ ), map: { Green0: ( - vox_spec: ("armor.empty", (0.0, 0.0, 0.0)), + vox_spec: ("armor.lantern.green-0", (0.0, 0.0, 0.0)), + color: None + ), + Black0: ( + vox_spec: ("armor.lantern.black-0", (0.0, 0.0, 0.0)), color: None ), }, diff --git a/assets/voxygen/voxel/humanoid_armor_neck_manifest.ron b/assets/voxygen/voxel/humanoid_armor_neck_manifest.ron deleted file mode 100644 index 0e367e48af..0000000000 --- a/assets/voxygen/voxel/humanoid_armor_neck_manifest.ron +++ /dev/null @@ -1,12 +0,0 @@ -(( - default: ( - vox_spec: ("armor.empty", (0.0, 0.0, 0.0)), - color: None - ), - map: { - Neck0: ( - vox_spec: ("armor.empty", (0.0, 0.0, 0.0)), - color: None - ), - }, -)) diff --git a/assets/voxygen/voxel/humanoid_armor_ring_manifest.ron b/assets/voxygen/voxel/humanoid_armor_ring_manifest.ron deleted file mode 100644 index 6dc285312c..0000000000 --- a/assets/voxygen/voxel/humanoid_armor_ring_manifest.ron +++ /dev/null @@ -1,12 +0,0 @@ -(( - default: ( - vox_spec: ("armor.empty", (0.0, 0.0, 0.0)), - color: None - ), - map: { - Ring0: ( - vox_spec: ("armor.empty", (0.0, 0.0, 0.0)), - color: None - ), - }, -)) diff --git a/assets/voxygen/voxel/humanoid_armor_tabard_manifest.ron b/assets/voxygen/voxel/humanoid_armor_tabard_manifest.ron deleted file mode 100644 index 0e367e48af..0000000000 --- a/assets/voxygen/voxel/humanoid_armor_tabard_manifest.ron +++ /dev/null @@ -1,12 +0,0 @@ -(( - default: ( - vox_spec: ("armor.empty", (0.0, 0.0, 0.0)), - color: None - ), - map: { - Neck0: ( - vox_spec: ("armor.empty", (0.0, 0.0, 0.0)), - color: None - ), - }, -)) diff --git a/common/src/comp/inventory/item/armor.rs b/common/src/comp/inventory/item/armor.rs index 002ecb2789..233ad4a2f1 100644 --- a/common/src/comp/inventory/item/armor.rs +++ b/common/src/comp/inventory/item/armor.rs @@ -167,8 +167,9 @@ pub const ALL_SHOULDERS: [Shoulder; 9] = [ #[repr(u32)] pub enum Back { Short0 = 1, + Admin = 2, } -pub const ALL_BACKS: [Back; 1] = [Back::Short0]; +pub const ALL_BACKS: [Back; 2] = [Back::Short0, Back::Admin]; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(u32)] pub enum Ring { @@ -184,15 +185,17 @@ pub const ALL_NECKS: [Neck; 1] = [Neck::Neck0]; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(u32)] pub enum Lantern { - Lantern0 = 1, + Black0 = 1, + Green0 = 2, } -pub const ALL_LANTERNS: [Lantern; 1] = [Lantern::Lantern0]; +pub const ALL_LANTERNS: [Lantern; 2] = [Lantern::Black0, Lantern::Green0]; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(u32)] pub enum Head { - Head0 = 1, + Leather0 = 1, + AssaMask0 = 2, } -pub const ALL_HEADS: [Head; 1] = [Head::Head0]; +pub const ALL_HEADS: [Head; 2] = [Head::Leather0, Head::AssaMask0]; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(u32)] pub enum Tabard { From ca667fd02b87f721baedf6d4c4d0bb221fe301bb Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Tue, 7 Apr 2020 20:12:51 +0200 Subject: [PATCH 029/195] tooltips --- assets/common/items/debug/admin_back.ron | 9 ++++++ assets/common/items/debug/admin_tabard.ron | 9 ++++++ assets/common/items/debug/green_0.ron | 8 +++++ assets/common/items/debug/leather_0.ron | 8 +++++ assets/common/items/debug/neck_0.ron | 8 +++++ assets/common/items/debug/plate_belt.ron | 8 +++++ assets/common/items/debug/plate_chest.ron | 8 +++++ assets/common/items/debug/plate_feet.ron | 8 +++++ assets/common/items/debug/plate_hands.ron | 8 +++++ assets/common/items/debug/plate_legs.ron | 8 +++++ assets/common/items/debug/plate_shoulder.ron | 8 +++++ assets/common/items/debug/ring_0.ron | 8 +++++ assets/voxygen/item_image_manifest.ron | 2 +- common/src/comp/inventory/item/armor.rs | 4 +-- server/src/events/inventory_manip.rs | 10 +++--- voxygen/src/hud/bag.rs | 32 +++++++++++++++----- 16 files changed, 131 insertions(+), 15 deletions(-) create mode 100644 assets/common/items/debug/admin_back.ron create mode 100644 assets/common/items/debug/admin_tabard.ron create mode 100644 assets/common/items/debug/green_0.ron create mode 100644 assets/common/items/debug/leather_0.ron create mode 100644 assets/common/items/debug/neck_0.ron create mode 100644 assets/common/items/debug/plate_belt.ron create mode 100644 assets/common/items/debug/plate_chest.ron create mode 100644 assets/common/items/debug/plate_feet.ron create mode 100644 assets/common/items/debug/plate_hands.ron create mode 100644 assets/common/items/debug/plate_legs.ron create mode 100644 assets/common/items/debug/plate_shoulder.ron create mode 100644 assets/common/items/debug/ring_0.ron 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..c37daf9b99 --- /dev/null +++ b/assets/common/items/debug/green_0.ron @@ -0,0 +1,8 @@ +Item( + name: "Lime Zest Lantern", + description: "It has an opening that could fit a ring...", + kind: Armor( + kind: Lantern(Green0), + stats: (20), + ), +) 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/voxygen/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index fcf2045e59..3811169c64 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -252,7 +252,7 @@ (0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.0, ), // Tabards - Armor(Tabard(Tabard0)): VoxTrans( + Armor(Tabard(Admin)): VoxTrans( "voxel.armor.tabard.tabard-0", (0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.0, ), diff --git a/common/src/comp/inventory/item/armor.rs b/common/src/comp/inventory/item/armor.rs index 233ad4a2f1..07516175f9 100644 --- a/common/src/comp/inventory/item/armor.rs +++ b/common/src/comp/inventory/item/armor.rs @@ -199,9 +199,9 @@ pub const ALL_HEADS: [Head; 2] = [Head::Leather0, Head::AssaMask0]; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(u32)] pub enum Tabard { - Tabard0 = 1, + Admin = 1, } -pub const ALL_TABARDS: [Tabard; 1] = [Tabard::Tabard0]; +pub const ALL_TABARDS: [Tabard; 1] = [Tabard::Admin]; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum Armor { diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index 54fe6554bd..7174c15e64 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -139,11 +139,11 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv Pants(_) => &mut loadout.pants, Foot(_) => &mut loadout.foot, Back(_) => &mut loadout.back, - Ring(_) => &mut loadout.back, - Neck(_) => &mut loadout.back, - Lantern(_) => &mut loadout.back, - Head(_) => &mut loadout.back, - Tabard(_) => &mut loadout.back, + Ring(_) => &mut loadout.ring, + Neck(_) => &mut loadout.neck, + Lantern(_) => &mut loadout.lantern, + Head(_) => &mut loadout.head, + Tabard(_) => &mut loadout.tabard, }; // Insert old item into inventory diff --git a/voxygen/src/hud/bag.rs b/voxygen/src/hud/bag.rs index e166c8a166..3fb33df9ac 100644 --- a/voxygen/src/hud/bag.rs +++ b/voxygen/src/hud/bag.rs @@ -333,7 +333,10 @@ impl<'a> Widget for Bag<'a> { slot_manager: Some(self.slot_manager), }; // Head - let (title, desc) = ("Helmet", ""); + let (title, desc) = loadout + .head + .as_ref() + .map_or(("Head", ""), |item| (item.name(), item.description())); slot_maker .fabricate(ArmorSlot::Head, [45.0; 2]) .mid_top_with_margin_on(state.ids.bg_frame, 60.0) @@ -341,7 +344,10 @@ impl<'a> Widget for Bag<'a> { .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) .set(state.ids.head_slot, ui); // Necklace - let (title, desc) = ("Neck", ""); + let (title, desc) = loadout + .neck + .as_ref() + .map_or(("Neck", ""), |item| (item.name(), item.description())); slot_maker .fabricate(ArmorSlot::Neck, [45.0; 2]) .mid_bottom_with_margin_on(state.ids.head_slot, -55.0) @@ -395,7 +401,7 @@ impl<'a> Widget for Bag<'a> { .set(state.ids.belt_slot, ui); // Legs let (title, desc) = loadout - .belt + .pants .as_ref() .map_or(("Legs", ""), |item| (item.name(), item.description())); slot_maker @@ -405,7 +411,10 @@ impl<'a> Widget for Bag<'a> { .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) .set(state.ids.legs_slot, ui); // Lantern - let (title, desc) = ("Lantern", ""); + let (title, desc) = loadout + .lantern + .as_ref() + .map_or(("Lantern", ""), |item| (item.name(), item.description())); slot_maker .fabricate(ArmorSlot::Lantern, [45.0; 2]) .bottom_right_with_margins_on(state.ids.shoulders_slot, -55.0, 0.0) @@ -413,7 +422,10 @@ impl<'a> Widget for Bag<'a> { .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) .set(state.ids.lantern_slot, ui); // Ring - let (title, desc) = ("Ring", ""); + let (title, desc) = loadout + .ring + .as_ref() + .map_or(("Ring", ""), |item| (item.name(), item.description())); slot_maker .fabricate(ArmorSlot::Ring, [45.0; 2]) .bottom_left_with_margins_on(state.ids.hands_slot, -55.0, 0.0) @@ -421,7 +433,10 @@ impl<'a> Widget for Bag<'a> { .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) .set(state.ids.ring_slot, ui); // Back - let (title, desc) = ("Back", ""); + let (title, desc) = loadout + .back + .as_ref() + .map_or(("Back", ""), |item| (item.name(), item.description())); slot_maker .fabricate(ArmorSlot::Back, [45.0; 2]) .down_from(state.ids.lantern_slot, 10.0) @@ -440,7 +455,10 @@ impl<'a> Widget for Bag<'a> { .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) .set(state.ids.feet_slot, ui); // Tabard - let (title, desc) = ("Tabard", ""); + let (title, desc) = loadout + .tabard + .as_ref() + .map_or(("Tabard", ""), |item| (item.name(), item.description())); slot_maker .fabricate(ArmorSlot::Tabard, [70.0; 2]) .top_right_with_margins_on(state.ids.bg_frame, 80.5, 53.0) From 039f7f9f14a24723137bd621a79765af5b16cfca Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Tue, 7 Apr 2020 20:38:18 +0200 Subject: [PATCH 030/195] item images --- assets/voxygen/item_image_manifest.ron | 2 +- assets/voxygen/voxel/armor/back/admin.vox | 4 ++-- assets/voxygen/voxel/armor/lantern/lantern_green-0.vox | 3 +++ assets/voxygen/voxel/armor/lantern/lantern_grey-0.vox | 3 +++ assets/voxygen/voxel/armor/neck/neck-0.vox | 4 ++-- assets/voxygen/voxel/armor/ring/ring-0.vox | 4 ++-- assets/voxygen/voxel/armor/tabard/admin.vox | 3 +++ assets/voxygen/voxel/armor/tabard/tabard-0.vox | 3 --- 8 files changed, 16 insertions(+), 10 deletions(-) create mode 100644 assets/voxygen/voxel/armor/lantern/lantern_green-0.vox create mode 100644 assets/voxygen/voxel/armor/lantern/lantern_grey-0.vox create mode 100644 assets/voxygen/voxel/armor/tabard/admin.vox delete mode 100644 assets/voxygen/voxel/armor/tabard/tabard-0.vox diff --git a/assets/voxygen/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index 3811169c64..555e9e2183 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -253,7 +253,7 @@ ), // Tabards Armor(Tabard(Admin)): VoxTrans( - "voxel.armor.tabard.tabard-0", + "voxel.armor.tabard.admin", (0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.0, ), // Heads diff --git a/assets/voxygen/voxel/armor/back/admin.vox b/assets/voxygen/voxel/armor/back/admin.vox index 783f50d687..1822352b1c 100644 --- a/assets/voxygen/voxel/armor/back/admin.vox +++ b/assets/voxygen/voxel/armor/back/admin.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f9458d297c50bba40dec868ae542fd66b17f3b099173f076413e856360441d4c -size 55979 +oid sha256:7e9495cf6616f1aaf426d7762e09a75f43a07bf08329f91e029eb8e5d2b20f67 +size 1496 diff --git a/assets/voxygen/voxel/armor/lantern/lantern_green-0.vox b/assets/voxygen/voxel/armor/lantern/lantern_green-0.vox new file mode 100644 index 0000000000..001dafb7fb --- /dev/null +++ b/assets/voxygen/voxel/armor/lantern/lantern_green-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c9175bfc879e5a4b58a24a33b73c33078f0e3d96c72078666327ab1f9614acbf +size 3296 diff --git a/assets/voxygen/voxel/armor/lantern/lantern_grey-0.vox b/assets/voxygen/voxel/armor/lantern/lantern_grey-0.vox new file mode 100644 index 0000000000..dd8143b260 --- /dev/null +++ b/assets/voxygen/voxel/armor/lantern/lantern_grey-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f78ef091f014dadde93e01bfb60d8a70099549d7e37dfcb7f802dcd86e6ed8f3 +size 3288 diff --git a/assets/voxygen/voxel/armor/neck/neck-0.vox b/assets/voxygen/voxel/armor/neck/neck-0.vox index e9a2e55758..de9bdb394e 100644 --- a/assets/voxygen/voxel/armor/neck/neck-0.vox +++ b/assets/voxygen/voxel/armor/neck/neck-0.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b2e1e7b2dc1fbf34d262567d38aa55a4e71c690516d4d895a240b47786c709e -size 1472 +oid sha256:93e31a01dd678d0f68c58b8ccae280ab92990b99f5c7e083de6439082a61b17a +size 1552 diff --git a/assets/voxygen/voxel/armor/ring/ring-0.vox b/assets/voxygen/voxel/armor/ring/ring-0.vox index e9a2e55758..36ef575794 100644 --- a/assets/voxygen/voxel/armor/ring/ring-0.vox +++ b/assets/voxygen/voxel/armor/ring/ring-0.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b2e1e7b2dc1fbf34d262567d38aa55a4e71c690516d4d895a240b47786c709e -size 1472 +oid sha256:e8acd6abf30f6bf0405bcf9b804bbf554e6b237f967a868ad2da6079df3f0524 +size 2232 diff --git a/assets/voxygen/voxel/armor/tabard/admin.vox b/assets/voxygen/voxel/armor/tabard/admin.vox new file mode 100644 index 0000000000..71d1b4a657 --- /dev/null +++ b/assets/voxygen/voxel/armor/tabard/admin.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a752bff2296228bf89ff3f65f2011001c291a29f95db91564a24d9e3648f92b6 +size 1816 diff --git a/assets/voxygen/voxel/armor/tabard/tabard-0.vox b/assets/voxygen/voxel/armor/tabard/tabard-0.vox deleted file mode 100644 index e9a2e55758..0000000000 --- a/assets/voxygen/voxel/armor/tabard/tabard-0.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9b2e1e7b2dc1fbf34d262567d38aa55a4e71c690516d4d895a240b47786c709e -size 1472 From d5128b751d5addeb01e7d41a62e51cc079cbe041 Mon Sep 17 00:00:00 2001 From: Pfauenauge Date: Wed, 8 Apr 2020 00:12:44 +0200 Subject: [PATCH 031/195] png item images, fix map cursor --- .../common/items/armor/head/assa_mask_0.ron | 2 +- .../voxygen/element/icons/head_leather-0.png | 3 ++ .../voxygen/element/icons/lantern_black-0.png | 3 ++ .../voxygen/element/icons/lantern_green-0.png | 3 ++ .../voxygen/element/icons/lantern_grey-0.png | 3 ++ assets/voxygen/element/icons/neck-0.png | 3 ++ assets/voxygen/element/icons/ring-0.png | 3 ++ assets/voxygen/element/icons/tabard_admin.png | 3 ++ assets/voxygen/item_image_manifest.ron | 31 +++++++------------ assets/voxygen/voxel/armor/back/admin.vox | 2 +- assets/voxygen/voxel/armor/head/leather-0.vox | 3 -- .../voxel/armor/lantern/lantern_black-0.vox | 3 ++ .../voxel/armor/lantern/lantern_green-0.vox | 4 +-- .../voxel/armor/lantern/lantern_grey-0.vox | 3 -- .../voxygen/voxel/armor/lantern/leather-0.vox | 3 -- assets/voxygen/voxel/armor/neck/neck-0.vox | 3 -- assets/voxygen/voxel/armor/ring/ring-0.vox | 3 -- assets/voxygen/voxel/armor/tabard/admin.vox | 3 -- voxygen/src/hud/map.rs | 13 +++----- 19 files changed, 45 insertions(+), 49 deletions(-) create mode 100644 assets/voxygen/element/icons/head_leather-0.png create mode 100644 assets/voxygen/element/icons/lantern_black-0.png create mode 100644 assets/voxygen/element/icons/lantern_green-0.png create mode 100644 assets/voxygen/element/icons/lantern_grey-0.png create mode 100644 assets/voxygen/element/icons/neck-0.png create mode 100644 assets/voxygen/element/icons/ring-0.png create mode 100644 assets/voxygen/element/icons/tabard_admin.png delete mode 100644 assets/voxygen/voxel/armor/head/leather-0.vox create mode 100644 assets/voxygen/voxel/armor/lantern/lantern_black-0.vox delete mode 100644 assets/voxygen/voxel/armor/lantern/lantern_grey-0.vox delete mode 100644 assets/voxygen/voxel/armor/lantern/leather-0.vox delete mode 100644 assets/voxygen/voxel/armor/neck/neck-0.vox delete mode 100644 assets/voxygen/voxel/armor/ring/ring-0.vox delete mode 100644 assets/voxygen/voxel/armor/tabard/admin.vox diff --git a/assets/common/items/armor/head/assa_mask_0.ron b/assets/common/items/armor/head/assa_mask_0.ron index e30bde1424..34f242c778 100644 --- a/assets/common/items/armor/head/assa_mask_0.ron +++ b/assets/common/items/armor/head/assa_mask_0.ron @@ -2,7 +2,7 @@ Item( name: "Dark Assassin Mask", description: "WIP", kind: Armor( - kind: Head(Leather0), + kind: Head(AssaMask0), stats: (20), ), ) 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/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..9d1a68eff6 --- /dev/null +++ b/assets/voxygen/element/icons/tabard_admin.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6681d47e5186f70dcb6c30c32b2134a348f2cbb2fafb9f3cfe00d45ef29d9b32 +size 572 diff --git a/assets/voxygen/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index 555e9e2183..f7aa8bc366 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -232,34 +232,27 @@ (0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.0, ), // Rings - Armor(Ring(Ring0)): VoxTrans( - "voxel.armor.ring.ring-0", - (0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.0, + Armor(Ring(Ring0)): Png( + "element.icons.ring-0", ), // Necks - Armor(Neck(Neck0)): VoxTrans( - "voxel.armor.neck.neck-0", - (0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.0, + Armor(Neck(Neck0)): Png( + "element.icons.neck-0", ), - // Lanterns - Armor(Lantern(Black0)): VoxTrans( - "voxel.armor.lantern.lantern_black-0", - (0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.0, + Armor(Lantern(Black0)): Png( + "voxel.element.icons.lantern_grey-0", ), - Armor(Lantern(Green0)): VoxTrans( - "voxel.armor.lantern.lantern_green-0", - (0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.0, + Armor(Lantern(Green0)): Png( + "element.icons.lantern_green-0", ), // Tabards - Armor(Tabard(Admin)): VoxTrans( - "voxel.armor.tabard.admin", - (0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.0, + 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(Leather0)): Png( + "element.icons.head_leather-0", ), Armor(Head(AssaMask0)): VoxTrans( "voxel.armor.head.assa_mask-0", diff --git a/assets/voxygen/voxel/armor/back/admin.vox b/assets/voxygen/voxel/armor/back/admin.vox index 1822352b1c..3f11d5f1da 100644 --- a/assets/voxygen/voxel/armor/back/admin.vox +++ b/assets/voxygen/voxel/armor/back/admin.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7e9495cf6616f1aaf426d7762e09a75f43a07bf08329f91e029eb8e5d2b20f67 +oid sha256:37ccc4f22a88b5cf3a4ad9017297458d2444d0eb5f71c0505e90907e19b1dc53 size 1496 diff --git a/assets/voxygen/voxel/armor/head/leather-0.vox b/assets/voxygen/voxel/armor/head/leather-0.vox deleted file mode 100644 index e9a2e55758..0000000000 --- a/assets/voxygen/voxel/armor/head/leather-0.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9b2e1e7b2dc1fbf34d262567d38aa55a4e71c690516d4d895a240b47786c709e -size 1472 diff --git a/assets/voxygen/voxel/armor/lantern/lantern_black-0.vox b/assets/voxygen/voxel/armor/lantern/lantern_black-0.vox new file mode 100644 index 0000000000..2251bf2726 --- /dev/null +++ b/assets/voxygen/voxel/armor/lantern/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/armor/lantern/lantern_green-0.vox b/assets/voxygen/voxel/armor/lantern/lantern_green-0.vox index 001dafb7fb..a3ab8e8da7 100644 --- a/assets/voxygen/voxel/armor/lantern/lantern_green-0.vox +++ b/assets/voxygen/voxel/armor/lantern/lantern_green-0.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c9175bfc879e5a4b58a24a33b73c33078f0e3d96c72078666327ab1f9614acbf -size 3296 +oid sha256:e218a41ce27b3dd7af39c67d657881f304d39a5cda0fffe1f0958222a35d577b +size 44999 diff --git a/assets/voxygen/voxel/armor/lantern/lantern_grey-0.vox b/assets/voxygen/voxel/armor/lantern/lantern_grey-0.vox deleted file mode 100644 index dd8143b260..0000000000 --- a/assets/voxygen/voxel/armor/lantern/lantern_grey-0.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f78ef091f014dadde93e01bfb60d8a70099549d7e37dfcb7f802dcd86e6ed8f3 -size 3288 diff --git a/assets/voxygen/voxel/armor/lantern/leather-0.vox b/assets/voxygen/voxel/armor/lantern/leather-0.vox deleted file mode 100644 index e9a2e55758..0000000000 --- a/assets/voxygen/voxel/armor/lantern/leather-0.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9b2e1e7b2dc1fbf34d262567d38aa55a4e71c690516d4d895a240b47786c709e -size 1472 diff --git a/assets/voxygen/voxel/armor/neck/neck-0.vox b/assets/voxygen/voxel/armor/neck/neck-0.vox deleted file mode 100644 index de9bdb394e..0000000000 --- a/assets/voxygen/voxel/armor/neck/neck-0.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:93e31a01dd678d0f68c58b8ccae280ab92990b99f5c7e083de6439082a61b17a -size 1552 diff --git a/assets/voxygen/voxel/armor/ring/ring-0.vox b/assets/voxygen/voxel/armor/ring/ring-0.vox deleted file mode 100644 index 36ef575794..0000000000 --- a/assets/voxygen/voxel/armor/ring/ring-0.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e8acd6abf30f6bf0405bcf9b804bbf554e6b237f967a868ad2da6079df3f0524 -size 2232 diff --git a/assets/voxygen/voxel/armor/tabard/admin.vox b/assets/voxygen/voxel/armor/tabard/admin.vox deleted file mode 100644 index 71d1b4a657..0000000000 --- a/assets/voxygen/voxel/armor/tabard/admin.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a752bff2296228bf89ff3f65f2011001c291a29f95db91564a24d9e3648f92b6 -size 1816 diff --git a/voxygen/src/hud/map.rs b/voxygen/src/hud/map.rs index fcb29999d7..4b2f390d9b 100644 --- a/voxygen/src/hud/map.rs +++ b/voxygen/src/hud/map.rs @@ -13,13 +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 { @@ -201,12 +196,14 @@ impl<'a> Widget for Map<'a> { 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; + let x_rel = player_pos.x as f64 / worldsize.x; + let y_rel = player_pos.y as f64 / worldsize.y; + let indic_scale = 0.6; 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, + if y_rel > 0.0 && y_rel < 1.0 { y - 37.0 * indic_scale / 2.0 } else { 760.0 - 37.0 * indic_scale / 2.0 }, + if x_rel > 0.0 && x_rel < 1.0 { x - 32.0 * indic_scale / 2.0 } else { 760.0 - 32.0 * indic_scale / 2.0 }, ) .w_h(32.0 * indic_scale, 37.0 * indic_scale) .color(Some(UI_HIGHLIGHT_0)) From c6eda08679bfa7bde104b26bf574143c8101ce81 Mon Sep 17 00:00:00 2001 From: Pfauenauge Date: Wed, 8 Apr 2020 00:17:22 +0200 Subject: [PATCH 032/195] fmt --- voxygen/src/hud/map.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/voxygen/src/hud/map.rs b/voxygen/src/hud/map.rs index 4b2f390d9b..69a4efb66a 100644 --- a/voxygen/src/hud/map.rs +++ b/voxygen/src/hud/map.rs @@ -198,12 +198,20 @@ impl<'a> Widget for Map<'a> { let y = player_pos.y as f64 / worldsize.y * 760.0; let x_rel = player_pos.x as f64 / worldsize.x; let y_rel = player_pos.y as f64 / worldsize.y; - let indic_scale = 0.6; + let indic_scale = 0.6; Image::new(self.rot_imgs.indicator_mmap_small.target_north) .bottom_left_with_margins_on( state.ids.grid, - if y_rel > 0.0 && y_rel < 1.0 { y - 37.0 * indic_scale / 2.0 } else { 760.0 - 37.0 * indic_scale / 2.0 }, - if x_rel > 0.0 && x_rel < 1.0 { x - 32.0 * indic_scale / 2.0 } else { 760.0 - 32.0 * indic_scale / 2.0 }, + if y_rel > 0.0 && y_rel < 1.0 { + y - 37.0 * indic_scale / 2.0 + } else { + 760.0 - 37.0 * indic_scale / 2.0 + }, + if x_rel > 0.0 && x_rel < 1.0 { + x - 32.0 * indic_scale / 2.0 + } else { + 760.0 - 32.0 * indic_scale / 2.0 + }, ) .w_h(32.0 * indic_scale, 37.0 * indic_scale) .color(Some(UI_HIGHLIGHT_0)) From 6dcdd80ec8d2d0d8a5e9ac9d31e680e453345a6e Mon Sep 17 00:00:00 2001 From: Pfauenauge Date: Wed, 8 Apr 2020 01:01:46 +0200 Subject: [PATCH 033/195] smarter code --- assets/voxygen/item_image_manifest.ron | 2 +- voxygen/src/hud/map.rs | 29 ++++++++++---------------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/assets/voxygen/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index f7aa8bc366..78e4f1d861 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -241,7 +241,7 @@ ), // Lanterns Armor(Lantern(Black0)): Png( - "voxel.element.icons.lantern_grey-0", + "element.icons.lantern_black-0", ), Armor(Lantern(Green0)): Png( "element.icons.lantern_green-0", diff --git a/voxygen/src/hud/map.rs b/voxygen/src/hud/map.rs index 69a4efb66a..82c07d7c54 100644 --- a/voxygen/src/hud/map.rs +++ b/voxygen/src/hud/map.rs @@ -15,7 +15,6 @@ use conrod_core::{ }; use specs::WorldExt; use vek::*; - widget_ids! { struct Ids { frame, @@ -193,27 +192,21 @@ 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 x_rel = player_pos.x as f64 / worldsize.x; - let y_rel = player_pos.y as f64 / worldsize.y; - 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, - if y_rel > 0.0 && y_rel < 1.0 { - y - 37.0 * indic_scale / 2.0 - } else { - 760.0 - 37.0 * indic_scale / 2.0 - }, - if x_rel > 0.0 && x_rel < 1.0 { - x - 32.0 * indic_scale / 2.0 - } else { - 760.0 - 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) From 85f9f80024f8aaa5d11f005a3fed7dabd6dae20e Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Wed, 8 Apr 2020 17:38:34 +0200 Subject: [PATCH 034/195] starter equip --- .../items/armor/starter/rugged_pants.ron | 8 +++ .../common/items/armor/starter/sandals_0.ron | 8 +++ assets/voxygen/item_image_manifest.ron | 12 ++++- assets/voxygen/voxel/armor/pants/rugged-0.vox | 3 ++ .../voxel/humanoid_armor_foot_manifest.ron | 10 ++-- .../voxel/humanoid_armor_pants_manifest.ron | 4 ++ common/src/comp/inventory/item/armor.rs | 8 +-- server/src/state_ext.rs | 8 ++- voxygen/src/menu/char_selection/ui.rs | 54 ++++++++++++++----- 9 files changed, 89 insertions(+), 26 deletions(-) create mode 100644 assets/common/items/armor/starter/rugged_pants.ron create mode 100644 assets/common/items/armor/starter/sandals_0.ron create mode 100644 assets/voxygen/voxel/armor/pants/rugged-0.vox 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/voxygen/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index 78e4f1d861..f27302c55f 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -57,9 +57,17 @@ // 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, + ), // Assassin Set Armor(Chest(Assassin)): VoxTrans( "voxel.armor.chest.assa", 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/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/common/src/comp/inventory/item/armor.rs b/common/src/comp/inventory/item/armor.rs index 07516175f9..1ab163268a 100644 --- a/common/src/comp/inventory/item/armor.rs +++ b/common/src/comp/inventory/item/armor.rs @@ -76,8 +76,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 +92,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 +120,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 +131,7 @@ pub enum Foot { } pub const ALL_FEET: [Foot; 9] = [ Foot::Dark, - Foot::Sandal, + Foot::Sandal0, Foot::Jester, Foot::Assassin, Foot::Plate0, diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index b5ee99286d..12b559293b 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -182,8 +182,12 @@ impl StateExt for State { chest: None, 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, diff --git a/voxygen/src/menu/char_selection/ui.rs b/voxygen/src/menu/char_selection/ui.rs index 51c46309d7..ed29098b67 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}, }; @@ -353,8 +354,12 @@ impl CharSelectionUi { chest: None, 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, @@ -364,18 +369,39 @@ impl CharSelectionUi { }; Some(loadout) }, - Mode::Create { loadout, tool, .. } => { - loadout.active_item = tool.map(|tool| comp::ItemConfig { - item: (*load_expect::(tool)).clone(), - ability1: None, - ability2: None, - ability3: None, - block_ability: None, - dodge_ability: None, - }); - Some(loadout.clone()) - }, - } + Mode::Create (characterdata) => + let loadout = comp::Loadout { + active_item: characterdata + .as_ref() + .and_then(|d| d.tool.as_ref()) + .map(|tool| comp::ItemConfig { + item: (*load_expect::(&tool)).clone(), + ability1: None, + ability2: None, + ability3: None, + block_ability: None, + dodge_ability: None, + }), + second_item: None, + shoulder: None, + chest: None, + belt: None, + hand: 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) + }, } // TODO: Split this into multiple modules or functions. From 0fb5b7360690c305f3d2b4829b541ddaa47be9e2 Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Wed, 8 Apr 2020 18:20:49 +0200 Subject: [PATCH 035/195] fix --- voxygen/src/menu/char_selection/ui.rs | 45 +++++++-------------------- 1 file changed, 12 insertions(+), 33 deletions(-) diff --git a/voxygen/src/menu/char_selection/ui.rs b/voxygen/src/menu/char_selection/ui.rs index ed29098b67..db66460906 100644 --- a/voxygen/src/menu/char_selection/ui.rs +++ b/voxygen/src/menu/char_selection/ui.rs @@ -369,39 +369,18 @@ impl CharSelectionUi { }; Some(loadout) }, - Mode::Create (characterdata) => - let loadout = comp::Loadout { - active_item: characterdata - .as_ref() - .and_then(|d| d.tool.as_ref()) - .map(|tool| comp::ItemConfig { - item: (*load_expect::(&tool)).clone(), - ability1: None, - ability2: None, - ability3: None, - block_ability: None, - dodge_ability: None, - }), - second_item: None, - shoulder: None, - chest: None, - belt: None, - hand: 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) - }, + Mode::Create { loadout, tool, .. } => { + loadout.active_item = tool.map(|tool| comp::ItemConfig { + item: (*load_expect::(tool)).clone(), + ability1: None, + ability2: None, + ability3: None, + block_ability: None, + dodge_ability: None, + }); + Some(loadout.clone()) + }, + } } // TODO: Split this into multiple modules or functions. From d62dfffd6913fa02c2d13470002ca6fbae29d2a4 Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Wed, 8 Apr 2020 19:19:15 +0200 Subject: [PATCH 036/195] starter armor in char creation --- voxygen/src/menu/char_selection/ui.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/voxygen/src/menu/char_selection/ui.rs b/voxygen/src/menu/char_selection/ui.rs index db66460906..731b603776 100644 --- a/voxygen/src/menu/char_selection/ui.rs +++ b/voxygen/src/menu/char_selection/ui.rs @@ -378,6 +378,12 @@ impl CharSelectionUi { block_ability: None, dodge_ability: None, }); + 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()) }, } From 9b2785aeae5b60984691c18a324b1d62ce2b9ed2 Mon Sep 17 00:00:00 2001 From: Pfauenauge Date: Wed, 8 Apr 2020 23:22:48 +0200 Subject: [PATCH 037/195] german translation update --- assets/voxygen/i18n/de_DE.ron | 125 +++++++++++++--------------------- 1 file changed, 47 insertions(+), 78 deletions(-) diff --git a/assets/voxygen/i18n/de_DE.ron b/assets/voxygen/i18n/de_DE.ron index d28ae8fc35..b75b403ad7 100644 --- a/assets/voxygen/i18n/de_DE.ron +++ b/assets/voxygen/i18n/de_DE.ron @@ -257,92 +257,61 @@ Viel Spaß in der Welt von Veloren, Abenteurer!"#, "hud.settings.sound_effect_volume": "Geräusch Lautstärke", "hud.settings.audio_device": "Ausgabegerät", - /// 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 + "hud.settings.awaitingkey": "Drückt eine Taste...", - -Move Forward -Move Left -Move Right -Move Backwards - -Jump - -Glider - -Dodge - -Auto Walk - -Sheathe/Draw Weapons - -Put on/Remove Helmet - -Sit - - -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 - - -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.social": "Andere Spieler", + "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": "Online Spieler", + "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": "Freies Umsehen Aktiv", - /// End HUD section + "hud.free_look_indicator": "Freie Sicht aktiv", + + /// End HUD section + /// Start GameInput section + + "gameinput.primary": "Linker mittlerer Slot", + "gameinput.secondary": "Rechter mittlerer Slot", + "gameinput.ability3": "Hotbar Slot 1", + "gameinput.swaploadout": "Waffe wechseln", + "gameinput.togglecursor": "Freie Sicht", + "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": "Zauberbuch", + "gameinput.settings": "Einstellungen", + "gameinput.respawn": "Wiederbeleben", + "gameinput.charge": "Anstürmen", + "gameinput.togglewield": "Waffe ziehen/wegstecken", + "gameinput.interact": "Interagieren", + "gameinput.freelook": "Verhalten der freien Sicht", + + /// End GameInput section /// Start chracter selection section "char_selection.delete_permanently": "Diesen Charakter unwiderruflich löschen?", From 479fcaa603e8418099978f50c623bb8d9c482c5d Mon Sep 17 00:00:00 2001 From: Pfauenauge Date: Wed, 8 Apr 2020 23:57:25 +0200 Subject: [PATCH 038/195] bag tooltips translation entries --- assets/voxygen/i18n/de_DE.ron | 18 +++++++++++++++--- assets/voxygen/i18n/en.ron | 22 ++++++++++++++++++---- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/assets/voxygen/i18n/de_DE.ron b/assets/voxygen/i18n/de_DE.ron index b75b403ad7..6ee6d367f1 100644 --- a/assets/voxygen/i18n/de_DE.ron +++ b/assets/voxygen/i18n/de_DE.ron @@ -202,6 +202,18 @@ Viel Spaß in der Welt von Veloren, Abenteurer!"#, "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.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", @@ -278,7 +290,7 @@ Viel Spaß in der Welt von Veloren, Abenteurer!"#, "gameinput.secondary": "Rechter mittlerer Slot", "gameinput.ability3": "Hotbar Slot 1", "gameinput.swaploadout": "Waffe wechseln", - "gameinput.togglecursor": "Freie Sicht", + "gameinput.togglecursor": "Mauszeiger zeigen/verstecken", "gameinput.help": "Hilfe anzeigen", "gameinput.toggleinterface": "Interface verstecken/zeigen", "gameinput.toggledebug": "FPS und Debug Informationen", @@ -303,13 +315,13 @@ Viel Spaß in der Welt von Veloren, Abenteurer!"#, "gameinput.bag": "Inventar", "gameinput.social": "Sozial", "gameinput.sit": "Sitzen", - "gameinput.spellbook": "Zauberbuch", + "gameinput.spellbook": "Zauber", "gameinput.settings": "Einstellungen", "gameinput.respawn": "Wiederbeleben", "gameinput.charge": "Anstürmen", "gameinput.togglewield": "Waffe ziehen/wegstecken", "gameinput.interact": "Interagieren", - "gameinput.freelook": "Verhalten der freien Sicht", + "gameinput.freelook": "Freies Umsehen", /// End GameInput section diff --git a/assets/voxygen/i18n/en.ron b/assets/voxygen/i18n/en.ron index 368f02145b..18beb7df1c 100644 --- a/assets/voxygen/i18n/en.ron +++ b/assets/voxygen/i18n/en.ron @@ -195,6 +195,19 @@ Enjoy your stay in the World of Veloren."#, "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.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", @@ -261,7 +274,8 @@ Enjoy your stay in the World of Veloren."#, "hud.spell": "Spell", - "hud.free_look_indicator": "Free look active", + "hud.free_look_indicator": "Free look active", + /// End HUD section @@ -271,7 +285,7 @@ Enjoy your stay in the World of Veloren."#, "gameinput.secondary": "Secondary Attack/Block/Aim", "gameinput.ability3": "Hotbar Slot 1", "gameinput.swaploadout": "Swap Loadout", - "gameinput.togglecursor": "Free Cursor", + "gameinput.togglecursor": "Toggle Cursor", "gameinput.help": "Toggle Help Window", "gameinput.toggleinterface": "Toggle Interface", "gameinput.toggledebug": "Toggle FPS and Debug Info", @@ -296,13 +310,13 @@ Enjoy your stay in the World of Veloren."#, "gameinput.bag": "Bag", "gameinput.social": "Social", "gameinput.sit": "Sit", - "gameinput.spellbook": "Spell Book", + "gameinput.spellbook": "Spells", "gameinput.settings": "Settings", "gameinput.respawn": "Respawn", "gameinput.charge": "Charge", "gameinput.togglewield": "Toggle Wield", "gameinput.interact": "Interact", - "gameinput.freelook": "Free look behavior", + "gameinput.freelook": "Free Look", /// End GameInput section From 2263b9be95e0d6ab6c79a4615a3539d52ce4c8f3 Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Thu, 9 Apr 2020 01:23:51 +0200 Subject: [PATCH 039/195] starter chest, removed unused key commands --- .../items/armor/starter/rugged_chest.ron | 8 ++ assets/voxygen/i18n/de_DE.ron | 8 +- assets/voxygen/i18n/en.ron | 4 +- assets/voxygen/item_image_manifest.ron | 4 + assets/voxygen/voxel/armor/chest/rugged-0.vox | 3 + .../voxel/humanoid_armor_chest_manifest.ron | 4 + common/src/comp/inventory/item/armor.rs | 4 +- server/src/state_ext.rs | 4 +- voxygen/src/controller.rs | 12 +-- voxygen/src/hud/bag.rs | 74 ++++++++++++------- voxygen/src/menu/char_selection/ui.rs | 7 +- voxygen/src/session.rs | 8 +- voxygen/src/settings.rs | 8 +- voxygen/src/window.rs | 9 ++- 14 files changed, 103 insertions(+), 54 deletions(-) create mode 100644 assets/common/items/armor/starter/rugged_chest.ron create mode 100644 assets/voxygen/voxel/armor/chest/rugged-0.vox 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/voxygen/i18n/de_DE.ron b/assets/voxygen/i18n/de_DE.ron index 6ee6d367f1..a36aee2c6d 100644 --- a/assets/voxygen/i18n/de_DE.ron +++ b/assets/voxygen/i18n/de_DE.ron @@ -155,9 +155,7 @@ eurer erstellen Charaktere gespeichert."#, "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. - -Drückt Enter und tippt /waypoint in den Chat um den Wegpunkt hier zu erstellen."#, + "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. @@ -209,6 +207,8 @@ Viel Spaß in der Welt von Veloren, Abenteurer!"#, "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", @@ -321,7 +321,7 @@ Viel Spaß in der Welt von Veloren, Abenteurer!"#, "gameinput.charge": "Anstürmen", "gameinput.togglewield": "Waffe ziehen/wegstecken", "gameinput.interact": "Interagieren", - "gameinput.freelook": "Freies Umsehen", + "gameinput.freelook": "Freie Sicht", /// End GameInput section diff --git a/assets/voxygen/i18n/en.ron b/assets/voxygen/i18n/en.ron index 18beb7df1c..b78b7bceda 100644 --- a/assets/voxygen/i18n/en.ron +++ b/assets/voxygen/i18n/en.ron @@ -202,6 +202,8 @@ Enjoy your stay in the World of Veloren."#, "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", @@ -317,7 +319,7 @@ Enjoy your stay in the World of Veloren."#, "gameinput.togglewield": "Toggle Wield", "gameinput.interact": "Interact", "gameinput.freelook": "Free Look", - + /// End GameInput section diff --git a/assets/voxygen/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index f27302c55f..94bf948ef0 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -68,6 +68,10 @@ "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", 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/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/common/src/comp/inventory/item/armor.rs b/common/src/comp/inventory/item/armor.rs index 1ab163268a..b212474273 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)] diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 12b559293b..b5c178a23a 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -179,7 +179,9 @@ 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: Some(assets::load_expect_cloned( 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 3fb33df9ac..a4a21791de 100644 --- a/voxygen/src/hud/bag.rs +++ b/voxygen/src/hud/bag.rs @@ -336,7 +336,9 @@ impl<'a> Widget for Bag<'a> { let (title, desc) = loadout .head .as_ref() - .map_or(("Head", ""), |item| (item.name(), item.description())); + .map_or((self.localized_strings.get("hud.bag.head"), ""), |item| { + (item.name(), item.description()) + }); slot_maker .fabricate(ArmorSlot::Head, [45.0; 2]) .mid_top_with_margin_on(state.ids.bg_frame, 60.0) @@ -347,7 +349,9 @@ impl<'a> Widget for Bag<'a> { let (title, desc) = loadout .neck .as_ref() - .map_or(("Neck", ""), |item| (item.name(), item.description())); + .map_or((self.localized_strings.get("hud.bag.neck"), ""), |item| { + (item.name(), item.description()) + }); slot_maker .fabricate(ArmorSlot::Neck, [45.0; 2]) .mid_bottom_with_margin_on(state.ids.head_slot, -55.0) @@ -359,7 +363,9 @@ impl<'a> Widget for Bag<'a> { let (title, desc) = loadout .chest .as_ref() - .map_or(("Chest", ""), |item| (item.name(), item.description())); + .map_or((self.localized_strings.get("hud.bag.chest"), ""), |item| { + (item.name(), item.description()) + }); slot_maker .fabricate(ArmorSlot::Chest, [85.0; 2]) .mid_bottom_with_margin_on(state.ids.neck_slot, -95.0) @@ -367,10 +373,10 @@ impl<'a> Widget for Bag<'a> { .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(("Shoulders", ""), |item| (item.name(), item.description())); + let (title, desc) = loadout.shoulder.as_ref().map_or( + (self.localized_strings.get("hud.bag.shoulders"), ""), + |item| (item.name(), item.description()), + ); slot_maker .fabricate(ArmorSlot::Shoulders, [70.0; 2]) .bottom_left_with_margins_on(state.ids.chest_slot, 0.0, -80.0) @@ -381,7 +387,9 @@ impl<'a> Widget for Bag<'a> { let (title, desc) = loadout .hand .as_ref() - .map_or(("Hands", ""), |item| (item.name(), item.description())); + .map_or((self.localized_strings.get("hud.bag.hands"), ""), |item| { + (item.name(), item.description()) + }); slot_maker .fabricate(ArmorSlot::Hands, [70.0; 2]) .bottom_right_with_margins_on(state.ids.chest_slot, 0.0, -80.0) @@ -392,7 +400,9 @@ impl<'a> Widget for Bag<'a> { let (title, desc) = loadout .belt .as_ref() - .map_or(("Belt", ""), |item| (item.name(), item.description())); + .map_or((self.localized_strings.get("hud.bag.belt"), ""), |item| { + (item.name(), item.description()) + }); slot_maker .fabricate(ArmorSlot::Belt, [45.0; 2]) .mid_bottom_with_margin_on(state.ids.chest_slot, -55.0) @@ -403,7 +413,9 @@ impl<'a> Widget for Bag<'a> { let (title, desc) = loadout .pants .as_ref() - .map_or(("Legs", ""), |item| (item.name(), item.description())); + .map_or((self.localized_strings.get("hud.bag.legs"), ""), |item| { + (item.name(), item.description()) + }); slot_maker .fabricate(ArmorSlot::Legs, [85.0; 2]) .mid_bottom_with_margin_on(state.ids.belt_slot, -95.0) @@ -411,10 +423,10 @@ impl<'a> Widget for Bag<'a> { .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(("Lantern", ""), |item| (item.name(), item.description())); + let (title, desc) = loadout.lantern.as_ref().map_or( + (self.localized_strings.get("hud.bag.lantern"), ""), + |item| (item.name(), item.description()), + ); slot_maker .fabricate(ArmorSlot::Lantern, [45.0; 2]) .bottom_right_with_margins_on(state.ids.shoulders_slot, -55.0, 0.0) @@ -425,7 +437,9 @@ impl<'a> Widget for Bag<'a> { let (title, desc) = loadout .ring .as_ref() - .map_or(("Ring", ""), |item| (item.name(), item.description())); + .map_or((self.localized_strings.get("hud.bag.ring"), ""), |item| { + (item.name(), item.description()) + }); slot_maker .fabricate(ArmorSlot::Ring, [45.0; 2]) .bottom_left_with_margins_on(state.ids.hands_slot, -55.0, 0.0) @@ -436,7 +450,9 @@ impl<'a> Widget for Bag<'a> { let (title, desc) = loadout .back .as_ref() - .map_or(("Back", ""), |item| (item.name(), item.description())); + .map_or((self.localized_strings.get("hud.bag.back"), ""), |item| { + (item.name(), item.description()) + }); slot_maker .fabricate(ArmorSlot::Back, [45.0; 2]) .down_from(state.ids.lantern_slot, 10.0) @@ -447,7 +463,9 @@ impl<'a> Widget for Bag<'a> { let (title, desc) = loadout .foot .as_ref() - .map_or(("Feet", ""), |item| (item.name(), item.description())); + .map_or((self.localized_strings.get("hud.bag.feet"), ""), |item| { + (item.name(), item.description()) + }); slot_maker .fabricate(ArmorSlot::Feet, [45.0; 2]) .down_from(state.ids.ring_slot, 10.0) @@ -458,7 +476,9 @@ impl<'a> Widget for Bag<'a> { let (title, desc) = loadout .tabard .as_ref() - .map_or(("Tabard", ""), |item| (item.name(), item.description())); + .map_or((self.localized_strings.get("hud.bag.tabard"), ""), |item| { + (item.name(), item.description()) + }); slot_maker .fabricate(ArmorSlot::Tabard, [70.0; 2]) .top_right_with_margins_on(state.ids.bg_frame, 80.5, 53.0) @@ -466,11 +486,10 @@ impl<'a> Widget for Bag<'a> { .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(("Mainhand", ""), |item| (item.name(), item.description())); + 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(ArmorSlot::Mainhand, [85.0; 2]) .bottom_right_with_margins_on(state.ids.back_slot, -95.0, 0.0) @@ -478,11 +497,10 @@ impl<'a> Widget for Bag<'a> { .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(("Offhand", ""), |item| (item.name(), item.description())); + 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(ArmorSlot::Offhand, [85.0; 2]) .bottom_left_with_margins_on(state.ids.feet_slot, -95.0, 0.0) diff --git a/voxygen/src/menu/char_selection/ui.rs b/voxygen/src/menu/char_selection/ui.rs index 731b603776..4ea9422a5e 100644 --- a/voxygen/src/menu/char_selection/ui.rs +++ b/voxygen/src/menu/char_selection/ui.rs @@ -351,7 +351,9 @@ 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: Some(assets::load_expect_cloned( @@ -378,6 +380,9 @@ 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", )); diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 5348c6f602..380a108603 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -336,9 +336,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 => { @@ -420,9 +420,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) => { diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index c062ae420f..139d61b896 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -119,7 +119,7 @@ impl ControlSettings { GameInput::Glide => KeyMouse::Key(VirtualKeyCode::LShift), GameInput::Climb => KeyMouse::Key(VirtualKeyCode::Space), GameInput::ClimbDown => KeyMouse::Key(VirtualKeyCode::LControl), - GameInput::WallLeap => MIDDLE_CLICK_KEY, + //GameInput::WallLeap => MIDDLE_CLICK_KEY, GameInput::Mount => KeyMouse::Key(VirtualKeyCode::F), GameInput::Map => KeyMouse::Key(VirtualKeyCode::M), GameInput::Bag => KeyMouse::Key(VirtualKeyCode::B), @@ -136,7 +136,7 @@ impl ControlSettings { 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::Charge => KeyMouse::Key(VirtualKeyCode::Key1), GameInput::FreeLook => KeyMouse::Key(VirtualKeyCode::L), GameInput::Ability3 => KeyMouse::Key(VirtualKeyCode::Key1), GameInput::SwapLoadout => KeyMouse::Key(VirtualKeyCode::LAlt), @@ -165,7 +165,7 @@ impl Default for ControlSettings { GameInput::Glide, GameInput::Climb, GameInput::ClimbDown, - GameInput::WallLeap, + //GameInput::WallLeap, GameInput::Mount, GameInput::Enter, GameInput::Command, @@ -185,7 +185,7 @@ impl Default for ControlSettings { GameInput::Respawn, GameInput::Interact, GameInput::ToggleWield, - GameInput::Charge, + //GameInput::Charge, GameInput::FreeLook, GameInput::Ability3, GameInput::SwapLoadout, diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index da2ff7f6a9..4a5102b8b1 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -6,6 +6,7 @@ use crate::{ }; use gilrs::{EventType, Gilrs}; use hashbrown::HashMap; + use log::{error, warn}; use serde_derive::{Deserialize, Serialize}; use std::fmt; @@ -27,7 +28,7 @@ pub enum GameInput { Glide, Climb, ClimbDown, - WallLeap, + //WallLeap, Mount, Enter, Command, @@ -47,7 +48,7 @@ pub enum GameInput { Respawn, Interact, ToggleWield, - Charge, + //Charge, SwapLoadout, FreeLook, } @@ -67,7 +68,7 @@ impl GameInput { GameInput::Glide => "gameinput.glide", GameInput::Climb => "gameinput.climb", GameInput::ClimbDown => "gameinput.climbdown", - GameInput::WallLeap => "gameinput.wallleap", + //GameInput::WallLeap => "gameinput.wallleap", GameInput::Mount => "gameinput.mount", GameInput::Enter => "gameinput.enter", GameInput::Command => "gameinput.command", @@ -87,7 +88,7 @@ impl GameInput { GameInput::Respawn => "gameinput.respawn", GameInput::Interact => "gameinput.interact", GameInput::ToggleWield => "gameinput.togglewield", - GameInput::Charge => "gameinput.charge", + //GameInput::Charge => "gameinput.charge", GameInput::FreeLook => "gameinput.freelook", GameInput::Ability3 => "gameinput.ability3", GameInput::SwapLoadout => "gameinput.swaploadout", From a29f199e80af42f8912e6ba76660043b16560384 Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Thu, 9 Apr 2020 01:25:11 +0200 Subject: [PATCH 040/195] fmt --- voxygen/src/hud/map.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/voxygen/src/hud/map.rs b/voxygen/src/hud/map.rs index 82c07d7c54..9de88cf360 100644 --- a/voxygen/src/hud/map.rs +++ b/voxygen/src/hud/map.rs @@ -193,7 +193,8 @@ impl<'a> Widget for Map<'a> { .get(self.client.entity()) .map_or(Vec3::zero(), |pos| pos.0); // 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 + // 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) }); From f24ba71d9446ea8bcf259020125eaa4290bfde49 Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Thu, 9 Apr 2020 03:15:54 +0200 Subject: [PATCH 041/195] hotbar keybindings --- assets/voxygen/i18n/de_DE.ron | 11 +- assets/voxygen/i18n/en.ron | 11 +- voxygen/src/hud/skillbar.rs | 228 +++++++++++++++++++++++----------- voxygen/src/settings.rs | 26 +++- voxygen/src/window.rs | 24 +++- 5 files changed, 219 insertions(+), 81 deletions(-) diff --git a/assets/voxygen/i18n/de_DE.ron b/assets/voxygen/i18n/de_DE.ron index a36aee2c6d..bf0ab6bbae 100644 --- a/assets/voxygen/i18n/de_DE.ron +++ b/assets/voxygen/i18n/de_DE.ron @@ -288,7 +288,16 @@ Viel Spaß in der Welt von Veloren, Abenteurer!"#, "gameinput.primary": "Linker mittlerer Slot", "gameinput.secondary": "Rechter mittlerer Slot", - "gameinput.ability3": "Hotbar Slot 1", + "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", diff --git a/assets/voxygen/i18n/en.ron b/assets/voxygen/i18n/en.ron index b78b7bceda..7ce2170022 100644 --- a/assets/voxygen/i18n/en.ron +++ b/assets/voxygen/i18n/en.ron @@ -285,7 +285,16 @@ Enjoy your stay in the World of Veloren."#, "gameinput.primary": "Basic Attack", "gameinput.secondary": "Secondary Attack/Block/Aim", - "gameinput.ability3": "Hotbar Slot 1", + "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", diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index 036cbc8302..721e9b335d 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -925,78 +925,162 @@ impl<'a> Widget for Skillbar<'a> { // 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::Ability3) + { + Text::new(slot1.to_string().as_str()) + .top_right_with_margins_on(state.ids.slot1_bg, 1.0, 2.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_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_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_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_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(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(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_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); + } + if let Some(slot7) = &self + .global_state + .settings + .controls + .get_binding(GameInput::Slot7) + { + Text::new(slot7.to_string().as_str()) + .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); + } + if let Some(slot8) = &self + .global_state + .settings + .controls + .get_binding(GameInput::Slot8) + { + Text::new(slot8.to_string().as_str()) + .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); + } + if let Some(slot9) = &self + .global_state + .settings + .controls + .get_binding(GameInput::Slot9) + { + Text::new(slot9.to_string().as_str()) + .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); + } + if let Some(slot10) = &self + .global_state + .settings + .controls + .get_binding(GameInput::Slot10) + { + Text::new(slot10.to_string().as_str()) + .top_left_with_margins_on(state.ids.slotq_bg, 1.0, 2.0) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.slotq_text, ui); + } }; // Lifebar diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index 139d61b896..8ea726fa60 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -139,6 +139,15 @@ impl ControlSettings { //GameInput::Charge => KeyMouse::Key(VirtualKeyCode::Key1), GameInput::FreeLook => KeyMouse::Key(VirtualKeyCode::L), GameInput::Ability3 => 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), } } @@ -188,6 +197,15 @@ impl Default for ControlSettings { //GameInput::Charge, GameInput::FreeLook, GameInput::Ability3, + 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 { @@ -256,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, @@ -276,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)] @@ -342,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), @@ -362,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), } } } diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index 4a5102b8b1..7f15ee71df 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -18,6 +18,15 @@ pub enum GameInput { Primary, Secondary, Ability3, + Slot2, + Slot3, + Slot4, + Slot5, + Slot6, + Slot7, + Slot8, + Slot9, + Slot10, ToggleCursor, MoveForward, MoveBack, @@ -90,7 +99,16 @@ impl GameInput { GameInput::ToggleWield => "gameinput.togglewield", //GameInput::Charge => "gameinput.charge", GameInput::FreeLook => "gameinput.freelook", - GameInput::Ability3 => "gameinput.ability3", + GameInput::Ability3 => "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", } } @@ -341,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), From eb7ded989f8ed65aede09ff361c7b2506e94fc3d Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Thu, 9 Apr 2020 17:11:50 +0200 Subject: [PATCH 042/195] 7 dwarf accessories --- assets/common/items/armor/back/admin.ron | 3 ++- assets/common/items/armor/back/short_0.ron | 2 +- assets/common/items/armor/lantern/black_0.ron | 2 +- assets/common/items/armor/neck/neck_0.ron | 2 +- assets/common/items/armor/ring/ring_0.ron | 2 +- assets/common/items/armor/tabard/admin.ron | 3 ++- assets/voxygen/element/icons/tabard_admin.png | 4 ++-- assets/voxygen/item_image_manifest.ron | 5 +++-- .../voxel/armor/foot/cloth_sandals.vox | 4 ++-- assets/voxygen/voxel/armor/head/leather-0.vox | 3 +++ .../figure/accessory/dwarf/earring-0.vox | 3 +++ .../figure/accessory/dwarf/earring-1.vox | 3 +++ .../figure/accessory/dwarf/earrings-0.vox | 3 +++ .../figure/accessory/dwarf/earrings-1.vox | 3 +++ .../figure/accessory/dwarf/earrings-2.vox | 3 +++ .../figure/accessory/dwarf/earrings-3.vox | 3 +++ .../figure/accessory/dwarf/warpaint-0.vox | 4 ++-- .../voxygen/voxel/humanoid_head_manifest.ron | 19 ++++++++++++++++--- assets/voxygen/voxel/not_found.vox | 4 ++-- common/src/comp/body/humanoid.rs | 4 ++-- common/src/comp/inventory/item/mod.rs | 3 +++ 21 files changed, 61 insertions(+), 21 deletions(-) create mode 100644 assets/voxygen/voxel/armor/head/leather-0.vox create mode 100644 assets/voxygen/voxel/figure/accessory/dwarf/earring-0.vox create mode 100644 assets/voxygen/voxel/figure/accessory/dwarf/earring-1.vox create mode 100644 assets/voxygen/voxel/figure/accessory/dwarf/earrings-0.vox create mode 100644 assets/voxygen/voxel/figure/accessory/dwarf/earrings-1.vox create mode 100644 assets/voxygen/voxel/figure/accessory/dwarf/earrings-2.vox create mode 100644 assets/voxygen/voxel/figure/accessory/dwarf/earrings-3.vox diff --git a/assets/common/items/armor/back/admin.ron b/assets/common/items/armor/back/admin.ron index fc8f427533..04b45466e3 100644 --- a/assets/common/items/armor/back/admin.ron +++ b/assets/common/items/armor/back/admin.ron @@ -1,6 +1,7 @@ Item( name: "Admin's Cape", - description: "With great power comes + description: " + With great power comes great responsibility. ", kind: Armor( kind: Back(Admin), diff --git a/assets/common/items/armor/back/short_0.ron b/assets/common/items/armor/back/short_0.ron index 805b88edd0..953cb40e13 100644 --- a/assets/common/items/armor/back/short_0.ron +++ b/assets/common/items/armor/back/short_0.ron @@ -1,6 +1,6 @@ Item( name: "Short leather Cape", - description: "WIP", + description: "", kind: Armor( kind: Back(Short0), stats: (20), diff --git a/assets/common/items/armor/lantern/black_0.ron b/assets/common/items/armor/lantern/black_0.ron index d10d4150d9..950cc28f41 100644 --- a/assets/common/items/armor/lantern/black_0.ron +++ b/assets/common/items/armor/lantern/black_0.ron @@ -1,6 +1,6 @@ Item( name: "Black Lantern", - description: "WIP", + description: "Used by city guards.", kind: Armor( kind: Lantern(Black0), stats: (20), diff --git a/assets/common/items/armor/neck/neck_0.ron b/assets/common/items/armor/neck/neck_0.ron index 4dbd32ae6f..28125345bc 100644 --- a/assets/common/items/armor/neck/neck_0.ron +++ b/assets/common/items/armor/neck/neck_0.ron @@ -1,6 +1,6 @@ Item( name: "Plain Necklace", - description: "WIP", + 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 index 8544765246..d094e21af8 100644 --- a/assets/common/items/armor/ring/ring_0.ron +++ b/assets/common/items/armor/ring/ring_0.ron @@ -1,6 +1,6 @@ Item( name: "Scratched Ring", - description: "WIP", + description: "Barely fits your finger.", kind: Armor( kind: Ring(Ring0), stats: (20), diff --git a/assets/common/items/armor/tabard/admin.ron b/assets/common/items/armor/tabard/admin.ron index 9ad427f3ed..5bb3fd22fb 100644 --- a/assets/common/items/armor/tabard/admin.ron +++ b/assets/common/items/armor/tabard/admin.ron @@ -1,6 +1,7 @@ Item( name: "Admin's Tabard", - description: "With great power comes + description: " + With great power comes great responsibility. ", kind: Armor( kind: Tabard(Admin), diff --git a/assets/voxygen/element/icons/tabard_admin.png b/assets/voxygen/element/icons/tabard_admin.png index 9d1a68eff6..2061bd402e 100644 --- a/assets/voxygen/element/icons/tabard_admin.png +++ b/assets/voxygen/element/icons/tabard_admin.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6681d47e5186f70dcb6c30c32b2134a348f2cbb2fafb9f3cfe00d45ef29d9b32 -size 572 +oid sha256:7e4c27c15fdb78ecc91d3f7635bbb5f6db6ad8d7d550ded9d454f5e2955ec2e7 +size 662 diff --git a/assets/voxygen/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index 94bf948ef0..afc2378592 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -263,8 +263,9 @@ "element.icons.tabard_admin", ), // Heads - Armor(Head(Leather0)): Png( - "element.icons.head_leather-0", + 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", 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/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/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_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/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/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/inventory/item/mod.rs b/common/src/comp/inventory/item/mod.rs index 5cc42d5b8e..0fa256a5ef 100644 --- a/common/src/comp/inventory/item/mod.rs +++ b/common/src/comp/inventory/item/mod.rs @@ -164,6 +164,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 From 0a52dc61d6d524e856e8a9399c27e9675ba523b6 Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Thu, 9 Apr 2020 21:42:05 +0200 Subject: [PATCH 043/195] hotbar text shadows --- voxygen/src/hud/skillbar.rs | 108 ++++++++++++++++++++++++++++++++---- 1 file changed, 96 insertions(+), 12 deletions(-) diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index 721e9b335d..f3b726fac4 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -51,45 +51,57 @@ 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_text_bg, slot1_icon, 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_bg, + slot10_text, + slot10_text_bg, healthbar_bg, healthbar_filling, health_text, @@ -916,12 +928,12 @@ impl<'a> Widget for Skillbar<'a> { 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); + .set(state.ids.slot10, 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); + .middle_of(state.ids.slot10) + .set(state.ids.slot10_bg, ui); // Shortcuts if let ShortcutNumbers::On = shortcuts { @@ -935,6 +947,12 @@ impl<'a> Widget for Skillbar<'a> { .top_right_with_margins_on(state.ids.slot1_bg, 1.0, 2.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); } @@ -948,6 +966,12 @@ impl<'a> Widget for Skillbar<'a> { .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(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); } @@ -961,6 +985,12 @@ impl<'a> Widget for Skillbar<'a> { .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(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); } @@ -974,6 +1004,12 @@ impl<'a> Widget for Skillbar<'a> { .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(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); } @@ -987,6 +1023,12 @@ impl<'a> Widget for Skillbar<'a> { .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(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); } @@ -1000,6 +1042,12 @@ impl<'a> Widget for Skillbar<'a> { .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); } @@ -1013,6 +1061,12 @@ impl<'a> Widget for Skillbar<'a> { .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); } @@ -1023,7 +1077,13 @@ impl<'a> Widget for Skillbar<'a> { .get_binding(GameInput::Slot6) { Text::new(slot6.to_string().as_str()) - .top_left_with_margins_on(state.ids.slot6_bg, 1.0, 1.0) + .top_right_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(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) @@ -1036,7 +1096,13 @@ impl<'a> Widget for Skillbar<'a> { .get_binding(GameInput::Slot7) { Text::new(slot7.to_string().as_str()) - .top_left_with_margins_on(state.ids.slot7_bg, 1.0, 1.0) + .top_right_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(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) @@ -1049,7 +1115,13 @@ impl<'a> Widget for Skillbar<'a> { .get_binding(GameInput::Slot8) { Text::new(slot8.to_string().as_str()) - .top_left_with_margins_on(state.ids.slot8_bg, 1.0, 1.0) + .top_right_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(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) @@ -1062,7 +1134,13 @@ impl<'a> Widget for Skillbar<'a> { .get_binding(GameInput::Slot9) { Text::new(slot9.to_string().as_str()) - .top_left_with_margins_on(state.ids.slot9_bg, 1.0, 1.0) + .top_right_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(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) @@ -1075,11 +1153,17 @@ impl<'a> Widget for Skillbar<'a> { .get_binding(GameInput::Slot10) { Text::new(slot10.to_string().as_str()) - .top_left_with_margins_on(state.ids.slotq_bg, 1.0, 2.0) + .top_right_with_margins_on(state.ids.slot10_bg, 1.0, 1.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.slotq_text, ui); + .set(state.ids.slot10_text, ui); } }; From 88a938653be9a95128e938d24afba559bf57fdf5 Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Fri, 10 Apr 2020 00:07:46 +0200 Subject: [PATCH 044/195] translation fix --- assets/voxygen/i18n/de_DE.ron | 4 +-- assets/voxygen/i18n/en.ron | 4 +-- voxygen/src/hud/bag.rs | 66 +++++++++++++++++++++-------------- 3 files changed, 43 insertions(+), 31 deletions(-) diff --git a/assets/voxygen/i18n/de_DE.ron b/assets/voxygen/i18n/de_DE.ron index bf0ab6bbae..4c3d916e49 100644 --- a/assets/voxygen/i18n/de_DE.ron +++ b/assets/voxygen/i18n/de_DE.ron @@ -195,8 +195,8 @@ um dieses Fenster zu schließen? Drückt 'TAB'! Viel Spaß in der Welt von Veloren, Abenteurer!"#, // Inventory - "hud.bag.inventory": "s Inventar", - "hud.bag.stats_title": "s Werte", + "hud.bag.inventory": "{name}s Inventar", + "hud.bag.stats_title": "{playername}s Werte", "hud.bag.exp": "Erf", "hud.bag.armor": "Rüstung", "hud.bag.stats": "Werte", diff --git a/assets/voxygen/i18n/en.ron b/assets/voxygen/i18n/en.ron index 7ce2170022..219335f322 100644 --- a/assets/voxygen/i18n/en.ron +++ b/assets/voxygen/i18n/en.ron @@ -190,8 +190,8 @@ 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", diff --git a/voxygen/src/hud/bag.rs b/voxygen/src/hud/bag.rs index a4a21791de..fed6a7e0fc 100644 --- a/voxygen/src/hud/bag.rs +++ b/voxygen/src/hud/bag.rs @@ -94,6 +94,7 @@ pub struct Bag<'a> { slot_manager: &'a mut HudSlotManager, _pulse: f32, localized_strings: &'a std::sync::Arc, + stats: &'a Stats, show: &'a Show, } @@ -217,21 +218,28 @@ impl<'a> Widget for Bag<'a> { .color(Some(UI_HIGHLIGHT_0)) .set(state.ids.bg_frame, ui); // Title - Text::new(&format!( + /*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)) @@ -280,21 +288,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)) @@ -510,21 +520,23 @@ impl<'a> Widget for Bag<'a> { } 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)) From 66b283209b8aa56caccdde2bd8456353bcd55c01 Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Fri, 10 Apr 2020 12:02:25 +0200 Subject: [PATCH 045/195] Update de_DE.ron --- assets/voxygen/i18n/de_DE.ron | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/voxygen/i18n/de_DE.ron b/assets/voxygen/i18n/de_DE.ron index 4c3d916e49..cb941d59cb 100644 --- a/assets/voxygen/i18n/de_DE.ron +++ b/assets/voxygen/i18n/de_DE.ron @@ -195,7 +195,7 @@ um dieses Fenster zu schließen? Drückt 'TAB'! Viel Spaß in der Welt von Veloren, Abenteurer!"#, // Inventory - "hud.bag.inventory": "{name}s Inventar", + "hud.bag.inventory": "{playername}s Inventar", "hud.bag.stats_title": "{playername}s Werte", "hud.bag.exp": "Erf", "hud.bag.armor": "Rüstung", From e2d60b858ec7eba57fab43d43493d3fed5d34c49 Mon Sep 17 00:00:00 2001 From: jshipsey Date: Fri, 10 Apr 2020 20:40:34 -0400 Subject: [PATCH 046/195] cape work --- voxygen/src/anim/character/charge.rs | 4 +++ voxygen/src/anim/character/climb.rs | 4 +++ voxygen/src/anim/character/run.rs | 4 +-- voxygen/src/anim/character/shoot.rs | 3 ++ voxygen/src/anim/character/sit.rs | 4 +++ voxygen/src/anim/character/stand.rs | 2 +- voxygen/src/anim/character/swim.rs | 4 +++ voxygen/src/anim/character/wield.rs | 15 +++++++++- voxygen/src/scene/figure/cache.rs | 6 +++- voxygen/src/scene/figure/load.rs | 41 +++++++--------------------- 10 files changed, 51 insertions(+), 36 deletions(-) diff --git a/voxygen/src/anim/character/charge.rs b/voxygen/src/anim/character/charge.rs index bb6e3b168b..bd5d9d6b2a 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); diff --git a/voxygen/src/anim/character/climb.rs b/voxygen/src/anim/character/climb.rs index 463a4fdd0e..d2ea2f66da 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_z(-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) diff --git a/voxygen/src/anim/character/run.rs b/voxygen/src/anim/character/run.rs index 594b4634d2..3c9917ae2b 100644 --- a/voxygen/src/anim/character/run.rs +++ b/voxygen/src/anim/character/run.rs @@ -91,7 +91,7 @@ impl Animation for RunAnimation { 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.ori = Quaternion::rotation_x(-0.2 + short * 0.2); next.back.scale = Vec3::one() * 1.02; next.shorts.offset = Vec3::new(0.0, 0.0, -5.0); @@ -138,7 +138,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); diff --git a/voxygen/src/anim/character/shoot.rs b/voxygen/src/anim/character/shoot.rs index 26f0d6c443..f064477536 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); 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/stand.rs b/voxygen/src/anim/character/stand.rs index 97f9660e39..97baf7bba5 100644 --- a/voxygen/src/anim/character/stand.rs +++ b/voxygen/src/anim/character/stand.rs @@ -105,7 +105,7 @@ impl Animation for StandAnimation { next.lantern.offset = Vec3::new(0.0, 0.0, 0.0); next.lantern.ori = Quaternion::rotation_x(0.0); - next.lantern.scale = Vec3::one() * 5.0; + next.lantern.scale = Vec3::one() * 50.0; 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..0b2102f9ff 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(); diff --git a/voxygen/src/anim/character/wield.rs b/voxygen/src/anim/character/wield.rs index a35edb4b6c..c0c5143a0b 100644 --- a/voxygen/src/anim/character/wield.rs +++ b/voxygen/src/anim/character/wield.rs @@ -17,11 +17,16 @@ 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 wave = (anim_time as f32 * 16.0).sin(); match active_tool_kind { //TODO: Inventory @@ -193,6 +198,10 @@ 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.4 + short * 0.3); + 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 +241,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/scene/figure/cache.rs b/voxygen/src/scene/figure/cache.rs index fb12fe9740..cd7d738fd2 100644 --- a/voxygen/src/scene/figure/cache.rs +++ b/voxygen/src/scene/figure/cache.rs @@ -31,6 +31,7 @@ struct CharacterCacheKey { chest: Option, belt: Option, back: Option, + lantern: Option, hand: Option, pants: Option, foot: Option, @@ -51,6 +52,7 @@ impl CharacterCacheKey { 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(), @@ -122,6 +124,8 @@ impl FigureModelCache { 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 = @@ -234,7 +238,7 @@ impl FigureModelCache { } else { None }, - Some(mesh_lantern()), + Some(humanoid_armor_lantern_spec.mesh_lantern(&body, loadout)), None, None, ] diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs index bcae0fbf7c..7bfac88f5e 100644 --- a/voxygen/src/scene/figure/load.rs +++ b/voxygen/src/scene/figure/load.rs @@ -710,39 +710,21 @@ impl HumArmorLanternSpec { Some(spec) => spec, None => { error!("No lantern specification exists for {:?}", lantern); - return load_mesh("not_found", Vec3::new(-5.0, -3.5, 1.0)); + return load_mesh("not_found", Vec3::new(-4.0, -3.5, 2.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 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), + ); - let bare_lantern = graceful_load_mat_segment("armor.empty"); - - let mut lantern_armor = graceful_load_mat_segment(&spec.vox_spec.0); - - if let Some(color) = spec.color { - let lantern_color = Vec3::from(color); - lantern_armor = - lantern_armor.map_rgb(|rgb| recolor_grey(rgb, Rgb::from(lantern_color))); - } - - let lantern = DynaUnionizer::new() - .add(color(bare_lantern), Vec3::new(0, 0, 0)) - .add(color(lantern_armor), Vec3::new(0, 0, 0)) - .unify() - .0; - - generate_mesh(&lantern, Vec3::from(spec.vox_spec.1)) + generate_mesh(&lantern_segment, Vec3::from(spec.vox_spec.1)) } } impl HumArmorHeadSpec { @@ -751,7 +733,7 @@ impl HumArmorHeadSpec { .unwrap() } - pub fn mesh_lantern(&self, body: &Body, loadout: &Loadout) -> Mesh { + pub fn mesh_head(&self, body: &Body, loadout: &Loadout) -> Mesh { let spec = if let Some(ItemKind::Armor { kind: Armor::Head(head), .. @@ -801,7 +783,7 @@ impl HumArmorTabardSpec { .unwrap() } - pub fn mesh_lantern(&self, body: &Body, loadout: &Loadout) -> Mesh { + pub fn mesh_tabard(&self, body: &Body, loadout: &Loadout) -> Mesh { let spec = if let Some(ItemKind::Armor { kind: Armor::Tabard(tabard), .. @@ -849,9 +831,6 @@ impl HumArmorTabardSpec { 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.lantern0", Vec3::new(0.0, 0.0, 0.0)) -} ///////// #[derive(Serialize, Deserialize)] From 66b4c0d529b439dfa72d3750773ce3b499b0cadf Mon Sep 17 00:00:00 2001 From: Imbris Date: Mon, 6 Apr 2020 11:25:45 -0400 Subject: [PATCH 047/195] rework slot trait --- voxygen/src/hud/bag.rs | 6 +- voxygen/src/hud/mod.rs | 28 ++--- voxygen/src/hud/{slot_kinds.rs => slots.rs} | 70 +++++++----- voxygen/src/ui/widgets/slot.rs | 116 ++++++++++---------- 4 files changed, 115 insertions(+), 105 deletions(-) rename voxygen/src/hud/{slot_kinds.rs => slots.rs} (57%) diff --git a/voxygen/src/hud/bag.rs b/voxygen/src/hud/bag.rs index fed6a7e0fc..d7d54e61e3 100644 --- a/voxygen/src/hud/bag.rs +++ b/voxygen/src/hud/bag.rs @@ -1,7 +1,7 @@ use super::{ img_ids::{Imgs, ImgsRot}, item_imgs::ItemImgs, - slot_kinds::{ArmorSlot, HudSlotManager, InventorySlot}, + slots::{ArmorSlot, InventorySlot, SlotManager}, Event as HudEvent, Show, CRITICAL_HP_COLOR, LOW_HP_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN, XP_COLOR, }; @@ -91,7 +91,7 @@ pub struct Bag<'a> { common: widget::CommonBuilder, rot_imgs: &'a ImgsRot, tooltip_manager: &'a mut TooltipManager, - slot_manager: &'a mut HudSlotManager, + slot_manager: &'a mut SlotManager, _pulse: f32, localized_strings: &'a std::sync::Arc, @@ -107,7 +107,7 @@ impl<'a> Bag<'a> { fonts: &'a ConrodVoxygenFonts, rot_imgs: &'a ImgsRot, tooltip_manager: &'a mut TooltipManager, - slot_manager: &'a mut HudSlotManager, + slot_manager: &'a mut SlotManager, pulse: f32, localized_strings: &'a std::sync::Arc, stats: &'a Stats, diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 53cc3e70b7..44eaea390f 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -8,7 +8,7 @@ mod map; mod minimap; mod settings_window; mod skillbar; -mod slot_kinds; +mod slots; mod social; mod spell; @@ -234,8 +234,8 @@ pub enum Event { CharacterSelection, UseInventorySlot(usize), SwapInventorySlots(usize, usize), - SwapInventoryArmor(usize, slot_kinds::ArmorSlot), - SwapArmorSlots(slot_kinds::ArmorSlot, slot_kinds::ArmorSlot), + SwapInventoryArmor(usize, slots::ArmorSlot), + SwapArmorSlots(slots::ArmorSlot, slots::ArmorSlot), DropInventorySlot(usize), Logout, Quit, @@ -443,7 +443,7 @@ pub struct Hud { pulse: f32, velocity: f32, voxygen_i18n: std::sync::Arc, - slot_manager: slot_kinds::HudSlotManager, + slot_manager: slots::SlotManager, } impl Hud { @@ -474,8 +474,7 @@ impl Hud { let fonts = ConrodVoxygenFonts::load(&voxygen_i18n.fonts, &mut ui) .expect("Impossible to load fonts!"); - let slot_manager = - slot_kinds::HudSlotManager::new(ui.id_generator(), Vec2::broadcast(40.0)); + let slot_manager = slots::SlotManager::new(ui.id_generator(), Vec2::broadcast(40.0)); Self { ui, @@ -1962,29 +1961,26 @@ impl Hud { // Maintain slot manager for event in self.slot_manager.maintain(ui_widgets) { - use slot_kinds::HudSlotKinds; + use slots::SlotKind; match event { - slot::Event::Dragged( - HudSlotKinds::Inventory(from), - HudSlotKinds::Inventory(to), - ) => { + slot::Event::Dragged(SlotKind::Inventory(from), SlotKind::Inventory(to)) => { // Swap between inventory slots events.push(Event::SwapInventorySlots(from.0, to.0)); }, - slot::Event::Dragged(HudSlotKinds::Armor(from), HudSlotKinds::Armor(to)) => { + slot::Event::Dragged(SlotKind::Armor(from), SlotKind::Armor(to)) => { // Swap between two armor slots events.push(Event::SwapArmorSlots(from, to)); }, - slot::Event::Dragged(HudSlotKinds::Inventory(inv), HudSlotKinds::Armor(arm)) - | slot::Event::Dragged(HudSlotKinds::Armor(arm), HudSlotKinds::Inventory(inv)) => { + slot::Event::Dragged(SlotKind::Inventory(inv), SlotKind::Armor(arm)) + | slot::Event::Dragged(SlotKind::Armor(arm), SlotKind::Inventory(inv)) => { // Swap between inventory and armor slot events.push(Event::SwapInventoryArmor(inv.0, arm)); }, - slot::Event::Dropped(HudSlotKinds::Inventory(from)) => { + slot::Event::Dropped(SlotKind::Inventory(from)) => { // Drop item from inventory events.push(Event::DropInventorySlot(from.0)); }, - slot::Event::Used(HudSlotKinds::Inventory(inv)) => { + slot::Event::Used(SlotKind::Inventory(inv)) => { // Item in inventory used (selected and then clicked again) events.push(Event::UseInventorySlot(inv.0)); }, diff --git a/voxygen/src/hud/slot_kinds.rs b/voxygen/src/hud/slots.rs similarity index 57% rename from voxygen/src/hud/slot_kinds.rs rename to voxygen/src/hud/slots.rs index 6dcf3d260d..398abca975 100644 --- a/voxygen/src/hud/slot_kinds.rs +++ b/voxygen/src/hud/slots.rs @@ -1,17 +1,17 @@ use super::item_imgs::{ItemImgs, ItemKey}; -use crate::ui::slot::{ContentKey, SlotKinds, SlotManager}; +use crate::ui::slot::{self, SlotKey, SumSlot}; use common::comp::{item::ItemKind, Inventory, Loadout}; use conrod_core::image; #[derive(Clone, Copy, PartialEq)] -pub enum HudSlotKinds { +pub enum SlotKind { Inventory(InventorySlot), Armor(ArmorSlot), - Hotbar(HotbarSlot), - //Spellbook(SpellbookSlot), TODO + /*Hotbar(HotbarSlot), + *Spellbook(SpellbookSlot), TODO */ } -pub type HudSlotManager = SlotManager; +pub type SlotManager = slot::SlotManager; #[derive(Clone, Copy, PartialEq)] pub struct InventorySlot(pub usize); @@ -34,7 +34,7 @@ pub enum ArmorSlot { Tabard, } -#[derive(Clone, Copy, PartialEq)] +/*#[derive(Clone, Copy, PartialEq)] pub enum HotbarSlot { One, Two, @@ -46,18 +46,16 @@ pub enum HotbarSlot { Eight, Nine, Ten, -} +}*/ -impl ContentKey for InventorySlot { - type ContentSource = Inventory; +impl SlotKey for InventorySlot { type ImageKey = ItemKey; - type ImageSource = ItemImgs; - fn image_key(&self, source: &Self::ContentSource) -> Option { + fn image_key(&self, source: &Inventory) -> Option { source.get(self.0).map(Into::into) } - fn amount(&self, source: &Self::ContentSource) -> Option { + fn amount(&self, source: &Inventory) -> Option { source .get(self.0) .and_then(|item| match item.kind { @@ -69,17 +67,15 @@ impl ContentKey for InventorySlot { .filter(|amount| *amount > 1) } - fn image_id(key: &Self::ImageKey, source: &Self::ImageSource) -> image::Id { + fn image_id(key: &Self::ImageKey, source: &ItemImgs) -> image::Id { source.img_id_or_not_found_img(key.clone()) } } -impl ContentKey for ArmorSlot { - type ContentSource = Loadout; +impl SlotKey for ArmorSlot { type ImageKey = ItemKey; - type ImageSource = ItemImgs; - fn image_key(&self, source: &Self::ContentSource) -> Option { + fn image_key(&self, source: &Loadout) -> Option { let item = match self { ArmorSlot::Shoulders => source.shoulder.as_ref(), ArmorSlot::Chest => source.chest.as_ref(), @@ -101,23 +97,47 @@ impl ContentKey for ArmorSlot { item.map(Into::into) } - fn amount(&self, _: &Self::ContentSource) -> Option { None } + fn amount(&self, _: &Loadout) -> Option { None } - fn image_id(key: &Self::ImageKey, source: &Self::ImageSource) -> image::Id { + fn image_id(key: &Self::ImageKey, source: &ItemImgs) -> image::Id { source.img_id_or_not_found_img(key.clone()) } } -impl From for HudSlotKinds { +/*impl SlotKey for HotbarSlot { + type ImageKey = ItemKey; + + fn image_key(&self, source: &Inventory) -> Option { + source.get(self.0).map(Into::into) + } + + fn amount(&self, source: &Inventory) -> Option { + source + .get(self.0) + .and_then(|item| match item.kind { + ItemKind::Tool { .. } | 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 From for SlotKind { fn from(inventory: InventorySlot) -> Self { Self::Inventory(inventory) } } -impl From for HudSlotKinds { +impl From for SlotKind { fn from(armor: ArmorSlot) -> Self { Self::Armor(armor) } } -impl From for HudSlotKinds { - fn from(hotbar: HotbarSlot) -> Self { Self::Hotbar(hotbar) } -} +//impl From for SlotKind { +// fn from(hotbar: HotbarSlot) -> Self { Self::Hotbar(hotbar) } +//} -impl SlotKinds for HudSlotKinds {} +impl SumSlot for SlotKind {} diff --git a/voxygen/src/ui/widgets/slot.rs b/voxygen/src/ui/widgets/slot.rs index ff7ab353de..59576ce5b3 100644 --- a/voxygen/src/ui/widgets/slot.rs +++ b/voxygen/src/ui/widgets/slot.rs @@ -10,18 +10,15 @@ use vek::*; const AMOUNT_SHADOW_OFFSET: [f64; 2] = [1.0, 1.0]; -pub trait ContentKey: Copy { - type ContentSource; - type ImageSource; +pub trait SlotKey: Copy { type ImageKey: PartialEq + Send + 'static; /// Returns an Option since the slot could be empty - fn image_key(&self, source: &Self::ContentSource) -> Option; - // TODO: is this the right integer type? - fn amount(&self, source: &Self::ContentSource) -> Option; - fn image_id(key: &Self::ImageKey, source: &Self::ImageSource) -> image::Id; + fn image_key(&self, source: &C) -> Option; + fn amount(&self, source: &C) -> Option; + fn image_id(key: &Self::ImageKey, source: &I) -> image::Id; } -pub trait SlotKinds: Sized + PartialEq + Copy + Send + 'static {} +pub trait SumSlot: Sized + PartialEq + Copy + Send + 'static {} pub struct ContentSize { // Width divided by height @@ -30,7 +27,7 @@ pub struct ContentSize { pub max_fraction: f32, } -pub struct SlotMaker<'a, C: ContentKey + Into, K: SlotKinds> { +pub struct SlotMaker<'a, C, I, S: SumSlot> { pub empty_slot: image::Id, pub filled_slot: image::Id, pub selected_slot: image::Id, @@ -43,17 +40,20 @@ pub struct SlotMaker<'a, C: ContentKey + Into, K: SlotKinds> { pub amount_font_size: u32, pub amount_margins: Vec2, pub amount_text_color: Color, - pub content_source: &'a C::ContentSource, - pub image_source: &'a C::ImageSource, - pub slot_manager: Option<&'a mut SlotManager>, + pub content_source: &'a C, + pub image_source: &'a I, + pub slot_manager: Option<&'a mut SlotManager>, } -impl<'a, C, K> SlotMaker<'a, C, K> +impl<'a, C, I, S> SlotMaker<'a, C, I, S> where - C: ContentKey + Into, - K: SlotKinds, + S: SumSlot, { - pub fn fabricate(&mut self, contents: C, wh: [f32; 2]) -> Slot { + pub fn fabricate + Into>( + &mut self, + contents: K, + wh: [f32; 2], + ) -> Slot { let content_size = { let ContentSize { max_fraction, @@ -111,13 +111,13 @@ pub enum Event { Used(K), } // Handles interactions with slots -pub struct SlotManager { - state: ManagerState, +pub struct SlotManager { + state: ManagerState, // Rebuilt every frame slot_ids: Vec, // Rebuilt every frame - slot_kinds: Vec, - events: Vec>, + slots: Vec, + events: Vec>, // Widget id for dragging image drag_id: widget::Id, // Size to display dragged content @@ -125,25 +125,25 @@ pub struct SlotManager { drag_img_size: Vec2, } -impl SlotManager +impl SlotManager where - K: SlotKinds, + S: SumSlot, { pub fn new(mut gen: widget::id::Generator, drag_img_size: Vec2) -> Self { Self { state: ManagerState::Idle, slot_ids: Vec::new(), - slot_kinds: 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> { + 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 slot_kinds = std::mem::replace(&mut self.slot_kinds, 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 { @@ -166,7 +166,7 @@ where 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, slot_kinds[idx])); + self.events.push(Event::Dragged(*slot, slots[idx])); } } // Mouse released stop dragging @@ -187,13 +187,13 @@ where fn update( &mut self, widget: widget::Id, - slot: K, + slot: S, ui: &conrod_core::Ui, content_img: Option, ) -> Interaction { // Add to list of slots self.slot_ids.push(widget); - self.slot_kinds.push(slot); + self.slots.push(slot); let filled = content_img.is_some(); // If the slot is no longer filled deselect it or cancel dragging @@ -280,8 +280,8 @@ where } #[derive(WidgetCommon)] -pub struct Slot<'a, C: ContentKey + Into, K: SlotKinds> { - content: C, +pub struct Slot<'a, K: SlotKey + Into, C, I, S: SumSlot> { + slot_key: K, // Images for slot background and frame empty_slot: image::Id, @@ -301,10 +301,10 @@ pub struct Slot<'a, C: ContentKey + Into, K: SlotKinds> { amount_margins: Vec2, amount_text_color: Color, - slot_manager: Option<&'a mut SlotManager>, + slot_manager: Option<&'a mut SlotManager>, // Should we just pass in the ImageKey? - content_source: &'a C::ContentSource, - image_source: &'a C::ImageSource, + content_source: &'a C, + image_source: &'a I, #[conrod(common_builder)] common: widget::CommonBuilder, @@ -322,29 +322,32 @@ widget_ids! { } /// Represents the state of the Slot widget. -pub struct State { +pub struct State { ids: Ids, cached_image: Option<(K, image::Id)>, - slot_kind: S, } -impl<'a, C, K> Slot<'a, C, K> +impl<'a, K, C, I, S> Slot<'a, K, C, I, S> where - C: ContentKey + Into, - K: SlotKinds, + K: SlotKey + Into, + S: SumSlot, { builder_methods! { - pub with_manager { slot_manager = Some(&'a mut SlotManager) } 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( - content: C, + slot_key: K, empty_slot: image::Id, filled_slot: image::Id, selected_slot: image::Id, @@ -354,11 +357,11 @@ where amount_font_size: u32, amount_margins: Vec2, amount_text_color: Color, - content_source: &'a C::ContentSource, - image_source: &'a C::ImageSource, + content_source: &'a C, + image_source: &'a I, ) -> Self { Self { - content, + slot_key, empty_slot, filled_slot, selected_slot, @@ -378,20 +381,19 @@ where } } -impl<'a, C, K> Widget for Slot<'a, C, K> +impl<'a, K, C, I, S> Widget for Slot<'a, K, C, I, S> where - C: ContentKey + Into, - K: SlotKinds, + K: SlotKey + Into, + S: SumSlot, { type Event = (); - type State = State; + 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, - slot_kind: self.content.into(), } } @@ -407,7 +409,7 @@ where .. } = args; let Slot { - content, + slot_key, empty_slot, filled_slot, selected_slot, @@ -425,29 +427,21 @@ where } = self; // If the key changed update the cached image id - let image_key = content.image_key(content_source); + let image_key = slot_key.image_key(content_source); 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 = C::image_id(&key, &image_source); + let image_id = K::image_id(&key, &image_source); (key, image_id) }); }); } - // If the slot kind value changed update the state - let slot_kind = content.into(); - if slot_kind != state.slot_kind { - state.update(|state| { - state.slot_kind = slot_kind; - }); - } - // 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, content.into(), ui, content_image) + m.update(id, slot_key.into(), ui, content_image) }); // No content if it is being dragged let content_image = if let Interaction::Dragging = interaction { @@ -465,7 +459,7 @@ where }; // Get amount (None => no amount text) - let amount = content.amount(content_source); + let amount = slot_key.amount(content_source); // Get slot widget dimensions and position let (x, y, w, h) = rect.x_y_w_h(); From c1c09dce1b11202876a46a86bc71540ebb98e64c Mon Sep 17 00:00:00 2001 From: Imbris Date: Thu, 9 Apr 2020 22:36:35 -0400 Subject: [PATCH 048/195] Enable unequipping as well as equipping to specific slots --- assets/voxygen/item_image_manifest.ron | 14 +- client/src/lib.rs | 6 +- common/src/comp/controller.rs | 8 +- common/src/comp/inventory/item/armor.rs | 8 - common/src/comp/inventory/item/mod.rs | 9 + common/src/comp/inventory/mod.rs | 9 +- common/src/comp/inventory/slot.rs | 250 ++++++++++++++++++++ common/src/comp/mod.rs | 2 +- common/src/lib.rs | 1 + server/src/events/inventory_manip.rs | 288 ++++++++++++------------ voxygen/src/hud/bag.rs | 37 ++- voxygen/src/hud/item_imgs.rs | 4 +- voxygen/src/hud/mod.rs | 49 ++-- voxygen/src/hud/slots.rs | 59 ++--- voxygen/src/scene/figure/load.rs | 31 ++- voxygen/src/session.rs | 19 +- voxygen/src/ui/widgets/slot.rs | 4 +- 17 files changed, 506 insertions(+), 292 deletions(-) create mode 100644 common/src/comp/inventory/slot.rs diff --git a/assets/voxygen/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index afc2378592..58dde6e4f9 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -54,6 +54,13 @@ "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", @@ -251,13 +258,6 @@ Armor(Neck(Neck0)): Png( "element.icons.neck-0", ), - // Lanterns - Armor(Lantern(Black0)): Png( - "element.icons.lantern_black-0", - ), - Armor(Lantern(Green0)): Png( - "element.icons.lantern_green-0", - ), // Tabards Armor(Tabard(Admin)): Png( "element.icons.tabard_admin", diff --git a/client/src/lib.rs b/client/src/lib.rs index 1cbe9a72ce..1e4aea04b6 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -244,21 +244,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), diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs index 49c5cf8658..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; @@ -11,9 +11,9 @@ pub const DEFAULT_HOLD_DURATION: Duration = Duration::from_millis(200); pub enum InventoryManip { Pickup(Uid), Collect(Vec3), - Use(usize), - Swap(usize, usize), - Drop(usize), + Use(Slot), + Swap(Slot, Slot), + Drop(Slot), } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] diff --git a/common/src/comp/inventory/item/armor.rs b/common/src/comp/inventory/item/armor.rs index b212474273..74885598be 100644 --- a/common/src/comp/inventory/item/armor.rs +++ b/common/src/comp/inventory/item/armor.rs @@ -188,13 +188,6 @@ pub enum Neck { pub const ALL_NECKS: [Neck; 1] = [Neck::Neck0]; #[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]; -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[repr(u32)] pub enum Head { Leather0 = 1, AssaMask0 = 2, @@ -218,7 +211,6 @@ pub enum Armor { Back(Back), Ring(Ring), Neck(Neck), - Lantern(Lantern), Head(Head), Tabard(Tabard), } diff --git a/common/src/comp/inventory/item/mod.rs b/common/src/comp/inventory/item/mod.rs index 0fa256a5ef..42dcdd6a01 100644 --- a/common/src/comp/inventory/item/mod.rs +++ b/common/src/comp/inventory/item/mod.rs @@ -36,12 +36,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, 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..25730c626b --- /dev/null +++ b/common/src/comp/inventory/slot.rs @@ -0,0 +1,250 @@ +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: shouldn't need this +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(); // 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/lib.rs b/common/src/lib.rs index 70df9a2c1d..df969e5af4 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, diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index 7174c15e64..9181ce12f5 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,162 +87,145 @@ 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, - Back(_) => &mut loadout.back, - Ring(_) => &mut loadout.ring, - Neck(_) => &mut loadout.neck, - Lantern(_) => &mut loadout.lantern, - Head(_) => &mut loadout.head, - Tabard(_) => &mut loadout.tabard, - }; - - // 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 _ = state + .ecs() + .write_storage::() + .get_mut(entity) + .map(|inv| inv.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), @@ -246,11 +233,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/voxygen/src/hud/bag.rs b/voxygen/src/hud/bag.rs index d7d54e61e3..09f8209ee8 100644 --- a/voxygen/src/hud/bag.rs +++ b/voxygen/src/hud/bag.rs @@ -1,9 +1,8 @@ use super::{ img_ids::{Imgs, ImgsRot}, item_imgs::ItemImgs, - slots::{ArmorSlot, InventorySlot, SlotManager}, - Event as HudEvent, Show, CRITICAL_HP_COLOR, LOW_HP_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN, - XP_COLOR, + 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, @@ -135,7 +134,6 @@ pub struct State { } pub enum Event { - HudEvent(HudEvent), Stats, Close, } @@ -350,7 +348,7 @@ impl<'a> Widget for Bag<'a> { (item.name(), item.description()) }); slot_maker - .fabricate(ArmorSlot::Head, [45.0; 2]) + .fabricate(EquipSlot::Armor(ArmorSlot::Head), [45.0; 2]) .mid_top_with_margin_on(state.ids.bg_frame, 60.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) @@ -363,7 +361,7 @@ impl<'a> Widget for Bag<'a> { (item.name(), item.description()) }); slot_maker - .fabricate(ArmorSlot::Neck, [45.0; 2]) + .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) @@ -377,7 +375,7 @@ impl<'a> Widget for Bag<'a> { (item.name(), item.description()) }); slot_maker - .fabricate(ArmorSlot::Chest, [85.0; 2]) + .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) @@ -388,7 +386,7 @@ impl<'a> Widget for Bag<'a> { |item| (item.name(), item.description()), ); slot_maker - .fabricate(ArmorSlot::Shoulders, [70.0; 2]) + .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) @@ -401,7 +399,7 @@ impl<'a> Widget for Bag<'a> { (item.name(), item.description()) }); slot_maker - .fabricate(ArmorSlot::Hands, [70.0; 2]) + .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) @@ -414,7 +412,7 @@ impl<'a> Widget for Bag<'a> { (item.name(), item.description()) }); slot_maker - .fabricate(ArmorSlot::Belt, [45.0; 2]) + .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) @@ -427,7 +425,7 @@ impl<'a> Widget for Bag<'a> { (item.name(), item.description()) }); slot_maker - .fabricate(ArmorSlot::Legs, [85.0; 2]) + .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) @@ -438,7 +436,7 @@ impl<'a> Widget for Bag<'a> { |item| (item.name(), item.description()), ); slot_maker - .fabricate(ArmorSlot::Lantern, [45.0; 2]) + .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) @@ -451,7 +449,7 @@ impl<'a> Widget for Bag<'a> { (item.name(), item.description()) }); slot_maker - .fabricate(ArmorSlot::Ring, [45.0; 2]) + .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) @@ -464,7 +462,7 @@ impl<'a> Widget for Bag<'a> { (item.name(), item.description()) }); slot_maker - .fabricate(ArmorSlot::Back, [45.0; 2]) + .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) @@ -477,7 +475,7 @@ impl<'a> Widget for Bag<'a> { (item.name(), item.description()) }); slot_maker - .fabricate(ArmorSlot::Feet, [45.0; 2]) + .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) @@ -490,7 +488,7 @@ impl<'a> Widget for Bag<'a> { (item.name(), item.description()) }); slot_maker - .fabricate(ArmorSlot::Tabard, [70.0; 2]) + .fabricate(EquipSlot::Armor(ArmorSlot::Tabard), [70.0; 2]) .top_right_with_margins_on(state.ids.bg_frame, 80.5, 53.0) .with_icon(self.imgs.tabard_bg, Vec2::new(60.0, 60.0), Some(UI_MAIN)) .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) @@ -501,7 +499,7 @@ impl<'a> Widget for Bag<'a> { |item| (item.name(), item.description()), ); slot_maker - .fabricate(ArmorSlot::Mainhand, [85.0; 2]) + .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) @@ -512,7 +510,7 @@ impl<'a> Widget for Bag<'a> { |item| (item.name(), item.description()), ); slot_maker - .fabricate(ArmorSlot::Offhand, [85.0; 2]) + .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) @@ -681,9 +679,6 @@ impl<'a> Widget for Bag<'a> { } } - // Drop selected item - // if ui.widget_input(ui.window).clicks().left().next().is_some() { - // Stats Button if Button::image(self.imgs.button) .w_h(92.0, 22.0) diff --git a/voxygen/src/hud/item_imgs.rs b/voxygen/src/hud/item_imgs.rs index 647c91d3ac..3c76f58087 100644 --- a/voxygen/src/hud/item_imgs.rs +++ b/voxygen/src/hud/item_imgs.rs @@ -4,7 +4,7 @@ use common::{ comp::item::{ armor::Armor, tool::{Tool, ToolKind}, - Consumable, Ingredient, Item, ItemKind, Utility, + Consumable, Ingredient, Item, ItemKind, Lantern, Utility, }, figure::Segment, }; @@ -20,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), @@ -30,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()), diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 44eaea390f..93e67ff70f 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -232,11 +232,9 @@ pub enum Event { ToggleDebug(bool), UiScale(ScaleChange), CharacterSelection, - UseInventorySlot(usize), - SwapInventorySlots(usize, usize), - SwapInventoryArmor(usize, slots::ArmorSlot), - SwapArmorSlots(slots::ArmorSlot, slots::ArmorSlot), - DropInventorySlot(usize), + UseSlot(comp::slot::Slot), + SwapSlots(comp::slot::Slot, comp::slot::Slot), + DropSlot(comp::slot::Slot), Logout, Quit, ChangeLanguage(LanguageMetadata), @@ -1660,7 +1658,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); @@ -1961,30 +1958,32 @@ impl Hud { // 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 { + SlotKind::Inventory(i) => Some(Slot::Inventory(i.0)), + SlotKind::Equip(e) => Some(Slot::Equip(e)), + //SlotKind::Hotbar(h) => None, + }; match event { - slot::Event::Dragged(SlotKind::Inventory(from), SlotKind::Inventory(to)) => { - // Swap between inventory slots - events.push(Event::SwapInventorySlots(from.0, to.0)); + 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)); + } }, - slot::Event::Dragged(SlotKind::Armor(from), SlotKind::Armor(to)) => { - // Swap between two armor slots - events.push(Event::SwapArmorSlots(from, to)); + slot::Event::Dropped(from) => { + // Drop item + if let Some(from) = to_slot(from) { + events.push(Event::DropSlot(from)); + } }, - slot::Event::Dragged(SlotKind::Inventory(inv), SlotKind::Armor(arm)) - | slot::Event::Dragged(SlotKind::Armor(arm), SlotKind::Inventory(inv)) => { - // Swap between inventory and armor slot - events.push(Event::SwapInventoryArmor(inv.0, arm)); + slot::Event::Used(from) => { + // Item used (selected and then clicked again) + if let Some(from) = to_slot(from) { + events.push(Event::UseSlot(from)); + } }, - slot::Event::Dropped(SlotKind::Inventory(from)) => { - // Drop item from inventory - events.push(Event::DropInventorySlot(from.0)); - }, - slot::Event::Used(SlotKind::Inventory(inv)) => { - // Item in inventory used (selected and then clicked again) - events.push(Event::UseInventorySlot(inv.0)); - }, - _ => {}, } } diff --git a/voxygen/src/hud/slots.rs b/voxygen/src/hud/slots.rs index 398abca975..3e22307cbd 100644 --- a/voxygen/src/hud/slots.rs +++ b/voxygen/src/hud/slots.rs @@ -3,10 +3,12 @@ use crate::ui::slot::{self, SlotKey, SumSlot}; use common::comp::{item::ItemKind, Inventory, Loadout}; use conrod_core::image; +pub use common::comp::slot::{ArmorSlot, EquipSlot}; + #[derive(Clone, Copy, PartialEq)] pub enum SlotKind { Inventory(InventorySlot), - Armor(ArmorSlot), + Equip(EquipSlot), /*Hotbar(HotbarSlot), *Spellbook(SpellbookSlot), TODO */ } @@ -16,24 +18,6 @@ pub type SlotManager = slot::SlotManager; #[derive(Clone, Copy, PartialEq)] pub struct InventorySlot(pub usize); -#[derive(Clone, Copy, PartialEq)] -pub enum ArmorSlot { - Head, - Neck, - Shoulders, - Chest, - Hands, - Ring, - Lantern, - Back, - Belt, - Legs, - Feet, - Mainhand, - Offhand, - Tabard, -} - /*#[derive(Clone, Copy, PartialEq)] pub enum HotbarSlot { One, @@ -59,7 +43,7 @@ impl SlotKey for InventorySlot { source .get(self.0) .and_then(|item| match item.kind { - ItemKind::Tool { .. } | ItemKind::Armor { .. } => None, + ItemKind::Tool { .. } | ItemKind::Lantern(_) | ItemKind::Armor { .. } => None, ItemKind::Utility { amount, .. } | ItemKind::Consumable { amount, .. } | ItemKind::Ingredient { amount, .. } => Some(amount), @@ -72,26 +56,25 @@ impl SlotKey for InventorySlot { } } -impl SlotKey for ArmorSlot { +impl SlotKey for EquipSlot { type ImageKey = ItemKey; fn image_key(&self, source: &Loadout) -> Option { let item = match self { - ArmorSlot::Shoulders => source.shoulder.as_ref(), - ArmorSlot::Chest => source.chest.as_ref(), - ArmorSlot::Belt => source.belt.as_ref(), - ArmorSlot::Hands => source.hand.as_ref(), - ArmorSlot::Legs => source.pants.as_ref(), - ArmorSlot::Feet => source.foot.as_ref(), - ArmorSlot::Back => source.back.as_ref(), - ArmorSlot::Ring => source.ring.as_ref(), - ArmorSlot::Neck => source.neck.as_ref(), - ArmorSlot::Head => source.head.as_ref(), - ArmorSlot::Lantern => source.lantern.as_ref(), - ArmorSlot::Tabard => source.tabard.as_ref(), - ArmorSlot::Mainhand => source.active_item.as_ref().map(|i| &i.item), - ArmorSlot::Offhand => source.second_item.as_ref().map(|i| &i.item), - _ => None, + 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(Into::into) @@ -132,8 +115,8 @@ impl From for SlotKind { fn from(inventory: InventorySlot) -> Self { Self::Inventory(inventory) } } -impl From for SlotKind { - fn from(armor: ArmorSlot) -> Self { Self::Armor(armor) } +impl From for SlotKind { + fn from(equip: EquipSlot) -> Self { Self::Equip(equip) } } //impl From for SlotKind { diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs index 7bfac88f5e..6c3d24a37a 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, Back, Belt, Chest, Foot, Hand, Head, Lantern, Pants, Shoulder, Tabard}, + 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}, @@ -701,21 +701,18 @@ impl HumArmorLanternSpec { } pub fn mesh_lantern(&self, body: &Body, loadout: &Loadout) -> Mesh { - let spec = if let Some(ItemKind::Armor { - kind: Armor::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 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), diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 380a108603..80b121c897 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -643,22 +643,9 @@ 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::SwapInventoryArmor(inv_slot, armor_slot) => { - // Swapping between inventory and armor slot - // TODO: don't do this - self.client.borrow_mut().use_inventory_slot(inv_slot) - }, - HudEvent::SwapArmorSlots(from, to) => { - // Only works with rings currently - // TODO: implement - }, - 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::ChangeFOV(new_fov) => { global_state.settings.graphics.fov = new_fov; global_state.settings.save_to_file_warn(); diff --git a/voxygen/src/ui/widgets/slot.rs b/voxygen/src/ui/widgets/slot.rs index 59576ce5b3..0da5ceae01 100644 --- a/voxygen/src/ui/widgets/slot.rs +++ b/voxygen/src/ui/widgets/slot.rs @@ -153,9 +153,9 @@ where } } - // If dragging and mouse if released check if there is a slot widget under the + // If dragging and mouse is released check if there is a slot widget under the // mouse - if let ManagerState::Dragging(id, slot, content_img) = &self.state { + 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() { From f52d66d721d24ea7bde15b11a7a9865cc4c4064a Mon Sep 17 00:00:00 2001 From: Imbris Date: Thu, 9 Apr 2020 22:59:28 -0400 Subject: [PATCH 049/195] Fix selecting and remove amount text when dragging --- common/src/comp/inventory/slot.rs | 2 +- voxygen/src/ui/widgets/slot.rs | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/common/src/comp/inventory/slot.rs b/common/src/comp/inventory/slot.rs index 25730c626b..6144a3b6d5 100644 --- a/common/src/comp/inventory/slot.rs +++ b/common/src/comp/inventory/slot.rs @@ -246,5 +246,5 @@ 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(); // Never fails + .unwrap_none(); // Never fails } diff --git a/voxygen/src/ui/widgets/slot.rs b/voxygen/src/ui/widgets/slot.rs index 0da5ceae01..e1ba13e862 100644 --- a/voxygen/src/ui/widgets/slot.rs +++ b/voxygen/src/ui/widgets/slot.rs @@ -255,11 +255,10 @@ where }; } - // If not dragging and the mouse is down and started on this slot start dragging - let input = &ui.global_input().current; - if let mouse::ButtonPosition::Down(_, Some(id)) = input.mouse.buttons.left() { + // If not dragging and there is a drag event on this slot start dragging + if ui.widget_input(widget).drags().left().next().is_some() { match self.state { - ManagerState::Selected(_, _) | ManagerState::Idle if widget == *id => { + ManagerState::Selected(_, _) | ManagerState::Idle => { // Start dragging if widget is filled if let Some(img) = content_img { self.state = ManagerState::Dragging(widget, slot, img); @@ -459,7 +458,11 @@ where }; // Get amount (None => no amount text) - let amount = slot_key.amount(content_source); + 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(); From 77d13376d3cb2221e35f842fb4694ee952682c94 Mon Sep 17 00:00:00 2001 From: Imbris Date: Fri, 10 Apr 2020 00:33:35 -0400 Subject: [PATCH 050/195] Add right click to use, latern is not armor tweaks --- assets/common/items/armor/lantern/black_0.ron | 8 ------ assets/common/items/armor/lantern/green_0.ron | 8 ------ assets/common/items/debug/green_0.ron | 7 ++--- assets/common/items/lantern/black_0.ron | 5 ++++ assets/common/items/lantern/green_0.ron | 5 ++++ voxygen/src/ui/widgets/slot.rs | 27 ++++++++++++------- 6 files changed, 30 insertions(+), 30 deletions(-) delete mode 100644 assets/common/items/armor/lantern/black_0.ron delete mode 100644 assets/common/items/armor/lantern/green_0.ron create mode 100644 assets/common/items/lantern/black_0.ron create mode 100644 assets/common/items/lantern/green_0.ron diff --git a/assets/common/items/armor/lantern/black_0.ron b/assets/common/items/armor/lantern/black_0.ron deleted file mode 100644 index 950cc28f41..0000000000 --- a/assets/common/items/armor/lantern/black_0.ron +++ /dev/null @@ -1,8 +0,0 @@ -Item( - name: "Black Lantern", - description: "Used by city guards.", - kind: Armor( - kind: Lantern(Black0), - stats: (20), - ), -) diff --git a/assets/common/items/armor/lantern/green_0.ron b/assets/common/items/armor/lantern/green_0.ron deleted file mode 100644 index c37daf9b99..0000000000 --- a/assets/common/items/armor/lantern/green_0.ron +++ /dev/null @@ -1,8 +0,0 @@ -Item( - name: "Lime Zest Lantern", - description: "It has an opening that could fit a ring...", - kind: Armor( - kind: Lantern(Green0), - stats: (20), - ), -) diff --git a/assets/common/items/debug/green_0.ron b/assets/common/items/debug/green_0.ron index c37daf9b99..ea7d5cbcc7 100644 --- a/assets/common/items/debug/green_0.ron +++ b/assets/common/items/debug/green_0.ron @@ -1,8 +1,5 @@ Item( name: "Lime Zest Lantern", - description: "It has an opening that could fit a ring...", - kind: Armor( - kind: Lantern(Green0), - stats: (20), - ), + description: "It has an opening that could fit a ring...", + kind: Lantern(Green0), ) 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/voxygen/src/ui/widgets/slot.rs b/voxygen/src/ui/widgets/slot.rs index e1ba13e862..82607485e4 100644 --- a/voxygen/src/ui/widgets/slot.rs +++ b/voxygen/src/ui/widgets/slot.rs @@ -217,9 +217,10 @@ where _ => (), } + 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 = ui.widget_input(widget).clicks().left().count(); + 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 { @@ -255,17 +256,25 @@ where }; } - // If not dragging and there is a drag event on this slot start dragging - if ui.widget_input(widget).drags().left().next().is_some() { + // Use on right click if not dragging + if input.clicks().right().next().is_some() { match self.state { ManagerState::Selected(_, _) | ManagerState::Idle => { - // Start dragging if widget is filled - if let Some(img) = content_img { - self.state = ManagerState::Dragging(widget, slot, img); - } + self.events.push(Event::Used(slot)); + // If something is selected, deselect + self.state = ManagerState::Idle; }, - // Already dragging or id doesn't match - _ => {}, + 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); } } From f81f91162d82ef724946909f8e5594c4b5d64b30 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sat, 11 Apr 2020 02:33:06 -0400 Subject: [PATCH 051/195] Basic hotbar functionality --- voxygen/src/hud/hotbar.rs | 97 +++++++++++++++++ voxygen/src/hud/mod.rs | 193 ++++++++++++++++++++++++++++++--- voxygen/src/hud/skillbar.rs | 168 +++++++++++++--------------- voxygen/src/hud/slots.rs | 107 +++++++++++------- voxygen/src/lib.rs | 2 +- voxygen/src/session.rs | 4 +- voxygen/src/settings.rs | 4 +- voxygen/src/ui/widgets/slot.rs | 19 +++- voxygen/src/window.rs | 4 +- 9 files changed, 441 insertions(+), 157 deletions(-) create mode 100644 voxygen/src/hud/hotbar.rs diff --git a/voxygen/src/hud/hotbar.rs b/voxygen/src/hud/hotbar.rs new file mode 100644 index 0000000000..5eb1a3ae53 --- /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 is 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/mod.rs b/voxygen/src/hud/mod.rs index 93e67ff70f..93899d73b8 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -2,6 +2,7 @@ mod bag; mod buttons; mod chat; mod esc_menu; +mod hotbar; mod img_ids; mod item_imgs; mod map; @@ -235,6 +236,7 @@ pub enum Event { UseSlot(comp::slot::Slot), SwapSlots(comp::slot::Slot, comp::slot::Slot), DropSlot(comp::slot::Slot), + Ability3(bool), Logout, Quit, ChangeLanguage(LanguageMetadata), @@ -442,6 +444,8 @@ pub struct Hud { velocity: f32, voxygen_i18n: std::sync::Arc, slot_manager: slots::SlotManager, + hotbar: hotbar::State, + events: Vec, } impl Hud { @@ -513,6 +517,8 @@ impl Hud { velocity: 0.0, voxygen_i18n, slot_manager, + hotbar: hotbar::State::new(), + events: Vec::new(), } } @@ -529,7 +535,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(); @@ -1676,16 +1682,26 @@ 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, &stats, &loadout, @@ -1693,6 +1709,9 @@ impl Hud { &character_state, self.pulse, &controller, + &inventory, + &self.hotbar, + &mut self.slot_manager, ) .set(self.ids.skillbar, ui_widgets); } @@ -1959,33 +1978,50 @@ impl Hud { // Maintain slot manager for event in self.slot_manager.maintain(ui_widgets) { use comp::slot::Slot; - use slots::SlotKind; + use slots::SlotKind::*; let to_slot = |slot_kind| match slot_kind { - SlotKind::Inventory(i) => Some(Slot::Inventory(i.0)), - SlotKind::Equip(e) => Some(Slot::Equip(e)), - //SlotKind::Hotbar(h) => None, + 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 } @@ -2018,6 +2054,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) => { @@ -2058,46 +2118,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/skillbar.rs b/voxygen/src/hud/skillbar.rs index f3b726fac4..e8bb9f3dce 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -1,10 +1,13 @@ 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, 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, + ui::{ + fonts::ConrodVoxygenFonts, + slot::{ContentSize, SlotMaker}, + }, window::GameInput, GlobalState, }; @@ -15,7 +18,7 @@ use common::{ tool::{DebugKind, StaffKind, Tool, ToolKind}, ItemKind, }, - CharacterState, ControllerInputs, Energy, Loadout, Stats, + CharacterState, ControllerInputs, Energy, Inventory, Loadout, Stats, }, }; use conrod_core::{ @@ -23,8 +26,8 @@ use conrod_core::{ widget::{self, Button, Image, Rectangle, Text}, widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, }; - use std::time::{Duration, Instant}; +use vek::*; /* use const_tweaker::tweak; #[tweak(min = 0.0, max = 1.0, step = 0.01)] @@ -130,12 +133,16 @@ pub enum ResourceType { pub struct Skillbar<'a> { global_state: &'a GlobalState, imgs: &'a Imgs, + item_imgs: &'a ItemImgs, fonts: &'a ConrodVoxygenFonts, stats: &'a Stats, loadout: &'a Loadout, energy: &'a Energy, character_state: &'a CharacterState, controller: &'a ControllerInputs, + inventory: &'a Inventory, + hotbar: &'a hotbar::State, + slot_manager: &'a mut slots::SlotManager, pulse: f32, #[conrod(common_builder)] common: widget::CommonBuilder, @@ -146,6 +153,7 @@ impl<'a> Skillbar<'a> { pub fn new( global_state: &'a GlobalState, imgs: &'a Imgs, + item_imgs: &'a ItemImgs, fonts: &'a ConrodVoxygenFonts, stats: &'a Stats, loadout: &'a Loadout, @@ -153,10 +161,14 @@ impl<'a> Skillbar<'a> { character_state: &'a CharacterState, pulse: f32, controller: &'a ControllerInputs, + inventory: &'a Inventory, + hotbar: &'a hotbar::State, + slot_manager: &'a mut slots::SlotManager, ) -> Self { Self { global_state, imgs, + item_imgs, fonts, stats, loadout, @@ -166,6 +178,9 @@ impl<'a> Skillbar<'a> { character_state, pulse, controller, + inventory, + hotbar, + slot_manager, } } } @@ -783,51 +798,53 @@ impl<'a> Widget for Skillbar<'a> { }, ) .set(state.ids.m2_content, 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, + background_color: Some(BG_COLOR), + content_size: ContentSize { + width_height_ratio: 1.0, + max_fraction: 0.9, + }, + 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: &content_source, + image_source: &image_source, + slot_manager: Some(self.slot_manager), + }; //Slot 5 - Image::new(self.imgs.skillbar_slot) - .w_h(20.0 * scale, 20.0 * scale) + 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) .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) + slot_maker + .fabricate(hotbar::Slot::Four, [20.0 * scale as f32; 2]) .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) + slot_maker + .fabricate(hotbar::Slot::Three, [20.0 * scale as f32; 2]) .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) + slot_maker + .fabricate(hotbar::Slot::Two, [20.0 * scale as f32; 2]) .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 { - /* + /*match self.character_state { CharacterState::Charge { time_left } => { let fade = time_left.as_secs_f32() * 10.0; Image::new(self.imgs.skillbar_slot_l) @@ -845,29 +862,14 @@ impl<'a> Widget for Skillbar<'a> { ))) .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); }, - } - 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), - }, - ) - .middle_of(state.ids.slot1) - .set(state.ids.slot1_bg, ui); + }*/ + slot_maker + .fabricate(hotbar::Slot::One, [20.0 * scale as f32; 2]) + .left_from(state.ids.slot2, 0.0) + .set(state.ids.slot1, ui); // TODO: Changeable slot image - match self.loadout.active_item.as_ref().map(|i| &i.item.kind) { + /*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) @@ -883,65 +885,43 @@ impl<'a> Widget for Skillbar<'a> { _ => {}, }, _ => {}, - } + }*/ // Slot 6 - Image::new(self.imgs.skillbar_slot) - .w_h(20.0 * scale, 20.0 * scale) + 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) .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 7 - Image::new(self.imgs.skillbar_slot) - .w_h(20.0 * scale, 20.0 * scale) + slot_maker + .fabricate(hotbar::Slot::Seven, [20.0 * scale as f32; 2]) .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); // Slot 8 - Image::new(self.imgs.skillbar_slot) - .w_h(20.0 * scale, 20.0 * scale) + slot_maker + .fabricate(hotbar::Slot::Eight, [20.0 * scale as f32; 2]) .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); // Slot 9 - Image::new(self.imgs.skillbar_slot) - .w_h(20.0 * scale, 20.0 * scale) + slot_maker + .fabricate(hotbar::Slot::Nine, [20.0 * scale as f32; 2]) .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); // Quickslot - Image::new(self.imgs.skillbar_slot_r) - .w_h(20.0 * scale, 20.0 * scale) + slot_maker.filled_slot = self.imgs.skillbar_slot_r; + slot_maker.selected_slot = self.imgs.skillbar_slot_r; + slot_maker.empty_slot = self.imgs.skillbar_slot_r; + slot_maker + .fabricate(hotbar::Slot::Ten, [20.0 * scale as f32; 2]) .right_from(state.ids.slot9, 0.0) .set(state.ids.slot10, ui); - Image::new(self.imgs.skillbar_slot_bg) - .w_h(19.0 * scale, 19.0 * scale) - .color(Some(BG_COLOR)) - .middle_of(state.ids.slot10) - .set(state.ids.slot10_bg, ui); - // Shortcuts + // Shortcuts if let ShortcutNumbers::On = shortcuts { if let Some(slot1) = &self .global_state .settings .controls - .get_binding(GameInput::Ability3) + .get_binding(GameInput::Slot1) { Text::new(slot1.to_string().as_str()) .top_right_with_margins_on(state.ids.slot1_bg, 1.0, 2.0) diff --git a/voxygen/src/hud/slots.rs b/voxygen/src/hud/slots.rs index 3e22307cbd..52797b232e 100644 --- a/voxygen/src/hud/slots.rs +++ b/voxygen/src/hud/slots.rs @@ -1,7 +1,11 @@ -use super::item_imgs::{ItemImgs, ItemKey}; +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, Inventory, Loadout}; -use conrod_core::image; +use common::comp::{item::ItemKind, Energy, Inventory, Loadout}; +use conrod_core::{image, Color}; pub use common::comp::slot::{ArmorSlot, EquipSlot}; @@ -9,8 +13,8 @@ pub use common::comp::slot::{ArmorSlot, EquipSlot}; pub enum SlotKind { Inventory(InventorySlot), Equip(EquipSlot), - /*Hotbar(HotbarSlot), - *Spellbook(SpellbookSlot), TODO */ + Hotbar(HotbarSlot), + /* Spellbook(SpellbookSlot), TODO */ } pub type SlotManager = slot::SlotManager; @@ -18,25 +22,11 @@ pub type SlotManager = slot::SlotManager; #[derive(Clone, Copy, PartialEq)] pub struct InventorySlot(pub usize); -/*#[derive(Clone, Copy, PartialEq)] -pub enum HotbarSlot { - One, - Two, - Three, - Four, - Five, - Six, - Seven, - Eight, - Nine, - Ten, -}*/ - impl SlotKey for InventorySlot { type ImageKey = ItemKey; - fn image_key(&self, source: &Inventory) -> Option { - source.get(self.0).map(Into::into) + 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 { @@ -59,7 +49,7 @@ impl SlotKey for InventorySlot { impl SlotKey for EquipSlot { type ImageKey = ItemKey; - fn image_key(&self, source: &Loadout) -> Option { + 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(), @@ -77,7 +67,7 @@ impl SlotKey for EquipSlot { EquipSlot::Lantern => source.lantern.as_ref(), }; - item.map(Into::into) + item.map(|i| (i.into(), None)) } fn amount(&self, _: &Loadout) -> Option { None } @@ -87,18 +77,58 @@ impl SlotKey for EquipSlot { } } -/*impl SlotKey for HotbarSlot { - type ImageKey = ItemKey; +#[derive(Clone, PartialEq)] +pub enum HotbarImage { + Item(ItemKey), + Ability3, +} - fn image_key(&self, source: &Inventory) -> Option { - source.get(self.0).map(Into::into) +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, source: &Inventory) -> Option { - source - .get(self.0) + 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::Armor { .. } => None, + ItemKind::Tool { .. } | ItemKind::Lantern(_) | ItemKind::Armor { .. } => None, ItemKind::Utility { amount, .. } | ItemKind::Consumable { amount, .. } | ItemKind::Ingredient { amount, .. } => Some(amount), @@ -106,10 +136,13 @@ impl SlotKey for EquipSlot { .filter(|amount| *amount > 1) } - fn image_id(key: &Self::ImageKey, source: &ItemImgs) -> image::Id { - source.img_id_or_not_found_img(key.clone()) + 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) } @@ -119,8 +152,8 @@ 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 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/session.rs b/voxygen/src/session.rs index 80b121c897..564a2c375f 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -282,9 +282,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 @@ -646,6 +643,7 @@ impl PlayState for SessionState { 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(); diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index 8ea726fa60..899e85261d 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -138,7 +138,7 @@ impl ControlSettings { GameInput::ToggleWield => KeyMouse::Key(VirtualKeyCode::T), //GameInput::Charge => KeyMouse::Key(VirtualKeyCode::Key1), GameInput::FreeLook => KeyMouse::Key(VirtualKeyCode::L), - GameInput::Ability3 => KeyMouse::Key(VirtualKeyCode::Key1), + GameInput::Slot1 => KeyMouse::Key(VirtualKeyCode::Key1), GameInput::Slot2 => KeyMouse::Key(VirtualKeyCode::Key2), GameInput::Slot3 => KeyMouse::Key(VirtualKeyCode::Key3), GameInput::Slot4 => KeyMouse::Key(VirtualKeyCode::Key4), @@ -196,7 +196,7 @@ impl Default for ControlSettings { GameInput::ToggleWield, //GameInput::Charge, GameInput::FreeLook, - GameInput::Ability3, + GameInput::Slot1, GameInput::Slot2, GameInput::Slot3, GameInput::Slot4, diff --git a/voxygen/src/ui/widgets/slot.rs b/voxygen/src/ui/widgets/slot.rs index 82607485e4..7c0344cd41 100644 --- a/voxygen/src/ui/widgets/slot.rs +++ b/voxygen/src/ui/widgets/slot.rs @@ -13,7 +13,7 @@ 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; + 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; } @@ -285,6 +285,18 @@ where _ => 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)] @@ -435,7 +447,9 @@ where } = self; // If the key changed update the cached image id - let image_key = slot_key.image_key(content_source); + 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| { @@ -510,6 +524,7 @@ where }) .map(|e| e as f64) .into_array()) + .color(content_color) .parent(id) .graphics_for(id) .set(state.ids.content, ui); diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index 7f15ee71df..45ef3bc0e3 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -17,7 +17,7 @@ use vek::*; pub enum GameInput { Primary, Secondary, - Ability3, + Slot1, Slot2, Slot3, Slot4, @@ -99,7 +99,7 @@ impl GameInput { GameInput::ToggleWield => "gameinput.togglewield", //GameInput::Charge => "gameinput.charge", GameInput::FreeLook => "gameinput.freelook", - GameInput::Ability3 => "gameinput.slot1", + GameInput::Slot1 => "gameinput.slot1", GameInput::Slot2 => "gameinput.slot2", GameInput::Slot3 => "gameinput.slot3", GameInput::Slot4 => "gameinput.slot4", From 2c4c006396c3d35ed9d9878dd71f301a2b8f5d85 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sat, 11 Apr 2020 13:43:45 -0400 Subject: [PATCH 052/195] Lantern tweaks, remove unused bones --- ..._lantern_manifest.ron => humanoid_lantern_manifest.ron} | 4 ++-- .../lantern/lantern_black-0.vox => lantern/black-0.vox} | 0 .../lantern/lantern_green-0.vox => lantern/green-0.vox} | 0 voxygen/src/anim/biped_large/mod.rs | 5 +---- voxygen/src/anim/bird_medium/mod.rs | 5 +---- voxygen/src/anim/bird_small/mod.rs | 5 +---- voxygen/src/anim/character/mod.rs | 7 ++----- voxygen/src/anim/character/stand.rs | 2 +- voxygen/src/anim/critter/mod.rs | 5 +---- voxygen/src/anim/dragon/mod.rs | 5 +---- voxygen/src/anim/fish_medium/mod.rs | 5 +---- voxygen/src/anim/fish_small/mod.rs | 5 +---- voxygen/src/anim/fixture/mod.rs | 5 +---- voxygen/src/anim/mod.rs | 2 +- voxygen/src/anim/object/mod.rs | 5 +---- voxygen/src/anim/quadruped_medium/mod.rs | 5 +---- voxygen/src/anim/quadruped_small/mod.rs | 5 +---- voxygen/src/scene/figure/cache.rs | 2 +- voxygen/src/scene/figure/load.rs | 3 +-- 19 files changed, 19 insertions(+), 56 deletions(-) rename assets/voxygen/voxel/{humanoid_armor_lantern_manifest.ron => humanoid_lantern_manifest.ron} (63%) rename assets/voxygen/voxel/{armor/lantern/lantern_black-0.vox => lantern/black-0.vox} (100%) rename assets/voxygen/voxel/{armor/lantern/lantern_green-0.vox => lantern/green-0.vox} (100%) diff --git a/assets/voxygen/voxel/humanoid_armor_lantern_manifest.ron b/assets/voxygen/voxel/humanoid_lantern_manifest.ron similarity index 63% rename from assets/voxygen/voxel/humanoid_armor_lantern_manifest.ron rename to assets/voxygen/voxel/humanoid_lantern_manifest.ron index 8f4a831912..2eab34212a 100644 --- a/assets/voxygen/voxel/humanoid_armor_lantern_manifest.ron +++ b/assets/voxygen/voxel/humanoid_lantern_manifest.ron @@ -5,11 +5,11 @@ ), map: { Green0: ( - vox_spec: ("armor.lantern.green-0", (0.0, 0.0, 0.0)), + vox_spec: ("lantern.green-0", (0.0, 0.0, 0.0)), color: None ), Black0: ( - vox_spec: ("armor.lantern.black-0", (0.0, 0.0, 0.0)), + vox_spec: ("lantern.black-0", (0.0, 0.0, 0.0)), color: None ), }, diff --git a/assets/voxygen/voxel/armor/lantern/lantern_black-0.vox b/assets/voxygen/voxel/lantern/black-0.vox similarity index 100% rename from assets/voxygen/voxel/armor/lantern/lantern_black-0.vox rename to assets/voxygen/voxel/lantern/black-0.vox diff --git a/assets/voxygen/voxel/armor/lantern/lantern_green-0.vox b/assets/voxygen/voxel/lantern/green-0.vox similarity index 100% rename from assets/voxygen/voxel/armor/lantern/lantern_green-0.vox rename to assets/voxygen/voxel/lantern/green-0.vox diff --git a/voxygen/src/anim/biped_large/mod.rs b/voxygen/src/anim/biped_large/mod.rs index 0a066963a4..85611e944e 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; 19] { + 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(); @@ -78,9 +78,6 @@ impl Skeleton for BipedLargeSkeleton { FigureBoneData::default(), FigureBoneData::default(), FigureBoneData::default(), - FigureBoneData::default(), - FigureBoneData::default(), - FigureBoneData::default(), ] } diff --git a/voxygen/src/anim/bird_medium/mod.rs b/voxygen/src/anim/bird_medium/mod.rs index 0d134a31cb..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; 19] { + fn compute_matrices(&self) -> [FigureBoneData; 16] { let torso_mat = self.torso.compute_base_matrix(); [ @@ -47,9 +47,6 @@ impl Skeleton for BirdMediumSkeleton { FigureBoneData::default(), 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 b2989c9cee..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; 19] { + fn compute_matrices(&self) -> [FigureBoneData; 16] { let torso_mat = self.torso.compute_base_matrix(); [ @@ -51,9 +51,6 @@ impl Skeleton for BirdSmallSkeleton { FigureBoneData::default(), FigureBoneData::default(), FigureBoneData::default(), - FigureBoneData::default(), - FigureBoneData::default(), - FigureBoneData::default(), ] } diff --git a/voxygen/src/anim/character/mod.rs b/voxygen/src/anim/character/mod.rs index 8dc6e083f3..5268345286 100644 --- a/voxygen/src/anim/character/mod.rs +++ b/voxygen/src/anim/character/mod.rs @@ -62,7 +62,7 @@ impl CharacterSkeleton { impl Skeleton for CharacterSkeleton { type Attr = SkeletonAttr; - fn compute_matrices(&self) -> [FigureBoneData; 19] { + 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(); @@ -90,10 +90,7 @@ impl Skeleton for CharacterSkeleton { 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::default(), ] } diff --git a/voxygen/src/anim/character/stand.rs b/voxygen/src/anim/character/stand.rs index 97baf7bba5..2159798342 100644 --- a/voxygen/src/anim/character/stand.rs +++ b/voxygen/src/anim/character/stand.rs @@ -105,7 +105,7 @@ impl Animation for StandAnimation { next.lantern.offset = Vec3::new(0.0, 0.0, 0.0); next.lantern.ori = Quaternion::rotation_x(0.0); - next.lantern.scale = Vec3::one() * 50.0; + next.lantern.scale = Vec3::one() * 1.0; 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/critter/mod.rs b/voxygen/src/anim/critter/mod.rs index 3c0538b5e4..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; 19] { + fn compute_matrices(&self) -> [FigureBoneData; 16] { [ FigureBoneData::new(self.head.compute_base_matrix()), FigureBoneData::new(self.chest.compute_base_matrix()), @@ -50,9 +50,6 @@ impl Skeleton for CritterSkeleton { FigureBoneData::default(), 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 5121b7f7c0..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; 19] { + 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,9 +72,6 @@ impl Skeleton for DragonSkeleton { FigureBoneData::default(), 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 919a08918c..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; 19] { + fn compute_matrices(&self) -> [FigureBoneData; 16] { let torso_mat = self.torso.compute_base_matrix(); let rear_mat = self.rear.compute_base_matrix(); @@ -56,9 +56,6 @@ impl Skeleton for FishMediumSkeleton { FigureBoneData::default(), 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 1b652c61c5..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; 19] { + fn compute_matrices(&self) -> [FigureBoneData; 16] { let torso_mat = self.torso.compute_base_matrix(); [ @@ -47,9 +47,6 @@ impl Skeleton for FishSmallSkeleton { FigureBoneData::default(), 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 9b45c2fca8..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; 19] { + fn compute_matrices(&self) -> [FigureBoneData; 16] { [ FigureBoneData::new(vek::Mat4::identity()), FigureBoneData::new(vek::Mat4::identity()), @@ -31,9 +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()), - FigureBoneData::new(vek::Mat4::identity()), ] } diff --git a/voxygen/src/anim/mod.rs b/voxygen/src/anim/mod.rs index 82950e49da..7d6e5b2086 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; 19]; + fn compute_matrices(&self) -> [FigureBoneData; 16]; /// Change the current skeleton to be more like `target`. fn interpolate(&mut self, target: &Self, dt: f32); diff --git a/voxygen/src/anim/object/mod.rs b/voxygen/src/anim/object/mod.rs index e2b587139e..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; 19] { + fn compute_matrices(&self) -> [FigureBoneData; 16] { [ FigureBoneData::new(Mat4::scaling_3d(Vec3::broadcast(SCALE))), FigureBoneData::new(Mat4::scaling_3d(Vec3::broadcast(SCALE))), @@ -33,9 +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))), - 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 3be2ef9599..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; 19] { + 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,9 +53,6 @@ impl Skeleton for QuadrupedMediumSkeleton { FigureBoneData::default(), 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 c76b1e6a92..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; 19] { + fn compute_matrices(&self) -> [FigureBoneData; 16] { [ FigureBoneData::new(self.head.compute_base_matrix()), FigureBoneData::new(self.chest.compute_base_matrix()), @@ -44,9 +44,6 @@ impl Skeleton for QuadrupedSmallSkeleton { FigureBoneData::default(), FigureBoneData::default(), FigureBoneData::default(), - FigureBoneData::default(), - FigureBoneData::default(), - FigureBoneData::default(), ] } diff --git a/voxygen/src/scene/figure/cache.rs b/voxygen/src/scene/figure/cache.rs index cd7d738fd2..7903b30d3c 100644 --- a/voxygen/src/scene/figure/cache.rs +++ b/voxygen/src/scene/figure/cache.rs @@ -238,8 +238,8 @@ impl FigureModelCache { } else { None }, - Some(humanoid_armor_lantern_spec.mesh_lantern(&body, loadout)), 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 6c3d24a37a..5f9762bcf3 100644 --- a/voxygen/src/scene/figure/load.rs +++ b/voxygen/src/scene/figure/load.rs @@ -696,8 +696,7 @@ impl HumMainWeaponSpec { // Lantern impl HumArmorLanternSpec { pub fn load_watched(indicator: &mut ReloadIndicator) -> Arc { - assets::load_watched::("voxygen.voxel.humanoid_armor_lantern_manifest", indicator) - .unwrap() + assets::load_watched::("voxygen.voxel.humanoid_lantern_manifest", indicator).unwrap() } pub fn mesh_lantern(&self, body: &Body, loadout: &Loadout) -> Mesh { From d7e605e658eb2d3fcfdc331ec00372f5ae4b50d2 Mon Sep 17 00:00:00 2001 From: jshipsey Date: Sat, 11 Apr 2020 21:02:23 -0400 Subject: [PATCH 053/195] lantern visuals, random cape flutter --- assets/voxygen/voxel/armor/lantern/black-0.vox | 3 +++ assets/voxygen/voxel/armor/lantern/green-0.vox | 3 +++ .../voxel/humanoid_lantern_manifest.ron | 4 ++-- voxygen/src/anim/character/alpha.rs | 17 +++++++++++++---- voxygen/src/anim/character/beta.rs | 7 ++++--- voxygen/src/anim/character/charge.rs | 6 +++--- voxygen/src/anim/character/climb.rs | 9 +++++---- voxygen/src/anim/character/dash.rs | 7 ++++--- voxygen/src/anim/character/jump.rs | 7 ++++--- voxygen/src/anim/character/mod.rs | 8 +++++--- voxygen/src/anim/character/roll.rs | 16 ++++++++-------- voxygen/src/anim/character/run.rs | 16 ++++++++++++---- voxygen/src/anim/character/shoot.rs | 7 ++++--- voxygen/src/anim/character/spin.rs | 7 ++++--- voxygen/src/anim/character/stand.rs | 18 +++++++++--------- voxygen/src/anim/character/swim.rs | 6 +++--- voxygen/src/anim/character/wield.rs | 6 +++++- 17 files changed, 91 insertions(+), 56 deletions(-) create mode 100644 assets/voxygen/voxel/armor/lantern/black-0.vox create mode 100644 assets/voxygen/voxel/armor/lantern/green-0.vox 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/humanoid_lantern_manifest.ron b/assets/voxygen/voxel/humanoid_lantern_manifest.ron index 2eab34212a..2ad5c02a5b 100644 --- a/assets/voxygen/voxel/humanoid_lantern_manifest.ron +++ b/assets/voxygen/voxel/humanoid_lantern_manifest.ron @@ -5,11 +5,11 @@ ), map: { Green0: ( - vox_spec: ("lantern.green-0", (0.0, 0.0, 0.0)), + vox_spec: ("armor.lantern.green-0", (-2.0, -2.0, -7.0)), color: None ), Black0: ( - vox_spec: ("lantern.black-0", (0.0, 0.0, 0.0)), + vox_spec: ("armor.lantern.black-0", (-2.0, -2.0, -7.0)), color: None ), }, 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 bd5d9d6b2a..ea016471d8 100644 --- a/voxygen/src/anim/character/charge.rs +++ b/voxygen/src/anim/character/charge.rs @@ -175,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 d2ea2f66da..5787dd4fc3 100644 --- a/voxygen/src/anim/character/climb.rs +++ b/voxygen/src/anim/character/climb.rs @@ -58,7 +58,7 @@ impl Animation for ClimbAnimation { next.belt.scale = Vec3::one(); next.back.offset = Vec3::new(0.0, -2.8, 7.25); - next.back.ori = Quaternion::rotation_z(-0.2); + 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); @@ -111,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 82d09590c3..9961a6e356 100644 --- a/voxygen/src/anim/character/jump.rs +++ b/voxygen/src/anim/character/jump.rs @@ -98,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 5268345286..9f50a3bd06 100644 --- a/voxygen/src/anim/character/mod.rs +++ b/voxygen/src/anim/character/mod.rs @@ -72,14 +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.back.compute_base_matrix()), - FigureBoneData::new(torso_mat * chest_mat * self.shorts.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()), @@ -89,7 +89,9 @@ 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 * chest_mat * shorts_mat * self.lantern.compute_base_matrix(), + ), FigureBoneData::default(), ] } 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 3c9917ae2b..7c475ba3bb 100644 --- a/voxygen/src/anim/character/run.rs +++ b/voxygen/src/anim/character/run.rs @@ -31,6 +31,13 @@ impl Animation for RunAnimation { / (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 shorte = (((5.0) + / (4.0 + 1.0 * ((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 shortalt = (((5.0) / (1.5 @@ -91,7 +98,7 @@ impl Animation for RunAnimation { next.belt.scale = Vec3::one(); next.back.offset = Vec3::new(0.0, -2.8, 7.25); - next.back.ori = Quaternion::rotation_x(-0.2 + short * 0.2); + 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); @@ -152,9 +159,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 f064477536..50525f6a49 100644 --- a/voxygen/src/anim/character/shoot.rs +++ b/voxygen/src/anim/character/shoot.rs @@ -154,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/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 2159798342..486d350313 100644 --- a/voxygen/src/anim/character/stand.rs +++ b/voxygen/src/anim/character/stand.rs @@ -44,19 +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.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); //2 - next.shorts.ori = Quaternion::rotation_x(head_look.x * -0.2); - next.shorts.scale = Vec3::one() + breathe * -0.05; + 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); @@ -103,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() * 1.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 0b2102f9ff..1fd23c4c8a 100644 --- a/voxygen/src/anim/character/swim.rs +++ b/voxygen/src/anim/character/swim.rs @@ -115,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 c0c5143a0b..403796c601 100644 --- a/voxygen/src/anim/character/wield.rs +++ b/voxygen/src/anim/character/wield.rs @@ -27,6 +27,8 @@ impl Animation for WieldAnimation { / (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 @@ -199,7 +201,9 @@ impl Animation for WieldAnimation { 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.4 + short * 0.3); + 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); From cae5313127481eea77958ab1ea60a04473e66b93 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sat, 11 Apr 2020 21:20:48 -0400 Subject: [PATCH 054/195] tooltips for the skillbar --- voxygen/src/hud/mod.rs | 3 + voxygen/src/hud/skillbar.rs | 221 +++++++++++++++++++++--------- voxygen/src/ui/widgets/tooltip.rs | 14 +- 3 files changed, 170 insertions(+), 68 deletions(-) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 93899d73b8..1f713de20e 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -1703,6 +1703,7 @@ impl Hud { &self.imgs, &self.item_imgs, &self.fonts, + &self.rot_imgs, &stats, &loadout, &energy, @@ -1711,7 +1712,9 @@ impl Hud { &controller, &inventory, &self.hotbar, + tooltip_manager, &mut self.slot_manager, + &self.voxygen_i18n, ) .set(self.ids.skillbar, ui_widgets); } diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index e8bb9f3dce..11dd6ed3db 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -1,25 +1,26 @@ use super::{ - hotbar, img_ids::Imgs, item_imgs::ItemImgs, slots, 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}, + 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, Inventory, Loadout, Stats, +use common::comp::{ + item::{ + tool::{DebugKind, StaffKind, Tool, ToolKind}, + ItemKind, }, + CharacterState, ControllerInputs, Energy, Inventory, Loadout, Stats, }; use conrod_core::{ color, @@ -64,45 +65,34 @@ widget_ids! { m2_slot_act, m2_content, slot1, - slot1_bg, slot1_text, slot1_text_bg, - slot1_icon, - slot1_act, + //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, slot9_text_bg, slot10, - slot10_bg, slot10_text, slot10_text_bg, healthbar_bg, @@ -135,6 +125,7 @@ pub struct Skillbar<'a> { imgs: &'a Imgs, item_imgs: &'a ItemImgs, fonts: &'a ConrodVoxygenFonts, + rot_imgs: &'a ImgsRot, stats: &'a Stats, loadout: &'a Loadout, energy: &'a Energy, @@ -142,7 +133,9 @@ pub struct Skillbar<'a> { 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, @@ -155,6 +148,7 @@ impl<'a> Skillbar<'a> { imgs: &'a Imgs, item_imgs: &'a ItemImgs, fonts: &'a ConrodVoxygenFonts, + rot_imgs: &'a ImgsRot, stats: &'a Stats, loadout: &'a Loadout, energy: &'a Energy, @@ -163,13 +157,16 @@ impl<'a> Skillbar<'a> { 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, @@ -180,7 +177,9 @@ impl<'a> Skillbar<'a> { controller, inventory, hotbar, + tooltip_manager, slot_manager, + localized_strings, } } } @@ -234,9 +233,7 @@ impl<'a> Widget for Skillbar<'a> { 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, - )); + let localized_strings = self.localized_strings; // Stamina Wheel /* @@ -821,26 +818,82 @@ impl<'a> Widget for Skillbar<'a> { 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, + ) + }) + .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 - slot_maker + 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) - .set(state.ids.slot5, ui); + .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 - slot_maker + let slot = slot_maker .fabricate(hotbar::Slot::Four, [20.0 * scale as f32; 2]) - .left_from(state.ids.slot5, 0.0) - .set(state.ids.slot4, ui); + .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 - slot_maker + let slot = slot_maker .fabricate(hotbar::Slot::Three, [20.0 * scale as f32; 2]) - .left_from(state.ids.slot4, 0.0) - .set(state.ids.slot3, ui); + .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 - slot_maker + let slot = slot_maker .fabricate(hotbar::Slot::Two, [20.0 * scale as f32; 2]) - .left_from(state.ids.slot3, 0.0) - .set(state.ids.slot2, ui); + .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 // TODO: Don't hardcode this to one Skill... // Frame flashes whenever the active skill inside this slot is activated @@ -864,10 +917,16 @@ impl<'a> Widget for Skillbar<'a> { .set(state.ids.slot1_act, ui); }, }*/ - slot_maker + let slot = slot_maker .fabricate(hotbar::Slot::One, [20.0 * scale as f32; 2]) - .left_from(state.ids.slot2, 0.0) - .set(state.ids.slot1, ui); + .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); + } // TODO: Changeable slot image /*match self.loadout.active_item.as_ref().map(|i| &i.item.kind) { Some(ItemKind::Tool(Tool { kind, .. })) => match kind { @@ -887,33 +946,63 @@ impl<'a> Widget for Skillbar<'a> { _ => {}, }*/ // Slot 6 - slot_maker + 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) - .set(state.ids.slot6, ui); + .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 - slot_maker + let slot = slot_maker .fabricate(hotbar::Slot::Seven, [20.0 * scale as f32; 2]) - .right_from(state.ids.slot6, 0.0) - .set(state.ids.slot7, ui); + .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 - slot_maker + let slot = slot_maker .fabricate(hotbar::Slot::Eight, [20.0 * scale as f32; 2]) - .right_from(state.ids.slot7, 0.0) - .set(state.ids.slot8, ui); + .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 - slot_maker + let slot = slot_maker .fabricate(hotbar::Slot::Nine, [20.0 * scale as f32; 2]) - .right_from(state.ids.slot8, 0.0) - .set(state.ids.slot9, ui); + .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 slot_maker.filled_slot = self.imgs.skillbar_slot_r; slot_maker.selected_slot = self.imgs.skillbar_slot_r; slot_maker.empty_slot = self.imgs.skillbar_slot_r; - slot_maker + let slot = slot_maker .fabricate(hotbar::Slot::Ten, [20.0 * scale as f32; 2]) - .right_from(state.ids.slot9, 0.0) - .set(state.ids.slot10, ui); + .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 { @@ -924,7 +1013,7 @@ impl<'a> Widget for Skillbar<'a> { .get_binding(GameInput::Slot1) { Text::new(slot1.to_string().as_str()) - .top_right_with_margins_on(state.ids.slot1_bg, 1.0, 2.0) + .top_right_with_margins_on(state.ids.slot1, 2.0, 2.0) .font_size(self.fonts.cyri.scale(8)) .font_id(self.fonts.cyri.conrod_id) .color(BLACK) @@ -943,7 +1032,7 @@ impl<'a> Widget for Skillbar<'a> { .get_binding(GameInput::Slot2) { Text::new(slot2.to_string().as_str()) - .top_right_with_margins_on(state.ids.slot2_bg, 1.0, 1.0) + .top_right_with_margins_on(state.ids.slot2, 2.0, 2.0) .font_size(self.fonts.cyri.scale(8)) .font_id(self.fonts.cyri.conrod_id) .color(BLACK) @@ -962,7 +1051,7 @@ impl<'a> Widget for Skillbar<'a> { .get_binding(GameInput::Slot3) { Text::new(slot3.to_string().as_str()) - .top_right_with_margins_on(state.ids.slot3_bg, 1.0, 1.0) + .top_right_with_margins_on(state.ids.slot3, 2.0, 2.0) .font_size(self.fonts.cyri.scale(8)) .font_id(self.fonts.cyri.conrod_id) .color(BLACK) @@ -981,7 +1070,7 @@ impl<'a> Widget for Skillbar<'a> { .get_binding(GameInput::Slot4) { Text::new(slot4.to_string().as_str()) - .top_right_with_margins_on(state.ids.slot4_bg, 1.0, 1.0) + .top_right_with_margins_on(state.ids.slot4, 2.0, 2.0) .font_size(self.fonts.cyri.scale(8)) .font_id(self.fonts.cyri.conrod_id) .color(BLACK) @@ -1000,7 +1089,7 @@ impl<'a> Widget for Skillbar<'a> { .get_binding(GameInput::Slot5) { Text::new(slot5.to_string().as_str()) - .top_right_with_margins_on(state.ids.slot5_bg, 1.0, 1.0) + .top_right_with_margins_on(state.ids.slot5, 2.0, 2.0) .font_size(self.fonts.cyri.scale(8)) .font_id(self.fonts.cyri.conrod_id) .color(BLACK) @@ -1057,7 +1146,7 @@ impl<'a> Widget for Skillbar<'a> { .get_binding(GameInput::Slot6) { Text::new(slot6.to_string().as_str()) - .top_right_with_margins_on(state.ids.slot6_bg, 1.0, 1.0) + .top_right_with_margins_on(state.ids.slot6, 2.0, 2.0) .font_size(self.fonts.cyri.scale(8)) .font_id(self.fonts.cyri.conrod_id) .color(BLACK) @@ -1076,7 +1165,7 @@ impl<'a> Widget for Skillbar<'a> { .get_binding(GameInput::Slot7) { Text::new(slot7.to_string().as_str()) - .top_right_with_margins_on(state.ids.slot7_bg, 1.0, 1.0) + .top_right_with_margins_on(state.ids.slot7, 2.0, 2.0) .font_size(self.fonts.cyri.scale(8)) .font_id(self.fonts.cyri.conrod_id) .color(BLACK) @@ -1095,7 +1184,7 @@ impl<'a> Widget for Skillbar<'a> { .get_binding(GameInput::Slot8) { Text::new(slot8.to_string().as_str()) - .top_right_with_margins_on(state.ids.slot8_bg, 1.0, 1.0) + .top_right_with_margins_on(state.ids.slot8, 2.0, 2.0) .font_size(self.fonts.cyri.scale(8)) .font_id(self.fonts.cyri.conrod_id) .color(BLACK) @@ -1114,7 +1203,7 @@ impl<'a> Widget for Skillbar<'a> { .get_binding(GameInput::Slot9) { Text::new(slot9.to_string().as_str()) - .top_right_with_margins_on(state.ids.slot9_bg, 1.0, 1.0) + .top_right_with_margins_on(state.ids.slot9, 2.0, 2.0) .font_size(self.fonts.cyri.scale(8)) .font_id(self.fonts.cyri.conrod_id) .color(BLACK) @@ -1133,7 +1222,7 @@ impl<'a> Widget for Skillbar<'a> { .get_binding(GameInput::Slot10) { Text::new(slot10.to_string().as_str()) - .top_right_with_margins_on(state.ids.slot10_bg, 1.0, 1.0) + .top_right_with_margins_on(state.ids.slot10, 2.0, 2.0) .font_size(self.fonts.cyri.scale(8)) .font_id(self.fonts.cyri.conrod_id) .color(BLACK) 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, } } From c6b55706375cc417446acdc16370e7a29e980a31 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sat, 11 Apr 2020 21:51:36 -0400 Subject: [PATCH 055/195] Move item amount text to bottom right --- voxygen/src/hud/skillbar.rs | 2 +- voxygen/src/ui/widgets/slot.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index 11dd6ed3db..8879a42623 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -811,7 +811,7 @@ impl<'a> Widget for Skillbar<'a> { }, selected_content_scale: 1.067, amount_font: self.fonts.cyri.conrod_id, - amount_margins: Vec2::new(-4.0, 0.0), + amount_margins: Vec2::new(3.0, 2.0), amount_font_size: self.fonts.cyri.scale(12), amount_text_color: TEXT_COLOR, content_source: &content_source, diff --git a/voxygen/src/ui/widgets/slot.rs b/voxygen/src/ui/widgets/slot.rs index 7c0344cd41..d45bbb5e20 100644 --- a/voxygen/src/ui/widgets/slot.rs +++ b/voxygen/src/ui/widgets/slot.rs @@ -537,7 +537,7 @@ where Text::new(&amount) .font_id(amount_font) .font_size(amount_font_size) - .top_right_with_margins_on( + .bottom_right_with_margins_on( state.ids.content, amount_margins.x as f64, amount_margins.y as f64, From b9a66a93b7405dae4d77a15dc7a874230cc00264 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sun, 12 Apr 2020 00:13:42 -0400 Subject: [PATCH 056/195] Add backgound to hotbar slots --- assets/voxygen/element/skillbar/skillbar_slot.png | 3 +++ assets/voxygen/element/skillbar/skillbar_slot.vox | 3 --- .../element/skillbar/skillbar_slot_active.png | 3 +++ .../element/skillbar/skillbar_slot_active.vox | 3 --- .../voxygen/element/skillbar/skillbar_slot_bg.vox | 3 --- .../voxygen/element/skillbar/skillbar_slot_l.png | 3 +++ .../voxygen/element/skillbar/skillbar_slot_l.vox | 3 --- .../element/skillbar/skillbar_slot_l_active.png | 3 +++ .../element/skillbar/skillbar_slot_l_active.vox | 3 --- .../voxygen/element/skillbar/skillbar_slot_r.png | 3 +++ .../voxygen/element/skillbar/skillbar_slot_r.vox | 3 --- .../element/skillbar/skillbar_slot_r_active.png | 3 +++ .../element/skillbar/skillbar_slot_r_active.vox | 3 --- voxygen/src/hud/img_ids.rs | 15 ++++++++------- voxygen/src/hud/skillbar.rs | 15 ++++++++++----- 15 files changed, 36 insertions(+), 33 deletions(-) create mode 100644 assets/voxygen/element/skillbar/skillbar_slot.png delete mode 100644 assets/voxygen/element/skillbar/skillbar_slot.vox create mode 100644 assets/voxygen/element/skillbar/skillbar_slot_active.png delete mode 100644 assets/voxygen/element/skillbar/skillbar_slot_active.vox delete mode 100644 assets/voxygen/element/skillbar/skillbar_slot_bg.vox create mode 100644 assets/voxygen/element/skillbar/skillbar_slot_l.png delete mode 100644 assets/voxygen/element/skillbar/skillbar_slot_l.vox create mode 100644 assets/voxygen/element/skillbar/skillbar_slot_l_active.png delete mode 100644 assets/voxygen/element/skillbar/skillbar_slot_l_active.vox create mode 100644 assets/voxygen/element/skillbar/skillbar_slot_r.png delete mode 100644 assets/voxygen/element/skillbar/skillbar_slot_r.vox create mode 100644 assets/voxygen/element/skillbar/skillbar_slot_r_active.png delete mode 100644 assets/voxygen/element/skillbar/skillbar_slot_r_active.vox diff --git a/assets/voxygen/element/skillbar/skillbar_slot.png b/assets/voxygen/element/skillbar/skillbar_slot.png new file mode 100644 index 0000000000..811579b09d --- /dev/null +++ b/assets/voxygen/element/skillbar/skillbar_slot.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3804ce85831c53666987128f7589faa40b08cd37f01a8d112bd7e2eca07cd724 +size 2344 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_l.png b/assets/voxygen/element/skillbar/skillbar_slot_l.png new file mode 100644 index 0000000000..69a87a9c96 --- /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:05cafd02908849e40fe20fb1cc2877b54ebb5d198b73663fe0c61e22ea3d021c +size 2343 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..c9caaf553e --- /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:f58abab20455ea0b8c5b96ad96b8c39d1d0aaac996afc507074be45cbc928b73 +size 2344 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/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index 719018b21d..98c3e7986f 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -25,13 +25,6 @@ image_ids! { 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", @@ -151,6 +144,14 @@ image_ids! { + // Skillbar + 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", diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index 8879a42623..784ec156ab 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -228,7 +228,6 @@ 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); @@ -803,8 +802,8 @@ impl<'a> Widget for Skillbar<'a> { // 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, - background_color: Some(BG_COLOR), + selected_slot: self.imgs.skillbar_slot_act, + background_color: None, content_size: ContentSize { width_height_ratio: 1.0, max_fraction: 0.9, @@ -917,6 +916,9 @@ impl<'a> Widget for Skillbar<'a> { .set(state.ids.slot1_act, ui); }, }*/ + 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); @@ -946,6 +948,9 @@ impl<'a> Widget for Skillbar<'a> { _ => {}, }*/ // Slot 6 + 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); @@ -990,9 +995,9 @@ impl<'a> Widget for Skillbar<'a> { slot.set(state.ids.slot9, ui); } // Quickslot - slot_maker.filled_slot = self.imgs.skillbar_slot_r; - slot_maker.selected_slot = self.imgs.skillbar_slot_r; 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); From 053458c2e36ee1df83f0f06ef9eafcaf0eff13b7 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sun, 12 Apr 2020 00:23:17 -0400 Subject: [PATCH 057/195] Changelog and small fix --- CHANGELOG.md | 3 +++ server/src/events/inventory_manip.rs | 6 +----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c02676c7ba..aecaf98d91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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 slotskillbars +- Added capes, lanterns, and more armor items ### Changed diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index 9181ce12f5..2228af5424 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -174,11 +174,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv }; if reinsert { - let _ = state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|inv| inv.insert(slot, item)); + let _ = inventory.insert(slot, item); } Some(comp::InventoryUpdateEvent::Used) From cee1c2f85b9d90e65ee7ceed5bf947ffaa6fcdbf Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Sun, 12 Apr 2020 18:35:41 +0200 Subject: [PATCH 058/195] cleanup, slot background, scaling --- assets/voxygen/element/misc_bg/level_down.png | 3 + assets/voxygen/element/misc_bg/level_down.vox | 3 - assets/voxygen/element/misc_bg/level_up.png | 3 + assets/voxygen/element/misc_bg/level_up.vox | 3 - .../voxygen/element/skillbar/bar_content.png | 3 + .../voxygen/element/skillbar/bar_content.vox | 3 - .../voxygen/element/skillbar/energybar_bg.png | 3 + .../voxygen/element/skillbar/energybar_bg.vox | 3 - .../voxygen/element/skillbar/healthbar_bg.png | 3 + .../voxygen/element/skillbar/healthbar_bg.vox | 3 - .../element/skillbar/skillbar_slot.png | 4 +- .../element/skillbar/skillbar_slot_big.png | 3 + .../element/skillbar/skillbar_slot_big.vox | 3 - .../skillbar/skillbar_slot_big_active.vox | 3 - .../element/skillbar/skillbar_slot_big_bg.vox | 3 - .../element/skillbar/skillbar_slot_l.png | 4 +- .../element/skillbar/skillbar_slot_r.png | 4 +- .../element/skillbar/stamina_wheel-0.vox | 3 - .../element/skillbar/stamina_wheel-1.vox | 3 - .../element/skillbar/stamina_wheel-2.vox | 3 - .../element/skillbar/stamina_wheel-3.vox | 3 - .../element/skillbar/stamina_wheel-4.vox | 3 - .../element/skillbar/stamina_wheel-5.vox | 3 - .../element/skillbar/stamina_wheel-6.vox | 3 - .../element/skillbar/stamina_wheel-7.vox | 3 - .../element/skillbar/stamina_wheel-empty.vox | 3 - .../element/skillbar/xp_bar_content.png | 3 + .../element/skillbar/xp_bar_content.vox | 3 - .../voxygen/element/skillbar/xp_bar_left.png | 3 + .../voxygen/element/skillbar/xp_bar_left.vox | 3 - .../voxygen/element/skillbar/xp_bar_mid.png | 3 + .../voxygen/element/skillbar/xp_bar_mid.vox | 3 - .../voxygen/element/skillbar/xp_bar_right.png | 3 + .../voxygen/element/skillbar/xp_bar_right.vox | 3 - voxygen/src/hud/bag.rs | 9 +- voxygen/src/hud/img_ids.rs | 34 ++--- voxygen/src/hud/skillbar.rs | 123 ++---------------- 37 files changed, 64 insertions(+), 207 deletions(-) create mode 100644 assets/voxygen/element/misc_bg/level_down.png delete mode 100644 assets/voxygen/element/misc_bg/level_down.vox create mode 100644 assets/voxygen/element/misc_bg/level_up.png delete mode 100644 assets/voxygen/element/misc_bg/level_up.vox create mode 100644 assets/voxygen/element/skillbar/bar_content.png delete mode 100644 assets/voxygen/element/skillbar/bar_content.vox create mode 100644 assets/voxygen/element/skillbar/energybar_bg.png delete mode 100644 assets/voxygen/element/skillbar/energybar_bg.vox create mode 100644 assets/voxygen/element/skillbar/healthbar_bg.png delete mode 100644 assets/voxygen/element/skillbar/healthbar_bg.vox create mode 100644 assets/voxygen/element/skillbar/skillbar_slot_big.png delete mode 100644 assets/voxygen/element/skillbar/skillbar_slot_big.vox delete mode 100644 assets/voxygen/element/skillbar/skillbar_slot_big_active.vox delete mode 100644 assets/voxygen/element/skillbar/skillbar_slot_big_bg.vox delete mode 100644 assets/voxygen/element/skillbar/stamina_wheel-0.vox delete mode 100644 assets/voxygen/element/skillbar/stamina_wheel-1.vox delete mode 100644 assets/voxygen/element/skillbar/stamina_wheel-2.vox delete mode 100644 assets/voxygen/element/skillbar/stamina_wheel-3.vox delete mode 100644 assets/voxygen/element/skillbar/stamina_wheel-4.vox delete mode 100644 assets/voxygen/element/skillbar/stamina_wheel-5.vox delete mode 100644 assets/voxygen/element/skillbar/stamina_wheel-6.vox delete mode 100644 assets/voxygen/element/skillbar/stamina_wheel-7.vox delete mode 100644 assets/voxygen/element/skillbar/stamina_wheel-empty.vox create mode 100644 assets/voxygen/element/skillbar/xp_bar_content.png delete mode 100644 assets/voxygen/element/skillbar/xp_bar_content.vox create mode 100644 assets/voxygen/element/skillbar/xp_bar_left.png delete mode 100644 assets/voxygen/element/skillbar/xp_bar_left.vox create mode 100644 assets/voxygen/element/skillbar/xp_bar_mid.png delete mode 100644 assets/voxygen/element/skillbar/xp_bar_mid.vox create mode 100644 assets/voxygen/element/skillbar/xp_bar_right.png delete mode 100644 assets/voxygen/element/skillbar/xp_bar_right.vox 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 index 811579b09d..b23b25967f 100644 --- a/assets/voxygen/element/skillbar/skillbar_slot.png +++ b/assets/voxygen/element/skillbar/skillbar_slot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3804ce85831c53666987128f7589faa40b08cd37f01a8d112bd7e2eca07cd724 -size 2344 +oid sha256:e684e902c11fe48c4875997799834fa597cc14a7eab86cf48d63c56de1191f11 +size 610 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 index 69a87a9c96..615e91ffd2 100644 --- a/assets/voxygen/element/skillbar/skillbar_slot_l.png +++ b/assets/voxygen/element/skillbar/skillbar_slot_l.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:05cafd02908849e40fe20fb1cc2877b54ebb5d198b73663fe0c61e22ea3d021c -size 2343 +oid sha256:0308eac5f6ca52d9e1425ae5c7f17c0451f628d2b8443d699eece95e3480adfd +size 607 diff --git a/assets/voxygen/element/skillbar/skillbar_slot_r.png b/assets/voxygen/element/skillbar/skillbar_slot_r.png index c9caaf553e..16591871c0 100644 --- a/assets/voxygen/element/skillbar/skillbar_slot_r.png +++ b/assets/voxygen/element/skillbar/skillbar_slot_r.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f58abab20455ea0b8c5b96ad96b8c39d1d0aaac996afc507074be45cbc928b73 -size 2344 +oid sha256:56dbe92fe64b64b59b554ada0d1eb0dbb537593e3a1d87dfccd721c6519e521d +size 608 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/voxygen/src/hud/bag.rs b/voxygen/src/hud/bag.rs index 09f8209ee8..03a813b47a 100644 --- a/voxygen/src/hud/bag.rs +++ b/voxygen/src/hud/bag.rs @@ -216,11 +216,6 @@ 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 @@ -329,7 +324,9 @@ impl<'a> Widget for Bag<'a> { background_color: Some(UI_HIGHLIGHT_0), content_size: ContentSize { width_height_ratio: 1.0, - max_fraction: 0.75, + 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, diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index 98c3e7986f..867202305e 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -20,29 +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_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", @@ -145,6 +122,17 @@ 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", diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index 784ec156ab..c3a9ee7386 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -29,10 +29,6 @@ use conrod_core::{ }; use std::time::{Duration, Instant}; use vek::*; -/* -use const_tweaker::tweak; -#[tweak(min = 0.0, max = 1.0, step = 0.01)] -const RGB: f32 = 0.1;*/ widget_ids! { struct Ids { @@ -108,7 +104,6 @@ widget_ids! { level_align, level_message, level_message_bg, - stamina_wheel, death_bg, hurt_bg, } @@ -234,36 +229,6 @@ impl<'a> Widget for Skillbar<'a> { let localized_strings = self.localized_strings; - // 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); - } - */ - // Level Up Message let current_level = self.stats.level.level(); @@ -674,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() { @@ -806,11 +750,12 @@ impl<'a> Widget for Skillbar<'a> { background_color: None, content_size: ContentSize { width_height_ratio: 1.0, - max_fraction: 0.9, + max_fraction: 0.8, /* Changes the item image size by setting a maximum fraction + * of either the width or height */ }, - selected_content_scale: 1.067, + selected_content_scale: 1.0, amount_font: self.fonts.cyri.conrod_id, - amount_margins: Vec2::new(3.0, 2.0), + 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, @@ -894,28 +839,6 @@ impl<'a> Widget for Skillbar<'a> { slot.set(state.ids.slot2, 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); - }, - }*/ 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; @@ -929,24 +852,6 @@ impl<'a> Widget for Skillbar<'a> { } else { slot.set(state.ids.slot1, 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); - }, - _ => {}, - }, - _ => {}, - }*/ // Slot 6 slot_maker.empty_slot = self.imgs.skillbar_slot; slot_maker.filled_slot = self.imgs.skillbar_slot; @@ -1018,7 +923,7 @@ impl<'a> Widget for Skillbar<'a> { .get_binding(GameInput::Slot1) { Text::new(slot1.to_string().as_str()) - .top_right_with_margins_on(state.ids.slot1, 2.0, 2.0) + .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) @@ -1037,7 +942,7 @@ impl<'a> Widget for Skillbar<'a> { .get_binding(GameInput::Slot2) { Text::new(slot2.to_string().as_str()) - .top_right_with_margins_on(state.ids.slot2, 2.0, 2.0) + .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) @@ -1056,7 +961,7 @@ impl<'a> Widget for Skillbar<'a> { .get_binding(GameInput::Slot3) { Text::new(slot3.to_string().as_str()) - .top_right_with_margins_on(state.ids.slot3, 2.0, 2.0) + .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) @@ -1075,7 +980,7 @@ impl<'a> Widget for Skillbar<'a> { .get_binding(GameInput::Slot4) { Text::new(slot4.to_string().as_str()) - .top_right_with_margins_on(state.ids.slot4, 2.0, 2.0) + .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) @@ -1094,7 +999,7 @@ impl<'a> Widget for Skillbar<'a> { .get_binding(GameInput::Slot5) { Text::new(slot5.to_string().as_str()) - .top_right_with_margins_on(state.ids.slot5, 2.0, 2.0) + .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) @@ -1151,7 +1056,7 @@ impl<'a> Widget for Skillbar<'a> { .get_binding(GameInput::Slot6) { Text::new(slot6.to_string().as_str()) - .top_right_with_margins_on(state.ids.slot6, 2.0, 2.0) + .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) @@ -1170,7 +1075,7 @@ impl<'a> Widget for Skillbar<'a> { .get_binding(GameInput::Slot7) { Text::new(slot7.to_string().as_str()) - .top_right_with_margins_on(state.ids.slot7, 2.0, 2.0) + .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) @@ -1189,7 +1094,7 @@ impl<'a> Widget for Skillbar<'a> { .get_binding(GameInput::Slot8) { Text::new(slot8.to_string().as_str()) - .top_right_with_margins_on(state.ids.slot8, 2.0, 2.0) + .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) @@ -1208,7 +1113,7 @@ impl<'a> Widget for Skillbar<'a> { .get_binding(GameInput::Slot9) { Text::new(slot9.to_string().as_str()) - .top_right_with_margins_on(state.ids.slot9, 2.0, 2.0) + .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) @@ -1227,7 +1132,7 @@ impl<'a> Widget for Skillbar<'a> { .get_binding(GameInput::Slot10) { Text::new(slot10.to_string().as_str()) - .top_right_with_margins_on(state.ids.slot10, 2.0, 2.0) + .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) From 4cdc1300a1c4968c9ddd878423e8290147daef8a Mon Sep 17 00:00:00 2001 From: ElXreno Date: Sun, 12 Apr 2020 19:44:21 +0300 Subject: [PATCH 059/195] Find assets by using other environment variables By using HOME, XDG_DATA_HOME and XDG_DATA_DIRS we can build potentially proper paths for finding assets folder. --- common/src/assets/mod.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) 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) From b98680b9cbb746d1e70858c0a44fc7e00c565aa7 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sun, 12 Apr 2020 13:39:21 -0400 Subject: [PATCH 060/195] fix example --- voxygen/examples/character_renderer.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/voxygen/examples/character_renderer.rs b/voxygen/examples/character_renderer.rs index c19515da68..05483f2680 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`) From 9ff816f006005a83e155c3d1f321cdabd0fd3ea3 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sun, 12 Apr 2020 15:35:43 -0400 Subject: [PATCH 061/195] Clarify todo comment --- common/src/comp/inventory/slot.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/src/comp/inventory/slot.rs b/common/src/comp/inventory/slot.rs index 6144a3b6d5..b7d6f27842 100644 --- a/common/src/comp/inventory/slot.rs +++ b/common/src/comp/inventory/slot.rs @@ -76,7 +76,9 @@ impl ArmorSlot { } } -// TODO: shouldn't need this +// 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() From 25a9d5bf2fe04271cd59786d855be100086176e3 Mon Sep 17 00:00:00 2001 From: Pfauenauge Date: Sun, 12 Apr 2020 21:45:01 +0200 Subject: [PATCH 062/195] resolved comments --- CHANGELOG.md | 4 ++-- assets/voxygen/i18n/de_DE.ron | 2 +- voxygen/src/hud/hotbar.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aecaf98d91..08b67cc0e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,8 +45,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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 slotskillbars -- Added capes, lanterns, and more armor items +- Added dragging and right-click to use functionality to inventory, armor & hotbar slots +- Added capes, lanterns, tabards, rings, helmets & necklaces as equippable armor ### Changed diff --git a/assets/voxygen/i18n/de_DE.ron b/assets/voxygen/i18n/de_DE.ron index cb941d59cb..5c9e7b21d8 100644 --- a/assets/voxygen/i18n/de_DE.ron +++ b/assets/voxygen/i18n/de_DE.ron @@ -213,7 +213,7 @@ Viel Spaß in der Welt von Veloren, Abenteurer!"#, "hud.bag.legs": "Beine", "hud.bag.feet": "Füße", "hud.bag.mainhand": "Haupthand", - "hud.bag.offhand": "Nebenhand ", + "hud.bag.offhand": "Nebenhand", // Map and Questlog "hud.map.map_title": "Karte", diff --git a/voxygen/src/hud/hotbar.rs b/voxygen/src/hud/hotbar.rs index 5eb1a3ae53..e9958f5d66 100644 --- a/voxygen/src/hud/hotbar.rs +++ b/voxygen/src/hud/hotbar.rs @@ -31,7 +31,7 @@ impl State { } } - /// Returns true is the button was just pressed + /// 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; From 027d0f11da1be08148814c7ca491fd0b30ac86df Mon Sep 17 00:00:00 2001 From: Olexorus Date: Mon, 13 Apr 2020 19:28:13 +0200 Subject: [PATCH 063/195] Fix panic if default server index is out of bounds --- voxygen/src/menu/main/ui.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/voxygen/src/menu/main/ui.rs b/voxygen/src/menu/main/ui.rs index 39f3e32175..aa16b84a6e 100644 --- a/voxygen/src/menu/main/ui.rs +++ b/voxygen/src/menu/main/ui.rs @@ -205,7 +205,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, From 996eee0b42e577886b1d0288a00ece2b7af12377 Mon Sep 17 00:00:00 2001 From: Songtronix Date: Wed, 15 Apr 2020 13:31:16 +0200 Subject: [PATCH 064/195] change(server): rename settings file to `server_settings.ron` --- .gitignore | 1 + server/src/settings.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) 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/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") } } From 32a8e6d304a2f12e8f9e915470c7a6babf8c6a5d Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 15 Apr 2020 12:49:33 +0000 Subject: [PATCH 065/195] Update docker-compose.yml with the same as in production by official server --- server-cli/docker-compose.yml | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) 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 From 4aa176daecde09f7b107d1f5fba0f9d3ec1e84cd Mon Sep 17 00:00:00 2001 From: Artem Date: Thu, 16 Apr 2020 05:25:04 +0000 Subject: [PATCH 066/195] Update AppData manifest to conform guidelines https://github.com/flathub/flathub/wiki/AppData-Guidelines Fix desktop file --- .../voxygen/net.veloren.veloren.appdata.xml | 61 ---------------- assets/voxygen/net.veloren.veloren.desktop | 2 +- .../voxygen/net.veloren.veloren.metainfo.xml | 73 +++++++++++++++++++ 3 files changed, 74 insertions(+), 62 deletions(-) delete mode 100644 assets/voxygen/net.veloren.veloren.appdata.xml create mode 100644 assets/voxygen/net.veloren.veloren.metainfo.xml 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 +
From 5daa7d7b02b4c45444384f658bbbc42c7cf1607f Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Thu, 16 Apr 2020 15:04:02 +0200 Subject: [PATCH 067/195] insert correct model, change offset accordingly --- assets/world/manifests/fruit_trees.ron | 2 +- assets/world/tree/fruit/1.vox | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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/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 From bf437709934b6f45d70342af9182d8a788ea9012 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 18 Apr 2020 15:07:13 +0000 Subject: [PATCH 068/195] update toolchain to `nightly-2020-04-17` --- rust-toolchain | 1 - xMAC94x/update-toolchain | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 rust-toolchain create mode 100644 xMAC94x/update-toolchain diff --git a/rust-toolchain b/rust-toolchain deleted file mode 100644 index be5bcdaf12..0000000000 --- a/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -nightly-2020-03-05 diff --git a/xMAC94x/update-toolchain b/xMAC94x/update-toolchain new file mode 100644 index 0000000000..1ff3407a54 --- /dev/null +++ b/xMAC94x/update-toolchain @@ -0,0 +1 @@ +nightly-2020-04-17 \ No newline at end of file From 26e27f4198f475e96ae2ffefb98306f0bce66103 Mon Sep 17 00:00:00 2001 From: Songtronix Date: Tue, 21 Apr 2020 20:04:55 +0000 Subject: [PATCH 069/195] fix toolchain --- xMAC94x/update-toolchain => rust-toolchain | 0 voxygen/src/menu/main/mod.rs | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename xMAC94x/update-toolchain => rust-toolchain (100%) diff --git a/xMAC94x/update-toolchain b/rust-toolchain similarity index 100% rename from xMAC94x/update-toolchain rename to rust-toolchain 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 { From 465d4d44ee643f3e2bd75d95c7ccda77ab01d391 Mon Sep 17 00:00:00 2001 From: Songtronix Date: Thu, 23 Apr 2020 14:47:21 +0200 Subject: [PATCH 070/195] fix(ci): allow coverage test to fail temporarily #538 --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) 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: From 7519e842e9a4ac6fca16a23d9ed69a05b113d65f Mon Sep 17 00:00:00 2001 From: Justin Shipsey Date: Thu, 23 Apr 2020 17:16:45 +0000 Subject: [PATCH 071/195] 6 new music tracks --- CHANGELOG.md | 1 + assets/voxygen/audio/soundtrack.ron | 44 ++++++++++++++++++- .../audio/soundtrack/between_the_fairies.ogg | 3 ++ .../audio/soundtrack/campfire_stories.ogg | 3 ++ .../audio/soundtrack/down_the_rabbit_hole.ogg | 3 ++ .../audio/soundtrack/just_the_beginning.ogg | 3 ++ assets/voxygen/audio/soundtrack/limits.ogg | 3 ++ .../voxygen/audio/soundtrack/rest_assured.ogg | 3 ++ 8 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 assets/voxygen/audio/soundtrack/between_the_fairies.ogg create mode 100644 assets/voxygen/audio/soundtrack/campfire_stories.ogg create mode 100644 assets/voxygen/audio/soundtrack/down_the_rabbit_hole.ogg create mode 100644 assets/voxygen/audio/soundtrack/just_the_beginning.ogg create mode 100644 assets/voxygen/audio/soundtrack/limits.ogg create mode 100644 assets/voxygen/audio/soundtrack/rest_assured.ogg diff --git a/CHANGELOG.md b/CHANGELOG.md index 08b67cc0e8..9fadccdab1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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 ### Changed 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 From 944a37b848d17ebc8b526b34434a4875e4a37cac Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 6 Feb 2020 22:32:26 +0000 Subject: [PATCH 072/195] Initial settlement generation work --- common/src/astar.rs | 9 + common/src/lib.rs | 1 + common/src/path.rs | 2 + common/src/spiral.rs | 37 ++ voxygen/src/scene/terrain.rs | 35 +- world/examples/settlement_viewer.rs | 55 +++ world/src/generator/mod.rs | 1 + world/src/generator/settlement/mod.rs | 604 ++++++++++++++++++++++++++ world/src/sim/mod.rs | 4 +- world/src/util/structure.rs | 12 +- 10 files changed, 720 insertions(+), 40 deletions(-) create mode 100644 common/src/spiral.rs create mode 100644 world/examples/settlement_viewer.rs create mode 100644 world/src/generator/settlement/mod.rs diff --git a/common/src/astar.rs b/common/src/astar.rs index 4c153c9de9..8bbb5bc7b4 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, diff --git a/common/src/lib.rs b/common/src/lib.rs index df969e5af4..f8da49935d 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -25,6 +25,7 @@ pub mod npc; pub mod path; pub mod ray; pub mod region; +pub mod spiral; pub mod state; pub mod states; pub mod sync; diff --git a/common/src/path.rs b/common/src/path.rs index 85e8f174af..fdbc9164f0 100644 --- a/common/src/path.rs +++ b/common/src/path.rs @@ -33,6 +33,8 @@ 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() } diff --git a/common/src/spiral.rs b/common/src/spiral.rs new file mode 100644 index 0000000000..f0044763ca --- /dev/null +++ b/common/src/spiral.rs @@ -0,0 +1,37 @@ +use vek::*; + +/// An iterator of coordinates that create a rectangular spiral out from the +/// origin +#[derive(Clone)] +pub struct Spiral2d { + layer: i32, + i: i32, +} + +impl Spiral2d { + pub fn new() -> Self { Self { layer: 0, i: 0 } } +} + +impl Iterator for Spiral2d { + type Item = Vec2; + + fn next(&mut self) -> Option { + let layer_size = (self.layer * 8 + 4 * self.layer.min(1) - 4).max(1); + if self.i >= layer_size { + self.layer += 1; + self.i = 0; + } + let layer_size = (self.layer * 8 + 4 * self.layer.min(1) - 4).max(1); + + let pos = Vec2::new( + -self.layer + (self.i - (layer_size / 4) * 0).max(0).min(self.layer * 2) + - (self.i - (layer_size / 4) * 2).max(0).min(self.layer * 2), + -self.layer + (self.i - (layer_size / 4) * 1).max(0).min(self.layer * 2) + - (self.i - (layer_size / 4) * 3).max(0).min(self.layer * 2), + ); + + self.i += 1; + + Some(pos) + } +} diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index be5f7c40c3..a4c4d25ce6 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -10,6 +10,7 @@ use super::SceneData; use common::{ assets, figure::Segment, + spiral::Spiral2D, terrain::{Block, BlockKind, TerrainChunk}, vol::{BaseVol, ReadVol, RectRasterableVol, SampleVol, Vox}, volumes::vol_grid_2d::{VolGrid2d, VolGrid2dError}, @@ -1487,37 +1488,3 @@ impl Terrain { }); } } - -#[derive(Clone)] -struct Spiral2d { - layer: i32, - i: i32, -} - -impl Spiral2d { - pub fn new() -> Self { Self { layer: 0, i: 0 } } -} - -impl Iterator for Spiral2d { - type Item = Vec2; - - fn next(&mut self) -> Option { - let layer_size = (self.layer * 8 + 4 * self.layer.min(1) - 4).max(1); - if self.i >= layer_size { - self.layer += 1; - self.i = 0; - } - let layer_size = (self.layer * 8 + 4 * self.layer.min(1) - 4).max(1); - - let pos = Vec2::new( - -self.layer + (self.i - (layer_size / 4) * 0).max(0).min(self.layer * 2) - - (self.i - (layer_size / 4) * 2).max(0).min(self.layer * 2), - -self.layer + (self.i - (layer_size / 4) * 1).max(0).min(self.layer * 2) - - (self.i - (layer_size / 4) * 3).max(0).min(self.layer * 2), - ); - - self.i += 1; - - Some(pos) - } -} diff --git a/world/examples/settlement_viewer.rs b/world/examples/settlement_viewer.rs new file mode 100644 index 0000000000..903dba861f --- /dev/null +++ b/world/examples/settlement_viewer.rs @@ -0,0 +1,55 @@ +use rand::thread_rng; +use vek::*; +use veloren_world::generator::settlement::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(&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); + + 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(&buf).unwrap(); + } +} diff --git a/world/src/generator/mod.rs b/world/src/generator/mod.rs index 46c9d41640..6ac7240b8d 100644 --- a/world/src/generator/mod.rs +++ b/world/src/generator/mod.rs @@ -1,3 +1,4 @@ +pub mod settlement; mod town; // Reexports diff --git a/world/src/generator/settlement/mod.rs b/world/src/generator/settlement/mod.rs new file mode 100644 index 0000000000..bbd5a0c272 --- /dev/null +++ b/world/src/generator/settlement/mod.rs @@ -0,0 +1,604 @@ +use crate::util::{Sampler, StructureGen2d}; +use common::{astar::Astar, path::Path, spiral::Spiral2d}; +use hashbrown::{HashMap, HashSet}; +use rand::prelude::*; +use std::{collections::VecDeque, f32, marker::PhantomData}; +use vek::*; + +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 } +} + +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)) + } +} + +pub fn dist_to_line(line: [Vec2; 2], p: Vec2) -> f32 { + let lsq = line[0].distance_squared(line[1]); + + if lsq == 0.0 { + line[0].distance(p) + } else { + let t = ((p - line[0]).dot(line[1] - line[0]) / lsq) + .max(0.0) + .min(1.0); + p.distance(line[0] + (line[1] - line[0]) * t) + } +} + +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) +} + +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, +} + +pub struct Structure { + kind: StructureKind, + bounds: Aabr, +} + +pub struct Settlement { + land: Land, + farms: Store, + structures: Vec, + town: Option, +} + +pub struct Town { + base_tile: Vec2, +} + +pub struct Farm { + base_tile: Vec2, +} + +impl Settlement { + pub fn generate(rng: &mut impl Rng) -> Self { + let mut this = Self { + land: Land::new(rng), + farms: Store::default(), + structures: Vec::new(), + town: None, + }; + + this.place_river(rng); + + this.place_farms(rng); + this.place_town(rng); + this.place_paths(rng); + + this + } + + 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); + + for theta in (0..500).map(|x| (x as f32) * f32::consts::PI / 250.0) { + let pos = river_dir * radius + Vec2::new(theta.sin(), theta.cos()) * radius; + + if pos.magnitude() > 300.0 { + continue; + } + + for dir in CARDINALS.iter() { + self.land.set( + pos.map2(*dir, |e, d| e.floor() as i32 + d * 12) + .map(to_tile), + river, + ); + } + } + } + + pub fn place_paths(&mut self, rng: &mut impl Rng) { + let mut dir = Vec2::zero(); + for _ in 0..6 { + 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 * 20.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(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, true); + } + } + } + + pub fn place_town(&mut self, rng: &mut impl Rng) { + let mut origin = Vec2::new(rng.gen_range(-2, 3), rng.gen_range(-2, 3)); + + let town = self.land.new_plot(Plot::Town); + for i in 0..4 { + 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.set(base_tile, town); + + if i == 0 { + for dir in CARDINALS.iter() { + self.land.set(base_tile + *dir, town); + } + + self.town = Some(Town { base_tile }); + origin = base_tile; + } + } + } + + // Border wall + let spokes = CARDINALS + .iter() + .filter_map(|dir| { + self.land.find_tile_dir(origin, *dir, |plot| match plot { + 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::Town) => 1000.0, + _ => 1.0, + }) + .map(|path| wall_path.extend(path.iter().copied())); + } + let grass = self.land.new_plot(Plot::Grass); + for pos in wall_path.iter() { + if self.land.tile_at(*pos).is_none() { + self.land.set(*pos, grass); + } + } + wall_path.push(wall_path[0]); + self.land.write_path( + &wall_path, + WayKind::Wall, + |plot| match plot { + Plot::Water => false, + _ => true, + }, + true, + ); + } + + pub fn place_farms(&mut self, rng: &mut impl Rng) { + for _ in 0..6 { + 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..rng.gen_range(1, 4) { + let house_pos = base_tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32 / 2) + + Vec2::new(rng.gen_range(-16, 16), rng.gen_range(-16, 16)); + + self.structures.push(Structure { + kind: StructureKind::House, + bounds: Aabr { + min: house_pos - Vec2::new(rng.gen_range(4, 6), rng.gen_range(4, 6)), + max: house_pos + Vec2::new(rng.gen_range(4, 6), rng.gen_range(4, 6)), + }, + }); + } + + // Fields + let farmland = self.farms.insert(Farm { base_tile }); + for _ in 0..5 { + self.place_field(farmland, base_tile, rng); + } + } + } + } + + pub fn place_field( + &mut self, + farm: Id, + origin: Vec2, + rng: &mut impl Rng, + ) -> Option> { + let max_size = 7; + + 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(), + }); + let tiles = self + .land + .grow_from(center, rng.gen_range(1, max_size), rng, |plot| { + plot.is_none() + }); + for pos in tiles.into_iter() { + self.land.set(pos, field); + } + Some(field) + } else { + None + } + } + + pub fn get_color(&self, pos: Vec2) -> Rgb { + let pos = pos.map(|e| e.floor() as i32); + + if let Some(structure) = self + .structures + .iter() + .find(|s| s.bounds.contains_point(pos)) + { + return match structure.kind { + StructureKind::House => Rgb::new(200, 80, 50), + }; + } + + match self.land.get_at_block(pos) { + Sample::Wilderness => Rgb::zero(), + Sample::Way(WayKind::Path) => Rgb::new(130, 100, 0), + Sample::Way(WayKind::Hedge) => Rgb::new(0, 150, 0), + Sample::Way(WayKind::Wall) => Rgb::new(60, 60, 60), + Sample::Plot(Plot::Dirt) => Rgb::new(130, 100, 0), + Sample::Plot(Plot::Grass) => Rgb::new(100, 200, 0), + Sample::Plot(Plot::Water) => Rgb::new(100, 150, 250), + Sample::Plot(Plot::Town) => { + if pos.map(|e| e.rem_euclid(4) < 2).reduce(|x, y| x ^ y) { + Rgb::new(200, 130, 120) + } else { + Rgb::new(160, 150, 120) + } + }, + Sample::Plot(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(4) < 2; + Rgb::new( + if furrow { + 150 + } else { + 48 + seed.to_le_bytes()[0] % 64 + }, + 128 + seed.to_le_bytes()[1] % 128, + 16 + seed.to_le_bytes()[2] % 32, + ) + }, + } + } +} + +#[derive(Copy, Clone, PartialEq)] +pub enum Plot { + Dirt, + Grass, + Water, + Town, + Field { farm: Id, seed: u32 }, +} + +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, + Hedge, + Wall, +} + +pub struct Tile { + plot: Id, + ways: [Option; 4], +} + +impl Tile { + pub fn contains(&self, kind: WayKind) -> bool { self.ways.iter().any(|way| way == &Some(kind)) } +} + +pub enum Sample<'a> { + Wilderness, + Plot(&'a Plot), + Way(&'a WayKind), +} + +pub struct Land { + tiles: HashMap, Tile>, + plots: Store, + sampler_warp: StructureGen2d, +} + +impl Land { + pub fn new(rng: &mut impl Rng) -> Self { + Self { + tiles: HashMap::new(), + plots: Store::default(), + sampler_warp: StructureGen2d::new(rng.gen(), AREA_SIZE, 12), + } + } + + pub fn get_at_block(&self, pos: Vec2) -> Sample { + let neighbors = self.sampler_warp.get(pos); + let closest = neighbors + .iter() + .min_by_key(|(center, _)| center.distance_squared(pos)) + .unwrap() + .0; + + let map = [1, 5, 7, 3]; + for (i, dir) in CARDINALS.iter().enumerate() { + let line = [ + neighbors[4].0.map(|e| e as f32), + neighbors[map[i]].0.map(|e| e as f32), + ]; + if dist_to_line(line, pos.map(|e| e as f32)) < 1.5 { + if let Some(way) = self + .tile_at(neighbors[4].0.map(to_tile)) + .and_then(|tile| tile.ways[i].as_ref()) + { + return Sample::Way(way); + } + } + } + + let plot = self.plot_at(closest.map(to_tile)); + + plot.map(|plot| Sample::Plot(plot)) + .unwrap_or(Sample::Wilderness) + } + + pub fn tile_at(&self, pos: Vec2) -> Option<&Tile> { self.tiles.get(&pos) } + + 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], + }); + } + + 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))) + } + + 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.distance_squared(dest) as f32; + 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; + }; + let mut 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) } +} + +#[derive(Hash)] +pub struct Id(usize, PhantomData); + +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 } +} + +pub struct Store { + items: HashMap, + id_counter: usize, +} + +impl Default for Store { + fn default() -> Self { + Self { + items: HashMap::new(), + id_counter: 0, + } + } +} + +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 iter(&self) -> impl Iterator { self.items.values() } + + pub fn insert(&mut self, item: T) -> Id { + self.id_counter += 1; + let id = Id(self.id_counter, PhantomData); + self.items.insert(id.0, item); + id + } +} diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index feb3643eb5..9b75fd9bb4 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -65,8 +65,8 @@ use vek::*; // don't think we actually cast a chunk id to float, just coordinates... could // be wrong though! pub const WORLD_SIZE: Vec2 = Vec2 { - x: 1024 * 1, - y: 1024 * 1, + x: 256 * 1, + y: 256 * 1, }; /// A structure that holds cached noise values and cumulative distribution 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), ) } From 20b16d229cfc339b1ccb40715dac01d0e01e0394 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 6 Feb 2020 23:37:17 +0000 Subject: [PATCH 073/195] Added river obstacle example --- world/src/generator/settlement/mod.rs | 32 ++++++++++++++++++--------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/world/src/generator/settlement/mod.rs b/world/src/generator/settlement/mod.rs index bbd5a0c272..12b7fd4d97 100644 --- a/world/src/generator/settlement/mod.rs +++ b/world/src/generator/settlement/mod.rs @@ -106,20 +106,29 @@ impl Settlement { 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 theta in (0..500).map(|x| (x as f32) * f32::consts::PI / 250.0) { - let pos = river_dir * radius + Vec2::new(theta.sin(), theta.cos()) * radius; + 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; - if pos.magnitude() > 300.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; } - for dir in CARDINALS.iter() { - self.land.set( - pos.map2(*dir, |e, d| e.floor() as i32 + d * 12) - .map(to_tile), - river, - ); + if let Some(path) = self.land.find_path(pos0, pos1, |_, _| 1.0) { + for pos in path.iter().copied() { + self.land.set(pos, river); + } } } } @@ -165,7 +174,7 @@ impl Settlement { let mut origin = Vec2::new(rng.gen_range(-2, 3), rng.gen_range(-2, 3)); let town = self.land.new_plot(Plot::Town); - for i in 0..4 { + for i in 0..6 { if let Some(base_tile) = self.land.find_tile_near(origin, |plot| match plot { Some(Plot::Field { .. }) => true, Some(Plot::Dirt) => true, @@ -174,9 +183,11 @@ impl Settlement { self.land.set(base_tile, town); if i == 0 { + /* for dir in CARDINALS.iter() { self.land.set(base_tile + *dir, town); } + */ self.town = Some(Town { base_tile }); origin = base_tile; @@ -394,6 +405,7 @@ impl Land { .min_by_key(|(center, _)| center.distance_squared(pos)) .unwrap() .0; + //let locals = self.sampler_warp.get(closest); let map = [1, 5, 7, 3]; for (i, dir) in CARDINALS.iter().enumerate() { From 48fb14116aa61808ee415210fc249701b77e9378 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 7 Feb 2020 16:08:36 +0000 Subject: [PATCH 074/195] Town walls, wall towers --- world/src/generator/settlement/mod.rs | 63 +++++++++++++++++++-------- 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/world/src/generator/settlement/mod.rs b/world/src/generator/settlement/mod.rs index 12b7fd4d97..8e353bfd84 100644 --- a/world/src/generator/settlement/mod.rs +++ b/world/src/generator/settlement/mod.rs @@ -56,7 +56,7 @@ pub fn center_of(p: [Vec2; 3]) -> Vec2 { Vec2::new(x, y) } -const AREA_SIZE: u32 = 32; +const AREA_SIZE: u32 = 48; fn to_tile(e: i32) -> i32 { ((e as f32).div_euclid(AREA_SIZE as f32)).floor() as i32 } @@ -195,7 +195,7 @@ impl Settlement { } } - // Border wall + // Boundary wall let spokes = CARDINALS .iter() .filter_map(|dir| { @@ -217,21 +217,23 @@ impl Settlement { .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)); + } } wall_path.push(wall_path[0]); - self.land.write_path( - &wall_path, - WayKind::Wall, - |plot| match plot { - Plot::Water => false, - _ => true, - }, - true, - ); + self.land + .write_path(&wall_path, WayKind::Wall, buildable, true); } pub fn place_farms(&mut self, rng: &mut impl Rng) { @@ -312,6 +314,7 @@ impl Settlement { Sample::Way(WayKind::Path) => Rgb::new(130, 100, 0), Sample::Way(WayKind::Hedge) => Rgb::new(0, 150, 0), Sample::Way(WayKind::Wall) => Rgb::new(60, 60, 60), + Sample::Tower(Tower::Wall) => Rgb::new(50, 50, 50), Sample::Plot(Plot::Dirt) => Rgb::new(130, 100, 0), Sample::Plot(Plot::Grass) => Rgb::new(100, 200, 0), Sample::Plot(Plot::Water) => Rgb::new(100, 150, 250), @@ -368,9 +371,25 @@ pub enum WayKind { Wall, } +impl WayKind { + pub fn width(&self) -> f32 { + match self { + WayKind::Path => 2.5, + WayKind::Hedge => 1.5, + WayKind::Wall => 2.5, + } + } +} + +#[derive(Copy, Clone, PartialEq)] +pub enum Tower { + Wall, +} + pub struct Tile { plot: Id, ways: [Option; 4], + tower: Option, } impl Tile { @@ -381,6 +400,7 @@ pub enum Sample<'a> { Wilderness, Plot(&'a Plot), Way(&'a WayKind), + Tower(&'a Tower), } pub struct Land { @@ -394,7 +414,7 @@ impl Land { Self { tiles: HashMap::new(), plots: Store::default(), - sampler_warp: StructureGen2d::new(rng.gen(), AREA_SIZE, 12), + sampler_warp: StructureGen2d::new(rng.gen(), AREA_SIZE, 16), } } @@ -405,19 +425,23 @@ impl Land { .min_by_key(|(center, _)| center.distance_squared(pos)) .unwrap() .0; - //let locals = self.sampler_warp.get(closest); - let map = [1, 5, 7, 3]; + let center_tile = self.tile_at(neighbors[4].0.map(to_tile)); + + if neighbors[4].0.distance_squared(pos) < 6i32.pow(2) { + if let Some(tower) = center_tile.and_then(|tile| tile.tower.as_ref()) { + return Sample::Tower(tower); + } + } + for (i, dir) in CARDINALS.iter().enumerate() { + let map = [1, 5, 7, 3]; let line = [ neighbors[4].0.map(|e| e as f32), neighbors[map[i]].0.map(|e| e as f32), ]; - if dist_to_line(line, pos.map(|e| e as f32)) < 1.5 { - if let Some(way) = self - .tile_at(neighbors[4].0.map(to_tile)) - .and_then(|tile| tile.ways[i].as_ref()) - { + if let Some(way) = center_tile.and_then(|tile| tile.ways[i].as_ref()) { + if dist_to_line(line, pos.map(|e| e as f32)) < way.width() { return Sample::Way(way); } } @@ -450,6 +474,7 @@ impl Land { self.tiles.insert(pos, Tile { plot, ways: [None; 4], + tower: None, }); } From 0021bd64520fe466c533a94a7a5cf55714374f56 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 7 Feb 2020 22:19:25 +0000 Subject: [PATCH 075/195] Added new settlement generation to world, basic rendering --- assets/voxygen/shaders/include/sky.glsl | 4 +- voxygen/src/scene/terrain.rs | 2 +- world/examples/settlement_viewer.rs | 4 +- world/src/block/mod.rs | 4 ++ world/src/column/mod.rs | 13 +++++++ world/src/generator/mod.rs | 23 ++++++++++- world/src/generator/settlement/mod.rs | 52 +++++++++++++++++-------- world/src/lib.rs | 23 ++++++----- world/src/sim/location.rs | 3 -- world/src/sim/mod.rs | 41 +++++++------------ world/src/util/grid.rs | 18 ++++++++- 11 files changed, 124 insertions(+), 63 deletions(-) diff --git a/assets/voxygen/shaders/include/sky.glsl b/assets/voxygen/shaders/include/sky.glsl index 7ac0dbc6c9..806d1bf140 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); const vec3 SUN_HALO_DAY = vec3(0.35, 0.35, 0.0); diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index a4c4d25ce6..74a2f94852 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -10,7 +10,7 @@ use super::SceneData; use common::{ assets, figure::Segment, - spiral::Spiral2D, + spiral::Spiral2d, terrain::{Block, BlockKind, TerrainChunk}, vol::{BaseVol, ReadVol, RectRasterableVol, SampleVol, Vox}, volumes::vol_grid_2d::{VolGrid2d, VolGrid2dError}, diff --git a/world/examples/settlement_viewer.rs b/world/examples/settlement_viewer.rs index 903dba861f..2d963d07a9 100644 --- a/world/examples/settlement_viewer.rs +++ b/world/examples/settlement_viewer.rs @@ -9,7 +9,7 @@ fn main() { let mut win = minifb::Window::new("Settlement Viewer", W, H, minifb::WindowOptions::default()).unwrap(); - let settlement = Settlement::generate(&mut thread_rng()); + let settlement = Settlement::generate(Vec2::zero(), &mut thread_rng()); let mut focus = Vec2::::zero(); let mut zoom = 1.0; @@ -24,7 +24,7 @@ fn main() { for j in 0..H { let pos = focus + win_to_pos(Vec2::new(i, j)) * zoom; - let color = settlement.get_color(pos); + let color = settlement.get_color(pos).unwrap_or(Rgb::new(35, 50, 20)); buf[j * W + i] = u32::from_le_bytes([color.b, color.g, color.r, 255]); } diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index 8f60479481..fc923416aa 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -398,12 +398,14 @@ impl<'a> BlockGen<'a> { }; // 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() @@ -472,6 +474,7 @@ impl<'a> ZCache<'a> { let max = (ground_max + structure_max).max(self.sample.water_level + 2.0); // Structures + /* let (min, max) = self .sample .chunk @@ -483,6 +486,7 @@ impl<'a> ZCache<'a> { (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); diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index c7a8f1babf..2a7f02976b 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -1000,6 +1000,13 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { humidity.sub(CONFIG.jungle_hum).mul(1.0), ); + let ground = sim_chunk.sites.iter().fold(ground, |ground, site| { + site.get_surface(wpos) + .and_then(|block| block.get_color()) + .map(|col| col.map(|e| e as f32 / 255.0)) + .unwrap_or(ground) + }); + // Snow covering let snow_cover = temp .sub(CONFIG.snow_temp) @@ -1095,6 +1102,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { stone_col, chunk: sim_chunk, + /* spawn_rules: sim_chunk .structures .town @@ -1105,6 +1113,11 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { cliffs: !in_water, trees: true, }), + */ + spawn_rules: SpawnRules { + cliffs: !in_water, + trees: true, + }, }) } } diff --git a/world/src/generator/mod.rs b/world/src/generator/mod.rs index 6ac7240b8d..2b92859af5 100644 --- a/world/src/generator/mod.rs +++ b/world/src/generator/mod.rs @@ -2,12 +2,33 @@ pub mod settlement; mod town; // Reexports -pub use self::town::{TownGen, TownState}; +pub use self::{ + settlement::Settlement, + town::{TownGen, TownState}, +}; use crate::{column::ColumnSample, util::Sampler}; use common::terrain::Block; +use std::sync::Arc; use vek::*; +#[derive(Clone)] +pub enum Site { + Settlement(Arc), +} + +impl Site { + pub fn get_surface(&self, wpos: Vec2) -> Option { + match self { + Site::Settlement(settlement) => settlement.get_surface(wpos), + } + } +} + +impl From for Site { + fn from(settlement: Settlement) -> Self { Site::Settlement(Arc::new(settlement)) } +} + #[derive(Copy, Clone, Debug)] pub struct SpawnRules { pub trees: bool, diff --git a/world/src/generator/settlement/mod.rs b/world/src/generator/settlement/mod.rs index 8e353bfd84..74d9929967 100644 --- a/world/src/generator/settlement/mod.rs +++ b/world/src/generator/settlement/mod.rs @@ -1,5 +1,10 @@ use crate::util::{Sampler, StructureGen2d}; -use common::{astar::Astar, path::Path, spiral::Spiral2d}; +use common::{ + astar::Astar, + path::Path, + spiral::Spiral2d, + terrain::{Block, BlockKind}, +}; use hashbrown::{HashMap, HashSet}; use rand::prelude::*; use std::{collections::VecDeque, f32, marker::PhantomData}; @@ -56,7 +61,7 @@ pub fn center_of(p: [Vec2; 3]) -> Vec2 { Vec2::new(x, y) } -const AREA_SIZE: u32 = 48; +const AREA_SIZE: u32 = 64; fn to_tile(e: i32) -> i32 { ((e as f32).div_euclid(AREA_SIZE as f32)).floor() as i32 } @@ -70,6 +75,7 @@ pub struct Structure { } pub struct Settlement { + origin: Vec2, land: Land, farms: Store, structures: Vec, @@ -85,15 +91,16 @@ pub struct Farm { } impl Settlement { - pub fn generate(rng: &mut impl Rng) -> Self { + pub fn generate(wpos: Vec2, rng: &mut impl Rng) -> Self { let mut this = Self { + origin: wpos, land: Land::new(rng), farms: Store::default(), structures: Vec::new(), town: None, }; - this.place_river(rng); + //this.place_river(rng); this.place_farms(rng); this.place_town(rng); @@ -296,7 +303,12 @@ impl Settlement { } } - pub fn get_color(&self, pos: Vec2) -> Rgb { + pub fn get_surface(&self, wpos: Vec2) -> Option { + self.get_color((wpos - self.origin).map(|e| e as f32)) + .map(|col| Block::new(BlockKind::Normal, col)) + } + + pub fn get_color(&self, pos: Vec2) -> Option> { let pos = pos.map(|e| e.floor() as i32); if let Some(structure) = self @@ -304,13 +316,13 @@ impl Settlement { .iter() .find(|s| s.bounds.contains_point(pos)) { - return match structure.kind { + return Some(match structure.kind { StructureKind::House => Rgb::new(200, 80, 50), - }; + }); } - match self.land.get_at_block(pos) { - Sample::Wilderness => Rgb::zero(), + Some(match self.land.get_at_block(pos) { + Sample::Wilderness => return None, Sample::Way(WayKind::Path) => Rgb::new(130, 100, 0), Sample::Way(WayKind::Hedge) => Rgb::new(0, 150, 0), Sample::Way(WayKind::Wall) => Rgb::new(60, 60, 60), @@ -336,7 +348,7 @@ impl Settlement { let furrow = (pos * furrow_dir).sum().rem_euclid(4) < 2; Rgb::new( if furrow { - 150 + 120 } else { 48 + seed.to_le_bytes()[0] % 64 }, @@ -344,7 +356,7 @@ impl Settlement { 16 + seed.to_le_bytes()[2] % 32, ) }, - } + }) } } @@ -374,9 +386,9 @@ pub enum WayKind { impl WayKind { pub fn width(&self) -> f32 { match self { - WayKind::Path => 2.5, + WayKind::Path => 4.0, WayKind::Hedge => 1.5, - WayKind::Wall => 2.5, + WayKind::Wall => 3.5, } } } @@ -386,6 +398,14 @@ pub enum Tower { Wall, } +impl Tower { + pub fn radius(&self) -> f32 { + match self { + Tower::Wall => 8.0, + } + } +} + pub struct Tile { plot: Id, ways: [Option; 4], @@ -414,7 +434,7 @@ impl Land { Self { tiles: HashMap::new(), plots: Store::default(), - sampler_warp: StructureGen2d::new(rng.gen(), AREA_SIZE, 16), + sampler_warp: StructureGen2d::new(rng.gen(), AREA_SIZE, AREA_SIZE * 2 / 5), } } @@ -428,8 +448,8 @@ impl Land { let center_tile = self.tile_at(neighbors[4].0.map(to_tile)); - if neighbors[4].0.distance_squared(pos) < 6i32.pow(2) { - if let Some(tower) = center_tile.and_then(|tile| tile.tower.as_ref()) { + 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) { return Sample::Tower(tower); } } diff --git a/world/src/lib.rs b/world/src/lib.rs index acb9b94ca4..365c49adaa 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -16,7 +16,7 @@ pub use crate::config::CONFIG; use crate::{ block::BlockGen, column::{ColumnGen, ColumnSample}, - util::Sampler, + util::{Grid, Sampler}, }; use common::{ generation::{ChunkSupplement, EntityInfo, EntityKind}, @@ -95,7 +95,11 @@ 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 chunk_wpos2d = Vec2::from(chunk_pos) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32); + let zcache_grid = + Grid::populate_from(TerrainChunkSize::RECT_SIZE.map(|e| e as i32), |offs| { + sampler.get_z_cache(chunk_wpos2d + offs) + }); let mut chunk = TerrainChunk::new(base_z, stone, air, meta); for y in 0..TerrainChunkSize::RECT_SIZE.y as i32 { @@ -103,12 +107,13 @@ 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 wpos2d = chunk_wpos2d + offs; + + let z_cache = match zcache_grid.get(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 +124,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) = @@ -140,7 +145,7 @@ impl World { 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; 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/mod.rs b/world/src/sim/mod.rs index 9b75fd9bb4..e21efd45a1 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -15,7 +15,6 @@ pub use self::{ }, location::Location, map::{MapConfig, MapDebug}, - settlement::Settlement, util::{ cdf_irwin_hall, downhill, get_oceans, local_cells, map_edge_factor, neighbors, uniform_idx_as_vec2, uniform_noise, uphill, vec2_as_uniform_idx, InverseCdf, ScaleBias, @@ -27,7 +26,7 @@ use crate::{ all::ForestKind, block::BlockGen, column::ColumnGen, - generator::TownState, + generator::{Settlement, Site}, util::{seed_expan, FastNoise, RandomField, Sampler, StructureGen2d}, CONFIG, }; @@ -1450,7 +1449,7 @@ impl WorldSim { e * sz as i32 + sz as i32 / 2 }) }; - let maybe_towns = self + let sites = self .gen_ctx .town_gen .par_iter( @@ -1462,11 +1461,12 @@ impl WorldSim { |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))) + //TownState::generate(pos, &mut block_gen, &mut rng).map(|t| (pos, + // Arc::new(t))) + (pos, Site::from(Settlement::generate(pos, &mut rng))) }, ) - .filter_map(|x| x) - .collect::>(); + .collect::>(); let gen_ctx = &self.gen_ctx; self.chunks @@ -1476,21 +1476,13 @@ impl WorldSim { 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 + if let Some((pos, site)) = sites .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; + .filter(|(pos, _)| pos.distance_squared(wpos) < 1200i32.pow(2)) + .min_by_key(|(pos, _)| wpos.distance_squared(*pos)) + { + chunk.sites.push(site.clone()); + } }); // Create waypoints @@ -1771,7 +1763,7 @@ pub struct SimChunk { pub location: Option, pub river: RiverData, - pub structures: Structures, + pub sites: Vec, pub contains_waypoint: bool, } @@ -1789,11 +1781,6 @@ pub struct LocationInfo { 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); @@ -2015,7 +2002,7 @@ impl SimChunk { spawn_rate: 1.0, location: None, river, - structures: Structures { town: None }, + sites: Vec::new(), contains_waypoint: false, } } diff --git a/world/src/util/grid.rs b/world/src/util/grid.rs index b6a421ceb0..198679c8af 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(default_cell: T, size: Vec2) -> Self + where + T: Clone, + { Self { cells: vec![default_cell; size.product() as usize], size, From 085a115e2bc9b538d76fc0fef3ec14c4fb36e4ee Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 8 Feb 2020 11:38:44 +0000 Subject: [PATCH 076/195] Began work on post-generation town rendering, fixed overflow bug on large maps --- world/src/generator/mod.rs | 20 ++++++++++++++++++-- world/src/generator/settlement/mod.rs | 21 +++++++-------------- world/src/lib.rs | 5 +++++ world/src/sim/mod.rs | 6 +++++- 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/world/src/generator/mod.rs b/world/src/generator/mod.rs index 2b92859af5..6c0cfb95c2 100644 --- a/world/src/generator/mod.rs +++ b/world/src/generator/mod.rs @@ -7,8 +7,15 @@ pub use self::{ town::{TownGen, TownState}, }; -use crate::{column::ColumnSample, util::Sampler}; -use common::terrain::Block; +use crate::{ + block::ZCache, + column::ColumnSample, + util::{Grid, Sampler}, +}; +use common::{ + terrain::Block, + vol::{BaseVol, WriteVol}, +}; use std::sync::Arc; use vek::*; @@ -23,6 +30,15 @@ impl Site { Site::Settlement(settlement) => settlement.get_surface(wpos), } } + + pub fn apply_to( + &self, + wpos2d: Vec2, + zcaches: &Grid>, + vol: &mut (impl BaseVol + WriteVol), + ) { + // TODO + } } impl From for Site { diff --git a/world/src/generator/settlement/mod.rs b/world/src/generator/settlement/mod.rs index 74d9929967..f504f9093f 100644 --- a/world/src/generator/settlement/mod.rs +++ b/world/src/generator/settlement/mod.rs @@ -632,30 +632,23 @@ impl PartialEq for Id { } pub struct Store { - items: HashMap, - id_counter: usize, + items: Vec, } impl Default for Store { - fn default() -> Self { - Self { - items: HashMap::new(), - id_counter: 0, - } - } + 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(&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 get_mut(&mut self, id: Id) -> &mut T { self.items.get_mut(id.0).unwrap() } - pub fn iter(&self) -> impl Iterator { self.items.values() } + pub fn iter(&self) -> impl Iterator { self.items.iter() } pub fn insert(&mut self, item: T) -> Id { - self.id_counter += 1; - let id = Id(self.id_counter, PhantomData); - self.items.insert(id.0, item); + let id = Id(self.items.len(), PhantomData); + self.items.push(item); id } } diff --git a/world/src/lib.rs b/world/src/lib.rs index 365c49adaa..d69a3bce6f 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -136,6 +136,11 @@ impl World { } } + sim_chunk + .sites + .iter() + .for_each(|site| site.apply_to(chunk_wpos2d, &zcache_grid, &mut chunk)); + let gen_entity_pos = || { let lpos2d = TerrainChunkSize::RECT_SIZE .map(|sz| rand::thread_rng().gen::().rem_euclid(sz)); diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index e21efd45a1..f0de053a28 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -1478,7 +1478,11 @@ impl WorldSim { if let Some((pos, site)) = sites .iter() - .filter(|(pos, _)| pos.distance_squared(wpos) < 1200i32.pow(2)) + .filter(|(pos, _)| { + pos.map(|e| e as i64) + .distance_squared(wpos.map(|e| e as i64)) + < 1200i64.pow(2) + }) .min_by_key(|(pos, _)| wpos.distance_squared(*pos)) { chunk.sites.push(site.clone()); From 5d5e8e32380a86230b39b0d395c16c9ed85f5d21 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 8 Feb 2020 19:58:42 +0000 Subject: [PATCH 077/195] Added basic wall and tower rendering --- world/examples/settlement_viewer.rs | 4 +- world/src/block/mod.rs | 2 +- world/src/generator/mod.rs | 4 +- world/src/generator/settlement/mod.rs | 53 ++++++++++++++++++++++++--- 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/world/examples/settlement_viewer.rs b/world/examples/settlement_viewer.rs index 2d963d07a9..878967a5f5 100644 --- a/world/examples/settlement_viewer.rs +++ b/world/examples/settlement_viewer.rs @@ -24,7 +24,9 @@ fn main() { for j in 0..H { let pos = focus + win_to_pos(Vec2::new(i, j)) * zoom; - let color = settlement.get_color(pos).unwrap_or(Rgb::new(35, 50, 20)); + 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]); } diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index fc923416aa..57e72eed5c 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -421,7 +421,7 @@ impl<'a> BlockGen<'a> { pub struct ZCache<'a> { wpos: Vec2, - sample: ColumnSample<'a>, + pub sample: ColumnSample<'a>, structures: [Option<(StructureInfo, ColumnSample<'a>)>; 9], } diff --git a/world/src/generator/mod.rs b/world/src/generator/mod.rs index 6c0cfb95c2..25dc11924b 100644 --- a/world/src/generator/mod.rs +++ b/world/src/generator/mod.rs @@ -37,7 +37,9 @@ impl Site { zcaches: &Grid>, vol: &mut (impl BaseVol + WriteVol), ) { - // TODO + match self { + Site::Settlement(settlement) => settlement.apply_to(wpos2d, zcaches, vol), + } } } diff --git a/world/src/generator/settlement/mod.rs b/world/src/generator/settlement/mod.rs index f504f9093f..491ac6dd97 100644 --- a/world/src/generator/settlement/mod.rs +++ b/world/src/generator/settlement/mod.rs @@ -1,9 +1,13 @@ -use crate::util::{Sampler, StructureGen2d}; +use crate::{ + block::ZCache, + util::{Grid, Sampler, StructureGen2d}, +}; use common::{ astar::Astar, path::Path, spiral::Spiral2d, terrain::{Block, BlockKind}, + vol::{BaseVol, WriteVol}, }; use hashbrown::{HashMap, HashSet}; use rand::prelude::*; @@ -303,14 +307,53 @@ impl Settlement { } } + pub fn apply_to( + &self, + wpos2d: Vec2, + zcaches: &Grid>, + vol: &mut (impl BaseVol + WriteVol), + ) { + for y in 0..zcaches.size().y { + for x in 0..zcaches.size().x { + let offs = Vec2::new(x, y); + + let zcache = if let Some(Some(zcache)) = zcaches.get(offs) { + zcache + } else { + continue; + }; + + let wpos2d = wpos2d + offs; + + match self.land.get_at_block(wpos2d - self.origin) { + Sample::Way(WayKind::Wall) => { + for z in 0..12 { + vol.set( + Vec3::new(offs.x, offs.y, zcache.sample.alt.floor() as i32 + z), + Block::new(BlockKind::Normal, Rgb::new(60, 60, 60)), + ); + } + }, + Sample::Tower(Tower::Wall) => { + for z in 0..16 { + vol.set( + Vec3::new(offs.x, offs.y, zcache.sample.alt.floor() as i32 + z), + Block::new(BlockKind::Normal, Rgb::new(50, 50, 50)), + ); + } + }, + _ => {}, + } + } + } + } + pub fn get_surface(&self, wpos: Vec2) -> Option { - self.get_color((wpos - self.origin).map(|e| e as f32)) + self.get_color(wpos - self.origin) .map(|col| Block::new(BlockKind::Normal, col)) } - pub fn get_color(&self, pos: Vec2) -> Option> { - let pos = pos.map(|e| e.floor() as i32); - + pub fn get_color(&self, pos: Vec2) -> Option> { if let Some(structure) = self .structures .iter() From 9dc46c490e3b0905380c822d7d13e841bd1642c4 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sun, 9 Feb 2020 21:56:52 +0000 Subject: [PATCH 078/195] Better town walls, made settlements avoid rivers --- world/examples/settlement_viewer.rs | 2 +- world/src/generator/mod.rs | 6 ++ world/src/generator/settlement/mod.rs | 132 +++++++++++++++++++------- world/src/sim/erosion.rs | 2 + world/src/sim/mod.rs | 11 ++- 5 files changed, 111 insertions(+), 42 deletions(-) diff --git a/world/examples/settlement_viewer.rs b/world/examples/settlement_viewer.rs index 878967a5f5..ca3da4a5b4 100644 --- a/world/examples/settlement_viewer.rs +++ b/world/examples/settlement_viewer.rs @@ -9,7 +9,7 @@ fn main() { let mut win = minifb::Window::new("Settlement Viewer", W, H, minifb::WindowOptions::default()).unwrap(); - let settlement = Settlement::generate(Vec2::zero(), &mut thread_rng()); + let settlement = Settlement::generate(Vec2::zero(), None, &mut thread_rng()); let mut focus = Vec2::::zero(); let mut zoom = 1.0; diff --git a/world/src/generator/mod.rs b/world/src/generator/mod.rs index 25dc11924b..66a6aea4fc 100644 --- a/world/src/generator/mod.rs +++ b/world/src/generator/mod.rs @@ -25,6 +25,12 @@ pub enum Site { } impl Site { + pub fn radius(&self) -> f32 { + match self { + Site::Settlement(settlement) => settlement.radius(), + } + } + pub fn get_surface(&self, wpos: Vec2) -> Option { match self { Site::Settlement(settlement) => settlement.get_surface(wpos), diff --git a/world/src/generator/settlement/mod.rs b/world/src/generator/settlement/mod.rs index 491ac6dd97..bab462ba22 100644 --- a/world/src/generator/settlement/mod.rs +++ b/world/src/generator/settlement/mod.rs @@ -1,6 +1,7 @@ use crate::{ block::ZCache, - util::{Grid, Sampler, StructureGen2d}, + sim::{SimChunk, WorldSim}, + util::{Grid, RandomField, Sampler, StructureGen2d}, }; use common::{ astar::Astar, @@ -65,7 +66,13 @@ pub fn center_of(p: [Vec2; 3]) -> Vec2 { Vec2::new(x, y) } -const AREA_SIZE: u32 = 64; +impl SimChunk { + fn can_host_settlement(&self) -> bool { + !self.near_cliffs && !self.river.is_river() && !self.river.is_lake() + } +} + +const AREA_SIZE: u32 = 32; fn to_tile(e: i32) -> i32 { ((e as f32).div_euclid(AREA_SIZE as f32)).floor() as i32 } @@ -95,7 +102,7 @@ pub struct Farm { } impl Settlement { - pub fn generate(wpos: Vec2, rng: &mut impl Rng) -> Self { + pub fn generate(wpos: Vec2, sim: Option<&WorldSim>, rng: &mut impl Rng) -> Self { let mut this = Self { origin: wpos, land: Land::new(rng), @@ -104,6 +111,10 @@ impl Settlement { town: None, }; + if let Some(sim) = sim { + this.designate_from_world(sim, rng); + } + //this.place_river(rng); this.place_farms(rng); @@ -113,6 +124,28 @@ impl Settlement { this } + 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.new_plot(Plot::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| { + sim.get_wpos(wpos + offs * AREA_SIZE as i32 / 2) + .map(|chunk| !chunk.can_host_settlement()) + .unwrap_or(true) + }) + { + self.land.set(tile, hazard); + } + }) + } + 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; @@ -145,12 +178,14 @@ impl Settlement { } pub fn place_paths(&mut self, rng: &mut impl Rng) { + const PATH_COUNT: usize = 6; + let mut dir = Vec2::zero(); - for _ in 0..6 { + 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 * 20.0) as i32); + let origin = dir.map(|e| (e * 100.0) as i32); let origin = self .land .find_tile_near(origin, |plot| match plot { @@ -164,6 +199,7 @@ impl Settlement { .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 @@ -176,22 +212,25 @@ impl Settlement { }) }) { let path = path.iter().copied().collect::>(); - self.land.write_path(&path, WayKind::Path, |_| true, true); + self.land.write_path(&path, WayKind::Path, |_| true, false); } } } pub fn place_town(&mut self, rng: &mut impl Rng) { + const PLOT_COUNT: usize = 2; + let mut origin = Vec2::new(rng.gen_range(-2, 3), rng.gen_range(-2, 3)); - let town = self.land.new_plot(Plot::Town); - for i in 0..6 { + 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.set(base_tile, town); + self.land + .plot_at_mut(base_tile) + .map(|plot| *plot = Plot::Town); if i == 0 { /* @@ -222,6 +261,7 @@ impl Settlement { .find_path(spokes[i], spokes[(i + 1) % spokes.len()], |_, to| match to .map(|to| self.land.plot(to.plot)) { + Some(Plot::Hazard) => 10000.0, Some(Plot::Town) => 1000.0, _ => 1.0, }) @@ -248,7 +288,10 @@ impl Settlement { } pub fn place_farms(&mut self, rng: &mut impl Rng) { - for _ in 0..6 { + const FARM_COUNT: usize = 4; + 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()) @@ -258,7 +301,7 @@ impl Settlement { self.land.set(base_tile, farmhouse); // Farmhouses - for _ in 0..rng.gen_range(1, 4) { + for _ in 0..rng.gen_range(1, 3) { let house_pos = base_tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32 / 2) + Vec2::new(rng.gen_range(-16, 16), rng.gen_range(-16, 16)); @@ -273,7 +316,7 @@ impl Settlement { // Fields let farmland = self.farms.insert(Farm { base_tile }); - for _ in 0..5 { + for _ in 0..FIELDS_PER_FARM { self.place_field(farmland, base_tile, rng); } } @@ -286,18 +329,18 @@ impl Settlement { origin: Vec2, rng: &mut impl Rng, ) -> Option> { - let max_size = 7; + 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(), }); - let tiles = self - .land - .grow_from(center, rng.gen_range(1, max_size), rng, |plot| { - plot.is_none() - }); + 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); } @@ -307,12 +350,16 @@ impl Settlement { } } + pub fn radius(&self) -> f32 { 1200.0 } + pub fn apply_to( &self, wpos2d: Vec2, zcaches: &Grid>, vol: &mut (impl BaseVol + WriteVol), ) { + let rand_field = RandomField::new(0); + for y in 0..zcaches.size().y { for x in 0..zcaches.size().x { let offs = Vec2::new(x, y); @@ -326,15 +373,25 @@ impl Settlement { let wpos2d = wpos2d + offs; match self.land.get_at_block(wpos2d - self.origin) { - Sample::Way(WayKind::Wall) => { + Sample::Way(WayKind::Wall, dist) => { + let color = Lerp::lerp( + Rgb::new(130i32, 100, 0), + Rgb::new(90, 70, 50), + (rand_field.get(wpos2d.into()) % 256) as f32 / 256.0, + ) + .map(|e| (e % 256) as u8); for z in 0..12 { - vol.set( - Vec3::new(offs.x, offs.y, zcache.sample.alt.floor() as i32 + z), - Block::new(BlockKind::Normal, Rgb::new(60, 60, 60)), - ); + if dist / WayKind::Wall.width() + < ((1.0 - z as f32 / 12.0) * 2.0).min(1.0) + { + vol.set( + Vec3::new(offs.x, offs.y, zcache.sample.alt.floor() as i32 + z), + Block::new(BlockKind::Normal, color), + ); + } } }, - Sample::Tower(Tower::Wall) => { + Sample::Tower(Tower::Wall, _pos) => { for z in 0..16 { vol.set( Vec3::new(offs.x, offs.y, zcache.sample.alt.floor() as i32 + z), @@ -366,11 +423,12 @@ impl Settlement { Some(match self.land.get_at_block(pos) { Sample::Wilderness => return None, - Sample::Way(WayKind::Path) => Rgb::new(130, 100, 0), - Sample::Way(WayKind::Hedge) => Rgb::new(0, 150, 0), - Sample::Way(WayKind::Wall) => Rgb::new(60, 60, 60), - Sample::Tower(Tower::Wall) => Rgb::new(50, 50, 50), - Sample::Plot(Plot::Dirt) => Rgb::new(130, 100, 0), + Sample::Plot(Plot::Hazard) => return None, + Sample::Way(WayKind::Path, _) => Rgb::new(90, 70, 50), + Sample::Way(WayKind::Hedge, _) => Rgb::new(0, 150, 0), + Sample::Way(WayKind::Wall, _) => Rgb::new(60, 60, 60), + Sample::Tower(Tower::Wall, _) => Rgb::new(50, 50, 50), + Sample::Plot(Plot::Dirt) => Rgb::new(90, 70, 50), Sample::Plot(Plot::Grass) => Rgb::new(100, 200, 0), Sample::Plot(Plot::Water) => Rgb::new(100, 150, 250), Sample::Plot(Plot::Town) => { @@ -405,6 +463,7 @@ impl Settlement { #[derive(Copy, Clone, PartialEq)] pub enum Plot { + Hazard, Dirt, Grass, Water, @@ -431,7 +490,7 @@ impl WayKind { match self { WayKind::Path => 4.0, WayKind::Hedge => 1.5, - WayKind::Wall => 3.5, + WayKind::Wall => 2.5, } } } @@ -444,7 +503,7 @@ pub enum Tower { impl Tower { pub fn radius(&self) -> f32 { match self { - Tower::Wall => 8.0, + Tower::Wall => 6.0, } } } @@ -462,8 +521,8 @@ impl Tile { pub enum Sample<'a> { Wilderness, Plot(&'a Plot), - Way(&'a WayKind), - Tower(&'a Tower), + Way(&'a WayKind, f32), + Tower(&'a Tower, Vec2), } pub struct Land { @@ -493,7 +552,7 @@ impl Land { 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) { - return Sample::Tower(tower); + return Sample::Tower(tower, neighbors[4].0); } } @@ -504,8 +563,9 @@ impl Land { neighbors[map[i]].0.map(|e| e as f32), ]; if let Some(way) = center_tile.and_then(|tile| tile.ways[i].as_ref()) { - if dist_to_line(line, pos.map(|e| e as f32)) < way.width() { - return Sample::Way(way); + let dist = dist_to_line(line, pos.map(|e| e as f32)); + if dist < way.width() { + return Sample::Way(way, dist); } } } diff --git a/world/src/sim/erosion.rs b/world/src/sim/erosion.rs index d359e04baf..b737bf2798 100644 --- a/world/src/sim/erosion.rs +++ b/world/src/sim/erosion.rs @@ -200,6 +200,8 @@ impl RiverData { .map(RiverKind::is_lake) .unwrap_or(false) } + + pub fn near_river(&self) -> bool { self.is_river() || self.neighbor_rivers.len() > 0 } } /// Draw rivers and assign them heights, widths, and velocities. Take some diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index f0de053a28..c4423b46ab 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -1463,7 +1463,10 @@ impl WorldSim { // println!("Town: {:?}", town); //TownState::generate(pos, &mut block_gen, &mut rng).map(|t| (pos, // Arc::new(t))) - (pos, Site::from(Settlement::generate(pos, &mut rng))) + ( + pos, + Site::from(Settlement::generate(pos, Some(self), &mut rng)), + ) }, ) .collect::>(); @@ -1478,10 +1481,8 @@ impl WorldSim { if let Some((pos, site)) = sites .iter() - .filter(|(pos, _)| { - pos.map(|e| e as i64) - .distance_squared(wpos.map(|e| e as i64)) - < 1200i64.pow(2) + .filter(|(pos, site)| { + pos.map(|e| e as f32).distance(wpos.map(|e| e as f32)) < site.radius() }) .min_by_key(|(pos, _)| wpos.distance_squared(*pos)) { From 5f2d0021a3763c4b452ab4862ff36791dbb37c99 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Tue, 11 Feb 2020 12:18:08 +0000 Subject: [PATCH 079/195] Reversed water draw order --- assets/voxygen/shaders/fluid-frag/shiny.glsl | 4 ++-- voxygen/src/scene/terrain.rs | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/assets/voxygen/shaders/fluid-frag/shiny.glsl b/assets/voxygen/shaders/fluid-frag/shiny.glsl index 8d06ee468c..12af11e6f3 100644 --- a/assets/voxygen/shaders/fluid-frag/shiny.glsl +++ b/assets/voxygen/shaders/fluid-frag/shiny.glsl @@ -89,9 +89,9 @@ void main() { 0.1 / slope ); - nmap = mix(vec3(0, 0, 1), normalize(nmap), min(1.0 / pow(frag_dist, 0.75), 1)); + nmap = mix(f_norm, 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; + vec3 norm = vec3(0, 0, 1) * nmap.z + b_norm * nmap.x + c_norm * nmap.y; vec3 light, diffuse_light, ambient_light; get_sun_diffuse(norm, time_of_day.x, light, diffuse_light, ambient_light, 0.0); diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index 74a2f94852..8ae5b602a2 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -1483,6 +1483,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, globals, locals, lights, shadows, &self.waves) }); From 3d4a294b83a5f37ae8aa46542b2153c99b414eb2 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 17 Feb 2020 15:28:17 +0000 Subject: [PATCH 080/195] Smoothed camera motion, gentler orientation lerping --- voxygen/src/scene/camera.rs | 45 ++++++++++++++++++++++++++++++------- voxygen/src/scene/mod.rs | 2 +- voxygen/src/scene/simple.rs | 2 +- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/voxygen/src/scene/camera.rs b/voxygen/src/scene/camera.rs index 3847b0293e..020263f035 100644 --- a/voxygen/src/scene/camera.rs +++ b/voxygen/src/scene/camera.rs @@ -8,6 +8,7 @@ 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 @@ -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, @@ -121,21 +124,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. @@ -175,7 +190,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) { // 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 { @@ -193,6 +208,20 @@ 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) + }; + + 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), + )); } pub fn interp_time(&self) -> f32 { diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index d8a2fbe300..4689ea6fc2 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -240,7 +240,7 @@ impl Scene { ); // 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()); // Compute camera matrices. self.camera.compute_dependents(&*scene_data.state.terrain()); diff --git a/voxygen/src/scene/simple.rs b/voxygen/src/scene/simple.rs index e663fb841f..7a17754231 100644 --- a/voxygen/src/scene/simple.rs +++ b/voxygen/src/scene/simple.rs @@ -146,7 +146,7 @@ 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); self.camera.compute_dependents(&VoidVol); let camera::Dependents { From d91d66027a147171ff22395b5955bc0415b3d528 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Tue, 18 Feb 2020 12:57:33 +0000 Subject: [PATCH 081/195] Fixed cloud bugs, made pets run into you less --- assets/voxygen/shaders/include/cloud/regular.glsl | 8 ++++---- common/src/sys/agent.rs | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/assets/voxygen/shaders/include/cloud/regular.glsl b/assets/voxygen/shaders/include/cloud/regular.glsl index 285a5d3646..8d39471076 100644 --- a/assets/voxygen/shaders/include/cloud/regular.glsl +++ b/assets/voxygen/shaders/include/cloud/regular.glsl @@ -1,8 +1,8 @@ uniform sampler2D t_noise; const float CLOUD_AVG_HEIGHT = 1025.0; -const float CLOUD_HEIGHT_MIN = CLOUD_AVG_HEIGHT - 50.0; -const float CLOUD_HEIGHT_MAX = CLOUD_AVG_HEIGHT + 50.0; +const float CLOUD_HEIGHT_MIN = CLOUD_AVG_HEIGHT - 60.0; +const float CLOUD_HEIGHT_MAX = CLOUD_AVG_HEIGHT + 60.0; const float CLOUD_THRESHOLD = 0.25; const float CLOUD_SCALE = 5.0; const float CLOUD_DENSITY = 100.0; @@ -67,8 +67,8 @@ 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)) { - break; + if (passthrough < 0.025 || (passthrough > 0.8 && dist > (maxd + mind) * 0.7) && mind > 100.0) { + //break; } } } diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index b11dcf22bb..b47f05f2b9 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -99,6 +99,7 @@ impl<'a> System<'a> for Sys { - *bearing * 0.01 - if let Some(patrol_origin) = agent.patrol_origin { Vec2::::from(pos.0 - patrol_origin) * 0.0002 + + Vec3::one() / Vec2::::from(pos.0 - patrol_origin) } else { Vec2::zero() }; From df5aa93fd7c0e5f8a48536d3db5fd1ff905641c7 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 21 Feb 2020 10:51:59 +0000 Subject: [PATCH 082/195] Better AO, fixed cloud pixelation --- assets/voxygen/shaders/include/cloud/regular.glsl | 5 +---- voxygen/src/mesh/segment.rs | 2 +- voxygen/src/mesh/vol.rs | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/assets/voxygen/shaders/include/cloud/regular.glsl b/assets/voxygen/shaders/include/cloud/regular.glsl index 8d39471076..bbd5ed8471 100644 --- a/assets/voxygen/shaders/include/cloud/regular.glsl +++ b/assets/voxygen/shaders/include/cloud/regular.glsl @@ -16,15 +16,12 @@ vec2 cloud_at(vec3 pos) { 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.0015).x * 0.25; 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 diff --git a/voxygen/src/mesh/segment.rs b/voxygen/src/mesh/segment.rs index f01513e6df..6c7ef46835 100644 --- a/voxygen/src/mesh/segment.rs +++ b/voxygen/src/mesh/segment.rs @@ -87,7 +87,7 @@ impl Meshable for Segment { SpriteVertex::new( origin, norm, - linear_to_srgb(srgb_to_linear(col) * ao * light), + linear_to_srgb(srgb_to_linear(col) * light.min(ao)), ) }, &[[[1.0; 3]; 3]; 3], diff --git a/voxygen/src/mesh/vol.rs b/voxygen/src/mesh/vol.rs index 76979bf2e8..46ea72808a 100644 --- a/voxygen/src/mesh/vol.rs +++ b/voxygen/src/mesh/vol.rs @@ -112,7 +112,7 @@ fn create_quad, Vec3, Rgb, f32, f32) -> P let darkness = darkness_ao.map(|e| e.0); let ao = darkness_ao.map(|e| e.1); - let ao_map = ao; + let ao_map = ao * 0.75 + 0.25; if ao[0].min(ao[2]).min(darkness[0]).min(darkness[2]) < ao[1].min(ao[3]).min(darkness[1]).min(darkness[3]) From b9cc0f387b2af70965b2e0a86b30d224687e9c04 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 21 Feb 2020 11:07:02 +0000 Subject: [PATCH 083/195] Cleaner clouds, better movement --- .../shaders/include/cloud/regular.glsl | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/assets/voxygen/shaders/include/cloud/regular.glsl b/assets/voxygen/shaders/include/cloud/regular.glsl index bbd5ed8471..4f5fdb5559 100644 --- a/assets/voxygen/shaders/include/cloud/regular.glsl +++ b/assets/voxygen/shaders/include/cloud/regular.glsl @@ -1,9 +1,9 @@ uniform sampler2D t_noise; const float CLOUD_AVG_HEIGHT = 1025.0; -const float CLOUD_HEIGHT_MIN = CLOUD_AVG_HEIGHT - 60.0; -const float CLOUD_HEIGHT_MAX = CLOUD_AVG_HEIGHT + 60.0; -const float CLOUD_THRESHOLD = 0.25; +const float CLOUD_HEIGHT_MIN = CLOUD_AVG_HEIGHT - 30.0; +const float CLOUD_HEIGHT_MAX = CLOUD_AVG_HEIGHT + 30.0; +const float CLOUD_THRESHOLD = 0.27; const float CLOUD_SCALE = 5.0; const float CLOUD_DENSITY = 100.0; @@ -15,8 +15,8 @@ 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.0015).x * 0.25; + + 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 @@ -25,11 +25,11 @@ vec2 cloud_at(vec3 pos) { ) / 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 - CLOUD_AVG_HEIGHT) / 400.0, 0.0) * CLOUD_DENSITY; + float density = max((value - CLOUD_THRESHOLD) - abs(pos.z - CLOUD_AVG_HEIGHT) / 200.0, 0.0) * CLOUD_DENSITY; const float SHADE_GRADIENT = 1.5 / (CLOUD_AVG_HEIGHT - CLOUD_HEIGHT_MIN); float shade = ((pos.z - CLOUD_AVG_HEIGHT) / (CLOUD_HEIGHT_MAX - CLOUD_HEIGHT_MIN)) * 2.5 + 0.7; @@ -64,8 +64,8 @@ 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.025 || (passthrough > 0.8 && dist > (maxd + mind) * 0.7) && mind > 100.0) { - //break; + if (passthrough < 0.1) { + break; } } } From 1c6a6cd6cf003cc1cb90c2fb7217938cb47d2dd5 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 26 Feb 2020 21:43:31 +0000 Subject: [PATCH 084/195] Moved settlement code to site directory, removed old town generation code --- world/src/block/mod.rs | 34 +- world/src/block/natural.rs | 1 - world/src/column/mod.rs | 18 - world/src/generator/town/mod.rs | 710 ------------------ world/src/generator/town/util.rs | 36 - world/src/generator/town/vol.rs | 215 ------ world/src/lib.rs | 2 +- world/src/sim/mod.rs | 2 +- world/src/{generator => site}/mod.rs | 39 +- .../src/{generator => site}/settlement/mod.rs | 0 10 files changed, 6 insertions(+), 1051 deletions(-) delete mode 100644 world/src/generator/town/mod.rs delete mode 100644 world/src/generator/town/util.rs delete mode 100644 world/src/generator/town/vol.rs rename world/src/{generator => site}/mod.rs (54%) rename world/src/{generator => site}/settlement/mod.rs (100%) diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index 57e72eed5c..3a6cfd571a 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] @@ -80,7 +75,7 @@ impl<'a> BlockGen<'a> { 0.0 }, ) - } + }, _ => max_height, }, ) @@ -397,16 +392,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| { @@ -473,21 +458,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) diff --git a/world/src/block/natural.rs b/world/src/block/natural.rs index 0c2814dada..0f285fef28 100644 --- a/world/src/block/natural.rs +++ b/world/src/block/natural.rs @@ -28,7 +28,6 @@ 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 { return None; } diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index 2a7f02976b..d822099bd2 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -1,7 +1,6 @@ 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, @@ -1102,22 +1101,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { stone_col, 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, - }), - */ - spawn_rules: SpawnRules { - cliffs: !in_water, - trees: true, - }, }) } } @@ -1150,7 +1133,6 @@ pub struct ColumnSample<'a> { pub stone_col: Rgb, pub chunk: &'a SimChunk, - pub spawn_rules: SpawnRules, } #[derive(Copy, Clone)] 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/lib.rs b/world/src/lib.rs index d69a3bce6f..00e627bd46 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -6,8 +6,8 @@ mod all; mod block; mod column; pub mod config; -pub mod generator; pub mod sim; +pub mod site; pub mod util; // Reexports diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index c4423b46ab..16552bd397 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -26,7 +26,7 @@ use crate::{ all::ForestKind, block::BlockGen, column::ColumnGen, - generator::{Settlement, Site}, + site::{Settlement, Site}, util::{seed_expan, FastNoise, RandomField, Sampler, StructureGen2d}, CONFIG, }; diff --git a/world/src/generator/mod.rs b/world/src/site/mod.rs similarity index 54% rename from world/src/generator/mod.rs rename to world/src/site/mod.rs index 66a6aea4fc..e42016b9ee 100644 --- a/world/src/generator/mod.rs +++ b/world/src/site/mod.rs @@ -1,11 +1,7 @@ -pub mod settlement; -mod town; +mod settlement; // Reexports -pub use self::{ - settlement::Settlement, - town::{TownGen, TownState}, -}; +pub use self::settlement::Settlement; use crate::{ block::ZCache, @@ -52,34 +48,3 @@ impl Site { impl From for Site { fn from(settlement: Settlement) -> Self { Site::Settlement(Arc::new(settlement)) } } - -#[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/settlement/mod.rs b/world/src/site/settlement/mod.rs similarity index 100% rename from world/src/generator/settlement/mod.rs rename to world/src/site/settlement/mod.rs From 41b77a9b107198a482d9b80cda05f3a186af24d3 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 26 Feb 2020 22:44:30 +0000 Subject: [PATCH 085/195] Removed old settlement code, removed zcache from dependency of site generators for forward compatibility --- common/src/sys/agent.rs | 5 +- world/src/lib.rs | 18 +++++-- world/src/sim/mod.rs | 1 - world/src/sim/settlement.rs | 86 -------------------------------- world/src/site/mod.rs | 13 +++-- world/src/site/settlement/mod.rs | 34 +++++++------ 6 files changed, 42 insertions(+), 115 deletions(-) delete mode 100644 world/src/sim/settlement.rs diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index b47f05f2b9..941cff84ab 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -109,7 +109,10 @@ impl<'a> System<'a> for Sys { .ray( pos.0 + Vec3::unit_z(), pos.0 - + Vec3::from(*bearing).normalized() * 1.5 + + Vec3::from(*bearing) + .try_normalized() + .unwrap_or(Vec3::zero()) + * 1.5 + Vec3::unit_z(), ) .until(|block| block.is_solid()) diff --git a/world/src/lib.rs b/world/src/lib.rs index 00e627bd46..2d23d2aba3 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -136,10 +136,20 @@ impl World { } } - sim_chunk - .sites - .iter() - .for_each(|site| site.apply_to(chunk_wpos2d, &zcache_grid, &mut chunk)); + // Apply site generation + sim_chunk.sites.iter().for_each(|site| { + site.apply_to( + chunk_wpos2d, + |offs| { + zcache_grid + .get(offs) + .map(Option::as_ref) + .flatten() + .map(|zc| &zc.sample) + }, + &mut chunk, + ) + }); let gen_entity_pos = || { let lpos2d = TerrainChunkSize::RECT_SIZE diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 16552bd397..5eae203b22 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -2,7 +2,6 @@ mod diffusion; mod erosion; mod location; mod map; -mod settlement; mod util; // Reexports 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/mod.rs b/world/src/site/mod.rs index e42016b9ee..6245c40e07 100644 --- a/world/src/site/mod.rs +++ b/world/src/site/mod.rs @@ -4,13 +4,12 @@ mod settlement; pub use self::settlement::Settlement; use crate::{ - block::ZCache, column::ColumnSample, util::{Grid, Sampler}, }; use common::{ terrain::Block, - vol::{BaseVol, WriteVol}, + vol::{BaseVol, RectSizedVol, WriteVol}, }; use std::sync::Arc; use vek::*; @@ -33,14 +32,14 @@ impl Site { } } - pub fn apply_to( - &self, + pub fn apply_to<'a>( + &'a self, wpos2d: Vec2, - zcaches: &Grid>, - vol: &mut (impl BaseVol + WriteVol), + get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, + vol: &mut (impl BaseVol + RectSizedVol + WriteVol), ) { match self { - Site::Settlement(settlement) => settlement.apply_to(wpos2d, zcaches, vol), + Site::Settlement(settlement) => settlement.apply_to(wpos2d, get_column, vol), } } } diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index bab462ba22..6f19c608bd 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -1,5 +1,5 @@ use crate::{ - block::ZCache, + column::ColumnSample, sim::{SimChunk, WorldSim}, util::{Grid, RandomField, Sampler, StructureGen2d}, }; @@ -8,7 +8,7 @@ use common::{ path::Path, spiral::Spiral2d, terrain::{Block, BlockKind}, - vol::{BaseVol, WriteVol}, + vol::{BaseVol, RectSizedVol, WriteVol}, }; use hashbrown::{HashMap, HashSet}; use rand::prelude::*; @@ -124,6 +124,7 @@ impl Settlement { this } + /// 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.new_plot(Plot::Hazard); @@ -146,6 +147,7 @@ impl Settlement { }) } + /// 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; @@ -352,20 +354,20 @@ impl Settlement { pub fn radius(&self) -> f32 { 1200.0 } - pub fn apply_to( - &self, + pub fn apply_to<'a>( + &'a self, wpos2d: Vec2, - zcaches: &Grid>, - vol: &mut (impl BaseVol + WriteVol), + mut get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, + vol: &mut (impl BaseVol + RectSizedVol + WriteVol), ) { let rand_field = RandomField::new(0); - for y in 0..zcaches.size().y { - for x in 0..zcaches.size().x { + 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 zcache = if let Some(Some(zcache)) = zcaches.get(offs) { - zcache + let col_sample = if let Some(col_sample) = get_column(offs) { + col_sample } else { continue; }; @@ -385,7 +387,7 @@ impl Settlement { < ((1.0 - z as f32 / 12.0) * 2.0).min(1.0) { vol.set( - Vec3::new(offs.x, offs.y, zcache.sample.alt.floor() as i32 + z), + Vec3::new(offs.x, offs.y, col_sample.alt.floor() as i32 + z), Block::new(BlockKind::Normal, color), ); } @@ -394,7 +396,7 @@ impl Settlement { Sample::Tower(Tower::Wall, _pos) => { for z in 0..16 { vol.set( - Vec3::new(offs.x, offs.y, zcache.sample.alt.floor() as i32 + z), + Vec3::new(offs.x, offs.y, col_sample.alt.floor() as i32 + z), Block::new(BlockKind::Normal, Rgb::new(50, 50, 50)), ); } @@ -446,14 +448,14 @@ impl Settlement { Vec2::new(-1, 1), ]; let furrow_dir = furrow_dirs[*seed as usize % furrow_dirs.len()]; - let furrow = (pos * furrow_dir).sum().rem_euclid(4) < 2; + let furrow = (pos * furrow_dir).sum().rem_euclid(6) < 3; Rgb::new( if furrow { - 120 + 100 } else { - 48 + seed.to_le_bytes()[0] % 64 + 32 + seed.to_le_bytes()[0] % 64 }, - 128 + seed.to_le_bytes()[1] % 128, + 64 + seed.to_le_bytes()[1] % 128, 16 + seed.to_le_bytes()[2] % 32, ) }, From c1514fc37b6249b4dd40beafa2931bc8826ee8ff Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 27 Mar 2020 13:16:02 +0000 Subject: [PATCH 086/195] Added initial civ generation --- world/examples/view.rs | 10 +- world/src/civ/mod.rs | 248 +++++++++++++++++++++++++++++++ world/src/lib.rs | 8 +- world/src/sim/erosion.rs | 15 ++ world/src/sim/map.rs | 10 +- world/src/sim/mod.rs | 23 ++- world/src/site/settlement/mod.rs | 37 +---- 7 files changed, 306 insertions(+), 45 deletions(-) create mode 100644 world/src/civ/mod.rs diff --git a/world/examples/view.rs b/world/examples/view.rs index eab8ef1398..3bb1b7e440 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/src/civ/mod.rs b/world/src/civ/mod.rs new file mode 100644 index 0000000000..402506e31f --- /dev/null +++ b/world/src/civ/mod.rs @@ -0,0 +1,248 @@ +use std::ops::Range; +use hashbrown::{HashMap, HashSet}; +use vek::*; +use rand::prelude::*; +use common::{ + terrain::TerrainChunkSize, + vol::RectVolSize, + store::{Id, Store}, + path::Path, + astar::Astar, +}; +use crate::sim::WorldSim; + +const CARDINALS: [Vec2; 4] = [ + Vec2::new(1, 0), + Vec2::new(-1, 0), + Vec2::new(0, 1), + Vec2::new(0, -1), +]; + +const DIAGONALS: [Vec2; 8] = [ + Vec2::new(1, 0), + Vec2::new(1, 1), + Vec2::new(-1, 0), + Vec2::new(-1, 1), + Vec2::new(0, 1), + Vec2::new(1, -1), + Vec2::new(0, -1), + Vec2::new(-1, -1), +]; + +fn attempt(max_iters: usize, mut f: impl FnMut() -> Option) -> Option { + (0..max_iters).find_map(|_| f()) +} + +const INITIAL_CIV_COUNT: usize = 20; + +#[derive(Default)] +pub struct Civs { + civs: Store, + places: Store, + routes: HashMap<(Id, Id), Route>, +} + +struct GenCtx<'a, R: Rng> { + sim: &'a mut WorldSim, + rng: &'a mut R, +} + +impl Civs { + pub fn generate(seed: u32, sim: &mut WorldSim) -> Self { + let mut this = Self::default(); + let mut rng = sim.rng.clone(); + let mut ctx = GenCtx { sim, rng: &mut rng }; + + for _ in 0..INITIAL_CIV_COUNT { + if let Some(civ) = this.birth_civ(&mut ctx) { + println!("Initial civilisation: {:#?}", this.civs.get(civ)); + } else { + println!("Failed to find starting site"); + } + } + + // Temporary! + for route in this.routes.values() { + for loc in route.path.iter() { + sim.get_mut(*loc).unwrap().place = Some(this.civs.iter().next().unwrap().homeland); + } + } + + this + } + + fn birth_civ(&mut self, ctx: &mut GenCtx) -> Option> { + const CIV_BIRTHPLACE_AREA: Range = 64..256; + let place = attempt(5, || { + let loc = find_site_loc(ctx, None)?; + self.establish_place(ctx, loc, CIV_BIRTHPLACE_AREA) + })?; + + let civ = self.civs.insert(Civ { + homeland: 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(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; + } + + // Find neighbors + const MAX_NEIGHBOR_DISTANCE: f32 = 100.0; + let mut nearby = self.places + .iter_ids() + .map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt())) + .filter(|(p, dist)| *dist < MAX_NEIGHBOR_DISTANCE) + .collect::>(); + nearby.sort_by_key(|(_, dist)| -*dist as i32); + let route_count = ctx.rng.gen_range(1, 3); + let neighbors = nearby + .into_iter() + .map(|(p, _)| p) + .filter_map(|p| if let Some(path) = find_path(ctx, loc, self.places.get(p).center) { + Some((p, path)) + } else { + None + }) + .take(route_count) + .collect::>(); + + let place = self.places.insert(Place { + center: loc, + neighbors: neighbors.iter().map(|(p, _)| *p).collect(), + }); + + // Insert routes to neighbours into route list + for (p, path) in neighbors { + self.routes.insert((place, p), Route { path }); + } + + // Write place to map + for cell in dead.union(&alive) { + if let Some(chunk) = ctx.sim.get_mut(*cell) { + chunk.place = Some(place); + } + } + + Some(place) + } +} + +/// Attempt to find a path between two locations +fn find_path(ctx: &mut GenCtx, a: Vec2, b: Vec2) -> Option>> { + let sim = &ctx.sim; + let heuristic = move |l: &Vec2| (l.distance_squared(b) as f32).sqrt(); + let neighbors = |l: &Vec2| { + let l = *l; + DIAGONALS.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; + Astar::new(5000, a, heuristic) + .poll(5000, heuristic, neighbors, transition, satisfied) + .into_path() +} + +/// Return true if travel between a location and a chunk next to it is permitted (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_alt = sim.get(a)?.alt; + let b_alt = sim.get(a + dir)?.alt; + Some((b_alt - a_alt).max(0.0).powf(2.0).abs() / 50.0) + } 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.is_underwater() && + 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 { + homeland: Id, +} + +pub struct Place { + center: Vec2, + neighbors: Vec>, +} + +pub struct Route { + path: Path>, +} diff --git a/world/src/lib.rs b/world/src/lib.rs index 2d23d2aba3..98ec73eb10 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -9,6 +9,7 @@ pub mod config; pub mod sim; pub mod site; pub mod util; +pub mod civ; // Reexports pub use crate::config::CONFIG; @@ -34,13 +35,14 @@ 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 } diff --git a/world/src/sim/erosion.rs b/world/src/sim/erosion.rs index b737bf2798..61ee68eb43 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() diff --git a/world/src/sim/map.rs b/world/src/sim/map.rs index 0707abd0d6..2a4d71d277 100644 --- a/world/src/sim/map.rs +++ b/world/src/sim/map.rs @@ -147,7 +147,7 @@ impl MapConfig { let pos = (focus_rect + Vec2::new(i as f64, j as f64) * scale).map(|e: f64| e as i32); - let (alt, basement, water_alt, humidity, temperature, downhill, river_kind) = + let (alt, basement, water_alt, humidity, temperature, downhill, river_kind, place) = sampler .get(pos) .map(|sample| { @@ -159,6 +159,7 @@ impl MapConfig { sample.temp, sample.downhill, sample.river.river_kind, + sample.place, ) }) .unwrap_or(( @@ -169,6 +170,7 @@ impl MapConfig { 0.0, None, None, + None, )); let humidity = humidity.min(1.0).max(0.0); let temperature = temperature.min(1.0).max(-1.0) * 0.5 + 0.5; @@ -295,6 +297,12 @@ impl MapConfig { ), }; + let rgba = if let Some(place) = place { + (((place.id() * 64) % 256) as u8, 0, 0, 0) + } else { + rgba + }; + write_pixel(Vec2::new(i, j), rgba); }); diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 5eae203b22..b12a2a07ce 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -26,6 +26,7 @@ use crate::{ block::BlockGen, column::ColumnGen, site::{Settlement, Site}, + civ::Place, util::{seed_expan, FastNoise, RandomField, Sampler, StructureGen2d}, CONFIG, }; @@ -33,6 +34,7 @@ use common::{ assets, terrain::{BiomeKind, TerrainChunkSize}, vol::RectVolSize, + store::Id, }; use hashbrown::HashMap; use noise::{ @@ -1303,6 +1305,10 @@ 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) -> Vec { @@ -1548,6 +1554,18 @@ 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_wpos(&self, wpos: Vec2) -> Option<&SimChunk> { self.get( wpos.map2(Vec2::from(TerrainChunkSize::RECT_SIZE), |e, sz: u32| { @@ -1768,6 +1786,7 @@ pub struct SimChunk { pub river: RiverData, pub sites: Vec, + pub place: Option>, pub contains_waypoint: bool, } @@ -2006,12 +2025,14 @@ impl SimChunk { spawn_rate: 1.0, location: None, river, + sites: Vec::new(), + place: None, 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 } diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 6f19c608bd..6989fb7dc6 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -9,10 +9,11 @@ use common::{ spiral::Spiral2d, terrain::{Block, BlockKind}, vol::{BaseVol, RectSizedVol, WriteVol}, + store::{Id, Store}, }; use hashbrown::{HashMap, HashSet}; use rand::prelude::*; -use std::{collections::VecDeque, f32, marker::PhantomData}; +use std::{collections::VecDeque, f32}; use vek::*; pub fn gradient(line: [Vec2; 2]) -> f32 { @@ -723,37 +724,3 @@ impl Land { pub fn new_plot(&mut self, plot: Plot) -> Id { self.plots.insert(plot) } } - -#[derive(Hash)] -pub struct Id(usize, PhantomData); - -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 } -} - -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 iter(&self) -> impl Iterator { self.items.iter() } - - pub fn insert(&mut self, item: T) -> Id { - let id = Id(self.items.len(), PhantomData); - self.items.push(item); - id - } -} From fe4418bc0d0f1d34b8e57f6bb13d399aed7c2cb6 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 27 Mar 2020 13:18:16 +0000 Subject: [PATCH 087/195] Added store --- common/src/lib.rs | 1 + common/src/store.rs | 61 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 common/src/store.rs diff --git a/common/src/lib.rs b/common/src/lib.rs index f8da49935d..c9c27fc778 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -34,6 +34,7 @@ pub mod terrain; pub mod util; pub mod vol; pub mod volumes; +pub mod store; /// The networking module containing high-level wrappers of `TcpListener` and /// `TcpStream` (`PostOffice` and `PostBox` respectively) and data types used by diff --git a/common/src/store.rs b/common/src/store.rs new file mode 100644 index 0000000000..cb0916691c --- /dev/null +++ b/common/src/store.rs @@ -0,0 +1,61 @@ +use std::{ + fmt, + hash, + cmp::{PartialEq, Eq}, + marker::PhantomData, +}; + +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 iter(&self) -> impl Iterator { self.items.iter() } + + 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 + } +} From 348003fc1aeba1d00400f29c6db100d6c95b347e Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 27 Mar 2020 18:52:28 +0000 Subject: [PATCH 088/195] Better track routing for civsim --- common/src/astar.rs | 19 ++++++--- world/src/civ/mod.rs | 99 ++++++++++++++++++++++++++++++-------------- world/src/sim/mod.rs | 4 +- 3 files changed, 84 insertions(+), 38 deletions(-) diff --git a/common/src/astar.rs b/common/src/astar.rs index 8bbb5bc7b4..3468c687ac 100644 --- a/common/src/astar.rs +++ b/common/src/astar.rs @@ -52,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 { @@ -69,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, } } @@ -86,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 = @@ -114,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(), @@ -126,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(), @@ -136,6 +139,10 @@ 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/world/src/civ/mod.rs b/world/src/civ/mod.rs index 402506e31f..7b1183f9b2 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -39,7 +39,8 @@ const INITIAL_CIV_COUNT: usize = 20; pub struct Civs { civs: Store, places: Store, - routes: HashMap<(Id, Id), Route>, + tracks: Store, + track_map: HashMap, HashMap, Id>>, } struct GenCtx<'a, R: Rng> { @@ -62,8 +63,8 @@ impl Civs { } // Temporary! - for route in this.routes.values() { - for loc in route.path.iter() { + for track in this.tracks.iter() { + for loc in track.path.iter() { sim.get_mut(*loc).unwrap().place = Some(this.civs.iter().next().unwrap().homeland); } } @@ -71,6 +72,35 @@ impl Civs { this } + /// 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() + } + + /// Find the cheapest route between two places + fn route_between(&self, a: Id, b: Id) -> Option<(Path>, f32)> { + let heuristic = move |p: &Id| (self.places.get(*p).center.distance_squared(self.places.get(b).center) as f32).sqrt(); + let neighbors = |p: &Id| { + let p = *p; + let to = self.track_map.get(&p).map(|dests| dests.keys()).into_iter().flatten(); + let fro = self.track_map.iter().filter(move |(_, dests)| dests.contains_key(&p)).map(|(p, _)| p); + to.chain(fro).filter(|p| **p != a).copied() + }; + 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> { const CIV_BIRTHPLACE_AREA: Range = 64..256; let place = attempt(5, || { @@ -112,34 +142,39 @@ impl Civs { return None; } + let place = self.places.insert(Place { + center: loc, + }); + // Find neighbors - const MAX_NEIGHBOR_DISTANCE: f32 = 100.0; + const MAX_NEIGHBOR_DISTANCE: f32 = 250.0; let mut nearby = self.places .iter_ids() .map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt())) .filter(|(p, dist)| *dist < MAX_NEIGHBOR_DISTANCE) .collect::>(); - nearby.sort_by_key(|(_, dist)| -*dist as i32); - let route_count = ctx.rng.gen_range(1, 3); - let neighbors = nearby - .into_iter() - .map(|(p, _)| p) - .filter_map(|p| if let Some(path) = find_path(ctx, loc, self.places.get(p).center) { - Some((p, path)) - } else { - None - }) - .take(route_count) - .collect::>(); + nearby.sort_by_key(|(_, dist)| *dist as i32); - let place = self.places.insert(Place { - center: loc, - neighbors: neighbors.iter().map(|(p, _)| *p).collect(), - }); - - // Insert routes to neighbours into route list - for (p, path) in neighbors { - self.routes.insert((place, p), Route { path }); + for (nearby, _) in nearby.into_iter().take(ctx.rng.gen_range(3, 5)) { + // Find a novel path + if let Some((path, cost)) = find_path(ctx, loc, self.places.get(nearby).center) { + // Find a path using existing paths + if self + .route_between(place, 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() + { + let track = self.tracks.insert(Track { + cost, + path, + }); + self.track_map + .entry(place) + .or_default() + .insert(nearby, track); + } + } } // Write place to map @@ -154,7 +189,7 @@ impl Civs { } /// Attempt to find a path between two locations -fn find_path(ctx: &mut GenCtx, a: Vec2, b: Vec2) -> Option>> { +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| { @@ -163,9 +198,11 @@ fn find_path(ctx: &mut GenCtx, a: Vec2, b: Vec2) -> Option

, b: &Vec2| 1.0 + walk_in_dir(sim, *a, *b - *a).unwrap_or(10000.0); let satisfied = |l: &Vec2| *l == b; - Astar::new(5000, a, heuristic) - .poll(5000, heuristic, neighbors, transition, satisfied) + 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 true if travel between a location and a chunk next to it is permitted (TODO: by whom?) @@ -175,7 +212,7 @@ fn walk_in_dir(sim: &WorldSim, a: Vec2, dir: Vec2) -> Option { { let a_alt = sim.get(a)?.alt; let b_alt = sim.get(a + dir)?.alt; - Some((b_alt - a_alt).max(0.0).powf(2.0).abs() / 50.0) + Some(0.5 + (b_alt - a_alt).max(-0.5).abs() / 5.0) } else { None } @@ -240,9 +277,11 @@ pub struct Civ { pub struct Place { center: Vec2, - neighbors: Vec>, } -pub struct Route { +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>, } diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index b12a2a07ce..22afd5ab35 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -65,8 +65,8 @@ use vek::*; // don't think we actually cast a chunk id to float, just coordinates... could // be wrong though! pub const WORLD_SIZE: Vec2 = Vec2 { - x: 256 * 1, - y: 256 * 1, + x: 512 * 1, + y: 512 * 1, }; /// A structure that holds cached noise values and cumulative distribution From 46190aa63473134dec06cd73b04f7f6d2b9b717b Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 27 Mar 2020 23:06:23 +0000 Subject: [PATCH 089/195] Began work on basic economic simulation --- assets/voxygen/shaders/fluid-vert.glsl | 2 + common/src/store.rs | 2 + world/examples/water.rs | 24 +-- world/src/civ/mod.rs | 218 +++++++++++++++++++++---- world/src/column/mod.rs | 4 +- world/src/lib.rs | 2 + world/src/sim/mod.rs | 16 +- world/src/site/mod.rs | 10 +- 8 files changed, 223 insertions(+), 55 deletions(-) diff --git a/assets/voxygen/shaders/fluid-vert.glsl b/assets/voxygen/shaders/fluid-vert.glsl index 412b46bd80..3386ebba06 100644 --- a/assets/voxygen/shaders/fluid-vert.glsl +++ b/assets/voxygen/shaders/fluid-vert.glsl @@ -40,6 +40,8 @@ void main() { f_pos_norm = v_pos_norm; + f_pos.z -= 0.2; + gl_Position = all_mat * vec4(f_pos, 1); diff --git a/common/src/store.rs b/common/src/store.rs index cb0916691c..6d6320bdbc 100644 --- a/common/src/store.rs +++ b/common/src/store.rs @@ -45,6 +45,8 @@ impl Store { 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 diff --git a/world/examples/water.rs b/world/examples/water.rs index 4f6696b152..74695b5fb9 100644 --- a/world/examples/water.rs +++ b/world/examples/water.rs @@ -18,14 +18,14 @@ fn main() { let map_file = // "map_1575990726223.bin"; // "map_1575987666972.bin"; - "map_1576046079066.bin"; - let mut _map_file = PathBuf::from("./maps"); - _map_file.push(map_file); + "map_1585335358316.bin"; + let mut map_path = PathBuf::from("./maps"); + map_path.push(map_file); let world = World::generate(5284, WorldOpts { seed_elements: false, - // world_file: sim::FileOpts::Load(_map_file), - world_file: sim::FileOpts::Save, + world_file: sim::FileOpts::Load(map_path), + //world_file: sim::FileOpts::Save, ..WorldOpts::default() }); @@ -150,12 +150,16 @@ 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); - println!( - "Chunk position: {:?}", - pos.map2(TerrainChunkSize::RECT_SIZE, |e, f| e * f as i32) - ); + let block_pos = chunk_pos.map2(TerrainChunkSize::RECT_SIZE, |e, f| e * f as i32); + println!("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(place) = &chunk.place { + println!("Place {} info: {:#?}", place.id(), world.civs().place(*place)); + } + } } } let is_camera = win.is_key_down(minifb::Key::C); diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 7b1183f9b2..fe7778d8d9 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -9,7 +9,7 @@ use common::{ path::Path, astar::Astar, }; -use crate::sim::WorldSim; +use crate::sim::{WorldSim, SimChunk}; const CARDINALS: [Vec2; 4] = [ Vec2::new(1, 0), @@ -39,8 +39,11 @@ const INITIAL_CIV_COUNT: usize = 20; pub struct Civs { civs: Store, places: Store, + tracks: Store, - track_map: HashMap, HashMap, Id>>, + track_map: HashMap, HashMap, Id>>, + + sites: Store, } struct GenCtx<'a, R: Rng> { @@ -55,13 +58,18 @@ impl Civs { let mut ctx = GenCtx { sim, rng: &mut rng }; for _ in 0..INITIAL_CIV_COUNT { - if let Some(civ) = this.birth_civ(&mut ctx) { - println!("Initial civilisation: {:#?}", this.civs.get(civ)); - } else { - println!("Failed to find starting site"); + println!("Creating civilisation..."); + if let None = this.birth_civ(&mut ctx) { + println!("Failed to find starting site for civilisation."); } } + // Tick + const SIM_YEARS: usize = 100; + for _ in 0..SIM_YEARS { + this.tick(1.0); + } + // Temporary! for track in this.tracks.iter() { for loc in track.path.iter() { @@ -69,11 +77,28 @@ impl Civs { } } + this.display_info(); + this } + pub fn place(&self, id: Id) -> &Place { self.places.get(id) } + + 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> { + fn track_between(&self, a: Id, b: Id) -> Option> { self.track_map .get(&a) .and_then(|dests| dests.get(&b)) @@ -83,17 +108,19 @@ impl Civs { .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.places.get(*p).center.distance_squared(self.places.get(b).center) as f32).sqrt(); - let neighbors = |p: &Id| { - let p = *p; - let to = self.track_map.get(&p).map(|dests| dests.keys()).into_iter().flatten(); - let fro = self.track_map.iter().filter(move |(_, dests)| dests.contains_key(&p)).map(|(p, _)| p); - to.chain(fro).filter(|p| **p != a).copied() - }; - let transition = |a: &Id, b: &Id| self.tracks.get(self.track_between(*a, *b).unwrap()).cost; - let satisfied = |p: &Id| *p == b; + 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) @@ -102,14 +129,17 @@ impl Civs { } fn birth_civ(&mut self, ctx: &mut GenCtx) -> Option> { - const CIV_BIRTHPLACE_AREA: Range = 64..256; - let place = attempt(5, || { + let site = attempt(5, || { let loc = find_site_loc(ctx, None)?; - self.establish_place(ctx, loc, CIV_BIRTHPLACE_AREA) + self.establish_site(ctx, loc, SiteKind::Settlement(Settlement { + stocks: Stocks::default(), + population: 24, + })) })?; let civ = self.civs.insert(Civ { - homeland: place, + capital: site, + homeland: self.sites.get(site).place, }); Some(civ) @@ -144,11 +174,37 @@ impl Civs { 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, kind: SiteKind) -> 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 { + kind, + center: loc, + place: place, }); // Find neighbors const MAX_NEIGHBOR_DISTANCE: f32 = 250.0; - let mut nearby = self.places + let mut nearby = self.sites .iter_ids() .map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt())) .filter(|(p, dist)| *dist < MAX_NEIGHBOR_DISTANCE) @@ -157,10 +213,10 @@ impl Civs { for (nearby, _) in nearby.into_iter().take(ctx.rng.gen_range(3, 5)) { // Find a novel path - if let Some((path, cost)) = find_path(ctx, loc, self.places.get(nearby).center) { + if let Some((path, cost)) = find_path(ctx, loc, self.sites.get(nearby).center) { // Find a path using existing paths if self - .route_between(place, nearby) + .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() @@ -170,21 +226,25 @@ impl Civs { path, }); self.track_map - .entry(place) + .entry(site) .or_default() .insert(nearby, track); } } } - // Write place to map - for cell in dead.union(&alive) { - if let Some(chunk) = ctx.sim.get_mut(*cell) { - chunk.place = Some(place); + Some(site) + } + + pub fn tick(&mut self, years: f32) { + for site in self.sites.iter_mut() { + match &mut site.kind { + SiteKind::Settlement(s) => { + s.collect_stocks(years, &self.places.get(site.place).nat_res); + s.consume_stocks(years); + }, } } - - Some(place) } } @@ -212,7 +272,7 @@ fn walk_in_dir(sim: &WorldSim, a: Vec2, dir: Vec2) -> Option { { let a_alt = sim.get(a)?.alt; let b_alt = sim.get(a + dir)?.alt; - Some(0.5 + (b_alt - a_alt).max(-0.5).abs() / 5.0) + Some((b_alt - a_alt).abs() / 2.5) } else { None } @@ -236,7 +296,8 @@ fn site_in_dir(sim: &WorldSim, a: Vec2, dir: Vec2) -> bool { /// 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.is_underwater() && + !chunk.river.is_ocean() && + !chunk.river.is_lake() && sim.get_gradient_approx(loc).map(|grad| grad < 1.0).unwrap_or(false) } else { false @@ -272,11 +333,39 @@ fn find_site_loc(ctx: &mut GenCtx, near: Option<(Vec2, f32)>) -> #[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, + stone: 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.stone += chunk.rockiness; + self.river += if chunk.river.is_river() { 1.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 { @@ -285,3 +374,66 @@ pub struct Track { cost: f32, path: Path>, } + +#[derive(Debug)] +pub struct Site { + kind: SiteKind, + center: Vec2, + place: Id, +} + +#[derive(Debug)] +pub enum SiteKind { + Settlement(Settlement), +} + +#[derive(Default, Debug)] +pub struct Settlement { + stocks: Stocks, + population: u32, +} + +impl Settlement { + pub fn collect_stocks(&mut self, years: f32, nat_res: &NaturalResources) { + // Per labourer, per year + const LUMBER_RATE: f32 = 0.5; + const MINE_RATE: f32 = 0.3; + const FARM_RATE: f32 = 0.4; + + // No more that 1.0 in total + let lumberjacks = 0.2 * self.population as f32; + let miners = 0.15 * self.population as f32; + let farmers = 0.4 * self.population as f32; + + self.stocks.logs += years * nat_res.wood.min(lumberjacks * LUMBER_RATE); + self.stocks.rocks += years * nat_res.stone.min(miners * MINE_RATE); + self.stocks.food += years * nat_res.farmland.min(farmers * FARM_RATE); + } + + pub fn consume_stocks(&mut self, years: f32) { + const EAT_RATE: f32 = 0.15; + // Food required to give birth + const BIRTH_FOOD: f32 = 0.25; + const MAX_ANNUAL_BABIES: f32 = 0.15; + + let needed_food = self.population as f32 * EAT_RATE; + let food_surplus = (self.stocks.food - needed_food).max(0.0); + let food_deficit = -(self.stocks.food - needed_food).min(0.0); + + self.stocks.food = (self.stocks.food - needed_food).max(0.0); + + self.population -= (food_deficit * EAT_RATE).round() as u32; + self.population += (food_surplus / BIRTH_FOOD).round().min(self.population as f32 * MAX_ANNUAL_BABIES) as u32; + } + + pub fn happiness(&self) -> f32 { + self.stocks.food / self.population as f32 + } +} + +#[derive(Default, Debug)] +pub struct Stocks { + logs: f32, + rocks: f32, + food: f32, +} diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index d822099bd2..3e9e74a702 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -2,7 +2,7 @@ use crate::{ all::ForestKind, block::StructureMeta, sim::{ - local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, LocationInfo, RiverKind, SimChunk, + local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, RiverKind, SimChunk, WorldSim, }, util::{RandomPerm, Sampler, UnitChooser}, @@ -1097,7 +1097,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { temp, humidity, spawn_rate, - location: sim_chunk.location.as_ref(), stone_col, chunk: sim_chunk, @@ -1129,7 +1128,6 @@ pub struct ColumnSample<'a> { pub temp: f32, pub humidity: f32, pub spawn_rate: f32, - pub location: Option<&'a LocationInfo>, pub stone_col: Rgb, pub chunk: &'a SimChunk, diff --git a/world/src/lib.rs b/world/src/lib.rs index 98ec73eb10..5f82b0fa90 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -47,6 +47,8 @@ impl World { pub fn sim(&self) -> &sim::WorldSim { &self.sim } + pub fn civs(&self) -> &civ::Civs { &self.civs } + pub fn tick(&self, _dt: Duration) { // TODO } diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 22afd5ab35..ea7b8c5039 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -1391,6 +1391,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 .par_iter_mut() .enumerate() @@ -1447,6 +1448,7 @@ impl WorldSim { } } }); + */ // Stage 2 - towns! let chunk_idx_center = |e: Vec2| { @@ -1767,6 +1769,7 @@ impl WorldSim { } } +#[derive(Debug)] pub struct SimChunk { pub chaos: f32, pub alt: f32, @@ -1782,7 +1785,6 @@ pub struct SimChunk { pub tree_density: f32, pub forest_kind: ForestKind, pub spawn_rate: f32, - pub location: Option, pub river: RiverData, pub sites: Vec, @@ -1798,12 +1800,6 @@ pub struct RegionInfo { pub seed: u32, } -#[derive(Clone)] -pub struct LocationInfo { - pub loc_idx: usize, - pub near: Vec, -} - impl SimChunk { fn generate(posi: usize, gen_ctx: &GenCtx, gen_cdf: &GenCdf) -> Self { let pos = uniform_idx_as_vec2(posi); @@ -2023,7 +2019,6 @@ impl SimChunk { } }, spawn_rate: 1.0, - location: None, river, sites: Vec::new(), @@ -2037,11 +2032,16 @@ impl SimChunk { pub fn get_base_z(&self) -> f32 { self.alt - self.chaos * 50.0 - 16.0 } 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/site/mod.rs b/world/src/site/mod.rs index 6245c40e07..1fe239851b 100644 --- a/world/src/site/mod.rs +++ b/world/src/site/mod.rs @@ -11,7 +11,7 @@ use common::{ terrain::Block, vol::{BaseVol, RectSizedVol, WriteVol}, }; -use std::sync::Arc; +use std::{fmt, sync::Arc}; use vek::*; #[derive(Clone)] @@ -47,3 +47,11 @@ impl Site { impl From for Site { fn from(settlement: Settlement) -> Self { Site::Settlement(Arc::new(settlement)) } } + +impl fmt::Debug for Site { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Site::Settlement(_) => write!(f, "Settlement"), + } + } +} From cee1b1f962d8d6d607270f449acab6f4be65b51b Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 28 Mar 2020 18:16:19 +0000 Subject: [PATCH 090/195] Basic trading simulation --- common/src/store.rs | 4 + world/examples/water.rs | 7 +- world/src/civ/econ.rs | 77 +++++++++ world/src/civ/mod.rs | 358 ++++++++++++++++++++++++++++++++++------ 4 files changed, 396 insertions(+), 50 deletions(-) create mode 100644 world/src/civ/econ.rs diff --git a/common/src/store.rs b/common/src/store.rs index 6d6320bdbc..ac6e0b5f6f 100644 --- a/common/src/store.rs +++ b/common/src/store.rs @@ -43,6 +43,10 @@ impl Store { 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() } diff --git a/world/examples/water.rs b/world/examples/water.rs index 74695b5fb9..308d83a53f 100644 --- a/world/examples/water.rs +++ b/world/examples/water.rs @@ -156,8 +156,11 @@ fn main() { println!("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(place) = &chunk.place { - println!("Place {} info: {:#?}", place.id(), world.civs().place(*place)); + if let Some(id) = &chunk.place { + let place = world.civs().place(*id); + println!("Place {} info: {:#?}", id.id(), place); + + println!("Site: {:#?}", world.civs().sites().find(|site| site.place == *id)); } } } diff --git a/world/src/civ/econ.rs b/world/src/civ/econ.rs new file mode 100644 index 0000000000..a0d3390aea --- /dev/null +++ b/world/src/civ/econ.rs @@ -0,0 +1,77 @@ +use rand::prelude::*; +use super::GenCtx; + +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 index fe7778d8d9..bf62c4a4cf 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -1,3 +1,5 @@ +mod econ; + use std::ops::Range; use hashbrown::{HashMap, HashSet}; use vek::*; @@ -33,7 +35,7 @@ fn attempt(max_iters: usize, mut f: impl FnMut() -> Option) -> Option { (0..max_iters).find_map(|_| f()) } -const INITIAL_CIV_COUNT: usize = 20; +const INITIAL_CIV_COUNT: usize = 32; #[derive(Default)] pub struct Civs { @@ -46,7 +48,7 @@ pub struct Civs { sites: Store, } -struct GenCtx<'a, R: Rng> { +pub struct GenCtx<'a, R: Rng> { sim: &'a mut WorldSim, rng: &'a mut R, } @@ -65,9 +67,9 @@ impl Civs { } // Tick - const SIM_YEARS: usize = 100; + const SIM_YEARS: usize = 1000; for _ in 0..SIM_YEARS { - this.tick(1.0); + this.tick(&mut ctx, 1.0); } // Temporary! @@ -84,6 +86,10 @@ impl Civs { pub fn place(&self, id: Id) -> &Place { self.places.get(id) } + pub fn sites(&self) -> impl Iterator + '_ { + self.sites.iter() + } + fn display_info(&self) { for (id, civ) in self.civs.iter_ids() { println!("# Civilisation {:?}", id); @@ -93,7 +99,7 @@ impl Civs { for (id, site) in self.sites.iter_ids() { println!("# Site {:?}", id); - println!("{:?}", site); + println!("{:#?}", site); } } @@ -131,10 +137,7 @@ impl Civs { 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, SiteKind::Settlement(Settlement { - stocks: Stocks::default(), - population: 24, - })) + self.establish_site(ctx, loc, SiteKind::Settlement(Settlement::civ_birthplace())) })?; let civ = self.civs.insert(Civ { @@ -211,7 +214,7 @@ impl Civs { .collect::>(); nearby.sort_by_key(|(_, dist)| *dist as i32); - for (nearby, _) in nearby.into_iter().take(ctx.rng.gen_range(3, 5)) { + for (nearby, _) in nearby.into_iter() { // Find a novel path if let Some((path, cost)) = find_path(ctx, loc, self.sites.get(nearby).center) { // Find a path using existing paths @@ -236,13 +239,125 @@ impl Civs { Some(site) } - pub fn tick(&mut self, years: f32) { + fn tick(&mut self, ctx: &mut GenCtx, years: f32) { + // Collect stocks for site in self.sites.iter_mut() { - match &mut site.kind { - SiteKind::Settlement(s) => { - s.collect_stocks(years, &self.places.get(site.place).nat_res); - s.consume_stocks(years); - }, + if let SiteKind::Settlement(s) = &mut site.kind { + s.collect_stocks(years, &self.places.get(site.place).nat_res); + } + } + + // Trade stocks + let mut stocks = [FOOD, WOOD, ROCK]; + 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() + .filter_map(|(id, site)| site.as_settlement().map(|s| (id, s))) + .map(|(id, settlement)| (id, econ::SellOrder { + quantity: settlement.trade_states[stock].surplus.min(settlement.stocks[stock]), + price: settlement.trade_states[stock].sell_belief.choose_price(ctx), + q_sold: 0.0, + })) + .filter(|(_, order)| order.quantity > 0.0) + .collect::>(); + + let mut sites = self.sites + .ids() + .filter(|id| self.sites.get(*id).as_settlement().is_some()) + .collect::>(); + sites.shuffle(ctx.rng); // Give all sites a chance to buy first + for site in sites { + let (max_spend, max_price) = { + let settlement = self.sites.get(site).as_settlement().unwrap(); + ( + settlement.trade_states[stock].purchase_priority * settlement.coin, + settlement.trade_states[stock].buy_belief.price, + ) + }; + 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), + 1000000.0, // Max quantity TODO + 1000000.0, // Max price TODO + max_spend, + ); + let mut settlement = self.sites.get_mut(site).as_settlement_mut().unwrap(); + settlement.coin -= spent; + if quantity > 0.0 { + settlement.stocks[stock] += quantity; + settlement.trade_states[stock].buy_belief.update_buyer(years, spent / quantity); + println!("Belief: {:?}", settlement.trade_states[stock].buy_belief); + } + } + + for (site, order) in sell_orders { + let mut settlement = self.sites.get_mut(site).as_settlement_mut().unwrap(); + settlement.coin += order.q_sold * order.price; + if order.q_sold > 0.0 { + settlement.stocks[stock] -= order.q_sold; + settlement.trade_states[stock].sell_belief.update_seller(order.q_sold / order.quantity); + } + } + } + + // Trade stocks + /* + let mut sites = self.sites.ids().collect::>(); + sites.shuffle(ctx.rng); // Give all sites a chance to buy first + for site in sites { + let mut stocks = [FOOD, WOOD, ROCK]; + stocks.shuffle(ctx.rng); // Give each stock a chance to be traded first + for stock in stocks.iter().copied() { + if self.sites.get(site).as_settlement().is_none() { + continue; + } + + let settlement = self.sites.get(site).as_settlement().unwrap(); + let quantity_to_buy = settlement.trade_states[stock].buy_q; + let mut bought_quantity = 0.0; + let mut coin = settlement.coin; + drop(settlement); + let mut sell_orders = self + .neighbors(site) + .collect::>() + .into_iter() + .filter_map(|n| self.sites.get(n).as_settlement().map(|s| (n, s))) + .map(|(n, neighbor)| { + // TODO: Add speculation, don't use the domestic value to rationalise price + let trade_state = &neighbor.trade_states[stock]; + let sell_q = trade_state.sell_q.min(neighbor.stocks[stock]); + (n, trade_state.domestic_value, sell_q) + }) + .collect::>(); + sell_orders.sort_by_key(|(_, price, _)| (*price * 1000.0) as i64); + + for (n, price, sell_q) in sell_orders { + if bought_quantity >= quantity_to_buy { + break; + } else { + let buy_quantity = (quantity_to_buy - bought_quantity).min(sell_q).min(coin / price); + let payment = buy_quantity * price; + bought_quantity += buy_quantity; + coin -= payment; + let mut neighbor = self.sites.get_mut(n).as_settlement_mut().unwrap(); + neighbor.stocks[stock] -= buy_quantity; + neighbor.coin += payment; + } + } + + let mut settlement = self.sites.get_mut(site).as_settlement_mut().unwrap(); + settlement.stocks[stock] += bought_quantity; + settlement.coin = coin; + } + } + */ + + // Consume stocks + for site in self.sites.iter_mut() { + if let SiteKind::Settlement(s) = &mut site.kind { + s.consume_stocks(years); } } } @@ -379,7 +494,25 @@ pub struct Track { pub struct Site { kind: SiteKind, center: Vec2, - place: Id, + pub place: Id, +} + +impl Site { + pub fn as_settlement(&self) -> Option<&Settlement> { + if let SiteKind::Settlement(s) = &self.kind { + Some(s) + } else { + None + } + } + + pub fn as_settlement_mut(&mut self) -> Option<&mut Settlement> { + if let SiteKind::Settlement(s) = &mut self.kind { + Some(s) + } else { + None + } + } } #[derive(Debug)] @@ -387,53 +520,182 @@ pub enum SiteKind { Settlement(Settlement), } -#[derive(Default, Debug)] +#[derive(Debug)] pub struct Settlement { - stocks: Stocks, - population: u32, + population: f32, + stocks: Stocks, + trade_states: Stocks, + coin: f32, } impl Settlement { + pub fn civ_birthplace() -> Self { + Self { + population: 24.0, + stocks: Stocks::default(), + trade_states: Stocks::default(), + coin: 1000.0, + } + } + pub fn collect_stocks(&mut self, years: f32, nat_res: &NaturalResources) { // Per labourer, per year - const LUMBER_RATE: f32 = 0.5; - const MINE_RATE: f32 = 0.3; - const FARM_RATE: f32 = 0.4; + let collection_rate = Stocks::from_list(&[ + (FOOD, 2.0), + (ROCK, 0.6), + (WOOD, 1.5), + ]); - // No more that 1.0 in total - let lumberjacks = 0.2 * self.population as f32; - let miners = 0.15 * self.population as f32; - let farmers = 0.4 * self.population as f32; + // Proportion of the population dedicated to each task + let workforce_ratios = Stocks::from_list(&[ + (FOOD, self.trade_states[FOOD].domestic_value), + (ROCK, self.trade_states[ROCK].domestic_value), + (WOOD, self.trade_states[WOOD].domestic_value), + ]); + // Normalise workforce proportions + let wf_total = workforce_ratios.iter().map(|(_, r)| *r).sum::(); + let workforce = workforce_ratios.map(|stock, r| r / wf_total * self.population); - self.stocks.logs += years * nat_res.wood.min(lumberjacks * LUMBER_RATE); - self.stocks.rocks += years * nat_res.stone.min(miners * MINE_RATE); - self.stocks.food += years * nat_res.farmland.min(farmers * FARM_RATE); + self.stocks[FOOD] += years * (workforce[FOOD] * collection_rate[FOOD] + nat_res.farmland * 0.01).min(nat_res.farmland); + self.stocks[ROCK] += years * (workforce[ROCK] * collection_rate[ROCK] + nat_res.stone * 0.01).min(nat_res.stone); + self.stocks[WOOD] += years * (workforce[WOOD] * collection_rate[WOOD] + nat_res.wood * 0.01).min(nat_res.wood); + + println!("{:?}", nat_res); + println!("{:?}", self.stocks); } pub fn consume_stocks(&mut self, years: f32) { - const EAT_RATE: f32 = 0.15; - // Food required to give birth - const BIRTH_FOOD: f32 = 0.25; - const MAX_ANNUAL_BABIES: f32 = 0.15; + const EAT_RATE: f32 = 0.5; + const USE_WOOD_RATE: f32 = 0.75; + const BIRTH_RATE: f32 = 0.1; - let needed_food = self.population as f32 * EAT_RATE; - let food_surplus = (self.stocks.food - needed_food).max(0.0); - let food_deficit = -(self.stocks.food - needed_food).min(0.0); + self.population += years * BIRTH_RATE; - self.stocks.food = (self.stocks.food - needed_food).max(0.0); + let required = Stocks::from_list(&[ + (FOOD, self.population as f32 * years * EAT_RATE), + (WOOD, self.population as f32 * years * USE_WOOD_RATE), + ]); - self.population -= (food_deficit * EAT_RATE).round() as u32; - self.population += (food_surplus / BIRTH_FOOD).round().min(self.population as f32 * MAX_ANNUAL_BABIES) as u32; - } + // Calculate surplus and deficit of each stock + let surplus = required.clone().map(|stock, required| (self.stocks[stock] - required).max(0.0)); + let deficit = required.clone().map(|stock, required| (required - self.stocks[stock]).max(0.0)); - pub fn happiness(&self) -> f32 { - self.stocks.food / self.population as f32 + // Deplete stocks + self.stocks.iter_mut().for_each(|(stock, v)| *v = (*v - required[stock]).max(0.0)); + + // Kill people + self.population = (self.population - deficit[FOOD] * years * EAT_RATE).max(0.0); + + // If in deficit, value the stock more + deficit.iter().for_each(|(stock, deficit)| { + if *deficit > 0.0 { + let mut trade_state = &mut self.trade_states[stock]; + trade_state.domestic_value += *deficit * 0.01; + trade_state.surplus = -*deficit; + trade_state.purchase_priority *= 1.1; + } + }); + + // If in surplus, value the stock less + surplus.iter().for_each(|(stock, surplus)| { + if *surplus > 0.0 { + let mut trade_state = &mut self.trade_states[stock]; + trade_state.domestic_value /= 1.0 + *surplus * 0.01; + trade_state.surplus = *surplus; + } + }); + + // Normalise purchasing priorities + let pp_avg = self.trade_states.iter().map(|(_, ts)| ts.purchase_priority).sum::() / self.trade_states.iter().count() as f32; + self.trade_states.iter_mut().for_each(|(_, ts)| ts.purchase_priority /= pp_avg); } } -#[derive(Default, Debug)] -pub struct Stocks { - logs: f32, - rocks: f32, - food: f32, +type Stock = &'static str; +const FOOD: Stock = "food"; +const WOOD: Stock = "wood"; +const ROCK: Stock = "rock"; + +#[derive(Debug, Clone)] +struct TradeState { + buy_belief: econ::Belief, + sell_belief: econ::Belief, + /// The price/value assigned to the stock by the host settlement + domestic_value: f32, + surplus: f32, + purchase_priority: f32, } + +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, + }, + domestic_value: 1.0, + surplus: 0.0, + purchase_priority: 1.0, + } + } +} + +#[derive(Default, Clone, Debug)] +pub struct Stocks { + stocks: HashMap, + zero: T, +} + +impl Stocks { + pub fn from_list<'a>(i: impl IntoIterator) -> Self + where T: 'a + { + Self { + stocks: i.into_iter().cloned().collect(), + zero: T::default(), + } + } + + pub fn get_mut(&mut self, stock: Stock) -> &mut T { + self + .stocks + .entry(stock) + .or_default() + } + + pub fn get(&self, stock: Stock) -> &T { + self.stocks.get(&stock).unwrap_or(&self.zero) + } + + pub fn map(mut self, mut f: impl FnMut(Stock, T) -> T) -> Self { + self.stocks.iter_mut().for_each(|(s, v)| *v = f(*s, std::mem::take(v))); + self + } + + pub fn iter(&self) -> impl Iterator + '_ { + self.stocks.iter().map(|(s, v)| (*s, v)) + } + + pub fn iter_mut(&mut self) -> impl Iterator + '_ { + self.stocks.iter_mut().map(|(s, v)| (*s, v)) + } +} + +impl std::ops::Index for Stocks { + type Output = T; + fn index(&self, stock: Stock) -> &Self::Output { self.get(stock) } +} + +impl std::ops::IndexMut for Stocks { + fn index_mut(&mut self, stock: Stock) -> &mut Self::Output { self.get_mut(stock) } +} + + + + + + From a0dae82a2b37022e37aaa2401b6dc1fedc1a261f Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sun, 29 Mar 2020 20:48:51 +0100 Subject: [PATCH 091/195] Added correctly allocated labours, fishing --- Cargo.toml | 4 +- assets/voxygen/shaders/fluid-vert.glsl | 3 +- assets/voxygen/shaders/postprocess-frag.glsl | 3 +- assets/world/map/veloren_0_5_0_0.bin | 4 +- voxygen/src/mesh/vol.rs | 2 +- world/src/civ/mod.rs | 249 ++++++++----------- world/src/site/settlement/mod.rs | 4 +- 7 files changed, 109 insertions(+), 160 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f4a8194459..41d8a76f9b 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 = true codegen-units = 8 lto = false incremental = true diff --git a/assets/voxygen/shaders/fluid-vert.glsl b/assets/voxygen/shaders/fluid-vert.glsl index 3386ebba06..a5317dba17 100644 --- a/assets/voxygen/shaders/fluid-vert.glsl +++ b/assets/voxygen/shaders/fluid-vert.glsl @@ -28,7 +28,8 @@ void main() { f_pos.z -= 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 * (sin(tick.x * 2.0 + f_pos.x * 2.0 + f_pos.y * 2.0) + 1.0) * 0.5 - 0.1; f_col = vec3( float((v_col_light >> 8) & 0xFFu), 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/world/map/veloren_0_5_0_0.bin b/assets/world/map/veloren_0_5_0_0.bin index f121098936..663a225c4c 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 -size 16777236 +oid sha256:ea65d94f251f47d8d577fa6485655d22d3fbb317210ab4826ae87d6463977165 +size 4194324 diff --git a/voxygen/src/mesh/vol.rs b/voxygen/src/mesh/vol.rs index 46ea72808a..dafbee7b00 100644 --- a/voxygen/src/mesh/vol.rs +++ b/voxygen/src/mesh/vol.rs @@ -112,7 +112,7 @@ fn create_quad, Vec3, Rgb, f32, f32) -> P let darkness = darkness_ao.map(|e| e.0); let ao = darkness_ao.map(|e| e.1); - let ao_map = ao * 0.75 + 0.25; + let ao_map = ao * 0.85 + 0.15; if ao[0].min(ao[2]).min(darkness[0]).min(darkness[2]) < ao[1].min(ao[3]).min(darkness[1]).min(darkness[3]) diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index bf62c4a4cf..e2453a23b5 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -1,6 +1,9 @@ mod econ; -use std::ops::Range; +use std::{ + ops::Range, + hash::Hash, +}; use hashbrown::{HashMap, HashSet}; use vek::*; use rand::prelude::*; @@ -137,7 +140,7 @@ impl Civs { 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, SiteKind::Settlement(Settlement::civ_birthplace())) + self.establish_site(ctx, loc) })?; let civ = self.civs.insert(Civ { @@ -191,7 +194,7 @@ impl Civs { Some(place) } - fn establish_site(&mut self, ctx: &mut GenCtx, loc: Vec2, kind: SiteKind) -> Option> { + fn establish_site(&mut self, ctx: &mut GenCtx, loc: Vec2) -> Option> { const SITE_AREA: Range = 64..256; let place = match ctx.sim.get(loc).and_then(|site| site.place) { @@ -200,9 +203,16 @@ impl Civs { }; let site = self.sites.insert(Site { - kind, + kind: SiteKind::Settlement, center: loc, place: place, + + population: 24.0, + labor: MapVec::default(), + output: MapVec::default(), + stocks: Stocks::default(), + trade_states: Stocks::default(), + coin: 1000.0, }); // Find neighbors @@ -214,7 +224,7 @@ impl Civs { .collect::>(); nearby.sort_by_key(|(_, dist)| *dist as i32); - for (nearby, _) in nearby.into_iter() { + 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 @@ -242,9 +252,7 @@ impl Civs { fn tick(&mut self, ctx: &mut GenCtx, years: f32) { // Collect stocks for site in self.sites.iter_mut() { - if let SiteKind::Settlement(s) = &mut site.kind { - s.collect_stocks(years, &self.places.get(site.place).nat_res); - } + site.collect_stocks(years, &self.places.get(site.place).nat_res); } // Trade stocks @@ -253,10 +261,9 @@ impl Civs { for stock in stocks.iter().copied() { let mut sell_orders = self.sites .iter_ids() - .filter_map(|(id, site)| site.as_settlement().map(|s| (id, s))) - .map(|(id, settlement)| (id, econ::SellOrder { - quantity: settlement.trade_states[stock].surplus.min(settlement.stocks[stock]), - price: settlement.trade_states[stock].sell_belief.choose_price(ctx), + .map(|(id, site)| (id, econ::SellOrder { + quantity: site.trade_states[stock].surplus.min(site.stocks[stock]), + price: site.trade_states[stock].sell_belief.choose_price(ctx) * 1.5, // Transport cost of 1.5x q_sold: 0.0, })) .filter(|(_, order)| order.quantity > 0.0) @@ -264,15 +271,15 @@ impl Civs { let mut sites = self.sites .ids() - .filter(|id| self.sites.get(*id).as_settlement().is_some()) .collect::>(); sites.shuffle(ctx.rng); // Give all sites a chance to buy first for site in sites { let (max_spend, max_price) = { - let settlement = self.sites.get(site).as_settlement().unwrap(); + let site = self.sites.get(site); + let budget = site.coin * 0.5; ( - settlement.trade_states[stock].purchase_priority * settlement.coin, - settlement.trade_states[stock].buy_belief.price, + (site.trade_states[stock].purchase_priority * budget).min(budget), + site.trade_states[stock].buy_belief.price, ) }; let (quantity, spent) = econ::buy_units(ctx, sell_orders @@ -283,82 +290,28 @@ impl Civs { 1000000.0, // Max price TODO max_spend, ); - let mut settlement = self.sites.get_mut(site).as_settlement_mut().unwrap(); - settlement.coin -= spent; + let mut site = self.sites.get_mut(site); + site.coin -= spent; if quantity > 0.0 { - settlement.stocks[stock] += quantity; - settlement.trade_states[stock].buy_belief.update_buyer(years, spent / quantity); - println!("Belief: {:?}", settlement.trade_states[stock].buy_belief); + site.stocks[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 settlement = self.sites.get_mut(site).as_settlement_mut().unwrap(); - settlement.coin += order.q_sold * order.price; + let mut site = self.sites.get_mut(site); + site.coin += order.q_sold * order.price; if order.q_sold > 0.0 { - settlement.stocks[stock] -= order.q_sold; - settlement.trade_states[stock].sell_belief.update_seller(order.q_sold / order.quantity); + site.stocks[stock] -= order.q_sold; + site.trade_states[stock].sell_belief.update_seller(order.q_sold / order.quantity); } } } - // Trade stocks - /* - let mut sites = self.sites.ids().collect::>(); - sites.shuffle(ctx.rng); // Give all sites a chance to buy first - for site in sites { - let mut stocks = [FOOD, WOOD, ROCK]; - stocks.shuffle(ctx.rng); // Give each stock a chance to be traded first - for stock in stocks.iter().copied() { - if self.sites.get(site).as_settlement().is_none() { - continue; - } - - let settlement = self.sites.get(site).as_settlement().unwrap(); - let quantity_to_buy = settlement.trade_states[stock].buy_q; - let mut bought_quantity = 0.0; - let mut coin = settlement.coin; - drop(settlement); - let mut sell_orders = self - .neighbors(site) - .collect::>() - .into_iter() - .filter_map(|n| self.sites.get(n).as_settlement().map(|s| (n, s))) - .map(|(n, neighbor)| { - // TODO: Add speculation, don't use the domestic value to rationalise price - let trade_state = &neighbor.trade_states[stock]; - let sell_q = trade_state.sell_q.min(neighbor.stocks[stock]); - (n, trade_state.domestic_value, sell_q) - }) - .collect::>(); - sell_orders.sort_by_key(|(_, price, _)| (*price * 1000.0) as i64); - - for (n, price, sell_q) in sell_orders { - if bought_quantity >= quantity_to_buy { - break; - } else { - let buy_quantity = (quantity_to_buy - bought_quantity).min(sell_q).min(coin / price); - let payment = buy_quantity * price; - bought_quantity += buy_quantity; - coin -= payment; - let mut neighbor = self.sites.get_mut(n).as_settlement_mut().unwrap(); - neighbor.stocks[stock] -= buy_quantity; - neighbor.coin += payment; - } - } - - let mut settlement = self.sites.get_mut(site).as_settlement_mut().unwrap(); - settlement.stocks[stock] += bought_quantity; - settlement.coin = coin; - } - } - */ - // Consume stocks for site in self.sites.iter_mut() { - if let SiteKind::Settlement(s) = &mut site.kind { - s.consume_stocks(years); - } + site.consume_stocks(years); } } } @@ -473,7 +426,7 @@ impl NaturalResources { self.wood += chunk.tree_density; self.stone += chunk.rockiness; - self.river += if chunk.river.is_river() { 1.0 } else { 0.0 }; + 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 && @@ -495,81 +448,63 @@ pub struct Site { kind: SiteKind, center: Vec2, pub place: Id, -} -impl Site { - pub fn as_settlement(&self) -> Option<&Settlement> { - if let SiteKind::Settlement(s) = &self.kind { - Some(s) - } else { - None - } - } - - pub fn as_settlement_mut(&mut self) -> Option<&mut Settlement> { - if let SiteKind::Settlement(s) = &mut self.kind { - Some(s) - } else { - None - } - } -} - -#[derive(Debug)] -pub enum SiteKind { - Settlement(Settlement), -} - -#[derive(Debug)] -pub struct Settlement { population: f32, + labor: MapVec, + output: MapVec, stocks: Stocks, trade_states: Stocks, coin: f32, } -impl Settlement { - pub fn civ_birthplace() -> Self { - Self { - population: 24.0, - stocks: Stocks::default(), - trade_states: Stocks::default(), - coin: 1000.0, - } - } +#[derive(Debug)] +pub enum SiteKind { + Settlement, +} +impl Site { pub fn collect_stocks(&mut self, years: f32, nat_res: &NaturalResources) { // Per labourer, per year let collection_rate = Stocks::from_list(&[ - (FOOD, 2.0), - (ROCK, 0.6), - (WOOD, 1.5), + (FARMER, 2.0), + (LUMBERJACK, 1.5), + (MINER, 0.6), + (FISHER, 5.0), ]); - // Proportion of the population dedicated to each task - let workforce_ratios = Stocks::from_list(&[ - (FOOD, self.trade_states[FOOD].domestic_value), - (ROCK, self.trade_states[ROCK].domestic_value), - (WOOD, self.trade_states[WOOD].domestic_value), + // Proportion of the population dedicated to each task (output * price) + let labor_ratios = Stocks::from_list(&[ + (FARMER, self.output[FARMER] * self.trade_states[FOOD].domestic_value), + (LUMBERJACK, self.output[LUMBERJACK] * self.trade_states[WOOD].domestic_value), + (MINER, self.output[MINER] * self.trade_states[ROCK].domestic_value), + (FISHER, self.output[FISHER] * self.trade_states[FOOD].domestic_value), ]); - // Normalise workforce proportions - let wf_total = workforce_ratios.iter().map(|(_, r)| *r).sum::(); - let workforce = workforce_ratios.map(|stock, r| r / wf_total * self.population); - self.stocks[FOOD] += years * (workforce[FOOD] * collection_rate[FOOD] + nat_res.farmland * 0.01).min(nat_res.farmland); - self.stocks[ROCK] += years * (workforce[ROCK] * collection_rate[ROCK] + nat_res.stone * 0.01).min(nat_res.stone); - self.stocks[WOOD] += years * (workforce[WOOD] * collection_rate[WOOD] + nat_res.wood * 0.01).min(nat_res.wood); + // Normalise workforce proportions (so we aren't over-allocating our workforce) + let wf_total = labor_ratios.iter().map(|(_, r)| *r).sum::(); + if wf_total == 0.0 { // 0 output doesn't mean NaNs + let n = labor_ratios.iter().count() as f32; + self.labor = labor_ratios.map(|stock, _| self.population / n); + } else { + self.labor = labor_ratios.map(|stock, r| r / wf_total * self.population); + } - println!("{:?}", nat_res); - println!("{:?}", self.stocks); + self.output[FARMER] = (self.labor[FARMER] * collection_rate[FARMER] + nat_res.farmland * 0.01).min(nat_res.farmland); + self.output[LUMBERJACK] = (self.labor[LUMBERJACK] * collection_rate[LUMBERJACK] + nat_res.wood * 0.01).min(nat_res.wood); + self.output[MINER] = (self.labor[MINER] * collection_rate[MINER] + nat_res.stone * 0.01).min(nat_res.stone); + self.output[FISHER] = (self.labor[FISHER] * collection_rate[FISHER] + nat_res.river * 0.01).min(nat_res.river); + + self.stocks[FOOD] += years * self.output[FARMER]; + self.stocks[WOOD] += years * self.output[LUMBERJACK]; + self.stocks[ROCK] += years * self.output[MINER]; + self.stocks[FOOD] += years * self.output[FISHER]; } pub fn consume_stocks(&mut self, years: f32) { - const EAT_RATE: f32 = 0.5; + const EAT_RATE: f32 = 1.0; const USE_WOOD_RATE: f32 = 0.75; - const BIRTH_RATE: f32 = 0.1; - - self.population += years * BIRTH_RATE; + const BIRTH_RATE: f32 = 0.15; + const DEATH_RATE: f32 = 0.05; let required = Stocks::from_list(&[ (FOOD, self.population as f32 * years * EAT_RATE), @@ -583,8 +518,12 @@ impl Settlement { // Deplete stocks self.stocks.iter_mut().for_each(|(stock, v)| *v = (*v - required[stock]).max(0.0)); + // Births + self.population += years * self.population * BIRTH_RATE; + // Kill people - self.population = (self.population - deficit[FOOD] * years * EAT_RATE).max(0.0); + self.population -= years * self.population * DEATH_RATE; // Natural death rate + self.population = (self.population - deficit[FOOD] * years * EAT_RATE).max(0.0); // Starvation // If in deficit, value the stock more deficit.iter().for_each(|(stock, deficit)| { @@ -611,6 +550,12 @@ impl Settlement { } } +type Occupation = &'static str; +const FARMER: Occupation = "farmer"; +const LUMBERJACK: Occupation = "lumberjack"; +const MINER: Occupation = "miner"; +const FISHER: Occupation = "fisher"; + type Stock = &'static str; const FOOD: Stock = "food"; const WOOD: Stock = "wood"; @@ -644,15 +589,17 @@ impl Default for TradeState { } } +pub type Stocks = MapVec; + #[derive(Default, Clone, Debug)] -pub struct Stocks { - stocks: HashMap, +pub struct MapVec { + stocks: HashMap, zero: T, } -impl Stocks { - pub fn from_list<'a>(i: impl IntoIterator) -> Self - where T: 'a +impl MapVec { + pub fn from_list<'a>(i: impl IntoIterator) -> Self + where K: 'a, T: 'a { Self { stocks: i.into_iter().cloned().collect(), @@ -660,38 +607,38 @@ impl Stocks { } } - pub fn get_mut(&mut self, stock: Stock) -> &mut T { + pub fn get_mut(&mut self, stock: K) -> &mut T { self .stocks .entry(stock) .or_default() } - pub fn get(&self, stock: Stock) -> &T { + pub fn get(&self, stock: K) -> &T { self.stocks.get(&stock).unwrap_or(&self.zero) } - pub fn map(mut self, mut f: impl FnMut(Stock, T) -> T) -> Self { + pub fn map(mut self, mut f: impl FnMut(K, T) -> T) -> Self { self.stocks.iter_mut().for_each(|(s, v)| *v = f(*s, std::mem::take(v))); self } - pub fn iter(&self) -> impl Iterator + '_ { + pub fn iter(&self) -> impl Iterator + '_ { self.stocks.iter().map(|(s, v)| (*s, v)) } - pub fn iter_mut(&mut self) -> impl Iterator + '_ { + pub fn iter_mut(&mut self) -> impl Iterator + '_ { self.stocks.iter_mut().map(|(s, v)| (*s, v)) } } -impl std::ops::Index for Stocks { +impl std::ops::Index for MapVec { type Output = T; - fn index(&self, stock: Stock) -> &Self::Output { self.get(stock) } + fn index(&self, stock: K) -> &Self::Output { self.get(stock) } } -impl std::ops::IndexMut for Stocks { - fn index_mut(&mut self, stock: Stock) -> &mut Self::Output { self.get_mut(stock) } +impl std::ops::IndexMut for MapVec { + fn index_mut(&mut self, stock: K) -> &mut Self::Output { self.get_mut(stock) } } diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 6989fb7dc6..92922f0324 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -285,7 +285,9 @@ impl Settlement { .map(|tile| tile.tower = Some(Tower::Wall)); } } - wall_path.push(wall_path[0]); + if wall_path.len() > 0 { + wall_path.push(wall_path[0]); + } self.land .write_path(&wall_path, WayKind::Wall, buildable, true); } From 16a175abe098639eb6ae86c615b5557177ee944c Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sun, 29 Mar 2020 21:46:19 +0100 Subject: [PATCH 092/195] Nicer water shaders --- assets/voxygen/shaders/fluid-frag/shiny.glsl | 33 +++++++++++--------- assets/voxygen/shaders/include/sky.glsl | 2 +- world/src/civ/mod.rs | 24 +++++++------- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/assets/voxygen/shaders/fluid-frag/shiny.glsl b/assets/voxygen/shaders/fluid-frag/shiny.glsl index 12af11e6f3..a1d638ed34 100644 --- a/assets/voxygen/shaders/fluid-frag/shiny.glsl +++ b/assets/voxygen/shaders/fluid-frag/shiny.glsl @@ -28,30 +28,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() { @@ -101,7 +104,7 @@ void main() { vec3 point_light = light_at(f_pos, norm); light += point_light; diffuse_light += point_light; - vec3 surf_color = srgb_to_linear(vec3(0.2, 0.5, 1.0)) * light * diffuse_light * ambient_light; + vec3 surf_color = srgb_to_linear(vec3(0.1)) * light * diffuse_light * ambient_light; float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x); vec4 clouds; @@ -116,9 +119,9 @@ void main() { // Tint reflect_color = mix(reflect_color, surf_color, 0.6); // 0 = 100% reflection, 1 = translucent water - float passthrough = pow(dot(faceforward(f_norm, f_norm, cam_to_frag), -cam_to_frag), 0.5); + float passthrough = pow(dot(faceforward(f_norm, f_norm, cam_to_frag), -cam_to_frag), 1.0); - vec4 color = mix(vec4(reflect_color * 2.0, 1.0), vec4(surf_color, 1.0 / (1.0 + diffuse_light * 0.25)), passthrough); + vec4 color = mix(vec4(reflect_color, 1.0), vec4(surf_color, 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/include/sky.glsl b/assets/voxygen/shaders/include/sky.glsl index 806d1bf140..6fd726230b 100644 --- a/assets/voxygen/shaders/include/sky.glsl +++ b/assets/voxygen/shaders/include/sky.glsl @@ -187,7 +187,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/world/src/civ/mod.rs b/world/src/civ/mod.rs index e2453a23b5..c21c7efb43 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -593,7 +593,7 @@ pub type Stocks = MapVec; #[derive(Default, Clone, Debug)] pub struct MapVec { - stocks: HashMap, + entries: HashMap, zero: T, } @@ -602,43 +602,43 @@ impl MapVec { where K: 'a, T: 'a { Self { - stocks: i.into_iter().cloned().collect(), + entries: i.into_iter().cloned().collect(), zero: T::default(), } } - pub fn get_mut(&mut self, stock: K) -> &mut T { + pub fn get_mut(&mut self, entry: K) -> &mut T { self - .stocks - .entry(stock) + .entries + .entry(entry) .or_default() } - pub fn get(&self, stock: K) -> &T { - self.stocks.get(&stock).unwrap_or(&self.zero) + pub fn get(&self, entry: K) -> &T { + self.entries.get(&entry).unwrap_or(&self.zero) } pub fn map(mut self, mut f: impl FnMut(K, T) -> T) -> Self { - self.stocks.iter_mut().for_each(|(s, v)| *v = f(*s, std::mem::take(v))); + self.entries.iter_mut().for_each(|(s, v)| *v = f(*s, std::mem::take(v))); self } pub fn iter(&self) -> impl Iterator + '_ { - self.stocks.iter().map(|(s, v)| (*s, v)) + self.entries.iter().map(|(s, v)| (*s, v)) } pub fn iter_mut(&mut self) -> impl Iterator + '_ { - self.stocks.iter_mut().map(|(s, v)| (*s, v)) + self.entries.iter_mut().map(|(s, v)| (*s, v)) } } impl std::ops::Index for MapVec { type Output = T; - fn index(&self, stock: K) -> &Self::Output { self.get(stock) } + fn index(&self, entry: K) -> &Self::Output { self.get(entry) } } impl std::ops::IndexMut for MapVec { - fn index_mut(&mut self, stock: K) -> &mut Self::Output { self.get_mut(stock) } + fn index_mut(&mut self, entry: K) -> &mut Self::Output { self.get_mut(entry) } } From d7385fd99b9d9bc06390c5903f35effa2cad7954 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sun, 29 Mar 2020 22:06:28 +0100 Subject: [PATCH 093/195] Fixed cheap shader --- assets/voxygen/shaders/fluid-frag/cheap.glsl | 6 +++--- assets/voxygen/shaders/fluid-frag/shiny.glsl | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/assets/voxygen/shaders/fluid-frag/cheap.glsl b/assets/voxygen/shaders/fluid-frag/cheap.glsl index f3c92ab429..e56796d1d5 100644 --- a/assets/voxygen/shaders/fluid-frag/cheap.glsl +++ b/assets/voxygen/shaders/fluid-frag/cheap.glsl @@ -48,9 +48,9 @@ void main() { vec4 clouds; vec3 fog_color = get_sky_color(normalize(f_pos - cam_pos.xyz), 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), -cam_to_frag), 0.5); + float passthrough = dot(faceforward(f_norm, f_norm, cam_to_frag), -cam_to_frag); - vec4 color = mix(vec4(surf_color, 1.0), vec4(surf_color, 1.0 / (1.0 + diffuse_light * 0.25)), passthrough); + vec4 color = mix(vec4(surf_color, 1.0), vec4(surf_color, 1.0 / (1.0 + diffuse_light)), 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 a1d638ed34..f6692cbeb9 100644 --- a/assets/voxygen/shaders/fluid-frag/shiny.glsl +++ b/assets/voxygen/shaders/fluid-frag/shiny.glsl @@ -104,7 +104,6 @@ void main() { vec3 point_light = light_at(f_pos, norm); light += point_light; diffuse_light += point_light; - vec3 surf_color = srgb_to_linear(vec3(0.1)) * light * diffuse_light * ambient_light; float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x); vec4 clouds; @@ -117,11 +116,11 @@ void main() { vec4 _clouds; vec3 reflect_color = get_sky_color(reflect_ray_dir, time_of_day.x, f_pos, vec3(-100000), 0.25, false, _clouds) * f_light; // Tint - reflect_color = mix(reflect_color, surf_color, 0.6); + reflect_color = reflect_color * 0.5 * (diffuse_light + ambient_light); // 0 = 100% reflection, 1 = translucent water - float passthrough = pow(dot(faceforward(f_norm, f_norm, cam_to_frag), -cam_to_frag), 1.0); + float passthrough = dot(faceforward(f_norm, f_norm, cam_to_frag), -cam_to_frag); - vec4 color = mix(vec4(reflect_color, 1.0), vec4(surf_color, 1.0 / (1.0 + diffuse_light * 0.25)), passthrough); + 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); } From b3c9122395dc50cd63f363338631a6b5d01af006 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 30 Mar 2020 17:46:44 +0100 Subject: [PATCH 094/195] Overhauled domestic economy simulation, better debug information --- voxygen/src/anim/quadruped_small/run.rs | 6 + world/examples/water.rs | 4 +- world/src/civ/mod.rs | 392 +++++++++++++++--------- 3 files changed, 254 insertions(+), 148 deletions(-) diff --git a/voxygen/src/anim/quadruped_small/run.rs b/voxygen/src/anim/quadruped_small/run.rs index 801c66ddb4..3f679ca859 100644 --- a/voxygen/src/anim/quadruped_small/run.rs +++ b/voxygen/src/anim/quadruped_small/run.rs @@ -17,6 +17,12 @@ impl Animation for RunAnimation { ) -> Self::Skeleton { let mut next = (*skeleton).clone(); + let anim = assets::load::>("anims.quadruped_small.run"); + + + + + let slow = (anim_time as f32 * 14.0).sin(); let fast = (anim_time as f32 * 20.0).sin(); let fast_alt = (anim_time as f32 * 20.0 + PI / 2.0).sin(); diff --git a/world/examples/water.rs b/world/examples/water.rs index 308d83a53f..40f80a39a3 100644 --- a/world/examples/water.rs +++ b/world/examples/water.rs @@ -160,7 +160,9 @@ fn main() { let place = world.civs().place(*id); println!("Place {} info: {:#?}", id.id(), place); - println!("Site: {:#?}", world.civs().sites().find(|site| site.place == *id)); + if let Some(site) = world.civs().sites().find(|site| site.place == *id) { + println!("Site: {}", site); + } } } } diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index c21c7efb43..fb38821cfe 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -3,6 +3,7 @@ mod econ; use std::{ ops::Range, hash::Hash, + fmt, }; use hashbrown::{HashMap, HashSet}; use vek::*; @@ -38,7 +39,7 @@ fn attempt(max_iters: usize, mut f: impl FnMut() -> Option) -> Option { (0..max_iters).find_map(|_| f()) } -const INITIAL_CIV_COUNT: usize = 32; +const INITIAL_CIV_COUNT: usize = 16; #[derive(Default)] pub struct Civs { @@ -208,11 +209,15 @@ impl Civs { place: place, population: 24.0, - labor: MapVec::default(), - output: MapVec::default(), - stocks: Stocks::default(), - trade_states: Stocks::default(), - coin: 1000.0, + + stocks: Stocks::from_default(100.0), + values: Stocks::from_default(None), + + labors: MapVec::from_default(0.01), + yields: MapVec::from_default(1.0), + + //trade_states: Stocks::default(), + //coin: 1000.0, }); // Find neighbors @@ -250,69 +255,69 @@ impl Civs { } fn tick(&mut self, ctx: &mut GenCtx, years: f32) { - // Collect stocks + println!("Tick!"); for site in self.sites.iter_mut() { - site.collect_stocks(years, &self.places.get(site.place).nat_res); + site.simulate(years, &self.places.get(site.place).nat_res); } // Trade stocks - let mut stocks = [FOOD, WOOD, ROCK]; - 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.trade_states[stock].surplus.min(site.stocks[stock]), - price: site.trade_states[stock].sell_belief.choose_price(ctx) * 1.5, // Transport cost of 1.5x - q_sold: 0.0, - })) - .filter(|(_, order)| order.quantity > 0.0) - .collect::>(); + // let mut stocks = [FOOD, WOOD, ROCK]; + // 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.trade_states[stock].surplus.min(site.stocks[stock]), + // price: site.trade_states[stock].sell_belief.choose_price(ctx) * 1.2, // Transport cost of 1.2x + // 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) = { - let site = self.sites.get(site); - let budget = site.coin * 0.5; - ( - (site.trade_states[stock].purchase_priority * budget).min(budget), - site.trade_states[stock].buy_belief.price, - ) - }; - 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), - 1000000.0, // Max quantity TODO - 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.trade_states[stock].buy_belief.update_buyer(years, spent / quantity); - println!("Belief: {:?}", site.trade_states[stock].buy_belief); - } - } + // 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) = { + // let site = self.sites.get(site); + // let budget = site.coin * 0.5; + // ( + // (site.trade_states[stock].purchase_priority * budget).min(budget), + // site.trade_states[stock].buy_belief.price, + // ) + // }; + // 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), + // 1000000.0, // Max quantity TODO + // 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.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.trade_states[stock].sell_belief.update_seller(order.q_sold / order.quantity); - } - } - } + // 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.trade_states[stock].sell_belief.update_seller(order.q_sold / order.quantity); + // } + // } + // } // Consume stocks - for site in self.sites.iter_mut() { - site.consume_stocks(years); - } + //for site in self.sites.iter_mut() { + // site.consume_stocks(years); + //} } } @@ -415,7 +420,7 @@ pub struct Place { #[derive(Default, Debug)] pub struct NaturalResources { wood: f32, - stone: f32, + rock: f32, river: f32, farmland: f32, } @@ -425,7 +430,7 @@ impl NaturalResources { let chunk = if let Some(chunk) = ctx.sim.get(loc) { chunk } else { return }; self.wood += chunk.tree_density; - self.stone += chunk.rockiness; + self.rock += chunk.rockiness; self.river += if chunk.river.is_river() { 5.0 } else { 0.0 }; self.farmland += if chunk.humidity > 0.35 && @@ -450,11 +455,42 @@ pub struct Site { pub place: Id, population: f32, - labor: MapVec, - output: MapVec, + + // Total amount of each stock stocks: Stocks, - trade_states: Stocks, - coin: f32, + // 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, + + //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")?, + } + writeln!(f, "- population: {}", self.population.floor() as u32)?; + writeln!(f, "Stocks")?; + for (stock, q) in self.stocks.iter() { + writeln!(f, "- {}: {}", stock, q.floor())?; + } + writeln!(f, "Prices")?; + for (stock, v) in self.values.iter() { + writeln!(f, "- {}: {}", stock, v.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)?; + } + + Ok(()) + } } #[derive(Debug)] @@ -463,90 +499,133 @@ pub enum SiteKind { } impl Site { - pub fn collect_stocks(&mut self, years: f32, nat_res: &NaturalResources) { - // Per labourer, per year - let collection_rate = Stocks::from_list(&[ - (FARMER, 2.0), - (LUMBERJACK, 1.5), - (MINER, 0.6), - (FISHER, 5.0), - ]); - - // Proportion of the population dedicated to each task (output * price) - let labor_ratios = Stocks::from_list(&[ - (FARMER, self.output[FARMER] * self.trade_states[FOOD].domestic_value), - (LUMBERJACK, self.output[LUMBERJACK] * self.trade_states[WOOD].domestic_value), - (MINER, self.output[MINER] * self.trade_states[ROCK].domestic_value), - (FISHER, self.output[FISHER] * self.trade_states[FOOD].domestic_value), - ]); - - // Normalise workforce proportions (so we aren't over-allocating our workforce) - let wf_total = labor_ratios.iter().map(|(_, r)| *r).sum::(); - if wf_total == 0.0 { // 0 output doesn't mean NaNs - let n = labor_ratios.iter().count() as f32; - self.labor = labor_ratios.map(|stock, _| self.population / n); - } else { - self.labor = labor_ratios.map(|stock, r| r / wf_total * self.population); + 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; } - self.output[FARMER] = (self.labor[FARMER] * collection_rate[FARMER] + nat_res.farmland * 0.01).min(nat_res.farmland); - self.output[LUMBERJACK] = (self.labor[LUMBERJACK] * collection_rate[LUMBERJACK] + nat_res.wood * 0.01).min(nat_res.wood); - self.output[MINER] = (self.labor[MINER] * collection_rate[MINER] + nat_res.stone * 0.01).min(nat_res.stone); - self.output[FISHER] = (self.labor[FISHER] * collection_rate[FISHER] + nat_res.river * 0.01).min(nat_res.river); + let orders = vec![ + (None, vec![(FOOD, 0.25)]), + (Some(COOK), vec![(FLOUR, 6.5), (MEAT, 1.5)]), + (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::>>(); - self.stocks[FOOD] += years * self.output[FARMER]; - self.stocks[WOOD] += years * self.output[LUMBERJACK]; - self.stocks[ROCK] += years * self.output[MINER]; - self.stocks[FOOD] += years * self.output[FISHER]; - } + 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 { + debug_assert!(!amount.is_nan(), "{:?}, {}", labor, stock); + debug_assert!(!scale.is_nan(), "{:?}, {}, {}", labor, stock, self.population); + demand[*stock] += *amount * scale; + } + } - pub fn consume_stocks(&mut self, years: f32) { - const EAT_RATE: f32 = 1.0; - const USE_WOOD_RATE: f32 = 0.75; - const BIRTH_RATE: f32 = 0.15; - const DEATH_RATE: f32 = 0.05; + let surplus = demand.clone().map(|stock, tgt| { + debug_assert!(!self.stocks[stock].is_nan()); + debug_assert!(!demand[stock].is_nan()); + self.stocks[stock] - demand[stock] + }); - let required = Stocks::from_list(&[ - (FOOD, self.population as f32 * years * EAT_RATE), - (WOOD, self.population as f32 * years * USE_WOOD_RATE), + // Update values according to the surplus of each stock + surplus.iter().for_each(|(stock, surplus)| { + let val = 2.5f32.powf(-*surplus / demand[stock]); + self.values[stock] = if val > 0.01 && val < 10000.0 { Some(val) } else { None }; + }); + + // 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.5)), + (COOK, (FOOD, 8.0)), ]); - // Calculate surplus and deficit of each stock - let surplus = required.clone().map(|stock, required| (self.stocks[stock] - required).max(0.0)); - let deficit = required.clone().map(|stock, required| (required - self.stocks[stock]).max(0.0)); + let population = self.population; - // Deplete stocks - self.stocks.iter_mut().for_each(|(stock, v)| *v = (*v - required[stock]).max(0.0)); - - // Births - self.population += years * self.population * BIRTH_RATE; - - // Kill people - self.population -= years * self.population * DEATH_RATE; // Natural death rate - self.population = (self.population - deficit[FOOD] * years * EAT_RATE).max(0.0); // Starvation - - // If in deficit, value the stock more - deficit.iter().for_each(|(stock, deficit)| { - if *deficit > 0.0 { - let mut trade_state = &mut self.trade_states[stock]; - trade_state.domestic_value += *deficit * 0.01; - trade_state.surplus = -*deficit; - trade_state.purchase_priority *= 1.1; - } + // Redistribute workforce according to relative good values + let labor_ratios = production.clone().map(|labor, (output_stock, _)| { + debug_assert!(self.values[output_stock].unwrap_or(0.0) < 1000000.0, "{:?}", self.values[output_stock]); + debug_assert!(self.yields[labor] < 1000000.0, "{}", self.yields[labor]); + self.values[output_stock].unwrap_or(0.0) * self.yields[labor] + }); + let labor_ratio_sum = labor_ratios.iter().map(|(_, r)| *r).sum::().max(0.01); + assert!(labor_ratio_sum > 0.0); + production.iter().for_each(|(labor, _)| { + debug_assert!(!labor_ratios[labor].is_nan() && !labor_ratios[labor].is_infinite(), "{:?}, {}", labor, labor_ratios[labor]); + debug_assert!(!labor_ratio_sum.is_nan() && !labor_ratio_sum.is_infinite(), "{:?}, {}", labor, labor_ratio_sum); + let smooth = 0.5; + self.labors[labor] = smooth * self.labors[labor] + (1.0 - smooth) * (labor_ratios[labor].max(labor_ratio_sum / 1000.0) / labor_ratio_sum); }); - // If in surplus, value the stock less - surplus.iter().for_each(|(stock, surplus)| { - if *surplus > 0.0 { - let mut trade_state = &mut self.trade_states[stock]; - trade_state.domestic_value /= 1.0 + *surplus * 0.01; - trade_state.surplus = *surplus; - } - }); + // Production + let stocks_before = self.stocks.clone(); + self.stocks = Stocks::from_default(0.0); + for (labor, orders) in orders.iter() { + let scale = if let Some(labor) = labor { self.labors[*labor] } else { 1.0 } * population; - // Normalise purchasing priorities - let pp_avg = self.trade_states.iter().map(|(_, ts)| ts.purchase_priority).sum::() / self.trade_states.iter().count() as f32; - self.trade_states.iter_mut().for_each(|(_, ts)| ts.purchase_priority /= pp_avg); + // 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 + let min_satisfaction = 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 * min_satisfaction; + + // 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 yield_per_worker = min_satisfaction * rate; + self.yields[*labor] = yield_per_worker; + self.stocks[stock] += yield_per_worker * self.labors[*labor] * population; + } + } + + // Births/deaths + const NATURAL_BIRTH_RATE: f32 = 0.15; + const DEATH_RATE: f32 = 0.05; + debug_assert!(!surplus[FOOD].is_nan()); + debug_assert!(!surplus[FOOD].is_infinite()); + let birth_rate = if surplus[FOOD] > 0.0 { NATURAL_BIRTH_RATE } else { 0.0 }; + self.population += years * self.population * (birth_rate - DEATH_RATE); } } @@ -555,11 +634,20 @@ 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"; #[derive(Debug, Clone)] struct TradeState { @@ -594,7 +682,7 @@ pub type Stocks = MapVec; #[derive(Default, Clone, Debug)] pub struct MapVec { entries: HashMap, - zero: T, + default: T, } impl MapVec { @@ -603,24 +691,34 @@ impl MapVec { { Self { entries: i.into_iter().cloned().collect(), - zero: T::default(), + 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_default() + .or_insert_with(|| default.clone()) } pub fn get(&self, entry: K) -> &T { - self.entries.get(&entry).unwrap_or(&self.zero) + self.entries.get(&entry).unwrap_or(&self.default) } - pub fn map(mut self, mut f: impl FnMut(K, T) -> T) -> Self { - self.entries.iter_mut().for_each(|(s, v)| *v = f(*s, std::mem::take(v))); - self + pub fn map(mut 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 + '_ { From 71b8cde2666e4899ada1b52922cffed7c9ec8117 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 30 Mar 2020 23:45:41 +0100 Subject: [PATCH 095/195] Added trading to new economic model --- world/src/civ/mod.rs | 164 +++++++++++++++++++++++-------------------- 1 file changed, 88 insertions(+), 76 deletions(-) diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index fb38821cfe..0dbf842983 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -211,13 +211,14 @@ impl Civs { 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), - //trade_states: Stocks::default(), - //coin: 1000.0, + trade_states: Stocks::default(), + coin: 1000.0, }); // Find neighbors @@ -261,63 +262,67 @@ impl Civs { } // Trade stocks - // let mut stocks = [FOOD, WOOD, ROCK]; - // 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.trade_states[stock].surplus.min(site.stocks[stock]), - // price: site.trade_states[stock].sell_belief.choose_price(ctx) * 1.2, // Transport cost of 1.2x - // q_sold: 0.0, - // })) - // .filter(|(_, order)| order.quantity > 0.0) - // .collect::>(); + 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, { + let total_value = site.values.iter().map(|(_, v)| (*v).unwrap_or(0.0)).sum::(); + let quantity = if let Some(val) = site.values[stock] { + ((total_value / val) * site.surplus[stock].max(0.0)).min(site.stocks[stock]) + } else { + 0.0 + }; + econ::SellOrder { + quantity, + 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) = { - // let site = self.sites.get(site); - // let budget = site.coin * 0.5; - // ( - // (site.trade_states[stock].purchase_priority * budget).min(budget), - // site.trade_states[stock].buy_belief.price, - // ) - // }; - // 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), - // 1000000.0, // Max quantity TODO - // 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.trade_states[stock].buy_belief.update_buyer(years, spent / quantity); - // println!("Belief: {:?}", site.trade_states[stock].buy_belief); - // } - // } + 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) = { + 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::(); + ( + (site.values[stock].unwrap_or(0.1) / total_value * budget).min(budget), + site.trade_states[stock].buy_belief.price, + ) + }; + 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), + 1000000.0, // Max quantity TODO + 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.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.trade_states[stock].sell_belief.update_seller(order.q_sold / order.quantity); - // } - // } - // } - - // Consume stocks - //for site in self.sites.iter_mut() { - // site.consume_stocks(years); - //} + 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.trade_states[stock].sell_belief.update_seller(order.q_sold / order.quantity); + } + } + } } } @@ -458,6 +463,8 @@ pub struct Site { // 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>, @@ -466,8 +473,8 @@ pub struct Site { // Per worker, per year, of their output good yields: MapVec, - //trade_states: Stocks, - //coin: f32, + trade_states: Stocks, + coin: f32, } impl fmt::Display for Site { @@ -476,6 +483,7 @@ impl fmt::Display for Site { SiteKind::Settlement => writeln!(f, "Settlement")?, } 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())?; @@ -539,16 +547,17 @@ impl Site { } } - let surplus = demand.clone().map(|stock, tgt| { + self.surplus = demand.clone().map(|stock, tgt| { debug_assert!(!self.stocks[stock].is_nan()); debug_assert!(!demand[stock].is_nan()); self.stocks[stock] - demand[stock] }); // Update values according to the surplus of each stock - surplus.iter().for_each(|(stock, surplus)| { - let val = 2.5f32.powf(-*surplus / demand[stock]); - self.values[stock] = if val > 0.01 && val < 10000.0 { Some(val) } else { None }; + let values = &mut self.values; + self.surplus.iter().for_each(|(stock, surplus)| { + let val = 3.5f32.powf(-*surplus / demand[stock]); + values[stock] = if val > 0.001 && val < 1000.0 { Some(val) } else { None }; }); // Per labourer, per year @@ -580,7 +589,6 @@ impl Site { // Production let stocks_before = self.stocks.clone(); - self.stocks = Stocks::from_default(0.0); for (labor, orders) in orders.iter() { let scale = if let Some(labor) = labor { self.labors[*labor] } else { 1.0 } * population; @@ -607,7 +615,7 @@ impl Site { let used = quantity * min_satisfaction; // Deplete stocks accordingly - //self.stocks[*stock] = (self.stocks[*stock] - used).max(0.0); + self.stocks[*stock] = (self.stocks[*stock] - used).max(0.0); } // Industries produce things @@ -615,16 +623,20 @@ impl Site { let (stock, rate) = production[*labor]; let yield_per_worker = min_satisfaction * rate; self.yields[*labor] = yield_per_worker; - self.stocks[stock] += yield_per_worker * self.labors[*labor] * population; + let workers = self.labors[*labor] * population; + 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; - debug_assert!(!surplus[FOOD].is_nan()); - debug_assert!(!surplus[FOOD].is_infinite()); - let birth_rate = if surplus[FOOD] > 0.0 { NATURAL_BIRTH_RATE } else { 0.0 }; + debug_assert!(!self.surplus[FOOD].is_nan()); + debug_assert!(!self.surplus[FOOD].is_infinite()); + let birth_rate = if self.surplus[FOOD] > 0.0 { NATURAL_BIRTH_RATE } else { 0.0 }; self.population += years * self.population * (birth_rate - DEATH_RATE); } } @@ -648,15 +660,18 @@ 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, - /// The price/value assigned to the stock by the host settlement - domestic_value: f32, - surplus: f32, - purchase_priority: f32, } impl Default for TradeState { @@ -670,9 +685,6 @@ impl Default for TradeState { price: 1.0, confidence: 0.25, }, - domestic_value: 1.0, - surplus: 0.0, - purchase_priority: 1.0, } } } From 9daf20e87e470cdb10b5b0ad74642f0d3d9be9b1 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Tue, 31 Mar 2020 00:03:56 +0100 Subject: [PATCH 096/195] Fixed value rationalisation --- world/src/civ/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 0dbf842983..1520f39ae9 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -556,7 +556,7 @@ impl Site { // 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(-*surplus / demand[stock]); + let val = 3.5f32.powf(1.0 - *surplus / demand[stock]); values[stock] = if val > 0.001 && val < 1000.0 { Some(val) } else { None }; }); From 966f96c5889d2a469e5d777df88d9e6fe0a0fb6e Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 1 Apr 2020 21:38:01 +0100 Subject: [PATCH 097/195] Start of layered settlement generation, better settlement terraingen --- world/examples/settlement_viewer.rs | 2 +- world/src/civ/mod.rs | 207 +++++++++++++++------------- world/src/column/mod.rs | 14 +- world/src/site/mod.rs | 8 +- world/src/site/settlement/mod.rs | 172 ++++++++++++++--------- 5 files changed, 233 insertions(+), 170 deletions(-) diff --git a/world/examples/settlement_viewer.rs b/world/examples/settlement_viewer.rs index ca3da4a5b4..44c392cc6e 100644 --- a/world/examples/settlement_viewer.rs +++ b/world/examples/settlement_viewer.rs @@ -1,6 +1,6 @@ use rand::thread_rng; use vek::*; -use veloren_world::generator::settlement::Settlement; +use veloren_world::site::Settlement; const W: usize = 640; const H: usize = 480; diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 1520f39ae9..298f3050d9 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -39,7 +39,7 @@ fn attempt(max_iters: usize, mut f: impl FnMut() -> Option) -> Option { (0..max_iters).find_map(|_| f()) } -const INITIAL_CIV_COUNT: usize = 16; +const INITIAL_CIV_COUNT: usize = 32; #[derive(Default)] pub struct Civs { @@ -216,7 +216,10 @@ impl Civs { 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, }); @@ -262,67 +265,64 @@ impl Civs { } // 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, { - let total_value = site.values.iter().map(|(_, v)| (*v).unwrap_or(0.0)).sum::(); - let quantity = if let Some(val) = site.values[stock] { - ((total_value / val) * site.surplus[stock].max(0.0)).min(site.stocks[stock]) - } else { - 0.0 - }; - econ::SellOrder { - quantity, - 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 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) = { - 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::(); - ( - (site.values[stock].unwrap_or(0.1) / total_value * budget).min(budget), - site.trade_states[stock].buy_belief.price, - ) - }; - 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), - 1000000.0, // Max quantity TODO - 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.trade_states[stock].buy_belief.update_buyer(years, spent / quantity); - println!("Belief: {:?}", site.trade_states[stock].buy_belief); - } - } + // 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.trade_states[stock].sell_belief.update_seller(order.q_sold / order.quantity); - } - } - } + // 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); + // } + // } + // } } } @@ -472,7 +472,10 @@ pub struct Site { 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, } @@ -488,14 +491,18 @@ impl fmt::Display for Site { for (stock, q) in self.stocks.iter() { writeln!(f, "- {}: {}", stock, q.floor())?; } - writeln!(f, "Prices")?; - for (stock, v) in self.values.iter() { - writeln!(f, "- {}: {}", stock, v.map(|x| x.to_string()).unwrap_or_else(|| "N/A".to_string()))?; + 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(()) } @@ -526,8 +533,8 @@ impl Site { } let orders = vec![ - (None, vec![(FOOD, 0.25)]), - (Some(COOK), vec![(FLOUR, 6.5), (MEAT, 1.5)]), + (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)]), @@ -537,20 +544,33 @@ impl Site { .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 { - debug_assert!(!amount.is_nan(), "{:?}, {}", labor, stock); - debug_assert!(!scale.is_nan(), "{:?}, {}, {}", labor, stock, self.population); 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, tgt| { - debug_assert!(!self.stocks[stock].is_nan()); - debug_assert!(!demand[stock].is_nan()); - self.stocks[stock] - demand[stock] + supply[stock] + stocks[stock] - demand[stock] - last_exports[stock] }); // Update values according to the surplus of each stock @@ -560,30 +580,28 @@ impl Site { values[stock] = if val > 0.001 && val < 1000.0 { Some(val) } else { None }; }); - // 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.5)), - (COOK, (FOOD, 8.0)), - ]); + // 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; + let trade_states = &self.trade_states; + 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, _)| { - debug_assert!(self.values[output_stock].unwrap_or(0.0) < 1000000.0, "{:?}", self.values[output_stock]); - debug_assert!(self.yields[labor] < 1000000.0, "{}", self.yields[labor]); - self.values[output_stock].unwrap_or(0.0) * self.yields[labor] + 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); - assert!(labor_ratio_sum > 0.0); production.iter().for_each(|(labor, _)| { - debug_assert!(!labor_ratios[labor].is_nan() && !labor_ratios[labor].is_infinite(), "{:?}, {}", labor, labor_ratios[labor]); - debug_assert!(!labor_ratio_sum.is_nan() && !labor_ratio_sum.is_infinite(), "{:?}, {}", labor, labor_ratio_sum); - let smooth = 0.5; + 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); }); @@ -596,7 +614,8 @@ impl Site { // 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 - let min_satisfaction = orders + // In effect, this is the productivity + let productivity = orders .iter() .map(|(stock, amount)| { // What quantity is this order requesting? @@ -612,7 +631,7 @@ impl Site { // What quantity is this order requesting? let quantity = *amount * scale; // What amount gets actually used in production? - let used = quantity * min_satisfaction; + let used = quantity * productivity; // Deplete stocks accordingly self.stocks[*stock] = (self.stocks[*stock] - used).max(0.0); @@ -621,9 +640,11 @@ impl Site { // Industries produce things if let Some(labor) = labor { let (stock, rate) = production[*labor]; - let yield_per_worker = min_satisfaction * rate; - self.yields[*labor] = yield_per_worker; 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); } } @@ -634,8 +655,6 @@ impl Site { // Births/deaths const NATURAL_BIRTH_RATE: f32 = 0.15; const DEATH_RATE: f32 = 0.05; - debug_assert!(!self.surplus[FOOD].is_nan()); - debug_assert!(!self.surplus[FOOD].is_infinite()); let birth_rate = if self.surplus[FOOD] > 0.0 { NATURAL_BIRTH_RATE } else { 0.0 }; self.population += years * self.population * (birth_rate - DEATH_RATE); } @@ -752,7 +771,3 @@ impl std::ops::IndexMut for MapVec Sampler<'a> for ColumnGen<'a> { humidity.sub(CONFIG.jungle_hum).mul(1.0), ); - let ground = sim_chunk.sites.iter().fold(ground, |ground, site| { - site.get_surface(wpos) - .and_then(|block| block.get_color()) - .map(|col| col.map(|e| e as f32 / 255.0)) - .unwrap_or(ground) - }); - // Snow covering let snow_cover = temp .sub(CONFIG.snow_temp) @@ -1082,7 +1075,12 @@ 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, diff --git a/world/src/site/mod.rs b/world/src/site/mod.rs index 1fe239851b..892078a0c3 100644 --- a/world/src/site/mod.rs +++ b/world/src/site/mod.rs @@ -14,6 +14,10 @@ use common::{ use std::{fmt, sync::Arc}; use vek::*; +pub struct SpawnRules { + pub trees: bool, +} + #[derive(Clone)] pub enum Site { Settlement(Arc), @@ -26,9 +30,9 @@ impl Site { } } - pub fn get_surface(&self, wpos: Vec2) -> Option { + pub fn spawn_rules(&self, wpos: Vec2) -> SpawnRules { match self { - Site::Settlement(settlement) => settlement.get_surface(wpos), + Site::Settlement(s) => s.spawn_rules(wpos) } } diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 92922f0324..1c43e5f965 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -3,12 +3,13 @@ use crate::{ sim::{SimChunk, WorldSim}, util::{Grid, RandomField, Sampler, StructureGen2d}, }; +use super::SpawnRules; use common::{ astar::Astar, path::Path, spiral::Spiral2d, terrain::{Block, BlockKind}, - vol::{BaseVol, RectSizedVol, WriteVol}, + vol::{BaseVol, RectSizedVol, WriteVol, Vox}, store::{Id, Store}, }; use hashbrown::{HashMap, HashSet}; @@ -357,6 +358,12 @@ impl Settlement { 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.is_none(), + } + } + pub fn apply_to<'a>( &'a self, wpos2d: Vec2, @@ -369,52 +376,81 @@ impl Settlement { 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.alt.floor() as i32; - let wpos2d = wpos2d + offs; + // Sample settlement + let sample = self.land.get_at_block(rpos); - match self.land.get_at_block(wpos2d - self.origin) { - Sample::Way(WayKind::Wall, dist) => { - let color = Lerp::lerp( - Rgb::new(130i32, 100, 0), - Rgb::new(90, 70, 50), - (rand_field.get(wpos2d.into()) % 256) as f32 / 256.0, - ) - .map(|e| (e % 256) as u8); - for z in 0..12 { - if dist / WayKind::Wall.width() - < ((1.0 - z as f32 / 12.0) * 2.0).min(1.0) - { - vol.set( - Vec3::new(offs.x, offs.y, col_sample.alt.floor() as i32 + z), - Block::new(BlockKind::Normal, color), - ); - } - } - }, - Sample::Tower(Tower::Wall, _pos) => { - for z in 0..16 { + // Ground color + if let Some(color) = self.get_color(rpos) { + for z in -3..3 { + vol.set( + Vec3::new(offs.x, offs.y, surface_z + z), + if z >= 0 { Block::empty() } else { Block::new(BlockKind::Normal, color) }, + ); + } + } + + // Walls + if let Some((WayKind::Wall, dist)) = sample.way { + let color = Lerp::lerp( + Rgb::new(130i32, 100, 0), + Rgb::new(90, 70, 50), + (rand_field.get(wpos2d.into()) % 256) as f32 / 256.0, + ) + .map(|e| (e % 256) as u8); + for z in 0..12 { + if dist / WayKind::Wall.width() + < ((1.0 - z as f32 / 12.0) * 2.0).min(1.0) + { vol.set( - Vec3::new(offs.x, offs.y, col_sample.alt.floor() as i32 + z), - Block::new(BlockKind::Normal, Rgb::new(50, 50, 50)), + 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 0..16 { + vol.set( + Vec3::new(offs.x, offs.y, surface_z + z), + Block::new(BlockKind::Normal, Rgb::new(50, 50, 50)), + ); + } + } + + // Paths + if let Some((WayKind::Path, dist)) = sample.way { + let inset = -1; + for z in -3..inset { + vol.set( + Vec3::new(offs.x, offs.y, surface_z + z), + Block::new(BlockKind::Normal, Rgb::new(90, 70, 50)), + ); + } + let head_space = (6 - (dist * 0.4).powf(6.0).round() as i32).max(1); + for z in inset..inset + head_space { + vol.set( + Vec3::new(offs.x, offs.y, surface_z + z), + Block::empty(), + ); + } } } } } - pub fn get_surface(&self, wpos: Vec2) -> Option { - self.get_color(wpos - self.origin) - .map(|col| Block::new(BlockKind::Normal, col)) - } - pub fn get_color(&self, pos: Vec2) -> Option> { if let Some(structure) = self .structures @@ -426,24 +462,30 @@ impl Settlement { }); } - Some(match self.land.get_at_block(pos) { - Sample::Wilderness => return None, - Sample::Plot(Plot::Hazard) => return None, - Sample::Way(WayKind::Path, _) => Rgb::new(90, 70, 50), - Sample::Way(WayKind::Hedge, _) => Rgb::new(0, 150, 0), - Sample::Way(WayKind::Wall, _) => Rgb::new(60, 60, 60), - Sample::Tower(Tower::Wall, _) => Rgb::new(50, 50, 50), - Sample::Plot(Plot::Dirt) => Rgb::new(90, 70, 50), - Sample::Plot(Plot::Grass) => Rgb::new(100, 200, 0), - Sample::Plot(Plot::Water) => Rgb::new(100, 150, 250), - Sample::Plot(Plot::Town) => { - if pos.map(|e| e.rem_euclid(4) < 2).reduce(|x, y| x ^ y) { - Rgb::new(200, 130, 120) - } else { - Rgb::new(160, 150, 120) - } - }, - Sample::Plot(Plot::Field { seed, .. }) => { + 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(if pos.map(|e| e.rem_euclid(4) < 2).reduce(|x, y| x ^ y) { + Rgb::new(200, 130, 120) + } else { + Rgb::new(160, 150, 120) + }), + Some(Plot::Field { seed, .. }) => { let furrow_dirs = [ Vec2::new(1, 0), Vec2::new(0, 1), @@ -452,7 +494,7 @@ impl Settlement { ]; let furrow_dir = furrow_dirs[*seed as usize % furrow_dirs.len()]; let furrow = (pos * furrow_dir).sum().rem_euclid(6) < 3; - Rgb::new( + return Some(Rgb::new( if furrow { 100 } else { @@ -460,9 +502,12 @@ impl Settlement { }, 64 + seed.to_le_bytes()[1] % 128, 16 + seed.to_le_bytes()[2] % 32, - ) + )); }, - }) + _ => {}, + } + + None } } @@ -523,11 +568,11 @@ impl Tile { pub fn contains(&self, kind: WayKind) -> bool { self.ways.iter().any(|way| way == &Some(kind)) } } -pub enum Sample<'a> { - Wilderness, - Plot(&'a Plot), - Way(&'a WayKind, f32), - Tower(&'a Tower, Vec2), +#[derive(Default)] +pub struct Sample<'a> { + plot: Option<&'a Plot>, + way: Option<(&'a WayKind, f32)>, + tower: Option<(&'a Tower, Vec2)>, } pub struct Land { @@ -546,6 +591,8 @@ impl Land { } 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() @@ -557,7 +604,7 @@ impl Land { 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) { - return Sample::Tower(tower, neighbors[4].0); + sample.tower = Some((tower, neighbors[4].0)); } } @@ -570,15 +617,14 @@ impl Land { if let Some(way) = center_tile.and_then(|tile| tile.ways[i].as_ref()) { let dist = dist_to_line(line, pos.map(|e| e as f32)); if dist < way.width() { - return Sample::Way(way, dist); + sample.way = Some((way, dist)); } } } - let plot = self.plot_at(closest.map(to_tile)); + sample.plot = self.plot_at(closest.map(to_tile)); - plot.map(|plot| Sample::Plot(plot)) - .unwrap_or(Sample::Wilderness) + sample } pub fn tile_at(&self, pos: Vec2) -> Option<&Tile> { self.tiles.get(&pos) } From 8c73ec982cd595bf81418cbe966c2db9b03f9155 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 1 Apr 2020 23:40:59 +0100 Subject: [PATCH 098/195] Resolved rebase problems --- voxygen/src/anim/quadruped_small/run.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/voxygen/src/anim/quadruped_small/run.rs b/voxygen/src/anim/quadruped_small/run.rs index 3f679ca859..801c66ddb4 100644 --- a/voxygen/src/anim/quadruped_small/run.rs +++ b/voxygen/src/anim/quadruped_small/run.rs @@ -17,12 +17,6 @@ impl Animation for RunAnimation { ) -> Self::Skeleton { let mut next = (*skeleton).clone(); - let anim = assets::load::>("anims.quadruped_small.run"); - - - - - let slow = (anim_time as f32 * 14.0).sin(); let fast = (anim_time as f32 * 20.0).sin(); let fast_alt = (anim_time as f32 * 20.0 + PI / 2.0).sin(); From dde0319293b3dfc106dc882d418433c218714a8c Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Tue, 7 Apr 2020 21:23:04 +0100 Subject: [PATCH 099/195] Lighting improvements, fixed NaN issue for agents --- assets/voxygen/shaders/include/light.glsl | 2 +- common/src/sys/agent.rs | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/assets/voxygen/shaders/include/light.glsl b/assets/voxygen/shaders/include/light.glsl index 1496dd2d42..cbcee4770e 100644 --- a/assets/voxygen/shaders/include/light.glsl +++ b/assets/voxygen/shaders/include/light.glsl @@ -25,7 +25,7 @@ vec3 illuminate(vec3 color, vec3 light, vec3 diffuse, vec3 ambience) { } float attenuation_strength(vec3 rpos) { - return 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.8); } vec3 light_at(vec3 wpos, vec3 wnorm) { diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 941cff84ab..937af57f5d 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -99,10 +99,10 @@ impl<'a> System<'a> for Sys { - *bearing * 0.01 - if let Some(patrol_origin) = agent.patrol_origin { Vec2::::from(pos.0 - patrol_origin) * 0.0002 - + Vec3::one() / Vec2::::from(pos.0 - patrol_origin) } else { Vec2::zero() }; + // Stop if we're too close to a wall *bearing *= 0.1 + if terrain @@ -111,7 +111,7 @@ impl<'a> System<'a> for Sys { pos.0 + Vec3::from(*bearing) .try_normalized() - .unwrap_or(Vec3::zero()) + .unwrap_or(Vec3::unit_y()) * 1.5 + Vec3::unit_z(), ) @@ -126,9 +126,8 @@ 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; } // Sometimes try searching for new targets From 4830757bc9c5d7144c052c9be36c69000230fba7 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 8 Apr 2020 20:04:49 +0100 Subject: [PATCH 100/195] Began adding building skeleton structure --- assets/voxygen/shaders/include/light.glsl | 2 +- world/src/site/settlement/building/mod.rs | 1 + .../src/site/settlement/building/skeleton.rs | 64 +++++++++++++++++++ world/src/site/settlement/mod.rs | 2 + 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 world/src/site/settlement/building/mod.rs create mode 100644 world/src/site/settlement/building/skeleton.rs diff --git a/assets/voxygen/shaders/include/light.glsl b/assets/voxygen/shaders/include/light.glsl index cbcee4770e..1fbe9b1876 100644 --- a/assets/voxygen/shaders/include/light.glsl +++ b/assets/voxygen/shaders/include/light.glsl @@ -25,7 +25,7 @@ vec3 illuminate(vec3 color, vec3 light, vec3 diffuse, vec3 ambience) { } float attenuation_strength(vec3 rpos) { - return 1.0 / pow(rpos.x * rpos.x + rpos.y * rpos.y + rpos.z * rpos.z, 0.8); + return 0.3 / pow(rpos.x * rpos.x + rpos.y * rpos.y + rpos.z * rpos.z, 0.5); } vec3 light_at(vec3 wpos, vec3 wnorm) { diff --git a/world/src/site/settlement/building/mod.rs b/world/src/site/settlement/building/mod.rs new file mode 100644 index 0000000000..416c94767a --- /dev/null +++ b/world/src/site/settlement/building/mod.rs @@ -0,0 +1 @@ +mod skeleton; diff --git a/world/src/site/settlement/building/skeleton.rs b/world/src/site/settlement/building/skeleton.rs new file mode 100644 index 0000000000..e278136307 --- /dev/null +++ b/world/src/site/settlement/building/skeleton.rs @@ -0,0 +1,64 @@ +use vek::*; + +#[derive(Copy, Clone)] +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 { + len: i32, + locus: i32, + children: Vec<(i32, Branch)>, +} + +impl Branch { + fn for_each<'a>(&'a self, node: Vec2, ori: Ori, f: &mut impl FnMut(Vec2, Ori, &'a Branch)) { + f(node, ori, self); + for (offset, child) in &self.children { + child.for_each(node + ori.dir() * *offset, ori.flip(), f); + } + } +} + +pub struct Skeleton { + offset: i32, + ori: Ori, + root: Branch, +} + +impl Skeleton { + pub fn for_each<'a>(&'a self, mut f: impl FnMut(Vec2, Ori, &'a Branch)) { + self.root.for_each(self.ori.dir() * self.offset, self.ori, &mut f); + } + + pub fn closest(&self, pos: Vec2) -> (i32, &Branch) { + let mut min = None; + self.for_each(|node, ori, branch| { + let bounds = Aabr::new_empty(node - ori.flip().dir() * branch.locus) + .expanded_to_contain_point(node + ori.dir() * branch.len + ori.flip().dir() * branch.locus); + let projected = pos.map2(bounds.min.zip(bounds.max), |e, (min, max)| Clamp::clamp(e, min, max)); + let dist = (projected - pos).map(|e| e.abs()).reduce_max(); + if min.map(|(min_dist, _)| dist < min_dist).unwrap_or(true) { + min = Some((dist, branch)); + } + }); + min.unwrap() + } +} diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 1c43e5f965..37b0be67b9 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -1,3 +1,5 @@ +mod building; + use crate::{ column::ColumnSample, sim::{SimChunk, WorldSim}, From b0f9ef5f30b1caf310f5122d499f652233531b95 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 10 Apr 2020 12:16:30 +0100 Subject: [PATCH 101/195] Added skeleton house generation --- world/src/sim/mod.rs | 4 + .../settlement/building/archetype/house.rs | 86 +++++++++++++++++++ .../settlement/building/archetype/keep.rs | 53 ++++++++++++ .../site/settlement/building/archetype/mod.rs | 20 +++++ world/src/site/settlement/building/mod.rs | 70 +++++++++++++++ .../src/site/settlement/building/skeleton.rs | 64 +++++++++----- world/src/site/settlement/mod.rs | 78 +++++++++++------ 7 files changed, 326 insertions(+), 49 deletions(-) create mode 100644 world/src/site/settlement/building/archetype/house.rs create mode 100644 world/src/site/settlement/building/archetype/keep.rs create mode 100644 world/src/site/settlement/building/archetype/mod.rs diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index ea7b8c5039..b210f52766 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -1568,6 +1568,10 @@ impl WorldSim { } } + pub fn get_alt_approx(&self, pos: Vec2) -> Option { + self.get_interpolated(pos, |chunk| chunk.alt) + } + pub fn get_wpos(&self, wpos: Vec2) -> Option<&SimChunk> { self.get( wpos.map2(Vec2::from(TerrainChunkSize::RECT_SIZE), |e, sz: u32| { 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..0d972d9beb --- /dev/null +++ b/world/src/site/settlement/building/archetype/house.rs @@ -0,0 +1,86 @@ +use vek::*; +use rand::prelude::*; +use common::{ + terrain::{Block, BlockKind}, + vol::Vox, +}; +use super::{ + Archetype, + super::skeleton::*, +}; + +pub struct House { + roof_color: Rgb, +} + +impl Archetype for House { + type Attr = (); + + fn generate(rng: &mut R) -> Self { + Self { + roof_color: Rgb::new( + rng.gen_range(50, 200), + rng.gen_range(50, 200), + rng.gen_range(50, 200), + ), + } + } + + fn draw( + &self, + dist: i32, + offset: Vec2, + z: i32, + branch: &Branch, + ) -> Option { + let profile = Vec2::new(offset.x, z); + + let foundation = Block::new(BlockKind::Normal, Rgb::new(100, 100, 100)); + let log = Block::new(BlockKind::Normal, Rgb::new(60, 45, 30)); + let floor = Block::new(BlockKind::Normal, Rgb::new(100, 75, 50)); + let wall = Block::new(BlockKind::Normal, Rgb::new(200, 180, 150)); + let roof = Block::new(BlockKind::Normal, self.roof_color); + let empty = Block::empty(); + + let width = 3 + branch.locus; + let roof_height = 8 + width; + let ceil_height = 6; + + if profile.y <= 1 - (dist - width - 1).max(0) && dist < width + 3 { // Foundations + if dist < width { // Floor + Some(floor) + } else { + Some(foundation) + } + } else if profile.y > roof_height - profile.x { // Air above roof + None + } else if profile.y == roof_height - profile.x + && profile.y >= ceil_height + && dist <= width + 2 + { // Roof + if profile.x == 0 || dist == width + 2 { // Eaves + Some(log) + } else { + Some(roof) + } + } else if dist == width { // Wall + if offset.x == offset.y || profile.y == ceil_height || offset.x == 0 { + Some(log) + } else { + Some(wall) + } + } else if dist < width { // Internals + if profile.y == ceil_height { + if profile.x == 0 {// Rafters + Some(log) + } else { // Ceiling + Some(floor) + } + } else { + Some(empty) + } + } else { + None + } + } +} 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..77817d44c0 --- /dev/null +++ b/world/src/site/settlement/building/archetype/keep.rs @@ -0,0 +1,53 @@ +use vek::*; +use rand::prelude::*; +use common::{ + terrain::{Block, BlockKind}, + vol::Vox, +}; +use super::{ + Archetype, + super::skeleton::*, +}; + +pub struct Keep; + +impl Archetype for Keep { + type Attr = (); + + fn generate(rng: &mut R) -> Self { + Self + } + + fn draw( + &self, + dist: i32, + offset: Vec2, + z: i32, + branch: &Branch, + ) -> Option { + let profile = Vec2::new(offset.x, z); + + let foundation = Block::new(BlockKind::Normal, Rgb::new(100, 100, 100)); + let log = Block::new(BlockKind::Normal, Rgb::new(60, 45, 30)); + let wall = Block::new(BlockKind::Normal, Rgb::new(75, 100, 125)); + let roof = Block::new(BlockKind::Normal, Rgb::new(150, 120, 50)); + let empty = Block::empty(); + + let width = 3 + branch.locus; + let rampart_width = 5 + branch.locus; + let roof_height = 12 + width; + let ceil_height = 8; + + if profile.y <= 1 - (dist - width - 1).max(0) && dist < width + 3 { // Foundations + Some(foundation) + } else if profile.y == ceil_height && dist < rampart_width { + Some(roof) + } else if dist == rampart_width && profile.y >= ceil_height && profile.y < ceil_height + 4 { + Some(wall) + } else if dist == width && profile.y <= ceil_height { + Some(wall) + } else { + None + } + } +} 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..bb427798ff --- /dev/null +++ b/world/src/site/settlement/building/archetype/mod.rs @@ -0,0 +1,20 @@ +pub mod house; +pub mod keep; + +use vek::*; +use rand::prelude::*; +use common::terrain::Block; +use super::skeleton::*; + +pub trait Archetype { + type Attr: Default; + + fn generate(rng: &mut R) -> Self where Self: Sized; + fn draw( + &self, + dist: i32, + offset: Vec2, + z: i32, + branch: &Branch, + ) -> Option; +} diff --git a/world/src/site/settlement/building/mod.rs b/world/src/site/settlement/building/mod.rs index 416c94767a..1eebfb02f9 100644 --- a/world/src/site/settlement/building/mod.rs +++ b/world/src/site/settlement/building/mod.rs @@ -1 +1,71 @@ mod skeleton; +mod archetype; + +// Reexports +pub use self::archetype::Archetype; + +use vek::*; +use rand::prelude::*; +use self::skeleton::*; +use common::terrain::Block; + +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 len = rng.gen_range(-8, 12).max(0); + let archetype = A::generate(rng); + Self { + skel: Skeleton { + offset: -len / 2, + ori: Ori::East, + root: Branch { + len, + attr: A::Attr::default(), + locus: 3 + rng.gen_range(0, 6), + children: (0..rng.gen_range(1, 3)) + .map(|_| (rng.gen_range(0, len + 1), Branch { + len: rng.gen_range(5, 12) * if rng.gen() { 1 } else { -1 }, + attr: A::Attr::default(), + locus: 1 + rng.gen_range(0, 3), + children: Vec::new(), + })) + .collect(), + }, + }, + archetype, + origin, + } + } + + pub fn bounds_2d(&self) -> Aabr { + let b = self.skel.bounds(); + Aabr { + min: Vec2::from(self.origin) + b.min - 12, + max: Vec2::from(self.origin) + b.max + 12, + } + } + + pub fn bounds(&self) -> Aabb { + let aabr = self.bounds_2d(); + Aabb { + min: Vec3::from(aabr.min) + Vec3::unit_z() * (self.origin.z - 5), + max: Vec3::from(aabr.max) + Vec3::unit_z() * (self.origin.z + 32), + } + } + + pub fn sample(&self, pos: Vec3) -> Option { + let rpos = pos - self.origin; + let (dist, offset, branch) = self.skel.closest(rpos.into()); + + self.archetype.draw(dist, offset, rpos.z, branch) + } +} diff --git a/world/src/site/settlement/building/skeleton.rs b/world/src/site/settlement/building/skeleton.rs index e278136307..08a2fb14f1 100644 --- a/world/src/site/settlement/building/skeleton.rs +++ b/world/src/site/settlement/building/skeleton.rs @@ -1,6 +1,6 @@ use vek::*; -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialEq, Eq)] pub enum Ori { East, North, @@ -22,14 +22,15 @@ impl Ori { } } -pub struct Branch { - len: i32, - locus: i32, - children: Vec<(i32, Branch)>, +pub struct Branch { + pub len: i32, + pub attr: T, + pub locus: i32, + pub children: Vec<(i32, Branch)>, } -impl Branch { - fn for_each<'a>(&'a self, node: Vec2, ori: Ori, f: &mut impl FnMut(Vec2, Ori, &'a Branch)) { +impl Branch { + fn for_each<'a>(&'a self, node: Vec2, ori: Ori, f: &mut impl FnMut(Vec2, Ori, &'a Branch)) { f(node, ori, self); for (offset, child) in &self.children { child.for_each(node + ori.dir() * *offset, ori.flip(), f); @@ -37,28 +38,49 @@ impl Branch { } } -pub struct Skeleton { - offset: i32, - ori: Ori, - root: Branch, +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)) { +impl Skeleton { + pub fn for_each<'a>(&'a self, mut f: impl FnMut(Vec2, Ori, &'a Branch)) { self.root.for_each(self.ori.dir() * self.offset, self.ori, &mut f); } - pub fn closest(&self, pos: Vec2) -> (i32, &Branch) { + pub fn bounds(&self) -> Aabr { + let mut bounds = Aabr::new_empty(self.ori.dir() * self.offset); + self.for_each(|node, ori, branch| { + bounds.expand_to_contain(Aabr::new_empty(node - ori.flip().dir() * branch.locus) + .expanded_to_contain_point(node + ori.dir() * branch.len + ori.flip().dir() * branch.locus)); + }); + bounds + } + + pub fn closest(&self, pos: Vec2) -> (i32, Vec2, &Branch) { let mut min = None; self.for_each(|node, ori, branch| { - let bounds = Aabr::new_empty(node - ori.flip().dir() * branch.locus) - .expanded_to_contain_point(node + ori.dir() * branch.len + ori.flip().dir() * branch.locus); - let projected = pos.map2(bounds.min.zip(bounds.max), |e, (min, max)| Clamp::clamp(e, min, max)); - let dist = (projected - pos).map(|e| e.abs()).reduce_max(); - if min.map(|(min_dist, _)| dist < min_dist).unwrap_or(true) { - min = Some((dist, branch)); + let node2 = node + ori.dir() * branch.len; + let bounds = Aabr::new_empty(node) + .expanded_to_contain_point(node2); + let offs = 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 dist = offs.reduce_max(); + let dist_locus = dist - branch.locus; + if min.map(|(min_dist_locus, _, _, _)| dist_locus < min_dist_locus).unwrap_or(true) { + min = Some((dist_locus, dist, offs, branch)); } }); - min.unwrap() + min.map(|(_, dist, offs, branch)| (dist, offs, branch)).unwrap() } } diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 37b0be67b9..7192541533 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -5,6 +5,7 @@ use crate::{ sim::{SimChunk, WorldSim}, util::{Grid, RandomField, Sampler, StructureGen2d}, }; +use self::building::HouseBuilding; use super::SpawnRules; use common::{ astar::Astar, @@ -81,12 +82,11 @@ 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, + House(HouseBuilding), } pub struct Structure { kind: StructureKind, - bounds: Aabr, } pub struct Settlement { @@ -105,25 +105,31 @@ pub struct Farm { 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 { origin: wpos, - land: Land::new(rng), + land: Land::new(ctx.rng), farms: Store::default(), structures: Vec::new(), town: None, }; - if let Some(sim) = sim { - this.designate_from_world(sim, rng); + if let Some(sim) = ctx.sim { + this.designate_from_world(sim, ctx.rng); } //this.place_river(rng); - this.place_farms(rng); - this.place_town(rng); - this.place_paths(rng); + this.place_farms(&mut ctx); + this.place_town(ctx.rng); + this.place_paths(ctx.rng); this } @@ -295,8 +301,8 @@ impl Settlement { .write_path(&wall_path, WayKind::Wall, buildable, true); } - pub fn place_farms(&mut self, rng: &mut impl Rng) { - const FARM_COUNT: usize = 4; + 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 { @@ -309,23 +315,26 @@ impl Settlement { self.land.set(base_tile, farmhouse); // Farmhouses - for _ in 0..rng.gen_range(1, 3) { + 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(rng.gen_range(-16, 16), rng.gen_range(-16, 16)); + + Vec2::new(ctx.rng.gen_range(-16, 16), ctx.rng.gen_range(-16, 16)); self.structures.push(Structure { - kind: StructureKind::House, - bounds: Aabr { - min: house_pos - Vec2::new(rng.gen_range(4, 6), rng.gen_range(4, 6)), - max: house_pos + Vec2::new(rng.gen_range(4, 6), rng.gen_range(4, 6)), - }, + 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, rng); + self.place_field(farmland, base_tile, ctx.rng); } } } @@ -451,19 +460,32 @@ impl Settlement { } } } + + // Apply structures + for structure in &self.structures { + match &structure.kind { + StructureKind::House(b) => { + let centre = b.bounds_2d().center(); + let bounds = b.bounds(); + for x in bounds.min.x..bounds.max.x { + for y in bounds.min.y..bounds.max.y { + for z in bounds.min.z..bounds.max.z { + 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) { + vol.set(coffs, block); + } + } + } + } + }, + } + } } pub fn get_color(&self, pos: Vec2) -> Option> { - if let Some(structure) = self - .structures - .iter() - .find(|s| s.bounds.contains_point(pos)) - { - return Some(match structure.kind { - StructureKind::House => Rgb::new(200, 80, 50), - }); - } - let sample = self.land.get_at_block(pos); match sample.tower { From c3e5b3057f84f0766349dfba2ad2d25f36ef0311 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 10 Apr 2020 15:49:59 +0100 Subject: [PATCH 102/195] Significantly better house generation --- .../settlement/building/archetype/house.rs | 74 ++++++++++++------- .../settlement/building/archetype/keep.rs | 33 +++++---- .../site/settlement/building/archetype/mod.rs | 5 +- world/src/site/settlement/building/mod.rs | 18 ++--- .../src/site/settlement/building/skeleton.rs | 17 +++-- world/src/site/settlement/mod.rs | 2 +- 6 files changed, 92 insertions(+), 57 deletions(-) diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index 0d972d9beb..021999a3de 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -4,6 +4,7 @@ use common::{ terrain::{Block, BlockKind}, vol::Vox, }; +use crate::util::{RandomField, Sampler}; use super::{ Archetype, super::skeleton::*, @@ -11,6 +12,7 @@ use super::{ pub struct House { roof_color: Rgb, + noise: RandomField, } impl Archetype for House { @@ -23,61 +25,83 @@ impl Archetype for House { rng.gen_range(50, 200), rng.gen_range(50, 200), ), + noise: RandomField::new(rng.gen()), } } fn draw( &self, dist: i32, - offset: Vec2, + bound_offset: Vec2, + center_offset: Vec2, z: i32, branch: &Branch, - ) -> Option { - let profile = Vec2::new(offset.x, z); + ) -> Option> { + let profile = Vec2::new(bound_offset.x, z); - let foundation = Block::new(BlockKind::Normal, Rgb::new(100, 100, 100)); - let log = Block::new(BlockKind::Normal, Rgb::new(60, 45, 30)); - let floor = Block::new(BlockKind::Normal, Rgb::new(100, 75, 50)); - let wall = Block::new(BlockKind::Normal, Rgb::new(200, 180, 150)); - let roof = Block::new(BlockKind::Normal, self.roof_color); - let empty = Block::empty(); + let make_block = |r, g, b| { + let nz = self.noise.get(Vec3::new(center_offset.x, center_offset.y, z * 8)); + Some(Some(Block::new(BlockKind::Normal, Rgb::new(r, g, b) + (nz & 0x0F) as u8 - 8))) + }; + + let foundation = make_block(100, 100, 100); + let log = make_block(60, 45, 30); + let floor = make_block(100, 75, 50); + let wall = make_block(200, 180, 150); + let roof = make_block(self.roof_color.r, self.roof_color.g, self.roof_color.b); + let empty = Some(Some(Block::empty())); - let width = 3 + branch.locus; - let roof_height = 8 + width; let ceil_height = 6; + let width = 3 + branch.locus + if profile.y >= ceil_height { 1 } else { 0 }; + let foundation_height = 1 - (dist - width - 1).max(0); + let roof_height = 8 + width; - if profile.y <= 1 - (dist - width - 1).max(0) && dist < width + 3 { // Foundations - if dist < width { // Floor - Some(floor) + if center_offset.map(|e| e.abs()).reduce_max() == 0 && profile.y > foundation_height + 1 { // Chimney shaft + empty + } else if center_offset.map(|e| e.abs()).reduce_max() <= 1 && profile.y < roof_height + 2 { // Chimney + if center_offset.product() == 0 && profile.y > foundation_height + 1 && profile.y <= foundation_height + 3 { // Fireplace + empty } else { - Some(foundation) + foundation + } + } else if profile.y <= foundation_height && dist < width + 3 { // Foundations + if dist == width - 1 { // Floor lining + log + } else if dist < width - 1 && profile.y == foundation_height { // Floor + floor + } else if dist < width && profile.y >= foundation_height - 3 { // Basement + empty + } else { + foundation } } else if profile.y > roof_height - profile.x { // Air above roof - None + Some(None) } else if profile.y == roof_height - profile.x && profile.y >= ceil_height && dist <= width + 2 { // Roof - if profile.x == 0 || dist == width + 2 { // Eaves - Some(log) + if profile.x == 0 || dist == width + 2 || profile.x.abs() % 3 == 0 { // Eaves + log } else { - Some(roof) + roof } } else if dist == width { // Wall - if offset.x == offset.y || profile.y == ceil_height || offset.x == 0 { - Some(log) + if bound_offset.x == bound_offset.y || profile.y == ceil_height || bound_offset.x == 0 { + log + } else if profile.x >= 2 && profile.x <= width - 2 && profile.y >= foundation_height + 2 && profile.y <= foundation_height + 3 { // Windows + empty } else { - Some(wall) + wall } } else if dist < width { // Internals if profile.y == ceil_height { if profile.x == 0 {// Rafters - Some(log) + log } else { // Ceiling - Some(floor) + floor } } else { - Some(empty) + empty } } else { None diff --git a/world/src/site/settlement/building/archetype/keep.rs b/world/src/site/settlement/building/archetype/keep.rs index 77817d44c0..6669425ce5 100644 --- a/world/src/site/settlement/building/archetype/keep.rs +++ b/world/src/site/settlement/building/archetype/keep.rs @@ -21,33 +21,38 @@ impl Archetype for Keep { fn draw( &self, dist: i32, - offset: Vec2, + bound_offset: Vec2, + center_offset: Vec2, z: i32, branch: &Branch, - ) -> Option { - let profile = Vec2::new(offset.x, z); + ) -> Option> { + let profile = Vec2::new(bound_offset.x, z); - let foundation = Block::new(BlockKind::Normal, Rgb::new(100, 100, 100)); - let log = Block::new(BlockKind::Normal, Rgb::new(60, 45, 30)); - let wall = Block::new(BlockKind::Normal, Rgb::new(75, 100, 125)); - let roof = Block::new(BlockKind::Normal, Rgb::new(150, 120, 50)); - let empty = Block::empty(); + let make_block = |r, g, b| { + Some(Some(Block::new(BlockKind::Normal, Rgb::new(r, g, b)))) + }; + + let foundation = make_block(100, 100, 100); + let log = make_block(60, 45, 30); + let wall = make_block(75, 100, 125); + let roof = make_block(150, 120, 50); + let empty = Some(Some(Block::empty())); let width = 3 + branch.locus; let rampart_width = 5 + branch.locus; let roof_height = 12 + width; - let ceil_height = 8; + let ceil_height = 16; if profile.y <= 1 - (dist - width - 1).max(0) && dist < width + 3 { // Foundations - Some(foundation) + foundation } else if profile.y == ceil_height && dist < rampart_width { - Some(roof) + roof } else if dist == rampart_width && profile.y >= ceil_height && profile.y < ceil_height + 4 { - Some(wall) + wall } else if dist == width && profile.y <= ceil_height { - Some(wall) + wall } else { - None + empty } } } diff --git a/world/src/site/settlement/building/archetype/mod.rs b/world/src/site/settlement/building/archetype/mod.rs index bb427798ff..45281912aa 100644 --- a/world/src/site/settlement/building/archetype/mod.rs +++ b/world/src/site/settlement/building/archetype/mod.rs @@ -13,8 +13,9 @@ pub trait Archetype { fn draw( &self, dist: i32, - offset: Vec2, + bound_offset: Vec2, + center_offset: Vec2, z: i32, branch: &Branch, - ) -> Option; + ) -> Option>; } diff --git a/world/src/site/settlement/building/mod.rs b/world/src/site/settlement/building/mod.rs index 1eebfb02f9..7b3bc2eeca 100644 --- a/world/src/site/settlement/building/mod.rs +++ b/world/src/site/settlement/building/mod.rs @@ -25,13 +25,13 @@ impl Building { let archetype = A::generate(rng); Self { skel: Skeleton { - offset: -len / 2, + offset: -rng.gen_range(-4, len + 4).clamped(0, len), ori: Ori::East, root: Branch { len, attr: A::Attr::default(), - locus: 3 + rng.gen_range(0, 6), - children: (0..rng.gen_range(1, 3)) + locus: 2 + rng.gen_range(0, 5), + children: (0..rng.gen_range(0, 4)) .map(|_| (rng.gen_range(0, len + 1), Branch { len: rng.gen_range(5, 12) * if rng.gen() { 1 } else { -1 }, attr: A::Attr::default(), @@ -49,23 +49,23 @@ impl Building { pub fn bounds_2d(&self) -> Aabr { let b = self.skel.bounds(); Aabr { - min: Vec2::from(self.origin) + b.min - 12, - max: Vec2::from(self.origin) + b.max + 12, + min: Vec2::from(self.origin) + b.min - 14, + max: Vec2::from(self.origin) + b.max + 14, } } pub fn bounds(&self) -> Aabb { let aabr = self.bounds_2d(); Aabb { - min: Vec3::from(aabr.min) + Vec3::unit_z() * (self.origin.z - 5), + 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; - let (dist, offset, branch) = self.skel.closest(rpos.into()); - - self.archetype.draw(dist, offset, rpos.z, branch) + self.skel.closest(rpos.into(), |dist, bound_offset, center_offset, branch| { + self.archetype.draw(dist, bound_offset, center_offset, rpos.z, branch) + }).flatten() } } diff --git a/world/src/site/settlement/building/skeleton.rs b/world/src/site/settlement/building/skeleton.rs index 08a2fb14f1..0df56d9b50 100644 --- a/world/src/site/settlement/building/skeleton.rs +++ b/world/src/site/settlement/building/skeleton.rs @@ -58,13 +58,13 @@ impl Skeleton { bounds } - pub fn closest(&self, pos: Vec2) -> (i32, Vec2, &Branch) { + pub fn closest(&self, pos: Vec2, mut f: impl FnMut(i32, Vec2, Vec2, &Branch) -> Option) -> Option { let mut min = None; self.for_each(|node, ori, branch| { let node2 = node + ori.dir() * branch.len; let bounds = Aabr::new_empty(node) .expanded_to_contain_point(node2); - let offs = if ori == Ori::East { + let bound_offset = if ori == Ori::East { Vec2::new( node.y - pos.y, pos.x - pos.x.clamped(bounds.min.x, bounds.max.x) @@ -75,12 +75,17 @@ impl Skeleton { pos.y - pos.y.clamped(bounds.min.y, bounds.max.y) ) }.map(|e| e.abs()); - let dist = offs.reduce_max(); + let center_offset = if ori == Ori::East { + Vec2::new(pos.y, pos.x) + } else { + Vec2::new(pos.x, pos.y) + }; + let dist = bound_offset.reduce_max(); let dist_locus = dist - branch.locus; - if min.map(|(min_dist_locus, _, _, _)| dist_locus < min_dist_locus).unwrap_or(true) { - min = Some((dist_locus, dist, offs, branch)); + if min.as_ref().map(|(min_dist_locus, _)| dist_locus < *min_dist_locus).unwrap_or(true) { + min = f(dist, bound_offset, center_offset, branch).map(|r| (dist_locus, r)); } }); - min.map(|(_, dist, offs, branch)| (dist, offs, branch)).unwrap() + min.map(|(_, r)| r) } } diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 7192541533..48075fbb0e 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -403,7 +403,7 @@ impl Settlement { // Ground color if let Some(color) = self.get_color(rpos) { - for z in -3..3 { + for z in -3..5 { vol.set( Vec3::new(offs.x, offs.y, surface_z + z), if z >= 0 { Block::empty() } else { Block::new(BlockKind::Normal, color) }, From 15971f0deffb06e689737f498c771d164ea3891e Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 10 Apr 2020 18:18:18 +0100 Subject: [PATCH 103/195] Refactored town generation code --- .../settlement/building/archetype/house.rs | 93 +++++++++++++------ world/src/site/settlement/building/mod.rs | 4 +- 2 files changed, 68 insertions(+), 29 deletions(-) diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index 021999a3de..681138f16d 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -53,48 +53,87 @@ impl Archetype for House { let ceil_height = 6; let width = 3 + branch.locus + if profile.y >= ceil_height { 1 } else { 0 }; - let foundation_height = 1 - (dist - width - 1).max(0); + let foundation_height = 0 - (dist - width - 1).max(0); let roof_height = 8 + width; if center_offset.map(|e| e.abs()).reduce_max() == 0 && profile.y > foundation_height + 1 { // Chimney shaft - empty - } else if center_offset.map(|e| e.abs()).reduce_max() <= 1 && profile.y < roof_height + 2 { // Chimney + return empty; + } + + if center_offset.map(|e| e.abs()).reduce_max() <= 1 && profile.y < roof_height + 2 { // Chimney if center_offset.product() == 0 && profile.y > foundation_height + 1 && profile.y <= foundation_height + 3 { // Fireplace - empty + return empty; } else { - foundation + return foundation; } - } else if profile.y <= foundation_height && dist < width + 3 { // Foundations + } + + if profile.y <= foundation_height && dist < width + 3 { // Foundations if dist == width - 1 { // Floor lining - log + return log; } else if dist < width - 1 && profile.y == foundation_height { // Floor - floor + return floor; } else if dist < width && profile.y >= foundation_height - 3 { // Basement - empty + return empty; } else { - foundation + return foundation; } - } else if profile.y > roof_height - profile.x { // Air above roof - Some(None) - } else if profile.y == roof_height - profile.x + } + + if profile.y > roof_height - profile.x { // Air above roof + return Some(None); + } + + // Roof + if profile.y == roof_height - profile.x && profile.y >= ceil_height && dist <= width + 2 - { // Roof - if profile.x == 0 || dist == width + 2 || profile.x.abs() % 3 == 0 { // Eaves - log + { + if profile.x == 0 || dist == width + 2 || (roof_height - profile.y) % 3 == 0 { // Eaves + return log; } else { - roof + return roof; } - } else if dist == width { // Wall - if bound_offset.x == bound_offset.y || profile.y == ceil_height || bound_offset.x == 0 { + } + + // Walls + if dist == width { + let frame_bounds = if profile.y >= ceil_height { + Aabr { + min: Vec2::new(-1, ceil_height + 2), + max: Vec2::new(1, ceil_height + 5), + } + } else { + Aabr { + min: Vec2::new(2, foundation_height + 2), + max: Vec2::new(width - 2, ceil_height - 2), + } + }; + let window_bounds = Aabr { + min: (frame_bounds.min + 1).map2(frame_bounds.center(), |a, b| a.min(b)), + max: (frame_bounds.max - 1).map2(frame_bounds.center(), |a, b| a.max(b)), + }; + + // Window + if (frame_bounds.size() + 1).reduce_min() > 2 { + let surface_pos = Vec2::new(bound_offset.x, profile.y); + if window_bounds.contains_point(surface_pos) { + return empty; + } else if frame_bounds.contains_point(surface_pos) { + return log; + }; + } + + // Wall + return if bound_offset.x == bound_offset.y || profile.x == 0 || profile.y == ceil_height { // Support beams log - } else if profile.x >= 2 && profile.x <= width - 2 && profile.y >= foundation_height + 2 && profile.y <= foundation_height + 3 { // Windows - empty } else { wall - } - } else if dist < width { // Internals - if profile.y == ceil_height { + }; + } + + if dist < width { // Internals + return if profile.y == ceil_height { if profile.x == 0 {// Rafters log } else { // Ceiling @@ -102,9 +141,9 @@ impl Archetype for House { } } else { empty - } - } else { - None + }; } + + None } } diff --git a/world/src/site/settlement/building/mod.rs b/world/src/site/settlement/building/mod.rs index 7b3bc2eeca..5363467bfa 100644 --- a/world/src/site/settlement/building/mod.rs +++ b/world/src/site/settlement/building/mod.rs @@ -25,8 +25,8 @@ impl Building { let archetype = A::generate(rng); Self { skel: Skeleton { - offset: -rng.gen_range(-4, len + 4).clamped(0, len), - ori: Ori::East, + offset: -rng.gen_range(0, len + 7).clamped(0, len), + ori: if rng.gen() { Ori::East } else { Ori::North }, root: Branch { len, attr: A::Attr::default(), From 67f27ef9704b37d81cda3bd89b74b4e4ef20d1f2 Mon Sep 17 00:00:00 2001 From: Pfauenauge Date: Fri, 10 Apr 2020 21:08:21 +0200 Subject: [PATCH 104/195] ember sprite --- assets/voxygen/voxel/sprite/ember/1.vox | 3 +++ common/src/terrain/block.rs | 9 ++++++--- voxygen/src/scene/terrain.rs | 12 ++++++++++++ world/src/block/mod.rs | 2 +- 4 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 assets/voxygen/voxel/sprite/ember/1.vox diff --git a/assets/voxygen/voxel/sprite/ember/1.vox b/assets/voxygen/voxel/sprite/ember/1.vox new file mode 100644 index 0000000000..3e90fa7a2c --- /dev/null +++ b/assets/voxygen/voxel/sprite/ember/1.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d670e5a5e3ba552154e19ce963fe83bdbe8556ebf3f907c59a93e9d6e853f5d7 +size 2232 diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index 0c5c97155a..efb815f024 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -40,6 +40,7 @@ pub enum BlockKind { Fern, DeadBush, Blueberry, + Ember, } impl BlockKind { @@ -81,13 +82,14 @@ impl BlockKind { BlockKind::Fern => true, BlockKind::DeadBush => true, BlockKind::Blueberry => true, + BlockKind::Ember => true, _ => false, } } pub fn is_fluid(&self) -> bool { match self { - BlockKind::Water => true, + BlockKind::Water => true, _ => false, } } @@ -123,7 +125,7 @@ impl BlockKind { BlockKind::LeafyPlant => false, BlockKind::Fern => false, BlockKind::DeadBush => false, - BlockKind::Blueberry => false, + BlockKind::Blueberry => false, _ => true, } } @@ -159,6 +161,7 @@ impl BlockKind { BlockKind::Fern => false, BlockKind::DeadBush => false, BlockKind::Blueberry => false, + BlockKind::Ember => false, _ => true, } } @@ -180,7 +183,7 @@ impl BlockKind { BlockKind::Velorite => true, BlockKind::VeloriteFrag => true, BlockKind::Chest => true, - BlockKind::Pumpkin => true, + BlockKind::Pumpkin => true, _ => false, } } diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index 8ae5b602a2..b432b64e13 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -170,6 +170,10 @@ fn sprite_config_for(kind: BlockKind) -> Option { variations: 4, wind_sway: 0.1, }), + BlockKind::Ember => Some(SpriteConfig { + variations: 1, + wind_sway: 4.0, + }), _ => None, } @@ -1066,6 +1070,14 @@ impl Terrain { Vec3::new(-6.0, -6.0, -0.0), ), ), + // Ember + ( + (BlockKind::Ember, 0), + make_model( + "voxygen.voxel.sprite.ember.1", + Vec3::new(-6.0, -6.0, -0.0), + ), + ), ] .into_iter() .collect(), diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index 3a6cfd571a..75994a3738 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -604,7 +604,7 @@ 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::Ember, Rgb::new(0, 0, 0)) }), StructureBlock::Chest => Some(if structure_seed % 10 < 7 { Block::empty() From fd3ffdf28bbe1f1ad74ee333a2e8ca58555ccd01 Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Sat, 11 Apr 2020 02:13:48 +0200 Subject: [PATCH 105/195] pizza sprite --- assets/voxygen/voxel/sprite/ember/1.vox | 4 ++-- common/src/terrain/block.rs | 7 ++++--- voxygen/src/scene/terrain.rs | 7 ++----- world/src/block/mod.rs | 4 ++-- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/assets/voxygen/voxel/sprite/ember/1.vox b/assets/voxygen/voxel/sprite/ember/1.vox index 3e90fa7a2c..f7b6f438ee 100644 --- a/assets/voxygen/voxel/sprite/ember/1.vox +++ b/assets/voxygen/voxel/sprite/ember/1.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d670e5a5e3ba552154e19ce963fe83bdbe8556ebf3f907c59a93e9d6e853f5d7 -size 2232 +oid sha256:4fb63355560eb8f6d4661f8e3eb41ea9a5b3ede8628a84c3d36d5c1063e7154c +size 3124 diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index efb815f024..fb2548f15c 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -89,7 +89,7 @@ impl BlockKind { pub fn is_fluid(&self) -> bool { match self { - BlockKind::Water => true, + BlockKind::Water => true, _ => false, } } @@ -125,7 +125,8 @@ impl BlockKind { BlockKind::LeafyPlant => false, BlockKind::Fern => false, BlockKind::DeadBush => false, - BlockKind::Blueberry => false, + BlockKind::Blueberry => false, + BlockKind::Ember => false, _ => true, } } @@ -183,7 +184,7 @@ impl BlockKind { BlockKind::Velorite => true, BlockKind::VeloriteFrag => true, BlockKind::Chest => true, - BlockKind::Pumpkin => true, + BlockKind::Pumpkin => true, _ => false, } } diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index b432b64e13..a2905f8c5a 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -172,7 +172,7 @@ fn sprite_config_for(kind: BlockKind) -> Option { }), BlockKind::Ember => Some(SpriteConfig { variations: 1, - wind_sway: 4.0, + wind_sway: 0.8, }), _ => None, @@ -1073,10 +1073,7 @@ impl Terrain { // Ember ( (BlockKind::Ember, 0), - make_model( - "voxygen.voxel.sprite.ember.1", - Vec3::new(-6.0, -6.0, -0.0), - ), + make_model("voxygen.voxel.sprite.ember.1", Vec3::new(-7.0, -7.0, -2.9)), ), ] .into_iter() diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index 75994a3738..173aa7d1f7 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -604,12 +604,12 @@ pub fn block_from_structure( StructureBlock::Fruit => Some(if field.get(pos + structure_pos) % 3 > 0 { Block::empty() } else { - Block::new(BlockKind::Ember, Rgb::new(0, 0, 0)) + Block::new(BlockKind::Apple, 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, From 8499143f777684adbce58304c2f3fe008e236e38 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 11 Apr 2020 01:09:01 +0100 Subject: [PATCH 106/195] Basic town house generation --- world/src/civ/mod.rs | 20 +++- world/src/sim/mod.rs | 9 +- .../settlement/building/archetype/house.rs | 4 +- world/src/site/settlement/building/mod.rs | 10 +- .../src/site/settlement/building/skeleton.rs | 8 +- world/src/site/settlement/mod.rs | 96 +++++++++++++++---- 6 files changed, 111 insertions(+), 36 deletions(-) diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 298f3050d9..35b20b2bbf 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -14,8 +14,12 @@ use common::{ store::{Id, Store}, path::Path, astar::Astar, + spiral::Spiral2d, +}; +use crate::{ + sim::{WorldSim, SimChunk}, + site::{Site as WorldSite, Settlement}, }; -use crate::sim::{WorldSim, SimChunk}; const CARDINALS: [Vec2; 4] = [ Vec2::new(1, 0), @@ -79,10 +83,22 @@ impl Civs { // Temporary! for track in this.tracks.iter() { for loc in track.path.iter() { - sim.get_mut(*loc).unwrap().place = Some(this.civs.iter().next().unwrap().homeland); + ctx.sim.get_mut(*loc).unwrap().place = Some(this.civs.iter().next().unwrap().homeland); } } + // Place sites in world + for site in this.sites.iter() { + let wpos = site.center * Vec2::from(TerrainChunkSize::RECT_SIZE).map(|e: u32| e as i32); + let settlement = WorldSite::from(Settlement::generate(wpos, Some(ctx.sim), ctx.rng)); + for pos in Spiral2d::new().map(|offs| site.center + offs).take(32usize.pow(2)) { + ctx.sim + .get_mut(pos) + .map(|chunk| chunk.sites.push(settlement.clone())); + } + println!("Placed site at {:?}", site.center); + } + this.display_info(); this diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index b210f52766..1f1b820c85 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -1451,6 +1451,7 @@ impl WorldSim { */ // 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 @@ -1464,12 +1465,9 @@ impl WorldSim { chunk_idx_center(WORLD_SIZE.map(|e| e as i32)), ) .map_init( - || Box::new(BlockGen::new(ColumnGen::new(self))), - |mut block_gen, (pos, seed)| { + || (), + |_, (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))) ( pos, Site::from(Settlement::generate(pos, Some(self), &mut rng)), @@ -1496,6 +1494,7 @@ impl WorldSim { chunk.sites.push(site.clone()); } }); + */ // Create waypoints const WAYPOINT_EVERY: usize = 16; diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index 681138f16d..b97c8a755f 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -52,7 +52,7 @@ impl Archetype for House { let empty = Some(Some(Block::empty())); let ceil_height = 6; - let width = 3 + branch.locus + if profile.y >= ceil_height { 1 } else { 0 }; + let width = -3 + branch.locus + if profile.y >= ceil_height { 1 } else { 0 }; let foundation_height = 0 - (dist - width - 1).max(0); let roof_height = 8 + width; @@ -115,7 +115,7 @@ impl Archetype for House { }; // Window - if (frame_bounds.size() + 1).reduce_min() > 2 { + 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 empty; diff --git a/world/src/site/settlement/building/mod.rs b/world/src/site/settlement/building/mod.rs index 5363467bfa..70ded7e16b 100644 --- a/world/src/site/settlement/building/mod.rs +++ b/world/src/site/settlement/building/mod.rs @@ -30,12 +30,12 @@ impl Building { root: Branch { len, attr: A::Attr::default(), - locus: 2 + rng.gen_range(0, 5), + locus: 8 + rng.gen_range(0, 5), children: (0..rng.gen_range(0, 4)) - .map(|_| (rng.gen_range(0, len + 1), Branch { + .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: A::Attr::default(), - locus: 1 + rng.gen_range(0, 3), + locus: 8 + rng.gen_range(0, 3), children: Vec::new(), })) .collect(), @@ -49,8 +49,8 @@ impl Building { pub fn bounds_2d(&self) -> Aabr { let b = self.skel.bounds(); Aabr { - min: Vec2::from(self.origin) + b.min - 14, - max: Vec2::from(self.origin) + b.max + 14, + min: Vec2::from(self.origin) + b.min, + max: Vec2::from(self.origin) + b.max, } } diff --git a/world/src/site/settlement/building/skeleton.rs b/world/src/site/settlement/building/skeleton.rs index 0df56d9b50..b8c6219c01 100644 --- a/world/src/site/settlement/building/skeleton.rs +++ b/world/src/site/settlement/building/skeleton.rs @@ -52,8 +52,12 @@ impl Skeleton { pub fn bounds(&self) -> Aabr { let mut bounds = Aabr::new_empty(self.ori.dir() * self.offset); self.for_each(|node, ori, branch| { - bounds.expand_to_contain(Aabr::new_empty(node - ori.flip().dir() * branch.locus) - .expanded_to_contain_point(node + ori.dir() * branch.len + ori.flip().dir() * branch.locus)); + let node2 = node + ori.dir() * branch.len; + + let a = node.map2(node2, |a, b| a.min(b)) - branch.locus; + let b = node.map2(node2, |a, b| a.max(b)) + branch.locus; + bounds.expand_to_contain_point(a); + bounds.expand_to_contain_point(b); }); bounds } diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 48075fbb0e..6d47f2f23d 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -89,6 +89,14 @@ 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 { origin: Vec2, land: Land, @@ -128,7 +136,7 @@ impl Settlement { //this.place_river(rng); this.place_farms(&mut ctx); - this.place_town(ctx.rng); + this.place_town(&mut ctx); this.place_paths(ctx.rng); this @@ -229,10 +237,10 @@ impl Settlement { } } - pub fn place_town(&mut self, rng: &mut impl Rng) { + pub fn place_town(&mut self, ctx: &mut GenCtx) { const PLOT_COUNT: usize = 2; - let mut origin = Vec2::new(rng.gen_range(-2, 3), rng.gen_range(-2, 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 { @@ -244,6 +252,43 @@ impl Settlement { .plot_at_mut(base_tile) .map(|plot| *plot = Plot::Town); + for _ in 0..ctx.rng.gen_range(10, 30) { + for _ in 0..10 { + let house_pos = base_tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32 / 2) + + Vec2::::zero().map(|_| ctx.rng.gen_range(-(AREA_SIZE as i32) * 3, AREA_SIZE as i32 * 3)); + + if let Some(Plot::Town) = self.land + .plot_at(house_pos.map(|e| e.div_euclid(AREA_SIZE as i32))) + {} else { + 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; + } + } + if i == 0 { /* for dir in CARDINALS.iter() { @@ -315,21 +360,21 @@ impl Settlement { 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)); + // 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, - ))), - }); - } + // 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 }); @@ -463,13 +508,24 @@ impl Settlement { // 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), + }) { + continue; + } + match &structure.kind { StructureKind::House(b) => { let centre = b.bounds_2d().center(); let bounds = b.bounds(); - for x in bounds.min.x..bounds.max.x { - for y in bounds.min.y..bounds.max.y { - for z in bounds.min.z..bounds.max.z { + + for x in bounds.min.x..bounds.max.x + 1 { + for y in bounds.min.y..bounds.max.y + 1 { + for z in bounds.min.z..bounds.max.z + 1 { let rpos = Vec3::new(x, y, z); let wpos = Vec3::from(self.origin) + rpos; let coffs = wpos - Vec3::from(wpos2d); From 68c561269208e8f9a34bac859cd6653836e9cba2 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 11 Apr 2020 16:50:40 +0100 Subject: [PATCH 107/195] Innumerable minor improvements to towns, added bridges, better paths, more house variations, etc. --- world/src/civ/mod.rs | 29 ++- world/src/column/mod.rs | 28 ++- world/src/lib.rs | 9 +- world/src/sim/erosion.rs | 4 + world/src/sim/mod.rs | 4 +- .../settlement/building/archetype/house.rs | 36 ++- world/src/site/settlement/mod.rs | 222 ++++++++++-------- 7 files changed, 218 insertions(+), 114 deletions(-) diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 35b20b2bbf..548d66a185 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -8,6 +8,7 @@ use std::{ use hashbrown::{HashMap, HashSet}; use vek::*; use rand::prelude::*; +use rand_chacha::ChaChaRng; use common::{ terrain::TerrainChunkSize, vol::RectVolSize, @@ -19,6 +20,7 @@ use common::{ use crate::{ sim::{WorldSim, SimChunk}, site::{Site as WorldSite, Settlement}, + util::seed_expan, }; const CARDINALS: [Vec2; 4] = [ @@ -64,7 +66,7 @@ pub struct GenCtx<'a, R: Rng> { impl Civs { pub fn generate(seed: u32, sim: &mut WorldSim) -> Self { let mut this = Self::default(); - let mut rng = sim.rng.clone(); + let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed)); let mut ctx = GenCtx { sim, rng: &mut rng }; for _ in 0..INITIAL_CIV_COUNT { @@ -89,9 +91,32 @@ impl Civs { // Place sites in world for site in this.sites.iter() { + let radius = 48i32; + let wpos = site.center * Vec2::from(TerrainChunkSize::RECT_SIZE).map(|e: u32| e as i32); + let nearby_chunks = Spiral2d::new().map(|offs| site.center + offs).take(radius.pow(2) as usize); + + // Flatten ground + let flatten_radius = 12.0; + if let Some(center_alt) = ctx.sim.get_alt_approx(wpos) { + for pos in nearby_chunks.clone() { + let factor = (1.0 - (site.center - pos).map(|e| e as f32).magnitude() / flatten_radius) * 1.3; + 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; + }); + } + } + let settlement = WorldSite::from(Settlement::generate(wpos, Some(ctx.sim), ctx.rng)); - for pos in Spiral2d::new().map(|offs| site.center + offs).take(32usize.pow(2)) { + + for pos in nearby_chunks { ctx.sim .get_mut(pos) .map(|chunk| chunk.sites.push(settlement.clone())); diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index 5f9ba59f14..cca3731201 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -592,7 +592,7 @@ 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(( + let (in_water, water_dist, alt_, water_level, warp_factor) = if let Some(( max_border_river_pos, river_chunk, max_border_river, @@ -603,7 +603,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // // 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 + let (in_water, water_dist, new_alt, new_water_alt, warp_factor) = max_border_river .river_kind .and_then(|river_kind| { if let RiverKind::River { cross_section } = river_kind { @@ -623,6 +623,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { Some(( true, + Some(river_dist as f32), Lerp::lerp( new_alt - cross_section.y.max(1.0), new_alt - 1.0, @@ -672,6 +673,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let _river_height_factor = river_dist / (river_width * 0.5); return Some(( true, + Some(river_dist as f32), alt_for_river.min(lake_water_alt - 1.0 - river_gouge), lake_water_alt - river_gouge, 0.0, @@ -680,6 +682,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { Some(( river_scale_factor <= 1.0, + Some(wposf.distance(river_pos) as f32), alt_for_river, downhill_water_alt, river_scale_factor as f32, @@ -711,6 +714,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { || downhill_water_alt .max(river_chunk.water_alt) > alt_for_river, + None, alt_for_river, (downhill_water_alt.max(river_chunk.water_alt) - river_gouge), @@ -720,6 +724,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } else { return Some(( false, + Some(lake_dist as f32), alt_for_river, downhill_water_alt, river_scale_factor as f32, @@ -738,6 +743,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { 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, 0.0, @@ -756,6 +762,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { if gouge_factor == 1.0 { return Some(( true, + None, alt.min(lake_water_alt - 1.0 - river_gouge), downhill_water_alt.max(lake_water_alt) - river_gouge, @@ -764,6 +771,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } else { return Some(( true, + None, alt_for_river, if in_bounds_ { downhill_water_alt.max(lake_water_alt) @@ -777,15 +785,21 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } Some(( river_scale_factor <= 1.0, + Some(lake_dist as f32), alt_for_river, downhill_water_alt, river_scale_factor as f32, )) }, RiverKind::River { .. } => { + let (_, _, _, (_, (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 as f32), alt_for_river, downhill_water_alt, river_scale_factor as f32, @@ -795,19 +809,21 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { }) .unwrap_or(( false, + None, alt_for_river, downhill_water_alt, river_scale_factor as f32, )) }); - (in_water, new_alt, new_water_alt, warp_factor) + (in_water, water_dist, new_alt, new_water_alt, warp_factor) } else { - (false, alt_for_river, downhill_water_alt, 1.0) + (false, None, alt_for_river, downhill_water_alt, 1.0) }; // 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 riverless_alt = alt + riverless_alt_delta; let alt = alt_ + riverless_alt_delta; let basement = alt + sim.get_interpolated_monotone(wpos, |chunk| chunk.basement.sub(chunk.alt))?; @@ -1062,6 +1078,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { Some(ColumnSample { alt, + riverless_alt, basement, chaos, water_level, @@ -1096,6 +1113,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { humidity, spawn_rate, stone_col, + water_dist, chunk: sim_chunk, }) @@ -1105,6 +1123,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, @@ -1127,6 +1146,7 @@ pub struct ColumnSample<'a> { pub humidity: f32, pub spawn_rate: f32, pub stone_col: Rgb, + pub water_dist: Option, pub chunk: &'a SimChunk, } diff --git a/world/src/lib.rs b/world/src/lib.rs index 5f82b0fa90..4e78d67454 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -100,9 +100,10 @@ impl World { 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), |offs| { - sampler.get_z_cache(chunk_wpos2d + offs) + 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 mut chunk = TerrainChunk::new(base_z, stone, air, meta); @@ -115,7 +116,7 @@ impl World { let offs = Vec2::new(x, y); let wpos2d = chunk_wpos2d + offs; - let z_cache = match zcache_grid.get(offs) { + let z_cache = match zcache_grid.get(offs + grid_border) { Some(Some(z_cache)) => z_cache, _ => continue, }; @@ -146,7 +147,7 @@ impl World { chunk_wpos2d, |offs| { zcache_grid - .get(offs) + .get(grid_border + offs) .map(Option::as_ref) .flatten() .map(|zc| &zc.sample) diff --git a/world/src/sim/erosion.rs b/world/src/sim/erosion.rs index 61ee68eb43..41db868ebe 100644 --- a/world/src/sim/erosion.rs +++ b/world/src/sim/erosion.rs @@ -217,6 +217,10 @@ impl RiverData { } 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/mod.rs b/world/src/sim/mod.rs index 1f1b820c85..7c0ac61f25 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -1567,8 +1567,8 @@ impl WorldSim { } } - pub fn get_alt_approx(&self, pos: Vec2) -> Option { - self.get_interpolated(pos, |chunk| chunk.alt) + 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> { diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index b97c8a755f..d032e5a264 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -13,6 +13,9 @@ use super::{ pub struct House { roof_color: Rgb, noise: RandomField, + roof_ribbing: bool, + central_supports: bool, + chimney: Option, } impl Archetype for House { @@ -26,6 +29,9 @@ impl Archetype for House { rng.gen_range(50, 200), ), noise: RandomField::new(rng.gen()), + roof_ribbing: rng.gen(), + central_supports: rng.gen(), + chimney: if rng.gen() { Some(rng.gen_range(1, 6)) } else { None }, } } @@ -56,15 +62,20 @@ impl Archetype for House { let foundation_height = 0 - (dist - width - 1).max(0); let roof_height = 8 + width; - if center_offset.map(|e| e.abs()).reduce_max() == 0 && profile.y > foundation_height + 1 { // Chimney shaft - return empty; - } - - if center_offset.map(|e| e.abs()).reduce_max() <= 1 && profile.y < roof_height + 2 { // Chimney - if center_offset.product() == 0 && profile.y > foundation_height + 1 && profile.y <= foundation_height + 3 { // Fireplace + if let Some(chimney_height) = self.chimney { + // Chimney shaft + if center_offset.map(|e| e.abs()).reduce_max() == 0 && profile.y > foundation_height + 1 { return empty; - } else { - return foundation; + } + + // Chimney + if center_offset.map(|e| e.abs()).reduce_max() <= 1 && profile.y < roof_height + chimney_height { + // Fireplace + if center_offset.product() == 0 && profile.y > foundation_height + 1 && profile.y <= foundation_height + 3 { + return empty; + } else { + return foundation; + } } } @@ -89,7 +100,8 @@ impl Archetype for House { && profile.y >= ceil_height && dist <= width + 2 { - if profile.x == 0 || dist == width + 2 || (roof_height - profile.y) % 3 == 0 { // Eaves + let is_ribbing = (roof_height - profile.y) % 3 == 0 && self.roof_ribbing; + if profile.x == 0 || dist == width + 2 || is_ribbing { // Eaves return log; } else { return roof; @@ -125,7 +137,11 @@ impl Archetype for House { } // Wall - return if bound_offset.x == bound_offset.y || profile.x == 0 || profile.y == ceil_height { // Support beams + return if + bound_offset.x == bound_offset.y || + (profile.x == 0 && self.central_supports) || + profile.y == ceil_height + { // Support beams log } else { wall diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 6d47f2f23d..6a5546972b 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -11,8 +11,8 @@ use common::{ astar::Astar, path::Path, spiral::Spiral2d, - terrain::{Block, BlockKind}, - vol::{BaseVol, RectSizedVol, WriteVol, Vox}, + terrain::{Block, BlockKind, TerrainChunkSize}, + vol::{BaseVol, RectSizedVol, RectVolSize, WriteVol, Vox}, store::{Id, Store}, }; use hashbrown::{HashMap, HashSet}; @@ -71,9 +71,18 @@ pub fn center_of(p: [Vec2; 3]) -> Vec2 { Vec2::new(x, y) } -impl SimChunk { - fn can_host_settlement(&self) -> bool { - !self.near_cliffs && !self.river.is_river() && !self.river.is_lake() +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) } } @@ -103,6 +112,7 @@ pub struct Settlement { farms: Store, structures: Vec, town: Option, + noise: RandomField, } pub struct Town { @@ -127,6 +137,7 @@ impl Settlement { farms: Store::default(), structures: Vec::new(), town: None, + noise: RandomField::new(ctx.rng.gen()), }; if let Some(sim) = ctx.sim { @@ -138,6 +149,7 @@ impl Settlement { this.place_farms(&mut ctx); this.place_town(&mut ctx); this.place_paths(ctx.rng); + this.place_buildings(&mut ctx); this } @@ -155,10 +167,12 @@ impl Settlement { .map(|x| (0..4).map(move |y| Vec2::new(x, y))) .flatten() .any(|offs| { - sim.get_wpos(wpos + offs * AREA_SIZE as i32 / 2) - .map(|chunk| !chunk.can_host_settlement()) - .unwrap_or(true) + 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); } @@ -252,50 +266,7 @@ impl Settlement { .plot_at_mut(base_tile) .map(|plot| *plot = Plot::Town); - for _ in 0..ctx.rng.gen_range(10, 30) { - for _ in 0..10 { - let house_pos = base_tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32 / 2) - + Vec2::::zero().map(|_| ctx.rng.gen_range(-(AREA_SIZE as i32) * 3, AREA_SIZE as i32 * 3)); - - if let Some(Plot::Town) = self.land - .plot_at(house_pos.map(|e| e.div_euclid(AREA_SIZE as i32))) - {} else { - 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; - } - } - if i == 0 { - /* - for dir in CARDINALS.iter() { - self.land.set(base_tile + *dir, town); - } - */ - self.town = Some(Town { base_tile }); origin = base_tile; } @@ -307,6 +278,7 @@ impl Settlement { .iter() .filter_map(|dir| { self.land.find_tile_dir(origin, *dir, |plot| match plot { + Some(Plot::Hazard) => false, Some(Plot::Town) => false, _ => true, }) @@ -318,8 +290,8 @@ impl Settlement { .find_path(spokes[i], spokes[(i + 1) % spokes.len()], |_, to| match to .map(|to| self.land.plot(to.plot)) { - Some(Plot::Hazard) => 10000.0, - Some(Plot::Town) => 1000.0, + Some(Plot::Hazard) => 100000.0, + Some(Plot::Town) => 10000.0, _ => 1.0, }) .map(|path| wall_path.extend(path.iter().copied())); @@ -346,6 +318,54 @@ impl Settlement { .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)) { + for _ in 0..ctx.rng.gen_range(1, 5) { + for _ in 0..10 { + 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) + { + 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; @@ -416,7 +436,11 @@ impl Settlement { pub fn spawn_rules(&self, wpos: Vec2) -> SpawnRules { SpawnRules { - trees: self.land.get_at_block(wpos - self.origin).plot.is_none(), + trees: self.land + .get_at_block(wpos - self.origin) + .plot + .map(|p| if let Plot::Hazard = p { true } else { false }) + .unwrap_or(true), } } @@ -441,23 +465,58 @@ impl Settlement { } else { continue; }; - let surface_z = col_sample.alt.floor() as i32; + let surface_z = col_sample.riverless_alt.floor() as i32; // Sample settlement let sample = self.land.get_at_block(rpos); - // Ground color - if let Some(color) = self.get_color(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 = if let Some(water_dist) = col.water_dist { + ((water_dist.abs() * 0.15).min(f32::consts::PI).cos() + 1.0) * 5.0 + } else { + 0.0 + }; + let surface_z = (col.riverless_alt + bridge_offset).floor() as i32; + + for z in inset - 2..inset { + vol.set( + Vec3::new(offs.x, offs.y, surface_z + z), + if bridge_offset >= 2.0 { + Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 80, 100), 8)) + } else { + Block::new(BlockKind::Normal, noisy_color(Rgb::new(90, 70, 50), 8)) + }, + ); + } + let head_space = 6;//(6 - (dist * 0.4).powf(6.0).round() as i32).max(1); + for z in inset..inset + head_space { + vol.set( + Vec3::new(offs.x, offs.y, surface_z + z), + Block::empty(), + ); + } + // Ground colour + } else if let Some(color) = self.get_color(rpos) { for z in -3..5 { vol.set( Vec3::new(offs.x, offs.y, surface_z + z), - if z >= 0 { Block::empty() } else { Block::new(BlockKind::Normal, color) }, + if z >= 0 { Block::empty() } else { Block::new(BlockKind::Normal, noisy_color(color, 4)) }, ); } } // Walls - if let Some((WayKind::Wall, dist)) = sample.way { + if let Some((WayKind::Wall, dist, _)) = sample.way { let color = Lerp::lerp( Rgb::new(130i32, 100, 0), Rgb::new(90, 70, 50), @@ -485,24 +544,6 @@ impl Settlement { ); } } - - // Paths - if let Some((WayKind::Path, dist)) = sample.way { - let inset = -1; - for z in -3..inset { - vol.set( - Vec3::new(offs.x, offs.y, surface_z + z), - Block::new(BlockKind::Normal, Rgb::new(90, 70, 50)), - ); - } - let head_space = (6 - (dist * 0.4).powf(6.0).round() as i32).max(1); - for z in inset..inset + head_space { - vol.set( - Vec3::new(offs.x, offs.y, surface_z + z), - Block::empty(), - ); - } - } } } @@ -550,9 +591,9 @@ impl Settlement { } 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)), + 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)), _ => {}, } @@ -560,11 +601,7 @@ impl Settlement { 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(if pos.map(|e| e.rem_euclid(4) < 2).reduce(|x, y| x ^ y) { - Rgb::new(200, 130, 120) - } else { - Rgb::new(160, 150, 120) - }), + Some(Plot::Town) => return Some(Rgb::new(130, 120, 80)), Some(Plot::Field { seed, .. }) => { let furrow_dirs = [ Vec2::new(1, 0), @@ -651,7 +688,7 @@ impl Tile { #[derive(Default)] pub struct Sample<'a> { plot: Option<&'a Plot>, - way: Option<(&'a WayKind, f32)>, + way: Option<(&'a WayKind, f32, Vec2)>, tower: Option<(&'a Tower, Vec2)>, } @@ -690,14 +727,15 @@ impl Land { for (i, dir) in CARDINALS.iter().enumerate() { let map = [1, 5, 7, 3]; - let line = [ - neighbors[4].0.map(|e| e as f32), - neighbors[map[i]].0.map(|e| e as f32), - ]; + 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 dist = dist_to_line(line, pos.map(|e| e as f32)); + 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 = Some((way, dist)); + sample.way = Some((way, dist, proj_point)); } } } From d3bf856c339fde1b8dee2206cfa5c410898c0f4f Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 11 Apr 2020 20:04:19 +0100 Subject: [PATCH 108/195] Improvements to town generation --- world/src/column/mod.rs | 40 +++++--- world/src/site/mod.rs | 4 +- .../settlement/building/archetype/house.rs | 9 +- world/src/site/settlement/mod.rs | 91 ++++++++++++------- 4 files changed, 94 insertions(+), 50 deletions(-) diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index cca3731201..201d23df51 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -592,7 +592,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let near_cliffs = sim_chunk.near_cliffs; let river_gouge = 0.5; - let (in_water, water_dist, alt_, water_level, warp_factor) = if let Some(( + let (in_water, water_dist, alt_, water_level, riverless_alt, warp_factor) = if let Some(( max_border_river_pos, river_chunk, max_border_river, @@ -603,7 +603,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // // If we are <= water_alt, we are in the lake; otherwise, we are flowing into // it. - let (in_water, water_dist, new_alt, new_water_alt, warp_factor) = max_border_river + 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 { @@ -621,15 +621,18 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let river_dist = wposf.distance(river_pos); let river_height_factor = river_dist / (river_width * 0.5); + 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, + ); + Some(( true, Some(river_dist as f32), - Lerp::lerp( - new_alt - cross_section.y.max(1.0), - new_alt - 1.0, - (river_height_factor * river_height_factor) as f32, - ), + valley_alt, new_alt, + valley_alt + river_gouge, 0.0, )) } else { @@ -676,6 +679,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { Some(river_dist as f32), alt_for_river.min(lake_water_alt - 1.0 - river_gouge), lake_water_alt - river_gouge, + alt_for_river.min(lake_water_alt - 1.0), 0.0, )); } @@ -685,6 +689,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { Some(wposf.distance(river_pos) as f32), alt_for_river, downhill_water_alt, + alt_for_river, river_scale_factor as f32, )) }, @@ -714,10 +719,11 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { || downhill_water_alt .max(river_chunk.water_alt) > alt_for_river, - None, + Some(lake_dist as f32), alt_for_river, (downhill_water_alt.max(river_chunk.water_alt) - river_gouge), + alt_for_river, river_scale_factor as f32 * (1.0 - gouge_factor), )); @@ -727,6 +733,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { Some(lake_dist as f32), alt_for_river, downhill_water_alt, + alt_for_river, river_scale_factor as f32, )); } @@ -746,6 +753,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { Some(lake_dist as f32), alt_for_river.min(lake_water_alt - 1.0 - river_gouge), lake_water_alt - river_gouge, + alt_for_river.min(lake_water_alt - 1.0), 0.0, )); } @@ -766,6 +774,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { alt.min(lake_water_alt - 1.0 - river_gouge), downhill_water_alt.max(lake_water_alt) - river_gouge, + alt.min(lake_water_alt - 1.0), 0.0, )); } else { @@ -775,10 +784,10 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { alt_for_river, if in_bounds_ { downhill_water_alt.max(lake_water_alt) - - river_gouge } else { - downhill_water_alt - river_gouge - }, + downhill_water_alt + } - river_gouge, + alt_for_river, river_scale_factor as f32 * (1.0 - gouge_factor), )); } @@ -788,6 +797,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { Some(lake_dist as f32), alt_for_river, downhill_water_alt, + alt_for_river, river_scale_factor as f32, )) }, @@ -802,6 +812,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { Some(river_dist as f32), alt_for_river, downhill_water_alt, + alt_for_river, river_scale_factor as f32, )) }, @@ -812,19 +823,20 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { None, alt_for_river, downhill_water_alt, + alt_for_river, river_scale_factor as f32, )) }); - (in_water, water_dist, new_alt, new_water_alt, warp_factor) + (in_water, water_dist, new_alt, new_water_alt, riverless_alt, warp_factor) } else { - (false, None, alt_for_river, downhill_water_alt, 1.0) + (false, None, alt_for_river, downhill_water_alt, alt_for_river, 1.0) }; // 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 riverless_alt = alt + riverless_alt_delta; 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))?; diff --git a/world/src/site/mod.rs b/world/src/site/mod.rs index 892078a0c3..58ac2c7cf8 100644 --- a/world/src/site/mod.rs +++ b/world/src/site/mod.rs @@ -9,7 +9,7 @@ use crate::{ }; use common::{ terrain::Block, - vol::{BaseVol, RectSizedVol, WriteVol}, + vol::{BaseVol, RectSizedVol, ReadVol, WriteVol}, }; use std::{fmt, sync::Arc}; use vek::*; @@ -40,7 +40,7 @@ impl Site { &'a self, wpos2d: Vec2, get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, - vol: &mut (impl BaseVol + RectSizedVol + WriteVol), + vol: &mut (impl BaseVol + RectSizedVol + ReadVol + WriteVol), ) { match self { Site::Settlement(settlement) => settlement.apply_to(wpos2d, get_column, vol), diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index d032e5a264..5405598706 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -56,6 +56,7 @@ impl Archetype for House { let wall = make_block(200, 180, 150); let roof = make_block(self.roof_color.r, self.roof_color.g, self.roof_color.b); let empty = Some(Some(Block::empty())); + let fire = Some(Some(Block::new(BlockKind::Ember, Rgb::white()))); let ceil_height = 6; let width = -3 + branch.locus + if profile.y >= ceil_height { 1 } else { 0 }; @@ -64,8 +65,12 @@ impl Archetype for House { if let Some(chimney_height) = self.chimney { // Chimney shaft - if center_offset.map(|e| e.abs()).reduce_max() == 0 && profile.y > foundation_height + 1 { - return empty; + if center_offset.map(|e| e.abs()).reduce_max() == 0 && profile.y >= foundation_height + 1 { + return if profile.y == foundation_height + 1 { + fire + } else { + empty + }; } // Chimney diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 6a5546972b..e5f9ee4582 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -12,7 +12,7 @@ use common::{ path::Path, spiral::Spiral2d, terrain::{Block, BlockKind, TerrainChunkSize}, - vol::{BaseVol, RectSizedVol, RectVolSize, WriteVol, Vox}, + vol::{BaseVol, RectSizedVol, RectVolSize, ReadVol, WriteVol, Vox}, store::{Id, Store}, }; use hashbrown::{HashMap, HashSet}; @@ -76,7 +76,7 @@ impl WorldSim { self .get(pos) .map(|chunk| { - chunk.near_cliffs && !chunk.river.is_river() && !chunk.river.is_lake() + !chunk.near_cliffs && !chunk.river.is_river() && !chunk.river.is_lake() }) .unwrap_or(false) && self @@ -157,7 +157,7 @@ impl Settlement { /// 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.new_plot(Plot::Hazard); + let hazard = self.land.hazard; Spiral2d::new() .take_while(|tile| tile.map(|e| e.abs()).reduce_max() < tile_radius) .for_each(|tile| { @@ -169,8 +169,7 @@ impl Settlement { .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) + !sim.can_host_settlement(cpos) }) || rng.gen_range(0, 16) == 0 // Randomly consider some tiles inaccessible { @@ -278,7 +277,7 @@ impl Settlement { .iter() .filter_map(|dir| { self.land.find_tile_dir(origin, *dir, |plot| match plot { - Some(Plot::Hazard) => false, + Some(Plot::Water) => false, Some(Plot::Town) => false, _ => true, }) @@ -290,9 +289,10 @@ impl Settlement { .find_path(spokes[i], spokes[(i + 1) % spokes.len()], |_, to| match to .map(|to| self.land.plot(to.plot)) { - Some(Plot::Hazard) => 100000.0, - Some(Plot::Town) => 10000.0, - _ => 1.0, + Some(Plot::Hazard) => 200.0, + Some(Plot::Water) => 40.0, + Some(Plot::Town) => 1000.0, + _ => 10.0, }) .map(|path| wall_path.extend(path.iter().copied())); } @@ -448,7 +448,7 @@ impl Settlement { &'a self, wpos2d: Vec2, mut get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, - vol: &mut (impl BaseVol + RectSizedVol + WriteVol), + vol: &mut (impl BaseVol + RectSizedVol + ReadVol + WriteVol), ) { let rand_field = RandomField::new(0); @@ -482,13 +482,13 @@ impl Settlement { // 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 = if let Some(water_dist) = col.water_dist { - ((water_dist.abs() * 0.15).min(f32::consts::PI).cos() + 1.0) * 5.0 + ((water_dist.abs() * 0.2).min(f32::consts::PI).cos() + 1.0) * 5.0 } else { 0.0 }; let surface_z = (col.riverless_alt + bridge_offset).floor() as i32; - for z in inset - 2..inset { + for z in inset - 3..inset { vol.set( Vec3::new(offs.x, offs.y, surface_z + z), if bridge_offset >= 2.0 { @@ -498,7 +498,7 @@ impl Settlement { }, ); } - let head_space = 6;//(6 - (dist * 0.4).powf(6.0).round() as i32).max(1); + let head_space = (8 - (dist * 0.25).powf(6.0).round() as i32).max(1); for z in inset..inset + head_space { vol.set( Vec3::new(offs.x, offs.y, surface_z + z), @@ -507,11 +507,16 @@ impl Settlement { } // Ground colour } else if let Some(color) = self.get_color(rpos) { - for z in -3..5 { - vol.set( - Vec3::new(offs.x, offs.y, surface_z + z), - if z >= 0 { Block::empty() } else { Block::new(BlockKind::Normal, noisy_color(color, 4)) }, - ); + for z in -8..6 { + let pos = Vec3::new(offs.x, offs.y, surface_z + z); + + if z >= 0 { + if vol.get(pos).unwrap().kind() != BlockKind::Water { + vol.set(pos, Block::empty()); + } + } else { + vol.set(pos, Block::new(BlockKind::Normal, noisy_color(color, 4))); + } } } @@ -523,7 +528,15 @@ impl Settlement { (rand_field.get(wpos2d.into()) % 256) as f32 / 256.0, ) .map(|e| (e % 256) as u8); - for z in 0..12 { + + let z_offset = if let Some(water_dist) = col_sample.water_dist { + // Water gate + ((water_dist.abs() * 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) { @@ -566,7 +579,13 @@ impl Settlement { for x in bounds.min.x..bounds.max.x + 1 { for y in bounds.min.y..bounds.max.y + 1 { - for z in bounds.min.z..bounds.max.z + 1 { + let col = if let Some(col) = get_column(offs) { + col + } else { + continue; + }; + + for z in bounds.min.z.min(col.alt 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); @@ -585,17 +604,17 @@ impl Settlement { 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.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.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)), @@ -696,14 +715,18 @@ 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: Store::default(), + plots, sampler_warp: StructureGen2d::new(rng.gen(), AREA_SIZE, AREA_SIZE * 2 / 5), + hazard, } } @@ -797,7 +820,7 @@ impl Land { dest: Vec2, mut path_cost_fn: impl FnMut(Option<&Tile>, Option<&Tile>) -> f32, ) -> Option>> { - let heuristic = |pos: &Vec2| pos.distance_squared(dest) as f32; + 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) @@ -868,7 +891,11 @@ impl Land { } else { continue; }; + if self.tile_at(tiles[0]).is_none() { + self.set(tiles[0], self.hazard); + } let mut plots = &self.plots; + self.tiles .get_mut(&tiles[1]) .filter(|tile| permit_fn(plots.get(tile.plot))) From 31b2693b78983575cb20b9468d1d1f184cd938ee Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 11 Apr 2020 21:03:32 +0100 Subject: [PATCH 109/195] Fixed river width determination --- world/src/civ/mod.rs | 2 +- world/src/column/mod.rs | 6 +++--- world/src/site/settlement/mod.rs | 34 +++++++++++++++++--------------- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 548d66a185..b1f9005b29 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -100,7 +100,7 @@ impl Civs { let flatten_radius = 12.0; if let Some(center_alt) = ctx.sim.get_alt_approx(wpos) { for pos in nearby_chunks.clone() { - let factor = (1.0 - (site.center - pos).map(|e| e as f32).magnitude() / flatten_radius) * 1.3; + 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 diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index 201d23df51..d7aab1809b 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -629,7 +629,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { Some(( true, - Some(river_dist as f32), + Some((river_dist - river_width * 0.5) as f32), valley_alt, new_alt, valley_alt + river_gouge, @@ -676,7 +676,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let _river_height_factor = river_dist / (river_width * 0.5); return Some(( true, - Some(river_dist as f32), + 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.min(lake_water_alt - 1.0), @@ -686,7 +686,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { Some(( river_scale_factor <= 1.0, - Some(wposf.distance(river_pos) as f32), + Some((wposf.distance(river_pos) - river_width * 0.5) as f32), alt_for_river, downhill_water_alt, alt_for_river, diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index e5f9ee4582..223b179252 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -291,7 +291,7 @@ impl Settlement { { Some(Plot::Hazard) => 200.0, Some(Plot::Water) => 40.0, - Some(Plot::Town) => 1000.0, + Some(Plot::Town) => 10000.0, _ => 10.0, }) .map(|path| wall_path.extend(path.iter().copied())); @@ -482,7 +482,7 @@ impl Settlement { // 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 = if let Some(water_dist) = col.water_dist { - ((water_dist.abs() * 0.2).min(f32::consts::PI).cos() + 1.0) * 5.0 + ((water_dist.max(0.0) * 0.2).min(f32::consts::PI).cos() + 1.0) * 5.0 } else { 0.0 }; @@ -500,22 +500,24 @@ impl Settlement { } let head_space = (8 - (dist * 0.25).powf(6.0).round() as i32).max(1); for z in inset..inset + head_space { - vol.set( - Vec3::new(offs.x, offs.y, surface_z + z), - Block::empty(), - ); + let pos = Vec3::new(offs.x, offs.y, surface_z + z); + if vol.get(pos).unwrap().kind() != BlockKind::Water { + vol.set(pos, Block::empty()); + } } // Ground colour } else if let Some(color) = self.get_color(rpos) { - for z in -8..6 { - let pos = Vec3::new(offs.x, offs.y, surface_z + z); + if col_sample.water_dist.map(|dist| dist > 2.0).unwrap_or(true) { + for z in -8..6 { + let pos = Vec3::new(offs.x, offs.y, surface_z + z); - if z >= 0 { - if vol.get(pos).unwrap().kind() != BlockKind::Water { - vol.set(pos, Block::empty()); + if z >= 0 { + if vol.get(pos).unwrap().kind() != BlockKind::Water { + vol.set(pos, Block::empty()); + } + } else { + vol.set(pos, Block::new(BlockKind::Normal, noisy_color(color, 4))); } - } else { - vol.set(pos, Block::new(BlockKind::Normal, noisy_color(color, 4))); } } } @@ -531,7 +533,7 @@ impl Settlement { let z_offset = if let Some(water_dist) = col_sample.water_dist { // Water gate - ((water_dist.abs() * 0.45).min(f32::consts::PI).cos() + 1.0) * 4.0 + ((water_dist.max(0.0) * 0.45).min(f32::consts::PI).cos() + 1.0) * 4.0 } else { 0.0 } as i32; @@ -550,7 +552,7 @@ impl Settlement { // Towers if let Some((Tower::Wall, _pos)) = sample.tower { - for z in 0..16 { + for z in -2..16 { vol.set( Vec3::new(offs.x, offs.y, surface_z + z), Block::new(BlockKind::Normal, Rgb::new(50, 50, 50)), @@ -579,7 +581,7 @@ impl Settlement { 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(offs) { + let col = if let Some(col) = get_column(self.origin + Vec2::new(x, y) - wpos2d) { col } else { continue; From a754b341050ef25263e0583c9af88fa15c74b01c Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 11 Apr 2020 21:13:40 +0100 Subject: [PATCH 110/195] Temporarily removed fire --- world/src/site/settlement/building/archetype/house.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index 5405598706..96cf3dca1f 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -67,7 +67,7 @@ impl Archetype for House { // 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 + empty//fire } else { empty }; From c1945a14456d60d22258b78b0cdec48fa382f90b Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sun, 12 Apr 2020 22:44:15 +0100 Subject: [PATCH 111/195] Added house overhangs, better per-wing generation options --- world/src/civ/mod.rs | 14 +- .../settlement/building/archetype/house.rs | 135 +++++++++++++----- .../settlement/building/archetype/keep.rs | 23 ++- .../site/settlement/building/archetype/mod.rs | 4 +- world/src/site/settlement/building/mod.rs | 20 +-- .../src/site/settlement/building/skeleton.rs | 4 +- world/src/site/settlement/mod.rs | 4 +- 7 files changed, 134 insertions(+), 70 deletions(-) diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index b1f9005b29..edf842d98e 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -89,17 +89,15 @@ impl Civs { } } - // Place sites in world + // Flatten ground around sites for site in this.sites.iter() { let radius = 48i32; - let wpos = site.center * Vec2::from(TerrainChunkSize::RECT_SIZE).map(|e: u32| e as i32); - let nearby_chunks = Spiral2d::new().map(|offs| site.center + offs).take(radius.pow(2) as usize); // Flatten ground let flatten_radius = 12.0; if let Some(center_alt) = ctx.sim.get_alt_approx(wpos) { - for pos in nearby_chunks.clone() { + for pos in Spiral2d::new().map(|offs| site.center + offs).take(radius.pow(2) as usize) { let factor = (1.0 - (site.center - pos).map(|e| e as f32).magnitude() / flatten_radius) * 1.15; ctx.sim .get_mut(pos) @@ -113,10 +111,16 @@ impl Civs { }); } } + } + + // Place sites in world + for site in this.sites.iter() { + let radius = 48i32; + let wpos = site.center * Vec2::from(TerrainChunkSize::RECT_SIZE).map(|e: u32| e as i32); let settlement = WorldSite::from(Settlement::generate(wpos, Some(ctx.sim), ctx.rng)); - for pos in nearby_chunks { + for pos in Spiral2d::new().map(|offs| site.center + offs).take(radius.pow(2) as usize) { ctx.sim .get_mut(pos) .map(|chunk| chunk.sites.push(settlement.clone())); diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index 96cf3dca1f..c3d15ccf31 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -13,26 +13,70 @@ use super::{ pub struct House { roof_color: Rgb, noise: RandomField, - roof_ribbing: bool, - central_supports: bool, chimney: Option, + roof_ribbing: bool, +} + +pub struct Attr { + central_supports: bool, + lower_walls: bool, +} + +impl Attr { + fn generate(rng: &mut R) -> Self { + Self { + central_supports: rng.gen(), + lower_walls: rng.gen(), + } + } } impl Archetype for House { - type Attr = (); + type Attr = Attr; - fn generate(rng: &mut R) -> Self { - Self { + fn generate(rng: &mut R) -> (Self, Skeleton) { + let this = Self { roof_color: Rgb::new( rng.gen_range(50, 200), rng.gen_range(50, 200), rng.gen_range(50, 200), ), noise: RandomField::new(rng.gen()), - roof_ribbing: rng.gen(), - central_supports: rng.gen(), chimney: if rng.gen() { Some(rng.gen_range(1, 6)) } else { None }, - } + roof_ribbing: rng.gen(), + }; + + let len = rng.gen_range(-8, 20).clamped(0, 16); + let branches_per_side = 1 + len as usize / 16; + 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 { + central_supports: rng.gen(), + lower_walls: true, + }, + locus: 8 + rng.gen_range(0, 5), + children: [1, -1] + .iter() + .map(|flip| (0..branches_per_side).map(move |i| (i, *flip))) + .flatten() + .filter_map(move |(i, flip)| if rng.gen() { + Some((i as i32 * len / (branches_per_side - 1).max(1) as i32, Branch { + len: rng.gen_range(0, 12) * flip, + attr: Attr::generate(rng), + locus: 8 + rng.gen_range(0, 3), + children: Vec::new(), + })) + } else { + None + }) + .collect(), + }, + }; + + (this, skel) } fn draw( @@ -59,7 +103,9 @@ impl Archetype for House { let fire = Some(Some(Block::new(BlockKind::Ember, Rgb::white()))); let ceil_height = 6; - let width = -3 + branch.locus + if profile.y >= ceil_height { 1 } else { 0 }; + let lower_width = -3 + branch.locus; + let upper_width = -2 + 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_height = 8 + width; @@ -67,7 +113,7 @@ impl Archetype for House { // Chimney shaft if center_offset.map(|e| e.abs()).reduce_max() == 0 && profile.y >= foundation_height + 1 { return if profile.y == foundation_height + 1 { - empty//fire + fire } else { empty }; @@ -85,36 +131,55 @@ impl Archetype for House { } if profile.y <= foundation_height && dist < width + 3 { // Foundations - if dist == width - 1 { // Floor lining - return log; - } else if dist < width - 1 && profile.y == foundation_height { // Floor - return floor; - } else if dist < width && profile.y >= foundation_height - 3 { // Basement + if branch.attr.lower_walls { + if dist == width - 1 { // Floor lining + return log; + } else if dist < width - 1 && profile.y == foundation_height { // Floor + return floor; + } + } + + if dist < width && profile.y < foundation_height && profile.y >= foundation_height - 3 { // Basement return empty; } else { return foundation; } } - if profile.y > roof_height - profile.x { // Air above roof - return Some(None); - } - - // Roof - if profile.y == roof_height - profile.x - && profile.y >= ceil_height - && dist <= width + 2 - { - let is_ribbing = (roof_height - profile.y) % 3 == 0 && self.roof_ribbing; - if profile.x == 0 || dist == width + 2 || is_ribbing { // Eaves - return log; - } else { - return roof; + let do_roof = |profile: Vec2, dist, roof_height, roof_width| { + if profile.y > roof_height - profile.x { // Air above roof + return Some(Some(None)); } + + // Roof + if profile.y == roof_height - profile.x + && dist <= roof_width + { + let is_ribbing = (roof_height - profile.y) % 3 == 0 && self.roof_ribbing; + if profile.x == 0 || dist == roof_width|| is_ribbing { // Eaves + return Some(log); + } else { + return Some(roof); + } + } + + None + }; + + if let Some(block) = do_roof(profile, dist, roof_height, width + 2) { + return block; } // Walls - if dist == width { + if dist == width && ( + bound_offset.x == bound_offset.y || + (profile.x == 0 && branch.attr.central_supports) || + profile.y == ceil_height + ) { // Support beams + return log; + } else if !branch.attr.lower_walls && profile.y < ceil_height { + return None; + } else if dist == width { let frame_bounds = if profile.y >= ceil_height { Aabr { min: Vec2::new(-1, ceil_height + 2), @@ -142,15 +207,7 @@ impl Archetype for House { } // Wall - return if - bound_offset.x == bound_offset.y || - (profile.x == 0 && self.central_supports) || - profile.y == ceil_height - { // Support beams - log - } else { - wall - }; + return wall; } if dist < width { // Internals diff --git a/world/src/site/settlement/building/archetype/keep.rs b/world/src/site/settlement/building/archetype/keep.rs index 6669425ce5..887e2ba8c0 100644 --- a/world/src/site/settlement/building/archetype/keep.rs +++ b/world/src/site/settlement/building/archetype/keep.rs @@ -14,8 +14,27 @@ pub struct Keep; impl Archetype for Keep { type Attr = (); - fn generate(rng: &mut R) -> Self { - Self + 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: 8 + rng.gen_range(0, 5), + 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: 8 + rng.gen_range(0, 3), + children: Vec::new(), + })) + .collect(), + }, + }; + + (Self, skel) } fn draw( diff --git a/world/src/site/settlement/building/archetype/mod.rs b/world/src/site/settlement/building/archetype/mod.rs index 45281912aa..0dbf6b3254 100644 --- a/world/src/site/settlement/building/archetype/mod.rs +++ b/world/src/site/settlement/building/archetype/mod.rs @@ -7,9 +7,9 @@ use common::terrain::Block; use super::skeleton::*; pub trait Archetype { - type Attr: Default; + type Attr; - fn generate(rng: &mut R) -> Self where Self: Sized; + fn generate(rng: &mut R) -> (Self, Skeleton) where Self: Sized; fn draw( &self, dist: i32, diff --git a/world/src/site/settlement/building/mod.rs b/world/src/site/settlement/building/mod.rs index 70ded7e16b..1aaff09e72 100644 --- a/world/src/site/settlement/building/mod.rs +++ b/world/src/site/settlement/building/mod.rs @@ -22,25 +22,9 @@ impl Building { where A: Sized { let len = rng.gen_range(-8, 12).max(0); - let archetype = A::generate(rng); + let (archetype, skel) = A::generate(rng); Self { - 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: A::Attr::default(), - locus: 8 + rng.gen_range(0, 5), - 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: A::Attr::default(), - locus: 8 + rng.gen_range(0, 3), - children: Vec::new(), - })) - .collect(), - }, - }, + skel, archetype, origin, } diff --git a/world/src/site/settlement/building/skeleton.rs b/world/src/site/settlement/building/skeleton.rs index b8c6219c01..882d8f5ce6 100644 --- a/world/src/site/settlement/building/skeleton.rs +++ b/world/src/site/settlement/building/skeleton.rs @@ -62,7 +62,7 @@ impl Skeleton { bounds } - pub fn closest(&self, pos: Vec2, mut f: impl FnMut(i32, Vec2, Vec2, &Branch) -> Option) -> Option { + pub fn closest(&self, pos: Vec2, mut f: impl FnMut(i32, Vec2, Vec2, &Branch) -> Option) -> Option { let mut min = None; self.for_each(|node, ori, branch| { let node2 = node + ori.dir() * branch.len; @@ -87,7 +87,7 @@ impl Skeleton { let dist = bound_offset.reduce_max(); let dist_locus = dist - branch.locus; if min.as_ref().map(|(min_dist_locus, _)| dist_locus < *min_dist_locus).unwrap_or(true) { - min = f(dist, bound_offset, center_offset, branch).map(|r| (dist_locus, r)); + min = f(dist, bound_offset, center_offset, branch).map(|r| (dist_locus, r)).or(min.clone()); } }); min.map(|(_, r)| r) diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 223b179252..c2174e6fc9 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -376,8 +376,8 @@ impl Settlement { .find_tile_near(Vec2::zero(), |plot| plot.is_none()) { // Farm - let farmhouse = self.land.new_plot(Plot::Dirt); - self.land.set(base_tile, farmhouse); + //let farmhouse = self.land.new_plot(Plot::Dirt); + //self.land.set(base_tile, farmhouse); // Farmhouses // for _ in 0..ctx.rng.gen_range(1, 3) { From a5ccfe3bc9453c6776cf6722b2e96813cbb46661 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 13 Apr 2020 12:38:47 +0100 Subject: [PATCH 112/195] Significantly improved house variation --- .../settlement/building/archetype/house.rs | 220 +++++++++++------- .../settlement/building/archetype/keep.rs | 15 +- .../site/settlement/building/archetype/mod.rs | 50 +++- world/src/site/settlement/building/mod.rs | 4 +- .../src/site/settlement/building/skeleton.rs | 37 +-- 5 files changed, 217 insertions(+), 109 deletions(-) diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index c3d15ccf31..859f725845 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -7,6 +7,7 @@ use common::{ use crate::util::{RandomField, Sampler}; use super::{ Archetype, + BlockMask, super::skeleton::*, }; @@ -17,16 +18,45 @@ pub struct House { roof_ribbing: bool, } +enum RoofStyle { + Hip, + Gable, + Rounded, +} + +enum StoreyFill { + None, + Upper, + All, +} + +impl StoreyFill { + fn has_lower(&self) -> bool { !if let StoreyFill::None = self { true } else { false } } + fn has_upper(&self) -> bool { if let StoreyFill::None = self { false } else { true } } +} + pub struct Attr { central_supports: bool, - lower_walls: bool, + storey_fill: StoreyFill, + roof_style: RoofStyle, + mansard: i32, } impl Attr { fn generate(rng: &mut R) -> Self { Self { central_supports: rng.gen(), - lower_walls: 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(-8, 6).max(0), } } } @@ -35,38 +65,31 @@ impl Archetype for House { type Attr = Attr; fn generate(rng: &mut R) -> (Self, Skeleton) { - let this = Self { - roof_color: Rgb::new( - rng.gen_range(50, 200), - rng.gen_range(50, 200), - rng.gen_range(50, 200), - ), - noise: RandomField::new(rng.gen()), - chimney: if rng.gen() { Some(rng.gen_range(1, 6)) } else { None }, - roof_ribbing: rng.gen(), - }; - - let len = rng.gen_range(-8, 20).clamped(0, 16); - let branches_per_side = 1 + len as usize / 16; + 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 { - central_supports: rng.gen(), - lower_walls: true, + storey_fill: StoreyFill::All, + mansard: 0, + ..Attr::generate(rng) }, - locus: 8 + rng.gen_range(0, 5), + locus, + border: 4, children: [1, -1] .iter() .map(|flip| (0..branches_per_side).map(move |i| (i, *flip))) .flatten() - .filter_map(move |(i, flip)| if rng.gen() { + .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(0, 12) * flip, + len: rng.gen_range(5, 16) * flip, attr: Attr::generate(rng), - locus: 8 + rng.gen_range(0, 3), + locus: (6 + rng.gen_range(0, 3)).min(locus), + border: 4, children: Vec::new(), })) } else { @@ -76,6 +99,17 @@ impl Archetype for House { }, }; + let this = Self { + roof_color: Rgb::new( + rng.gen_range(50, 200), + rng.gen_range(50, 200), + rng.gen_range(50, 200), + ), + noise: RandomField::new(rng.gen()), + chimney: if rng.gen() { Some(8 + skel.root.locus + rng.gen_range(1, 5)) } else { None }, + roof_ribbing: rng.gen(), + }; + (this, skel) } @@ -86,44 +120,45 @@ impl Archetype for House { center_offset: Vec2, z: i32, branch: &Branch, - ) -> Option> { + ) -> BlockMask { let profile = Vec2::new(bound_offset.x, z); let make_block = |r, g, b| { let nz = self.noise.get(Vec3::new(center_offset.x, center_offset.y, z * 8)); - Some(Some(Block::new(BlockKind::Normal, Rgb::new(r, g, b) + (nz & 0x0F) as u8 - 8))) + BlockMask::new(Block::new(BlockKind::Normal, Rgb::new(r, g, b) + (nz & 0x0F) as u8 - 8), 2) }; let foundation = make_block(100, 100, 100); let log = make_block(60, 45, 30); - let floor = make_block(100, 75, 50); + let floor = make_block(100, 75, 50).with_priority(3); let wall = make_block(200, 180, 150); let roof = make_block(self.roof_color.r, self.roof_color.g, self.roof_color.b); - let empty = Some(Some(Block::empty())); - let fire = Some(Some(Block::new(BlockKind::Ember, Rgb::white()))); + let empty = BlockMask::nothing(); + let internal = BlockMask::new(Block::empty(), 4); + let fire = BlockMask::new(Block::new(BlockKind::Ember, Rgb::white()), 2); let ceil_height = 6; - let lower_width = -3 + branch.locus; - let upper_width = -2 + branch.locus; + 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_height = 8 + width; + let roof_top = 8 + width; - if let Some(chimney_height) = self.chimney { + if let Some(chimney_top) = self.chimney { // 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 { - empty + internal }; } // Chimney - if center_offset.map(|e| e.abs()).reduce_max() <= 1 && profile.y < roof_height + chimney_height { + 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 empty; + return internal; } else { return foundation; } @@ -131,7 +166,7 @@ impl Archetype for House { } if profile.y <= foundation_height && dist < width + 3 { // Foundations - if branch.attr.lower_walls { + if branch.attr.storey_fill.has_lower() { if dist == width - 1 { // Floor lining return log; } else if dist < width - 1 && profile.y == foundation_height { // Floor @@ -140,88 +175,103 @@ impl Archetype for House { } if dist < width && profile.y < foundation_height && profile.y >= foundation_height - 3 { // Basement - return empty; + return internal; } else { - return foundation; + return foundation.with_priority(1); } } - let do_roof = |profile: Vec2, dist, roof_height, roof_width| { - if profile.y > roof_height - profile.x { // Air above roof - return Some(Some(None)); + let do_roof = |profile: Vec2, dist, roof_top, roof_width, mansard| { + if profile.y > roof_top - profile.x.max(mansard) && profile.y >= roof_top - roof_width { // Air above roof + return Some(empty); } // Roof - if profile.y == roof_height - profile.x + if profile.y == roof_top - profile.x.max(mansard) && dist <= roof_width { - let is_ribbing = (roof_height - profile.y) % 3 == 0 && self.roof_ribbing; - if profile.x == 0 || dist == roof_width|| is_ribbing { // Eaves - return Some(log); + let is_ribbing = (profile.y - ceil_height) % 3 == 0 && self.roof_ribbing; + if (profile.x == 0 && mansard == 0) || dist == roof_width|| is_ribbing { // Eaves + return Some(log.with_priority(1)); } else { - return Some(roof); + return Some(roof.with_priority(1)); } } None }; - if let Some(block) = do_roof(profile, dist, roof_height, width + 2) { + if let Some(block) = match &branch.attr.roof_style { + RoofStyle::Hip => do_roof(Vec2::new(dist, profile.y), dist, roof_top, width + 2, branch.attr.mansard), + RoofStyle::Gable => do_roof(profile, dist, roof_top, width + 2, branch.attr.mansard), + RoofStyle::Rounded => { + let circular_dist = (bound_offset.map(|e| e.pow(4) as f32).sum().powf(0.25) + 0.5).ceil() as i32; + do_roof(Vec2::new(circular_dist, profile.y), circular_dist, roof_top, width + 2, branch.attr.mansard) + }, + } { return block; } // Walls - if dist == width && ( - bound_offset.x == bound_offset.y || - (profile.x == 0 && branch.attr.central_supports) || - profile.y == ceil_height - ) { // Support beams - return log; - } else if !branch.attr.lower_walls && profile.y < ceil_height { - return None; - } else if dist == width { - let frame_bounds = if profile.y >= ceil_height { - Aabr { - min: Vec2::new(-1, ceil_height + 2), - max: Vec2::new(1, ceil_height + 5), - } + if dist == width { + if bound_offset.x == bound_offset.y || profile.y == ceil_height { // Support beams + return log; + } else if !branch.attr.storey_fill.has_lower() && profile.y < ceil_height { + return empty; + } else if !branch.attr.storey_fill.has_upper() { + return empty; } else { - Aabr { - min: Vec2::new(2, foundation_height + 2), - max: Vec2::new(width - 2, ceil_height - 2), - } - }; - let window_bounds = Aabr { - min: (frame_bounds.min + 1).map2(frame_bounds.center(), |a, b| a.min(b)), - max: (frame_bounds.max - 1).map2(frame_bounds.center(), |a, b| a.max(b)), - }; + let frame_bounds = if profile.y >= ceil_height { + Aabr { + min: Vec2::new(-1, ceil_height + 2), + max: Vec2::new(1, ceil_height + 5), + } + } else { + Aabr { + min: Vec2::new(2, foundation_height + 2), + max: Vec2::new(width - 2, ceil_height - 2), + } + }; + let window_bounds = Aabr { + min: (frame_bounds.min + 1).map2(frame_bounds.center(), |a, b| a.min(b)), + max: (frame_bounds.max - 1).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 empty; - } else if frame_bounds.contains_point(surface_pos) { - return log; + // 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 internal; + } else if frame_bounds.contains_point(surface_pos) { + return log.with_priority(3); + }; + } + + // Wall + return if branch.attr.central_supports && profile.x == 0 { // Support beams + log.with_priority(4) + } else { + wall }; } - - // Wall - return wall; } if dist < width { // Internals - return if profile.y == ceil_height { + if profile.y == ceil_height { if profile.x == 0 {// Rafters - log - } else { // Ceiling - floor + return log; + } else if branch.attr.storey_fill.has_upper() { // Ceiling + return floor; } + } else if (!branch.attr.storey_fill.has_lower() && profile.y < ceil_height) + || (!branch.attr.storey_fill.has_upper() && profile.y >= ceil_height) + { + return empty; } else { - empty - }; + return internal; + } } - None + empty } } diff --git a/world/src/site/settlement/building/archetype/keep.rs b/world/src/site/settlement/building/archetype/keep.rs index 887e2ba8c0..52c48f09c2 100644 --- a/world/src/site/settlement/building/archetype/keep.rs +++ b/world/src/site/settlement/building/archetype/keep.rs @@ -6,6 +6,7 @@ use common::{ }; use super::{ Archetype, + BlockMask, super::skeleton::*, }; @@ -22,12 +23,14 @@ impl Archetype for Keep { root: Branch { len, attr: Self::Attr::default(), - locus: 8 + rng.gen_range(0, 5), + 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: 8 + rng.gen_range(0, 3), + locus: 5 + rng.gen_range(0, 3), + border: 3, children: Vec::new(), })) .collect(), @@ -44,20 +47,20 @@ impl Archetype for Keep { center_offset: Vec2, z: i32, branch: &Branch, - ) -> Option> { + ) -> BlockMask { let profile = Vec2::new(bound_offset.x, z); let make_block = |r, g, b| { - Some(Some(Block::new(BlockKind::Normal, Rgb::new(r, g, b)))) + BlockMask::new(Block::new(BlockKind::Normal, Rgb::new(r, g, b)), 2) }; let foundation = make_block(100, 100, 100); let log = make_block(60, 45, 30); let wall = make_block(75, 100, 125); let roof = make_block(150, 120, 50); - let empty = Some(Some(Block::empty())); + let empty = BlockMask::new(Block::empty(), 2); - let width = 3 + branch.locus; + let width = branch.locus; let rampart_width = 5 + branch.locus; let roof_height = 12 + width; let ceil_height = 16; diff --git a/world/src/site/settlement/building/archetype/mod.rs b/world/src/site/settlement/building/archetype/mod.rs index 0dbf6b3254..667f965865 100644 --- a/world/src/site/settlement/building/archetype/mod.rs +++ b/world/src/site/settlement/building/archetype/mod.rs @@ -3,9 +3,55 @@ pub mod keep; use vek::*; use rand::prelude::*; -use common::terrain::Block; +use common::{terrain::Block, vol::Vox}; use super::skeleton::*; +#[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, dist_self: i32, other: Self, dist_other: i32) -> Self { + if self.priority == other.priority { + if dist_self <= dist_other { + self + } else { + other + } + } else if self.priority >= other.priority { + self + } else { + other + } + } + + pub fn finish(self) -> Option { + if self.priority > 0 { + Some(self.block) + } else { + None + } + } +} + pub trait Archetype { type Attr; @@ -17,5 +63,5 @@ pub trait Archetype { center_offset: Vec2, z: i32, branch: &Branch, - ) -> Option>; + ) -> BlockMask; } diff --git a/world/src/site/settlement/building/mod.rs b/world/src/site/settlement/building/mod.rs index 1aaff09e72..3ddc7c3f04 100644 --- a/world/src/site/settlement/building/mod.rs +++ b/world/src/site/settlement/building/mod.rs @@ -48,8 +48,8 @@ impl Building { pub fn sample(&self, pos: Vec3) -> Option { let rpos = pos - self.origin; - self.skel.closest(rpos.into(), |dist, bound_offset, center_offset, branch| { + self.skel.sample_closest(rpos.into(), |dist, bound_offset, center_offset, branch| { self.archetype.draw(dist, bound_offset, center_offset, rpos.z, branch) - }).flatten() + }).finish() } } diff --git a/world/src/site/settlement/building/skeleton.rs b/world/src/site/settlement/building/skeleton.rs index 882d8f5ce6..cde77ebb49 100644 --- a/world/src/site/settlement/building/skeleton.rs +++ b/world/src/site/settlement/building/skeleton.rs @@ -1,4 +1,5 @@ use vek::*; +use super::archetype::BlockMask; #[derive(Copy, Clone, PartialEq, Eq)] pub enum Ori { @@ -26,14 +27,15 @@ 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, f: &mut impl FnMut(Vec2, Ori, &'a Branch)) { - f(node, ori, self); + 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(), f); + child.for_each(node + ori.dir() * *offset, ori.flip(), true, self.locus, f); } } } @@ -45,27 +47,28 @@ pub struct Skeleton { } impl Skeleton { - pub fn for_each<'a>(&'a self, mut f: impl FnMut(Vec2, Ori, &'a Branch)) { - self.root.for_each(self.ori.dir() * self.offset, self.ori, &mut f); + 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| { + 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; - let b = node.map2(node2, |a, b| a.max(b)) + branch.locus; + 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 closest(&self, pos: Vec2, mut f: impl FnMut(i32, Vec2, Vec2, &Branch) -> Option) -> Option { - let mut min = None; - self.for_each(|node, ori, branch| { + pub fn sample_closest(&self, pos: Vec2, mut f: impl FnMut(i32, Vec2, Vec2, &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 { @@ -86,10 +89,16 @@ impl Skeleton { }; let dist = bound_offset.reduce_max(); let dist_locus = dist - branch.locus; - if min.as_ref().map(|(min_dist_locus, _)| dist_locus < *min_dist_locus).unwrap_or(true) { - min = f(dist, bound_offset, center_offset, branch).map(|r| (dist_locus, r)).or(min.clone()); + 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, branch); + min = min + .map(|(min_dist_locus, bm)| (dist_locus, bm.resolve_with(min_dist_locus, new_bm, dist_locus))) + .or(Some((dist_locus, new_bm))); } }); - min.map(|(_, r)| r) + min.map(|(_, bm)| bm).unwrap_or(BlockMask::nothing()) } } From 1b12ab0d299cad68c695da7f2eb695a5b8810ab2 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 13 Apr 2020 14:41:47 +0100 Subject: [PATCH 113/195] Experimental house towers, wall/roof refactor --- world/src/column/mod.rs | 2 +- .../settlement/building/archetype/house.rs | 204 ++++++++++-------- .../site/settlement/building/archetype/mod.rs | 10 +- .../src/site/settlement/building/skeleton.rs | 6 +- 4 files changed, 125 insertions(+), 97 deletions(-) diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index d7aab1809b..59b9f260eb 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -770,7 +770,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { if gouge_factor == 1.0 { return Some(( true, - None, + Some(lake_dist as f32), alt.min(lake_water_alt - 1.0 - river_gouge), downhill_water_alt.max(lake_water_alt) - river_gouge, diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index 859f725845..696abb1065 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -14,8 +14,14 @@ use super::{ pub struct House { roof_color: Rgb, noise: RandomField, - chimney: Option, roof_ribbing: bool, + roof_ribbing_diagonal: bool, +} + +enum Pillar { + None, + Chimney(i32), + Tower(i32), } enum RoofStyle { @@ -31,7 +37,7 @@ enum StoreyFill { } impl StoreyFill { - fn has_lower(&self) -> bool { !if let StoreyFill::None = self { true } else { false } } + 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 } } } @@ -40,10 +46,11 @@ pub struct Attr { storey_fill: StoreyFill, roof_style: RoofStyle, mansard: i32, + pillar: Pillar, } impl Attr { - fn generate(rng: &mut R) -> Self { + fn generate(rng: &mut R, locus: i32) -> Self { Self { central_supports: rng.gen(), storey_fill: match rng.gen_range(0, 2) { @@ -56,7 +63,11 @@ impl Attr { 1 => RoofStyle::Gable, _ => RoofStyle::Rounded, }, - mansard: rng.gen_range(-8, 6).max(0), + 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, + }, } } } @@ -76,7 +87,12 @@ impl Archetype for House { attr: Attr { storey_fill: StoreyFill::All, mansard: 0, - ..Attr::generate(rng) + 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, @@ -86,8 +102,8 @@ impl Archetype for House { .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(5, 16) * flip, - attr: Attr::generate(rng), + 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(), @@ -106,8 +122,8 @@ impl Archetype for House { rng.gen_range(50, 200), ), noise: RandomField::new(rng.gen()), - chimney: if rng.gen() { Some(8 + skel.root.locus + rng.gen_range(1, 5)) } else { None }, roof_ribbing: rng.gen(), + roof_ribbing_diagonal: rng.gen(), }; (this, skel) @@ -144,7 +160,7 @@ impl Archetype for House { let foundation_height = 0 - (dist - width - 1).max(0); let roof_top = 8 + width; - if let Some(chimney_top) = self.chimney { + 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 { @@ -181,97 +197,115 @@ impl Archetype for House { } } - let do_roof = |profile: Vec2, dist, roof_top, roof_width, mansard| { - if profile.y > roof_top - profile.x.max(mansard) && profile.y >= roof_top - roof_width { // Air above roof - return Some(empty); + // 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_top - profile.x.max(mansard) - && dist <= roof_width - { - let is_ribbing = (profile.y - ceil_height) % 3 == 0 && self.roof_ribbing; - if (profile.x == 0 && mansard == 0) || dist == roof_width|| is_ribbing { // Eaves - return Some(log.with_priority(1)); + 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.with_priority(1)); + return Some(roof); + } + } + + // Wall + + if dist == width && profile.y < roof_level { + 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 = if profile.y >= ceil_height { + Aabr { + min: Vec2::new(-1, ceil_height + 2), + max: Vec2::new(1, ceil_height + 5), + } + } else { + Aabr { + min: Vec2::new(2, foundation_height + 2), + max: Vec2::new(width - 2, ceil_height - 2), + } + }; + let window_bounds = Aabr { + min: (frame_bounds.min + 1).map2(frame_bounds.center(), |a, b| a.min(b)), + max: (frame_bounds.max - 1).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(internal); + } else if frame_bounds.contains_point(surface_pos) { + return Some(log.with_priority(3)); + }; + } + + // Wall + return Some(if branch.attr.central_supports && profile.x == 0 { // Support beams + log.with_priority(4) + } 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 }; - if let Some(block) = match &branch.attr.roof_style { - RoofStyle::Hip => do_roof(Vec2::new(dist, profile.y), dist, roof_top, width + 2, branch.attr.mansard), - RoofStyle::Gable => do_roof(profile, dist, roof_top, width + 2, branch.attr.mansard), - RoofStyle::Rounded => { - let circular_dist = (bound_offset.map(|e| e.pow(4) as f32).sum().powf(0.25) + 0.5).ceil() as i32; - do_roof(Vec2::new(circular_dist, profile.y), circular_dist, roof_top, width + 2, branch.attr.mansard) - }, - } { - return block; + 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); } - // Walls - if dist == width { - if bound_offset.x == bound_offset.y || profile.y == ceil_height { // Support beams - return log; - } else if !branch.attr.storey_fill.has_lower() && profile.y < ceil_height { - return empty; - } else if !branch.attr.storey_fill.has_upper() { - return empty; - } else { - let frame_bounds = if profile.y >= ceil_height { - Aabr { - min: Vec2::new(-1, ceil_height + 2), - max: Vec2::new(1, ceil_height + 5), - } - } else { - Aabr { - min: Vec2::new(2, foundation_height + 2), - max: Vec2::new(width - 2, ceil_height - 2), - } - }; - let window_bounds = Aabr { - min: (frame_bounds.min + 1).map2(frame_bounds.center(), |a, b| a.min(b)), - max: (frame_bounds.max - 1).map2(frame_bounds.center(), |a, b| a.max(b)), - }; + 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(); - // 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 internal; - } else if frame_bounds.contains_point(surface_pos) { - return log.with_priority(3); - }; - } - - // Wall - return if branch.attr.central_supports && profile.x == 0 { // Support beams - log.with_priority(4) - } else { - wall - }; + 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); } } - if dist < width { // Internals - if profile.y == ceil_height { - if profile.x == 0 {// Rafters - return log; - } else if branch.attr.storey_fill.has_upper() { // Ceiling - return floor; - } - } else if (!branch.attr.storey_fill.has_lower() && profile.y < ceil_height) - || (!branch.attr.storey_fill.has_upper() && profile.y >= ceil_height) - { - return empty; - } else { - return internal; - } - } - - empty + cblock } } diff --git a/world/src/site/settlement/building/archetype/mod.rs b/world/src/site/settlement/building/archetype/mod.rs index 667f965865..32645a255e 100644 --- a/world/src/site/settlement/building/archetype/mod.rs +++ b/world/src/site/settlement/building/archetype/mod.rs @@ -29,14 +29,8 @@ impl BlockMask { self } - pub fn resolve_with(self, dist_self: i32, other: Self, dist_other: i32) -> Self { - if self.priority == other.priority { - if dist_self <= dist_other { - self - } else { - other - } - } else if self.priority >= other.priority { + pub fn resolve_with(self, other: Self) -> Self { + if self.priority >= other.priority { self } else { other diff --git a/world/src/site/settlement/building/skeleton.rs b/world/src/site/settlement/building/skeleton.rs index cde77ebb49..d027e49521 100644 --- a/world/src/site/settlement/building/skeleton.rs +++ b/world/src/site/settlement/building/skeleton.rs @@ -83,9 +83,9 @@ impl Skeleton { ) }.map(|e| e.abs()); let center_offset = if ori == Ori::East { - Vec2::new(pos.y, pos.x) + Vec2::new(pos.y - bounds.center().y, pos.x - bounds.center().x) } else { - Vec2::new(pos.x, pos.y) + Vec2::new(pos.x - bounds.center().x, pos.y - bounds.center().y) }; let dist = bound_offset.reduce_max(); let dist_locus = dist - branch.locus; @@ -95,7 +95,7 @@ impl Skeleton { } || true { let new_bm = f(dist, bound_offset, center_offset, branch); min = min - .map(|(min_dist_locus, bm)| (dist_locus, bm.resolve_with(min_dist_locus, new_bm, dist_locus))) + .map(|(_, bm)| (dist_locus, bm.resolve_with(new_bm))) .or(Some((dist_locus, new_bm))); } }); From ea933530420f3131620b61a8a292b25ed479fe0a Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 13 Apr 2020 14:48:22 +0100 Subject: [PATCH 114/195] Fixed chimneys --- world/src/site/settlement/building/archetype/house.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index 696abb1065..1dbcd1ae94 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -144,9 +144,9 @@ impl Archetype for House { BlockMask::new(Block::new(BlockKind::Normal, Rgb::new(r, g, b) + (nz & 0x0F) as u8 - 8), 2) }; - let foundation = make_block(100, 100, 100); + let foundation = make_block(100, 100, 100).with_priority(5); let log = make_block(60, 45, 30); - let floor = make_block(100, 75, 50).with_priority(3); + let floor = make_block(100, 75, 50).with_priority(7); let wall = make_block(200, 180, 150); let roof = make_block(self.roof_color.r, self.roof_color.g, self.roof_color.b); let empty = BlockMask::nothing(); @@ -184,7 +184,7 @@ impl Archetype for House { if profile.y <= foundation_height && dist < width + 3 { // Foundations if branch.attr.storey_fill.has_lower() { if dist == width - 1 { // Floor lining - return log; + return log.with_priority(6); } else if dist < width - 1 && profile.y == foundation_height { // Floor return floor; } From 5146cc198130f48d3306e8e93fea923a0b745503 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 13 Apr 2020 17:17:04 +0100 Subject: [PATCH 115/195] More consistent bridge generation --- world/src/column/mod.rs | 6 ++--- .../settlement/building/archetype/house.rs | 23 +++++++++++-------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index 59b9f260eb..97a2c9108c 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -632,7 +632,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { Some((river_dist - river_width * 0.5) as f32), valley_alt, new_alt, - valley_alt + river_gouge, + river_alt, 0.0, )) } else { @@ -802,14 +802,14 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { )) }, RiverKind::River { .. } => { - let (_, _, _, (_, (river_pos, _), _)) = + 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 as f32), + Some((river_dist - river_width * 0.5) as f32), alt_for_river, downhill_water_alt, alt_for_river, diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index 1dbcd1ae94..0e5ee2992e 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -144,14 +144,19 @@ impl Archetype for House { BlockMask::new(Block::new(BlockKind::Normal, Rgb::new(r, g, b) + (nz & 0x0F) as u8 - 8), 2) }; - let foundation = make_block(100, 100, 100).with_priority(5); + 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).with_priority(7); - let wall = make_block(200, 180, 150); - let roof = make_block(self.roof_color.r, self.roof_color.g, self.roof_color.b); + 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(), 4); - let fire = BlockMask::new(Block::new(BlockKind::Ember, Rgb::white()), 2); + let internal = BlockMask::new(Block::empty(), structural_layer); + let fire = BlockMask::new(Block::new(BlockKind::Ember, Rgb::white()), foundation_layer); let ceil_height = 6; let lower_width = branch.locus - 1; @@ -184,9 +189,9 @@ impl Archetype for House { 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(6); + return log.with_priority(floor_layer); } else if dist < width - 1 && profile.y == foundation_height { // Floor - return floor; + return floor.with_priority(floor_layer); } } @@ -205,7 +210,7 @@ impl Archetype for House { 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; + 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) }, }; @@ -265,7 +270,7 @@ impl Archetype for House { // Wall return Some(if branch.attr.central_supports && profile.x == 0 { // Support beams - log.with_priority(4) + log.with_priority(structural_layer) } else { wall }); From e352858c03c3203d68d7172d505ca2a9de550f61 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 13 Apr 2020 17:26:08 +0100 Subject: [PATCH 116/195] Fixed chimney blockage --- world/src/site/settlement/building/archetype/house.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index 0e5ee2992e..25040f44d4 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -151,7 +151,7 @@ impl Archetype for House { 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).with_priority(7); + 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(); From a51d214c21dc2cc181c578a59d3d1a4a1f6990d8 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 13 Apr 2020 21:37:47 +0100 Subject: [PATCH 117/195] Fixed path irregularities, better bridges --- world/src/block/mod.rs | 22 +++++++----- world/src/block/natural.rs | 1 + world/src/civ/mod.rs | 6 ++-- world/src/column/mod.rs | 6 ++-- world/src/sim/mod.rs | 3 +- .../settlement/building/archetype/house.rs | 25 +++++++++---- world/src/site/settlement/mod.rs | 35 ++++++++++++------- 7 files changed, 65 insertions(+), 33 deletions(-) diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index 173aa7d1f7..d10478e5f7 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -66,15 +66,19 @@ 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, }, diff --git a/world/src/block/natural.rs b/world/src/block/natural.rs index 0f285fef28..ecd223dfd8 100644 --- a/world/src/block/natural.rs +++ b/world/src/block/natural.rs @@ -28,6 +28,7 @@ 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.water_dist.map(|d| d < 8.0).unwrap_or(false) { return None; } diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index edf842d98e..b693f5a41a 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -95,9 +95,11 @@ impl Civs { let wpos = site.center * Vec2::from(TerrainChunkSize::RECT_SIZE).map(|e: u32| e as i32); // Flatten ground - let flatten_radius = 12.0; + let flatten_radius = 10.0; if let Some(center_alt) = ctx.sim.get_alt_approx(wpos) { - for pos in Spiral2d::new().map(|offs| site.center + offs).take(radius.pow(2) as usize) { + 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) diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index 97a2c9108c..6de86303b8 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -679,7 +679,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { 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.min(lake_water_alt - 1.0), + alt_for_river.max(lake_water_alt), 0.0, )); } @@ -753,7 +753,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { Some(lake_dist as f32), alt_for_river.min(lake_water_alt - 1.0 - river_gouge), lake_water_alt - river_gouge, - alt_for_river.min(lake_water_alt - 1.0), + alt_for_river.max(lake_water_alt), 0.0, )); } @@ -774,7 +774,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { alt.min(lake_water_alt - 1.0 - river_gouge), downhill_water_alt.max(lake_water_alt) - river_gouge, - alt.min(lake_water_alt - 1.0), + alt.max(lake_water_alt), 0.0, )); } else { diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 7c0ac61f25..dab24656ea 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -1867,7 +1867,8 @@ 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(); diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index 25040f44d4..25101a4abd 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -11,6 +11,20 @@ use super::{ super::skeleton::*, }; +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, @@ -116,11 +130,10 @@ impl Archetype for House { }; let this = Self { - roof_color: Rgb::new( - rng.gen_range(50, 200), - rng.gen_range(50, 200), - rng.gen_range(50, 200), - ), + 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(), @@ -141,7 +154,7 @@ impl Archetype for House { 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) + (nz & 0x0F) as u8 - 8), 2) + 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; diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index c2174e6fc9..002253bd88 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -251,7 +251,7 @@ impl Settlement { } pub fn place_town(&mut self, ctx: &mut GenCtx) { - const PLOT_COUNT: usize = 2; + const PLOT_COUNT: usize = 3; let mut origin = Vec2::new(ctx.rng.gen_range(-2, 3), ctx.rng.gen_range(-2, 3)); @@ -273,6 +273,7 @@ impl Settlement { } // Boundary wall + /* let spokes = CARDINALS .iter() .filter_map(|dir| { @@ -316,6 +317,7 @@ impl Settlement { } self.land .write_path(&wall_path, WayKind::Wall, buildable, true); + */ } pub fn place_buildings(&mut self, ctx: &mut GenCtx) { @@ -326,10 +328,11 @@ impl Settlement { }; for tile in Spiral2d::new().map(|offs| town_center + offs).take(16usize.pow(2)) { - for _ in 0..ctx.rng.gen_range(1, 5) { - for _ in 0..10 { + // 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); + + 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)) @@ -481,20 +484,23 @@ impl Settlement { // 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 = if let Some(water_dist) = col.water_dist { - ((water_dist.max(0.0) * 0.2).min(f32::consts::PI).cos() + 1.0) * 5.0 + 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 + (0.0, 3) }; let surface_z = (col.riverless_alt + bridge_offset).floor() as i32; - for z in inset - 3..inset { + for z in inset - depth..inset { vol.set( Vec3::new(offs.x, offs.y, surface_z + z), - if bridge_offset >= 2.0 { + if bridge_offset >= 2.0 && dist >= 2.5 || z < inset - 1 { Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 80, 100), 8)) } else { - Block::new(BlockKind::Normal, noisy_color(Rgb::new(90, 70, 50), 8)) + Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 50, 30), 8)) }, ); } @@ -622,7 +628,10 @@ impl Settlement { 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(130, 120, 80)), + 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), @@ -760,7 +769,9 @@ impl Land { 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 = Some((way, dist, proj_point)); + sample.way = sample.way + .filter(|(_, d, _)| *d < dist) + .or(Some((way, dist, proj_point))); } } } From 4b72a07e223a08405ed0862ee1c51779c43b6b76 Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Tue, 14 Apr 2020 01:13:03 +0200 Subject: [PATCH 118/195] sprites --- assets/common/items/coconut.ron | 13 + assets/voxygen/element/icons/item_coconut.png | 3 + assets/voxygen/item_image_manifest.ron | 6 +- .../voxel/sprite/cabbage/cabbage-0.vox | 3 + .../voxel/sprite/cabbage/cabbage-1.vox | 3 + .../voxel/sprite/cabbage/cabbage-2.vox | 3 + assets/voxygen/voxel/sprite/corn/corn-0.vox | 3 + assets/voxygen/voxel/sprite/corn/corn-1.vox | 3 + assets/voxygen/voxel/sprite/corn/corn-2.vox | 3 + assets/voxygen/voxel/sprite/corn/corn-3.vox | 3 + assets/voxygen/voxel/sprite/corn/corn-4.vox | 3 + assets/voxygen/voxel/sprite/corn/corn-5.vox | 3 + assets/voxygen/voxel/sprite/fruit/coconut.vox | 3 + .../voxel/sprite/wheat_green/wheat-0.vox | 3 + .../voxel/sprite/wheat_green/wheat-1.vox | 3 + .../voxel/sprite/wheat_green/wheat-2.vox | 3 + .../voxel/sprite/wheat_green/wheat-3.vox | 3 + .../voxel/sprite/wheat_green/wheat-4.vox | 3 + .../voxel/sprite/wheat_green/wheat-5.vox | 3 + .../voxel/sprite/wheat_green/wheat-6.vox | 3 + .../voxel/sprite/wheat_green/wheat-7.vox | 3 + .../voxel/sprite/wheat_green/wheat-8.vox | 3 + .../voxel/sprite/wheat_green/wheat-9.vox | 3 + .../voxel/sprite/wheat_yellow/wheat-0.vox | 3 + .../voxel/sprite/wheat_yellow/wheat-1.vox | 3 + .../voxel/sprite/wheat_yellow/wheat-2.vox | 3 + .../voxel/sprite/wheat_yellow/wheat-3.vox | 3 + .../voxel/sprite/wheat_yellow/wheat-4.vox | 3 + .../voxel/sprite/wheat_yellow/wheat-5.vox | 3 + .../voxel/sprite/wheat_yellow/wheat-6.vox | 3 + .../voxel/sprite/wheat_yellow/wheat-7.vox | 3 + .../voxel/sprite/wheat_yellow/wheat-8.vox | 3 + .../voxel/sprite/wheat_yellow/wheat-9.vox | 3 + assets/world/tree/desert_palm/1.vox | 4 +- assets/world/tree/desert_palm/10.vox | 4 +- assets/world/tree/desert_palm/2.vox | 4 +- assets/world/tree/desert_palm/3.vox | 4 +- assets/world/tree/desert_palm/4.vox | 4 +- assets/world/tree/desert_palm/5.vox | 4 +- assets/world/tree/desert_palm/6.vox | 4 +- assets/world/tree/desert_palm/7.vox | 4 +- assets/world/tree/desert_palm/8.vox | 4 +- assets/world/tree/desert_palm/9.vox | 4 +- common/src/comp/inventory/item/mod.rs | 2 + common/src/terrain/block.rs | 21 ++ common/src/terrain/structure.rs | 2 + voxygen/src/scene/terrain.rs | 236 +++++++++++++++++- world/src/block/mod.rs | 5 + 48 files changed, 396 insertions(+), 22 deletions(-) create mode 100644 assets/common/items/coconut.ron create mode 100644 assets/voxygen/element/icons/item_coconut.png create mode 100644 assets/voxygen/voxel/sprite/cabbage/cabbage-0.vox create mode 100644 assets/voxygen/voxel/sprite/cabbage/cabbage-1.vox create mode 100644 assets/voxygen/voxel/sprite/cabbage/cabbage-2.vox create mode 100644 assets/voxygen/voxel/sprite/corn/corn-0.vox create mode 100644 assets/voxygen/voxel/sprite/corn/corn-1.vox create mode 100644 assets/voxygen/voxel/sprite/corn/corn-2.vox create mode 100644 assets/voxygen/voxel/sprite/corn/corn-3.vox create mode 100644 assets/voxygen/voxel/sprite/corn/corn-4.vox create mode 100644 assets/voxygen/voxel/sprite/corn/corn-5.vox create mode 100644 assets/voxygen/voxel/sprite/fruit/coconut.vox create mode 100644 assets/voxygen/voxel/sprite/wheat_green/wheat-0.vox create mode 100644 assets/voxygen/voxel/sprite/wheat_green/wheat-1.vox create mode 100644 assets/voxygen/voxel/sprite/wheat_green/wheat-2.vox create mode 100644 assets/voxygen/voxel/sprite/wheat_green/wheat-3.vox create mode 100644 assets/voxygen/voxel/sprite/wheat_green/wheat-4.vox create mode 100644 assets/voxygen/voxel/sprite/wheat_green/wheat-5.vox create mode 100644 assets/voxygen/voxel/sprite/wheat_green/wheat-6.vox create mode 100644 assets/voxygen/voxel/sprite/wheat_green/wheat-7.vox create mode 100644 assets/voxygen/voxel/sprite/wheat_green/wheat-8.vox create mode 100644 assets/voxygen/voxel/sprite/wheat_green/wheat-9.vox create mode 100644 assets/voxygen/voxel/sprite/wheat_yellow/wheat-0.vox create mode 100644 assets/voxygen/voxel/sprite/wheat_yellow/wheat-1.vox create mode 100644 assets/voxygen/voxel/sprite/wheat_yellow/wheat-2.vox create mode 100644 assets/voxygen/voxel/sprite/wheat_yellow/wheat-3.vox create mode 100644 assets/voxygen/voxel/sprite/wheat_yellow/wheat-4.vox create mode 100644 assets/voxygen/voxel/sprite/wheat_yellow/wheat-5.vox create mode 100644 assets/voxygen/voxel/sprite/wheat_yellow/wheat-6.vox create mode 100644 assets/voxygen/voxel/sprite/wheat_yellow/wheat-7.vox create mode 100644 assets/voxygen/voxel/sprite/wheat_yellow/wheat-8.vox create mode 100644 assets/voxygen/voxel/sprite/wheat_yellow/wheat-9.vox diff --git a/assets/common/items/coconut.ron b/assets/common/items/coconut.ron new file mode 100644 index 0000000000..f0cd3398c8 --- /dev/null +++ b/assets/common/items/coconut.ron @@ -0,0 +1,13 @@ +Item( + name: "Mushroom", + description: "Reliable Source of water and fat. + +Restores 20 Health.", + kind: Consumable( + kind: Coconut, + effect: Health(( + amount: 20, + cause: Item, + )), + ), +) 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/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index 58dde6e4f9..a2c33550b6 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -272,9 +272,13 @@ (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/voxel/sprite/cabbage/cabbage-0.vox b/assets/voxygen/voxel/sprite/cabbage/cabbage-0.vox new file mode 100644 index 0000000000..fa2b836395 --- /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:fc86c7aec7a72bbde175d6663e0d308cdc823d5eeabd84a06438a27ac4ba10ec +size 2060 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..e9687ef70e --- /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:bac99ca3cfc05aa66cb7999d5e3af6467f3e0a41ba8bab4b339e37db01a3ff72 +size 2060 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..0326cba8e8 --- /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:89df0256d7b39a9b0a9b6349b9c8fa3885bab3ce3e14dc90f8a6a69e23d27e5c +size 2060 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/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/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/world/tree/desert_palm/1.vox b/assets/world/tree/desert_palm/1.vox index bec36a7ddf..10d86ddf6e 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:cb2ab9b7dab8a3806035a651ef45373d19bf5eb75aa27879274abad643243904 +size 2580 diff --git a/assets/world/tree/desert_palm/10.vox b/assets/world/tree/desert_palm/10.vox index c0daf1cff2..1f310bd412 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 -size 3356 +oid sha256:e0deb3f56dd5c3585dec5e7b33b99cb45d4546bfc9a8847292ac3b5bb79e2a37 +size 3364 diff --git a/assets/world/tree/desert_palm/2.vox b/assets/world/tree/desert_palm/2.vox index 7c582c5ae1..7040de600c 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:83a26ee836b81eb521cf8c9ac32eaf65adb749d37e2fbbc5f9cbb6f6f49423af +size 2544 diff --git a/assets/world/tree/desert_palm/3.vox b/assets/world/tree/desert_palm/3.vox index c8862eaee3..eace282e91 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:18654995a7575fb34dc26792395e558faf9f14e0fd63922c933faec1c04f60ea +size 2740 diff --git a/assets/world/tree/desert_palm/4.vox b/assets/world/tree/desert_palm/4.vox index 83ee9bcdbb..a4fa730635 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:028cc39e9930d439add126d8915273c77b8ba80a31d3f7a6446286e84c021665 +size 2676 diff --git a/assets/world/tree/desert_palm/5.vox b/assets/world/tree/desert_palm/5.vox index 3ca1dc1d68..03a5b24e51 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:c906818d61d00d17ee1263d3dff11b285a765103dbd0a16dd1b7c6ae12bf224e +size 2680 diff --git a/assets/world/tree/desert_palm/6.vox b/assets/world/tree/desert_palm/6.vox index 93e02f1ba3..5546e98098 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:5623e0b6bad9e628a2ff47d8b78f5fbea6bac3263c0e29d732658a9ac813b78e +size 2780 diff --git a/assets/world/tree/desert_palm/7.vox b/assets/world/tree/desert_palm/7.vox index 4445dbf3b3..a5f4aaef27 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:9e16fbe4c4b055bcb8d15a06ca28d0b54f6805a008c9133952dec8c340d08c53 +size 3048 diff --git a/assets/world/tree/desert_palm/8.vox b/assets/world/tree/desert_palm/8.vox index 579d53bf10..833fe244ea 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:de446c9f596278bd36fbec49791ed2f7b100f0755fc70be39ab5ae711e9b4bab +size 2712 diff --git a/assets/world/tree/desert_palm/9.vox b/assets/world/tree/desert_palm/9.vox index 58315225c9..4ac78f20fc 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:e51849f6c3faf01fb9526174733911c75f92204e914fd973894c71c09bd470e9 +size 3164 diff --git a/common/src/comp/inventory/item/mod.rs b/common/src/comp/inventory/item/mod.rs index 42dcdd6a01..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, @@ -126,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", diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index fb2548f15c..ed68abcc34 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -41,6 +41,11 @@ pub enum BlockKind { DeadBush, Blueberry, Ember, + Corn, + WheatYellow, + WheatGreen, + Cabbage, + Coconut, } impl BlockKind { @@ -83,6 +88,11 @@ impl BlockKind { BlockKind::DeadBush => true, BlockKind::Blueberry => true, BlockKind::Ember => true, + BlockKind::Corn => true, + BlockKind::WheatYellow => true, + BlockKind::WheatGreen => true, + BlockKind::Cabbage => true, + BlockKind::Coconut => true, _ => false, } } @@ -127,6 +137,11 @@ impl BlockKind { BlockKind::DeadBush => false, BlockKind::Blueberry => false, BlockKind::Ember => false, + BlockKind::Corn => false, + BlockKind::WheatYellow => false, + BlockKind::WheatGreen => false, + BlockKind::Cabbage => false, + BlockKind::Coconut => false, _ => true, } } @@ -163,6 +178,11 @@ impl BlockKind { BlockKind::DeadBush => false, BlockKind::Blueberry => false, BlockKind::Ember => false, + BlockKind::Corn => false, + BlockKind::WheatYellow => false, + BlockKind::WheatGreen => false, + BlockKind::Cabbage => false, + BlockKind::Coconut => false, _ => true, } } @@ -185,6 +205,7 @@ impl BlockKind { BlockKind::VeloriteFrag => true, BlockKind::Chest => true, BlockKind::Pumpkin => true, + BlockKind::Coconut => true, _ => false, } } 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/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index a2905f8c5a..2652fad6a6 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -174,7 +174,26 @@ fn sprite_config_for(kind: BlockKind) -> Option { 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::Coconut => Some(SpriteConfig { + variations: 1, + wind_sway: 0.0, + }), _ => None, } } @@ -1075,6 +1094,221 @@ impl Terrain { (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_yellow.wheat-0", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatGreen, 1), + make_model( + "voxygen.voxel.sprite.wheat_yellow.wheat-1", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatGreen, 2), + make_model( + "voxygen.voxel.sprite.wheat_yellow.wheat-2", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatGreen, 3), + make_model( + "voxygen.voxel.sprite.wheat_yellow.wheat-3", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatGreen, 4), + make_model( + "voxygen.voxel.sprite.wheat_yellow.wheat-4", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatGreen, 5), + make_model( + "voxygen.voxel.sprite.wheat_yellow.wheat-5", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatGreen, 6), + make_model( + "voxygen.voxel.sprite.wheat_yellow.wheat-6", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatGreen, 7), + make_model( + "voxygen.voxel.sprite.wheat_yellow.wheat-7", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatGreen, 8), + make_model( + "voxygen.voxel.sprite.wheat_yellow.wheat-8", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatGreen, 9), + make_model( + "voxygen.voxel.sprite.wheat_yellow.wheat-9", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + // Cabbage + ( + (BlockKind::WheatGreen, 0), + make_model( + "voxygen.voxel.sprite.cabbage.cabbage-0", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatGreen, 1), + make_model( + "voxygen.voxel.sprite.cabbage.cabbage-1", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatGreen, 2), + make_model( + "voxygen.voxel.sprite.cabbage.cabbage-2", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + // Coconut + ( + (BlockKind::WheatGreen, 0), + make_model( + "voxygen.voxel.sprite.fruit.coconut", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), ] .into_iter() .collect(), diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index d10478e5f7..c52953dd02 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -610,6 +610,11 @@ pub fn block_from_structure( } else { 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 { From d57ca371e2f3645b2619b93a912012ad193f0f96 Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Tue, 14 Apr 2020 02:21:35 +0200 Subject: [PATCH 119/195] fix panic --- assets/world/tree/desert_palm/1.vox | 2 +- assets/world/tree/desert_palm/10.vox | 4 ++-- assets/world/tree/desert_palm/2.vox | 2 +- assets/world/tree/desert_palm/3.vox | 2 +- assets/world/tree/desert_palm/4.vox | 2 +- assets/world/tree/desert_palm/5.vox | 2 +- assets/world/tree/desert_palm/6.vox | 2 +- assets/world/tree/desert_palm/7.vox | 2 +- assets/world/tree/desert_palm/8.vox | 4 ++-- assets/world/tree/desert_palm/9.vox | 2 +- voxygen/src/scene/terrain.rs | 2 +- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/assets/world/tree/desert_palm/1.vox b/assets/world/tree/desert_palm/1.vox index 10d86ddf6e..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:cb2ab9b7dab8a3806035a651ef45373d19bf5eb75aa27879274abad643243904 +oid sha256:6cfa51cd9bedf24d727aeffb809b0c673dc8cb15aeb0a7b6a23e05aac2121436 size 2580 diff --git a/assets/world/tree/desert_palm/10.vox b/assets/world/tree/desert_palm/10.vox index 1f310bd412..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:e0deb3f56dd5c3585dec5e7b33b99cb45d4546bfc9a8847292ac3b5bb79e2a37 -size 3364 +oid sha256:51c8a5c03190aaf0752b0c2dcc1f2d3aa4744afadd3b45774b157498ac0ad717 +size 3356 diff --git a/assets/world/tree/desert_palm/2.vox b/assets/world/tree/desert_palm/2.vox index 7040de600c..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:83a26ee836b81eb521cf8c9ac32eaf65adb749d37e2fbbc5f9cbb6f6f49423af +oid sha256:3980edde33843925e017e7986c3722ce03202119f95b177f3dbde45dd47084ef size 2544 diff --git a/assets/world/tree/desert_palm/3.vox b/assets/world/tree/desert_palm/3.vox index eace282e91..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:18654995a7575fb34dc26792395e558faf9f14e0fd63922c933faec1c04f60ea +oid sha256:65973116a2c4e2166b06c93c40bbdf2cb0cd055186e997eb13a9d4665a360407 size 2740 diff --git a/assets/world/tree/desert_palm/4.vox b/assets/world/tree/desert_palm/4.vox index a4fa730635..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:028cc39e9930d439add126d8915273c77b8ba80a31d3f7a6446286e84c021665 +oid sha256:c739af1da0a6502dc333e8f26e6aa3c5b43dd1bf9a739fa1183a6925b06c3d95 size 2676 diff --git a/assets/world/tree/desert_palm/5.vox b/assets/world/tree/desert_palm/5.vox index 03a5b24e51..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:c906818d61d00d17ee1263d3dff11b285a765103dbd0a16dd1b7c6ae12bf224e +oid sha256:839d59a6899359d75ae40ede6eab197e4438753afb36b59dac03217a1867cdf1 size 2680 diff --git a/assets/world/tree/desert_palm/6.vox b/assets/world/tree/desert_palm/6.vox index 5546e98098..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:5623e0b6bad9e628a2ff47d8b78f5fbea6bac3263c0e29d732658a9ac813b78e +oid sha256:fa64cff8589f64f1857975b3acba8fa16c24b1d19d6482f9ff08fdd95d8705e7 size 2780 diff --git a/assets/world/tree/desert_palm/7.vox b/assets/world/tree/desert_palm/7.vox index a5f4aaef27..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:9e16fbe4c4b055bcb8d15a06ca28d0b54f6805a008c9133952dec8c340d08c53 +oid sha256:2bf99aeaf0936a67fea13d715095ed53506859723e2543838de9d561c8b9e2cb size 3048 diff --git a/assets/world/tree/desert_palm/8.vox b/assets/world/tree/desert_palm/8.vox index 833fe244ea..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:de446c9f596278bd36fbec49791ed2f7b100f0755fc70be39ab5ae711e9b4bab -size 2712 +oid sha256:2581ec5257928a5d5fd64e533bef1ba7267438ce6fae5849a39bccdd547b7ba3 +size 2716 diff --git a/assets/world/tree/desert_palm/9.vox b/assets/world/tree/desert_palm/9.vox index 4ac78f20fc..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:e51849f6c3faf01fb9526174733911c75f92204e914fd973894c71c09bd470e9 +oid sha256:b787e74402728a73be2963533ae5f2e1438fdb90345b0e3d0e6f0452ae314b60 size 3164 diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index 2652fad6a6..8f75cdd7e5 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -1303,7 +1303,7 @@ impl Terrain { ), // Coconut ( - (BlockKind::WheatGreen, 0), + (BlockKind::Coconut, 0), make_model( "voxygen.voxel.sprite.fruit.coconut", Vec3::new(-6.0, -6.0, 0.0), From 0125897117277e6e7012156b157fcc087ec16eb4 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Tue, 14 Apr 2020 00:39:52 +0100 Subject: [PATCH 120/195] Slightly better clouds --- assets/voxygen/shaders/include/cloud/regular.glsl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/voxygen/shaders/include/cloud/regular.glsl b/assets/voxygen/shaders/include/cloud/regular.glsl index 4f5fdb5559..76436f13d6 100644 --- a/assets/voxygen/shaders/include/cloud/regular.glsl +++ b/assets/voxygen/shaders/include/cloud/regular.glsl @@ -1,8 +1,8 @@ uniform sampler2D t_noise; const float CLOUD_AVG_HEIGHT = 1025.0; -const float CLOUD_HEIGHT_MIN = CLOUD_AVG_HEIGHT - 30.0; -const float CLOUD_HEIGHT_MAX = CLOUD_AVG_HEIGHT + 30.0; +const float CLOUD_HEIGHT_MIN = CLOUD_AVG_HEIGHT - 60.0; +const float CLOUD_HEIGHT_MAX = CLOUD_AVG_HEIGHT + 60.0; const float CLOUD_THRESHOLD = 0.27; const float CLOUD_SCALE = 5.0; const float CLOUD_DENSITY = 100.0; From a4872c4e8dc7bb25a7528001ee81b9c8093a17d9 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Tue, 14 Apr 2020 01:25:19 +0100 Subject: [PATCH 121/195] Added crop terrain sprite generation --- world/src/site/settlement/mod.rs | 83 +++++++++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 8 deletions(-) diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 002253bd88..5c3f9ae294 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -420,6 +420,13 @@ impl Settlement { let field = self.land.new_plot(Plot::Field { farm, seed: rng.gen(), + crop: match rng.gen_range(0, 5) { + 0 => Crop::Corn, + 1 => Crop::Wheat, + 2 => Crop::Cabbage, + 3 => Crop::Pumpkin, + _ => Crop::Sunflower, + }, }); let tiles = self.land @@ -513,16 +520,67 @@ impl Settlement { } // Ground colour } else if let Some(color) = self.get_color(rpos) { - if col_sample.water_dist.map(|dist| dist > 2.0).unwrap_or(true) { - for z in -8..6 { - let pos = Vec3::new(offs.x, offs.y, surface_z + z); + let mut surface_block = None; - if z >= 0 { - if vol.get(pos).unwrap().kind() != BlockKind::Water { - vol.set(pos, Block::empty()); + 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) => Some(Rgb::new(150, 110, 60) + .map2(Rgb::iota(), |e: u8, i: i32| e + .saturating_add((self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, i * 5)) % 16) 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(6) < 3; + + let dirt = Rgb::new(70, 55, 35); + let mound = Rgb::new(65, 75, 30); + + let roll = |seed, n| self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, seed * 5)) % n; + + if in_furrow && 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 => Some(BlockKind::Cabbage), + Crop::Pumpkin if roll(2, 3) == 0 => Some(BlockKind::Pumpkin), + Crop::Sunflower => Some(BlockKind::Sunflower), + _ => None, } + .map(|kind| Block::new(kind, Rgb::white())); + } + + Some(if in_furrow { + dirt } else { - vol.set(pos, Block::new(BlockKind::Normal, noisy_color(color, 4))); + mound + }) + }, + _ => None, + }; + + if let Some(color) = color { + if col_sample.water_dist.map(|dist| dist > 2.0).unwrap_or(true) { + for z in -8..6 { + let pos = Vec3::new(offs.x, offs.y, surface_z + z); + + if let (0, Some(block)) = (z, surface_block) { + vol.set(pos, block); + } else if z >= 0 { + if vol.get(pos).unwrap().kind() != BlockKind::Water { + vol.set(pos, Block::empty()); + } + } else { + vol.set(pos, Block::new(BlockKind::Normal, noisy_color(color, 4))); + } } } } @@ -658,6 +716,15 @@ impl Settlement { } } +#[derive(Copy, Clone, PartialEq)] +pub enum Crop { + Corn, + Wheat, + Cabbage, + Pumpkin, + Sunflower, +} + #[derive(Copy, Clone, PartialEq)] pub enum Plot { Hazard, @@ -665,7 +732,7 @@ pub enum Plot { Grass, Water, Town, - Field { farm: Id, seed: u32 }, + Field { farm: Id, seed: u32, crop: Crop }, } const CARDINALS: [Vec2; 4] = [ From f999edffaa814fe7bc5ec040e97c3a351208516b Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Tue, 14 Apr 2020 01:50:58 +0100 Subject: [PATCH 122/195] Fixed crop crash issues --- assets/common/items/coconut.ron | 2 +- common/src/terrain/block.rs | 1 + voxygen/src/scene/terrain.rs | 26 +++++++++++++------------- world/src/site/settlement/mod.rs | 4 ++-- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/assets/common/items/coconut.ron b/assets/common/items/coconut.ron index f0cd3398c8..76d3212e39 100644 --- a/assets/common/items/coconut.ron +++ b/assets/common/items/coconut.ron @@ -1,5 +1,5 @@ Item( - name: "Mushroom", + name: "Coconut", description: "Reliable Source of water and fat. Restores 20 Health.", diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index ed68abcc34..4a373efe39 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -130,6 +130,7 @@ impl BlockKind { BlockKind::Velorite => false, BlockKind::VeloriteFrag => false, BlockKind::Chest => false, + BlockKind::Pumpkin => false, BlockKind::Welwitch => false, BlockKind::LingonBerry => false, BlockKind::LeafyPlant => false, diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index 8f75cdd7e5..a620e5b44f 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -1212,90 +1212,90 @@ impl Terrain { ( (BlockKind::WheatGreen, 0), make_model( - "voxygen.voxel.sprite.wheat_yellow.wheat-0", + "voxygen.voxel.sprite.wheat_green.wheat-0", Vec3::new(-6.0, -6.0, 0.0), ), ), ( (BlockKind::WheatGreen, 1), make_model( - "voxygen.voxel.sprite.wheat_yellow.wheat-1", + "voxygen.voxel.sprite.wheat_green.wheat-1", Vec3::new(-6.0, -6.0, 0.0), ), ), ( (BlockKind::WheatGreen, 2), make_model( - "voxygen.voxel.sprite.wheat_yellow.wheat-2", + "voxygen.voxel.sprite.wheat_green.wheat-2", Vec3::new(-6.0, -6.0, 0.0), ), ), ( (BlockKind::WheatGreen, 3), make_model( - "voxygen.voxel.sprite.wheat_yellow.wheat-3", + "voxygen.voxel.sprite.wheat_green.wheat-3", Vec3::new(-6.0, -6.0, 0.0), ), ), ( (BlockKind::WheatGreen, 4), make_model( - "voxygen.voxel.sprite.wheat_yellow.wheat-4", + "voxygen.voxel.sprite.wheat_green.wheat-4", Vec3::new(-6.0, -6.0, 0.0), ), ), ( (BlockKind::WheatGreen, 5), make_model( - "voxygen.voxel.sprite.wheat_yellow.wheat-5", + "voxygen.voxel.sprite.wheat_green.wheat-5", Vec3::new(-6.0, -6.0, 0.0), ), ), ( (BlockKind::WheatGreen, 6), make_model( - "voxygen.voxel.sprite.wheat_yellow.wheat-6", + "voxygen.voxel.sprite.wheat_green.wheat-6", Vec3::new(-6.0, -6.0, 0.0), ), ), ( (BlockKind::WheatGreen, 7), make_model( - "voxygen.voxel.sprite.wheat_yellow.wheat-7", + "voxygen.voxel.sprite.wheat_green.wheat-7", Vec3::new(-6.0, -6.0, 0.0), ), ), ( (BlockKind::WheatGreen, 8), make_model( - "voxygen.voxel.sprite.wheat_yellow.wheat-8", + "voxygen.voxel.sprite.wheat_green.wheat-8", Vec3::new(-6.0, -6.0, 0.0), ), ), ( (BlockKind::WheatGreen, 9), make_model( - "voxygen.voxel.sprite.wheat_yellow.wheat-9", + "voxygen.voxel.sprite.wheat_green.wheat-9", Vec3::new(-6.0, -6.0, 0.0), ), ), // Cabbage ( - (BlockKind::WheatGreen, 0), + (BlockKind::Cabbage, 0), make_model( "voxygen.voxel.sprite.cabbage.cabbage-0", Vec3::new(-6.0, -6.0, 0.0), ), ), ( - (BlockKind::WheatGreen, 1), + (BlockKind::Cabbage, 1), make_model( "voxygen.voxel.sprite.cabbage.cabbage-1", Vec3::new(-6.0, -6.0, 0.0), ), ), ( - (BlockKind::WheatGreen, 2), + (BlockKind::Cabbage, 2), make_model( "voxygen.voxel.sprite.cabbage.cabbage-2", Vec3::new(-6.0, -6.0, 0.0), diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 5c3f9ae294..ebce8bfe77 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -550,8 +550,8 @@ impl Settlement { Crop::Corn => Some(BlockKind::Corn), Crop::Wheat if roll(1, 2) == 0 => Some(BlockKind::WheatYellow), Crop::Wheat => Some(BlockKind::WheatGreen), - Crop::Cabbage => Some(BlockKind::Cabbage), - Crop::Pumpkin if roll(2, 3) == 0 => Some(BlockKind::Pumpkin), + Crop::Cabbage if roll(2, 2) == 0 => Some(BlockKind::Cabbage), + Crop::Pumpkin if roll(3, 2) == 0 => Some(BlockKind::Pumpkin), Crop::Sunflower => Some(BlockKind::Sunflower), _ => None, } From 48e67ba9c0135b891235a4cbb5682466fcdd3661 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Tue, 14 Apr 2020 02:32:58 +0100 Subject: [PATCH 123/195] Fixed terrain sprite AO --- voxygen/src/mesh/segment.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/voxygen/src/mesh/segment.rs b/voxygen/src/mesh/segment.rs index 6c7ef46835..c88635681e 100644 --- a/voxygen/src/mesh/segment.rs +++ b/voxygen/src/mesh/segment.rs @@ -90,7 +90,25 @@ impl Meshable for Segment { linear_to_srgb(srgb_to_linear(col) * light.min(ao)), ) }, - &[[[1.0; 3]; 3]; 3], + &{ + let mut ls = [[[0.0; 3]; 3]; 3]; + for x in 0..3 { + for y in 0..3 { + for z in 0..3 { + ls[z][y][x] = if 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 + }; + } + } + } + ls + }, ); } } From 32d2274cafb2be1e0d45d4eaface6d2c266ac7ee Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Tue, 14 Apr 2020 03:13:57 +0100 Subject: [PATCH 124/195] Better fields --- world/src/site/settlement/mod.rs | 36 +++++++++++++++++++------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index ebce8bfe77..650cfb8b41 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -538,24 +538,32 @@ impl Settlement { Vec2::new(-1, 1), ]; let furrow_dir = furrow_dirs[*seed as usize % furrow_dirs.len()]; - let in_furrow = (wpos2d * furrow_dir).sum().rem_euclid(6) < 3; - - let dirt = Rgb::new(70, 55, 35); - let mound = Rgb::new(65, 75, 30); + let in_furrow = (wpos2d * furrow_dir).sum().rem_euclid(5) < 2; let roll = |seed, n| self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, seed * 5)) % n; - if in_furrow && 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::Sunflower => Some(BlockKind::Sunflower), - _ => None, + let dirt = Rgb::new(80, 55, 35).map(|e| e + (self.noise.get(Vec3::broadcast((seed % 4096 + 0) as i32)) % 8) as u8); + let mound = Rgb::new(100, 130, 40).map(|e| e + roll(0, 8) as u8).map(|e| e + (self.noise.get(Vec3::broadcast((seed % 4096 + 1) as i32)) % 8) 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::Sunflower => Some(BlockKind::Sunflower), + _ => None, + } + .map(|kind| Block::new(kind, Rgb::white())); + } + } else { + if roll(0, 30) == 0 { + surface_block = Some(Block::new(BlockKind::ShortGrass, Rgb::white())); + } else if roll(0, 30) == 0 { + surface_block = Some(Block::new(BlockKind::MediumGrass, Rgb::white())); } - .map(|kind| Block::new(kind, Rgb::white())); } Some(if in_furrow { From 568a8ab87c7cd07eb96416df7607974d424aa90d Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 15 Apr 2020 00:02:15 +0100 Subject: [PATCH 125/195] Increased block rendering altitude range --- assets/voxygen/shaders/fluid-vert.glsl | 8 +++----- assets/voxygen/shaders/terrain-vert.glsl | 4 +++- voxygen/src/render/pipelines/fluid.rs | 12 +++++++----- voxygen/src/render/pipelines/terrain.rs | 8 +++++--- .../src/site/settlement/building/archetype/house.rs | 2 +- world/src/site/settlement/mod.rs | 6 +++--- 6 files changed, 22 insertions(+), 18 deletions(-) diff --git a/assets/voxygen/shaders/fluid-vert.glsl b/assets/voxygen/shaders/fluid-vert.glsl index a5317dba17..1bf7e172a9 100644 --- a/assets/voxygen/shaders/fluid-vert.glsl +++ b/assets/voxygen/shaders/fluid-vert.glsl @@ -18,12 +18,10 @@ 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 = vec3((uvec3(v_pos_norm) >> uvec3(0, 6, 12)) & uvec3(0x3Fu, 0x3Fu, 0x1FFFFu)) - vec3(0, 0, EXTRA_NEG_Z) + model_offs; f_pos.z *= min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.0); f_pos.z -= 25.0 * pow(distance(focus_pos.xy, f_pos.xy) / view_distance.x, 20.0); diff --git a/assets/voxygen/shaders/terrain-vert.glsl b/assets/voxygen/shaders/terrain-vert.glsl index 52b21fad54..bfe1597ccd 100644 --- a/assets/voxygen/shaders/terrain-vert.glsl +++ b/assets/voxygen/shaders/terrain-vert.glsl @@ -17,8 +17,10 @@ flat out uint f_pos_norm; out vec3 f_col; out float f_light; +const float EXTRA_NEG_Z = 65536.0; + void main() { - f_pos = vec3((uvec3(v_pos_norm) >> uvec3(0, 8, 16)) & uvec3(0xFFu, 0xFFu, 0x1FFFu)) + model_offs; + 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 *= min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.0); f_pos.z -= 25.0 * pow(distance(focus_pos.xy, f_pos.xy) / view_distance.x, 20.0); diff --git a/voxygen/src/render/pipelines/fluid.rs b/voxygen/src/render/pipelines/fluid.rs index dfae0af2df..2e67ae9600 100644 --- a/voxygen/src/render/pipelines/fluid.rs +++ b/voxygen/src/render/pipelines/fluid.rs @@ -40,14 +40,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/terrain.rs b/voxygen/src/render/pipelines/terrain.rs index 8fe5658a6b..48281f616a 100644 --- a/voxygen/src/render/pipelines/terrain.rs +++ b/voxygen/src/render/pipelines/terrain.rs @@ -38,11 +38,13 @@ gfx_defines! { impl Vertex { pub fn new(norm_bits: u32, light: 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 diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index 25101a4abd..d2b105724f 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -184,7 +184,7 @@ impl Archetype for House { return if profile.y == foundation_height + 1 { fire } else { - internal + internal.with_priority(foundation_layer) }; } diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 650cfb8b41..f2d0164d25 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -504,7 +504,7 @@ impl Settlement { for z in inset - depth..inset { vol.set( Vec3::new(offs.x, offs.y, surface_z + z), - if bridge_offset >= 2.0 && dist >= 2.5 || z < inset - 1 { + 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)) @@ -542,8 +542,8 @@ impl Settlement { let roll = |seed, n| self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, seed * 5)) % n; - let dirt = Rgb::new(80, 55, 35).map(|e| e + (self.noise.get(Vec3::broadcast((seed % 4096 + 0) as i32)) % 8) as u8); - let mound = Rgb::new(100, 130, 40).map(|e| e + roll(0, 8) as u8).map(|e| e + (self.noise.get(Vec3::broadcast((seed % 4096 + 1) as i32)) % 8) as u8); + 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 { From 3ffc5a7d87e3a1193d6d31d2fe2a4d1ecd619be1 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 15 Apr 2020 20:41:40 +0100 Subject: [PATCH 126/195] Added simple dungeon impl --- world/src/civ/mod.rs | 89 +++++--- world/src/site/dungeon/mod.rs | 198 ++++++++++++++++++ world/src/site/mod.rs | 63 +++++- .../settlement/building/archetype/house.rs | 6 +- .../settlement/building/archetype/keep.rs | 2 +- .../site/settlement/building/archetype/mod.rs | 42 +--- .../src/site/settlement/building/skeleton.rs | 2 +- world/src/site/settlement/mod.rs | 5 +- 8 files changed, 331 insertions(+), 76 deletions(-) create mode 100644 world/src/site/dungeon/mod.rs diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index b693f5a41a..31d15cf51c 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -19,7 +19,7 @@ use common::{ }; use crate::{ sim::{WorldSim, SimChunk}, - site::{Site as WorldSite, Settlement}, + site::{Site as WorldSite, Settlement, Dungeon}, util::seed_expan, }; @@ -76,6 +76,32 @@ impl Civs { } } + for _ in 0..256 { + attempt(5, || { + let loc = find_site_loc(&mut ctx, None)?; + this.establish_site(&mut ctx, 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 { @@ -91,6 +117,10 @@ impl Civs { // 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); @@ -117,15 +147,18 @@ impl Civs { // Place sites in world for site in this.sites.iter() { - let radius = 48i32; let wpos = site.center * Vec2::from(TerrainChunkSize::RECT_SIZE).map(|e: u32| e as i32); - let settlement = WorldSite::from(Settlement::generate(wpos, Some(ctx.sim), ctx.rng)); + let world_site = match &site.kind { + SiteKind::Settlement => WorldSite::from(Settlement::generate(wpos, Some(ctx.sim), ctx.rng)), + SiteKind::Dungeon => WorldSite::from(Dungeon::generate(wpos, Some(ctx.sim), ctx.rng)), + }; - for pos in Spiral2d::new().map(|offs| site.center + offs).take(radius.pow(2) as usize) { + 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(settlement.clone())); + .map(|chunk| chunk.sites.push(world_site.clone())); } println!("Placed site at {:?}", site.center); } @@ -188,7 +221,26 @@ impl Civs { 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) + 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 { @@ -242,7 +294,7 @@ impl Civs { Some(place) } - fn establish_site(&mut self, ctx: &mut GenCtx, loc: Vec2) -> Option> { + 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) { @@ -250,26 +302,7 @@ impl Civs { None => self.establish_place(ctx, loc, SITE_AREA)?, }; - let site = self.sites.insert(Site { - kind: SiteKind::Settlement, - center: loc, - place: 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 site = self.sites.insert(site_fn(place)); // Find neighbors const MAX_NEIGHBOR_DISTANCE: f32 = 250.0; @@ -531,6 +564,7 @@ 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)?; @@ -558,6 +592,7 @@ impl fmt::Display for Site { #[derive(Debug)] pub enum SiteKind { Settlement, + Dungeon, } impl Site { diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs new file mode 100644 index 0000000000..579b2698cd --- /dev/null +++ b/world/src/site/dungeon/mod.rs @@ -0,0 +1,198 @@ +use crate::{ + column::ColumnSample, + sim::{SimChunk, WorldSim}, + util::{Grid, RandomField, Sampler, StructureGen2d}, + site::BlockMask, +}; +use super::SpawnRules; +use common::{ + astar::Astar, + path::Path, + spiral::Spiral2d, + terrain::{Block, BlockKind, TerrainChunkSize}, + vol::{BaseVol, RectSizedVol, RectVolSize, ReadVol, WriteVol, Vox}, + store::{Id, Store}, +}; +use hashbrown::{HashMap, HashSet}; +use rand::prelude::*; +use std::{collections::VecDeque, f32}; +use vek::*; + +impl WorldSim { + 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, + 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 mut this = Self { + origin: wpos, + alt: ctx.sim.and_then(|sim| sim.get_alt_approx(wpos)).unwrap_or(0.0) as i32, + noise: RandomField::new(ctx.rng.gen()), + floors: (0..6) + .scan(Vec2::zero(), |stair_tile, _| { + let (floor, st) = Floor::generate(&mut ctx, *stair_tile); + *stair_tile = st; + Some(floor) + }) + .collect(), + }; + + this + } + + 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, + mut get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, + vol: &mut (impl BaseVol + RectSizedVol + ReadVol + WriteVol), + ) { + let rand_field = RandomField::new(0); + + 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; + + let make_staircase = |pos: Vec3, radius: f32, inner_radius: f32, stretch| { + 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 + pos.z as f32) % stretch < 3.0 + || (pos.xy().magnitude_squared() as f32) < inner_radius.powf(2.0) + { + BlockMask::new(Block::new(BlockKind::Normal, Rgb::new(150, 150, 175)), 5) + } else { + BlockMask::new(Block::empty(), 1) + } + } else { + BlockMask::nothing() + } + }; + + let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE)); + let tile_center = tile_pos * TILE_SIZE + TILE_SIZE / 2; + + let mut z = self.alt + 20; + for floor in &self.floors { + match floor.sample(tile_pos) { + Some(Tile::DownStair) | Some(Tile::Empty) => { + z -= floor.solid_depth; + for _ in 0..floor.hollow_depth { + vol.set(Vec3::new(offs.x, offs.y, z), Block::empty()); + z -= 1; + } + }, + Some(Tile::UpStair) => { + for i in 0..floor.solid_depth + floor.hollow_depth { + let rtile_pos = rpos - tile_center; + let mut block = make_staircase(Vec3::new(rtile_pos.x, rtile_pos.y, z), TILE_SIZE as f32 / 2.0, 1.5, 13.0); + if i >= floor.solid_depth { + block = block.resolve_with(BlockMask::new(Block::empty(), 1)); + } + if let Some(block) = block.finish() { + vol.set(Vec3::new(offs.x, offs.y, z), block); + } + z -= 1; + } + }, + None => z -= floor.solid_depth + floor.hollow_depth, + } + } + } + } + } +} + +const CARDINALS: [Vec2; 4] = [ + Vec2::new(0, 1), + Vec2::new(1, 0), + Vec2::new(0, -1), + Vec2::new(-1, 0), +]; + +const TILE_SIZE: i32 = 17; + +pub enum Tile { + UpStair, + DownStair, + Empty, +} + +pub struct Floor { + tile_offset: Vec2, + tiles: Grid, + solid_depth: i32, + hollow_depth: i32, +} + +impl Floor { + pub fn generate(ctx: &mut GenCtx, stair_tile: Vec2) -> (Self, Vec2) { + let new_stair_tile = std::iter::from_fn(|| Some(FLOOR_SIZE.map(|sz| ctx.rng.gen_range(-sz / 2 + 1, sz / 2)))) + .find(|pos| *pos != stair_tile) + .unwrap(); + + const FLOOR_SIZE: Vec2 = Vec2::new(12, 12); + let tile_offset = -FLOOR_SIZE / 2; + let this = Floor { + tile_offset, + tiles: Grid::populate_from(FLOOR_SIZE, |pos| { + let tile_pos = tile_offset + pos; + if tile_pos == stair_tile { + Tile::UpStair + } else if tile_pos == new_stair_tile { + Tile::DownStair + } else { + Tile::Empty + } + }), + solid_depth: 13 * 3, + hollow_depth: 13, + }; + + (this, new_stair_tile) + } + + pub fn sample(&self, tile_pos: Vec2) -> Option<&Tile> { + self.tiles.get(tile_pos - self.tile_offset) + } +} diff --git a/world/src/site/mod.rs b/world/src/site/mod.rs index 58ac2c7cf8..27f5369522 100644 --- a/world/src/site/mod.rs +++ b/world/src/site/mod.rs @@ -1,7 +1,9 @@ mod settlement; +mod dungeon; // Reexports pub use self::settlement::Settlement; +pub use self::dungeon::Dungeon; use crate::{ column::ColumnSample, @@ -9,30 +11,81 @@ use crate::{ }; use common::{ terrain::Block, - vol::{BaseVol, RectSizedVol, ReadVol, WriteVol}, + vol::{Vox, BaseVol, RectSizedVol, ReadVol, WriteVol}, }; 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 spawn_rules(&self, wpos: Vec2) -> SpawnRules { match self { - Site::Settlement(s) => s.spawn_rules(wpos) + Site::Settlement(s) => s.spawn_rules(wpos), + Site::Dungeon(d) => d.spawn_rules(wpos), } } @@ -44,6 +97,7 @@ impl Site { ) { match self { Site::Settlement(settlement) => settlement.apply_to(wpos2d, get_column, vol), + Site::Dungeon(dungeon) => dungeon.apply_to(wpos2d, get_column, vol), } } } @@ -52,10 +106,15 @@ 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 index d2b105724f..ae05edac7e 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -4,10 +4,12 @@ use common::{ terrain::{Block, BlockKind}, vol::Vox, }; -use crate::util::{RandomField, Sampler}; +use crate::{ + util::{RandomField, Sampler}, + site::BlockMask, +}; use super::{ Archetype, - BlockMask, super::skeleton::*, }; diff --git a/world/src/site/settlement/building/archetype/keep.rs b/world/src/site/settlement/building/archetype/keep.rs index 52c48f09c2..af0e95314a 100644 --- a/world/src/site/settlement/building/archetype/keep.rs +++ b/world/src/site/settlement/building/archetype/keep.rs @@ -6,9 +6,9 @@ use common::{ }; use super::{ Archetype, - BlockMask, super::skeleton::*, }; +use crate::site::BlockMask; pub struct Keep; diff --git a/world/src/site/settlement/building/archetype/mod.rs b/world/src/site/settlement/building/archetype/mod.rs index 32645a255e..d029a9c819 100644 --- a/world/src/site/settlement/building/archetype/mod.rs +++ b/world/src/site/settlement/building/archetype/mod.rs @@ -3,48 +3,8 @@ pub mod keep; use vek::*; use rand::prelude::*; -use common::{terrain::Block, vol::Vox}; use super::skeleton::*; - -#[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 - } - } -} +use crate::site::BlockMask; pub trait Archetype { type Attr; diff --git a/world/src/site/settlement/building/skeleton.rs b/world/src/site/settlement/building/skeleton.rs index d027e49521..7b30f95ecc 100644 --- a/world/src/site/settlement/building/skeleton.rs +++ b/world/src/site/settlement/building/skeleton.rs @@ -1,5 +1,5 @@ use vek::*; -use super::archetype::BlockMask; +use crate::site::BlockMask; #[derive(Copy, Clone, PartialEq, Eq)] pub enum Ori { diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index f2d0164d25..e340f96e3d 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -451,6 +451,7 @@ impl Settlement { .plot .map(|p| if let Plot::Hazard = p { true } else { false }) .unwrap_or(true), + ..SpawnRules::default() } } @@ -641,7 +642,7 @@ impl Settlement { // 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), + max: wpos2d - self.origin + vol.size_xy().map(|e| e as i32 + 1), }) { continue; } @@ -659,7 +660,7 @@ impl Settlement { continue; }; - for z in bounds.min.z.min(col.alt as i32 - 1)..bounds.max.z + 1 { + 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); From 525cba50297c87902525a28978c0a13d6650b4a7 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 16 Apr 2020 02:40:09 +0100 Subject: [PATCH 127/195] Added dungeon rooms, corridors, mazes --- assets/voxygen/shaders/fluid-vert.glsl | 2 +- assets/voxygen/shaders/terrain-vert.glsl | 2 +- world/src/civ/mod.rs | 6 +- world/src/lib.rs | 25 ++- world/src/site/dungeon/mod.rs | 263 ++++++++++++++++++----- world/src/util/grid.rs | 2 +- world/src/util/mod.rs | 4 + 7 files changed, 227 insertions(+), 77 deletions(-) diff --git a/assets/voxygen/shaders/fluid-vert.glsl b/assets/voxygen/shaders/fluid-vert.glsl index 1bf7e172a9..cb7effea26 100644 --- a/assets/voxygen/shaders/fluid-vert.glsl +++ b/assets/voxygen/shaders/fluid-vert.glsl @@ -22,7 +22,7 @@ const float EXTRA_NEG_Z = 65536.0; void main() { 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 *= min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.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 -= 25.0 * pow(distance(focus_pos.xy, f_pos.xy) / view_distance.x, 20.0); // Small waves diff --git a/assets/voxygen/shaders/terrain-vert.glsl b/assets/voxygen/shaders/terrain-vert.glsl index bfe1597ccd..8632190220 100644 --- a/assets/voxygen/shaders/terrain-vert.glsl +++ b/assets/voxygen/shaders/terrain-vert.glsl @@ -22,7 +22,7 @@ const float EXTRA_NEG_Z = 65536.0; void main() { 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 *= min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.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 -= 25.0 * pow(distance(focus_pos.xy, f_pos.xy) / view_distance.x, 20.0); f_col = vec3((uvec3(v_col_light) >> uvec3(8, 16, 24)) & uvec3(0xFFu)) / 255.0; diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 31d15cf51c..68ce27ce98 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -20,7 +20,7 @@ use common::{ use crate::{ sim::{WorldSim, SimChunk}, site::{Site as WorldSite, Settlement, Dungeon}, - util::seed_expan, + util::{seed_expan, attempt}, }; const CARDINALS: [Vec2; 4] = [ @@ -41,10 +41,6 @@ const DIAGONALS: [Vec2; 8] = [ Vec2::new(-1, -1), ]; -fn attempt(max_iters: usize, mut f: impl FnMut() -> Option) -> Option { - (0..max_iters).find_map(|_| f()) -} - const INITIAL_CIV_COUNT: usize = 32; #[derive(Default)] diff --git a/world/src/lib.rs b/world/src/lib.rs index 4e78d67454..073e0f15b6 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -67,8 +67,21 @@ 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; @@ -97,14 +110,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_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 mut chunk = TerrainChunk::new(base_z, stone, air, meta); for y in 0..TerrainChunkSize::RECT_SIZE.y as i32 { @@ -116,7 +121,7 @@ impl World { let offs = Vec2::new(x, y); let wpos2d = chunk_wpos2d + offs; - let z_cache = match zcache_grid.get(offs + grid_border) { + let z_cache = match zcache_grid.get(grid_border + offs) { Some(Some(z_cache)) => z_cache, _ => continue, }; diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index 579b2698cd..b42b09db26 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -1,7 +1,7 @@ use crate::{ column::ColumnSample, sim::{SimChunk, WorldSim}, - util::{Grid, RandomField, Sampler, StructureGen2d}, + util::{attempt, Grid, RandomField, Sampler, StructureGen2d}, site::BlockMask, }; use super::SpawnRules; @@ -53,8 +53,8 @@ impl Dungeon { alt: ctx.sim.and_then(|sim| sim.get_alt_approx(wpos)).unwrap_or(0.0) as i32, noise: RandomField::new(ctx.rng.gen()), floors: (0..6) - .scan(Vec2::zero(), |stair_tile, _| { - let (floor, st) = Floor::generate(&mut ctx, *stair_tile); + .scan(Vec2::zero(), |stair_tile, level| { + let (floor, st) = Floor::generate(&mut ctx, *stair_tile, level); *stair_tile = st; Some(floor) }) @@ -95,47 +95,18 @@ impl Dungeon { }; let surface_z = col_sample.riverless_alt.floor() as i32; - let make_staircase = |pos: Vec3, radius: f32, inner_radius: f32, stretch| { - 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 + pos.z as f32) % stretch < 3.0 - || (pos.xy().magnitude_squared() as f32) < inner_radius.powf(2.0) - { - BlockMask::new(Block::new(BlockKind::Normal, Rgb::new(150, 150, 175)), 5) - } else { - BlockMask::new(Block::empty(), 1) - } - } else { - BlockMask::nothing() - } - }; - let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE)); let tile_center = tile_pos * TILE_SIZE + TILE_SIZE / 2; - let mut z = self.alt + 20; + let mut z = self.alt + 6; for floor in &self.floors { - match floor.sample(tile_pos) { - Some(Tile::DownStair) | Some(Tile::Empty) => { - z -= floor.solid_depth; - for _ in 0..floor.hollow_depth { - vol.set(Vec3::new(offs.x, offs.y, z), Block::empty()); - z -= 1; - } - }, - Some(Tile::UpStair) => { - for i in 0..floor.solid_depth + floor.hollow_depth { - let rtile_pos = rpos - tile_center; - let mut block = make_staircase(Vec3::new(rtile_pos.x, rtile_pos.y, z), TILE_SIZE as f32 / 2.0, 1.5, 13.0); - if i >= floor.solid_depth { - block = block.resolve_with(BlockMask::new(Block::empty(), 1)); - } - if let Some(block) = block.finish() { - vol.set(Vec3::new(offs.x, offs.y, z), block); - } - z -= 1; - } - }, - None => z -= floor.solid_depth + floor.hollow_depth, + let mut sampler = floor.col_sampler(rpos); + + z -= floor.total_depth(); + for rz in 0..floor.total_depth() { + if let Some(block) = sampler(rz).finish() { + vol.set(Vec3::new(offs.x, offs.y, z + rz), block); + } } } } @@ -150,49 +121,223 @@ const CARDINALS: [Vec2; 4] = [ Vec2::new(-1, 0), ]; -const TILE_SIZE: i32 = 17; +const DIRS: [Vec2; 8] = [ + 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), +]; +const TILE_SIZE: i32 = 13; + +#[derive(Clone)] pub enum Tile { UpStair, DownStair, - Empty, + Room, + 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 Floor { tile_offset: Vec2, tiles: Grid, + rooms: Vec>, solid_depth: i32, hollow_depth: i32, + stair_tile: Vec2, } +const FLOOR_SIZE: Vec2 = Vec2::new(18, 18); + impl Floor { - pub fn generate(ctx: &mut GenCtx, stair_tile: Vec2) -> (Self, Vec2) { + 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 + 1, sz / 2)))) - .find(|pos| *pos != stair_tile) + .filter(|pos| *pos != stair_tile) + .take(8) + .max_by_key(|pos| (*pos - stair_tile).map(|e| e.abs()).sum()) .unwrap(); - const FLOOR_SIZE: Vec2 = Vec2::new(12, 12); let tile_offset = -FLOOR_SIZE / 2; - let this = Floor { + let mut this = Floor { tile_offset, - tiles: Grid::populate_from(FLOOR_SIZE, |pos| { - let tile_pos = tile_offset + pos; - if tile_pos == stair_tile { - Tile::UpStair - } else if tile_pos == new_stair_tile { - Tile::DownStair - } else { - Tile::Empty - } - }), - solid_depth: 13 * 3, + tiles: Grid::new(FLOOR_SIZE, Tile::Solid), + rooms: Vec::new(), + solid_depth: if level == 0 { + 80 + } else { + 13 * 2 + }, hollow_depth: 13, + stair_tile: new_stair_tile - tile_offset, }; + this.create_rooms(ctx, 10); + // Create routes between all rooms + let rooms = this.rooms.clone(); + for a in rooms.iter() { + for b in rooms.iter() { + this.create_route(ctx, a.center(), b.center(), true); + } + } + this.create_route(ctx, stair_tile - tile_offset, new_stair_tile - tile_offset, false); + + this.tiles.set(stair_tile - tile_offset, Tile::UpStair); + this.tiles.set(new_stair_tile - tile_offset, Tile::DownStair); (this, new_stair_tile) } - pub fn sample(&self, tile_pos: Vec2) -> Option<&Tile> { - self.tiles.get(tile_pos - self.tile_offset) + fn create_rooms(&mut self, ctx: &mut GenCtx, n: usize) { + let dim_limits = (3, 8); + + for _ in 0..n { + let room = match attempt(30, || { + 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 room = Rect::from((pos, Extent2::from(sz))); + let room_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.collides_with_rect(room_border))// || r.contains_point(self.stair_tile)) + { + return None; + } + + Some(room) + }) { + Some(room) => room, + None => return, + }; + + self.rooms.push(room); + + for x in 0..room.extent().w { + for y in 0..room.extent().h { + self.tiles.set(room.position() + Vec2::new(x, y), Tile::Room); + } + } + } + } + + fn create_route(&mut self, ctx: &mut GenCtx, a: Vec2, b: Vec2, optimise_longest: bool) { + let sim = &ctx.sim; + let heuristic = move |l: &Vec2| if optimise_longest { + (l.distance_squared(b) as f32).sqrt() + } else { + 100.0 - (l.distance_squared(b) as f32).sqrt() + }; + 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) => ctx.rng.gen_range(1.0, 10.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 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)); + let tile_center = tile_pos * TILE_SIZE + TILE_SIZE / 2; + + 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) -> 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 + pos.z as f32).rem_euclid(stretch) < 1.5 { + stone + } else { + empty + } + } else { + BlockMask::nothing() + } + }; + + let tunnel_dist = self.nearest_wall(rpos).map(|nearest| 1.0 - (nearest.distance_squared(rpos) as f32).sqrt() / TILE_SIZE as f32).unwrap_or(0.0); + + move |z| { + match self.tiles.get(tile_pos) { + Some(Tile::Solid) => BlockMask::nothing(), + Some(Tile::Tunnel) => { + if (z as f32) < 8.0 - 8.0 * tunnel_dist.powf(4.0) { + empty + } else { + BlockMask::nothing() + } + }, + Some(Tile::Room) | Some(Tile::DownStair) if z as f32 >= self.hollow_depth as f32 - 13.0 * tunnel_dist.powf(4.0) => BlockMask::nothing(), + Some(Tile::Room) => 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/util/grid.rs b/world/src/util/grid.rs index 198679c8af..7c57cc2050 100644 --- a/world/src/util/grid.rs +++ b/world/src/util/grid.rs @@ -17,7 +17,7 @@ impl Grid { } } - pub fn new(default_cell: T, size: Vec2) -> Self + pub fn new(size: Vec2, default_cell: T) -> Self where T: Clone, { diff --git a/world/src/util/mod.rs b/world/src/util/mod.rs index 489e3469b2..abe9d8d0e9 100644 --- a/world/src/util/mod.rs +++ b/world/src/util/mod.rs @@ -19,3 +19,7 @@ pub use self::{ structure::StructureGen2d, unit_chooser::UnitChooser, }; + +pub fn attempt(max_iters: usize, mut f: impl FnMut() -> Option) -> Option { + (0..max_iters).find_map(|_| f()) +} From c4879e991d1e2e5592c3383b9b2ac8d86389037f Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 16 Apr 2020 14:42:20 +0100 Subject: [PATCH 128/195] Enabled AO in darkness, better light attenuation --- assets/voxygen/shaders/include/light.glsl | 6 ++-- assets/voxygen/shaders/terrain-frag.glsl | 11 +++++-- assets/voxygen/shaders/terrain-vert.glsl | 4 ++- voxygen/src/mesh/segment.rs | 14 +++++---- voxygen/src/mesh/terrain.rs | 36 +++++++++++++++-------- voxygen/src/mesh/vol.rs | 26 +++++++++------- voxygen/src/render/pipelines/terrain.rs | 5 ++-- world/src/site/dungeon/mod.rs | 2 +- 8 files changed, 65 insertions(+), 39 deletions(-) diff --git a/assets/voxygen/shaders/include/light.glsl b/assets/voxygen/shaders/include/light.glsl index 1fbe9b1876..ee0920ccc6 100644 --- a/assets/voxygen/shaders/include/light.glsl +++ b/assets/voxygen/shaders/include/light.glsl @@ -25,7 +25,9 @@ vec3 illuminate(vec3 color, vec3 light, vec3 diffuse, vec3 ambience) { } float attenuation_strength(vec3 rpos) { - return 0.3 / pow(rpos.x * rpos.x + rpos.y * rpos.y + rpos.z * rpos.z, 0.5); + // 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) { @@ -43,7 +45,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/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl index 069cc01f11..21001e4f7c 100644 --- a/assets/voxygen/shaders/terrain-frag.glsl +++ b/assets/voxygen/shaders/terrain-frag.glsl @@ -6,6 +6,7 @@ in vec3 f_pos; flat in uint f_pos_norm; in vec3 f_col; in float f_light; +in float f_ao; layout (std140) uniform u_locals { @@ -29,14 +30,18 @@ void main() { // Use an array to avoid conditional branching vec3 f_norm = normals[(f_pos_norm >> 29) & 0x7u]; + float ao = pow(f_ao, 0.5) * 0.9 + 0.1; + vec3 light, diffuse_light, ambient_light; get_sun_diffuse(f_norm, time_of_day.x, light, diffuse_light, ambient_light, 1.0); float point_shadow = shadow_at(f_pos, f_norm); - diffuse_light *= f_light * point_shadow; - ambient_light *= f_light * point_shadow; + diffuse_light *= point_shadow; + ambient_light *= point_shadow; vec3 point_light = light_at(f_pos, f_norm); light += point_light; - diffuse_light += point_light; + ambient_light *= min(f_light, ao); + diffuse_light *= min(f_light, ao); + diffuse_light += point_light * ao; vec3 surf_color = illuminate(srgb_to_linear(f_col), light, diffuse_light, ambient_light); float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x); diff --git a/assets/voxygen/shaders/terrain-vert.glsl b/assets/voxygen/shaders/terrain-vert.glsl index 8632190220..a1a0bb6be9 100644 --- a/assets/voxygen/shaders/terrain-vert.glsl +++ b/assets/voxygen/shaders/terrain-vert.glsl @@ -16,6 +16,7 @@ out vec3 f_pos; flat out uint f_pos_norm; out vec3 f_col; out float f_light; +out float f_ao; const float EXTRA_NEG_Z = 65536.0; @@ -27,7 +28,8 @@ void main() { 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/voxygen/src/mesh/segment.rs b/voxygen/src/mesh/segment.rs index c88635681e..4558c88f09 100644 --- a/voxygen/src/mesh/segment.rs +++ b/voxygen/src/mesh/segment.rs @@ -31,6 +31,7 @@ impl Meshable for Segment { offs + pos.map(|e| e as f32), &[[[Rgba::from_opaque(col); 3]; 3]; 3], |origin, norm, col, ao, light| { + let ao = ao * 0.95 + 0.05; FigureVertex::new( origin, norm, @@ -39,7 +40,7 @@ impl Meshable for Segment { ) }, &{ - 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 { @@ -48,9 +49,9 @@ impl Meshable for Segment { .map(|v| v.is_empty()) .unwrap_or(true) { - 1.0 + Some(1.0) } else { - 0.0 + None }; } } @@ -84,6 +85,7 @@ impl Meshable for Segment { offs + pos.map(|e| e as f32), &[[[Rgba::from_opaque(col); 3]; 3]; 3], |origin, norm, col, ao, light| { + let ao = ao * 0.95 + 0.05; SpriteVertex::new( origin, norm, @@ -91,7 +93,7 @@ impl Meshable for Segment { ) }, &{ - 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 { @@ -100,9 +102,9 @@ impl Meshable for Segment { .map(|v| v.is_empty()) .unwrap_or(true) { - 1.0 + Some(1.0) } else { - 0.0 + None }; } } diff --git a/voxygen/src/mesh/terrain.rs b/voxygen/src/mesh/terrain.rs index 362fb81408..d3fe6dabb2 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) -> Option + '_ { const UNKNOWN: u8 = 255; const OPAQUE: u8 = 254; const SUNLIGHT: u8 = 24; @@ -189,12 +189,20 @@ fn calc_light + ReadVol + Debug>( } move |wpos| { - let pos = wpos - outer.min; - light_map - .get(lm_idx(pos.x, pos.y, pos.z)) - .filter(|l| **l != OPAQUE && **l != UNKNOWN) - .map(|l| *l as f32 / SUNLIGHT as f32) - .unwrap_or(0.0) + if vol_cached + .get(wpos) + .map(|block| block.is_opaque()) + .unwrap_or(false) + { + None + } else { + let pos = wpos - outer.min; + Some(light_map + .get(lm_idx(pos.x, pos.y, pos.z)) + .filter(|l| **l != OPAQUE && **l != UNKNOWN) + .map(|l| *l as f32 / SUNLIGHT as f32) + .unwrap_or(0.0)) + } } } @@ -212,7 +220,7 @@ impl + ReadVol + Debug> Meshable + ReadVol + Debug> Meshable + ReadVol + Debug> Meshable + ReadVol + Debug> Meshable + ReadVol + Debug> Meshable, dirs: &[Vec3], - darknesses: &[[[f32; 3]; 3]; 3], + darknesses: &[[[Option; 3]; 3]; 3], ) -> Vec4<(f32, f32)> { dirs.windows(2) .map(|offs| { @@ -23,7 +23,7 @@ fn get_ao_quad( .get_unchecked(pos.z) .get_unchecked(pos.y) .get_unchecked(pos.x) - <= &0.0 + .is_none() } }; @@ -41,17 +41,21 @@ 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) = unsafe { darknesses + .get_unchecked(dark_pos.z as usize) + .get_unchecked(dark_pos.y as usize) + .get_unchecked(dark_pos.x as usize) } + { + darkness += dark; + total += 1.0; + } } } + let darkness = darkness / total.max(1.0); ( darkness, @@ -60,7 +64,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 } }, ) }) @@ -112,7 +116,7 @@ fn create_quad, Vec3, Rgb, f32, f32) -> P let darkness = darkness_ao.map(|e| e.0); let ao = darkness_ao.map(|e| e.1); - let ao_map = ao * 0.85 + 0.15; + 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]) @@ -151,7 +155,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/pipelines/terrain.rs b/voxygen/src/render/pipelines/terrain.rs index 48281f616a..9d6f884ff4 100644 --- a/voxygen/src/render/pipelines/terrain.rs +++ b/voxygen/src/render/pipelines/terrain.rs @@ -37,7 +37,7 @@ gfx_defines! { } 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 { @@ -50,7 +50,8 @@ impl Vertex { | ((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/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index b42b09db26..cb181b8c57 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -216,7 +216,7 @@ impl Floor { // Ensure no overlap if self.rooms .iter() - .any(|r| r.collides_with_rect(room_border))// || r.contains_point(self.stair_tile)) + .any(|r| r.collides_with_rect(room_border) || r.contains_point(self.stair_tile)) { return None; } From fd14223c33da8b53e8724f5b37545eb219bddc4e Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 16 Apr 2020 22:01:13 +0100 Subject: [PATCH 129/195] Basic boss spawning in dungeons, better AO --- assets/voxygen/shaders/terrain-frag.glsl | 2 +- common/src/generation.rs | 3 +- voxygen/src/mesh/segment.rs | 6 +-- world/src/lib.rs | 17 +++++++- world/src/site/dungeon/mod.rs | 50 +++++++++++++++++++++++- world/src/site/mod.rs | 13 ++++++ world/src/site/settlement/mod.rs | 10 +++++ 7 files changed, 91 insertions(+), 10 deletions(-) diff --git a/assets/voxygen/shaders/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl index 21001e4f7c..7f3a00558f 100644 --- a/assets/voxygen/shaders/terrain-frag.glsl +++ b/assets/voxygen/shaders/terrain-frag.glsl @@ -30,7 +30,7 @@ void main() { // Use an array to avoid conditional branching vec3 f_norm = normals[(f_pos_norm >> 29) & 0x7u]; - float ao = pow(f_ao, 0.5) * 0.9 + 0.1; + float ao = pow(f_ao, 0.6) * 0.9 + 0.1; vec3 light, diffuse_light, ambient_light; get_sun_diffuse(f_norm, time_of_day.x, light, diffuse_light, ambient_light, 1.0); diff --git a/common/src/generation.rs b/common/src/generation.rs index c88ddc9c5c..06de93e0ab 100644 --- a/common/src/generation.rs +++ b/common/src/generation.rs @@ -17,8 +17,7 @@ pub struct ChunkSupplement { } impl ChunkSupplement { - pub fn with_entity(mut self, entity: EntityInfo) -> Self { + pub fn add_entity(&mut self, entity: EntityInfo) { self.entities.push(entity); - self } } diff --git a/voxygen/src/mesh/segment.rs b/voxygen/src/mesh/segment.rs index 4558c88f09..9669528288 100644 --- a/voxygen/src/mesh/segment.rs +++ b/voxygen/src/mesh/segment.rs @@ -31,11 +31,10 @@ impl Meshable for Segment { offs + pos.map(|e| e as f32), &[[[Rgba::from_opaque(col); 3]; 3]; 3], |origin, norm, col, ao, light| { - let ao = ao * 0.95 + 0.05; FigureVertex::new( origin, norm, - linear_to_srgb(srgb_to_linear(col) * light.min(ao)), + linear_to_srgb(srgb_to_linear(col) * light.min(ao.powf(0.5) * 0.75 + 0.25)), 0, ) }, @@ -85,11 +84,10 @@ impl Meshable for Segment { offs + pos.map(|e| e as f32), &[[[Rgba::from_opaque(col); 3]; 3]; 3], |origin, norm, col, ao, light| { - let ao = ao * 0.95 + 0.05; SpriteVertex::new( origin, norm, - linear_to_srgb(srgb_to_linear(col) * light.min(ao)), + linear_to_srgb(srgb_to_linear(col) * light.min(ao.powf(0.5) * 0.75 + 0.25)), ) }, &{ diff --git a/world/src/lib.rs b/world/src/lib.rs index 073e0f15b6..03b78ce578 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -194,12 +194,27 @@ impl World { }; if sim_chunk.contains_waypoint { - supplement = supplement.with_entity(EntityInfo { + supplement.add_entity(EntityInfo { pos: gen_entity_pos(), kind: EntityKind::Waypoint, }); } + // Apply site supplementary information + sim_chunk.sites.iter().for_each(|site| { + site.apply_supplement( + chunk_wpos2d, + |offs| { + zcache_grid + .get(grid_border + offs) + .map(Option::as_ref) + .flatten() + .map(|zc| &zc.sample) + }, + &mut supplement, + ) + }); + Ok((chunk, supplement)) } } diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index cb181b8c57..8b7199d309 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -12,6 +12,7 @@ use common::{ terrain::{Block, BlockKind, TerrainChunkSize}, vol::{BaseVol, RectSizedVol, RectVolSize, ReadVol, WriteVol, Vox}, store::{Id, Store}, + generation::{ChunkSupplement, EntityInfo, EntityKind}, }; use hashbrown::{HashMap, HashSet}; use rand::prelude::*; @@ -50,7 +51,7 @@ impl Dungeon { let mut ctx = GenCtx { sim, rng }; let mut this = Self { origin: wpos, - alt: ctx.sim.and_then(|sim| sim.get_alt_approx(wpos)).unwrap_or(0.0) as i32, + 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| { @@ -98,7 +99,7 @@ impl Dungeon { let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE)); let tile_center = tile_pos * TILE_SIZE + TILE_SIZE / 2; - let mut z = self.alt + 6; + let mut z = self.alt; for floor in &self.floors { let mut sampler = floor.col_sampler(rpos); @@ -112,6 +113,26 @@ impl Dungeon { } } } + + pub fn apply_supplement<'a>( + &'a self, + wpos2d: Vec2, + mut 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), + }; + + 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(area, origin, supplement); + } + } } const CARDINALS: [Vec2; 4] = [ @@ -271,6 +292,31 @@ impl Floor { } } + pub fn apply_supplement(&self, 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) = self.tiles.get(tile_pos) { + if tile_pos.x % 4 != 0 || tile_pos.y % 4 != 0 { continue; } // This is so bad + supplement.add_entity(EntityInfo { + pos: (origin + Vec3::from(self.tile_offset + tile_pos) * TILE_SIZE).map(|e| e as f32), + kind: EntityKind::Boss, + }); + } + } + } + } + pub fn total_depth(&self) -> i32 { self.solid_depth + self.hollow_depth } diff --git a/world/src/site/mod.rs b/world/src/site/mod.rs index 27f5369522..baebf2814a 100644 --- a/world/src/site/mod.rs +++ b/world/src/site/mod.rs @@ -12,6 +12,7 @@ use crate::{ use common::{ terrain::Block, vol::{Vox, BaseVol, RectSizedVol, ReadVol, WriteVol}, + generation::ChunkSupplement, }; use std::{fmt, sync::Arc}; use vek::*; @@ -100,6 +101,18 @@ impl Site { Site::Dungeon(dungeon) => dungeon.apply_to(wpos2d, get_column, vol), } } + + pub fn apply_supplement<'a>( + &'a self, + wpos2d: Vec2, + get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, + supplement: &mut ChunkSupplement, + ) { + match self { + Site::Settlement(settlement) => settlement.apply_supplement(wpos2d, get_column, supplement), + Site::Dungeon(dungeon) => dungeon.apply_supplement(wpos2d, get_column, supplement), + } + } } impl From for Site { diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index e340f96e3d..d78c484dd2 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -14,6 +14,7 @@ use common::{ terrain::{Block, BlockKind, TerrainChunkSize}, vol::{BaseVol, RectSizedVol, RectVolSize, ReadVol, WriteVol, Vox}, store::{Id, Store}, + generation::ChunkSupplement, }; use hashbrown::{HashMap, HashSet}; use rand::prelude::*; @@ -676,6 +677,15 @@ impl Settlement { } } + pub fn apply_supplement<'a>( + &'a self, + wpos2d: Vec2, + mut get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, + supplement: &mut ChunkSupplement, + ) { + // TODO + } + pub fn get_color(&self, pos: Vec2) -> Option> { let sample = self.land.get_at_block(pos); From 866fd1992ec24900637c33450db115248c109a9d Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 17 Apr 2020 00:07:29 +0100 Subject: [PATCH 130/195] Fixed bad AO quad flipping in dark places, bosses spawning in dungeon walls, large creatues being uncompromisingly pedantic when trying to path towards targets --- common/src/path.rs | 9 +++++---- common/src/sys/agent.rs | 12 ++++++++---- voxygen/src/mesh/vol.rs | 4 +--- world/src/site/dungeon/mod.rs | 4 ++-- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/common/src/path.rs b/common/src/path.rs index fdbc9164f0..df0d1d73a7 100644 --- a/common/src/path.rs +++ b/common/src/path.rs @@ -59,7 +59,7 @@ 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, { @@ -68,7 +68,7 @@ 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::new(1.0, 1.0, 0.3)).magnitude_squared() < traversal_tolerance.powf(2.0) { self.next_idx += 1; } @@ -93,13 +93,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; } @@ -113,7 +114,7 @@ impl Chaser { self.route = Route::default(); } - self.route.traverse(vol, pos) + self.route.traverse(vol, pos, traversal_tolerance) } } else { None diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 937af57f5d..97e78a10d1 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -1,5 +1,5 @@ use crate::{ - comp::{self, agent::Activity, Agent, Alignment, Controller, MountState, Ori, Pos, Stats}, + comp::{self, agent::Activity, Agent, Alignment, Controller, MountState, Ori, Scale, Pos, Stats}, path::Chaser, state::Time, sync::UidAllocator, @@ -23,6 +23,7 @@ impl<'a> System<'a> for Sys { Entities<'a>, ReadStorage<'a, Pos>, ReadStorage<'a, Ori>, + ReadStorage<'a, Scale>, ReadStorage<'a, Stats>, ReadExpect<'a, TerrainGrid>, ReadStorage<'a, Alignment>, @@ -39,6 +40,7 @@ impl<'a> System<'a> for Sys { entities, positions, orientations, + scales, stats, terrain, alignments, @@ -83,9 +85,11 @@ impl<'a> System<'a> for Sys { 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 SIGHT_DIST: f32 = 128.0; const MIN_ATTACK_DIST: f32 = 3.25; + let traversal_tolerance = scales.get(entity).map(|s| s.0).unwrap_or(1.0); + let mut do_idle = false; let mut choose_target = false; @@ -143,7 +147,7 @@ impl<'a> System<'a> for Sys { // 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) + chaser.chase(&*terrain, pos.0, tgt_pos.0, AVG_FOLLOW_DIST, traversal_tolerance) { inputs.move_dir = Vec2::from(bearing) .try_normalized() @@ -202,7 +206,7 @@ impl<'a> System<'a> for Sys { // Long-range chase if let Some(bearing) = - chaser.chase(&*terrain, pos.0, tgt_pos.0, 1.25) + chaser.chase(&*terrain, pos.0, tgt_pos.0, 1.25, traversal_tolerance) { inputs.move_dir = Vec2::from(bearing) .try_normalized() diff --git a/voxygen/src/mesh/vol.rs b/voxygen/src/mesh/vol.rs index e1bd84e778..ae693a9bb9 100644 --- a/voxygen/src/mesh/vol.rs +++ b/voxygen/src/mesh/vol.rs @@ -118,9 +118,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]), diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index 8b7199d309..49004c79ce 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -274,7 +274,7 @@ impl Floor { }; let transition = |a: &Vec2, b: &Vec2| match self.tiles.get(*b) { Some(Tile::Room) | Some(Tile::Tunnel) => 1.0, - Some(Tile::Solid) => ctx.rng.gen_range(1.0, 10.0), + Some(Tile::Solid) => 25.0, Some(Tile::UpStair) | Some(Tile::DownStair) => 0.0, _ => 100000.0, }; @@ -309,7 +309,7 @@ impl Floor { if let Some(Tile::Room) = self.tiles.get(tile_pos) { if tile_pos.x % 4 != 0 || tile_pos.y % 4 != 0 { continue; } // This is so bad supplement.add_entity(EntityInfo { - pos: (origin + Vec3::from(self.tile_offset + tile_pos) * TILE_SIZE).map(|e| e as f32), + pos: (origin + Vec3::from(self.tile_offset + tile_pos) * TILE_SIZE + TILE_SIZE / 2).map(|e| e as f32), kind: EntityKind::Boss, }); } From 3042a09dfdf7019a84bf8aa56f1826b80de01087 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 17 Apr 2020 16:46:08 +0100 Subject: [PATCH 131/195] Disabled warping in towns --- world/src/civ/mod.rs | 12 ++++++------ world/src/column/mod.rs | 2 ++ world/src/sim/mod.rs | 2 ++ world/src/site/settlement/mod.rs | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 68ce27ce98..bc881b6dae 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -66,13 +66,13 @@ impl Civs { let mut ctx = GenCtx { sim, rng: &mut rng }; for _ in 0..INITIAL_CIV_COUNT { - println!("Creating civilisation..."); + log::info!("Creating civilisation..."); if let None = this.birth_civ(&mut ctx) { - println!("Failed to find starting site for civilisation."); + log::warn!("Failed to find starting site for civilisation."); } } - for _ in 0..256 { + for _ in 0..100 { attempt(5, || { let loc = find_site_loc(&mut ctx, None)?; this.establish_site(&mut ctx, loc, |place| Site { @@ -136,6 +136,7 @@ impl Civs { chunk.alt += diff; chunk.basement += diff; chunk.rockiness = 0.0; + chunk.warp_factor = 0.0; }); } } @@ -156,10 +157,10 @@ impl Civs { .get_mut(pos) .map(|chunk| chunk.sites.push(world_site.clone())); } - println!("Placed site at {:?}", site.center); + log::info!("Placed site at {:?}", site.center); } - this.display_info(); + //this.display_info(); this } @@ -335,7 +336,6 @@ impl Civs { } fn tick(&mut self, ctx: &mut GenCtx, years: f32) { - println!("Tick!"); for site in self.sites.iter_mut() { site.simulate(years, &self.places.get(site.place).nat_res); } diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index 6de86303b8..3377bc6ca5 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -233,6 +233,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); @@ -831,6 +832,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } else { (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; diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index dab24656ea..9198d48bcb 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -1789,6 +1789,7 @@ pub struct SimChunk { pub forest_kind: ForestKind, pub spawn_rate: f32, pub river: RiverData, + pub warp_factor: f32, pub sites: Vec, pub place: Option>, @@ -2024,6 +2025,7 @@ impl SimChunk { }, spawn_rate: 1.0, river, + warp_factor: 1.0, sites: Vec::new(), place: None, diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index d78c484dd2..789ebd062d 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -579,7 +579,7 @@ impl Settlement { if let Some(color) = color { if col_sample.water_dist.map(|dist| dist > 2.0).unwrap_or(true) { - for z in -8..6 { + for z in -8..3 { let pos = Vec3::new(offs.x, offs.y, surface_z + z); if let (0, Some(block)) = (z, surface_block) { From e3ebdc56b3a2258377f2a159f320d310ab2ce3e5 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 17 Apr 2020 21:58:36 +0100 Subject: [PATCH 132/195] Fixed figure AO (still need to do sprite AO), added more control over NPC spawning, loot in dungeons --- assets/voxygen/shaders/figure-frag.glsl | 9 +- assets/voxygen/shaders/figure-vert.glsl | 4 + assets/voxygen/shaders/terrain-frag.glsl | 6 +- common/src/generation.rs | 44 +- common/src/store.rs | 10 + server/src/sys/terrain.rs | 557 ++++++++++++----------- voxygen/src/mesh/segment.rs | 7 +- voxygen/src/render/pipelines/figure.rs | 4 +- world/src/lib.rs | 26 +- world/src/site/dungeon/mod.rs | 78 ++-- world/src/util/random.rs | 4 + 11 files changed, 416 insertions(+), 333 deletions(-) diff --git a/assets/voxygen/shaders/figure-frag.glsl b/assets/voxygen/shaders/figure-frag.glsl index df2439e47d..ee5d6d5d47 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; layout (std140) @@ -39,6 +40,12 @@ void main() { vec3 point_light = light_at(f_pos, f_norm); light += point_light; diffuse_light += point_light; + + float ao = pow(f_ao, 0.5) * 0.85 + 0.15; + + ambient_light *= ao; + diffuse_light *= ao; + vec3 surf_color = illuminate(srgb_to_linear(model_col.rgb * f_col), light, diffuse_light, ambient_light); float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x); @@ -48,7 +55,7 @@ void main() { 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) { diff --git a/assets/voxygen/shaders/figure-vert.glsl b/assets/voxygen/shaders/figure-vert.glsl index c42e5d68bc..b9fa68bf15 100644 --- a/assets/voxygen/shaders/figure-vert.glsl +++ b/assets/voxygen/shaders/figure-vert.glsl @@ -5,6 +5,7 @@ in vec3 v_pos; in vec3 v_norm; in vec3 v_col; +in float v_ao; in uint v_bone_idx; layout (std140) @@ -27,6 +28,7 @@ uniform u_bones { out vec3 f_pos; out vec3 f_col; +out float f_ao; flat out vec3 f_norm; void main() { @@ -39,6 +41,8 @@ void main() { f_col = 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/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl index 7f3a00558f..9d36f02fbd 100644 --- a/assets/voxygen/shaders/terrain-frag.glsl +++ b/assets/voxygen/shaders/terrain-frag.glsl @@ -19,6 +19,10 @@ out vec4 tgt_color; #include #include +float vmin(vec3 v) { + return min(v.x, min(v.y, v.z)); +} + void main() { // First 3 normals are negative, next 3 are positive vec3 normals[6] = vec3[](vec3(-1,0,0), vec3(1,0,0), vec3(0,-1,0), vec3(0,1,0), vec3(0,0,-1), vec3(0,0,1)); @@ -30,7 +34,7 @@ void main() { // Use an array to avoid conditional branching vec3 f_norm = normals[(f_pos_norm >> 29) & 0x7u]; - float ao = pow(f_ao, 0.6) * 0.9 + 0.1; + float ao = pow(f_ao, 0.5) * 0.9 + 0.1; vec3 light, diffuse_light, ambient_light; get_sun_diffuse(f_norm, time_of_day.x, light, diffuse_light, ambient_light, 1.0); diff --git a/common/src/generation.rs b/common/src/generation.rs index 06de93e0ab..2e2035922d 100644 --- a/common/src/generation.rs +++ b/common/src/generation.rs @@ -1,14 +1,44 @@ use vek::*; - -pub enum EntityKind { - Enemy, - Boss, - Waypoint, -} +use crate::comp::{Alignment}; 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, +} + +impl EntityInfo { + pub fn at(pos: Vec3) -> Self { + Self { + pos, + is_waypoint: false, + is_giant: false, + alignment: Alignment::Wild, + } + } + + 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 + } } #[derive(Default)] diff --git a/common/src/store.rs b/common/src/store.rs index ac6e0b5f6f..5f81925c30 100644 --- a/common/src/store.rs +++ b/common/src/store.rs @@ -1,6 +1,7 @@ use std::{ fmt, hash, + ops::{Index, IndexMut}, cmp::{PartialEq, Eq}, marker::PhantomData, }; @@ -65,3 +66,12 @@ impl Store { 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/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 352ed91ecc..5bb5f10ab0 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -4,7 +4,6 @@ use common::{ assets, comp::{self, item, CharacterAbility, Item, ItemConfig, Player, Pos}, event::{EventBus, ServerEvent}, - generation::EntityKind, msg::ServerMsg, npc::{self, NPC_NAMES}, state::TerrainChanges, @@ -101,295 +100,297 @@ 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); + 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 + } - 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(..); + 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); - 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 alignment = entity.alignment; - 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, - }, + 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(..); + + 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 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", - )), - back: None, - ring: None, - neck: None, - lantern: None, - head: None, - tabard: None, - }; + 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/src/mesh/segment.rs b/voxygen/src/mesh/segment.rs index 9669528288..bf02458f28 100644 --- a/voxygen/src/mesh/segment.rs +++ b/voxygen/src/mesh/segment.rs @@ -30,11 +30,12 @@ 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.powf(0.5) * 0.75 + 0.25)), + linear_to_srgb(srgb_to_linear(col) * light), + ao, 0, ) }, @@ -83,7 +84,7 @@ 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, diff --git a/voxygen/src/render/pipelines/figure.rs b/voxygen/src/render/pipelines/figure.rs index 94a252e68c..2fa38cbc1d 100644 --- a/voxygen/src/render/pipelines/figure.rs +++ b/voxygen/src/render/pipelines/figure.rs @@ -14,6 +14,7 @@ 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", } @@ -44,11 +45,12 @@ gfx_defines! { } 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, } } diff --git a/world/src/lib.rs b/world/src/lib.rs index 03b78ce578..50137592a6 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -20,7 +20,8 @@ use crate::{ util::{Grid, Sampler}, }; use common::{ - generation::{ChunkSupplement, EntityInfo, EntityKind}, + generation::{ChunkSupplement, EntityInfo}, + comp::{Alignment}, terrain::{Block, BlockKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize}, vol::{ReadVol, RectVolSize, Vox, WriteVol}, }; @@ -173,31 +174,28 @@ impl World { (Vec3::from(chunk_wpos2d) + lpos).map(|e: i32| e as f32) + 0.5 }; + let mut rng = rand::thread_rng(); + 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(Alignment::Wild) + .do_if(rng.gen(), |e| e.into_giant()); + + vec![entity] } else { Vec::new() }, }; if sim_chunk.contains_waypoint { - supplement.add_entity(EntityInfo { - pos: gen_entity_pos(), - kind: EntityKind::Waypoint, - }); + supplement.add_entity(EntityInfo::at(gen_entity_pos()) + .into_waypoint()); } // Apply site supplementary information diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index 49004c79ce..c96e76a9e1 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -12,7 +12,8 @@ use common::{ terrain::{Block, BlockKind, TerrainChunkSize}, vol::{BaseVol, RectSizedVol, RectVolSize, ReadVol, WriteVol, Vox}, store::{Id, Store}, - generation::{ChunkSupplement, EntityInfo, EntityKind}, + generation::{ChunkSupplement, EntityInfo}, + comp::{Alignment}, }; use hashbrown::{HashMap, HashSet}; use rand::prelude::*; @@ -159,7 +160,7 @@ const TILE_SIZE: i32 = 13; pub enum Tile { UpStair, DownStair, - Room, + Room(Id), Tunnel, Solid, } @@ -169,17 +170,24 @@ impl Tile { match self { Tile::UpStair => true, Tile::DownStair => true, - Tile::Room => true, + Tile::Room(_) => true, Tile::Tunnel => true, _ => false, } } } +pub struct Room { + seed: u32, + enemies: bool, + loot_density: f32, + area: Rect, +} + pub struct Floor { tile_offset: Vec2, tiles: Grid, - rooms: Vec>, + rooms: Store, solid_depth: i32, hollow_depth: i32, stair_tile: Vec2, @@ -199,7 +207,7 @@ impl Floor { let mut this = Floor { tile_offset, tiles: Grid::new(FLOOR_SIZE, Tile::Solid), - rooms: Vec::new(), + rooms: Store::default(), solid_depth: if level == 0 { 80 } else { @@ -208,11 +216,11 @@ impl Floor { hollow_depth: 13, stair_tile: new_stair_tile - tile_offset, }; - this.create_rooms(ctx, 10); + this.create_rooms(ctx, level, 10); // Create routes between all rooms - let rooms = this.rooms.clone(); - for a in rooms.iter() { - for b in rooms.iter() { + 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(), true); } } @@ -224,35 +232,40 @@ impl Floor { (this, new_stair_tile) } - fn create_rooms(&mut self, ctx: &mut GenCtx, n: usize) { + fn create_rooms(&mut self, ctx: &mut GenCtx, level: i32, n: usize) { let dim_limits = (3, 8); for _ in 0..n { - let room = match attempt(30, || { + let area = match attempt(30, || { 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 room = Rect::from((pos, Extent2::from(sz))); - let room_border = Rect::from((pos - 1, Extent2::from(sz) + 2)); // The room, but with some personal space + 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.collides_with_rect(room_border) || r.contains_point(self.stair_tile)) + .any(|r| r.area.collides_with_rect(area_border) || r.area.contains_point(self.stair_tile)) { return None; } - Some(room) + Some(area) }) { Some(room) => room, None => return, }; - self.rooms.push(room); + let room = self.rooms.insert(Room { + seed: ctx.rng.gen(), + enemies: ctx.rng.gen(), + loot_density: level as f32 * 0.00025, + area, + }); - for x in 0..room.extent().w { - for y in 0..room.extent().h { - self.tiles.set(room.position() + Vec2::new(x, y), Tile::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(room)); } } } @@ -273,7 +286,7 @@ impl Floor { .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::Room(_)) | Some(Tile::Tunnel) => 1.0, Some(Tile::Solid) => 25.0, Some(Tile::UpStair) | Some(Tile::DownStair) => 0.0, _ => 100000.0, @@ -306,12 +319,14 @@ impl Floor { 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) = self.tiles.get(tile_pos) { - if tile_pos.x % 4 != 0 || tile_pos.y % 4 != 0 { continue; } // This is so bad - supplement.add_entity(EntityInfo { - pos: (origin + Vec3::from(self.tile_offset + tile_pos) * TILE_SIZE + TILE_SIZE / 2).map(|e| e as f32), - kind: EntityKind::Boss, - }); + if let Some(Tile::Room(room)) = self.tiles.get(tile_pos) { + let room = &self.rooms[*room]; + if room.enemies && tile_pos.x % 4 == 0 && tile_pos.y % 4 == 0 { // Bad + let entity = EntityInfo::at((origin + Vec3::from(self.tile_offset + tile_pos) * TILE_SIZE + TILE_SIZE / 2).map(|e| e as f32)) + .into_giant() + .with_alignment(Alignment::Enemy); + supplement.add_entity(entity); + } } } } @@ -372,8 +387,15 @@ impl Floor { BlockMask::nothing() } }, - Some(Tile::Room) | Some(Tile::DownStair) if z as f32 >= self.hollow_depth as f32 - 13.0 * tunnel_dist.powf(4.0) => BlockMask::nothing(), - Some(Tile::Room) => empty, + Some(Tile::Room(_)) | Some(Tile::DownStair) if 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); 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 { From 3541305ff4ee5f8aa46b6ba7a4136d043393ac2d Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 17 Apr 2020 22:18:42 +0100 Subject: [PATCH 133/195] Narrower tunnels --- world/src/site/dungeon/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index c96e76a9e1..aa0c13dad8 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -375,7 +375,9 @@ impl Floor { } }; - let tunnel_dist = self.nearest_wall(rpos).map(|nearest| 1.0 - (nearest.distance_squared(rpos) as f32).sqrt() / TILE_SIZE as f32).unwrap_or(0.0); + let wall_thickness = 3.0; + let dist_to_wall = self.nearest_wall(rpos).map(|nearest| nearest.distance(rpos) as f32).unwrap_or(TILE_SIZE as f32); + let tunnel_dist = 1.0 - (dist_to_wall.powf(2.0) - wall_thickness).max(0.0).sqrt() / TILE_SIZE as f32; move |z| { match self.tiles.get(tile_pos) { @@ -387,7 +389,7 @@ impl Floor { BlockMask::nothing() } }, - Some(Tile::Room(_)) | Some(Tile::DownStair) if z as f32 >= self.hollow_depth as f32 - 13.0 * tunnel_dist.powf(4.0) => 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) { From 7d4443cbcd048f45cc15aced50150a92b94e8005 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 17 Apr 2020 23:21:58 +0100 Subject: [PATCH 134/195] Fixed screw-up --- world/src/site/dungeon/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index aa0c13dad8..99cefc0ea9 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -376,7 +376,7 @@ impl Floor { }; let wall_thickness = 3.0; - let dist_to_wall = self.nearest_wall(rpos).map(|nearest| nearest.distance(rpos) as f32).unwrap_or(TILE_SIZE as f32); + 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.powf(2.0) - wall_thickness).max(0.0).sqrt() / TILE_SIZE as f32; move |z| { From 0fb3a115da8de34b32bb63d1b23e00f0734fd326 Mon Sep 17 00:00:00 2001 From: Monty Marz Date: Fri, 17 Apr 2020 23:29:01 +0000 Subject: [PATCH 135/195] Add more crops --- Cargo.toml | 2 +- assets/voxygen/background/bg_10.png | 3 + assets/voxygen/background/bg_11.png | 3 + .../voxel/sprite/cabbage/cabbage-0.vox | 4 +- .../voxel/sprite/cabbage/cabbage-1.vox | 4 +- .../voxel/sprite/cabbage/cabbage-2.vox | 4 +- assets/voxygen/voxel/sprite/carrot/0.vox | 3 + assets/voxygen/voxel/sprite/carrot/1.vox | 3 + assets/voxygen/voxel/sprite/carrot/2.vox | 3 + assets/voxygen/voxel/sprite/carrot/3.vox | 3 + assets/voxygen/voxel/sprite/carrot/4.vox | 3 + assets/voxygen/voxel/sprite/carrot/5.vox | 3 + assets/voxygen/voxel/sprite/flax/flax-0.vox | 3 + assets/voxygen/voxel/sprite/flax/flax-1.vox | 3 + assets/voxygen/voxel/sprite/flax/flax-2.vox | 3 + assets/voxygen/voxel/sprite/flax/flax-3.vox | 3 + assets/voxygen/voxel/sprite/flax/flax-4.vox | 3 + assets/voxygen/voxel/sprite/flax/flax-5.vox | 3 + assets/voxygen/voxel/sprite/radish/0.vox | 3 + assets/voxygen/voxel/sprite/radish/1.vox | 3 + assets/voxygen/voxel/sprite/radish/2.vox | 3 + assets/voxygen/voxel/sprite/radish/3.vox | 3 + assets/voxygen/voxel/sprite/radish/4.vox | 3 + assets/voxygen/voxel/sprite/tomato/0.vox | 3 + assets/voxygen/voxel/sprite/tomato/1.vox | 3 + assets/voxygen/voxel/sprite/tomato/2.vox | 3 + assets/voxygen/voxel/sprite/tomato/3.vox | 3 + assets/voxygen/voxel/sprite/tomato/4.vox | 3 + .../voxygen/voxel/sprite/turnip/turnip-0.vox | 3 + .../voxygen/voxel/sprite/turnip/turnip-1.vox | 3 + .../voxygen/voxel/sprite/turnip/turnip-2.vox | 3 + .../voxygen/voxel/sprite/turnip/turnip-3.vox | 3 + .../voxygen/voxel/sprite/turnip/turnip-4.vox | 3 + .../voxygen/voxel/sprite/turnip/turnip-5.vox | 3 + common/src/astar.rs | 4 +- common/src/generation.rs | 4 +- common/src/lib.rs | 2 +- common/src/store.rs | 7 +- common/src/terrain/block.rs | 22 +- voxygen/src/menu/main/ui.rs | 2 + voxygen/src/mesh/segment.rs | 4 +- voxygen/src/mesh/terrain.rs | 12 +- voxygen/src/mesh/vol.rs | 11 +- voxygen/src/scene/mod.rs | 5 +- voxygen/src/scene/terrain.rs | 157 ++++++ world/examples/water.rs | 8 +- world/src/block/mod.rs | 6 +- world/src/civ/econ.rs | 20 +- world/src/civ/mod.rs | 364 +++++++++----- world/src/column/mod.rs | 473 ++++++++++-------- world/src/lib.rs | 23 +- world/src/sim/erosion.rs | 4 +- world/src/sim/mod.rs | 23 +- world/src/site/dungeon/mod.rs | 247 +++++---- world/src/site/mod.rs | 23 +- .../settlement/building/archetype/house.rs | 301 ++++++----- .../settlement/building/archetype/keep.rs | 38 +- .../site/settlement/building/archetype/mod.rs | 8 +- world/src/site/settlement/building/mod.rs | 18 +- .../src/site/settlement/building/skeleton.rs | 50 +- world/src/site/settlement/mod.rs | 204 +++++--- 61 files changed, 1391 insertions(+), 753 deletions(-) create mode 100644 assets/voxygen/background/bg_10.png create mode 100644 assets/voxygen/background/bg_11.png create mode 100644 assets/voxygen/voxel/sprite/carrot/0.vox create mode 100644 assets/voxygen/voxel/sprite/carrot/1.vox create mode 100644 assets/voxygen/voxel/sprite/carrot/2.vox create mode 100644 assets/voxygen/voxel/sprite/carrot/3.vox create mode 100644 assets/voxygen/voxel/sprite/carrot/4.vox create mode 100644 assets/voxygen/voxel/sprite/carrot/5.vox create mode 100644 assets/voxygen/voxel/sprite/flax/flax-0.vox create mode 100644 assets/voxygen/voxel/sprite/flax/flax-1.vox create mode 100644 assets/voxygen/voxel/sprite/flax/flax-2.vox create mode 100644 assets/voxygen/voxel/sprite/flax/flax-3.vox create mode 100644 assets/voxygen/voxel/sprite/flax/flax-4.vox create mode 100644 assets/voxygen/voxel/sprite/flax/flax-5.vox create mode 100644 assets/voxygen/voxel/sprite/radish/0.vox create mode 100644 assets/voxygen/voxel/sprite/radish/1.vox create mode 100644 assets/voxygen/voxel/sprite/radish/2.vox create mode 100644 assets/voxygen/voxel/sprite/radish/3.vox create mode 100644 assets/voxygen/voxel/sprite/radish/4.vox create mode 100644 assets/voxygen/voxel/sprite/tomato/0.vox create mode 100644 assets/voxygen/voxel/sprite/tomato/1.vox create mode 100644 assets/voxygen/voxel/sprite/tomato/2.vox create mode 100644 assets/voxygen/voxel/sprite/tomato/3.vox create mode 100644 assets/voxygen/voxel/sprite/tomato/4.vox create mode 100644 assets/voxygen/voxel/sprite/turnip/turnip-0.vox create mode 100644 assets/voxygen/voxel/sprite/turnip/turnip-1.vox create mode 100644 assets/voxygen/voxel/sprite/turnip/turnip-2.vox create mode 100644 assets/voxygen/voxel/sprite/turnip/turnip-3.vox create mode 100644 assets/voxygen/voxel/sprite/turnip/turnip-4.vox create mode 100644 assets/voxygen/voxel/sprite/turnip/turnip-5.vox diff --git a/Cargo.toml b/Cargo.toml index 41d8a76f9b..98990c57d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ opt-level = 2 overflow-checks = true debug-assertions = true panic = "abort" -debug = true +debug = false codegen-units = 8 lto = false incremental = true 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/voxel/sprite/cabbage/cabbage-0.vox b/assets/voxygen/voxel/sprite/cabbage/cabbage-0.vox index fa2b836395..44e668cd82 100644 --- a/assets/voxygen/voxel/sprite/cabbage/cabbage-0.vox +++ b/assets/voxygen/voxel/sprite/cabbage/cabbage-0.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fc86c7aec7a72bbde175d6663e0d308cdc823d5eeabd84a06438a27ac4ba10ec -size 2060 +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 index e9687ef70e..34e4d5cb5e 100644 --- a/assets/voxygen/voxel/sprite/cabbage/cabbage-1.vox +++ b/assets/voxygen/voxel/sprite/cabbage/cabbage-1.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bac99ca3cfc05aa66cb7999d5e3af6467f3e0a41ba8bab4b339e37db01a3ff72 -size 2060 +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 index 0326cba8e8..da5cdbed44 100644 --- a/assets/voxygen/voxel/sprite/cabbage/cabbage-2.vox +++ b/assets/voxygen/voxel/sprite/cabbage/cabbage-2.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:89df0256d7b39a9b0a9b6349b9c8fa3885bab3ce3e14dc90f8a6a69e23d27e5c -size 2060 +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/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/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/common/src/astar.rs b/common/src/astar.rs index 3468c687ac..3ad4912733 100644 --- a/common/src/astar.rs +++ b/common/src/astar.rs @@ -139,9 +139,7 @@ impl Astar { } } - pub fn get_cheapest_cost(&self) -> Option { - self.cheapest_cost - } + 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()]; diff --git a/common/src/generation.rs b/common/src/generation.rs index 2e2035922d..383b23cfa2 100644 --- a/common/src/generation.rs +++ b/common/src/generation.rs @@ -47,7 +47,5 @@ pub struct ChunkSupplement { } impl ChunkSupplement { - pub fn add_entity(&mut self, entity: EntityInfo) { - self.entities.push(entity); - } + pub fn add_entity(&mut self, entity: EntityInfo) { self.entities.push(entity); } } diff --git a/common/src/lib.rs b/common/src/lib.rs index c9c27fc778..899578a760 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -28,13 +28,13 @@ pub mod region; pub mod spiral; pub mod state; pub mod states; +pub mod store; pub mod sync; pub mod sys; pub mod terrain; pub mod util; pub mod vol; pub mod volumes; -pub mod store; /// The networking module containing high-level wrappers of `TcpListener` and /// `TcpStream` (`PostOffice` and `PostBox` respectively) and data types used by diff --git a/common/src/store.rs b/common/src/store.rs index 5f81925c30..febad3be2d 100644 --- a/common/src/store.rs +++ b/common/src/store.rs @@ -26,9 +26,7 @@ impl fmt::Debug for Id { } } impl hash::Hash for Id { - fn hash(&self, h: &mut H) { - self.0.hash(h); - } + fn hash(&self, h: &mut H) { self.0.hash(h); } } pub struct Store { @@ -53,8 +51,7 @@ impl Store { pub fn iter_mut(&mut self) -> impl Iterator { self.items.iter_mut() } pub fn iter_ids(&self) -> impl Iterator, &T)> { - self - .items + self.items .iter() .enumerate() .map(|(i, item)| (Id(i, PhantomData), item)) diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index 4a373efe39..7e3c63937c 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -45,6 +45,10 @@ pub enum BlockKind { WheatYellow, WheatGreen, Cabbage, + Flax, + Carrot, + Tomato, + Radish, Coconut, } @@ -91,7 +95,12 @@ impl BlockKind { BlockKind::Corn => true, BlockKind::WheatYellow => true, BlockKind::WheatGreen => true, - BlockKind::Cabbage => true, + BlockKind::Cabbage => false, + BlockKind::Pumpkin => false, + BlockKind::Flax => true, + BlockKind::Carrot => true, + BlockKind::Tomato => false, + BlockKind::Radish => true, BlockKind::Coconut => true, _ => false, } @@ -142,6 +151,10 @@ impl BlockKind { BlockKind::WheatYellow => false, BlockKind::WheatGreen => false, BlockKind::Cabbage => false, + BlockKind::Flax => false, + BlockKind::Carrot => false, + BlockKind::Tomato => false, + BlockKind::Radish => false, BlockKind::Coconut => false, _ => true, } @@ -171,7 +184,7 @@ impl BlockKind { BlockKind::Mushroom => false, BlockKind::Liana => false, BlockKind::Chest => true, - BlockKind::Pumpkin => true, + BlockKind::Pumpkin => false, BlockKind::Welwitch => false, BlockKind::LingonBerry => false, BlockKind::LeafyPlant => false, @@ -183,6 +196,10 @@ impl BlockKind { BlockKind::WheatYellow => false, BlockKind::WheatGreen => false, BlockKind::Cabbage => false, + BlockKind::Flax => false, + BlockKind::Carrot => false, + BlockKind::Tomato => true, + BlockKind::Radish => false, BlockKind::Coconut => false, _ => true, } @@ -205,7 +222,6 @@ impl BlockKind { BlockKind::Velorite => true, BlockKind::VeloriteFrag => true, BlockKind::Chest => true, - BlockKind::Pumpkin => true, BlockKind::Coconut => true, _ => false, } diff --git a/voxygen/src/menu/main/ui.rs b/voxygen/src/menu/main/ui.rs index e841d304bf..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(); diff --git a/voxygen/src/mesh/segment.rs b/voxygen/src/mesh/segment.rs index bf02458f28..2d9a4b36a1 100644 --- a/voxygen/src/mesh/segment.rs +++ b/voxygen/src/mesh/segment.rs @@ -88,7 +88,9 @@ impl Meshable for Segment { SpriteVertex::new( origin, norm, - linear_to_srgb(srgb_to_linear(col) * light.min(ao.powf(0.5) * 0.75 + 0.25)), + linear_to_srgb( + srgb_to_linear(col) * light.min(ao.powf(0.5) * 0.75 + 0.25), + ), ) }, &{ diff --git a/voxygen/src/mesh/terrain.rs b/voxygen/src/mesh/terrain.rs index d3fe6dabb2..82a301040d 100644 --- a/voxygen/src/mesh/terrain.rs +++ b/voxygen/src/mesh/terrain.rs @@ -197,11 +197,13 @@ fn calc_light + ReadVol + Debug>( None } else { let pos = wpos - outer.min; - Some(light_map - .get(lm_idx(pos.x, pos.y, pos.z)) - .filter(|l| **l != OPAQUE && **l != UNKNOWN) - .map(|l| *l as f32 / SUNLIGHT as f32) - .unwrap_or(0.0)) + Some( + light_map + .get(lm_idx(pos.x, pos.y, pos.z)) + .filter(|l| **l != OPAQUE && **l != UNKNOWN) + .map(|l| *l as f32 / SUNLIGHT as f32) + .unwrap_or(0.0), + ) } } } diff --git a/voxygen/src/mesh/vol.rs b/voxygen/src/mesh/vol.rs index ae693a9bb9..90f8b11ee0 100644 --- a/voxygen/src/mesh/vol.rs +++ b/voxygen/src/mesh/vol.rs @@ -45,11 +45,12 @@ fn get_ao_quad( for x in 0..2 { for y in 0..2 { let dark_pos = shift + offs[0] * x + offs[1] * y + 1; - if let Some(dark) = unsafe { darknesses - .get_unchecked(dark_pos.z as usize) - .get_unchecked(dark_pos.y as usize) - .get_unchecked(dark_pos.x as usize) } - { + if let Some(dark) = unsafe { + darknesses + .get_unchecked(dark_pos.z as usize) + .get_unchecked(dark_pos.y as usize) + .get_unchecked(dark_pos.x as usize) + } { darkness += dark; total += 1.0; } diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 4689ea6fc2..5a0a15e4e9 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -240,7 +240,10 @@ impl Scene { ); // Tick camera for interpolation. - self.camera.update(scene_data.state.get_time(), scene_data.state.get_delta_time()); + self.camera.update( + scene_data.state.get_time(), + scene_data.state.get_delta_time(), + ); // Compute camera matrices. self.camera.compute_dependents(&*scene_data.state.terrain()); diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index a620e5b44f..c3cea733cc 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -190,6 +190,22 @@ fn sprite_config_for(kind: BlockKind) -> Option { 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::Coconut => Some(SpriteConfig { variations: 1, wind_sway: 0.0, @@ -1301,6 +1317,147 @@ impl Terrain { 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)), + ), + ( + (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), + ), + ), // Coconut ( (BlockKind::Coconut, 0), diff --git a/world/examples/water.rs b/world/examples/water.rs index 40f80a39a3..0011211c9a 100644 --- a/world/examples/water.rs +++ b/world/examples/water.rs @@ -150,10 +150,14 @@ fn main() { } if win.get_mouse_down(minifb::MouseButton::Left) { if let Some((mx, my)) = win.get_mouse_pos(minifb::MouseMode::Clamp) { - let chunk_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!("Block: ({}, {}), Chunk: ({}, {})", block_pos.x, block_pos.y, chunk_pos.x, chunk_pos.y); + println!( + "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 { diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index c52953dd02..ebbfe5ecc5 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -66,7 +66,11 @@ impl<'a> BlockGen<'a> { // Conservative range of radius: [8, 47] let radius = RandomField::new(seed + 2).get(cliff_pos3d) % 48 + 8; - if cliff_sample.water_dist.map(|d| d > radius as f32).unwrap_or(true) { + 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) diff --git a/world/src/civ/econ.rs b/world/src/civ/econ.rs index a0d3390aea..97181d94c7 100644 --- a/world/src/civ/econ.rs +++ b/world/src/civ/econ.rs @@ -1,5 +1,5 @@ -use rand::prelude::*; use super::GenCtx; +use rand::prelude::*; pub struct SellOrder { pub quantity: f32, @@ -42,25 +42,27 @@ impl Belief { pub fn buy_units<'a>( ctx: &mut GenCtx, - sellers: impl Iterator, + 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::>(); + 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))); + 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 + if quantity >= max_quantity || // We've purchased enough spent >= max_spend || // We've spent enough - order.price > max_price // Price is too high + order.price > max_price + // Price is too high { break; } else { diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index bc881b6dae..36b315c676 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -1,27 +1,23 @@ mod econ; -use std::{ - ops::Range, - hash::Hash, - fmt, +use crate::{ + sim::{SimChunk, WorldSim}, + site::{Dungeon, Settlement, Site as WorldSite}, + util::{attempt, seed_expan}, }; -use hashbrown::{HashMap, HashSet}; -use vek::*; -use rand::prelude::*; -use rand_chacha::ChaChaRng; use common::{ + astar::Astar, + path::Path, + spiral::Spiral2d, + store::{Id, Store}, terrain::TerrainChunkSize, vol::RectVolSize, - store::{Id, Store}, - path::Path, - astar::Astar, - spiral::Spiral2d, -}; -use crate::{ - sim::{WorldSim, SimChunk}, - site::{Site as WorldSite, Settlement, Dungeon}, - util::{seed_expan, attempt}, }; +use hashbrown::{HashMap, HashSet}; +use rand::prelude::*; +use rand_chacha::ChaChaRng; +use std::{fmt, hash::Hash, ops::Range}; +use vek::*; const CARDINALS: [Vec2; 4] = [ Vec2::new(1, 0), @@ -107,13 +103,15 @@ impl Civs { // Temporary! for track in this.tracks.iter() { for loc in track.path.iter() { - ctx.sim.get_mut(*loc).unwrap().place = Some(this.civs.iter().next().unwrap().homeland); + ctx.sim.get_mut(*loc).unwrap().place = + Some(this.civs.iter().next().unwrap().homeland); } } // Flatten ground around sites for site in this.sites.iter() { - if let SiteKind::Settlement = &site.kind {} else { + if let SiteKind::Settlement = &site.kind { + } else { continue; } @@ -124,9 +122,16 @@ impl Civs { 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 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; + 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 @@ -147,12 +152,20 @@ impl Civs { let wpos = site.center * Vec2::from(TerrainChunkSize::RECT_SIZE).map(|e: u32| e as i32); let world_site = match &site.kind { - SiteKind::Settlement => WorldSite::from(Settlement::generate(wpos, Some(ctx.sim), ctx.rng)), - SiteKind::Dungeon => WorldSite::from(Dungeon::generate(wpos, Some(ctx.sim), ctx.rng)), + SiteKind::Settlement => { + WorldSite::from(Settlement::generate(wpos, Some(ctx.sim), ctx.rng)) + }, + SiteKind::Dungeon => { + WorldSite::from(Dungeon::generate(wpos, Some(ctx.sim), 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)) { + 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())); @@ -167,9 +180,7 @@ impl Civs { pub fn place(&self, id: Id) -> &Place { self.places.get(id) } - pub fn sites(&self) -> impl Iterator + '_ { - self.sites.iter() - } + pub fn sites(&self) -> impl Iterator + '_ { self.sites.iter() } fn display_info(&self) { for (id, civ) in self.civs.iter_ids() { @@ -189,24 +200,39 @@ impl Civs { self.track_map .get(&a) .and_then(|dests| dests.get(&b)) - .or_else(|| self.track_map - .get(&b) - .and_then(|dests| dests.get(&a))) + .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); + 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 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 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 @@ -248,7 +274,12 @@ impl Civs { Some(civ) } - fn establish_place(&mut self, ctx: &mut GenCtx, loc: Vec2, area: Range) -> Option> { + 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); @@ -258,7 +289,13 @@ impl Civs { 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) { + if !dead.contains(&rloc) + && ctx + .sim + .get(rloc) + .map(|c| c.place.is_none()) + .unwrap_or(false) + { alive.insert(rloc); } } @@ -291,7 +328,12 @@ impl Civs { Some(place) } - fn establish_site(&mut self, ctx: &mut GenCtx, loc: Vec2, site_fn: impl FnOnce(Id) -> Site) -> Option> { + 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) { @@ -303,7 +345,8 @@ impl Civs { // Find neighbors const MAX_NEIGHBOR_DISTANCE: f32 = 250.0; - let mut nearby = self.sites + let mut nearby = self + .sites .iter_ids() .map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt())) .filter(|(p, dist)| *dist < MAX_NEIGHBOR_DISTANCE) @@ -320,10 +363,7 @@ impl Civs { .filter(|(_, route_cost)| *route_cost < cost * 3.0) .is_none() { - let track = self.tracks.insert(Track { - cost, - path, - }); + let track = self.tracks.insert(Track { cost, path }); self.track_map .entry(site) .or_default() @@ -342,15 +382,17 @@ impl Civs { // 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() { + // 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, + // 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) @@ -364,17 +406,17 @@ impl Civs { // 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 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), + // .filter(|(id, _)| site != *id && self.track_between(site, + // *id).is_some()) .map(|(_, order)| order), // max_import, // 1000000.0, // Max price TODO // max_spend, @@ -384,9 +426,9 @@ impl Civs { // 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); - // } + // 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 { @@ -395,22 +437,31 @@ impl Civs { // 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); - // } + // + // 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)> { +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; - DIAGONALS.iter().filter(move |dir| walk_in_dir(sim, l, **dir).is_some()).map(move |dir| l + *dir) + DIAGONALS + .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 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 @@ -419,11 +470,10 @@ fn find_path(ctx: &mut GenCtx, a: Vec2, b: Vec2) -> Option<( .and_then(|path| astar.get_cheapest_cost().map(|cost| (path, cost))) } -/// Return true if travel between a location and a chunk next to it is permitted (TODO: by whom?) +/// Return true if travel between a location and a chunk next to it is permitted +/// (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) - { + if loc_suitable_for_walking(sim, a) && loc_suitable_for_walking(sim, a + dir) { let a_alt = sim.get(a)?.alt; let b_alt = sim.get(a + dir)?.alt; Some((b_alt - a_alt).abs() / 2.5) @@ -441,18 +491,22 @@ fn loc_suitable_for_walking(sim: &WorldSim, loc: Vec2) -> bool { } } -/// Return true if a site could be constructed between a location and a chunk next to it is permitted (TODO: by whom?) +/// 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) + 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?) +/// 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) + !chunk.river.is_ocean() + && !chunk.river.is_lake() + && sim + .get_gradient_approx(loc) + .map(|grad| grad < 1.0) + .unwrap_or(false) } else { false } @@ -464,10 +518,15 @@ fn find_site_loc(ctx: &mut GenCtx, near: Option<(Vec2, f32)>) -> 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), + 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), @@ -478,9 +537,14 @@ fn find_site_loc(ctx: &mut GenCtx, near: Option<(Vec2, f32)>) -> 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) - }))); + 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 } @@ -508,23 +572,36 @@ pub struct NaturalResources { 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 }; + 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 }; + 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 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>, } @@ -570,7 +647,14 @@ impl fmt::Display for Site { } 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, + "- {}: {}", + 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() { @@ -619,8 +703,8 @@ impl Site { (Some(HUNTER), vec![(GAME, 4.0)]), (Some(FARMER), vec![(WHEAT, 4.0)]), ] - .into_iter() - .collect::>>(); + .into_iter() + .collect::>>(); // Per labourer, per year let production = Stocks::from_list(&[ @@ -634,7 +718,11 @@ impl Site { 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; + let scale = if let Some(labor) = labor { + self.labors[*labor] + } else { + 1.0 + } * self.population; for (stock, amount) in orders { demand[*stock] += *amount * scale; } @@ -647,20 +735,27 @@ impl Site { let last_exports = &self.last_exports; let stocks = &self.stocks; - self.surplus = demand.clone().map(|stock, tgt| { - supply[stock] + stocks[stock] - demand[stock] - last_exports[stock] - }); + self.surplus = demand + .clone() + .map(|stock, tgt| 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 }; + 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) + 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; @@ -668,7 +763,7 @@ impl Site { 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; + 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; @@ -680,17 +775,24 @@ impl Site { 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); + 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; + 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 + // 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 @@ -703,7 +805,9 @@ impl Site { satisfaction }) .min_by(|a, b| a.partial_cmp(b).unwrap()) - .unwrap_or_else(|| panic!("Industry {:?} requires at least one input order", labor)); + .unwrap_or_else(|| { + panic!("Industry {:?} requires at least one input order", labor) + }); for (stock, amount) in orders { // What quantity is this order requesting? @@ -733,7 +837,11 @@ impl Site { // 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 }; + let birth_rate = if self.surplus[FOOD] > 0.0 { + NATURAL_BIRTH_RATE + } else { + 0.0 + }; self.population += years * self.population * (birth_rate - DEATH_RATE); } } @@ -757,13 +865,7 @@ 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, -]; +const TRADE_STOCKS: [Stock; 5] = [FLOUR, MEAT, FOOD, WOOD, STONE]; #[derive(Debug, Clone)] struct TradeState { @@ -795,8 +897,10 @@ pub struct MapVec { } impl MapVec { - pub fn from_list<'a>(i: impl IntoIterator) -> Self - where K: 'a, T: 'a + pub fn from_list<'a>(i: impl IntoIterator) -> Self + where + K: 'a, + T: 'a, { Self { entries: i.into_iter().cloned().collect(), @@ -813,39 +917,37 @@ impl MapVec { pub fn get_mut(&mut self, entry: K) -> &mut T { let default = &self.default; - self - .entries - .entry(entry) - .or_insert_with(|| default.clone()) + 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 get(&self, entry: K) -> &T { self.entries.get(&entry).unwrap_or(&self.default) } pub fn map(mut self, mut f: impl FnMut(K, T) -> U) -> MapVec { MapVec { - entries: self.entries.into_iter().map(|(s, v)| (s.clone(), f(s, v))).collect(), + entries: self + .entries + .into_iter() + .map(|(s, v)| (s.clone(), f(s, v))) + .collect(), default: U::default(), } } - pub fn iter(&self) -> impl Iterator + '_ { + pub fn iter(&self) -> impl Iterator + '_ { self.entries.iter().map(|(s, v)| (*s, v)) } - pub fn iter_mut(&mut self) -> impl Iterator + '_ { + 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 3377bc6ca5..94e993b9aa 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -1,10 +1,7 @@ use crate::{ all::ForestKind, block::StructureMeta, - sim::{ - local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, RiverKind, SimChunk, - WorldSim, - }, + sim::{local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, RiverKind, SimChunk, WorldSim}, util::{RandomPerm, Sampler, UnitChooser}, CONFIG, }; @@ -598,239 +595,270 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { river_chunk, max_border_river, max_border_river_dist, - )) = max_river + )) = + 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, 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 (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); + + 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, + ); + + Some(( + true, + Some((river_dist - river_width * 0.5) as f32), + valley_alt, + new_alt, + river_alt, + 0.0, + )) + } else { + 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 valley_alt = Lerp::lerp( - new_alt - cross_section.y.max(1.0), - new_alt - 1.0, - (river_height_factor * river_height_factor) as f32, - ); - - 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 { - 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 { + }) + .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, + - 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(( + true, + None, alt_for_river, - (downhill_water_alt.max(river_chunk.water_alt) - - river_gouge), + 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), )); - } 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(( - true, - None, - alt_for_river, - 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), - )); } - } - 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); + 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) + // 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, None, alt_for_river, downhill_water_alt, alt_for_river, 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. @@ -1106,8 +1134,13 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { ), sub_surface_color, // No growing directly on bedrock. - // 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) { + // 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 diff --git a/world/src/lib.rs b/world/src/lib.rs index 50137592a6..4775b1162b 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -4,12 +4,12 @@ mod all; mod block; +pub mod civ; mod column; pub mod config; pub mod sim; pub mod site; pub mod util; -pub mod civ; // Reexports pub use crate::config::CONFIG; @@ -72,17 +72,20 @@ impl World { 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 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, 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 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; diff --git a/world/src/sim/erosion.rs b/world/src/sim/erosion.rs index 41db868ebe..27d4f05014 100644 --- a/world/src/sim/erosion.rs +++ b/world/src/sim/erosion.rs @@ -218,9 +218,7 @@ impl RiverData { 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() - } + 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/mod.rs b/world/src/sim/mod.rs index 9198d48bcb..894dd46b14 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -24,17 +24,17 @@ pub use self::{ use crate::{ all::ForestKind, block::BlockGen, + civ::Place, column::ColumnGen, site::{Settlement, Site}, - civ::Place, util::{seed_expan, FastNoise, RandomField, Sampler, StructureGen2d}, CONFIG, }; use common::{ assets, + store::Id, terrain::{BiomeKind, TerrainChunkSize}, vol::RectVolSize, - store::Id, }; use hashbrown::HashMap; use noise::{ @@ -1305,9 +1305,7 @@ impl WorldSim { this } - pub fn get_size(&self) -> Vec2 { - WORLD_SIZE.map(|e| e as u32) - } + 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. @@ -1558,9 +1556,11 @@ 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) - }))?; + 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) @@ -1868,7 +1868,8 @@ 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). @@ -2033,7 +2034,9 @@ impl SimChunk { } } - pub fn is_underwater(&self) -> bool { self.water_alt > self.alt || 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 } diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index 99cefc0ea9..cb1f76ede8 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -1,19 +1,19 @@ +use super::SpawnRules; use crate::{ column::ColumnSample, sim::{SimChunk, WorldSim}, - util::{attempt, Grid, RandomField, Sampler, StructureGen2d}, site::BlockMask, + util::{attempt, Grid, RandomField, Sampler, StructureGen2d}, }; -use super::SpawnRules; use common::{ astar::Astar, + comp::Alignment, + generation::{ChunkSupplement, EntityInfo, EntityKind}, path::Path, spiral::Spiral2d, - terrain::{Block, BlockKind, TerrainChunkSize}, - vol::{BaseVol, RectSizedVol, RectVolSize, ReadVol, WriteVol, Vox}, store::{Id, Store}, - generation::{ChunkSupplement, EntityInfo}, - comp::{Alignment}, + terrain::{Block, BlockKind, TerrainChunkSize}, + vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol}, }; use hashbrown::{HashMap, HashSet}; use rand::prelude::*; @@ -22,16 +22,13 @@ use vek::*; impl WorldSim { 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) + 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) } } @@ -52,7 +49,11 @@ impl Dungeon { let mut ctx = GenCtx { sim, rng }; let mut this = Self { origin: wpos, - alt: ctx.sim.and_then(|sim| sim.get_alt_approx(wpos)).unwrap_or(0.0) as i32 + 6, + 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| { @@ -196,23 +197,25 @@ pub struct Floor { 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 + 1, sz / 2)))) - .filter(|pos| *pos != stair_tile) - .take(8) - .max_by_key(|pos| (*pos - stair_tile).map(|e| e.abs()).sum()) - .unwrap(); + 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 + 1, sz / 2))) + }) + .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 - }, + solid_depth: if level == 0 { 80 } else { 13 * 2 }, hollow_depth: 13, stair_tile: new_stair_tile - tile_offset, }; @@ -224,10 +227,16 @@ impl Floor { this.create_route(ctx, a.center(), b.center(), true); } } - this.create_route(ctx, stair_tile - tile_offset, new_stair_tile - tile_offset, false); + this.create_route( + ctx, + stair_tile - tile_offset, + new_stair_tile - tile_offset, + false, + ); this.tiles.set(stair_tile - tile_offset, Tile::UpStair); - this.tiles.set(new_stair_tile - tile_offset, Tile::DownStair); + this.tiles + .set(new_stair_tile - tile_offset, Tile::DownStair); (this, new_stair_tile) } @@ -238,15 +247,16 @@ impl Floor { for _ in 0..n { let area = match attempt(30, || { 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 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) || r.area.contains_point(self.stair_tile)) - { + if self.rooms.iter().any(|r| { + r.area.collides_with_rect(area_border) || r.area.contains_point(self.stair_tile) + }) { return None; } @@ -265,18 +275,27 @@ impl Floor { 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(room)); + self.tiles + .set(area.position() + Vec2::new(x, y), Tile::Room(room)); } } } } - fn create_route(&mut self, ctx: &mut GenCtx, a: Vec2, b: Vec2, optimise_longest: bool) { + fn create_route( + &mut self, + ctx: &mut GenCtx, + a: Vec2, + b: Vec2, + optimise_longest: bool, + ) { let sim = &ctx.sim; - let heuristic = move |l: &Vec2| if optimise_longest { - (l.distance_squared(b) as f32).sqrt() - } else { - 100.0 - (l.distance_squared(b) as f32).sqrt() + let heuristic = move |l: &Vec2| { + if optimise_longest { + (l.distance_squared(b) as f32).sqrt() + } else { + 100.0 - (l.distance_squared(b) as f32).sqrt() + } }; let neighbors = |l: &Vec2| { let l = *l; @@ -294,7 +313,13 @@ impl Floor { 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) + .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"); @@ -305,11 +330,19 @@ impl Floor { } } - pub fn apply_supplement(&self, 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 + pub fn apply_supplement( + &self, + 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, @@ -321,10 +354,16 @@ impl Floor { let tile_pos = Vec2::new(x, y); if let Some(Tile::Room(room)) = self.tiles.get(tile_pos) { let room = &self.rooms[*room]; - if room.enemies && tile_pos.x % 4 == 0 && tile_pos.y % 4 == 0 { // Bad - let entity = EntityInfo::at((origin + Vec3::from(self.tile_offset + tile_pos) * TILE_SIZE + TILE_SIZE / 2).map(|e| e as f32)) - .into_giant() - .with_alignment(Alignment::Enemy); + if room.enemies && tile_pos.x % 4 == 0 && tile_pos.y % 4 == 0 { + // Bad + let entity = EntityInfo::at( + (origin + + Vec3::from(self.tile_offset + tile_pos) * TILE_SIZE + + TILE_SIZE / 2) + .map(|e| e as f32), + ) + .into_giant() + .with_alignment(Alignment::Enemy); supplement.add_entity(entity); } } @@ -332,22 +371,26 @@ impl Floor { } } - pub fn total_depth(&self) -> i32 { - self.solid_depth + self.hollow_depth - } + 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)); let tile_center = tile_pos * TILE_SIZE + TILE_SIZE / 2; - DIRS - .iter() + 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, - )) + .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)) } @@ -365,7 +408,11 @@ impl Floor { 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 + pos.z as f32).rem_euclid(stretch) < 1.5 { + if ((pos.x as f32).atan2(pos.y as f32) / (f32::consts::PI * 2.0) * stretch + + pos.z as f32) + .rem_euclid(stretch) + < 1.5 + { stone } else { empty @@ -376,38 +423,54 @@ impl Floor { }; 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.powf(2.0) - wall_thickness).max(0.0).sqrt() / TILE_SIZE as f32; + 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.powf(2.0) - wall_thickness).max(0.0).sqrt() / TILE_SIZE as f32; - move |z| { - match self.tiles.get(tile_pos) { - Some(Tile::Solid) => BlockMask::nothing(), - Some(Tile::Tunnel) => { - if (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(), - } + move |z| match self.tiles.get(tile_pos) { + Some(Tile::Solid) => BlockMask::nothing(), + Some(Tile::Tunnel) => { + if (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 index baebf2814a..44775325ba 100644 --- a/world/src/site/mod.rs +++ b/world/src/site/mod.rs @@ -1,18 +1,17 @@ -mod settlement; mod dungeon; +mod settlement; // Reexports -pub use self::settlement::Settlement; -pub use self::dungeon::Dungeon; +pub use self::{dungeon::Dungeon, settlement::Settlement}; use crate::{ column::ColumnSample, util::{Grid, Sampler}, }; use common::{ - terrain::Block, - vol::{Vox, BaseVol, RectSizedVol, ReadVol, WriteVol}, generation::ChunkSupplement, + terrain::Block, + vol::{BaseVol, ReadVol, RectSizedVol, Vox, WriteVol}, }; use std::{fmt, sync::Arc}; use vek::*; @@ -24,9 +23,7 @@ pub struct BlockMask { } impl BlockMask { - pub fn new(block: Block, priority: i32) -> Self { - Self { block, priority } - } + pub fn new(block: Block, priority: i32) -> Self { Self { block, priority } } pub fn nothing() -> Self { Self { @@ -62,11 +59,7 @@ pub struct SpawnRules { } impl Default for SpawnRules { - fn default() -> Self { - Self { - trees: true, - } - } + fn default() -> Self { Self { trees: true } } } #[derive(Clone)] @@ -109,7 +102,9 @@ impl Site { supplement: &mut ChunkSupplement, ) { match self { - Site::Settlement(settlement) => settlement.apply_supplement(wpos2d, get_column, supplement), + Site::Settlement(settlement) => { + settlement.apply_supplement(wpos2d, get_column, supplement) + }, Site::Dungeon(dungeon) => dungeon.apply_supplement(wpos2d, get_column, supplement), } } diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index ae05edac7e..96c3279bc8 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -1,17 +1,14 @@ -use vek::*; -use rand::prelude::*; +use super::{super::skeleton::*, Archetype}; +use crate::{ + site::BlockMask, + util::{RandomField, Sampler}, +}; use common::{ terrain::{Block, BlockKind}, vol::Vox, }; -use crate::{ - util::{RandomField, Sampler}, - site::BlockMask, -}; -use super::{ - Archetype, - super::skeleton::*, -}; +use rand::prelude::*; +use vek::*; const COLOR_THEMES: [Rgb; 11] = [ Rgb::new(0x1D, 0x4D, 0x45), @@ -53,8 +50,21 @@ enum StoreyFill { } 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 } } + 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 { @@ -116,16 +126,21 @@ impl Archetype for House { .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 + .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(), }, @@ -155,8 +170,17 @@ impl Archetype for House { let profile = Vec2::new(bound_offset.x, z); 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 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; @@ -168,7 +192,8 @@ impl Archetype for House { 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 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 fire = BlockMask::new(Block::new(BlockKind::Ember, Rgb::white()), foundation_layer); @@ -176,13 +201,19 @@ impl Archetype for House { 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 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 { + if center_offset.map(|e| e.abs()).reduce_max() == 0 + && profile.y >= foundation_height + 1 + { return if profile.y == foundation_height + 1 { fire } else { @@ -193,7 +224,10 @@ impl Archetype for House { // 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 { + if center_offset.product() == 0 + && profile.y > foundation_height + 1 + && profile.y <= foundation_height + 3 + { return internal; } else { return foundation; @@ -201,16 +235,20 @@ impl Archetype for House { } } - if profile.y <= foundation_height && dist < width + 3 { // Foundations + if profile.y <= foundation_height && dist < width + 3 { + // Foundations if branch.attr.storey_fill.has_lower() { - if dist == width - 1 { // Floor lining + if dist == width - 1 { + // Floor lining return log.with_priority(floor_layer); - } else if dist < width - 1 && profile.y == foundation_height { // Floor + } 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 + if dist < width && profile.y < foundation_height && profile.y >= foundation_height - 3 { + // Basement return internal; } else { return foundation.with_priority(1); @@ -218,102 +256,122 @@ impl Archetype for House { } // Roofs and walls - let do_roof_wall = |profile: Vec2, width, dist, bound_offset: Vec2, roof_top, mansard| { - // Roof + 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_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); + 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); + if profile.y > roof_level { + return None; } - } - // Wall - - if dist == width && profile.y < roof_level { - 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 = if profile.y >= ceil_height { - Aabr { - min: Vec2::new(-1, ceil_height + 2), - max: Vec2::new(1, ceil_height + 5), - } - } else { - Aabr { - min: Vec2::new(2, foundation_height + 2), - max: Vec2::new(width - 2, ceil_height - 2), - } - }; - let window_bounds = Aabr { - min: (frame_bounds.min + 1).map2(frame_bounds.center(), |a, b| a.min(b)), - max: (frame_bounds.max - 1).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(internal); - } else if frame_bounds.contains_point(surface_pos) { - return Some(log.with_priority(3)); - }; - } - - // 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 + // 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 if branch.attr.storey_fill.has_upper() { // Ceiling - return Some(floor); + } else { + return Some(roof); } - } 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 - }; + // Wall + + if dist == width && profile.y < roof_level { + 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 = if profile.y >= ceil_height { + Aabr { + min: Vec2::new(-1, ceil_height + 2), + max: Vec2::new(1, ceil_height + 5), + } + } else { + Aabr { + min: Vec2::new(2, foundation_height + 2), + max: Vec2::new(width - 2, ceil_height - 2), + } + }; + let window_bounds = Aabr { + min: (frame_bounds.min + 1) + .map2(frame_bounds.center(), |a, b| a.min(b)), + max: (frame_bounds.max - 1) + .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(internal); + } else if frame_bounds.contains_point(surface_pos) { + return Some(log.with_priority(3)); + }; + } + + // 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) { + if let Some(block) = do_roof_wall( + profile, + width, + dist, + bound_offset, + roof_top, + branch.attr.mansard, + ) { cblock = cblock.resolve_with(block); } @@ -321,8 +379,15 @@ impl Archetype for House { 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); + 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); } } diff --git a/world/src/site/settlement/building/archetype/keep.rs b/world/src/site/settlement/building/archetype/keep.rs index af0e95314a..8758c67bae 100644 --- a/world/src/site/settlement/building/archetype/keep.rs +++ b/world/src/site/settlement/building/archetype/keep.rs @@ -1,14 +1,11 @@ -use vek::*; -use rand::prelude::*; +use super::{super::skeleton::*, Archetype}; +use crate::site::BlockMask; use common::{ terrain::{Block, BlockKind}, vol::Vox, }; -use super::{ - Archetype, - super::skeleton::*, -}; -use crate::site::BlockMask; +use rand::prelude::*; +use vek::*; pub struct Keep; @@ -26,13 +23,18 @@ impl Archetype for Keep { 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(), - })) + .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(), }, }; @@ -50,9 +52,8 @@ impl Archetype for Keep { ) -> 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 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 log = make_block(60, 45, 30); @@ -65,7 +66,8 @@ impl Archetype for Keep { let roof_height = 12 + width; let ceil_height = 16; - if profile.y <= 1 - (dist - width - 1).max(0) && dist < width + 3 { // Foundations + if profile.y <= 1 - (dist - width - 1).max(0) && dist < width + 3 { + // Foundations foundation } else if profile.y == ceil_height && dist < rampart_width { roof diff --git a/world/src/site/settlement/building/archetype/mod.rs b/world/src/site/settlement/building/archetype/mod.rs index d029a9c819..a40b87035c 100644 --- a/world/src/site/settlement/building/archetype/mod.rs +++ b/world/src/site/settlement/building/archetype/mod.rs @@ -1,15 +1,17 @@ pub mod house; pub mod keep; -use vek::*; -use rand::prelude::*; 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 generate(rng: &mut R) -> (Self, Skeleton) + where + Self: Sized; fn draw( &self, dist: i32, diff --git a/world/src/site/settlement/building/mod.rs b/world/src/site/settlement/building/mod.rs index 3ddc7c3f04..9b44c5316a 100644 --- a/world/src/site/settlement/building/mod.rs +++ b/world/src/site/settlement/building/mod.rs @@ -1,13 +1,13 @@ -mod skeleton; mod archetype; +mod skeleton; // Reexports pub use self::archetype::Archetype; -use vek::*; -use rand::prelude::*; use self::skeleton::*; use common::terrain::Block; +use rand::prelude::*; +use vek::*; pub type HouseBuilding = Building; @@ -19,7 +19,8 @@ pub struct Building { impl Building { pub fn generate(rng: &mut impl Rng, origin: Vec3) -> Self - where A: Sized + where + A: Sized, { let len = rng.gen_range(-8, 12).max(0); let (archetype, skel) = A::generate(rng); @@ -48,8 +49,11 @@ impl Building { pub fn sample(&self, pos: Vec3) -> Option { let rpos = pos - self.origin; - self.skel.sample_closest(rpos.into(), |dist, bound_offset, center_offset, branch| { - self.archetype.draw(dist, bound_offset, center_offset, rpos.z, branch) - }).finish() + self.skel + .sample_closest(rpos.into(), |dist, bound_offset, center_offset, branch| { + self.archetype + .draw(dist, bound_offset, center_offset, rpos.z, branch) + }) + .finish() } } diff --git a/world/src/site/settlement/building/skeleton.rs b/world/src/site/settlement/building/skeleton.rs index 7b30f95ecc..ac2cc0404b 100644 --- a/world/src/site/settlement/building/skeleton.rs +++ b/world/src/site/settlement/building/skeleton.rs @@ -1,5 +1,5 @@ -use vek::*; use crate::site::BlockMask; +use vek::*; #[derive(Copy, Clone, PartialEq, Eq)] pub enum Ori { @@ -32,7 +32,14 @@ pub struct 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)) { + 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); @@ -48,7 +55,8 @@ pub struct Skeleton { 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); + self.root + .for_each(self.ori.dir() * self.offset, self.ori, false, 0, &mut f); } pub fn bounds(&self) -> Aabr { @@ -64,24 +72,35 @@ impl Skeleton { bounds } - pub fn sample_closest(&self, pos: Vec2, mut f: impl FnMut(i32, Vec2, Vec2, &Branch) -> BlockMask) -> BlockMask { + pub fn sample_closest( + &self, + pos: Vec2, + mut f: impl FnMut(i32, Vec2, Vec2, &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 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) + 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) + pos.y - pos.y.clamped(bounds.min.y, bounds.max.y), ) - }.map(|e| e.abs()); + } + .map(|e| e.abs()); let center_offset = if ori == Ori::East { Vec2::new(pos.y - bounds.center().y, pos.x - bounds.center().x) } else { @@ -89,10 +108,13 @@ impl Skeleton { }; 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 { + 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, branch); min = min .map(|(_, bm)| (dist_locus, bm.resolve_with(new_bm))) diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 789ebd062d..b89757cc8a 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -1,20 +1,20 @@ mod building; +use self::building::HouseBuilding; +use super::SpawnRules; use crate::{ column::ColumnSample, sim::{SimChunk, WorldSim}, util::{Grid, RandomField, Sampler, StructureGen2d}, }; -use self::building::HouseBuilding; -use super::SpawnRules; use common::{ astar::Astar, + generation::ChunkSupplement, path::Path, spiral::Spiral2d, - terrain::{Block, BlockKind, TerrainChunkSize}, - vol::{BaseVol, RectSizedVol, RectVolSize, ReadVol, WriteVol, Vox}, store::{Id, Store}, - generation::ChunkSupplement, + terrain::{Block, BlockKind, TerrainChunkSize}, + vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol}, }; use hashbrown::{HashMap, HashSet}; use rand::prelude::*; @@ -74,16 +74,13 @@ pub fn center_of(p: [Vec2; 3]) -> Vec2 { 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) + 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) } } @@ -172,7 +169,8 @@ impl Settlement { 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 + || rng.gen_range(0, 16) == 0 + // Randomly consider some tiles inaccessible { self.land.set(tile, hazard); } @@ -328,35 +326,49 @@ impl Settlement { return; }; - for tile in Spiral2d::new().map(|offs| town_center + offs).take(16usize.pow(2)) { + 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)); + + 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) + || self + .land + .tile_at(tile_pos) + .map(|t| t.contains(WayKind::Path)) + .unwrap_or(true) { 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, - ))), + 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 + if self + .structures .iter() .any(|s| s.bounds_2d().collides_with_aabr(bounds)) { @@ -385,12 +397,13 @@ impl Settlement { // 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)); + // 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, + // 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)) @@ -421,11 +434,15 @@ impl Settlement { let field = self.land.new_plot(Plot::Field { farm, seed: rng.gen(), - crop: match rng.gen_range(0, 5) { + 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, }, }); @@ -447,7 +464,8 @@ impl Settlement { pub fn spawn_rules(&self, wpos: Vec2) -> SpawnRules { SpawnRules { - trees: self.land + trees: self + .land .get_at_block(wpos - self.origin) .plot .map(|p| if let Plot::Hazard = p { true } else { false }) @@ -484,19 +502,28 @@ impl Settlement { 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) + 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); + // 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, + ((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) @@ -528,10 +555,15 @@ impl Settlement { 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) => Some(Rgb::new(150, 110, 60) - .map2(Rgb::iota(), |e: u8, i: i32| e - .saturating_add((self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, i * 5)) % 16) as u8) - .saturating_sub(8))), + Some(Plot::Town) => { + Some(Rgb::new(150, 110, 60).map2(Rgb::iota(), |e: u8, i: i32| { + e.saturating_add( + (self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, i * 5)) % 16) + as u8, + ) + .saturating_sub(8) + })) + }, Some(Plot::Field { seed, crop, .. }) => { let furrow_dirs = [ Vec2::new(1, 0), @@ -542,37 +574,60 @@ impl Settlement { let furrow_dir = furrow_dirs[*seed as usize % furrow_dirs.len()]; let in_furrow = (wpos2d * furrow_dir).sum().rem_euclid(5) < 2; - let roll = |seed, n| self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, seed * 5)) % n; + let roll = |seed, n| { + self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, seed * 5)) % n + }; - 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); + 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 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::Cabbage if roll(2, 2) == 0 => { + Some(BlockKind::Cabbage) + }, + Crop::Pumpkin if roll(3, 2) == 0 => { + Some(BlockKind::Pumpkin) + }, + Crop::Flax if roll(4, 100) < 80 => Some(BlockKind::Flax), + Crop::Carrot if roll(5, 100) < 80 => { + Some(BlockKind::Carrot) + }, + Crop::Tomato if roll(6, 100) < 80 => { + Some(BlockKind::Tomato) + }, + Crop::Radish if roll(7, 100) < 80 => { + Some(BlockKind::Radish) + }, Crop::Sunflower => Some(BlockKind::Sunflower), _ => None, } - .map(|kind| Block::new(kind, Rgb::white())); + .map(|kind| Block::new(kind, Rgb::white())); } } else { if roll(0, 30) == 0 { - surface_block = Some(Block::new(BlockKind::ShortGrass, Rgb::white())); + surface_block = + Some(Block::new(BlockKind::ShortGrass, Rgb::white())); } else if roll(0, 30) == 0 { - surface_block = Some(Block::new(BlockKind::MediumGrass, Rgb::white())); + surface_block = + Some(Block::new(BlockKind::MediumGrass, Rgb::white())); } } - Some(if in_furrow { - dirt - } else { - mound - }) + Some(if in_furrow { dirt } else { mound }) }, _ => None, }; @@ -589,7 +644,10 @@ impl Settlement { vol.set(pos, Block::empty()); } } else { - vol.set(pos, Block::new(BlockKind::Normal, noisy_color(color, 4))); + vol.set( + pos, + Block::new(BlockKind::Normal, noisy_color(color, 4)), + ); } } } @@ -613,9 +671,7 @@ impl Settlement { } 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) - { + if dist / WayKind::Wall.width() < ((1.0 - z as f32 / 12.0) * 2.0).min(1.0) { vol.set( Vec3::new(offs.x, offs.y, surface_z + z), Block::new(BlockKind::Normal, color), @@ -655,13 +711,16 @@ impl Settlement { 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) { + 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 { + 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); @@ -705,10 +764,12 @@ impl Settlement { 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::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), @@ -741,6 +802,10 @@ pub enum Crop { Wheat, Cabbage, Pumpkin, + Flax, + Carrot, + Tomato, + Radish, Sunflower, } @@ -751,7 +816,11 @@ pub enum Plot { Grass, Water, Town, - Field { farm: Id, seed: u32, crop: Crop }, + Field { + farm: Id, + seed: u32, + crop: Crop, + }, } const CARDINALS: [Vec2; 4] = [ @@ -855,7 +924,8 @@ impl Land { 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 + sample.way = sample + .way .filter(|(_, d, _)| *d < dist) .or(Some((way, dist, proj_point))); } From 5229bcc8e279c729ec7f8f3d9fcea1c36a2f0e71 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 18 Apr 2020 00:31:04 +0100 Subject: [PATCH 136/195] Reduced crop densities --- world/src/site/settlement/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index b89757cc8a..54ae31e41f 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -602,14 +602,14 @@ impl Settlement { Crop::Pumpkin if roll(3, 2) == 0 => { Some(BlockKind::Pumpkin) }, - Crop::Flax if roll(4, 100) < 80 => Some(BlockKind::Flax), - Crop::Carrot if roll(5, 100) < 80 => { + Crop::Flax if roll(4, 2) == 80 => Some(BlockKind::Flax), + Crop::Carrot if roll(5, 2) == 80 => { Some(BlockKind::Carrot) }, - Crop::Tomato if roll(6, 100) < 80 => { + Crop::Tomato if roll(6, 2) == 0 => { Some(BlockKind::Tomato) }, - Crop::Radish if roll(7, 100) < 80 => { + Crop::Radish if roll(7, 2) == 0 => { Some(BlockKind::Radish) }, Crop::Sunflower => Some(BlockKind::Sunflower), From 2f96f3e0b90e6f7fe59ee27a11257fd06850e5e4 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 18 Apr 2020 00:33:41 +0100 Subject: [PATCH 137/195] Removed EntityKind for a second time because Pfau screwed up a rebase --- world/src/site/dungeon/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index cb1f76ede8..ca2b3ecebf 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -8,7 +8,7 @@ use crate::{ use common::{ astar::Astar, comp::Alignment, - generation::{ChunkSupplement, EntityInfo, EntityKind}, + generation::{ChunkSupplement, EntityInfo}, path::Path, spiral::Spiral2d, store::{Id, Store}, From aee7802d483578215e30095028e64c13c70ae473 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 18 Apr 2020 01:06:27 +0100 Subject: [PATCH 138/195] Reduced tunnel width --- world/src/site/dungeon/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index ca2b3ecebf..2cf98f3857 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -428,12 +428,12 @@ impl Floor { .map(|nearest| (nearest.distance_squared(rpos) as f32).sqrt()) .unwrap_or(TILE_SIZE as f32); let tunnel_dist = - 1.0 - (dist_to_wall.powf(2.0) - wall_thickness).max(0.0).sqrt() / TILE_SIZE as f32; + 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 (z as f32) < 8.0 - 8.0 * tunnel_dist.powf(4.0) { + if dist_to_wall < wall_thickness && (z as f32) < 8.0 - 8.0 * tunnel_dist.powf(4.0) { empty } else { BlockMask::nothing() From d0641ecbe3dfe2b808f99978e10b4fabd16bbbf7 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 18 Apr 2020 01:12:20 +0100 Subject: [PATCH 139/195] Unblocked tunnels --- world/src/site/dungeon/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index 2cf98f3857..97718b512a 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -433,7 +433,7 @@ impl Floor { 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) { + if dist_to_wall >= wall_thickness && (z as f32) < 8.0 - 8.0 * tunnel_dist.powf(4.0) { empty } else { BlockMask::nothing() From 6448c17110d525f3cabf4082fc2a0bcbbdc33dee Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 18 Apr 2020 21:26:43 +0100 Subject: [PATCH 140/195] Fixed sprite AO, overhauled entity spawning, better enemy spawning in dungeons, made agents more skilled at combat --- assets/voxygen/shaders/sprite-frag.glsl | 4 + assets/voxygen/shaders/sprite-vert.glsl | 3 + common/src/comp/agent.rs | 1 + common/src/generation.rs | 57 ++++++++- common/src/sys/agent.rs | 62 +++++++++- common/src/sys/phys.rs | 40 +++--- common/src/terrain/block.rs | 12 ++ server/src/sys/terrain.rs | 156 ++++++++++++------------ voxygen/src/mesh/segment.rs | 5 +- voxygen/src/render/pipelines/sprite.rs | 4 +- world/src/lib.rs | 46 +++---- world/src/site/dungeon/mod.rs | 61 ++++++--- world/src/site/mod.rs | 6 +- world/src/site/settlement/mod.rs | 69 ++++++++++- 14 files changed, 376 insertions(+), 150 deletions(-) diff --git a/assets/voxygen/shaders/sprite-frag.glsl b/assets/voxygen/shaders/sprite-frag.glsl index 302bd0faeb..4fd0a975f8 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; @@ -24,6 +25,9 @@ void main() { vec3 point_light = light_at(f_pos, f_norm); light += point_light; diffuse_light += point_light; + float ao = pow(f_ao, 0.5) * 0.85 + 0.15; + ambient_light *= ao; + diffuse_light *= ao; vec3 surf_color = illuminate(f_col, light, diffuse_light, ambient_light); float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x); diff --git a/assets/voxygen/shaders/sprite-vert.glsl b/assets/voxygen/shaders/sprite-vert.glsl index 39dbdb003b..45b3d60dea 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; @@ -41,6 +43,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/common/src/comp/agent.rs b/common/src/comp/agent.rs index 80965cf2cc..e3edda4dbf 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -61,6 +61,7 @@ pub enum Activity { chaser: Chaser, time: f64, been_close: bool, + powerup: f32, }, } diff --git a/common/src/generation.rs b/common/src/generation.rs index 383b23cfa2..26cdd2bc1a 100644 --- a/common/src/generation.rs +++ b/common/src/generation.rs @@ -1,11 +1,21 @@ use vek::*; -use crate::comp::{Alignment}; +use crate::{ + comp::{self, Alignment, Body, Item, humanoid}, + npc::{self, NPC_NAMES}, +}; + +pub enum EntityTemplate { + Traveller, +} pub struct EntityInfo { pub pos: Vec3, 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 { @@ -15,6 +25,9 @@ impl EntityInfo { is_waypoint: false, is_giant: false, alignment: Alignment::Wild, + body: Body::Humanoid(humanoid::Body::random()), + name: None, + main_tool: None, } } @@ -39,6 +52,37 @@ impl EntityInfo { 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)] @@ -49,3 +93,14 @@ pub struct ChunkSupplement { impl ChunkSupplement { pub fn add_entity(&mut self, entity: EntityInfo) { self.entities.push(entity); } } + +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/sys/agent.rs b/common/src/sys/agent.rs index 97e78a10d1..8e20961915 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -1,7 +1,7 @@ use crate::{ - comp::{self, agent::Activity, Agent, Alignment, Controller, MountState, Ori, Scale, Pos, Stats}, + comp::{self, agent::Activity, Agent, Alignment, Controller, MountState, Ori, Scale, Pos, Stats, Loadout, item::{ItemKind, tool::ToolKind}, CharacterState}, path::Chaser, - state::Time, + state::{Time, DeltaTime}, sync::UidAllocator, terrain::TerrainGrid, util::Dir, @@ -20,11 +20,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>, @@ -37,11 +40,14 @@ impl<'a> System<'a> for Sys { ( uid_allocator, time, + dt, entities, positions, orientations, scales, stats, + loadouts, + character_states, terrain, alignments, mut agents, @@ -49,11 +55,13 @@ 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(), @@ -100,7 +108,7 @@ 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 { @@ -165,8 +173,24 @@ impl<'a> System<'a> for Sys { target, chaser, been_close, + powerup, .. } => { + enum Tactic { + Melee, // Attack: primary/secondary + RangedPowerup, + RangedConstant, + } + + 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::RangedConstant, + _ => Tactic::Melee, + }; + if let (Some(tgt_pos), Some(tgt_stats), tgt_alignment) = ( positions.get(*target), stats.get(*target), @@ -196,10 +220,35 @@ impl<'a> System<'a> for Sys { .try_normalized() .unwrap_or(Vec2::unit_y()) * 0.7; - inputs.primary.set_state(true); + + if let Tactic::Melee = tactic { + inputs.primary.set_state(true); + } else if let Tactic::RangedPowerup = tactic { + inputs.primary.set_state(true); + } else { + inputs.move_dir = -Vec2::from(tgt_pos.0 - pos.0) + .try_normalized() + .unwrap_or(Vec2::unit_y()); + } } else if dist_sqrd < MAX_CHASE_DIST.powf(2.0) || (dist_sqrd < SIGHT_DIST.powf(2.0) && !*been_close) { + 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::RangedConstant = 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; } @@ -257,6 +306,7 @@ impl<'a> System<'a> for Sys { chaser: Chaser::default(), time: time.0, been_close: false, + powerup: 0.0, }; } } @@ -280,6 +330,7 @@ impl<'a> System<'a> for Sys { chaser: Chaser::default(), time: time.0, been_close: false, + powerup: 0.0, }; } } @@ -310,6 +361,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..c1337e374b 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,7 @@ 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(); @@ -154,14 +154,14 @@ impl<'a> System<'a> for Sys { 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) { @@ -204,22 +204,24 @@ impl<'a> System<'a> for Sys { .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()), + }, + )) + } else { + None + } }) // Determine whether the block's AABB collides with the player's AABB .filter(|(_, block_aabb)| block_aabb.collides_with_aabb(player_aabb)) diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index 7e3c63937c..b78fe69434 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -205,6 +205,18 @@ impl BlockKind { } } + pub const MAX_HEIGHT: f32 = 3.0; + + // 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::LargeCactus => 2.5, + _ => 1.0, + } + } + pub fn is_collectible(&self) -> bool { match self { BlockKind::BlueFlower => false, diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 5bb5f10ab0..d36bfb6f91 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -116,88 +116,92 @@ impl<'a> System<'a> for Sys { &body_data.species[&species].generic } - 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")( - ); + // 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 body = entity.body; + let mut name = entity.name.unwrap_or("Unnamed".to_string()); + let alignment = entity.alignment; + let main_tool = entity.main_tool; + let mut stats = comp::Stats::new(name, body); - let alignment = entity.alignment; - let active_item = - if let Some(item::ItemKind::Tool(tool)) = main.as_ref().map(|i| &i.kind) { + 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(..); - main.map(|item| comp::ItemConfig { + main_tool.map(|item| comp::ItemConfig { item, ability1: ability_drain.next(), ability2: ability_drain.next(), diff --git a/voxygen/src/mesh/segment.rs b/voxygen/src/mesh/segment.rs index 2d9a4b36a1..b49e3a4799 100644 --- a/voxygen/src/mesh/segment.rs +++ b/voxygen/src/mesh/segment.rs @@ -88,9 +88,8 @@ impl Meshable for Segment { SpriteVertex::new( origin, norm, - linear_to_srgb( - srgb_to_linear(col) * light.min(ao.powf(0.5) * 0.75 + 0.25), - ), + linear_to_srgb(srgb_to_linear(col) * light), + ao, ) }, &{ diff --git a/voxygen/src/render/pipelines/sprite.rs b/voxygen/src/render/pipelines/sprite.rs index b39ca90a17..74091e3bd5 100644 --- a/voxygen/src/render/pipelines/sprite.rs +++ b/voxygen/src/render/pipelines/sprite.rs @@ -14,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 { @@ -41,11 +42,12 @@ gfx_defines! { } 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/world/src/lib.rs b/world/src/lib.rs index 4775b1162b..8d070fbe92 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -21,7 +21,7 @@ use crate::{ }; use common::{ generation::{ChunkSupplement, EntityInfo}, - comp::{Alignment}, + comp::{self, humanoid, quadruped_medium, bird_medium, critter, quadruped_small}, terrain::{Block, BlockKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize}, vol::{ReadVol, RectVolSize, Vox, WriteVol}, }; @@ -150,25 +150,29 @@ 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, - |offs| { - zcache_grid - .get(grid_border + offs) - .map(Option::as_ref) - .flatten() - .map(|zc| &zc.sample) - }, + 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; @@ -177,8 +181,6 @@ impl World { (Vec3::from(chunk_wpos2d) + lpos).map(|e: i32| e as f32) + 0.5 }; - let mut rng = rand::thread_rng(); - const SPAWN_RATE: f32 = 0.1; const BOSS_RATE: f32 = 0.03; let mut supplement = ChunkSupplement { @@ -187,8 +189,15 @@ impl World { && !sim_chunk.is_underwater() { let entity = EntityInfo::at(gen_entity_pos()) - .with_alignment(Alignment::Wild) - .do_if(rng.gen(), |e| e.into_giant()); + .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 { @@ -204,14 +213,9 @@ impl World { // Apply site supplementary information sim_chunk.sites.iter().for_each(|site| { site.apply_supplement( + &mut rng, chunk_wpos2d, - |offs| { - zcache_grid - .get(grid_border + offs) - .map(Option::as_ref) - .flatten() - .map(|zc| &zc.sample) - }, + sample_get, &mut supplement, ) }); diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index 97718b512a..c3164e1304 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -6,9 +6,10 @@ use crate::{ util::{attempt, Grid, RandomField, Sampler, StructureGen2d}, }; use common::{ - astar::Astar, - comp::Alignment, + assets, + comp, generation::{ChunkSupplement, EntityInfo}, + astar::Astar, path::Path, spiral::Spiral2d, store::{Id, Store}, @@ -118,6 +119,7 @@ impl Dungeon { 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, @@ -132,7 +134,7 @@ impl Dungeon { for floor in &self.floors { z -= floor.total_depth(); let origin = Vec3::new(self.origin.x, self.origin.y, z); - floor.apply_supplement(area, origin, supplement); + floor.apply_supplement(rng, area, origin, supplement); } } } @@ -182,6 +184,7 @@ pub struct Room { seed: u32, enemies: bool, loot_density: f32, + enemy_density: f32, area: Rect, } @@ -242,10 +245,10 @@ impl Floor { } fn create_rooms(&mut self, ctx: &mut GenCtx, level: i32, n: usize) { - let dim_limits = (3, 8); + let dim_limits = (3, 6); for _ in 0..n { - let area = match attempt(30, || { + 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) @@ -269,7 +272,8 @@ impl Floor { let room = self.rooms.insert(Room { seed: ctx.rng.gen(), enemies: ctx.rng.gen(), - loot_density: level as f32 * 0.00025, + loot_density: 0.00005 + level as f32 * 0.00015, + enemy_density: 0.0005 + level as f32 * 0.00015, area, }); @@ -332,6 +336,7 @@ impl Floor { pub fn apply_supplement( &self, + rng: &mut impl Rng, area: Aabr, origin: Vec3, supplement: &mut ChunkSupplement, @@ -354,17 +359,39 @@ impl Floor { let tile_pos = Vec2::new(x, y); if let Some(Tile::Room(room)) = self.tiles.get(tile_pos) { let room = &self.rooms[*room]; - if room.enemies && tile_pos.x % 4 == 0 && tile_pos.y % 4 == 0 { - // Bad - let entity = EntityInfo::at( - (origin - + Vec3::from(self.tile_offset + tile_pos) * TILE_SIZE - + TILE_SIZE / 2) - .map(|e| e as f32), - ) - .into_giant() - .with_alignment(Alignment::Enemy); - supplement.add_entity(entity); + + for x in 0..TILE_SIZE { + for y in 0..TILE_SIZE { + let pos = tile_pos * TILE_SIZE + Vec2::new(x, y); + + if room.enemies && (pos.x + pos.y * TILE_SIZE * FLOOR_SIZE.x).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); + } + } } } } diff --git a/world/src/site/mod.rs b/world/src/site/mod.rs index 44775325ba..90eaee3422 100644 --- a/world/src/site/mod.rs +++ b/world/src/site/mod.rs @@ -13,6 +13,7 @@ use common::{ terrain::Block, vol::{BaseVol, ReadVol, RectSizedVol, Vox, WriteVol}, }; +use rand::Rng; use std::{fmt, sync::Arc}; use vek::*; @@ -97,15 +98,16 @@ impl Site { 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(wpos2d, get_column, supplement) + settlement.apply_supplement(rng, wpos2d, get_column, supplement) }, - Site::Dungeon(dungeon) => dungeon.apply_supplement(wpos2d, get_column, supplement), + Site::Dungeon(dungeon) => dungeon.apply_supplement(rng, wpos2d, get_column, supplement), } } } diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 54ae31e41f..ac4dd25394 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -8,8 +8,9 @@ use crate::{ util::{Grid, RandomField, Sampler, StructureGen2d}, }; use common::{ + comp::{self, humanoid, quadruped_medium, bird_medium, critter, quadruped_small}, + generation::{ChunkSupplement, EntityInfo}, astar::Astar, - generation::ChunkSupplement, path::Path, spiral::Spiral2d, store::{Id, Store}, @@ -105,6 +106,7 @@ impl Structure { } pub struct Settlement { + seed: u32, origin: Vec2, land: Land, farms: Store, @@ -130,6 +132,7 @@ 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(), @@ -480,8 +483,6 @@ impl Settlement { mut get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, vol: &mut (impl BaseVol + RectSizedVol + ReadVol + WriteVol), ) { - let rand_field = RandomField::new(0); - 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); @@ -659,7 +660,7 @@ impl Settlement { let color = Lerp::lerp( Rgb::new(130i32, 100, 0), Rgb::new(90, 70, 50), - (rand_field.get(wpos2d.into()) % 256) as f32 / 256.0, + (RandomField::new(0).get(wpos2d.into()) % 256) as f32 / 256.0, ) .map(|e| (e % 256) as u8); @@ -738,11 +739,69 @@ impl Settlement { 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, ) { - // TODO + 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 surface_z = col_sample.riverless_alt.floor() as i32; + + 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 / (32.0 * 32.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()), + }) + .with_automatic_name(); + + supplement.add_entity(entity); + } + } + } } pub fn get_color(&self, pos: Vec2) -> Option> { From 8af3187df4af75916b5003a76e6a500df125d676 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 18 Apr 2020 21:46:39 +0100 Subject: [PATCH 141/195] Made tomatoes taller --- common/src/terrain/block.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index b78fe69434..67b65ac8e8 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -212,6 +212,7 @@ impl BlockKind { // 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, _ => 1.0, } From d28f5f24fb394cd775e9b0efaf8f9f777e1db4fb Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sun, 19 Apr 2020 12:50:25 +0100 Subject: [PATCH 142/195] Made agents cleverer --- common/src/comp/agent.rs | 1 + common/src/sys/agent.rs | 80 ++++++++++++++++++++++++++-------------- 2 files changed, 53 insertions(+), 28 deletions(-) diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index e3edda4dbf..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, } diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 8e20961915..ea8c63d3de 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -1,5 +1,5 @@ use crate::{ - comp::{self, agent::Activity, Agent, Alignment, Controller, MountState, Ori, Scale, Pos, Stats, Loadout, item::{ItemKind, tool::ToolKind}, CharacterState}, + comp::{self, agent::Activity, Agent, Alignment, Controller, ControlAction, MountState, Ori, Scale, Pos, Stats, Loadout, item::{ItemKind, tool::ToolKind}, CharacterState}, path::Chaser, state::{Time, DeltaTime}, sync::UidAllocator, @@ -92,7 +92,8 @@ 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 LISTEN_DIST: f32 = 16.0; + const SEARCH_DIST: f32 = 48.0; const SIGHT_DIST: f32 = 128.0; const MIN_ATTACK_DIST: f32 = 3.25; @@ -124,7 +125,7 @@ impl<'a> System<'a> for Sys { + Vec3::from(*bearing) .try_normalized() .unwrap_or(Vec3::unit_y()) - * 1.5 + * 5.0 + Vec3::unit_z(), ) .until(|block| block.is_solid()) @@ -142,8 +143,18 @@ impl<'a> System<'a> for Sys { 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 - if thread_rng().gen::() < 0.1 { + if thread_rng().gen::() < 0.05 { choose_target = true; } }, @@ -177,9 +188,9 @@ impl<'a> System<'a> for Sys { .. } => { enum Tactic { - Melee, // Attack: primary/secondary + Melee, RangedPowerup, - RangedConstant, + Staff, } let tactic = match loadout.active_item @@ -187,7 +198,7 @@ impl<'a> System<'a> for Sys { .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::RangedConstant, + Some(ToolKind::Staff(_)) => Tactic::Staff, _ => Tactic::Melee, }; @@ -223,34 +234,40 @@ impl<'a> System<'a> for Sys { if let Tactic::Melee = tactic { inputs.primary.set_state(true); - } else if let Tactic::RangedPowerup = tactic { + } else if let Tactic::Staff = tactic { inputs.primary.set_state(true); } else { - inputs.move_dir = -Vec2::from(tgt_pos.0 - pos.0) - .try_normalized() - .unwrap_or(Vec2::unit_y()); + inputs.roll.set_state(true); } } else if dist_sqrd < MAX_CHASE_DIST.powf(2.0) || (dist_sqrd < SIGHT_DIST.powf(2.0) && !*been_close) { - 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::RangedConstant = tactic { - if !character_state.is_wield() { - inputs.primary.set_state(true); + 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); } - inputs.secondary.set_state(true); - } - - if dist_sqrd < MAX_CHASE_DIST.powf(2.0) { - *been_close = true; + if dist_sqrd < MAX_CHASE_DIST.powf(2.0) { + *been_close = true; + } } // Long-range chase @@ -290,13 +307,20 @@ 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) && (e_pos.0 - pos.0).try_normalized().map(|v| v.dot(*inputs.look_dir) > 0.3).unwrap_or(true)) + || 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); From cf986e75dcb2fbefc83f94c9c6ee79c8757b3429 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sun, 19 Apr 2020 13:12:08 +0100 Subject: [PATCH 143/195] Made civsim considerably more deterministic --- world/src/civ/mod.rs | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 36b315c676..ad060f7eef 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -52,18 +52,27 @@ pub struct Civs { pub struct GenCtx<'a, R: Rng> { sim: &'a mut WorldSim, - rng: &'a mut R, + 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 mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed)); - let mut ctx = GenCtx { sim, rng: &mut rng }; + 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 let None = this.birth_civ(&mut ctx) { + if let None = this.birth_civ(&mut ctx.reseed()) { log::warn!("Failed to find starting site for civilisation."); } } @@ -71,7 +80,7 @@ impl Civs { for _ in 0..100 { attempt(5, || { let loc = find_site_loc(&mut ctx, None)?; - this.establish_site(&mut ctx, loc, |place| Site { + this.establish_site(&mut ctx.reseed(), loc, |place| Site { kind: SiteKind::Dungeon, center: loc, place, @@ -153,10 +162,10 @@ impl Civs { let world_site = match &site.kind { SiteKind::Settlement => { - WorldSite::from(Settlement::generate(wpos, Some(ctx.sim), ctx.rng)) + WorldSite::from(Settlement::generate(wpos, Some(ctx.sim), &mut ctx.rng)) }, SiteKind::Dungeon => { - WorldSite::from(Dungeon::generate(wpos, Some(ctx.sim), ctx.rng)) + WorldSite::from(Dungeon::generate(wpos, Some(ctx.sim), &mut ctx.rng)) }, }; @@ -285,7 +294,7 @@ impl Civs { alive.insert(loc); // Fill the surrounding area - while let Some(cloc) = alive.iter().choose(ctx.rng).copied() { + 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; @@ -409,8 +418,8 @@ impl Civs { // 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, + // 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 @@ -437,7 +446,7 @@ impl Civs { // 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); } // } From 708f15915ad9a39e8be27230d6af14c2567d0e69 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sun, 19 Apr 2020 16:34:23 +0100 Subject: [PATCH 144/195] Agent adjustments, better dungeon stairwells --- common/src/sys/agent.rs | 22 ++++++---- world/src/site/dungeon/mod.rs | 79 ++++++++++++++++++----------------- 2 files changed, 54 insertions(+), 47 deletions(-) diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index ea8c63d3de..504ecb336d 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -154,7 +154,7 @@ impl<'a> System<'a> for Sys { } // Sometimes try searching for new targets - if thread_rng().gen::() < 0.05 { + if thread_rng().gen::() < 0.1 { choose_target = true; } }, @@ -240,7 +240,7 @@ impl<'a> System<'a> for Sys { 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()) @@ -264,10 +264,10 @@ impl<'a> System<'a> for Sys { inputs.secondary.set_state(true); } + } - if dist_sqrd < MAX_CHASE_DIST.powf(2.0) { - *been_close = true; - } + if dist_sqrd < MAX_CHASE_DIST.powf(2.0) { + *been_close = true; } // Long-range chase @@ -280,8 +280,9 @@ impl<'a> System<'a> for Sys { 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); } @@ -307,8 +308,11 @@ 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 - pos.0).try_normalized().map(|v| v.dot(*inputs.look_dir) > 0.3).unwrap_or(true)) - || e_pos.0.distance_squared(pos.0) < LISTEN_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 diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index c3164e1304..4fff9c4c94 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -104,9 +104,10 @@ impl Dungeon { let mut z = self.alt; for floor in &self.floors { - let mut sampler = floor.col_sampler(rpos); - 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() { vol.set(Vec3::new(offs.x, offs.y, z + rz), block); @@ -182,7 +183,6 @@ impl Tile { pub struct Room { seed: u32, - enemies: bool, loot_density: f32, enemy_density: f32, area: Rect, @@ -206,7 +206,7 @@ impl Floor { level: i32, ) -> (Self, Vec2) { let new_stair_tile = std::iter::from_fn(|| { - Some(FLOOR_SIZE.map(|sz| ctx.rng.gen_range(-sz / 2 + 1, sz / 2))) + Some(FLOOR_SIZE.map(|sz| ctx.rng.gen_range(-sz / 2 + 2, sz / 2 - 1))) }) .filter(|pos| *pos != stair_tile) .take(8) @@ -222,28 +222,46 @@ impl Floor { hollow_depth: 13, stair_tile: new_stair_tile - tile_offset, }; - this.create_rooms(ctx, level, 10); + + // 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(), true); + this.create_route(ctx, a.center(), b.center()); } } - this.create_route( - ctx, - stair_tile - tile_offset, - new_stair_tile - tile_offset, - false, - ); - - this.tiles.set(stair_tile - tile_offset, Tile::UpStair); - this.tiles - .set(new_stair_tile - tile_offset, Tile::DownStair); (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); @@ -258,31 +276,23 @@ impl Floor { // Ensure no overlap if self.rooms.iter().any(|r| { - r.area.collides_with_rect(area_border) || r.area.contains_point(self.stair_tile) + r.area.collides_with_rect(area_border) }) { return None; } Some(area) }) { - Some(room) => room, + Some(area) => area, None => return, }; - let room = self.rooms.insert(Room { + self.create_room(Room { seed: ctx.rng.gen(), - enemies: ctx.rng.gen(), loot_density: 0.00005 + level as f32 * 0.00015, enemy_density: 0.0005 + level as f32 * 0.00015, area, }); - - 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(room)); - } - } } } @@ -291,16 +301,9 @@ impl Floor { ctx: &mut GenCtx, a: Vec2, b: Vec2, - optimise_longest: bool, ) { let sim = &ctx.sim; - let heuristic = move |l: &Vec2| { - if optimise_longest { - (l.distance_squared(b) as f32).sqrt() - } else { - 100.0 - (l.distance_squared(b) as f32).sqrt() - } - }; + let heuristic = move |l: &Vec2| (l - b).map(|e| e.abs()).reduce_max() as f32; let neighbors = |l: &Vec2| { let l = *l; CARDINALS @@ -364,7 +367,7 @@ impl Floor { for y in 0..TILE_SIZE { let pos = tile_pos * TILE_SIZE + Vec2::new(x, y); - if room.enemies && (pos.x + pos.y * TILE_SIZE * FLOOR_SIZE.x).rem_euclid(room.enemy_density.recip() as i32) == 0 { + if (pos.x + pos.y * TILE_SIZE * FLOOR_SIZE.x).rem_euclid(room.enemy_density.recip() as i32) == 0 { // Bad let entity = EntityInfo::at( (origin @@ -421,7 +424,7 @@ impl Floor { .min_by_key(|nearest| rpos.distance_squared(*nearest)) } - pub fn col_sampler(&self, pos: Vec2) -> impl FnMut(i32) -> BlockMask + '_ { + 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; @@ -436,7 +439,7 @@ impl Floor { 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 - + pos.z as f32) + + (floor_z + pos.z) as f32) .rem_euclid(stretch) < 1.5 { From 4dbb6a631ff1fc22a5699067014bb96d1b47dc7c Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sun, 19 Apr 2020 18:11:24 +0100 Subject: [PATCH 145/195] Reduced enemy spawn rate in dungeons, fixed spawning --- world/src/site/dungeon/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index 4fff9c4c94..b39bd0174d 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -290,7 +290,7 @@ impl Floor { self.create_room(Room { seed: ctx.rng.gen(), loot_density: 0.00005 + level as f32 * 0.00015, - enemy_density: 0.0005 + level as f32 * 0.00015, + enemy_density: 0.0005 + level as f32 * 0.00005, area, }); } @@ -367,7 +367,8 @@ impl Floor { for y in 0..TILE_SIZE { let pos = tile_pos * TILE_SIZE + Vec2::new(x, y); - if (pos.x + pos.y * TILE_SIZE * FLOOR_SIZE.x).rem_euclid(room.enemy_density.recip() as i32) == 0 { + 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 From ca26efea473d4b088bfcad0d63cbe3e2f3a020fa Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sun, 19 Apr 2020 18:12:07 +0100 Subject: [PATCH 146/195] Better town spawn rates --- world/src/site/settlement/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index ac4dd25394..142d014cbf 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -764,7 +764,7 @@ impl Settlement { 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 / (32.0 * 32.0)) + 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) From c5efa43fa305a87d471def4e435a593b71731102 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sun, 19 Apr 2020 17:53:50 +0100 Subject: [PATCH 147/195] Reduced enemy spawn rate in dungeons, fixed spawning --- world/src/site/dungeon/mod.rs | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index b39bd0174d..e38e33b70f 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -7,9 +7,9 @@ use crate::{ }; use common::{ assets, + astar::Astar, comp, generation::{ChunkSupplement, EntityInfo}, - astar::Astar, path::Path, spiral::Spiral2d, store::{Id, Store}, @@ -237,7 +237,8 @@ impl Floor { 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.tiles + .set(new_stair_tile - tile_offset, Tile::DownStair); this.create_rooms(ctx, level, 7); // Create routes between all rooms @@ -256,7 +257,8 @@ impl Floor { 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)); + self.tiles + .set(area.position() + Vec2::new(x, y), Tile::Room(id)); } } id @@ -275,9 +277,11 @@ impl Floor { 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) - }) { + if self + .rooms + .iter() + .any(|r| r.area.collides_with_rect(area_border)) + { return None; } @@ -296,12 +300,7 @@ impl Floor { } } - fn create_route( - &mut self, - ctx: &mut GenCtx, - a: Vec2, - b: Vec2, - ) { + fn create_route(&mut self, ctx: &mut GenCtx, a: Vec2, b: Vec2) { let sim = &ctx.sim; let heuristic = move |l: &Vec2| (l - b).map(|e| e.abs()).reduce_max() as f32; let neighbors = |l: &Vec2| { @@ -367,7 +366,8 @@ impl Floor { 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; + 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( @@ -464,7 +464,8 @@ impl Floor { 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) { + if dist_to_wall >= wall_thickness && (z as f32) < 8.0 - 8.0 * tunnel_dist.powf(4.0) + { empty } else { BlockMask::nothing() From 98704a47532e17e710669d37cb810da6127f4e91 Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Sat, 18 Apr 2020 20:28:19 +0200 Subject: [PATCH 148/195] turnip and window blocks --- .../voxygen/voxel/sprite/window/window-0.vox | 3 + .../voxygen/voxel/sprite/window/window-1.vox | 3 + .../voxygen/voxel/sprite/window/window-2.vox | 3 + .../voxygen/voxel/sprite/window/window-3.vox | 3 + common/src/generation.rs | 2 + common/src/path.rs | 10 +- common/src/store.rs | 8 +- common/src/sys/agent.rs | 66 +++++--- common/src/terrain/block.rs | 12 ++ server/src/sys/terrain.rs | 142 +++++++++--------- voxygen/src/scene/terrain.rs | 93 ++++++++++++ world/src/lib.rs | 5 +- world/src/site/settlement/mod.rs | 13 +- 13 files changed, 256 insertions(+), 107 deletions(-) create mode 100644 assets/voxygen/voxel/sprite/window/window-0.vox create mode 100644 assets/voxygen/voxel/sprite/window/window-1.vox create mode 100644 assets/voxygen/voxel/sprite/window/window-2.vox create mode 100644 assets/voxygen/voxel/sprite/window/window-3.vox 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..04bdfdc2b9 --- /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:9fd60602a5c127f2bc5ac39d784750893a0deeb076c8cc7db7f6fc8c01f9c0a4 +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..f65ebd8134 --- /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:d4560a4b13fce2a288424359d2c98a3b7126d8d811cc658386283244cd133025 +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..5c19985d68 --- /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:7c9452ccb947465df5379284345e813975253232ca0123c2fc44e407d8de7ad4 +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..6e38678d7f --- /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:2b811dbf279876fd406a6e1e2261bdfdcb3f24959515270b1c4a38ed22cdc1c8 +size 1540 diff --git a/common/src/generation.rs b/common/src/generation.rs index 26cdd2bc1a..429666353e 100644 --- a/common/src/generation.rs +++ b/common/src/generation.rs @@ -1,3 +1,4 @@ +use crate::comp::Alignment; use vek::*; use crate::{ comp::{self, Alignment, Body, Item, humanoid}, @@ -8,6 +9,7 @@ pub enum EntityTemplate { Traveller, } + pub struct EntityInfo { pub pos: Vec3, pub is_waypoint: bool, // Edge case, overrides everything else diff --git a/common/src/path.rs b/common/src/path.rs index df0d1d73a7..f826d18bcb 100644 --- a/common/src/path.rs +++ b/common/src/path.rs @@ -59,7 +59,12 @@ impl Route { pub fn is_finished(&self) -> bool { self.next().is_none() } - pub fn traverse(&mut self, vol: &V, pos: Vec3, traversal_tolerance: f32) -> Option> + pub fn traverse( + &mut self, + vol: &V, + pos: Vec3, + traversal_tolerance: f32, + ) -> Option> where V: BaseVol + ReadVol, { @@ -68,7 +73,8 @@ 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() < traversal_tolerance.powf(2.0) + if ((pos - next_tgt) * Vec3::new(1.0, 1.0, 0.3)).magnitude_squared() + < traversal_tolerance.powf(2.0) { self.next_idx += 1; } diff --git a/common/src/store.rs b/common/src/store.rs index febad3be2d..983d6a156b 100644 --- a/common/src/store.rs +++ b/common/src/store.rs @@ -1,9 +1,8 @@ use std::{ - fmt, - hash, - ops::{Index, IndexMut}, - cmp::{PartialEq, Eq}, + cmp::{Eq, PartialEq}, + fmt, hash, marker::PhantomData, + ops::{Index, IndexMut}, }; pub struct Id(usize, PhantomData); @@ -66,6 +65,7 @@ impl Store { impl Index> for Store { type Output = T; + fn index(&self, id: Id) -> &Self::Output { self.get(id) } } diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 504ecb336d..1cd87e55a6 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, ControlAction, MountState, Ori, Scale, Pos, Stats, Loadout, item::{ItemKind, tool::ToolKind}, CharacterState}, + comp::{ + self, + agent::Activity, + item::{tool::ToolKind, ItemKind}, + Agent, Alignment, CharacterState, ControlAction, Controller, Loadout, MountState, Ori, Pos, + Scale, Stats, + }, path::Chaser, - state::{Time, DeltaTime}, + state::{DeltaTime, Time}, sync::UidAllocator, terrain::TerrainGrid, util::Dir, @@ -55,7 +61,17 @@ impl<'a> System<'a> for Sys { mount_states, ): Self::SystemData, ) { - for (entity, pos, ori, alignment, loadout, character_state, agent, controller, mount_state) in ( + for ( + entity, + pos, + ori, + alignment, + loadout, + character_state, + agent, + controller, + mount_state, + ) in ( &entities, &positions, &orientations, @@ -144,17 +160,17 @@ impl<'a> System<'a> for Sys { } // Put away weapon - if thread_rng().gen::() < 0.005 { + if thread_rng().gen::() < 0.005 { controller.actions.push(ControlAction::Unwield); } // Sit - if thread_rng().gen::() < 0.0035 { + if thread_rng().gen::() < 0.0035 { controller.actions.push(ControlAction::Sit); } // Sometimes try searching for new targets - if thread_rng().gen::() < 0.1 { + if thread_rng().gen::() < 0.1 { choose_target = true; } }, @@ -165,9 +181,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, traversal_tolerance) - { + 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()); @@ -193,10 +213,13 @@ impl<'a> System<'a> for Sys { 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}) - { + 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, @@ -240,13 +263,16 @@ impl<'a> System<'a> for Sys { inputs.roll.set_state(true); } } else if dist_sqrd < MAX_CHASE_DIST.powf(2.0) - || (dist_sqrd < SIGHT_DIST.powf(2.0) && (!*been_close || !matches!(tactic, Tactic::Melee))) + || (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; + .0 + .powf(2.0) + >= dist_sqrd; if can_see_tgt { if let Tactic::RangedPowerup = tactic { @@ -271,9 +297,13 @@ impl<'a> System<'a> for Sys { } // Long-range chase - if let Some(bearing) = - chaser.chase(&*terrain, pos.0, tgt_pos.0, 1.25, traversal_tolerance) - { + 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()); diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index 67b65ac8e8..900aa8fbd4 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -10,6 +10,10 @@ pub enum BlockKind { Normal, Dense, Water, + Window1, + Window2, + Window3, + Window4, LargeCactus, BarrelCactus, RoundCactus, @@ -49,6 +53,7 @@ pub enum BlockKind { Carrot, Tomato, Radish, + Turnip, Coconut, } @@ -101,6 +106,7 @@ impl BlockKind { BlockKind::Carrot => true, BlockKind::Tomato => false, BlockKind::Radish => true, + BlockKind::Turnip => true, BlockKind::Coconut => true, _ => false, } @@ -155,7 +161,12 @@ impl BlockKind { 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, _ => true, } } @@ -200,6 +211,7 @@ impl BlockKind { BlockKind::Carrot => false, BlockKind::Tomato => true, BlockKind::Radish => false, + BlockKind::Turnip => false, BlockKind::Coconut => false, _ => true, } diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index d36bfb6f91..e7c3b9b073 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -116,78 +116,76 @@ impl<'a> System<'a> for Sys { &body_data.species[&species].generic } - // 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")( - // ); + 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 mut name = entity.name.unwrap_or("Unnamed".to_string()); diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index c3cea733cc..d30fa01c02 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, @@ -206,6 +222,10 @@ fn sprite_config_for(kind: BlockKind) -> Option { 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, @@ -311,6 +331,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(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::Window2, 0), + make_model( + "voxygen.voxel.sprite.window.window-1", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::Window3, 0), + make_model( + "voxygen.voxel.sprite.window.window-2", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::Window4, 0), + make_model( + "voxygen.voxel.sprite.window.window-3", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), // Cacti ( (BlockKind::LargeCactus, 0), @@ -1423,6 +1472,7 @@ impl Terrain { (BlockKind::Tomato, 4), make_model("voxygen.voxel.sprite.tomato.4", Vec3::new(-5.5, -5.5, 0.0)), ), + // Radish ( (BlockKind::Radish, 0), make_model( @@ -1458,6 +1508,49 @@ impl Terrain { 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), diff --git a/world/src/lib.rs b/world/src/lib.rs index 8d070fbe92..fa7b3b79ef 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -20,8 +20,10 @@ use crate::{ util::{Grid, Sampler}, }; use common::{ + comp::Alignment, generation::{ChunkSupplement, EntityInfo}, comp::{self, humanoid, quadruped_medium, bird_medium, critter, quadruped_small}, + terrain::{Block, BlockKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize}, vol::{ReadVol, RectVolSize, Vox, WriteVol}, }; @@ -206,8 +208,7 @@ impl World { }; if sim_chunk.contains_waypoint { - supplement.add_entity(EntityInfo::at(gen_entity_pos()) - .into_waypoint()); + supplement.add_entity(EntityInfo::at(gen_entity_pos()).into_waypoint()); } // Apply site supplementary information diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 142d014cbf..51665670f4 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -604,15 +604,10 @@ impl Settlement { Some(BlockKind::Pumpkin) }, Crop::Flax if roll(4, 2) == 80 => Some(BlockKind::Flax), - Crop::Carrot if roll(5, 2) == 80 => { - Some(BlockKind::Carrot) - }, - Crop::Tomato if roll(6, 2) == 0 => { - Some(BlockKind::Tomato) - }, - Crop::Radish if roll(7, 2) == 0 => { - Some(BlockKind::Radish) - }, + Crop::Carrot if roll(5, 2) == 80 => 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(7, 2) == 0 => Some(BlockKind::Turnip), Crop::Sunflower => Some(BlockKind::Sunflower), _ => None, } From 49d61b33c569afc43184cb49362f958c82fb5f34 Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Sat, 18 Apr 2020 20:55:54 +0200 Subject: [PATCH 149/195] turnip --- world/src/site/settlement/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 51665670f4..38f59ea84d 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -607,7 +607,7 @@ impl Settlement { Crop::Carrot if roll(5, 2) == 80 => 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(7, 2) == 0 => Some(BlockKind::Turnip), + Crop::Turnip if roll(8, 2) == 0 => Some(BlockKind::Turnip), Crop::Sunflower => Some(BlockKind::Sunflower), _ => None, } @@ -860,6 +860,7 @@ pub enum Crop { Carrot, Tomato, Radish, + Turnip, Sunflower, } From 9572c2eaad769a6aadf6a03ccc352e1001773ba4 Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Sat, 18 Apr 2020 22:59:26 +0200 Subject: [PATCH 150/195] spawn rates --- world/src/site/settlement/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 38f59ea84d..7c229ecff8 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -603,8 +603,8 @@ impl Settlement { Crop::Pumpkin if roll(3, 2) == 0 => { Some(BlockKind::Pumpkin) }, - Crop::Flax if roll(4, 2) == 80 => Some(BlockKind::Flax), - Crop::Carrot if roll(5, 2) == 80 => Some(BlockKind::Carrot), + 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), @@ -614,10 +614,10 @@ impl Settlement { .map(|kind| Block::new(kind, Rgb::white())); } } else { - if roll(0, 30) == 0 { + if roll(0, 20) == 0 { surface_block = Some(Block::new(BlockKind::ShortGrass, Rgb::white())); - } else if roll(0, 30) == 0 { + } else if roll(1, 30) == 0 { surface_block = Some(Block::new(BlockKind::MediumGrass, Rgb::white())); } From a89b28e1178282b09f80ec7f09cd462bce0b598a Mon Sep 17 00:00:00 2001 From: jshipsey Date: Sat, 18 Apr 2020 23:56:14 -0400 Subject: [PATCH 151/195] adjusted run animation to allow villager walking --- common/src/generation.rs | 25 ++++++++----- common/src/sys/phys.rs | 7 +++- common/src/terrain/block.rs | 8 ++-- voxygen/src/anim/character/run.rs | 62 ++++++++++++++++++------------- world/src/lib.rs | 28 ++++++-------- world/src/site/settlement/mod.rs | 14 +++---- 6 files changed, 77 insertions(+), 67 deletions(-) diff --git a/common/src/generation.rs b/common/src/generation.rs index 429666353e..b39a8a2ac6 100644 --- a/common/src/generation.rs +++ b/common/src/generation.rs @@ -1,15 +1,13 @@ -use crate::comp::Alignment; -use vek::*; use crate::{ - comp::{self, Alignment, Body, Item, humanoid}, + comp::{self, humanoid, Alignment, Body, Item}, npc::{self, NPC_NAMES}, }; +use vek::*; pub enum EntityTemplate { Traveller, } - pub struct EntityInfo { pub pos: Vec3, pub is_waypoint: bool, // Edge case, overrides everything else @@ -73,15 +71,22 @@ impl EntityInfo { 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::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)), + 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() + } + .map(|s| { + if self.is_giant { + format!("Giant {}", s) + } else { + s.to_string() + } }); self } diff --git a/common/src/sys/phys.rs b/common/src/sys/phys.rs index c1337e374b..36cceb287f 100644 --- a/common/src/sys/phys.rs +++ b/common/src/sys/phys.rs @@ -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| (1 - BlockKind::MAX_HEIGHT.ceil() as i32..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(); @@ -161,7 +163,8 @@ impl<'a> System<'a> for Sys { }; let block_aabb = 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()), + 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) { diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index 900aa8fbd4..0fbcdfda50 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -58,6 +58,8 @@ pub enum BlockKind { } impl BlockKind { + pub const MAX_HEIGHT: f32 = 3.0; + pub fn is_tangible(&self) -> bool { match self { BlockKind::Air => false, @@ -217,12 +219,10 @@ impl BlockKind { } } - pub const MAX_HEIGHT: f32 = 3.0; - // 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! + // 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, diff --git a/voxygen/src/anim/character/run.rs b/voxygen/src/anim/character/run.rs index 7c475ba3bb..6b4226c01d 100644 --- a/voxygen/src/anim/character/run.rs +++ b/voxygen/src/anim/character/run.rs @@ -21,35 +21,45 @@ 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 walk = if speed > 5.0 { 1.0 } else { 0.5 }; + 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).sin()).powf(2.0 as f32))) + / (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).sin()); + * ((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(); @@ -89,8 +99,8 @@ 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); @@ -106,37 +116,37 @@ impl Animation for RunAnimation { 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); diff --git a/world/src/lib.rs b/world/src/lib.rs index fa7b3b79ef..6c580de87a 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -20,10 +20,8 @@ use crate::{ util::{Grid, Sampler}, }; use common::{ - comp::Alignment, + comp::{self, bird_medium, critter, humanoid, quadruped_medium, quadruped_small, Alignment}, generation::{ChunkSupplement, EntityInfo}, - comp::{self, humanoid, quadruped_medium, bird_medium, critter, quadruped_small}, - terrain::{Block, BlockKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize}, vol::{ReadVol, RectVolSize, Vox, WriteVol}, }; @@ -163,18 +161,19 @@ impl World { 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, - ) - }); + sim_chunk + .sites + .iter() + .for_each(|site| site.apply_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) as i32); - let mut lpos = Vec3::new(lpos2d.x, lpos2d.y, sample_get(lpos2d).map(|s| s.alt as i32 - 32).unwrap_or(0)); + 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; @@ -213,12 +212,7 @@ impl World { // Apply site supplementary information sim_chunk.sites.iter().for_each(|site| { - site.apply_supplement( - &mut rng, - chunk_wpos2d, - sample_get, - &mut supplement, - ) + site.apply_supplement(&mut rng, chunk_wpos2d, sample_get, &mut supplement) }); Ok((chunk, supplement)) diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 7c229ecff8..4f9418d35a 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -8,9 +8,9 @@ use crate::{ util::{Grid, RandomField, Sampler, StructureGen2d}, }; use common::{ - comp::{self, humanoid, quadruped_medium, bird_medium, critter, quadruped_small}, - generation::{ChunkSupplement, EntityInfo}, astar::Astar, + comp::{self, bird_medium, critter, humanoid, quadruped_medium, quadruped_small}, + generation::{ChunkSupplement, EntityInfo}, path::Path, spiral::Spiral2d, store::{Id, Store}, @@ -758,8 +758,8 @@ impl Settlement { 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)) + 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) @@ -772,8 +772,7 @@ impl Settlement { }; comp::Body::QuadrupedSmall(quadruped_small::Body::random_with( - rng, - &species, + rng, &species, )) }, 1 => { @@ -785,8 +784,7 @@ impl Settlement { }; comp::Body::BirdMedium(bird_medium::Body::random_with( - rng, - &species, + rng, &species, )) }, _ => comp::Body::Humanoid(humanoid::Body::random()), From 729912d33b1fc79f8bdee526136dad8cbb48bd55 Mon Sep 17 00:00:00 2001 From: jshipsey Date: Sun, 19 Apr 2020 02:09:58 -0400 Subject: [PATCH 152/195] corrected villager empty weapon issue --- .../voxel/biped_large_lateral_manifest.ron | 8 ++--- .../voxel/humanoid_main_weapon_manifest.ron | 4 +++ voxygen/src/anim/biped_large/idle.rs | 20 +++++------ voxygen/src/anim/biped_large/mod.rs | 28 +++++++-------- voxygen/src/anim/biped_large/run.rs | 36 +++++++++---------- voxygen/src/anim/character/run.rs | 1 - 6 files changed, 48 insertions(+), 49 deletions(-) diff --git a/assets/voxygen/voxel/biped_large_lateral_manifest.ron b/assets/voxygen/voxel/biped_large_lateral_manifest.ron index 764872c8ef..a8077141e4 100644 --- a/assets/voxygen/voxel/biped_large_lateral_manifest.ron +++ b/assets/voxygen/voxel/biped_large_lateral_manifest.ron @@ -25,11 +25,11 @@ lateral: ("npc.giant.male.leg_r"), ), foot_l: ( - offset: (-3.0, -5.5, -10.5), + offset: (-3.0, -5.5, -2.5), lateral: ("npc.giant.male.foot_l"), ), foot_r: ( - offset: (-3.0, -5.5, -10.5), + offset: (-3.0, -5.5, -2.5), lateral: ("npc.giant.male.foot_r"), ) ), @@ -59,11 +59,11 @@ lateral: ("npc.giant.female.leg_r"), ), foot_l: ( - offset: (-3.0, -5.5, -10.5), + offset: (-3.0, -5.5, -2.5), lateral: ("npc.giant.female.foot_l"), ), foot_r: ( - offset: (-3.0, -5.5, -10.5), + offset: (-3.0, -5.5, -2.5), lateral: ("npc.giant.female.foot_r"), ) ), 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/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 85611e944e..67f60ceacc 100644 --- a/voxygen/src/anim/biped_large/mod.rs +++ b/voxygen/src/anim/biped_large/mod.rs @@ -56,23 +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::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(), @@ -137,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), @@ -149,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, _) => (8.5, -1.0, 4.0), }, leg: match (body.species, body.body_type) { - (Giant, _) => (3.5, 0.0, 14.0), + (Giant, _) => (3.5, 0.0, -5.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..acbed5f302 100644 --- a/voxygen/src/anim/biped_large/run.rs +++ b/voxygen/src/anim/biped_large/run.rs @@ -17,7 +17,7 @@ impl Animation for RunAnimation { ) -> Self::Skeleton { let mut next = (*skeleton).clone(); - let lab = 14.0; + let lab = 1.0; let legl = (anim_time as f32 * lab as f32).sin(); let legr = (anim_time as f32 * lab as f32 + PI).sin(); @@ -30,9 +30,9 @@ impl Animation for RunAnimation { let footvertr = (anim_time as f32 * lab as f32 + PI * 0.4).sin().max(0.0); next.head.offset = - Vec3::new(0.0, skeleton_attr.head.0, skeleton_attr.head.1 + belt * 1.0) / 8.0; + Vec3::new(0.0, skeleton_attr.head.0, skeleton_attr.head.1 + belt * 1.0) * 1.02; next.head.ori = Quaternion::rotation_z(belt * 0.1) * Quaternion::rotation_x(0.3); - next.head.scale = Vec3::one() / 8.0; + next.head.scale = Vec3::one() * 1.02; next.upper_torso.offset = Vec3::new( 0.0, @@ -72,7 +72,7 @@ impl Animation for RunAnimation { 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.scale = Vec3::one() * 1.02; next.hand_r.offset = Vec3::new( skeleton_attr.hand.0, @@ -80,42 +80,42 @@ impl Animation for RunAnimation { 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.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; + skeleton_attr.leg.2 + legl * 0.4, + ) * 1.02; next.leg_l.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(-legl * 0.3); - 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 + belt * 0.4, - ) / 8.0; + skeleton_attr.leg.2 + legr * 0.4, + ) * 1.02; next.leg_r.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(-legr * 0.3); - 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, - skeleton_attr.foot.1 - foothoril * 4.5 - 0.5, - skeleton_attr.foot.2 + footvertl * 6.0, + skeleton_attr.foot.1 + foothoril * -4.0, + skeleton_attr.foot.2 + footvertl * 3.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_z(0.0) * Quaternion::rotation_x(0.0); 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 * -4.0, + skeleton_attr.foot.2 + footvertr * 3.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(0.0); 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.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(-0.2); next.torso.scale = Vec3::one(); next } diff --git a/voxygen/src/anim/character/run.rs b/voxygen/src/anim/character/run.rs index 6b4226c01d..64d41f5e67 100644 --- a/voxygen/src/anim/character/run.rs +++ b/voxygen/src/anim/character/run.rs @@ -24,7 +24,6 @@ impl Animation for RunAnimation { 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 walk = if speed > 5.0 { 1.0 } else { 0.5 }; let snapfoot = if speed > 5.0 { 1.1 } else { 2.0 }; let lab = 1.0; let long = (((5.0) From 43b4ae6e472a0deaea06b5e84558efb76d778360 Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Sun, 19 Apr 2020 17:48:12 +0200 Subject: [PATCH 153/195] Update block.rs --- common/src/terrain/block.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index 0fbcdfda50..2620673e94 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -10,10 +10,6 @@ pub enum BlockKind { Normal, Dense, Water, - Window1, - Window2, - Window3, - Window4, LargeCactus, BarrelCactus, RoundCactus, @@ -53,8 +49,12 @@ pub enum BlockKind { Carrot, Tomato, Radish, - Turnip, Coconut, + Turnip, + Window1, + Window2, + Window3, + Window4, } impl BlockKind { From 27dab981d2c8e421c645da7c6b948173f779d129 Mon Sep 17 00:00:00 2001 From: jshipsey Date: Sun, 19 Apr 2020 10:53:16 -0400 Subject: [PATCH 154/195] town ground color --- world/src/site/settlement/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 4f9418d35a..fba588ca17 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -557,9 +557,9 @@ impl Settlement { Some(Plot::Grass) => Some(Rgb::new(100, 200, 0)), Some(Plot::Water) => Some(Rgb::new(100, 150, 250)), Some(Plot::Town) => { - Some(Rgb::new(150, 110, 60).map2(Rgb::iota(), |e: u8, i: i32| { + 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)) % 16) + (self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, i * 5)) % 1) as u8, ) .saturating_sub(8) From db5311189d74fdf62bf717af27e14d6972036bf3 Mon Sep 17 00:00:00 2001 From: Pfauenauge90 <44173739+Pfauenauge90@users.noreply.github.com> Date: Sun, 19 Apr 2020 19:59:59 +0200 Subject: [PATCH 155/195] scarecrow --- assets/voxygen/voxel/sprite/misc/scarecrow.vox | 3 +++ common/src/terrain/block.rs | 4 ++++ voxygen/src/scene/terrain.rs | 12 ++++++++++++ 3 files changed, 19 insertions(+) create mode 100644 assets/voxygen/voxel/sprite/misc/scarecrow.vox diff --git a/assets/voxygen/voxel/sprite/misc/scarecrow.vox b/assets/voxygen/voxel/sprite/misc/scarecrow.vox new file mode 100644 index 0000000000..de0f55b6c4 --- /dev/null +++ b/assets/voxygen/voxel/sprite/misc/scarecrow.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b9a45478e4c9c49509b72ae9f4ba1951620a535ef37baedcc3a1d209bdbe7b8f +size 4468 diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index 2620673e94..a9278f8508 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -55,6 +55,7 @@ pub enum BlockKind { Window2, Window3, Window4, + Scarecrow, } impl BlockKind { @@ -110,6 +111,7 @@ impl BlockKind { BlockKind::Radish => true, BlockKind::Turnip => true, BlockKind::Coconut => true, + BlockKind::Scarecrow => true, _ => false, } } @@ -169,6 +171,7 @@ impl BlockKind { BlockKind::Window2 => false, BlockKind::Window3 => false, BlockKind::Window4 => false, + BlockKind::Scarecrow => false, _ => true, } } @@ -215,6 +218,7 @@ impl BlockKind { BlockKind::Radish => false, BlockKind::Turnip => false, BlockKind::Coconut => false, + BlockKind::Scarecrow => true, _ => true, } } diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index d30fa01c02..3b7d46a271 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -230,6 +230,10 @@ fn sprite_config_for(kind: BlockKind) -> Option { variations: 1, wind_sway: 0.0, }), + BlockKind::Scarecrow => Some(SpriteConfig { + variations: 1, + wind_sway: 0.0, + }), _ => None, } } @@ -1559,6 +1563,14 @@ impl Terrain { 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), + ), + ), ] .into_iter() .collect(), From a9adebcab33986adfe998ecef09b0a4bab036bfb Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sun, 19 Apr 2020 20:46:04 +0100 Subject: [PATCH 156/195] Improved rendering performance with smarter meshing order --- voxygen/src/mesh/terrain.rs | 25 +++++++++++++++++++++---- voxygen/src/render/mesh.rs | 24 +++++++++++++++++++++++- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/voxygen/src/mesh/terrain.rs b/voxygen/src/mesh/terrain.rs index 82a301040d..43ee5261d4 100644 --- a/voxygen/src/mesh/terrain.rs +++ b/voxygen/src/mesh/terrain.rs @@ -219,9 +219,6 @@ impl + ReadVol + Debug> Meshable (Mesh, Mesh) { - let mut opaque_mesh = Mesh::new(); - let mut fluid_mesh = Mesh::new(); - let mut light = calc_light(range, self); let mut lowest_opaque = range.size().d; @@ -300,6 +297,12 @@ impl + 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/render/mesh.rs b/voxygen/src/render/mesh.rs index 1d3a45431b..9a0b6a3251 100644 --- a/voxygen/src/render/mesh.rs +++ b/voxygen/src/render/mesh.rs @@ -1,11 +1,16 @@ use super::Pipeline; /// 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![] } } @@ -55,6 +60,23 @@ impl Mesh

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

{ + type Item = P::Vertex; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.verts.into_iter() + } } /// Represents a triangle stored on the CPU. From 0329b355ef7fed4707400fe4caeb5dda730c0d0e Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 20 Apr 2020 01:17:54 +0100 Subject: [PATCH 157/195] Made civsim paths visible in-game --- common/src/path.rs | 6 +++- server/src/sys/terrain.rs | 9 ++--- world/src/civ/mod.rs | 48 ++++++++++++++----------- world/src/column/mod.rs | 11 +++++- world/src/sim/mod.rs | 73 +++++++++++++++++++++++++++++++++++++-- world/src/sim/path.rs | 16 +++++++++ world/src/util/mod.rs | 51 +++++++++++++++++++++++++++ 7 files changed, 185 insertions(+), 29 deletions(-) create mode 100644 world/src/sim/path.rs diff --git a/common/src/path.rs b/common/src/path.rs index f826d18bcb..b3b88e3057 100644 --- a/common/src/path.rs +++ b/common/src/path.rs @@ -38,6 +38,10 @@ impl Path { 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 @@ -74,7 +78,7 @@ impl Route { } 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() - < traversal_tolerance.powf(2.0) + < (traversal_tolerance * 2.0).powf(2.0) { self.next_idx += 1; } diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index e7c3b9b073..d268daa5f0 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -9,7 +9,7 @@ use common::{ 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::*; @@ -116,6 +116,7 @@ impl<'a> System<'a> for Sys { &body_data.species[&species].generic } + /* const SPAWN_NPCS: &'static [fn() -> ( String, comp::Body, @@ -181,14 +182,14 @@ impl<'a> System<'a> for Sys { ) }), ]; - let (name, mut body, main, mut alignment) = SPAWN_NPCS + let (name, mut body, main, 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 mut name = entity.name.unwrap_or("Unnamed".to_string()); + let name = entity.name.unwrap_or("Unnamed".to_string()); let alignment = entity.alignment; let main_tool = entity.main_tool; diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index ad060f7eef..712281800c 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -3,7 +3,7 @@ mod econ; use crate::{ sim::{SimChunk, WorldSim}, site::{Dungeon, Settlement, Site as WorldSite}, - util::{attempt, seed_expan}, + util::{attempt, seed_expan, NEIGHBORS, CARDINALS}, }; use common::{ astar::Astar, @@ -19,24 +19,6 @@ use rand_chacha::ChaChaRng; use std::{fmt, hash::Hash, ops::Range}; use vek::*; -const CARDINALS: [Vec2; 4] = [ - Vec2::new(1, 0), - Vec2::new(-1, 0), - Vec2::new(0, 1), - Vec2::new(0, -1), -]; - -const DIAGONALS: [Vec2; 8] = [ - Vec2::new(1, 0), - Vec2::new(1, 1), - Vec2::new(-1, 0), - Vec2::new(-1, 1), - Vec2::new(0, 1), - Vec2::new(1, -1), - Vec2::new(0, -1), - Vec2::new(-1, -1), -]; - const INITIAL_CIV_COUNT: usize = 32; #[derive(Default)] @@ -111,6 +93,30 @@ impl Civs { // Temporary! for track in this.tracks.iter() { + for locs in track.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; + + 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), + ); + } + for loc in track.path.iter() { ctx.sim.get_mut(*loc).unwrap().place = Some(this.civs.iter().next().unwrap().homeland); @@ -158,7 +164,7 @@ impl Civs { // Place sites in world for site in this.sites.iter() { - let wpos = site.center * Vec2::from(TerrainChunkSize::RECT_SIZE).map(|e: u32| e as i32); + 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 => { @@ -464,7 +470,7 @@ fn find_path( let heuristic = move |l: &Vec2| (l.distance_squared(b) as f32).sqrt(); let neighbors = |l: &Vec2| { let l = *l; - DIAGONALS + NEIGHBORS .iter() .filter(move |dir| walk_in_dir(sim, l, **dir).is_some()) .map(move |dir| l + *dir) diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index 94e993b9aa..0c350d3097 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -141,7 +141,7 @@ fn river_spline_coeffs( /// curve"... hopefully this works out okay and gives us what we want (a /// river that extends outwards tangent to a quadratic curve, with width /// configured by distance along the line). -fn quadratic_nearest_point( +pub fn quadratic_nearest_point( spline: &Vec3>, point: Vec2, ) -> Option<(f64, Vec2, f64)> { @@ -1118,6 +1118,15 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { 5.0 }; + const PATH_WIDTH: f32 = 5.0; + let path_dist_factor = sim.get_nearest_path(wpos).map(|(dist, _)| dist / PATH_WIDTH).unwrap_or(1.0).min(1.0); + let ground = Lerp::lerp( + sub_surface_color, + ground, + (path_dist_factor.max(0.8) - 0.8) / 0.2, + ); + let alt = alt - if path_dist_factor < 0.8 { 1.0 } else { 0.0 }; + Some(ColumnSample { alt, riverless_alt, diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 894dd46b14..89a56f47ec 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -3,6 +3,7 @@ mod erosion; mod location; mod map; mod util; +mod path; // Reexports use self::erosion::Compute; @@ -19,6 +20,7 @@ pub use self::{ uniform_idx_as_vec2, uniform_noise, uphill, vec2_as_uniform_idx, InverseCdf, ScaleBias, NEIGHBOR_DELTA, }, + path::PathData, }; use crate::{ @@ -27,7 +29,7 @@ use crate::{ civ::Place, column::ColumnGen, site::{Settlement, Site}, - util::{seed_expan, FastNoise, RandomField, Sampler, StructureGen2d}, + util::{seed_expan, FastNoise, RandomField, Sampler, StructureGen2d, LOCALITY, CARDINAL_LOCALITY, NEIGHBORS}, CONFIG, }; use common::{ @@ -1574,7 +1576,7 @@ impl WorldSim { 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) }), ) } @@ -1770,6 +1772,71 @@ 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 |(i, 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), + 6, + 0.01, + ) + .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.map(|e| e.floor() as i32))) + })) + }) + .flatten() + .min_by_key(|(dist_sqrd, _)| (dist_sqrd * 1024.0) as i32) + .map(|(dist, pos)| (dist.sqrt(), pos)) + } } #[derive(Debug)] @@ -1793,6 +1860,7 @@ pub struct SimChunk { pub sites: Vec, pub place: Option>, + pub path: PathData, pub contains_waypoint: bool, } @@ -2030,6 +2098,7 @@ impl SimChunk { sites: Vec::new(), place: None, + path: PathData::default(), contains_waypoint: false, } } diff --git a/world/src/sim/path.rs b/world/src/sim/path.rs new file mode 100644 index 0000000000..25563669d2 --- /dev/null +++ b/world/src/sim/path.rs @@ -0,0 +1,16 @@ +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 Default for PathData { + fn default() -> Self { + Self { + offset: Vec2::zero(), + neighbors: 0, + } + } +} diff --git a/world/src/util/mod.rs b/world/src/util/mod.rs index abe9d8d0e9..89b0be7241 100644 --- a/world/src/util/mod.rs +++ b/world/src/util/mod.rs @@ -20,6 +20,57 @@ pub use self::{ 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(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 NEIGHBORS: [Vec2; 8] = [ + 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 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), +]; From e242604a4c70d845ec1782e8f1c0ce4487cf38d0 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 20 Apr 2020 15:50:03 +0100 Subject: [PATCH 158/195] Further improved AO --- assets/voxygen/shaders/terrain-frag.glsl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/voxygen/shaders/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl index 9d36f02fbd..6930f085d4 100644 --- a/assets/voxygen/shaders/terrain-frag.glsl +++ b/assets/voxygen/shaders/terrain-frag.glsl @@ -43,8 +43,8 @@ void main() { ambient_light *= point_shadow; vec3 point_light = light_at(f_pos, f_norm); light += point_light; - ambient_light *= min(f_light, ao); - diffuse_light *= min(f_light, ao); + ambient_light *= f_light * ao; + diffuse_light *= f_light * ao; diffuse_light += point_light * ao; vec3 surf_color = illuminate(srgb_to_linear(f_col), light, diffuse_light, ambient_light); From 77d0292e91532113f464119c1d825c3845201526 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 20 Apr 2020 15:50:33 +0100 Subject: [PATCH 159/195] Added windows to buildings and scarecrows --- common/src/terrain/block.rs | 15 +++++++++++ voxygen/src/scene/terrain.rs | 19 +++++++------ world/src/civ/mod.rs | 1 + .../settlement/building/archetype/house.rs | 27 ++++++++++++------- .../settlement/building/archetype/keep.rs | 1 + .../site/settlement/building/archetype/mod.rs | 1 + world/src/site/settlement/building/mod.rs | 4 +-- .../src/site/settlement/building/skeleton.rs | 4 +-- world/src/site/settlement/mod.rs | 5 ++++ 9 files changed, 56 insertions(+), 21 deletions(-) diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index a9278f8508..0a3e40d00a 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -111,6 +111,10 @@ impl BlockKind { BlockKind::Radish => true, BlockKind::Turnip => true, BlockKind::Coconut => true, + BlockKind::Window1 => true, + BlockKind::Window2 => true, + BlockKind::Window3 => true, + BlockKind::Window4 => true, BlockKind::Scarecrow => true, _ => false, } @@ -230,6 +234,7 @@ impl BlockKind { match self { BlockKind::Tomato => 1.65, BlockKind::LargeCactus => 2.5, + BlockKind::Scarecrow => 3.0, _ => 1.0, } } @@ -280,6 +285,16 @@ impl Block { } } + pub fn get_ori(&self) -> Option { + match self.kind { + BlockKind::Window1 + | BlockKind::Window2 + | BlockKind::Window3 + | BlockKind::Window4 => Some(self.color[0] & 0b111), + _ => None, + } + } + pub fn kind(&self) -> BlockKind { self.kind } } diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index 3b7d46a271..dfbf48f669 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -262,16 +262,19 @@ 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 % 8) as u8); 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), ), @@ -280,7 +283,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); } @@ -340,28 +343,28 @@ impl Terrain { (BlockKind::Window1, 0), make_model( "voxygen.voxel.sprite.window.window-0", - Vec3::new(-6.0, -6.0, 0.0), + Vec3::new(-5.5, -5.5, 0.0), ), ), ( (BlockKind::Window2, 0), make_model( "voxygen.voxel.sprite.window.window-1", - Vec3::new(-6.0, -6.0, 0.0), + Vec3::new(-5.5, -5.5, 0.0), ), ), ( (BlockKind::Window3, 0), make_model( "voxygen.voxel.sprite.window.window-2", - Vec3::new(-6.0, -6.0, 0.0), + Vec3::new(-5.5, -5.5, 0.0), ), ), ( (BlockKind::Window4, 0), make_model( "voxygen.voxel.sprite.window.window-3", - Vec3::new(-6.0, -6.0, 0.0), + Vec3::new(-5.5, -5.5, 0.0), ), ), // Cacti diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 712281800c..f25318b6e8 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -491,6 +491,7 @@ 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_alt = sim.get(a)?.alt; let b_alt = sim.get(a + dir)?.alt; + let water_cost = if sim.get(a + dir)?.river.near_water() { 25.0 } else { 0.0 }; Some((b_alt - a_alt).abs() / 2.5) } else { None diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index 96c3279bc8..d78016d66c 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -165,10 +165,18 @@ impl Archetype for House { 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 @@ -196,6 +204,7 @@ impl Archetype for House { .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())), facade_layer); let fire = BlockMask::new(Block::new(BlockKind::Ember, Rgb::white()), foundation_layer); let ceil_height = 6; @@ -301,21 +310,21 @@ impl Archetype for House { } else if !branch.attr.storey_fill.has_upper() { return Some(empty); } else { - let frame_bounds = if profile.y >= ceil_height { - Aabr { + 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 { + (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 + 1) + min: (frame_bounds.min + frame_borders) .map2(frame_bounds.center(), |a, b| a.min(b)), - max: (frame_bounds.max - 1) + max: (frame_bounds.max - frame_borders) .map2(frame_bounds.center(), |a, b| a.max(b)), }; @@ -324,9 +333,9 @@ impl Archetype for House { // 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(internal); + return Some(end_window); } else if frame_bounds.contains_point(surface_pos) { - return Some(log.with_priority(3)); + return Some(log.with_priority(facade_layer)); }; } diff --git a/world/src/site/settlement/building/archetype/keep.rs b/world/src/site/settlement/building/archetype/keep.rs index 8758c67bae..9810ec5e48 100644 --- a/world/src/site/settlement/building/archetype/keep.rs +++ b/world/src/site/settlement/building/archetype/keep.rs @@ -48,6 +48,7 @@ impl Archetype for Keep { bound_offset: Vec2, center_offset: Vec2, z: i32, + ori: Ori, branch: &Branch, ) -> BlockMask { let profile = Vec2::new(bound_offset.x, z); diff --git a/world/src/site/settlement/building/archetype/mod.rs b/world/src/site/settlement/building/archetype/mod.rs index a40b87035c..89ca5dca24 100644 --- a/world/src/site/settlement/building/archetype/mod.rs +++ b/world/src/site/settlement/building/archetype/mod.rs @@ -18,6 +18,7 @@ pub trait Archetype { 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 index 9b44c5316a..cc37d4acc5 100644 --- a/world/src/site/settlement/building/mod.rs +++ b/world/src/site/settlement/building/mod.rs @@ -50,9 +50,9 @@ impl Building { pub fn sample(&self, pos: Vec3) -> Option { let rpos = pos - self.origin; self.skel - .sample_closest(rpos.into(), |dist, bound_offset, center_offset, branch| { + .sample_closest(rpos.into(), |dist, bound_offset, center_offset, ori, branch| { self.archetype - .draw(dist, bound_offset, center_offset, rpos.z, branch) + .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 index ac2cc0404b..4387a9e590 100644 --- a/world/src/site/settlement/building/skeleton.rs +++ b/world/src/site/settlement/building/skeleton.rs @@ -75,7 +75,7 @@ impl Skeleton { pub fn sample_closest( &self, pos: Vec2, - mut f: impl FnMut(i32, Vec2, Vec2, &Branch) -> BlockMask, + 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| { @@ -115,7 +115,7 @@ impl Skeleton { } || true { - let new_bm = f(dist, bound_offset, center_offset, branch); + 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))); diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index fba588ca17..5e0ebbe689 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -611,6 +611,11 @@ impl Settlement { Crop::Sunflower => Some(BlockKind::Sunflower), _ => None, } + .or_else(|| if roll(9, 256) == 0 { + Some(BlockKind::Scarecrow) + } else { + None + }) .map(|kind| Block::new(kind, Rgb::white())); } } else { From 58874803a371cf56eea3d8b18fbd6f6c9668bab8 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 20 Apr 2020 16:41:04 +0100 Subject: [PATCH 160/195] Disabled chunk mesh sorting for now --- voxygen/src/mesh/terrain.rs | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/voxygen/src/mesh/terrain.rs b/voxygen/src/mesh/terrain.rs index 43ee5261d4..116210d37b 100644 --- a/voxygen/src/mesh/terrain.rs +++ b/voxygen/src/mesh/terrain.rs @@ -298,9 +298,10 @@ impl + 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 - }); + // let opaque_mesh = opaque_meshes + // .into_iter() + // .rev() + // .fold(Mesh::new(), |mut opaque_mesh, m: Mesh| { + // 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) } From 781a3e1377ba7bd11d347db81abd7231ae2585de Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 20 Apr 2020 17:06:19 +0100 Subject: [PATCH 161/195] Patched but not fixed pathfinding for variable-sized blocks --- common/src/path.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/path.rs b/common/src/path.rs index b3b88e3057..f152ed9668 100644 --- a/common/src/path.rs +++ b/common/src/path.rs @@ -158,7 +158,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)) From 431b2ae07b2db109b6feafe3a32b1aab1708a839 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 20 Apr 2020 17:30:39 +0100 Subject: [PATCH 162/195] Fixed window mask layering, fmt --- common/src/path.rs | 4 +- common/src/terrain/block.rs | 7 +- voxygen/src/mesh/terrain.rs | 16 +++-- voxygen/src/render/mesh.rs | 23 +++---- voxygen/src/scene/terrain.rs | 4 +- world/src/civ/mod.rs | 18 +++-- world/src/column/mod.rs | 6 +- world/src/sim/mod.rs | 69 ++++++++++--------- world/src/sim/path.rs | 3 +- .../settlement/building/archetype/house.rs | 41 +++++++---- world/src/site/settlement/building/mod.rs | 11 +-- world/src/site/settlement/mod.rs | 10 +-- 12 files changed, 122 insertions(+), 90 deletions(-) diff --git a/common/src/path.rs b/common/src/path.rs index f152ed9668..ec30217a23 100644 --- a/common/src/path.rs +++ b/common/src/path.rs @@ -39,9 +39,7 @@ impl Path { pub fn end(&self) -> Option<&T> { self.nodes.last() } - pub fn nodes(&self) -> &[T] { - &self.nodes - } + pub fn nodes(&self) -> &[T] { &self.nodes } } // Route: A path that can be progressed along diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index 0a3e40d00a..e91e75e706 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -287,10 +287,9 @@ impl Block { pub fn get_ori(&self) -> Option { match self.kind { - BlockKind::Window1 - | BlockKind::Window2 - | BlockKind::Window3 - | BlockKind::Window4 => Some(self.color[0] & 0b111), + BlockKind::Window1 | BlockKind::Window2 | BlockKind::Window3 | BlockKind::Window4 => { + Some(self.color[0] & 0b111) + }, _ => None, } } diff --git a/voxygen/src/mesh/terrain.rs b/voxygen/src/mesh/terrain.rs index 116210d37b..59e9be7aef 100644 --- a/voxygen/src/mesh/terrain.rs +++ b/voxygen/src/mesh/terrain.rs @@ -298,9 +298,10 @@ impl + ReadVol + Debug> Meshable + ReadVol + Debug> Meshable { verts: Vec, } -impl Clone for Mesh

where P::Vertex: Clone { +impl Clone for Mesh

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

{ } } - pub fn verts(&self) -> &[P::Vertex] { - &self.verts - } + pub fn verts(&self) -> &[P::Vertex] { &self.verts } - pub fn iter(&self) -> std::slice::Iter { - self.verts.iter() - } + pub fn iter(&self) -> std::slice::Iter { self.verts.iter() } } impl IntoIterator for Mesh

{ - type Item = P::Vertex; type IntoIter = std::vec::IntoIter; + type Item = P::Vertex; - fn into_iter(self) -> Self::IntoIter { - self.verts.into_iter() - } + fn into_iter(self) -> Self::IntoIter { self.verts.into_iter() } } /// Represents a triangle stored on the CPU. diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index dfbf48f669..856797b3fa 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -268,9 +268,7 @@ fn mesh_worker + RectRasterableVol + ReadVol + Debug>( 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 % 8) as u8); + let ori = block.get_ori().unwrap_or((seed % 8) as u8); let instance = SpriteInstance::new( Mat4::identity() diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index f25318b6e8..1504b6dd7c 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -3,7 +3,7 @@ mod econ; use crate::{ sim::{SimChunk, WorldSim}, site::{Dungeon, Settlement, Site as WorldSite}, - util::{attempt, seed_expan, NEIGHBORS, CARDINALS}, + util::{attempt, seed_expan, CARDINALS, NEIGHBORS}, }; use common::{ astar::Astar, @@ -108,9 +108,7 @@ impl Civs { .0; 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.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), @@ -164,7 +162,11 @@ impl Civs { // 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 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 => { @@ -491,7 +493,11 @@ 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_alt = sim.get(a)?.alt; let b_alt = sim.get(a + dir)?.alt; - let water_cost = if sim.get(a + dir)?.river.near_water() { 25.0 } else { 0.0 }; + let water_cost = if sim.get(a + dir)?.river.near_water() { + 25.0 + } else { + 0.0 + }; Some((b_alt - a_alt).abs() / 2.5) } else { None diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index 0c350d3097..c5ec89e2b1 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -1119,7 +1119,11 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { }; const PATH_WIDTH: f32 = 5.0; - let path_dist_factor = sim.get_nearest_path(wpos).map(|(dist, _)| dist / PATH_WIDTH).unwrap_or(1.0).min(1.0); + let path_dist_factor = sim + .get_nearest_path(wpos) + .map(|(dist, _)| dist / PATH_WIDTH) + .unwrap_or(1.0) + .min(1.0); let ground = Lerp::lerp( sub_surface_color, ground, diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 89a56f47ec..78e2074dfc 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -2,8 +2,8 @@ mod diffusion; mod erosion; mod location; mod map; -mod util; mod path; +mod util; // Reexports use self::erosion::Compute; @@ -15,12 +15,12 @@ pub use self::{ }, location::Location, map::{MapConfig, MapDebug}, + path::PathData, util::{ cdf_irwin_hall, downhill, get_oceans, local_cells, map_edge_factor, neighbors, uniform_idx_as_vec2, uniform_noise, uphill, vec2_as_uniform_idx, InverseCdf, ScaleBias, NEIGHBOR_DELTA, }, - path::PathData, }; use crate::{ @@ -29,7 +29,10 @@ use crate::{ civ::Place, column::ColumnGen, site::{Settlement, Site}, - util::{seed_expan, FastNoise, RandomField, Sampler, StructureGen2d, LOCALITY, CARDINAL_LOCALITY, NEIGHBORS}, + util::{ + seed_expan, FastNoise, RandomField, Sampler, StructureGen2d, CARDINAL_LOCALITY, LOCALITY, + NEIGHBORS, + }, CONFIG, }; use common::{ @@ -1777,15 +1780,18 @@ impl WorldSim { 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 - }); + 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 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 { @@ -1803,35 +1809,36 @@ impl WorldSim { .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, + 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 |(i, 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; + Some( + NEIGHBORS + .iter() + .enumerate() + .filter(move |(i, _)| chunk.path.neighbors & (1 << *i as u8) != 0) + .filter_map(move |(i, 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), - 6, - 0.01, - ) - .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.map(|e| e.floor() as i32))) - })) + 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), 6, 0.01) + .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.map(|e| e.floor() as i32))) + }), + ) }) .flatten() .min_by_key(|(dist_sqrd, _)| (dist_sqrd * 1024.0) as i32) diff --git a/world/src/sim/path.rs b/world/src/sim/path.rs index 25563669d2..ea4f24df42 100644 --- a/world/src/sim/path.rs +++ b/world/src/sim/path.rs @@ -2,7 +2,8 @@ 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 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 } diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index d78016d66c..fb86d4f494 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -171,10 +171,14 @@ impl Archetype for House { let profile = Vec2::new(bound_offset.x, z); let make_meta = |ori| { - Rgb::new(match ori { - Ori::East => 0, - Ori::North => 2, - }, 0, 0) + Rgb::new( + match ori { + Ori::East => 0, + Ori::North => 2, + }, + 0, + 0, + ) }; let make_block = |r, g, b| { @@ -204,7 +208,10 @@ impl Archetype for House { .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())), facade_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; @@ -311,15 +318,21 @@ impl Archetype for House { 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)) + ( + 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)) + ( + 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) @@ -335,7 +348,7 @@ impl Archetype for House { if window_bounds.contains_point(surface_pos) { return Some(end_window); } else if frame_bounds.contains_point(surface_pos) { - return Some(log.with_priority(facade_layer)); + return Some(log.with_priority(structural_layer)); }; } diff --git a/world/src/site/settlement/building/mod.rs b/world/src/site/settlement/building/mod.rs index cc37d4acc5..97087a4af7 100644 --- a/world/src/site/settlement/building/mod.rs +++ b/world/src/site/settlement/building/mod.rs @@ -50,10 +50,13 @@ impl Building { 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) - }) + .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/mod.rs b/world/src/site/settlement/mod.rs index 5e0ebbe689..79152914e8 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -611,10 +611,12 @@ impl Settlement { Crop::Sunflower => Some(BlockKind::Sunflower), _ => None, } - .or_else(|| if roll(9, 256) == 0 { - Some(BlockKind::Scarecrow) - } else { - None + .or_else(|| { + if roll(9, 256) == 0 { + Some(BlockKind::Scarecrow) + } else { + None + } }) .map(|kind| Block::new(kind, Rgb::white())); } From 431e19973136c439652f0b1de1135d246994c2ff Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 20 Apr 2020 20:18:51 +0100 Subject: [PATCH 163/195] Added correctly generating world paths, sub-voxel shader noise --- assets/voxygen/shaders/terrain-frag.glsl | 5 +- voxygen/src/scene/mod.rs | 2 +- world/src/block/natural.rs | 1 + world/src/civ/mod.rs | 4 +- world/src/column/mod.rs | 15 +---- world/src/layer/mod.rs | 81 ++++++++++++++++++++++++ world/src/lib.rs | 4 ++ world/src/site/settlement/mod.rs | 6 +- 8 files changed, 101 insertions(+), 17 deletions(-) create mode 100644 world/src/layer/mod.rs diff --git a/assets/voxygen/shaders/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl index 6930f085d4..44b86077c3 100644 --- a/assets/voxygen/shaders/terrain-frag.glsl +++ b/assets/voxygen/shaders/terrain-frag.glsl @@ -1,6 +1,7 @@ #version 330 core #include +#include in vec3 f_pos; flat in uint f_pos_norm; @@ -46,7 +47,9 @@ void main() { ambient_light *= f_light * ao; diffuse_light *= f_light * ao; diffuse_light += point_light * ao; - vec3 surf_color = illuminate(srgb_to_linear(f_col), light, diffuse_light, ambient_light); + + vec3 col = f_col + snoise(vec4(mod(floor(f_pos * 3.0), 100.0) * 10.0, 0)) * 0.02; // Small-scale noise + vec3 surf_color = illuminate(srgb_to_linear(col), light, diffuse_light, ambient_light); float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x); vec4 clouds; diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 5a0a15e4e9..8edf8e4635 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -236,7 +236,7 @@ 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. diff --git a/world/src/block/natural.rs b/world/src/block/natural.rs index ecd223dfd8..232708dad6 100644 --- a/world/src/block/natural.rs +++ b/world/src/block/natural.rs @@ -29,6 +29,7 @@ pub fn structure_gen<'a>( || st_sample.alt < st_sample.water_level || st_sample.spawn_rate < 0.5 || 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/mod.rs b/world/src/civ/mod.rs index 1504b6dd7c..c4edef3992 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -59,7 +59,7 @@ impl Civs { } } - for _ in 0..100 { + for _ in 0..32 { attempt(5, || { let loc = find_site_loc(&mut ctx, None)?; this.establish_site(&mut ctx.reseed(), loc, |place| Site { @@ -498,7 +498,7 @@ fn walk_in_dir(sim: &WorldSim, a: Vec2, dir: Vec2) -> Option { } else { 0.0 }; - Some((b_alt - a_alt).abs() / 2.5) + Some(1.0 + ((b_alt - a_alt).abs() / 2.5).powf(2.0) + water_cost) } else { None } diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index c5ec89e2b1..7769eb7edd 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -1118,18 +1118,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { 5.0 }; - const PATH_WIDTH: f32 = 5.0; - let path_dist_factor = sim - .get_nearest_path(wpos) - .map(|(dist, _)| dist / PATH_WIDTH) - .unwrap_or(1.0) - .min(1.0); - let ground = Lerp::lerp( - sub_surface_color, - ground, - (path_dist_factor.max(0.8) - 0.8) / 0.2, - ); - let alt = alt - if path_dist_factor < 0.8 { 1.0 } else { 0.0 }; + let path = sim.get_nearest_path(wpos); Some(ColumnSample { alt, @@ -1174,6 +1163,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { spawn_rate, stone_col, water_dist, + path, chunk: sim_chunk, }) @@ -1207,6 +1197,7 @@ pub struct ColumnSample<'a> { pub spawn_rate: f32, pub stone_col: Rgb, pub water_dist: Option, + pub path: Option<(f32, Vec2)>, pub chunk: &'a SimChunk, } diff --git a/world/src/layer/mod.rs b/world/src/layer/mod.rs new file mode 100644 index 0000000000..0947016c6b --- /dev/null +++ b/world/src/layer/mod.rs @@ -0,0 +1,81 @@ +use std::f32; +use vek::*; +use common::{ + terrain::{Block, BlockKind}, + vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol}, +}; +use crate::{ + column::ColumnSample, + util::{RandomField, Sampler}, +}; + +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 = get_column(offs + path_nearest - wpos2d) + .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 { + 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 { + vol.set(pos, Block::empty()); + } + } + } + } + } +} diff --git a/world/src/lib.rs b/world/src/lib.rs index 6c580de87a..dc9cfcb912 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -9,6 +9,7 @@ mod column; pub mod config; pub mod sim; pub mod site; +pub mod layer; pub mod util; // Reexports @@ -166,6 +167,9 @@ impl World { .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) as i32); diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 79152914e8..88fd59a590 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -149,7 +149,7 @@ impl Settlement { this.place_farms(&mut ctx); this.place_town(&mut ctx); - this.place_paths(ctx.rng); + //this.place_paths(ctx.rng); this.place_buildings(&mut ctx); this @@ -349,6 +349,10 @@ impl Settlement { .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; } From 44c5002db35731f144f1518030757f1a57e471a5 Mon Sep 17 00:00:00 2001 From: Monty Marz Date: Mon, 20 Apr 2020 19:52:46 +0000 Subject: [PATCH 164/195] Pfau/crops --- .../voxel/biped_large_lateral_manifest.ron | 24 +++---- .../voxygen/voxel/npc/giant/female/foot_l.vox | 4 +- .../voxygen/voxel/npc/giant/female/foot_r.vox | 4 +- .../voxygen/voxel/npc/giant/female/hand_l.vox | 4 +- .../voxygen/voxel/npc/giant/female/hand_r.vox | 4 +- .../voxygen/voxel/npc/giant/female/head.vox | 4 +- .../voxygen/voxel/npc/giant/female/leg_l.vox | 4 +- .../voxygen/voxel/npc/giant/female/leg_r.vox | 4 +- .../voxel/npc/giant/female/torso_lower.vox | 2 +- .../voxel/npc/giant/female/torso_upper.vox | 4 +- .../voxygen/voxel/npc/giant/male/foot_l.vox | 4 +- .../voxygen/voxel/npc/giant/male/foot_r.vox | 4 +- .../voxygen/voxel/npc/giant/male/hand_l.vox | 4 +- .../voxygen/voxel/npc/giant/male/hand_r.vox | 4 +- assets/voxygen/voxel/npc/giant/male/head.vox | 4 +- assets/voxygen/voxel/npc/giant/male/leg_l.vox | 4 +- assets/voxygen/voxel/npc/giant/male/leg_r.vox | 4 +- .../voxel/npc/giant/male/torso_lower.vox | 2 +- .../voxel/npc/giant/male/torso_upper.vox | 4 +- assets/voxygen/voxel/object.zip | Bin 0 -> 154285 bytes assets/voxygen/voxel/sprite/door/door-0.vox | 3 + .../voxygen/voxel/sprite/misc/scarecrow.vox | 2 +- .../voxygen/voxel/sprite/misc/street_lamp.vox | 3 + assets/voxygen/voxel/sprite/pumpkin/1.vox | 2 +- assets/voxygen/voxel/sprite/pumpkin/2.vox | 2 +- assets/voxygen/voxel/sprite/pumpkin/3.vox | 2 +- assets/voxygen/voxel/sprite/pumpkin/4.vox | 2 +- assets/voxygen/voxel/sprite/pumpkin/5.vox | 2 +- assets/voxygen/voxel/sprite/pumpkin/6.vox | 3 + assets/voxygen/voxel/sprite/pumpkin/7.vox | 3 + .../voxygen/voxel/sprite/window/window-0.vox | 2 +- .../voxygen/voxel/sprite/window/window-1.vox | 2 +- .../voxygen/voxel/sprite/window/window-2.vox | 2 +- .../voxygen/voxel/sprite/window/window-3.vox | 2 +- common/src/terrain/block.rs | 28 ++++++-- voxygen/src/anim/biped_large/mod.rs | 4 +- voxygen/src/anim/biped_large/run.rs | 66 +++++++++++------- voxygen/src/anim/mod.rs | 2 +- voxygen/src/scene/terrain.rs | 40 ++++++++++- 39 files changed, 173 insertions(+), 91 deletions(-) create mode 100644 assets/voxygen/voxel/object.zip create mode 100644 assets/voxygen/voxel/sprite/door/door-0.vox create mode 100644 assets/voxygen/voxel/sprite/misc/street_lamp.vox create mode 100644 assets/voxygen/voxel/sprite/pumpkin/6.vox create mode 100644 assets/voxygen/voxel/sprite/pumpkin/7.vox diff --git a/assets/voxygen/voxel/biped_large_lateral_manifest.ron b/assets/voxygen/voxel/biped_large_lateral_manifest.ron index a8077141e4..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, -2.5), + offset: (-3.0, -5.0, -2.5), lateral: ("npc.giant.male.foot_l"), ), foot_r: ( - offset: (-3.0, -5.5, -2.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, -2.5), + offset: (-3.0, -5.0, -2.5), lateral: ("npc.giant.female.foot_l"), ), foot_r: ( - offset: (-3.0, -5.5, -2.5), + offset: (-3.0, -5.0, -2.5), lateral: ("npc.giant.female.foot_r"), ) ), 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.zip b/assets/voxygen/voxel/object.zip new file mode 100644 index 0000000000000000000000000000000000000000..400c60dd6a8ae9ca77b66546f19dace23e875bf0 GIT binary patch literal 154285 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 M5XWBGy&OpW9}XJi4*&oF literal 0 HcmV?d00001 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/misc/scarecrow.vox b/assets/voxygen/voxel/sprite/misc/scarecrow.vox index de0f55b6c4..85d65d4841 100644 --- a/assets/voxygen/voxel/sprite/misc/scarecrow.vox +++ b/assets/voxygen/voxel/sprite/misc/scarecrow.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b9a45478e4c9c49509b72ae9f4ba1951620a535ef37baedcc3a1d209bdbe7b8f +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/window/window-0.vox b/assets/voxygen/voxel/sprite/window/window-0.vox index 04bdfdc2b9..1bd7d003d9 100644 --- a/assets/voxygen/voxel/sprite/window/window-0.vox +++ b/assets/voxygen/voxel/sprite/window/window-0.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9fd60602a5c127f2bc5ac39d784750893a0deeb076c8cc7db7f6fc8c01f9c0a4 +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 index f65ebd8134..24e3d6877f 100644 --- a/assets/voxygen/voxel/sprite/window/window-1.vox +++ b/assets/voxygen/voxel/sprite/window/window-1.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d4560a4b13fce2a288424359d2c98a3b7126d8d811cc658386283244cd133025 +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 index 5c19985d68..07bfd7f345 100644 --- a/assets/voxygen/voxel/sprite/window/window-2.vox +++ b/assets/voxygen/voxel/sprite/window/window-2.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7c9452ccb947465df5379284345e813975253232ca0123c2fc44e407d8de7ad4 +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 index 6e38678d7f..f0d8901ae4 100644 --- a/assets/voxygen/voxel/sprite/window/window-3.vox +++ b/assets/voxygen/voxel/sprite/window/window-3.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b811dbf279876fd406a6e1e2261bdfdcb3f24959515270b1c4a38ed22cdc1c8 +oid sha256:6ae2e9c20bb7a32202f3c72abc3793725be8059f5b877e161762c28cd6b2cdbb size 1540 diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index e91e75e706..1f1059a16f 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -56,6 +56,8 @@ pub enum BlockKind { Window3, Window4, Scarecrow, + StreetLamp, + Door, } impl BlockKind { @@ -116,6 +118,8 @@ impl BlockKind { BlockKind::Window3 => true, BlockKind::Window4 => true, BlockKind::Scarecrow => true, + BlockKind::StreetLamp => true, + BlockKind::Door => false, _ => false, } } @@ -176,6 +180,8 @@ impl BlockKind { BlockKind::Window3 => false, BlockKind::Window4 => false, BlockKind::Scarecrow => false, + BlockKind::StreetLamp => false, + BlockKind::Door => false, _ => true, } } @@ -204,7 +210,7 @@ impl BlockKind { BlockKind::Mushroom => false, BlockKind::Liana => false, BlockKind::Chest => true, - BlockKind::Pumpkin => false, + BlockKind::Pumpkin => true, BlockKind::Welwitch => false, BlockKind::LingonBerry => false, BlockKind::LeafyPlant => false, @@ -215,14 +221,16 @@ impl BlockKind { BlockKind::Corn => false, BlockKind::WheatYellow => false, BlockKind::WheatGreen => false, - BlockKind::Cabbage => false, + BlockKind::Cabbage => true, BlockKind::Flax => false, - BlockKind::Carrot => false, + BlockKind::Carrot => true, BlockKind::Tomato => true, - BlockKind::Radish => false, - BlockKind::Turnip => false, - BlockKind::Coconut => false, + BlockKind::Radish => true, + BlockKind::Turnip => true, + BlockKind::Coconut => true, BlockKind::Scarecrow => true, + BlockKind::StreetLamp => true, + BlockKind::Door => false, _ => true, } } @@ -235,6 +243,14 @@ impl BlockKind { 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, } } diff --git a/voxygen/src/anim/biped_large/mod.rs b/voxygen/src/anim/biped_large/mod.rs index 67f60ceacc..ba73161fb7 100644 --- a/voxygen/src/anim/biped_large/mod.rs +++ b/voxygen/src/anim/biped_large/mod.rs @@ -145,10 +145,10 @@ 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, _) => (8.5, -1.0, 4.0), + (Giant, _) => (10.5, -1.0, 3.5), }, leg: match (body.species, body.body_type) { - (Giant, _) => (3.5, 0.0, -5.0), + (Giant, _) => (0.0, 0.0, -6.0), }, foot: match (body.species, body.body_type) { (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 acbed5f302..9c54a2ac61 100644 --- a/voxygen/src/anim/biped_large/run.rs +++ b/voxygen/src/anim/biped_large/run.rs @@ -17,21 +17,30 @@ impl Animation for RunAnimation { ) -> Self::Skeleton { let mut next = (*skeleton).clone(); - let lab = 1.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) * 1.02; - next.head.ori = Quaternion::rotation_z(belt * 0.1) * Quaternion::rotation_x(0.3); + 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( @@ -39,7 +48,7 @@ impl Animation for RunAnimation { 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 +56,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 +64,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 +73,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,7 +82,8 @@ 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.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( @@ -79,42 +91,46 @@ impl Animation for RunAnimation { 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.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 + legl * 0.4, + skeleton_attr.leg.2, ) * 1.02; - next.leg_l.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(-legl * 0.3); + 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 + legr * 0.4, + skeleton_attr.leg.2, ) * 1.02; - next.leg_r.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(-legr * 0.3); + + 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.0, - skeleton_attr.foot.2 + footvertl * 3.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); + 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.0, - skeleton_attr.foot.2 + footvertr * 3.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); + 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.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/mod.rs b/voxygen/src/anim/mod.rs index 7d6e5b2086..de57de2c81 100644 --- a/voxygen/src/anim/mod.rs +++ b/voxygen/src/anim/mod.rs @@ -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/scene/terrain.rs b/voxygen/src/scene/terrain.rs index 856797b3fa..9982181167 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -167,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 { @@ -234,6 +234,14 @@ fn sprite_config_for(kind: BlockKind) -> Option { 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, } } @@ -924,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), @@ -1572,6 +1594,22 @@ impl Terrain { 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(-6.5, -6.5, 0.0), + ), + ), ] .into_iter() .collect(), From 628dc7c47ec56af4f57ba6b4ba24cb6ca1e0ee2c Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 20 Apr 2020 23:25:29 +0100 Subject: [PATCH 165/195] Added waypoints outside of dungeons --- world/src/site/dungeon/mod.rs | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index e38e33b70f..87ef02cf18 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -3,7 +3,7 @@ use crate::{ column::ColumnSample, sim::{SimChunk, WorldSim}, site::BlockMask, - util::{attempt, Grid, RandomField, Sampler, StructureGen2d}, + util::{attempt, DIRS, CARDINALS, Grid, RandomField, Sampler, StructureGen2d}, }; use common::{ assets, @@ -131,6 +131,19 @@ impl Dungeon { 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(); @@ -140,24 +153,6 @@ impl Dungeon { } } -const CARDINALS: [Vec2; 4] = [ - Vec2::new(0, 1), - Vec2::new(1, 0), - Vec2::new(0, -1), - Vec2::new(-1, 0), -]; - -const DIRS: [Vec2; 8] = [ - 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), -]; - const TILE_SIZE: i32 = 13; #[derive(Clone)] From cd925e8ceaab624de54527ea4552ba0f7c310e7e Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 20 Apr 2020 23:25:52 +0100 Subject: [PATCH 166/195] Faster sub-voxel noise function --- assets/voxygen/shaders/include/random.glsl | 2 +- assets/voxygen/shaders/terrain-frag.glsl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/voxygen/shaders/include/random.glsl b/assets/voxygen/shaders/include/random.glsl index b1f0e18a21..b29d44d229 100644 --- a/assets/voxygen/shaders/include/random.glsl +++ b/assets/voxygen/shaders/include/random.glsl @@ -33,7 +33,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/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl index 44b86077c3..3b8e0bd45b 100644 --- a/assets/voxygen/shaders/terrain-frag.glsl +++ b/assets/voxygen/shaders/terrain-frag.glsl @@ -48,7 +48,7 @@ void main() { diffuse_light *= f_light * ao; diffuse_light += point_light * ao; - vec3 col = f_col + snoise(vec4(mod(floor(f_pos * 3.0), 100.0) * 10.0, 0)) * 0.02; // Small-scale noise + vec3 col = f_col + hash(vec4(mod(floor(f_pos * 3.0), 100.0) * 10.0, 0)) * 0.02; // Small-scale noise vec3 surf_color = illuminate(srgb_to_linear(col), light, diffuse_light, ambient_light); float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x); From 89f99dbcc9c7a08869c36bcbf4be2ea2ebcf845e Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 20 Apr 2020 23:53:36 +0100 Subject: [PATCH 167/195] Added lanterns to town streets --- world/src/site/settlement/mod.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 88fd59a590..f1bf566459 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -556,11 +556,21 @@ impl Settlement { } else if let Some(color) = self.get_color(rpos) { 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 (col_sample.path.map(|(dist, _)| dist > 7.0 && dist < 8.0).unwrap_or(false) && 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) @@ -579,10 +589,6 @@ impl Settlement { let furrow_dir = furrow_dirs[*seed as usize % furrow_dirs.len()]; let in_furrow = (wpos2d * furrow_dir).sum().rem_euclid(5) < 2; - let roll = |seed, n| { - self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, seed * 5)) % n - }; - let dirt = Rgb::new(80, 55, 35).map(|e| { e + (self.noise.get(Vec3::broadcast((seed % 4096 + 0) as i32)) % 32) as u8 @@ -616,7 +622,7 @@ impl Settlement { _ => None, } .or_else(|| { - if roll(9, 256) == 0 { + if roll(9, 400) == 0 { Some(BlockKind::Scarecrow) } else { None From 68732bebdeacddbdb4a5c249e3380549deca44f0 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Tue, 21 Apr 2020 00:34:35 +0100 Subject: [PATCH 168/195] Fixed block snapping for bizarre block heights --- common/src/sys/phys.rs | 46 ++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/common/src/sys/phys.rs b/common/src/sys/phys.rs index 36cceb287f..8cfdb9ed48 100644 --- a/common/src/sys/phys.rs +++ b/common/src/sys/phys.rs @@ -152,7 +152,7 @@ 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); @@ -192,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 @@ -203,7 +203,7 @@ 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)) @@ -221,15 +221,16 @@ impl<'a> System<'a> for Sys { 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() @@ -262,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... @@ -270,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; @@ -309,20 +310,25 @@ 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; } @@ -334,7 +340,7 @@ 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) @@ -346,7 +352,7 @@ 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); } From 514281c837591f687d7b201ca45c6d833f5b6911 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Tue, 21 Apr 2020 12:18:35 +0100 Subject: [PATCH 169/195] Made lantern placement on paths more regular --- world/src/site/settlement/mod.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index f1bf566459..0633c07853 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -565,10 +565,18 @@ impl Settlement { Some(Plot::Grass) => Some(Rgb::new(100, 200, 0)), Some(Plot::Water) => Some(Rgb::new(100, 150, 250)), Some(Plot::Town) => { - if (col_sample.path.map(|(dist, _)| dist > 7.0 && dist < 8.0).unwrap_or(false) && roll(0, 50) == 0) - || roll(0, 2000) == 0 - { - surface_block = Some(Block::new(BlockKind::StreetLamp, Rgb::white())); + if let Some((path_dist, 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| { From 7edd0e701c47b0ebc495a9a299e6734021cc225d Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Tue, 21 Apr 2020 14:15:32 +0100 Subject: [PATCH 170/195] Fixed sub-voxel noise flickering on NVIDIA cards --- assets/voxygen/shaders/terrain-frag.glsl | 3 ++- assets/voxygen/shaders/terrain-vert.glsl | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/assets/voxygen/shaders/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl index 3b8e0bd45b..ba16a1c28f 100644 --- a/assets/voxygen/shaders/terrain-frag.glsl +++ b/assets/voxygen/shaders/terrain-frag.glsl @@ -4,6 +4,7 @@ #include in vec3 f_pos; +in vec3 f_chunk_pos; flat in uint f_pos_norm; in vec3 f_col; in float f_light; @@ -48,7 +49,7 @@ void main() { diffuse_light *= f_light * ao; diffuse_light += point_light * ao; - vec3 col = f_col + hash(vec4(mod(floor(f_pos * 3.0), 100.0) * 10.0, 0)) * 0.02; // Small-scale noise + vec3 col = f_col + hash(vec4(floor(mod(f_chunk_pos, 100.0) * 3.0) * 10.0, 0)) * 0.02; // Small-scale noise vec3 surf_color = illuminate(srgb_to_linear(col), light, diffuse_light, ambient_light); float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x); diff --git a/assets/voxygen/shaders/terrain-vert.glsl b/assets/voxygen/shaders/terrain-vert.glsl index a1a0bb6be9..eb81ee8bef 100644 --- a/assets/voxygen/shaders/terrain-vert.glsl +++ b/assets/voxygen/shaders/terrain-vert.glsl @@ -13,6 +13,7 @@ uniform u_locals { }; out vec3 f_pos; +out vec3 f_chunk_pos; flat out uint f_pos_norm; out vec3 f_col; out float f_light; @@ -21,7 +22,8 @@ out float f_ao; const float EXTRA_NEG_Z = 65536.0; void main() { - f_pos = vec3((uvec3(v_pos_norm) >> uvec3(0, 6, 12)) & uvec3(0x3Fu, 0x3Fu, 0x1FFFFu)) - vec3(0, 0, EXTRA_NEG_Z) + model_offs; + f_chunk_pos = vec3((uvec3(v_pos_norm) >> uvec3(0, 6, 12)) & uvec3(0x3Fu, 0x3Fu, 0x1FFFFu)) - vec3(0, 0, EXTRA_NEG_Z); + f_pos = f_chunk_pos + 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 -= 25.0 * pow(distance(focus_pos.xy, f_pos.xy) / view_distance.x, 20.0); From a65dbc570afdf467b0245130a9f2471aa1e0da75 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Tue, 21 Apr 2020 21:05:01 +0100 Subject: [PATCH 171/195] Returned to normal world size --- assets/voxygen/shaders/include/random.glsl | 4 ++-- assets/voxygen/shaders/terrain-frag.glsl | 2 +- assets/world/map/veloren_0_5_0_0.bin | 4 ++-- world/examples/water.rs | 4 ++-- world/src/column/mod.rs | 4 ++-- world/src/layer/mod.rs | 4 +++- world/src/sim/mod.rs | 8 ++++---- world/src/site/settlement/mod.rs | 2 +- 8 files changed, 17 insertions(+), 15 deletions(-) diff --git a/assets/voxygen/shaders/include/random.glsl b/assets/voxygen/shaders/include/random.glsl index b29d44d229..240d17e50a 100644 --- a/assets/voxygen/shaders/include/random.glsl +++ b/assets/voxygen/shaders/include/random.glsl @@ -1,7 +1,7 @@ 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) { diff --git a/assets/voxygen/shaders/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl index ba16a1c28f..7a38f62146 100644 --- a/assets/voxygen/shaders/terrain-frag.glsl +++ b/assets/voxygen/shaders/terrain-frag.glsl @@ -49,7 +49,7 @@ void main() { diffuse_light *= f_light * ao; diffuse_light += point_light * ao; - vec3 col = f_col + hash(vec4(floor(mod(f_chunk_pos, 100.0) * 3.0) * 10.0, 0)) * 0.02; // Small-scale noise + vec3 col = f_col + hash(vec4(floor(f_chunk_pos * 3.0), 0)) * 0.02; // Small-scale noise vec3 surf_color = illuminate(srgb_to_linear(col), light, diffuse_light, ambient_light); float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x); diff --git a/assets/world/map/veloren_0_5_0_0.bin b/assets/world/map/veloren_0_5_0_0.bin index 663a225c4c..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:ea65d94f251f47d8d577fa6485655d22d3fbb317210ab4826ae87d6463977165 -size 4194324 +oid sha256:267884532ae482c58f8cc2308c36824b4f4216b124b0310104364c7619e20130 +size 16777236 diff --git a/world/examples/water.rs b/world/examples/water.rs index 0011211c9a..0468962942 100644 --- a/world/examples/water.rs +++ b/world/examples/water.rs @@ -24,8 +24,8 @@ fn main() { let world = World::generate(5284, WorldOpts { seed_elements: false, - world_file: sim::FileOpts::Load(map_path), - //world_file: sim::FileOpts::Save, + //world_file: sim::FileOpts::Load(map_path), + world_file: sim::FileOpts::Save, ..WorldOpts::default() }); diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index 7769eb7edd..2204c9b91d 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -221,7 +221,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { 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)?; @@ -1197,7 +1197,7 @@ pub struct ColumnSample<'a> { pub spawn_rate: f32, pub stone_col: Rgb, pub water_dist: Option, - pub path: Option<(f32, Vec2)>, + pub path: Option<(f32, Vec2)>, pub chunk: &'a SimChunk, } diff --git a/world/src/layer/mod.rs b/world/src/layer/mod.rs index 0947016c6b..5d0e7c06ae 100644 --- a/world/src/layer/mod.rs +++ b/world/src/layer/mod.rs @@ -3,6 +3,7 @@ use vek::*; use common::{ terrain::{Block, BlockKind}, vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol}, + spiral::Spiral2d, }; use crate::{ column::ColumnSample, @@ -42,7 +43,8 @@ pub fn apply_paths_to<'a>( // Try to use the column at the centre of the path for sampling to make them // flatter - let col = get_column(offs + path_nearest - wpos2d) + let col_pos = offs + path_nearest.map(|e| e.floor() as i32) - wpos2d; + let col = get_column(col_pos) .unwrap_or(col_sample); let (bridge_offset, depth) = if let Some(water_dist) = col.water_dist { ( diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 78e2074dfc..7e0e65052d 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -70,8 +70,8 @@ use vek::*; // don't think we actually cast a chunk id to float, just coordinates... could // be wrong though! pub const WORLD_SIZE: Vec2 = Vec2 { - x: 512 * 1, - y: 512 * 1, + x: 1024 * 1, + y: 1024 * 1, }; /// A structure that holds cached noise values and cumulative distribution @@ -1776,7 +1776,7 @@ impl WorldSim { Some(z0 + z1 + z2 + z3) } - pub fn get_nearest_path(&self, wpos: Vec2) -> Option<(f32, Vec2)> { + 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) }); @@ -1836,7 +1836,7 @@ impl WorldSim { .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.map(|e| e.floor() as i32))) + Some((dist_sqrd, pos)) }), ) }) diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 0633c07853..459ad921ef 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -566,7 +566,7 @@ impl Settlement { Some(Plot::Water) => Some(Rgb::new(100, 150, 250)), Some(Plot::Town) => { if let Some((path_dist, 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 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 { From 60bf396e4fcddc067bf80224a1483e758105afb1 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 22 Apr 2020 10:07:50 +0100 Subject: [PATCH 172/195] Added doors to houses --- common/src/path.rs | 2 +- common/src/terrain/block.rs | 2 +- voxygen/src/scene/terrain.rs | 2 +- .../settlement/building/archetype/house.rs | 24 +++++++++++++++++++ 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/common/src/path.rs b/common/src/path.rs index ec30217a23..77906f8a56 100644 --- a/common/src/path.rs +++ b/common/src/path.rs @@ -74,7 +74,7 @@ impl Route { if vol.get(next).map(|b| b.is_solid()).unwrap_or(false) { None } else { - let next_tgt = next.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0); + let next_tgt = next.map(|e| e as f32) + 0.5; if ((pos - next_tgt) * Vec3::new(1.0, 1.0, 0.3)).magnitude_squared() < (traversal_tolerance * 2.0).powf(2.0) { diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index 1f1059a16f..9b3d664b95 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -303,7 +303,7 @@ impl Block { pub fn get_ori(&self) -> Option { match self.kind { - BlockKind::Window1 | BlockKind::Window2 | BlockKind::Window3 | BlockKind::Window4 => { + BlockKind::Window1 | BlockKind::Window2 | BlockKind::Window3 | BlockKind::Window4 | BlockKind::Door => { Some(self.color[0] & 0b111) }, _ => None, diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index 9982181167..b9f8391a25 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -1607,7 +1607,7 @@ impl Terrain { (BlockKind::Door, 0), make_model( "voxygen.voxel.sprite.door.door-0", - Vec3::new(-6.5, -6.5, 0.0), + Vec3::new(-5.5, -5.5, 0.0), ), ), ] diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index fb86d4f494..1302d667d3 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -309,6 +309,30 @@ impl Archetype for House { // 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); From 8d4f1da3a6ebcb8e71af6f4abf8b0f9ef9e3ce8e Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 22 Apr 2020 21:12:57 +0100 Subject: [PATCH 173/195] Improved path rendering on the map, removed ugly path noise --- .../shaders/include/cloud/regular.glsl | 2 +- world/src/civ/mod.rs | 71 ++++++++++--------- world/src/layer/mod.rs | 20 ++++-- world/src/sim/map.rs | 10 +-- world/src/sim/mod.rs | 2 +- world/src/sim/path.rs | 6 ++ 6 files changed, 65 insertions(+), 46 deletions(-) diff --git a/assets/voxygen/shaders/include/cloud/regular.glsl b/assets/voxygen/shaders/include/cloud/regular.glsl index 76436f13d6..585e76ce69 100644 --- a/assets/voxygen/shaders/include/cloud/regular.glsl +++ b/assets/voxygen/shaders/include/cloud/regular.glsl @@ -32,7 +32,7 @@ vec2 cloud_at(vec3 pos) { float density = max((value - CLOUD_THRESHOLD) - abs(pos.z - CLOUD_AVG_HEIGHT) / 200.0, 0.0) * CLOUD_DENSITY; const float SHADE_GRADIENT = 1.5 / (CLOUD_AVG_HEIGHT - CLOUD_HEIGHT_MIN); - float shade = ((pos.z - CLOUD_AVG_HEIGHT) / (CLOUD_HEIGHT_MAX - CLOUD_HEIGHT_MIN)) * 2.5 + 0.7; + float shade = ((pos.z - CLOUD_AVG_HEIGHT) / (CLOUD_HEIGHT_MAX - CLOUD_HEIGHT_MIN)) * 10.5 + 0.7; return vec2(shade, density / (1.0 + vsum(abs(pos - cam_pos.xyz)) / 5000)); } diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index c4edef3992..103e9b7d14 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -91,36 +91,6 @@ impl Civs { this.tick(&mut ctx, 1.0); } - // Temporary! - for track in this.tracks.iter() { - for locs in track.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; - - 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), - ); - } - - for loc in track.path.iter() { - ctx.sim.get_mut(*loc).unwrap().place = - Some(this.civs.iter().next().unwrap().homeland); - } - } - // Flatten ground around sites for site in this.sites.iter() { if let SiteKind::Settlement = &site.kind { @@ -380,6 +350,30 @@ impl Civs { .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; + + 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) @@ -491,14 +485,21 @@ fn find_path( /// (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_alt = sim.get(a)?.alt; - let b_alt = sim.get(a + dir)?.alt; - let water_cost = if sim.get(a + dir)?.river.near_water() { - 25.0 + 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 }; - Some(1.0 + ((b_alt - a_alt).abs() / 2.5).powf(2.0) + water_cost) + 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 } diff --git a/world/src/layer/mod.rs b/world/src/layer/mod.rs index 5d0e7c06ae..348f59b9c1 100644 --- a/world/src/layer/mod.rs +++ b/world/src/layer/mod.rs @@ -43,21 +43,33 @@ pub fn apply_paths_to<'a>( // Try to use the column at the centre of the path for sampling to make them // flatter - let col_pos = offs + path_nearest.map(|e| e.floor() as i32) - wpos2d; - let col = get_column(col_pos) + 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 riverless_alt = match (col00, col10, col01, col11) { + (Some(col00), Some(col10), Some(col01), Some(col11)) => Lerp::lerp( + Lerp::lerp(col00.riverless_alt, col10.riverless_alt, path_nearest.x.fract()), + Lerp::lerp(col01.riverless_alt, col11.riverless_alt, path_nearest.x.fract()), + path_nearest.y.fract(), + ), + _ => col_sample.riverless_alt, + }; + let col = get_column(col_pos.map(|e| e.floor() as i32)) .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) + * (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; + let surface_z = (riverless_alt + bridge_offset).floor() as i32; for z in inset - depth..inset { vol.set( diff --git a/world/src/sim/map.rs b/world/src/sim/map.rs index 2a4d71d277..4dd96c3700 100644 --- a/world/src/sim/map.rs +++ b/world/src/sim/map.rs @@ -147,7 +147,7 @@ impl MapConfig { let pos = (focus_rect + Vec2::new(i as f64, j as f64) * scale).map(|e: f64| e as i32); - let (alt, basement, water_alt, humidity, temperature, downhill, river_kind, place) = + let (alt, basement, water_alt, humidity, temperature, downhill, river_kind, is_path) = sampler .get(pos) .map(|sample| { @@ -159,7 +159,7 @@ impl MapConfig { sample.temp, sample.downhill, sample.river.river_kind, - sample.place, + sample.path.is_path(), ) }) .unwrap_or(( @@ -170,7 +170,7 @@ impl MapConfig { 0.0, None, None, - None, + false, )); let humidity = humidity.min(1.0).max(0.0); let temperature = temperature.min(1.0).max(-1.0) * 0.5 + 0.5; @@ -297,8 +297,8 @@ impl MapConfig { ), }; - let rgba = if let Some(place) = place { - (((place.id() * 64) % 256) as u8, 0, 0, 0) + let rgba = if is_path { + (0x20, 0x19, 0x13, 255) } else { rgba }; diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 7e0e65052d..d651df51a6 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -1831,7 +1831,7 @@ impl WorldSim { end: (end_pos + ctrl_pos) / 2.0, }; let nearest_interval = bez - .binary_search_point_by_steps(wpos.map(|e| e as f32), 6, 0.01) + .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); diff --git a/world/src/sim/path.rs b/world/src/sim/path.rs index ea4f24df42..0ccdb910b8 100644 --- a/world/src/sim/path.rs +++ b/world/src/sim/path.rs @@ -7,6 +7,12 @@ pub struct PathData { 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 { From e35dff2f4b5e8d10216707365c75ab53a7ddc57b Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 22 Apr 2020 21:54:46 +0100 Subject: [PATCH 174/195] Fixed noise on bridges --- world/src/layer/mod.rs | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/world/src/layer/mod.rs b/world/src/layer/mod.rs index 348f59b9c1..87642c991b 100644 --- a/world/src/layer/mod.rs +++ b/world/src/layer/mod.rs @@ -48,27 +48,22 @@ pub fn apply_paths_to<'a>( 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 riverless_alt = match (col00, col10, col01, col11) { + 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(col00.riverless_alt, col10.riverless_alt, path_nearest.x.fract()), - Lerp::lerp(col01.riverless_alt, col11.riverless_alt, path_nearest.x.fract()), + 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_sample.riverless_alt, - }; - let col = get_column(col_pos.map(|e| e.floor() as i32)) - .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()) - * (riverless_alt + 5.0 - col.alt).max(0.0) - * 1.75 - + 3.0) as i32, - ) - } else { - (0.0, 3) - }; + _ => 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 { From 1983fd8ec16be5d4621edbcc3077dcf682ce7290 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 23 Apr 2020 01:37:16 +0100 Subject: [PATCH 175/195] Cleanup, fixed clouds, removed 45-degree terrain sprites --- assets/voxygen/shaders/include/cloud/regular.glsl | 2 +- assets/voxygen/shaders/terrain-frag.glsl | 4 ---- voxygen/src/scene/terrain.rs | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/assets/voxygen/shaders/include/cloud/regular.glsl b/assets/voxygen/shaders/include/cloud/regular.glsl index 585e76ce69..a40c700e01 100644 --- a/assets/voxygen/shaders/include/cloud/regular.glsl +++ b/assets/voxygen/shaders/include/cloud/regular.glsl @@ -32,7 +32,7 @@ vec2 cloud_at(vec3 pos) { float density = max((value - CLOUD_THRESHOLD) - abs(pos.z - CLOUD_AVG_HEIGHT) / 200.0, 0.0) * CLOUD_DENSITY; const float SHADE_GRADIENT = 1.5 / (CLOUD_AVG_HEIGHT - CLOUD_HEIGHT_MIN); - float shade = ((pos.z - CLOUD_AVG_HEIGHT) / (CLOUD_HEIGHT_MAX - CLOUD_HEIGHT_MIN)) * 10.5 + 0.7; + float shade = (pos.z - CLOUD_AVG_HEIGHT) / (CLOUD_HEIGHT_MAX - CLOUD_HEIGHT_MIN) * 5.0 + 0.3; return vec2(shade, density / (1.0 + vsum(abs(pos - cam_pos.xyz)) / 5000)); } diff --git a/assets/voxygen/shaders/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl index 7a38f62146..020edd0156 100644 --- a/assets/voxygen/shaders/terrain-frag.glsl +++ b/assets/voxygen/shaders/terrain-frag.glsl @@ -21,10 +21,6 @@ out vec4 tgt_color; #include #include -float vmin(vec3 v) { - return min(v.x, min(v.y, v.z)); -} - void main() { // First 3 normals are negative, next 3 are positive vec3 normals[6] = vec3[](vec3(-1,0,0), vec3(1,0,0), vec3(0,-1,0), vec3(0,1,0), vec3(0,0,-1), vec3(0,0,1)); diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index b9f8391a25..ff06971d1d 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -276,7 +276,7 @@ fn mesh_worker + RectRasterableVol + ReadVol + Debug>( 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 % 8) as u8); + let ori = block.get_ori().unwrap_or((seed % 4) as u8 * 2); let instance = SpriteInstance::new( Mat4::identity() From a7b5d6b2700bce41860f532ceaad2d3677fb28da Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 23 Apr 2020 13:29:22 +0100 Subject: [PATCH 176/195] Fix minor bugs with NPCs --- common/src/generation.rs | 2 +- common/src/path.rs | 4 ++-- common/src/sys/agent.rs | 14 ++++++-------- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/common/src/generation.rs b/common/src/generation.rs index b39a8a2ac6..f33583d243 100644 --- a/common/src/generation.rs +++ b/common/src/generation.rs @@ -27,7 +27,7 @@ impl EntityInfo { alignment: Alignment::Wild, body: Body::Humanoid(humanoid::Body::random()), name: None, - main_tool: None, + main_tool: Some(Item::empty()), } } diff --git a/common/src/path.rs b/common/src/path.rs index 77906f8a56..35c744b211 100644 --- a/common/src/path.rs +++ b/common/src/path.rs @@ -74,8 +74,8 @@ impl Route { if vol.get(next).map(|b| b.is_solid()).unwrap_or(false) { None } else { - let next_tgt = next.map(|e| e as f32) + 0.5; - if ((pos - next_tgt) * Vec3::new(1.0, 1.0, 0.3)).magnitude_squared() + let next_tgt = next.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.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; diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 1cd87e55a6..af72e1b215 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -113,7 +113,8 @@ impl<'a> System<'a> for Sys { const SIGHT_DIST: f32 = 128.0; const MIN_ATTACK_DIST: f32 = 3.25; - let traversal_tolerance = scales.get(entity).map(|s| s.0).unwrap_or(1.0); + let scale = scales.get(entity).map(|s| s.0).unwrap_or(1.0); + let traversal_tolerance = scale; let mut do_idle = false; let mut choose_target = false; @@ -248,19 +249,16 @@ 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; - if let Tactic::Melee = tactic { - inputs.primary.set_state(true); - } else if let Tactic::Staff = tactic { - inputs.primary.set_state(true); - } else { - inputs.roll.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) From 444f2ecb45c35eb49973c99fb85954db9877bc2c Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 23 Apr 2020 15:01:37 +0100 Subject: [PATCH 177/195] Made /waypoint admin-only, MR cleanup --- assets/voxygen/shaders/fluid-vert.glsl | 4 +- assets/voxygen/shaders/terrain-frag.glsl | 2 +- assets/voxygen/shaders/terrain-vert.glsl | 4 +- common/src/generation.rs | 2 +- common/src/sys/agent.rs | 4 ++ server/src/cmd.rs | 2 +- server/src/sys/terrain.rs | 86 +----------------------- voxygen/src/mesh/segment.rs | 16 ++--- voxygen/src/mesh/terrain.rs | 80 +++++++++++----------- voxygen/src/mesh/vol.rs | 19 ++---- voxygen/src/render/mesh.rs | 2 - world/src/civ/mod.rs | 7 +- world/src/sim/map.rs | 2 +- world/src/site/settlement/mod.rs | 13 ---- 14 files changed, 66 insertions(+), 177 deletions(-) diff --git a/assets/voxygen/shaders/fluid-vert.glsl b/assets/voxygen/shaders/fluid-vert.glsl index cb7effea26..52907b1ef6 100644 --- a/assets/voxygen/shaders/fluid-vert.glsl +++ b/assets/voxygen/shaders/fluid-vert.glsl @@ -27,7 +27,7 @@ void main() { // Small waves f_pos.xy += 0.01; // Avoid z-fighting - f_pos.z -= 0.1 * (sin(tick.x * 2.0 + f_pos.x * 2.0 + f_pos.y * 2.0) + 1.0) * 0.5 - 0.1; + 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), @@ -39,8 +39,6 @@ void main() { f_pos_norm = v_pos_norm; - f_pos.z -= 0.2; - gl_Position = all_mat * vec4(f_pos, 1); diff --git a/assets/voxygen/shaders/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl index 020edd0156..a1d526bd95 100644 --- a/assets/voxygen/shaders/terrain-frag.glsl +++ b/assets/voxygen/shaders/terrain-frag.glsl @@ -45,7 +45,7 @@ void main() { diffuse_light *= f_light * ao; diffuse_light += point_light * ao; - vec3 col = f_col + hash(vec4(floor(f_chunk_pos * 3.0), 0)) * 0.02; // Small-scale noise + vec3 col = f_col + hash(vec4(floor(f_chunk_pos * 3.0 + 0.5), 0)) * 0.02; // Small-scale noise vec3 surf_color = illuminate(srgb_to_linear(col), light, diffuse_light, ambient_light); float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x); diff --git a/assets/voxygen/shaders/terrain-vert.glsl b/assets/voxygen/shaders/terrain-vert.glsl index eb81ee8bef..0b4664b24d 100644 --- a/assets/voxygen/shaders/terrain-vert.glsl +++ b/assets/voxygen/shaders/terrain-vert.glsl @@ -19,10 +19,10 @@ out vec3 f_col; out float f_light; out float f_ao; -const float EXTRA_NEG_Z = 65536.0; +const int EXTRA_NEG_Z = 65536; void main() { - f_chunk_pos = vec3((uvec3(v_pos_norm) >> uvec3(0, 6, 12)) & uvec3(0x3Fu, 0x3Fu, 0x1FFFFu)) - vec3(0, 0, EXTRA_NEG_Z); + 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 -= 250.0 * (1.0 - min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.0)); diff --git a/common/src/generation.rs b/common/src/generation.rs index f33583d243..22cf420021 100644 --- a/common/src/generation.rs +++ b/common/src/generation.rs @@ -101,7 +101,7 @@ impl ChunkSupplement { pub fn add_entity(&mut self, entity: EntityInfo) { self.entities.push(entity); } } -fn get_npc_name< +pub fn get_npc_name< 'a, Species, SpeciesData: for<'b> core::ops::Index<&'b Species, Output = npc::SpeciesNames>, diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index af72e1b215..092777e205 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -114,6 +114,10 @@ impl<'a> System<'a> for Sys { 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; 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/sys/terrain.rs b/server/src/sys/terrain.rs index d268daa5f0..b76d06ccec 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -5,9 +5,10 @@ use common::{ comp::{self, item, CharacterAbility, Item, ItemConfig, Player, Pos}, event::{EventBus, ServerEvent}, msg::ServerMsg, - npc::{self, NPC_NAMES}, + npc::NPC_NAMES, state::TerrainChanges, terrain::TerrainGrid, + generation::get_npc_name, }; use rand::Rng; use specs::{Join, Read, ReadStorage, System, Write, WriteExpect, WriteStorage}; @@ -105,89 +106,6 @@ impl<'a> System<'a> for Sys { continue; } - 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 - } - - /* - 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, alignment) = SPAWN_NPCS - .choose(&mut rand::thread_rng()) - .expect("SPAWN_NPCS is nonempty")( - ); - */ - let mut body = entity.body; let name = entity.name.unwrap_or("Unnamed".to_string()); let alignment = entity.alignment; diff --git a/voxygen/src/mesh/segment.rs b/voxygen/src/mesh/segment.rs index b49e3a4799..962a6ed93e 100644 --- a/voxygen/src/mesh/segment.rs +++ b/voxygen/src/mesh/segment.rs @@ -44,15 +44,11 @@ impl Meshable for Segment { 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) - { - Some(1.0) - } else { - None - }; + .then_some(1.0); } } } @@ -97,15 +93,11 @@ impl Meshable for Segment { 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) - { - Some(1.0) - } else { - None - }; + .then_some(1.0); } } } diff --git a/voxygen/src/mesh/terrain.rs b/voxygen/src/mesh/terrain.rs index 59e9be7aef..402b442ed3 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 FnMut(Vec3) -> Option + '_ { +) -> impl FnMut(Vec3) -> f32 + '_ { const UNKNOWN: u8 = 255; const OPAQUE: u8 = 254; const SUNLIGHT: u8 = 24; @@ -189,22 +189,12 @@ fn calc_light + ReadVol + Debug>( } move |wpos| { - if vol_cached - .get(wpos) - .map(|block| block.is_opaque()) - .unwrap_or(false) - { - None - } else { - let pos = wpos - outer.min; - Some( - light_map - .get(lm_idx(pos.x, pos.y, pos.z)) - .filter(|l| **l != OPAQUE && **l != UNKNOWN) - .map(|l| *l as f32 / SUNLIGHT as f32) - .unwrap_or(0.0), - ) - } + let pos = wpos - outer.min; + light_map + .get(lm_idx(pos.x, pos.y, pos.z)) + .filter(|l| **l != OPAQUE && **l != UNKNOWN) + .map(|l| *l as f32 / SUNLIGHT as f32) + .unwrap_or(0.0) } } @@ -307,15 +297,32 @@ impl + ReadVol + Debug> Meshable + ReadVol + Debug> Meshable + ReadVol + Debug> Meshable| { let pos = (pos + 1).map(|e| e as usize); - unsafe { - darknesses - .get_unchecked(pos.z) - .get_unchecked(pos.y) - .get_unchecked(pos.x) - .is_none() - } + darknesses[pos.z][pos.y][pos.x].is_none() }; let (s1, s2) = ( @@ -45,12 +39,11 @@ fn get_ao_quad( for x in 0..2 { for y in 0..2 { let dark_pos = shift + offs[0] * x + offs[1] * y + 1; - if let Some(dark) = unsafe { - darknesses - .get_unchecked(dark_pos.z as usize) - .get_unchecked(dark_pos.y as usize) - .get_unchecked(dark_pos.x as usize) - } { + 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; } diff --git a/voxygen/src/render/mesh.rs b/voxygen/src/render/mesh.rs index 2b708f1fde..d0c48275c6 100644 --- a/voxygen/src/render/mesh.rs +++ b/voxygen/src/render/mesh.rs @@ -66,8 +66,6 @@ impl Mesh

{ } } - pub fn verts(&self) -> &[P::Vertex] { &self.verts } - pub fn iter(&self) -> std::slice::Iter { self.verts.iter() } } diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 103e9b7d14..901e09523e 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -54,7 +54,7 @@ impl Civs { for _ in 0..INITIAL_CIV_COUNT { log::info!("Creating civilisation..."); - if let None = this.birth_civ(&mut ctx.reseed()) { + if this.birth_civ(&mut ctx.reseed()).is_none() { log::warn!("Failed to find starting site for civilisation."); } } @@ -481,8 +481,9 @@ fn find_path( .and_then(|path| astar.get_cheapest_cost().map(|cost| (path, cost))) } -/// Return true if travel between a location and a chunk next to it is permitted -/// (TODO: by whom?) +/// 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)?; diff --git a/world/src/sim/map.rs b/world/src/sim/map.rs index 4dd96c3700..d37e5641a5 100644 --- a/world/src/sim/map.rs +++ b/world/src/sim/map.rs @@ -298,7 +298,7 @@ impl MapConfig { }; let rgba = if is_path { - (0x20, 0x19, 0x13, 255) + (0x20, 0x19, 0x13, 0xFF) } else { rgba }; diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 459ad921ef..56a0d2b67e 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -44,19 +44,6 @@ pub fn intersect(a: [Vec2; 2], b: [Vec2; 2]) -> Option> { } } -pub fn dist_to_line(line: [Vec2; 2], p: Vec2) -> f32 { - let lsq = line[0].distance_squared(line[1]); - - if lsq == 0.0 { - line[0].distance(p) - } else { - let t = ((p - line[0]).dot(line[1] - line[0]) / lsq) - .max(0.0) - .min(1.0); - p.distance(line[0] + (line[1] - line[0]) * t) - } -} - 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]]); From cd5e4ef3b78fff709ceafb96b1609b8296fb1b12 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 23 Apr 2020 15:14:42 +0100 Subject: [PATCH 178/195] Better path colour on map, typo fixes --- assets/common/items/coconut.ron | 6 +++--- world/src/sim/map.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/common/items/coconut.ron b/assets/common/items/coconut.ron index 76d3212e39..219af26991 100644 --- a/assets/common/items/coconut.ron +++ b/assets/common/items/coconut.ron @@ -1,12 +1,12 @@ Item( name: "Coconut", - description: "Reliable Source of water and fat. + description: "Reliable source of water and fat. -Restores 20 Health.", +Restores 30 health.", kind: Consumable( kind: Coconut, effect: Health(( - amount: 20, + amount: 30, cause: Item, )), ), diff --git a/world/src/sim/map.rs b/world/src/sim/map.rs index d37e5641a5..69e22466da 100644 --- a/world/src/sim/map.rs +++ b/world/src/sim/map.rs @@ -298,7 +298,7 @@ impl MapConfig { }; let rgba = if is_path { - (0x20, 0x19, 0x13, 0xFF) + (0x37, 0x29, 0x23, 0xFF) } else { rgba }; From 57153a1720ccebab843c1053f888ea505d14e9e2 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 23 Apr 2020 15:18:42 +0100 Subject: [PATCH 179/195] Balanced dungeon spawn rates slightly --- world/src/site/dungeon/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index 87ef02cf18..81dfc57d9b 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -288,8 +288,8 @@ impl Floor { self.create_room(Room { seed: ctx.rng.gen(), - loot_density: 0.00005 + level as f32 * 0.00015, - enemy_density: 0.0005 + level as f32 * 0.00005, + loot_density: 0.000025 + level as f32 * 0.00015, + enemy_density: 0.001 + level as f32 * 0.00004, area, }); } From 982886b3f8311d82b3d3e27067fd72c2f05cbcd2 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 23 Apr 2020 15:30:19 +0100 Subject: [PATCH 180/195] Made players spawn in the nearest town to the centre of the world, delete object.zip --- assets/voxygen/voxel/object.zip | Bin 154285 -> 0 bytes server/src/lib.rs | 13 ++++++++++++- world/src/civ/mod.rs | 4 ++-- 3 files changed, 14 insertions(+), 3 deletions(-) delete mode 100644 assets/voxygen/voxel/object.zip diff --git a/assets/voxygen/voxel/object.zip b/assets/voxygen/voxel/object.zip deleted file mode 100644 index 400c60dd6a8ae9ca77b66546f19dace23e875bf0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 154285 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 M5XWBGy&OpW9}XJi4*&oF diff --git a/server/src/lib.rs b/server/src/lib.rs index 825583c5d9..d823ba2480 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -51,6 +51,7 @@ use vek::*; use world::{ sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP, WORLD_SIZE}, World, + civ::SiteKind, }; const CLIENT_TIMEOUT: f64 = 20.0; // Seconds @@ -126,7 +127,17 @@ 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) diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 901e09523e..6606682da1 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -632,8 +632,8 @@ pub struct Track { #[derive(Debug)] pub struct Site { - kind: SiteKind, - center: Vec2, + pub kind: SiteKind, + pub center: Vec2, pub place: Id, population: f32, From 840078d2b790e19e0d856502259e6025549818a6 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 23 Apr 2020 15:35:22 +0100 Subject: [PATCH 181/195] Gave some villagers weapons --- world/src/site/settlement/mod.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 56a0d2b67e..704c343c7d 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -16,6 +16,7 @@ use common::{ store::{Id, Store}, terrain::{Block, BlockKind, TerrainChunkSize}, vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol}, + assets, }; use hashbrown::{HashMap, HashSet}; use rand::prelude::*; @@ -801,6 +802,14 @@ impl Settlement { }, _ => 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); From 01e2cd2b881a068588d425dca5a5ca1279e6f325 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 23 Apr 2020 16:16:08 +0100 Subject: [PATCH 182/195] Warning cleanup --- voxygen/src/anim/biped_large/run.rs | 2 - world/src/block/mod.rs | 3 +- world/src/civ/econ.rs | 6 +- world/src/civ/mod.rs | 16 ++-- world/src/column/mod.rs | 4 +- world/src/layer/mod.rs | 7 +- world/src/lib.rs | 4 +- world/src/sim/mod.rs | 79 ++----------------- world/src/site/dungeon/mod.rs | 41 +++------- world/src/site/mod.rs | 5 +- .../settlement/building/archetype/house.rs | 2 + .../settlement/building/archetype/keep.rs | 6 +- world/src/site/settlement/building/mod.rs | 1 - world/src/site/settlement/mod.rs | 46 ++++++----- 14 files changed, 69 insertions(+), 153 deletions(-) diff --git a/voxygen/src/anim/biped_large/run.rs b/voxygen/src/anim/biped_large/run.rs index 9c54a2ac61..e41f67a41e 100644 --- a/voxygen/src/anim/biped_large/run.rs +++ b/voxygen/src/anim/biped_large/run.rs @@ -19,8 +19,6 @@ impl Animation for RunAnimation { 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 + PI * 1.4).sin(); diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index ebbfe5ecc5..8aa8020af6 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -172,7 +172,6 @@ impl<'a> BlockGen<'a> { close_cliffs, temp, humidity, - chunk, stone_col, .. } = sample; @@ -181,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 diff --git a/world/src/civ/econ.rs b/world/src/civ/econ.rs index 97181d94c7..5245627305 100644 --- a/world/src/civ/econ.rs +++ b/world/src/civ/econ.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + use super::GenCtx; use rand::prelude::*; @@ -25,7 +27,7 @@ impl Belief { self.price + ctx.rng.gen_range(-1.0, 1.0) * self.confidence } - pub fn update_buyer(&mut self, years: f32, new_price: f32) { + pub fn update_buyer(&mut self, _years: f32, new_price: f32) { if (self.price - new_price).abs() < self.confidence { self.confidence *= 0.8; } else { @@ -41,7 +43,7 @@ impl Belief { } pub fn buy_units<'a>( - ctx: &mut GenCtx, + _ctx: &mut GenCtx, sellers: impl Iterator, max_quantity: f32, max_price: f32, diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 6606682da1..bd56b24e1e 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -1,7 +1,9 @@ +#![allow(dead_code)] + mod econ; use crate::{ - sim::{SimChunk, WorldSim}, + sim::WorldSim, site::{Dungeon, Settlement, Site as WorldSite}, util::{attempt, seed_expan, CARDINALS, NEIGHBORS}, }; @@ -169,6 +171,7 @@ impl Civs { 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); @@ -336,7 +339,7 @@ impl Civs { .sites .iter_ids() .map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt())) - .filter(|(p, dist)| *dist < MAX_NEIGHBOR_DISTANCE) + .filter(|(_, dist)| *dist < MAX_NEIGHBOR_DISTANCE) .collect::>(); nearby.sort_by_key(|(_, dist)| *dist as i32); @@ -386,7 +389,7 @@ impl Civs { Some(site) } - fn tick(&mut self, ctx: &mut GenCtx, years: f32) { + 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); } @@ -761,7 +764,7 @@ impl Site { let stocks = &self.stocks; self.surplus = demand .clone() - .map(|stock, tgt| supply[stock] + stocks[stock] - demand[stock] - last_exports[stock]); + .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; @@ -783,7 +786,6 @@ impl Site { / values.iter().filter(|(_, v)| v.is_some()).count() as f32; let export_targets = &mut self.export_targets; let last_exports = &self.last_exports; - let trade_states = &self.trade_states; 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 }; @@ -823,7 +825,7 @@ impl Site { .iter() .map(|(stock, amount)| { // What quantity is this order requesting? - let quantity = *amount * scale; + 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 @@ -946,7 +948,7 @@ impl MapVec { pub fn get(&self, entry: K) -> &T { self.entries.get(&entry).unwrap_or(&self.default) } - pub fn map(mut self, mut f: impl FnMut(K, T) -> U) -> MapVec { + pub fn map(self, mut f: impl FnMut(K, T) -> U) -> MapVec { MapVec { entries: self .entries diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index 2204c9b91d..e436f23c54 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -217,7 +217,7 @@ 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; @@ -590,7 +590,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let near_cliffs = sim_chunk.near_cliffs; let river_gouge = 0.5; - let (in_water, water_dist, alt_, water_level, riverless_alt, warp_factor) = if let Some(( + let (_in_water, water_dist, alt_, water_level, riverless_alt, warp_factor) = if let Some(( max_border_river_pos, river_chunk, max_border_river, diff --git a/world/src/layer/mod.rs b/world/src/layer/mod.rs index 87642c991b..56aebdbbba 100644 --- a/world/src/layer/mod.rs +++ b/world/src/layer/mod.rs @@ -2,8 +2,7 @@ use std::f32; use vek::*; use common::{ terrain::{Block, BlockKind}, - vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol}, - spiral::Spiral2d, + vol::{BaseVol, ReadVol, RectSizedVol, Vox, WriteVol}, }; use crate::{ column::ColumnSample, @@ -67,7 +66,7 @@ pub fn apply_paths_to<'a>( let surface_z = (riverless_alt + bridge_offset).floor() as i32; for z in inset - depth..inset { - vol.set( + 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)) @@ -81,7 +80,7 @@ pub fn apply_paths_to<'a>( 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 { - vol.set(pos, Block::empty()); + let _ = vol.set(pos, Block::empty()); } } } diff --git a/world/src/lib.rs b/world/src/lib.rs index dc9cfcb912..5fb8a99faf 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -21,7 +21,7 @@ use crate::{ util::{Grid, Sampler}, }; use common::{ - comp::{self, bird_medium, critter, humanoid, quadruped_medium, quadruped_small, Alignment}, + comp::{self, bird_medium, critter, quadruped_medium, quadruped_small}, generation::{ChunkSupplement, EntityInfo}, terrain::{Block, BlockKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize}, vol::{ReadVol, RectVolSize, Vox, WriteVol}, @@ -124,7 +124,6 @@ impl World { }; let offs = Vec2::new(x, y); - let wpos2d = chunk_wpos2d + offs; let z_cache = match zcache_grid.get(grid_border + offs) { Some(Some(z_cache)) => z_cache, @@ -187,7 +186,6 @@ impl World { }; const SPAWN_RATE: f32 = 0.1; - const BOSS_RATE: f32 = 0.03; let mut supplement = ChunkSupplement { entities: if rng.gen::() < SPAWN_RATE && sim_chunk.chaos < 0.5 diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index d651df51a6..47d6fa7e23 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -25,12 +25,10 @@ pub use self::{ use crate::{ all::ForestKind, - block::BlockGen, civ::Place, - column::ColumnGen, - site::{Settlement, Site}, + site::Site, util::{ - seed_expan, FastNoise, RandomField, Sampler, StructureGen2d, CARDINAL_LOCALITY, LOCALITY, + seed_expan, FastNoise, RandomField, StructureGen2d, LOCALITY, NEIGHBORS, }, CONFIG, @@ -41,7 +39,6 @@ use common::{ terrain::{BiomeKind, TerrainChunkSize}, vol::RectVolSize, }; -use hashbrown::HashMap; use noise::{ BasicMulti, Billow, Fbm, HybridMulti, MultiFractal, NoiseFn, RangeFunction, RidgedMulti, Seedable, SuperSimplex, Worley, @@ -57,7 +54,6 @@ use std::{ io::{BufReader, BufWriter}, ops::{Add, Div, Mul, Neg, Sub}, path::PathBuf, - sync::Arc, }; use vek::*; @@ -1392,9 +1388,9 @@ 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 .par_iter_mut() .enumerate() @@ -1432,69 +1428,6 @@ 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 sites = self - .gen_ctx - .town_gen - .par_iter( - chunk_idx_center(Vec2::zero()), - chunk_idx_center(WORLD_SIZE.map(|e| e as i32)), - ) - .map_init( - || (), - |_, (pos, seed)| { - let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed)); - ( - pos, - Site::from(Settlement::generate(pos, Some(self), &mut rng)), - ) - }, - ) - .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); - - if let Some((pos, site)) = sites - .iter() - .filter(|(pos, site)| { - pos.map(|e| e as f32).distance(wpos.map(|e| e as f32)) < site.radius() - }) - .min_by_key(|(pos, _)| wpos.distance_squared(*pos)) - { - chunk.sites.push(site.clone()); } }); */ @@ -1798,7 +1731,7 @@ impl WorldSim { return None; } - let (start_pos, start_idx) = if chunk_connections != 2 { + let (start_pos, _start_idx) = if chunk_connections != 2 { (ctrl_pos, None) } else { let (start_idx, start_rpos) = NEIGHBORS @@ -1820,7 +1753,7 @@ impl WorldSim { .iter() .enumerate() .filter(move |(i, _)| chunk.path.neighbors & (1 << *i as u8) != 0) - .filter_map(move |(i, end_rpos)| { + .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; @@ -2116,7 +2049,7 @@ impl SimChunk { 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 diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index 81dfc57d9b..14837717c6 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -1,27 +1,25 @@ use super::SpawnRules; use crate::{ column::ColumnSample, - sim::{SimChunk, WorldSim}, + sim::WorldSim, site::BlockMask, - util::{attempt, DIRS, CARDINALS, Grid, RandomField, Sampler, StructureGen2d}, + util::{attempt, DIRS, CARDINALS, Grid, RandomField, Sampler}, }; use common::{ assets, astar::Astar, comp, 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 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()) @@ -36,6 +34,7 @@ impl WorldSim { pub struct Dungeon { origin: Vec2, alt: i32, + #[allow(dead_code)] noise: RandomField, floors: Vec, } @@ -48,7 +47,7 @@ pub struct GenCtx<'a, R: Rng> { impl Dungeon { pub fn generate(wpos: Vec2, sim: Option<&WorldSim>, rng: &mut impl Rng) -> Self { let mut ctx = GenCtx { sim, rng }; - let mut this = Self { + let this = Self { origin: wpos, alt: ctx .sim @@ -70,7 +69,7 @@ impl Dungeon { pub fn radius(&self) -> f32 { 1200.0 } - pub fn spawn_rules(&self, wpos: Vec2) -> SpawnRules { + pub fn spawn_rules(&self, _wpos: Vec2) -> SpawnRules { SpawnRules { ..SpawnRules::default() } @@ -79,11 +78,9 @@ impl Dungeon { pub fn apply_to<'a>( &'a self, wpos2d: Vec2, - mut get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, + _get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, vol: &mut (impl BaseVol + RectSizedVol + ReadVol + WriteVol), ) { - let rand_field = RandomField::new(0); - 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); @@ -91,17 +88,6 @@ impl Dungeon { 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; - - let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE)); - let tile_center = tile_pos * TILE_SIZE + TILE_SIZE / 2; - let mut z = self.alt; for floor in &self.floors { z -= floor.total_depth(); @@ -110,7 +96,7 @@ impl Dungeon { for rz in 0..floor.total_depth() { if let Some(block) = sampler(rz).finish() { - vol.set(Vec3::new(offs.x, offs.y, z + rz), block); + let _ = vol.set(Vec3::new(offs.x, offs.y, z + rz), block); } } } @@ -122,7 +108,7 @@ impl Dungeon { &'a self, rng: &mut impl Rng, wpos2d: Vec2, - mut get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, + _get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, supplement: &mut ChunkSupplement, ) { let rpos = wpos2d - self.origin; @@ -189,6 +175,7 @@ pub struct Floor { rooms: Store, solid_depth: i32, hollow_depth: i32, + #[allow(dead_code)] stair_tile: Vec2, } @@ -295,8 +282,7 @@ impl Floor { } } - fn create_route(&mut self, ctx: &mut GenCtx, a: Vec2, b: Vec2) { - let sim = &ctx.sim; + 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; @@ -305,7 +291,7 @@ impl Floor { .map(move |dir| l + dir) .filter(|pos| self.tiles.get(*pos).is_some()) }; - let transition = |a: &Vec2, b: &Vec2| match self.tiles.get(*b) { + 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, @@ -401,7 +387,6 @@ impl Floor { pub fn nearest_wall(&self, rpos: Vec2) -> Option> { let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE)); - let tile_center = tile_pos * TILE_SIZE + TILE_SIZE / 2; DIRS.iter() .map(|dir| tile_pos + *dir) diff --git a/world/src/site/mod.rs b/world/src/site/mod.rs index 90eaee3422..12cd3d996b 100644 --- a/world/src/site/mod.rs +++ b/world/src/site/mod.rs @@ -4,10 +4,7 @@ mod settlement; // Reexports pub use self::{dungeon::Dungeon, settlement::Settlement}; -use crate::{ - column::ColumnSample, - util::{Grid, Sampler}, -}; +use crate::column::ColumnSample; use common::{ generation::ChunkSupplement, terrain::Block, diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index 1302d667d3..5db70181d4 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + use super::{super::skeleton::*, Archetype}; use crate::{ site::BlockMask, diff --git a/world/src/site/settlement/building/archetype/keep.rs b/world/src/site/settlement/building/archetype/keep.rs index 9810ec5e48..6354413547 100644 --- a/world/src/site/settlement/building/archetype/keep.rs +++ b/world/src/site/settlement/building/archetype/keep.rs @@ -46,9 +46,9 @@ impl Archetype for Keep { &self, dist: i32, bound_offset: Vec2, - center_offset: Vec2, + _center_offset: Vec2, z: i32, - ori: Ori, + _ori: Ori, branch: &Branch, ) -> BlockMask { let profile = Vec2::new(bound_offset.x, z); @@ -57,14 +57,12 @@ impl Archetype for Keep { |r, g, b| BlockMask::new(Block::new(BlockKind::Normal, Rgb::new(r, g, b)), 2); let foundation = make_block(100, 100, 100); - let log = make_block(60, 45, 30); 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 roof_height = 12 + width; let ceil_height = 16; if profile.y <= 1 - (dist - width - 1).max(0) && dist < width + 3 { diff --git a/world/src/site/settlement/building/mod.rs b/world/src/site/settlement/building/mod.rs index 97087a4af7..7687bd4bf9 100644 --- a/world/src/site/settlement/building/mod.rs +++ b/world/src/site/settlement/building/mod.rs @@ -22,7 +22,6 @@ impl Building { where A: Sized, { - let len = rng.gen_range(-8, 12).max(0); let (archetype, skel) = A::generate(rng); Self { skel, diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 704c343c7d..700f58927d 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -4,12 +4,12 @@ use self::building::HouseBuilding; use super::SpawnRules; use crate::{ column::ColumnSample, - sim::{SimChunk, WorldSim}, - util::{Grid, RandomField, Sampler, StructureGen2d}, + sim::WorldSim, + util::{RandomField, Sampler, StructureGen2d}, }; use common::{ astar::Astar, - comp::{self, bird_medium, critter, humanoid, quadruped_medium, quadruped_small}, + comp::{self, bird_medium, humanoid, quadruped_small}, generation::{ChunkSupplement, EntityInfo}, path::Path, spiral::Spiral2d, @@ -23,11 +23,13 @@ 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); @@ -45,6 +47,7 @@ pub fn intersect(a: [Vec2; 2], b: [Vec2; 2]) -> Option> { } } +#[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]]); @@ -108,6 +111,7 @@ pub struct Town { } pub struct Farm { + #[allow(dead_code)] base_tile: Vec2, } @@ -524,7 +528,7 @@ impl Settlement { let surface_z = (col.riverless_alt + bridge_offset).floor() as i32; for z in inset - depth..inset { - vol.set( + 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)) @@ -537,11 +541,11 @@ impl Settlement { 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 { - vol.set(pos, Block::empty()); + let _ = vol.set(pos, Block::empty()); } } // Ground colour - } else if let Some(color) = self.get_color(rpos) { + } else { let mut surface_block = None; let roll = |seed, n| { @@ -553,7 +557,7 @@ impl Settlement { 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_dist, path_nearest)) = col_sample.path { + 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 @@ -647,13 +651,13 @@ impl Settlement { let pos = Vec3::new(offs.x, offs.y, surface_z + z); if let (0, Some(block)) = (z, surface_block) { - vol.set(pos, block); + let _ = vol.set(pos, block); } else if z >= 0 { if vol.get(pos).unwrap().kind() != BlockKind::Water { - vol.set(pos, Block::empty()); + let _ = vol.set(pos, Block::empty()); } } else { - vol.set( + let _ = vol.set( pos, Block::new(BlockKind::Normal, noisy_color(color, 4)), ); @@ -681,7 +685,7 @@ impl Settlement { for z in z_offset..12 { if dist / WayKind::Wall.width() < ((1.0 - z as f32 / 12.0) * 2.0).min(1.0) { - vol.set( + let _ = vol.set( Vec3::new(offs.x, offs.y, surface_z + z), Block::new(BlockKind::Normal, color), ); @@ -692,7 +696,7 @@ impl Settlement { // Towers if let Some((Tower::Wall, _pos)) = sample.tower { for z in -2..16 { - vol.set( + let _ = vol.set( Vec3::new(offs.x, offs.y, surface_z + z), Block::new(BlockKind::Normal, Rgb::new(50, 50, 50)), ); @@ -715,7 +719,6 @@ impl Settlement { match &structure.kind { StructureKind::House(b) => { - let centre = b.bounds_2d().center(); let bounds = b.bounds(); for x in bounds.min.x..bounds.max.x + 1 { @@ -735,7 +738,7 @@ impl Settlement { let coffs = wpos - Vec3::from(wpos2d); if let Some(block) = b.sample(rpos) { - vol.set(coffs, block); + let _ = vol.set(coffs, block); } } } @@ -765,7 +768,6 @@ impl Settlement { } else { continue; }; - let surface_z = col_sample.riverless_alt.floor() as i32; let sample = self.land.get_at_block(rpos); @@ -907,7 +909,7 @@ const CARDINALS: [Vec2; 4] = [ #[derive(Copy, Clone, PartialEq)] pub enum WayKind { Path, - Hedge, + #[allow(dead_code)] Wall, } @@ -915,14 +917,14 @@ impl WayKind { pub fn width(&self) -> f32 { match self { WayKind::Path => 4.0, - WayKind::Hedge => 1.5, - WayKind::Wall => 2.5, + WayKind::Wall => 3.0, } } } #[derive(Copy, Clone, PartialEq)] pub enum Tower { + #[allow(dead_code)] Wall, } @@ -988,7 +990,7 @@ impl Land { } } - for (i, dir) in CARDINALS.iter().enumerate() { + for (i, _) in CARDINALS.iter().enumerate() { let map = [1, 5, 7, 3]; let line = LineSegment2 { start: neighbors[4].0.map(|e| e as f32), @@ -1013,6 +1015,7 @@ impl Land { 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) } @@ -1046,6 +1049,7 @@ impl Land { .find(|pos| match_fn(self.plot_at(*pos))) } + #[allow(dead_code)] fn find_tile_dir( &self, origin: Vec2, @@ -1081,7 +1085,7 @@ impl Land { &self, start: Vec2, max_size: usize, - rng: &mut impl Rng, + _rng: &mut impl Rng, mut match_fn: impl FnMut(Option<&Plot>) -> bool, ) -> HashSet> { let mut open = VecDeque::new(); @@ -1137,7 +1141,7 @@ impl Land { if self.tile_at(tiles[0]).is_none() { self.set(tiles[0], self.hazard); } - let mut plots = &self.plots; + let plots = &self.plots; self.tiles .get_mut(&tiles[1]) From 44ace13d85c7ebcaa3522362d401a8cd500d0b29 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 23 Apr 2020 16:26:36 +0100 Subject: [PATCH 183/195] Stopped player spawning underground --- server/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/lib.rs b/server/src/lib.rs index d823ba2480..82fa74c41a 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -141,7 +141,7 @@ impl Server { // 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(); From 06dc96c56b300136404ff752188ca4d2dd857b4a Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 23 Apr 2020 16:51:35 +0100 Subject: [PATCH 184/195] Made paths connect at the ends, removed paths from dungeons --- world/src/civ/mod.rs | 80 +++++++++++++++++++++++-------------------- world/src/util/mod.rs | 16 ++++----- 2 files changed, 50 insertions(+), 46 deletions(-) diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index bd56b24e1e..968731825c 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -61,7 +61,7 @@ impl Civs { } } - for _ in 0..32 { + 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 { @@ -343,45 +343,49 @@ impl Civs { .collect::>(); nearby.sort_by_key(|(_, dist)| *dist as i32); - 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; + 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; - 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), - ); + 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); } - - // Take note of the track - let track = self.tracks.insert(Track { cost, path }); - self.track_map - .entry(site) - .or_default() - .insert(nearby, track); } } } diff --git a/world/src/util/mod.rs b/world/src/util/mod.rs index 89b0be7241..ce4dbd3971 100644 --- a/world/src/util/mod.rs +++ b/world/src/util/mod.rs @@ -34,25 +34,25 @@ pub const CARDINALS: [Vec2; 4] = [ ]; pub const DIRS: [Vec2; 8] = [ - 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(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(0, 1), Vec2::new(1, 0), - Vec2::new(0, -1), - Vec2::new(-1, 0), Vec2::new(1, 1), - 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] = [ From 0c491bb558343d42561a5203ceff062a08cc64f8 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 23 Apr 2020 16:57:47 +0100 Subject: [PATCH 185/195] Fixed incorrect chunk render counter --- voxygen/src/scene/terrain.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index ff06971d1d..b91481a136 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -1948,9 +1948,11 @@ impl Terrain { if *n >= chunks.len() { None } else { - *n += 1; let pos = focus_chunk + rpos; - Some(chunks.get(&pos).map(|c| (pos, c))) + Some(chunks.get(&pos).map(|c| { + *n += 1; + (pos, c) + })) } }) .filter_map(|x| x); From 2a6a19f7ef91a3a0769d71a4b973c1330253525c Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 23 Apr 2020 17:00:48 +0100 Subject: [PATCH 186/195] fmt --- common/src/path.rs | 3 +- common/src/sys/agent.rs | 7 ++- common/src/sys/phys.rs | 16 +++-- common/src/terrain/block.rs | 8 ++- server/src/lib.rs | 6 +- server/src/sys/terrain.rs | 2 +- voxygen/src/mesh/terrain.rs | 33 +++++++---- voxygen/src/mesh/vol.rs | 6 +- world/src/civ/mod.rs | 9 ++- world/src/column/mod.rs | 11 ++-- world/src/layer/mod.rs | 26 +++++---- world/src/lib.rs | 2 +- world/src/sim/map.rs | 58 +++++++++++-------- world/src/sim/mod.rs | 5 +- world/src/sim/path.rs | 4 +- world/src/site/dungeon/mod.rs | 23 ++++---- .../settlement/building/archetype/house.rs | 44 ++++++++------ world/src/site/settlement/mod.rs | 43 ++++++++------ 18 files changed, 177 insertions(+), 129 deletions(-) diff --git a/common/src/path.rs b/common/src/path.rs index 35c744b211..a3c649fc57 100644 --- a/common/src/path.rs +++ b/common/src/path.rs @@ -75,7 +75,8 @@ 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::unit_z() * 0.5)) * Vec3::new(1.0, 1.0, 0.3)).magnitude_squared() + 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; diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 092777e205..61ce280579 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -115,9 +115,10 @@ impl<'a> System<'a> for Sys { 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). + // 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; diff --git a/common/src/sys/phys.rs b/common/src/sys/phys.rs index 8cfdb9ed48..45cf64efdc 100644 --- a/common/src/sys/phys.rs +++ b/common/src/sys/phys.rs @@ -317,13 +317,14 @@ impl<'a> System<'a> for Sys { && was_on_ground && !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), + &|block| { + block.is_solid() && block.get_height() >= (pos.0.z - 0.05).rem_euclid(1.0) + }, near_iter.clone(), ) { let snap_height = terrain - .get(Vec3::new(pos.0.x, pos.0.y, pos.0.z - 0.05) - .map(|e| e.floor() as i32)) + .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()) @@ -340,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, &|block| block.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) @@ -352,7 +357,8 @@ impl<'a> System<'a> for Sys { } // Figure out if we're in water - physics_state.in_fluid = collision_with(pos.0, &|block| block.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/terrain/block.rs b/common/src/terrain/block.rs index 9b3d664b95..056b8715eb 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -303,9 +303,11 @@ 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) - }, + BlockKind::Window1 + | BlockKind::Window2 + | BlockKind::Window3 + | BlockKind::Window4 + | BlockKind::Door => Some(self.color[0] & 0b111), _ => None, } } diff --git a/server/src/lib.rs b/server/src/lib.rs index 82fa74c41a..ecb35c6594 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -49,9 +49,9 @@ use uvth::{ThreadPool, ThreadPoolBuilder}; use vek::*; #[cfg(feature = "worldgen")] use world::{ + civ::SiteKind, sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP, WORLD_SIZE}, World, - civ::SiteKind, }; const CLIENT_TIMEOUT: f64 = 20.0; // Seconds @@ -141,7 +141,9 @@ impl Server { // 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.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e as i32 * sz as i32 + sz as i32 / 2); + 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/sys/terrain.rs b/server/src/sys/terrain.rs index b76d06ccec..dab026bf29 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -4,11 +4,11 @@ use common::{ assets, comp::{self, item, CharacterAbility, Item, ItemConfig, Player, Pos}, event::{EventBus, ServerEvent}, + generation::get_npc_name, msg::ServerMsg, npc::NPC_NAMES, state::TerrainChanges, terrain::TerrainGrid, - generation::get_npc_name, }; use rand::Rng; use specs::{Join, Read, ReadStorage, System, Write, WriteExpect, WriteStorage}; diff --git a/voxygen/src/mesh/terrain.rs b/voxygen/src/mesh/terrain.rs index 402b442ed3..fa8e94ea01 100644 --- a/voxygen/src/mesh/terrain.rs +++ b/voxygen/src/mesh/terrain.rs @@ -302,9 +302,8 @@ impl + ReadVol + Debug> Meshable + ReadVol + Debug> Meshable + ReadVol + Debug> Meshable + ReadVol + Debug> Meshable Sampler<'a> for ColumnGen<'a> { 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)?; @@ -590,12 +590,9 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let near_cliffs = sim_chunk.near_cliffs; let river_gouge = 0.5; - 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, - )) = + 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. diff --git a/world/src/layer/mod.rs b/world/src/layer/mod.rs index 56aebdbbba..6b3ebfbb50 100644 --- a/world/src/layer/mod.rs +++ b/world/src/layer/mod.rs @@ -1,13 +1,13 @@ -use std::f32; -use vek::*; -use common::{ - terrain::{Block, BlockKind}, - vol::{BaseVol, ReadVol, RectSizedVol, Vox, WriteVol}, -}; 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, @@ -37,7 +37,8 @@ pub fn apply_paths_to<'a>( }) }; - if let Some((path_dist, path_nearest)) = col_sample.path.filter(|(dist, _)| *dist < 5.0) { + 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 @@ -47,7 +48,9 @@ pub fn apply_paths_to<'a>( 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 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()), @@ -55,7 +58,8 @@ pub fn apply_paths_to<'a>( path_nearest.y.fract(), ), _ => col_attr(col_sample), - }.into_array(); + } + .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()) @@ -71,7 +75,9 @@ pub fn apply_paths_to<'a>( 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); + 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)) }, ); diff --git a/world/src/lib.rs b/world/src/lib.rs index 5fb8a99faf..6f47c7a311 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -7,9 +7,9 @@ mod block; pub mod civ; mod column; pub mod config; +pub mod layer; pub mod sim; pub mod site; -pub mod layer; pub mod util; // Reexports diff --git a/world/src/sim/map.rs b/world/src/sim/map.rs index 69e22466da..00849496e3 100644 --- a/world/src/sim/map.rs +++ b/world/src/sim/map.rs @@ -147,31 +147,39 @@ impl MapConfig { let pos = (focus_rect + Vec2::new(i as f64, j as f64) * scale).map(|e: f64| e as i32); - let (alt, basement, water_alt, humidity, temperature, downhill, river_kind, is_path) = - sampler - .get(pos) - .map(|sample| { - ( - sample.alt, - sample.basement, - sample.water_alt, - sample.humidity, - sample.temp, - sample.downhill, - sample.river.river_kind, - sample.path.is_path(), - ) - }) - .unwrap_or(( - CONFIG.sea_level, - CONFIG.sea_level, - CONFIG.sea_level, - 0.0, - 0.0, - None, - None, - false, - )); + let ( + alt, + basement, + water_alt, + humidity, + temperature, + downhill, + river_kind, + is_path, + ) = sampler + .get(pos) + .map(|sample| { + ( + sample.alt, + sample.basement, + sample.water_alt, + sample.humidity, + sample.temp, + sample.downhill, + sample.river.river_kind, + sample.path.is_path(), + ) + }) + .unwrap_or(( + CONFIG.sea_level, + CONFIG.sea_level, + CONFIG.sea_level, + 0.0, + 0.0, + None, + None, + false, + )); let humidity = humidity.min(1.0).max(0.0); let temperature = temperature.min(1.0).max(-1.0) * 0.5 + 0.5; let pos = pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32); diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 47d6fa7e23..131eabac01 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -27,10 +27,7 @@ use crate::{ all::ForestKind, civ::Place, site::Site, - util::{ - seed_expan, FastNoise, RandomField, StructureGen2d, LOCALITY, - NEIGHBORS, - }, + util::{seed_expan, FastNoise, RandomField, StructureGen2d, LOCALITY, NEIGHBORS}, CONFIG, }; use common::{ diff --git a/world/src/sim/path.rs b/world/src/sim/path.rs index 0ccdb910b8..525069a1d8 100644 --- a/world/src/sim/path.rs +++ b/world/src/sim/path.rs @@ -8,9 +8,7 @@ pub struct PathData { } impl PathData { - pub fn is_path(&self) -> bool { - self.neighbors != 0 - } + pub fn is_path(&self) -> bool { self.neighbors != 0 } } impl Default for PathData { diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index 14837717c6..881b5dea4d 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -3,7 +3,7 @@ use crate::{ column::ColumnSample, sim::WorldSim, site::BlockMask, - util::{attempt, DIRS, CARDINALS, Grid, RandomField, Sampler}, + util::{attempt, Grid, RandomField, Sampler, CARDINALS, DIRS}, }; use common::{ assets, @@ -118,16 +118,17 @@ impl Dungeon { }; 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 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; diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index 5db70181d4..cc499d61e2 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -312,27 +312,37 @@ impl Archetype for House { 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 + 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, - ) + 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 { - empty.with_priority(structural_layer) - } - } else { - wall - }); + wall + }, + ); } if bound_offset.x == bound_offset.y || profile.y == ceil_height { diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 700f58927d..89484dadbb 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -8,6 +8,7 @@ use crate::{ util::{RandomField, Sampler, StructureGen2d}, }; use common::{ + assets, astar::Astar, comp::{self, bird_medium, humanoid, quadruped_small}, generation::{ChunkSupplement, EntityInfo}, @@ -16,7 +17,6 @@ use common::{ store::{Id, Store}, terrain::{Block, BlockKind, TerrainChunkSize}, vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol}, - assets, }; use hashbrown::{HashMap, HashSet}; use rand::prelude::*; @@ -341,7 +341,8 @@ impl Settlement { .tile_at(tile_pos) .map(|t| t.contains(WayKind::Path)) .unwrap_or(true) - || ctx.sim + || ctx + .sim .and_then(|sim| sim.get_nearest_path(self.origin + house_pos)) .map(|(dist, _)| dist < 28.0) .unwrap_or(false) @@ -548,9 +549,8 @@ impl Settlement { } else { let mut surface_block = None; - let roll = |seed, n| { - self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, seed * 5)) % n - }; + 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)), @@ -558,16 +558,21 @@ impl Settlement { 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 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 + 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 + 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())); + surface_block = + Some(Block::new(BlockKind::StreetLamp, Rgb::white())); } } @@ -804,14 +809,18 @@ impl Settlement { }, _ => 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", - }))) + .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); From 1a4c7f46f49ec9dc5ac2c239cc8bb189914262d4 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 23 Apr 2020 17:09:53 +0100 Subject: [PATCH 187/195] Fixed second render chunk counting issue --- voxygen/src/scene/terrain.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index b91481a136..b6bd3f4450 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -1989,9 +1989,11 @@ impl Terrain { if *n >= chunks.len() { None } else { - *n += 1; let pos = focus_chunk + rpos; - Some(chunks.get(&pos).map(|c| (pos, c))) + Some(chunks.get(&pos).map(|c| { + *n += 1; + (pos, c) + })) } }) .filter_map(|x| x); From 7a11da1540f768b4a0fc9eab3f17e78b05b5eb95 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 23 Apr 2020 17:14:33 +0100 Subject: [PATCH 188/195] Updated changelog --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fadccdab1..628f68ae7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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 From 0a892e488a930ab5958eec2f2c213c3fd6142bf4 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 23 Apr 2020 17:52:26 +0100 Subject: [PATCH 189/195] Updated examples --- Cargo.lock | 134 ++++++++++++++++++++++++- voxygen/examples/character_renderer.rs | 2 +- world/Cargo.toml | 2 +- world/examples/city.rs | 35 ------- world/examples/settlement_viewer.rs | 2 +- world/examples/turb.rs | 2 +- world/examples/view.rs | 2 +- world/examples/water.rs | 2 +- 8 files changed, 138 insertions(+), 43 deletions(-) delete mode 100644 world/examples/city.rs diff --git a/Cargo.lock b/Cargo.lock index 98cf25c4af..4ebcf41d72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2589,17 +2589,23 @@ dependencies = [ [[package]] name = "minifb" -version = "0.14.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799c20458eb0dd69f48cea5014afe736b363818c86d7ca61dbb56e1c0585014c" +checksum = "b18d2987dac6afdd7f6d81101a3b422b7da3e6799d7f11863ad006d8ccd562b2" dependencies = [ "cast", "cc", "orbclient", "raw-window-handle", + "tempfile", "time", + "wayland-client 0.25.0", + "wayland-cursor", + "wayland-protocols 0.25.0", "winapi 0.3.8", "x11-dl", + "xkb", + "xkbcommon-sys", ] [[package]] @@ -2728,6 +2734,19 @@ dependencies = [ "void", ] +[[package]] +name = "nix" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "void", +] + [[package]] name = "nodrop" version = "0.1.14" @@ -3947,6 +3966,12 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + [[package]] name = "scoped_threadpool" version = "0.1.9" @@ -4406,6 +4431,20 @@ dependencies = [ "remove_dir_all", ] +[[package]] +name = "tempfile" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +dependencies = [ + "cfg-if", + "libc", + "rand 0.7.3", + "redox_syscall", + "remove_dir_all", + "winapi 0.3.8", +] + [[package]] name = "term" version = "0.5.2" @@ -5217,6 +5256,22 @@ dependencies = [ "wayland-sys 0.23.6", ] +[[package]] +name = "wayland-client" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a42cb608953ec8e132c7f53fde722cca9bfbf8b2071d685dbbb8df2b567fee8b" +dependencies = [ + "bitflags", + "downcast-rs", + "libc", + "nix 0.17.0", + "scoped-tls", + "wayland-commons 0.25.0", + "wayland-scanner 0.25.0", + "wayland-sys 0.25.0", +] + [[package]] name = "wayland-commons" version = "0.21.13" @@ -5237,6 +5292,28 @@ dependencies = [ "wayland-sys 0.23.6", ] +[[package]] +name = "wayland-commons" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8caa2f106138cf71358c6a9e84468e4406069cec93cbd6dbfce92225fc175932" +dependencies = [ + "nix 0.17.0", + "once_cell", + "smallvec 1.2.0", + "wayland-sys 0.25.0", +] + +[[package]] +name = "wayland-cursor" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d774f69a6a4a9eac6d1a29cea45a4750ee7f997520421b2068f099a11b4cbba" +dependencies = [ + "wayland-client 0.25.0", + "wayland-sys 0.25.0", +] + [[package]] name = "wayland-protocols" version = "0.21.13" @@ -5262,6 +5339,18 @@ dependencies = [ "wayland-scanner 0.23.6", ] +[[package]] +name = "wayland-protocols" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f784a990d5fa6d846fa93eb8d3bb744ff1e6ec60c7f785b0a0ee2f1a1f20bee9" +dependencies = [ + "bitflags", + "wayland-client 0.25.0", + "wayland-commons 0.25.0", + "wayland-scanner 0.25.0", +] + [[package]] name = "wayland-scanner" version = "0.21.13" @@ -5284,6 +5373,17 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "wayland-scanner" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f45ddc08a8078f3efa96b5f413268cc9c53b30712891de081fbc1d5846fbc736" +dependencies = [ + "proc-macro2 1.0.9", + "quote 1.0.3", + "xml-rs", +] + [[package]] name = "wayland-sys" version = "0.21.13" @@ -5304,6 +5404,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "wayland-sys" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80f9fc64f9045ad5ff491886a9460437655353e8be73c1b3f29f569342553319" +dependencies = [ + "dlib", +] + [[package]] name = "web-sys" version = "0.3.36" @@ -5467,6 +5576,27 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" +[[package]] +name = "xkb" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aec02bc5de902aa579f3d2f2c522edaf40fa42963cbaffe645b058ddcc68fdb2" +dependencies = [ + "bitflags", + "libc", + "xkbcommon-sys", +] + +[[package]] +name = "xkbcommon-sys" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa434980dca02ebf28795d71e570dbb78316d095a228707efd6117bf8246d78b" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "xml-rs" version = "0.8.0" diff --git a/voxygen/examples/character_renderer.rs b/voxygen/examples/character_renderer.rs index 05483f2680..289227bcb1 100644 --- a/voxygen/examples/character_renderer.rs +++ b/voxygen/examples/character_renderer.rs @@ -56,7 +56,7 @@ fn main() { }; 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.maintain(&mut renderer, scene_data); // Render diff --git a/world/Cargo.toml b/world/Cargo.toml index 0ff1838510..6a28140cc0 100644 --- a/world/Cargo.toml +++ b/world/Cargo.toml @@ -29,4 +29,4 @@ ron = "0.5.1" [dev-dependencies] pretty_env_logger = "0.3.0" -minifb = "0.14.0" +minifb = "0.16.0" diff --git a/world/examples/city.rs b/world/examples/city.rs deleted file mode 100644 index e98b379220..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(&buf).unwrap(); - } -} diff --git a/world/examples/settlement_viewer.rs b/world/examples/settlement_viewer.rs index 44c392cc6e..ccfb4e321d 100644 --- a/world/examples/settlement_viewer.rs +++ b/world/examples/settlement_viewer.rs @@ -52,6 +52,6 @@ fn main() { zoom /= 1.05; } - win.update_with_buffer(&buf).unwrap(); + win.update_with_buffer_size(&buf, W, H).unwrap(); } } diff --git a/world/examples/turb.rs b/world/examples/turb.rs index 302306527e..aa4444c36f 100644 --- a/world/examples/turb.rs +++ b/world/examples/turb.rs @@ -34,7 +34,7 @@ fn main() { } } - win.update_with_buffer(&buf).unwrap(); + win.update_with_buffer(&buf, W, H).unwrap(); _time += 1.0 / 60.0; } diff --git a/world/examples/view.rs b/world/examples/view.rs index 3bb1b7e440..7fa4bd7c38 100644 --- a/world/examples/view.rs +++ b/world/examples/view.rs @@ -71,6 +71,6 @@ fn main() { scale -= 6; } - win.update_with_buffer(&buf).unwrap(); + win.update_with_buffer(&buf, W, H).unwrap(); } } diff --git a/world/examples/water.rs b/world/examples/water.rs index 0468962942..e9207bdbf5 100644 --- a/world/examples/water.rs +++ b/world/examples/water.rs @@ -252,6 +252,6 @@ fn main() { } } - win.update_with_buffer(&buf).unwrap(); + win.update_with_buffer(&buf, W, H).unwrap(); } } From 8c1fb3255c7357d21dc095b1bf75dfcebcfde02f Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 23 Apr 2020 20:05:44 +0100 Subject: [PATCH 190/195] Downgraded minifb --- Cargo.lock | 134 +--------------------------------------- world/Cargo.toml | 2 +- world/examples/turb.rs | 2 +- world/examples/view.rs | 2 +- world/examples/water.rs | 2 +- 5 files changed, 6 insertions(+), 136 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ebcf41d72..98cf25c4af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2589,23 +2589,17 @@ dependencies = [ [[package]] name = "minifb" -version = "0.16.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b18d2987dac6afdd7f6d81101a3b422b7da3e6799d7f11863ad006d8ccd562b2" +checksum = "799c20458eb0dd69f48cea5014afe736b363818c86d7ca61dbb56e1c0585014c" dependencies = [ "cast", "cc", "orbclient", "raw-window-handle", - "tempfile", "time", - "wayland-client 0.25.0", - "wayland-cursor", - "wayland-protocols 0.25.0", "winapi 0.3.8", "x11-dl", - "xkb", - "xkbcommon-sys", ] [[package]] @@ -2734,19 +2728,6 @@ dependencies = [ "void", ] -[[package]] -name = "nix" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" -dependencies = [ - "bitflags", - "cc", - "cfg-if", - "libc", - "void", -] - [[package]] name = "nodrop" version = "0.1.14" @@ -3966,12 +3947,6 @@ dependencies = [ "winapi 0.3.8", ] -[[package]] -name = "scoped-tls" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" - [[package]] name = "scoped_threadpool" version = "0.1.9" @@ -4431,20 +4406,6 @@ dependencies = [ "remove_dir_all", ] -[[package]] -name = "tempfile" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" -dependencies = [ - "cfg-if", - "libc", - "rand 0.7.3", - "redox_syscall", - "remove_dir_all", - "winapi 0.3.8", -] - [[package]] name = "term" version = "0.5.2" @@ -5256,22 +5217,6 @@ dependencies = [ "wayland-sys 0.23.6", ] -[[package]] -name = "wayland-client" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a42cb608953ec8e132c7f53fde722cca9bfbf8b2071d685dbbb8df2b567fee8b" -dependencies = [ - "bitflags", - "downcast-rs", - "libc", - "nix 0.17.0", - "scoped-tls", - "wayland-commons 0.25.0", - "wayland-scanner 0.25.0", - "wayland-sys 0.25.0", -] - [[package]] name = "wayland-commons" version = "0.21.13" @@ -5292,28 +5237,6 @@ dependencies = [ "wayland-sys 0.23.6", ] -[[package]] -name = "wayland-commons" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8caa2f106138cf71358c6a9e84468e4406069cec93cbd6dbfce92225fc175932" -dependencies = [ - "nix 0.17.0", - "once_cell", - "smallvec 1.2.0", - "wayland-sys 0.25.0", -] - -[[package]] -name = "wayland-cursor" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d774f69a6a4a9eac6d1a29cea45a4750ee7f997520421b2068f099a11b4cbba" -dependencies = [ - "wayland-client 0.25.0", - "wayland-sys 0.25.0", -] - [[package]] name = "wayland-protocols" version = "0.21.13" @@ -5339,18 +5262,6 @@ dependencies = [ "wayland-scanner 0.23.6", ] -[[package]] -name = "wayland-protocols" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f784a990d5fa6d846fa93eb8d3bb744ff1e6ec60c7f785b0a0ee2f1a1f20bee9" -dependencies = [ - "bitflags", - "wayland-client 0.25.0", - "wayland-commons 0.25.0", - "wayland-scanner 0.25.0", -] - [[package]] name = "wayland-scanner" version = "0.21.13" @@ -5373,17 +5284,6 @@ dependencies = [ "xml-rs", ] -[[package]] -name = "wayland-scanner" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f45ddc08a8078f3efa96b5f413268cc9c53b30712891de081fbc1d5846fbc736" -dependencies = [ - "proc-macro2 1.0.9", - "quote 1.0.3", - "xml-rs", -] - [[package]] name = "wayland-sys" version = "0.21.13" @@ -5404,15 +5304,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "wayland-sys" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f9fc64f9045ad5ff491886a9460437655353e8be73c1b3f29f569342553319" -dependencies = [ - "dlib", -] - [[package]] name = "web-sys" version = "0.3.36" @@ -5576,27 +5467,6 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" -[[package]] -name = "xkb" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aec02bc5de902aa579f3d2f2c522edaf40fa42963cbaffe645b058ddcc68fdb2" -dependencies = [ - "bitflags", - "libc", - "xkbcommon-sys", -] - -[[package]] -name = "xkbcommon-sys" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa434980dca02ebf28795d71e570dbb78316d095a228707efd6117bf8246d78b" -dependencies = [ - "libc", - "pkg-config", -] - [[package]] name = "xml-rs" version = "0.8.0" diff --git a/world/Cargo.toml b/world/Cargo.toml index 6a28140cc0..0ff1838510 100644 --- a/world/Cargo.toml +++ b/world/Cargo.toml @@ -29,4 +29,4 @@ ron = "0.5.1" [dev-dependencies] pretty_env_logger = "0.3.0" -minifb = "0.16.0" +minifb = "0.14.0" diff --git a/world/examples/turb.rs b/world/examples/turb.rs index aa4444c36f..8249feba9c 100644 --- a/world/examples/turb.rs +++ b/world/examples/turb.rs @@ -34,7 +34,7 @@ fn main() { } } - win.update_with_buffer(&buf, W, H).unwrap(); + win.update_with_buffer_size(&buf, W, H).unwrap(); _time += 1.0 / 60.0; } diff --git a/world/examples/view.rs b/world/examples/view.rs index 7fa4bd7c38..f80bbca0de 100644 --- a/world/examples/view.rs +++ b/world/examples/view.rs @@ -71,6 +71,6 @@ fn main() { scale -= 6; } - win.update_with_buffer(&buf, W, H).unwrap(); + win.update_with_buffer_size(&buf, W, H).unwrap(); } } diff --git a/world/examples/water.rs b/world/examples/water.rs index e9207bdbf5..5c6e35a834 100644 --- a/world/examples/water.rs +++ b/world/examples/water.rs @@ -252,6 +252,6 @@ fn main() { } } - win.update_with_buffer(&buf, W, H).unwrap(); + win.update_with_buffer_size(&buf, W, H).unwrap(); } } From c0bd0279fcf4838ce50a0d39f5ad57ecf64af3f5 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 23 Apr 2020 21:59:09 +0100 Subject: [PATCH 191/195] Added sites to the map --- assets/voxygen/shaders/terrain-frag.glsl | 2 +- world/src/sim/map.rs | 9 ++++++++- world/src/site/dungeon/mod.rs | 2 ++ world/src/site/mod.rs | 7 +++++++ world/src/site/settlement/mod.rs | 4 ++++ 5 files changed, 22 insertions(+), 2 deletions(-) diff --git a/assets/voxygen/shaders/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl index a1d526bd95..f569b728a2 100644 --- a/assets/voxygen/shaders/terrain-frag.glsl +++ b/assets/voxygen/shaders/terrain-frag.glsl @@ -45,7 +45,7 @@ void main() { diffuse_light *= f_light * ao; diffuse_light += point_light * ao; - vec3 col = f_col + hash(vec4(floor(f_chunk_pos * 3.0 + 0.5), 0)) * 0.02; // Small-scale noise + vec3 col = f_col + hash(vec4(floor(f_chunk_pos * 3.0 + 0.01), 0)) * 0.02; // Small-scale noise vec3 surf_color = illuminate(srgb_to_linear(col), light, diffuse_light, ambient_light); float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x); diff --git a/world/src/sim/map.rs b/world/src/sim/map.rs index 00849496e3..8ef77be281 100644 --- a/world/src/sim/map.rs +++ b/world/src/sim/map.rs @@ -156,6 +156,7 @@ impl MapConfig { downhill, river_kind, is_path, + near_site, ) = sampler .get(pos) .map(|sample| { @@ -168,6 +169,9 @@ impl MapConfig { sample.downhill, sample.river.river_kind, 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(( @@ -179,6 +183,7 @@ impl MapConfig { None, None, false, + false, )); let humidity = humidity.min(1.0).max(0.0); let temperature = temperature.min(1.0).max(-1.0) * 0.5 + 0.5; @@ -305,7 +310,9 @@ impl MapConfig { ), }; - let rgba = if is_path { + let rgba = if near_site { + (0x57, 0x39, 0x33, 0xFF) + } else if is_path { (0x37, 0x29, 0x23, 0xFF) } else { rgba diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index 881b5dea4d..a696907e7a 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -67,6 +67,8 @@ impl Dungeon { this } + pub fn get_origin(&self) -> Vec2 { self.origin } + pub fn radius(&self) -> f32 { 1200.0 } pub fn spawn_rules(&self, _wpos: Vec2) -> SpawnRules { diff --git a/world/src/site/mod.rs b/world/src/site/mod.rs index 12cd3d996b..b0fab2d161 100644 --- a/world/src/site/mod.rs +++ b/world/src/site/mod.rs @@ -74,6 +74,13 @@ impl Site { } } + 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), diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 89484dadbb..9fb4081102 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -147,6 +147,10 @@ impl Settlement { 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; From 1c2f05ac517998dc6e409586b37f1ecf6ba5f8d8 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 23 Apr 2020 22:05:14 +0100 Subject: [PATCH 192/195] Fully fixed sub-voxel noise issue --- assets/voxygen/shaders/terrain-frag.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/voxygen/shaders/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl index f569b728a2..edbd8833f8 100644 --- a/assets/voxygen/shaders/terrain-frag.glsl +++ b/assets/voxygen/shaders/terrain-frag.glsl @@ -45,7 +45,7 @@ void main() { diffuse_light *= f_light * ao; diffuse_light += point_light * ao; - vec3 col = f_col + hash(vec4(floor(f_chunk_pos * 3.0 + 0.01), 0)) * 0.02; // Small-scale noise + vec3 col = f_col + hash(vec4(floor(f_chunk_pos * 3.0 - f_norm * 0.5), 0)) * 0.02; // Small-scale noise vec3 surf_color = illuminate(srgb_to_linear(col), light, diffuse_light, ambient_light); float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x); From b588be496292a86ea43339cd066ccc81966b0b7f Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 23 Apr 2020 22:10:05 +0100 Subject: [PATCH 193/195] fmt --- world/src/sim/map.rs | 8 +++++--- world/src/site/settlement/mod.rs | 4 +--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/world/src/sim/map.rs b/world/src/sim/map.rs index 8ef77be281..aecea6880c 100644 --- a/world/src/sim/map.rs +++ b/world/src/sim/map.rs @@ -169,9 +169,11 @@ impl MapConfig { sample.downhill, sample.river.river_kind, sample.path.is_path(), - sample.sites - .iter() - .any(|site| site.get_origin().distance_squared(pos * TerrainChunkSize::RECT_SIZE.x as i32) < 64i32.pow(2)), + sample.sites.iter().any(|site| { + site.get_origin() + .distance_squared(pos * TerrainChunkSize::RECT_SIZE.x as i32) + < 64i32.pow(2) + }), ) }) .unwrap_or(( diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 9fb4081102..cba520fa10 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -147,9 +147,7 @@ impl Settlement { this } - pub fn get_origin(&self) -> Vec2 { - self.origin - } + 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) { From dd08def18fc6bd22207fae215242b3e055c91c72 Mon Sep 17 00:00:00 2001 From: Treeco <5021038-Treeco@users.noreply.gitlab.com> Date: Thu, 23 Apr 2020 22:59:34 +0000 Subject: [PATCH 194/195] Add camera smoothing setting --- assets/voxygen/i18n/en.ron | 1 + voxygen/examples/character_renderer.rs | 5 +++- voxygen/src/hud/mod.rs | 4 +++ voxygen/src/hud/settings_window.rs | 35 +++++++++++++++++++++++++- voxygen/src/menu/char_selection/mod.rs | 1 + voxygen/src/scene/camera.rs | 16 +++++++----- voxygen/src/scene/mod.rs | 6 +++-- voxygen/src/scene/simple.rs | 4 ++- voxygen/src/session.rs | 7 +++++- voxygen/src/settings.rs | 2 ++ 10 files changed, 69 insertions(+), 12 deletions(-) diff --git a/assets/voxygen/i18n/en.ron b/assets/voxygen/i18n/en.ron index 219335f322..5659b9ad51 100644 --- a/assets/voxygen/i18n/en.ron +++ b/assets/voxygen/i18n/en.ron @@ -246,6 +246,7 @@ Enjoy your stay in the World of Veloren."#, "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", diff --git a/voxygen/examples/character_renderer.rs b/voxygen/examples/character_renderer.rs index 289227bcb1..a26255d38d 100644 --- a/voxygen/examples/character_renderer.rs +++ b/voxygen/examples/character_renderer.rs @@ -53,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, 1.0 / 60.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/hud/mod.rs b/voxygen/src/hud/mod.rs index 1f713de20e..cf1b4a174a 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -208,6 +208,7 @@ pub enum Event { AdjustMouseZoom(u32), ToggleZoomInvert(bool), ToggleMouseYInvert(bool), + ToggleSmoothPan(bool), AdjustViewDistance(u32), AdjustMusicVolume(f32), AdjustSfxVolume(f32), @@ -1794,6 +1795,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)); }, diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs index 8ead5fb68d..7f2e517c51 100644 --- a/voxygen/src/hud/settings_window.rs +++ b/voxygen/src/hud/settings_window.rs @@ -66,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, @@ -205,6 +207,7 @@ pub enum Event { AdjustMouseZoom(u32), ToggleZoomInvert(bool), ToggleMouseYInvert(bool), + ToggleSmoothPan(bool), AdjustViewDistance(u32), AdjustFOV(u16), AdjustGamma(f32), @@ -1239,7 +1242,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); @@ -1262,6 +1265,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 diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index 3ca05435d4..c05eca5ad3 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -106,6 +106,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/scene/camera.rs b/voxygen/src/scene/camera.rs index 020263f035..2dc24fa000 100644 --- a/voxygen/src/scene/camera.rs +++ b/voxygen/src/scene/camera.rs @@ -190,7 +190,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, dt: f32) { + 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 { @@ -217,11 +217,15 @@ impl Camera { Lerp::lerp(a, b + *offs, rate) }; - 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), - )); + 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/mod.rs b/voxygen/src/scene/mod.rs index 8edf8e4635..f3b7771e2d 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -74,6 +74,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 { @@ -177,7 +179,6 @@ impl Scene { renderer: &mut Renderer, audio: &mut AudioFrontend, scene_data: &SceneData, - gamma: f32, ) { // Get player position. let ecs = scene_data.state.ecs(); @@ -243,6 +244,7 @@ impl Scene { self.camera.update( scene_data.state.get_time(), scene_data.state.get_delta_time(), + scene_data.mouse_smoothing, ); // Compute camera matrices. @@ -349,7 +351,7 @@ impl Scene { .map(|b| b.kind()) .unwrap_or(BlockKind::Air), self.select_pos, - gamma, + scene_data.gamma, self.camera.get_mode(), )]) .expect("Failed to update global constants"); diff --git a/voxygen/src/scene/simple.rs b/voxygen/src/scene/simple.rs index 7a17754231..77c1dbcf0f 100644 --- a/voxygen/src/scene/simple.rs +++ b/voxygen/src/scene/simple.rs @@ -73,6 +73,7 @@ pub struct SceneData { pub tick: u64, pub body: Option, pub gamma: f32, + pub mouse_smoothing: bool, } impl Scene { @@ -146,7 +147,8 @@ impl Scene { } pub fn maintain(&mut self, renderer: &mut Renderer, scene_data: SceneData) { - self.camera.update(scene_data.time, 1.0 / 60.0); + self.camera + .update(scene_data.time, 1.0 / 60.0, scene_data.mouse_smoothing); self.camera.compute_dependents(&VoidVol); let camera::Dependents { diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 564a2c375f..25b8f7e1df 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -579,6 +579,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); @@ -728,12 +732,13 @@ 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, &scene_data, - global_state.settings.graphics.gamma, ); } diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index 899e85261d..11ac56aa6c 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -448,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, @@ -466,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, From e10f27f4806d9c9493bda4b5990a3d44b317fe10 Mon Sep 17 00:00:00 2001 From: Songtronix Date: Fri, 24 Apr 2020 18:30:58 +0200 Subject: [PATCH 195/195] fix: misspelling in english translation --- assets/voxygen/i18n/en.ron | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/voxygen/i18n/en.ron b/assets/voxygen/i18n/en.ron index 5659b9ad51..6e39d56c43 100644 --- a/assets/voxygen/i18n/en.ron +++ b/assets/voxygen/i18n/en.ron @@ -149,8 +149,8 @@ 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."#,