| @ -0,0 +1,140 @@ | |||||
| <?xml version="1.0" encoding="UTF-8"?> | |||||
| <jdr-desc> | |||||
| <custom-types> | |||||
| <enum name="peuple" base-type="string"> | |||||
| <value>humain</value> | |||||
| <value>nain</value> | |||||
| <value>kobold</value> | |||||
| </enum> | |||||
| <enum name="sexe" base-type="string"> | |||||
| <value>male</value> | |||||
| <value>female</value> | |||||
| </enum> | |||||
| <type name="bonne_aventure"> | |||||
| <variable type="int" name="base">Base</variable> | |||||
| <variable type="int" name="actuelle">Actuelle</variable> | |||||
| </type> | |||||
| <type name="predilection" base-type="struct"> | |||||
| <variable type="string">Predilection</variable> | |||||
| <list base-type="string" name="Compétence(s) associée(s)"></list> | |||||
| <variable type="bool">Relance utilisée</variable> | |||||
| </type> | |||||
| <type name="bm" base-type="struct"> | |||||
| <variable type="int" name=""></variable> | |||||
| <variable type="int" name=""></variable> | |||||
| </type> | |||||
| <type name="arme-melee" base-type="struct"> | |||||
| <variable type="string" name="nom">Nom</variable> | |||||
| <variable type="bm" name="bm">BM</variable> | |||||
| <variable type="int" name="cap_off">Cap. Off</variable> | |||||
| <variable type="int" name="def">Def.</variable> | |||||
| <variable type="d" name="d">D</variable> | |||||
| </type> | |||||
| <type name="arme-tir" base-type="struct"> | |||||
| <!--<variable --> | |||||
| </type> | |||||
| </custom-types> | |||||
| <page> | |||||
| <block> | |||||
| <block> | |||||
| <variable type="string" name="nom">nom</variable> | |||||
| <variable type="string" name="joueur">joueur</variable> | |||||
| <variable type="peuple" name="peuple">peuple</variable> | |||||
| <variable type="sexe" name="sexe">sexe</variable> | |||||
| <variable type="string" name="metier">métier</variable><!--TODO custom enum--> | |||||
| <variable type="string" name="origine">origine</variable><!--TODO custom enum--> | |||||
| <variable type="string" name="heritage">héritage</variable><!--TODO custom enum--> | |||||
| </block> | |||||
| <block> | |||||
| <variable type="bonne_aventure" name="bonne_aventure">Bonne Aventure</variable> | |||||
| <variable type="int" name="eclat">Éclat</variable> | |||||
| <variable type="int" name="xp">Expérience</variable> | |||||
| </block> | |||||
| </block> | |||||
| <block name="Attributs"> | |||||
| <variable type="int" name="adr">ADResse</variable> | |||||
| <variable type="int" name="pui">PUIssance</variable> | |||||
| <variable type="int" name="cla">CLAirvoyance</variable> | |||||
| <variable type="int" name="pre">PRÉsence</variable> | |||||
| <variable type="int" name="tre">TREmpe</variable> | |||||
| </block> | |||||
| <block name="Compétences"> | |||||
| <variable-group default-type="int"> | |||||
| <variable>Armes à distance</variable> | |||||
| <variable>Coercition</variable> | |||||
| <variable>Commerce</variable> | |||||
| <variable>Discrétion</variable> | |||||
| <variable>Filouterie</variable> | |||||
| <variable>Mêlée</variable> | |||||
| <variable>Monte</variable> | |||||
| <variable>Mouvements</variable> | |||||
| <variable>Nage</variable> | |||||
| <variable>Navigation</variable> | |||||
| <variable>Perception</variable> | |||||
| <variable>Persuasion</variable> | |||||
| <variable>Soins</variable> | |||||
| <variable>Survie</variable> | |||||
| <variable>Savoir [hier]</variable> | |||||
| <variable>Savoir [Malroyaume]</variable> | |||||
| <variable>Savoir [Wasteland]</variable> | |||||
| <variable>Savoir [<generic />]</variable> | |||||
| <variable>Savoir [<generic />]</variable> | |||||
| <variable>Savoir [<generic />]</variable> | |||||
| <variable>Savoir [<generic />]</variable> | |||||
| </variable-group> | |||||
| <list base-type="predilection" name="Prédilections"></list> | |||||
| </block> | |||||
| <block name="Santé"> | |||||
| <variable id="sante_base" type="int" value="(${pui}+${tre})*2+5">Niveau de base</variable> | |||||
| <block name="Dégats non létaux"> | |||||
| <variable id="non_letal_deg" type="int" minvalue="0" maxvalue="${sante_base}" defaultvalue="max">Dégats non létaux</variable> | |||||
| <variable type="bool" value="${non_letal_deg} \le 9">Incommodé (-2)</variable> | |||||
| <variable type="bool" value="${non_letal_deg} \le 5">Sonné (-5)</variable> | |||||
| <variable type="bool" value="${non_letal_deg} \eq 0">Affaibli (-5)</variable> | |||||
| </block> | |||||
| <block name="Dégats létaux"> | |||||
| <variable id="letal_deg" type="int" minvalue="0" maxvalue="${sante_base}" defaultvalue="max">Dégats létaux</variable> | |||||
| <variable type="bool" value="${letal_deg} \le 9">Blessé (-2)</variable> | |||||
| <variable type="bool" value="${letal_deg} \le 5">Gravement Blessé (-5)</variable> | |||||
| <variable type="bool" value="${letal_deg} \eq 0">Inconscient</variable> | |||||
| <variable type="int">Séquelles</variable><!--TODO check wasteland rule--> | |||||
| </block> | |||||
| </block> | |||||
| <block name="Psyché"> | |||||
| <variable id="psyche_base" type="int" value="(${cla}+${tre})*2+5">Niveau de base</variable> | |||||
| <variable id="psyche_deg" type="int" minvalue="0" maxvalue="${psyche_base}" defaultvalue="max">Psyché</variable> | |||||
| <variable type="bool" value="${psyche_deg} \le 9">Destabilisé (-2)</variable> | |||||
| <variable type="bool" value="${psyche_deg} \le 5">Choqué (-5)</variable> | |||||
| <variable type="bool" value="${psyche_deg} \eq 0">Fou</variable> | |||||
| <variable type="int">Traumatismes</variable><!--TODO check wasteland rule--> | |||||
| </block> | |||||
| <block name="Capacités Spéciales"> | |||||
| <list base-type="string"></list> | |||||
| </block> | |||||
| <block name="Combat"> | |||||
| <block name="combat_stats"> | |||||
| <variable type="int">Initiative</variable> | |||||
| <variable type="int">Bonus Dégats</variable> | |||||
| <variable type="int">Vitesse</variable> | |||||
| <variable type="int">Défense (base)</variable> | |||||
| </block> | |||||
| <block name="Armes"> | |||||
| <block name="Mêlée"> | |||||
| <list base-type="arme-melee"> | |||||
| <value type="arme-melee"> | |||||
| <value type="string" name="nom">Armes naturelles</value> | |||||
| <value type="int" name="bm"></value> | |||||
| <value type="int" name="cap_off"></value> | |||||
| <value type="int" name="def"></value> | |||||
| <value type="d" name="d">1d4</value> | |||||
| </value> | |||||
| </list> | |||||
| </block> | |||||
| <block name="tir"> | |||||
| <list base-type="arme-tir"> | |||||
| </list> | |||||
| </block> | |||||
| </block> | |||||
| </block> | |||||
| </page> | |||||
| </jdr-desc> | |||||
| @ -0,0 +1,200 @@ | |||||
| package jdr | |||||
| import ( | |||||
| //"html/template" | |||||
| "encoding/xml" | |||||
| "errors" | |||||
| "os" | |||||
| ) | |||||
| type Page struct { | |||||
| Items []SheetItem | |||||
| } | |||||
| type SheetItem interface{ | |||||
| } | |||||
| type Block struct { | |||||
| Items []SheetItem | |||||
| } | |||||
| type Variable struct { | |||||
| Type VariableType | |||||
| Name string | |||||
| } | |||||
| type CharacterSheet struct { | |||||
| types map[string]VariableType | |||||
| Pages []*Page | |||||
| } | |||||
| func (self *CharacterSheet) AddType(t VariableType) { | |||||
| if self.types == nil { | |||||
| self.types = make(map[string]VariableType) | |||||
| } | |||||
| name := t.Name() | |||||
| self.types[name] = t | |||||
| } | |||||
| func (self *CharacterSheet) AddDefaultTypes() { | |||||
| self.AddType(&VariableType_bool{}) | |||||
| self.AddType(&VariableType_int{}) | |||||
| self.AddType(&VariableType_float{}) | |||||
| self.AddType(&VariableType_string{}) | |||||
| self.AddType(&VariableType_d{}) | |||||
| } | |||||
| func (self *CharacterSheet) getType(typename string) (VariableType, error) { | |||||
| t, ok := self.types[typename] | |||||
| if !ok { | |||||
| return &VariableType_badType{}, errors.New("unknown type \"" + typename + "\"") | |||||
| } | |||||
| return t, nil | |||||
| } | |||||
| type xmlNode struct { | |||||
| XMLName xml.Name | |||||
| Attrs []xml.Attr `xml:"-"` | |||||
| Content []byte `xml:",innerxml"` | |||||
| Nodes []xmlNode `xml:",any"` | |||||
| } | |||||
| func (n *xmlNode) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { | |||||
| n.Attrs = start.Attr | |||||
| type node xmlNode | |||||
| return d.DecodeElement((*node)(n), &start) | |||||
| } | |||||
| func parseVariable(root xmlNode, sheet *CharacterSheet) (*Variable, error) { | |||||
| ret := &Variable{} | |||||
| typeOk := false | |||||
| nameOk := false | |||||
| for i := 0; i < len(root.Attrs); i++ { | |||||
| attrName := root.Attrs[i].Name.Local | |||||
| attrVal := root.Attrs[i].Value | |||||
| switch attrName { | |||||
| case "type": | |||||
| t, err := sheet.getType(attrVal) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| ret.Type = t | |||||
| typeOk = true | |||||
| case "name": | |||||
| ret.Name = attrVal | |||||
| nameOk = true | |||||
| } | |||||
| } | |||||
| if !nameOk { | |||||
| return nil, errors.New("Unnamed variable") | |||||
| } | |||||
| if !typeOk { | |||||
| return nil, errors.New("No type provided for variable \"" + ret.Name + "\"") | |||||
| } | |||||
| return ret, nil | |||||
| } | |||||
| func parseBlock(root xmlNode, sheet *CharacterSheet) (*Block, error) { | |||||
| ret := &Block{} | |||||
| for i := 0; i < len(root.Nodes); i++ { | |||||
| item, err := parseItem(root.Nodes[i], sheet) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| ret.Items = append(ret.Items, item) | |||||
| } | |||||
| return ret, nil | |||||
| } | |||||
| func parseItem(root xmlNode, sheet *CharacterSheet) (SheetItem, error) { | |||||
| switch root.XMLName.Local { | |||||
| case "block": | |||||
| return parseBlock(root, sheet) | |||||
| case "variable": | |||||
| return parseVariable(root, sheet) | |||||
| default: | |||||
| return nil, errors.New("Unknown item: " + root.XMLName.Local) | |||||
| } | |||||
| } | |||||
| func parsePage(root xmlNode, sheet *CharacterSheet) (*Page, error) { | |||||
| ret := &Page{} | |||||
| for i := 0; i < len(root.Nodes); i++ { | |||||
| item, err := parseItem(root.Nodes[i], sheet) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| ret.Items = append(ret.Items, item) | |||||
| } | |||||
| return ret, nil | |||||
| } | |||||
| func parseSheet(root xmlNode) (*CharacterSheet, error) { | |||||
| if root.XMLName.Local != "jdr-desc" { | |||||
| return nil, errors.New("Wrong root node") | |||||
| } | |||||
| sheet := &CharacterSheet{} | |||||
| sheet.AddDefaultTypes() | |||||
| for i := 0; i < len(root.Nodes); i++ { | |||||
| node := root.Nodes[i] | |||||
| switch node.XMLName.Local { | |||||
| case "custom-types": | |||||
| types, err := parseCustomTypes(node) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| for j := 0; j < len(types); j++ { | |||||
| sheet.AddType(types[j]) | |||||
| } | |||||
| case "page": | |||||
| page, err := parsePage(node, sheet) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| sheet.Pages = append(sheet.Pages, page) | |||||
| default: | |||||
| return nil, errors.New("Wrong format") | |||||
| } | |||||
| } | |||||
| return sheet, nil | |||||
| } | |||||
| func ReadCharacterSheet(filename string) (*CharacterSheet, error) { | |||||
| file, err := os.Open(filename) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| defer file.Close() | |||||
| decoder := xml.NewDecoder(file) | |||||
| var n xmlNode | |||||
| err = decoder.Decode(&n) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| sheet, err := parseSheet(n) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| return sheet, nil | |||||
| } | |||||
| @ -0,0 +1,242 @@ | |||||
| package jdr | |||||
| import( | |||||
| "log" | |||||
| "errors" | |||||
| "strings" | |||||
| "regexp" | |||||
| ) | |||||
| var regexp_int *regexp.Regexp | |||||
| var regexp_float *regexp.Regexp | |||||
| var regexp_d *regexp.Regexp | |||||
| func init() { | |||||
| regexp_int = regexp.MustCompile(`^[0-9]+$`) | |||||
| regexp_float = regexp.MustCompile(`^[+-]?[0-9]*\.[0-9]+$`) | |||||
| regexp_d = regexp.MustCompile(`^[1-9][0-9]*d([468]|1[02]|20|100)$`) | |||||
| } | |||||
| type VariableType interface { | |||||
| Name() string | |||||
| Validate(s string) bool | |||||
| } | |||||
| type VariableType_badType struct {} | |||||
| func (*VariableType_badType) Name() string { | |||||
| return "unknown type" | |||||
| } | |||||
| func (*VariableType_badType) Validate(s string) bool { | |||||
| return false | |||||
| } | |||||
| type VariableType_bool struct {} | |||||
| func (*VariableType_bool) Name() string { | |||||
| return "bool" | |||||
| } | |||||
| func (*VariableType_bool) Validate(s string) bool { | |||||
| switch strings.ToLower(s) { | |||||
| case "true": | |||||
| return true | |||||
| case "false": | |||||
| return true | |||||
| default: | |||||
| return false | |||||
| } | |||||
| } | |||||
| type VariableType_int struct {} | |||||
| func (*VariableType_int) Name() string { | |||||
| return "int" | |||||
| } | |||||
| func (*VariableType_int) Validate(s string) bool { | |||||
| return regexp_int.MatchString(s) | |||||
| } | |||||
| type VariableType_float struct {} | |||||
| func (*VariableType_float) Name() string { | |||||
| return "float" | |||||
| } | |||||
| func (*VariableType_float) Validate(s string) bool { | |||||
| return regexp_float.MatchString(s) | |||||
| } | |||||
| type VariableType_string struct {} | |||||
| func (*VariableType_string) Name() string { | |||||
| return "string" | |||||
| } | |||||
| func (*VariableType_string) Validate(s string) bool { | |||||
| return true | |||||
| } | |||||
| type VariableType_d struct {} | |||||
| func (*VariableType_d) Name() string { | |||||
| return "d" | |||||
| } | |||||
| func (*VariableType_d) Validate(s string) bool { | |||||
| return regexp_d.MatchString(s) | |||||
| } | |||||
| func makeVariableType(s string) VariableType { | |||||
| switch(strings.ToLower(s)) { | |||||
| case "bool": | |||||
| return &VariableType_bool{} | |||||
| case "int": | |||||
| return &VariableType_int{} | |||||
| case "float": | |||||
| return &VariableType_float{} | |||||
| case "string": | |||||
| return &VariableType_string{} | |||||
| case "d": | |||||
| return &VariableType_d{} | |||||
| default: | |||||
| log.Println("Unknown type:", s) | |||||
| return &VariableType_badType{} | |||||
| } | |||||
| } | |||||
| type CustomEnum struct { | |||||
| name string | |||||
| BaseType VariableType | |||||
| Values []string | |||||
| } | |||||
| func (self *CustomEnum) Name() string { | |||||
| return self.name | |||||
| } | |||||
| func (self *CustomEnum) Validate(s string) bool { | |||||
| if !self.BaseType.Validate(s) { | |||||
| return false | |||||
| } | |||||
| for i := 0; i < len(self.Values); i++ { | |||||
| if self.Values[i] == s { | |||||
| return true | |||||
| } | |||||
| } | |||||
| return false | |||||
| } | |||||
| type CustomStruct struct { | |||||
| name string | |||||
| Variables []Variable | |||||
| } | |||||
| func (self *CustomStruct) Name() string { | |||||
| return self.name | |||||
| } | |||||
| func (self *CustomStruct) Validate(s string) bool { | |||||
| log.Println("Custom struct Validate is not (yet) implemented") | |||||
| return false | |||||
| } | |||||
| func parseCustomEnum(root xmlNode) (*CustomEnum, error) { | |||||
| customEnum := CustomEnum {} | |||||
| nameOk := false; | |||||
| typeOk := false; | |||||
| for i := 0; i < len(root.Attrs); i++ { | |||||
| attrName := root.Attrs[i].Name.Local | |||||
| attrVal := root.Attrs[i].Value | |||||
| switch attrName { | |||||
| case "name": | |||||
| nameOk = true | |||||
| customEnum.name = attrVal | |||||
| case "base-type": | |||||
| typeOk = true | |||||
| customEnum.BaseType = makeVariableType(attrVal) | |||||
| switch customEnum.BaseType.(type) { | |||||
| case *VariableType_badType: | |||||
| typeOk = false | |||||
| } | |||||
| } | |||||
| } | |||||
| if !nameOk { | |||||
| return nil, errors.New("Unnamed custom enum") | |||||
| } | |||||
| if !typeOk { | |||||
| return nil, errors.New("Bad base type for " + customEnum.Name()) | |||||
| } | |||||
| for i := 0; i < len(root.Nodes); i++ { | |||||
| node := root.Nodes[i] | |||||
| switch node.XMLName.Local { | |||||
| case "value": | |||||
| value := string(node.Content) | |||||
| if customEnum.BaseType.Validate(value) { | |||||
| customEnum.Values = append(customEnum.Values, value) | |||||
| } else { | |||||
| log.Println("Cannot validate value enum \"" + value + "\" with type \"" + | |||||
| customEnum.BaseType.Name() + "\". Discarded") | |||||
| } | |||||
| } | |||||
| } | |||||
| return &customEnum, nil | |||||
| } | |||||
| func parseCustomStruct(root xmlNode) (*CustomStruct, error) { | |||||
| customType := CustomStruct{} | |||||
| nameOk := false | |||||
| for i := 0; i < len(root.Attrs); i++ { | |||||
| attrName := root.Attrs[i].Name.Local | |||||
| attrVal := root.Attrs[i].Value | |||||
| switch attrName { | |||||
| case "name": | |||||
| nameOk = true | |||||
| customType.name = attrVal | |||||
| } | |||||
| } | |||||
| if !nameOk { | |||||
| return nil, errors.New("Unnamed custom type") | |||||
| } | |||||
| return &customType, nil | |||||
| } | |||||
| func parseCustomTypes(root xmlNode) ([]VariableType, error) { | |||||
| types := []VariableType{} | |||||
| for i := 0; i < len(root.Nodes); i++ { | |||||
| node := root.Nodes[i] | |||||
| switch node.XMLName.Local { | |||||
| case "enum": | |||||
| customEnum, err := parseCustomEnum(node) | |||||
| if err != nil { | |||||
| return []VariableType{}, err | |||||
| } | |||||
| types = append(types, customEnum) | |||||
| case "type": | |||||
| customType, err := parseCustomStruct(node) | |||||
| if err != nil { | |||||
| return []VariableType{}, err | |||||
| } | |||||
| types = append(types, customType) | |||||
| default: | |||||
| return []VariableType{}, errors.New("Unknown CustomType: " + root.Nodes[i].XMLName.Local) | |||||
| } | |||||
| } | |||||
| return types, nil | |||||
| } | |||||