use std::collections::HashMap;
use std::ops::{Deref, DerefMut};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum QuestState {
InProgress,
Success,
Failure,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct QuestEntry {
pub state: QuestState,
pub description: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy", derive(bevy::reflect::TypeUuid))]
#[cfg_attr(feature = "bevy", uuid = "95fe3fd8-e3bc-11ed-a37a-03780a996b9a")]
pub struct Quest {
pub id: QuestID,
pub name: String,
pub states: HashMap<StateID, QuestEntry>,
}
#[cfg(any(feature = "bevy"))]
use bevy::reflect::{ReflectDeserialize, ReflectSerialize};
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy",
derive(
bevy::prelude::Resource,
bevy::reflect::Reflect,
bevy::reflect::FromReflect
)
)]
#[cfg_attr(feature = "bevy", reflect(Serialize, Deserialize))]
pub struct QuestLog(pub HashMap<QuestID, StateID>);
impl Deref for QuestLog {
type Target = HashMap<QuestID, StateID>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for QuestLog {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl QuestLog {
pub fn get_quest_state<'a>(
&self,
id: QuestID,
locator: &'a impl QuestLocator,
) -> Option<&'a QuestEntry> {
locator
.get_quest(&id)
.zip(self.get(&id))
.and_then(|(quest, state)| quest.states.get(state))
}
}
pub type QuestID = String;
pub type StateID = String;
pub trait QuestLocator {
fn get_quest(&self, id: impl ToString) -> Option<&Quest>;
}
#[cfg(all(test, any(feature = "bevy", feature = "serde")))]
mod parsing_test {
use crate::quest_log::QuestLocator;
use crate::{Quest, QuestState};
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct QuestFile {
quests: Vec<Quest>,
}
impl QuestLocator for QuestFile {
fn get_quest(&self, id: impl ToString) -> Option<&Quest> {
self.quests.iter().find(|q| q.id == id.to_string())
}
}
const RAW: &str = r#"
[[quests]]
id = "CRST_TOWN_GUARD_Q_001"
name = "A Sound in the night..."
[quests.states.10]
state = "in_progress"
description = "Ernesto the guard has given you some information about a strange noise heard in the depths of the night"
[quests.states.20]
state = "in_progress"
description = "You kept watch in an alleyway, but unfortunately fell asleep. While asleep, the sound was heard once more. When you awoke, all you found was fur"
[quests.states.30]
state = "success"
description = "You searched far and wide, and ended up finding out that it was a cat that was making that noise all along"
[quests.states.40]
state = "failure"
description = "With the death of Count Mortant, and the dissolution of this realm, there is no longer any way to track down the source of the noise"
"#;
#[test]
fn parses_simple_toml() {
let value = toml::from_str(RAW);
eprintln!("{:?}", value);
assert!(value.is_ok());
let list: QuestFile = value.unwrap();
let value = list.quests.first().expect("Missing quest list quest");
assert_eq!(value.id, "CRST_TOWN_GUARD_Q_001".to_string());
assert_eq!(value.name, "A Sound in the night...".to_string());
assert_eq!(value.states.len(), 4);
assert!(value.states.contains_key(&"10".to_string()));
assert!(value.states.contains_key(&"20".to_string()));
assert!(value.states.contains_key(&"30".to_string()));
assert!(value.states.contains_key(&"40".to_string()));
assert_eq!(
value.states[&"10".to_string()].state,
QuestState::InProgress
);
assert_eq!(
value.states[&"20".to_string()].state,
QuestState::InProgress
);
assert_eq!(value.states[&"30".to_string()].state, QuestState::Success);
assert_eq!(value.states[&"40".to_string()].state, QuestState::Failure);
}
}