ap_manual 0.2.0

A rust package to cannonically interact with manual AP worlds
Documentation
use std::collections::HashMap;

use serde::{Deserialize, Serialize};
use crate::bool_is_false;

use crate::Either;
use crate::ItemOrList;

#[derive(Debug,Clone,Serialize,Deserialize,PartialEq, Eq,Default)]
pub struct OptionBase{
    #[serde(default,skip_serializing_if="String::is_empty")]
    pub display_name: String,
    #[serde(default,skip_serializing_if="ItemOrList::is_empty")]
    pub description: ItemOrList<String>,
    #[serde(default,skip_serializing_if="bool_is_false")]
    pub rich_text_doc: bool,
    #[serde(default,skip_serializing_if="String::is_empty")]
    pub group: String,
    #[serde(default,skip_serializing_if="bool_is_false")]
    pub hidden: bool,
    #[serde(default,skip_serializing_if="ItemOrList::is_empty")]
    pub visibility: ItemOrList<Either<String,u32>>
}
#[derive(Debug,Clone,Serialize,Deserialize,PartialEq, Eq)]
#[serde(tag = "type")]
pub enum OType{
    Toggle{#[serde(default,skip_serializing_if="bool_is_false")] default:bool},
    Choice{values:HashMap<String,u32>,
        #[serde(default,skip_serializing_if="HashMap::is_empty")] aliases:HashMap<String,Either<u32,String>>,
        #[serde(default)] default:i32,
        #[serde(default,skip_serializing_if="bool_is_false")] allow_custom_value:bool},
    Range{range_start:i32,range_end:i32,#[serde(default)] default:i32,values:HashMap<String,u32>}
}
#[derive(Debug,Clone,Serialize,Deserialize,PartialEq, Eq)]
pub struct OptionCore{
    #[serde(flatten)]
    pub base: OptionBase,
    #[serde(default)]
    pub default: Either<bool,i32>,
    #[serde(default,skip_serializing_if="HashMap::is_empty")]
    pub aliases: HashMap<String,Either<u32,String>>,
    #[serde(default)]
    pub values: HashMap<String,u32>
}
#[derive(Debug,Clone,Serialize,Deserialize,PartialEq, Eq)]
pub struct OptionUser{
    #[serde(flatten)]
    pub base: OptionBase,
    #[serde(flatten)]
    pub kind: OType
}
#[derive(Debug,Clone,Serialize,Deserialize,PartialEq, Eq)]
pub struct Options{
    core: HashMap<String,OptionCore>,
    user: HashMap<String,OptionUser>
}
impl Default for Options{
    fn default() -> Self {
        let mut core=HashMap::new();
        let user = HashMap::new();
        let mut ta=HashMap::new();
        ta.insert("easiest".to_string(), Either::Left(0));
        core.insert("goal".to_string(),OptionCore { base:OptionBase {
            display_name: "The End Objective".to_string(),
            description: ItemOrList::from_item("How to End your Randomized playthrough.".to_string()),
            rich_text_doc: false, group: String::new(), hidden: false, visibility: ItemOrList::default() }, default: Either::default(),
            aliases: ta, values: HashMap::new() });
        Self { core, user }
    }
}
#[cfg(test)]
pub mod test{
    use std::collections::HashMap;

    use crate::{options::{OType, Options}, Either, ItemOrList};

    #[test]
    fn option_test(){
        let tv="{
            \"$schema\": \"https://raw.githubusercontent.com/ManualForArchipelago/Manual/main/schemas/Manual.options.schema.json\",
            \"_comment\": \"Add a _ before an option name to comment it out and it wont be added to the apworld\",
            \"core\": {
                \"goal\":{
                    \"_comment\": [
                        \"Using 'goal' as the name of an option let you override some of the generated goal parameters\"
                    ],
                    \"display_name\": \"The End Objective\",
                    \"description\": \"How to End your Randomized playthrough.\",
                    \"aliases\": {\"easiest\": 0},
                    \"hidden\": false
                },  
                \"death_link\": {
                    \"default\": false
                },
                \"filler_traps\": {
                    \"_comment\": \"Using values here let you set a 'recommended' filler_trap percentage or even a default percentage\",
                    \"values\": {
                        \"recommended\": 20
                    }
                }
            },
            \"user\": {
                \"_commented_out_option\":{
                    \"type\": \"Toggle\",
                    \"default\": true
                },
                \"Example_Toggle\":{
                    \"description\": [
                        \"This is a Toggle\",
                        \"A simple boolean, aka True or False set the initial value with \\\"default\\\"\"
                    ],
                    \"type\": \"Toggle\",
                    \"default\": false,
                    \"group\": \"Example Options\"
                },
                \"Example_Choice\":{
                    \"type\": \"Choice\",
                    \"description\": [
                        \"This is a Choice\",
                        \"Let the user pick from a list of values\",
                        \"allow_custom_value let a player define their own text value\",
                        \"Currently the only use for Choice in Manual outside of hooks is the goal\"
                    ],
                    \"values\": {
                        \"start\":0,
                        \"test\":1
                    },
                    \"default\": 0,
                    \"allow_custom_value\": false,
                    \"group\": \"Example Options\"
                },
                \"Example_Range\":{
                    \"type\": \"Range\",
                    \"description\": [
                        \"This is a Range\",
                        \"Allow the player to specify a value between 'start' and 'end'\",
                        \"if you include a \\\"values\\\" you can define the label for certain values\",
                        \"Currently the only use for Range in Manual outside of hooks is 'filler_trap'\"
                    ],
                    \"range_start\": 0,
                    \"default\": 1,
                    \"range_end\": 10,
                    \"values\": {\"example\": 2},
                    \"group\": \"Example Options\"
                },

                \"DLC_enabled\":{
                    \"display_name\": \"DLC Enabled\",
                    \"description\": \"Is the Dlc enabled?\",
                    \"type\": \"Toggle\",
                    \"visibility\": [
                        \"simple_ui\",
                        \"complex_ui\",
                        \"template\"
                    ],
                    \"default\": true
                }
            }
        }";
        let tst: Options = serde_json::from_str(tv).unwrap();
        let ct = tst.core;
        let uo = tst.user;
        assert_eq!(ct.get("goal").unwrap().base.display_name,"The End Objective");
        assert_eq!(ct.get("goal").unwrap().base.description,ItemOrList::from_item("How to End your Randomized playthrough.".to_string()));
        assert_eq!(ct.get("goal").unwrap().base.group,"");
        assert_eq!(ct.get("goal").unwrap().base.hidden,false);
        assert_eq!(ct.get("goal").unwrap().aliases.get("easiest").unwrap(),&Either::Left(0));
        assert_eq!(ct.get("goal").unwrap().default,Either::Left(false));
        assert_eq!(ct.get("goal").unwrap().values,HashMap::new());
        assert_eq!(ct.get("death_link").unwrap().default,Either::Left(false));
        assert_eq!(ct.get("filler_traps").unwrap().values.get("recommended").unwrap(),&20);
        
        let tt = uo.get("Example_Toggle").unwrap();
        assert_eq!(tt.base.display_name,"");
        assert_eq!(tt.base.description.iter().next().unwrap(),"This is a Toggle");
        assert_eq!(tt.base.group,"Example Options");
        assert_eq!(tt.kind,OType::Toggle { default: false });
        let mut ok: HashMap<String, u32> = HashMap::new();
        ok.insert("start".to_string(), 0);
        ok.insert("test".to_string(), 1);

        assert_eq!(uo.get("Example_Choice").unwrap().kind,OType::Choice { values: ok, aliases: HashMap::new(), default: 0, allow_custom_value: false });
        let mut rk: HashMap<String,u32>=HashMap::new();
        rk.insert("example".to_string(), 2);
        assert_eq!(uo.get("Example_Range").unwrap().kind,OType::Range { range_start: 0, range_end: 10, default: 1, values: rk});
    }
}