Browse Source

Server now has supervisor that enables restart on the fly. Starting with command actions.

Beoran 8 years ago
parent
commit
9a25d76146

+ 1 - 3
README

@@ -1,8 +1,6 @@
 WOE - Workers of Eruta 
 WOE - Workers of Eruta 
 
 
-Woe is a MUD server implemented in ruby, used to implement the Workers of Eruta 
-MUD.
+Woe is a MUD server implemented in Go Language, used to implement the Workers of Eruta  MUD.
 
 
 Workers of Eruta is set in the land of Eruta, 60000 years in the future.
 Workers of Eruta is set in the land of Eruta, 60000 years in the future.
 
 
-

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

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

+ 62 - 0
src/woe/server/action.go

@@ -0,0 +1,62 @@
+package server
+
+// import "github.com/beoran/woe/telnet"
+import "github.com/beoran/woe/world"
+import "github.com/beoran/woe/monolog"
+
+
+/* Actiondata are the params passed to an Actin, neatly wrapped in 
+ * a struct */
+type ActionData struct {
+    Client * Client
+    Server * Server
+    World  * world.World
+    Action * Action
+    Command  string
+    Argv   []string
+}
+
+/* A handler for an action. */
+type ActionHandler func(data * ActionData) (err error) 
+
+/* Actions that  a client can perform on a server or in 
+ * the server's world. */
+type Action struct {
+    Name        string
+    Privilege   world.Privilege
+    Handler     ActionHandler
+}
+
+
+var ActionMap map[string] Action
+
+func AddAction(name string, privilege world.Privilege, handler ActionHandler) {
+    monolog.Info("Adding new action %s with privilege %d", name, privilege)
+    action := Action{name, privilege, handler}
+    ActionMap[name] = action
+}
+
+func doShutdown(data * ActionData) (err error) {
+    return nil
+}
+
+func doRestart(data * ActionData) (err error) {
+    return nil
+}
+
+
+func doQuit(data * ActionData) (err error) {    
+    return nil
+}
+
+func ParseCommand(command string, data * ActionData) {
+    data.Command = command
+} 
+
+func init() {
+    ActionMap = make(map[string] Action)
+    AddAction("/shutdown"   , world.PRIVILEGE_LORD, doShutdown)
+    AddAction("/restart"    , world.PRIVILEGE_LORD, doRestart)
+    AddAction("/quit"       , world.PRIVILEGE_ZERO, doQuit)
+}
+

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

@@ -9,7 +9,7 @@ import "github.com/beoran/woe/world"
 import "github.com/beoran/woe/monolog"
 import "github.com/beoran/woe/monolog"
 import "bytes"
 import "bytes"
 import "regexp"
 import "regexp"
-import "fmt"
+// import "fmt"
 // import "strconv"
 // import "strconv"
 
 
 
 
@@ -101,6 +101,7 @@ func (me * Client) AskSomething(prompt string, re string, nomatch_prompt string,
     
     
     if noecho {
     if noecho {
       me.NormalMode()
       me.NormalMode()
+      me.Printf("\n")
     }
     }
     
     
     return something
     return something
@@ -132,9 +133,14 @@ func (me * Client) HandleCommand() {
     if bytes.HasPrefix(command, []byte("/quit")) {
     if bytes.HasPrefix(command, []byte("/quit")) {
       me.Printf("Byebye!\n")
       me.Printf("Byebye!\n")
       me.alive = false
       me.alive = false
+    } else if bytes.HasPrefix(command, []byte("/shutdown")) {
+      me.server.Broadcast("Shutting down server NOW!\n")
+      me.server.Shutdown();
+    } else if bytes.HasPrefix(command, []byte("/restart")) {
+      me.server.Broadcast("Restarting down server NOW!\n")
+      me.server.Restart();
     } else {
     } else {
-      bro := fmt.Sprintf("Client %d said %s\r\n", me.id, command)  
-      me.server.Broadcast(bro)
+      me.server.Broadcast("Client %d said %s\r\n", me.id, command)  
     }
     }
 }
 }
  
  
@@ -196,7 +202,14 @@ func (me * Client) AccountDialog() bool {
     login  := me.AskLogin()
     login  := me.AskLogin()
     if login == nil { return false }
     if login == nil { return false }
     var err error
     var err error
-    me.account, err = world.LoadAccount(me.server.DataPath(), string(login))    
+    
+    if me.server.World.GetAccount(string(login)) != nil {
+        me.Printf("Account already logged in!\n")
+        me.Printf("Disconnecting!\n")
+        return false 
+    }
+    
+    me.account, err = me.server.World.LoadAccount(me.server.DataPath(), string(login))    
     if err != nil {
     if err != nil {
         monolog.Warning("Could not load account %s: %v", login, err)  
         monolog.Warning("Could not load account %s: %v", login, err)  
     }
     }
@@ -207,4 +220,18 @@ func (me * Client) AccountDialog() bool {
     }
     }
 }
 }
  
  
+func (me * Client) CharacterDialog() bool {
+    login  := me.AskLogin()
+    if login == nil { return false }
+    var err error
+    me.account, err = world.LoadAccount(me.server.DataPath(), string(login))    
+    if err != nil {
+        monolog.Warning("Could not load account %s: %v", login, err)  
+    }
+    if me.account != nil {
+      return me.ExistingAccountDialog()
+    } else {
+      return me.NewAccountDialog(string(login))
+    }
+}
 
 

+ 5 - 1
src/woe/server/client.go

@@ -56,6 +56,10 @@ func NewClient(server * Server, id int, conn net.Conn) * Client {
 func (me * Client) Close() {
 func (me * Client) Close() {
     me.conn.Close()
     me.conn.Close()
     me.alive = false
     me.alive = false
+    if me.account != nil {     
+        me.server.World.RemoveAccount(me.account.Name)
+    }
+    me.account = nil
 }
 }
 
 
 /** Goroutine that does the actual reading of input data, and sends it to the 
 /** Goroutine that does the actual reading of input data, and sends it to the 
@@ -68,7 +72,7 @@ func (me * Client) ServeRead() {
             me.errchan <- err
             me.errchan <- err
             return
             return
         }
         }
-        // reply will be stored in me.telnet.Events
+        // reply will be stored in me.telnet.Events channel
         me.telnet.ProcessBytes(buffer[:read])
         me.telnet.ProcessBytes(buffer[:read])
     }
     }
 }
 }

+ 76 - 21
src/woe/server/server.go

@@ -16,7 +16,11 @@ import (
 
 
 var MSSP map[string] string
 var MSSP map[string] string
 
 
-const MAX_CLIENTS = 1000
+const STATUS_OK             = 0
+const STATUS_CANNOT_LISTEN  = 1
+const STATUS_RESTART        = 0
+const STATUS_SHUTDOWN       = 4
+const MAX_CLIENTS           = 1000
 
 
 func init() {
 func init() {
      MSSP = map[string] string {
      MSSP = map[string] string {
@@ -76,6 +80,7 @@ type Server struct {
     tickers map[string] * Ticker
     tickers map[string] * Ticker
     alive                 bool
     alive                 bool
     World               * world.World
     World               * world.World
+    exitstatus            int
 }
 }
 
 
 
 
@@ -88,7 +93,7 @@ type Ticker struct {
 }
 }
 
 
 
 
-const DEFAULT_MOTD =
+const DEFAULT_MOTD_OK =
 
 
 `
 `
 ###############################
 ###############################
@@ -97,10 +102,21 @@ const DEFAULT_MOTD =
 
 
 `
 `
 
 
+const DEFAULT_MOTD =
+`
+Welcome!
+`
+
 
 
-func (me * Server) SetupWorld() error {
-    /*
-    me.World, _ = world.LoadWorld(me.DataPath(), "WOE")
+
+func (me * Server) SetupWorld() (err error) {
+    me.World, err = world.LoadWorld(me.DataPath(), "WOE")
+    
+    if err != nil { 
+        monolog.Error("Could not load world WOE: %s", err)
+        return err
+    }
+    
     if me.World == nil {
     if me.World == nil {
         monolog.Info("Creating new default world...")
         monolog.Info("Creating new default world...")
         me.World = world.NewWorld("WOE", DEFAULT_MOTD)
         me.World = world.NewWorld("WOE", DEFAULT_MOTD)
@@ -112,7 +128,6 @@ func (me * Server) SetupWorld() error {
             monolog.Info("Saved default world.")
             monolog.Info("Saved default world.")
         }
         }
     }
     }
-    */
     return nil
     return nil
 }
 }
 
 
@@ -120,7 +135,6 @@ func (me * Server) SetupWorld() error {
 func NewServer(address string) (server * Server, err error) {
 func NewServer(address string) (server * Server, err error) {
     listener, err := net.Listen("tcp", address);
     listener, err := net.Listen("tcp", address);
     if (err != nil) { 
     if (err != nil) { 
-        io.Printf("")
         return nil, err
         return nil, err
     }
     }
     
     
@@ -133,16 +147,13 @@ func NewServer(address string) (server * Server, err error) {
     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}
+    server = &Server{address, listener, logger, logfile, clients, tickers, true, nil, STATUS_RESTART}
     err = server.SetupWorld()
     err = server.SetupWorld()
     server.AddDefaultTickers()
     server.AddDefaultTickers()
     
     
     return server, err
     return server, err
 }
 }
 
 
-func (me * Server) Close() {
-    me.logfile.Close();
-}
 
 
 
 
 func NewTicker(server * Server, name string, milliseconds int, callback func (me * Ticker, t time.Time) bool) (* Ticker) {
 func NewTicker(server * Server, name string, milliseconds int, callback func (me * Ticker, t time.Time) bool) (* Ticker) {
@@ -180,8 +191,6 @@ func (me * Server) StopTicker(name string) {
     ticker.Stop();
     ticker.Stop();
 }
 }
 
 
-
-
 func (me * Server) AddTicker(name string, milliseconds int, callback func (me * Ticker, t time.Time) bool) (* Ticker) {
 func (me * Server) AddTicker(name string, milliseconds int, callback func (me * Ticker, t time.Time) bool) (* Ticker) {
     _, have := me.tickers[name]
     _, have := me.tickers[name]
     
     
@@ -198,17 +207,17 @@ func (me * Server) AddTicker(name string, milliseconds int, callback func (me *
 
 
 
 
 func onWeatherTicker (me * Ticker, t time.Time) bool {
 func onWeatherTicker (me * Ticker, t time.Time) bool {
-    monolog.Info("Weather Ticker tick tock.")
+    me.Server.Broadcast("The weather is changing...\n")
     return true
     return true
 }
 }
 
 
 
 
 func (me * Server) AddDefaultTickers() {
 func (me * Server) AddDefaultTickers() {
-    me.AddTicker("weather", 10000, onWeatherTicker)    
+    me.AddTicker("weather", 30000, onWeatherTicker)    
 }
 }
 
 
 func (me * Server) handleDisconnectedClients() {
 func (me * Server) handleDisconnectedClients() {
-    for { 
+    for me.alive { 
         time.Sleep(1)
         time.Sleep(1)
         for id, client := range me.clients {
         for id, client := range me.clients {
             if (!client.IsAlive()) {
             if (!client.IsAlive()) {
@@ -243,24 +252,65 @@ func (me * Server) onConnect(conn net.Conn) (err error) {
     return client.Serve()
     return client.Serve()
 }
 }
 
 
-func (me * Server) Serve() (err error) { 
+func (me * Server) Shutdown() {
+    monolog.Info("Server is going to shut down.")
+    me.alive        = false
+    me.exitstatus   = STATUS_SHUTDOWN
+}
+
+func (me * Server) Restart() {
+    monolog.Info("Server is going to restart.")
+    me.alive        = false
+    me.exitstatus   = STATUS_RESTART
+}
+
+
+func (me * Server) Close() {
+    monolog.Info("Closing server, shutting down tickers.")
+    
+    for name, _ := range me.tickers {
+        me.RemoveTicker(name);
+    }
+
+    monolog.Info("Closing server, shutting down clients.")
+    for _, client := range me.clients {
+        if (client.IsAlive()) {
+            client.Close()
+        }
+    }
+    
+    me.handleDisconnectedClients()
+    monolog.Info("Closing server, closing logfile.")
+    me.logfile.Close();
+}
+
+func (me * Server) Serve() (status int, err error) { 
     // Setup random seed here, or whatever
     // Setup random seed here, or whatever
     rand.Seed(time.Now().UTC().UnixNano())
     rand.Seed(time.Now().UTC().UnixNano())
     
     
     go me.handleDisconnectedClients()
     go me.handleDisconnectedClients()
     
     
     for (me.alive) {
     for (me.alive) {
+        if tcplistener, ok := me.listener.(*net.TCPListener) ; ok {
+          tcplistener.SetDeadline(time.Now().Add(5*time.Second))
+        }
         conn, err := me.listener.Accept()
         conn, err := me.listener.Accept()
         if err != nil {
         if err != nil {
-            return err
+            if noe, ok := err.(*net.OpError) ; ok && noe.Timeout() {
+                // it's a timeout. Do nothing, just listen again.
+                // this to allow the alive flag to do it's work.
+            } else {
+                return STATUS_CANNOT_LISTEN, err
+            }
+        } else {
+            go me.onConnect(conn)
         }
         }
-        go me.onConnect(conn)
     }
     }
-    return nil
+    return me.exitstatus, nil
 }
 }
 
 
 
 
-func (me * Server) Broadcast(message string) {
+func (me * Server) BroadcastString(message string) {
     for _, client := range me.clients {
     for _, client := range me.clients {
         if (client.IsAlive()) {
         if (client.IsAlive()) {
             client.WriteString(message)
             client.WriteString(message)
@@ -268,6 +318,11 @@ func (me * Server) Broadcast(message string) {
     }       
     }       
 }
 }
 
 
+func (me * Server) Broadcast(format string, args ...interface{}) {
+    msg := fmt.Sprintf(format, args...)
+    me.BroadcastString(msg)
+}
+
 
 
 // Returns the data path of the server
 // Returns the data path of the server
 func (me * Server) DataPath() string {
 func (me * Server) DataPath() string {

+ 2 - 135
src/woe/server/setuptelnet.go

@@ -217,7 +217,7 @@ func (me * Client) SetupTelnet() {
     me.SetupMXP()
     me.SetupMXP()
     me.SetupMSP()
     me.SetupMSP()
     me.SetupMSDP()
     me.SetupMSDP()
-    // color_test
+    // me.ColorTest()
 }
 }
 
 
 
 
@@ -248,95 +248,8 @@ func (me * Client) SetupTelnet() {
     return nil
     return nil
   end
   end
   
   
-  def color_test
-    self.write("\e[1mBold\e[0m\r\n")
-    self.write("\e[3mItalic\e[0m\r\n")
-    self.write("\e[4mUnderline\e[0m\r\n")
-    30.upto(37) do | fg |
-      self.write("\e[#{fg}mForeground Color #{fg}\e[0m\r\n")
-      self.write("\e[1;#{fg}mBold Foreground Color #{fg}\e[0m\r\n")
-    end  
-    40.upto(47) do | bg |
-      self.write("\e[#{bg}mBackground Color #{bg}\e[0m\r\n")
-      self.write("\e[1;#{bg}mBold Background Color #{bg}\e[0m\r\n")
-    end    
   end
   end
-  
-  def setup_telnet
-    loop do
-      tev = wait_for_input(0.5)
-      if tev
-        p "setup_telnet", tev
-      else
-        p "no telnet setup received..."
-        break
-      end
-    end
-    setup_mssp
-    setup_compress2
-    setup_naws
-    setup_ttype
-    setup_mxp
-    setup_msp
-    setup_msdp
-    # color_test
-    
-    
-    #p "mssp ev #{tev}"
-    # @telnet.telnet_send_negotiate(TELNET_WILL, TELNET_TELOPT_MSSP)        
-    # tev = wait_for_input(0.5)
-    # p "mssp ev #{tev}"
-    
-    # @telnet.telnet_ttype_send
-    
-    
-  end
- 
-  LOGIN_RE = /\A[A-Za-z][A-Za-z0-9]*\Z/
-  
-  def ask_something(prompt, re, nomatch_prompt, noecho=false)
-    something = nil
-    
-    if noecho
-      password_mode
-    end
 
 
-    while  something.nil? || something.empty? 
-      write("#{prompt}:")
-      something = wait_for_command
-      if something
-          something.chomp!
-        if re && something !~ re
-          write("\r\n#{nomatch_prompt}\r\n")
-          something = nil
-        end
-      end
-    end
-    
-    if noecho
-      normal_mode
-    end
-    
-    something.chomp!
-    return something
-  end
-  
-  
-  
-  def ask_login
-    return ask_something("Login", LOGIN_RE, "Login must consist of a letter followed by letters or numbers.")
-  end
-
-  EMAIL_RE = /@/
-
-  def ask_email
-    return ask_something("E-mail", EMAIL_RE, "Email must have at least an @ in there somewhere.")
-  end
-
-
-  def ask_password(prompt = "Password")
-    return ask_something(prompt, nil, "", true) 
-  end
   
   
   def handle_command
   def handle_command
     order = wait_for_command
     order = wait_for_command
@@ -348,51 +261,5 @@ func (me * Client) SetupTelnet() {
       @server.broadcast("#{@account.id} said #{order}\r\n")
       @server.broadcast("#{@account.id} said #{order}\r\n")
     end
     end
   end
   end
-  
-  def existing_account_dialog
-    pass  = ask_password
-    return false unless pass
-    unless @account.challenge?(pass)
-      printf("Password not correct!\n")
-      return false
-    end
-    return true
-  end
-  
-  def new_account_dialog(login)
-    while !@account 
-      printf("\nWelcome, %s! Creating new account...\n", login)
-      pass1  = ask_password
-      return false unless pass1
-      pass2 = ask_password("Repeat Password")
-      return false unless pass2
-      if pass1 != pass2
-        printf("\nPasswords do not match! Please try again!\n")
-        next
-      end
-      email = ask_email
-      return false unless email
-      @account = Woe::Account.new(:id => login, :email => email )
-      @account.password   = pass1
-      @account.woe_points = 7
-      unless @account.save_one
-        printf("\nFailed to save your account! Please contact a WOE administrator!\n")
-        return false
-      end
-      printf("\nSaved your account.\n")
-      return true
-    end
-  end
-  
-  def account_dialog
-    login  = ask_login
-    return false unless login
-    @account = Account.fetch(login)
-    if @account
-      return existing_account_dialog
-    else
-      return new_account_dialog(login)
-    end
-  end
- 
+   
 */
 */

+ 8 - 0
src/woe/sitef/marshal.go

@@ -3,3 +3,11 @@ package sitef
 
 
 // Marshallling of structs from and to sitef format
 // Marshallling of structs from and to sitef format
 
 
+/*
+func Marshal(value interface{}) (byte [], error)
+
+st := reflect.TypeOf(s)
+field := st.Field(0)
+fmt.Println(field.Tag.Get("color"), field.Tag.Get("species"))
+
+*/

+ 58 - 0
src/woe/sitef/sitef.go

@@ -6,6 +6,7 @@ import "strings"
 import "fmt"
 import "fmt"
 import "bytes"
 import "bytes"
 import "bufio"
 import "bufio"
+import "strconv"
 
 
 // Sitef format for serialization
 // Sitef format for serialization
 // Sitef is a simple text format for serializing data to
 // Sitef is a simple text format for serializing data to
@@ -36,6 +37,63 @@ import "bufio"
 
 
 type Record map[string]string
 type Record map[string]string
 
 
+func (me * Record) Put(key string, val string) {
+    (*me)[key] = val
+}
+
+func (me * Record) Putf(key string, format string, values ...interface{}) {
+    me.Put(key, fmt.Sprintf(format, values...)) 
+}
+
+func (me * Record) PutArray(key string, values []string) {
+    for i, value := range values {
+        realkey := fmt.Sprintf("%s[%d]", key, i)
+        me.Put(realkey, value)
+    }
+} 
+
+func (me * Record) PutInt(key string, val int) {
+    me.Putf(key, "%d", val)
+}
+
+func (me * Record) PutFloat64(key string, val float64) {
+    me.Putf(key, "%lf", val)
+}
+
+
+func (me Record) Get(key string) (result string) {
+    return me[key]
+}
+
+func (me Record) Getf(key string, format string, 
+    values ...interface{}) (amount int, ok bool) {
+    val := me.Get(key)
+    count, err := fmt.Sscanf(val, format, values...)
+    if err != nil {
+        return 0, false
+    }
+    return count, true
+}
+
+func (me Record) GetInt(key string) (val int, err error) {
+    i, err := strconv.ParseInt(me.Get(key), 0, 0)
+    return int(i), err
+}
+
+func (me Record) GetIntDefault(key string, def int) (val int) {
+    i, err := strconv.ParseInt(me.Get(key), 0, 0)
+    if err != nil {
+        return def;
+    }
+    return int(i);
+}
+
+
+func (me Record) GetFloat(key string) (val float64, error error) {
+    return strconv.ParseFloat(me.Get(key), 64)
+}
+
+
 type Error struct {
 type Error struct {
     error   string
     error   string
     lineno  int
     lineno  int

+ 61 - 5
src/woe/woe.go

@@ -3,20 +3,76 @@ package main
 // import "fmt"
 // import "fmt"
 import "github.com/beoran/woe/server"
 import "github.com/beoran/woe/server"
 import "github.com/beoran/woe/monolog"
 import "github.com/beoran/woe/monolog"
+import "os"
+import "os/exec"
+import "flag"
+import "fmt"
 
 
+/* Command line flags. */
+var server_mode  = flag.Bool("s", false, "Run in server mode");
+var server_tcpip = flag.String("l", ":7000", "TCP/IP Address where the server will listen");
+ 
+/* Need to restart the server or not? */
+var server_restart = true
 
 
-
-func main() {
+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("Starting WOE!")
-    monolog.Info("Server runs at port %d!", 7000)
-    woe, err := server.NewServer(":7000")
+    monolog.Info("Server runs at %s!", *server_tcpip)
+    woe, err := server.NewServer(*server_tcpip)
     if err != nil {
     if err != nil {
         monolog.Error(err.Error())
         monolog.Error(err.Error())
         panic(err)
         panic(err)
     }
     }
     defer woe.Close()
     defer woe.Close()
-    woe.Serve()
+    status, err = woe.Serve()
+    if err != nil {
+        monolog.Error(err.Error())
+        panic(err)
+    }
+    return status
+}
+
+
+
+func runSupervisor() (status int) {
+    monolog.Setup("woe.log", true, false)
+    defer monolog.Close()
+    monolog.Info("Starting WOE supervisor!")
+    for (server_restart) {
+        // wd  , _ := os.Getwd()
+        exe  := fmt.Sprintf("%s", os.Args[0]) 
+        argp := fmt.Sprintf("-l=%s", *server_tcpip)
+        cmd  := exec.Command(exe, "-s=true", argp)
+        monolog.Info("Starting server %s at %s!", exe, *server_tcpip)
+        cmd.Stderr = os.Stderr
+        cmd.Stdout = os.Stdout
+        err  := cmd.Run()
+        monolog.Info("Server at %s shut down!", *server_tcpip)
+        // monolog.Info("Server output: %s!", out);
+        if (err != nil ) { 
+            monolog.Error("Server shut down with error %s!", err)
+            server_restart = false;
+            return 1
+        }
+    }
+    return 0
+}
+
+
+
+/* Woe can be run in supervisor mode (the default) or server mode (-s).
+ * Server mode is the mode in which the real server is run. In supervisor mode, 
+ * woe runs a single woe server in server mode using os/exec. This is used to 
+ * be able to restart the server gracefully on recompile of the sources. 
+ */
+func main() {
+    flag.Parse()
+    if *server_mode {
+        os.Exit(runServer())
+    } else {
+        os.Exit(runSupervisor())
+    }
 }
 }
 
 

+ 26 - 16
src/woe/world/account.go

@@ -7,8 +7,16 @@ import "github.com/beoran/woe/sitef"
 import "github.com/beoran/woe/monolog"
 import "github.com/beoran/woe/monolog"
 import "fmt"
 import "fmt"
 import "errors"
 import "errors"
-import "strconv"
 
 
+type Privilege int
+
+const (
+    PRIVILEGE_ZERO        = Privilege(iota * 100)
+    PRIVILEGE_NORMAL
+    PRIVILEGE_MASTER
+    PRIVILEGE_LORD
+    PRIVILEGE_IMPLEMENTOR
+)
 
 
 
 
 type Named struct {
 type Named struct {
@@ -22,6 +30,7 @@ type Account struct {
     Algo              string
     Algo              string
     Email             string
     Email             string
     Points            int
     Points            int
+    Privilege         Privilege
     CharacterNames  []string
     CharacterNames  []string
     characters      [] * Character
     characters      [] * Character
 }
 }
@@ -37,7 +46,7 @@ func SavePathFor(dirname string, typename string, name string) string {
 
 
 
 
 func NewAccount(name string, pass string, email string, points int) (*Account) {
 func NewAccount(name string, pass string, email string, points int) (*Account) {
-    return &Account{name, pass, "plain", email, points, nil, nil}
+    return &Account{name, pass, "plain", email, points, PRIVILEGE_NORMAL, nil, nil}
 }
 }
 
 
 // Password Challenge for an account.
 // Password Challenge for an account.
@@ -69,15 +78,16 @@ 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                := make(sitef.Record)
-    rec["name"]         = me.Name
-    rec["hash"]         = me.Hash
-    rec["algo"]         = me.Algo
-    rec["email"]        = me.Email
-    rec["points"]       = fmt.Sprintf("%d", me.Points)
-    rec["characters"]   = fmt.Sprintf("%d", len(me.characters))
+    rec.Put("name",         me.Name)
+    rec.Put("hash",         me.Hash)
+    rec.Put("algo",         me.Algo)
+    rec.Put("email",        me.Email)
+    rec.PutInt("points",    me.Points)
+    rec.PutInt("privilege", int(me.Privilege))
+    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[key]    = chara.Name
+        rec.Put(key, chara.Name)
     }
     }
     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)
@@ -101,20 +111,20 @@ func LoadAccount(dirname string, name string) (account *Account, err error) {
     monolog.Info("Loading Account record: %s %v", path, record)
     monolog.Info("Loading Account record: %s %v", path, record)
     
     
     account = new(Account)
     account = new(Account)
-    account.Name            = record["name"]
-    account.Hash            = record["hash"]
-    account.Algo            = record["algo"]
-    account.Email           = record["email"]
-    account.Points,  err    = strconv.Atoi(record["points"])    
+    account.Name            = record.Get("name")
+    account.Hash            = record.Get("hash")
+    account.Algo            = record.Get("algo")
+    account.Email           = record.Get("email")
+    account.Points,  err    = record.GetInt("points")
     if err != nil {
     if err != nil {
         account.Points = 0
         account.Points = 0
     }
     }
     var nchars int
     var nchars int
-    nchars,  err            = strconv.Atoi(record["characters"])
+    nchars,  err            = record.GetInt("characters")
     if err != nil {
     if err != nil {
         nchars = 0
         nchars = 0
     } 
     } 
-    _ = nchars   
+    _ = nchars
     /* Todo: load characters here... */    
     /* Todo: load characters here... */    
     monolog.Info("Loaded Account: %s %v", path, record)
     monolog.Info("Loaded Account: %s %v", path, record)
     return account, nil
     return account, nil

+ 1 - 0
src/woe/world/being.go

@@ -3,6 +3,7 @@ package world
 import (
 import (
     "fmt"
     "fmt"
     "strings"
     "strings"
+    //"github.com/beoran/woe/sitef"
 )
 )
 
 
 /* Aptitudes of a being, species or profession */
 /* Aptitudes of a being, species or profession */

+ 70 - 0
src/woe/world/character.go

@@ -1,5 +1,12 @@
 package world
 package world
 
 
+import "fmt"
+// import "strconv"
+import "github.com/beoran/woe/monolog"
+import "github.com/beoran/woe/sitef"
+
+/* Characters may exist "outside" of any world, because, at login of a client,
+ * the Character must be loaded or created before it enters a World. */
 
 
 type Character struct {
 type Character struct {
     Being       
     Being       
@@ -8,3 +15,66 @@ type Character struct {
 }
 }
 
 
 
 
+func NewCharacter(being Being, accountname string, account * Account) (*Character) {
+    return &Character{being, accountname, account}
+}
+
+
+// Save a character as a sitef record.
+func (me * Character) SaveSitef(rec sitef.Record) (err error) {
+    rec["accountname"]  = me.AccountName
+    // TODO: saving: me.Being.SaveSitef(rec)
+    return nil
+}
+
+// Load a character from a sitef record.
+func (me * Character) LoadSitef(rec sitef.Record) (err error) {
+    me.AccountName = rec["accountname"] 
+    // TODO: load being. me.Being.SaveSitef(rec)
+    return nil
+}
+
+
+// Save a character as a sitef file.
+func (me * Character) Save(dirname string) (err error) {
+    path := SavePathFor(dirname, "character", me.Name)
+    
+    rec                := make(sitef.Record)
+    me.SaveSitef(rec)
+    monolog.Debug("Saving Character record: %s %v", path, rec)
+    return sitef.SaveRecord(path, rec)
+}
+
+// Load an character from a sitef file.
+func LoadCharacter(dirname string, name string) (character *Character, err error) {
+    
+    path := SavePathFor(dirname, "character", name)
+    
+    records, err := sitef.ParseFilename(path)
+    if err != nil {
+        return nil, err
+    }
+    
+    if len(records) < 1 {
+        return nil, fmt.Errorf("No sitef record found for %s!", name)
+    }
+    
+    record := records[0]
+    monolog.Info("Loading Account record: %s %v", path, record)
+    
+    character               = new(Character)
+    character.AccountName   = record["AccountName"]
+    account, err           := DefaultWorld.LoadAccount(dirname, character.AccountName);
+    if err != nil  {
+        return nil, err
+    } 
+    
+    if account == nil {
+        return nil, fmt.Errorf("Cound not load account %s for character %s", 
+            character.AccountName, character.Name)
+    }
+    
+    character.account = account
+    
+    return character, nil
+}

+ 61 - 27
src/woe/world/world.go

@@ -1,8 +1,8 @@
 package world
 package world
 
 
-import "os"
-import "encoding/xml"
 import "github.com/beoran/woe/monolog"
 import "github.com/beoran/woe/monolog"
+import "github.com/beoran/woe/sitef"
+import "errors"
 
 
 /* Elements of the WOE game world.  
 /* Elements of the WOE game world.  
  * Only Zones, Rooms and their Exits, Items, 
  * Only Zones, Rooms and their Exits, Items, 
@@ -29,6 +29,8 @@ type World struct {
     items                []   Item
     items                []   Item
     mobilemap       map[ID] * Mobile
     mobilemap       map[ID] * Mobile
     mobiles              []   Mobile
     mobiles              []   Mobile
+    accounts             [] * Account
+    accountmap      map[string] * Account
 }
 }
 
 
 
 
@@ -47,6 +49,7 @@ func NewWorld(name string, motd string) (*World) {
     world := new(World)
     world := new(World)
     world.Name = name
     world.Name = name
     world.MOTD = motd
     world.MOTD = motd
+    world.accountmap = make(map[string] * Account)
     
     
     world.AddWoeDefaults()
     world.AddWoeDefaults()
     return world;
     return world;
@@ -70,44 +73,75 @@ func (me * World) AddZone(zone * Zone) {
     me.AddEntity(&zone.Entity);
     me.AddEntity(&zone.Entity);
 }
 }
 
 
+// Save an account as a sitef file.
 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)
     
     
-    file, err := os.Create(path)
-    if err != nil {
-        monolog.Error("Could not load %name: %v", err)
-        return err
-    }
-    enc := xml.NewEncoder(file)
-    enc.Indent(" ", "  ")
-    res := enc.Encode(me)
-    if (res != nil) {
-        monolog.Error("Could not save %s: %v", me.Name, err)
-    }
-    return res
-}
-
-func (me * World) onLoad() {
+    rec                := make(sitef.Record)
+    rec.Put("name",         me.Name)
+    rec.Put("motd",         me.MOTD)
+    monolog.Debug("Saving World record: %s %v", path, rec)
+    return sitef.SaveRecord(path, rec)
 }
 }
 
 
-func LoadWorld(dirname string, name string) (result *World, err error) {
+// Load a world from a sitef file.
+func LoadWorld(dirname string, name string) (world * World, err error) {
+    
     path := SavePathFor(dirname, "world", name)
     path := SavePathFor(dirname, "world", name)
     
     
-    file, err := os.Open(path)
+    records, err := sitef.ParseFilename(path)
     if err != nil {
     if err != nil {
         return nil, err
         return nil, err
     }
     }
-    dec := xml.NewDecoder(file)    
-    result = new(World)
-    err = dec.Decode(result)
-    if err != nil {
-        monolog.Error("Could not load %s: %v", name, err)
-        panic(err)
+    
+    if len(records) < 1 {
+        return nil, errors.New("No record found!")
     }
     }
     
     
-    result.onLoad()
-    return result, nil
+    record := records[0]
+    monolog.Info("Loading World record: %s %v", path, record)
+    
+    world = NewWorld(record.Get("name"), record.Get("motd"))
+    monolog.Info("Loaded World: %s %v", path, world)
+    return world, nil
+}
+
+
+// Returns an acccount that has already been loaded or nil if not found
+func (me * World) GetAccount(name string) (account * Account) {
+    account, ok := me.accountmap[name];
+    if !ok {
+        return nil
+    }
+    return account
+} 
+
+// Loads an account to be used with this world. Characters will be linked.
+// If the account was already loaded, returns that in stead.
+func (me * World) LoadAccount(dirname string, name string) (account *Account, err error) {
+    account = me.GetAccount(name)
+    if (account != nil) {
+        return account, nil
+    }
+    
+    account, err = LoadAccount(dirname, name);
+    if err != nil {
+        return account, err
+    }
+    me.accountmap[account.Name] = account
+    return account, nil
+}
+
+// Removes an account from this world by name.
+func (me * World) RemoveAccount(name string) {
+    _, have := me.accountmap[name]
+    if (!have) {
+        return
+    }    
+    delete(me.accountmap, name)
 }
 }
 
 
+// Default world pointer
+var DefaultWorld * World