Skip to content
Snippets Groups Projects
spawning.rs 7.16 KiB
Newer Older
use bevy::prelude::*;
use bevy_ecs_tilemap::prelude::*;
Louis's avatar
Louis committed
use micro_banimate::definitions::SimpleAnimationBundle;
use num_traits::AsPrimitive;
Louis's avatar
Louis committed
use serde_json::Value;
Louis's avatar
Louis committed
use crate::assets::{AssetHandles, LdtkProject, LevelIndex, TilesetIndex};
Louis's avatar
Louis committed
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;
Louis's avatar
Louis committed
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>,
Louis's avatar
Louis committed
	tileset_index: Res<TilesetIndex>,
	mut last_spawned_level: Local<String>,
Louis's avatar
Louis committed
	mut events: EventWriter<PopulateWorldEvent>,
) {
	let mut active_level = match active_level {
		Some(l) => l,
		None => return,
	};
Louis's avatar
Louis committed
	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;

Louis's avatar
Louis committed
				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,
			));
		});

Louis's avatar
Louis committed
		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));
Louis's avatar
Louis committed

		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()
					},
Louis's avatar
Louis committed
					..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);
Louis's avatar
Louis committed

		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(),
		);