use crate::app_state::AppState;
use crate::config;
use crate::core::camera::Followable;
use crate::core::sprite::ModuleSpriteRegistry;
use bevy::app::{App, Plugin, Update};
use bevy::asset::LoadedFolder;
use bevy::prelude::*;
use std::fs;
pub(crate) struct AppSetupPlugin;
impl Plugin for AppSetupPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
OnEnter(AppState::AppSetup),
(load_textures_system, setup_camera_system),
)
.add_systems(
Update,
check_textures_system.run_if(in_state(AppState::AppSetup)),
);
}
}
fn discover_texture_modules() -> Vec<String> {
let config = config::load_config();
let roots = config::get_asset_roots(&config.project.mod_name);
for root in roots {
let textures_path = root.join("textures");
if textures_path.exists() && textures_path.is_dir() {
if let Ok(entries) = fs::read_dir(&textures_path) {
let modules: Vec<String> = entries
.filter_map(|entry| entry.ok())
.filter(|entry| entry.path().is_dir())
.filter_map(|entry| entry.file_name().into_string().ok())
.collect();
if !modules.is_empty() {
info!(
"Discovered {} texture modules in {:?}: {:?}",
modules.len(),
textures_path,
modules
);
return modules;
}
}
}
}
warn!("No texture modules discovered, using defaults");
vec![
"overworld".to_string(),
"battle".to_string(),
"common".to_string(),
]
}
fn load_textures_system(mut commands: Commands, asset_server: Res<AssetServer>) {
let mut registry = ModuleSpriteRegistry::new();
let discovered_modules = discover_texture_modules();
for module_name in &discovered_modules {
registry.register_module(
module_name.clone(),
asset_server.load_folder(format!("textures/{}", module_name)),
);
info!("Registered texture module: {}", module_name);
}
commands.insert_resource(DiscoveredModules(discovered_modules));
commands.insert_resource(registry);
}
#[derive(Resource)]
pub struct DiscoveredModules(pub Vec<String>);
fn check_textures_system(
mut next_state: ResMut<NextState<AppState>>,
sprite_registry: Res<ModuleSpriteRegistry>,
asset_server: Res<AssetServer>,
mut events: MessageReader<AssetEvent<LoadedFolder>>,
souprune_config: Res<crate::config::SoupruneConfig>,
discovered_modules: Res<DiscoveredModules>,
) {
for _ in events.read() {
let required_loaded = souprune_config.game.required_modules.iter().all(|module| {
if !discovered_modules.0.contains(module) {
warn!(
"Required module '{}' not found in discovered modules",
module
);
return false;
}
if let Some(handle) = sprite_registry.get_module(module) {
asset_server.is_loaded_with_dependencies(handle)
} else {
false
}
});
let all_discovered_loaded = discovered_modules.0.iter().all(|module| {
if let Some(handle) = sprite_registry.get_module(module) {
asset_server.is_loaded_with_dependencies(handle)
} else {
false
}
});
if required_loaded && all_discovered_loaded {
info!("All texture modules loaded: {:?}", discovered_modules.0);
if souprune_config.game.initial_map_path.is_empty()
&& !souprune_config.game.initial_battle_path.is_empty()
{
info!(
"No initial map path, but initial battle path found. Entering Battle: {}",
souprune_config.game.initial_battle_path
);
next_state.set(AppState::Battle);
} else {
next_state.set(AppState::Overworld);
}
break;
}
}
}
fn setup_camera_system(mut commands: Commands, resolution_scale: Res<ResolutionScale>) {
commands.spawn((
Name::new("Overworld Camera2d"),
Camera2d,
Projection::Orthographic(OrthographicProjection {
scale: 1.0 / resolution_scale.get() as f32,
..OrthographicProjection::default_2d()
}),
Followable::default(),
));
}
#[derive(Resource)]
pub(crate) struct ResolutionScale(pub(crate) u32);
impl ResolutionScale {
pub(crate) fn get(&self) -> u32 {
self.0
}
}
impl Default for ResolutionScale {
fn default() -> Self {
Self(5)
}
}