Browse Source

Working on serialization for all kinds of data.

Beoran 8 years ago
parent
commit
5f9dca41ae

+ 0 - 5
data/var/account/B/Beoran/Beoran.account

@@ -1,5 +0,0 @@
-:name:Beoran
-:algo:plain
-:pass:hello
-:privilege:500
---

+ 0 - 3
data/var/account/C/Celia/Celia.account

@@ -1,3 +0,0 @@
-name=Celia
-algo=plain
-pass=hello1woe

+ 68 - 13
src/woe/monolog/monolog.go

@@ -8,6 +8,8 @@ import (
     "time"
     "runtime"
     "path/filepath"
+    "unicode"
+    "strings"
 )
 
   
@@ -17,6 +19,20 @@ type Logger interface {
 }
 
 
+func GetCallerName(depth int) {
+    pc := make([]uintptr, depth+1)
+    runtime.Callers(depth, pc)
+    for i, v := range pc {
+        fun := runtime.FuncForPC(v)
+        if fun != nil { 
+            fmt.Printf("GetCallerName %d %s\n", i, fun.Name()) 
+        } else {
+              fmt.Printf("GetCallerName %d nil\n", i) 
+        }
+    }
+}
+
+
 func (me * FileLogger) WriteLog(depth int, level string, format string, args ... interface{}) {
     _ ,file, line, ok := runtime.Caller(depth)
     
@@ -52,7 +68,7 @@ func (me * FileLogger) Debug(format string, args ... interface{}) {
     me.WriteLog(2, "DEBUG", format, args...)
 }
 
-  
+ 
 type FileLogger struct {
     filename      string
     file        * os.File
@@ -97,34 +113,58 @@ func NewStdoutLogger() (logger Logger, err error) {
     return &FileLogger{"/dev/stderr", os.Stdout}, nil
 }
 
-type Log struct {
+type Logbook struct {
     loggers          [] Logger
     levels  map[string] bool
 }
 
 
-func NewLog() * Log {
+func NewLog() * Logbook {
     loggers :=  make([] Logger, 32)
     levels  :=  make(map[string] bool)
-    return &Log{loggers, levels}
+    return &Logbook{loggers, levels}
 }
 
-func (me * Log) AddLogger(logger Logger) {
+func (me * Logbook) AddLogger(logger Logger) {
     me.loggers = append(me.loggers, logger)
 }
 
-func (me * Log) EnableLevel(level string) {
+func (me * Logbook) EnableLevel(level string) {
     me.levels[level] = true
 }
 
-func (me * Log) DisableLevel(level string) {
+func (me * Logbook) DisableLevel(level string) {
     me.levels[level] = false
 }
 
-func (me * Log) LogVa(name string, file string, line int, format string, args...interface{}) {
-    _, ok := me.levels[name]
+func enableDisableSplitter(c rune) (bool) {
+        ok :=  (!unicode.IsLetter(c))
+        ok = ok && (!unicode.IsNumber(c))
+        ok = ok && (c != '_')
+        return ok
+}
+
+
+func (me * Logbook) EnableLevels(list string) {    
+    to_enable := strings.FieldsFunc(list, enableDisableSplitter)
+    for _, level := range to_enable {
+        me.EnableLevel(level)
+    }
+}
+
     
-    if !ok {
+func (me * Logbook) DisableLevels(list string) {    
+    to_disable := strings.FieldsFunc(list, enableDisableSplitter)
+    for _, level := range to_disable {
+        me.DisableLevel(level)
+    }
+}
+
+
+func (me * Logbook) LogVa(name string, file string, line int, format string, args...interface{}) {
+    enabled, ok := me.levels[name]
+    
+    if (!ok) || (!enabled) {
         return
     }
     
@@ -135,7 +175,7 @@ func (me * Log) LogVa(name string, file string, line int, format string, args...
     }
 }
 
-func (me * Log) Close() {    
+func (me * Logbook) Close() {    
     for index , logger := range me.loggers {
         if logger != nil {
             logger.Close()
@@ -144,7 +184,7 @@ func (me * Log) Close() {
     }
 }
 
-var DefaultLog * Log
+var DefaultLog * Logbook
 
 func init() {
     DefaultLog = NewLog()
@@ -159,6 +199,15 @@ func DisableLevel(level string) {
     DefaultLog.DisableLevel(level)
 }
 
+func EnableLevels(list string) {    
+  DefaultLog.EnableLevels(list)
+}
+
+func DisableLevels(list string) {    
+  DefaultLog.DisableLevels(list)
+}
+
+
 func AddLogger(logger Logger, err error) {
     if err == nil {
         DefaultLog.AddLogger(logger)
@@ -199,7 +248,7 @@ func WriteLog(depth int, name string, format string, args ... interface{}) {
     DefaultLog.LogVa(name, file, line, format, args...)
 }
 
-func NamedLog(name string, format string, args ...interface{}) {
+func Log(name string, format string, args ...interface{}) {
     WriteLog(2, name, format, args)
 }
 
@@ -223,5 +272,11 @@ func Debug(format string, args ...interface{}) {
     WriteLog(2, "DEBUG", format, args...)
 }
 
+func エラ(err error) {
+    WriteLog(2, "ERROR", "%s", err.Error())
+}
 
+func WriteError(err error) {
+    WriteLog(2, "ERROR", "%s", err.Error())
+}
 

+ 12 - 2
src/woe/server/action.go

@@ -2,6 +2,7 @@ package server
 
 import "bytes"
 import "errors"
+import "regexp"
 // import "github.com/beoran/woe/telnet"
 import "github.com/beoran/woe/world"
 import "github.com/beoran/woe/monolog"
@@ -63,10 +64,19 @@ func doQuit(data * ActionData) (err error) {
     return nil
 }
 
+func doEnableLog(data * ActionData) (err error) {  
+    // strings. string(data.Rest)
+    return nil
+}
+
+
 func ParseCommand(command []byte, data * ActionData) (err error) {
     /* strip any leading blanks  */
     trimmed    := bytes.TrimLeft(command, " \t")
-    parts      := bytes.SplitN(trimmed, []byte(" \t,"), 2)  
+    re         := regexp.MustCompile("[^ \t,]+")
+    parts      := re.FindAll(command, -1)
+    
+    bytes.SplitN(trimmed, []byte(" \t,"), 2)  
     
     if len(parts) < 1 {
         data.Command = nil
@@ -75,7 +85,7 @@ func ParseCommand(command []byte, data * ActionData) (err error) {
     data.Command = parts[0]
     if len(parts) > 1 { 
         data.Rest    = parts[1]
-        data.Argv    = bytes.Split(data.Rest, []byte(" \t,"))
+        data.Argv    = parts
     } else {
         data.Rest    = nil
         data.Argv    = nil

+ 524 - 31
src/woe/server/ask.go

@@ -8,6 +8,7 @@ import "github.com/beoran/woe/telnet"
 import "github.com/beoran/woe/world"
 import "github.com/beoran/woe/monolog"
 import "bytes"
+import "strings"
 import "regexp"
 // import "fmt"
 import "strconv"
@@ -86,7 +87,7 @@ func (me * Client) AskSomething(prompt string, re string, nomatch_prompt string,
     }
 
     for something == nil || len(something) == 0 { 
-        me.Printf("%s:", prompt)
+        me.Printf("%s", prompt)
         something, _, _ = me.TryRead(-1)
         if something != nil {
             something = bytes.TrimRight(something, "\r\n")
@@ -114,72 +115,417 @@ func (me * Client) AskYesNo(prompt string) bool {
         return true
     } else {
         return false
-    }    
+    }
+}
+
+// Interface for an item in a list of options. 
+type AskOption interface {
+    // Name of the option, also used to compare input
+    AskName() string
+    // Short description of the option, shown after name
+    AskShort() string
+    // Long description, displayed if "help name" is requested
+    AskLong() string
+    // Accound privilege required or the option to be selectable.
+    AskPrivilege() world.Privilege
+}
+
+type AskOptionList interface {
+    AskOptionListLen() int
+    AskOptionListGet(index int) AskOption
+}
+
+type TrivialAskOption string
+
+func (me TrivialAskOption) AskName() (string) {
+    return string(me)
+}
+
+func (me TrivialAskOption) AskLong() (string) {
+    return ""
+}
+
+func (me TrivialAskOption) AskShort() (string) {
+    return ""
+}
+
+func (me TrivialAskOption) AskPrivilege() (world.Privilege) {
+    return world.PRIVILEGE_ZERO
+}
+
+type TrivialAskOptionList []TrivialAskOption
+
+func (me TrivialAskOptionList) AskOptionListLen() int {
+    return len(me)
+}
+
+func (me TrivialAskOptionList) AskOptionListGet(index int) AskOption {
+    return me[index]
+}
+
+
+
+type SimpleAskOption struct {
+    Name string
+    Short string
+    Long string
+    Privilege world.Privilege
+}
+
+func (me SimpleAskOption) AskName() string {
+    return me.Name
+}
+
+func (me SimpleAskOption) AskShort() string {
+    return me.Short
 }
 
-func (me * Client) AskEntityListOnce(heading string, prompt string, noecho bool, elist world.EntitylikeSlice) (result world.Entitylike) { 
+func (me SimpleAskOption) AskLong() string {
+    return me.Long
+}
+
+func (me SimpleAskOption) AskPrivilege() world.Privilege {
+    return me.Privilege
+}
+
+type SimpleAskOptionList []SimpleAskOption
+
+func (me SimpleAskOptionList) AskOptionListLen() int {
+    return len(me)
+}
+
+func (me SimpleAskOptionList) AskOptionListGet(index int) AskOption {
+    return me[index]
+}
+
+
+type JobListAsker []world.Job
+
+func (me JobListAsker) AskOptionListLen() int {
+    return len(me)
+}
+
+func (me JobListAsker) AskOptionListGet(index int) AskOption {
+    return me[index]
+}
+
+type KinListAsker []world.Kin
+
+func (me KinListAsker) AskOptionListLen() int {
+    return len(me)
+}
+
+func (me KinListAsker) AskOptionListGet(index int) AskOption {
+    return me[index]
+}
+
+type GenderListAsker []world.Gender
+
+func (me GenderListAsker) AskOptionListLen() int {
+    return len(me)
+}
+
+func (me GenderListAsker) AskOptionListGet(index int) AskOption {
+    return me[index]
+}
+
+
+
+type AskOptionSlice []AskOption
+type AskOptionFilterFunc func(AskOption) (AskOption)
+
+func (me AskOptionSlice) AskOptionListLen() int {
+    return len(me)
+}
+
+func (me AskOptionSlice) AskOptionListGet(index int) AskOption {
+    return me[index]
+}
+
+func AskOptionListEach(me AskOptionList, cb AskOptionFilterFunc) (AskOption) {
+    for i := 0; i <  me.AskOptionListLen() ; i++ {
+        res := cb(me.AskOptionListGet(i))
+        if (res != nil) {
+            return res
+        }
+    }
+    return nil
+}
+
+
+func AskOptionListFilter(me AskOptionList, cb AskOptionFilterFunc) (AskOptionList) {
+    result := make(AskOptionSlice, 0)
+    for i := 0; i <  me.AskOptionListLen() ; i++ {
+        res := cb(me.AskOptionListGet(i))
+        if (res != nil) {
+            result = append(result, res)
+        }
+    }
+    return result
+}
+
+/* Finds the name irrespecful of the case */
+func AskOptionListFindName(me AskOptionList, name string) (AskOption) {
+    return AskOptionListEach(me, func (e AskOption) (AskOption) {
+        if strings.ToLower(e.AskName()) == strings.ToLower(name) {
+            return e
+        } else {
+            return nil
+        }
+    })
+}
+
+
+/* Filters the list by privilege level (only those allowed by the level are retained) */
+func AskOptionListFilterPrivilege(me AskOptionList, privilege world.Privilege) (AskOptionList) {
+    return AskOptionListFilter(me, func (e AskOption) (AskOption) {
+        if (e.AskPrivilege() <= privilege) {
+            return e
+        } else {
+            return nil
+        }
+    })
+}
+
+type MergedAskOptionList struct {
+    head AskOptionList
+    tail AskOptionList
+}
+
+func (me * MergedAskOptionList) AskOptionListLen() int {
+    return me.head.AskOptionListLen() + me.tail.AskOptionListLen()
+}
+
+func (me * MergedAskOptionList) AskOptionListGet(index int) (AskOption) {
+    headlen := me.head.AskOptionListLen()
+    if (index < headlen) {
+        return me.head.AskOptionListGet(index)
+    }
+    return me.tail.AskOptionListGet(index - headlen)
+}
+
+
+/* Merges two AskOptionLists without copying using the MergedAskOptionList rtype  */
+func AskOptionListMerge(me AskOptionList, you AskOptionList) (AskOptionList) {
+    return &MergedAskOptionList{me, you}
+}
+
+
+func (me AskOptionSlice) Each(cb AskOptionFilterFunc) (AskOption) {
+    for i := 0; i <  len(me) ; i++ {
+        res := cb(me[i])
+        if (res != nil) {
+            return res
+        }
+    }
+    return nil
+}
+
+
+func (me AskOptionSlice) Filter(cb AskOptionFilterFunc) (AskOptionSlice) {
+    result := make(AskOptionSlice, 0)
+    for i := 0; i <  len(me) ; i++ {
+        res := cb(me[i])
+        if (res != nil) {
+            result = append(result, res)
+        }
+    }
+    return result
+}
+
+/* Finds the name irrespecful of the case */
+func (me AskOptionSlice) FindName(name string) (AskOption) {
+    return me.Each(func (e AskOption) (AskOption) {
+        if strings.ToLower(e.AskName()) == strings.ToLower(name) {
+            return e
+        } else {
+            return nil
+        }
+    })
+}
+
+
+/* Filters the list by privilege level (only those allowed by the level are retained) */
+func (me AskOptionSlice) FilterPrivilege(privilege world.Privilege) (AskOptionSlice) {
+    return me.Filter(func (e AskOption) (AskOption) {
+        if (e.AskPrivilege() <= privilege) {
+            return e
+        } else {
+            return nil
+        }
+    })
+}
+
+func (me * Client) AskOptionListHelp(alist AskOptionList, input []byte) { 
+    re := regexp.MustCompile("[^ \t,]+")
+    argv  := re.FindAll(input, -1)
+    if (len(argv) < 2) {
+        me.Printf("Help usage: help <topic>.\n")
+        return
+    } 
+    e := AskOptionListFindName(alist, string(argv[1])) 
+    if (e == nil) {
+        me.Printf("Cannot find topic %s in list. No help available.\n", string(argv[1]))
+    } else  {
+        al := e.AskLong()
+        if (al == "") {
+            me.Printf("Topic %s found, but help is unavailable.\n", string(argv[1]))
+        } else {
+            me.Printf("Help on %s:\n%s\n", string(argv[1]), e.AskLong())
+        }
+    }
+}
+
+
+func (me * Client) AskOptionListOnce(heading string,  prompt string, noecho bool, alist AskOptionList) (result AskOption) { 
+    list := AskOptionListFilterPrivilege(alist, me.account.Privilege)
+    me.Printf("\n%s\n\n",heading)
+    for i := 0; i < list.AskOptionListLen(); i++ {
+        v := list.AskOptionListGet(i)
+        sh := v.AskShort() 
+        if sh == "" { 
+            me.Printf("[%d] %s\n", i+1, v.AskName())
+        } else {
+            me.Printf("[%d] %s: %s\n", i+1, v.AskName(), sh)
+        }
+    }
+    me.Printf("\n")
+    aid := me.AskSomething(prompt, "", "", false);
+    iresp, err := strconv.Atoi(string(aid))
+    if err != nil { /* Try by name if not a number. */
+        e := AskOptionListFindName(alist, string(aid))
+        if e != nil {
+            return e
+        } else if ok, _ := regexp.Match("help", bytes.ToLower(aid)) ; ok {
+            me.AskOptionListHelp(list, aid)
+        } else {    
+        me.Printf("Name not found in list. Please choose a number or name from the list above. Or type help <option> for help on that option.\n")
+        }
+    } else if (iresp>0) && (iresp<=list.AskOptionListLen()) { 
+        /* In range of list. */
+        return list.AskOptionListGet(iresp-1)
+    } else {
+        me.Printf("Please choose a number or name from the list above.\n")
+    }
+    return nil
+}
+
+func (me * Client) AskOptionList(
+    heading string, prompt string, noecho bool, 
+    noconfirm bool, list AskOptionList) (result AskOption) {     
+    for {
+        result = me.AskOptionListOnce(heading, prompt, noecho, list)
+        if result != nil {
+            if noconfirm || me.AskYesNo(heading + "\nConfirm " + result.AskName() + "? ") {
+                return result
+            }
+        }
+    }
+}
+
+func (me * Client) AskOptionListExtra(heading string, 
+prompt string, noecho bool, noconfirm bool, list AskOptionList, 
+extra AskOptionList) (result AskOption) {
+    xlist := AskOptionListMerge(list, extra)
+    return me.AskOptionList(heading, prompt, noecho, noconfirm, xlist)
+}
+
+
+
+func (me * Client) AskEntityListOnce(
+        heading string,  prompt string, noecho bool, 
+        elist world.EntitylikeSlice, extras []string) (result world.Entitylike, alternative string) { 
     list := elist.FilterPrivilege(me.account.Privilege)
     me.Printf("\n%s\n\n",heading)
+    last := 0
     for i, v := range(list) {
         e := v.AsEntity()
         me.Printf("[%d] %s: %s\n", i+1, e.Name, e.Short)
+        last = i+1
     }
+    
+    if extras != nil {
+        for i, v := range(extras) {
+            me.Printf("[%d] %s\n", last+i+1, v)
+        }
+    }
+    
     me.Printf("\n")
     aid := me.AskSomething(prompt, "", "", false);
     iresp, err := strconv.Atoi(string(aid))
-    if err != nil { /* Try name. */
+    if err != nil { /* Try by name if not a number. */
         e := list.FindName(string(aid))
         if e != nil {
-            return e
+            return e, ""
         } else {
+            if extras != nil {
+                for _, v := range(extras) {
+                    if strings.ToLower(v) == strings.ToLower(string(aid)) {
+                        return nil, v
+                    }
+                }
+            }
             me.Printf("Name not found in list. Please choose a number or name from the list above.\n")
         }
-    } else if (iresp>0) && (iresp<=len(list)) { /* In range. */
-        return list[iresp-1]
+    } else if (iresp>0) && (iresp<=len(list)) { /* In range of list. */
+        return list[iresp-1], ""
+    } else if (extras != nil) && (iresp>last) && (iresp <= last + len(extras)) { 
+        return nil, extras[iresp - last - 1]
     } else {
         me.Printf("Please choose a number or name from the list above.\n")
     }
-    return nil
+    return nil, ""
 }
     
 
-func (me * Client) AskEntityList(heading string, prompt string, noecho bool, list world.EntitylikeSlice) (result world.Entitylike) {     
+func (me * Client) AskEntityList(
+    heading string, prompt string, noecho bool, 
+    noconfirm bool, list world.EntitylikeSlice, extras []string) (result world.Entitylike, alternative string) {     
     for {
-        result = me.AskEntityListOnce(heading, prompt, noecho, list)
+        result, alternative = me.AskEntityListOnce(heading, prompt, noecho, list, extras)
         if result != nil {
             e := result.AsEntity()
-            me.Printf("\n%s: %s\n\n%s\n\n", e.Name, e.Short, e.Long)
-            if noecho || me.AskYesNo("Confirm?") {
-                return result
+            if (!noconfirm) {
+                me.Printf("\n%s: %s\n\n%s\n\n", e.Name, e.Short, e.Long)
+            } 
+            if noconfirm || me.AskYesNo(heading + "\nConfirm " + e.Name + "? ") {
+                return result, ""
+            }
+        } else if alternative != "" {
+            if noconfirm || me.AskYesNo("Confirm " + alternative + " ?") {
+                return result, alternative
             }
         }
     }
 }
     
 
-const LOGIN_RE = "^[A-Za-z]+$"
+const LOGIN_RE = "^[A-Za-z][A-Za-z0-9]*$"
 
 func (me * Client) AskLogin() []byte {
-    return me.AskSomething("Login", LOGIN_RE, "Login must consist of a letter followed by letters or numbers.", false)
+    return me.AskSomething("Login?>", LOGIN_RE, "Login must consist of letters followed by letters or numbers.", false)
 }
 
 
 const EMAIL_RE = "@"
 
 func (me * Client) AskEmail() []byte {
-    return me.AskSomething("E-mail", EMAIL_RE, "Email must have at least an @ in there somewhere.", false)
+    return me.AskSomething("E-mail?>", EMAIL_RE, "Email must have at least an @ in there somewhere.", false)
 }
 
+const CHARNAME_RE = "^[A-Z][A-Za-z]+$"
+
+
 func (me * Client) AskCharacterName() []byte {
-    return me.AskSomething("Character Name", LOGIN_RE, "Character name consisst of letters only.", false)
+    return me.AskSomething("Character Name?>", CHARNAME_RE, "Character name consist of a capital letter followed by at least one letter.", false)
 }
 
 func (me * Client) AskPassword() []byte {
-    return me.AskSomething("Password", "", "", true)
+    return me.AskSomething("Password?>", "", "", true)
 }
 
 func (me * Client) AskRepeatPassword() []byte {
-    return me.AskSomething("Repeat Password", "", "", true)
+    return me.AskSomething("Repeat Password?>", "", "", true)
 }
 
 func (me * Client) HandleCommand() {
@@ -264,20 +610,64 @@ func (me * Client) AccountDialog() bool {
 }
 
 func (me * Client) NewCharacterDialog() bool {
+    noconfirm := true
+    extra := TrivialAskOptionList { TrivialAskOption("Cancel") }
+
     me.Printf("New character:\n")
     charname := me.AskCharacterName()
     
-    kin := me.AskEntityList("Please choose the kin of this character", "Kin of character? ", false, world.KinEntityList)
-    me.Printf("%s %v\n", charname, kin)
-     
-    gender := me.AskEntityList("Please choose the gender of this character", "Gender? ", false, world.GenderList)
-    me.Printf("%s %v\n", charname, gender)
+    existing, aname, _ := world.LoadCharacterByName(me.server.DataPath(), string(charname))
+    
+    for (existing != nil) {
+        if (aname == me.account.Name) {
+            me.Printf("You already have a character with a similar name!\n")
+        } else {
+            me.Printf("That character name is already taken by someone else.\n")
+        }
+        charname := me.AskCharacterName()
+        existing, aname, _ = world.LoadCharacterByName(me.server.DataPath(), string(charname))
+    }
+    
+    kinres := me.AskOptionListExtra("Please choose the kin of this character", "Kin?> ", false, noconfirm, KinListAsker(world.KinList), extra)
 
-    job := me.AskEntityList("Please choose the job of this character", "Job? ", false, world.JobEntityList)
-    me.Printf("%s %v\n", charname, job)
+    if sopt, ok := kinres.(TrivialAskOption) ; ok  {
+        if string(sopt) == "Cancel" { 
+            me.Printf("Character creation canceled.\n")
+            return true
+        } else {
+            return true
+        }
+    }
     
+    kin := kinres.(world.Kin)
+    
+    genres := me.AskOptionListExtra("Please choose the gender of this character", "Gender?> ", false, noconfirm, GenderListAsker(world.GenderList), extra)
+    if sopt, ok := kinres.(TrivialAskOption) ; ok  {
+        if string(sopt) == "Cancel" { 
+            me.Printf("Character creation canceled.\n")
+            return true
+        } else {
+            return true
+        }
+    }
+     
+    gender := genres.(world.Gender)
+
+
+    jobres := me.AskOptionListExtra("Please choose the job of this character", "Gender?> ", false, noconfirm, JobListAsker(world.JobList), extra)
+    if sopt, ok := kinres.(TrivialAskOption) ; ok  {
+        if string(sopt) == "Cancel" { 
+            me.Printf("Character creation canceled.\n")
+            return true
+        } else {
+            return true
+        }
+    }
+     
+    job := jobres.(world.Job)
+
     character := world.NewCharacter(me.account, 
-                    string(charname), kin, gender, job)
+                    string(charname), &kin, &gender, &job)
     
     me.Printf("%s", character.Being.ToStatus());
     
@@ -298,21 +688,124 @@ func (me * Client) NewCharacterDialog() bool {
     return true
 }    
 
+
+func (me * Client) DeleteCharacterDialog() (bool) {
+    extra := []AskOption { TrivialAskOption("Cancel"), TrivialAskOption("Disconnect") }
+
+    els := me.AccountCharacterList()
+    els = append(els, extra...)
+    result := me.AskOptionList("Character to delete?", 
+        "Character?>", false, false, els)
+
+    if alt, ok :=  result.(TrivialAskOption) ; ok {
+        if string(alt) == "Disconnect" {
+            me.Printf("Disconnecting")
+            return false
+        } else if string(alt) == "Cancel" {
+            me.Printf("Canceled")
+            return true 
+        } else {
+            monolog.Warning("Internal error, unhandled case.")
+            return true
+        }
+    }
+    
+    
+    character := result.(*world.Character)
+    /* A character that is deleted gives NEW_CHARACTER_PRICE + 
+     * level / (NEW_CHARACTER_PRICE * 2) points, but only after the delete. */
+    np := NEW_CHARACTER_PRICE + character.Level / (NEW_CHARACTER_PRICE * 2)
+    me.account.DeleteCharacter(me.server.DataPath(), character)
+    me.account.Points += np
+    
+    
+    return true
+}
+
+
+
+func (me * Client) AccountCharacterList() AskOptionSlice {
+    els := make(AskOptionSlice, 0, 16)
+    for i:= 0 ; i < me.account.NumCharacters(); i++ {
+        chara := me.account.GetCharacter(i)
+        els = append(els, chara)
+    }
+    return els
+}
+
+func (me * Client) ChooseCharacterDialog() (bool) {
+    extra := []AskOption { 
+        SimpleAskOption{"New", "Create New character", 
+            "Create a new character. This option costs 4 points.",
+             world.PRIVILEGE_ZERO},
+        SimpleAskOption{"Disconnect", "Disconnect from server", 
+            "Disconnect your client from this server.", 
+                        world.PRIVILEGE_ZERO},
+        SimpleAskOption{"Delete", "Delete character", 
+            "Delete a character. A character that has been deleted cannot be reinstated. You will receive point bonuses for deleting your characters that depend on their level.", 
+                        world.PRIVILEGE_ZERO},
+    }
+        
+        
+  
+    var pchara * world.Character = nil
+    
+    for pchara == nil { 
+        els := me.AccountCharacterList()
+        els = append(els, extra...)
+        result := me.AskOptionList("Choose a character?", "Character?>", false, true, els)
+        switch opt := result.(type) { 
+        case SimpleAskOption:
+        if (opt.Name == "New") {
+           if (me.account.Points >= NEW_CHARACTER_PRICE) {
+                if (!me.NewCharacterDialog()) {
+                    return false
+                }
+            } else {
+                me.Printf("Sorry, you have no points left to make new characters!\n")
+            }  
+        } else if opt.Name == "Disconnect" {
+            me.Printf("Disconnecting\n")
+            return false
+        } else if opt.Name == "Delete" {
+            if (!me.DeleteCharacterDialog()) {
+                return false
+            }
+        } else {
+            me.Printf("Internal error, alt not valid: %v.", opt)
+        }
+        case * world.Character: 
+            pchara = opt
+        default:
+            me.Printf("What???")
+        }
+        me.Printf("You have %d points left.\n", me.account.Points)
+    }
+    
+    me.character = pchara
+    me.Printf("%s\n", me.character.Being.ToStatus())
+    me.Printf("Welcome, %s!\n", me.character.Name)
+   
+    return true
+}
  
 func (me * Client) CharacterDialog() bool {
     me.Printf("You have %d remaining points.\n", me.account.Points)
     for me.account.NumCharacters() < 1 {
         me.Printf("You have no characters yet!\n")
-        if (me.account.Points > 0) {
-            me.NewCharacterDialog();
+        if (me.account.Points >= NEW_CHARACTER_PRICE) {
+            if (!me.NewCharacterDialog()) { 
+                return false
+            }
         } else {
-            me.Printf("Sorry, you have no points left to make new characters!\n")
+            me.Printf("Sorry, you have no characters, and no points left to make new characters!\n")
             me.Printf("Please contact the staff of WOE if you think this is a mistake.\n")
             me.Printf("Disconnecting!\n")
             return false 
         }
     }
-    return true
+    
+    return me.ChooseCharacterDialog()
 }
  
 

+ 9 - 4
src/woe/server/client.go

@@ -40,7 +40,11 @@ type Client struct {
     timechan chan time.Time 
     telnet * telnet.Telnet
     info     ClientInfo
+    
+    // Account of client or nil if not yet selected.
     account* world.Account
+    // Character client is plaing with or nil if not yet selected.
+    character * world.Character 
 }
 
 
@@ -50,7 +54,7 @@ func NewClient(server * Server, id int, conn net.Conn) * Client {
     timechan := make (chan time.Time, 32)
     telnet   := telnet.New()
     info     := ClientInfo{w : -1, h : -1, terminal: "none"}
-    return &Client{server, id, conn, true, -1, datachan, errchan, timechan, telnet, info, nil}
+    return &Client{server, id, conn, true, -1, datachan, errchan, timechan, telnet, info, nil, nil}
 }
 
 func (me * Client) Close() {
@@ -78,13 +82,13 @@ func (me * Client) ServeRead() {
 }
 
 /* Goroutine that sends any data that must be sent through the Telnet protocol 
- * (that any data, really) other to the connected client.
+ * to the connected client.
  */
  func (me * Client) ServeWrite() {
      for (me.alive) {
         select {
             case data := <- me.telnet.ToClient:
-            monolog.Debug("Will send to client: %v", data)
+            monolog.Log("SERVEWRITE","Will send to client: %v", data)
             me.conn.Write(data)
         }
     }
@@ -132,9 +136,10 @@ func (me * Client) TryRead(millis int) (data []byte, timeout bool, close bool) {
         }
         switch event := event.(type) {
             case * telnet.DataEvent:
-                monolog.Debug("Telnet data event %T : %d.", event, len(event.Data))
+                monolog.Log("TELNETDATAEVENT", "Telnet data event %T : %d.", event, len(event.Data))
                 return event.Data, false, false
             case * telnet.NAWSEvent:
+                monolog.Log("TELNETNAWSEVENT", "Telnet NAWS event %T.", event)
                 me.HandleNAWSEvent(event);
             default:
                 monolog.Info("Ignoring telnet event %T : %v for now.", event, event)

+ 17 - 13
src/woe/server/server.go

@@ -1,7 +1,6 @@
 package server
 
 import (
-    "log"
   //  "io"
     "net"
   //  "errors"
@@ -74,8 +73,6 @@ func init() {
 type Server struct {
     address               string
     listener              net.Listener
-    logger              * log.Logger
-    logfile             * os.File
     clients map[int]    * Client 
     tickers map[string] * Ticker
     alive                 bool
@@ -138,19 +135,22 @@ func NewServer(address string) (server * Server, err error) {
         return nil, err
     }
     
-    logfile, err := os.OpenFile("log.woe", os.O_WRONLY | os.O_APPEND | os.O_CREATE, 0660)
-    if (err != nil) {
-        return nil, err
-    }
+    monolog.Info("Server listening on %s.", address)
     
-    logger := log.New(logfile, "woe", log.Llongfile | log.LstdFlags)
     clients := make(map[int] * Client)
     tickers := make(map[string] * Ticker)
 
-    server = &Server{address, listener, logger, logfile, clients, tickers, true, nil, STATUS_RESTART}
+    server = &Server{address, listener, clients, tickers, true, nil, STATUS_RESTART}
     err = server.SetupWorld()
-    server.AddDefaultTickers()
+    if err != nil {
+        monolog.Error("Could not set up or load world!")
+        return nil, err
+    }
     
+    monolog.Info("Server world set up.")    
+    server.AddDefaultTickers()
+    monolog.Info("Tickers set up.")
+
     return server, err
 }
 
@@ -280,8 +280,7 @@ func (me * Server) Close() {
     }
     
     me.handleDisconnectedClients()
-    monolog.Info("Closing server, closing logfile.")
-    me.logfile.Close();
+    monolog.Info("Closed server.")
 }
 
 func (me * Server) Serve() (status int, err error) { 
@@ -328,11 +327,16 @@ func (me * Server) Broadcast(format string, args ...interface{}) {
 func (me * Server) DataPath() string {
     // 
     cwd, err := os.Getwd();
+    monolog.Debug("Current direcory: %s (%v).", cwd, err)
+    
     if  err != nil {
         cwd = "."
     }
     
-    return filepath.Join(cwd, "data", "var")
+    fp := filepath.Join(cwd, "data", "var")
+    monolog.Debug("Data path: %s (%v). ", fp, err)
+
+    return fp
 }
 
 // Returns the script path of the server

+ 122 - 25
src/woe/sitef/sitef.go

@@ -39,20 +39,36 @@ import "github.com/beoran/woe/monolog"
 // A # at the start optionally after whitespace is a comment
 // 
 
-type Record map[string]string
+type Record struct { 
+        dict map[string]string
+        order []string
+}
+
+func NewRecord() (* Record) {
+    rec := &Record{}
+    rec.dict  = make(map[string]string)
+    rec.order = make([]string, 0)
+    return rec
+}
 
 func (me * Record) Put(key string, val string) {
-    (*me)[key] = val
+    me.order = append(me.order, key)
+    me.dict[key] = val
 }
 
 func (me * Record) Putf(key string, format string, values ...interface{}) {
-    me.Put(key, fmt.Sprintf(format, values...)) 
+    me.Put(key, fmt.Sprintf(format, values...))
+    monolog.Debug("After putf: %s %v", key, me.order)
 }
 
+func (me * Record) PutArrayIndex(key string, index int, value string) {
+    realkey := fmt.Sprintf("%s[%d]", key, index)
+    me.Put(realkey, value)
+} 
+
 func (me * Record) PutArray(key string, values []string) {
     for i, value := range values {
-        realkey := fmt.Sprintf("%s[%d]", key, i)
-        me.Put(realkey, value)
+        me.PutArrayIndex(key, i, value)
     }
 } 
 
@@ -70,16 +86,22 @@ func (me * Record) PutFloat64(key string, val float64) {
 }
 
 func (me Record) MayGet(key string) (result string, ok bool) {
-    result, ok = me[key]
+    result, ok = me.dict[key]
     return result, ok
 }
 
 
 func (me Record) Get(key string) (result string) {
-    result= me[key]
+    result= me.dict[key]
     return result
 }
 
+func (me * Record) GetArrayIndex(key string, i int) (result string) {
+    realkey := fmt.Sprintf("%s[%d]", key, i)
+    return me.Get(realkey)
+}
+
+
 func (me Record) Getf(key string, format string, 
     values ...interface{}) (amount int, ok bool) {
     val := me.Get(key)
@@ -128,7 +150,16 @@ func (me * Record) convSimple(typ reflect.Type, val reflect.Value) (res string,
 }
 
 
-func (me Record) PutValue(key string, value reflect.Value) {
+func (me * Record) PutValue(key string, value reflect.Value) {
+
+    monolog.Debug("PutValue: %s %v", key, value)
+
+    stringer, ok := value.Interface().(fmt.Stringer)
+    if ok {
+        me.Put(key, stringer.String())
+        return
+    }
+    
     switch (value.Kind()) {
         case reflect.Int, reflect.Int32, reflect.Int64:
             me.Putf(key, "%d", value.Int())
@@ -138,21 +169,76 @@ func (me Record) PutValue(key string, value reflect.Value) {
             me.Putf(key, "%f", value.Float())
         case reflect.String:
             me.Putf(key, "%s", value.String())
+        case reflect.Struct:
+            me.PutStruct(key + ".", value.Interface());
         default:
             me.Put(key, "???")
     }
+    
+    monolog.Debug("Put: key %s value %s, result %v", key, value, me) 
+    
 }
 
-func (me Record) PutStruct(prefix string, structure interface {}) {
+func (me * Record) PutStruct(prefix string, structure interface {}) {
     st := reflect.TypeOf(structure)
     vt := reflect.ValueOf(structure)
-    monolog.Info("PutStruct: type %v value %v\n", st, vt)
     
     for i:= 0 ; i < st.NumField() ; i++ {
         field := st.Field(i)
         key := strings.ToLower(field.Name)
-        value :=  vt.Field(i).String()
-        me.Put(prefix + key, value)
+        value :=  vt.Field(i)
+        me.PutValue(prefix + key, value)
+    }
+}
+
+
+func (me Record) GetValue(key string, value reflect.Value) (err error){
+    /*stringer, ok := value.Interface().(fmt.Stringer)
+    if ok {
+        me.Gut(key, stringer.String())
+        return
+    }*/
+    monolog.Debug("GetValue: %s %v", key, value)
+    
+    switch (value.Kind()) {
+        case reflect.Int, reflect.Int32, reflect.Int64:
+            value.SetInt(int64(me.GetIntDefault(key, 0)))
+        case reflect.Uint, reflect.Uint32, reflect.Uint64:
+            value.SetUint(uint64(me.GetIntDefault(key, 0)))
+        case reflect.Float32, reflect.Float64:
+            f, err := me.GetFloat(key)
+            if (err != nil) { 
+                return err
+            }
+            value.SetFloat(f)
+        case reflect.String:
+            s, ok := me.MayGet(key)
+            if (!ok) {
+                return fmt.Errorf("Could not get string for key %s", key)
+            }
+            value.SetString(s)
+        case reflect.Struct:
+            me.GetStruct(key + ".", value.Addr().Interface());
+        default:
+            monolog.Warning("Don't know what to do with %v", value)
+    }
+    return nil
+}
+
+
+func (me Record) GetStruct(prefix string, structure interface {}) {
+    monolog.Info("GetStruct: structure %v, %v\n", structure, 
+        reflect.TypeOf(structure))
+    
+    st := reflect.TypeOf(structure).Elem()
+    vt := reflect.Indirect(reflect.ValueOf(structure))
+    monolog.Info("GetStruct: type %v value %v\n", st, vt)
+    
+    for i:= 0 ; i < st.NumField() ; i++ {
+        field := st.Field(i)
+        key := prefix + strings.ToLower(field.Name)
+        value :=  reflect.Indirect(vt).Field(i)
+        me.GetValue(key, value)
     }
 }
 
@@ -179,12 +265,12 @@ const (
     PARSER_STATE_VALUE
 )
 
-type RecordList []Record
+type RecordList []*Record
 
 
 func ParseReader(read io.Reader) (RecordList, error) {
     var records     RecordList
-    var record      Record = make(Record)
+    record      := NewRecord()
     var err         Error
     lineno      := 0
     scanner     := bufio.NewScanner(read)
@@ -197,13 +283,17 @@ func ParseReader(read io.Reader) (RecordList, error) {
         line := scanner.Text()
         // End of record?
         if (len(line) < 1) || line[0] == '-' {
+            // Append last record if needed. 
+            if len(key.String()) > 0 {
+                record.Put(key.String(), value.String())
+            }
             // save the record and make a new one
-            records = append(records, record) 
-            record  = make(Record)
-            // comment?
+            records = append(records, record)
+            record  = NewRecord()
+        // comment?
         } else if line[0] == '#' {
             continue; 
-            // continue value?
+        // continue value?
         } else if line[0] == '\t' || line[0] == ' '|| line[0] == '+' {
             
             /* Add a newline unless + is used */
@@ -213,11 +303,11 @@ func ParseReader(read io.Reader) (RecordList, error) {
             
             // continue the value, skipping the first character
             value.WriteString(line[1:])            
-            // new key
+        // new key
         } else if strings.ContainsRune(line, ':') {
             // save the previous key/value pair if needed
             if len(key.String()) > 0 {
-                record[key.String()] = value.String()
+                record.Put(key.String(), value.String())
             }
             
             key.Reset()
@@ -237,10 +327,10 @@ func ParseReader(read io.Reader) (RecordList, error) {
     
     // Append last record if needed. 
     if len(key.String()) > 0 {
-        record[key.String()] = value.String()
+        record.Put(key.String(), value.String())
     }
     
-    if (len(record) > 0) {
+    if (len(record.order) > 0) {
         records = append(records, record)
     }
 
@@ -248,7 +338,8 @@ func ParseReader(read io.Reader) (RecordList, error) {
 
     if serr := scanner.Err(); serr != nil {
        err.lineno = lineno
-       err.error  = serr.Error() 
+       err.error  = serr.Error()
+       monolog.Error("Sitef parse error: %d %s", lineno, serr.Error) 
        return records, err
     }
     
@@ -266,6 +357,7 @@ func ParseFilename(filename string) (RecordList, error) {
 }
 
 func WriteField(writer io.Writer, key string, value string) {
+    monolog.Debug("WriteField %s:%s", key, value)
     replacer := strings.NewReplacer("\n", "\n\t")    
     writer.Write([]byte(key))
     writer.Write([]byte{':'})    
@@ -274,7 +366,11 @@ func WriteField(writer io.Writer, key string, value string) {
 }
 
 func WriteRecord(writer io.Writer, record Record) {
-    for key, value := range record {
+    monolog.Debug("WriteRecord %v", record)
+
+    for index := 0 ; index < len(record.order) ; index++ {
+        key := record.order[index];
+        value := record.dict[key];
         WriteField(writer, key, value);
     }
     writer.Write([]byte{'-', '-', '-', '-', '\n'})
@@ -282,7 +378,7 @@ func WriteRecord(writer io.Writer, record Record) {
 
 func WriteRecordList(writer io.Writer, records RecordList) {
     for _, record := range records {
-        WriteRecord(writer, record);
+        WriteRecord(writer, *record);
     }
 }
 
@@ -290,6 +386,7 @@ func WriteRecordList(writer io.Writer, records RecordList) {
 func SaveRecord(filename string, record Record) (error) {
     file, err := os.Create(filename)
     if err != nil {
+        monolog.WriteError(err)
         return err
     }
     defer file.Close()

+ 3 - 2
src/woe/telnet/telnet.go

@@ -206,6 +206,7 @@ func New() (telnet * Telnet) {
 
 // Starts compresssion
 func (me * Telnet) StartCompression() {
+    // XXX implement compression.
     // var zwbuf  bytes.Buffer
     // me.zwriter = zlib.NewWriter(&zwbuf);
 }
@@ -476,7 +477,7 @@ func (me * Telnet) maybeSendDataEventAndEmptyBuffer() {
 
 // Append a byte to the data buffer
 func (me * Telnet) appendByte(bin byte) {
-    monolog.Debug("Appending to telnet buffer: %d %d", len(me.buffer), cap(me.buffer))
+    monolog.Log("TELNET", "Appending to telnet buffer: %d %d", len(me.buffer), cap(me.buffer))
     me.buffer = append(me.buffer, bin)
 }
 
@@ -580,7 +581,7 @@ func (me * Telnet) sbdataiacStateProcessByte(bin byte) bool {
 
 // Process a single byte received from the client 
 func (me * Telnet) ProcessByte(bin byte) bool {
-    monolog.Debug("ProcessByte %d %d", bin, me.state)
+    monolog.Log("TELNET", "ProcessByte %d %d", bin, me.state)
     switch me.state {
     // regular data
         case data_state:

+ 41 - 8
src/woe/woe.go

@@ -8,29 +8,51 @@ import "os/exec"
 import "flag"
 import "fmt"
 
+
+type serverLogLevels []string;
+
+var server_loglevels serverLogLevels = serverLogLevels { 
+    "FATAL", "ERROR", "WARNING", "INFO",
+}
+
+
+
 /* Command line flags. */
 var server_mode  = flag.Bool("s", false, "Run in server mode");
 var server_tcpip = flag.String("l", ":7000", "TCP/IP Address where the server will listen");
+var enable_logs  = flag.String("el", "FATAL,ERROR,WARNING,INFO", "Log levels to enable");
+var disable_logs = flag.String("dl", "", "Log levels to disable");
+
+
+func enableDisableLogs() {
+    monolog.EnableLevels(*enable_logs)
+    monolog.EnableLevels(*disable_logs)
+}
  
 /* Need to restart the server or not? */
 var server_restart = true
 
 func runServer() (status int) {
-    monolog.Setup("woe.log", true, false)
+    monolog.Setup("woe.log", true, false)    
     defer monolog.Close()
-    monolog.Info("Starting WOE!")
-    monolog.Info("Server runs at %s!", *server_tcpip)
+    enableDisableLogs()
+    monolog.Info("Starting WOE server...")
+    monolog.Info("Server will run at %s.", *server_tcpip)
     woe, err := server.NewServer(*server_tcpip)
     if err != nil {
+        monolog.Error("Could not initialize server!")
         monolog.Error(err.Error())
         panic(err)
     }
+    monolog.Info("Server at %s init ok.", *server_tcpip)
     defer woe.Close()
     status, err = woe.Serve()
     if err != nil {
+        monolog.Error("Error while running WOE server!")
         monolog.Error(err.Error())
         panic(err)
     }
+    monolog.Info("Server shut down without error indication.", *server_tcpip)
     return status
 }
 
@@ -39,18 +61,21 @@ func runServer() (status int) {
 func runSupervisor() (status int) {
     monolog.Setup("woe.log", true, false)
     defer monolog.Close()
-    monolog.Info("Starting WOE supervisor!")
+    enableDisableLogs()
+    monolog.Info("Starting WOE supervisor.")
     for (server_restart) {
         // wd  , _ := os.Getwd()
         exe  := fmt.Sprintf("%s", os.Args[0]) 
         argp := fmt.Sprintf("-l=%s", *server_tcpip)
-        cmd  := exec.Command(exe, "-s=true", argp)
-        monolog.Info("Starting server %s at %s!", exe, *server_tcpip)
+        argel:= fmt.Sprintf("-el=%s", *enable_logs)
+        argdl:= fmt.Sprintf("-dl=%s", *disable_logs)
+        cmd  := exec.Command(exe, "-s=true", argp, argel, argdl)
+        monolog.Info("Starting server %s at %s.", exe, *server_tcpip)
         cmd.Stderr = os.Stderr
         cmd.Stdout = os.Stdout
+        monolog.Debug("Server command line: %s.", cmd.Args)
         err  := cmd.Run()
-        monolog.Info("Server at %s shut down!", *server_tcpip)
-        // monolog.Info("Server output: %s!", out);
+        monolog.Info("Server at %s shut down.", *server_tcpip)
         if (err != nil ) { 
             monolog.Error("Server shut down with error %s!", err)
             server_restart = false;
@@ -68,6 +93,14 @@ func runSupervisor() (status int) {
  * be able to restart the server gracefully on recompile of the sources. 
  */
 func main() {
+    defer func () { 
+        pani := recover()
+        if (pani != nil) {
+            monolog.Fatal("Panic: %s", pani)
+            os.Exit(255)
+        }
+    } ()
+    
     flag.Parse()
     if *server_mode {
         os.Exit(runServer())

+ 56 - 42
src/woe/world/account.go

@@ -1,8 +1,6 @@
 package world
 
 import "path/filepath"
-import "os"
-import "encoding/xml"
 import "github.com/beoran/woe/sitef"
 import "github.com/beoran/woe/monolog"
 import "fmt"
@@ -58,20 +56,6 @@ func (me * Account) Challenge(challenge string) bool {
     return false
 }
 
-// Helpers for use with sitef records
-func SitefStoreString(rec sitef.Record, key string, val string) {
-    rec[key] = val
-}
-
-func SitefStoreInt(rec sitef.Record, key string, val int) {
-    rec[key] = fmt.Sprintf("%d", val)
-}
-
-
-func SitefStoreArray(rec sitef.Record, key string, val LabeledList) {
-
-}
-
 
 // Add a character to an account.
 func (me * Account) AddCharacter(chara * Character) {
@@ -83,7 +67,7 @@ func (me * Account) AddCharacter(chara * Character) {
 func (me * Account) Save(dirname string) (err error) {
     path := SavePathFor(dirname, "account", me.Name)
     
-    rec                := make(sitef.Record)
+    rec                := sitef.NewRecord()
     rec.Put("name",         me.Name)
     rec.Put("hash",         me.Hash)
     rec.Put("algo",         me.Algo)
@@ -93,11 +77,11 @@ func (me * Account) Save(dirname string) (err error) {
     rec.PutInt("characters",len(me.characters))
     for i, chara   := range me.characters {
         key        := fmt.Sprintf("characters[%d]", i)
-        rec.Put(key, chara.Name)
+        rec.Put(key, chara.ID)
         
     }
     monolog.Debug("Saving Acccount record: %s %v", path, rec)
-    return sitef.SaveRecord(path, rec)
+    return sitef.SaveRecord(path, *rec)
 }
 
 // Load an account from a sitef file.
@@ -126,9 +110,24 @@ func LoadAccount(dirname string, name string) (account *Account, err error) {
     account.Privilege       = Privilege(record.GetIntDefault("privilege", 
                                 int(PRIVILEGE_NORMAL)))
     
-    var nchars int
-    nchars                  = record.GetIntDefault("characters", 0)
-    account.characters      = make([] * Character, nchars)
+    nchars                 := record.GetIntDefault("characters", 0)
+    account.characters      = make([] * Character, 0, nchars)
+    monolog.Info("Try to load %d characters:\n", nchars)
+    for index := 0 ; index < nchars ; index ++ {
+
+        chid := record.GetArrayIndex("characters", index)
+        monolog.Info("Loading character: %d %s\n", index, chid)
+        
+        ch, err := account.LoadCharacter(dirname, chid);
+        if err != nil {
+            monolog.Error("Could not load character %s: %s", chid, err.Error())
+            // return nil, err
+        } else {
+            account.characters = append(account.characters, ch)
+        } 
+    }
+    
+    
     /* Todo: load characters here... */    
     monolog.Info("Loaded Account: %s %v", path, account)
     return account, nil
@@ -139,32 +138,47 @@ func (me * Account) NumCharacters() int {
     return len(me.characters)
 } 
 
-func (me * Account) SaveXML(dirname string) (err error) {
-    path := SavePathForXML(dirname, "account", me.Name)
-    
-    file, err := os.Create(path)
-    if err != nil {
-        return err
+func (me * Account) GetCharacter(index int) (* Character) {
+    return me.characters[index]
+} 
+
+func (me * Account) FindCharacter(character * Character) (index int) {
+    for k, c := range me.characters {
+        if c == character  {
+            return k
+        }
     }
-    enc := xml.NewEncoder(file)
-    enc.Indent(" ", "  ")
-    return enc.Encode(me)
-}
+    return -1;
+} 
 
-func LoadAccountXML(dirname string, name string) (account *Account, err error) {
-    path := SavePathForXML(dirname, "account", name)
+// Delete a character from this account.
+func (me * Account) DeleteCharacter(dirname string, character * Character) bool {
     
-    file, err := os.Open(path)
-    if err != nil {
-        return nil, err
+    if i:= me.FindCharacter(character) ; i < 0 {
+        monolog.Warning("Could not find character: %v %d", character, i)
+        return false;  
+    } else {
+        copy(me.characters[i:], me.characters[i+1:])
+        newlen := len(me.characters) - 1 
+        me.characters[newlen] = nil
+        me.characters = me.characters[:newlen]
     }
-    dec := xml.NewDecoder(file)    
-    account = new(Account)
-    err = dec.Decode(account)
-    return account, nil
-}
+    /// Save self so the deletion is correctly recorded.
+    me.Save(dirname)
+    
+    return character.Delete(dirname)
+} 
 
 
+func (me * Account) CharacterEntitylikeSlice() EntitylikeSlice {
+    els := make(EntitylikeSlice, 0, 16)
+    for i:= 0 ; i < me.NumCharacters(); i++ {
+        chara := me.GetCharacter(i)
+        els = append(els, chara)
+    }
+    return els
+}
+
 
 
 

+ 212 - 38
src/woe/world/being.go

@@ -37,8 +37,8 @@ type Kin struct {
     Mechanical  float64
     // Level of Corruption
     Corruption  float64
-    // If players can choose this or not
-    Playable    bool
+    // Level of "unliving", i,e, previously alive, matter in the being 
+    Unlife      float64 
 }
 
 
@@ -50,6 +50,23 @@ func NewKin(id string, name string) * Kin {
 }
 
 
+/* Kin modifier display as a string */
+func (me Kin) ToKinModifiers() string {
+    return fmt.Sprintf("ARTS : %4.2f TECHS: %4.2f LEARN: %4.2f\n" + 
+        "MECHA: %4.2f CORRU: %4.2f UNLIF: %4.2f", 
+        me.Arts, me.Techniques, me.Learning, me.Mechanical,
+        me.Corruption, me.Unlife)
+}
+
+/* Help for a kin */
+func (me Kin) AskLong() string {
+    return me.Long + "\n\nTalent modifiers for " + me.Name + 
+    " kin:\n" + me.ToTalents() + "\n\nKin modifiers for " + me.Name + 
+    " kin:\n" + me.ToKinModifiers() + "\n"
+}
+
+
+
 /* Job of a being */
 type Job struct {
     Entity
@@ -67,6 +84,12 @@ type Job struct {
     Playable    bool
 }
 
+/* Help for a job */
+func (me Job) AskLong() string {
+    return me.Long + "\n\nTalent modifiers for " + me.Name + 
+    " job:\n" + me.ToTalents() + "\n"
+}
+
 
 /* Gender of a being */
 type Gender struct {
@@ -77,6 +100,13 @@ type Gender struct {
     Vitals
 }
 
+/* Help for a gender */
+func (me Gender) AskLong() string {
+    return me.Long + "\n\nGender modifiers for " + me.Name + 
+    " gender:\n" + me.ToTalents() + "\n"
+}
+
+
 /* Struct, list and map of all genders in WOE. */
 var Genders = struct {
     Female          Gender
@@ -118,10 +148,24 @@ var Genders = struct {
     
 }
 
+func EntitylikeToGender(me Entitylike) (*Gender) {
+    v, ok := me.(*Gender)
+    if ok {
+        return v
+    } else {
+        return nil
+    }
+}
+
+
 
 type EntitylikeSlice []Entitylike
 
-var GenderList  = EntitylikeSlice{&Genders.Female, &Genders.Male, 
+var GenderList  = []Gender{Genders.Female, Genders.Male, 
+    Genders.Intersex, Genders.Genderless }
+
+
+var GenderEntityList  = EntitylikeSlice{&Genders.Female, &Genders.Male, 
     &Genders.Intersex, &Genders.Genderless }
 
 var GenderMap = map[string]*Gender {
@@ -167,6 +211,22 @@ func (me EntitylikeSlice) FindName(name string) (Entitylike) {
     })
 }
 
+/* Finds the ID  */
+func (me EntitylikeSlice) FindID(id string) (Entitylike) {
+    if id == "" {
+        return nil
+    }
+    
+    return me.Each(func (e Entitylike) (Entitylike) {
+        if strings.ToLower(e.AsEntity().ID) == id {
+            return e
+        } else {
+            return nil
+        }
+    })
+}
+
+
 /* Filters the list by privilege level (only those allowed by the level are retained) */
 func (me EntitylikeSlice) FilterPrivilege(privilege Privilege) (EntitylikeSlice) {
     return me.Filter(func (e Entitylike) (Entitylike) {
@@ -178,15 +238,6 @@ func (me EntitylikeSlice) FilterPrivilege(privilege Privilege) (EntitylikeSlice)
     })
 }
 
-func EntitylikeToGender(me Entitylike) (*Gender) {
-    v, ok := me.(*Gender)
-    if ok {
-        return v
-    } else {
-        return nil
-    }
-}
-
 /* All Kins of  WOE */
 var KinList = [] Kin {
     Kin {
@@ -201,6 +252,7 @@ They excel at nothing in particular, but are fast learners.`,
         // No stats either because no bonuses there either.
         Arts : 1.0,
         Techniques : 1.0,
+        Learning: 1.5,
     },
     {
         Entity: Entity {
@@ -209,7 +261,7 @@ They excel at nothing in particular, but are fast learners.`,
             Long: 
 `Neosa are descendents of humans genetically modified to be lite, agile 
 and skilled with the Numen Arts. They are less tough and strong, and less adept
-with techniques. They can be recognized by their extremely long and pointy ears.`,
+with techniques. They can be recognized by their long and pointy ears.`,
         },
         
         // AGI+1 EMO+1 STR-1 TOU-1 
@@ -217,6 +269,7 @@ with techniques. They can be recognized by their extremely long and pointy ears.
             Agility : 1, Emotion : 1, },
         Arts        : 1.2,
         Techniques  : 0.8,
+        Learning    : 1.0,
     },
     {
         Entity: Entity {
@@ -233,6 +286,7 @@ Numen Arts than humans. They have a soft fur which covers their whole body.`,
         
         Arts : 0.8,
         Techniques : 1.2,
+        Learning    : 1.0,
     },
     {
         Entity: Entity {
@@ -251,6 +305,7 @@ is not as effective on them, but they can be repaired.`,
         Arts : 0.5,
         Techniques : 1.5,
         Mechanical : 0.5,
+        Learning   : 1.1,
     },
     {
         Entity: Entity {
@@ -259,7 +314,7 @@ is not as effective on them, but they can be repaired.`,
             Long: 
 `Androids are conscious human shaped robots with the imperative to serve humans.  
 Highly effective with Techniques, and can install many Parts, but cannot use 
-any Nummen arts. Since thay are not alive, they technically cannot die.`,
+any Numen arts. Since thay are not alive, they technically cannot die.`,
         },
         // STR+1 1 TOU+1 DEX+1 INT+1 
         Talents : Talents {Strength : +2, Toughness: +2, 
@@ -267,6 +322,7 @@ any Nummen arts. Since thay are not alive, they technically cannot die.`,
         Arts : 0.0,
         Techniques : 2.0,
         Mechanical : 1.0,
+        Learning    : 1.0,
     },
 
     {
@@ -276,7 +332,7 @@ any Nummen arts. Since thay are not alive, they technically cannot die.`,
             Long: 
 `Mavericks are androids in which the imperative to serve humans has 
 been destroyed or disabled.  Highly effective with Techniques, and can install 
-many Parts, but cannot use any Nummen arts. Since thay are not alive, they 
+many Parts, but cannot use any Numen arts. Since thay are not alive, they 
 technically cannot die.  They are feared by Humans and hated by Androids.`,
             Privilege: PRIVILEGE_IMPLEMENTOR,
         },
@@ -286,6 +342,7 @@ technically cannot die.  They are feared by Humans and hated by Androids.`,
         Arts : 0.0,
         Techniques : 2.0,
         Mechanical : 1.0,
+        Learning   : 1.0,
     },
 
     {
@@ -305,6 +362,7 @@ the Earth millennia after.`,
         Arts : 0.0,
         Techniques : 2.0,
         Mechanical: 1.0,
+        Learning   : 1.0,
     },
 
     {
@@ -322,6 +380,7 @@ They might be less though than normal robots, but they move extremely quickly.
         Arts : 0.0,
         Techniques : 2.0,
         Mechanical : 1.0,
+        Learning    : 1.0,
     },
 
     {
@@ -342,6 +401,7 @@ No wonder they are still active after all these years.
         Arts : 0.0,
         Techniques : 2.0,
         Mechanical : 1.0,
+        Learning    : 1.0,
     },
     
 
@@ -359,6 +419,7 @@ and aggressive, to protect themselves and their offspring from Humans.`,
             Agility : +1, Intelligence: -5, },
         Arts : 1.0,
         Techniques : 1.0,
+        Learning    : 1.0,
     },
     
     {
@@ -374,6 +435,7 @@ They might be less resillient, but all the more agile.`,
             Agility : +3, Intelligence: -5, },
         Arts : 1.0,
         Techniques : 1.0,
+        Learning    : 1.0,
     },
 
     {
@@ -390,6 +452,7 @@ Fish dart through the water, attacking with their razor sharp teeth.
             Agility : +2, Intelligence: -5, },
         Arts : 1.0,
         Techniques : 1.0,
+        Learning    : 1.0,
     },
 
     {
@@ -405,6 +468,7 @@ but also purse you on land.`,
             Agility : +1, Dexterity: +1, Intelligence: -5, },
         Arts : 1.0,
         Techniques : 1.0,
+        Learning    : 1.0,
     },
 
     {
@@ -422,6 +486,7 @@ they remain dangerous.
             Agility: -1, Intelligence: -5, },
         Arts : 1.0,
         Techniques : 1.0,
+        Learning    : 1.0,
     },
  
     {
@@ -439,6 +504,7 @@ and as well on the land.`,
                             Intelligence: -5,  },
         Arts : 1.0,
         Techniques : 1.0,
+        Learning    : 1.0,
     },
     
     {
@@ -455,6 +521,7 @@ and as a result, larger creepy crawlers became more successful.
         Talents : Talents {Strength : +2, Toughness: +4,  Intelligence: -5, },
         Arts : 1.0,
         Techniques : 1.0,
+        Learning   : 1.0,
     },
 
     {
@@ -464,10 +531,12 @@ and as a result, larger creepy crawlers became more successful.
             Long: 
 `Whether in the rivers or the deep seas, these soft bodies creatures 
 are often toxic and quite dangerous.`,
+            Privilege: PRIVILEGE_IMPLEMENTOR,
         },
         Talents : Talents {Strength : +2, Dexterity: +2, Intelligence: -5, },
         Arts : 1.0,
         Techniques : 1.0,
+        Learning   : 1.0,
     },
 
     {
@@ -484,6 +553,8 @@ Beware, their attacks might be contageous...`,
             Agility : +1, Intelligence: -3, Wisdom: -5 },
         Arts : 1.0,
         Techniques : 1.0,
+        Corruption: 1.0,
+        Learning  : 1.0,
     },
 
     {
@@ -503,6 +574,9 @@ attacks might be contageous...
             Agility : +1, Intelligence: -1, Wisdom: -7 },
         Arts : 1.0,
         Techniques : 1.0,
+        Learning    : 1.0,
+        Corruption: 1.0,
+        Unlife: 1.0,
     },
 }
 
@@ -822,36 +896,36 @@ var BasicTalent Talents = Talents {
 }
 
 // Derived stats 
-func (me *Being) Force() int {
+func (me *Talents) Force() int {
     return (me.Strength * 2 + me.Wisdom) / 3
 }
     
-func (me *Being) Vitality() int {
+func (me *Talents) Vitality() int {
     return (me.Toughness * 2 + me.Charisma) / 3
 }
 
-func (me *Being) Quickness() int {
+func (me * Talents) Quickness() int {
     return (me.Agility * 2 + me.Intelligence) / 3
 }
 
-func (me * Being) Knack() int {
+func (me * Talents) Knack() int {
     return (me.Dexterity * 2 + me.Emotion) / 3
 }
     
     
-func (me * Being) Understanding() int {
+func (me * Talents) Understanding() int {
     return (me.Intelligence * 2 + me.Toughness) / 3
 }
 
-func (me * Being) Grace() int {
+func (me * Talents) Grace() int {
     return (me.Charisma * 2 + me.Agility) / 3
 }
     
-func (me * Being) Zeal() int {
+func (me * Talents) Zeal() int {
     return (me.Wisdom * 2 + me.Strength) / 3
 }
 
-func (me * Being) Numen() int {
+func (me * Talents) Numen() int {
       return (me.Emotion * 2 + me.Dexterity) / 3
 }
 
@@ -928,32 +1002,78 @@ func (me * Being) ToPrompt() string {
     }
 }
 
+func (me * Being) GenderName() string {
+    if ( me.Gender == nil ) { 
+        return "????" 
+    } 
+    return me.Gender.Name
+}
+
+func (me * Being) KinName() string {
+    if ( me.Kin == nil ) { 
+        return "????" 
+    } 
+    return me.Kin.Name
+}
+
+func (me * Being) JobName() string {
+    if ( me.Job == nil ) { 
+        return "????" 
+    } 
+    return me.Job.Name
+}
+
+
 
 // Generates an overview of the essentials of the being as a string.
 func (me * Being) ToEssentials() string {
-    return fmt.Sprintf("%s lvl %d %s %s %s", me.Name, me.Level, me.Gender.Name, me.Kin.Name, me.Job.Name)
+    return fmt.Sprintf("%s lvl %d %s %s %s", me.Name, me.Level, me.GenderName(), me.KinName(), me.JobName())
 }
 
 // Generates an overview of the physical talents of the being as a string.
-func (me * Being) ToBodyTalents() string {
+func (me * Talents) ToBodyTalents() string {
     return fmt.Sprintf("STR: %3d    TOU: %3d    AGI: %3d    DEX: %3d", me.Strength, me.Toughness, me.Agility, me.Dexterity)
 }
 
 // Generates an overview of the mental talents of the being as a string.
-func (me * Being) ToMindTalents() string {
+func (me * Talents) ToMindTalents() string {
     return fmt.Sprintf("INT: %3d    WIS: %3d    CHA: %3d    EMO: %3d", me.Intelligence, me.Wisdom, me.Charisma, me.Emotion)
 }
 
+// Generates an overview of the physical derived talents of the being as a string.
+func (me * Talents) ToBodyDerived() string {
+    return fmt.Sprintf("FOR: %3d    VIT: %3d    QUI: %3d    KNA: %3d", me.Force(), me.Vitality(), me.Quickness(), me.Knack())
+}
+
+// Generates an overview of the mental derived talents of the being as a string.
+func (me * Talents) ToMindDerived() string {
+    return fmt.Sprintf("UND: %3d    GRA: %3d    ZEA: %3d    NUM: %3d", me.Understanding(), me.Grace(), me.Zeal(), me.Numen())
+}
+
+// Generates an overview of the derived talents of the being as a string.
+func (me * Talents) ToDerived() string {
+    status := me.ToBodyDerived();
+    status += "\n" + me.ToMindDerived();
+    return status
+}
+
+// Generates an overview of all talents as a string.
+func (me * Talents) ToTalents() string {
+    status := me.ToBodyTalents();
+    status += "\n" + me.ToMindTalents();
+    return status
+}
+
 // Generates an overview of the equipment values of the being as a string.
-func (me * Being) ToEquipmentValues() string {
+func (me * EquipmentValues) ToEquipmentValues() string {
     return fmt.Sprintf("OFF: %3d    PRO: %3d    BLO: %3d    RAP: %3d    YIE: %3d", me.Offense, me.Protection, me.Block, me.Rapidity, me.Yield)
 }
 
 // Generates an overview of the status of the being as a string.
 func (me * Being) ToStatus() string {
     status := me.ToEssentials()
-    status += "\n" + me.ToBodyTalents();
-    status += "\n" + me.ToMindTalents();
+    status += "\n" + me.ToTalents();
+    status += "\n" + me.ToDerived();
     status += "\n" + me.ToEquipmentValues();
     status += "\n" + me.ToPrompt();
     status += "\n"
@@ -1027,7 +1147,7 @@ func (me * Being) Init(kind string, name string, privilege Privilege,
     
     me.Kin         = realkin
     me.Gender      = realgen
-    me.Job         = realjob 
+    me.Job         = realjob
        
     me.Talents.GrowFrom(BasicTalent)    
     me.Talents.GrowFrom(me.Kin.Talents)
@@ -1063,31 +1183,39 @@ func LoadBeing(datadir string, nameid string) * Being {
     return res
 }
 
-func (me * Talents) SaveSitef(rec sitef.Record) (err error) {
+func (me * Talents) SaveSitef(rec * sitef.Record) (err error) {
     rec.PutStruct("", *me)
     return nil
 }
 
-func (me * Vitals) SaveSitef(rec sitef.Record) (err error) {
+func (me * Vitals) SaveSitef(rec * sitef.Record) (err error) {
     rec.PutStruct("", *me)
     return nil
 }
 
-func (me * EquipmentValues) SaveSitef(rec sitef.Record) (err error) {
+func (me * EquipmentValues) SaveSitef(rec * sitef.Record) (err error) {
+    rec.PutStruct("", *me)
     return nil
 }
 
-func (me * Aptitudes) SaveSitef(rec sitef.Record) (err error) {
+func (me * Aptitudes) SaveSitef(rec * sitef.Record) (err error) {
+    nskills := len(me.Skills)
+    rec.PutInt("skills", nskills)
+    for i := 0 ; i < nskills; i ++ {
+        rec.PutArrayIndex("skills", i, me.Skills[i].skill.ID)
+    }
+ 
     return nil
 }
 
-func (me * Inventory) SaveSitef(rec sitef.Record) (err error) {
+func (me * Inventory) SaveSitef(rec * sitef.Record) (err error) {
+ 
     return nil
 }
 
 
 // Save a being to a sitef record.
-func (me * Being) SaveSitef(rec sitef.Record) (err error) {
+func (me * Being) SaveSitef(rec * sitef.Record) (err error) {
     me.Entity.SaveSitef(rec)
     rec.PutInt("level", me.Level)
 
@@ -1110,17 +1238,63 @@ func (me * Being) SaveSitef(rec sitef.Record) (err error) {
     me.Inventory.SaveSitef(rec)
 
     if me.Room != nil {
-        rec.Put("kin", me.Room.ID)
+        rec.Put("room", me.Room.ID)
     }
     
     // TODO: saving: me.Being.SaveSitef(rec)
     return nil
 }
 
+func (me * Talents) LoadSitef(rec sitef.Record) (err error) {
+    rec.GetStruct("", me)
+    return nil
+}
+
+func (me * Vitals) LoadSitef(rec sitef.Record) (err error) {
+    rec.GetStruct("", me)
+    return nil
+}
+
+func (me * EquipmentValues) LoadSitef(rec sitef.Record) (err error) {
+    rec.GetStruct("", me)
+    return nil
+}
+
+func (me * Aptitudes) LoadSitef(rec sitef.Record) (err error) {
+//    rec.GetStruct("", *me)
+    return nil
+}
+
+func (me * Inventory) LoadSitef(rec sitef.Record) (err error) {
+//    rec.GetStruct("", *me)
+    return nil
+}
+
+
 // Load a being from a sitef record.
 func (me * Being) LoadSitef(rec sitef.Record) (err error) {
     me.Entity.LoadSitef(rec) 
-    // TODO: load being. me.Being.SaveSitef(rec)
+
+    me.Level   = rec.GetIntDefault("level", 1)
+    
+    me.Gender  = EntitylikeToGender(GenderEntityList.FindID(rec.Get("gender")))
+    me.Job     = EntitylikeToJob(JobEntityList.FindID(rec.Get("job")))
+    me.Kin     = EntitylikeToKin(KinEntityList.FindID(rec.Get("kin")))
+    
+    me.Talents.LoadSitef(rec)
+    me.Vitals.LoadSitef(rec)
+    me.EquipmentValues.LoadSitef(rec)
+    me.Aptitudes.LoadSitef(rec)
+    me.Inventory.LoadSitef(rec)
+    
+    if (rec.Get("room") != "") {
+        var err error
+        me.Room, err = DefaultWorld.LoadRoom(rec.Get("room"))
+        if err != nil {
+            monolog.WriteError(err)
+            return err
+        }
+    }
     return nil
 }
 

+ 64 - 28
src/woe/world/character.go

@@ -1,6 +1,7 @@
 package world
 
 import "fmt"
+import "os"
 // import "strconv"
 import "github.com/beoran/woe/monolog"
 import "github.com/beoran/woe/sitef"
@@ -10,7 +11,7 @@ import "github.com/beoran/woe/sitef"
 
 type Character struct {
     Being       
-    * Account
+    Account * Account
 }
 
 
@@ -31,68 +32,103 @@ func NewCharacter(account * Account, name string,
     return me.Init(account, name, kin, gender, job)
 }
 
-// Save a character as a sitef record.
-func (me * Character) SaveSitef(rec sitef.Record) (err error) {
-    rec["accountname"]  = me.Account.Name
+// Save a character into a a sitef record.
+func (me * Character) SaveSirec(rec * sitef.Record) (err error) {
+    rec.Put("accountname", me.Account.Name)
     me.Being.SaveSitef(rec)
-    
     return nil
 }
 
 // Load a character from a sitef record.
-func (me * Character) LoadSitef(rec sitef.Record) (err error) {
-    aname := rec["accountname"]
+func (me * Character) LoadSirec(rec sitef.Record) (err error) {
+    aname := rec.Get("accountname")
     account, err := DefaultWorld.LoadAccount(aname)
     if err != nil {
         return err
     } 
     me.Account = account
     me.Being.LoadSitef(rec)
-    // TODO: load being. me.Being.SaveSitef(rec)
     return nil
 }
 
 
 // Save a character as a sitef file.
 func (me * Character) Save(dirname string) (err error) {
-    path := SavePathFor(dirname, "character", me.Name)
+    path := SavePathFor(dirname, "character", me.ID)
     
-    rec                := make(sitef.Record)
-    me.SaveSitef(rec)
+    rec                := sitef.NewRecord()
+    me.SaveSirec(rec)
     monolog.Debug("Saving Character record: %s %v", path, rec)
-    return sitef.SaveRecord(path, rec)
+    return sitef.SaveRecord(path, *rec)
 }
 
-// Load an character from a sitef file.
-func LoadCharacter(dirname string, name string) (character *Character, err error) {
+
+// Load a character from a sitef file. Does no account checking, but returns the account name.
+func LoadCharacter(dirname string, id string) (character *Character, aname string, err error) {
     
-    path := SavePathFor(dirname, "character", name)
+    path := SavePathFor(dirname, "character", id)
     
     records, err := sitef.ParseFilename(path)
     if err != nil {
-        return nil, err
+        return nil, "", err
     }
     
     if len(records) < 1 {
-        return nil, fmt.Errorf("No sitef record found for %s!", name)
+        return nil, "",  fmt.Errorf("No sitef record found for %s!", id)
     }
     
     record := records[0]
-    monolog.Info("Loading Account record: %s %v", path, record)
+    monolog.Info("Loading Character record: %s %v", path, record)
     
     character               = new(Character)
-    aname                  := record["AccountName"]
-    account, err           := DefaultWorld.LoadAccount(aname);
-    if err != nil  {
-        return nil, err
-    } 
+    aname                   = record.Get("accountname")
+    character.Being.LoadSitef(*record);
     
-    if account == nil {
-        return nil, fmt.Errorf("Cound not load account %s for character %s", 
-            aname, character.Name)
-    }
+    return character, aname, nil
+}
+
+// Load a character WITH A GIVEN NAME from a sitef file. Does no account checking, but returns the account name.
+func LoadCharacterByName(dirname string, name string) (character *Character, aname string, err error) {
+    id := EntityNameToID("character", name)
+    return LoadCharacter(dirname, id)
+}
+
+
+// Load an character from a sitef file for the given account.
+func (account * Account) LoadCharacter(dirname string, id string) (character *Character, err error) {
     
-    character.Account = account
+    character, aname, err := LoadCharacter(dirname, id)
+    if character == nil {
+        return character, err
+    }
     
+    if aname != account.Name  {
+        err := fmt.Errorf("Account doesn't match! %s %s", aname, account.Name)
+        monolog.Error("%s", err.Error())
+        return nil, err
+    }
+        
+    character.Account = account    
     return character, nil
 }
+
+
+
+// Deletes the character itself from disk
+func (me * Character) Delete(dirname string) bool {
+    path := SavePathFor(dirname, "character", me.ID)
+    
+    if err := os.Remove(path) ; err != nil {
+        monolog.Warning("Could not delete character: %v %s: %s", 
+            me, path, err.Error())
+        return false
+    }
+    
+    me.Account = nil
+    monolog.Info("Character deleted: %s", me.ID)    
+    return true
+} 
+
+func (me * Character) AskLong() string {
+    return me.Long + "\n" + me.ToStatus()
+}

+ 31 - 10
src/woe/world/entity.go

@@ -31,12 +31,17 @@ type Entity struct {
 }
 
 
+func EntityNameToID(kind string, name string) string {
+    return kind + "_" + strings.ToLower(name)
+}
+
+
 func (me * Entity) InitKind(kind string, name string, 
     privilege Privilege) (* Entity) {
     if me == nil {
         return me
     }
-    me.ID       = kind + "_" + strings.ToLower(name) 
+    me.ID       = EntityNameToID(kind, name) 
     me.Name     = name
     me.Short    = name
     me.Long     = name
@@ -236,24 +241,40 @@ func (me * LabeledList) Index(index int) Labeled {
 
 
 // Save an entity to a sitef record.
-func (me * Entity) SaveSitef(rec sitef.Record) (err error) {
-    rec["id"]    = string(me.ID)
-    rec["name"]  = me.Name
-    rec["short"] = me.Short
-    rec["long"]  = me.Long
+func (me * Entity) SaveSitef(rec * sitef.Record) (err error) {
+    rec.Put("id", me.ID)
+    rec.Put("name", me.Name)
+    rec.Put("short", me.Short)
+    rec.Put("long",  me.Long)
     return nil
 }
 
 // Load an entity from a sitef record.
 func (me * Entity) LoadSitef(rec sitef.Record) (err error) {
-    me.ID       = rec["id"]   
-    me.Name     = rec["name"]
-    me.Short    = rec["short"]
-    me.Long     = rec["long"]
+    me.ID       = rec.Get("id")
+    me.Name     = rec.Get("name")
+    me.Short    = rec.Get("short")
+    me.Long     = rec.Get("long")
     return nil
 }
 
 
+func (me Entity) AskName() string {
+    return me.Name
+}
+
+func (me Entity) AskShort() string {
+    return me.Short
+}
+
+func (me Entity) AskLong() string {
+    return me.Long
+}
+
+func (me Entity) AskPrivilege() Privilege {
+    return me.Privilege
+}
+
 
 
 

+ 23 - 16
src/woe/world/item.go

@@ -180,7 +180,7 @@ type Item struct {
     Kind          ItemKind
     Damage        DamageKind
     // Equipment location,  "none" if not equippable
-    Equippable    EquipWhere
+    Equip         EquipWhere
     // Level of crafing skill needed to craft this, or of harvesting skill 
     // to harvest this, or of mining skill to mine this. Negative if cannot 
     // be crafted nor harvested, nor mined.    
@@ -193,10 +193,12 @@ type Item struct {
     // ID of item this item can degrade into. empty or "none" if cannot be 
     // degraded.
     Degrade       string
-    // ID of technique/art/item to craft this item teaches when used, empty or 
+    // ID of technique/art/exploit this item teaches when used, empty or 
     // none if it teaches nothing. If it's a skill, the XP of teaching is 
     // determined by the Quality of the item.   
     Teaches       string
+     // ID of skill needed to craft this item   
+    Craft         string
 }
 
 // Load an item from a sitef file.
@@ -216,21 +218,26 @@ func LoadItem(dirname string, id string) (item *Item, err error) {
     record := records[0]
     monolog.Info("Loading Item record: %s %v", path, record)
     
-    item = new(Item)
-    item.Entity.LoadSitef(record)
-    /*
-    account.Name            = record.Get("name")
-    account.Hash            = record.Get("hash")
-    account.Algo            = record.Get("algo")
-    account.Email           = record.Get("email")
-    account.Points          = record.GetIntDefault("points", 0)
-    account.Privilege       = Privilege(record.GetIntDefault("privilege", 
-                                int(PRIVILEGE_NORMAL)))
+    item            = new(Item)
+    item.Entity.LoadSitef(*record)
+    item.Quality    = record.GetIntDefault("quality", 0)
+    item.Price      = record.GetIntDefault("price", -1)
+    item.Level      = record.GetIntDefault("level", -1)
+    item.Kind       = ItemKind(record.Get("kind"))
+    item.Damage     = DamageKind(record.Get("damage"))
+    item.Equip      = EquipWhere(record.Get("equip"))
+    item.Upgrade    = record.Get("upgrade")
+    item.Degrade    = record.Get("degrade")
+    item.Teaches    = record.Get("teaches")
+    item.Craft      = record.Get("craft")
+    
+    ningredients   := record.GetIntDefault("ingredients", 0)
+    
+    for i := 0; i < ningredients; i++ {
+        ingr := record.GetArrayIndex("ingredients", i)
+        item.Ingredients = append(item.Ingredients, ingr) 
+    }
     
-    var nchars int
-    nchars                  = record.GetIntDefault("characters", 0)
-    _ = nchars    
-    */
     monolog.Info("Loaded Item: %s %v", path, item)
     return item, nil
 }

+ 41 - 0
src/woe/world/room.go

@@ -1,5 +1,11 @@
 package world
 
+import "github.com/beoran/woe/sitef"
+import "github.com/beoran/woe/monolog"
+// import "fmt"
+import "errors"
+
+
 
 type Direction  string
 
@@ -14,3 +20,38 @@ type Room struct {
     Exits   map[Direction]Exit
 }
 
+// Load a room from a sitef file.
+func LoadRoom(dirname string, id string) (room * Room, err error) {
+    
+    path := SavePathFor(dirname, "room", id)
+    
+    records, err := sitef.ParseFilename(path)
+    if err != nil {
+        return nil, err
+    }
+    
+    if len(records) < 1 {
+        return nil, errors.New("No room found!")
+    }
+    
+    record := records[0]
+    monolog.Info("Loading Room record: %s %v", path, record)
+    
+    room = new(Room)
+    room.Entity.LoadSitef(*record)
+    /*
+    account.Name            = record.Get("name")
+    account.Hash            = record.Get("hash")
+    account.Algo            = record.Get("algo")
+    account.Email           = record.Get("email")
+    account.Points          = record.GetIntDefault("points", 0)
+    account.Privilege       = Privilege(record.GetIntDefault("privilege", 
+                                int(PRIVILEGE_NORMAL)))
+    
+    var nchars int
+    nchars                  = record.GetIntDefault("characters", 0)
+    _ = nchars    
+    */
+    monolog.Info("Loaded Room: %s %v", path, room)
+    return room, nil
+}

+ 42 - 2
src/woe/world/world.go

@@ -79,11 +79,11 @@ func (me * World) AddZone(zone * Zone) {
 func (me * World) Save(dirname string) (err error) {
     path := SavePathFor(dirname, "world", me.Name)
     
-    rec                := make(sitef.Record)
+    rec                  := sitef.NewRecord()
     rec.Put("name",         me.Name)
     rec.Put("motd",         me.MOTD)
     monolog.Debug("Saving World record: %s %v", path, rec)
-    return sitef.SaveRecord(path, rec)
+    return sitef.SaveRecord(path, *rec)
 }
 
 // Load a world from a sitef file.
@@ -182,3 +182,43 @@ func (me * World) RemoveItem(id string) {
     delete(me.itemmap, id)
 }
 
+
+// Returns a Room that has already been loaded or nil if not found
+func (me * World) GetRoom(id string) (room * Room) {
+    room, ok := me.roommap[id]
+    if !ok {
+        return nil
+    }
+    return room
+} 
+
+
+
+// Loads a Room to be used with this world. 
+// If the room was already loaded, returns that in stead.
+func (me * World) LoadRoom(id string) (room *Room, err error) {
+    room = me.GetRoom(id)
+    
+    if (room != nil) {
+        return room, nil
+    }
+    
+    room, err = LoadRoom(me.dirname, id);
+    if err != nil {
+        return room, err
+    }
+    me.roommap[room.ID] = room
+    return room, nil
+}
+
+// Removes an item from this world by ID.
+func (me * World) RemoveRoom(id string) {
+    _, have := me.roommap[id]
+    if (!have) {
+        return
+    }    
+    delete(me.roommap, id)
+}
+
+
+