Skip to content
Snippets Groups Projects
spawning.rs 11.1 KiB
Newer Older
Louis's avatar
Louis committed
use std::collections::HashMap;
Louis's avatar
Louis committed
use std::time::Duration;
Louis's avatar
Louis committed

Louis's avatar
Louis committed
use bevy::math::vec3;
use bevy::prelude::*;
use bevy_ecs_tilemap::prelude::*;
Louis's avatar
Louis committed
use bevy_tweening::lens::TransformPositionLens;
use bevy_tweening::{
	Animator, EaseFunction, EaseMethod, RepeatCount, RepeatStrategy, Sequence, Tracks, Tween,
};
Louis's avatar
Louis committed
use ldtk_rust::EntityInstance;
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};
use crate::const_data::SPECIALISMS;
Louis's avatar
Louis committed
use crate::persistance::PersistenceState;
use crate::states::Player;
use crate::system::camera::ChaseCam;
use crate::world::encounters::WorldZones;
Louis's avatar
Louis committed
use crate::world::towns::{CurrentResidence, TownBundle, TownPaths};
use crate::world::travel::DistanceTravelled;
Louis's avatar
Louis committed
use crate::world::utils::{
	entity_to_worldspace, 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::{
	EncounterState, HungerState, TownName, TradeManifest, TradeManifestTickState, TradingState,
	TravelPath, TravelTarget,
Louis's avatar
Louis committed
};
Louis's avatar
Louis committed

#[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(WorldLinked).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;

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

#[derive(Resource)]
pub struct PendingLoadState(pub PersistenceState);
Louis's avatar
Louis committed

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>>,
	manifests: Res<Assets<TradeManifest>>,
Louis's avatar
Louis committed
) {
	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()
				},
				Player,
				ChaseCam,
Louis's avatar
Louis committed
			));

			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);
					}
				}

			cmds.insert(
				pending_load
					.as_ref()
					.map(|pending| pending.0.player_total_distance)
					.unwrap_or_else(|| DistanceTravelled(0.0)),
			);
			cmds.insert(
				pending_load
					.as_ref()
					.map(|pending| pending.0.player_inventory.clone())
					.unwrap_or_else(|| TradingState {
						gold: 500,
						items: Default::default(),
					}),
			);
			cmds.insert(
				pending_load
					.as_ref()
					.map(|pending| pending.0.player_hunger.clone())
					.unwrap_or_else(HungerState::initial_player),
		let world_zones = WorldZones::from_entities(level.px_hei, MapQuery::get_entities_of(level));
Louis's avatar
Louis committed
		MapQuery::get_entities_of(level)
			.into_iter()
			.filter(|instance| instance.identifier == *"Town")
			.map(|instance| {
				let mut name = String::new();
				let mut manifest_name = String::new();

Louis's avatar
Louis committed
				let (cx, cy) = entity_to_worldspace(level.px_hei, instance);

Louis's avatar
Louis committed
				instance
					.field_instances
					.iter()
					.for_each(|inst| match &*inst.identifier {
						"name" => name = inst.value.as_ref().unwrap().as_str().unwrap().to_string(),
						"trade_manifest" => {
							manifest_name = inst
								.value
								.as_ref()
								.map(|val| String::from(val.as_str().unwrap()))
								.unwrap_or_default()
						}
						_ => {}
					});

				if manifest_name.is_empty() {
Louis's avatar
Louis committed
					(name, String::from("whitestone"), Vec3::new(cx, cy, 500.0))
Louis's avatar
Louis committed
				} else {
Louis's avatar
Louis committed
					(name, manifest_name, Vec3::new(cx, cy, 500.0))
Louis's avatar
Louis committed
				}
			})
Louis's avatar
Louis committed
			.map(|(name, manifest, transform)| {
				let handle = assets.trade_manifest(manifest);
				let asset = manifests.get(&handle).unwrap();

Louis's avatar
Louis committed
				match pending_load
					.as_ref()
					.and_then(|pl| pl.0.town_states.get(&name))
				{
					Some((trade, hunger, tick)) => TownBundle {
						town_name: TownName(name.clone()),
						manifest: handle,
Louis's avatar
Louis committed
						trade_state: trade.clone(),
						hunger_state: hunger.clone(),
						manifest_state: tick.clone(),
Louis's avatar
Louis committed
						world_linked: WorldLinked,
						location: TransformBundle::from_transform(Transform::from_translation(
							transform,
						)),
Louis's avatar
Louis committed
					},
					None => TownBundle {
						town_name: TownName(name.clone()),
						manifest: handle,
Louis's avatar
Louis committed
						trade_state: TradingState {
							gold: fastrand::isize(0..250) + 250,
							items: asset.create_randomised_inventory(),
Louis's avatar
Louis committed
						},
						hunger_state: HungerState::initial_town(),
Louis's avatar
Louis committed
						manifest_state: TradeManifestTickState::default(),
Louis's avatar
Louis committed
						world_linked: WorldLinked,
						location: TransformBundle::from_transform(Transform::from_translation(
							transform,
						)),
Louis's avatar
Louis committed
					},
				}
			})
			.for_each(|bundle| {
Louis's avatar
Louis committed
				let ent = commands.spawn((bundle, VisibilityBundle::default())).id();
				apply_skull_marker(&mut commands, &assets, ent);
Louis's avatar
Louis committed
			});
		log::info!("Spec {:#?}", *SPECIALISMS);

		commands.insert_resource(trade_routes);
		commands.insert_resource(world_zones);
Louis's avatar
Louis committed
pub fn apply_skull_marker(commands: &mut Commands, assets: &Res<AssetHandles>, entity: Entity) {
	commands.entity(entity).with_children(|builder| {
		let tween = Tween::new(
			EaseFunction::SineInOut,
			Duration::from_secs(2),
			TransformPositionLens {
				start: vec3(0.0, 0.0, 400.0),
				end: vec3(0.0, 2.0, 400.0),
			},
		)
		.with_repeat_strategy(RepeatStrategy::MirroredRepeat)
		.with_repeat_count(RepeatCount::Infinite);

		builder.spawn((
			SpriteSheetBundle {
				sprite: TextureAtlasSprite::new(42),
				texture_atlas: assets.atlas("icons"),
				transform: Transform::from_xyz(0.0, 0.0, 400.0),
				..Default::default()
			},
			Animator::new(tween),
		));
	});
}

pub fn clean_game_state(
	mut commands: Commands,
	world_entities: Query<Entity, (Without<TileStorage>, With<WorldLinked>)>,
	mut tile_storages: Query<Entity, Or<(With<TilemapId>, With<TileStorage>)>>,
) {
	commands.remove_resource::<PendingLoadState>();
	commands.remove_resource::<PersistenceState>();

	commands.insert_resource(TownPaths::default());
	commands.insert_resource(WorldZones::default());
	commands.insert_resource(EncounterState::default());

	//
	log::info!("Cleaning {} world objects", world_entities.iter().len());
	for entity in &world_entities {
		commands.entity(entity).despawn_recursive();
	}
	//
	log::info!("Cleaning {} tile storages", tile_storages.iter().len());
	for entity in &mut tile_storages {
		commands.entity(entity).despawn();
	}
}