diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua index a05747933..adaaaf58d 100644 --- a/Moose Development/Moose/Functional/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -230,8 +230,10 @@ SCORING = { ClassID = 0, Players = {}, AutoSave = true, - version = "1.18.4", + version = "1.18.5", ScoringScenery = nil, -- Core.Set#SET_SCENERY + SceneryHitsInZone = false, + LoadSave = false, } local _SCORINGCoalition = { @@ -250,15 +252,16 @@ local _SCORINGCategory = { --- Creates a new SCORING object to administer the scoring achieved by players. -- @param #SCORING self -- @param #string GameName The name of the game. This name is also logged in the CSV score file. --- @param #string SavePath (Optional) Path where to save the CSV file, defaults to your **\\Saved Games\\DCS\\Logs** folder. --- @param #boolean AutoSave (Optional) If passed as `false`, then swith autosave off. +-- @param #string SavePath (Optional) Path where to save the CSV files, defaults to your **\\Saved Games\\DCS\\Logs** folder. See next two options. +-- @param #boolean AutoSave (Optional) If passed as `false`, then swith autosave off. This stores a detailed table which will never be loaded again by SCORING (for e.g. Discord purposes). +-- @param #boolean LoadSave (Optional) If passed as `true` save summary scores per player, and load at restart of the mission. -- @return #SCORING self -- @usage -- -- -- Define a new scoring object for the mission Gori Valley. -- ScoringObject = SCORING:New( "Gori Valley" ) -- -function SCORING:New( GameName, SavePath, AutoSave ) +function SCORING:New( GameName, SavePath, AutoSave, LoadSave ) -- Inherits from BASE local self = BASE:Inherit( self, BASE:New() ) -- #SCORING @@ -266,7 +269,7 @@ function SCORING:New( GameName, SavePath, AutoSave ) if GameName then self.GameName = GameName else - error( "A game name must be given to register the scoring results" ) + error( "A game name must be given to register the scoring results!" ) end -- Additional Object scores @@ -301,6 +304,12 @@ function SCORING:New( GameName, SavePath, AutoSave ) self.penaltyoncoalitionchange = true self:SetDisplayMessagePrefix() + + self.SceneryHitsInZone = false + + if LoadSave then + self.LoadSave = LoadSave + end -- Event handlers self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) @@ -328,11 +337,61 @@ function SCORING:New( GameName, SavePath, AutoSave ) end self:I("SCORING "..tostring(GameName).." started! v"..self.version) - + + if LoadSave == true then + self:_LoadPlayerSummaryScore() + end + return self end +--- [Internal] Helper to load scores from disk at scoring start +-- @param #SCORING self +-- @return #SCORING self +function SCORING:_LoadPlayerSummaryScore() + + if lfs and io and self.LoadSave == true then + local path = self.AutoSavePath or lfs.writedir() .. [[Logs\]] + local filename = self.GameName or "PlayerScoresSummary" + filename = filename..".csv" + if UTILS.CheckFileExists(path,filename) then + local ok, data = UTILS.LoadFromFile(path,filename) + -- Playername;;Score;;Penalty + table.remove(data,1) + for _,_data in pairs(data) do + local line = UTILS.Split(_data,";;") + local playername = tostring(line[1]) + local score = tonumber(line[2]) + local penalty = tonumber(line[3]) + self:I(string.format("Player %s Score %d Penalty %d",playername,score,penalty)) + local PlayerData = self.Players[playername] + if not PlayerData then + PlayerData = {} + PlayerData.Hit = {} + PlayerData.Destroy = {} + PlayerData.Goals = {} + PlayerData.Goals[self.GameName] = {Score = score, Penalty = penalty} + PlayerData.Mission = {} + PlayerData.HitPlayers = {} + PlayerData.Score = score + PlayerData.Penalty = penalty + PlayerData.PenaltyCoalition = 0 + PlayerData.PenaltyWarning = 0 + self.Players[playername] = PlayerData + else + PlayerData.Score = score + PlayerData.Penalty =penalty + self.Players[playername] = PlayerData + PlayerData.Goals[self.GameName] = {Score = score, Penalty = penalty} + end + end + end + end + + return self +end + --- Set a prefix string that will be displayed at each scoring message sent. -- @param #SCORING self -- @param #string DisplayMessagePrefix (Default="Scoring: ") The scoring prefix string. @@ -579,6 +638,22 @@ function SCORING:AddZoneScore( ScoreZone, Score ) return self end +--- Allow Scenery hits in Zones to count (no specific(!) scenery targets). NOTE - Allowing this can spam your scoring display! +-- @param #SCORING self +-- @return #SCORING self +function SCORING:EnableSceneryHitsinZones() + self.SceneryHitsInZone = true + return self +end + +--- Disallow Scenery hits in Zones to count (no specific(!) scenery targets). +-- @param #SCORING self +-- @return #SCORING self +function SCORING:DisableSceneryHitsinZones() + self.SceneryHitsInZone = false + return self +end + --- Add a @{Core.Set#SET_ZONE} to define additional scoring when any object is destroyed in that zone. -- Note that if a @{Core.Zone} with the same name is already within the scoring added, the @{Core.Zone} (type) and Score will be replaced! -- This allows for a dynamic destruction zone evolution within your mission. @@ -877,6 +952,20 @@ function SCORING:AddGoalScorePlayer( PlayerName, GoalTag, Text, Score ) -- PlayerName can be nil, if the Unit with the player crashed or due to another reason. if PlayerName then local PlayerData = self.Players[PlayerName] + if not PlayerData then + PlayerData = {} + PlayerData.Goals = {} + PlayerData.Hit = {} + PlayerData.Destroy = {} + PlayerData.Goals = {} + PlayerData.Mission = {} + PlayerData.HitPlayers = {} + PlayerData.Score = 0 + PlayerData.Penalty = 0 + PlayerData.PenaltyCoalition = 0 + PlayerData.PenaltyWarning = 0 + self.Players[PlayerName] = PlayerData + end PlayerData.Goals[GoalTag] = PlayerData.Goals[GoalTag] or { Score = 0 } PlayerData.Goals[GoalTag].Score = PlayerData.Goals[GoalTag].Score + Score @@ -1568,22 +1657,24 @@ function SCORING:_EventOnDeadOrCrash( Event ) end else - -- Check if there are Zones where the destruction happened. - for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do - self:F( { ScoringZone = ScoreZoneData } ) - local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE - local Score = ScoreZoneData.Score - if ScoreZone:IsVec2InZone( TargetUnit:GetVec2() ) then - Player.Score = Player.Score + Score - TargetDestroy.Score = TargetDestroy.Score + Score - MESSAGE:NewType( self.DisplayMessagePrefix .. "Scenery destroyed in zone '" .. ScoreZone:GetName() .. "'." .. - "Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty, - MESSAGE.Type.Information ) - :ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() ) - - self:ScoreCSV( PlayerName, "", "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Scenery", TargetUnitType ) - Destroyed = true + if self.SceneryHitsInZone == true then + -- Check if there are Zones where the destruction happened. + for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do + self:F( { ScoringZone = ScoreZoneData } ) + local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE + local Score = ScoreZoneData.Score + if ScoreZone:IsVec2InZone( TargetUnit:GetVec2() ) then + Player.Score = Player.Score + Score + TargetDestroy.Score = TargetDestroy.Score + Score + MESSAGE:NewType( self.DisplayMessagePrefix .. "Scenery destroyed in zone '" .. ScoreZone:GetName() .. "'." .. + "Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty, + MESSAGE.Type.Information ) + :ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() ) + :ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() ) + + self:ScoreCSV( PlayerName, "", "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Scenery", TargetUnitType ) + Destroyed = true + end end end end @@ -1927,9 +2018,12 @@ end --- Report all players score -- @param #SCORING self -- @param Wrapper.Group#GROUP PlayerGroup The player group. -function SCORING:ReportScoreAllSummary( PlayerGroup ) +-- @param #boolean JustScore If this is true, return just a table with playernames and overall scores. +-- @return #table ReportTable Table returned if JustScore is true. +function SCORING:ReportScoreAllSummary( PlayerGroup, JustScore ) local PlayerMessage = "" + local ReportTable = {} self:T( { "Summary Score Report of All Players", Players = self.Players } ) @@ -1961,20 +2055,28 @@ function SCORING:ReportScoreAllSummary( PlayerGroup ) local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + PenaltyGoals + PenaltyMissions - - PlayerMessage = - string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )", - PlayerName, - PlayerScore - PlayerPenalty, - PlayerScore, - PlayerPenalty - ) - MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Overview ):ToGroup( PlayerGroup ) + + if JustScore~=true then + PlayerMessage = + string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )", + PlayerName, + PlayerScore - PlayerPenalty, + PlayerScore, + PlayerPenalty + ) + MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Overview ):ToGroup( PlayerGroup ) + else + ReportTable[PlayerName] = {["Score"]=PlayerScore,["Penalty"]=PlayerPenalty} + end end end - + return ReportTable end +--- Opens a score CSV file to log the scores. +-- @param #SCORING self +-- @param #number sSeconds +-- @return #string ClockString function SCORING:SecondsToClock( sSeconds ) local nSeconds = sSeconds if nSeconds == 0 then @@ -2102,6 +2204,19 @@ function SCORING:ScoreCSV( PlayerName, TargetPlayerName, ScoreType, ScoreTimes, self.CSVFile:write( "\n" ) end + + if lfs and io and self.LoadSave == true then + local path = self.AutoSavePath or lfs.writedir() .. [[Logs\]] + local filename = self.GameName or "PlayerScoresSummary" + filename = filename..".csv" + local data = self:ReportScoreAllSummary("",true) + local text = "-- Playername;;Score;;Penalty\n" + for _playername,_data in pairs(data or {}) do + text = text..string.format("%s;;%d;;%d\n") + end + UTILS.SaveToFile(path,filename,text) + end + end --- Close CSV file