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});
}
}