use serde::{Deserialize, Serialize};
pub mod category;
pub mod game;
pub mod item;
pub mod location;
pub mod region;
pub mod meta;
pub mod options;
#[cfg(feature="file_io")]
pub mod apworld_writer;
#[derive(Debug,Clone,Serialize,Deserialize,PartialEq,Eq)]
#[serde(untagged)]
pub enum Either<T,U>{
Left(T),
Right(U)
}
impl<T: Default,U> Default for Either<T,U>{
fn default() -> Self {
Either::Left(T::default())
}
}
#[derive(Debug,PartialEq, Eq,Clone)]
pub struct ItemOrList<T>{
pub inner: Vec<T>
}
impl<T: Serialize> Serialize for ItemOrList<T>{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer {
if self.inner.len()==1{
self.inner[0].serialize(serializer)
} else {
self.inner.serialize(serializer)
}
}
}
impl<'de,T: Deserialize<'de>> Deserialize<'de> for ItemOrList<T>{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de> {
#[derive(Debug,PartialEq,Clone,Serialize,Deserialize)]
#[serde(untagged)]
enum CITemOrList<V>{
Item(V),
List(Vec<V>)
}
let a: CITemOrList<T>= CITemOrList::deserialize(deserializer)?;
match a{
CITemOrList::Item(v)=>Ok(ItemOrList{inner: vec![v]}),
CITemOrList::List(v)=>Ok(ItemOrList { inner: v })
}
}
}
impl<T> Default for ItemOrList<T>{
fn default() -> Self {
Self{inner: vec![]}
}
}
impl<T> ItemOrList<T>{
fn is_empty(&self)->bool{
self.inner.is_empty()
}
}
impl<T> ItemOrList<T>{
pub fn iter<'a>(&'a self)->std::slice::Iter<'a,T>{
self.inner.iter()
}
pub fn push(&mut self, item: T){
self.inner.push(item);
}
pub fn get(&self,idx: usize)->Option<&T>{
self.inner.get(idx)
}
pub fn remove(&mut self, idx:usize)->Option<T>{
if idx<self.inner.len(){
Some(self.inner.remove(idx))
} else {
None
}
}
pub fn len(&self)->usize{
self.inner.len()
}
pub fn from_item(item: T)->Self{
Self { inner: vec![item] }
}
pub fn from_list(ls: Vec<T>)->Self{
Self {inner: ls}
}
}
#[derive(Debug,Serialize,Deserialize,PartialEq, Eq,Clone)]
#[serde(untagged)]
#[serde(expecting="boolean or integer")]
pub enum BoolInt{
Bool(bool),
Int(u32)
}
impl Default for BoolInt{
fn default() -> Self {
Self::Bool(false)
}
}
impl BoolInt{
pub fn is_false_or_zero(&self)->bool{
match self{
BoolInt::Bool(v)=>!v,
BoolInt::Int(v)=>*v==0
}
}
}
fn bool_is_false(b: &bool)->bool{
!*b
}
fn bool_is_true(b: &bool)->bool{
*b
}
#[derive(Debug)]
pub struct APWorld{
pub category: category::Categories,
pub game: game::Game,
pub items: Vec<item::Item>,
pub locations: Vec<location::Location>,
pub meta: meta::Meta,
pub options: options::Options,
pub regions: region::Regions
}
impl APWorld{
pub fn simple(g: game::Game)->Self{
Self { category: category::Categories::default(),
game: g, items: Vec::default(), locations: Vec::new(), meta: meta::Meta::default(),
options: options::Options::default(), regions: region::Regions::default() }
}
pub fn with_category(mut self,name: String,c: category::Category)->Self{
self.category.add(name, c);
self
}
pub fn with_item(mut self,item: item::Item)->Self{
self.items.push(item);
self
}
pub fn with_location(mut self,location: location::Location)->Self{
self.locations.push(location);
self
}
pub fn with_region(self,name: String,r: region::Region)->Self{
Self { regions:self.regions.with_region(name, r),..self}
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use crate::{region::Region};
use super::*;
#[test]
fn category_test(){
}
#[test]
fn game_test(){
let gt = "{\"game\": \"CelesteStrawberryJam\",\"creator\": \"Gregovin\",\"filler_item_name\": \"Filler\",\"starting_items\": []}";
let tst: game::Game = serde_json::from_str(gt).unwrap();
assert_eq!(&tst.game,"CelesteStrawberryJam");
assert_eq!(&tst.creator,"Gregovin");
assert_eq!(&tst.filler_item_name,"Filler");
assert_eq!(tst.death_link,false);
assert_eq!(tst.starting_index,1);
assert_eq!(tst.starting_items,vec![]);
}
#[test]
fn item_test(){
let it = "{\"count\": 1,\"name\": \"Forest Path Access\",\"category\": [\"Beginner\",\"Access\"],\"progression\": true}";
let tst: item::Item = serde_json::from_str(it).unwrap();
assert_eq!(&tst.name,"Forest Path Access");
assert_eq!(*tst.count,1);
assert_eq!(tst.category,ItemOrList::from_list(vec![String::from("Beginner"),String::from("Access")]));
assert!(tst.progression);
assert_eq!(tst.trap,false);
assert_eq!(tst.progression_skip_balancing,false);
assert_eq!(tst.classification_count,HashMap::new());
assert_eq!(tst.value,HashMap::new());
}
#[test]
fn locations_test(){
let it = "{\"name\": \"Forest Path Strawberry 1\",\"region\": \"Default\",\"category\": [\"Beginner\",\"Forest Path\",\"Strawberry\"],\"requires\": \"|Forest Path Access|\"}";
let tst: location::Location = serde_json::from_str(it).unwrap();
assert_eq!(&tst.name,"Forest Path Strawberry 1");
assert_eq!(tst.region,Some("Default".to_string()));
assert_eq!(tst.category,ItemOrList{ inner: vec!["Beginner".to_string(),"Forest Path".to_string(),"Strawberry".to_string()]});
assert_eq!(tst.requires,ItemOrList{inner:vec!["|Forest Path Access|".to_owned()]});
}
#[test]
fn region_test(){
let it="{\"Default\": {\"starting\": true,\"connects_to\": [],\"requires\": []}}";
let tst: region::Regions=serde_json::from_str(it).unwrap();
assert_eq!(tst.regions.get("Default").cloned(),Some(
Region{
starting: true,
requires: ItemOrList::from_list(vec![]),
connects_to: vec![],
exit_requires: ItemOrList::default(),
entrance_requires: ItemOrList::default()
}
));
}
}