use crate::prelude::*;
use crate::project::{LoadYarnProjectEvent, WatchingForChanges};
use bevy::prelude::*;
use std::path::PathBuf;
pub use yarn_file_source::YarnFileSource;
mod yarn_file_source;
#[derive(Debug)]
#[cfg_attr(
not(any(target_arch = "wasm32", target_os = "android")),
derive(Default)
)]
pub struct YarnSpinnerPlugin {
project: LoadYarnProjectEvent,
}
#[derive(Debug, Default, Clone, Copy, SystemSet, Eq, PartialEq, Hash)]
pub struct YarnSpinnerSystemSet;
impl YarnSpinnerPlugin {
#[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_yarn_sources<T, U>(yarn_files: T) -> Self
where
T: IntoIterator<Item = U>,
U: Into<YarnFileSource>,
{
Self {
project: LoadYarnProjectEvent::with_yarn_sources(yarn_files),
}
}
#[must_use]
pub fn with_yarn_source(yarn_file_source: impl Into<YarnFileSource>) -> Self {
Self {
project: LoadYarnProjectEvent::with_yarn_source(yarn_file_source),
}
}
#[must_use]
pub fn deferred() -> DeferredYarnSpinnerPlugin {
DeferredYarnSpinnerPlugin
}
#[must_use]
pub fn add_yarn_source(mut self, yarn_file: impl Into<YarnFileSource>) -> Self {
self.project = self.project.add_yarn_source(yarn_file);
self
}
#[must_use]
pub fn add_yarn_sources(
mut self,
yarn_files: impl IntoIterator<Item = impl Into<YarnFileSource>>,
) -> Self {
self.project = self.project.add_yarn_sources(yarn_files);
self
}
#[must_use]
pub fn with_localizations(mut self, localizations: impl Into<Option<Localizations>>) -> Self {
self.project = self.project.with_localizations(localizations);
self
}
#[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
#[must_use]
pub fn with_development_file_generation(
mut self,
development_file_generation: DevelopmentFileGeneration,
) -> Self {
self.project = self
.project
.with_development_file_generation(development_file_generation);
self
}
}
impl Plugin for YarnSpinnerPlugin {
fn build(&self, app: &mut App) {
assert!(
!self.project.yarn_files.is_empty(),
"Cannot initialize Yarn Spinner plugin because no Yarn files were specified. \
Did you call `YarnSpinnerPlugin::with_yarn_files()` without any Yarn file sources? \
If you really want to load no Yarn files right now and do that later, use `YarnSpinnerPlugin::deferred()` instead.\
If you wanted to load from the default directory instead, use `YarnSpinnerPlugin::default()`."
);
app.add_plugins(Self::deferred())
.world_mut()
.write_message(self.project.clone());
}
}
#[derive(Debug)]
#[non_exhaustive]
pub struct DeferredYarnSpinnerPlugin;
impl Plugin for DeferredYarnSpinnerPlugin {
fn build(&self, app: &mut App) {
app.register_yarn_types()
.register_sub_plugins()
.register_watching_for_changes()
.register_asset_root();
}
}
trait YarnApp {
fn register_yarn_types(&mut self) -> &mut Self;
fn register_sub_plugins(&mut self) -> &mut Self;
fn register_watching_for_changes(&mut self) -> &mut Self;
fn register_asset_root(&mut self) -> &mut Self;
}
impl YarnApp for App {
fn register_yarn_types(&mut self) -> &mut Self {
self.register_type::<YarnCompiler>()
.register_type::<YarnFile>()
.register_type::<CompilationType>()
.register_type::<Compilation>()
.register_type::<CompilerError>()
.register_type::<yarnspinner::compiler::Diagnostic>()
.register_type::<yarnspinner::compiler::DiagnosticSeverity>()
.register_type::<yarnspinner::compiler::DebugInfo>()
.register_type::<LineInfo>()
.register_type::<yarnspinner::compiler::Declaration>()
.register_type::<yarnspinner::compiler::DeclarationSource>()
.register_type::<StringInfo>()
.register_type::<LineId>()
.register_type::<yarnspinner::core::Position>()
.register_type::<YarnValue>()
.register_type::<yarnspinner::core::InvalidOpCodeError>()
.register_type::<yarnspinner::core::Program>()
.register_type::<yarnspinner::core::Node>()
.register_type::<yarnspinner::core::Header>()
.register_type::<yarnspinner::core::Instruction>()
.register_type::<yarnspinner::core::Type>()
.register_type::<yarnspinner::runtime::Command>()
.register_type::<yarnspinner::prelude::DialogueOption>()
.register_type::<OptionId>()
.register_type::<DialogueEvent>()
.register_type::<yarnspinner::runtime::Line>()
.register_type::<yarnspinner::runtime::Diagnosis>()
.register_type::<yarnspinner::runtime::DiagnosisSeverity>()
.register_type::<yarnspinner::runtime::MarkupParseError>()
.register_type::<MarkupAttribute>()
.register_type::<MarkupValue>()
}
fn register_sub_plugins(&mut self) -> &mut Self {
self.add_plugins(crate::yarn_file_asset::yarnspinner_asset_loader_plugin)
.add_plugins(crate::localization::localization_plugin)
.add_plugins(crate::dialogue_runner::dialogue_plugin)
.add_plugins(crate::line_provider::line_provider_plugin)
.add_plugins(crate::project::project_plugin)
.add_plugins(crate::commands::commands_plugin)
.add_plugins(crate::development_file_generation::development_file_generation_plugin)
}
fn register_watching_for_changes(&mut self) -> &mut Self {
let asset_server = self
.world()
.get_resource::<AssetServer>()
.expect(ASSET_ERROR);
let watching_for_changes = asset_server.watching_for_changes();
self.insert_resource(WatchingForChanges(watching_for_changes))
}
fn register_asset_root(&mut self) -> &mut Self {
let asset_plugin = get_asset_plugin(self);
let path_str = asset_plugin.file_path.clone();
let path = PathBuf::from(path_str);
self.insert_resource(AssetRoot(path))
}
}
fn get_asset_plugin(app: &App) -> &AssetPlugin {
let asset_plugins: Vec<&AssetPlugin> = app.get_added_plugins();
asset_plugins.into_iter().next().expect(ASSET_ERROR)
}
const ASSET_ERROR: &str = "Yarn Spinner requires access to the Bevy asset plugin. \
Please add `YarnSpinnerPlugin` after `AssetPlugin`, which is commonly added as part of the `DefaultPlugins`";
#[derive(Debug, Clone, PartialEq, Eq, Hash, Resource)]
pub(crate) struct AssetRoot(pub(crate) PathBuf);