Ver Fonte

Switching to sitef format for compactness.

Beoran há 8 anos atrás
pai
commit
dd2e02da62

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

@@ -49,7 +49,7 @@ func NewClient(server * Server, id int, conn net.Conn) * Client {
     errchan  := make (chan error, 1)
     timechan := make (chan time.Time, 32)
     telnet   := telnet.New()
-    info     := ClientInfo{-1, -1, 0, false, false, false, false, false, false, false, false, nil, "none"}
+    info     := ClientInfo{w : -1, h : -1, terminal: "none"}
     return &Client{server, id, conn, true, -1, datachan, errchan, timechan, telnet, info, nil}
 }
 
@@ -137,6 +137,9 @@ func (me * Client) Serve() (err error) {
     go me.ServeWrite()
     go me.ServeRead()
     me.SetupTelnet()
+    if me.server.World != nil {
+        me.Printf(me.server.World.MOTD)
+    }
     if (!me.AccountDialog()) {
         time.Sleep(3); 
         // sleep so output gets flushed, hopefully. Also slow down brute force attacks.

+ 35 - 3
src/woe/server/server.go

@@ -11,6 +11,7 @@ import (
     "fmt"
     "path/filepath"
     "github.com/beoran/woe/monolog"
+    "github.com/beoran/woe/world"
 )
 
 var MSSP map[string] string
@@ -73,7 +74,8 @@ type Server struct {
     logfile             * os.File
     clients map[int]    * Client 
     tickers map[string] * Ticker
-    alive                 bool                
+    alive                 bool
+    World               * world.World             
 }
 
 
@@ -86,6 +88,34 @@ type Ticker struct {
 }
 
 
+const DEFAULT_MOTD =
+
+`
+###############################
+#       Workers Of Eruta      # 
+###############################
+
+`
+
+
+func (me * Server) SetupWorld() error {
+    /*
+    me.World, _ = world.LoadWorld(me.DataPath(), "WOE")
+    if me.World == nil {
+        monolog.Info("Creating new default world...")
+        me.World = world.NewWorld("WOE", DEFAULT_MOTD)
+        err := me.World.Save(me.DataPath())
+        if err != nil {
+            monolog.Error("Could not save world: %v", err)
+            return err
+        } else {
+            monolog.Info("Saved default world.")
+        }
+    }
+    */
+    return nil
+}
+
 
 func NewServer(address string) (server * Server, err error) {
     listener, err := net.Listen("tcp", address);
@@ -102,10 +132,12 @@ func NewServer(address string) (server * Server, err error) {
     clients := make(map[int] * Client)
     tickers := make(map[string] * Ticker)
 
-    server = &Server{address, listener, logger, logfile, clients, tickers, true}    
+    server = &Server{address, listener, logger, logfile, clients, tickers, true, nil}    
+    err = server.SetupWorld()
     server.AddDefaultTickers()
     
-    return server, nil
+    
+    return server, err
 }
 
 func (me * Server) Close() {

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

@@ -0,0 +1,5 @@
+package sitef
+
+
+// Marshallling of structs from and to sitef format
+

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

@@ -0,0 +1,351 @@
+package sitef
+
+import "os"
+import "io"
+import "strings"
+import "fmt"
+import "bytes"
+import "bufio"
+
+// Sitef format for serialization
+// Sitef is a simple text format for serializing data to
+// It's intent is to be human readable and easy to 
+// use for multi line text.
+// It is quite similar to recfiles, though not compatible because 
+// in sitef files the first character on the line determines the meaning, 
+// and there is no backslash escaping.
+//
+// Sitef is a line based syntax where the first character on the line
+// determines the meaning of the line.
+// Several lines together form a record.
+// A line that starts with # is a comment. There may be no whitespace
+// in front of the comment.
+// A newline character by itself (that is, an empty line), 
+// or a - ends a record.
+// A plus character, an escape on the previous line or a tab or 
+// a space continues a value.
+// A Continues value gets a newline inserted only when a space or tab was used.
+// + supresses the newline.
+// Anything else signifies the beginning of the next key.
+// % is allowed for special keys for recfile compatibility.  
+// However % directives are not implemented.
+// Keys may not be nested, however, you could use spaces or dots, 
+// or array indexes to emulate nexted keys. 
+// A # at the start optionally after whitespace is a comment
+// 
+
+type Record map[string]string
+
+type Error struct {
+    error   string
+    lineno  int
+}
+
+func (me Error) Error() string {
+    return fmt.Sprintf("%d: %s", me.Lineno, me.error)
+}
+
+func (me Error) Lineno() int {
+    return me.lineno
+}
+
+
+type ParserState int
+
+const (
+    PARSER_STATE_INIT   ParserState = iota
+    PARSER_STATE_KEY
+    PARSER_STATE_VALUE
+)
+
+type RecordList []Record
+
+
+func ParseReader(read io.Reader) (RecordList, error) {
+    var records     RecordList
+    var record      Record = make(Record)
+    var err         Error
+    lineno      := 0
+    scanner     := bufio.NewScanner(read)
+    var key     bytes.Buffer
+    var value   bytes.Buffer
+    
+    
+    for scanner.Scan() {
+        lineno++
+        line := scanner.Text()
+        // End of record?
+        if (len(line) < 1) || line[0] == '-' {
+            // save the record and make a new one
+            records = append(records, record) 
+            record  = make(Record)
+            // comment?
+        } else if line[0] == '#' {
+            continue; 
+            // continue value?
+        } else if line[0] == '\t' || line[0] == ' '|| line[0] == '+' {
+            
+            /* Add a newline unless + is used */
+            if (line[0] != '+') {
+                value.WriteRune('\n')          
+            }
+            
+            // continue the value, skipping the first character
+            value.WriteString(line[1:])            
+            // new key
+        } else if strings.ContainsRune(line, ':') {
+            // save the previous key/value pair if needed
+            if len(key.String()) > 0 {
+                record[key.String()] = value.String()
+            }
+            
+            key.Reset()
+            value.Reset()
+
+            parts := strings.SplitN(line, ":", 2)
+                            
+            key.WriteString(parts[0])
+            if len(parts) > 1 {
+               value.WriteString(parts[1])   
+            }            
+        // Not a key. Be lenient and assume this is a continued value.
+        } else {
+            value.WriteString(line)   
+        }
+    }
+    
+    // Append last record if needed. 
+    if len(key.String()) > 0 {
+        record[key.String()] = value.String()
+    }
+    
+    if (len(record) > 0) {
+        records = append(records, record)
+    }
+
+    
+
+    if serr := scanner.Err(); serr != nil {
+       err.lineno = lineno
+       err.error  = serr.Error() 
+       return records, err
+    }
+    
+    return records, nil
+    
+}
+
+func ParseFilename(filename string) (RecordList, error) {
+    file, err := os.Open(filename)
+    if err != nil {
+        return nil, err
+    }
+    defer file.Close()
+    return ParseReader(file)
+}
+
+func WriteField(writer io.Writer, key string, value string) {
+    replacer := strings.NewReplacer("\n", "\n\t")    
+    writer.Write([]byte(key))
+    writer.Write([]byte{':'})    
+    writer.Write([]byte(replacer.Replace(value)))
+    writer.Write([]byte{'\n'})
+}
+
+func WriteRecord(writer io.Writer, record Record) {
+    for key, value := range record {
+        WriteField(writer, key, value);
+    }
+    writer.Write([]byte{'-', '-', '-', '-', '\n'})
+}
+
+func WriteRecordList(writer io.Writer, records RecordList) {
+    for _, record := range records {
+        WriteRecord(writer, record);
+    }
+}
+
+
+func SaveRecord(filename string, record Record) (error) {
+    file, err := os.Create(filename)
+    if err != nil {
+        return err
+    }
+    defer file.Close()
+    WriteRecord(file, record)
+    return nil
+}
+
+
+
+func SaveRecordList(filename string, records RecordList) (error) {
+    file, err := os.Create(filename)
+    if err != nil {
+        return err
+    }
+    defer file.Close()
+    WriteRecordList(file, records)
+    return nil
+}
+
+
+/*
+func ParseFile(file)
+    lineno   = 0
+    results  = []
+    errors   = []
+    
+    record   = {}
+    key      = nil
+    value    = nil
+    until file.eof?
+      lineno     += 1 
+      line        = file.gets(256)
+      break if line.nil?
+      next if line.empty? 
+      // new record
+      if line[0,2] == '--' 
+        // Store last key used if any.
+        if key          
+          record[key] = value.chomp
+          key = nil
+        end  
+        results << record
+        record = {}
+      elsif line[0] == '//'
+      // Comments start with //
+      elsif line[0] == ':'
+      // a key/value pair
+      key, value  = line[1,line.size].split(':', 2)
+      record[key] = value.chomp
+      key = value = nil
+      elsif line[0, 2] == '..'
+      // end of multiline value 
+      record[key] = value.chomp
+      key = value = nil
+      elsif (line[0] == '.') && key.nil?
+      // Multiline key/value starts here (but is ignored 
+      // until .. is encountered)
+      key   = line[1, line.size]
+      key.chomp!
+      value = ""
+      // multiline value
+      elsif key
+          if line[0] == '\\'
+            // remove any escapes
+            line.slice!(0)
+          end
+          // continue the value
+          value << line
+      else
+          // Not in a key, sntax error.
+          errors << "//{lineno}: Don't know how to process line"
+      end      
+    end
+    // Store last key used if any.
+    if key      
+      record[key] = value.chomp
+    end  
+    // store last record 
+    results << record unless record.empty?
+    return results, errors
+  end  
+  
+  func load_filename(filename)
+    results, errors = nil, nil, nil;
+    file = File.open(filename, 'rt') rescue nil
+    return nil, ["Could not open //{filename}"] unless file
+    begin 
+      results, errors = parse_file(file)
+    ensure
+      file.close
+    end
+    return results, errors
+  end
+  
+  // Loads a Sitef fileas obejcts. Uses the ruby_klass atribute to load the object
+  // If that is missing, uses defklass
+  func load_objects(filename, defklass=nil)
+    results, errors = load_filename(filename)
+    p filename, results, errors
+    unless errors.nil? || errors.empty?
+      return nil, errors 
+    end
+    
+    objres = [] 
+    results.each do | result |
+      klassname = result['ruby_class'] || defklass
+      return nil unless klassname
+      klass = klassname.split('::').inject(Kernel) { |klass, name| klass.const_get(name) rescue nil } 
+      return nil unless klass
+      if klass.respond_to? :from_sitef
+        objres << klass.from_sitef(result)
+      else
+        objres << klass.new(result)
+      end      
+    end
+    return objres, errors    
+  end
+  
+  
+  // Saves a single field to a file in Sitef format.
+  func save_field(file, key, value)
+    if value.is_a? String
+      sval = value.dup
+    else
+      sval = value.to_s
+    end
+    if sval["\n"]
+      file.puts(".//{key}\n")
+      // Escape everything that could be misinterpreted with a \\
+      sval.gsub!(/\n([\.\-\:\//\\]+)/, "\n\\\\\\1")
+      sval.gsub!(/\A([\.\-\:\//\\]+)/, "\\\\\\1")
+      file.printf("%s", sval)
+      file.printf("\n..\n")
+    else
+      file.printf("://{key}://{sval}\n")
+    end
+  end
+  
+  func save_object(file, object, *fields)
+    save_field(file, :ruby_class, object.class.to_s)
+    fields.each do | field |
+      value = object.send(field.to_sym)
+      save_field(file, field, value)
+    end
+  end
+  
+  func save_record(file, record, *fields)
+    record.each do | key, value |
+      next if fields && !fields.empty? && !fields.member?(key)
+      save_field(file, key, value)
+    end
+  end
+
+  func save_file(file, records, *fields)
+    records.each do | record |
+      if record.is_a? Hash
+        save_record(file, record, *fields)
+      else 
+        save_object(file, record, *fields)
+      end
+      file.puts("--\n")
+    end
+  end
+  
+  func save_filename(filename, records, *fields)
+    results , errors = nil, nil
+    file = File.open(filename, 'wt')
+    return false, ["Could not open //{filename}"] unless file
+    begin 
+      save_file(file, records, *fields)
+    ensure
+      file.close
+    end
+    return true, []
+  end
+  
+end
+
+*/
+

+ 95 - 9
src/woe/world/account.go

@@ -3,6 +3,12 @@ package world
 import "path/filepath"
 import "os"
 import "encoding/xml"
+import "github.com/beoran/woe/sitef"
+import "github.com/beoran/woe/monolog"
+import "fmt"
+import "errors"
+import "strconv"
+
 
 
 type Named struct {
@@ -12,33 +18,113 @@ type Named struct {
 
 type Account struct {
     Name              string
-    PasswordHash      string
-    PasswordAlgo      string
+    Hash              string
+    Algo              string
     Email             string
-    WoePoints         int
+    Points            int
     CharacterNames  []string
+    characters      [] * Character
 }
 
-func SavePathFor(dirname string, typename string, name string) string {
+func SavePathForXML(dirname string, typename string, name string) string {
     return filepath.Join(dirname, typename, name + ".xml")
 } 
 
+func SavePathFor(dirname string, typename string, name string) string {
+    return filepath.Join(dirname, typename, name + ".sitef")
+}
+
+
+
 func NewAccount(name string, pass string, email string, points int) (*Account) {
-    return &Account{name, pass, "plain", email, points, nil}
+    return &Account{name, pass, "plain", email, points, nil, nil}
 }
 
 // Password Challenge for an account.
 func (me * Account) Challenge(challenge string) bool {
-    if me.PasswordAlgo == "plain" {
-        return me.PasswordHash == challenge
+    if me.Algo == "plain" {
+        return me.Hash == challenge
     }
     // XXX implement encryption later
     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) {
+
+}
+
+
+// Save an account as a sitef file.
 func (me * Account) Save(dirname string) (err error) {
     path := SavePathFor(dirname, "account", me.Name)
     
+    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))
+    for i, chara   := range me.characters {
+        key        := fmt.Sprintf("characters[%d]", i)
+        rec[key]    = chara.Name
+    }
+    monolog.Debug("Saving Acccount record: %s %v", path, rec)
+    return sitef.SaveRecord(path, rec)
+}
+
+// Load an account from a sitef file.
+func LoadAccount(dirname string, name string) (account *Account, err error) {
+    
+    path := SavePathFor(dirname, "account", name)
+    
+    records, err := sitef.ParseFilename(path)
+    if err != nil {
+        return nil, err
+    }
+    
+    if len(records) < 1 {
+        return nil, errors.New("No record found!")
+    }
+    
+    record := records[0]
+    monolog.Info("Loading Account record: %s %v", path, record)
+    
+    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"])    
+    if err != nil {
+        account.Points = 0
+    }
+    var nchars int
+    nchars,  err            = strconv.Atoi(record["characters"])
+    if err != nil {
+        nchars = 0
+    } 
+    _ = nchars   
+    /* Todo: load characters here... */    
+    monolog.Info("Loaded Account: %s %v", path, record)
+    return account, nil
+}
+
+ 
+
+func (me * Account) SaveXML(dirname string) (err error) {
+    path := SavePathForXML(dirname, "account", me.Name)
+    
     file, err := os.Create(path)
     if err != nil {
         return err
@@ -48,8 +134,8 @@ func (me * Account) Save(dirname string) (err error) {
     return enc.Encode(me)
 }
 
-func LoadAccount(dirname string, name string) (account *Account, err error) {
-    path := SavePathFor(dirname, "account", name)
+func LoadAccountXML(dirname string, name string) (account *Account, err error) {
+    path := SavePathForXML(dirname, "account", name)
     
     file, err := os.Open(path)
     if err != nil {

+ 18 - 0
src/woe/world/art.go

@@ -0,0 +1,18 @@
+package world
+
+type Art struct {
+    // An art is a special case of technique that requires Essence, 
+    // and consumes JP in stead of MP
+    Technique
+}
+
+
+type BeingArt struct {
+    being       * Being
+    art         * Art
+    Experience    int
+    Level         int
+}
+
+
+

+ 180 - 83
src/woe/world/being.go

@@ -5,80 +5,144 @@ import (
     "strings"
 )
 
-type BeingKind int
-
-const (
-    BEING_KIND_NONE BeingKind = iota
-    BEING_KIND_CHARACTER
-    BEING_KIND_MANTUH
-    BEING_KIND_NEOMAN
-    BEING_KIND_HUMAN    
-    BEING_KIND_CYBORG
-    BEING_KIND_ANDROID    
-    BEING_KIND_NONCHARACTER
-    BEING_KIND_MAVERICK
-    BEING_KIND_ROBOT
-    BEING_KIND_BEAST
-    BEING_KIND_SLIME
-    BEING_KIND_BIRD
-    BEING_KIND_REPTILE
-    BEING_KIND_FISH
-    BEING_KIND_CORRUPT    
-    BEING_KIND_NONHUMAN
-)
+/* Aptitudes of a being, species or profession */
+type Aptitudes struct {
+    Skills      []BeingSkill
+    Arts        []BeingArt
+    Techniques  []BeingTechnique
+    Exploits    []BeingExploit
+}
 
-func (me BeingKind) ToString() string {
-    switch me {
-        case BEING_KIND_NONE:       return "None"
-        case BEING_KIND_CHARACTER:  return "Character"
-        case BEING_KIND_MANTUH:     return "Mantuh"
-        case BEING_KIND_NEOMAN:     return "Neoman"
-        case BEING_KIND_HUMAN:      return "Human"    
-        case BEING_KIND_CYBORG:     return "Cyborg"
-        case BEING_KIND_ANDROID:    return "Android"
-        case BEING_KIND_NONCHARACTER: return "Non Character"
-        case BEING_KIND_MAVERICK:   return "Maverick"
-        case BEING_KIND_ROBOT:      return "Robot"
-        case BEING_KIND_BEAST:      return "Beast"
-        case BEING_KIND_SLIME:      return "Slime"
-        case BEING_KIND_BIRD:       return "Bird"
-        case BEING_KIND_REPTILE:    return "Reptile"
-        case BEING_KIND_FISH:       return "Fish"
-        case BEING_KIND_CORRUPT:    return "Corrupted"
-        default: return ""
-    }
-    return ""
+/* Kind of a being*/
+type BeingKind struct {
+    Entity
+    // Talent modifiers of the species
+    Talents
+    // Vitals modifiers of the species
+    Vitals
+    Aptitudes 
+    // Arts multiplier in %. If zero arts cannot be used.
+    Arts        float64
+    // If players can choose this or not
+    Playable    bool
 }
 
 
-type BeingProfession int
+func NewBeingKind(id ID, name string) * BeingKind {
+    res := new(BeingKind)
+    res.ID = id;
+    res.Name = name
+    return res
+}
 
-const (
-    BEING_PROFESSION_NONE       BeingProfession = iota
-    BEING_PROFESSION_OFFICER
-    BEING_PROFESSION_WORKER
-    BEING_PROFESSION_ENGINEER
-    BEING_PROFESSION_HUNTER
-    BEING_PROFESSION_SCHOLAR
-    BEING_PROFESSION_MEDIC
-    BEING_PROFESSION_CLERIC  
-    BEING_PROFESSION_ROGUE
-)
+/* Profession of a being */
+type Profession struct {
+    Entity
+    // Talent modifiers of the profession
+    Talents
+    // Vitals modifiers of the profession
+    Vitals
+    Aptitudes    
+}
+
+
+/* Gender of a being */
+type Gender struct {
+    Entity
+    // Talent modifiers of the gender
+    Talents
+    // Vitals modifiers of the gender
+    Vitals
+}
+
+/* Struct, list and map of all genders in WOE. */
+var Genders = struct {
+    Female          Gender
+    Male            Gender
+    Intersex        Gender 
+    Genderless      Gender
+}{
+    Gender {
+    Entity : Entity{ ID: "female", Name: "Female"},
+    Talents : Talents { Agility: 1, Charisma: 1 }, 
+    }, 
+
+    Gender { 
+    Entity : Entity{ ID: "male", Name: "Male"},
+    Talents : Talents { Strength: 1, Intelligence: 1 },
+    }, 
+
+    Gender {
+    Entity : Entity{ ID: "intersex", Name: "Intersex"},
+    Talents : Talents { Dexterity: 1, Wisdom: 1 }, 
+    },
+
+    Gender {
+    Entity : Entity{ ID: "genderless", Name: "Genderless"},
+    Talents : Talents { Toughness: 1, Wisdom: 1 }, 
+    },
+    
+}
+
+
+var GenderList  = []*Gender{&Genders.Female, &Genders.Male, 
+    &Genders.Intersex, &Genders.Genderless }
 
-func (me BeingProfession) ToString() string {
-    return ""
+var GenderMap = map[ID]*Gender {
+    Genders.Female.ID       : &Genders.Female,
+    Genders.Male.ID         : &Genders.Male,
+    Genders.Intersex.ID     : &Genders.Intersex,
+    Genders.Genderless.ID   : &Genders.Genderless,
 }
 
 
+
+/* All BeingKinds of  WOE */
+var BeingKindList = [] BeingKind {  
+    {   Entity: Entity {
+            ID: "kin_", Name: "Human",
+            Short: "The primordial conscious beings on Earth.",
+            Long: 
+            `Humans are the primordial kind of conscious beings on Earth. The excel at nothing in particular, but are fast learners.`,      
+        },
+        // No talents because humans get no talent bonuses
+        // No stats either because no bonuses there either.
+        Arts : 1.0,
+        Playable : true,
+    },
+}
+
+
+
+
+
+
+
+
+
+
+
+type LabeledPointer struct {
+    ID ID
+    labeled * Labeled
+}
+
+type GenderPointer struct {
+    ID ID
+    gender * Gender
+}
+
+//}
+
 /* Vital statistic of a Being. */
 type Vital struct {
-    Now int
-    Max int
+    Now int `xml:"N,attr"`
+    Max int `xml:"X,attr"`
 }
 
 /* Report a vital statistic as a Now/Max string */
 func (me * Vital) ToNowMax() string {
-    return fmt.Sprintf("%d/%d", me.Now , me.Max)
+    return fmt.Sprintf("%4d/%4d", me.Now , me.Max)
 }
 
 // alias of the above, since I'm lazy at times
@@ -101,39 +165,52 @@ func (me * Vital) ToBar(full string, empty string, length int) string {
 }
 
 
+type Talents struct {
+    Strength        int     `xml:"Talents>STR,omitempty"`
+    Toughness       int     `xml:"Talents>TOU,omitempty"`
+    Agility         int     `xml:"Talents>AGI,omitempty"`
+    Dexterity       int     `xml:"Talents>DEX,omitempty"`
+    Intelligence    int     `xml:"Talents>INT,omitempty"`
+    Wisdom          int     `xml:"Talents>WIS,omitempty"`
+    Charisma        int     `xml:"Talents>CHA,omitempty"`
+    Essence         int     `xml:"Talents>ESS,omitempty"`
+}
+  
 
-type Being struct {
-    Entity
-    
-    // Essentials
-    Kind            BeingKind
-    Profession      BeingProfession
-    Level           int
-    
-    
-    // Talents
-    Strength        int
-    Toughness       int
-    Agility         int
-    Dexterity       int
-    Intelligence    int
-    Wisdom          int
-    Charisma        int
-    Essence         int
-        
-    // Vitals
+type Vitals struct {
     HP              Vital
     MP              Vital
     JP              Vital
     LP              Vital
-        
-    // Equipment values
+}
+
+type EquipmentValues struct {
     Offense         int
     Protection      int
     Block           int
     Rapidity        int
     Yield           int
+}
+
 
+type Being struct {
+    Entity
+    
+    // Essentials
+    Gender          
+    BeingKind    
+    Profession
+    Level           int
+    
+    // A being has talents.
+    Talents
+    // A being has vitals
+    Vitals
+    // A being has Equipment values
+    EquipmentValues
+    // A being has aptitudes
+    Aptitudes
+    
     // Skills array
     // Skills       []Skill
        
@@ -146,7 +223,11 @@ type Being struct {
     // Equipment
     
     // Inventory
-    // Inventory 
+    Inventory         Inventory
+    
+    // Location pointer
+    room            * Room
+    
 }
 
 // Derived stats 
@@ -196,7 +277,7 @@ func (me * Being) ToPrompt() string {
 
 // Generates an overview of the essentials of the being as a string.
 func (me * Being) ToEssentials() string {
-    return fmt.Sprintf("%s %d %s %s", me.Name, me.Level, me.Kind, me.Profession)
+    return fmt.Sprintf("%s lvl %d %s %s %s", me.Name, me.Level, me.Gender.Name, me.BeingKind.Name, me.Profession.Name)
 }
 
 // Generates an overview of the physical talents of the being as a string.
@@ -225,5 +306,21 @@ func (me * Being) ToStatus() string {
       return status
 }
 
+func (me *Being) Type() string {
+    return "being"
+}
+
+func (me *Being) Save(datadir string) {
+    SaveSavable(datadir, me)
+}
+
+func LoadBeing(datadir string, nameid string) * Being {    
+    res, _  := LoadLoadable(datadir, nameid, new(Being)).(*Being)
+    return res
+}
+
+
+
+
 
 

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

@@ -4,6 +4,7 @@ package world
 type Character struct {
     Being       
     AccountName string
+    account   * Account
 }
 
 

+ 99 - 5
src/woe/world/entity.go

@@ -1,12 +1,106 @@
 package world
+import "os"
+import "encoding/xml"
+
+
+
+
+// Anything insdie the WOE World can be identified by a unique short string 
+// description, the Label
+type Labeled interface {
+    Label() string // Returns a unique label of the thing.
+}
+
+type Typed interface {
+    Type() string // Returns a tring description of the type of the thing. 
+}
 
 
 // An entity is anything that can exist in a World
 type Entity struct {
-    ID                  string
-    Name                string
-    ShortDescription    string
-    Description         string
-    Aliases           []string
+    ID                  ID          `xml:"id,attr"`
+    Name                string      `xml:"name,attr"`
+    Short               string      `xml:"short,attr"`
+    Long                string
+    Aliases           []string          
+}
+
+func (me * Entity) Label() string {
+    return string(me.ID)
+}
+
+
+// Interface 
+type Savable interface {
+    Labeled
+    Typed
+}
+
+type Loadable interface {    
+    Typed
+}
+
+func SaveSavable(dirname string, savable Savable) (err error) {
+    path := SavePathFor(dirname, savable.Type(), savable.Label())
+    
+    file, err := os.Create(path)
+    if err != nil {
+        return err
+    }
+    enc := xml.NewEncoder(file)
+    enc.Indent(" ", "  ")
+    return enc.Encode(savable)
+}
+
+func LoadLoadable(dirname string, nameid string, result Loadable) (Loadable) {
+    path := SavePathFor(dirname, result.Type(), nameid)
+    
+    file, err := os.Open(path)
+    if err != nil {
+        return nil
+    }
+    dec := xml.NewDecoder(file)    
+    err = dec.Decode(result)
+    if err != nil { return nil }
+    return result
 }
 
+// A list of Identifier items mapped to their ID's 
+type LabeledLister interface {
+    Get(ID)         Labeled
+    Put(Labeled)
+    Size()          int
+    Index(int)      Labeled
+    PutIndex(int)
+}
+
+type LabeledList struct {
+    byList        []Labeled
+    byLabel       map[ID] Labeled
+}
+
+func NewLabeledList() * LabeledList {
+    byname := make(map[ID] Labeled)
+    return &LabeledList{nil, byname}
+}
+
+func (me * LabeledList) Get(id ID) Labeled {
+    val, ok := me.byLabel[id]
+    if !ok { return nil }
+    return val
+}
+
+func (me * LabeledList) Index(index int) Labeled {
+    if index < 0 { return nil } 
+    if index > len(me.byList) { return nil }
+    val := me.byList[index]
+    return val
+}
+
+
+
+
+
+
+
+

+ 9 - 0
src/woe/world/inventory.go

@@ -1 +1,10 @@
 package world
+
+
+
+type Inventory struct {
+    ItemIDS []int
+    items   []Item   
+}
+
+

+ 17 - 0
src/woe/world/item.go

@@ -1 +1,18 @@
 package world
+
+type Item struct {
+    Entity
+    Quality     int
+    Price       int
+    Type        int
+    Equippable  int
+}
+
+type ItemPointer struct {
+    ID     ID
+    item * Item
+}
+
+
+
+

+ 6 - 0
src/woe/world/mobile.go

@@ -1 +1,7 @@
 package world
+
+
+type Mobile struct {
+    Being
+}
+

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

@@ -1 +1,16 @@
 package world
+
+
+type Direction  string
+
+type Exit struct {
+    Direction 
+    ToRoomID    int
+    toRoom    * Room         
+}
+
+type Room struct {
+    Entity
+    Exits   map[Direction]Exit
+}
+

+ 17 - 0
src/woe/world/skill.go

@@ -0,0 +1,17 @@
+package world
+
+type Skill struct {
+    Entity
+    Kind              int
+    
+    derived           func (m * Being)  int 
+    talent          * int 
+}
+
+type BeingSkill struct {
+    being       * Being
+    technique   * Skill
+    Experience    int
+    Level         int
+}
+

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

@@ -0,0 +1,41 @@
+package world
+
+
+type Technique struct {
+    Entity
+    Kind        int
+    Effect      int
+    Level       int
+    Cost        int
+    SkillID     int
+    skill     * Skill
+    onUse       func (me * Technique, caster * Being, targets ...*Being) (bool)
+}
+
+
+type BeingTechnique struct {
+    being       * Being
+    technique   * Technique
+    Experience    int
+    Level         int
+}
+
+/* An exploit is a special technique that can only be used a few times 
+ * per day.
+ */
+type Exploit struct {
+    // it's a special case of a technique
+    Technique
+}
+
+type BeingExploit struct {
+    being       * Being
+    exploit     * Exploit
+    Experience    int
+    Level         int
+    // How many times the exploit may be used.
+    Uses          Vital
+}
+
+
+

+ 147 - 0
src/woe/world/world.go

@@ -1,8 +1,155 @@
 package world
 
+import "os"
+import "encoding/xml"
+import "github.com/beoran/woe/monolog"
+
+/* Ekements of the WOE game world.  
+ * Only Zones, Rooms and their Exits, Items, 
+ * Mobiles & Characters are saved
+ * and loaded from disk. All the rest 
+ * is kept statically delared in code for simplocoty.
+*/
+
+/* ID used for anything in a world but the world itself and the account. */
+type ID string 
+
 
 type World struct {
+    Name                 string
+    ZoneIDS         []   ID
+    zones           [] * Zone
+    CharacterIDS    []   ID
+    characters      [] * Character
+    MOTD                 string
     
+    /* Skills, etc that exist in this world */
+    genders         map[ID] *Gender    
+    species         map[ID] *BeingKind
+    professions     map[ID] *Profession
+    skills          map[ID] *Skill
+    arts            map[ID] *Art
+    techniques      map[ID] *Technique
+    exploits        map[ID] *Exploit
+    
+    /* Botha array and map are needed for serialization. */
+    Genders         [] Gender
+    Species         [] BeingKind
+    Professions     [] Profession
+    Skills          [] Skill
+    Arts            [] Art
+    Techniques      [] Technique
+    Exploits        [] Exploit
+        
+}
+
+
+
+func (me * World) AddBeingKind(toadd * BeingKind) {
+    me.species[toadd.ID] = toadd
+    me.Species = append(me.Species, *toadd)
+}
+
+func (me * World) AddProfession(toadd * Profession) {
+    me.professions[toadd.ID] = toadd
+    me.Professions = append(me.Professions, *toadd)
 }
 
+func (me * World) AddSkill(toadd * Skill) {
+    me.skills[toadd.ID] = toadd
+    me.Skills = append(me.Skills, *toadd)
+}
+
+func (me * World) AddArt(toadd * Art) {
+    me.arts[toadd.ID] = toadd
+    me.Arts = append(me.Arts, *toadd)
+}
+
+func (me * World) AddTechnique(toadd * Technique) {
+    me.techniques[toadd.ID] = toadd
+    me.Techniques = append(me.Techniques, *toadd)
+}
+
+func (me * World) AddExploit(toadd * Exploit) {
+    me.exploits[toadd.ID] = toadd
+    me.Exploits = append(me.Exploits, *toadd)
+}
+
+
+func (me * World) AddWoeDefaults() {
+    /*
+    me.AddSpecies(NewSpecies("sp_human"  , "Human"))
+    me.AddSpecies(NewSpecies("sp_neosa"  , "Neosa"))
+    me.AddSpecies(NewSpecies("sp_mantu"  , "Mantu"))
+    me.AddSpecies(NewSpecies("sp_cyborg" , "Cyborg"))
+    me.AddSpecies(NewSpecies("sp_android", "Android"))
+    */
+}
+
+func NewWorld(name string, motd string) (*World) {
+    world := new(World)
+    world.Name = name
+    world.MOTD = motd
+    world.species = make(map[ID] *BeingKind)
+    
+    world.AddWoeDefaults()
+    return world;
+}
+
+
+func HaveID(ids [] ID, id ID) bool {
+    for index := 0 ; index < len(ids) ; index++ {
+        if ids[index] == id { return true }  
+    }
+    return false
+}
+
+func (me * World) AddZone(zone * Zone) {
+    me.zones = append(me.zones, zone)
+    if (!HaveID(me.ZoneIDS, zone.ID)) {
+        me.ZoneIDS = append(me.ZoneIDS, zone.ID)
+    }    
+}
+
+func (me * World) Save(dirname string) (err error) {
+    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() {
+    for _, v := range me.Species {me.species[v.ID] = &v }
+}
+
+func LoadWorld(dirname string, name string) (result *World, err error) {
+    path := SavePathFor(dirname, "world", name)
+    
+    file, err := os.Open(path)
+    if err != nil {
+        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)
+    }
+    
+    result.onLoad()
+    return result, nil
+}
+
+
 

+ 9 - 0
src/woe/world/zone.go

@@ -1 +1,10 @@
 package world
+
+type Zone struct {
+    Entity
+    RoomIDS []int
+    rooms   []Room
+}
+
+
+