use bevy::prelude::*; use bevy_ecs_tilemap::prelude::*; use micro_banimate::definitions::SimpleAnimationBundle; use num_traits::AsPrimitive; use serde_json::Value; use crate::assets::{AssetHandles, LdtkProject, LevelIndex, TilesetIndex}; use crate::persistance::PersistenceState; use crate::states::Player; use crate::system::camera::ChaseCam; use crate::world::encounters::WorldZones; use crate::world::towns::{CurrentResidence, TownPaths}; use crate::world::utils::{grid_to_px, px_to_grid, ActiveLevel, WorldLinked, TILE_SCALE_F32}; use crate::world::world_query::MapQuery; use crate::world::{TravelPath, TravelTarget}; #[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] pub struct PopulateWorldEvent; pub fn spawn_world_data( mut commands: Commands, mut active_level: Option<ResMut<ActiveLevel>>, assets: Res<AssetHandles>, projects: Res<Assets<LdtkProject>>, level_index: Res<LevelIndex>, tileset_index: Res<TilesetIndex>, mut last_spawned_level: Local<String>, mut events: EventWriter<PopulateWorldEvent>, ) { let mut active_level = match active_level { Some(l) => l, None => return, }; let mut tileset = match tileset_index.get(&String::from("Overworld")) { Some(l) => l, None => return, }; if *last_spawned_level == active_level.map && !active_level.dirty { return; } else { *last_spawned_level = active_level.map.clone(); } if active_level.is_changed() || active_level.is_added() { let level = match level_index.get(&active_level.map) { Some(l) => l, _ => return, }; let map_tile_width = px_to_grid(level.px_wid); let map_tile_height = px_to_grid(level.px_hei); let tilemap_size = TilemapSize { x: map_tile_width.as_(), y: map_tile_height.as_(), }; let tilemap_tile_size = TilemapTileSize { x: TILE_SCALE_F32, y: TILE_SCALE_F32, }; MapQuery::for_each_layer_of(level, |layer| { if !layer.has_tiles() { return; } let map_entity = commands.spawn_empty().id(); let mut storage = TileStorage::empty(tilemap_size); layer.for_each_tile(|x, y, tile| { let mut tile_pos = TilePos { x: px_to_grid(x).as_(), y: px_to_grid(y).as_(), }; tile_pos.y = map_tile_height as u32 - tile_pos.y - 1; let tile_entity = if let Some(tile_entity) = tileset.0.get(&tile.tile.t) { match tile_entity.get("animations") { Some(Value::Array(frames)) => commands .spawn(( TileBundle { position: tile_pos, tilemap_id: TilemapId(map_entity), texture_index: TileTextureIndex(tile.tile.t as u32), ..Default::default() }, SimpleAnimationBundle::new( frames .iter() .map(|val| val.as_u64().unwrap() as usize) .collect(), 0.5, ), )) .id(), _ => commands .spawn(TileBundle { position: tile_pos, tilemap_id: TilemapId(map_entity), texture_index: TileTextureIndex(tile.tile.t as u32), ..Default::default() }) .id(), } } else { commands .spawn(TileBundle { position: tile_pos, tilemap_id: TilemapId(map_entity), texture_index: TileTextureIndex(tile.tile.t as u32), ..Default::default() }) .id() }; storage.set(&tile_pos, tile_entity); }); let grid_size = tilemap_tile_size.into(); let map_type = TilemapType::Square; log::info!("Spawning tilemap"); let bg = level .level_bg_color .clone() .unwrap_or_else(|| level.bg_color.clone()); match bg.len() { 6 => commands.insert_resource(ClearColor( Color::hex(bg.clone()).expect("Failed to set background color"), )), 7 => commands.insert_resource(ClearColor( Color::hex(&bg[1..]).expect("Failed to set background color"), )), _ => { log::warn!("Strange colour {}", &bg) } } commands.entity(map_entity).insert(( TilemapBundle { grid_size, map_type, size: tilemap_size, storage, texture: TilemapTexture::Single(assets.image("overworld")), tile_size: tilemap_tile_size, transform: Transform::from_xyz(0.0, 0.0, 5.0 + layer.get_z_delta()), // get_tilemap_center_transform(&tilemap_size, &grid_size, 5.0), ..Default::default() }, WorldLinked, )); }); events.send(PopulateWorldEvent); } } #[derive(Resource)] pub struct PendingLoadState(PersistenceState); pub fn populate_world( mut commands: Commands, mut events: ResMut<Events<PopulateWorldEvent>>, mut active_level: Option<ResMut<ActiveLevel>>, assets: Res<AssetHandles>, level_index: Res<LevelIndex>, existing_player: Query<Entity, With<Player>>, pending_load: Option<Res<PendingLoadState>>, ) { let should_populate = !events .drain() .collect::<Vec<PopulateWorldEvent>>() .is_empty(); if should_populate { let mut active_level = match active_level { Some(l) => l, None => return, }; let level = match level_index.get(&active_level.map) { Some(l) => l, None => return, }; let trade_routes = TownPaths::from(MapQuery::get_entities_of(level)); let start = trade_routes .routes .get(&String::from("The Royal Lampoon")) .unwrap(); if let Some(route) = start.routes.values().next() { let point = route.nodes[0]; let mut cmds = commands.spawn(( SpriteSheetBundle { transform: pending_load .as_ref() .map(|pending| Transform::from_translation(pending.0.player_location)) .unwrap_or_else(|| { Transform::from_xyz( grid_to_px(point.tile_x), level.px_hei as f32 - grid_to_px(point.tile_y), 400.0, ) }), texture_atlas: assets.atlas("icons"), sprite: TextureAtlasSprite { index: 0, ..Default::default() }, ..Default::default() }, start .create_route_bundle_for( start .routes .keys() .nth(fastrand::usize(0..start.routes.len())) .cloned() .unwrap(), level, ) .unwrap(), Player, ChaseCam, )); match &pending_load { Some(ref state) => { match ( &state.0.previous_location, &state.0.travel_path, &state.0.travel_target, ) { (res, Some(path), Some(target)) => { cmds.insert((res.clone(), path.clone(), target.clone())); } (res, None, Some(target)) => { cmds.insert((res.clone(), target.clone())); } (res, Some(path), None) => { cmds.insert((res.clone(), path.clone())); } (res, None, None) => { cmds.insert(res.clone()); } } } None => { if let Some(bundle) = start.create_route_bundle_for( start .routes .keys() .nth(fastrand::usize(0..start.routes.len())) .cloned() .unwrap(), level, ) { cmds.insert(bundle); } } } } let world_zones = WorldZones::from_entities(level.px_hei, MapQuery::get_entities_of(level)); commands.insert_resource(trade_routes); commands.insert_resource(world_zones); commands.insert_resource( pending_load .as_ref() .map(|pending| &pending.0.player_inventory) .cloned() .unwrap_or_default(), ); commands.insert_resource( pending_load .as_ref() .map(|pending| &pending.0.player_hunger) .cloned() .unwrap_or_default(), ); } }