use std::str::FromStr;
use bevy::{
asset::{Asset, AssetLoader, LoadContext, LoadedAsset},
prelude::*,
utils::BoxedFuture,
};
use serde::de::DeserializeOwned;
use socratic::DialogState;
pub struct SocraticPlugin<AssetWrapper, State>(std::marker::PhantomData<(AssetWrapper, State)>);
impl<W, S> Default for SocraticPlugin<W, S> {
fn default() -> Self {
Self(std::marker::PhantomData)
}
}
impl<AssetWrapper, State> Plugin for SocraticPlugin<AssetWrapper, State>
where
State: BevyDialogState + Default + Send + Sync + 'static,
State::DoAction: FromStr + DeserializeOwned + std::fmt::Debug,
State::IF: FromStr + DeserializeOwned,
State::Interpolation: FromStr + DeserializeOwned,
<State::IF as FromStr>::Err: Sync + Send + std::fmt::Debug + std::fmt::Display,
<State::DoAction as FromStr>::Err: Sync + Send + std::fmt::Debug + std::fmt::Display,
<State::Interpolation as FromStr>::Err: Sync + Send + std::fmt::Debug + std::fmt::Display,
AssetWrapper: Asset
+ std::ops::Deref<Target = socratic::Dialog<State::DoAction, State::IF, State::Interpolation>>
+ std::ops::DerefMut
+ From<socratic::Dialog<State::DoAction, State::IF, State::Interpolation>>,
{
fn build(&self, app: &mut App) {
app.add_asset::<AssetWrapper>()
.init_asset_loader::<CBORDialogLoader<AssetWrapper, State>>()
.init_asset_loader::<DialogLoader<AssetWrapper, State>>()
.add_event::<DialogInputEvent<AssetWrapper>>()
.add_event::<DialogOutputEvent<AssetWrapper>>()
.add_system(handle_events::<AssetWrapper, State>);
}
}
pub trait BevyDialogState {
type EmitAction: Send + Sync + 'static;
type DoAction;
type IF;
type Interpolation;
fn do_action(&mut self, events: &mut EventWriter<Self::EmitAction>, command: &Self::DoAction);
fn check_condition(&self, cond: &Self::IF) -> bool;
fn interpolate(&self, command: &Self::Interpolation) -> String;
}
struct StateWrapper<'a, 'b, 'c, BDS: BevyDialogState>(
&'a mut EventWriter<'b, 'c, BDS::EmitAction>,
&'a mut BDS,
);
impl<'a, 'b, 'c, BDS: BevyDialogState> DialogState for StateWrapper<'a, 'b, 'c, BDS> {
type DoAction = BDS::DoAction;
type IF = BDS::IF;
type Interpolation = BDS::Interpolation;
fn do_action(&mut self, command: &Self::DoAction) {
self.1.do_action(self.0, command);
}
fn check_condition(&self, cond: &Self::IF) -> bool {
self.1.check_condition(cond)
}
fn interpolate(&self, command: &Self::Interpolation) -> String {
self.1.interpolate(command)
}
}
pub struct CBORDialogLoader<Wrapper, State>(std::marker::PhantomData<(Wrapper, State)>);
impl<W, S> Default for CBORDialogLoader<W, S> {
fn default() -> Self {
Self(std::marker::PhantomData)
}
}
impl<S, W> AssetLoader for CBORDialogLoader<W, S>
where
S: BevyDialogState + Send + Sync + 'static,
S::DoAction: DeserializeOwned,
S::IF: DeserializeOwned,
S::Interpolation: DeserializeOwned,
W: Asset + From<socratic::Dialog<S::DoAction, S::IF, S::Interpolation>>,
{
fn load<'a>(
&'a self,
bytes: &'a [u8],
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, anyhow::Result<()>> {
Box::pin(async move {
load_context.set_default_asset(LoadedAsset::new(W::from(
socratic::Dialog::packed_from_reader(bytes)?,
)));
Ok(())
})
}
fn extensions(&self) -> &[&str] {
&["csdlg"]
}
}
pub struct DialogLoader<Wrapper, State>(std::marker::PhantomData<(Wrapper, State)>);
impl<W, S> Default for DialogLoader<W, S> {
fn default() -> Self {
Self(std::marker::PhantomData)
}
}
impl<S, W> AssetLoader for DialogLoader<W, S>
where
S: BevyDialogState + Send + Sync + 'static,
W: Asset + From<socratic::Dialog<S::DoAction, S::IF, S::Interpolation>>,
S::DoAction: FromStr,
S::IF: FromStr,
S::Interpolation: FromStr,
<S::IF as FromStr>::Err: Sync + Send + std::fmt::Debug + std::fmt::Display,
<S::DoAction as FromStr>::Err: Sync + Send + std::fmt::Debug + std::fmt::Display,
<S::Interpolation as FromStr>::Err: Sync + Send + std::fmt::Debug + std::fmt::Display,
{
fn load<'a>(
&'a self,
bytes: &'a [u8],
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, anyhow::Result<()>> {
Box::pin(async move {
load_context.set_default_asset(LoadedAsset::new(W::from(
socratic::Dialog::parse_from_reader(bytes)?,
)));
Ok(())
})
}
fn extensions(&self) -> &[&str] {
&["sdlg"]
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DialogInputEvent<AssetWrapper: Asset> {
Begin(Handle<AssetWrapper>, String),
Next(Handle<AssetWrapper>, socratic::DialogIndex),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DialogOutputEvent<AssetWrapper: Asset> {
DialogLine(
Handle<AssetWrapper>,
socratic::DialogItem,
socratic::DialogIndex,
),
DialogEnd(Handle<AssetWrapper>),
}
fn handle_events<Wrapper, State>(
mut input_events: EventReader<DialogInputEvent<Wrapper>>,
mut output_events: EventWriter<DialogOutputEvent<Wrapper>>,
mut emit_events: EventWriter<State::EmitAction>,
assets: Res<Assets<Wrapper>>,
mut state: ResMut<State>,
) where
Wrapper: Asset
+ std::ops::Deref<Target = socratic::Dialog<State::DoAction, State::IF, State::Interpolation>>
+ std::ops::DerefMut,
State: BevyDialogState + Sync + Send + 'static,
State::DoAction: std::fmt::Debug,
{
for event in input_events.iter() {
match event {
DialogInputEvent::Begin(handle, section) => {
let dialog = assets.get(handle).expect("invalid dialog handle");
let mut wrapped_state = StateWrapper(&mut emit_events, state.as_mut());
if let Some((line, index)) = dialog.begin(section, &mut wrapped_state) {
info!("Beginning {line}");
output_events.send(DialogOutputEvent::DialogLine(
handle.clone_weak(),
line,
index,
));
} else {
error!("Failed to begin!");
}
}
DialogInputEvent::Next(handle, index) => {
let dialog = assets.get(handle).expect("invalid dialog handle");
let mut wrapped_state = StateWrapper(&mut emit_events, state.as_mut());
if let Some((line, index)) = dialog.get(index.clone(), &mut wrapped_state) {
info!("Next {line}");
output_events.send(DialogOutputEvent::DialogLine(
handle.clone_weak(),
line,
index,
));
} else {
output_events.send(DialogOutputEvent::DialogEnd(handle.clone_weak()));
}
}
}
}
}