diff --git a/.gitignore b/.gitignore index 274d5fc..e54744b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ hemtt.exe *.biprivatekey *.dll -*.so \ No newline at end of file +*.so +*.exe \ No newline at end of file diff --git a/.hemtt/project.toml b/.hemtt/project.toml index 8438561..dd0bdbd 100644 --- a/.hemtt/project.toml +++ b/.hemtt/project.toml @@ -10,7 +10,7 @@ include=["mod.cpp", "LICENSE.md", "README.md", "*.dll", "*.so"] path="addons/main/script_version.hpp" # Default major=0 # Overrides path when set -minor=3 +minor=4 patch=0 # build = 0 # Optional diff --git a/addons/main/config.cpp b/addons/main/config.cpp index 033626e..606e39b 100644 --- a/addons/main/config.cpp +++ b/addons/main/config.cpp @@ -5,9 +5,9 @@ class CfgPatches { author = "ilbinek"; authors[] = {"ilbinek"}; url = "https://github.com/ilbinek/statsLogger"; - version = 0.3; - versionStr = "0.3"; - versionAr[] = {0, 3}; + version = 0.4; + versionStr = "0.4"; + versionAr[] = {0, 4}; requiredAddons[] = {}; requiredVersion = 2.04; units[] = {}; diff --git a/addons/main/functions/fn_init.sqf b/addons/main/functions/fn_init.sqf index d15890e..a4e9064 100644 --- a/addons/main/functions/fn_init.sqf +++ b/addons/main/functions/fn_init.sqf @@ -22,7 +22,7 @@ "Status", ( "Stats Addon initialised
" + - "Version 0.3
" + + "Version 0.4
" + "Capture is running." ) ] @@ -65,3 +65,6 @@ diag_log(text ('[STATS] Called Stats with ' + str(_tmp))); diag_log(text ('[STATS] Starting FPS LOOP')); call statslogger_fnc_fpsLoop; diag_log(text ('[STATS] Started FPS LOOP')); + + +"stats_logger" callExtension ":SET:DEBUG:OFF:"; \ No newline at end of file diff --git a/extension/data.go b/extension/data.go index 2ac89cc..b0aedbc 100644 --- a/extension/data.go +++ b/extension/data.go @@ -1,114 +1,55 @@ package main import ( - "sync" "time" ) type Mission struct { - mu sync.Mutex `json:"-"` - MissionName string `json:"missionName"` - Worldname string `json:"worldname"` - MissionAuthor string `json:"missionAuthor"` - MissionType string `json:"missionType"` - Victory string `json:"victory"` - MissionStart string `json:"missionStart"` - MissionEnd string `json:"missionEnd"` - Date string `json:"date"` - ScoreBlue string `json:"scoreBlue"` - ScoreRed string `json:"scoreRed"` - ScoreGreen string `json:"scoreGreen"` - Players []*Player `json:"players"` - Kills []*Kill `json:"kills"` - FPS []*FPSRecord `json:"fps"` -} - -func (m *Mission) AddPlayer(p *Player) { - m.mu.Lock() - defer m.mu.Unlock() - m.Players = append(m.Players, p) -} - -func (m *Mission) AddKill(k *Kill) { - m.mu.Lock() - defer m.mu.Unlock() - m.Kills = append(m.Kills, k) -} - -func (m *Mission) AddFPS(f *FPSRecord) { - m.mu.Lock() - defer m.mu.Unlock() - m.FPS = append(m.FPS, f) + ID uint `json:"id" gorm:"primaryKey"` // Primary key + MissionName string `json:"missionName"` + Worldname string `json:"worldname"` + MissionAuthor string `json:"missionAuthor"` + MissionType string `json:"missionType"` + Victory string `json:"victory"` + MissionStart string `json:"missionStart"` + MissionEnd string `json:"missionEnd"` + Date string `json:"date"` + ScoreBlue string `json:"scoreBlue"` + ScoreRed string `json:"scoreRed"` + ScoreGreen string `json:"scoreGreen"` + Players []Player `json:"players"` + Kills []Kill `json:"kills"` + FPSRecords []FPSRecord `json:"fps"` } type Player struct { - mu sync.Mutex `json:"-"` - UID string `json:"uid"` - Name string `json:"name"` - Side string `json:"side"` - Shots int `json:"shots"` - Hits int `json:"hits"` - Kills int `json:"kills"` - Squad string `json:"squad"` - Role string `json:"role"` - Class string `json:"class"` -} - -type PlayerUpdateOptions struct { - Name string `json:"name"` - Side string `json:"side"` - Squad string `json:"squad"` - Role string `json:"role"` - Class string `json:"class"` -} - -func (p *Player) Update(options PlayerUpdateOptions) { - p.mu.Lock() - if options.Name != "" { - p.Name = options.Name - } - if options.Side != "" { - p.Side = options.Side - } - if options.Squad != "" { - p.Squad = options.Squad - } - if options.Role != "" { - p.Role = options.Role - } - if options.Class != "" { - p.Class = options.Class - } - p.mu.Unlock() -} - -func (p *Player) AddShot() { - p.mu.Lock() - p.Shots++ - p.mu.Unlock() -} - -func (p *Player) AddHit() { - p.mu.Lock() - p.Hits++ - p.mu.Unlock() -} - -func (p *Player) AddKill() { - p.mu.Lock() - p.Kills++ - p.mu.Unlock() + ID uint `json:"id" gorm:"uniqueIndex"` + PlayerUID string `json:"playerUID" gorm:"index"` // Player UID + Name string `json:"name"` + Side string `json:"side"` + Shots int `json:"shots"` + Hits int `json:"hits"` + Squad string `json:"squad"` + Role string `json:"role"` + Class string `json:"class"` + Mission *Mission `json:"-" gorm:"foreignKey:MissionID"` + MissionID uint `json:"-" gorm:"index"` } type Kill struct { - Time string `json:"time"` - Victim string `json:"victim"` - Killer string `json:"killer"` - Weapon string `json:"weapon"` - Distance string `json:"distance"` + ID uint `json:"id" gorm:"primaryKey"` // Primary key + Time string `json:"time"` + Victim string `json:"victim"` + Killer string `json:"killer"` + Weapon string `json:"weapon"` + Distance string `json:"distance"` + Mission *Mission `json:"-"` + MissionID uint `json:"-" gorm:"index"` } type FPSRecord struct { - TimeUTC time.Time `json:"timeUTC"` - FPS float64 `json:"fps"` + TimeUTC time.Time `json:"timeUTC" gorm:"primaryKey"` // Primary key + FPS float64 `json:"fps"` + Mission *Mission `json:"-"` + MissionID uint `json:"-" gorm:"index"` } diff --git a/extension/db/db.go b/extension/db/db.go new file mode 100644 index 0000000..a0ff18f --- /dev/null +++ b/extension/db/db.go @@ -0,0 +1,35 @@ +package db + +import ( + "path/filepath" + + "github.com/indig0fox/a3go/assemblyfinder" + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +var db *gorm.DB +var configuredDbPath string = filepath.Join( + filepath.Dir(assemblyfinder.GetModulePath()), + "stats.db", +) + +func Client() *gorm.DB { + if db == nil { + err := Connect(configuredDbPath) + if err != nil { + panic(err) + } + } + return db +} + +func Connect(dbPath string) error { + var err error + configuredDbPath = dbPath + db, err = gorm.Open(sqlite.Open(dbPath), &gorm.Config{}) + if err != nil { + return err + } + return nil +} diff --git a/extension/go.mod b/extension/go.mod index 8097543..fb26fec 100644 --- a/extension/go.mod +++ b/extension/go.mod @@ -1,5 +1,19 @@ -module statsLoggerExtension +module github.com/ilbinek/statsLogger go 1.20 -require github.com/indig0fox/a3go v0.3.2 +require ( + github.com/indig0fox/a3go v0.3.2 + github.com/rs/zerolog v1.31.0 + gorm.io/driver/sqlite v1.5.4 + gorm.io/gorm v1.25.5 +) + +require ( + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-sqlite3 v1.14.17 // indirect + golang.org/x/sys v0.13.0 // indirect +) diff --git a/extension/go.sum b/extension/go.sum index bd4776c..a746719 100644 --- a/extension/go.sum +++ b/extension/go.sum @@ -1,4 +1,30 @@ -github.com/indig0fox/a3go v0.2.1 h1:Ixr/182pGd5qPbFYgHINWt5YtyKaO7Yo/va/17nF/Vg= -github.com/indig0fox/a3go v0.2.1/go.mod h1:8htVwBiIAVKpT1Jyb+5dm7GuLAAevTXgw7UKxSlOawY= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/indig0fox/a3go v0.3.2 h1:bNL90pffeOnS6Qtjoo5JHpdpZn1f0BZmRZR8nz/xcvQ= github.com/indig0fox/a3go v0.3.2/go.mod h1:8htVwBiIAVKpT1Jyb+5dm7GuLAAevTXgw7UKxSlOawY= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= +github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= +github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0= +gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= +gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= +gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= diff --git a/extension/main.go b/extension/main.go index 4dc4356..3cf6ca4 100644 --- a/extension/main.go +++ b/extension/main.go @@ -4,13 +4,17 @@ import ( "encoding/json" "errors" "fmt" - "log" "os" "path/filepath" "strconv" "strings" "time" + "github.com/ilbinek/statsLogger/db" + "gorm.io/gorm" + + "github.com/rs/zerolog" + "github.com/indig0fox/a3go/a3interface" "github.com/indig0fox/a3go/assemblyfinder" ) @@ -25,6 +29,9 @@ var EXTENSION_NAME = "STATS_LOGGER" var mission Mission +var logFile *os.File +var logger zerolog.Logger + func init() { a3interface.SetVersion("1.0.0") a3interface.NewRegistration(":GET:TIME:"). @@ -32,6 +39,43 @@ func init() { SetRunInBackground(false). Register() + // when a3 sends this, set logging to INFO+ + a3interface.NewRegistration(":SET:DEBUG:OFF:"). + SetFunction(func(ctx a3interface.ArmaExtensionContext, data string) (string, error) { + thisLogger := logger.With(). + Interface("context", ctx). + Str("call_type", "RvExtension"). + Str("command", ":SET:DEBUG:OFF:"). + Str("data", data). + Logger() + + thisLogger.Debug().Send() + + zerolog.SetGlobalLevel(zerolog.InfoLevel) + + return "", nil + }). + SetRunInBackground(false). + Register() + + a3interface.NewRegistration(":SET:DEBUG:ON:"). + SetFunction(func(ctx a3interface.ArmaExtensionContext, data string) (string, error) { + thisLogger := logger.With(). + Interface("context", ctx). + Str("call_type", "RvExtension"). + Str("command", ":SET:DEBUG:ON:"). + Str("data", data). + Logger() + + thisLogger.Debug().Send() + + zerolog.SetGlobalLevel(zerolog.DebugLevel) + + return "", nil + }). + SetRunInBackground(false). + Register() + a3interface.NewRegistration(":RESET:"). SetFunction(onReset). SetRunInBackground(false). @@ -80,6 +124,18 @@ func init() { SetFunction(onExport). SetRunInBackground(false). Register() + + logFile, err := os.Create(filepath.Join(modulePathDir, "stats.log")) + if err != nil { + panic(err) + } + + // default to debug on logger + logger = zerolog.New(zerolog.ConsoleWriter{ + Out: logFile, + TimeFormat: time.RFC3339, + NoColor: true, + }).With().Timestamp().Caller().Logger().Level(zerolog.DebugLevel) } // onTimeNowUTC :GET:TIME: returns the current time in UTC @@ -87,10 +143,21 @@ func onTimeNowUTC( ctx a3interface.ArmaExtensionContext, data string, ) (string, error) { + thisLogger := logger.With(). + Interface("context", ctx). + Str("call_type", "RvExtension"). + Str("command", ":GET:TIME:"). + Str("data", data). + Logger() + + thisLogger.Debug().Send() + + // get time t := time.Now().UTC() // format time timeNow := t.Format("2006-01-02 15:04:05") // send data back to Arma + thisLogger.Debug().Msgf("Returning time: %s", timeNow) return string(timeNow), nil } @@ -99,6 +166,14 @@ func onReset( ctx a3interface.ArmaExtensionContext, data string, ) (string, error) { + thisLogger := logger.With(). + Interface("context", ctx). + Str("call_type", "RvExtension"). + Str("command", ":RESET:"). + Str("data", data). + Logger() + + thisLogger.Debug().Send() // reset mission struct mission = Mission{} // send data back to Arma @@ -118,9 +193,17 @@ func onSetupMissionArgs( // 3: mission type (ex. public) // 4: time of day at mission start (daytime) + thisLogger := logger.With(). + Interface("context", ctx). + Str("call_type", "RvExtensionArgs"). + Str("command", ":MISSION:"). + Logger() + + thisLogger.Debug().Send() + // Check size if len(args) != 5 { - log.Println("Error: Mission array size is not 5") + thisLogger.Error().Err(errors.New("mission array size is not 5")).Strs("args", args).Send() a3interface.WriteArmaCallback(EXTENSION_NAME, "DEBUG", "MISSION ERROR", "WRONG MISSION PARAMS COUNT - ["+strings.Join(args, ", ")+"]") return "", errors.New("mission array size is not 5") } @@ -138,6 +221,25 @@ func onSetupMissionArgs( // Victory: args[4], // not in sqf MissionStart: args[4], } + + thisLogger.Info().Errs( + "migrations", []error{ + db.Client().AutoMigrate(&Mission{}), + db.Client().AutoMigrate(&Player{}), + db.Client().AutoMigrate(&Kill{}), + db.Client().AutoMigrate(&FPSRecord{}), + }, + ).Msg("Migrating tables") + + // Create new mission + thisLogger.Debug().Interface("mission", &mission).Msg("Creating mission") + + err := db.Client().Create(&mission).Error + if err != nil { + thisLogger.Error().Err(err).Msg("Could not create mission") + a3interface.WriteArmaCallback(EXTENSION_NAME, "DEBUG", "MISSION ERROR", err.Error()) + return "", err + } // send data back to Arma return `["Saved mission data!"]`, nil } @@ -154,9 +256,17 @@ func onWinArgs( // 2: blue score // 3: red score + thisLogger := logger.With(). + Interface("context", ctx). + Str("call_type", "RvExtensionArgs"). + Str("command", ":WIN:"). + Logger() + + thisLogger.Debug().Send() + // Check size if len(args) != 4 { - log.Println("Error: Win array size is not 4") + thisLogger.Error().Err(errors.New("win array size is not 4")).Strs("args", args).Send() a3interface.WriteArmaCallback(EXTENSION_NAME, "DEBUG", "WIN ERROR", "WRONG WIN PARAMS COUNT") return "", errors.New("win array size is not 4") } @@ -171,6 +281,16 @@ func onWinArgs( mission.ScoreBlue = args[2] mission.ScoreRed = args[3] + thisLogger.Trace().Interface("mission", &mission).Msg("Updating mission") + + // Update mission + err := db.Client().Save(&mission).Error + if err != nil { + thisLogger.Error().Err(err).Msg("Could not update mission") + a3interface.WriteArmaCallback(EXTENSION_NAME, "DEBUG", "WIN ERROR", err.Error()) + return "", err + } + // send data back to Arma return `["Saved win data!"]`, nil } @@ -189,9 +309,17 @@ func onAddPlayerArgs( // 4: side (WEST, EAST, etc.) // 5: groupID (str group _x) + thisLogger := logger.With(). + Interface("context", ctx). + Str("call_type", "RvExtensionArgs"). + Str("command", ":PLAYER:"). + Logger() + + thisLogger.Debug().Send() + // Check size if len(args) != 6 { - log.Println("Error: Player array size is not 6") + thisLogger.Error().Err(errors.New("player array size is not 6")).Strs("args", args).Send() a3interface.WriteArmaCallback(EXTENSION_NAME, "DEBUG", "PLAYER ERROR", "WRONG PLAYER PARAMS COUNT") return "", errors.New("player array size is not 6") } @@ -201,32 +329,55 @@ func onAddPlayerArgs( } // Create new player - player := Player{ - UID: args[0], - Name: args[1], - Role: args[2], - Class: args[3], - Side: args[4], - Squad: args[5], + receivedPlayer := Player{ + PlayerUID: args[0], + Name: args[1], + Role: args[2], + Class: args[3], + Side: args[4], + Squad: args[5], + Mission: &mission, } - // Check if this player is already in the mission - for _, p := range mission.Players { - if p.UID == player.UID { - // Player already in mission so just update - p.Update(PlayerUpdateOptions{ - Name: player.Name, - Side: player.Side, - Squad: player.Squad, - Role: player.Role, - Class: player.Class, - }) - return `["Updated player data!"]`, nil - } + var dbPlayer Player + db.Client().Model(&Player{}).Where( + "player_uid = ? AND mission_id = ?", + receivedPlayer.PlayerUID, + mission.ID, + ).First(&dbPlayer) + if (db.Client().Error != nil) && !errors.Is(db.Client().Error, gorm.ErrRecordNotFound) { + thisLogger.Error().Err(db.Client().Error). + Str("player_uid", receivedPlayer.PlayerUID). + Msg("DB Error") + a3interface.WriteArmaCallback(EXTENSION_NAME, "DEBUG", "PLAYER ERROR", "DB ERROR") + return "", db.Client().Error } - // Add player to mission - mission.AddPlayer(&player) + if dbPlayer.ID == 0 { + err := db.Client().Model(&mission).Association("Players").Append(&receivedPlayer) + if err != nil { + thisLogger.Error().Err(err). + Interface("player", &receivedPlayer). + Msg("Could not create player") + a3interface.WriteArmaCallback(EXTENSION_NAME, "DEBUG", "PLAYER ERROR", err.Error()) + return "", err + } + } else { + err := db.Client().Model(&dbPlayer).Updates(Player{ + Name: receivedPlayer.Name, + Side: receivedPlayer.Side, + Squad: receivedPlayer.Squad, + Role: receivedPlayer.Role, + Class: receivedPlayer.Class, + }).Error + if err != nil { + thisLogger.Error().Err(err). + Interface("player", &dbPlayer). + Msg("Could not update player") + a3interface.WriteArmaCallback(EXTENSION_NAME, "DEBUG", "PLAYER ERROR", err.Error()) + return "", err + } + } // Send data back to Arma return `["Saved player data!"]`, nil @@ -245,9 +396,17 @@ func onAddKillArgs( // 3: distance // 4: time + thisLogger := logger.With(). + Interface("context", ctx). + Str("call_type", "RvExtensionArgs"). + Str("command", ":KILL:"). + Logger() + + thisLogger.Debug().Send() + // Check size if len(args) != 5 { - log.Println("Error: Kill array size is not 5") + thisLogger.Error().Err(errors.New("kill array size is not 5")).Strs("args", args).Send() a3interface.WriteArmaCallback(EXTENSION_NAME, "DEBUG", "KILL ERROR", "WRONG KILL PARAMS COUNT") return "", errors.New("kill array size is not 5") } @@ -264,16 +423,15 @@ func onAddKillArgs( Distance: args[3], Time: args[4], } - // Add kill to mission - mission.AddKill(&kill) - - // Find the killer - for i, p := range mission.Players { - if p.UID == args[0] { - // Increment kills - mission.Players[i].AddKill() - return `["Saved kill data!"]`, nil - } + + // Add kill to mission associations + err := db.Client().Model(&mission).Association("Kills").Append(&kill) + if err != nil { + thisLogger.Error().Err(err). + Interface("kill", &kill). + Msg("Could not add kill to mission") + a3interface.WriteArmaCallback(EXTENSION_NAME, "DEBUG", "KILL ERROR", err.Error()) + return "", err } // Send data back to Arma @@ -289,9 +447,17 @@ func onAddShotArgs( // args are: // 0: playerUID + thisLogger := logger.With(). + Interface("context", ctx). + Str("call_type", "RvExtensionArgs"). + Str("command", ":SHOT:"). + Logger() + + thisLogger.Debug().Send() + // Check size if len(args) != 1 { - log.Println("Error: Shot array size is not 1") + thisLogger.Error().Err(errors.New("shot array size is not 1")).Strs("args", args).Send() a3interface.WriteArmaCallback(EXTENSION_NAME, "DEBUG", "SHOT ERROR", "WRONG SHOT PARAMS COUNT") return "", errors.New("shot array size is not 1") } @@ -300,18 +466,26 @@ func onAddShotArgs( args[i] = a3interface.RemoveEscapeQuotes(v) } - // Find the player - for i, p := range mission.Players { - if p.UID == args[0] { - // Increment shots - // do so using a method which uses the mutex - mission.Players[i].AddShot() + // Find the player and update it + for i, v := range mission.Players { + if v.PlayerUID == args[0] { + err := db.Client().Model(&(mission.Players[i])).Updates(&Player{ + Shots: (mission.Players[i].Shots + 1), + }).Error + + if err != nil { + thisLogger.Error().Err(db.Client().Error). + Str("player_uid", args[0]). + Msg("DB Error") + a3interface.WriteArmaCallback(EXTENSION_NAME, "DEBUG", "SHOT ERROR", "DB ERROR") + return "", errors.New("db error") + } return `["Saved shot data!"]`, nil } } // Send data back to Arma - return `["Saved shot data!"]`, nil + return "", errors.New("no player found error") } // addHit :HIT: adds a hit to the mission @@ -323,9 +497,17 @@ func onAddHitArgs( // args are: // 0: playerUID + thisLogger := logger.With(). + Interface("context", ctx). + Str("call_type", "RvExtensionArgs"). + Str("command", ":HIT:"). + Logger() + + thisLogger.Debug().Send() + // Check size if len(args) != 1 { - log.Println("Error: Hit array size is not 1") + thisLogger.Error().Err(errors.New("hit array size is not 1")).Strs("args", args).Send() a3interface.WriteArmaCallback(EXTENSION_NAME, "DEBUG", "HIT ERROR", "WRONG HIT PARAMS COUNT") return "", errors.New("hit array size is not 1") } @@ -334,17 +516,27 @@ func onAddHitArgs( args[i] = a3interface.RemoveEscapeQuotes(v) } - // Find the player - for i, p := range mission.Players { - if p.UID == args[0] { - // Increment hits - mission.Players[i].Hits++ + // Find the shooter and update it + for i, v := range mission.Players { + if v.PlayerUID == args[0] { + err := db.Client().Model(&(mission.Players[i])).Updates(&Player{ + Hits: (mission.Players[i].Hits + 1), + }).Error + + if err != nil { + thisLogger.Error().Err(db.Client().Error). + Str("player_uid", args[0]). + Msg("DB Error") + a3interface.WriteArmaCallback(EXTENSION_NAME, "DEBUG", "SHOT ERROR", "DB ERROR") + return "", errors.New("db error") + } + return `["Saved hit data!"]`, nil } } // Send data back to Arma - return `["Saved hit data!"]`, nil + return "", errors.New("no player found error") } // onAddFPSArgs :FPS: adds FPS data to the mission @@ -356,9 +548,17 @@ func onAddFPSArgs( // args are: // 0: fps + thisLogger := logger.With(). + Interface("context", ctx). + Str("call_type", "RvExtensionArgs"). + Str("command", ":FPS:"). + Logger() + + thisLogger.Debug().Send() + // Check size if len(args) != 1 { - log.Println("Error: FPS array size is not 1") + thisLogger.Error().Err(errors.New("fps array size is not 1")).Strs("args", args).Send() a3interface.WriteArmaCallback(EXTENSION_NAME, "DEBUG", "FPS ERROR", "WRONG FPS PARAMS COUNT") return "", errors.New("fps array size is not 1") } @@ -370,16 +570,28 @@ func onAddFPSArgs( // Convert to float64 f, err := strconv.ParseFloat(args[0], 64) if err != nil { - log.Println("Error: FPS is not a float64") + thisLogger.Error().Err(err). + Str("fps", args[0]). + Msg("FPS is not a float64") a3interface.WriteArmaCallback(EXTENSION_NAME, "DEBUG", "FPS ERROR", "FPS IS NOT A FLOAT64") return "", errors.New("fps is not a float64") } // Add FPS to mission - mission.AddFPS(&FPSRecord{ + fps := &FPSRecord{ FPS: f, TimeUTC: time.Now().UTC(), - }) + } + + // Add FPS to mission associations + err = db.Client().Model(&mission).Association("FPSRecords").Append(fps) + if err != nil { + thisLogger.Error().Err(err). + Interface("fps", fps). + Msg("Could not add FPS to mission") + a3interface.WriteArmaCallback(EXTENSION_NAME, "DEBUG", "FPS ERROR", err.Error()) + return "", err + } // Send data back to Arma return `["Saved FPS data!"]`, nil @@ -391,12 +603,23 @@ func onExport( data string, ) (string, error) { + thisLogger := logger.With(). + Interface("context", ctx). + Str("call_type", "RvExtension"). + Str("command", ":EXPORT:"). + Str("data", data). + Logger() + + thisLogger.Debug().Send() + // Export mission // Get executablepath/stats p := filepath.Join(modulePathDir, "stats-output") err := os.MkdirAll(p, os.ModePerm) if err != nil { - log.Println("Error: Could not create stats folder") + thisLogger.Error().Err(err). + Str("path", p). + Msg("Could not create stats-output folder") a3interface.WriteArmaCallback(EXTENSION_NAME, "DEBUG", "EXPORT ERROR", err.Error()) return "", err } @@ -418,7 +641,9 @@ func onExport( missionBytes, err := json.MarshalIndent(&mission, "", " ") // catch error if err != nil { - log.Println("Error: Could not marshal mission") + thisLogger.Error().Err(err). + Interface("mission", &mission). + Msg("Could not marshal mission to JSON") a3interface.WriteArmaCallback(EXTENSION_NAME, "DEBUG", "EXPORT ERROR", err.Error()) return "", err } @@ -428,11 +653,14 @@ func onExport( // Check for errors if err != nil { - log.Println("Error: Could not write mission to file") + thisLogger.Error().Err(err). + Str("path", fileDestinationAbsolute). + Msg("Could not write mission to file") a3interface.WriteArmaCallback(EXTENSION_NAME, "DEBUG", "EXPORT ERROR", err.Error()) return "", err } + thisLogger.Info().Str("path", fileDestinationAbsolute).Msg("Exported mission to file") a3interface.WriteArmaCallback(EXTENSION_NAME, "DEBUG", "EXPORT DONE", "EXPORT FINISHED") return fmt.Sprintf( `["Successfully exported data to %s"]`, @@ -442,5 +670,9 @@ func onExport( } func main() { + db.Client().AutoMigrate(&Mission{}) + db.Client().AutoMigrate(&Player{}) + db.Client().AutoMigrate(&Kill{}) + db.Client().AutoMigrate(&FPSRecord{}) fmt.Scanln() } diff --git a/mod.cpp b/mod.cpp index 48c596c..a4ad1bb 100644 --- a/mod.cpp +++ b/mod.cpp @@ -1,11 +1,7 @@ -name = "Stats Logger"; // Name of your mod -//picture = "\Samples_F\Data_01\Logos\logo.paa"; // Picture displayed from the expansions menu. Optimal size is 2048x1024 -//logoSmall = "\Samples_F\Data_01\Logos\logo_small.paa"; // Display next to the item added by the mod -//logo = "\Samples_F\Data_01\Logos\logo.paa"; // Logo displayed in the main menu -//logoOver = "\Samples_F\Data_01\Logos\logoOver.paa"; // When the mouse is over, in the main menu -actionName = "Author"; // Text displayed on the "website" button -action = "https://github.com/ilbinek"; // Website URL, that can accessed from the expansions menu -tooltipOwned = "Stats logger v0.1"; // Tool tip displayed when the mouse is left over, in the main menu +name = "Stats Logger"; +actionName = "Author"; +action = "https://github.com/ilbinek/statsLogger"; +tooltipOwned = "Stats logger v0.4"; // Color used for DLC stripes and backgrounds (RGBA) dlcColor[] = @@ -18,5 +14,5 @@ dlcColor[] = // Overview text, displayed from the extension menu overview = "A mod that logs various statistics from Arma 3 PvP missions."; -hideName = 0; // Hide the extension name -hidePicture = 0; // Hide the extension menu \ No newline at end of file +hideName = 0; +hidePicture = 0; \ No newline at end of file