diff --git a/Cargo.lock b/Cargo.lock index c94aed1c5b0af214129004fb50fd2737cf7971d7..1c95b736ee6c90c505d3cad2ef52ea762398986b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,7 +30,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom", + "getrandom 0.2.8", "once_cell", "version_check", ] @@ -344,6 +344,16 @@ dependencies = [ "regex", ] +[[package]] +name = "bevy_ecs_tilemap" +version = "0.9.0" +source = "git+https://github.com/StarArawn/bevy_ecs_tilemap?rev=eb20fcaccdd253ea5bf280cac7ffc5a69b674df2#eb20fcaccdd253ea5bf280cac7ffc5a69b674df2" +dependencies = [ + "bevy", + "log", + "regex", +] + [[package]] name = "bevy_encase_derive" version = "0.9.1" @@ -780,7 +790,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16750aae52cd35bd7b60eb61cee883420b250e11b4a290b8d44b2b2941795739" dependencies = [ "ahash", - "getrandom", + "getrandom 0.2.8", "hashbrown", "instant", "tracing", @@ -1441,6 +1451,7 @@ version = "0.1.0" dependencies = [ "anyhow", "bevy", + "bevy_ecs_tilemap 0.9.0 (git+https://github.com/StarArawn/bevy_ecs_tilemap?rev=eb20fcaccdd253ea5bf280cac7ffc5a69b674df2)", "bevy_tweening", "fastrand", "iyes_loopless", @@ -1448,6 +1459,7 @@ dependencies = [ "micro_asset_io", "micro_banimate", "micro_musicbox", + "noise", "serde", "serde_json", "thiserror", @@ -1455,6 +1467,17 @@ dependencies = [ "web-sys", ] +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.2.8" @@ -1464,7 +1487,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -1965,7 +1988,7 @@ checksum = "db0f957d47035e81cda7636e2c9e17aa8fb8816a29bd1739935893b605ad4a09" dependencies = [ "anyhow", "bevy", - "bevy_ecs_tilemap", + "bevy_ecs_tilemap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde", "serde_json", ] @@ -2003,7 +2026,7 @@ checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.42.0", ] @@ -2148,6 +2171,17 @@ dependencies = [ "memoffset", ] +[[package]] +name = "noise" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba869e17168793186c10ca82c7079a4ffdeac4f1a7d9e755b9491c028180e40" +dependencies = [ + "num-traits", + "rand", + "rand_xorshift", +] + [[package]] name = "nom" version = "7.1.1" @@ -2395,6 +2429,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro-crate" version = "1.2.1" @@ -2436,6 +2476,56 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17fd96390ed3feda12e1dfe2645ed587e0bea749e319333f104a33ff62f77a0b" +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_xorshift" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8" +dependencies = [ + "rand_core", +] + [[package]] name = "range-alloc" version = "0.1.2" @@ -3015,7 +3105,7 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" dependencies = [ - "getrandom", + "getrandom 0.2.8", "serde", ] @@ -3054,6 +3144,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/assets/resources.apack b/assets/resources.apack index b3121861cf1be79f9eaebfdcd0074d351e85a440..1f14276a8d0e7aeeac57a6708122b23946fdcd1c 100644 --- a/assets/resources.apack +++ b/assets/resources.apack @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:14b68833d9afa61d753201afec2aa717ed8266344799c5a46bdb03892288331a -size 44828 +oid sha256:e279a1551f7c02cfba8b16f358e4bf8ae62065e97d511f935984f5548a20fbf5 +size 49928 diff --git a/game_core/Cargo.toml b/game_core/Cargo.toml index af78c8b55d9699089c7d2dd1150694471dc23aa3..59880345d99d8b513f08625262388a0c0b16ea1c 100644 --- a/game_core/Cargo.toml +++ b/game_core/Cargo.toml @@ -21,8 +21,10 @@ micro_musicbox.workspace = true micro_asset_io = { path = "../micro_asset_io" } +bevy_ecs_tilemap = { git = "https://github.com/StarArawn/bevy_ecs_tilemap", rev = "eb20fcaccdd253ea5bf280cac7ffc5a69b674df2" } bevy_tweening = "0.6.0" toml = "0.5.9" +noise = "0.8.2" #remote_events = { git = "https://lab.lcr.gr/microhacks/micro-bevy-remote-events.git", rev = "be0c6b43a73e4c5e7ece20797e3d6f59340147b4"} diff --git a/game_core/src/lib.rs b/game_core/src/lib.rs index cd6d8dcfd7d64c2e9d4066f4b458ed62d3343301..d487f56ae4e6cfd195d84d3fa124ee1fe6d0fec9 100644 --- a/game_core/src/lib.rs +++ b/game_core/src/lib.rs @@ -5,3 +5,4 @@ pub mod multiplayer; pub mod splash_screen; pub mod states; pub mod system; +pub mod world; diff --git a/game_core/src/main.rs b/game_core/src/main.rs index ba118436d60bf522ed2e16438c5a429e1ff02229..f41b9b4ffec62f4b80e41baa2c8d82a9ed3937c5 100644 --- a/game_core/src/main.rs +++ b/game_core/src/main.rs @@ -1,4 +1,5 @@ use bevy::prelude::*; +use bevy_ecs_tilemap::TilemapPlugin; use game_core::assets::AssetHandles; use game_core::system::flow::AppState; use game_core::system::resources::InitAppPlugins; @@ -16,5 +17,7 @@ fn main() { .add_plugin(game_core::states::StatesPlugin) .add_plugin(micro_asset_io::MicroAssetIOPlugin) .add_plugin(bevy_tweening::TweeningPlugin) + .add_plugin(game_core::world::WorldPlugin) + .add_plugin(TilemapPlugin) .run(); } diff --git a/game_core/src/states/menu_state.rs b/game_core/src/states/menu_state.rs index e0638347a92677d9bbc5b4df9598f7b8e2c5b7cf..23ab273af1a8ac5c9b35a3e6774fd3f70079e2bd 100644 --- a/game_core/src/states/menu_state.rs +++ b/game_core/src/states/menu_state.rs @@ -3,8 +3,10 @@ use std::time::Duration; use bevy::prelude::*; use bevy_tweening::lens::TextColorLens; use bevy_tweening::{Animator, EaseFunction, RepeatCount, RepeatStrategy, Tween}; +use iyes_loopless::state::NextState; use crate::assets::AssetHandles; +use crate::system::flow::AppState; #[derive(Component)] pub struct MenuStateEntity; @@ -19,15 +21,18 @@ pub fn spawn_menu_entities(mut commands: Commands, assets: Res<AssetHandles>) { )); commands - .spawn(NodeBundle { - style: Style { - size: Size::new(Val::Percent(100.0), Val::Percent(100.0)), - flex_direction: FlexDirection::Column, - align_items: AlignItems::Center, + .spawn(( + MenuStateEntity, + NodeBundle { + style: Style { + size: Size::new(Val::Percent(100.0), Val::Percent(100.0)), + flex_direction: FlexDirection::Column, + align_items: AlignItems::Center, + ..Default::default() + }, ..Default::default() }, - ..Default::default() - }) + )) .with_children(|commands| { commands.spawn(TextBundle { text: Text::from_section( @@ -47,7 +52,7 @@ pub fn spawn_menu_entities(mut commands: Commands, assets: Res<AssetHandles>) { commands.spawn(( TextBundle { text: Text::from_section( - "> Get Trading <", + "> Press Space <", TextStyle { font_size: 48.0, font: assets.font("default"), @@ -77,6 +82,12 @@ pub fn spawn_menu_entities(mut commands: Commands, assets: Res<AssetHandles>) { }); } +pub fn go_to_game(input: Res<Input<KeyCode>>, mut commands: Commands) { + if input.just_released(KeyCode::Space) { + commands.insert_resource(NextState(AppState::InGame)); + } +} + pub fn despawn_menu_entities(mut commands: Commands, query: Query<Entity, With<MenuStateEntity>>) { for entity in &query { commands.entity(entity).despawn_recursive(); diff --git a/game_core/src/states/mod.rs b/game_core/src/states/mod.rs index 20d5cbde4e5d2574926702faf92e26d62847f9d0..a75b179f11527d0472451760455a3929f6957d0a 100644 --- a/game_core/src/states/mod.rs +++ b/game_core/src/states/mod.rs @@ -1,6 +1,7 @@ use bevy::app::{App, Plugin}; -use iyes_loopless::prelude::AppLooplessStateExt; +use iyes_loopless::prelude::{AppLooplessStateExt, ConditionSet}; +use crate::states::menu_state::go_to_game; use crate::system::flow::AppState; mod menu_state; @@ -9,6 +10,12 @@ pub struct StatesPlugin; impl Plugin for StatesPlugin { fn build(&self, app: &mut App) { app.add_enter_system(AppState::Menu, menu_state::spawn_menu_entities) - .add_exit_system(AppState::Menu, menu_state::despawn_menu_entities); + .add_exit_system(AppState::Menu, menu_state::despawn_menu_entities) + .add_system_set( + ConditionSet::new() + .run_in_state(AppState::Menu) + .with_system(go_to_game) + .into(), + ); } } diff --git a/game_core/src/system/camera.rs b/game_core/src/system/camera.rs index e3caa697be976867215dfc1f8cb7075486102bee..072a1856af668db76a8143c1461f1325d7fd2867 100644 --- a/game_core/src/system/camera.rs +++ b/game_core/src/system/camera.rs @@ -1,14 +1,16 @@ use bevy::app::App; +use bevy::input::mouse::MouseMotion; use bevy::math::{Vec2, Vec3Swizzles}; use bevy::prelude::{ - Camera2dBundle, Commands, Component, CoreStage, Entity, OrthographicProjection, Plugin, Query, - Transform, With, + Camera2dBundle, Commands, Component, CoreStage, Entity, EventReader, Input, MouseButton, + OrthographicProjection, Plugin, Projection, Query, Res, Transform, Windows, With, }; use bevy::render::camera::ScalingMode; -use iyes_loopless::prelude::AppLooplessStateExt; +use iyes_loopless::prelude::{AppLooplessStateExt, ConditionSet}; use crate::system::flow::AppState; use crate::system::load_config::virtual_size; +use crate::system::window::WindowManager; /// A flag component to indicate which entity should be followed by the camera #[derive(Component)] @@ -39,6 +41,25 @@ pub fn spawn_orthographic_camera(mut commands: Commands) { )); } +pub fn pan_camera( + mut ev_motion: EventReader<MouseMotion>, + input_mouse: Res<Input<MouseButton>>, + mut query: Query<(&mut Transform, &Projection), With<GameCamera>>, + windows: Res<Windows>, +) { + let mut pan = Vec2::ZERO; + + if input_mouse.pressed(MouseButton::Right) { + for ev in ev_motion.iter() { + pan += ev.delta; + } + } + + for (mut transform, _) in &mut query { + if pan.length_squared() > 0.0 {} + } +} + /// System that takes the average location of all chase camera entities, and updates the location /// of all world cameras to track the average location. /// @@ -49,6 +70,10 @@ pub fn sync_chase_camera_location( chased_query: Query<&Transform, With<ChaseCam>>, camera_query: Query<(Entity, &Transform), With<GameCamera>>, ) { + if chased_query.is_empty() { + return; + } + let mut average_location = Vec2::new(0.0, 0.0); let mut count = 0; for location in chased_query.iter() { @@ -75,6 +100,12 @@ pub struct CameraManagementPlugin; impl Plugin for CameraManagementPlugin { fn build(&self, app: &mut App) { app.add_enter_system(AppState::Preload, spawn_orthographic_camera) - .add_system_to_stage(CoreStage::PreUpdate, sync_chase_camera_location); + .add_system_to_stage(CoreStage::PreUpdate, sync_chase_camera_location) + .add_system_set( + ConditionSet::new() + .run_in_state(AppState::InGame) + .with_system(pan_camera) + .into(), + ); } } diff --git a/game_core/src/system/utilities.rs b/game_core/src/system/utilities.rs index 0244a876e4e91ca99e6e4935d84ac387b8b956fb..180bb4226b0a162371f898f14644acc439fd5f39 100644 --- a/game_core/src/system/utilities.rs +++ b/game_core/src/system/utilities.rs @@ -1,3 +1,7 @@ +use std::ops::{Deref, DerefMut}; + +use bevy::prelude::UVec2; + #[inline] pub fn f32_max(a: f32, b: f32) -> f32 { if a > b { @@ -30,3 +34,54 @@ pub fn f32_min_mag(a: f32, b: f32) -> f32 { b.abs() } } + +#[derive(Debug, Copy, Clone)] +pub struct Indexer { + width: usize, + height: usize, +} + +impl Indexer { + pub fn new(width: usize, height: usize) -> Self { + Indexer { width, height } + } + pub fn index(&self, x: usize, y: usize) -> usize { + (y * self.width) + x + } + pub fn checked_index(&self, x: usize, y: usize) -> Option<usize> { + if self.is_coordinate_valid(x, y) { + Some(self.index(x, y)) + } else { + None + } + } + pub fn reverse(&self, index: usize) -> (usize, usize) { + (index % self.width, index / self.width) + } + pub fn checked_reverse(&self, idx: usize) -> Option<(usize, usize)> { + if self.is_index_valid(idx) { + Some(self.reverse(idx)) + } else { + None + } + } + pub fn index_within(&self, idx: usize) -> bool { + let (x, y) = self.reverse(idx); + x >= 0 && x < self.width && y >= 0 && y < self.height + } + pub fn is_uvec2_valid(&self, point: UVec2) -> bool { + self.is_coordinate_valid(point.x as usize, point.y as usize) + } + pub fn is_coordinate_valid(&self, x: usize, y: usize) -> bool { + x < self.width && y < self.height + } + pub fn is_index_valid(&self, idx: usize) -> bool { + idx < self.width * self.height + } + pub fn width(&self) -> usize { + self.width + } + pub fn height(&self) -> usize { + self.height + } +} diff --git a/game_core/src/world/debug_gen.rs b/game_core/src/world/debug_gen.rs new file mode 100644 index 0000000000000000000000000000000000000000..d8481accaf111268b0a819c6e1760ce879d457a1 --- /dev/null +++ b/game_core/src/world/debug_gen.rs @@ -0,0 +1,55 @@ +use bevy::prelude::*; +use bevy::render::render_resource::{Extent3d, TextureDimension, TextureFormat}; +use bevy::render::texture::{BevyDefault, TextureFormatPixelInfo}; +use fastrand::Rng; + +use crate::system::utilities::Indexer; +use crate::world::generate_overworld::{generate_overworld, WorldTileType}; + +pub fn generate_and_spawn_world(mut commands: Commands, mut images: ResMut<Assets<Image>>) { + let world = generate_overworld(300, 300, Rng::new()); + let format = TextureFormat::bevy_default(); + + let mut img = Image::new_fill( + Extent3d { + height: 300, + width: 300, + depth_or_array_layers: 1, + }, + TextureDimension::D2, + vec![100; format.pixel_size()].as_slice(), + TextureFormat::bevy_default(), + ); + + log::info!("DOING IT"); + log::info!("{:?}", format.describe()); + + let mut write_pixel = |index: usize, data: &[u8; 4]| { + img.data[index * format.pixel_size()] = data[0]; + img.data[index * format.pixel_size() + 1] = data[1]; + img.data[index * format.pixel_size() + 2] = data[2]; + if format.pixel_size() == 4 { + img.data[index * format.pixel_size() + 3] = data[3]; + } + }; + + for (index, tile) in world.terrain.iter().enumerate() { + match tile { + WorldTileType::Water => write_pixel(index, &[30, 144, 255, 255]), + WorldTileType::Sand => write_pixel(index, &[255, 255, 224, 255]), + WorldTileType::Dirt => write_pixel(index, &[165, 42, 42, 255]), + WorldTileType::Grass => write_pixel(index, &[34, 139, 34, 255]), + WorldTileType::Mountain => write_pixel(index, &[105, 105, 105, 255]), + WorldTileType::Swamp => write_pixel(index, &[75, 0, 130, 255]), + WorldTileType::Snow => write_pixel(index, &[240, 255, 255, 255]), + WorldTileType::Desert => write_pixel(index, &[240, 230, 140, 255]), + } + } + + let handle = images.add(img); + commands.spawn(SpriteBundle { + texture: handle, + transform: Transform::from_xyz(0.0, 0.0, 100.0), + ..Default::default() + }); +} diff --git a/game_core/src/world/generate_overworld.rs b/game_core/src/world/generate_overworld.rs new file mode 100644 index 0000000000000000000000000000000000000000..94702f068578f25aa2f8b2cc607169bfda1a63a0 --- /dev/null +++ b/game_core/src/world/generate_overworld.rs @@ -0,0 +1,335 @@ +use std::env::var; + +use bevy::math::{uvec2, UVec2}; +use fastrand::Rng; +use noise::{Blend, Clamp, NoiseFn, Perlin, Simplex}; + +use crate::system::utilities::Indexer; + +pub struct Overworld { + pub width: usize, + pub height: usize, + pub terrain: Vec<WorldTileType>, +} + +#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Ord, Eq)] +#[repr(C)] +pub enum WorldTileType { + Water = 0, + Sand = 1, + Dirt = 2, + Grass = 3, + Mountain = 4, + Swamp = 5, + Snow = 6, + Desert = 7, +} + +impl WorldTileType { + pub fn get_basic_tile(&self) -> usize { + match self { + WorldTileType::Water => 225, + WorldTileType::Sand => vec![294, 295, 296, 297, 298][fastrand::usize(0..5)], + WorldTileType::Dirt => vec![1, 2, 3, 4, 5][fastrand::usize(0..5)], + WorldTileType::Grass => 0, + WorldTileType::Mountain => 68, // vec![68, 6, 7, 8, 9, 10][fastrand::usize(0..6)], + WorldTileType::Swamp => vec![43, 44, 45][fastrand::usize(0..3)], + WorldTileType::Snow => vec![11, 12, 13, 14][fastrand::usize(0..4)], + WorldTileType::Desert => vec![326, 327, 328, 329, 330][fastrand::usize(0..5)], + } + } +} + +fn cap_variance(var: &mut usize) { + *var = (*var).clamp(35, 65); +} + +fn adjust_variance(var: &mut usize, rng: &Rng) { + if (rng.f32() - (*var as f32 / 100.0)) > 0.3 { + *var = var.saturating_add(rng.usize(0..2)); + } else { + *var = var.saturating_sub(rng.usize(0..2)); + } +} + +fn random_ground_spot(indexer: &Indexer, values: &[WorldTileType], rng: &Rng) -> UVec2 { + let width = indexer.width() as u32; + let height = indexer.height() as u32; + + let min_x = width / 4; + let min_y = height / 4; + let max_x = (width / 4) * 3; + let max_y = (height / 4) * 3; + + let mut attempts = 500; + while attempts > 0 { + let guess_x = rng.u32(min_x..=max_x); + let guess_y = rng.u32(min_y..=max_y); + + if values[indexer.index(guess_x as usize, guess_y as usize)] == WorldTileType::Grass { + return uvec2(guess_x, guess_y); + } + + attempts -= 1; + } + + UVec2::new(0, 0) +} + +fn draw_across( + x: usize, + y: usize, + width: usize, + tile: WorldTileType, + idx: &Indexer, + values: &mut [WorldTileType], +) { + let half = width / 2; + for x_val in x - half..x + half { + if idx.is_coordinate_valid(x_val, y) && values[idx.index(x_val, y)] == WorldTileType::Grass + { + values[idx.index(x_val, y)] = tile; + } + } +} + +fn draw_updown( + x: usize, + y: usize, + height: usize, + tile: WorldTileType, + idx: &Indexer, + values: &mut [WorldTileType], +) { + let half = height / 2; + for y_val in y - half..y + half { + if idx.is_coordinate_valid(x, y_val) && values[idx.index(x, y_val)] == WorldTileType::Grass + { + values[idx.index(x, y_val)] = tile; + } + } +} + +pub fn count_world_tiles(world: &Overworld, tile_type: WorldTileType) -> usize { + world.terrain.iter().fold(0, |count, current| { + if *current == tile_type { + count + 1 + } else { + count + } + }) +} + +pub fn generate_overworld(width: usize, height: usize, rng: Rng) -> Overworld { + let mut attempts = 100; + let mut world = generate_overworld_data(width, height, &rng); + + loop { + let total_tiles = width * height; + let water_tiles = count_world_tiles(&world, WorldTileType::Water); + let mountain_tiles = count_world_tiles(&world, WorldTileType::Mountain); + let ground_tiles = count_world_tiles(&world, WorldTileType::Grass); + + // Regen world if it's not too good: + // - more than 10% mountain + // - less than 50% regular grass + if mountain_tiles > ((total_tiles - water_tiles) / 10) + || ground_tiles < ((total_tiles - water_tiles) / 2) + { + world = generate_overworld_data(width, height, &rng); + } else { + return world; + } + + attempts -= 1; + if attempts == 0 { + break; + } + } + + world +} + +pub fn generate_overworld_data(width: usize, height: usize, rng: &Rng) -> Overworld { + let indexer = Indexer::new(width, height); + let mut values = vec![WorldTileType::Grass; width * height]; + + let mut variance = 50; + + for x in 0..width { + let mut y = variance; + for y_val in 0..y { + values[indexer.index(x, y_val)] = WorldTileType::Water; + } + for y_val in y..y + (y / 2) { + values[indexer.index(x, y_val)] = WorldTileType::Sand; + } + + adjust_variance(&mut variance, &rng); + } + + cap_variance(&mut variance); + + for x in 0..width { + let mut y = height - variance; + for y_val in y..height { + values[indexer.index(x, y_val)] = WorldTileType::Water; + } + for y_val in y - (variance / 2)..y { + values[indexer.index(x, y_val)] = WorldTileType::Sand; + } + + adjust_variance(&mut variance, &rng); + } + + cap_variance(&mut variance); + + for y in 0..width { + let mut x = variance; + for x_val in 0..x { + values[indexer.index(x_val, y)] = WorldTileType::Water; + } + for x_val in x..x + (x / 2) { + if values[indexer.index(x_val, y)] == WorldTileType::Water { + break; + } + values[indexer.index(x_val, y)] = WorldTileType::Sand; + } + + adjust_variance(&mut variance, &rng); + } + + cap_variance(&mut variance); + + for y in 0..width { + let mut x = width - variance; + for x_val in x..width { + values[indexer.index(x_val, y)] = WorldTileType::Water; + } + // + (rng.usize(0..(variance / 2).max(1)))) + for x_val in x - (variance / 2)..x { + if values[indexer.index(x_val, y)] == WorldTileType::Water { + break; + } + values[indexer.index(x_val, y)] = WorldTileType::Sand; + } + + adjust_variance(&mut variance, &rng); + } + + let mountains = rng.usize(1..4); + for _ in 0..mountains { + let position = random_ground_spot(&indexer, values.as_slice(), &rng); + let width = rng.usize(35..65); + let height = rng.usize(35..65); + let vertical = rng.bool(); + + if vertical { + let mut width_remaining = width; + for y in position.y as usize..position.y as usize + (height / 3) * 2 { + if width_remaining == 0 { + break; + } + + draw_across( + position.x as usize, + y, + width_remaining, + WorldTileType::Mountain, + &indexer, + &mut values, + ); + + width_remaining -= rng.usize(0..3); + } + + let mut width_remaining = width; + for y in (position.y as usize - (height / 3)..position.y as usize).rev() { + if width_remaining == 0 { + break; + } + + draw_across( + position.x as usize, + y, + width_remaining, + WorldTileType::Mountain, + &indexer, + &mut values, + ); + + width_remaining -= rng.usize(0..4); + } + } else { + let mut height_remaining = height; + for x in position.x as usize..position.x as usize + (width / 3) * 2 { + if height_remaining == 0 { + break; + } + + draw_updown( + x, + position.y as usize, + height_remaining, + WorldTileType::Mountain, + &indexer, + &mut values, + ); + + height_remaining -= rng.usize(0..3); + } + + let mut height_remaining = height; + for x in (position.x as usize - (width / 3)..position.x as usize).rev() { + if height_remaining == 0 { + break; + } + + draw_updown( + x, + position.y as usize, + height_remaining, + WorldTileType::Mountain, + &indexer, + &mut values, + ); + + height_remaining -= rng.usize(0..4); + } + } + } + // + let biome_noise = Blend::new( + Perlin::new(rng.u32(0..u32::MAX)), + Simplex::new(rng.u32(0..u32::MAX)), + Perlin::new(rng.u32(0..u32::MAX)), + ); + + // let biome_noise = Clamp::new(Simplex::new(rng.u32(0..u32::MAX))); + + for x in 0..width { + for y in 0..height { + let value = biome_noise + .get([x as f64 / width as f64, y as f64 / height as f64]) + .abs(); + + if values[indexer.index(x, y)] == WorldTileType::Grass { + if value > 0.8 { + values[indexer.index(x, y)] = WorldTileType::Mountain; + } else if value > 0.6 { + values[indexer.index(x, y)] = WorldTileType::Snow; + } else if value > 0.3 && value < 0.45 { + values[indexer.index(x, y)] = WorldTileType::Desert; + } else if value > 0.15 && value <= 0.2 { + values[indexer.index(x, y)] = WorldTileType::Swamp; + } + } + } + } + + Overworld { + terrain: values, + width, + height, + } +} diff --git a/game_core/src/world/handle_overworld.rs b/game_core/src/world/handle_overworld.rs new file mode 100644 index 0000000000000000000000000000000000000000..27f432d0158c37838bad6257ac22030863aead92 --- /dev/null +++ b/game_core/src/world/handle_overworld.rs @@ -0,0 +1,78 @@ +use bevy::math::vec3; +use bevy::prelude::*; +use bevy_ecs_tilemap::map::{TilemapSize, TilemapTileSize, TilemapType}; +use bevy_ecs_tilemap::prelude::{TilePos, TileTextureIndex, TilemapId, TilemapTexture}; +use bevy_ecs_tilemap::tiles::{TileBundle, TileStorage}; +use bevy_ecs_tilemap::TilemapBundle; +use fastrand::Rng; + +use crate::assets::AssetHandles; +use crate::system::camera::GameCamera; +use crate::system::utilities::Indexer; +use crate::world::generate_overworld::{generate_overworld, Overworld}; + +pub fn spawn_new_map( + mut commands: Commands, + assets: Res<AssetHandles>, + mut query: Query<&mut Transform, With<GameCamera>>, +) { + let world = generate_overworld(300, 300, Rng::new()); + spawn_overworld(&world, &mut commands, &assets); + for mut camera_transform in &mut query { + log::info!("SET CAM TRANSFORM"); + camera_transform.translation = + vec3(150.0 * 4.0, 150.0 * 4.0, camera_transform.translation.z); + } +} + +pub fn spawn_overworld( + overworld: &Overworld, + commands: &mut Commands, + assets: &Res<AssetHandles>, +) -> Entity { + let indexer = Indexer::new(overworld.width, overworld.height); + let tilemap_size = TilemapSize { + x: overworld.width as u32, + y: overworld.height as u32, + }; + + let mut map_entity = commands.spawn_empty().id(); + let mut tile_storage = TileStorage::empty(tilemap_size); + + for (idx, tile) in overworld.terrain.iter().enumerate() { + let (x, y) = indexer.reverse(idx); + + let tile_pos = TilePos { + x: x as u32, + y: y as u32, + }; + + let tile_entity = commands + .spawn(TileBundle { + position: tile_pos, + tilemap_id: TilemapId(map_entity), + texture_index: TileTextureIndex(tile.get_basic_tile() as u32), + ..Default::default() + }) + .id(); + + tile_storage.set(&tile_pos, tile_entity) + } + + let tile_size = TilemapTileSize { x: 4.0, y: 4.0 }; + let grid_size = tile_size.into(); + let map_type = TilemapType::Square; + + commands.entity(map_entity).insert(TilemapBundle { + grid_size, + tile_size, + size: tilemap_size, + storage: tile_storage, + texture: TilemapTexture::Single(assets.image("overworld")), + map_type, + transform: Transform::from_xyz(0.0, 0.0, 100.0), + ..Default::default() + }); + + map_entity +} diff --git a/game_core/src/world/mod.rs b/game_core/src/world/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..f9931258f6b783ec6d1ac18f0ba83f0d0387e288 --- /dev/null +++ b/game_core/src/world/mod.rs @@ -0,0 +1,18 @@ +use bevy::app::App; +use bevy::prelude::Plugin; +use fastrand::Rng; +use iyes_loopless::prelude::AppLooplessStateExt; + +use crate::system::flow::AppState; +use crate::world::debug_gen::generate_and_spawn_world; + +mod debug_gen; +mod generate_overworld; +mod handle_overworld; + +pub struct WorldPlugin; +impl Plugin for WorldPlugin { + fn build(&self, app: &mut App) { + app.add_enter_system(AppState::InGame, handle_overworld::spawn_new_map); + } +}