| @ -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 | |||
| } | |||