Merge branch 'master' of gitlab.com:veloren/veloren into sharp/small-fixes

This commit is contained in:
Joshua Yanovski 2020-08-13 05:52:56 +02:00
commit 862df3c997
95 changed files with 4609 additions and 1547 deletions

View File

@ -54,6 +54,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Some Campfire, fireball & bomb; particle, light & sound effects. - Some Campfire, fireball & bomb; particle, light & sound effects.
- Added firework recipe - Added firework recipe
- Added setting to change resolution - Added setting to change resolution
- Rare (unfinished) castles
- Caves with monsters and treasure
- Furniture and decals in towns
### Changed ### Changed
@ -86,6 +89,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Dehardcoded many item variants - Dehardcoded many item variants
- Tooltips avoid the mouse better and disappear when hovered - Tooltips avoid the mouse better and disappear when hovered
- Improved social window functions and visuals - Improved social window functions and visuals
- Changed agent behaviour to allow fleeing
- Waypoints now spawn on dungeon staircases
### Removed ### Removed

21
Cargo.lock generated
View File

@ -1195,6 +1195,26 @@ version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
[[package]]
name = "enum-iterator"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c79a6321a1197d7730510c7e3f6cb80432dfefecb32426de8cea0aa19b4bb8d7"
dependencies = [
"enum-iterator-derive",
]
[[package]]
name = "enum-iterator-derive"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e94aa31f7c0dc764f57896dc615ddd76fc13b0d5dca7eb6cc5e018a5a09ec06"
dependencies = [
"proc-macro2 1.0.18",
"quote 1.0.7",
"syn 1.0.33",
]
[[package]] [[package]]
name = "env_logger" name = "env_logger"
version = "0.6.2" version = "0.6.2"
@ -4691,6 +4711,7 @@ dependencies = [
"criterion", "criterion",
"crossbeam", "crossbeam",
"dot_vox", "dot_vox",
"enum-iterator",
"find_folder", "find_folder",
"hashbrown", "hashbrown",
"image", "image",

View File

@ -0,0 +1,12 @@
[
(20, Velorite),
(30, VeloriteFrag),
(60, Stones),
(15, PurpleFlower),
(130, ShortGrass),
(120, Mushroom),
(8, ShinyGem),
(15, Chest),
(2, Crate),
(0.25, Scarecrow),
]

View File

@ -13,11 +13,8 @@
(0.01, "common.items.utility.bomb_pile"), (0.01, "common.items.utility.bomb_pile"),
(0.1, "common.items.utility.bomb"), (0.1, "common.items.utility.bomb"),
// crafting ingredients // crafting ingredients
(0.5, "common.items.crafting_ing.shiny_gem"),
(2, "common.items.crafting_ing.leather_scraps"), (2, "common.items.crafting_ing.leather_scraps"),
(1, "common.items.crafting_ing.empty_vial"), (1, "common.items.crafting_ing.empty_vial"),
(2, "common.items.crafting_ing.stones"),
(3, "common.items.crafting_ing.twigs"),
// swords // swords
(0.1, "common.items.weapons.sword.starter_sword"), (0.1, "common.items.weapons.sword.starter_sword"),
(0.1, "common.items.weapons.sword.wood_sword"), (0.1, "common.items.weapons.sword.wood_sword"),

View File

@ -11,10 +11,10 @@
artist: "https://www.youtube.com/watch?v=FwVTkB-BIvM", artist: "https://www.youtube.com/watch?v=FwVTkB-BIvM",
), ),
( (
title: "A Solemn Quest Day", title: "A Solemn Quest",
path: "voxygen.audio.ambient.a_solemn_quest", path: "voxygen.audio.soundtrack.a_solemn_quest",
length: 206.0, length: 206.0,
timing: Some(Day), timing: Some(Night),
artist: "Eden", artist: "Eden",
), ),
( (

View File

@ -51,11 +51,14 @@ VoxygenLocalization(
"common.create": "Создать", "common.create": "Создать",
"common.okay": "Хорошо", "common.okay": "Хорошо",
"common.accept": "Принять", "common.accept": "Принять",
"common.decline": "Отклонить",
"common.disclaimer": "Дисклеймер", "common.disclaimer": "Дисклеймер",
"common.cancel": "Отмена", "common.cancel": "Отмена",
"common.none": "Нет", "common.none": "Нет",
"common.error": "Ошибка", "common.error": "Ошибка",
"common.fatal_error": "Критическая ошибка", "common.fatal_error": "Критическая ошибка",
"common.you": "Вы",
"common.automatic": "Авто",
// Message when connection to the server is lost // Message when connection to the server is lost
"common.connection_lost": r#"Соединение потеряно! "common.connection_lost": r#"Соединение потеряно!
@ -81,6 +84,7 @@ VoxygenLocalization(
/// Start Main screen section /// Start Main screen section
"main.connecting": "Подключение", "main.connecting": "Подключение",
"main.creating_world": "Создание мира", "main.creating_world": "Создание мира",
"main.tip": "Совет:",
// Welcome notice that appears the first time Veloren is started // Welcome notice that appears the first time Veloren is started
"main.notice": r#"Добро пожаловать в Veloren-Alpha! "main.notice": r#"Добро пожаловать в Veloren-Alpha!
@ -88,24 +92,21 @@ VoxygenLocalization(
Прежде чем начать веселье, прими во внимание следующие вещи: Прежде чем начать веселье, прими во внимание следующие вещи:
- Это очень ранняя альфа. Тут есть баги, крайне незавершенный геймплей, неотполированные механики и отсутсвующие фичи. - Это очень ранняя альфа. Тут есть баги, крайне незавершенный геймплей, неотполированные механики и отсутсвующие фичи.
- Если у вас есть конструктивный фидбек или сообщение об ошибке, вы можете связаться с нами через Reddit, GitLab или наш Discord-сервер. - Если у вас есть конструктивный фидбек или сообщение об ошибке, вы можете связаться с нами через Reddit, GitLab или наш Discord-сервер.
- Veloren лицензирован GPL 3 open-source licence. Это означает, игра бесплатна, ее можно модифицировать и переделывать на свой вкус (при условии, что готовая работа тоже лицензирована GPL 3). - Veloren лицензирован GPL 3 open-source licence. Это означает, игра бесплатна, ее можно модифицировать и переделывать на свой вкус (при условии, что готовая работа тоже лицензирована GPL 3).
- Veloren - некоммерческий проект, каждый работает над ним добровольно. - Veloren - некоммерческий проект, каждый работает над ним добровольно.
Если тебе нравится, что ты видишь, милости просим присоединиться к команде разработчиков или художественной команде! Если тебе нравится, что ты видишь, милости просим присоединиться к команде разработчиков или художественной команде!
- 'Воксельная РПГ' - самобытный жанр. ФПС тоже привыкли называть 'Клонами Дума'.
Как и они, мы пытаемся построить нишу. Эта игра не клон, ее развитие в будущем будет отличаться от существующих игр.
Спасибо за прочтение, мы надеемся, вам понравится игра! Спасибо за прочтение, мы надеемся, вам понравится игра!
~ Команда разработчиков Veloren"#, ~ Команда разработчиков Veloren"#,
// Login process description // Login process description
"main.login_process": r#"Информация по входу: "main.login_process": r#"Информация по входу:
Если у вас есть проблемы со входом:
Обратите внимание, что теперь вам нужен аккаунт Обратите внимание, что теперь вам нужен аккаунт
играть на серверах с включенной аутентификацией. играть на серверах с включенной аутентификацией.
@ -122,7 +123,9 @@ https://account.veloren.net."#,
"main.login.already_logged_in": "Вы уже вошли на сервер.", "main.login.already_logged_in": "Вы уже вошли на сервер.",
"main.login.network_error": "Ошибка сети", "main.login.network_error": "Ошибка сети",
"main.login.failed_sending_request": "Запрос аутентификации провален", "main.login.failed_sending_request": "Запрос аутентификации провален",
"main.login.invalid_character": "Выбранный персонаж недоступен",
"main.login.client_crashed": "Клиент вылетел", "main.login.client_crashed": "Клиент вылетел",
"main.login.not_on_whitelist": "Чтобы войти, необходимо быть внесенным в Вайтлист",
/// End Main screen section /// End Main screen section
@ -132,12 +135,25 @@ https://account.veloren.net."#,
"hud.show_tips": "Показать советы", "hud.show_tips": "Показать советы",
"hud.quests": "Квесты", "hud.quests": "Квесты",
"hud.you_died": "Вы мертвы", "hud.you_died": "Вы мертвы",
"hud.waypoint_saved": "Точка спауна сохранена",
"hud.press_key_to_show_keybindings_fmt": "Нажмите {key}, чтобы посмотреть раскладку", "hud.press_key_to_show_keybindings_fmt": "Нажмите {key}, чтобы посмотреть раскладку",
"hud.press_key_to_show_debug_info_fmt": "Нажмите {key}, чтобы показать панель отладки", "hud.press_key_to_show_debug_info_fmt": "Нажмите {key}, чтобы показать панель отладки",
"hud.press_key_to_toggle_keybindings_fmt": "Нажмите {key}, чтобы привязать клавишу", "hud.press_key_to_toggle_keybindings_fmt": "Нажмите {key}, чтобы привязать клавишу",
"hud.press_key_to_toggle_debug_info_fmt": "Нажмите {key}, чтобы переназначить панель отладки", "hud.press_key_to_toggle_debug_info_fmt": "Нажмите {key}, чтобы переназначить панель отладки",
// Chat outputs
"hud.chat.online_msg": "[{name}] сейчас онлайн.",
"hud.chat.offline_msg": "{name} сейчас оффлайн.",
"hud.chat.loot_msg": "Вы подобрали [{item}]",
"hud.chat.loot_fail": "Ваш инвентарь полон!",
"hud.chat.goodbye": "До встречи!",
"hud.chat.connection_lost": "Соединение потеряно. Кик через {time} секунд.",
// SCT outputs
"hud.sct.experience": "{amount} Опыт",
"hud.sct.block": "ЗАБЛОКИРОВАНО",
// Respawn message // Respawn message
"hud.press_key_to_respawn": r#"Нажмите {key}, чтобы возродиться на последнем костре, который вы посетили."#, "hud.press_key_to_respawn": r#"Нажмите {key}, чтобы возродиться на последнем костре, который вы посетили."#,
@ -148,11 +164,6 @@ https://account.veloren.net."#,
Немного советов перед тем началом игры: Немного советов перед тем началом игры:
НАИБОЛЕЕ ВАЖНО: Чтобы установить точку спауна, напишите /waypoint в чат.
Это возможно, даже если вы уже мертвы!
Нажмите F1, чтобы увидеть доступые команды. Нажмите F1, чтобы увидеть доступые команды.
Напишите /help, чтобы увидеть команды чата. Напишите /help, чтобы увидеть команды чата.
@ -171,7 +182,7 @@ https://account.veloren.net."#,
Ночи в Veloren могут быть довольно темными. Ночи в Veloren могут быть довольно темными.
Зажгите свой фонарь, написав /lantern в чат. Зажгите свой фонарь, нажав на 'G'.
Хотите увидеть курсор, чтобы закрыть это окно? Нажмите TAB! Хотите увидеть курсор, чтобы закрыть это окно? Нажмите TAB!
@ -180,21 +191,22 @@ https://account.veloren.net."#,
Наслаждайтесь миром Veloren."#, Наслаждайтесь миром Veloren."#,
"hud.temp_quest_headline": r#"Пожалуйста, путешественник, помоги нам!"#, "hud.temp_quest_headline": r#"Пожалуйста, путешественник, помоги нам!"#,
"hud.temp_quest_text": r#"Dungeons filled with evil cultists "hud.temp_quest_text": r#"Подземелья наполнены злыми культистами,
have emerged all around our peaceful towns! которые появились вокруг наших мирных городов!
Gather some company, stack up on food Собери компанию, запасись едой
and defeat their vile leaders and acolytes. и победи мерзкого лидера и его приспешников.
Maybe you can even obtain one of their Может ты даже сможешь получить один из их
magically infused items?"#, магических предметов?"#,
// Inventory // Inventory
"hud.bag.inventory": "Инвентарь", "hud.bag.inventory": "Инвентарь",
"hud.bag.stats_title": "Статы", "hud.bag.stats_title": "Характеристики",
"hud.bag.exp": "Опыт", "hud.bag.exp": "Опыт",
"hud.bag.armor": "Броня", "hud.bag.armor": "Броня",
"hud.bag.stats": "Статы", "hud.bag.stats": "Статы",
@ -239,19 +251,29 @@ magically infused items?"#,
"hud.settings.cumulated_damage": "Суммарный урон", "hud.settings.cumulated_damage": "Суммарный урон",
"hud.settings.incoming_damage": "Входящий урон", "hud.settings.incoming_damage": "Входящий урон",
"hud.settings.cumulated_incoming_damage": "Суммарный входящий урон", "hud.settings.cumulated_incoming_damage": "Суммарный входящий урон",
"hud.settings.speech_bubble": "Текстовые облачка",
"hud.settings.speech_bubble_dark_mode": "Темный режим текстовых облачков",
"hud.settings.speech_bubble_icon": "Значок текстовых облачков",
"hud.settings.energybar_numbers": "Отображение полоски энергии", "hud.settings.energybar_numbers": "Отображение полоски энергии",
"hud.settings.values": "Значение", "hud.settings.values": "Значение",
"hud.settings.percentages": "Проценты", "hud.settings.percentages": "Проценты",
"hud.settings.chat": "Чат", "hud.settings.chat": "Чат",
"hud.settings.background_transparency": "Прозрачность заднего фона", "hud.settings.background_transparency": "Прозрачность заднего фона",
"hud.settings.chat_character_name": "Имя персонажа в чате",
"hud.settings.loading_tips": "Советы при загрузке",
"hud.settings.pan_sensitivity": "Чувствительность камеры", "hud.settings.pan_sensitivity": "Чувствительность камеры",
"hud.settings.zoom_sensitivity": "Чувствительность зума", "hud.settings.zoom_sensitivity": "Чувствительность зума",
"hud.settings.invert_scroll_zoom": "Инвертировать прокрутку зума", "hud.settings.invert_scroll_zoom": "Инвертировать прокрутку зума",
"hud.settings.invert_mouse_y_axis": "Инвертировать ось Y", "hud.settings.invert_mouse_y_axis": "Инвертировать ось Y",
"hud.settings.enable_mouse_smoothing": "Размытие камеры",
"hud.settings.free_look_behavior": "Настройка свободной камеры", "hud.settings.free_look_behavior": "Настройка свободной камеры",
"hud.settings.auto_walk_behavior": "Автодвижение",
"hud.settings.stop_auto_walk_on_input": "Остановить автодвижение на кнопку движения",
"hud.settings.view_distance": "Дальность прорисовки", "hud.settings.view_distance": "Дальность прорисовки",
"hud.settings.sprites_view_distance": "Дальность прорисовки спрайтов",
"hud.settings.figures_view_distance": "Дальность прорисовки объектов",
"hud.settings.maximum_fps": "Максимум FPS", "hud.settings.maximum_fps": "Максимум FPS",
"hud.settings.fov": "Поле зрения (градусы)", "hud.settings.fov": "Поле зрения (градусы)",
"hud.settings.gamma": "Гамма", "hud.settings.gamma": "Гамма",
@ -261,6 +283,10 @@ magically infused items?"#,
"hud.settings.fluid_rendering_mode.cheap": "Низко", "hud.settings.fluid_rendering_mode.cheap": "Низко",
"hud.settings.fluid_rendering_mode.shiny": "Высоко", "hud.settings.fluid_rendering_mode.shiny": "Высоко",
"hud.settings.cloud_rendering_mode.regular": "Обычно", "hud.settings.cloud_rendering_mode.regular": "Обычно",
"hud.settings.particles": "Частицы",
"hud.settings.resolution": "Разрешение",
"hud.settings.bit_depth": "Разрядность",
"hud.settings.refresh_rate": "Частота обновления",
"hud.settings.fullscreen": "Полный экран", "hud.settings.fullscreen": "Полный экран",
"hud.settings.save_window_size": "Сохранить размер окна", "hud.settings.save_window_size": "Сохранить размер окна",
@ -269,17 +295,43 @@ magically infused items?"#,
"hud.settings.audio_device": "Аудио устройство", "hud.settings.audio_device": "Аудио устройство",
"hud.settings.awaitingkey": "Нажми клавишу...", "hud.settings.awaitingkey": "Нажми клавишу...",
"hud.settings.unbound": "Ничего",
"hud.settings.reset_keybinds": "По-умолчанию",
"hud.social": "Социальное", "hud.social": "Другие игроки",
"hud.social.online": "Онлайн", "hud.social.online": "Онлайн",
"hud.social.friends": "Друзья", "hud.social.friends": "Друзья",
"hud.social.not_yet_available": "Пока недоступно", "hud.social.not_yet_available": "Пока недоступно",
"hud.social.faction": "Фракция", "hud.social.faction": "Фракция",
"hud.social.play_online_fmt": "{nb_player} игрок(ов) онлайн", "hud.social.play_online_fmt": "{nb_player} игрок(ов) онлайн",
"hud.social.name": "Имя",
"hud.social.level": "Ур.",
"hud.social.zone": "Зона",
"hud.crafting": "Крафт",
"hud.crafting.recipes": "Рецепты",
"hud.crafting.ingredients": "Ингредиенты:",
"hud.crafting.craft": "Создать",
"hud.crafting.tool_cata": "Требуется:",
"hud.group": "Группа",
"hud.group.invite_to_join": "{name} пригласил вас в свою группу!",
"hud.group.invite": "Пригласить",
"hud.group.kick": "Кикнуть",
"hud.group.assign_leader": "Назначить лидером",
"hud.group.leave": "Покинуть группу",
"hud.group.dead" : "Мертв",
"hud.group.out_of_range": "Слишком далеко",
"hud.group.add_friend": "Добавить в друзья",
"hud.group.link_group": "Объединить группы",
"hud.group.in_menu": "В меню",
"hud.group.members": "Участники группы",
"hud.spell": "Заклинание", "hud.spell": "Заклинание",
"hud.free_look_indicator": "Свободная камера активна", "hud.free_look_indicator": "Свободная камера активна",
"hud.auto_walk_indicator": "Автодвижение активно",
/// End HUD section /// End HUD section
@ -316,6 +368,7 @@ magically infused items?"#,
"gameinput.climb": "Карабкаться", "gameinput.climb": "Карабкаться",
"gameinput.climbdown": "Карабкаться вниз", "gameinput.climbdown": "Карабкаться вниз",
"gameinput.wallleap": "Прыжок от стены", "gameinput.wallleap": "Прыжок от стены",
"gameinput.togglelantern": "Включить фонарь",
"gameinput.mount": "Оседлать", "gameinput.mount": "Оседлать",
"gameinput.enter": "Войти", "gameinput.enter": "Войти",
"gameinput.command": "Командовать", "gameinput.command": "Командовать",
@ -331,30 +384,38 @@ magically infused items?"#,
"gameinput.togglewield": "Достать/убрать оружие", "gameinput.togglewield": "Достать/убрать оружие",
"gameinput.interact": "Взаимодействовать", "gameinput.interact": "Взаимодействовать",
"gameinput.freelook": "Свободная камера", "gameinput.freelook": "Свободная камера",
"gameinput.autowalk": "Автодвижение",
"gameinput.dance": "Танцевать",
"gameinput.select": "Выбрать объект",
"gameinput.acceptgroupinvite": "Принять приглашение в группу",
"gameinput.declinegroupinvite": "Отклонить приглашение в группу",
/// End GameInput section /// End GameInput section
/// Start chracter selection section /// Start chracter selection section
"char_selection.loading_characters": "Загрузка персонажей...",
"char_selection.delete_permanently": "Навсегда удалить этого персонажа?", "char_selection.delete_permanently": "Навсегда удалить этого персонажа?",
"char_selection.deleting_character": "Удаление персонажа...",
"char_selection.change_server": "Сменить сервер", "char_selection.change_server": "Сменить сервер",
"char_selection.enter_world": "Войти в мир", "char_selection.enter_world": "Войти в мир",
"char_selection.logout": "Выйти в меню", "char_selection.logout": "Выйти в меню",
"char_selection.create_new_charater": "Создать нового персонажа", "char_selection.create_new_charater": "Создать нового персонажа",
"char_selection.creating_character": "Создание персонажа...",
"char_selection.character_creation": "Создание персонажа", "char_selection.character_creation": "Создание персонажа",
"char_selection.human_default": "Стандартный человек", "char_selection.human_default": "Стандартный человек",
"char_selection.level_fmt": "Уровень {level_nb}", "char_selection.level_fmt": "Уровень {level_nb}",
"char_selection.uncanny_valley": "Uncanny Valley", "char_selection.uncanny_valley": "Wilderness",
"char_selection.plains_of_uncertainty": "Plains of Uncertainty", "char_selection.plains_of_uncertainty": "Plains of Uncertainty",
"char_selection.beard": "Борода", "char_selection.beard": "Борода",
"char_selection.hair_style": "Прическа", "char_selection.hair_style": "Прическа",
"char_selection.hair_color": "Цвет волос", "char_selection.hair_color": "Цвет волос",
"char_selection.chest_color": "Цвет нагрудника",
"char_selection.eye_color": "Цвет глаз", "char_selection.eye_color": "Цвет глаз",
"char_selection.skin": "Кожа", "char_selection.skin": "Кожа",
"char_selection.eyebrows": "Брови", "char_selection.eyeshape": "Детали глаз",
"char_selection.accessories": "Аксессуары", "char_selection.accessories": "Аксессуары",
"char_selection.create_info_name": "Вашему персонажу необходимо имя!",
/// End chracter selection section /// End chracter selection section
@ -367,18 +428,122 @@ magically infused items?"#,
Выносливость Выносливость
Сила воли Сила воли
Защита
"#, "#,
/// End character window section
/// Start character window section
/// Start Escape Menu Section /// Start Escape Menu Section
"esc_menu.logout": "Выйти в меню", "esc_menu.logout": "Выйти в меню",
"esc_menu.quit_game": "Выйти из игры", "esc_menu.quit_game": "Выйти из игры",
/// End Escape Menu Section /// End Escape Menu Section
}, },
vector_map: { vector_map: {
"loading.tips": [
"Нажмите 'G', чтобы зажечь фонарь.",
"Нажмите 'F1', чтобы увидеть управление по-умолчанию.",
"Вы можете написать /say или /s, чтобы обратиться только к игрокам вблизи вас.",
"Вы можете написать /region или /r, чтобы обратиться к игрокам в нескольких сотнях блоков вокруг вас.",
"Вы можете написать /group или /g, чтобы обратиться к игрокам в вашей группе.",
"Чтобы отправить приватное сообщение, напишите /tell, а затем имя персонажа и сообщение.",
"NPC одного уровня могут быть разной сложности.",
"Осматривайтесь, чтобы найти еду, сундуки и другой лут!",
"Инвентарь забит едой? Попробуйте скрафтить из нее еду получше!",
"Думаете, чем заняться? Подземелья отмечены коричневыми метками на карте!",
"Не забудьте настроить графику под свою систему. Нажмите 'N', чтобы открыть настройки.",
"Играть с остальными веселее! Нажмите 'O', чтобы посмотреть кто онлайн.",
"NPC с черепом около полоски здоровья намного сильнее, по сравнению с вами.",
"Нажмите 'J', чтобы танцевать. Тусовка!",
"Нажмите 'L-Shift', чтобы открыть Глайдер и покорить небеса.",
"Veloren все еще пре-альфа. Мы стараемся улучшать его каждый день!",
"Если вы хотите присоединиться к команде разработчиков или просто пообщаться с нами, заходите на наш Дискорд-сервер.",
"Вы можете включить отображение чисел на полосе здоровья в настройках.",
"Чтобы увидеть свои характеристики нажмите на 'Статы' в инвентаре.",
],
"npc.speech.villager_under_attack": [
"Помогите, меня атакуют!",
"Помогите, меня атакуют!",
"Ай! Меня атакуют!",
"Ай! Меня атакуют! На помощь!",
"Помогите мне! Меня атакуют!",
"Меня атакуют! Помогите!",
"Меня атакуют!! На помощь!",
"Помогите!",
"На помощь! На помощь!",
"Помогите! Помогите! Помогите!",
"Меня атакуют!",
"ААА! Меня атакуют!",
"AAA! Меня атакуют! На помощь!",
"Помогите! Нас атакуют!",
"На помощь! Убийца!",
"Помогите! Здесь убийца!",
"На помощь! Меня пытаются убить!",
"Стража, меня атакуют!",
"Стража, на помощь!",
"Меня атакуют! Стража!",
"Помогите! Стража! Меня атакуют!",
"Стража! Cкорее!",
"Стража! Стража!",
"Стража! На меня напали!",
"Стража, убейте этого мерзкого злодея",
"Стража! Тут убийца!",
"Стража! Помогите мне!",
"Тебе это не сойдет с рук! Охрана!",
"Ты враг!",
"Помогите!",
"На помощь! Пожалуйста!",
"Ай! Стража, помогите!",
"Они пришли за мной!",
"На помощь, на помощь, на меня напали!",
"Ах, теперь мы видим насилие, присущее системе.",
"Это всего лишь царапина!",
"Прекрати!",
"Что я вообще тебе сделал?!",
"Пожалуйста, прекрати меня бить!",
"Эй, поаккуратнее с этой штукой!",
"Мерзкий негодняй, отстань!",
"Остановись! Уходи!",
"Ты злишь меня!",
"Ай! Кем ты себя возомнил?!",
"Я лишу тебя головы за это!",
"Остановись! У меня нет ничего ценного!",
"Я натравлю на тебя братьев! Они больше меня!",
"Нееет, я расскажу маме!",
"Будь ты проклят!",
"Пожалуйста, не надо!",
"Это было недружелюбно!",
"Хорошо, ты сильный, а теперь убери оружие!",
"Пощади меня!",
"Пожалуйста, у меня семья!",
"Я слишком молод, чтобы умирать!",
"Мы можем решить все словами?",
"Насилие не выход!",
"Так и знал, что день будет плохим...",
"Эй, больно же!",
"Эй!",
"Как некультурно!",
"Остановись, я прошу!",
"Проклятие!",
"Это не смешно.",
"Как ты смеешь?!",
"Ты заплатишь за это!",
"Не продолжай, а то пожалеешь!",
"Не заставляй делать тебе больно!",
"Ты все неправильно понял!",
"Зачем ты так?!",
"Проваливай, вражина!",
"Это было больно!",
"Почему ты это делаешь?",
"Ради духов, уймись!",
"Ты меня с кем-то спутал!",
"Я не заслужил этого!",
"Пожалуйста, не делай так больше.",
"Стража, киньте этого монстра в озеро!",
"Я натравлю на тебя своего тараска!",
],
} }
) )

BIN
assets/voxygen/voxel/sprite/castle/drop_gate_bars-0.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/castle/drop_gate_bottom-0.vox (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/grass/grass_snow_0.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/grass/grass_snow_1.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/grass/grass_snow_2.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/grass/grass_snow_3.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/grass/grass_snow_4.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/grass/grass_snow_5.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/grass/grass_snow_6.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/grass/grass_snow_7.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/grass/grass_snow_8.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/grass/grass_snow_9.vox (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -182,7 +182,7 @@ impl Client {
recipe_book, recipe_book,
} => { } => {
// TODO: Display that versions don't match in Voxygen // TODO: Display that versions don't match in Voxygen
if &server_info.git_hash != *common::util::GIT_HASH { if server_info.git_hash != *common::util::GIT_HASH {
warn!( warn!(
"Server is running {}[{}], you are running {}[{}], versions might \ "Server is running {}[{}], you are running {}[{}], versions might \
be incompatible!", be incompatible!",
@ -555,7 +555,7 @@ impl Client {
} }
pub fn pick_up(&mut self, entity: EcsEntity) { pub fn pick_up(&mut self, entity: EcsEntity) {
if let Some(uid) = self.state.read_component_copied(entity) { if let Some(uid) = self.state.read_component_cloned(entity) {
self.singleton_stream self.singleton_stream
.send(ClientMsg::ControlEvent(ControlEvent::InventoryManip( .send(ClientMsg::ControlEvent(ControlEvent::InventoryManip(
InventoryManip::Pickup(uid), InventoryManip::Pickup(uid),
@ -679,7 +679,7 @@ impl Client {
} }
pub fn mount(&mut self, entity: EcsEntity) { pub fn mount(&mut self, entity: EcsEntity) {
if let Some(uid) = self.state.read_component_copied(entity) { if let Some(uid) = self.state.read_component_cloned(entity) {
self.singleton_stream self.singleton_stream
.send(ClientMsg::ControlEvent(ControlEvent::Mount(uid))) .send(ClientMsg::ControlEvent(ControlEvent::Mount(uid)))
.unwrap(); .unwrap();
@ -1453,7 +1453,7 @@ impl Client {
pub fn entity(&self) -> EcsEntity { self.entity } pub fn entity(&self) -> EcsEntity { self.entity }
/// Get the player's Uid. /// Get the player's Uid.
pub fn uid(&self) -> Option<Uid> { self.state.read_component_copied(self.entity) } pub fn uid(&self) -> Option<Uid> { self.state.read_component_cloned(self.entity) }
/// Get the client state /// Get the client state
pub fn get_client_state(&self) -> ClientState { self.client_state } pub fn get_client_state(&self) -> ClientState { self.client_state }
@ -1506,7 +1506,7 @@ impl Client {
pub fn is_admin(&self) -> bool { pub fn is_admin(&self) -> bool {
let client_uid = self let client_uid = self
.state .state
.read_component_copied::<Uid>(self.entity) .read_component_cloned::<Uid>(self.entity)
.expect("Client doesn't have a Uid!!!"); .expect("Client doesn't have a Uid!!!");
self.player_list self.player_list

View File

@ -32,6 +32,7 @@ indexmap = "1.3.0"
sum_type = "0.2.0" sum_type = "0.2.0"
authc = { git = "https://gitlab.com/veloren/auth.git", rev = "b943c85e4a38f5ec60cd18c34c73097640162bfe" } authc = { git = "https://gitlab.com/veloren/auth.git", rev = "b943c85e4a38f5ec60cd18c34c73097640162bfe" }
slab = "0.4.2" slab = "0.4.2"
enum-iterator = "0.6"
[dev-dependencies] [dev-dependencies]
criterion = "0.3" criterion = "0.3"

View File

@ -121,12 +121,10 @@ impl<S: Clone + Eq + Hash, H: BuildHasher + Clone> Astar<S, H> {
{ {
let iter_limit = self.max_iters.min(self.iter + iters); let iter_limit = self.max_iters.min(self.iter + iters);
while self.iter < iter_limit { while self.iter < iter_limit {
if let Some(PathEntry { node, cost }) = self.potential_nodes.pop() { if let Some(PathEntry { node, .. }) = self.potential_nodes.pop() {
self.cheapest_cost = Some(cost);
if satisfied(&node) { if satisfied(&node) {
return PathResult::Path(self.reconstruct_path_to(node)); return PathResult::Path(self.reconstruct_path_to(node));
} else { } else {
self.cheapest_node = Some(node.clone());
for neighbor in neighbors(&node) { for neighbor in neighbors(&node) {
let node_cheapest = self.cheapest_scores.get(&node).unwrap_or(&f32::MAX); let node_cheapest = self.cheapest_scores.get(&node).unwrap_or(&f32::MAX);
let neighbor_cheapest = let neighbor_cheapest =
@ -136,12 +134,18 @@ impl<S: Clone + Eq + Hash, H: BuildHasher + Clone> Astar<S, H> {
if cost < *neighbor_cheapest { if cost < *neighbor_cheapest {
self.came_from.insert(neighbor.clone(), node.clone()); self.came_from.insert(neighbor.clone(), node.clone());
self.cheapest_scores.insert(neighbor.clone(), cost); self.cheapest_scores.insert(neighbor.clone(), cost);
let neighbor_cost = cost + heuristic(&neighbor); let h = heuristic(&neighbor);
let neighbor_cost = cost + h;
self.final_scores.insert(neighbor.clone(), neighbor_cost); self.final_scores.insert(neighbor.clone(), neighbor_cost);
if self.cheapest_cost.map(|cc| h < cc).unwrap_or(true) {
self.cheapest_node = Some(node.clone());
self.cheapest_cost = Some(h);
};
if self.visited.insert(neighbor.clone()) { if self.visited.insert(neighbor.clone()) {
self.potential_nodes.push(PathEntry { self.potential_nodes.push(PathEntry {
node: neighbor.clone(), node: neighbor,
cost: neighbor_cost, cost: neighbor_cost,
}); });
} }

View File

@ -1,4 +1,4 @@
use crate::{assets, comp, npc}; use crate::{assets, comp, npc, terrain};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use std::{ use std::{
collections::HashMap, collections::HashMap,
@ -56,6 +56,7 @@ pub enum ChatCommand {
KillNpcs, KillNpcs,
Lantern, Lantern,
Light, Light,
MakeBlock,
Motd, Motd,
Object, Object,
Players, Players,
@ -98,6 +99,7 @@ pub static CHAT_COMMANDS: &[ChatCommand] = &[
ChatCommand::KillNpcs, ChatCommand::KillNpcs,
ChatCommand::Lantern, ChatCommand::Lantern,
ChatCommand::Light, ChatCommand::Light,
ChatCommand::MakeBlock,
ChatCommand::Motd, ChatCommand::Motd,
ChatCommand::Object, ChatCommand::Object,
ChatCommand::Players, ChatCommand::Players,
@ -149,6 +151,11 @@ lazy_static! {
.map(|s| s.to_string()) .map(|s| s.to_string())
.collect(); .collect();
static ref BLOCK_KINDS: Vec<String> = terrain::block::BLOCK_KINDS
.keys()
.cloned()
.collect();
/// List of item specifiers. Useful for tab completing /// List of item specifiers. Useful for tab completing
static ref ITEM_SPECS: Vec<String> = { static ref ITEM_SPECS: Vec<String> = {
let path = assets::ASSETS_PATH.join("common").join("items"); let path = assets::ASSETS_PATH.join("common").join("items");
@ -281,6 +288,11 @@ impl ChatCommand {
"Spawn entity with light", "Spawn entity with light",
Admin, Admin,
), ),
ChatCommand::MakeBlock => cmd(
vec![Enum("block", BLOCK_KINDS.clone(), Required)],
"Make a block",
Admin,
),
ChatCommand::Motd => cmd( ChatCommand::Motd => cmd(
vec![Message(Optional)], vec![Message(Optional)],
"View the server description", "View the server description",
@ -386,6 +398,7 @@ impl ChatCommand {
ChatCommand::KillNpcs => "kill_npcs", ChatCommand::KillNpcs => "kill_npcs",
ChatCommand::Lantern => "lantern", ChatCommand::Lantern => "lantern",
ChatCommand::Light => "light", ChatCommand::Light => "light",
ChatCommand::MakeBlock => "make_block",
ChatCommand::Motd => "motd", ChatCommand::Motd => "motd",
ChatCommand::Object => "object", ChatCommand::Object => "object",
ChatCommand::Players => "players", ChatCommand::Players => "players",

View File

@ -1,4 +1,4 @@
use crate::{path::Chaser, sync::Uid}; use crate::{comp::Body, path::Chaser, sync::Uid};
use specs::{Component, Entity as EcsEntity}; use specs::{Component, Entity as EcsEntity};
use specs_idvs::IdvStorage; use specs_idvs::IdvStorage;
use vek::*; use vek::*;
@ -54,6 +54,33 @@ impl Component for Alignment {
type Storage = IdvStorage<Self>; type Storage = IdvStorage<Self>;
} }
#[derive(Clone, Debug, Default)]
pub struct Psyche {
pub aggro: f32, // 0.0 = always flees, 1.0 = always attacks
}
impl<'a> From<&'a Body> for Psyche {
fn from(body: &'a Body) -> Self {
Self {
aggro: match body {
Body::Humanoid(_) => 0.5,
Body::QuadrupedSmall(_) => 0.35,
Body::QuadrupedMedium(_) => 0.5,
Body::QuadrupedLow(_) => 0.65,
Body::BirdMedium(_) => 1.0,
Body::BirdSmall(_) => 0.2,
Body::FishMedium(_) => 0.15,
Body::FishSmall(_) => 0.0,
Body::BipedLarge(_) => 1.0,
Body::Object(_) => 0.0,
Body::Golem(_) => 1.0,
Body::Critter(_) => 0.1,
Body::Dragon(_) => 1.0,
},
}
}
}
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct Agent { pub struct Agent {
pub patrol_origin: Option<Vec3<f32>>, pub patrol_origin: Option<Vec3<f32>>,
@ -61,6 +88,7 @@ pub struct Agent {
/// Does the agent talk when e.g. hit by the player /// Does the agent talk when e.g. hit by the player
// TODO move speech patterns into a Behavior component // TODO move speech patterns into a Behavior component
pub can_speak: bool, pub can_speak: bool,
pub psyche: Psyche,
} }
impl Agent { impl Agent {
@ -69,11 +97,12 @@ impl Agent {
self self
} }
pub fn new(origin: Vec3<f32>, can_speak: bool) -> Self { pub fn new(origin: Vec3<f32>, can_speak: bool, body: &Body) -> Self {
let patrol_origin = Some(origin); let patrol_origin = Some(origin);
Agent { Agent {
patrol_origin, patrol_origin,
can_speak, can_speak,
psyche: Psyche::from(body),
..Default::default() ..Default::default()
} }
} }

View File

@ -1,5 +1,4 @@
pub mod armor; pub mod armor;
pub mod lottery;
pub mod tool; pub mod tool;
// Reexports // Reexports
@ -8,6 +7,7 @@ pub use tool::{Hands, Tool, ToolCategory, ToolKind};
use crate::{ use crate::{
assets::{self, Asset}, assets::{self, Asset},
effect::Effect, effect::Effect,
lottery::Lottery,
terrain::{Block, BlockKind}, terrain::{Block, BlockKind},
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -171,7 +171,7 @@ impl Item {
BlockKind::ShortGrass => Some(assets::load_expect_cloned("common.items.grasses.short")), BlockKind::ShortGrass => Some(assets::load_expect_cloned("common.items.grasses.short")),
BlockKind::Coconut => Some(assets::load_expect_cloned("common.items.food.coconut")), BlockKind::Coconut => Some(assets::load_expect_cloned("common.items.food.coconut")),
BlockKind::Chest => { BlockKind::Chest => {
let chosen = assets::load_expect::<lottery::Lottery<_>>("common.loot_table"); let chosen = assets::load_expect::<Lottery<String>>("common.loot_table");
let chosen = chosen.choose(); let chosen = chosen.choose();
Some(assets::load_expect_cloned(chosen)) Some(assets::load_expect_cloned(chosen))

View File

@ -503,7 +503,7 @@ impl Inventory {
} }
} }
if missing.len() == 0 { if missing.is_empty() {
Ok(slot_claims) Ok(slot_claims)
} else { } else {
Err(missing) Err(missing)

View File

@ -80,7 +80,7 @@ pub struct PhysicsState {
pub on_ceiling: bool, pub on_ceiling: bool,
pub on_wall: Option<Vec3<f32>>, pub on_wall: Option<Vec3<f32>>,
pub touch_entity: Option<Uid>, pub touch_entity: Option<Uid>,
pub in_fluid: bool, pub in_fluid: Option<f32>, // Depth
} }
impl PhysicsState { impl PhysicsState {

View File

@ -116,6 +116,8 @@ impl EntityInfo {
}, },
Body::Dragon(body) => Some(get_npc_name(&NPC_NAMES.dragon, body.species)), Body::Dragon(body) => Some(get_npc_name(&NPC_NAMES.dragon, body.species)),
Body::QuadrupedLow(body) => Some(get_npc_name(&NPC_NAMES.quadruped_low, body.species)), Body::QuadrupedLow(body) => Some(get_npc_name(&NPC_NAMES.quadruped_low, body.species)),
Body::Golem(body) => Some(get_npc_name(&NPC_NAMES.golem, body.species)),
Body::BipedLarge(body) => Some(get_npc_name(&NPC_NAMES.biped_large, body.species)),
_ => None, _ => None,
} }
.map(|s| { .map(|s| {

View File

@ -24,6 +24,7 @@ pub mod event;
pub mod figure; pub mod figure;
pub mod generation; pub mod generation;
pub mod loadout_builder; pub mod loadout_builder;
pub mod lottery;
pub mod msg; pub mod msg;
pub mod npc; pub mod npc;
pub mod outcome; pub mod outcome;

View File

@ -1,25 +1,19 @@
use crate::assets::{self, Asset}; use crate::assets::{self, Asset};
use rand::prelude::*; use rand::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{fs::File, io::BufReader}; use std::{fs::File, io::BufReader};
// Generate a random float between 0 and 1
pub fn rand() -> f32 {
let mut rng = rand::thread_rng();
rng.gen()
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Lottery<T> { pub struct Lottery<T> {
items: Vec<(f32, T)>, items: Vec<(f32, T)>,
total: f32, total: f32,
} }
impl Asset for Lottery<String> { impl<T: DeserializeOwned + Send + Sync> Asset for Lottery<T> {
const ENDINGS: &'static [&'static str] = &["ron"]; const ENDINGS: &'static [&'static str] = &["ron"];
fn parse(buf_reader: BufReader<File>) -> Result<Self, assets::Error> { fn parse(buf_reader: BufReader<File>) -> Result<Self, assets::Error> {
ron::de::from_reader::<BufReader<File>, Vec<(f32, String)>>(buf_reader) ron::de::from_reader::<BufReader<File>, Vec<(f32, T)>>(buf_reader)
.map(|items| Lottery::from_rates(items.into_iter())) .map(|items| Lottery::from_rates(items.into_iter()))
.map_err(assets::Error::parse_error) .map_err(assets::Error::parse_error)
} }
@ -37,8 +31,8 @@ impl<T> Lottery<T> {
Self { items, total } Self { items, total }
} }
pub fn choose(&self) -> &T { pub fn choose_seeded(&self, seed: u32) -> &T {
let x = rand() * self.total; let x = ((seed % 65536) as f32 / 65536.0) * self.total;
&self.items[self &self.items[self
.items .items
.binary_search_by(|(y, _)| y.partial_cmp(&x).unwrap()) .binary_search_by(|(y, _)| y.partial_cmp(&x).unwrap())
@ -46,19 +40,18 @@ impl<T> Lottery<T> {
.1 .1
} }
pub fn choose(&self) -> &T { self.choose_seeded(thread_rng().gen()) }
pub fn iter(&self) -> impl Iterator<Item = &(f32, T)> { self.items.iter() } pub fn iter(&self) -> impl Iterator<Item = &(f32, T)> { self.items.iter() }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{ use super::*;
assets, use crate::{assets, comp::Item};
comp::inventory::item::{lottery::Lottery, Item},
};
#[test] #[test]
fn test_loot_table() { fn test_loot_table() {
let test = assets::load_expect::<Lottery<_>>("common.loot_table"); let test = assets::load_expect::<Lottery<String>>("common.loot_table");
let test = test;
for (_, item) in test.iter() { for (_, item) in test.iter() {
assert!( assert!(

View File

@ -68,6 +68,17 @@ pub struct TraversalConfig {
pub min_tgt_dist: f32, pub min_tgt_dist: f32,
} }
const DIAGONALS: [Vec2<i32>; 8] = [
Vec2::new(1, 0),
Vec2::new(1, 1),
Vec2::new(0, 1),
Vec2::new(-1, 1),
Vec2::new(-1, 0),
Vec2::new(-1, -1),
Vec2::new(0, -1),
Vec2::new(1, -1),
];
impl Route { impl Route {
pub fn path(&self) -> &Path<Vec3<i32>> { &self.path } pub fn path(&self) -> &Path<Vec3<i32>> { &self.path }
@ -88,45 +99,29 @@ impl Route {
V: BaseVol<Vox = Block> + ReadVol, V: BaseVol<Vox = Block> + ReadVol,
{ {
let (next0, next1, next_tgt, be_precise) = loop { let (next0, next1, next_tgt, be_precise) = loop {
// If we've reached the end of the path, stop
self.next(0)?;
let next0 = self let next0 = self
.next(0) .next(0)
.unwrap_or_else(|| pos.map(|e| e.floor() as i32)); .unwrap_or_else(|| pos.map(|e| e.floor() as i32));
let next1 = self.next(1).unwrap_or(next0);
// Stop using obstructed paths // Stop using obstructed paths
if vol.get(next0).map(|b| b.is_solid()).unwrap_or(false) { if !walkable(vol, next1) {
return None; return None;
} }
let diagonals = [ let be_precise = DIAGONALS.iter().any(|pos| {
Vec2::new(1, 0), (-1..2).all(|z| {
Vec2::new(1, 1), vol.get(next0 + Vec3::new(pos.x, pos.y, z))
Vec2::new(0, 1), .map(|b| !b.is_solid())
Vec2::new(-1, 1), .unwrap_or(false)
Vec2::new(-1, 0), })
Vec2::new(-1, -1),
Vec2::new(0, -1),
Vec2::new(1, -1),
];
let next1 = self.next(1).unwrap_or(next0);
let be_precise = diagonals.iter().any(|pos| {
!walkable(vol, next0 + Vec3::new(pos.x, pos.y, 0))
&& !walkable(vol, next0 + Vec3::new(pos.x, pos.y, -1))
&& !walkable(vol, next0 + Vec3::new(pos.x, pos.y, -2))
&& !walkable(vol, next0 + Vec3::new(pos.x, pos.y, 1))
}); });
let next0_tgt = next0.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0); let next_tgt = next0.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0);
let next1_tgt = next1.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0); let closest_tgt = next_tgt.map2(pos, |tgt, pos| pos.clamped(tgt.floor(), tgt.ceil()));
let next_tgt = next0_tgt;
// Maybe skip a node (useful with traversing downhill)
let closest_tgt = if next0_tgt.distance_squared(pos) < next1_tgt.distance_squared(pos) {
next0_tgt
} else {
next1_tgt
};
// Determine whether we're close enough to the next to to consider it completed // Determine whether we're close enough to the next to to consider it completed
let dist_sqrd = pos.xy().distance_squared(closest_tgt.xy()); let dist_sqrd = pos.xy().distance_squared(closest_tgt.xy());
@ -135,12 +130,12 @@ impl Route {
&& (pos.z - closest_tgt.z < 1.2 || (pos.z - closest_tgt.z < 2.9 && vel.z < -0.05)) && (pos.z - closest_tgt.z < 1.2 || (pos.z - closest_tgt.z < 2.9 && vel.z < -0.05))
&& vel.z <= 0.0 && vel.z <= 0.0
// Only consider the node reached if there's nothing solid between us and it // Only consider the node reached if there's nothing solid between us and it
&& vol && (vol
.ray(pos + Vec3::unit_z() * 1.5, closest_tgt + Vec3::unit_z() * 1.5) .ray(pos + Vec3::unit_z() * 1.5, closest_tgt + Vec3::unit_z() * 1.5)
.until(|block| block.is_solid()) .until(|block| block.is_solid())
.cast() .cast()
.0 .0
> pos.distance(closest_tgt) * 0.9 > pos.distance(closest_tgt) * 0.9 || dist_sqrd < 0.5)
&& self.next_idx < self.path.len() && self.next_idx < self.path.len()
{ {
// Node completed, move on to the next one // Node completed, move on to the next one
@ -312,7 +307,7 @@ impl Route {
#[derive(Default, Clone, Debug)] #[derive(Default, Clone, Debug)]
pub struct Chaser { pub struct Chaser {
last_search_tgt: Option<Vec3<f32>>, last_search_tgt: Option<Vec3<f32>>,
route: Option<Route>, route: Option<(Route, bool)>,
/// We use this hasher (AAHasher) because: /// We use this hasher (AAHasher) because:
/// (1) we care about DDOS attacks (ruling out FxHash); /// (1) we care about DDOS attacks (ruling out FxHash);
/// (2) we don't care about determinism across computers (we can use /// (2) we don't care about determinism across computers (we can use
@ -335,14 +330,24 @@ impl Chaser {
let pos_to_tgt = pos.distance(tgt); let pos_to_tgt = pos.distance(tgt);
// If we're already close to the target then there's nothing to do // If we're already close to the target then there's nothing to do
if ((pos - tgt) * Vec3::new(1.0, 1.0, 2.0)).magnitude_squared() let end = self
.route
.as_ref()
.and_then(|(r, _)| r.path.end().copied())
.map(|e| e.map(|e| e as f32 + 0.5))
.unwrap_or(tgt);
if ((pos - end) * Vec3::new(1.0, 1.0, 2.0)).magnitude_squared()
< traversal_cfg.min_tgt_dist.powf(2.0) < traversal_cfg.min_tgt_dist.powf(2.0)
{ {
self.route = None; self.route = None;
return None; return None;
} }
let bearing = if let Some(end) = self.route.as_ref().and_then(|r| r.path().end().copied()) { let bearing = if let Some((end, complete)) = self
.route
.as_ref()
.and_then(|(r, complete)| Some((r.path().end().copied()?, *complete)))
{
let end_to_tgt = end.map(|e| e as f32).distance(tgt); let end_to_tgt = end.map(|e| e as f32).distance(tgt);
// If the target has moved significantly since the path was generated then it's // If the target has moved significantly since the path was generated then it's
// time to search for a new path. Also, do this randomly from time // time to search for a new path. Also, do this randomly from time
@ -350,21 +355,14 @@ impl Chaser {
// theory this shouldn't happen, but in practice the world is full // theory this shouldn't happen, but in practice the world is full
// of unpredictable obstacles that are more than willing to mess up // of unpredictable obstacles that are more than willing to mess up
// our day. TODO: Come up with a better heuristic for this // our day. TODO: Come up with a better heuristic for this
if end_to_tgt > pos_to_tgt * 0.3 + 5.0 if (end_to_tgt > pos_to_tgt * 0.3 + 5.0 && complete)
/* || thread_rng().gen::<f32>() < 0.005 */ || thread_rng().gen::<f32>() < 0.001
{ {
None None
} else { } else {
self.route self.route
.as_mut() .as_mut()
.and_then(|r| r.traverse(vol, pos, vel, traversal_cfg)) .and_then(|(r, _)| r.traverse(vol, pos, vel, traversal_cfg))
// In theory this filter isn't needed, but in practice agents often try to take
// stale paths that start elsewhere. This code makes sure that we're only using
// paths that start near us, avoiding the agent doubling back to chase a stale
// path.
.filter(|(bearing, _)| bearing.xy()
.magnitude_squared() < 1.75f32.powf(2.0)
&& thread_rng().gen::<f32>() > 0.025)
} }
} else { } else {
None None
@ -373,6 +371,8 @@ impl Chaser {
if let Some((bearing, speed)) = bearing { if let Some((bearing, speed)) = bearing {
Some((bearing, speed)) Some((bearing, speed))
} else { } else {
let tgt_dir = (tgt - pos).xy().try_normalized().unwrap_or_default();
// Only search for a path if the target has moved from their last position. We // Only search for a path if the target has moved from their last position. We
// don't want to be thrashing the pathfinding code for targets that // don't want to be thrashing the pathfinding code for targets that
// we're unable to access! // we're unable to access!
@ -383,16 +383,45 @@ impl Chaser {
|| self.astar.is_some() || self.astar.is_some()
|| self.route.is_none() || self.route.is_none()
{ {
let (start_pos, path) = find_path(&mut self.astar, vol, pos, tgt); self.last_search_tgt = Some(tgt);
// Don't use a stale path
if start_pos.distance_squared(pos) < 4.0f32.powf(2.0) { let (path, complete) = find_path(&mut self.astar, vol, pos, tgt);
self.route = path.map(Route::from);
} else { self.route = path.map(|path| {
self.route = None; let start_index = path
} .iter()
.enumerate()
.min_by_key(|(_, node)| {
node.xy()
.map(|e| e as f32)
.distance_squared(pos.xy() + tgt_dir)
as i32
})
.map(|(idx, _)| idx);
(
Route {
path,
next_idx: start_index.unwrap_or(0),
},
complete,
)
});
} }
let walking_towards_edge = (-3..2).all(|z| {
vol.get(
(pos + Vec3::<f32>::from(tgt_dir) * 2.5).map(|e| e as i32) + Vec3::unit_z() * z,
)
.map(|b| !b.is_solid())
.unwrap_or(false)
});
if !walking_towards_edge {
Some(((tgt - pos) * Vec3::new(1.0, 1.0, 0.0), 0.75)) Some(((tgt - pos) * Vec3::new(1.0, 1.0, 0.0), 0.75))
} else {
None
}
} }
} }
} }
@ -415,13 +444,14 @@ where
.unwrap_or(true) .unwrap_or(true)
} }
#[allow(clippy::float_cmp)] // TODO: Pending review in #587 /// Attempt to search for a path to a target, returning the path (if one was
/// found) and whether it is complete (reaches the target)
fn find_path<V>( fn find_path<V>(
astar: &mut Option<Astar<Vec3<i32>, DefaultHashBuilder>>, astar: &mut Option<Astar<Vec3<i32>, DefaultHashBuilder>>,
vol: &V, vol: &V,
startf: Vec3<f32>, startf: Vec3<f32>,
endf: Vec3<f32>, endf: Vec3<f32>,
) -> (Vec3<f32>, Option<Path<Vec3<i32>>>) ) -> (Option<Path<Vec3<i32>>>, bool)
where where
V: BaseVol<Vox = Block> + ReadVol, V: BaseVol<Vox = Block> + ReadVol,
{ {
@ -443,29 +473,33 @@ where
get_walkable_z(endf.map(|e| e.floor() as i32)), get_walkable_z(endf.map(|e| e.floor() as i32)),
) { ) {
(Some(start), Some(end)) => (start, end), (Some(start), Some(end)) => (start, end),
_ => return (startf, None), _ => return (None, false),
}; };
let heuristic = |pos: &Vec3<i32>| (pos.distance_squared(end) as f32).sqrt(); let heuristic = |pos: &Vec3<i32>| (pos.distance_squared(end) as f32).sqrt();
let neighbors = |pos: &Vec3<i32>| { let neighbors = |pos: &Vec3<i32>| {
let pos = *pos; let pos = *pos;
const DIRS: [Vec3<i32>; 17] = [ const DIRS: [Vec3<i32>; 21] = [
Vec3::new(0, 1, 0), // Forward Vec3::new(0, 1, 0), // Forward
Vec3::new(0, 1, 1), // Forward upward Vec3::new(0, 1, 1), // Forward upward
Vec3::new(0, 1, 2), // Forward Upwardx2 Vec3::new(0, 1, 2), // Forward Upwardx2
Vec3::new(0, 1, -1), // Forward downward Vec3::new(0, 1, -1), // Forward downward
Vec3::new(0, 1, -2), // Forward downwardx2
Vec3::new(1, 0, 0), // Right Vec3::new(1, 0, 0), // Right
Vec3::new(1, 0, 1), // Right upward Vec3::new(1, 0, 1), // Right upward
Vec3::new(1, 0, 2), // Right Upwardx2 Vec3::new(1, 0, 2), // Right Upwardx2
Vec3::new(1, 0, -1), // Right downward Vec3::new(1, 0, -1), // Right downward
Vec3::new(1, 0, -2), // Right downwardx2
Vec3::new(0, -1, 0), // Backwards Vec3::new(0, -1, 0), // Backwards
Vec3::new(0, -1, 1), // Backward Upward Vec3::new(0, -1, 1), // Backward Upward
Vec3::new(0, -1, 2), // Backward Upwardx2 Vec3::new(0, -1, 2), // Backward Upwardx2
Vec3::new(0, -1, -1), // Backward downward Vec3::new(0, -1, -1), // Backward downward
Vec3::new(0, -1, -2), // Backward downwardx2
Vec3::new(-1, 0, 0), // Left Vec3::new(-1, 0, 0), // Left
Vec3::new(-1, 0, 1), // Left upward Vec3::new(-1, 0, 1), // Left upward
Vec3::new(-1, 0, 2), // Left Upwardx2 Vec3::new(-1, 0, 2), // Left Upwardx2
Vec3::new(-1, 0, -1), // Left downward Vec3::new(-1, 0, -1), // Left downward
Vec3::new(-1, 0, -2), // Left downwardx2
Vec3::new(0, 0, -1), // Downwards Vec3::new(0, 0, -1), // Downwards
]; ];
@ -541,19 +575,19 @@ where
*astar = Some(new_astar); *astar = Some(new_astar);
(startf, match path_result { match path_result {
PathResult::Path(path) => { PathResult::Path(path) => {
*astar = None; *astar = None;
Some(path) (Some(path), true)
}, },
PathResult::None(path) => { PathResult::None(path) => {
*astar = None; *astar = None;
Some(path) (Some(path), false)
}, },
PathResult::Exhausted(path) => { PathResult::Exhausted(path) => {
*astar = None; *astar = None;
Some(path) (Some(path), false)
}, },
PathResult::Pending => None, PathResult::Pending => (None, false),
}) }
} }

View File

@ -11,12 +11,17 @@ pub struct Spiral2d {
impl Spiral2d { impl Spiral2d {
#[allow(clippy::new_without_default)] // TODO: Pending review in #587 #[allow(clippy::new_without_default)] // TODO: Pending review in #587
pub fn new() -> Self { Self { layer: 0, i: 0 } } pub fn new() -> Self { Self { layer: 0, i: 0 } }
pub fn radius(self, radius: i32) -> impl Iterator<Item = Vec2<i32>> {
self.take((radius * 2 + 1).pow(2) as usize)
.filter(move |pos| pos.magnitude_squared() < (radius + 1).pow(2))
}
} }
impl Iterator for Spiral2d { impl Iterator for Spiral2d {
type Item = Vec2<i32>; type Item = Vec2<i32>;
#[allow(clippy::erasing_op)] #[allow(clippy::erasing_op, clippy::identity_op)]
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let layer_size = (self.layer * 8 + 4 * self.layer.min(1) - 4).max(1); let layer_size = (self.layer * 8 + 4 * self.layer.min(1) - 4).max(1);
if self.i >= layer_size { if self.i >= layer_size {

View File

@ -199,8 +199,8 @@ impl State {
} }
/// Read a component attributed to a particular entity. /// Read a component attributed to a particular entity.
pub fn read_component_copied<C: Component + Copy>(&self, entity: EcsEntity) -> Option<C> { pub fn read_component_cloned<C: Component + Copy>(&self, entity: EcsEntity) -> Option<C> {
self.ecs.read_storage().get(entity).copied() self.ecs.read_storage().get(entity).cloned()
} }
/// Get a read-only reference to the storage of a particular component type. /// Get a read-only reference to the storage of a particular component type.

View File

@ -24,7 +24,12 @@ impl CharacterBehavior for Data {
update.character = CharacterState::GlideWield; update.character = CharacterState::GlideWield;
return update; return update;
} }
if data.physics.in_fluid { if data
.physics
.in_fluid
.map(|depth| depth > 0.5)
.unwrap_or(false)
{
update.character = CharacterState::Idle; update.character = CharacterState::Idle;
} }
// If there is a wall in front of character and they are trying to climb go to // If there is a wall in front of character and they are trying to climb go to

View File

@ -19,7 +19,12 @@ impl CharacterBehavior for Data {
if !data.physics.on_ground { if !data.physics.on_ground {
update.character = CharacterState::Glide; update.character = CharacterState::Glide;
} }
if data.physics.in_fluid { if data
.physics
.in_fluid
.map(|depth| depth > 0.5)
.unwrap_or(false)
{
update.character = CharacterState::Idle; update.character = CharacterState::Idle;
} }

View File

@ -8,7 +8,7 @@ use crate::{
sys::{character_behavior::JoinData, phys::GRAVITY}, sys::{character_behavior::JoinData, phys::GRAVITY},
util::Dir, util::Dir,
}; };
use vek::vec::Vec2; use vek::*;
pub const MOVEMENT_THRESHOLD_VEL: f32 = 3.0; pub const MOVEMENT_THRESHOLD_VEL: f32 = 3.0;
const BASE_HUMANOID_AIR_ACCEL: f32 = 8.0; const BASE_HUMANOID_AIR_ACCEL: f32 = 8.0;
@ -67,8 +67,8 @@ impl Body {
/// Handles updating `Components` to move player based on state of `JoinData` /// Handles updating `Components` to move player based on state of `JoinData`
pub fn handle_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) { pub fn handle_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) {
if data.physics.in_fluid { if let Some(depth) = data.physics.in_fluid {
swim_move(data, update, efficiency); swim_move(data, update, efficiency, depth);
} else { } else {
basic_move(data, update, efficiency); basic_move(data, update, efficiency);
} }
@ -104,7 +104,7 @@ pub fn handle_orientation(data: &JoinData, update: &mut StateUpdate, rate: f32)
} }
/// Updates components to move player as if theyre swimming /// Updates components to move player as if theyre swimming
fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) { fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32, depth: f32) {
// Update velocity // Update velocity
update.vel.0 += Vec2::broadcast(data.dt.0) update.vel.0 += Vec2::broadcast(data.dt.0)
* data.inputs.move_dir * data.inputs.move_dir
@ -119,8 +119,9 @@ fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) {
// Swim // Swim
if data.inputs.swimup.is_pressed() { if data.inputs.swimup.is_pressed() {
update.vel.0.z = update.vel.0.z = (update.vel.0.z
(update.vel.0.z + data.dt.0 * GRAVITY * 4.0).min(BASE_HUMANOID_WATER_SPEED); + data.dt.0 * GRAVITY * 4.0 * depth.clamped(0.0, 1.0).powf(3.0))
.min(BASE_HUMANOID_WATER_SPEED);
} }
// Swim // Swim
if data.inputs.swimdown.is_pressed() { if data.inputs.swimdown.is_pressed() {
@ -192,14 +193,28 @@ pub fn attempt_swap_loadout(data: &JoinData, update: &mut StateUpdate) {
/// Checks that player can wield the glider and updates `CharacterState` if so /// Checks that player can wield the glider and updates `CharacterState` if so
pub fn attempt_glide_wield(data: &JoinData, update: &mut StateUpdate) { pub fn attempt_glide_wield(data: &JoinData, update: &mut StateUpdate) {
if data.physics.on_ground && !data.physics.in_fluid && data.body.is_humanoid() { if data.physics.on_ground
&& !data
.physics
.in_fluid
.map(|depth| depth > 1.0)
.unwrap_or(false)
&& data.body.is_humanoid()
{
update.character = CharacterState::GlideWield; update.character = CharacterState::GlideWield;
} }
} }
/// Checks that player can jump and sends jump event if so /// Checks that player can jump and sends jump event if so
pub fn handle_jump(data: &JoinData, update: &mut StateUpdate) { pub fn handle_jump(data: &JoinData, update: &mut StateUpdate) {
if data.inputs.jump.is_pressed() && data.physics.on_ground && !data.physics.in_fluid { if data.inputs.jump.is_pressed()
&& data.physics.on_ground
&& !data
.physics
.in_fluid
.map(|depth| depth > 1.0)
.unwrap_or(false)
{
update update
.local_events .local_events
.push_front(LocalEvent::Jump(data.entity)); .push_front(LocalEvent::Jump(data.entity));

View File

@ -50,20 +50,17 @@ impl<T> Store<T> {
} }
pub fn ids(&self) -> impl Iterator<Item = Id<T>> { pub fn ids(&self) -> impl Iterator<Item = Id<T>> {
// NOTE: Assumes usize fits into 8 bytes. (0..self.items.len()).map(|i| Id(i as u64, PhantomData))
(0..self.items.len() as u64).map(|i| Id(i, PhantomData))
} }
pub fn iter(&self) -> impl Iterator<Item = &T> { self.items.iter() } pub fn values(&self) -> impl Iterator<Item = &T> { self.items.iter() }
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> { self.items.iter_mut() } pub fn values_mut(&mut self) -> impl Iterator<Item = &mut T> { self.items.iter_mut() }
pub fn iter_ids(&self) -> impl Iterator<Item = (Id<T>, &T)> { pub fn iter(&self) -> impl Iterator<Item = (Id<T>, &T)> { self.ids().zip(self.values()) }
self.items
.iter() pub fn iter_mut(&mut self) -> impl Iterator<Item = (Id<T>, &mut T)> {
.enumerate() self.ids().zip(self.values_mut())
// NOTE: Assumes usize fits into 8 bytes.
.map(|(i, item)| (Id(i as u64, PhantomData), item))
} }
pub fn insert(&mut self, item: T) -> Id<T> { pub fn insert(&mut self, item: T) -> Id<T> {

View File

@ -151,6 +151,7 @@ impl<'a> System<'a> for Sys {
const SEARCH_DIST: f32 = 48.0; const SEARCH_DIST: f32 = 48.0;
const SIGHT_DIST: f32 = 128.0; const SIGHT_DIST: f32 = 128.0;
const MIN_ATTACK_DIST: f32 = 3.5; const MIN_ATTACK_DIST: f32 = 3.5;
const MAX_FLEE_DIST: f32 = 32.0;
let scale = scales.get(entity).map(|s| s.0).unwrap_or(1.0); let scale = scales.get(entity).map(|s| s.0).unwrap_or(1.0);
@ -158,7 +159,7 @@ impl<'a> System<'a> for Sys {
// and so can afford to be less precise when trying to move around // and so can afford to be less precise when trying to move around
// the world (especially since they would otherwise get stuck on // the world (especially since they would otherwise get stuck on
// obstacles that smaller entities would not). // obstacles that smaller entities would not).
let node_tolerance = scale + vel.0.xy().magnitude() * 0.2; let node_tolerance = scale * 1.5;
let slow_factor = body.map(|b| b.base_accel() / 250.0).unwrap_or(0.0).min(1.0); let slow_factor = body.map(|b| b.base_accel() / 250.0).unwrap_or(0.0).min(1.0);
let mut do_idle = false; let mut do_idle = false;
@ -240,6 +241,8 @@ impl<'a> System<'a> for Sys {
bearing.xy().try_normalized().unwrap_or(Vec2::zero()) bearing.xy().try_normalized().unwrap_or(Vec2::zero())
* speed.min(0.2 + (dist - AVG_FOLLOW_DIST) / 8.0); * speed.min(0.2 + (dist - AVG_FOLLOW_DIST) / 8.0);
inputs.jump.set_state(bearing.z > 1.5); inputs.jump.set_state(bearing.z > 1.5);
inputs.swimup.set_state(bearing.z > 0.5);
inputs.swimdown.set_state(bearing.z < 0.5);
} }
} else { } else {
do_idle = true; do_idle = true;
@ -297,7 +300,47 @@ impl<'a> System<'a> for Sys {
} }
let dist_sqrd = pos.0.distance_squared(tgt_pos.0); let dist_sqrd = pos.0.distance_squared(tgt_pos.0);
if dist_sqrd < (MIN_ATTACK_DIST * scale).powf(2.0) {
let damage = stats
.get(entity)
.map(|s| s.health.current() as f32 / s.health.maximum() as f32)
.unwrap_or(0.5);
// Flee
let flees = alignment
.map(|a| !matches!(a, Alignment::Enemy | Alignment::Owned(_)))
.unwrap_or(true);
if 1.0 - agent.psyche.aggro > damage && flees {
if dist_sqrd < MAX_FLEE_DIST.powf(2.0) {
if let Some((bearing, speed)) = chaser.chase(
&*terrain,
pos.0,
vel.0,
// Away from the target (ironically)
pos.0
+ (pos.0 - tgt_pos.0)
.try_normalized()
.unwrap_or_else(Vec3::unit_y)
* 8.0,
TraversalConfig {
node_tolerance,
slow_factor,
on_ground: physics_state.on_ground,
min_tgt_dist: 1.25,
},
) {
inputs.move_dir = Vec2::from(bearing)
.try_normalized()
.unwrap_or(Vec2::zero())
* speed;
inputs.jump.set_state(bearing.z > 1.5);
inputs.swimup.set_state(bearing.z > 0.5);
inputs.swimdown.set_state(bearing.z < 0.5);
}
} else {
do_idle = true;
}
} else if dist_sqrd < (MIN_ATTACK_DIST * scale).powf(2.0) {
// Close-range attack // Close-range attack
inputs.move_dir = Vec2::from(tgt_pos.0 - pos.0) inputs.move_dir = Vec2::from(tgt_pos.0 - pos.0)
.try_normalized() .try_normalized()
@ -360,6 +403,8 @@ impl<'a> System<'a> for Sys {
.unwrap_or(Vec2::zero()) .unwrap_or(Vec2::zero())
* speed; * speed;
inputs.jump.set_state(bearing.z > 1.5); inputs.jump.set_state(bearing.z > 1.5);
inputs.swimup.set_state(bearing.z > 0.5);
inputs.swimdown.set_state(bearing.z < 0.5);
} }
if dist_sqrd < 16.0f32.powf(2.0) if dist_sqrd < 16.0f32.powf(2.0)
@ -427,7 +472,7 @@ impl<'a> System<'a> for Sys {
// Attack a target that's attacking us // Attack a target that's attacking us
if let Some(my_stats) = stats.get(entity) { if let Some(my_stats) = stats.get(entity) {
// Only if the attack was recent // Only if the attack was recent
if my_stats.health.last_change.0 < 5.0 { if my_stats.health.last_change.0 < 3.0 {
if let comp::HealthSource::Attack { by } if let comp::HealthSource::Attack { by }
| comp::HealthSource::Projectile { owner: Some(by) } = | comp::HealthSource::Projectile { owner: Some(by) } =
my_stats.health.last_change.1.cause my_stats.health.last_change.1.cause
@ -436,8 +481,12 @@ impl<'a> System<'a> for Sys {
if let Some(attacker) = uid_allocator.retrieve_entity_internal(by.id()) if let Some(attacker) = uid_allocator.retrieve_entity_internal(by.id())
{ {
if stats.get(attacker).map_or(false, |a| !a.is_dead) { if stats.get(attacker).map_or(false, |a| !a.is_dead) {
match agent.activity {
Activity::Attack { target, .. } if target == attacker => {},
_ => {
if agent.can_speak { if agent.can_speak {
let msg = "npc.speech.villager_under_attack".to_string(); let msg =
"npc.speech.villager_under_attack".to_string();
event_bus.emit_now(ServerEvent::Chat( event_bus.emit_now(ServerEvent::Chat(
UnresolvedChatMsg::npc(*uid, msg), UnresolvedChatMsg::npc(*uid, msg),
)); ));
@ -450,6 +499,8 @@ impl<'a> System<'a> for Sys {
been_close: false, been_close: false,
powerup: 0.0, powerup: 0.0,
}; };
},
}
} }
} }
} }

View File

@ -12,6 +12,7 @@ use crate::{
use specs::{ use specs::{
saveload::MarkerAllocator, Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage, saveload::MarkerAllocator, Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage,
}; };
use std::ops::Range;
use vek::*; use vek::*;
pub const GRAVITY: f32 = 9.81 * 5.0; pub const GRAVITY: f32 = 9.81 * 5.0;
@ -93,7 +94,7 @@ impl<'a> System<'a> for Sys {
let mut event_emitter = event_bus.emitter(); let mut event_emitter = event_bus.emitter();
// Apply movement inputs // Apply movement inputs
for (entity, scale, sticky, collider, mut pos, mut vel, _ori, _) in ( for (entity, _scale, sticky, collider, mut pos, mut vel, _ori, _) in (
&entities, &entities,
scales.maybe(), scales.maybe(),
stickies.maybe(), stickies.maybe(),
@ -112,7 +113,8 @@ impl<'a> System<'a> for Sys {
continue; continue;
} }
let scale = scale.map(|s| s.0).unwrap_or(1.0); // TODO: Use this
//let scale = scale.map(|s| s.0).unwrap_or(1.0);
let old_vel = *vel; let old_vel = *vel;
// Integrate forces // Integrate forces
@ -123,7 +125,7 @@ impl<'a> System<'a> for Sys {
} else { } else {
0.0 0.0
}) })
.max(if physics_state.in_fluid { .max(if physics_state.in_fluid.is_some() {
FRIC_FLUID FRIC_FLUID
} else { } else {
0.0 0.0
@ -133,7 +135,11 @@ impl<'a> System<'a> for Sys {
.is_some(); .is_some();
let downward_force = if !in_loaded_chunk { let downward_force = if !in_loaded_chunk {
0.0 // No gravity in unloaded chunks 0.0 // No gravity in unloaded chunks
} else if physics_state.in_fluid { } else if physics_state
.in_fluid
.map(|depth| depth > 0.75)
.unwrap_or(false)
{
(1.0 - BOUYANCY) * GRAVITY (1.0 - BOUYANCY) * GRAVITY
} else { } else {
GRAVITY GRAVITY
@ -157,9 +163,9 @@ impl<'a> System<'a> for Sys {
z_max, z_max,
} => { } => {
// Scale collider // Scale collider
let radius = *radius * scale; let radius = *radius; // * scale;
let z_min = *z_min * scale; let z_min = *z_min; // * scale;
let z_max = *z_max * scale; let z_max = *z_max; // * scale;
// Probe distances // Probe distances
let hdist = radius.ceil() as i32; let hdist = radius.ceil() as i32;
@ -175,18 +181,23 @@ impl<'a> System<'a> for Sys {
.flatten() .flatten()
.flatten(); .flatten();
// Function for determining whether the player at a specific position collides // Function for iterating over the blocks the player at a specific position
// with the ground // collides with
let collision_with = |pos: Vec3<f32>, fn collision_iter<'a>(
hit: &dyn Fn(&Block) -> bool, pos: Vec3<f32>,
near_iter| { terrain: &'a TerrainGrid,
for (i, j, k) in near_iter { hit: &'a dyn Fn(&Block) -> bool,
near_iter: impl Iterator<Item = (i32, i32, i32)> + 'a,
radius: f32,
z_range: Range<f32>,
) -> impl Iterator<Item = Aabb<f32>> + 'a {
near_iter.filter_map(move |(i, j, k)| {
let block_pos = pos.map(|e| e.floor() as i32) + Vec3::new(i, j, k); let block_pos = pos.map(|e| e.floor() as i32) + Vec3::new(i, j, k);
if let Some(block) = terrain.get(block_pos).ok().copied().filter(hit) { if let Some(block) = terrain.get(block_pos).ok().copied().filter(hit) {
let player_aabb = Aabb { let player_aabb = Aabb {
min: pos + Vec3::new(-radius, -radius, z_min), min: pos + Vec3::new(-radius, -radius, z_range.start),
max: pos + Vec3::new(radius, radius, z_max), max: pos + Vec3::new(radius, radius, z_range.end),
}; };
let block_aabb = Aabb { let block_aabb = Aabb {
min: block_pos.map(|e| e as f32), min: block_pos.map(|e| e as f32),
@ -195,11 +206,21 @@ impl<'a> System<'a> for Sys {
}; };
if player_aabb.collides_with_aabb(block_aabb) { if player_aabb.collides_with_aabb(block_aabb) {
return true; return Some(block_aabb);
} }
} }
}
false None
})
};
// Function for determining whether the player at a specific position collides
// with blocks with the given criteria
let collision_with = |pos: Vec3<f32>,
hit: &dyn Fn(&Block) -> bool,
near_iter| {
collision_iter(pos, &terrain, hit, near_iter, radius, z_min..z_max).count()
> 0
}; };
let was_on_ground = physics_state.on_ground; let was_on_ground = physics_state.on_ground;
@ -400,8 +421,16 @@ impl<'a> System<'a> for Sys {
} }
// Figure out if we're in water // Figure out if we're in water
physics_state.in_fluid = physics_state.in_fluid = collision_iter(
collision_with(pos.0, &|block| block.is_fluid(), near_iter.clone()); pos.0,
&terrain,
&|block| block.is_fluid(),
near_iter.clone(),
radius,
z_min..z_max,
)
.max_by_key(|block_aabb| (block_aabb.max.z * 100.0) as i32)
.map(|block_aabb| block_aabb.max.z - pos.0.z);
}, },
Collider::Point => { Collider::Point => {
let (dist, block) = terrain.ray(pos.0, pos.0 + pos_delta).ignore_error().cast(); let (dist, block) = terrain.ray(pos.0, pos.0 + pos_delta).ignore_error().cast();

View File

@ -1,9 +1,11 @@
use crate::vol::Vox; use crate::vol::Vox;
use enum_iterator::IntoEnumIterator;
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::ops::Deref; use std::{collections::HashMap, convert::TryFrom, fmt, ops::Deref};
use vek::*; use vek::*;
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize, IntoEnumIterator)]
#[repr(u8)] #[repr(u8)]
pub enum BlockKind { pub enum BlockKind {
Air, Air,
@ -86,6 +88,25 @@ pub enum BlockKind {
Stones, Stones,
Twigs, Twigs,
ShinyGem, ShinyGem,
DropGate,
DropGateBottom,
GrassSnow,
}
impl fmt::Display for BlockKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) }
}
lazy_static! {
pub static ref BLOCK_KINDS: HashMap<String, BlockKind> = BlockKind::into_enum_iter()
.map(|bk| (bk.to_string(), bk))
.collect();
}
impl<'a> TryFrom<&'a str> for BlockKind {
type Error = ();
fn try_from(s: &'a str) -> Result<Self, Self::Error> { BLOCK_KINDS.get(s).copied().ok_or(()) }
} }
impl BlockKind { impl BlockKind {
@ -173,6 +194,9 @@ impl BlockKind {
BlockKind::Stones => true, BlockKind::Stones => true,
BlockKind::Twigs => true, BlockKind::Twigs => true,
BlockKind::ShinyGem => true, BlockKind::ShinyGem => true,
BlockKind::DropGate => false,
BlockKind::DropGateBottom => false,
BlockKind::GrassSnow => true,
_ => false, _ => false,
} }
} }
@ -184,6 +208,16 @@ impl BlockKind {
} }
} }
pub fn get_glow(&self) -> Option<u8> {
// TODO: When we have proper volumetric lighting
// match self {
// BlockKind::StreetLamp | BlockKind::StreetLampTall => Some(20),
// BlockKind::Velorite | BlockKind::VeloriteFrag => Some(10),
// _ => None,
// }
None
}
pub fn is_opaque(&self) -> bool { pub fn is_opaque(&self) -> bool {
match self { match self {
BlockKind::Air => false, BlockKind::Air => false,
@ -261,6 +295,9 @@ impl BlockKind {
BlockKind::Stones => false, BlockKind::Stones => false,
BlockKind::Twigs => false, BlockKind::Twigs => false,
BlockKind::ShinyGem => false, BlockKind::ShinyGem => false,
BlockKind::DropGate => false,
BlockKind::DropGateBottom => false,
BlockKind::GrassSnow => false,
_ => true, _ => true,
} }
} }
@ -335,14 +372,18 @@ impl BlockKind {
BlockKind::Stones => false, BlockKind::Stones => false,
BlockKind::Twigs => false, BlockKind::Twigs => false,
BlockKind::ShinyGem => false, BlockKind::ShinyGem => false,
BlockKind::DropGate => true,
BlockKind::DropGateBottom => false,
BlockKind::GrassSnow => false,
_ => true, _ => true,
} }
} }
pub fn is_explodable(&self) -> bool { pub fn is_explodable(&self) -> bool {
match self { match self {
BlockKind::Leaves | BlockKind::Grass | BlockKind::Rock => true, BlockKind::Leaves | BlockKind::Grass | BlockKind::Rock | BlockKind::GrassSnow => true,
_ => false, BlockKind::Air => false,
bk => bk.is_air(), // Temporary catch for terrain sprites
} }
} }
@ -363,9 +404,9 @@ impl BlockKind {
BlockKind::Radish => 0.18, BlockKind::Radish => 0.18,
BlockKind::Door => 3.0, BlockKind::Door => 3.0,
BlockKind::Bed => 1.54, BlockKind::Bed => 1.54,
BlockKind::Bench => 1.45, BlockKind::Bench => 0.5,
BlockKind::ChairSingle => 1.36, BlockKind::ChairSingle => 0.5,
BlockKind::ChairDouble => 1.36, BlockKind::ChairDouble => 0.5,
BlockKind::CoatRack => 2.36, BlockKind::CoatRack => 2.36,
BlockKind::Crate => 0.90, BlockKind::Crate => 0.90,
BlockKind::DrawerSmall => 1.0, BlockKind::DrawerSmall => 1.0,
@ -438,6 +479,29 @@ impl Block {
| BlockKind::Window2 | BlockKind::Window2
| BlockKind::Window3 | BlockKind::Window3
| BlockKind::Window4 | BlockKind::Window4
| BlockKind::Bed
| BlockKind::Bench
| BlockKind::ChairSingle
| BlockKind::ChairDouble
| BlockKind::CoatRack
| BlockKind::Crate
| BlockKind::DrawerLarge
| BlockKind::DrawerMedium
| BlockKind::DrawerSmall
| BlockKind::DungeonWallDecor
| BlockKind::HangingBasket
| BlockKind::HangingSign
| BlockKind::WallLamp
| BlockKind::Planter
| BlockKind::Shelf
| BlockKind::TableSide
| BlockKind::TableDining
| BlockKind::TableDouble
| BlockKind::WardrobeSingle
| BlockKind::WardrobeDouble
| BlockKind::Pot
| BlockKind::DropGate
| BlockKind::DropGateBottom
| BlockKind::Door => Some(self.color[0] & 0b111), | BlockKind::Door => Some(self.color[0] & 0b111),
_ => None, _ => None,
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,14 @@
{ {
"auth-common 0.1.0 (git+https://gitlab.com/veloren/auth.git?rev=223a4097f7ebc8d451936dccb5e6517194bbf086#223a4097f7ebc8d451936dccb5e6517194bbf086)": "1b0ipil3b7kxss5wi7ij51wncbww9cdr5nydrh1dwd787agykrai", "auth-common 0.1.0 (git+https://gitlab.com/veloren/auth.git?rev=b943c85e4a38f5ec60cd18c34c73097640162bfe#b943c85e4a38f5ec60cd18c34c73097640162bfe)": "0ckpx36a5gmzirsih03ddfyp4iz23ffwb6sc75smb4srx1ivc7fy",
"authc 1.0.0 (git+https://gitlab.com/veloren/auth.git?rev=223a4097f7ebc8d451936dccb5e6517194bbf086#223a4097f7ebc8d451936dccb5e6517194bbf086)": "1b0ipil3b7kxss5wi7ij51wncbww9cdr5nydrh1dwd787agykrai", "authc 1.0.0 (git+https://gitlab.com/veloren/auth.git?rev=b943c85e4a38f5ec60cd18c34c73097640162bfe#b943c85e4a38f5ec60cd18c34c73097640162bfe)": "0ckpx36a5gmzirsih03ddfyp4iz23ffwb6sc75smb4srx1ivc7fy",
"conrod_core 0.63.0 (git+https://gitlab.com/veloren/conrod.git?branch=pre-winit-20#46b374edc9537300e5278905ebd14dff45cfd927)": "0lrh3v8dwr9x01qjry7p4wkcvd9r2cvn2865fa5nrbrc77d1qjkn", "conrod_core 0.63.0 (git+https://gitlab.com/veloren/conrod.git#1ab6eccf94b16a8977a3274b31d4dbfef9cf9a30)": "1xr12b422wb2ygfzn5mfbqyqp8zia2ciclxladzlan7hr5h226jc",
"conrod_derive 0.63.0 (git+https://gitlab.com/veloren/conrod.git?branch=pre-winit-20#46b374edc9537300e5278905ebd14dff45cfd927)": "0lrh3v8dwr9x01qjry7p4wkcvd9r2cvn2865fa5nrbrc77d1qjkn", "conrod_derive 0.63.0 (git+https://gitlab.com/veloren/conrod.git#1ab6eccf94b16a8977a3274b31d4dbfef9cf9a30)": "1xr12b422wb2ygfzn5mfbqyqp8zia2ciclxladzlan7hr5h226jc",
"conrod_winit 0.63.0 (git+https://gitlab.com/veloren/conrod.git?branch=pre-winit-20#46b374edc9537300e5278905ebd14dff45cfd927)": "0lrh3v8dwr9x01qjry7p4wkcvd9r2cvn2865fa5nrbrc77d1qjkn", "conrod_winit 0.63.0 (git+https://gitlab.com/veloren/conrod.git#1ab6eccf94b16a8977a3274b31d4dbfef9cf9a30)": "1xr12b422wb2ygfzn5mfbqyqp8zia2ciclxladzlan7hr5h226jc",
"euc 0.5.1 (git+https://github.com/zesterer/euc.git#c9a7c17a03d45fce00caeeca09afa1e1558cd183)": "0qvk0bx1arkgmdc59sip39zszdw2fwv6jcy5jinv18n5m1nrclw4", "euc 0.5.1 (git+https://github.com/zesterer/euc.git#c9a7c17a03d45fce00caeeca09afa1e1558cd183)": "0qvk0bx1arkgmdc59sip39zszdw2fwv6jcy5jinv18n5m1nrclw4",
"guillotiere 0.4.2 (git+https://github.com/Imberflur/guillotiere#42c298f5bcf0f95f1a004360d05e25ca3711e9ed)": "1knqbn777f3cgzbsaqawzclgrqf3nav6x3gjzc2hsls3acccx1ya", "guillotiere 0.4.2 (git+https://github.com/Imberflur/guillotiere#42c298f5bcf0f95f1a004360d05e25ca3711e9ed)": "1knqbn777f3cgzbsaqawzclgrqf3nav6x3gjzc2hsls3acccx1ya",
"msgbox 0.4.0 (git+https://github.com/bekker/msgbox-rs.git?rev=68fe39a#68fe39a60019b38a1569ae4e9ed796a0f0542673)": "18h6s50n7mz2ggfishhi91p2298shqhzx5xagpg9q4zb4y90c2wr", "msgbox 0.4.0 (git+https://github.com/bekker/msgbox-rs.git?rev=68fe39a#68fe39a60019b38a1569ae4e9ed796a0f0542673)": "18h6s50n7mz2ggfishhi91p2298shqhzx5xagpg9q4zb4y90c2wr",
"portpicker 0.1.0 (git+https://github.com/xMAC94x/portpicker-rs#9d6df36c53c94684080a64a7212dd6bfc3617ee4)": "00vl2k3xfihxq86kf5rsknjl8dxmrxqhwajwn0hj4gzgnbssr0rx", "portpicker 0.1.0 (git+https://github.com/xMAC94x/portpicker-rs#9d6df36c53c94684080a64a7212dd6bfc3617ee4)": "00vl2k3xfihxq86kf5rsknjl8dxmrxqhwajwn0hj4gzgnbssr0rx",
"specs 0.16.1 (git+https://github.com/amethyst/specs.git?rev=7a2e348ab2223818bad487695c66c43db88050a5#7a2e348ab2223818bad487695c66c43db88050a5)": "1z7gjiq7zirg9az3ly1y2ghi5m7s3x1bp35gw5x0cyv50fsi3pjq", "specs 0.16.1 (git+https://github.com/amethyst/specs.git?rev=7a2e348ab2223818bad487695c66c43db88050a5#7a2e348ab2223818bad487695c66c43db88050a5)": "1z7gjiq7zirg9az3ly1y2ghi5m7s3x1bp35gw5x0cyv50fsi3pjq",
"specs-idvs 0.1.0 (git+https://gitlab.com/veloren/specs-idvs.git?branch=specs-git#fcb0b2306b571f62f9f85d89e79e087454d95efd)": "00w4kc60d7mjb5gbkcxrxzarshmhf4idqm3sk8335f7s3pn9q0s5", "specs-idvs 0.1.0 (git+https://gitlab.com/veloren/specs-idvs.git?branch=specs-git#fcb0b2306b571f62f9f85d89e79e087454d95efd)": "00w4kc60d7mjb5gbkcxrxzarshmhf4idqm3sk8335f7s3pn9q0s5",
"treeculler 0.1.0 (git+https://gitlab.com/yusdacra/treeculler.git#efcf5283cf386117a7e654abdaa45ef664a08e42)": "19niwgha0jnvrp22pq0070dfimb2wkda53a3parhga3vhap2g01b" "winit 0.22.2 (git+https://github.com/Imberflur/winit.git?branch=macos-test#e98133adf2abbfc4368f6c069d0beb2b8b688b42)": "0hv9wfsn1d5fwrs37vqyazrwys8665q03m9sygk2kal1jbl3x8zj"
} }

View File

@ -5,10 +5,10 @@
"homepage": "", "homepage": "",
"owner": "kolloch", "owner": "kolloch",
"repo": "crate2nix", "repo": "crate2nix",
"rev": "5dfb0c155ca639591b82106b6455f24fc0a2f484", "rev": "ee584740abafedac0b0700c8bf5a32c1c76dd86e",
"sha256": "0nkvpk3m8wgnwd94520jnsxjq14q255s67rl885i75rm293j2yha", "sha256": "0ixbrrmriwjmwj94s8frmb7yl34mqnnyxd0imqipnvz8l98ma986",
"type": "tarball", "type": "tarball",
"url": "https://github.com/kolloch/crate2nix/archive/5dfb0c155ca639591b82106b6455f24fc0a2f484.tar.gz", "url": "https://github.com/kolloch/crate2nix/archive/ee584740abafedac0b0700c8bf5a32c1c76dd86e.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz" "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
}, },
"nixpkgsMoz": { "nixpkgsMoz": {
@ -17,10 +17,10 @@
"homepage": null, "homepage": null,
"owner": "mozilla", "owner": "mozilla",
"repo": "nixpkgs-mozilla", "repo": "nixpkgs-mozilla",
"rev": "18cd4300e9bf61c7b8b372f07af827f6ddc835bb", "rev": "efda5b357451dbb0431f983cca679ae3cd9b9829",
"sha256": "1s0d1l5y7a3kygjbibssjnj7fcc87qaa5s9k4kda0j13j9h4zwgr", "sha256": "11wqrg86g3qva67vnk81ynvqyfj0zxk83cbrf0p9hsvxiwxs8469",
"type": "tarball", "type": "tarball",
"url": "https://github.com/mozilla/nixpkgs-mozilla/archive/18cd4300e9bf61c7b8b372f07af827f6ddc835bb.tar.gz", "url": "https://github.com/mozilla/nixpkgs-mozilla/archive/efda5b357451dbb0431f983cca679ae3cd9b9829.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz" "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
} }
} }

View File

@ -1 +1 @@
economy.csv

View File

@ -13,13 +13,14 @@ use common::{
npc::{self, get_npc_name}, npc::{self, get_npc_name},
state::TimeOfDay, state::TimeOfDay,
sync::{Uid, WorldSyncExt}, sync::{Uid, WorldSyncExt},
terrain::TerrainChunkSize, terrain::{Block, BlockKind, TerrainChunkSize},
util::Dir, util::Dir,
vol::RectVolSize, vol::RectVolSize,
LoadoutBuilder, LoadoutBuilder,
}; };
use rand::Rng; use rand::Rng;
use specs::{Builder, Entity as EcsEntity, Join, WorldExt}; use specs::{Builder, Entity as EcsEntity, Join, WorldExt};
use std::convert::TryFrom;
use vek::*; use vek::*;
use world::util::Sampler; use world::util::Sampler;
@ -83,6 +84,7 @@ fn get_handler(cmd: &ChatCommand) -> CommandHandler {
ChatCommand::KillNpcs => handle_kill_npcs, ChatCommand::KillNpcs => handle_kill_npcs,
ChatCommand::Lantern => handle_lantern, ChatCommand::Lantern => handle_lantern,
ChatCommand::Light => handle_light, ChatCommand::Light => handle_light,
ChatCommand::MakeBlock => handle_make_block,
ChatCommand::Motd => handle_motd, ChatCommand::Motd => handle_motd,
ChatCommand::Object => handle_object, ChatCommand::Object => handle_object,
ChatCommand::Players => handle_players, ChatCommand::Players => handle_players,
@ -179,6 +181,39 @@ fn handle_give_item(
} }
} }
fn handle_make_block(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: String,
action: &ChatCommand,
) {
if let Some(block_name) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
if let Ok(bk) = BlockKind::try_from(block_name.as_str()) {
match server.state.read_component_cloned::<comp::Pos>(target) {
Some(pos) => server.state.set_block(
pos.0.map(|e| e.floor() as i32),
Block::new(bk, Rgb::broadcast(255)),
),
None => server.notify_client(
client,
ChatType::CommandError.server_msg(String::from("You have no position.")),
),
}
} else {
server.notify_client(
client,
ChatType::CommandError.server_msg(format!("Invalid block kind: {}", block_name)),
);
}
} else {
server.notify_client(
client,
ChatType::CommandError.server_msg(action.help_string()),
);
}
}
fn handle_motd( fn handle_motd(
server: &mut Server, server: &mut Server,
client: EcsEntity, client: EcsEntity,
@ -227,7 +262,7 @@ fn handle_jump(
action: &ChatCommand, action: &ChatCommand,
) { ) {
if let Ok((x, y, z)) = scan_fmt!(&args, &action.arg_fmt(), f32, f32, f32) { if let Ok((x, y, z)) = scan_fmt!(&args, &action.arg_fmt(), f32, f32, f32) {
match server.state.read_component_copied::<comp::Pos>(target) { match server.state.read_component_cloned::<comp::Pos>(target) {
Some(current_pos) => { Some(current_pos) => {
server server
.state .state
@ -252,7 +287,7 @@ fn handle_goto(
if let Ok((x, y, z)) = scan_fmt!(&args, &action.arg_fmt(), f32, f32, f32) { if let Ok((x, y, z)) = scan_fmt!(&args, &action.arg_fmt(), f32, f32, f32) {
if server if server
.state .state
.read_component_copied::<comp::Pos>(target) .read_component_cloned::<comp::Pos>(target)
.is_some() .is_some()
{ {
server server
@ -463,9 +498,9 @@ fn handle_tp(
); );
return; return;
}; };
if let Some(_pos) = server.state.read_component_copied::<comp::Pos>(target) { if let Some(_pos) = server.state.read_component_cloned::<comp::Pos>(target) {
if let Some(player) = opt_player { if let Some(player) = opt_player {
if let Some(pos) = server.state.read_component_copied::<comp::Pos>(player) { if let Some(pos) = server.state.read_component_cloned::<comp::Pos>(player) {
server.state.write_component(target, pos); server.state.write_component(target, pos);
server.state.write_component(target, comp::ForceUpdate); server.state.write_component(target, comp::ForceUpdate);
} else { } else {
@ -510,7 +545,7 @@ fn handle_spawn(
(Some(opt_align), Some(npc::NpcBody(id, mut body)), opt_amount, opt_ai) => { (Some(opt_align), Some(npc::NpcBody(id, mut body)), opt_amount, opt_ai) => {
let uid = server let uid = server
.state .state
.read_component_copied(target) .read_component_cloned(target)
.expect("Expected player to have a UID"); .expect("Expected player to have a UID");
if let Some(alignment) = parse_alignment(uid, &opt_align) { if let Some(alignment) = parse_alignment(uid, &opt_align) {
let amount = opt_amount let amount = opt_amount
@ -521,7 +556,7 @@ fn handle_spawn(
let ai = opt_ai.unwrap_or_else(|| "true".to_string()); let ai = opt_ai.unwrap_or_else(|| "true".to_string());
match server.state.read_component_copied::<comp::Pos>(target) { match server.state.read_component_cloned::<comp::Pos>(target) {
Some(pos) => { Some(pos) => {
let agent = let agent =
if let comp::Alignment::Owned(_) | comp::Alignment::Npc = alignment { if let comp::Alignment::Owned(_) | comp::Alignment::Npc = alignment {
@ -631,7 +666,7 @@ fn handle_spawn_training_dummy(
_args: String, _args: String,
_action: &ChatCommand, _action: &ChatCommand,
) { ) {
match server.state.read_component_copied::<comp::Pos>(target) { match server.state.read_component_cloned::<comp::Pos>(target) {
Some(pos) => { Some(pos) => {
let vel = Vec3::new( let vel = Vec3::new(
rand::thread_rng().gen_range(-2.0, 3.0), rand::thread_rng().gen_range(-2.0, 3.0),
@ -672,7 +707,7 @@ fn handle_spawn_campfire(
_args: String, _args: String,
_action: &ChatCommand, _action: &ChatCommand,
) { ) {
match server.state.read_component_copied::<comp::Pos>(target) { match server.state.read_component_cloned::<comp::Pos>(target) {
Some(pos) => { Some(pos) => {
server server
.state .state
@ -1031,7 +1066,7 @@ fn handle_explosion(
let ecs = server.state.ecs(); let ecs = server.state.ecs();
match server.state.read_component_copied::<comp::Pos>(target) { match server.state.read_component_cloned::<comp::Pos>(target) {
Some(pos) => { Some(pos) => {
ecs.read_resource::<EventBus<ServerEvent>>() ecs.read_resource::<EventBus<ServerEvent>>()
.emit_now(ServerEvent::Explosion { .emit_now(ServerEvent::Explosion {
@ -1056,7 +1091,7 @@ fn handle_waypoint(
_args: String, _args: String,
_action: &ChatCommand, _action: &ChatCommand,
) { ) {
match server.state.read_component_copied::<comp::Pos>(target) { match server.state.read_component_cloned::<comp::Pos>(target) {
Some(pos) => { Some(pos) => {
let time = server.state.ecs().read_resource(); let time = server.state.ecs().read_resource();
let _ = server let _ = server
@ -1092,7 +1127,7 @@ fn handle_adminify(
Some(player) => { Some(player) => {
let is_admin = if server let is_admin = if server
.state .state
.read_component_copied::<comp::Admin>(player) .read_component_cloned::<comp::Admin>(player)
.is_some() .is_some()
{ {
ecs.write_storage::<comp::Admin>().remove(player); ecs.write_storage::<comp::Admin>().remove(player);
@ -1438,7 +1473,7 @@ fn handle_debug_column(
let spawn_rate = sim.get_interpolated(wpos, |chunk| chunk.spawn_rate)?; let spawn_rate = sim.get_interpolated(wpos, |chunk| chunk.spawn_rate)?;
let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32); let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32);
let chunk = sim.get(chunk_pos)?; let chunk = sim.get(chunk_pos)?;
let col = sampler.get(wpos)?; let col = sampler.get((wpos, server.world.index()))?;
let downhill = chunk.downhill; let downhill = chunk.downhill;
let river = &chunk.river; let river = &chunk.river;
let flux = chunk.flux; let flux = chunk.flux;
@ -1636,7 +1671,7 @@ fn handle_remove_lights(
action: &ChatCommand, action: &ChatCommand,
) { ) {
let opt_radius = scan_fmt_some!(&args, &action.arg_fmt(), f32); let opt_radius = scan_fmt_some!(&args, &action.arg_fmt(), f32);
let opt_player_pos = server.state.read_component_copied::<comp::Pos>(target); let opt_player_pos = server.state.read_component_cloned::<comp::Pos>(target);
let mut to_delete = vec![]; let mut to_delete = vec![];
match opt_player_pos { match opt_player_pos {

View File

@ -2,9 +2,10 @@ use crate::{client::Client, Server, SpawnPoint, StateExt};
use common::{ use common::{
assets, assets,
comp::{ comp::{
self, item::lottery::Lottery, object, Alignment, Body, Damage, DamageSource, Group, self, object, Alignment, Body, Damage, DamageSource, Group, HealthChange, HealthSource,
HealthChange, HealthSource, Player, Pos, Stats, Player, Pos, Stats,
}, },
lottery::Lottery,
msg::{PlayerListUpdate, ServerMsg}, msg::{PlayerListUpdate, ServerMsg},
outcome::Outcome, outcome::Outcome,
state::BlockChange, state::BlockChange,
@ -183,7 +184,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
item_drops.remove(entity); item_drops.remove(entity);
item_drop.0 item_drop.0
} else { } else {
let chosen = assets::load_expect::<Lottery<_>>("common.loot_table"); let chosen = assets::load_expect::<Lottery<String>>("common.loot_table");
let chosen = chosen.choose(); let chosen = chosen.choose();
assets::load_expect_cloned(chosen) assets::load_expect_cloned(chosen)
@ -251,7 +252,7 @@ pub fn handle_respawn(server: &Server, entity: EcsEntity) {
.is_some() .is_some()
{ {
let respawn_point = state let respawn_point = state
.read_component_copied::<comp::Waypoint>(entity) .read_component_cloned::<comp::Waypoint>(entity)
.map(|wp| wp.get_pos()) .map(|wp| wp.get_pos())
.unwrap_or(state.ecs().read_resource::<SpawnPoint>().0); .unwrap_or(state.ecs().read_resource::<SpawnPoint>().0);

View File

@ -168,10 +168,10 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
thrown_items.push(( thrown_items.push((
*pos, *pos,
state state
.read_component_copied::<comp::Vel>(entity) .read_component_cloned::<comp::Vel>(entity)
.unwrap_or_default(), .unwrap_or_default(),
state state
.read_component_copied::<comp::Ori>(entity) .read_component_cloned::<comp::Ori>(entity)
.unwrap_or_default(), .unwrap_or_default(),
*kind, *kind,
)); ));
@ -186,7 +186,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
state.read_storage::<comp::Pos>().get(entity) state.read_storage::<comp::Pos>().get(entity)
{ {
let uid = state let uid = state
.read_component_copied(entity) .read_component_cloned(entity)
.expect("Expected player to have a UID"); .expect("Expected player to have a UID");
if ( if (
&state.read_storage::<comp::Alignment>(), &state.read_storage::<comp::Alignment>(),
@ -342,7 +342,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
dropped_items.push(( dropped_items.push((
*pos, *pos,
state state
.read_component_copied::<comp::Ori>(entity) .read_component_cloned::<comp::Ori>(entity)
.unwrap_or_default(), .unwrap_or_default(),
item, item,
)); ));
@ -374,10 +374,10 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
for _ in 0..amount { for _ in 0..amount {
dropped_items.push(( dropped_items.push((
state state
.read_component_copied::<comp::Pos>(entity) .read_component_cloned::<comp::Pos>(entity)
.unwrap_or_default(), .unwrap_or_default(),
state state
.read_component_copied::<comp::Ori>(entity) .read_component_cloned::<comp::Ori>(entity)
.unwrap_or_default(), .unwrap_or_default(),
item.clone(), item.clone(),
)); ));
@ -419,7 +419,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
}, },
}; };
let uid = state.read_component_copied::<Uid>(entity); let uid = state.read_component_cloned::<Uid>(entity);
let mut new_entity = state let mut new_entity = state
.create_object(Default::default(), match kind { .create_object(Default::default(), match kind {

View File

@ -20,7 +20,7 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) {
// Note: If other `ServerEvent`s are referring to this entity they will be // Note: If other `ServerEvent`s are referring to this entity they will be
// disrupted // disrupted
let maybe_client = state.ecs().write_storage::<Client>().remove(entity); let maybe_client = state.ecs().write_storage::<Client>().remove(entity);
let maybe_uid = state.read_component_copied::<Uid>(entity); let maybe_uid = state.read_component_cloned::<Uid>(entity);
let maybe_player = state.ecs().write_storage::<comp::Player>().remove(entity); let maybe_player = state.ecs().write_storage::<comp::Player>().remove(entity);
let maybe_group = state let maybe_group = state
.ecs() .ecs()

View File

@ -184,7 +184,7 @@ impl Server {
..WorldOpts::default() ..WorldOpts::default()
}); });
#[cfg(feature = "worldgen")] #[cfg(feature = "worldgen")]
let map = world.sim().get_map(); let map = world.get_map_data();
#[cfg(not(feature = "worldgen"))] #[cfg(not(feature = "worldgen"))]
let world = World::generate(settings.world_seed); let world = World::generate(settings.world_seed);
@ -224,11 +224,11 @@ impl Server {
// get a z cache for the collumn in which we want to spawn // get a z cache for the collumn in which we want to spawn
let mut block_sampler = world.sample_blocks(); let mut block_sampler = world.sample_blocks();
let z_cache = block_sampler let z_cache = block_sampler
.get_z_cache(spawn_location) .get_z_cache(spawn_location, world.index())
.expect(&format!("no z_cache found for chunk: {}", spawn_chunk)); .expect(&format!("no z_cache found for chunk: {}", spawn_chunk));
// get the minimum and maximum z values at which there could be soild blocks // get the minimum and maximum z values at which there could be soild blocks
let (min_z, _, max_z) = z_cache.get_z_limits(&mut block_sampler); let (min_z, _, max_z) = z_cache.get_z_limits(&mut block_sampler, world.index());
// round range outwards, so no potential air block is missed // round range outwards, so no potential air block is missed
let min_z = min_z.floor() as i32; let min_z = min_z.floor() as i32;
let max_z = max_z.ceil() as i32; let max_z = max_z.ceil() as i32;
@ -244,6 +244,7 @@ impl Server {
Vec3::new(spawn_location.x, spawn_location.y, *z), Vec3::new(spawn_location.x, spawn_location.y, *z),
Some(&z_cache), Some(&z_cache),
false, false,
world.index(),
) )
.map(|b| b.is_air()) .map(|b| b.is_air())
.unwrap_or(false) .unwrap_or(false)

View File

@ -173,7 +173,7 @@ impl StateExt for State {
self.write_component(entity, comp::CharacterState::default()); self.write_component(entity, comp::CharacterState::default());
self.write_component( self.write_component(
entity, entity,
comp::Alignment::Owned(self.read_component_copied(entity).unwrap()), comp::Alignment::Owned(self.read_component_cloned(entity).unwrap()),
); );
// Set the character id for the player // Set the character id for the player
@ -213,7 +213,7 @@ impl StateExt for State {
// Notify clients of a player list update // Notify clients of a player list update
let client_uid = self let client_uid = self
.read_component_copied::<Uid>(entity) .read_component_cloned::<Uid>(entity)
.map(|u| u) .map(|u| u)
.expect("Client doesn't have a Uid!!!"); .expect("Client doesn't have a Uid!!!");

View File

@ -319,12 +319,12 @@ impl<'a> System<'a> for Sys {
pos: Pos(entity.pos), pos: Pos(entity.pos),
stats, stats,
loadout, loadout,
body,
agent: if entity.has_agency { agent: if entity.has_agency {
Some(comp::Agent::new(entity.pos, can_speak)) Some(comp::Agent::new(entity.pos, can_speak, &body))
} else { } else {
None None
}, },
body,
alignment, alignment,
scale: comp::Scale(scale), scale: comp::Scale(scale),
drop_item: entity.loot_drop, drop_item: entity.loot_drop,

View File

@ -14,7 +14,6 @@ impl Animation for BetaAnimation {
const UPDATE_FN: &'static [u8] = b"character_beta\0"; const UPDATE_FN: &'static [u8] = b"character_beta\0";
#[cfg_attr(feature = "be-dyn-lib", export_name = "character_beta")] #[cfg_attr(feature = "be-dyn-lib", export_name = "character_beta")]
#[allow(clippy::unnested_or_patterns)] // TODO: Pending review in #587
fn update_skeleton_inner( fn update_skeleton_inner(
skeleton: &Self::Skeleton, skeleton: &Self::Skeleton,
(active_tool_kind, second_tool_kind, _velocity, _global_time): Self::Dependency, (active_tool_kind, second_tool_kind, _velocity, _global_time): Self::Dependency,
@ -44,15 +43,12 @@ impl Animation for BetaAnimation {
.sqrt()) .sqrt())
* ((anim_time as f32 * lab as f32 * 14.0).sin()); * ((anim_time as f32 * lab as f32 * 14.0).sin());
match active_tool_kind { if let Some(
//TODO: Inventory ToolKind::Axe(_) | ToolKind::Hammer(_) | ToolKind::Sword(_) | ToolKind::Dagger(_),
Some(ToolKind::Axe(_)) ) = active_tool_kind
| Some(ToolKind::Hammer(_)) {
| Some(ToolKind::Sword(_))
| Some(ToolKind::Dagger(_)) => {
//INTENTION: SWORD //INTENTION: SWORD
next.head.position = next.head.position = Vec3::new(0.0, -2.0 + skeleton_attr.head.0, skeleton_attr.head.1);
Vec3::new(0.0, -2.0 + skeleton_attr.head.0, skeleton_attr.head.1);
next.head.orientation = Quaternion::rotation_z(slow * -0.18) next.head.orientation = Quaternion::rotation_z(slow * -0.18)
* Quaternion::rotation_x(-0.1 + slow * -0.28) * Quaternion::rotation_x(-0.1 + slow * -0.28)
* Quaternion::rotation_y(0.2 + slow * 0.18); * Quaternion::rotation_y(0.2 + slow * 0.18);
@ -92,17 +88,15 @@ impl Animation for BetaAnimation {
footquick * -9.5, footquick * -9.5,
skeleton_attr.foot.2, skeleton_attr.foot.2,
); );
next.l_foot.orientation = Quaternion::rotation_x(footquick * 0.3) next.l_foot.orientation =
* Quaternion::rotation_y(footquick * -0.6); Quaternion::rotation_x(footquick * 0.3) * Quaternion::rotation_y(footquick * -0.6);
next.r_foot.position = next.r_foot.position =
Vec3::new(skeleton_attr.foot.0, footquick * 9.5, skeleton_attr.foot.2); Vec3::new(skeleton_attr.foot.0, footquick * 9.5, skeleton_attr.foot.2);
next.r_foot.orientation = Quaternion::rotation_x(footquick * -0.3) next.r_foot.orientation =
* Quaternion::rotation_y(footquick * 0.2); Quaternion::rotation_x(footquick * -0.3) * Quaternion::rotation_y(footquick * 0.2);
next.torso.position = Vec3::new(0.0, 0.0, 0.1) * skeleton_attr.scaler; next.torso.position = Vec3::new(0.0, 0.0, 0.1) * skeleton_attr.scaler;
next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler; next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler;
},
_ => {},
} }
next.l_shoulder.position = Vec3::new( next.l_shoulder.position = Vec3::new(

View File

@ -18,7 +18,6 @@ impl Animation for SpinAnimation {
const UPDATE_FN: &'static [u8] = b"character_spin\0"; const UPDATE_FN: &'static [u8] = b"character_spin\0";
#[cfg_attr(feature = "be-dyn-lib", export_name = "character_spin")] #[cfg_attr(feature = "be-dyn-lib", export_name = "character_spin")]
#[allow(clippy::unnested_or_patterns)] // TODO: Pending review in #587
fn update_skeleton_inner( fn update_skeleton_inner(
skeleton: &Self::Skeleton, skeleton: &Self::Skeleton,
(active_tool_kind, second_tool_kind, _global_time): Self::Dependency, (active_tool_kind, second_tool_kind, _global_time): Self::Dependency,
@ -41,12 +40,10 @@ impl Animation for SpinAnimation {
let spin = (anim_time as f32 * 2.8 * lab as f32).sin(); let spin = (anim_time as f32 * 2.8 * lab as f32).sin();
let spinhalf = (anim_time as f32 * 1.4 * lab as f32).sin(); let spinhalf = (anim_time as f32 * 1.4 * lab as f32).sin();
match active_tool_kind { if let Some(
//TODO: Inventory ToolKind::Axe(_) | ToolKind::Hammer(_) | ToolKind::Sword(_) | ToolKind::Dagger(_),
Some(ToolKind::Axe(_)) ) = active_tool_kind
| Some(ToolKind::Hammer(_)) {
| Some(ToolKind::Sword(_))
| Some(ToolKind::Dagger(_)) => {
//INTENTION: SWORD //INTENTION: SWORD
next.l_hand.position = Vec3::new(-0.75, -1.0, -2.5); next.l_hand.position = Vec3::new(-0.75, -1.0, -2.5);
next.l_hand.orientation = Quaternion::rotation_x(1.27); next.l_hand.orientation = Quaternion::rotation_x(1.27);
@ -91,10 +88,8 @@ impl Animation for SpinAnimation {
* Quaternion::rotation_x(0.0) * Quaternion::rotation_x(0.0)
* Quaternion::rotation_y(0.0); * Quaternion::rotation_y(0.0);
next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler; next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler;
},
_ => {},
} }
next.l_foot.position = Vec3::new(-skeleton_attr.foot.0, foot * 1.0, skeleton_attr.foot.2); next.l_foot.position = Vec3::new(-skeleton_attr.foot.0, foot * 1.0, skeleton_attr.foot.2);
next.l_foot.orientation = Quaternion::rotation_x(foot * -1.2); next.l_foot.orientation = Quaternion::rotation_x(foot * -1.2);
next.l_foot.scale = Vec3::one(); next.l_foot.scale = Vec3::one();

View File

@ -1,4 +1,5 @@
#![feature(const_generics)] #![feature(const_generics)]
#![feature(or_patterns)]
#![allow(incomplete_features)] #![allow(incomplete_features)]
#[cfg(all(feature = "be-dyn-lib", feature = "use-dyn-lib"))] #[cfg(all(feature = "be-dyn-lib", feature = "use-dyn-lib"))]
compile_error!("Can't use both \"be-dyn-lib\" and \"use-dyn-lib\" features at once"); compile_error!("Can't use both \"be-dyn-lib\" and \"use-dyn-lib\" features at once");

View File

@ -85,7 +85,7 @@ fn maps_idle() {
on_ceiling: false, on_ceiling: false,
on_wall: None, on_wall: None,
touch_entity: None, touch_entity: None,
in_fluid: false, in_fluid: None,
}, },
&PreviousEntityState { &PreviousEntityState {
event: SfxEvent::Idle, event: SfxEvent::Idle,
@ -107,7 +107,7 @@ fn maps_run_with_sufficient_velocity() {
on_ceiling: false, on_ceiling: false,
on_wall: None, on_wall: None,
touch_entity: None, touch_entity: None,
in_fluid: false, in_fluid: None,
}, },
&PreviousEntityState { &PreviousEntityState {
event: SfxEvent::Idle, event: SfxEvent::Idle,
@ -129,7 +129,7 @@ fn does_not_map_run_with_insufficient_velocity() {
on_ceiling: false, on_ceiling: false,
on_wall: None, on_wall: None,
touch_entity: None, touch_entity: None,
in_fluid: false, in_fluid: None,
}, },
&PreviousEntityState { &PreviousEntityState {
event: SfxEvent::Idle, event: SfxEvent::Idle,
@ -151,7 +151,7 @@ fn does_not_map_run_with_sufficient_velocity_but_not_on_ground() {
on_ceiling: false, on_ceiling: false,
on_wall: None, on_wall: None,
touch_entity: None, touch_entity: None,
in_fluid: false, in_fluid: None,
}, },
&PreviousEntityState { &PreviousEntityState {
event: SfxEvent::Idle, event: SfxEvent::Idle,
@ -176,7 +176,7 @@ fn maps_roll() {
on_ceiling: false, on_ceiling: false,
on_wall: None, on_wall: None,
touch_entity: None, touch_entity: None,
in_fluid: false, in_fluid: None,
}, },
&PreviousEntityState { &PreviousEntityState {
event: SfxEvent::Run, event: SfxEvent::Run,
@ -198,7 +198,7 @@ fn maps_land_on_ground_to_run() {
on_ceiling: false, on_ceiling: false,
on_wall: None, on_wall: None,
touch_entity: None, touch_entity: None,
in_fluid: false, in_fluid: None,
}, },
&PreviousEntityState { &PreviousEntityState {
event: SfxEvent::Idle, event: SfxEvent::Idle,
@ -220,7 +220,7 @@ fn maps_glider_open() {
on_ceiling: false, on_ceiling: false,
on_wall: None, on_wall: None,
touch_entity: None, touch_entity: None,
in_fluid: false, in_fluid: None,
}, },
&PreviousEntityState { &PreviousEntityState {
event: SfxEvent::Jump, event: SfxEvent::Jump,
@ -242,7 +242,7 @@ fn maps_glide() {
on_ceiling: false, on_ceiling: false,
on_wall: None, on_wall: None,
touch_entity: None, touch_entity: None,
in_fluid: false, in_fluid: None,
}, },
&PreviousEntityState { &PreviousEntityState {
event: SfxEvent::Glide, event: SfxEvent::Glide,
@ -264,7 +264,7 @@ fn maps_glider_close_when_closing_mid_flight() {
on_ceiling: false, on_ceiling: false,
on_wall: None, on_wall: None,
touch_entity: None, touch_entity: None,
in_fluid: false, in_fluid: None,
}, },
&PreviousEntityState { &PreviousEntityState {
event: SfxEvent::Glide, event: SfxEvent::Glide,
@ -287,7 +287,7 @@ fn maps_glider_close_when_landing() {
on_ceiling: false, on_ceiling: false,
on_wall: None, on_wall: None,
touch_entity: None, touch_entity: None,
in_fluid: false, in_fluid: None,
}, },
&PreviousEntityState { &PreviousEntityState {
event: SfxEvent::Glide, event: SfxEvent::Glide,
@ -308,7 +308,7 @@ fn maps_quadrupeds_running() {
on_ceiling: false, on_ceiling: false,
on_wall: None, on_wall: None,
touch_entity: None, touch_entity: None,
in_fluid: false, in_fluid: None,
}, },
Vec3::new(0.5, 0.8, 0.0), Vec3::new(0.5, 0.8, 0.0),
); );

View File

@ -505,7 +505,7 @@ impl<'a> Widget for Social<'a> {
}) })
.or_else(|| { .or_else(|| {
self.selected_entity self.selected_entity
.and_then(|s| self.client.state().read_component_copied(s.0)) .and_then(|s| self.client.state().read_component_cloned(s.0))
}) })
.filter(|selected| { .filter(|selected| {
// Prevent inviting entities already in the same group // Prevent inviting entities already in the same group

View File

@ -7,7 +7,7 @@ use crate::{
}; };
use common::{ use common::{
terrain::{Block, BlockKind}, terrain::{Block, BlockKind},
vol::{ReadVol, RectRasterableVol, Vox}, vol::{DefaultVolIterator, ReadVol, RectRasterableVol, Vox},
volumes::vol_grid_2d::{CachedVolGrid2d, VolGrid2d}, volumes::vol_grid_2d::{CachedVolGrid2d, VolGrid2d},
}; };
use std::{collections::VecDeque, fmt::Debug}; use std::{collections::VecDeque, fmt::Debug};
@ -39,13 +39,16 @@ impl Blendable for BlockKind {
} }
} }
const SUNLIGHT: u8 = 24;
const MAX_LIGHT_DIST: i32 = SUNLIGHT as i32;
fn calc_light<V: RectRasterableVol<Vox = Block> + ReadVol + Debug>( fn calc_light<V: RectRasterableVol<Vox = Block> + ReadVol + Debug>(
bounds: Aabb<i32>, bounds: Aabb<i32>,
vol: &VolGrid2d<V>, vol: &VolGrid2d<V>,
lit_blocks: impl Iterator<Item = (Vec3<i32>, u8)>,
) -> impl FnMut(Vec3<i32>) -> f32 + '_ { ) -> impl FnMut(Vec3<i32>) -> f32 + '_ {
const UNKNOWN: u8 = 255; const UNKNOWN: u8 = 255;
const OPAQUE: u8 = 254; const OPAQUE: u8 = 254;
const SUNLIGHT: u8 = 24;
let outer = Aabb { let outer = Aabb {
min: bounds.min - Vec3::new(SUNLIGHT as i32 - 1, SUNLIGHT as i32 - 1, 1), min: bounds.min - Vec3::new(SUNLIGHT as i32 - 1, SUNLIGHT as i32 - 1, 1),
@ -60,7 +63,13 @@ fn calc_light<V: RectRasterableVol<Vox = Block> + ReadVol + Debug>(
move |x, y, z| (z * h * w + x * h + y) as usize move |x, y, z| (z * h * w + x * h + y) as usize
}; };
// Light propagation queue // Light propagation queue
let mut prop_que = VecDeque::new(); let mut prop_que = lit_blocks
.map(|(pos, light)| {
let rpos = pos - outer.min;
light_map[lm_idx(rpos.x, rpos.y, rpos.z)] = light;
(rpos.x as u8, rpos.y as u8, rpos.z as u16)
})
.collect::<VecDeque<_>>();
// Start sun rays // Start sun rays
for x in 0..outer.size().w { for x in 0..outer.size().w {
for y in 0..outer.size().h { for y in 0..outer.size().h {
@ -231,7 +240,13 @@ impl<'a, V: RectRasterableVol<Vox = Block> + ReadVol + Debug>
self, self,
(range, max_texture_size): Self::Supplement, (range, max_texture_size): Self::Supplement,
) -> MeshGen<TerrainPipeline, FluidPipeline, Self> { ) -> MeshGen<TerrainPipeline, FluidPipeline, Self> {
let mut light = calc_light(range, self); // Find blocks that should glow
let lit_blocks =
DefaultVolIterator::new(self, range.min - MAX_LIGHT_DIST, range.max + MAX_LIGHT_DIST)
.filter_map(|(pos, block)| block.get_glow().map(|glow| (pos, glow)));
// Calculate chunk lighting
let mut light = calc_light(range, self, lit_blocks);
let mut lowest_opaque = range.size().d; let mut lowest_opaque = range.size().d;
let mut highest_opaque = 0; let mut highest_opaque = 0;

View File

@ -687,7 +687,7 @@ impl FigureMgr {
let target_base = match ( let target_base = match (
physics.on_ground, physics.on_ground,
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_fluid, // In water physics.in_fluid.is_some(), // In water
) { ) {
// Standing // Standing
(true, false, false) => anim::character::StandAnimation::update_skeleton( (true, false, false) => anim::character::StandAnimation::update_skeleton(
@ -929,7 +929,7 @@ impl FigureMgr {
) )
}, },
CharacterState::Wielding { .. } => { CharacterState::Wielding { .. } => {
if physics.in_fluid { if physics.in_fluid.is_some() {
anim::character::SwimWieldAnimation::update_skeleton( anim::character::SwimWieldAnimation::update_skeleton(
&target_base, &target_base,
(active_tool_kind, second_tool_kind, vel.0.magnitude(), time), (active_tool_kind, second_tool_kind, vel.0.magnitude(), time),
@ -1058,7 +1058,7 @@ impl FigureMgr {
let target_base = match ( let target_base = match (
physics.on_ground, physics.on_ground,
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_fluid, // In water physics.in_fluid.is_some(), // In water
) { ) {
// Standing // Standing
(true, false, false) => { (true, false, false) => {
@ -1159,7 +1159,7 @@ impl FigureMgr {
let target_base = match ( let target_base = match (
physics.on_ground, physics.on_ground,
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_fluid, // In water physics.in_fluid.is_some(), // In water
) { ) {
// Standing // Standing
(true, false, false) => { (true, false, false) => {
@ -1260,7 +1260,7 @@ impl FigureMgr {
let target_base = match ( let target_base = match (
physics.on_ground, physics.on_ground,
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_fluid, // In water physics.in_fluid.is_some(), // In water
) { ) {
// Standing // Standing
(true, false, false) => { (true, false, false) => {
@ -1358,7 +1358,7 @@ impl FigureMgr {
let target_base = match ( let target_base = match (
physics.on_ground, physics.on_ground,
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_fluid, // In water physics.in_fluid.is_some(), // In water
) { ) {
// Standing // Standing
(true, false, false) => anim::bird_medium::IdleAnimation::update_skeleton( (true, false, false) => anim::bird_medium::IdleAnimation::update_skeleton(
@ -1454,7 +1454,7 @@ impl FigureMgr {
let target_base = match ( let target_base = match (
physics.on_ground, physics.on_ground,
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_fluid, // In water physics.in_fluid.is_some(), // In water
) { ) {
// Standing // Standing
(true, false, false) => anim::fish_medium::IdleAnimation::update_skeleton( (true, false, false) => anim::fish_medium::IdleAnimation::update_skeleton(
@ -1530,7 +1530,7 @@ impl FigureMgr {
let target_base = match ( let target_base = match (
physics.on_ground, physics.on_ground,
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_fluid, // In water physics.in_fluid.is_some(), // In water
) { ) {
// Standing // Standing
(true, false, false) => anim::dragon::IdleAnimation::update_skeleton( (true, false, false) => anim::dragon::IdleAnimation::update_skeleton(
@ -1605,7 +1605,7 @@ impl FigureMgr {
let target_base = match ( let target_base = match (
physics.on_ground, physics.on_ground,
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_fluid, // In water physics.in_fluid.is_some(), // In water
) { ) {
// Standing // Standing
(true, false, false) => anim::critter::IdleAnimation::update_skeleton( (true, false, false) => anim::critter::IdleAnimation::update_skeleton(
@ -1684,7 +1684,7 @@ impl FigureMgr {
let target_base = match ( let target_base = match (
physics.on_ground, physics.on_ground,
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_fluid, // In water physics.in_fluid.is_some(), // In water
) { ) {
// Standing // Standing
(true, false, false) => anim::bird_small::IdleAnimation::update_skeleton( (true, false, false) => anim::bird_small::IdleAnimation::update_skeleton(
@ -1763,7 +1763,7 @@ impl FigureMgr {
let target_base = match ( let target_base = match (
physics.on_ground, physics.on_ground,
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_fluid, // In water physics.in_fluid.is_some(), // In water
) { ) {
// Standing // Standing
(true, false, false) => anim::fish_small::IdleAnimation::update_skeleton( (true, false, false) => anim::fish_small::IdleAnimation::update_skeleton(
@ -1842,7 +1842,7 @@ impl FigureMgr {
let target_base = match ( let target_base = match (
physics.on_ground, physics.on_ground,
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_fluid, // In water physics.in_fluid.is_some(), // In water
) { ) {
// Standing // Standing
(true, false, false) => anim::biped_large::IdleAnimation::update_skeleton( (true, false, false) => anim::biped_large::IdleAnimation::update_skeleton(
@ -1935,7 +1935,7 @@ impl FigureMgr {
let target_base = match ( let target_base = match (
physics.on_ground, physics.on_ground,
vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_fluid, // In water physics.in_fluid.is_some(), // In water
) { ) {
// Standing // Standing
(true, false, false) => anim::golem::IdleAnimation::update_skeleton( (true, false, false) => anim::golem::IdleAnimation::update_skeleton(

View File

@ -367,6 +367,18 @@ fn sprite_config_for(kind: BlockKind) -> Option<SpriteConfig> {
variations: 3, variations: 3,
wind_sway: 0.0, wind_sway: 0.0,
}), }),
BlockKind::DropGate => Some(SpriteConfig {
variations: 1,
wind_sway: 0.0,
}),
BlockKind::DropGateBottom => Some(SpriteConfig {
variations: 1,
wind_sway: 0.0,
}),
BlockKind::GrassSnow => Some(SpriteConfig {
variations: 10,
wind_sway: 0.2,
}),
_ => None, _ => None,
} }
} }
@ -1870,7 +1882,7 @@ impl<V: RectRasterableVol> Terrain<V> {
make_models( make_models(
(BlockKind::Bed, 0), (BlockKind::Bed, 0),
"voxygen.voxel.sprite.furniture.bed-0", "voxygen.voxel.sprite.furniture.bed-0",
Vec3::new(-9.5, -6.0, 0.0), Vec3::new(-9.5, -14.5, 0.0),
Vec3::one(), Vec3::one(),
), ),
// Bench // Bench
@ -2078,20 +2090,20 @@ impl<V: RectRasterableVol> Terrain<V> {
make_models( make_models(
(BlockKind::HangingSign, 0), (BlockKind::HangingSign, 0),
"voxygen.voxel.sprite.furniture.hanging_sign-0", "voxygen.voxel.sprite.furniture.hanging_sign-0",
Vec3::new(-3.5, -17.0, 0.0), Vec3::new(-3.5, -28.0, -4.0),
Vec3::one(), Vec3::one(),
), ),
// WallLamp // WallLamp
make_models( make_models(
(BlockKind::WallLamp, 0), (BlockKind::WallLamp, 0),
"voxygen.voxel.sprite.furniture.lamp_wall-0", "voxygen.voxel.sprite.furniture.lamp_wall-0",
Vec3::new(-5.5, -2.5, 0.0), Vec3::new(-6.5, -3.5, 0.0),
Vec3::one(), Vec3::one(),
), ),
make_models( make_models(
(BlockKind::WallLamp, 1), (BlockKind::WallLamp, 1),
"voxygen.voxel.sprite.furniture.lamp_wall-1", "voxygen.voxel.sprite.furniture.lamp_wall-1",
Vec3::new(-9.0, -10.5, 0.0), Vec3::new(-10.5, -9.0, 0.0),
Vec3::one(), Vec3::one(),
), ),
// Planter // Planter
@ -2180,13 +2192,13 @@ impl<V: RectRasterableVol> Terrain<V> {
make_models( make_models(
(BlockKind::TableDining, 0), (BlockKind::TableDining, 0),
"voxygen.voxel.sprite.furniture.table_dining-0", "voxygen.voxel.sprite.furniture.table_dining-0",
Vec3::new(-13.5, -13.5, 0.0), Vec3::new(-8.5, -8.5, 0.0),
Vec3::one(), Vec3::one(),
), ),
make_models( make_models(
(BlockKind::TableDining, 1), (BlockKind::TableDining, 1),
"voxygen.voxel.sprite.furniture.table_dining-1", "voxygen.voxel.sprite.furniture.table_dining-1",
Vec3::new(-13.5, -13.5, 0.0), Vec3::new(-8.5, -8.5, 0.0),
Vec3::one(), Vec3::one(),
), ),
// TableDouble // TableDouble
@ -2200,26 +2212,26 @@ impl<V: RectRasterableVol> Terrain<V> {
make_models( make_models(
(BlockKind::WardrobeSingle, 0), (BlockKind::WardrobeSingle, 0),
"voxygen.voxel.sprite.furniture.wardrobe_single-0", "voxygen.voxel.sprite.furniture.wardrobe_single-0",
Vec3::new(-6.0, -5.5, 0.0), Vec3::new(-5.5, -6.0, 0.0),
Vec3::one(), Vec3::one(),
), ),
make_models( make_models(
(BlockKind::WardrobeSingle, 1), (BlockKind::WardrobeSingle, 1),
"voxygen.voxel.sprite.furniture.wardrobe_single-1", "voxygen.voxel.sprite.furniture.wardrobe_single-1",
Vec3::new(-6.5, -5.5, 0.0), Vec3::new(-5.5, -6.5, 0.0),
Vec3::one(), Vec3::one(),
), ),
//WardrobeDouble //WardrobeDouble
make_models( make_models(
(BlockKind::WardrobeDouble, 0), (BlockKind::WardrobeDouble, 0),
"voxygen.voxel.sprite.furniture.wardrobe_double-0", "voxygen.voxel.sprite.furniture.wardrobe_double-0",
Vec3::new(-6.5, -10.5, 0.0), Vec3::new(-10.5, -6.5, 0.0),
Vec3::one(), Vec3::one(),
), ),
make_models( make_models(
(BlockKind::WardrobeDouble, 1), (BlockKind::WardrobeDouble, 1),
"voxygen.voxel.sprite.furniture.wardrobe_double-1", "voxygen.voxel.sprite.furniture.wardrobe_double-1",
Vec3::new(-6.0, -10.5, 0.0), Vec3::new(-10.5, -6.0, 0.0),
Vec3::one(), Vec3::one(),
), ),
/* Stones */ /* Stones */
@ -2279,6 +2291,80 @@ impl<V: RectRasterableVol> Terrain<V> {
Vec3::new(-3.0, -2.0, -2.0), Vec3::new(-3.0, -2.0, -2.0),
Vec3::one(), Vec3::one(),
), ),
// Drop Gate Parts
make_models(
(BlockKind::DropGate, 0),
"voxygen.voxel.sprite.castle.drop_gate_bars-0",
Vec3::new(-5.5, -5.5, 0.0),
Vec3::one(),
),
make_models(
(BlockKind::DropGateBottom, 0),
"voxygen.voxel.sprite.castle.drop_gate_bottom-0",
Vec3::new(-5.5, -5.5, 0.0),
Vec3::one(),
),
// Snow covered Grass
make_models(
(BlockKind::GrassSnow, 0),
"voxygen.voxel.sprite.grass.grass_snow_0",
Vec3::new(-2.5, -2.5, 0.0),
Vec3::one(),
),
make_models(
(BlockKind::GrassSnow, 1),
"voxygen.voxel.sprite.grass.grass_snow_1",
Vec3::new(-2.5, -2.5, 0.0),
Vec3::one(),
),
make_models(
(BlockKind::GrassSnow, 2),
"voxygen.voxel.sprite.grass.grass_snow_2",
Vec3::new(-2.5, -2.5, 0.0),
Vec3::one(),
),
make_models(
(BlockKind::GrassSnow, 3),
"voxygen.voxel.sprite.grass.grass_snow_3",
Vec3::new(-2.5, -2.5, 0.0),
Vec3::one(),
),
make_models(
(BlockKind::GrassSnow, 4),
"voxygen.voxel.sprite.grass.grass_snow_4",
Vec3::new(-2.5, -2.5, 0.0),
Vec3::one(),
),
make_models(
(BlockKind::GrassSnow, 5),
"voxygen.voxel.sprite.grass.grass_snow_5",
Vec3::new(-2.5, -2.5, 0.0),
Vec3::one(),
),
make_models(
(BlockKind::GrassSnow, 6),
"voxygen.voxel.sprite.grass.grass_snow_6",
Vec3::new(-2.5, -2.5, 0.0),
Vec3::one(),
),
make_models(
(BlockKind::GrassSnow, 7),
"voxygen.voxel.sprite.grass.grass_snow_7",
Vec3::new(-2.5, -2.5, 0.0),
Vec3::one(),
),
make_models(
(BlockKind::GrassSnow, 8),
"voxygen.voxel.sprite.grass.grass_snow_8",
Vec3::new(-2.5, -2.5, 0.0),
Vec3::one(),
),
make_models(
(BlockKind::GrassSnow, 9),
"voxygen.voxel.sprite.grass.grass_snow_9",
Vec3::new(-2.5, -2.5, 0.0),
Vec3::one(),
),
] ]
.into_iter() .into_iter()
.collect(); .collect();
@ -2438,26 +2524,6 @@ impl<V: RectRasterableVol> Terrain<V> {
.iter() .iter()
.map(|(p, _)| *p) .map(|(p, _)| *p)
{ {
let chunk_pos = scene_data.state.terrain().pos_key(pos);
// Only mesh if this chunk has all its neighbors
let mut neighbours = true;
for i in -1..2 {
for j in -1..2 {
neighbours &= scene_data
.state
.terrain()
.get_key(chunk_pos + Vec2::new(i, j))
.is_some();
}
}
if neighbours {
self.mesh_todo.insert(chunk_pos, ChunkMeshState {
pos: chunk_pos,
started_tick: current_tick,
active_worker: None,
});
}
// Handle block changes on chunk borders // Handle block changes on chunk borders
// Remesh all neighbours because we have complex lighting now // Remesh all neighbours because we have complex lighting now
// TODO: if lighting is on the server this can be updated to only remesh when // TODO: if lighting is on the server this can be updated to only remesh when
@ -2468,7 +2534,6 @@ impl<V: RectRasterableVol> Terrain<V> {
let neighbour_pos = pos + Vec3::new(x, y, 0); let neighbour_pos = pos + Vec3::new(x, y, 0);
let neighbour_chunk_pos = scene_data.state.terrain().pos_key(neighbour_pos); let neighbour_chunk_pos = scene_data.state.terrain().pos_key(neighbour_pos);
if neighbour_chunk_pos != chunk_pos {
// Only remesh if this chunk has all its neighbors // Only remesh if this chunk has all its neighbors
let mut neighbours = true; let mut neighbours = true;
for i in -1..2 { for i in -1..2 {
@ -2490,7 +2555,6 @@ impl<V: RectRasterableVol> Terrain<V> {
} }
} }
} }
}
// Remove any models for chunks that have been recently removed. // Remove any models for chunks that have been recently removed.
for &pos in &scene_data.state.terrain_changes().removed_chunks { for &pos in &scene_data.state.terrain_changes().removed_chunks {

View File

@ -28,7 +28,7 @@ fn main() {
let pos = focus + Vec2::new(i as i32, j as i32) * scale; let pos = focus + Vec2::new(i as i32, j as i32) * scale;
let (alt, place) = sampler let (alt, place) = sampler
.get(pos) .get((pos, world.index()))
.map(|sample| { .map(|sample| {
( (
sample.alt.sub(64.0).add(gain).mul(0.7).max(0.0).min(255.0) as u8, sample.alt.sub(64.0).add(gain).mul(0.7).max(0.0).min(255.0) as u8,

View File

@ -52,10 +52,11 @@ fn main() {
(0..map_size_lg.chunks_len()) (0..map_size_lg.chunks_len())
.into_par_iter() .into_par_iter()
.map(|posi| { .map(|posi| {
column_sample.get( column_sample.get((
uniform_idx_as_vec2(map_size_lg, posi) uniform_idx_as_vec2(map_size_lg, posi)
* TerrainChunkSize::RECT_SIZE.map(|e| e as i32), * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
) world.index(),
))
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
.into_boxed_slice() .into_boxed_slice()
@ -67,6 +68,7 @@ fn main() {
sample_pos( sample_pos(
config, config,
sampler, sampler,
world.index(),
samples, samples,
uniform_idx_as_vec2(map_size_lg, posi), uniform_idx_as_vec2(map_size_lg, posi),
) )

View File

@ -3,13 +3,13 @@ mod natural;
use crate::{ use crate::{
column::{ColumnGen, ColumnSample}, column::{ColumnGen, ColumnSample},
util::{RandomField, Sampler, SmallCache}, util::{RandomField, Sampler, SmallCache},
CONFIG, Index,
}; };
use common::{ use common::{
terrain::{structure::StructureBlock, Block, BlockKind, Structure}, terrain::{structure::StructureBlock, Block, BlockKind, Structure},
vol::{ReadVol, Vox}, vol::{ReadVol, Vox},
}; };
use std::ops::{Add, Div, Mul, Neg}; use std::ops::{Div, Mul};
use vek::*; use vek::*;
pub struct BlockGen<'a> { pub struct BlockGen<'a> {
@ -29,8 +29,11 @@ impl<'a> BlockGen<'a> {
column_gen: &ColumnGen<'a>, column_gen: &ColumnGen<'a>,
cache: &'b mut SmallCache<Option<ColumnSample<'a>>>, cache: &'b mut SmallCache<Option<ColumnSample<'a>>>,
wpos: Vec2<i32>, wpos: Vec2<i32>,
index: &'a Index,
) -> Option<&'b ColumnSample<'a>> { ) -> Option<&'b ColumnSample<'a>> {
cache.get(wpos, |wpos| column_gen.get(wpos)).as_ref() cache
.get(wpos, |wpos| column_gen.get((wpos, index)))
.as_ref()
} }
pub fn get_cliff_height( pub fn get_cliff_height(
@ -40,11 +43,13 @@ impl<'a> BlockGen<'a> {
close_cliffs: &[(Vec2<i32>, u32); 9], close_cliffs: &[(Vec2<i32>, u32); 9],
cliff_hill: f32, cliff_hill: f32,
tolerance: f32, tolerance: f32,
index: &'a Index,
) -> f32 { ) -> f32 {
close_cliffs.iter().fold( close_cliffs.iter().fold(
0.0f32, 0.0f32,
|max_height, (cliff_pos, seed)| match Self::sample_column(column_gen, cache, *cliff_pos) |max_height, (cliff_pos, seed)| match Self::sample_column(
{ column_gen, cache, *cliff_pos, index,
) {
Some(cliff_sample) if cliff_sample.is_cliffs && cliff_sample.spawn_rate > 0.5 => { Some(cliff_sample) if cliff_sample.is_cliffs && cliff_sample.spawn_rate > 0.5 => {
let cliff_pos3d = Vec3::from(*cliff_pos); let cliff_pos3d = Vec3::from(*cliff_pos);
@ -84,14 +89,14 @@ impl<'a> BlockGen<'a> {
) )
} }
pub fn get_z_cache(&mut self, wpos: Vec2<i32>) -> Option<ZCache<'a>> { pub fn get_z_cache(&mut self, wpos: Vec2<i32>, index: &'a Index) -> Option<ZCache<'a>> {
let BlockGen { let BlockGen {
column_cache, column_cache,
column_gen, column_gen,
} = self; } = self;
// Main sample // Main sample
let sample = column_gen.get(wpos)?; let sample = column_gen.get((wpos, index))?;
// Tree samples // Tree samples
let mut structures = [None, None, None, None, None, None, None, None, None]; let mut structures = [None, None, None, None, None, None, None, None, None];
@ -101,7 +106,7 @@ impl<'a> BlockGen<'a> {
.zip(structures.iter_mut()) .zip(structures.iter_mut())
.for_each(|(close_structure, structure)| { .for_each(|(close_structure, structure)| {
if let Some(st) = *close_structure { if let Some(st) = *close_structure {
let st_sample = Self::sample_column(column_gen, column_cache, st.pos); let st_sample = Self::sample_column(column_gen, column_cache, st.pos, index);
if let Some(st_sample) = st_sample { if let Some(st_sample) = st_sample {
let st_sample = st_sample.clone(); let st_sample = st_sample.clone();
let st_info = match st.meta { let st_info = match st.meta {
@ -111,6 +116,7 @@ impl<'a> BlockGen<'a> {
st.pos, st.pos,
st.seed, st.seed,
&st_sample, &st_sample,
index,
), ),
Some(meta) => Some(StructureInfo { Some(meta) => Some(StructureInfo {
pos: Vec3::from(st.pos) + Vec3::unit_z() * st_sample.alt as i32, pos: Vec3::from(st.pos) + Vec3::unit_z() * st_sample.alt as i32,
@ -137,6 +143,7 @@ impl<'a> BlockGen<'a> {
wpos: Vec3<i32>, wpos: Vec3<i32>,
z_cache: Option<&ZCache>, z_cache: Option<&ZCache>,
only_structures: bool, only_structures: bool,
index: &'a Index,
) -> Option<Block> { ) -> Option<Block> {
let BlockGen { let BlockGen {
column_cache, column_cache,
@ -156,16 +163,14 @@ impl<'a> BlockGen<'a> {
//tree_density, //tree_density,
//forest_kind, //forest_kind,
//close_structures, //close_structures,
cave_xy, // marble,
cave_alt, // marble_small,
marble,
marble_small,
rock, rock,
//cliffs, //cliffs,
cliff_hill, cliff_hill,
close_cliffs, close_cliffs,
temp, // temp,
humidity, // humidity,
stone_col, stone_col,
.. ..
} = sample; } = sample;
@ -175,7 +180,7 @@ impl<'a> BlockGen<'a> {
let wposf = wpos.map(|e| e as f64); 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) = let (_definitely_underground, height, _on_cliff, basement_height, water_height) =
if (wposf.z as f32) < alt - 64.0 * chaos { if (wposf.z as f32) < alt - 64.0 * chaos {
// Shortcut warping // Shortcut warping
(true, alt, false, basement, water_level) (true, alt, false, basement, water_level)
@ -208,6 +213,7 @@ impl<'a> BlockGen<'a> {
&close_cliffs, &close_cliffs,
cliff_hill, cliff_hill,
0.0, 0.0,
index,
); );
( (
@ -269,81 +275,84 @@ impl<'a> BlockGen<'a> {
}, },
col.map(|e| (e * 255.0) as u8), col.map(|e| (e * 255.0) as u8),
)) ))
} else if (wposf.z as f32) < height + 0.9 // } else if (wposf.z as f32) < height + 0.9
&& temp < CONFIG.desert_temp // && temp < CONFIG.desert_temp
&& (wposf.z as f32 > water_height + 3.0) // && (wposf.z as f32 > water_height + 3.0)
&& marble > 0.6 // && marble > 0.6
&& marble_small > 0.55 // && marble_small > 0.55
&& (marble * 3173.7).fract() < 0.6 // && (marble * 3173.7).fract() < 0.6
&& humidity > CONFIG.desert_hum // && humidity > CONFIG.desert_hum
{ // && false
let treasures = [BlockKind::Chest, BlockKind::Velorite]; // {
// let treasures = [BlockKind::Chest, BlockKind::Velorite];
let flowers = [ // let flowers = [
BlockKind::BlueFlower, // BlockKind::BlueFlower,
BlockKind::PinkFlower, // BlockKind::PinkFlower,
BlockKind::PurpleFlower, // BlockKind::PurpleFlower,
BlockKind::RedFlower, // BlockKind::RedFlower,
BlockKind::WhiteFlower, // BlockKind::WhiteFlower,
BlockKind::YellowFlower, // BlockKind::YellowFlower,
BlockKind::Sunflower, // BlockKind::Sunflower,
BlockKind::Mushroom, //TODO: Better spawnrules // BlockKind::Mushroom, //TODO: Better spawnrules
BlockKind::LeafyPlant, // BlockKind::LeafyPlant,
BlockKind::Blueberry, // BlockKind::Blueberry,
BlockKind::LingonBerry, // BlockKind::LingonBerry,
BlockKind::Fern, // BlockKind::Fern,
/*BlockKind::Twigs, // TODO: Better spawnrules // /*BlockKind::Twigs, // TODO: Better spawnrules
*BlockKind::Stones, // TODO: Better spawnrules // *BlockKind::Stones, // TODO: Better spawnrules
*BlockKind::ShinyGem, // TODO: Better spawnrules */ // *BlockKind::ShinyGem, // TODO: Better spawnrules */
]; // ];
let grasses = [ // let grasses = [
BlockKind::LongGrass, // BlockKind::LongGrass,
BlockKind::MediumGrass, // BlockKind::MediumGrass,
BlockKind::ShortGrass, // BlockKind::ShortGrass,
]; // ];
Some(Block::new( // Some(Block::new(
if on_cliff && (height * 1271.0).fract() < 0.015 { // if on_cliff && (height * 1271.0).fract() < 0.015 {
treasures[(height * 731.3) as usize % treasures.len()] // treasures[(height * 731.3) as usize %
} else if (height * 1271.0).fract() < 0.1 { // treasures.len()] } else if (height *
flowers[(height * 0.2) as usize % flowers.len()] // 1271.0).fract() < 0.1 { flowers[(height *
} else { // 0.2) as usize % flowers.len()] } else {
grasses[(height * 103.3) as usize % grasses.len()] // grasses[(height * 103.3) as usize % grasses.len()]
}, // },
Rgb::broadcast(0), // Rgb::broadcast(0),
)) // ))
} else if (wposf.z as f32) < height + 0.9 // } else if (wposf.z as f32) < height + 0.9
&& temp > CONFIG.desert_temp // && temp > CONFIG.desert_temp
&& (marble * 4423.5).fract() < 0.0005 // && (marble * 4423.5).fract() < 0.0005
{ // && false
let large_cacti = [ // {
BlockKind::LargeCactus, // let large_cacti = [
BlockKind::MedFlatCactus, // BlockKind::LargeCactus,
BlockKind::Welwitch, // BlockKind::MedFlatCactus,
]; // BlockKind::Welwitch,
// ];
let small_cacti = [ // let small_cacti = [
BlockKind::BarrelCactus, // BlockKind::BarrelCactus,
BlockKind::RoundCactus, // BlockKind::RoundCactus,
BlockKind::ShortCactus, // BlockKind::ShortCactus,
BlockKind::ShortFlatCactus, // BlockKind::ShortFlatCactus,
BlockKind::DeadBush, // BlockKind::DeadBush,
]; // ];
Some(Block::new( // Some(Block::new(
if (height * 1271.0).fract() < 0.5 { // if (height * 1271.0).fract() < 0.5 {
large_cacti[(height * 0.2) as usize % large_cacti.len()] // large_cacti[(height * 0.2) as usize %
} else { // large_cacti.len()] } else {
small_cacti[(height * 0.3) as usize % small_cacti.len()] // small_cacti[(height * 0.3) as usize %
}, // small_cacti.len()] },
Rgb::broadcast(0), // Rgb::broadcast(0),
)) // ))
} else { } else {
None None
} }
.or_else(|| { .or_else(|| {
// Rocks // Rocks
if (height + 2.5 - wposf.z as f32).div(7.5).abs().powf(2.0) < rock { if (height + 2.5 - wposf.z as f32).div(7.5).abs().powf(2.0) < rock {
#[allow(clippy::identity_op)]
let field0 = RandomField::new(world.seed + 0); let field0 = RandomField::new(world.seed + 0);
let field1 = RandomField::new(world.seed + 1); let field1 = RandomField::new(world.seed + 1);
let field2 = RandomField::new(world.seed + 2); let field2 = RandomField::new(world.seed + 2);
@ -361,23 +370,6 @@ impl<'a> BlockGen<'a> {
None None
} }
}) })
.and_then(|block| {
// Caves
// Underground
let cave = cave_xy.powf(2.0)
* (wposf.z as f32 - cave_alt)
.div(40.0)
.powf(4.0)
.neg()
.add(1.0)
> 0.9993;
if cave && wposf.z as f32 > water_height + 3.0 {
None
} else {
Some(block)
}
})
.or_else(|| { .or_else(|| {
// Water // Water
if (wposf.z as f32) < water_height { if (wposf.z as f32) < water_height {
@ -412,15 +404,12 @@ pub struct ZCache<'a> {
} }
impl<'a> ZCache<'a> { impl<'a> ZCache<'a> {
pub fn get_z_limits(&self, block_gen: &mut BlockGen) -> (f32, f32, f32) { pub fn get_z_limits<'b>(
let cave_depth = &self,
if self.sample.cave_xy.abs() > 0.9 && self.sample.water_level <= self.sample.alt { block_gen: &mut BlockGen<'b>,
(self.sample.alt - self.sample.cave_alt + 8.0).max(0.0) index: &'b Index,
} else { ) -> (f32, f32, f32) {
0.0 let min = self.sample.alt - (self.sample.chaos.min(1.0) * 16.0);
};
let min = self.sample.alt - (self.sample.chaos.min(1.0) * 16.0 + cave_depth);
let min = min - 4.0; let min = min - 4.0;
let cliff = BlockGen::get_cliff_height( let cliff = BlockGen::get_cliff_height(
@ -430,6 +419,7 @@ impl<'a> ZCache<'a> {
&self.sample.close_cliffs, &self.sample.close_cliffs,
self.sample.cliff_hill, self.sample.cliff_hill,
32.0, 32.0,
index,
); );
let rocks = if self.sample.rock > 0.0 { 12.0 } else { 0.0 }; let rocks = if self.sample.rock > 0.0 { 12.0 } else { 0.0 };
@ -550,7 +540,7 @@ pub fn block_from_structure(
structure_seed: u32, structure_seed: u32,
sample: &ColumnSample, sample: &ColumnSample,
) -> Option<Block> { ) -> Option<Block> {
let field = RandomField::new(structure_seed + 0); let field = RandomField::new(structure_seed);
let lerp = ((field.get(Vec3::from(structure_pos)).rem_euclid(256)) as f32 / 255.0) * 0.85 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; + ((field.get(pos + std::i32::MAX / 2).rem_euclid(256)) as f32 / 255.0) * 0.15;

View File

@ -3,7 +3,7 @@ use crate::{
all::ForestKind, all::ForestKind,
column::{ColumnGen, ColumnSample}, column::{ColumnGen, ColumnSample},
util::{RandomPerm, Sampler, SmallCache, UnitChooser}, util::{RandomPerm, Sampler, SmallCache, UnitChooser},
CONFIG, Index, CONFIG,
}; };
use common::terrain::Structure; use common::terrain::Structure;
use lazy_static::lazy_static; use lazy_static::lazy_static;
@ -20,6 +20,7 @@ pub fn structure_gen<'a>(
st_pos: Vec2<i32>, st_pos: Vec2<i32>,
st_seed: u32, st_seed: u32,
st_sample: &ColumnSample, st_sample: &ColumnSample,
index: &'a Index,
) -> Option<StructureInfo> { ) -> Option<StructureInfo> {
// Assuming it's a tree... figure out when it SHOULDN'T spawn // Assuming it's a tree... figure out when it SHOULDN'T spawn
let random_seed = (st_seed as f64) / (u32::MAX as f64); let random_seed = (st_seed as f64) / (u32::MAX as f64);
@ -27,7 +28,7 @@ pub fn structure_gen<'a>(
|| st_sample.alt < st_sample.water_level || st_sample.alt < st_sample.water_level
|| st_sample.spawn_rate < 0.5 || st_sample.spawn_rate < 0.5
|| st_sample.water_dist.map(|d| d < 8.0).unwrap_or(false) || st_sample.water_dist.map(|d| d < 8.0).unwrap_or(false)
|| st_sample.path.map(|(d, _)| d < 12.0).unwrap_or(false) || st_sample.path.map(|(d, _, _, _)| d < 12.0).unwrap_or(false)
{ {
return None; return None;
} }
@ -39,6 +40,7 @@ pub fn structure_gen<'a>(
&st_sample.close_cliffs, &st_sample.close_cliffs,
st_sample.cliff_hill, st_sample.cliff_hill,
0.0, 0.0,
index,
); );
let wheight = st_sample.alt.max(cliff_height); let wheight = st_sample.alt.max(cliff_height);

View File

@ -6,8 +6,9 @@ use self::{Occupation::*, Stock::*};
use crate::{ use crate::{
config::CONFIG, config::CONFIG,
sim::WorldSim, sim::WorldSim,
site::{Dungeon, Settlement, Site as WorldSite}, site::{Castle, Dungeon, Settlement, Site as WorldSite},
util::{attempt, seed_expan, CARDINALS, NEIGHBORS}, util::{attempt, seed_expan, MapVec, CARDINALS, NEIGHBORS},
Index,
}; };
use common::{ use common::{
astar::Astar, astar::Astar,
@ -75,11 +76,17 @@ impl<'a, R: Rng> GenCtx<'a, R> {
} }
impl Civs { impl Civs {
pub fn generate(seed: u32, sim: &mut WorldSim) -> Self { pub fn generate(seed: u32, sim: &mut WorldSim, index: &mut Index) -> Self {
let mut this = Self::default(); let mut this = Self::default();
let rng = ChaChaRng::from_seed(seed_expan::rng_state(seed)); let rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
let initial_civ_count = initial_civ_count(sim.map_size_lg()); let initial_civ_count = initial_civ_count(sim.map_size_lg());
let mut ctx = GenCtx { sim, rng }; let mut ctx = GenCtx { sim, rng };
// TODO: Care about world size when generating caves.
for _ in 0..100 {
this.generate_cave(&mut ctx);
}
for _ in 0..initial_civ_count { for _ in 0..initial_civ_count {
debug!("Creating civilisation..."); debug!("Creating civilisation...");
if this.birth_civ(&mut ctx.reseed()).is_none() { if this.birth_civ(&mut ctx.reseed()).is_none() {
@ -90,9 +97,13 @@ impl Civs {
for _ in 0..initial_civ_count * 3 { for _ in 0..initial_civ_count * 3 {
attempt(5, || { attempt(5, || {
let loc = find_site_loc(&mut ctx, None)?; let (kind, size) = match ctx.rng.gen_range(0, 8) {
0 => (SiteKind::Castle, 3),
_ => (SiteKind::Dungeon, 0),
};
let loc = find_site_loc(&mut ctx, None, size)?;
this.establish_site(&mut ctx.reseed(), loc, |place| Site { this.establish_site(&mut ctx.reseed(), loc, |place| Site {
kind: SiteKind::Dungeon, kind,
center: loc, center: loc,
place, place,
@ -108,7 +119,7 @@ impl Civs {
last_exports: Stocks::from_default(0.0), last_exports: Stocks::from_default(0.0),
export_targets: Stocks::from_default(0.0), export_targets: Stocks::from_default(0.0),
trade_states: Stocks::default(), //trade_states: Stocks::default(),
coin: 1000.0, coin: 1000.0,
}) })
}); });
@ -121,7 +132,7 @@ impl Civs {
} }
// Flatten ground around sites // Flatten ground around sites
for site in this.sites.iter() { for site in this.sites.values() {
let radius = 48i32; let radius = 48i32;
let wpos = site.center * TerrainChunkSize::RECT_SIZE.map(|e: u32| e as i32); let wpos = site.center * TerrainChunkSize::RECT_SIZE.map(|e: u32| e as i32);
@ -129,10 +140,12 @@ impl Civs {
let flatten_radius = match &site.kind { let flatten_radius = match &site.kind {
SiteKind::Settlement => 10.0, SiteKind::Settlement => 10.0,
SiteKind::Dungeon => 2.0, SiteKind::Dungeon => 2.0,
SiteKind::Castle => 5.0,
}; };
let (raise, raise_dist): (f32, i32) = match &site.kind { let (raise, raise_dist): (f32, i32) = match &site.kind {
SiteKind::Settlement => (10.0, 6), SiteKind::Settlement => (10.0, 6),
SiteKind::Castle => (0.0, 6),
_ => (0.0, 0), _ => (0.0, 0),
}; };
@ -146,9 +159,11 @@ impl Civs {
0.0 0.0
}; // Raise the town centre up a little }; // Raise the town centre up a little
let pos = site.center + offs; let pos = site.center + offs;
let factor = (1.0 let factor = ((1.0
- (site.center - pos).map(|e| e as f32).magnitude() / flatten_radius) - (site.center - pos).map(|e| e as f32).magnitude() / flatten_radius)
* 1.15; * 1.25)
.min(1.0);
let rng = &mut ctx.rng;
ctx.sim ctx.sim
.get_mut(pos) .get_mut(pos)
// Don't disrupt chunks that are near water // Don't disrupt chunks that are near water
@ -164,6 +179,7 @@ impl Civs {
chunk.basement += diff; chunk.basement += diff;
chunk.rockiness = 0.0; chunk.rockiness = 0.0;
chunk.warp_factor = 0.0; chunk.warp_factor = 0.0;
chunk.surface_veg *= 1.0 - factor * rng.gen_range(0.25, 0.9);
}); });
} }
} }
@ -171,33 +187,37 @@ impl Civs {
// Place sites in world // Place sites in world
let mut cnt = 0; let mut cnt = 0;
for site in this.sites.iter() { for sim_site in this.sites.values() {
cnt += 1; cnt += 1;
let wpos = site.center.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| { let wpos = sim_site
.center
.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| {
e * sz as i32 + sz as i32 / 2 e * sz as i32 + sz as i32 / 2
}); });
let mut rng = ctx.reseed().rng; let mut rng = ctx.reseed().rng;
let world_site = match &site.kind { let site = index.sites.insert(match &sim_site.kind {
SiteKind::Settlement => { SiteKind::Settlement => {
WorldSite::from(Settlement::generate(wpos, Some(ctx.sim), &mut rng)) WorldSite::settlement(Settlement::generate(wpos, Some(ctx.sim), &mut rng))
}, },
SiteKind::Dungeon => { SiteKind::Dungeon => {
WorldSite::from(Dungeon::generate(wpos, Some(ctx.sim), &mut rng)) WorldSite::dungeon(Dungeon::generate(wpos, Some(ctx.sim), &mut rng))
}, },
}; SiteKind::Castle => {
WorldSite::castle(Castle::generate(wpos, Some(ctx.sim), &mut rng))
},
});
let site_ref = &index.sites[site];
let radius_chunks = let radius_chunks =
(world_site.radius() / TerrainChunkSize::RECT_SIZE.x as f32).ceil() as usize; (site_ref.radius() / TerrainChunkSize::RECT_SIZE.x as f32).ceil() as usize;
for pos in Spiral2d::new() for pos in Spiral2d::new()
.map(|offs| site.center + offs) .map(|offs| sim_site.center + offs)
.take((radius_chunks * 2).pow(2)) .take((radius_chunks * 2).pow(2))
{ {
ctx.sim ctx.sim.get_mut(pos).map(|chunk| chunk.sites.push(site));
.get_mut(pos)
.map(|chunk| chunk.sites.push(world_site.clone()));
} }
debug!(?site.center, "Placed site at location"); debug!(?sim_site.center, "Placed site at location");
} }
info!(?cnt, "all sites placed"); info!(?cnt, "all sites placed");
@ -206,20 +226,84 @@ impl Civs {
this this
} }
// TODO: Move this
fn generate_cave(&self, ctx: &mut GenCtx<impl Rng>) {
let mut pos = ctx
.sim
.get_size()
.map(|sz| ctx.rng.gen_range(0, sz as i32) as f32);
let mut vel = pos
.map2(ctx.sim.get_size(), |pos, sz| sz as f32 / 2.0 - pos)
.try_normalized()
.unwrap_or_else(Vec2::unit_y);
let path = (-100..100)
.filter_map(|i: i32| {
let depth = (i.abs() as f32 / 100.0 * std::f32::consts::PI / 2.0).cos();
vel = (vel
+ Vec2::new(
ctx.rng.gen_range(-0.35, 0.35),
ctx.rng.gen_range(-0.35, 0.35),
))
.try_normalized()
.unwrap_or_else(Vec2::unit_y);
let old_pos = pos.map(|e| e as i32);
pos = (pos + vel * 0.5)
.clamped(Vec2::zero(), ctx.sim.get_size().map(|e| e as f32 - 1.0));
Some((pos.map(|e| e as i32), depth)).filter(|(pos, _)| *pos != old_pos)
})
.collect::<Vec<_>>();
for locs in path.windows(3) {
let to_prev_idx = NEIGHBORS
.iter()
.enumerate()
.find(|(_, dir)| **dir == locs[0].0 - locs[1].0)
.expect("Track locations must be neighbors")
.0;
let to_next_idx = NEIGHBORS
.iter()
.enumerate()
.find(|(_, dir)| **dir == locs[2].0 - locs[1].0)
.expect("Track locations must be neighbors")
.0;
ctx.sim.get_mut(locs[0].0).unwrap().cave.0.neighbors |=
1 << ((to_prev_idx as u8 + 4) % 8);
ctx.sim.get_mut(locs[1].0).unwrap().cave.0.neighbors |=
(1 << (to_prev_idx as u8)) | (1 << (to_next_idx as u8));
ctx.sim.get_mut(locs[2].0).unwrap().cave.0.neighbors |=
1 << ((to_next_idx as u8 + 4) % 8);
}
for loc in path.iter() {
let mut chunk = ctx.sim.get_mut(loc.0).unwrap();
let depth = loc.1 * 250.0 - 20.0;
chunk.cave.1.alt =
chunk.alt - depth + ctx.rng.gen_range(-4.0, 4.0) * (depth > 10.0) as i32 as f32;
chunk.cave.1.width = ctx.rng.gen_range(6.0, 32.0);
chunk.cave.0.offset = Vec2::new(ctx.rng.gen_range(-16, 17), ctx.rng.gen_range(-16, 17));
if chunk.cave.1.alt + chunk.cave.1.width + 5.0 > chunk.alt {
chunk.spawn_rate = 0.0;
}
}
}
pub fn place(&self, id: Id<Place>) -> &Place { self.places.get(id) } pub fn place(&self, id: Id<Place>) -> &Place { self.places.get(id) }
pub fn sites(&self) -> impl Iterator<Item = &Site> + '_ { self.sites.iter() } pub fn sites(&self) -> impl Iterator<Item = &Site> + '_ { self.sites.values() }
#[allow(dead_code)] #[allow(dead_code)]
#[allow(clippy::print_literal)] // TODO: Pending review in #587 #[allow(clippy::print_literal)] // TODO: Pending review in #587
fn display_info(&self) { fn display_info(&self) {
for (id, civ) in self.civs.iter_ids() { for (id, civ) in self.civs.iter() {
println!("# Civilisation {:?}", id); println!("# Civilisation {:?}", id);
println!("Name: {}", "<unnamed>"); println!("Name: {}", "<unnamed>");
println!("Homeland: {:#?}", self.places.get(civ.homeland)); println!("Homeland: {:#?}", self.places.get(civ.homeland));
} }
for (id, site) in self.sites.iter_ids() { for (id, site) in self.sites.iter() {
println!("# Site {:?}", id); println!("# Site {:?}", id);
println!("{:#?}", site); println!("{:#?}", site);
} }
@ -282,7 +366,7 @@ impl Civs {
fn birth_civ(&mut self, ctx: &mut GenCtx<impl Rng>) -> Option<Id<Civ>> { fn birth_civ(&mut self, ctx: &mut GenCtx<impl Rng>) -> Option<Id<Civ>> {
let site = attempt(5, || { let site = attempt(5, || {
let loc = find_site_loc(ctx, None)?; let loc = find_site_loc(ctx, None, 1)?;
self.establish_site(ctx, loc, |place| Site { self.establish_site(ctx, loc, |place| Site {
kind: SiteKind::Settlement, kind: SiteKind::Settlement,
center: loc, center: loc,
@ -300,7 +384,7 @@ impl Civs {
last_exports: Stocks::from_default(0.0), last_exports: Stocks::from_default(0.0),
export_targets: Stocks::from_default(0.0), export_targets: Stocks::from_default(0.0),
trade_states: Stocks::default(), //trade_states: Stocks::default(),
coin: 1000.0, coin: 1000.0,
}) })
})?; })?;
@ -377,7 +461,7 @@ impl Civs {
loc: Vec2<i32>, loc: Vec2<i32>,
site_fn: impl FnOnce(Id<Place>) -> Site, site_fn: impl FnOnce(Id<Place>) -> Site,
) -> Option<Id<Site>> { ) -> Option<Id<Site>> {
const SITE_AREA: Range<usize> = 64..256; const SITE_AREA: Range<usize> = 1..4; //64..256;
let place = match ctx.sim.get(loc).and_then(|site| site.place) { let place = match ctx.sim.get(loc).and_then(|site| site.place) {
Some(place) => place, Some(place) => place,
@ -390,13 +474,14 @@ impl Civs {
const MAX_NEIGHBOR_DISTANCE: f32 = 500.0; const MAX_NEIGHBOR_DISTANCE: f32 = 500.0;
let mut nearby = self let mut nearby = self
.sites .sites
.iter_ids() .iter()
.filter(|(_, p)| matches!(p.kind, SiteKind::Settlement | SiteKind::Castle))
.map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt())) .map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt()))
.filter(|(_, dist)| *dist < MAX_NEIGHBOR_DISTANCE) .filter(|(_, dist)| *dist < MAX_NEIGHBOR_DISTANCE)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
nearby.sort_by_key(|(_, dist)| *dist as i32); nearby.sort_by_key(|(_, dist)| *dist as i32);
if let SiteKind::Settlement = self.sites[site].kind { if let SiteKind::Settlement | SiteKind::Castle = self.sites[site].kind {
for (nearby, _) in nearby.into_iter().take(5) { for (nearby, _) in nearby.into_iter().take(5) {
// Find a novel path // Find a novel path
if let Some((path, cost)) = find_path(ctx, loc, self.sites.get(nearby).center) { if let Some((path, cost)) = find_path(ctx, loc, self.sites.get(nearby).center) {
@ -422,17 +507,15 @@ impl Civs {
.expect("Track locations must be neighbors") .expect("Track locations must be neighbors")
.0; .0;
ctx.sim.get_mut(locs[0]).unwrap().path.neighbors |= ctx.sim.get_mut(locs[0]).unwrap().path.0.neighbors |=
1 << ((to_prev_idx as u8 + 4) % 8); 1 << ((to_prev_idx as u8 + 4) % 8);
ctx.sim.get_mut(locs[2]).unwrap().path.neighbors |= ctx.sim.get_mut(locs[2]).unwrap().path.0.neighbors |=
1 << ((to_next_idx as u8 + 4) % 8); 1 << ((to_next_idx as u8 + 4) % 8);
let mut chunk = ctx.sim.get_mut(locs[1]).unwrap(); let mut chunk = ctx.sim.get_mut(locs[1]).unwrap();
chunk.path.neighbors |= chunk.path.0.neighbors |=
(1 << (to_prev_idx as u8)) | (1 << (to_next_idx as u8)); (1 << (to_prev_idx as u8)) | (1 << (to_next_idx as u8));
chunk.path.offset = Vec2::new( chunk.path.0.offset =
ctx.rng.gen_range(-16.0, 16.0), Vec2::new(ctx.rng.gen_range(-16, 17), ctx.rng.gen_range(-16, 17));
ctx.rng.gen_range(-16.0, 16.0),
);
} }
// Take note of the track // Take note of the track
@ -450,7 +533,7 @@ impl Civs {
} }
fn tick(&mut self, _ctx: &mut GenCtx<impl Rng>, years: f32) { fn tick(&mut self, _ctx: &mut GenCtx<impl Rng>, years: f32) {
for site in self.sites.iter_mut() { for site in self.sites.values_mut() {
site.simulate(years, &self.places.get(site.place).nat_res); site.simulate(years, &self.places.get(site.place).nat_res);
} }
@ -567,7 +650,7 @@ fn walk_in_dir(sim: &WorldSim, a: Vec2<i32>, dir: Vec2<i32>) -> Option<f32> {
} else { } else {
0.0 0.0
}; };
let wild_cost = if b_chunk.path.is_path() { let wild_cost = if b_chunk.path.0.is_way() {
0.0 // Traversing existing paths has no additional cost! 0.0 // Traversing existing paths has no additional cost!
} else { } else {
2.0 2.0
@ -599,6 +682,7 @@ fn loc_suitable_for_site(sim: &WorldSim, loc: Vec2<i32>) -> bool {
if let Some(chunk) = sim.get(loc) { if let Some(chunk) = sim.get(loc) {
!chunk.river.is_ocean() !chunk.river.is_ocean()
&& !chunk.river.is_lake() && !chunk.river.is_lake()
&& !chunk.river.is_river()
&& sim && sim
.get_gradient_approx(loc) .get_gradient_approx(loc)
.map(|grad| grad < 1.0) .map(|grad| grad < 1.0)
@ -609,9 +693,12 @@ fn loc_suitable_for_site(sim: &WorldSim, loc: Vec2<i32>) -> bool {
} }
/// Attempt to search for a location that's suitable for site construction /// Attempt to search for a location that's suitable for site construction
#[allow(clippy::useless_conversion)] // TODO: Pending review in #587
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587 #[allow(clippy::or_fun_call)] // TODO: Pending review in #587
fn find_site_loc(ctx: &mut GenCtx<impl Rng>, near: Option<(Vec2<i32>, f32)>) -> Option<Vec2<i32>> { fn find_site_loc(
ctx: &mut GenCtx<impl Rng>,
near: Option<(Vec2<i32>, f32)>,
size: i32,
) -> Option<Vec2<i32>> {
const MAX_ATTEMPTS: usize = 100; const MAX_ATTEMPTS: usize = 100;
let mut loc = None; let mut loc = None;
for _ in 0..MAX_ATTEMPTS { for _ in 0..MAX_ATTEMPTS {
@ -631,16 +718,16 @@ fn find_site_loc(ctx: &mut GenCtx<impl Rng>, near: Option<(Vec2<i32>, f32)>) ->
), ),
}); });
if loc_suitable_for_site(&ctx.sim, test_loc) { for offset in Spiral2d::new().take((size * 2 + 1).pow(2) as usize) {
if loc_suitable_for_site(&ctx.sim, test_loc + offset) {
return Some(test_loc); return Some(test_loc);
} }
}
loc = ctx.sim.get(test_loc).and_then(|c| { loc = ctx.sim.get(test_loc).and_then(|c| {
Some( Some(
c.downhill? c.downhill?
.map2(Vec2::from(TerrainChunkSize::RECT_SIZE), |e, sz: u32| { .map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / (sz as i32)),
e / (sz as i32)
}),
) )
}); });
} }
@ -727,16 +814,13 @@ pub struct Site {
last_exports: Stocks<f32>, last_exports: Stocks<f32>,
export_targets: Stocks<f32>, export_targets: Stocks<f32>,
trade_states: Stocks<TradeState>, //trade_states: Stocks<TradeState>,
coin: f32, coin: f32,
} }
impl fmt::Display for Site { impl fmt::Display for Site {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.kind { writeln!(f, "{:?}", self.kind)?;
SiteKind::Settlement => writeln!(f, "Settlement")?,
SiteKind::Dungeon => writeln!(f, "Dungeon")?,
}
writeln!(f, "- population: {}", self.population.floor() as u32)?; writeln!(f, "- population: {}", self.population.floor() as u32)?;
writeln!(f, "- coin: {}", self.coin.floor() as u32)?; writeln!(f, "- coin: {}", self.coin.floor() as u32)?;
writeln!(f, "Stocks")?; writeln!(f, "Stocks")?;
@ -776,6 +860,7 @@ impl fmt::Display for Site {
pub enum SiteKind { pub enum SiteKind {
Settlement, Settlement,
Dungeon, Dungeon,
Castle,
} }
impl Site { impl Site {
@ -1006,79 +1091,3 @@ impl Default for TradeState {
} }
pub type Stocks<T> = MapVec<Stock, T>; pub type Stocks<T> = MapVec<Stock, T>;
#[derive(Clone, Debug)]
pub struct MapVec<K, T> {
/// We use this hasher (FxHasher32) because
/// (1) we don't care about DDOS attacks (ruling out SipHash);
/// (2) we care about determinism across computers (ruling out AAHash);
/// (3) we have 1-byte keys (for which FxHash is supposedly fastest).
entries: HashMap<K, T, BuildHasherDefault<FxHasher32>>,
default: T,
}
/// Need manual implementation of Default since K doesn't need that bound.
impl<K, T: Default> Default for MapVec<K, T> {
fn default() -> Self {
Self {
entries: Default::default(),
default: Default::default(),
}
}
}
impl<K: Copy + Eq + Hash, T: Clone> MapVec<K, T> {
pub fn from_list<'a>(i: impl IntoIterator<Item = &'a (K, T)>, default: T) -> Self
where
K: 'a,
T: 'a,
{
Self {
entries: i.into_iter().cloned().collect(),
default,
}
}
pub fn from_default(default: T) -> Self {
Self {
entries: HashMap::default(),
default,
}
}
pub fn get_mut(&mut self, entry: K) -> &mut T {
let default = &self.default;
self.entries.entry(entry).or_insert_with(|| default.clone())
}
pub fn get(&self, entry: K) -> &T { self.entries.get(&entry).unwrap_or(&self.default) }
pub fn map<U: Default>(self, mut f: impl FnMut(K, T) -> U) -> MapVec<K, U> {
MapVec {
entries: self
.entries
.into_iter()
.map(|(s, v)| (s, f(s, v)))
.collect(),
default: U::default(),
}
}
pub fn iter(&self) -> impl Iterator<Item = (K, &T)> + '_ {
self.entries.iter().map(|(s, v)| (*s, v))
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (K, &mut T)> + '_ {
self.entries.iter_mut().map(|(s, v)| (*s, v))
}
}
impl<K: Copy + Eq + Hash, T: Clone> std::ops::Index<K> for MapVec<K, T> {
type Output = T;
fn index(&self, entry: K) -> &Self::Output { self.get(entry) }
}
impl<K: Copy + Eq + Hash, T: Clone> std::ops::IndexMut<K> for MapVec<K, T> {
fn index_mut(&mut self, entry: K) -> &mut Self::Output { self.get_mut(entry) }
}

View File

@ -1,9 +1,9 @@
use crate::{ use crate::{
all::ForestKind, all::ForestKind,
block::StructureMeta, block::StructureMeta,
sim::{local_cells, RiverKind, SimChunk, WorldSim}, sim::{local_cells, Cave, Path, RiverKind, SimChunk, WorldSim},
util::Sampler, util::Sampler,
CONFIG, Index, CONFIG,
}; };
use common::{ use common::{
terrain::{ terrain::{
@ -16,7 +16,7 @@ use noise::NoiseFn;
use std::{ use std::{
cmp::Reverse, cmp::Reverse,
f32, f64, f32, f64,
ops::{Add, Div, Mul, Neg, Sub}, ops::{Add, Div, Mul, Sub},
}; };
use tracing::error; use tracing::error;
use vek::*; use vek::*;
@ -80,15 +80,14 @@ impl<'a> ColumnGen<'a> {
} }
impl<'a> Sampler<'a> for ColumnGen<'a> { impl<'a> Sampler<'a> for ColumnGen<'a> {
type Index = Vec2<i32>; type Index = (Vec2<i32>, &'a Index);
type Sample = Option<ColumnSample<'a>>; type Sample = Option<ColumnSample<'a>>;
#[allow(clippy::float_cmp)] // TODO: Pending review in #587 #[allow(clippy::float_cmp)] // TODO: Pending review in #587
#[allow(clippy::if_same_then_else)] // TODO: Pending review in #587 #[allow(clippy::if_same_then_else)] // TODO: Pending review in #587
#[allow(clippy::nonminimal_bool)] // TODO: Pending review in #587 #[allow(clippy::nonminimal_bool)] // TODO: Pending review in #587
#[allow(clippy::single_match)] // TODO: Pending review in #587 #[allow(clippy::single_match)] // TODO: Pending review in #587
#[allow(clippy::bind_instead_of_map)] // TODO: Pending review in #587 fn get(&self, (wpos, index): Self::Index) -> Option<ColumnSample<'a>> {
fn get(&self, wpos: Vec2<i32>) -> Option<ColumnSample<'a>> {
let wposf = wpos.map(|e| e as f64); let wposf = wpos.map(|e| e as f64);
let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32); let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32);
@ -107,6 +106,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
let tree_density = sim.get_interpolated(wpos, |chunk| chunk.tree_density)?; let tree_density = sim.get_interpolated(wpos, |chunk| chunk.tree_density)?;
let spawn_rate = sim.get_interpolated(wpos, |chunk| chunk.spawn_rate)?; let spawn_rate = sim.get_interpolated(wpos, |chunk| chunk.spawn_rate)?;
let alt = sim.get_interpolated_monotone(wpos, |chunk| chunk.alt)?; let alt = sim.get_interpolated_monotone(wpos, |chunk| chunk.alt)?;
let surface_veg = sim.get_interpolated_monotone(wpos, |chunk| chunk.surface_veg)?;
let chunk_warp_factor = sim.get_interpolated_monotone(wpos, |chunk| chunk.warp_factor)?; let chunk_warp_factor = sim.get_interpolated_monotone(wpos, |chunk| chunk.warp_factor)?;
let sim_chunk = sim.get(chunk_pos)?; let sim_chunk = sim.get(chunk_pos)?;
let neighbor_coef = TerrainChunkSize::RECT_SIZE.map(|e| e as f64); let neighbor_coef = TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
@ -523,7 +523,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
.unwrap_or_else(|| { .unwrap_or_else(|| {
max_border_river max_border_river
.river_kind .river_kind
.and_then(|river_kind| { .map(|river_kind| {
match river_kind { match river_kind {
RiverKind::Ocean => { RiverKind::Ocean => {
let ( let (
@ -557,7 +557,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
let river_dist = wposf.distance(river_pos); let river_dist = wposf.distance(river_pos);
let _river_height_factor = let _river_height_factor =
river_dist / (river_width * 0.5); river_dist / (river_width * 0.5);
return Some(( return (
true, true,
Some((river_dist - river_width * 0.5) as f32), Some((river_dist - river_width * 0.5) as f32),
alt_for_river alt_for_river
@ -565,10 +565,10 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
lake_water_alt - river_gouge, lake_water_alt - river_gouge,
alt_for_river.max(lake_water_alt), alt_for_river.max(lake_water_alt),
0.0, 0.0,
)); );
} }
Some(( (
river_scale_factor <= 1.0, river_scale_factor <= 1.0,
Some( Some(
(wposf.distance(river_pos) - river_width * 0.5) (wposf.distance(river_pos) - river_width * 0.5)
@ -578,7 +578,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
downhill_water_alt, downhill_water_alt,
alt_for_river, alt_for_river,
river_scale_factor as f32, river_scale_factor as f32,
)) )
}, },
RiverKind::Lake { .. } => { RiverKind::Lake { .. } => {
let lake_dist = (max_border_river_pos.map(|e| e as f64) let lake_dist = (max_border_river_pos.map(|e| e as f64)
@ -600,7 +600,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|| in_bounds || in_bounds
{ {
let gouge_factor = 0.0; let gouge_factor = 0.0;
return Some(( return (
in_bounds in_bounds
|| downhill_water_alt || downhill_water_alt
.max(river_chunk.water_alt) .max(river_chunk.water_alt)
@ -612,16 +612,16 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
alt_for_river, alt_for_river,
river_scale_factor as f32 river_scale_factor as f32
* (1.0 - gouge_factor), * (1.0 - gouge_factor),
)); );
} else { } else {
return Some(( return (
false, false,
Some(lake_dist as f32), Some(lake_dist as f32),
alt_for_river, alt_for_river,
downhill_water_alt, downhill_water_alt,
alt_for_river, alt_for_river,
river_scale_factor as f32, river_scale_factor as f32,
)); );
}; };
let lake_dist = dist.y; let lake_dist = dist.y;
@ -633,7 +633,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
river_t as f32, river_t as f32,
); );
if dist == Vec2::zero() { if dist == Vec2::zero() {
return Some(( return (
true, true,
Some(lake_dist as f32), Some(lake_dist as f32),
alt_for_river alt_for_river
@ -641,7 +641,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
lake_water_alt - river_gouge, lake_water_alt - river_gouge,
alt_for_river.max(lake_water_alt), alt_for_river.max(lake_water_alt),
0.0, 0.0,
)); );
} }
if lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 1.0 if lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 1.0
|| in_bounds || in_bounds
@ -654,7 +654,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
let in_bounds_ = lake_dist let in_bounds_ = lake_dist
<= TerrainChunkSize::RECT_SIZE.x as f64 * 0.5; <= TerrainChunkSize::RECT_SIZE.x as f64 * 0.5;
if gouge_factor == 1.0 { if gouge_factor == 1.0 {
return Some(( return (
true, true,
Some(lake_dist as f32), Some(lake_dist as f32),
alt.min(lake_water_alt - 1.0 - river_gouge), alt.min(lake_water_alt - 1.0 - river_gouge),
@ -662,9 +662,9 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
- river_gouge, - river_gouge,
alt.max(lake_water_alt), alt.max(lake_water_alt),
0.0, 0.0,
)); );
} else { } else {
return Some(( return (
true, true,
None, None,
alt_for_river, alt_for_river,
@ -676,17 +676,17 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
alt_for_river, alt_for_river,
river_scale_factor as f32 river_scale_factor as f32
* (1.0 - gouge_factor), * (1.0 - gouge_factor),
)); );
} }
} }
Some(( (
river_scale_factor <= 1.0, river_scale_factor <= 1.0,
Some(lake_dist as f32), Some(lake_dist as f32),
alt_for_river, alt_for_river,
downhill_water_alt, downhill_water_alt,
alt_for_river, alt_for_river,
river_scale_factor as f32, river_scale_factor as f32,
)) )
}, },
RiverKind::River { .. } => { RiverKind::River { .. } => {
let (_, _, river_width, (_, (river_pos, _), _)) = let (_, _, river_width, (_, (river_pos, _), _)) =
@ -694,14 +694,14 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
let river_dist = wposf.distance(river_pos); let river_dist = wposf.distance(river_pos);
// FIXME: Make water altitude accurate. // FIXME: Make water altitude accurate.
Some(( (
river_scale_factor <= 1.0, river_scale_factor <= 1.0,
Some((river_dist - river_width * 0.5) as f32), Some((river_dist - river_width * 0.5) as f32),
alt_for_river, alt_for_river,
downhill_water_alt, downhill_water_alt,
alt_for_river, alt_for_river,
river_scale_factor as f32, river_scale_factor as f32,
)) )
}, },
} }
}) })
@ -772,8 +772,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
let cold_stone = Rgb::new(0.57, 0.67, 0.8); let cold_stone = Rgb::new(0.57, 0.67, 0.8);
let hot_stone = Rgb::new(0.07, 0.07, 0.06); let hot_stone = Rgb::new(0.07, 0.07, 0.06);
let warm_stone = Rgb::new(0.77, 0.77, 0.64); let warm_stone = Rgb::new(0.77, 0.77, 0.64);
let beach_sand = Rgb::new(0.9, 0.82, 0.6); let beach_sand = Rgb::new(0.8, 0.75, 0.5);
let desert_sand = Rgb::new(0.95, 0.75, 0.5); let desert_sand = Rgb::new(0.7, 0.4, 0.25);
let snow = Rgb::new(0.8, 0.85, 1.0); let snow = Rgb::new(0.8, 0.85, 1.0);
let stone_col = Rgb::new(195, 187, 201); let stone_col = Rgb::new(195, 187, 201);
@ -946,34 +946,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
(alt, ground, sub_surface_color) (alt, ground, sub_surface_color)
}; };
// Caves
let cave_at = |wposf: Vec2<f64>| {
(sim.gen_ctx.cave_0_nz.get(
Vec3::new(wposf.x, wposf.y, alt as f64 * 8.0)
.div(800.0)
.into_array(),
) as f32)
.powf(2.0)
.neg()
.add(1.0)
.mul((1.32 - chaos).min(1.0))
};
let cave_xy = cave_at(wposf);
let cave_alt = alt - 24.0
+ (sim
.gen_ctx
.cave_1_nz
.get(Vec2::new(wposf.x, wposf.y).div(48.0).into_array()) as f32)
* 8.0
+ (sim
.gen_ctx
.cave_1_nz
.get(Vec2::new(wposf.x, wposf.y).div(500.0).into_array()) as f32)
.add(1.0)
.mul(0.5)
.powf(15.0)
.mul(150.0);
let near_ocean = max_river.and_then(|(_, _, river_data, _)| { let near_ocean = max_river.and_then(|(_, _, river_data, _)| {
if (river_data.is_lake() || river_data.river_kind == Some(RiverKind::Ocean)) if (river_data.is_lake() || river_data.river_kind == Some(RiverKind::Ocean))
&& ((alt <= water_level.max(CONFIG.sea_level + 5.0) && !is_cliffs) || !near_cliffs) && ((alt <= water_level.max(CONFIG.sea_level + 5.0) && !is_cliffs) || !near_cliffs)
@ -991,6 +963,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
}; };
let path = sim.get_nearest_path(wpos); let path = sim.get_nearest_path(wpos);
let cave = sim.get_nearest_cave(wpos);
Some(ColumnSample { Some(ColumnSample {
alt, alt,
@ -1000,12 +973,16 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
water_level, water_level,
warp_factor, warp_factor,
surface_color: Rgb::lerp( surface_color: Rgb::lerp(
sub_surface_color,
Rgb::lerp(
Rgb::lerp(cliff, sand, alt.sub(basement).mul(0.25)), Rgb::lerp(cliff, sand, alt.sub(basement).mul(0.25)),
// Land // Land
ground, ground,
// Beach // Beach
((ocean_level - 1.0) / 2.0).max(0.0), ((ocean_level - 1.0) / 2.0).max(0.0),
), ),
surface_veg,
),
sub_surface_color, sub_surface_color,
// No growing directly on bedrock. // No growing directly on bedrock.
// And, no growing on sites that don't want them TODO: More precise than this when we // And, no growing on sites that don't want them TODO: More precise than this when we
@ -1013,7 +990,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
tree_density: if sim_chunk tree_density: if sim_chunk
.sites .sites
.iter() .iter()
.all(|site| site.spawn_rules(wpos).trees) .all(|site| index.sites[*site].spawn_rules(wpos).trees)
{ {
Lerp::lerp(0.0, tree_density, alt.sub(2.0).sub(basement).mul(0.5)) Lerp::lerp(0.0, tree_density, alt.sub(2.0).sub(basement).mul(0.5))
} else { } else {
@ -1021,8 +998,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
}, },
forest_kind: sim_chunk.forest_kind, forest_kind: sim_chunk.forest_kind,
close_structures: self.gen_close_structures(wpos), close_structures: self.gen_close_structures(wpos),
cave_xy,
cave_alt,
marble, marble,
marble_small, marble_small,
rock, rock,
@ -1036,6 +1011,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
stone_col, stone_col,
water_dist, water_dist,
path, path,
cave,
chunk: sim_chunk, chunk: sim_chunk,
}) })
@ -1055,8 +1031,6 @@ pub struct ColumnSample<'a> {
pub tree_density: f32, pub tree_density: f32,
pub forest_kind: ForestKind, pub forest_kind: ForestKind,
pub close_structures: [Option<StructureData>; 9], pub close_structures: [Option<StructureData>; 9],
pub cave_xy: f32,
pub cave_alt: f32,
pub marble: f32, pub marble: f32,
pub marble_small: f32, pub marble_small: f32,
pub rock: f32, pub rock: f32,
@ -1069,7 +1043,8 @@ pub struct ColumnSample<'a> {
pub spawn_rate: f32, pub spawn_rate: f32,
pub stone_col: Rgb<u8>, pub stone_col: Rgb<u8>,
pub water_dist: Option<f32>, pub water_dist: Option<f32>,
pub path: Option<(f32, Vec2<f32>)>, pub path: Option<(f32, Vec2<f32>, Path, Vec2<f32>)>,
pub cave: Option<(f32, Vec2<f32>, Cave, Vec2<f32>)>,
pub chunk: &'a SimChunk, pub chunk: &'a SimChunk,
} }

36
world/src/index.rs Normal file
View File

@ -0,0 +1,36 @@
use crate::site::Site;
use common::store::Store;
use noise::{Seedable, SuperSimplex};
pub struct Index {
pub seed: u32,
pub time: f32,
pub noise: Noise,
pub sites: Store<Site>,
}
impl Index {
pub fn new(seed: u32) -> Self {
Self {
seed,
time: 0.0,
noise: Noise::new(seed),
sites: Store::default(),
}
}
}
pub struct Noise {
pub cave_nz: SuperSimplex,
pub scatter_nz: SuperSimplex,
}
impl Noise {
#[allow(clippy::identity_op)]
fn new(seed: u32) -> Self {
Self {
cave_nz: SuperSimplex::new().set_seed(seed + 0),
scatter_nz: SuperSimplex::new().set_seed(seed + 1),
}
}
}

View File

@ -1,18 +1,156 @@
use crate::{ use crate::{
column::ColumnSample, column::ColumnSample,
sim::SimChunk,
util::{RandomField, Sampler}, util::{RandomField, Sampler},
Index,
}; };
use common::{ use common::{
assets, comp,
generation::{ChunkSupplement, EntityInfo},
lottery::Lottery,
terrain::{Block, BlockKind}, terrain::{Block, BlockKind},
vol::{BaseVol, ReadVol, RectSizedVol, Vox, WriteVol}, vol::{BaseVol, ReadVol, RectSizedVol, Vox, WriteVol},
}; };
use std::f32; use noise::NoiseFn;
use rand::prelude::*;
use std::{
f32,
ops::{Mul, Sub},
};
use vek::*; use vek::*;
fn close(x: f32, tgt: f32, falloff: f32) -> f32 {
(1.0 - (x - tgt).abs() / falloff).max(0.0).powf(0.5)
}
pub fn apply_scatter_to<'a>(
wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
index: &Index,
chunk: &SimChunk,
) {
use BlockKind::*;
#[allow(clippy::type_complexity)]
let scatter: &[(_, bool, fn(&SimChunk) -> (f32, Option<(f32, f32)>))] = &[
// (density, Option<(wavelen, threshold)>)
(BlueFlower, false, |c| {
(
close(c.temp, -0.3, 0.7).min(close(c.humidity, 0.6, 0.35)) * 0.05,
Some((48.0, 0.6)),
)
}),
(PinkFlower, false, |c| {
(
close(c.temp, 0.15, 0.5).min(close(c.humidity, 0.6, 0.35)) * 0.05,
Some((48.0, 0.6)),
)
}),
(DeadBush, false, |c| {
(
close(c.temp, 0.8, 0.3).min(close(c.humidity, 0.0, 0.4)) * 0.015,
None,
)
}),
(Twigs, false, |c| {
((c.tree_density - 0.5).max(0.0) * 0.0025, None)
}),
(Stones, false, |c| {
((c.rockiness - 0.5).max(0.0) * 0.005, None)
}),
(ShortGrass, false, |c| {
(
close(c.temp, 0.3, 0.4).min(close(c.humidity, 0.6, 0.35)) * 0.05,
Some((48.0, 0.4)),
)
}),
(MediumGrass, false, |c| {
(
close(c.temp, 0.0, 0.6).min(close(c.humidity, 0.6, 0.35)) * 0.05,
Some((48.0, 0.2)),
)
}),
(LongGrass, false, |c| {
(
close(c.temp, 0.4, 0.4).min(close(c.humidity, 0.8, 0.2)) * 0.05,
Some((48.0, 0.1)),
)
}),
(GrassSnow, false, |c| {
(
close(c.temp, -0.4, 0.4).min(close(c.rockiness, 0.0, 0.5)) * 0.1,
Some((48.0, 0.6)),
)
}),
];
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 underwater = col_sample.water_level > col_sample.alt;
let bk = scatter
.iter()
.enumerate()
.find_map(|(i, (bk, is_underwater, f))| {
let (density, patch) = f(chunk);
let is_patch = patch
.map(|(wavelen, threshold)| {
index.noise.scatter_nz.get(
wpos2d
.map(|e| e as f64 / wavelen as f64 + i as f64 * 43.0)
.into_array(),
) < threshold as f64
})
.unwrap_or(false);
if density <= 0.0
|| is_patch
|| !RandomField::new(i as u32)
.chance(Vec3::new(wpos2d.x, wpos2d.y, 0), density)
|| underwater != *is_underwater
{
None
} else {
Some(*bk)
}
});
if let Some(bk) = bk {
let mut z = col_sample.alt as i32 - 4;
for _ in 0..8 {
if vol
.get(Vec3::new(offs.x, offs.y, z))
.map(|b| !b.is_solid())
.unwrap_or(true)
{
let _ = vol.set(
Vec3::new(offs.x, offs.y, z),
Block::new(bk, Rgb::broadcast(0)),
);
break;
}
z += 1;
}
}
}
}
}
pub fn apply_paths_to<'a>( pub fn apply_paths_to<'a>(
wpos2d: Vec2<i32>, wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>, mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol), vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
_index: &Index,
) { ) {
for y in 0..vol.size_xy().y as i32 { for y in 0..vol.size_xy().y as i32 {
for x in 0..vol.size_xy().x as i32 { for x in 0..vol.size_xy().x as i32 {
@ -37,7 +175,9 @@ 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, path, _)) = col_sample
.path
.filter(|(dist, _, path, _)| *dist < path.width)
{ {
let inset = 0; let inset = 0;
@ -75,14 +215,14 @@ pub fn apply_paths_to<'a>(
if bridge_offset >= 2.0 && path_dist >= 3.0 || z < inset - 1 { if bridge_offset >= 2.0 && path_dist >= 3.0 || z < inset - 1 {
Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 80, 100), 8)) Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 80, 100), 8))
} else { } else {
let path_color = col_sample let path_color = path.surface_color(
.sub_surface_color col_sample.sub_surface_color.map(|e| (e * 255.0) as u8),
.map(|e| (e * 255.0 * 0.7) as u8); );
Block::new(BlockKind::Normal, noisy_color(path_color, 8)) 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); let head_space = path.head_space(path_dist);
for z in inset..inset + head_space { for z in inset..inset + head_space {
let pos = Vec3::new(offs.x, offs.y, surface_z + z); let pos = Vec3::new(offs.x, offs.y, surface_z + z);
if vol.get(pos).unwrap().kind() != BlockKind::Water { if vol.get(pos).unwrap().kind() != BlockKind::Water {
@ -93,3 +233,194 @@ pub fn apply_paths_to<'a>(
} }
} }
} }
pub fn apply_caves_to<'a>(
wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
index: &Index,
) {
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;
if let Some((cave_dist, _, cave, _)) = col_sample
.cave
.filter(|(dist, _, cave, _)| *dist < cave.width)
{
let cave_x = (cave_dist / cave.width).min(1.0);
// Relative units
let cave_floor = 0.0 - 0.5 * (1.0 - cave_x.powf(2.0)).max(0.0).sqrt() * cave.width;
let cave_height = (1.0 - cave_x.powf(2.0)).max(0.0).sqrt() * cave.width;
// Abs units
let cave_base = (cave.alt + cave_floor) as i32;
let cave_roof = (cave.alt + cave_height) as i32;
for z in cave_base..cave_roof {
if cave_x < 0.95
|| index.noise.cave_nz.get(
Vec3::new(wpos2d.x, wpos2d.y, z)
.map(|e| e as f64 * 0.15)
.into_array(),
) < 0.0
{
let _ = vol.set(Vec3::new(offs.x, offs.y, z), Block::empty());
}
}
// Stalagtites
let stalagtites = index
.noise
.cave_nz
.get(wpos2d.map(|e| e as f64 * 0.125).into_array())
.sub(0.5)
.max(0.0)
.mul(
(col_sample.alt - cave_roof as f32 - 5.0)
.mul(0.15)
.clamped(0.0, 1.0) as f64,
)
.mul(45.0) as i32;
for z in cave_roof - stalagtites..cave_roof {
let _ = vol.set(
Vec3::new(offs.x, offs.y, z),
Block::new(BlockKind::Rock, Rgb::broadcast(200)),
);
}
let cave_depth = (col_sample.alt - cave.alt).max(0.0);
let difficulty = cave_depth / 100.0;
// Scatter things in caves
if RandomField::new(index.seed).chance(wpos2d.into(), 0.002 * difficulty.powf(1.5))
&& cave_base < surface_z as i32 - 25
{
let kind = *assets::load_expect::<Lottery<BlockKind>>("common.cave_scatter")
.choose_seeded(RandomField::new(index.seed + 1).get(wpos2d.into()));
let _ = vol.set(
Vec3::new(offs.x, offs.y, cave_base),
Block::new(kind, Rgb::zero()),
);
}
}
}
}
}
pub fn apply_caves_supplement<'a>(
rng: &mut impl Rng,
wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &(impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
index: &Index,
supplement: &mut ChunkSupplement,
) {
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;
if let Some((cave_dist, _, cave, _)) = col_sample
.cave
.filter(|(dist, _, cave, _)| *dist < cave.width)
{
let cave_x = (cave_dist / cave.width).min(1.0);
// Relative units
let cave_floor = 0.0 - 0.5 * (1.0 - cave_x.powf(2.0)).max(0.0).sqrt() * cave.width;
// Abs units
let cave_base = (cave.alt + cave_floor) as i32;
let cave_depth = (col_sample.alt - cave.alt).max(0.0);
let difficulty = cave_depth / 200.0;
// Scatter things in caves
if RandomField::new(index.seed).chance(wpos2d.into(), 0.00005 * difficulty)
&& cave_base < surface_z as i32 - 40
{
let entity = EntityInfo::at(Vec3::new(
wpos2d.x as f32,
wpos2d.y as f32,
cave_base as f32,
))
.with_alignment(comp::Alignment::Enemy)
.with_body(match rng.gen_range(0, 6) {
0 => {
let species = match rng.gen_range(0, 2) {
0 => comp::quadruped_small::Species::Truffler,
_ => comp::quadruped_small::Species::Hyena,
};
comp::quadruped_small::Body::random_with(rng, &species).into()
},
1 => {
let species = match rng.gen_range(0, 3) {
0 => comp::quadruped_medium::Species::Tarasque,
1 => comp::quadruped_medium::Species::Frostfang,
_ => comp::quadruped_medium::Species::Bonerattler,
};
comp::quadruped_medium::Body::random_with(rng, &species).into()
},
2 => {
let species = match rng.gen_range(0, 3) {
0 => comp::quadruped_low::Species::Maneater,
1 => comp::quadruped_low::Species::Rocksnapper,
_ => comp::quadruped_low::Species::Salamander,
};
comp::quadruped_low::Body::random_with(rng, &species).into()
},
3 => {
let species = match rng.gen_range(0, 3) {
0 => comp::critter::Species::Fungome,
1 => comp::critter::Species::Axolotl,
_ => comp::critter::Species::Rat,
};
comp::critter::Body::random_with(rng, &species).into()
},
4 => {
#[allow(clippy::match_single_binding)]
let species = match rng.gen_range(0, 1) {
_ => comp::golem::Species::StoneGolem,
};
comp::golem::Body::random_with(rng, &species).into()
},
_ => {
let species = match rng.gen_range(0, 4) {
0 => comp::biped_large::Species::Ogre,
1 => comp::biped_large::Species::Cyclops,
2 => comp::biped_large::Species::Wendigo,
_ => comp::biped_large::Species::Troll,
};
comp::biped_large::Body::random_with(rng, &species).into()
},
})
.with_automatic_name();
supplement.add_entity(entity);
}
}
}
}
}

View File

@ -15,8 +15,10 @@ mod block;
pub mod civ; pub mod civ;
mod column; mod column;
pub mod config; pub mod config;
pub mod index;
pub mod layer; pub mod layer;
pub mod sim; pub mod sim;
pub mod sim2;
pub mod site; pub mod site;
pub mod util; pub mod util;
@ -27,11 +29,13 @@ pub use column::ColumnSample;
use crate::{ use crate::{
column::ColumnGen, column::ColumnGen,
index::Index,
util::{Grid, Sampler}, util::{Grid, Sampler},
}; };
use common::{ use common::{
comp::{self, bird_medium, critter, quadruped_low, quadruped_medium, quadruped_small}, comp::{self, bird_medium, critter, quadruped_low, quadruped_medium, quadruped_small},
generation::{ChunkSupplement, EntityInfo}, generation::{ChunkSupplement, EntityInfo},
msg::server::WorldMapMsg,
terrain::{Block, BlockKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize}, terrain::{Block, BlockKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
vol::{ReadVol, RectVolSize, Vox, WriteVol}, vol::{ReadVol, RectVolSize, Vox, WriteVol},
}; };
@ -47,26 +51,35 @@ pub enum Error {
pub struct World { pub struct World {
sim: sim::WorldSim, sim: sim::WorldSim,
civs: civ::Civs, civs: civ::Civs,
index: Index,
} }
impl World { impl World {
pub fn generate(seed: u32, opts: sim::WorldOpts) -> Self { pub fn generate(seed: u32, opts: sim::WorldOpts) -> Self {
let mut sim = sim::WorldSim::generate(seed, opts); let mut sim = sim::WorldSim::generate(seed, opts);
let civs = civ::Civs::generate(seed, &mut sim); let mut index = Index::new(seed);
Self { sim, civs } let civs = civ::Civs::generate(seed, &mut sim, &mut index);
sim2::simulate(&mut index, &mut sim);
Self { sim, civs, index }
} }
pub fn sim(&self) -> &sim::WorldSim { &self.sim } pub fn sim(&self) -> &sim::WorldSim { &self.sim }
pub fn civs(&self) -> &civ::Civs { &self.civs } pub fn civs(&self) -> &civ::Civs { &self.civs }
pub fn index(&self) -> &Index { &self.index }
pub fn tick(&self, _dt: Duration) { pub fn tick(&self, _dt: Duration) {
// TODO // TODO
} }
pub fn get_map_data(&self) -> WorldMapMsg { self.sim.get_map(&self.index) }
pub fn sample_columns( pub fn sample_columns(
&self, &self,
) -> impl Sampler<Index = Vec2<i32>, Sample = Option<ColumnSample>> + '_ { ) -> impl Sampler<Index = (Vec2<i32>, &Index), Sample = Option<ColumnSample>> + '_ {
ColumnGen::new(&self.sim) ColumnGen::new(&self.sim)
} }
@ -85,7 +98,7 @@ impl World {
let grid_border = 4; let grid_border = 4;
let zcache_grid = Grid::populate_from( let zcache_grid = Grid::populate_from(
TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + grid_border * 2, TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + grid_border * 2,
|offs| sampler.get_z_cache(chunk_wpos2d - grid_border + offs), |offs| sampler.get_z_cache(chunk_wpos2d - grid_border + offs, &self.index),
); );
let air = Block::empty(); let air = Block::empty();
@ -140,7 +153,8 @@ impl World {
_ => continue, _ => continue,
}; };
let (min_z, only_structures_min_z, max_z) = z_cache.get_z_limits(&mut sampler); let (min_z, only_structures_min_z, max_z) =
z_cache.get_z_limits(&mut sampler, &self.index);
(base_z..min_z as i32).for_each(|z| { (base_z..min_z as i32).for_each(|z| {
let _ = chunk.set(Vec3::new(x, y, z), stone); let _ = chunk.set(Vec3::new(x, y, z), stone);
@ -152,7 +166,7 @@ impl World {
let only_structures = lpos.z >= only_structures_min_z as i32; let only_structures = lpos.z >= only_structures_min_z as i32;
if let Some(block) = if let Some(block) =
sampler.get_with_z_cache(wpos, Some(&z_cache), only_structures) sampler.get_with_z_cache(wpos, Some(&z_cache), only_structures, &self.index)
{ {
let _ = chunk.set(lpos, block); let _ = chunk.set(lpos, block);
} }
@ -170,14 +184,15 @@ impl World {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
// Apply site generation // Apply layers (paths, caves, etc.)
sim_chunk layer::apply_scatter_to(chunk_wpos2d, sample_get, &mut chunk, &self.index, sim_chunk);
.sites layer::apply_paths_to(chunk_wpos2d, sample_get, &mut chunk, &self.index);
.iter() layer::apply_caves_to(chunk_wpos2d, sample_get, &mut chunk, &self.index);
.for_each(|site| site.apply_to(chunk_wpos2d, sample_get, &mut chunk));
// Apply paths // Apply site generation
layer::apply_paths_to(chunk_wpos2d, sample_get, &mut chunk); sim_chunk.sites.iter().for_each(|site| {
self.index.sites[*site].apply_to(chunk_wpos2d, sample_get, &mut chunk)
});
let gen_entity_pos = || { let gen_entity_pos = || {
let lpos2d = TerrainChunkSize::RECT_SIZE let lpos2d = TerrainChunkSize::RECT_SIZE
@ -223,9 +238,24 @@ impl World {
supplement.add_entity(EntityInfo::at(gen_entity_pos()).into_waypoint()); supplement.add_entity(EntityInfo::at(gen_entity_pos()).into_waypoint());
} }
// Apply layer supplement
layer::apply_caves_supplement(
&mut rng,
chunk_wpos2d,
sample_get,
&chunk,
&self.index,
&mut supplement,
);
// Apply site supplementary information // Apply site supplementary information
sim_chunk.sites.iter().for_each(|site| { sim_chunk.sites.iter().for_each(|site| {
site.apply_supplement(&mut rng, chunk_wpos2d, sample_get, &mut supplement) self.index.sites[*site].apply_supplement(
&mut rng,
chunk_wpos2d,
sample_get,
&mut supplement,
)
}); });
Ok((chunk, supplement)) Ok((chunk, supplement))

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
column::ColumnSample, column::ColumnSample,
sim::{RiverKind, WorldSim}, sim::{RiverKind, WorldSim},
CONFIG, Index, CONFIG,
}; };
use common::{ use common::{
terrain::{ terrain::{
@ -66,6 +66,7 @@ pub fn sample_wpos(config: &MapConfig, sampler: &WorldSim, wpos: Vec2<i32>) -> f
pub fn sample_pos( pub fn sample_pos(
config: &MapConfig, config: &MapConfig,
sampler: &WorldSim, sampler: &WorldSim,
index: &Index,
samples: Option<&[Option<ColumnSample>]>, samples: Option<&[Option<ColumnSample>]>,
pos: Vec2<i32>, pos: Vec2<i32>,
) -> MapSample { ) -> MapSample {
@ -96,6 +97,7 @@ pub fn sample_pos(
river_kind, river_kind,
spline_derivative, spline_derivative,
is_path, is_path,
is_cave,
near_site, near_site,
) = sampler ) = sampler
.get(pos) .get(pos)
@ -110,9 +112,11 @@ pub fn sample_pos(
sample.downhill, sample.downhill,
sample.river.river_kind, sample.river.river_kind,
sample.river.spline_derivative, sample.river.spline_derivative,
sample.path.is_path(), sample.path.0.is_way(),
sample.cave.0.is_way(),
sample.sites.iter().any(|site| { sample.sites.iter().any(|site| {
site.get_origin() index.sites[*site]
.get_origin()
.distance_squared(pos * TerrainChunkSize::RECT_SIZE.x as i32) .distance_squared(pos * TerrainChunkSize::RECT_SIZE.x as i32)
< 64i32.pow(2) < 64i32.pow(2)
}), }),
@ -130,6 +134,7 @@ pub fn sample_pos(
Vec2::zero(), Vec2::zero(),
false, false,
false, false,
false,
)); ));
let humidity = humidity.min(1.0).max(0.0); let humidity = humidity.min(1.0).max(0.0);
@ -243,6 +248,8 @@ pub fn sample_pos(
Rgb::new(0x57, 0x39, 0x33) Rgb::new(0x57, 0x39, 0x33)
} else if is_path { } else if is_path {
Rgb::new(0x37, 0x29, 0x23) Rgb::new(0x37, 0x29, 0x23)
} else if is_cave {
Rgb::new(0x37, 0x37, 0x37)
} else { } else {
rgb rgb
}; };

View File

@ -2,8 +2,8 @@ mod diffusion;
mod erosion; mod erosion;
mod location; mod location;
mod map; mod map;
mod path;
mod util; mod util;
mod way;
// Reexports // Reexports
use self::erosion::Compute; use self::erosion::Compute;
@ -15,11 +15,11 @@ pub use self::{
}, },
location::Location, location::Location,
map::{sample_pos, sample_wpos}, map::{sample_pos, sample_wpos},
path::PathData,
util::{ util::{
cdf_irwin_hall, downhill, get_horizon_map, get_oceans, local_cells, map_edge_factor, cdf_irwin_hall, downhill, get_horizon_map, get_oceans, local_cells, map_edge_factor,
uniform_noise, uphill, InverseCdf, ScaleBias, uniform_noise, uphill, InverseCdf, ScaleBias,
}, },
way::{Cave, Path, Way},
}; };
use crate::{ use crate::{
@ -29,7 +29,7 @@ use crate::{
column::ColumnGen, column::ColumnGen,
site::Site, site::Site,
util::{seed_expan, FastNoise, RandomField, Sampler, StructureGen2d, LOCALITY, NEIGHBORS}, util::{seed_expan, FastNoise, RandomField, Sampler, StructureGen2d, LOCALITY, NEIGHBORS},
CONFIG, Index, CONFIG,
}; };
use common::{ use common::{
assets, assets,
@ -1408,7 +1408,7 @@ impl WorldSim {
/// Draw a map of the world based on chunk information. Returns a buffer of /// Draw a map of the world based on chunk information. Returns a buffer of
/// u32s. /// u32s.
pub fn get_map(&self) -> WorldMapMsg { pub fn get_map(&self, index: &Index) -> WorldMapMsg {
let mut map_config = MapConfig::orthographic( let mut map_config = MapConfig::orthographic(
self.map_size_lg(), self.map_size_lg(),
core::ops::RangeInclusive::new(CONFIG.sea_level, CONFIG.sea_level + self.max_height), core::ops::RangeInclusive::new(CONFIG.sea_level, CONFIG.sea_level + self.max_height),
@ -1434,7 +1434,8 @@ impl WorldSim {
|block_gen, posi| { |block_gen, posi| {
let wpos = uniform_idx_as_vec2(self.map_size_lg(), posi); let wpos = uniform_idx_as_vec2(self.map_size_lg(), posi);
let mut sample = column_sample.get( let mut sample = column_sample.get(
uniform_idx_as_vec2(self.map_size_lg(), posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32), (uniform_idx_as_vec2(self.map_size_lg(), posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
index)
)?; )?;
let alt = sample.alt; let alt = sample.alt;
/* let z_cache = block_gen.get_z_cache(wpos); /* let z_cache = block_gen.get_z_cache(wpos);
@ -1446,6 +1447,7 @@ impl WorldSim {
&sample.close_cliffs, &sample.close_cliffs,
sample.cliff_hill, sample.cliff_hill,
32.0, 32.0,
index,
)); ));
sample.basement += sample.alt - alt; sample.basement += sample.alt - alt;
// sample.water_level = CONFIG.sea_level.max(sample.water_level); // sample.water_level = CONFIG.sea_level.max(sample.water_level);
@ -1489,7 +1491,7 @@ impl WorldSim {
map_config.is_shaded = false; map_config.is_shaded = false;
map_config.generate( map_config.generate(
|pos| sample_pos(&map_config, self, Some(&samples_data), pos), |pos| sample_pos(&map_config, self, index, Some(&samples_data), pos),
|pos| sample_wpos(&map_config, self, pos), |pos| sample_wpos(&map_config, self, pos),
|pos, (r, g, b, _a)| { |pos, (r, g, b, _a)| {
// We currently ignore alpha and replace it with the height at pos, scaled to // We currently ignore alpha and replace it with the height at pos, scaled to
@ -1901,7 +1903,14 @@ impl WorldSim {
Some(z0 + z1 + z2 + z3) Some(z0 + z1 + z2 + z3)
} }
pub fn get_nearest_path(&self, wpos: Vec2<i32>) -> Option<(f32, Vec2<f32>)> { /// Return the distance to the nearest way in blocks, along with the
/// closest point on the way, the way metadata, and the tangent vector
/// of that way.
pub fn get_nearest_way<M: Clone + Lerp<Output = M>>(
&self,
wpos: Vec2<i32>,
get_way: impl Fn(&SimChunk) -> Option<(Way, M)>,
) -> Option<(f32, Vec2<f32>, M, Vec2<f32>)> {
let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| { let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| {
e.div_euclid(sz as i32) e.div_euclid(sz as i32)
}); });
@ -1911,32 +1920,35 @@ impl WorldSim {
}) })
}; };
let get_way = &get_way;
LOCALITY LOCALITY
.iter() .iter()
.filter_map(|ctrl| { .filter_map(|ctrl| {
let chunk = self.get(chunk_pos + *ctrl)?; let (way, meta) = get_way(self.get(chunk_pos + *ctrl)?)?;
let ctrl_pos = let ctrl_pos = get_chunk_centre(chunk_pos + *ctrl).map(|e| e as f32)
get_chunk_centre(chunk_pos + *ctrl).map(|e| e as f32) + chunk.path.offset; + way.offset.map(|e| e as f32);
let chunk_connections = chunk.path.neighbors.count_ones(); let chunk_connections = way.neighbors.count_ones();
if chunk_connections == 0 { if chunk_connections == 0 {
return None; return None;
} }
let (start_pos, _start_idx) = if chunk_connections != 2 { let (start_pos, _start_idx, start_meta) = if chunk_connections != 2 {
(ctrl_pos, None) (ctrl_pos, None, meta.clone())
} else { } else {
let (start_idx, start_rpos) = NEIGHBORS let (start_idx, start_rpos) = NEIGHBORS
.iter() .iter()
.copied() .copied()
.enumerate() .enumerate()
.find(|(i, _)| chunk.path.neighbors & (1 << *i as u8) != 0) .find(|(i, _)| way.neighbors & (1 << *i as u8) != 0)
.unwrap(); .unwrap();
let start_pos_chunk = chunk_pos + *ctrl + start_rpos; let start_pos_chunk = chunk_pos + *ctrl + start_rpos;
let (start_way, start_meta) = get_way(self.get(start_pos_chunk)?)?;
( (
get_chunk_centre(start_pos_chunk).map(|e| e as f32) get_chunk_centre(start_pos_chunk).map(|e| e as f32)
+ self.get(start_pos_chunk)?.path.offset, + start_way.offset.map(|e| e as f32),
Some(start_idx), Some(start_idx),
start_meta,
) )
}; };
@ -1944,11 +1956,12 @@ impl WorldSim {
NEIGHBORS NEIGHBORS
.iter() .iter()
.enumerate() .enumerate()
.filter(move |(i, _)| chunk.path.neighbors & (1 << *i as u8) != 0) .filter(move |(i, _)| way.neighbors & (1 << *i as u8) != 0)
.filter_map(move |(_, end_rpos)| { .filter_map(move |(_, end_rpos)| {
let end_pos_chunk = chunk_pos + *ctrl + end_rpos; let end_pos_chunk = chunk_pos + *ctrl + end_rpos;
let (end_way, end_meta) = get_way(self.get(end_pos_chunk)?)?;
let end_pos = get_chunk_centre(end_pos_chunk).map(|e| e as f32) let end_pos = get_chunk_centre(end_pos_chunk).map(|e| e as f32)
+ self.get(end_pos_chunk)?.path.offset; + end_way.offset.map(|e| e as f32);
let bez = QuadraticBezier2 { let bez = QuadraticBezier2 {
start: (start_pos + ctrl_pos) / 2.0, start: (start_pos + ctrl_pos) / 2.0,
@ -1961,13 +1974,28 @@ impl WorldSim {
.clamped(0.0, 1.0); .clamped(0.0, 1.0);
let pos = bez.evaluate(nearest_interval); let pos = bez.evaluate(nearest_interval);
let dist_sqrd = pos.distance_squared(wpos.map(|e| e as f32)); let dist_sqrd = pos.distance_squared(wpos.map(|e| e as f32));
Some((dist_sqrd, pos)) let meta = if nearest_interval < 0.5 {
Lerp::lerp(start_meta.clone(), meta.clone(), 0.5 + nearest_interval)
} else {
Lerp::lerp(meta.clone(), end_meta, nearest_interval - 0.5)
};
Some((dist_sqrd, pos, meta, move || {
bez.evaluate_derivative(nearest_interval).normalized()
}))
}), }),
) )
}) })
.flatten() .flatten()
.min_by_key(|(dist_sqrd, _)| (dist_sqrd * 1024.0) as i32) .min_by_key(|(dist_sqrd, _, _, _)| (dist_sqrd * 1024.0) as i32)
.map(|(dist, pos)| (dist.sqrt(), pos)) .map(|(dist, pos, meta, calc_tangent)| (dist.sqrt(), pos, meta, calc_tangent()))
}
pub fn get_nearest_path(&self, wpos: Vec2<i32>) -> Option<(f32, Vec2<f32>, Path, Vec2<f32>)> {
self.get_nearest_way(wpos, |chunk| Some(chunk.path))
}
pub fn get_nearest_cave(&self, wpos: Vec2<i32>) -> Option<(f32, Vec2<f32>, Cave, Vec2<f32>)> {
self.get_nearest_way(wpos, |chunk| Some(chunk.cave))
} }
} }
@ -1989,10 +2017,14 @@ pub struct SimChunk {
pub spawn_rate: f32, pub spawn_rate: f32,
pub river: RiverData, pub river: RiverData,
pub warp_factor: f32, pub warp_factor: f32,
pub surface_veg: f32,
pub sites: Vec<Site>, pub sites: Vec<Id<Site>>,
pub place: Option<Id<Place>>, pub place: Option<Id<Place>>,
pub path: PathData,
pub path: (Way, Path),
pub cave: (Way, Cave),
pub contains_waypoint: bool, pub contains_waypoint: bool,
} }
@ -2223,10 +2255,12 @@ impl SimChunk {
spawn_rate: 1.0, spawn_rate: 1.0,
river, river,
warp_factor: 1.0, warp_factor: 1.0,
surface_veg: 1.0,
sites: Vec::new(), sites: Vec::new(),
place: None, place: None,
path: PathData::default(), path: Default::default(),
cave: Default::default(),
contains_waypoint: false, contains_waypoint: false,
} }
} }

View File

@ -1,21 +0,0 @@
use vek::*;
#[derive(Debug)]
pub struct PathData {
pub offset: Vec2<f32>, /* Offset from centre of chunk: must not be more than half chunk
* width in any direction */
pub neighbors: u8, // One bit for each neighbor
}
impl PathData {
pub fn is_path(&self) -> bool { self.neighbors != 0 }
}
impl Default for PathData {
fn default() -> Self {
Self {
offset: Vec2::zero(),
neighbors: 0,
}
}
}

72
world/src/sim/way.rs Normal file
View File

@ -0,0 +1,72 @@
use vek::*;
#[derive(Copy, Clone, Debug, Default)]
pub struct Way {
/// Offset from chunk center in blocks (no more than half chunk width)
pub offset: Vec2<i8>,
/// Neighbor connections, one bit each
pub neighbors: u8,
}
impl Way {
pub fn is_way(&self) -> bool { self.neighbors != 0 }
pub fn clear(&mut self) { self.neighbors = 0; }
}
#[derive(Copy, Clone, Debug)]
pub struct Path {
pub width: f32, // Actually radius
}
impl Default for Path {
fn default() -> Self { Self { width: 5.0 } }
}
impl Lerp for Path {
type Output = Self;
fn lerp_unclamped(from: Self, to: Self, factor: f32) -> Self::Output {
Self {
width: Lerp::lerp(from.width, to.width, factor),
}
}
}
impl Path {
/// Return the number of blocks of headspace required at the given path
/// distance
/// TODO: make this generic over width
pub fn head_space(&self, dist: f32) -> i32 {
(8 - (dist * 0.25).powf(6.0).round() as i32).max(1)
}
/// Get the surface colour of a path given the surrounding surface color
pub fn surface_color(&self, col: Rgb<u8>) -> Rgb<u8> { col.map(|e| (e as f32 * 0.7) as u8) }
}
#[derive(Copy, Clone, Debug)]
pub struct Cave {
pub width: f32, // Actually radius
pub alt: f32, // Actually radius
}
impl Default for Cave {
fn default() -> Self {
Self {
width: 32.0,
alt: 0.0,
}
}
}
impl Lerp for Cave {
type Output = Self;
fn lerp_unclamped(from: Self, to: Self, factor: f32) -> Self::Output {
Self {
width: Lerp::lerp(from.width, to.width, factor),
alt: Lerp::lerp(from.alt, to.alt, factor),
}
}
}

286
world/src/sim2/mod.rs Normal file
View File

@ -0,0 +1,286 @@
use crate::{
sim::WorldSim,
site::{
economy::{Good, Labor},
Site,
},
util::MapVec,
Index,
};
use common::store::Id;
use tracing::debug;
const MONTH: f32 = 30.0;
const YEAR: f32 = 12.0 * MONTH;
const TICK_PERIOD: f32 = 3.0 * MONTH; // 3 months
const HISTORY_DAYS: f32 = 500.0 * YEAR; // 500 years
const GENERATE_CSV: bool = false;
pub fn simulate(index: &mut Index, world: &mut WorldSim) {
use std::io::Write;
let mut f = if GENERATE_CSV {
let mut f = std::fs::File::create("economy.csv").unwrap();
write!(f, "Population,").unwrap();
for g in Good::list() {
write!(f, "{:?} Value,", g).unwrap();
}
for g in Good::list() {
write!(f, "{:?} LaborVal,", g).unwrap();
}
for g in Good::list() {
write!(f, "{:?} Stock,", g).unwrap();
}
for g in Good::list() {
write!(f, "{:?} Surplus,", g).unwrap();
}
for l in Labor::list() {
write!(f, "{:?} Labor,", l).unwrap();
}
for l in Labor::list() {
write!(f, "{:?} Productivity,", l).unwrap();
}
for l in Labor::list() {
write!(f, "{:?} Yields,", l).unwrap();
}
writeln!(f).unwrap();
Some(f)
} else {
None
};
for i in 0..(HISTORY_DAYS / TICK_PERIOD) as i32 {
if (index.time / YEAR) as i32 % 50 == 0 && (index.time % YEAR) as i32 == 0 {
debug!("Year {}", (index.time / YEAR) as i32);
}
tick(index, world, TICK_PERIOD);
if let Some(f) = f.as_mut() {
if i % 5 == 0 {
let site = index.sites.values().next().unwrap();
write!(f, "{},", site.economy.pop).unwrap();
for g in Good::list() {
write!(f, "{:?},", site.economy.values[*g].unwrap_or(-1.0)).unwrap();
}
for g in Good::list() {
write!(f, "{:?},", site.economy.labor_values[*g].unwrap_or(-1.0)).unwrap();
}
for g in Good::list() {
write!(f, "{:?},", site.economy.stocks[*g]).unwrap();
}
for g in Good::list() {
write!(f, "{:?},", site.economy.marginal_surplus[*g]).unwrap();
}
for l in Labor::list() {
write!(f, "{:?},", site.economy.labors[*l] * site.economy.pop).unwrap();
}
for l in Labor::list() {
write!(f, "{:?},", site.economy.productivity[*l]).unwrap();
}
for l in Labor::list() {
write!(f, "{:?},", site.economy.yields[*l]).unwrap();
}
writeln!(f).unwrap();
}
}
}
}
pub fn tick(index: &mut Index, _world: &mut WorldSim, dt: f32) {
for site in index.sites.ids() {
tick_site_economy(index, site, dt);
}
index.time += dt;
}
/// Simulate a site's economy. This simulation is roughly equivalent to the
/// Lange-Lerner model's solution to the socialist calculation problem. The
/// simulation begins by assigning arbitrary values to each commodity and then
/// incrementally updates them according to the final scarcity of the commodity
/// at the end of the tick. This results in the formulation of values that are
/// roughly analgous to prices for each commodity. The workforce is then
/// reassigned according to the respective commodity values. The simulation also
/// includes damping terms that prevent cyclical inconsistencies in value
/// rationalisation magnifying enough to crash the economy. We also ensure that
/// a small number of workers are allocated to every industry (even inactive
/// ones) each tick. This is not an accident: a small amount of productive
/// capacity in one industry allows the economy to quickly pivot to a different
/// prodution configuration should an additional commodity that acts as
/// production input become available. This means that the economy will
/// dynamically react to environmental changes. If a product becomes available
/// through a mechanism such as trade, an entire arm of the economy may
/// materialise to take advantage of this.
pub fn tick_site_economy(index: &mut Index, site: Id<Site>, dt: f32) {
let site = &mut index.sites[site];
let orders = site.economy.get_orders();
let productivity = site.economy.get_productivity();
let mut demand = MapVec::from_default(0.0);
for (labor, orders) in &orders {
let scale = if let Some(labor) = labor {
site.economy.labors[*labor]
} else {
1.0
} * site.economy.pop;
for (good, amount) in orders {
demand[*good] += *amount * scale;
}
}
let mut supply = site.economy.stocks.clone(); //MapVec::from_default(0.0);
for (labor, (output_good, _)) in productivity.iter() {
supply[*output_good] +=
site.economy.yields[labor] * site.economy.labors[labor] * site.economy.pop;
}
let stocks = &site.economy.stocks;
site.economy.surplus = demand
.clone()
.map(|g, demand| supply[g] + stocks[g] - demand);
site.economy.marginal_surplus = demand.clone().map(|g, demand| supply[g] - demand);
// Update values according to the surplus of each stock
// Note that values are used for workforce allocation and are not the same thing
// as price
let values = &mut site.economy.values;
site.economy.surplus.iter().for_each(|(good, surplus)| {
// Value rationalisation
let val = 2.0f32.powf(1.0 - *surplus / demand[good]);
let smooth = 0.8;
values[good] = if val > 0.001 && val < 1000.0 {
Some(smooth * values[good].unwrap_or(val) + (1.0 - smooth) * val)
} else {
None
};
});
// Update export targets based on relative values
// let value_avg = values
// .iter()
// .map(|(_, v)| (*v).unwrap_or(0.0))
// .sum::<f32>()
// .max(0.01)
// / values.iter().filter(|(_, v)| v.is_some()).count() as f32;
//let export_targets = &mut site.economy.export_targets;
//let last_exports = &self.last_exports;
// site.economy.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 pop = site.economy.pop;
// Redistribute workforce according to relative good values
let labor_ratios = productivity.clone().map(|labor, (output_good, _)| {
site.economy.values[output_good].unwrap_or(0.0)
* site.economy.productivity[labor]
//(site.economy.prices[output_good] - site.economy.material_costs[output_good]) * site.economy.yields[labor]
//* demand[output_good] / supply[output_good].max(0.001)
});
let labor_ratio_sum = labor_ratios.iter().map(|(_, r)| *r).sum::<f32>().max(0.01);
productivity.iter().for_each(|(labor, _)| {
let smooth = 0.8;
site.economy.labors[labor] = smooth * site.economy.labors[labor]
+ (1.0 - smooth)
* (labor_ratios[labor].max(labor_ratio_sum / 1000.0) / labor_ratio_sum);
});
// Production
let stocks_before = site.economy.stocks.clone();
let mut total_labor_values = MapVec::<_, f32>::default();
let mut total_outputs = MapVec::<_, f32>::default();
for (labor, orders) in orders.iter() {
let scale = if let Some(labor) = labor {
site.economy.labors[*labor]
} else {
1.0
} * site.economy.pop;
// 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 labor_productivity = orders
.iter()
.map(|(good, amount)| {
// What quantity is this order requesting?
let _quantity = *amount * scale;
// What proportion of this order is the economy able to satisfy?
(stocks_before[*good] / demand[*good]).min(1.0)
})
.min_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap_or_else(|| panic!("Industry {:?} requires at least one input order", labor));
let mut total_materials_cost = 0.0;
for (good, amount) in orders {
// What quantity is this order requesting?
let quantity = *amount * scale;
// What amount gets actually used in production?
let used = quantity * labor_productivity;
// Material cost of each factor of production
total_materials_cost += used * site.economy.labor_values[*good].unwrap_or(0.0);
// Deplete stocks accordingly
site.economy.stocks[*good] = (site.economy.stocks[*good] - used).max(0.0);
}
// Industries produce things
if let Some(labor) = labor {
let (stock, rate) = productivity[*labor];
let workers = site.economy.labors[*labor] * site.economy.pop;
let final_rate = rate;
let yield_per_worker =
labor_productivity * final_rate * (1.0 + workers / 100.0).min(3.0);
site.economy.yields[*labor] = yield_per_worker;
site.economy.productivity[*labor] = labor_productivity;
let total_output = yield_per_worker * workers;
site.economy.stocks[stock] += total_output;
// Materials cost per unit
site.economy.material_costs[stock] = total_materials_cost / total_output.max(0.001);
// Labor costs
let wages = 1.0;
let total_labor_cost = workers * wages;
total_labor_values[stock] += total_materials_cost + total_labor_cost;
total_outputs[stock] += total_output;
}
}
// Update labour values per unit
site.economy.labor_values = total_labor_values.map(|stock, tlv| {
let total_output = total_outputs[stock];
if total_output > 0.01 {
Some(tlv / total_outputs[stock])
} else {
None
}
});
// Decay stocks
site.economy
.stocks
.iter_mut()
.for_each(|(c, v)| *v *= 1.0 - c.decay_rate());
// Decay stocks
site.economy.replenish(index.time);
// Births/deaths
const NATURAL_BIRTH_RATE: f32 = 0.05;
const DEATH_RATE: f32 = 0.005;
let birth_rate = if site.economy.surplus[Good::Food] > 0.0 {
NATURAL_BIRTH_RATE
} else {
0.0
};
site.economy.pop += dt / YEAR * site.economy.pop * (birth_rate - DEATH_RATE);
}

View File

@ -0,0 +1,39 @@
use common::{terrain::Block, vol::Vox};
#[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<Block> {
if self.priority > 0 {
Some(self.block)
} else {
None
}
}
}

View File

@ -0,0 +1,416 @@
use super::SpawnRules;
use crate::{
column::ColumnSample,
sim::WorldSim,
site::settlement::building::{
archetype::keep::{Attr, Keep as KeepArchetype},
Archetype, Ori,
},
};
use common::{
generation::ChunkSupplement,
terrain::{Block, BlockKind},
vol::{BaseVol, ReadVol, RectSizedVol, Vox, WriteVol},
};
use core::f32;
use rand::prelude::*;
use vek::*;
struct Keep {
offset: Vec2<i32>,
locus: i32,
storeys: i32,
is_tower: bool,
alt: i32,
}
struct Tower {
offset: Vec2<i32>,
alt: i32,
}
pub struct Castle {
origin: Vec2<i32>,
//seed: u32,
radius: i32,
towers: Vec<Tower>,
keeps: Vec<Keep>,
rounded_towers: bool,
ridged: bool,
flags: bool,
evil: bool,
}
pub struct GenCtx<'a, R: Rng> {
sim: Option<&'a mut WorldSim>,
rng: &'a mut R,
}
impl Castle {
#[allow(clippy::let_and_return)] // TODO: Pending review in #587
pub fn generate(wpos: Vec2<i32>, sim: Option<&mut WorldSim>, rng: &mut impl Rng) -> Self {
let ctx = GenCtx { sim, rng };
let boundary_towers = ctx.rng.gen_range(5, 10);
let keep_count = ctx.rng.gen_range(1, 4);
let boundary_noise = ctx.rng.gen_range(-2i32, 8).max(1) as f32;
let radius = 150;
let this = Self {
origin: wpos,
// alt: ctx
// .sim
// .as_ref()
// .and_then(|sim| sim.get_alt_approx(wpos))
// .unwrap_or(0.0) as i32
// + 6,
//seed: ctx.rng.gen(),
radius,
towers: (0..boundary_towers)
.map(|i| {
let angle = (i as f32 / boundary_towers as f32) * f32::consts::PI * 2.0;
let dir = Vec2::new(angle.cos(), angle.sin());
let dist = radius as f32 + ((angle * boundary_noise).sin() - 1.0) * 40.0;
let mut offset = (dir * dist).map(|e| e as i32);
// Try to move the tower around until it's not intersecting a path
for i in (1..80).step_by(5) {
if ctx
.sim
.as_ref()
.and_then(|sim| sim.get_nearest_path(wpos + offset))
.map(|(dist, _, _, _)| dist > 24.0)
.unwrap_or(true)
{
break;
}
offset = (dir * dist)
.map(|e| (e + ctx.rng.gen_range(-1.0, 1.0) * i as f32) as i32);
}
Tower {
offset,
alt: ctx
.sim
.as_ref()
.and_then(|sim| sim.get_alt_approx(wpos + offset))
.unwrap_or(0.0) as i32
+ 2,
}
})
.collect(),
rounded_towers: ctx.rng.gen(),
ridged: ctx.rng.gen(),
flags: ctx.rng.gen(),
evil: ctx.rng.gen(),
keeps: (0..keep_count)
.map(|i| {
let angle = (i as f32 / keep_count as f32) * f32::consts::PI * 2.0;
let dir = Vec2::new(angle.cos(), angle.sin());
let dist =
(radius as f32 + ((angle * boundary_noise).sin() - 1.0) * 40.0) * 0.3;
let locus = ctx.rng.gen_range(20, 26);
let offset = (dir * dist).map(|e| e as i32);
let storeys = ctx.rng.gen_range(1, 8).clamped(3, 5);
Keep {
offset,
locus,
storeys,
is_tower: true,
alt: ctx
.sim
.as_ref()
.and_then(|sim| sim.get_alt_approx(wpos + offset))
.unwrap_or(0.0) as i32
+ 2,
}
})
.collect(),
};
this
}
pub fn contains_point(&self, wpos: Vec2<i32>) -> bool {
let lpos = wpos - self.origin;
for i in 0..self.towers.len() {
let tower0 = &self.towers[i];
let tower1 = &self.towers[(i + 1) % self.towers.len()];
if lpos.determine_side(Vec2::zero(), tower0.offset) > 0
&& lpos.determine_side(Vec2::zero(), tower1.offset) <= 0
&& lpos.determine_side(tower0.offset, tower1.offset) > 0
{
return true;
}
}
false
}
pub fn get_origin(&self) -> Vec2<i32> { self.origin }
pub fn radius(&self) -> f32 { 1200.0 }
#[allow(clippy::needless_update)] // TODO: Pending review in #587
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
SpawnRules {
trees: wpos.distance_squared(self.origin) > self.radius.pow(2),
..SpawnRules::default()
}
}
pub fn apply_to<'a>(
&'a self,
wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
) {
for y in 0..vol.size_xy().y as i32 {
for x in 0..vol.size_xy().x as i32 {
let offs = Vec2::new(x, y);
let wpos2d = wpos2d + offs;
let rpos = wpos2d - self.origin;
if rpos.magnitude_squared() > (self.radius + 64).pow(2) {
continue;
}
let col_sample = if let Some(col) = get_column(offs) {
col
} else {
continue;
};
// Inner ground
if self.contains_point(wpos2d) {
let surface_z = col_sample.alt as i32;
for z in -5..3 {
let pos = Vec3::new(offs.x, offs.y, surface_z + z);
if z > 0 {
if vol.get(pos).unwrap().kind() != BlockKind::Water {
let _ = vol.set(pos, Block::empty());
}
} else {
let _ = vol.set(
pos,
Block::new(
BlockKind::Normal,
col_sample.sub_surface_color.map(|e| (e * 255.0) as u8),
),
);
}
}
}
let (wall_dist, wall_pos, wall_alt, wall_ori, _towers) = (0..self.towers.len())
.map(|i| {
let tower0 = &self.towers[i];
let tower1 = &self.towers[(i + 1) % self.towers.len()];
let wall = LineSegment2 {
start: tower0.offset.map(|e| e as f32),
end: tower1.offset.map(|e| e as f32),
};
let projected = wall
.projected_point(rpos.map(|e| e as f32))
.map(|e| e.floor() as i32);
let tower0_dist = tower0
.offset
.map(|e| e as f32)
.distance(projected.map(|e| e as f32));
let tower1_dist = tower1
.offset
.map(|e| e as f32)
.distance(projected.map(|e| e as f32));
let tower_lerp = tower0_dist / (tower0_dist + tower1_dist);
let wall_ori = if (tower0.offset.x - tower1.offset.x).abs()
< (tower0.offset.y - tower1.offset.y).abs()
{
Ori::North
} else {
Ori::East
};
(
wall.distance_to_point(rpos.map(|e| e as f32)) as i32,
projected,
Lerp::lerp(tower0.alt as f32, tower1.alt as f32, tower_lerp) as i32,
wall_ori,
[tower0, tower1],
)
})
.min_by_key(|x| x.0)
.unwrap();
let border_pos = (wall_pos - rpos).map(|e| e.abs());
let wall_rpos = if wall_ori == Ori::East {
rpos
} else {
rpos.yx()
};
let head_space = col_sample
.path
.map(|(dist, _, path, _)| path.head_space(dist))
.unwrap_or(0);
let wall_sample = if let Some(col) = get_column(offs + wall_pos - rpos) {
col
} else {
col_sample
};
// Make sure particularly weird terrain doesn't give us underground walls
let wall_alt = wall_alt + (wall_sample.alt as i32 - wall_alt - 10).max(0);
let keep_archetype = KeepArchetype {
flag_color: if self.evil {
Rgb::new(80, 10, 130)
} else {
Rgb::new(200, 80, 40)
},
stone_color: if self.evil {
Rgb::new(65, 60, 55)
} else {
Rgb::new(100, 100, 110)
},
};
for z in -10..64 {
let wpos = Vec3::new(wpos2d.x, wpos2d.y, col_sample.alt as i32 + z);
// Boundary wall
let wall_z = wpos.z - wall_alt;
if z < head_space {
continue;
}
let mut mask = keep_archetype.draw(
Vec3::from(wall_rpos) + Vec3::unit_z() * wpos.z - wall_alt,
wall_dist,
border_pos,
rpos - wall_pos,
wall_z,
wall_ori,
4,
0,
&Attr {
storeys: 2,
is_tower: false,
flag: self.flags,
ridged: false,
rounded: true,
has_doors: false,
},
);
// Boundary towers
for tower in &self.towers {
let tower_wpos = Vec3::new(
self.origin.x + tower.offset.x,
self.origin.y + tower.offset.y,
tower.alt,
);
let tower_locus = 10;
let border_pos = (tower_wpos - wpos).xy().map(|e| e.abs());
mask = mask.resolve_with(keep_archetype.draw(
if (tower_wpos.x - wpos.x).abs() < (tower_wpos.y - wpos.y).abs() {
wpos - tower_wpos
} else {
Vec3::new(
wpos.y - tower_wpos.y,
wpos.x - tower_wpos.x,
wpos.z - tower_wpos.z,
)
},
border_pos.reduce_max() - tower_locus,
Vec2::new(border_pos.reduce_min(), border_pos.reduce_max()),
(wpos - tower_wpos).xy(),
wpos.z - tower.alt,
if border_pos.x > border_pos.y {
Ori::East
} else {
Ori::North
},
tower_locus,
0,
&Attr {
storeys: 3,
is_tower: true,
flag: self.flags,
ridged: self.ridged,
rounded: self.rounded_towers,
has_doors: false,
},
));
}
// Keeps
for keep in &self.keeps {
let keep_wpos = Vec3::new(
self.origin.x + keep.offset.x,
self.origin.y + keep.offset.y,
keep.alt,
);
let border_pos = (keep_wpos - wpos).xy().map(|e| e.abs());
mask = mask.resolve_with(keep_archetype.draw(
if (keep_wpos.x - wpos.x).abs() < (keep_wpos.y - wpos.y).abs() {
wpos - keep_wpos
} else {
Vec3::new(
wpos.y - keep_wpos.y,
wpos.x - keep_wpos.x,
wpos.z - keep_wpos.z,
)
},
border_pos.reduce_max() - keep.locus,
Vec2::new(border_pos.reduce_min(), border_pos.reduce_max()),
(wpos - keep_wpos).xy(),
wpos.z - keep.alt,
if border_pos.x > border_pos.y {
Ori::East
} else {
Ori::North
},
keep.locus,
0,
&Attr {
storeys: keep.storeys,
is_tower: keep.is_tower,
flag: self.flags,
ridged: self.ridged,
rounded: self.rounded_towers,
has_doors: true,
},
));
}
if let Some(block) = mask.finish() {
let _ = vol.set(Vec3::new(offs.x, offs.y, wpos.z), block);
}
}
}
}
}
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
pub fn apply_supplement<'a>(
&'a self,
_rng: &mut impl Rng,
_wpos2d: Vec2<i32>,
_get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
_supplement: &mut ChunkSupplement,
) {
// TODO
}
}

View File

@ -23,19 +23,6 @@ use rand::prelude::*;
use std::sync::Arc; use std::sync::Arc;
use vek::*; use vek::*;
impl WorldSim {
#[allow(dead_code)]
fn can_host_dungeon(&self, pos: Vec2<i32>) -> 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 { pub struct Dungeon {
origin: Vec2<i32>, origin: Vec2<i32>,
alt: i32, alt: i32,
@ -164,20 +151,6 @@ impl Dungeon {
max: rpos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32), 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())
* 12.0;
supplement.add_entity(
EntityInfo::at(
Vec3::new(self.origin.x, self.origin.y, self.alt + 16).map(|e| e as f32)
+ Vec3::from(offs),
)
.into_waypoint(),
);
}
let mut z = self.alt + ALT_OFFSET; let mut z = self.alt + ALT_OFFSET;
for floor in &self.floors { for floor in &self.floors {
z -= floor.total_depth(); z -= floor.total_depth();
@ -228,6 +201,7 @@ pub struct Floor {
hollow_depth: i32, hollow_depth: i32,
#[allow(dead_code)] #[allow(dead_code)]
stair_tile: Vec2<i32>, stair_tile: Vec2<i32>,
final_level: bool,
} }
const FLOOR_SIZE: Vec2<i32> = Vec2::new(18, 18); const FLOOR_SIZE: Vec2<i32> = Vec2::new(18, 18);
@ -260,6 +234,7 @@ impl Floor {
solid_depth: if level == 0 { 80 } else { 32 }, solid_depth: if level == 0 { 80 } else { 32 },
hollow_depth: 30, hollow_depth: 30,
stair_tile: new_stair_tile - tile_offset, stair_tile: new_stair_tile - tile_offset,
final_level,
}; };
const STAIR_ROOM_HEIGHT: i32 = 13; const STAIR_ROOM_HEIGHT: i32 = 13;
@ -305,7 +280,7 @@ impl Floor {
this.create_rooms(ctx, level, 7); this.create_rooms(ctx, level, 7);
// Create routes between all rooms // Create routes between all rooms
let room_areas = this.rooms.iter().map(|r| r.area).collect::<Vec<_>>(); let room_areas = this.rooms.values().map(|r| r.area).collect::<Vec<_>>();
for a in room_areas.iter() { for a in room_areas.iter() {
for b in room_areas.iter() { for b in room_areas.iter() {
this.create_route(ctx, a.center(), b.center()); this.create_route(ctx, a.center(), b.center());
@ -342,7 +317,7 @@ impl Floor {
// Ensure no overlap // Ensure no overlap
if self if self
.rooms .rooms
.iter() .values()
.any(|r| r.area.collides_with_rect(area_border)) .any(|r| r.area.collides_with_rect(area_border))
{ {
return None; return None;
@ -423,6 +398,24 @@ impl Floor {
origin: Vec3<i32>, origin: Vec3<i32>,
supplement: &mut ChunkSupplement, supplement: &mut ChunkSupplement,
) { ) {
let stair_rcenter =
Vec3::from((self.stair_tile + self.tile_offset).map(|e| e * TILE_SIZE + TILE_SIZE / 2));
if area.contains_point(stair_rcenter.xy()) {
let offs = Vec2::new(rng.gen_range(-1.0, 1.0), rng.gen_range(-1.0, 1.0))
.try_normalized()
.unwrap_or_else(Vec2::unit_y)
* FLOOR_SIZE.x as f32
/ 2.0
- 8.0;
if !self.final_level {
supplement.add_entity(
EntityInfo::at((origin + stair_rcenter).map(|e| e as f32) + Vec3::from(offs))
.into_waypoint(),
);
}
}
for x in area.min.x..area.max.x { for x in area.min.x..area.max.x {
for y in area.min.y..area.max.y { for y in area.min.y..area.max.y {
let tile_pos = Vec2::new(x, y).map(|e| e.div_euclid(TILE_SIZE)) - self.tile_offset; let tile_pos = Vec2::new(x, y).map(|e| e.div_euclid(TILE_SIZE)) - self.tile_offset;
@ -478,7 +471,16 @@ impl Floor {
if room.boss { if room.boss {
let boss_spawn_tile = room.area.center(); let boss_spawn_tile = room.area.center();
// Don't spawn the boss in a pillar // Don't spawn the boss in a pillar
let boss_spawn_tile = boss_spawn_tile + if tile_is_pillar { 1 } else { 0 }; let boss_tile_is_pillar = room
.pillars
.map(|pillar_space| {
boss_spawn_tile
.map(|e| e.rem_euclid(pillar_space) == 0)
.reduce_and()
})
.unwrap_or(false);
let boss_spawn_tile =
boss_spawn_tile + if boss_tile_is_pillar { 1 } else { 0 };
if tile_pos == boss_spawn_tile && tile_wcenter.xy() == wpos2d { if tile_pos == boss_spawn_tile && tile_wcenter.xy() == wpos2d {
let entity = EntityInfo::at(tile_wcenter.map(|e| e as f32)) let entity = EntityInfo::at(tile_wcenter.map(|e| e as f32))
@ -635,10 +637,13 @@ impl Floor {
empty empty
}; };
let tunnel_height = if self.final_level { 16.0 } else { 8.0 };
move |z| match self.tiles.get(tile_pos) { move |z| match self.tiles.get(tile_pos) {
Some(Tile::Solid) => BlockMask::nothing(), Some(Tile::Solid) => BlockMask::nothing(),
Some(Tile::Tunnel) => { 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) < tunnel_height * (1.0 - tunnel_dist.powf(4.0))
{ {
if z == 0 { floor_sprite } else { empty } if z == 0 { floor_sprite } else { empty }
} else { } else {

155
world/src/site/economy.rs Normal file
View File

@ -0,0 +1,155 @@
use crate::util::{DHashMap, MapVec};
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Good {
Wheat = 0,
Flour = 1,
Meat = 2,
Fish = 3,
Game = 4,
Food = 5,
Logs = 6,
Wood = 7,
Rock = 8,
Stone = 9,
}
use Good::*;
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Labor {
Farmer = 0,
Lumberjack = 1,
Miner = 2,
Fisher = 3,
Hunter = 4,
Cook = 5,
}
use Labor::*;
pub struct Economy {
pub pop: f32,
pub stocks: MapVec<Good, f32>,
pub surplus: MapVec<Good, f32>,
pub marginal_surplus: MapVec<Good, f32>,
pub values: MapVec<Good, Option<f32>>,
pub labor_values: MapVec<Good, Option<f32>>,
pub material_costs: MapVec<Good, f32>,
pub labors: MapVec<Labor, f32>,
pub yields: MapVec<Labor, f32>,
pub productivity: MapVec<Labor, f32>,
}
impl Default for Economy {
fn default() -> Self {
Self {
pop: 32.0,
stocks: Default::default(),
surplus: Default::default(),
marginal_surplus: Default::default(),
values: Default::default(),
labor_values: Default::default(),
material_costs: Default::default(),
labors: Default::default(),
yields: Default::default(),
productivity: Default::default(),
}
}
}
impl Economy {
pub fn get_orders(&self) -> DHashMap<Option<Labor>, Vec<(Good, f32)>> {
vec![
(None, vec![(Food, 0.5)]),
(Some(Cook), vec![
(Flour, 12.0),
(Meat, 4.0),
(Wood, 1.5),
(Stone, 1.0),
]),
(Some(Lumberjack), vec![(Logs, 0.5)]),
(Some(Miner), vec![(Rock, 0.5)]),
(Some(Fisher), vec![(Fish, 4.0)]),
(Some(Hunter), vec![(Game, 1.0)]),
(Some(Farmer), vec![(Wheat, 2.0)]),
]
.into_iter()
.collect()
}
pub fn get_productivity(&self) -> MapVec<Labor, (Good, f32)> {
// Per labourer, per year
MapVec::from_list(
&[
(Farmer, (Flour, 2.0)),
(Lumberjack, (Wood, 0.5)),
(Miner, (Stone, 0.5)),
(Fisher, (Meat, 4.0)),
(Hunter, (Meat, 1.0)),
(Cook, (Food, 16.0)),
],
(Rock, 0.0),
)
.map(|l, (good, v)| (good, v * (1.0 + self.labors[l])))
}
pub fn replenish(&mut self, time: f32) {
//use rand::Rng;
for (i, (g, v)) in [
(Wheat, 50.0),
(Logs, 20.0),
(Rock, 120.0),
(Game, 12.0),
(Fish, 10.0),
]
.iter()
.enumerate()
{
self.stocks[*g] = (*v
* (1.25 + (((time * 0.0001 + i as f32).sin() + 1.0) % 1.0) * 0.5)
- self.stocks[*g])
* 0.075; //rand::thread_rng().gen_range(0.05, 0.1);
}
}
}
impl Default for Good {
fn default() -> Self {
Good::Rock // Arbitrary
}
}
impl Good {
pub fn list() -> &'static [Self] {
static GOODS: [Good; 10] = [
Wheat, Flour, Meat, Fish, Game, Food, Logs, Wood, Rock, Stone,
];
&GOODS
}
pub fn decay_rate(&self) -> f32 {
match self {
Food => 0.2,
Wheat => 0.1,
Meat => 0.25,
Fish => 0.2,
_ => 0.0,
}
}
}
impl Labor {
pub fn list() -> &'static [Self] {
static LABORS: [Labor; 6] = [Farmer, Lumberjack, Miner, Fisher, Hunter, Cook];
&LABORS
}
}

View File

@ -1,57 +1,24 @@
mod block_mask;
mod castle;
mod dungeon; mod dungeon;
pub mod economy;
mod settlement; mod settlement;
// Reexports // Reexports
pub use self::{dungeon::Dungeon, settlement::Settlement}; pub use self::{
block_mask::BlockMask, castle::Castle, dungeon::Dungeon, economy::Economy,
settlement::Settlement,
};
use crate::column::ColumnSample; use crate::column::ColumnSample;
use common::{ use common::{
generation::ChunkSupplement, generation::ChunkSupplement,
terrain::Block, terrain::Block,
vol::{BaseVol, ReadVol, RectSizedVol, Vox, WriteVol}, vol::{BaseVol, ReadVol, RectSizedVol, WriteVol},
}; };
use rand::Rng; use rand::Rng;
use std::{fmt, sync::Arc};
use vek::*; 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<Block> {
if self.priority > 0 {
Some(self.block)
} else {
None
}
}
}
pub struct SpawnRules { pub struct SpawnRules {
pub trees: bool, pub trees: bool,
} }
@ -60,31 +27,60 @@ impl Default for SpawnRules {
fn default() -> Self { Self { trees: true } } fn default() -> Self { Self { trees: true } }
} }
#[derive(Clone)] pub struct Site {
pub enum Site { pub kind: SiteKind,
Settlement(Arc<Settlement>), pub economy: Economy,
Dungeon(Arc<Dungeon>), }
pub enum SiteKind {
Settlement(Settlement),
Dungeon(Dungeon),
Castle(Castle),
} }
impl Site { impl Site {
pub fn settlement(s: Settlement) -> Self {
Self {
kind: SiteKind::Settlement(s),
economy: Economy::default(),
}
}
pub fn dungeon(d: Dungeon) -> Self {
Self {
kind: SiteKind::Dungeon(d),
economy: Economy::default(),
}
}
pub fn castle(c: Castle) -> Self {
Self {
kind: SiteKind::Castle(c),
economy: Economy::default(),
}
}
pub fn radius(&self) -> f32 { pub fn radius(&self) -> f32 {
match self { match &self.kind {
Site::Settlement(settlement) => settlement.radius(), SiteKind::Settlement(s) => s.radius(),
Site::Dungeon(dungeon) => dungeon.radius(), SiteKind::Dungeon(d) => d.radius(),
SiteKind::Castle(c) => c.radius(),
} }
} }
pub fn get_origin(&self) -> Vec2<i32> { pub fn get_origin(&self) -> Vec2<i32> {
match self { match &self.kind {
Site::Settlement(s) => s.get_origin(), SiteKind::Settlement(s) => s.get_origin(),
Site::Dungeon(d) => d.get_origin(), SiteKind::Dungeon(d) => d.get_origin(),
SiteKind::Castle(c) => c.get_origin(),
} }
} }
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules { pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
match self { match &self.kind {
Site::Settlement(s) => s.spawn_rules(wpos), SiteKind::Settlement(s) => s.spawn_rules(wpos),
Site::Dungeon(d) => d.spawn_rules(wpos), SiteKind::Dungeon(d) => d.spawn_rules(wpos),
SiteKind::Castle(c) => c.spawn_rules(wpos),
} }
} }
@ -94,9 +90,10 @@ impl Site {
get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>, get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol), vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
) { ) {
match self { match &self.kind {
Site::Settlement(settlement) => settlement.apply_to(wpos2d, get_column, vol), SiteKind::Settlement(s) => s.apply_to(wpos2d, get_column, vol),
Site::Dungeon(dungeon) => dungeon.apply_to(wpos2d, get_column, vol), SiteKind::Dungeon(d) => d.apply_to(wpos2d, get_column, vol),
SiteKind::Castle(c) => c.apply_to(wpos2d, get_column, vol),
} }
} }
@ -107,28 +104,10 @@ impl Site {
get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>, get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
supplement: &mut ChunkSupplement, supplement: &mut ChunkSupplement,
) { ) {
match self { match &self.kind {
Site::Settlement(settlement) => { SiteKind::Settlement(s) => s.apply_supplement(rng, wpos2d, get_column, supplement),
settlement.apply_supplement(rng, wpos2d, get_column, supplement) SiteKind::Dungeon(d) => d.apply_supplement(rng, wpos2d, get_column, supplement),
}, SiteKind::Castle(c) => c.apply_supplement(rng, wpos2d, get_column, supplement),
Site::Dungeon(dungeon) => dungeon.apply_supplement(rng, wpos2d, get_column, supplement),
}
}
}
impl From<Settlement> for Site {
fn from(settlement: Settlement) -> Self { Site::Settlement(Arc::new(settlement)) }
}
impl From<Dungeon> 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"),
} }
} }
} }

View File

@ -12,40 +12,81 @@ use common::{
use rand::prelude::*; use rand::prelude::*;
use vek::*; use vek::*;
const COLOR_THEMES: [Rgb<u8>; 11] = [ pub struct ColorTheme {
Rgb::new(0x1D, 0x4D, 0x45), roof: Rgb<u8>,
Rgb::new(0xB3, 0x7D, 0x60), wall: Rgb<u8>,
Rgb::new(0xAC, 0x5D, 0x26), support: Rgb<u8>,
Rgb::new(0x32, 0x46, 0x6B), }
Rgb::new(0x2B, 0x19, 0x0F),
Rgb::new(0x93, 0x78, 0x51), const ROOF_COLORS: &[Rgb<u8>] = &[
Rgb::new(0x92, 0x57, 0x24), // Rgb::new(0x1D, 0x4D, 0x45),
Rgb::new(0x4A, 0x4E, 0x4E), // Rgb::new(0xB3, 0x7D, 0x60),
Rgb::new(0x2F, 0x32, 0x47), // Rgb::new(0xAC, 0x5D, 0x26),
Rgb::new(0x8F, 0x35, 0x43), // Rgb::new(0x32, 0x46, 0x6B),
Rgb::new(0x6D, 0x1E, 0x3A), // 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),
// Rgb::new(0x6D, 0xA7, 0x80),
// Rgb::new(0x4F, 0xA0, 0x95),
// Rgb::new(0xE2, 0xB9, 0x99),
// Rgb::new(0x7A, 0x30, 0x22),
// Rgb::new(0x4A, 0x06, 0x08),
// Rgb::new(0x8E, 0xB4, 0x57),
Rgb::new(0x99, 0x5E, 0x54),
Rgb::new(0x43, 0x63, 0x64),
Rgb::new(0x76, 0x6D, 0x68),
Rgb::new(0x7B, 0x41, 0x61),
Rgb::new(0x52, 0x20, 0x20),
Rgb::new(0x1A, 0x4A, 0x59),
Rgb::new(0xCC, 0x76, 0x4E),
];
const WALL_COLORS: &[Rgb<u8>] = &[
Rgb::new(200, 180, 150),
Rgb::new(0xB8, 0xB4, 0xA4),
Rgb::new(0x76, 0x6D, 0x68),
Rgb::new(0xF3, 0xC9, 0x8F),
Rgb::new(0xD3, 0xB7, 0x99),
Rgb::new(0xE1, 0xAB, 0x91),
Rgb::new(0x82, 0x57, 0x4C),
Rgb::new(0xB9, 0x96, 0x77),
Rgb::new(0xAE, 0x8D, 0x9C),
];
const SUPPORT_COLORS: &[Rgb<u8>] = &[
Rgb::new(60, 45, 30),
Rgb::new(0x65, 0x55, 0x56),
Rgb::new(0x53, 0x33, 0x13),
Rgb::new(0x58, 0x42, 0x33),
]; ];
pub struct House { pub struct House {
roof_color: Rgb<u8>, pub colors: ColorTheme,
noise: RandomField, pub noise: RandomField,
roof_ribbing: bool, pub roof_ribbing: bool,
roof_ribbing_diagonal: bool, pub roof_ribbing_diagonal: bool,
} }
enum Pillar { #[derive(Copy, Clone)]
pub enum Pillar {
None, None,
Chimney(i32), Chimney(i32),
Tower(i32), Tower(i32),
} }
enum RoofStyle { #[derive(Copy, Clone)]
pub enum RoofStyle {
Hip, Hip,
Gable, Gable,
Rounded, Rounded,
} }
enum StoreyFill { #[derive(Copy, Clone)]
pub enum StoreyFill {
None, None,
Upper, Upper,
All, All,
@ -69,16 +110,19 @@ impl StoreyFill {
} }
} }
#[derive(Copy, Clone)]
pub struct Attr { pub struct Attr {
central_supports: bool, pub central_supports: bool,
storey_fill: StoreyFill, pub storey_fill: StoreyFill,
roof_style: RoofStyle, pub roof_style: RoofStyle,
mansard: i32, pub mansard: i32,
pillar: Pillar, pub pillar: Pillar,
pub levels: i32,
pub window: BlockKind,
} }
impl Attr { impl Attr {
fn generate<R: Rng>(rng: &mut R, locus: i32) -> Self { pub fn generate<R: Rng>(rng: &mut R, _locus: i32) -> Self {
Self { Self {
central_supports: rng.gen(), central_supports: rng.gen(),
storey_fill: match rng.gen_range(0, 2) { storey_fill: match rng.gen_range(0, 2) {
@ -93,9 +137,16 @@ impl Attr {
}, },
mansard: rng.gen_range(-7, 4).max(0), mansard: rng.gen_range(-7, 4).max(0),
pillar: match rng.gen_range(0, 4) { pillar: match rng.gen_range(0, 4) {
0 => Pillar::Chimney(9 + locus + rng.gen_range(0, 4)), 0 => Pillar::Chimney(rng.gen_range(2, 6)),
_ => Pillar::None, _ => Pillar::None,
}, },
levels: rng.gen_range(1, 3),
window: match rng.gen_range(0, 4) {
0 => BlockKind::Window1,
1 => BlockKind::Window2,
2 => BlockKind::Window3,
_ => BlockKind::Window4,
},
} }
} }
} }
@ -107,6 +158,7 @@ impl Archetype for House {
let len = rng.gen_range(-8, 24).clamped(0, 20); let len = rng.gen_range(-8, 24).clamped(0, 20);
let locus = 6 + rng.gen_range(0, 5); let locus = 6 + rng.gen_range(0, 5);
let branches_per_side = 1 + len as usize / 20; let branches_per_side = 1 + len as usize / 20;
let levels = rng.gen_range(1, 3);
let skel = Skeleton { let skel = Skeleton {
offset: -rng.gen_range(0, len + 7).clamped(0, len), offset: -rng.gen_range(0, len + 7).clamped(0, len),
ori: if rng.gen() { Ori::East } else { Ori::North }, ori: if rng.gen() { Ori::East } else { Ori::North },
@ -116,10 +168,11 @@ impl Archetype for House {
storey_fill: StoreyFill::All, storey_fill: StoreyFill::All,
mansard: 0, mansard: 0,
pillar: match rng.gen_range(0, 3) { pillar: match rng.gen_range(0, 3) {
0 => Pillar::Chimney(10 + locus + rng.gen_range(0, 4)), 0 => Pillar::Chimney(rng.gen_range(2, 6)),
1 => Pillar::Tower(15 + locus + rng.gen_range(0, 4)), 1 => Pillar::Tower(5 + rng.gen_range(1, 5)),
_ => Pillar::None, _ => Pillar::None,
}, },
levels,
..Attr::generate(rng, locus) ..Attr::generate(rng, locus)
}, },
locus, locus,
@ -134,7 +187,10 @@ impl Archetype for House {
i as i32 * len / (branches_per_side - 1).max(1) as i32, i as i32 * len / (branches_per_side - 1).max(1) as i32,
Branch { Branch {
len: rng.gen_range(8, 16) * flip, len: rng.gen_range(8, 16) * flip,
attr: Attr::generate(rng, locus), attr: Attr {
levels: rng.gen_range(1, 4).min(levels),
..Attr::generate(rng, locus)
},
locus: (6 + rng.gen_range(0, 3)).min(locus), locus: (6 + rng.gen_range(0, 3)).min(locus),
border: 4, border: 4,
children: Vec::new(), children: Vec::new(),
@ -149,10 +205,11 @@ impl Archetype for House {
}; };
let this = Self { let this = Self {
roof_color: COLOR_THEMES colors: ColorTheme {
.choose(rng) roof: *ROOF_COLORS.choose(rng).unwrap(),
.unwrap() wall: *WALL_COLORS.choose(rng).unwrap(),
.map(|e| e.saturating_add(rng.gen_range(0, 20)) - 10), support: *SUPPORT_COLORS.choose(rng).unwrap(),
},
noise: RandomField::new(rng.gen()), noise: RandomField::new(rng.gen()),
roof_ribbing: rng.gen(), roof_ribbing: rng.gen(),
roof_ribbing_diagonal: rng.gen(), roof_ribbing_diagonal: rng.gen(),
@ -165,12 +222,15 @@ impl Archetype for House {
#[allow(clippy::int_plus_one)] // TODO: Pending review in #587 #[allow(clippy::int_plus_one)] // TODO: Pending review in #587
fn draw( fn draw(
&self, &self,
_pos: Vec3<i32>,
dist: i32, dist: i32,
bound_offset: Vec2<i32>, bound_offset: Vec2<i32>,
center_offset: Vec2<i32>, center_offset: Vec2<i32>,
z: i32, z: i32,
ori: Ori, ori: Ori,
branch: &Branch<Self::Attr>, locus: i32,
_len: i32,
attr: &Self::Attr,
) -> BlockMask { ) -> BlockMask {
let profile = Vec2::new(bound_offset.x, z); let profile = Vec2::new(bound_offset.x, z);
@ -185,7 +245,7 @@ impl Archetype for House {
) )
}; };
let make_block = |r, g, b| { let make_block = |(r, g, b)| {
let nz = self let nz = self
.noise .noise
.get(Vec3::new(center_offset.x, center_offset.y, z * 8)); .get(Vec3::new(center_offset.x, center_offset.y, z * 8));
@ -205,32 +265,48 @@ impl Archetype for House {
let foundation_layer = internal_layer + 1; let foundation_layer = internal_layer + 1;
let floor_layer = foundation_layer + 1; let floor_layer = foundation_layer + 1;
let foundation = make_block(100, 100, 100).with_priority(foundation_layer); let foundation = make_block((100, 100, 100)).with_priority(foundation_layer);
let log = make_block(60, 45, 30); let log = make_block(self.colors.support.into_tuple());
let floor = make_block(100, 75, 50); let floor = make_block((100, 75, 50));
let wall = make_block(200, 180, 150).with_priority(facade_layer); let wall = make_block(self.colors.wall.into_tuple()).with_priority(facade_layer);
let roof = make_block(self.roof_color.r, self.roof_color.g, self.roof_color.b) let roof = make_block(self.colors.roof.into_tuple()).with_priority(facade_layer - 1);
.with_priority(facade_layer - 1);
let empty = BlockMask::nothing(); let empty = BlockMask::nothing();
let internal = BlockMask::new(Block::empty(), internal_layer); let internal = BlockMask::new(Block::empty(), internal_layer);
let end_window = BlockMask::new( let end_window = BlockMask::new(
Block::new(BlockKind::Window1, make_meta(ori.flip())), Block::new(attr.window, make_meta(ori.flip())),
structural_layer, structural_layer,
); );
let fire = BlockMask::new(Block::new(BlockKind::Ember, Rgb::white()), foundation_layer); let fire = BlockMask::new(Block::new(BlockKind::Ember, Rgb::white()), foundation_layer);
let ceil_height = 6; let storey_height = 6;
let lower_width = branch.locus - 1; let storey = ((z - 1) / storey_height).min(attr.levels - 1);
let upper_width = branch.locus; let floor_height = storey_height * storey;
let ceil_height = storey_height * (storey + 1);
let lower_width = locus - 1;
let upper_width = locus;
let width = if profile.y >= ceil_height { let width = if profile.y >= ceil_height {
upper_width upper_width
} else { } else {
lower_width lower_width
}; };
let foundation_height = 0 - (dist - width - 1).max(0); let foundation_height = 0 - (dist - width - 1).max(0);
let roof_top = 8 + width; let roof_top = storey_height * attr.levels + 2 + width;
if let Pillar::Chimney(chimney_top) = branch.attr.pillar { let edge_ori = if bound_offset.x.abs() > bound_offset.y.abs() {
if center_offset.x > 0 { 6 } else { 2 }
} else if (center_offset.y > 0) ^ (ori == Ori::East) {
0
} else {
4
};
let edge_ori = if ori == Ori::East {
(edge_ori + 2) % 8
} else {
edge_ori
};
if let Pillar::Chimney(chimney_height) = attr.pillar {
let chimney_top = roof_top + chimney_height;
// Chimney shaft // Chimney shaft
if center_offset.map(|e| e.abs()).reduce_max() == 0 if center_offset.map(|e| e.abs()).reduce_max() == 0
&& profile.y >= foundation_height + 1 && profile.y >= foundation_height + 1
@ -258,7 +334,7 @@ impl Archetype for House {
if profile.y <= foundation_height && dist < width + 3 { if profile.y <= foundation_height && dist < width + 3 {
// Foundations // Foundations
if branch.attr.storey_fill.has_lower() { if attr.storey_fill.has_lower() {
if dist == width - 1 { if dist == width - 1 {
// Floor lining // Floor lining
return log.with_priority(floor_layer); return log.with_priority(floor_layer);
@ -281,7 +357,7 @@ impl Archetype for House {
|profile: Vec2<i32>, width, dist, bound_offset: Vec2<i32>, roof_top, mansard| { |profile: Vec2<i32>, width, dist, bound_offset: Vec2<i32>, roof_top, mansard| {
// Roof // Roof
let (roof_profile, roof_dist) = match &branch.attr.roof_style { let (roof_profile, roof_dist) = match &attr.roof_style {
RoofStyle::Hip => (Vec2::new(dist, profile.y), dist), RoofStyle::Hip => (Vec2::new(dist, profile.y), dist),
RoofStyle::Gable => (profile, dist), RoofStyle::Gable => (profile, dist),
RoofStyle::Rounded => { RoofStyle::Rounded => {
@ -320,13 +396,15 @@ impl Archetype for House {
&& bound_offset.x > 0 && bound_offset.x > 0
&& bound_offset.x < width && bound_offset.x < width
&& profile.y < ceil_height && profile.y < ceil_height
&& branch.attr.storey_fill.has_lower() && attr.storey_fill.has_lower()
&& storey == 0
{ {
return Some( return Some(
if (bound_offset.x == (width - 1) / 2 if (bound_offset.x == (width - 1) / 2
|| bound_offset.x == (width - 1) / 2 + 1) || bound_offset.x == (width - 1) / 2 + 1)
&& profile.y <= foundation_height + 3 && profile.y <= foundation_height + 3
{ {
// Doors on first floor only
if profile.y == foundation_height + 1 { if profile.y == foundation_height + 1 {
BlockMask::new( BlockMask::new(
Block::new( Block::new(
@ -351,9 +429,9 @@ impl Archetype for House {
if bound_offset.x == bound_offset.y || profile.y == ceil_height { if bound_offset.x == bound_offset.y || profile.y == ceil_height {
// Support beams // Support beams
return Some(log); return Some(log);
} else if !branch.attr.storey_fill.has_lower() && profile.y < ceil_height { } else if !attr.storey_fill.has_lower() && profile.y < ceil_height {
return Some(empty); return Some(empty);
} else if !branch.attr.storey_fill.has_upper() { } else if !attr.storey_fill.has_upper() {
return Some(empty); return Some(empty);
} else { } else {
let (frame_bounds, frame_borders) = if profile.y >= ceil_height { let (frame_bounds, frame_borders) = if profile.y >= ceil_height {
@ -367,7 +445,7 @@ impl Archetype for House {
} else { } else {
( (
Aabr { Aabr {
min: Vec2::new(2, foundation_height + 2), min: Vec2::new(2, floor_height + 2),
max: Vec2::new(width - 2, ceil_height - 2), max: Vec2::new(width - 2, ceil_height - 2),
}, },
Vec2::new(1, 0), Vec2::new(1, 0),
@ -392,7 +470,7 @@ impl Archetype for House {
} }
// Wall // Wall
return Some(if branch.attr.central_supports && profile.x == 0 { return Some(if attr.central_supports && profile.x == 0 {
// Support beams // Support beams
log.with_priority(structural_layer) log.with_priority(structural_layer)
} else { } else {
@ -407,36 +485,88 @@ impl Archetype for House {
if profile.x == 0 { if profile.x == 0 {
// Rafters // Rafters
return Some(log); return Some(log);
} else if branch.attr.storey_fill.has_upper() { } else if attr.storey_fill.has_upper() {
// Ceiling // Ceiling
return Some(floor); return Some(floor);
} }
} else if (!branch.attr.storey_fill.has_lower() && profile.y < ceil_height) } else if (!attr.storey_fill.has_lower() && profile.y < ceil_height)
|| (!branch.attr.storey_fill.has_upper() && profile.y >= ceil_height) || (!attr.storey_fill.has_upper() && profile.y >= ceil_height)
{ {
return Some(empty); return Some(empty);
// Furniture
} else if dist == width - 1
&& center_offset.sum() % 2 == 0
&& profile.y == floor_height + 1
&& self
.noise
.chance(Vec3::new(center_offset.x, center_offset.y, z), 0.2)
{
let furniture = match self.noise.get(Vec3::new(
center_offset.x,
center_offset.y,
z + 100,
)) % 11
{
0 => BlockKind::Planter,
1 => BlockKind::ChairSingle,
2 => BlockKind::ChairDouble,
3 => BlockKind::CoatRack,
4 => BlockKind::Crate,
6 => BlockKind::DrawerMedium,
7 => BlockKind::DrawerSmall,
8 => BlockKind::TableSide,
9 => BlockKind::WardrobeSingle,
_ => BlockKind::Pot,
};
return Some(BlockMask::new(
Block::new(furniture, Rgb::new(edge_ori, 0, 0)),
internal_layer,
));
} else { } else {
return Some(internal); return Some(internal);
} }
} }
// Wall ornaments
if dist == width + 1
&& center_offset.map(|e| e.abs()).reduce_min() == 0
&& profile.y == floor_height + 3
&& self
.noise
.chance(Vec3::new(center_offset.x, center_offset.y, z), 0.35)
&& attr.storey_fill.has_lower()
{
let ornament =
match self
.noise
.get(Vec3::new(center_offset.x, center_offset.y, z + 100))
% 4
{
0 => BlockKind::HangingSign,
1 | 2 | 3 => BlockKind::HangingBasket,
_ => BlockKind::DungeonWallDecor,
};
Some(BlockMask::new(
Block::new(ornament, Rgb::new((edge_ori + 4) % 8, 0, 0)),
internal_layer,
))
} else {
None None
}
}; };
let mut cblock = empty; let mut cblock = empty;
if let Some(block) = do_roof_wall( if let Some(block) =
profile, do_roof_wall(profile, width, dist, bound_offset, roof_top, attr.mansard)
width, {
dist,
bound_offset,
roof_top,
branch.attr.mansard,
) {
cblock = cblock.resolve_with(block); cblock = cblock.resolve_with(block);
} }
if let Pillar::Tower(tower_top) = branch.attr.pillar { if let Pillar::Tower(tower_height) = attr.pillar {
let tower_top = roof_top + tower_height;
let profile = Vec2::new(center_offset.x.abs(), profile.y); let profile = Vec2::new(center_offset.x.abs(), profile.y);
let dist = center_offset.map(|e| e.abs()).reduce_max(); let dist = center_offset.map(|e| e.abs()).reduce_max();
@ -446,7 +576,7 @@ impl Archetype for House {
dist, dist,
center_offset.map(|e| e.abs()), center_offset.map(|e| e.abs()),
tower_top, tower_top,
branch.attr.mansard, attr.mansard,
) { ) {
cblock = cblock.resolve_with(block); cblock = cblock.resolve_with(block);
} }

View File

@ -1,5 +1,8 @@
use super::{super::skeleton::*, Archetype}; use super::{super::skeleton::*, Archetype};
use crate::site::BlockMask; use crate::{
site::BlockMask,
util::{RandomField, Sampler},
};
use common::{ use common::{
terrain::{Block, BlockKind}, terrain::{Block, BlockKind},
vol::Vox, vol::Vox,
@ -7,29 +10,56 @@ use common::{
use rand::prelude::*; use rand::prelude::*;
use vek::*; use vek::*;
pub struct Keep; pub struct Keep {
pub flag_color: Rgb<u8>,
pub stone_color: Rgb<u8>,
}
pub struct Attr {
pub storeys: i32,
pub is_tower: bool,
pub flag: bool,
pub ridged: bool,
pub rounded: bool,
pub has_doors: bool,
}
impl Archetype for Keep { impl Archetype for Keep {
type Attr = (); type Attr = Attr;
fn generate<R: Rng>(rng: &mut R) -> (Self, Skeleton<Self::Attr>) { fn generate<R: Rng>(rng: &mut R) -> (Self, Skeleton<Self::Attr>) {
let len = rng.gen_range(-8, 12).max(0); let len = rng.gen_range(-8, 24).max(0);
let storeys = rng.gen_range(1, 3);
let skel = Skeleton { let skel = Skeleton {
offset: -rng.gen_range(0, len + 7).clamped(0, len), offset: -rng.gen_range(0, len + 7).clamped(0, len),
ori: if rng.gen() { Ori::East } else { Ori::North }, ori: if rng.gen() { Ori::East } else { Ori::North },
root: Branch { root: Branch {
len, len,
attr: Self::Attr::default(), attr: Attr {
locus: 5 + rng.gen_range(0, 5), storeys,
is_tower: false,
flag: false,
ridged: false,
rounded: true,
has_doors: true,
},
locus: 10 + rng.gen_range(0, 5),
border: 3, border: 3,
children: (0..rng.gen_range(0, 4)) children: (0..1)
.map(|_| { .map(|_| {
( (
rng.gen_range(-5, len + 5).clamped(0, len.max(1) - 1), rng.gen_range(-5, len + 5).clamped(0, len.max(1) - 1),
Branch { Branch {
len: rng.gen_range(5, 12) * if rng.gen() { 1 } else { -1 }, len: 0,
attr: Self::Attr::default(), attr: Attr {
locus: 5 + rng.gen_range(0, 3), storeys: storeys + rng.gen_range(1, 3),
is_tower: true,
flag: true,
ridged: false,
rounded: true,
has_doors: false,
},
locus: 6 + rng.gen_range(0, 3),
border: 3, border: 3,
children: Vec::new(), children: Vec::new(),
}, },
@ -39,44 +69,188 @@ impl Archetype for Keep {
}, },
}; };
(Self, skel) (
Self {
flag_color: Rgb::new(200, 80, 40),
stone_color: Rgb::new(100, 100, 110),
},
skel,
)
} }
#[allow(clippy::if_same_then_else)] // TODO: Pending review in #587 #[allow(clippy::if_same_then_else)] // TODO: Pending review in #587
fn draw( fn draw(
&self, &self,
dist: i32, pos: Vec3<i32>,
_dist: i32,
bound_offset: Vec2<i32>, bound_offset: Vec2<i32>,
_center_offset: Vec2<i32>, center_offset: Vec2<i32>,
z: i32, z: i32,
_ori: Ori, ori: Ori,
branch: &Branch<Self::Attr>, locus: i32,
_len: i32,
attr: &Self::Attr,
) -> BlockMask { ) -> BlockMask {
let profile = Vec2::new(bound_offset.x, z); let profile = Vec2::new(bound_offset.x, z);
let make_block = let weak_layer = 1;
|r, g, b| BlockMask::new(Block::new(BlockKind::Normal, Rgb::new(r, g, b)), 2); let normal_layer = weak_layer + 1;
let important_layer = normal_layer + 1;
let internal_layer = important_layer + 1;
let foundation = make_block(100, 100, 100); let make_meta = |ori| {
let wall = make_block(75, 100, 125); Rgb::new(
let roof = make_block(150, 120, 50); match ori {
let empty = BlockMask::new(Block::empty(), 2); Ori::East => 0,
Ori::North => 2,
},
0,
0,
)
};
let width = branch.locus; let make_block = |r, g, b| {
let rampart_width = 5 + branch.locus; BlockMask::new(
let ceil_height = 16; Block::new(BlockKind::Normal, Rgb::new(r, g, b)),
normal_layer,
)
};
if profile.y <= 1 - (dist - width - 1).max(0) && dist < width + 3 { let brick_tex_pos = (pos + Vec3::new(pos.z, pos.z, 0)) / Vec3::new(2, 2, 1);
let brick_tex = RandomField::new(0).get(brick_tex_pos) as u8 % 24;
let foundation = make_block(80 + brick_tex, 80 + brick_tex, 80 + brick_tex);
let wall = make_block(
self.stone_color.r + brick_tex,
self.stone_color.g + brick_tex,
self.stone_color.b + brick_tex,
);
let window = BlockMask::new(
Block::new(BlockKind::Window1, make_meta(ori.flip())),
normal_layer,
);
let floor = make_block(
80 + (pos.y.abs() % 2) as u8 * 15,
60 + (pos.y.abs() % 2) as u8 * 15,
10 + (pos.y.abs() % 2) as u8 * 15,
)
.with_priority(important_layer);
let pole = make_block(90, 70, 50).with_priority(important_layer);
let flag = make_block(self.flag_color.r, self.flag_color.g, self.flag_color.b)
.with_priority(important_layer);
let internal = BlockMask::new(Block::empty(), internal_layer);
let empty = BlockMask::nothing();
let make_staircase = move |pos: Vec3<i32>, 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) / (std::f32::consts::PI * 2.0) * stretch
+ pos.z as f32)
.rem_euclid(stretch)
< 1.5
{
stone
} else {
internal
}
} else {
BlockMask::nothing()
}
};
let ridge_x = (center_offset.map(|e| e.abs()).reduce_min() + 2) % 8;
let width = locus
+ if ridge_x < 4 && attr.ridged && !attr.rounded {
1
} else {
0
};
let rampart_width = 2 + width;
let storey_height = 9;
let roof_height = attr.storeys * storey_height;
let storey_y = profile.y % storey_height;
let door_height = 6;
let rampart_height = roof_height + if ridge_x % 2 == 0 { 3 } else { 4 };
let min_dist = if attr.rounded {
bound_offset.map(|e| e.pow(2) as f32).sum().powf(0.5) as i32
} else {
bound_offset.map(|e| e.abs()).reduce_max()
};
if profile.y <= 0 - (min_dist - width - 1).max(0) && min_dist < width + 3 {
// Foundations // Foundations
foundation foundation
} else if profile.y == ceil_height && dist < rampart_width { } else if (0..=roof_height).contains(&profile.y) && storey_y == 0 && min_dist <= width + 1 {
roof if min_dist < width { floor } else { wall }
} else if dist == rampart_width && profile.y >= ceil_height && profile.y < ceil_height + 4 { } else if bound_offset.x.abs() < 3
&& profile.y < door_height - bound_offset.x.abs()
&& profile.y > 0
&& min_dist >= width - 2
&& min_dist <= width + 1
&& attr.has_doors
{
internal
} else if (min_dist == width || (!attr.is_tower && min_dist == width + 1))
&& profile.y <= roof_height
{
if attr.is_tower
&& (3..7).contains(&storey_y)
&& bound_offset.x.abs() < width - 2
&& (5..7).contains(&ridge_x)
{
window
} else {
wall wall
} else if dist == width && profile.y <= ceil_height { }
} else if profile.y >= roof_height {
if profile.y > roof_height
&& (min_dist < rampart_width - 1 || (attr.is_tower && min_dist < rampart_width))
{
if attr.is_tower
&& attr.flag
&& center_offset == Vec2::zero()
&& profile.y < roof_height + 16
{
pole
} else if attr.is_tower
&& attr.flag
&& center_offset.x == 0
&& center_offset.y > 0
&& center_offset.y < 8
&& profile.y > roof_height + 8
&& profile.y < roof_height + 14
{
flag
} else {
empty
}
} else if min_dist <= rampart_width {
if profile.y < rampart_height {
wall wall
} else { } else {
empty empty
} }
} else {
empty
}
} else if profile.y < roof_height && min_dist < width {
internal
} else {
empty
}
.resolve_with(
if attr.is_tower && profile.y > 0 && profile.y <= roof_height {
make_staircase(
Vec3::new(center_offset.x, center_offset.y, pos.z),
7.0f32.min(width as f32 - 1.0),
0.5,
9.0,
)
} else {
BlockMask::nothing()
},
)
} }
} }

View File

@ -12,13 +12,18 @@ pub trait Archetype {
fn generate<R: Rng>(rng: &mut R) -> (Self, Skeleton<Self::Attr>) fn generate<R: Rng>(rng: &mut R) -> (Self, Skeleton<Self::Attr>)
where where
Self: Sized; Self: Sized;
#[allow(clippy::too_many_arguments)]
fn draw( fn draw(
&self, &self,
pos: Vec3<i32>,
dist: i32, dist: i32,
bound_offset: Vec2<i32>, bound_offset: Vec2<i32>,
center_offset: Vec2<i32>, center_offset: Vec2<i32>,
z: i32, z: i32,
ori: Ori, ori: Ori,
branch: &Branch<Self::Attr>, locus: i32,
len: i32,
attr: &Self::Attr,
) -> BlockMask; ) -> BlockMask;
} }

View File

@ -1,16 +1,16 @@
mod archetype; pub mod archetype;
mod skeleton; pub mod skeleton;
// Reexports // Reexports
pub use self::archetype::Archetype; pub use self::{
archetype::{house::House, keep::Keep, Archetype},
skeleton::*,
};
use self::skeleton::*;
use common::terrain::Block; use common::terrain::Block;
use rand::prelude::*; use rand::prelude::*;
use vek::*; use vek::*;
pub type HouseBuilding = Building<archetype::house::House>;
pub struct Building<A: Archetype> { pub struct Building<A: Archetype> {
skel: Skeleton<A::Attr>, skel: Skeleton<A::Attr>,
archetype: A, archetype: A,
@ -42,7 +42,7 @@ impl<A: Archetype> Building<A> {
let aabr = self.bounds_2d(); let aabr = self.bounds_2d();
Aabb { Aabb {
min: Vec3::from(aabr.min) + Vec3::unit_z() * (self.origin.z - 8), min: Vec3::from(aabr.min) + Vec3::unit_z() * (self.origin.z - 8),
max: Vec3::from(aabr.max) + Vec3::unit_z() * (self.origin.z + 32), max: Vec3::from(aabr.max) + Vec3::unit_z() * (self.origin.z + 48),
} }
} }
@ -50,10 +50,19 @@ impl<A: Archetype> Building<A> {
let rpos = pos - self.origin; let rpos = pos - self.origin;
self.skel self.skel
.sample_closest( .sample_closest(
rpos.into(), rpos,
|dist, bound_offset, center_offset, ori, branch| { |pos, dist, bound_offset, center_offset, ori, branch| {
self.archetype self.archetype.draw(
.draw(dist, bound_offset, center_offset, rpos.z, ori, branch) pos,
dist,
bound_offset,
center_offset,
rpos.z,
ori,
branch.locus,
branch.len,
&branch.attr,
)
}, },
) )
.finish() .finish()

View File

@ -76,8 +76,8 @@ impl<T> Skeleton<T> {
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587 #[allow(clippy::or_fun_call)] // TODO: Pending review in #587
pub fn sample_closest( pub fn sample_closest(
&self, &self,
pos: Vec2<i32>, pos: Vec3<i32>,
mut f: impl FnMut(i32, Vec2<i32>, Vec2<i32>, Ori, &Branch<T>) -> BlockMask, mut f: impl FnMut(Vec3<i32>, i32, Vec2<i32>, Vec2<i32>, Ori, &Branch<T>) -> BlockMask,
) -> BlockMask { ) -> BlockMask {
let mut min = None::<(_, BlockMask)>; let mut min = None::<(_, BlockMask)>;
self.for_each(|node, ori, branch, is_child, parent_locus| { self.for_each(|node, ori, branch, is_child, parent_locus| {
@ -117,7 +117,7 @@ impl<T> Skeleton<T> {
} }
|| true || true
{ {
let new_bm = f(dist, bound_offset, center_offset, ori, branch); let new_bm = f(pos, dist, bound_offset, center_offset, ori, branch);
min = min min = min
.map(|(_, bm)| (dist_locus, bm.resolve_with(new_bm))) .map(|(_, bm)| (dist_locus, bm.resolve_with(new_bm)))
.or(Some((dist_locus, new_bm))); .or(Some((dist_locus, new_bm)));

View File

@ -1,6 +1,10 @@
mod building; pub mod building;
mod town;
use self::building::HouseBuilding; use self::{
building::{Building, House, Keep},
town::{District, Town},
};
use super::SpawnRules; use super::SpawnRules;
use crate::{ use crate::{
column::ColumnSample, column::ColumnSample,
@ -82,7 +86,8 @@ const AREA_SIZE: u32 = 32;
fn to_tile(e: i32) -> i32 { ((e as f32).div_euclid(AREA_SIZE as f32)).floor() as i32 } fn to_tile(e: i32) -> i32 { ((e as f32).div_euclid(AREA_SIZE as f32)).floor() as i32 }
pub enum StructureKind { pub enum StructureKind {
House(HouseBuilding), House(Building<House>),
Keep(Building<Keep>),
} }
pub struct Structure { pub struct Structure {
@ -93,6 +98,21 @@ impl Structure {
pub fn bounds_2d(&self) -> Aabr<i32> { pub fn bounds_2d(&self) -> Aabr<i32> {
match &self.kind { match &self.kind {
StructureKind::House(house) => house.bounds_2d(), StructureKind::House(house) => house.bounds_2d(),
StructureKind::Keep(keep) => keep.bounds_2d(),
}
}
pub fn bounds(&self) -> Aabb<i32> {
match &self.kind {
StructureKind::House(house) => house.bounds(),
StructureKind::Keep(keep) => keep.bounds(),
}
}
pub fn sample(&self, rpos: Vec3<i32>) -> Option<Block> {
match &self.kind {
StructureKind::House(house) => house.sample(rpos),
StructureKind::Keep(keep) => keep.sample(rpos),
} }
} }
} }
@ -107,10 +127,6 @@ pub struct Settlement {
noise: RandomField, noise: RandomField,
} }
pub struct Town {
base_tile: Vec2<i32>,
}
pub struct Farm { pub struct Farm {
#[allow(dead_code)] #[allow(dead_code)]
base_tile: Vec2<i32>, base_tile: Vec2<i32>,
@ -216,7 +232,7 @@ impl Settlement {
for _ in 0..PATH_COUNT { for _ in 0..PATH_COUNT {
dir = (Vec2::new(rng.gen::<f32>() - 0.5, rng.gen::<f32>() - 0.5) * 2.0 - dir) dir = (Vec2::new(rng.gen::<f32>() - 0.5, rng.gen::<f32>() - 0.5) * 2.0 - dir)
.try_normalized() .try_normalized()
.unwrap_or(Vec2::zero()); .unwrap_or_else(Vec2::zero);
let origin = dir.map(|e| (e * 100.0) as i32); let origin = dir.map(|e| (e * 100.0) as i32);
let origin = self let origin = self
.land .land
@ -260,12 +276,28 @@ impl Settlement {
Some(Plot::Dirt) => true, Some(Plot::Dirt) => true,
_ => false, _ => false,
}) { }) {
self.land // self.land
.plot_at_mut(base_tile) // .plot_at_mut(base_tile)
.map(|plot| *plot = Plot::Town); // .map(|plot| *plot = Plot::Town { district: None });
if i == 0 { if i == 0 {
self.town = Some(Town { base_tile }); let town = Town::generate(self.origin, base_tile, ctx);
for (id, district) in town.districts().iter() {
let district_plot =
self.land.plots.insert(Plot::Town { district: Some(id) });
for x in district.aabr.min.x..district.aabr.max.x {
for y in district.aabr.min.y..district.aabr.max.y {
if !matches!(self.land.plot_at(Vec2::new(x, y)), Some(Plot::Hazard))
{
self.land.set(Vec2::new(x, y), district_plot);
}
}
}
}
self.town = Some(town);
origin = base_tile; origin = base_tile;
} }
} }
@ -331,17 +363,16 @@ impl Settlement {
.take(16usize.pow(2)) .take(16usize.pow(2))
{ {
// This is a stupid way to decide how to place buildings // This is a stupid way to decide how to place buildings
for _ in 0..ctx.rng.gen_range(2, 5) { for i in 0..ctx.rng.gen_range(2, 5) {
for _ in 0..25 { for _ in 0..25 {
let house_pos = tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32 / 2) let house_pos = tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32 / 2)
+ Vec2::<i32>::zero().map(|_| { + Vec2::<i32>::zero().map(|_| {
ctx.rng ctx.rng
.gen_range(-(AREA_SIZE as i32) / 2, AREA_SIZE as i32 / 2) .gen_range(-(AREA_SIZE as i32) / 4, AREA_SIZE as i32 / 4)
}); });
let tile_pos = house_pos.map(|e| e.div_euclid(AREA_SIZE as i32)); 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)) if self
|| self
.land .land
.tile_at(tile_pos) .tile_at(tile_pos)
.map(|t| t.contains(WayKind::Path)) .map(|t| t.contains(WayKind::Path))
@ -349,24 +380,39 @@ impl Settlement {
|| ctx || ctx
.sim .sim
.and_then(|sim| sim.get_nearest_path(self.origin + house_pos)) .and_then(|sim| sim.get_nearest_path(self.origin + house_pos))
.map(|(dist, _)| dist < 28.0) .map(|(dist, _, _, _)| dist < 28.0)
.unwrap_or(false) .unwrap_or(false)
{ {
continue; continue;
} }
let structure = Structure { let alt = if let Some(Plot::Town { district }) = self.land.plot_at(tile_pos) {
kind: StructureKind::House(HouseBuilding::generate( district
ctx.rng, .and_then(|d| self.town.as_ref().map(|t| t.districts().get(d)))
Vec3::new( .map(|d| d.alt)
house_pos.x, .filter(|_| false) // Temporary
house_pos.y, .unwrap_or_else(|| {
ctx.sim ctx.sim
.and_then(|sim| sim.get_alt_approx(self.origin + house_pos)) .and_then(|sim| sim.get_alt_approx(self.origin + house_pos))
.unwrap_or(0.0) .unwrap_or(0.0)
.ceil() as i32, .ceil() as i32
), })
)), } else {
continue;
};
let structure = Structure {
kind: if tile == town_center && i == 0 {
StructureKind::Keep(Building::<Keep>::generate(
ctx.rng,
Vec3::new(house_pos.x, house_pos.y, alt),
))
} else {
StructureKind::House(Building::<House>::generate(
ctx.rng,
Vec3::new(house_pos.x, house_pos.y, alt),
))
},
}; };
let bounds = structure.bounds_2d(); let bounds = structure.bounds_2d();
@ -501,12 +547,13 @@ impl Settlement {
} else { } else {
continue; continue;
}; };
let surface_z = col_sample.riverless_alt.floor() as i32; let land_surface_z = col_sample.riverless_alt.floor() as i32;
let mut surface_z = land_surface_z;
// Sample settlement // Sample settlement
let sample = self.land.get_at_block(rpos); let sample = self.land.get_at_block(rpos);
let noisy_color = |col: Rgb<u8>, factor: u32| { let noisy_color = move |col: Rgb<u8>, factor: u32| {
let nz = self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, surface_z)); let nz = self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, surface_z));
col.map(|e| { col.map(|e| {
(e as u32 + nz % (factor * 2)) (e as u32 + nz % (factor * 2))
@ -515,46 +562,75 @@ impl Settlement {
}) })
}; };
// District alt
if let Some(Plot::Town { district }) = sample.plot {
if let Some(d) = district
.and_then(|d| self.town.as_ref().map(|t| t.districts().get(d)))
.filter(|_| false)
// Temporary
{
let other = self
.land
.plot_at(sample.second_closest)
.and_then(|p| match p {
Plot::Town { district } => *district,
_ => None,
})
.and_then(|d| {
self.town.as_ref().map(|t| t.districts().get(d).alt as f32)
})
.filter(|_| false)
.unwrap_or(surface_z as f32);
surface_z = Lerp::lerp(
(other + d.alt as f32) / 2.0,
d.alt as f32,
(1.25 * sample.edge_dist / (d.alt as f32 - other).abs()).min(1.0),
) as i32;
}
}
// Paths // Paths
if let Some((WayKind::Path, dist, nearest)) = sample.way { // if let Some((WayKind::Path, dist, nearest)) = sample.way {
let inset = -1; // let inset = -1;
// Try to use the column at the centre of the path for sampling to make them // // Try to use the column at the centre of the path for sampling to make
// flatter // them // flatter
let col = get_column(offs + (nearest.floor().map(|e| e as i32) - rpos)) // let col = get_column(offs + (nearest.floor().map(|e| e as i32) - rpos))
.unwrap_or(col_sample); // .unwrap_or(col_sample);
let (bridge_offset, depth) = if let Some(water_dist) = col.water_dist { // 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, // ((water_dist.max(0.0) * 0.2).min(f32::consts::PI).cos() + 1.0) *
((1.0 - ((water_dist + 2.0) * 0.3).min(0.0).cos().abs()) // 5.0, ((1.0 - ((water_dist + 2.0) *
* (col.riverless_alt + 5.0 - col.alt).max(0.0) // 0.3).min(0.0).cos().abs())
* 1.75 // * (col.riverless_alt + 5.0 - col.alt).max(0.0)
+ 3.0) as i32, // * 1.75
) // + 3.0) as i32,
} else { // )
(0.0, 3) // } else {
}; // (0.0, 3)
let surface_z = (col.riverless_alt + bridge_offset).floor() as i32; // };
// let surface_z = (col.riverless_alt + bridge_offset).floor() as i32;
for z in inset - depth..inset { // for z in inset - depth..inset {
let _ = vol.set( // let _ = vol.set(
Vec3::new(offs.x, offs.y, surface_z + z), // Vec3::new(offs.x, offs.y, surface_z + z),
if bridge_offset >= 2.0 && dist >= 3.0 || 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)) // Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 80,
} else { // 100), 8)) } else {
Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 50, 30), 8)) // Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 50,
}, // 30), 8)) },
); // );
} // }
let head_space = (8 - (dist * 0.25).powf(6.0).round() as i32).max(1); // let head_space = (8 - (dist * 0.25).powf(6.0).round() as i32).max(1);
for z in inset..inset + head_space { // for z in inset..inset + head_space {
let pos = Vec3::new(offs.x, offs.y, surface_z + z); // let pos = Vec3::new(offs.x, offs.y, surface_z + z);
if vol.get(pos).unwrap().kind() != BlockKind::Water { // if vol.get(pos).unwrap().kind() != BlockKind::Water {
let _ = vol.set(pos, Block::empty()); // let _ = vol.set(pos, Block::empty());
} // }
} // }
// Ground colour // // Ground colour
} else { // } else
{
let mut surface_block = None; let mut surface_block = None;
let roll = let roll =
@ -564,27 +640,29 @@ impl Settlement {
Some(Plot::Dirt) => Some(Rgb::new(90, 70, 50)), Some(Plot::Dirt) => Some(Rgb::new(90, 70, 50)),
Some(Plot::Grass) => Some(Rgb::new(100, 200, 0)), Some(Plot::Grass) => Some(Rgb::new(100, 200, 0)),
Some(Plot::Water) => Some(Rgb::new(100, 150, 250)), Some(Plot::Water) => Some(Rgb::new(100, 150, 250)),
Some(Plot::Town) => { //Some(Plot::Town { district }) => None,
if let Some((_, path_nearest)) = col_sample.path { Some(Plot::Town { .. }) => {
if let Some((_, path_nearest, _, _)) = col_sample.path {
let path_dir = (path_nearest - wpos2d.map(|e| e as f32)) let path_dir = (path_nearest - wpos2d.map(|e| e as f32))
.rotated_z(f32::consts::PI / 2.0) .rotated_z(f32::consts::PI / 2.0)
.normalized(); .normalized();
let is_lamp = if path_dir.x.abs() > path_dir.y.abs() { 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() wpos2d.x as f32 % 30.0 / path_dir.dot(Vec2::unit_y()).abs()
<= 1.0 <= 1.0
} else { } else {
wpos2d.y as f32 % 20.0 / path_dir.dot(Vec2::unit_x()).abs() (wpos2d.y as f32 + 10.0) % 30.0
/ path_dir.dot(Vec2::unit_x()).abs()
<= 1.0 <= 1.0
}; };
if (col_sample.path.map(|(dist, _)| dist > 6.0 && dist < 7.0).unwrap_or(false) && is_lamp) //roll(0, 50) == 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 || (roll(0, 2000) == 0 && col_sample.path.map(|(dist, _, _, _)| dist > 20.0).unwrap_or(true))
{ {
surface_block = surface_block =
Some(Block::new(BlockKind::StreetLamp, Rgb::white())); Some(Block::new(BlockKind::StreetLamp, Rgb::white()));
} }
} }
Some(Rgb::new(100, 90, 75).map2(Rgb::iota(), |e: u8, i: i32| { Some(Rgb::new(100, 95, 65).map2(Rgb::iota(), |e: u8, i: i32| {
e.saturating_add( e.saturating_add(
(self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, i * 5)) % 1) (self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, i * 5)) % 1)
as u8, as u8,
@ -657,14 +735,27 @@ impl Settlement {
}; };
if let Some(color) = color { if let Some(color) = color {
if col_sample.water_dist.map(|dist| dist > 2.0).unwrap_or(true) { let is_path = col_sample
for z in -8..3 { .path
.map(|(dist, _, path, _)| dist < path.width)
.unwrap_or(false);
if col_sample.water_dist.map(|dist| dist > 2.0).unwrap_or(true) && !is_path
{
let diff = (surface_z - land_surface_z).abs();
for z in -8 - diff..4 + diff {
let pos = Vec3::new(offs.x, offs.y, surface_z + z); let pos = Vec3::new(offs.x, offs.y, surface_z + z);
let block = vol.get(pos).ok().copied().unwrap_or_else(Block::empty);
if block.kind() == BlockKind::Air {
break;
}
if let (0, Some(block)) = (z, surface_block) { if let (0, Some(block)) = (z, surface_block) {
let _ = vol.set(pos, block); let _ = vol.set(pos, block);
} else if z >= 0 { } else if z >= 0 {
if vol.get(pos).unwrap().kind() != BlockKind::Water { if block.kind() != BlockKind::Water {
let _ = vol.set(pos, Block::empty()); let _ = vol.set(pos, Block::empty());
} }
} else { } else {
@ -728,34 +819,28 @@ impl Settlement {
continue; continue;
} }
match &structure.kind { let bounds = structure.bounds();
StructureKind::House(b) => {
let bounds = b.bounds();
for x in bounds.min.x..bounds.max.x + 1 { for x in bounds.min.x..bounds.max.x + 1 {
for y in bounds.min.y..bounds.max.y + 1 { for y in bounds.min.y..bounds.max.y + 1 {
let col = if let Some(col) = let col = if let Some(col) = get_column(self.origin + Vec2::new(x, y) - wpos2d)
get_column(self.origin + Vec2::new(x, y) - wpos2d)
{ {
col col
} else { } else {
continue; 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 rpos = Vec3::new(x, y, z);
let wpos = Vec3::from(self.origin) + rpos; let wpos = Vec3::from(self.origin) + rpos;
let coffs = wpos - Vec3::from(wpos2d); let coffs = wpos - Vec3::from(wpos2d);
if let Some(block) = b.sample(rpos) { if let Some(block) = structure.sample(rpos) {
let _ = vol.set(coffs, block); let _ = vol.set(coffs, block);
} }
} }
} }
} }
},
}
} }
} }
@ -785,7 +870,7 @@ impl Settlement {
let entity_wpos = Vec3::new(wpos2d.x as f32, wpos2d.y as f32, col_sample.alt + 3.0); 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)) if matches!(sample.plot, Some(Plot::Town { .. }))
&& RandomField::new(self.seed).chance(Vec3::from(wpos2d), 1.0 / (50.0 * 50.0)) && RandomField::new(self.seed).chance(Vec3::from(wpos2d), 1.0 / (50.0 * 50.0))
{ {
let is_human: bool; let is_human: bool;
@ -875,7 +960,7 @@ impl Settlement {
Some(Plot::Dirt) => return Some(Rgb::new(90, 70, 50)), Some(Plot::Dirt) => return Some(Rgb::new(90, 70, 50)),
Some(Plot::Grass) => return Some(Rgb::new(100, 200, 0)), Some(Plot::Grass) => return Some(Rgb::new(100, 200, 0)),
Some(Plot::Water) => return Some(Rgb::new(100, 150, 250)), Some(Plot::Water) => return Some(Rgb::new(100, 150, 250)),
Some(Plot::Town) => { Some(Plot::Town { .. }) => {
return Some(Rgb::new(150, 110, 60).map2(Rgb::iota(), |e: u8, i: i32| { 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) e.saturating_add((self.noise.get(Vec3::new(pos.x, pos.y, i * 5)) % 16) as u8)
.saturating_sub(8) .saturating_sub(8)
@ -927,7 +1012,9 @@ pub enum Plot {
Dirt, Dirt,
Grass, Grass,
Water, Water,
Town, Town {
district: Option<Id<District>>,
},
Field { Field {
farm: Id<Farm>, farm: Id<Farm>,
seed: u32, seed: u32,
@ -987,6 +1074,8 @@ pub struct Sample<'a> {
plot: Option<&'a Plot>, plot: Option<&'a Plot>,
way: Option<(&'a WayKind, f32, Vec2<f32>)>, way: Option<(&'a WayKind, f32, Vec2<f32>)>,
tower: Option<(&'a Tower, Vec2<i32>)>, tower: Option<(&'a Tower, Vec2<i32>)>,
edge_dist: f32,
second_closest: Vec2<i32>,
} }
pub struct Land { pub struct Land {
@ -1021,6 +1110,15 @@ impl Land {
.min_by_key(|(center, _)| center.distance_squared(pos)) .min_by_key(|(center, _)| center.distance_squared(pos))
.unwrap() .unwrap()
.0; .0;
let second_closest = neighbors
.iter()
.filter(|(center, _)| *center != closest)
.min_by_key(|(center, _)| center.distance_squared(pos))
.unwrap()
.0;
sample.second_closest = second_closest.map(to_tile);
sample.edge_dist = (second_closest - pos).map(|e| e as f32).magnitude()
- (closest - pos).map(|e| e as f32).magnitude();
let center_tile = self.tile_at(neighbors[4].0.map(to_tile)); let center_tile = self.tile_at(neighbors[4].0.map(to_tile));
@ -1064,6 +1162,7 @@ impl Land {
self.tiles.get(&pos).map(|tile| self.plots.get(tile.plot)) self.tiles.get(&pos).map(|tile| self.plots.get(tile.plot))
} }
#[allow(dead_code)]
pub fn plot_at_mut(&mut self, pos: Vec2<i32>) -> Option<&mut Plot> { pub fn plot_at_mut(&mut self, pos: Vec2<i32>) -> Option<&mut Plot> {
self.tiles self.tiles
.get(&pos) .get(&pos)

View File

@ -0,0 +1,82 @@
use super::{GenCtx, AREA_SIZE};
use common::store::Store;
use rand::prelude::*;
use vek::*;
pub struct Town {
pub base_tile: Vec2<i32>,
radius: i32,
districts: Store<District>,
}
impl Town {
pub fn districts(&self) -> &Store<District> { &self.districts }
pub fn generate(origin: Vec2<i32>, base_tile: Vec2<i32>, ctx: &mut GenCtx<impl Rng>) -> Self {
let mut this = Self {
base_tile,
radius: 4,
districts: Store::default(),
};
this.generate_districts(origin, ctx);
this
}
fn generate_districts(&mut self, origin: Vec2<i32>, ctx: &mut GenCtx<impl Rng>) {
let base_aabr = Aabr {
min: self.base_tile - self.radius,
max: self.base_tile + self.radius,
};
gen_plot(base_aabr, ctx).for_each(base_aabr, &mut |aabr| {
if aabr.center().distance_squared(self.base_tile) < self.radius.pow(2) {
self.districts.insert(District {
seed: ctx.rng.gen(),
aabr,
alt: ctx
.sim
.and_then(|sim| {
sim.get_alt_approx(
origin + aabr.center() * AREA_SIZE as i32 + AREA_SIZE as i32 / 2,
)
})
.unwrap_or(0.0) as i32,
});
}
});
}
}
pub struct District {
pub seed: u32,
pub aabr: Aabr<i32>,
pub alt: i32,
}
enum Plot {
District,
Parent(Vec<(Aabr<i32>, Plot)>),
}
impl Plot {
fn for_each(&self, aabr: Aabr<i32>, f: &mut impl FnMut(Aabr<i32>)) {
match self {
Plot::District => f(aabr),
Plot::Parent(children) => children.iter().for_each(|(aabr, p)| p.for_each(*aabr, f)),
}
}
}
fn gen_plot(aabr: Aabr<i32>, ctx: &mut GenCtx<impl Rng>) -> Plot {
if aabr.size().product() <= 9 {
Plot::District
} else if aabr.size().w < aabr.size().h {
let [a, b] = aabr.split_at_y(aabr.min.y + ctx.rng.gen_range(1, aabr.size().h));
Plot::Parent(vec![(a, gen_plot(a, ctx)), (b, gen_plot(b, ctx))])
} else {
let [a, b] = aabr.split_at_x(aabr.min.x + ctx.rng.gen_range(1, aabr.size().w));
Plot::Parent(vec![(a, gen_plot(a, ctx)), (b, gen_plot(b, ctx))])
}
}

79
world/src/util/map_vec.rs Normal file
View File

@ -0,0 +1,79 @@
use crate::util::DHashMap;
use std::hash::Hash;
#[derive(Clone, Debug)]
pub struct MapVec<K, T> {
/// We use this hasher (FxHasher32) because
/// (1) we don't care about DDOS attacks (ruling out SipHash);
/// (2) we care about determinism across computers (ruling out AAHash);
/// (3) we have 1-byte keys (for which FxHash is supposedly fastest).
entries: DHashMap<K, T>,
default: T,
}
/// Need manual implementation of Default since K doesn't need that bound.
impl<K, T: Default> Default for MapVec<K, T> {
fn default() -> Self {
Self {
entries: Default::default(),
default: Default::default(),
}
}
}
impl<K: Copy + Eq + Hash, T: Clone> MapVec<K, T> {
pub fn from_list<'a>(i: impl IntoIterator<Item = &'a (K, T)>, default: T) -> Self
where
K: 'a,
T: 'a,
{
Self {
entries: i.into_iter().cloned().collect(),
default,
}
}
pub fn from_default(default: T) -> Self {
Self {
entries: DHashMap::default(),
default,
}
}
pub fn get_mut(&mut self, entry: K) -> &mut T {
let default = &self.default;
self.entries.entry(entry).or_insert_with(|| default.clone())
}
pub fn get(&self, entry: K) -> &T { self.entries.get(&entry).unwrap_or(&self.default) }
#[allow(clippy::clone_on_copy)] // TODO: Pending review in #587
pub fn map<U: Default>(self, mut f: impl FnMut(K, T) -> U) -> MapVec<K, U> {
MapVec {
entries: self
.entries
.into_iter()
.map(|(s, v)| (s.clone(), f(s, v)))
.collect(),
default: U::default(),
}
}
pub fn iter(&self) -> impl Iterator<Item = (K, &T)> + '_ {
self.entries.iter().map(|(s, v)| (*s, v))
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (K, &mut T)> + '_ {
self.entries.iter_mut().map(|(s, v)| (*s, v))
}
}
impl<K: Copy + Eq + Hash, T: Clone> std::ops::Index<K> for MapVec<K, T> {
type Output = T;
fn index(&self, entry: K) -> &Self::Output { self.get(entry) }
}
impl<K: Copy + Eq + Hash, T: Clone> std::ops::IndexMut<K> for MapVec<K, T> {
fn index_mut(&mut self, entry: K) -> &mut Self::Output { self.get_mut(entry) }
}

View File

@ -1,6 +1,7 @@
// pub mod boruvka; // pub mod boruvka;
pub mod fast_noise; pub mod fast_noise;
pub mod grid; pub mod grid;
pub mod map_vec;
pub mod random; pub mod random;
pub mod sampler; pub mod sampler;
pub mod seed_expan; pub mod seed_expan;
@ -12,6 +13,7 @@ pub mod unit_chooser;
pub use self::{ pub use self::{
fast_noise::FastNoise, fast_noise::FastNoise,
grid::Grid, grid::Grid,
map_vec::MapVec,
random::{RandomField, RandomPerm}, random::{RandomField, RandomPerm},
sampler::{Sampler, SamplerMut}, sampler::{Sampler, SamplerMut},
small_cache::SmallCache, small_cache::SmallCache,
@ -19,8 +21,15 @@ pub use self::{
unit_chooser::UnitChooser, unit_chooser::UnitChooser,
}; };
use fxhash::FxHasher32;
use hashbrown::{HashMap, HashSet};
use std::hash::BuildHasherDefault;
use vek::*; use vek::*;
// Deterministic HashMap and HashSet
pub type DHashMap<K, V> = HashMap<K, V, BuildHasherDefault<FxHasher32>>;
pub type DHashSet<T> = HashSet<T, BuildHasherDefault<FxHasher32>>;
pub fn attempt<T>(max_iters: usize, mut f: impl FnMut() -> Option<T>) -> Option<T> { pub fn attempt<T>(max_iters: usize, mut f: impl FnMut() -> Option<T>) -> Option<T> {
(0..max_iters).find_map(|_| f()) (0..max_iters).find_map(|_| f())
} }

View File

@ -20,7 +20,22 @@ impl Sampler<'static> for RandomField {
fn get(&self, pos: Self::Index) -> Self::Sample { fn get(&self, pos: Self::Index) -> Self::Sample {
let pos = pos.map(|e| u32::from_le_bytes(e.to_le_bytes())); let pos = pos.map(|e| u32::from_le_bytes(e.to_le_bytes()));
seed_expan::diffuse_mult(&[self.seed, pos.x, pos.y, pos.z])
let mut a = self.seed;
a = (a ^ 61) ^ (a >> 16);
a = a.wrapping_add(a << 3);
a ^= pos.x;
a ^= a >> 4;
a = a.wrapping_mul(0x27d4eb2d);
a ^= a >> 15;
a ^= pos.y;
a = (a ^ 61) ^ (a >> 16);
a = a.wrapping_add(a << 3);
a ^= a >> 4;
a ^= pos.z;
a = a.wrapping_mul(0x27d4eb2d);
a ^= a >> 15;
a
} }
} }

26
world/src/util/wgrid.rs Normal file
View File

@ -0,0 +1,26 @@
use super::Grid;
use vek::*;
pub struct WGrid<T> {
cell_size: u32,
grid: Grid<T>,
}
impl<T> WGrid<T> {
pub fn new(radius: u32, cell_size: u32, default_cell: T) -> Self
where T: Clone
{
Self {
cell_size,
grid: Grid::new(Vec2::broadcast(radius as i32 * 2 + 1), default_cell),
}
}
fn offset(&self) -> Vec2<i32> {
self.grid.size() / 2
}
pub fn get_local(&self, pos: Vec2<i32>) -> Option<&T> {
self.grid.get(pos + self.offset())
}
}