Selaa lähdekoodia

Working on serialization for al kinds of data objects.

Beoran 9 vuotta sitten
vanhempi
commit
729a24a500

+ 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"
     "time"
     "runtime"
     "runtime"
     "path/filepath"
     "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{}) {
 func (me * FileLogger) WriteLog(depth int, level string, format string, args ... interface{}) {
     _ ,file, line, ok := runtime.Caller(depth)
     _ ,file, line, ok := runtime.Caller(depth)
     
     
@@ -52,7 +68,7 @@ func (me * FileLogger) Debug(format string, args ... interface{}) {
     me.WriteLog(2, "DEBUG", format, args...)
     me.WriteLog(2, "DEBUG", format, args...)
 }
 }
 
 
-  
+ 
 type FileLogger struct {
 type FileLogger struct {
     filename      string
     filename      string
     file        * os.File
     file        * os.File
@@ -97,34 +113,58 @@ func NewStdoutLogger() (logger Logger, err error) {
     return &FileLogger{"/dev/stderr", os.Stdout}, nil
     return &FileLogger{"/dev/stderr", os.Stdout}, nil
 }
 }
 
 
-type Log struct {
+type Logbook struct {
     loggers          [] Logger
     loggers          [] Logger
     levels  map[string] bool
     levels  map[string] bool
 }
 }
 
 
 
 
-func NewLog() * Log {
+func NewLog() * Logbook {
     loggers :=  make([] Logger, 32)
     loggers :=  make([] Logger, 32)
     levels  :=  make(map[string] bool)
     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)
     me.loggers = append(me.loggers, logger)
 }
 }
 
 
-func (me * Log) EnableLevel(level string) {
+func (me * Logbook) EnableLevel(level string) {
     me.levels[level] = true
     me.levels[level] = true
 }
 }
 
 
-func (me * Log) DisableLevel(level string) {
+func (me * Logbook) DisableLevel(level string) {
     me.levels[level] = false
     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
         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 {
     for index , logger := range me.loggers {
         if logger != nil {
         if logger != nil {
             logger.Close()
             logger.Close()
@@ -144,7 +184,7 @@ func (me * Log) Close() {
     }
     }
 }
 }
 
 
-var DefaultLog * Log
+var DefaultLog * Logbook
 
 
 func init() {
 func init() {
     DefaultLog = NewLog()
     DefaultLog = NewLog()
@@ -159,6 +199,15 @@ func DisableLevel(level string) {
     DefaultLog.DisableLevel(level)
     DefaultLog.DisableLevel(level)
 }
 }
 
 
+func EnableLevels(list string) {    
+  DefaultLog.EnableLevels(list)
+}
+
+func DisableLevels(list string) {    
+  DefaultLog.DisableLevels(list)
+}
+
+
 func AddLogger(logger Logger, err error) {
 func AddLogger(logger Logger, err error) {
     if err == nil {
     if err == nil {
         DefaultLog.AddLogger(logger)
         DefaultLog.AddLogger(logger)
@@ -199,7 +248,7 @@ func WriteLog(depth int, name string, format string, args ... interface{}) {
     DefaultLog.LogVa(name, file, line, format, args...)
     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)
     WriteLog(2, name, format, args)
 }
 }
 
 
@@ -223,5 +272,11 @@ func Debug(format string, args ...interface{}) {
     WriteLog(2, "DEBUG", format, args...)
     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 "bytes"
 import "errors"
 import "errors"
+import "regexp"
 // import "github.com/beoran/woe/telnet"
 // import "github.com/beoran/woe/telnet"
 import "github.com/beoran/woe/world"
 import "github.com/beoran/woe/world"
 import "github.com/beoran/woe/monolog"
 import "github.com/beoran/woe/monolog"
@@ -63,10 +64,19 @@ func doQuit(data * ActionData) (err error) {
     return nil
     return nil
 }
 }
 
 
+func doEnableLog(data * ActionData) (err error) {  
+    // strings. string(data.Rest)
+    return nil
+}
+
+
 func ParseCommand(command []byte, data * ActionData) (err error) {
 func ParseCommand(command []byte, data * ActionData) (err error) {
     /* strip any leading blanks  */
     /* strip any leading blanks  */
     trimmed    := bytes.TrimLeft(command, " \t")
     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 {
     if len(parts) < 1 {
         data.Command = nil
         data.Command = nil
@@ -75,7 +85,7 @@ func ParseCommand(command []byte, data * ActionData) (err error) {
     data.Command = parts[0]
     data.Command = parts[0]
     if len(parts) > 1 { 
     if len(parts) > 1 { 
         data.Rest    = parts[1]
         data.Rest    = parts[1]
-        data.Argv    = bytes.Split(data.Rest, []byte(" \t,"))
+        data.Argv    = parts
     } else {
     } else {
         data.Rest    = nil
         data.Rest    = nil
         data.Argv    = 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/world"
 import "github.com/beoran/woe/monolog"
 import "github.com/beoran/woe/monolog"
 import "bytes"
 import "bytes"
+import "strings"
 import "regexp"
 import "regexp"
 // import "fmt"
 // import "fmt"
 import "strconv"
 import "strconv"
@@ -86,7 +87,7 @@ func (me * Client) AskSomething(prompt string, re string, nomatch_prompt string,
     }
     }
 
 
     for something == nil || len(something) == 0 { 
     for something == nil || len(something) == 0 { 
-        me.Printf("%s:", prompt)
+        me.Printf("%s", prompt)
         something, _, _ = me.TryRead(-1)
         something, _, _ = me.TryRead(-1)
         if something != nil {
         if something != nil {
             something = bytes.TrimRight(something, "\r\n")
             something = bytes.TrimRight(something, "\r\n")
@@ -114,72 +115,417 @@ func (me * Client) AskYesNo(prompt string) bool {
         return true
         return true
     } else {
     } else {
         return false
         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)
     list := elist.FilterPrivilege(me.account.Privilege)
     me.Printf("\n%s\n\n",heading)
     me.Printf("\n%s\n\n",heading)
+    last := 0
     for i, v := range(list) {
     for i, v := range(list) {
         e := v.AsEntity()
         e := v.AsEntity()
         me.Printf("[%d] %s: %s\n", i+1, e.Name, e.Short)
         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")
     me.Printf("\n")
     aid := me.AskSomething(prompt, "", "", false);
     aid := me.AskSomething(prompt, "", "", false);
     iresp, err := strconv.Atoi(string(aid))
     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))
         e := list.FindName(string(aid))
         if e != nil {
         if e != nil {
-            return e
+            return e, ""
         } else {
         } 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")
             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 {
     } else {
         me.Printf("Please choose a number or name from the list above.\n")
         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 {
     for {
-        result = me.AskEntityListOnce(heading, prompt, noecho, list)
+        result, alternative = me.AskEntityListOnce(heading, prompt, noecho, list, extras)
         if result != nil {
         if result != nil {
             e := result.AsEntity()
             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 {
 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 = "@"
 const EMAIL_RE = "@"
 
 
 func (me * Client) AskEmail() []byte {
 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 {
 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 {
 func (me * Client) AskPassword() []byte {
-    return me.AskSomething("Password", "", "", true)
+    return me.AskSomething("Password?>", "", "", true)
 }
 }
 
 
 func (me * Client) AskRepeatPassword() []byte {
 func (me * Client) AskRepeatPassword() []byte {
-    return me.AskSomething("Repeat Password", "", "", true)
+    return me.AskSomething("Repeat Password?>", "", "", true)
 }
 }
 
 
 func (me * Client) HandleCommand() {
 func (me * Client) HandleCommand() {
@@ -264,20 +610,64 @@ func (me * Client) AccountDialog() bool {
 }
 }
 
 
 func (me * Client) NewCharacterDialog() bool {
 func (me * Client) NewCharacterDialog() bool {
+    noconfirm := true
+    extra := TrivialAskOptionList { TrivialAskOption("Cancel") }
+
     me.Printf("New character:\n")
     me.Printf("New character:\n")
     charname := me.AskCharacterName()
     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, 
     character := world.NewCharacter(me.account, 
-                    string(charname), kin, gender, job)
+                    string(charname), &kin, &gender, &job)
     
     
     me.Printf("%s", character.Being.ToStatus());
     me.Printf("%s", character.Being.ToStatus());
     
     
@@ -298,21 +688,124 @@ func (me * Client) NewCharacterDialog() bool {
     return true
     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 {
 func (me * Client) CharacterDialog() bool {
     me.Printf("You have %d remaining points.\n", me.account.Points)
     me.Printf("You have %d remaining points.\n", me.account.Points)
     for me.account.NumCharacters() < 1 {
     for me.account.NumCharacters() < 1 {
         me.Printf("You have no characters yet!\n")
         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 {
         } 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("Please contact the staff of WOE if you think this is a mistake.\n")
             me.Printf("Disconnecting!\n")
             me.Printf("Disconnecting!\n")
             return false 
             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 
     timechan chan time.Time 
     telnet * telnet.Telnet
     telnet * telnet.Telnet
     info     ClientInfo
     info     ClientInfo
+    
+    // Account of client or nil if not yet selected.
     account* world.Account
     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)
     timechan := make (chan time.Time, 32)
     telnet   := telnet.New()
     telnet   := telnet.New()
     info     := ClientInfo{w : -1, h : -1, terminal: "none"}
     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() {
 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 
 /* 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() {
  func (me * Client) ServeWrite() {
      for (me.alive) {
      for (me.alive) {
         select {
         select {
             case data := <- me.telnet.ToClient:
             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)
             me.conn.Write(data)
         }
         }
     }
     }
@@ -132,9 +136,10 @@ func (me * Client) TryRead(millis int) (data []byte, timeout bool, close bool) {
         }
         }
         switch event := event.(type) {
         switch event := event.(type) {
             case * telnet.DataEvent:
             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
                 return event.Data, false, false
             case * telnet.NAWSEvent:
             case * telnet.NAWSEvent:
+                monolog.Log("TELNETNAWSEVENT", "Telnet NAWS event %T.", event)
                 me.HandleNAWSEvent(event);
                 me.HandleNAWSEvent(event);
             default:
             default:
                 monolog.Info("Ignoring telnet event %T : %v for now.", event, event)
                 monolog.Info("Ignoring telnet event %T : %v for now.", event, event)

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

@@ -1,7 +1,6 @@
 package server
 package server
 
 
 import (
 import (
-    "log"
   //  "io"
   //  "io"
     "net"
     "net"
   //  "errors"
   //  "errors"
@@ -74,8 +73,6 @@ func init() {
 type Server struct {
 type Server struct {
     address               string
     address               string
     listener              net.Listener
     listener              net.Listener
-    logger              * log.Logger
-    logfile             * os.File
     clients map[int]    * Client 
     clients map[int]    * Client 
     tickers map[string] * Ticker
     tickers map[string] * Ticker
     alive                 bool
     alive                 bool
@@ -138,19 +135,22 @@ func NewServer(address string) (server * Server, err error) {
         return nil, err
         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)
     clients := make(map[int] * Client)
     tickers := make(map[string] * Ticker)
     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()
     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
     return server, err
 }
 }
 
 
@@ -280,8 +280,7 @@ func (me * Server) Close() {
     }
     }
     
     
     me.handleDisconnectedClients()
     me.handleDisconnectedClients()
-    monolog.Info("Closing server, closing logfile.")
-    me.logfile.Close();
+    monolog.Info("Closed server.")
 }
 }
 
 
 func (me * Server) Serve() (status int, err error) { 
 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 {
 func (me * Server) DataPath() string {
     // 
     // 
     cwd, err := os.Getwd();
     cwd, err := os.Getwd();
+    monolog.Debug("Current direcory: %s (%v).", cwd, err)
+    
     if  err != nil {
     if  err != nil {
         cwd = "."
         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
 // 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
 // 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) {
 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{}) {
 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) {
 func (me * Record) PutArray(key string, values []string) {
     for i, value := range values {
     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) {
 func (me Record) MayGet(key string) (result string, ok bool) {
-    result, ok = me[key]
+    result, ok = me.dict[key]
     return result, ok
     return result, ok
 }
 }
 
 
 
 
 func (me Record) Get(key string) (result string) {
 func (me Record) Get(key string) (result string) {
-    result= me[key]
+    result= me.dict[key]
     return result
     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, 
 func (me Record) Getf(key string, format string, 
     values ...interface{}) (amount int, ok bool) {
     values ...interface{}) (amount int, ok bool) {
     val := me.Get(key)
     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()) {
     switch (value.Kind()) {
         case reflect.Int, reflect.Int32, reflect.Int64:
         case reflect.Int, reflect.Int32, reflect.Int64:
             me.Putf(key, "%d", value.Int())
             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())
             me.Putf(key, "%f", value.Float())
         case reflect.String:
         case reflect.String:
             me.Putf(key, "%s", value.String())
             me.Putf(key, "%s", value.String())
+        case reflect.Struct:
+            me.PutStruct(key + ".", value.Interface());
         default:
         default:
             me.Put(key, "???")
             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)
     st := reflect.TypeOf(structure)
     vt := reflect.ValueOf(structure)
     vt := reflect.ValueOf(structure)
-    monolog.Info("PutStruct: type %v value %v\n", st, vt)
     
     
     for i:= 0 ; i < st.NumField() ; i++ {
     for i:= 0 ; i < st.NumField() ; i++ {
         field := st.Field(i)
         field := st.Field(i)
         key := strings.ToLower(field.Name)
         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
     PARSER_STATE_VALUE
 )
 )
 
 
-type RecordList []Record
+type RecordList []*Record
 
 
 
 
 func ParseReader(read io.Reader) (RecordList, error) {
 func ParseReader(read io.Reader) (RecordList, error) {
     var records     RecordList
     var records     RecordList
-    var record      Record = make(Record)
+    record      := NewRecord()
     var err         Error
     var err         Error
     lineno      := 0
     lineno      := 0
     scanner     := bufio.NewScanner(read)
     scanner     := bufio.NewScanner(read)
@@ -197,13 +283,17 @@ func ParseReader(read io.Reader) (RecordList, error) {
         line := scanner.Text()
         line := scanner.Text()
         // End of record?
         // End of record?
         if (len(line) < 1) || line[0] == '-' {
         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
             // 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] == '#' {
         } else if line[0] == '#' {
             continue; 
             continue; 
-            // continue value?
+        // continue value?
         } else if line[0] == '\t' || line[0] == ' '|| line[0] == '+' {
         } else if line[0] == '\t' || line[0] == ' '|| line[0] == '+' {
             
             
             /* Add a newline unless + is used */
             /* Add a newline unless + is used */
@@ -213,11 +303,11 @@ func ParseReader(read io.Reader) (RecordList, error) {
             
             
             // continue the value, skipping the first character
             // continue the value, skipping the first character
             value.WriteString(line[1:])            
             value.WriteString(line[1:])            
-            // new key
+        // new key
         } else if strings.ContainsRune(line, ':') {
         } else if strings.ContainsRune(line, ':') {
             // save the previous key/value pair if needed
             // save the previous key/value pair if needed
             if len(key.String()) > 0 {
             if len(key.String()) > 0 {
-                record[key.String()] = value.String()
+                record.Put(key.String(), value.String())
             }
             }
             
             
             key.Reset()
             key.Reset()
@@ -237,10 +327,10 @@ func ParseReader(read io.Reader) (RecordList, error) {
     
     
     // Append last record if needed. 
     // Append last record if needed. 
     if len(key.String()) > 0 {
     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)
         records = append(records, record)
     }
     }
 
 
@@ -248,7 +338,8 @@ func ParseReader(read io.Reader) (RecordList, error) {
 
 
     if serr := scanner.Err(); serr != nil {
     if serr := scanner.Err(); serr != nil {
        err.lineno = lineno
        err.lineno = lineno
-       err.error  = serr.Error() 
+       err.error  = serr.Error()
+       monolog.Error("Sitef parse error: %d %s", lineno, serr.Error) 
        return records, err
        return records, err
     }
     }
     
     
@@ -266,6 +357,7 @@ func ParseFilename(filename string) (RecordList, error) {
 }
 }
 
 
 func WriteField(writer io.Writer, key string, value string) {
 func WriteField(writer io.Writer, key string, value string) {
+    monolog.Debug("WriteField %s:%s", key, value)
     replacer := strings.NewReplacer("\n", "\n\t")    
     replacer := strings.NewReplacer("\n", "\n\t")    
     writer.Write([]byte(key))
     writer.Write([]byte(key))
     writer.Write([]byte{':'})    
     writer.Write([]byte{':'})    
@@ -274,7 +366,11 @@ func WriteField(writer io.Writer, key string, value string) {
 }
 }
 
 
 func WriteRecord(writer io.Writer, record Record) {
 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);
         WriteField(writer, key, value);
     }
     }
     writer.Write([]byte{'-', '-', '-', '-', '\n'})
     writer.Write([]byte{'-', '-', '-', '-', '\n'})
@@ -282,7 +378,7 @@ func WriteRecord(writer io.Writer, record Record) {
 
 
 func WriteRecordList(writer io.Writer, records RecordList) {
 func WriteRecordList(writer io.Writer, records RecordList) {
     for _, record := range records {
     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) {
 func SaveRecord(filename string, record Record) (error) {
     file, err := os.Create(filename)
     file, err := os.Create(filename)
     if err != nil {
     if err != nil {
+        monolog.WriteError(err)
         return err
         return err
     }
     }
     defer file.Close()
     defer file.Close()

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

@@ -206,6 +206,7 @@ func New() (telnet * Telnet) {
 
 
 // Starts compresssion
 // Starts compresssion
 func (me * Telnet) StartCompression() {
 func (me * Telnet) StartCompression() {
+    // XXX implement compression.
     // var zwbuf  bytes.Buffer
     // var zwbuf  bytes.Buffer
     // me.zwriter = zlib.NewWriter(&zwbuf);
     // me.zwriter = zlib.NewWriter(&zwbuf);
 }
 }
@@ -476,7 +477,7 @@ func (me * Telnet) maybeSendDataEventAndEmptyBuffer() {
 
 
 // Append a byte to the data buffer
 // Append a byte to the data buffer
 func (me * Telnet) appendByte(bin byte) {
 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)
     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 
 // Process a single byte received from the client 
 func (me * Telnet) ProcessByte(bin byte) bool {
 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 {
     switch me.state {
     // regular data
     // regular data
         case data_state:
         case data_state:

+ 41 - 8
src/woe/woe.go

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

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

@@ -1,8 +1,6 @@
 package world
 package world
 
 
 import "path/filepath"
 import "path/filepath"
-import "os"
-import "encoding/xml"
 import "github.com/beoran/woe/sitef"
 import "github.com/beoran/woe/sitef"
 import "github.com/beoran/woe/monolog"
 import "github.com/beoran/woe/monolog"
 import "fmt"
 import "fmt"
@@ -58,20 +56,6 @@ func (me * Account) Challenge(challenge string) bool {
     return false
     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.
 // Add a character to an account.
 func (me * Account) AddCharacter(chara * Character) {
 func (me * Account) AddCharacter(chara * Character) {
@@ -83,7 +67,7 @@ func (me * Account) AddCharacter(chara * Character) {
 func (me * Account) Save(dirname string) (err error) {
 func (me * Account) Save(dirname string) (err error) {
     path := SavePathFor(dirname, "account", me.Name)
     path := SavePathFor(dirname, "account", me.Name)
     
     
-    rec                := make(sitef.Record)
+    rec                := sitef.NewRecord()
     rec.Put("name",         me.Name)
     rec.Put("name",         me.Name)
     rec.Put("hash",         me.Hash)
     rec.Put("hash",         me.Hash)
     rec.Put("algo",         me.Algo)
     rec.Put("algo",         me.Algo)
@@ -93,11 +77,11 @@ func (me * Account) Save(dirname string) (err error) {
     rec.PutInt("characters",len(me.characters))
     rec.PutInt("characters",len(me.characters))
     for i, chara   := range me.characters {
     for i, chara   := range me.characters {
         key        := fmt.Sprintf("characters[%d]", i)
         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)
     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.
 // 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", 
     account.Privilege       = Privilege(record.GetIntDefault("privilege", 
                                 int(PRIVILEGE_NORMAL)))
                                 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... */    
     /* Todo: load characters here... */    
     monolog.Info("Loaded Account: %s %v", path, account)
     monolog.Info("Loaded Account: %s %v", path, account)
     return account, nil
     return account, nil
@@ -139,32 +138,47 @@ func (me * Account) NumCharacters() int {
     return len(me.characters)
     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
     Mechanical  float64
     // Level of Corruption
     // Level of Corruption
     Corruption  float64
     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 */
 /* Job of a being */
 type Job struct {
 type Job struct {
     Entity
     Entity
@@ -67,6 +84,12 @@ type Job struct {
     Playable    bool
     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 */
 /* Gender of a being */
 type Gender struct {
 type Gender struct {
@@ -77,6 +100,13 @@ type Gender struct {
     Vitals
     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. */
 /* Struct, list and map of all genders in WOE. */
 var Genders = struct {
 var Genders = struct {
     Female          Gender
     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
 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 }
     &Genders.Intersex, &Genders.Genderless }
 
 
 var GenderMap = map[string]*Gender {
 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) */
 /* Filters the list by privilege level (only those allowed by the level are retained) */
 func (me EntitylikeSlice) FilterPrivilege(privilege Privilege) (EntitylikeSlice) {
 func (me EntitylikeSlice) FilterPrivilege(privilege Privilege) (EntitylikeSlice) {
     return me.Filter(func (e Entitylike) (Entitylike) {
     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 */
 /* All Kins of  WOE */
 var KinList = [] Kin {
 var KinList = [] Kin {
     Kin {
     Kin {
@@ -201,6 +252,7 @@ They excel at nothing in particular, but are fast learners.`,
         // No stats either because no bonuses there either.
         // No stats either because no bonuses there either.
         Arts : 1.0,
         Arts : 1.0,
         Techniques : 1.0,
         Techniques : 1.0,
+        Learning: 1.5,
     },
     },
     {
     {
         Entity: Entity {
         Entity: Entity {
@@ -209,7 +261,7 @@ They excel at nothing in particular, but are fast learners.`,
             Long: 
             Long: 
 `Neosa are descendents of humans genetically modified to be lite, agile 
 `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
 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 
         // 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, },
             Agility : 1, Emotion : 1, },
         Arts        : 1.2,
         Arts        : 1.2,
         Techniques  : 0.8,
         Techniques  : 0.8,
+        Learning    : 1.0,
     },
     },
     {
     {
         Entity: Entity {
         Entity: Entity {
@@ -233,6 +286,7 @@ Numen Arts than humans. They have a soft fur which covers their whole body.`,
         
         
         Arts : 0.8,
         Arts : 0.8,
         Techniques : 1.2,
         Techniques : 1.2,
+        Learning    : 1.0,
     },
     },
     {
     {
         Entity: Entity {
         Entity: Entity {
@@ -251,6 +305,7 @@ is not as effective on them, but they can be repaired.`,
         Arts : 0.5,
         Arts : 0.5,
         Techniques : 1.5,
         Techniques : 1.5,
         Mechanical : 0.5,
         Mechanical : 0.5,
+        Learning   : 1.1,
     },
     },
     {
     {
         Entity: Entity {
         Entity: Entity {
@@ -259,7 +314,7 @@ is not as effective on them, but they can be repaired.`,
             Long: 
             Long: 
 `Androids are conscious human shaped robots with the imperative to serve humans.  
 `Androids are conscious human shaped robots with the imperative to serve humans.  
 Highly effective with Techniques, and can install many Parts, but cannot use 
 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 
         // STR+1 1 TOU+1 DEX+1 INT+1 
         Talents : Talents {Strength : +2, Toughness: +2, 
         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,
         Arts : 0.0,
         Techniques : 2.0,
         Techniques : 2.0,
         Mechanical : 1.0,
         Mechanical : 1.0,
+        Learning    : 1.0,
     },
     },
 
 
     {
     {
@@ -276,7 +332,7 @@ any Nummen arts. Since thay are not alive, they technically cannot die.`,
             Long: 
             Long: 
 `Mavericks are androids in which the imperative to serve humans has 
 `Mavericks are androids in which the imperative to serve humans has 
 been destroyed or disabled.  Highly effective with Techniques, and can install 
 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.`,
 technically cannot die.  They are feared by Humans and hated by Androids.`,
             Privilege: PRIVILEGE_IMPLEMENTOR,
             Privilege: PRIVILEGE_IMPLEMENTOR,
         },
         },
@@ -286,6 +342,7 @@ technically cannot die.  They are feared by Humans and hated by Androids.`,
         Arts : 0.0,
         Arts : 0.0,
         Techniques : 2.0,
         Techniques : 2.0,
         Mechanical : 1.0,
         Mechanical : 1.0,
+        Learning   : 1.0,
     },
     },
 
 
     {
     {
@@ -305,6 +362,7 @@ the Earth millennia after.`,
         Arts : 0.0,
         Arts : 0.0,
         Techniques : 2.0,
         Techniques : 2.0,
         Mechanical: 1.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,
         Arts : 0.0,
         Techniques : 2.0,
         Techniques : 2.0,
         Mechanical : 1.0,
         Mechanical : 1.0,
+        Learning    : 1.0,
     },
     },
 
 
     {
     {
@@ -342,6 +401,7 @@ No wonder they are still active after all these years.
         Arts : 0.0,
         Arts : 0.0,
         Techniques : 2.0,
         Techniques : 2.0,
         Mechanical : 1.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, },
             Agility : +1, Intelligence: -5, },
         Arts : 1.0,
         Arts : 1.0,
         Techniques : 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, },
             Agility : +3, Intelligence: -5, },
         Arts : 1.0,
         Arts : 1.0,
         Techniques : 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, },
             Agility : +2, Intelligence: -5, },
         Arts : 1.0,
         Arts : 1.0,
         Techniques : 1.0,
         Techniques : 1.0,
+        Learning    : 1.0,
     },
     },
 
 
     {
     {
@@ -405,6 +468,7 @@ but also purse you on land.`,
             Agility : +1, Dexterity: +1, Intelligence: -5, },
             Agility : +1, Dexterity: +1, Intelligence: -5, },
         Arts : 1.0,
         Arts : 1.0,
         Techniques : 1.0,
         Techniques : 1.0,
+        Learning    : 1.0,
     },
     },
 
 
     {
     {
@@ -422,6 +486,7 @@ they remain dangerous.
             Agility: -1, Intelligence: -5, },
             Agility: -1, Intelligence: -5, },
         Arts : 1.0,
         Arts : 1.0,
         Techniques : 1.0,
         Techniques : 1.0,
+        Learning    : 1.0,
     },
     },
  
  
     {
     {
@@ -439,6 +504,7 @@ and as well on the land.`,
                             Intelligence: -5,  },
                             Intelligence: -5,  },
         Arts : 1.0,
         Arts : 1.0,
         Techniques : 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, },
         Talents : Talents {Strength : +2, Toughness: +4,  Intelligence: -5, },
         Arts : 1.0,
         Arts : 1.0,
         Techniques : 1.0,
         Techniques : 1.0,
+        Learning   : 1.0,
     },
     },
 
 
     {
     {
@@ -464,10 +531,12 @@ and as a result, larger creepy crawlers became more successful.
             Long: 
             Long: 
 `Whether in the rivers or the deep seas, these soft bodies creatures 
 `Whether in the rivers or the deep seas, these soft bodies creatures 
 are often toxic and quite dangerous.`,
 are often toxic and quite dangerous.`,
+            Privilege: PRIVILEGE_IMPLEMENTOR,
         },
         },
         Talents : Talents {Strength : +2, Dexterity: +2, Intelligence: -5, },
         Talents : Talents {Strength : +2, Dexterity: +2, Intelligence: -5, },
         Arts : 1.0,
         Arts : 1.0,
         Techniques : 1.0,
         Techniques : 1.0,
+        Learning   : 1.0,
     },
     },
 
 
     {
     {
@@ -484,6 +553,8 @@ Beware, their attacks might be contageous...`,
             Agility : +1, Intelligence: -3, Wisdom: -5 },
             Agility : +1, Intelligence: -3, Wisdom: -5 },
         Arts : 1.0,
         Arts : 1.0,
         Techniques : 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 },
             Agility : +1, Intelligence: -1, Wisdom: -7 },
         Arts : 1.0,
         Arts : 1.0,
         Techniques : 1.0,
         Techniques : 1.0,
+        Learning    : 1.0,
+        Corruption: 1.0,
+        Unlife: 1.0,
     },
     },
 }
 }
 
 
@@ -822,36 +896,36 @@ var BasicTalent Talents = Talents {
 }
 }
 
 
 // Derived stats 
 // Derived stats 
-func (me *Being) Force() int {
+func (me *Talents) Force() int {
     return (me.Strength * 2 + me.Wisdom) / 3
     return (me.Strength * 2 + me.Wisdom) / 3
 }
 }
     
     
-func (me *Being) Vitality() int {
+func (me *Talents) Vitality() int {
     return (me.Toughness * 2 + me.Charisma) / 3
     return (me.Toughness * 2 + me.Charisma) / 3
 }
 }
 
 
-func (me *Being) Quickness() int {
+func (me * Talents) Quickness() int {
     return (me.Agility * 2 + me.Intelligence) / 3
     return (me.Agility * 2 + me.Intelligence) / 3
 }
 }
 
 
-func (me * Being) Knack() int {
+func (me * Talents) Knack() int {
     return (me.Dexterity * 2 + me.Emotion) / 3
     return (me.Dexterity * 2 + me.Emotion) / 3
 }
 }
     
     
     
     
-func (me * Being) Understanding() int {
+func (me * Talents) Understanding() int {
     return (me.Intelligence * 2 + me.Toughness) / 3
     return (me.Intelligence * 2 + me.Toughness) / 3
 }
 }
 
 
-func (me * Being) Grace() int {
+func (me * Talents) Grace() int {
     return (me.Charisma * 2 + me.Agility) / 3
     return (me.Charisma * 2 + me.Agility) / 3
 }
 }
     
     
-func (me * Being) Zeal() int {
+func (me * Talents) Zeal() int {
     return (me.Wisdom * 2 + me.Strength) / 3
     return (me.Wisdom * 2 + me.Strength) / 3
 }
 }
 
 
-func (me * Being) Numen() int {
+func (me * Talents) Numen() int {
       return (me.Emotion * 2 + me.Dexterity) / 3
       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.
 // Generates an overview of the essentials of the being as a string.
 func (me * Being) ToEssentials() 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.
 // 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)
     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.
 // 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)
     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.
 // 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)
     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.
 // Generates an overview of the status of the being as a string.
 func (me * Being) ToStatus() string {
 func (me * Being) ToStatus() string {
     status := me.ToEssentials()
     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.ToEquipmentValues();
     status += "\n" + me.ToPrompt();
     status += "\n" + me.ToPrompt();
     status += "\n"
     status += "\n"
@@ -1027,7 +1147,7 @@ func (me * Being) Init(kind string, name string, privilege Privilege,
     
     
     me.Kin         = realkin
     me.Kin         = realkin
     me.Gender      = realgen
     me.Gender      = realgen
-    me.Job         = realjob 
+    me.Job         = realjob
        
        
     me.Talents.GrowFrom(BasicTalent)    
     me.Talents.GrowFrom(BasicTalent)    
     me.Talents.GrowFrom(me.Kin.Talents)
     me.Talents.GrowFrom(me.Kin.Talents)
@@ -1063,31 +1183,39 @@ func LoadBeing(datadir string, nameid string) * Being {
     return res
     return res
 }
 }
 
 
-func (me * Talents) SaveSitef(rec sitef.Record) (err error) {
+func (me * Talents) SaveSitef(rec * sitef.Record) (err error) {
     rec.PutStruct("", *me)
     rec.PutStruct("", *me)
     return nil
     return nil
 }
 }
 
 
-func (me * Vitals) SaveSitef(rec sitef.Record) (err error) {
+func (me * Vitals) SaveSitef(rec * sitef.Record) (err error) {
     rec.PutStruct("", *me)
     rec.PutStruct("", *me)
     return nil
     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
     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
     return nil
 }
 }
 
 
-func (me * Inventory) SaveSitef(rec sitef.Record) (err error) {
+func (me * Inventory) SaveSitef(rec * sitef.Record) (err error) {
+ 
     return nil
     return nil
 }
 }
 
 
 
 
 // Save a being to a sitef record.
 // 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)
     me.Entity.SaveSitef(rec)
     rec.PutInt("level", me.Level)
     rec.PutInt("level", me.Level)
 
 
@@ -1110,17 +1238,63 @@ func (me * Being) SaveSitef(rec sitef.Record) (err error) {
     me.Inventory.SaveSitef(rec)
     me.Inventory.SaveSitef(rec)
 
 
     if me.Room != nil {
     if me.Room != nil {
-        rec.Put("kin", me.Room.ID)
+        rec.Put("room", me.Room.ID)
     }
     }
     
     
     // TODO: saving: me.Being.SaveSitef(rec)
     // TODO: saving: me.Being.SaveSitef(rec)
     return nil
     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.
 // Load a being from a sitef record.
 func (me * Being) LoadSitef(rec sitef.Record) (err error) {
 func (me * Being) LoadSitef(rec sitef.Record) (err error) {
     me.Entity.LoadSitef(rec) 
     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
     return nil
 }
 }
 
 

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

@@ -1,6 +1,7 @@
 package world
 package world
 
 
 import "fmt"
 import "fmt"
+import "os"
 // import "strconv"
 // import "strconv"
 import "github.com/beoran/woe/monolog"
 import "github.com/beoran/woe/monolog"
 import "github.com/beoran/woe/sitef"
 import "github.com/beoran/woe/sitef"
@@ -10,7 +11,7 @@ import "github.com/beoran/woe/sitef"
 
 
 type Character struct {
 type Character struct {
     Being       
     Being       
-    * Account
+    Account * Account
 }
 }
 
 
 
 
@@ -31,68 +32,103 @@ func NewCharacter(account * Account, name string,
     return me.Init(account, name, kin, gender, job)
     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)
     me.Being.SaveSitef(rec)
-    
     return nil
     return nil
 }
 }
 
 
 // Load a character from a sitef record.
 // 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)
     account, err := DefaultWorld.LoadAccount(aname)
     if err != nil {
     if err != nil {
         return err
         return err
     } 
     } 
     me.Account = account
     me.Account = account
     me.Being.LoadSitef(rec)
     me.Being.LoadSitef(rec)
-    // TODO: load being. me.Being.SaveSitef(rec)
     return nil
     return nil
 }
 }
 
 
 
 
 // Save a character as a sitef file.
 // Save a character as a sitef file.
 func (me * Character) Save(dirname string) (err error) {
 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)
     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)
     records, err := sitef.ParseFilename(path)
     if err != nil {
     if err != nil {
-        return nil, err
+        return nil, "", err
     }
     }
     
     
     if len(records) < 1 {
     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]
     record := records[0]
-    monolog.Info("Loading Account record: %s %v", path, record)
+    monolog.Info("Loading Character record: %s %v", path, record)
     
     
     character               = new(Character)
     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
     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, 
 func (me * Entity) InitKind(kind string, name string, 
     privilege Privilege) (* Entity) {
     privilege Privilege) (* Entity) {
     if me == nil {
     if me == nil {
         return me
         return me
     }
     }
-    me.ID       = kind + "_" + strings.ToLower(name) 
+    me.ID       = EntityNameToID(kind, name) 
     me.Name     = name
     me.Name     = name
     me.Short    = name
     me.Short    = name
     me.Long     = name
     me.Long     = name
@@ -236,24 +241,40 @@ func (me * LabeledList) Index(index int) Labeled {
 
 
 
 
 // Save an entity to a sitef record.
 // 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
     return nil
 }
 }
 
 
 // Load an entity from a sitef record.
 // Load an entity from a sitef record.
 func (me * Entity) LoadSitef(rec sitef.Record) (err error) {
 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
     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
     Kind          ItemKind
     Damage        DamageKind
     Damage        DamageKind
     // Equipment location,  "none" if not equippable
     // Equipment location,  "none" if not equippable
-    Equippable    EquipWhere
+    Equip         EquipWhere
     // Level of crafing skill needed to craft this, or of harvesting skill 
     // 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 
     // to harvest this, or of mining skill to mine this. Negative if cannot 
     // be crafted nor harvested, nor mined.    
     // 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 
     // ID of item this item can degrade into. empty or "none" if cannot be 
     // degraded.
     // degraded.
     Degrade       string
     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 
     // none if it teaches nothing. If it's a skill, the XP of teaching is 
     // determined by the Quality of the item.   
     // determined by the Quality of the item.   
     Teaches       string
     Teaches       string
+     // ID of skill needed to craft this item   
+    Craft         string
 }
 }
 
 
 // Load an item from a sitef file.
 // Load an item from a sitef file.
@@ -216,21 +218,26 @@ func LoadItem(dirname string, id string) (item *Item, err error) {
     record := records[0]
     record := records[0]
     monolog.Info("Loading Item record: %s %v", path, record)
     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)
     monolog.Info("Loaded Item: %s %v", path, item)
     return item, nil
     return item, nil
 }
 }

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

@@ -1,5 +1,11 @@
 package world
 package world
 
 
+import "github.com/beoran/woe/sitef"
+import "github.com/beoran/woe/monolog"
+// import "fmt"
+import "errors"
+
+
 
 
 type Direction  string
 type Direction  string
 
 
@@ -14,3 +20,38 @@ type Room struct {
     Exits   map[Direction]Exit
     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) {
 func (me * World) Save(dirname string) (err error) {
     path := SavePathFor(dirname, "world", me.Name)
     path := SavePathFor(dirname, "world", me.Name)
     
     
-    rec                := make(sitef.Record)
+    rec                  := sitef.NewRecord()
     rec.Put("name",         me.Name)
     rec.Put("name",         me.Name)
     rec.Put("motd",         me.MOTD)
     rec.Put("motd",         me.MOTD)
     monolog.Debug("Saving World record: %s %v", path, rec)
     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.
 // Load a world from a sitef file.
@@ -182,3 +182,43 @@ func (me * World) RemoveItem(id string) {
     delete(me.itemmap, id)
     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)
+}
+
+
+