bevy_yarnspinner 0.1.2

Bevy plugin for Yarn Spinner for Rust, friendly tool for writing game dialogue
Documentation
use crate::commands::update_wait;
use crate::dialogue_runner::events::DialogueStartEvent;
use crate::events::*;
use crate::line_provider::LineProviderSystemSet;
use crate::prelude::*;
use anyhow::bail;
use bevy::asset::LoadedUntypedAsset;
use bevy::prelude::*;
use bevy::utils::HashMap;

pub(crate) fn runtime_interaction_plugin(app: &mut App) {
    app.add_systems(
        Update,
        (
            continue_runtime
                .pipe(panic_on_err)
                .run_if(resource_exists::<YarnProject>()),
            accept_line_hints,
        )
            .chain()
            .after(LineProviderSystemSet)
            .after(update_wait)
            .in_set(DialogueExecutionSystemSet)
            .in_set(YarnSpinnerSystemSet),
    );
}

#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, SystemSet)]
pub(crate) struct DialogueExecutionSystemSet;

fn continue_runtime(
    mut dialogue_runners: Query<(Entity, &mut DialogueRunner)>,
    mut present_line_events: EventWriter<PresentLineEvent>,
    mut present_options_events: EventWriter<PresentOptionsEvent>,
    mut execute_command_events: EventWriter<ExecuteCommandEvent>,
    mut node_complete_events: EventWriter<NodeCompleteEvent>,
    mut node_start_events: EventWriter<NodeStartEvent>,
    mut line_hints_events: EventWriter<LineHintsEvent>,
    mut dialogue_complete_events: EventWriter<DialogueCompleteEvent>,
    mut dialogue_start_events: EventWriter<DialogueStartEvent>,
    mut last_options: Local<HashMap<Entity, Vec<DialogueOption>>>,
    loaded_untyped_assets: Res<Assets<LoadedUntypedAsset>>,
    project: Res<YarnProject>,
) -> SystemResult {
    for (source, mut dialogue_runner) in dialogue_runners.iter_mut() {
        let is_sending_missed_events = !dialogue_runner.unsent_events.is_empty();
        if !is_sending_missed_events {
            if dialogue_runner.just_started {
                dialogue_start_events.send(DialogueStartEvent { source });
                dialogue_runner.just_started = false;
            }
            if !dialogue_runner.is_running {
                dialogue_runner.will_continue_in_next_update = false;
                continue;
            }

            if let Some(line_ids) = std::mem::take(&mut dialogue_runner.popped_line_hints) {
                line_hints_events.send(LineHintsEvent { line_ids, source });
            }

            if !(dialogue_runner.will_continue_in_next_update
                && dialogue_runner.poll_tasks_and_check_if_done()
                && dialogue_runner.update_line_availability(&loaded_untyped_assets))
            {
                continue;
            }
            dialogue_runner.will_continue_in_next_update = false;

            if dialogue_runner.run_selected_options_as_lines {
                if let Some(option) = dialogue_runner.last_selected_option.take() {
                    let options = last_options
                        .remove(&source)
                        .expect("Failed to get last presented options when trying to run selected option as line. \
                                  This is a bug. Please report it at https://github.com/YarnSpinnerTool/YarnSpinner-Rust/issues/new");
                    let Some(option) = options.into_iter().find(|o| o.id == option) else {
                        let expected_options = last_options
                            .values()
                            .flat_map(|options| options.iter().map(|option| option.id.to_string()))
                            .collect::<Vec<_>>()
                            .join(", ");
                        bail!("Dialogue options does not contain selected option. Expected one of [{expected_options}], but found {option}");
                    };
                    present_line_events.send(PresentLineEvent {
                        line: option.line,
                        source,
                    });
                    continue;
                }
            }
        }
        let events = if is_sending_missed_events {
            std::mem::take(&mut dialogue_runner.unsent_events)
        } else {
            dialogue_runner.dialogue.continue_()?
        };

        for event in events {
            match event {
                DialogueEvent::Line(line) => {
                    let assets = dialogue_runner.get_assets(&line);
                    let metadata = project.line_metadata(&line.id).unwrap_or_default().to_vec();
                    present_line_events.send(PresentLineEvent {
                        line: LocalizedLine::from_yarn_line(line, assets, metadata),
                        source,
                    });
                }
                DialogueEvent::Options(options) => {
                    let options: Vec<DialogueOption> = options
                        .into_iter()
                        .map(|option| {
                            let assets = dialogue_runner.get_assets(&option.line);
                            let metadata = project
                                .line_metadata(&option.line.id)
                                .unwrap_or_default()
                                .to_vec();
                            DialogueOption::from_yarn_dialogue_option(option, assets, metadata)
                        })
                        .collect();
                    last_options.insert(source, options.clone());
                    present_options_events.send(PresentOptionsEvent { options, source });
                }
                DialogueEvent::Command(command) => {
                    execute_command_events.send(ExecuteCommandEvent { command, source });
                    dialogue_runner.continue_in_next_update();
                }
                DialogueEvent::NodeComplete(node_name) => {
                    node_complete_events.send(NodeCompleteEvent { node_name, source });
                }
                DialogueEvent::NodeStart(node_name) => {
                    node_start_events.send(NodeStartEvent { node_name, source });
                }
                DialogueEvent::LineHints(line_ids) => {
                    line_hints_events.send(LineHintsEvent { line_ids, source });
                }
                DialogueEvent::DialogueComplete => {
                    if !is_sending_missed_events {
                        dialogue_runner.is_running = false;
                    }
                    dialogue_complete_events.send(DialogueCompleteEvent { source });
                }
            }
        }
    }
    Ok(())
}

fn accept_line_hints(
    mut events: EventReader<LineHintsEvent>,
    mut dialogue_runners: Query<&mut DialogueRunner>,
) {
    for event in events.read() {
        let mut dialogue_runner = dialogue_runners.get_mut(event.source).unwrap();
        for asset_provider in dialogue_runner.asset_providers.values_mut() {
            asset_provider.accept_line_hints(&event.line_ids);
        }
    }
}