// Helper function to create a user (license) and an avatar for that user func CreateUser(str string) { args := strings.Split(str, ",") if len(args) != 3 && len(args) != 4 { fmt.Println("Usage: server -createuser=email,password,avatar[,licensekey]") return } var up user up.New_WLwWLc(args[2]) up.Email = args[0] up.License, up.Password = license.Make(args[1], "") up.License = args[3] // Override c := ephenationdb.New().C("counters") var id struct { C uint32 } change := mgo.Change{ Update: bson.M{"$inc": bson.M{"c": 1}}, } _, err := c.FindId("avatarId").Apply(change, &id) if err != nil { fmt.Println("Failed to update unique counter 'avatarId' in collection 'counter'", err) return } up.Id = id.C db := ephenationdb.New() err = db.C("avatars").Insert(&up) if err != nil { log.Println("Save", up.Name, err) return } fmt.Println("Created avatar number", up.Id, ":", up.Name) }
// Some rudimentary tests to get SQL connections func DoTestSQL() bool { db := ephenationdb.New() DoTestCheck("DoTestSQL Got connection", db != nil) if db == nil { // Cancel the other tests return false } ephenationdb.Release(db) db2 := ephenationdb.New() DoTestCheck("DoTestSQL Got same (cached) connection again", db2 == db) db3 := ephenationdb.New() DoTestCheck("DoTestSQL Got new connection", db3 != db2 && db3 != nil) ephenationdb.Release(db3) ephenationdb.Release(db2) return true }
func main() { if !*logOnStdout { logFile, _ := os.OpenFile(*logFileName, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666) log.SetOutput(logFile) } log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) cnfg, err := config.ReadDefault(*configFileName) if err != nil { log.Println("Fail to find", *configFileName, err) return } configSection := "db" if cnfg.HasSection(configSection) { f := func(key string) string { value, err := cnfg.String(configSection, key) if err != nil { log.Println("Config file", *configFileName, "Failt to find key", key, err) return "" } return value } err = ephenationdb.SetConnection(f) if err != nil { log.Println("main: open DB:", err) return } } else { log.Println("Config file", configFileName, "missing, or no section 'db'") } db := ephenationdb.New() chunkdata(db.C("chunkdata")) }
// Return true if ok func (up *user) Load_WLwBlWLc(email string) bool { // Connect to database db := ephenationdb.New() if db == nil { log.Println("No DB cpnnection", email) return false } err := db.C("avatars").Find(bson.M{"email": email}).One(&up.UserLoad) if err != nil { log.Println("Avatar for", email, err) return false } // Some post processing if up.Maxchunks == 0 { // This parameter was not initialized. up.Maxchunks = CnfgMaxOwnChunk } up.logonTimer = time.Now() if up.ReviveSP.X == 0 && up.ReviveSP.Y == 0 && up.ReviveSP.Z == 0 { // Check if there is any spawn point defined. up.ReviveSP = up.Coord up.HomeSP = up.Coord } score.Initialize(up.Id) return true }
// Save the allocated chunks for the specified avatar func SaveAvatar_Bl(avatar uint32, chunks []CC) bool { if *disableSave { // Ignore the save, pretend everything is fine return true } db = ephenationdb.New() if db == nil { return false } defer ephenationdb.Release(db) // First delete the current allocation, and replace with the new query := fmt.Sprintf("DELETE FROM chunkdata WHERE avatarID=%d", avatar) err := db.Query(query) if err != nil { log.Println(err) return false } if len(chunks) == 0 { return true } query = "INSERT INTO chunkdata (x,y,z,avatarID) VALUES " comma := "" for _, ch := range chunks { query += fmt.Sprintf("%s(%d,%d,%d,%d)", comma, ch.X, ch.Y, ch.Z, avatar) comma = "," } err = db.Query(query) if err != nil { log.Println(err) return false } return true }
// Iterate over the copy of all scores, and save to SQL DB where needed. func update(list []*territoryScore) { if *disableSQL { return } db := ephenationdb.New() if db == nil { return // Give it up for this time } defer ephenationdb.Release(db) now := time.Now() for _, ts := range list { if !ts.initialized { log.Println("score.update initialize from DB for", ts.uid) loadFromSQL(db, ts) } if !ts.modified { continue } oldScore := ts.Score oldBalance := ts.ScoreBalance ts.decay(&now) if ts.initialized { // Normally, it is initialized, but stay on the safe side if there was a problem. saveToSQL(db, ts) log.Printf("score.update %s to %.1f (diff %f), Balance %.1f (diff %f)\n", ts.name, ts.Score, ts.Score-oldScore, ts.ScoreBalance, ts.ScoreBalance-oldBalance) } } }
// Load DB score data for territory owned by 'uid' into 'ts'. func loadFromSQL(ts *territoryScore, uid uint32) { var avatarScore struct { TScoreTotal, TScoreBalance float64 TScoreTime uint32 Name string Territory []chunkdb.CC // The chunks allocated for this player. } db := ephenationdb.New() query := db.C("avatars").FindId(uid) err := query.One(&avatarScore) if err != nil { log.Println(err) return } ts.uid = uid ts.handicap = computeFactor(len(avatarScore.Territory)) ts.Score = avatarScore.TScoreTotal ts.ScoreBalance = avatarScore.TScoreBalance ts.TimeStamp = time.Unix(int64(avatarScore.TScoreTime), 0) ts.name = avatarScore.Name ts.modified = true now := time.Now() ts.decay(&now) // Update the decay }
// #258 update user last seen func (lp *License) SaveLogonTime_Bl() { start := time.Now() db := ephenationdb.New() if db == nil { log.Println("license.SaveLogonTime: Failed to save logon time") return } defer ephenationdb.Release(db) // Defer database close when we are sure that the db is open // Update last seen online --> Moved from Save() due to issue #87: LastSeen not updated in database now := time.Now() nowstring := fmt.Sprintf("%4v-%02v-%02v", now.Year(), int(now.Month()), now.Day()) query := "UPDATE users SET lastseen='" + nowstring + "' WHERE email='" + lp.mail + "'" //fmt.Printf("%v\n",query) if *verboseFlag > 0 { log.Println(query) } err := db.Query(query) if err != nil { log.Println(err) return } elapsed := time.Now().Sub(start) // if elapsed > UPPER_TIME_LIMIT && *verboseFlag > 0 { log.Printf("license.SaveLogonTime %d ms\n", elapsed/1e6) // } return }
// Check the password of the player. // Return false if connection shall be disonnected func (up *user) CmdPassword_WLwWLuWLqBlWLc(encrPass []byte) bool { // The password is given by the client as an encrypted byte vector. // fmt.Printf("CmdPassword: New player encr passwd%v\n", encrPass) // Decrypt the password using the full license key. cipher, err := rc4.NewCipher(xorVector([]byte(up.License), up.challenge)) if err != nil { log.Printf("CmdPassword: NewCipher1 returned %v\n", err) return false } passw := make([]byte, len(encrPass)) cipher.XORKeyStream(passw, encrPass) // fmt.Printf("CmdPassword: Decrypted password is %#v\n", string(passw)) if !license.VerifyPassword(string(passw), up.Password, encryptionSalt) { // fmt.Println("CmdPassword: stored password doesn't match the given") // CmdLogin_WLwWLuWLqBlWLc(up.Name, index) if *verboseFlag > 0 { log.Println("Terminate because of bad password") } return false } // Save player logon time up.Lastseen = time.Now() db := ephenationdb.New() err = db.C("avatars").UpdateId(up.Id, bson.M{"$set": bson.M{"lastseen": up.Lastseen}}) if err != nil { log.Println("Update lastseen", err) } up.loginAck_WLuWLqBlWLa() return true }
// Find the list of all chunks allocated for an avatar // Return false in case of failure func ReadAvatar_Bl(avatarID uint32) ([]CC, bool) { db = ephenationdb.New() if db == nil { log.Println("chunkdb.ReadAvatar_Bl failed") return nil, false } defer ephenationdb.Release(db) // Build a query for the given chunk coordinate as an argument query := fmt.Sprintf("SELECT * FROM chunkdata WHERE avatarID=%d", avatarID) err := db.Query(query) if err != nil { // Fatal error log.Println(err) os.Exit(1) } // Store the result result, err := db.StoreResult() if err != nil { log.Println(err) return nil, false } // Fetch all rows rows := result.FetchRows() numRows := result.RowCount() FieldNames := result.FetchFields() // fmt.Printf("chunkdb.ReadAvatar rows: %v. numRows: %v\n", rows, numRows) if rows == nil || numRows == 0 { db.FreeResult() return nil, true } ret := make([]CC, numRows) for r, row := range rows { cnt := int(result.FieldCount()) for i := 0; i < cnt; i++ { switch FieldNames[i].Name { case "x": ret[r].X = int32(row[i].(int64)) case "y": ret[r].Y = int32(row[i].(int64)) case "z": ret[r].Z = int32(row[i].(int64)) } } } db.FreeResult() // fmt.Printf("chunkdb.ReadAvatar: Avatar %d (%d,%d,%d)\n", avatarID, x, y, z) return ret, true }
// Iterate over the copy of the list of all scores, and save to database where needed. // The map could be used here, in which case a lock would be required. But DB access take // a long time. func update(list []*territoryScore) { db := ephenationdb.New() now := time.Now() for _, ts := range list { if !ts.modified { continue } // The decay isn't executed unless there has been a change, to save from unnecessary DB updates. ts.decay(&now) saveToSQL(db, ts) } }
func (lp *License) Save_Bl() bool { start := time.Now() db := ephenationdb.New() if db == nil { return false } defer ephenationdb.Release(db) // Defer database close when we are sure that the db is open // Check if the user exists query := "SELECT * FROM users WHERE email='" + lp.mail + "'" err := db.Query(query) if err != nil { log.Println(err) return false } // Store the result result, err := db.StoreResult() if err != nil { log.Println(err) return false } if result.RowCount() == 0 { db.FreeResult() query = "INSERT INTO users SET email='" + lp.mail + "', password='******', isvalidated=1, " query = query + "licensekey='" + lp.License + "'" err = db.Query(query) } else { db.FreeResult() // Update password // TODO: check if password has changed? Add parameter in struct to know if changed or not? query = "UPDATE users SET password='******' WHERE email='" + lp.mail + "'" err = db.Query(query) if err != nil { log.Println(err) return false } } elapsed := time.Now().Sub(start) // if elapsed > UPPER_TIME_LIMIT && *verboseFlag > 0 { log.Printf("license.Save %d ms\n", elapsed/1e6) // } return true }
// This is the main polling function, meant to be executed for ever as a go routine. func Poll_Bl() { if *disableSave { log.Println("Saving to 'chunkdata' is disabled") } db = ephenationdb.New() if db == nil { log.Println("chunkdb.Poll_Bl requires access to SQL DB") return } for { req, ok := <-ch if !ok { log.Printf("Failed to read from channel\n") os.Exit(1) } avatarId := readChunk_Bl(req.chunk) req.cb(req.data, avatarId) } }
// Return true if the save succeeded. func (up *user) Save_Bl() bool { start := time.Now() up.Lastseen = start // Update last seen online up.TimeOnline += uint32(start.Sub(up.logonTimer) / time.Second) // Update total time online, in seconds up.logonTimer = start db := ephenationdb.New() err := db.C("avatars").UpdateId(up.Id, bson.M{"$set": &up.player}) // Only update the fields found in 'pl'. if err != nil { log.Println("Save", up.Name, err) log.Printf("%#v\n", up) return false } if *verboseFlag > 1 { log.Printf("up.Save_Bl saved %v\n", up.Name) } elapsed := time.Now().Sub(start) if *verboseFlag > 0 { log.Printf("up.Save_Bl elapsed %d ms\n", elapsed/1e6) } return true }
// TODO: Improve error handling // Return true if the save succeeded. func (pl *player) Save_Bl() bool { // Connect to database start := time.Now() db := ephenationdb.New() if db == nil { return false } // Do some preprocessing b, err := json.Marshal(pl) if err != nil { log.Printf("Marshal %s returned %v\n", pl.name, err) } inventory, err := pl.inventory.Serialize() // Update last seen online now := time.Now() nowstring := fmt.Sprintf("%4v-%02v-%02v", now.Year(), int(now.Month()), now.Day()) // Update total time online, in seconds TempTimer := time.Now() pl.timeOnline += uint32(TempTimer.Sub(pl.logonTimer) / time.Second) //fmt.Printf("User has been online %v seconds\n", TempTimer - pl.logonTimer) pl.logonTimer = TempTimer // Use the stored value to avoid mismatch // Write data on alternative format // This section makes the following assumptions: // - Avatar name cannot be changed from the server // - Avatar looks (head/body) cannot be changed from the server TODO: THIS WILL EVENTUALLY NOT BE TRUE! // Booleans doesn't work, transform them to numbers query := "UPDATE avatars SET jsonstring=?,PositionX=?,PositionY=?,PositionZ=?,isFlying=?,isClimbing=?,isDead=?,DirHor=?,DirVert=?,AdminLevel=?" + ",Level=?,Experience=?,HitPoints=?,Mana=?,Kills=?,HomeX=?,HomeY=?,HomeZ=?,ReviveX=?,ReviveY=?,ReviveZ=?" + ",maxchunks=?,BlocksAdded=?,BlocksRemoved=?,TimeOnline=?,HeadType=?,BodyType=?,lastseen=?,inventory=?,TargetX=?,TargetY=?,TargetZ=?" + " WHERE name='" + pl.name + "'" stmt, err := db.Prepare(query) if err != nil { log.Println(err) return false } stmt.BindParams(b, pl.coord.X, pl.coord.Y, pl.coord.Z, Q(pl.flying), Q(pl.climbing), Q(pl.dead), pl.dirHor, pl.dirVert, pl.adminLevel, pl.level, pl.exp, pl.hitPoints, pl.mana, pl.numKill, pl.homeSP.X, pl.homeSP.Y, pl.homeSP.Z, pl.reviveSP.X, pl.reviveSP.Y, pl.reviveSP.Z, pl.maxchunks, pl.blockAdd, pl.blockRem, pl.timeOnline, pl.head, pl.body, nowstring, inventory, pl.targetCoor.X, pl.targetCoor.Y, pl.targetCoor.Z) err = stmt.Execute() if err != nil { log.Println(err) return false } ephenationdb.Release(db) if *verboseFlag > 1 { log.Printf("up.Save_Bl saved %v\n", pl.name) } elapsed := time.Now().Sub(start) if *verboseFlag > 0 { log.Printf("up.Save_Bl elapsed %d ms\n", elapsed/1e6) } return true }
// Load a license. Return nil if error. func Load_Bl(mail string) *License { var license License start := time.Now() db := ephenationdb.New() if db == nil { return nil } // Build a query for the user email sent as an argument query := "SELECT * FROM users WHERE email='" + mail + "'" if *verboseFlag > 0 { log.Println(query) } err := db.Query(query) if err != nil { log.Println(err) return nil } defer ephenationdb.Release(db) // Defer database release when we are sure that the db is open // Store the result result, err := db.UseResult() if err != nil { log.Println(err) return nil } // Fetch row row := result.FetchRow() if row == nil { if *verboseFlag > 0 { log.Printf("No result for %v\n", mail) } db.FreeResult() return nil } // Assign mail as license.mail license.mail = mail idnumfields := 0 const ( ID_MAIL = 1 << iota ID_LIC = 1 << iota ID_PASSW = 1 << iota ID_LAST = 1 << iota ) // TODO: Read more information: license type, valid until (if license terminates during connection) for x := 0; x < int(result.FieldCount()); x++ { FieldName := result.FetchField().Name switch FieldName { case "email": license.mail = fmt.Sprint(row[x]) idnumfields |= ID_MAIL case "licensekey": license.License = fmt.Sprint(row[x]) idnumfields |= ID_LIC case "password": license.Password = fmt.Sprint(row[x]) idnumfields |= ID_PASSW case "lastseen": license.LastSeen = fmt.Sprintf("%s", row[x]) idnumfields |= ID_LAST } } db.FreeResult() // Required to get back in sync // If all fields have been identified, continue, otherwise return failure if idnumfields != ID_MAIL|ID_LIC|ID_PASSW|ID_LAST { if *verboseFlag > 0 { log.Printf("Not proper content: %v\n", idnumfields) } return nil // return fail } // Read avatar names query = "SELECT name FROM avatars WHERE owner='" + license.mail + "'" if *verboseFlag > 0 { log.Println(query) } err = db.Query(query) if err != nil { log.Println(err) return nil } // Store the result result, err = db.StoreResult() if err != nil { log.Println(err) return nil } rowcount := result.RowCount() temps := make([]string, rowcount) for x := 0; x < int(rowcount); x++ { // Fetch row row = result.FetchRow() if row == nil { // TODO: Error handling } temps[x] = fmt.Sprint(row[0]) } db.FreeResult() license.Names = temps if *verboseFlag > 0 { log.Printf("license.Load done: %v\n", license) elapsed := time.Now().Sub(start) if elapsed > UPPER_TIME_LIMIT { log.Printf("license.Load %d ms\n", elapsed/1e6) } } return &license }
// Return true if ok, and the uid of the player. // TODO: Improve error handlng func (pl *player) Load_WLwBlWLc(name string) (uint32, bool) { // Connect to database db := ephenationdb.New() if db == nil { return 0, false } defer ephenationdb.Release(db) // Build a query for the avatar name sent as an argument // TODO: Assert that the avatar name is unique and on this server for the current user? query := "SELECT jsonstring,id,PositionX,PositionY,PositionZ,isFlying,isClimbing,isDead,DirHor,DirVert,AdminLevel,Level,Experience,HitPoints,Mana,Kills,HomeX,HomeY,HomeZ,ReviveX,ReviveY,ReviveZ,maxchunks,BlocksAdded,BlocksRemoved,TimeOnline,HeadType,BodyType,inventory,TScoreTotal,TScoreBalance,TScoreTime,TargetX,TargetY,TargetZ FROM avatars WHERE name='" + name + "'" stmt, err := db.Prepare(query) if err != nil { log.Println(err) return 0, false } // Execute statement err = stmt.Execute() if err != nil { log.Println(err) return 0, false } // Some helper variables var packedline string var uid uint32 var packedInv []byte var terrScore, terrScoreBalance float64 var terrScoreTimestamp uint32 // Booleans doesn't work var flying, climbing, dead int stmt.BindResult(&packedline, &uid, &pl.coord.X, &pl.coord.Y, &pl.coord.Z, &flying, &climbing, &dead, &pl.dirHor, &pl.dirVert, &pl.adminLevel, &pl.level, &pl.exp, &pl.hitPoints, &pl.mana, &pl.numKill, &pl.homeSP.X, &pl.homeSP.Y, &pl.homeSP.Z, &pl.reviveSP.X, &pl.reviveSP.Y, &pl.reviveSP.Z, &pl.maxchunks, &pl.blockAdd, &pl.blockRem, &pl.timeOnline, &pl.head, &pl.body, &packedInv, &terrScore, &terrScoreBalance, &terrScoreTimestamp, &pl.targetCoor.X, &pl.targetCoor.Y, &pl.targetCoor.Z) for { eof, err := stmt.Fetch() if err != nil { log.Println(err) return 0, false } if eof { break } } // log.Println(pl.targetCoor) // Some post processing pl.name = name if flying == 1 { pl.flying = true } if climbing == 1 { pl.climbing = true } if dead == 1 { pl.dead = true } if pl.maxchunks == -1 { // This parameter was not initialized. pl.maxchunks = CnfgMaxOwnChunk } pl.logonTimer = time.Now() if err = json.Unmarshal([]uint8(packedline), pl); err != nil { log.Printf("Unmarshal player %s: %v (%v)\n", name, err, packedline) // TODO: This covers errors when updating the jsonstring, should be handled in a more approperiate way //return 0, false } // If there was data in the inventory "blob", unpack it. if len(packedInv) > 0 { err = pl.inventory.Unpack([]byte(packedInv)) if err != nil { log.Println("Failed to unpack", err, packedInv) } // Save what can be saved, and remove unknown objects. pl.inventory.CleanUp() } if *verboseFlag > 1 { log.Println("Inventory unpacked", pl.inventory) } //fmt.Printf("Coord: (%v,%v,%v)\n", pl.coord.X, pl.coord.Y, pl.coord.Z ) if pl.reviveSP.X == 0 && pl.reviveSP.Y == 0 && pl.reviveSP.Z == 0 { // Check if there is any spawn point defined. pl.reviveSP = pl.coord pl.homeSP = pl.coord } // Load the allocated territories. This is loaded every time a player logs in, but not at logout or player save. // It will however be updated immediately when the player changes his allocation. terr, ok := chunkdb.ReadAvatar_Bl(uint32(uid)) if !ok { return 0, false } pl.territory = terr score.Initialize(uid, terrScore, terrScoreBalance, terrScoreTimestamp, name, len(terr)) // fmt.Printf("User: %#v\n", pl) return uint32(uid), true }
func DumpSQL() { db := ephenationdb.New() if db == nil { return } defer ephenationdb.Release(db) // Build a query for the avatar name sent as an argument // TODO: Assert that the avatar name is unique and on this server for the current user? query := "SELECT name,jsonstring,id,PositionX,PositionY,PositionZ,isFlying,isClimbing,isDead,DirHor,DirVert,AdminLevel,Level,Experience,HitPoints,Mana,Kills,HomeX,HomeY,HomeZ,ReviveX,ReviveY,ReviveZ,maxchunks,BlocksAdded,BlocksRemoved,TimeOnline,HeadType,BodyType,inventory,TScoreTotal,TScoreBalance,TScoreTime,TargetX,TargetY,TargetZ FROM avatars" stmt, err := db.Prepare(query) if err != nil { log.Println(err) return } // Execute statement err = stmt.Execute() if err != nil { log.Println(err) return } // Some helper variables var packedline string var uid uint32 var packedInv []byte var terrScore, terrScoreBalance float64 var terrScoreTimestamp uint32 // Booleans doesn't work var flying, climbing, dead int var pl player stmt.BindResult(&pl.name, &packedline, &uid, &pl.coord.X, &pl.coord.Y, &pl.coord.Z, &flying, &climbing, &dead, &pl.dirHor, &pl.dirVert, &pl.adminLevel, &pl.level, &pl.exp, &pl.hitPoints, &pl.mana, &pl.numKill, &pl.homeSP.X, &pl.homeSP.Y, &pl.homeSP.Z, &pl.reviveSP.X, &pl.reviveSP.Y, &pl.reviveSP.Z, &pl.maxchunks, &pl.blockAdd, &pl.blockRem, &pl.timeOnline, &pl.head, &pl.body, &packedInv, &terrScore, &terrScoreBalance, &terrScoreTimestamp, &pl.targetCoor.X, &pl.targetCoor.Y, &pl.targetCoor.Z) for { eof, err := stmt.Fetch() if err != nil { log.Println(err) return } if eof { break } // Some post processing if flying == 1 { pl.flying = true } if climbing == 1 { pl.climbing = true } if dead == 1 { pl.dead = true } if pl.maxchunks == -1 { // This parameter was not initialized. pl.maxchunks = CnfgMaxOwnChunk } DumpSQLPlayer(&pl, packedline, packedInv) } }