//! Complete Example of a three Dialog. //! //! - Press any key to continue the dialog. //! - Choose your answer with the down right buttons. //! - You can press the reset button to ... to reset. //! - Click on one of the three frog portrait above. //! //! Also, on't worry about the timer. It's the lore. //! Press r to reset it but it won't be on the `ShortcutLess`. use bevy::{ input::{keyboard::KeyboardInput, ButtonState}, prelude::*, render::texture::ImagePlugin, time::Stopwatch, window::WindowResolution, winit::WinitSettings, }; use rand::seq::SliceRandom; use std::{collections::BTreeMap, fmt, str::FromStr}; use yml_dialog::{Content, DialogNode}; // dark purple #25131a = 39/255, 19/255, 26/255 const CLEAR: bevy::render::color::Color = bevy::render::color::Color::rgb(0.153, 0.07, 0.102); const FIXED_TIME_STEP: f32 = 1.0 / 60.0; const HEIGHT: f32 = 720.0; const RESOLUTION: f32 = 16.0 / 9.0; const NORMAL_BUTTON: Color = Color::rgb(0.15, 0.15, 0.15); const HOVERED_BUTTON: Color = Color::rgb(0.25, 0.25, 0.25); const PRESSED_BUTTON: Color = Color::rgb(0.35, 0.75, 0.35); /// Say, for whatever reason, we want to speedrun this example (bigup to Juju <3) #[derive(Resource, Debug, Reflect, Deref, DerefMut, Clone, Default)] struct SpeedrunTimer(Stopwatch); /// Points to the Speedrun Timer Visualizer #[derive(Component)] struct SpeedrunTimerText; /// Points to the current entity, if they exist, who we're talking with. /// Query this entity to get the current Dialog. #[derive(Debug, Reflect, Deref, DerefMut, Clone, Default, Resource)] struct CurrentInterlocutor { interlocutor: Option, } /// Points to the current entity, if they exist, who we're talking with. /// Query this entity to get the current Dialog. #[derive(Debug, Deref, DerefMut, Clone, Default, Resource)] struct ActiveWorldEvents { active_world_events: Vec, } #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] enum WorldEvent { FrogLove, FrogHate, FrogTalk, SpeedrunEnd, } impl fmt::Display for WorldEvent { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { WorldEvent::FrogLove => write!(f, "FrogLove"), WorldEvent::FrogHate => write!(f, "FrogHate"), WorldEvent::FrogTalk => write!(f, "FrogTalk"), WorldEvent::SpeedrunEnd => write!(f, "SpeedrunEnd"), } } } impl FromStr for WorldEvent { type Err = (); fn from_str(input: &str) -> Result { match input { "FrogTalk" => Ok(WorldEvent::FrogTalk), "FrogLove" => Ok(WorldEvent::FrogLove), "FrogHate" => Ok(WorldEvent::FrogHate), "SpeedrunEnd" => Ok(WorldEvent::SpeedrunEnd), _ => Err(()), } } } /// - `key`: interlocutor /// - `value`: (current state, BinaryTreeMap of the dialog) #[derive(Debug, Deref, DerefMut, Default, Resource)] struct DialogMap(BTreeMap)>); /// Contains all the line of the current monolog /// /// Help us keep the `DialogMap` unchanged #[derive(Debug, Reflect, Clone, Default, Resource)] struct Monolog { source: String, texts: Vec, } /// Points to a interactable portrait. /// /// REFACTOR: remove all the `Dialog` Component #[derive(Component)] struct Portrait; /// Points to the NPC portrait on the dialog Panel. #[derive(Component)] struct InterlocutorPortait; /// Contains the state number of the choice: `exit_state` and its position in the ui. #[derive(Debug, Reflect, PartialEq, Eq, PartialOrd, Ord, Clone, Default, Component)] struct ButtonChoice { exit_state: usize, ui_posiiton: usize, } impl ButtonChoice { fn new(ui_posiiton: usize) -> Self { ButtonChoice { exit_state: usize::default(), ui_posiiton, } } } #[derive(Component)] struct Reset; #[derive(Component)] struct PlayerPanel; #[derive(Component)] struct NPCPanel; // TODO: Visual - DialogPanel Seperator + background fn main() { let mut app = App::new(); app.insert_resource(FixedTime::new_from_secs(FIXED_TIME_STEP)) .insert_resource(ClearColor(CLEAR)) .insert_resource(Msaa::Off) // Only run the app when there is user input. This will significantly reduce CPU/GPU use. .insert_resource(WinitSettings::game()) .add_plugins( DefaultPlugins .set(WindowPlugin { primary_window: Some(Window { resolution: WindowResolution::new(HEIGHT * RESOLUTION, HEIGHT), title: "Complete Dialog".to_string(), resizable: true, ..default() }), ..default() }) .set(ImagePlugin::default_nearest()), ) .insert_resource(CurrentInterlocutor::default()) .insert_resource(ActiveWorldEvents::default()) .insert_resource(DialogMap::default()) .insert_resource(Monolog::default()) .insert_resource(SpeedrunTimer::default()) .add_event::() .add_event::() .add_startup_systems((setup, spawn_camera)) .add_systems(( continue_monolog, choose_answer, reset_system, switch_dialog, change_dialog_state, update_dialog_panel, update_monolog, trigger_event_handler.after(change_dialog_state), change_interlocutor_portrait, button_system, // button_visibility, update_speedrun_timer.run_if(speedrun_still_on), )); app.run(); } fn reset_system( mut active_world_events: ResMut, mut dialogs: ResMut, mut speedrun_timer: ResMut, keys: Res>, interaction_query: Query< (&Interaction, &Children), (Changed, With, With