2017-02-28 14:01:38 +01:00

1497 lines
60 KiB
Lua

--- Single-Player:**Yes** / Multi-Player:**Yes** / Core:**Yes** -- **Administer the scoring of player achievements,
-- and create a CSV file logging the scoring events for use at team or squadron websites.**
--
-- ![Banner Image](..\Presentations\SCORING\Dia1.JPG)
--
-- ===
--
-- # 1) @{Scoring#SCORING} class, extends @{Base#BASE}
--
-- The @{#SCORING} class administers the scoring of player achievements,
-- and creates a CSV file logging the scoring events for use at team or squadron websites.
--
-- The scores are calculated by scoring the hits and destroys of objects that players make,
-- which are @{Unit} and @{Static) objects within your mission.
--
-- Scores are calculated based on the threat level of the objects involved.
-- The threat level of a unit can be a value between 0 and 10.
-- A calculated score takes the threat level of the target divided by the threat level of the player unit.
-- This provides a value between 0.1 and 10.
-- The stronger or the higher the threat of the player unit, the less score will be given in destroys.
-- That value can then be multiplied by a multiplier. A specific multiplier can be set for enemies and friendlies destroys.
--
-- If multiple players hit the same target, and finally the target gets destroyed, each player who contributed to the target
-- destruction, will receive a score. This is important for targets that require significant damage before it can be destroyed, like
-- ships or heavy planes.
--
-- **Additional scores** can be granted to **specific objects**, when the player(s) destroy these objects.
-- **Various @{Zone}s** can be defined for which scores are also granted when objects in that @{Zone} are destroyed.
-- This is **specifically useful** to designate **scenery targets on the map** that will generate points when destroyed.
--
-- With a small change in MissionScripting.lua, the scoring can also be logged in a CSV file.
-- The CSV files can be used to:
--
-- * Upload scoring to a database or a BI tool to publish the scoring results to the player community.
-- * Upload scoring in an (online) Excel like tool, using pivot tables and pivot charts to show mission results.
-- * Share scoring amoung players after the mission to discuss mission results.
--
-- Scores can be reported. Menu options are automatically added to each group when a player joins a client slot or a CA unit.
-- Use the radio menu F10 to consult the scores while running the mission.
-- Scores can be reported for your user, or an overall score can be reported of all players currently active in the mission.
--
-- ## 1.1) Set the destroy score or penalty multiplier
--
-- Score multipliers can be set for scores granted when enemies or friendlies are destroyed.
-- Use the method @{#SCORING.SetMultiplierDestroyScore}() to set the multiplier of enemy destroys (positive destroys).
-- Use the method @{#SCORING.SetMultiplierDestroyPenalty}() to set the multiplier of friendly destroys (negative destroys).
--
-- local Scoring = SCORING:New( "Scoring File" )
-- Scoring:SetMultiplierDestroyScore( 10 )
-- Scoring:SetMultiplierDestroyPenalty( 40 )
--
-- The above sets the multiplier for valid scores to 10. So scores will be given in a scale from 0 to 10.
-- The penalties will be given in a scale from 0 to 40.
--
-- ## 1.2) Define special targets that will give extra scores.
--
-- Special targets can be set that will give extra scores to the players when these are destroyed.
-- Use the methods @{#SCORING.AddUnitScore}() and @{#SCORING.RemoveUnitScore}() to specify a special additional score for a specific @{Unit}s.
-- Use the methods @{#SCORING.AddStaticScore}() and @{#SCORING.RemoveStaticScore}() to specify a special additional score for a specific @{Static}s.
-- Use the method @{#SCORING.SetGroupGroup}() to specify a special additional score for a specific @{Group}s.
--
-- local Scoring = SCORING:New( "Scoring File" )
-- Scoring:AddUnitScore( UNIT:FindByName( "Unit #001" ), 200 )
-- Scoring:AddStaticScore( STATIC:FindByName( "Static #1" ), 100 )
--
-- The above grants an additional score of 200 points for Unit #001 and an additional 100 points of Static #1 if these are destroyed.
-- Note that later in the mission, one can remove these scores set, for example, when the a goal achievement time limit is over.
-- For example, this can be done as follows:
--
-- Scoring:RemoveUnitScore( UNIT:FindByName( "Unit #001" ) )
--
--
--
-- ## 1.3) Define destruction zones that will give extra scores.
--
-- Define zones of destruction. Any object destroyed within the zone of the given category will give extra points.
-- Use the method @{#SCORING.AddZoneScore} to add a @{Zone} for additional scoring.
-- Use the method @{#SCORING.RemoveZoneScore} to remove a @{Zone} for additional scoring.
-- There are interesting variations that can be achieved with this functionality. For example, if the @{Zone} is a @{Zone#ZONE_UNIT},
-- then the zone is a moving zone, and anything destroyed within that @{Zone} will generate points.
-- The other implementation could be to designate a scenery target (a building) in the mission editor surrounded by a @{Zone},
-- just large enough around that building.
--
-- ## 1.4) Define output CSV files.
--
-- The CSV file is given the name of the string given in the @{#SCORING.New}{} constructor, followed by the .csv extension.
-- The file is incrementally saved in the **<User>\\Saved Games\\DCS\\Logs** folder, and has a time stamp indicating each mission run.
-- See the following example:
--
-- local ScoringFirstMission = SCORING:New( "FirstMission" )
-- local ScoringSecondMission = SCORING:New( "SecondMission" )
--
-- The above documents that 2 Scoring objects are created, ScoringFirstMission and ScoringSecondMission.
--
-- ## 1.5) Configure messages.
--
-- When players hit or destroy targets, messages are sent.
-- Various methods exist to configure:
--
-- * Which messages are sent upon the event.
-- * Which audience receives the message.
--
-- ### 1.5.1) Configure the messages sent upon the event.
--
-- Use the following methods to configure when to send messages. By default, all messages are sent.
--
-- * @{#SCORING.SetMessagesHit}(): Configure to send messages after a target has been hit.
-- * @{#SCORING.SetMessagesDestroy}(): Configure to send messages after a target has been destroyed.
-- * @{#SCORING.SetMessagesAddon}(): Configure to send messages for additional score, after a target has been destroyed.
-- * @{#SCORING.SetMessagesZone}(): Configure to send messages for additional score, after a target has been destroyed within a given zone.
--
-- ### 1.5.2) Configure the audience of the messages.
--
-- Use the following methods to configure the audience of the messages. By default, the messages are sent to all players in the mission.
--
-- * @{#SCORING.SetMessagesToAll}(): Configure to send messages to all players.
-- * @{#SCORING.SetMessagesToCoalition}(): Configure to send messages to only those players within the same coalition as the player.
--
--
-- ====
--
-- # **API CHANGE HISTORY**
--
-- The underlying change log documents the API changes. Please read this carefully. The following notation is used:
--
-- * **Added** parts are expressed in bold type face.
-- * _Removed_ parts are expressed in italic type face.
--
-- Hereby the change log:
--
-- 2017-02-26: Initial class and API.
--
-- ===
--
-- # **AUTHORS and CONTRIBUTIONS**
--
-- ### Contributions:
--
-- * **Wingthor**: Testing & Advice.
-- * **Dutch-Baron**: Testing & Advice.
-- * **[Whisper](http://forums.eagle.ru/member.php?u=3829): Testing.
--
-- ### Authors:
--
-- * **FlightControl**: Concept, Design & Programming.
--
-- @module Scoring
--- The Scoring class
-- @type SCORING
-- @field Players A collection of the current players that have joined the game.
-- @extends Core.Base#BASE
SCORING = {
ClassName = "SCORING",
ClassID = 0,
Players = {},
}
local _SCORINGCoalition =
{
[1] = "Red",
[2] = "Blue",
}
local _SCORINGCategory =
{
[Unit.Category.AIRPLANE] = "Plane",
[Unit.Category.HELICOPTER] = "Helicopter",
[Unit.Category.GROUND_UNIT] = "Vehicle",
[Unit.Category.SHIP] = "Ship",
[Unit.Category.STRUCTURE] = "Structure",
}
--- 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.
-- @return #SCORING self
-- @usage
-- -- Define a new scoring object for the mission Gori Valley.
-- ScoringObject = SCORING:New( "Gori Valley" )
function SCORING:New( GameName )
-- Inherits from BASE
local self = BASE:Inherit( self, BASE:New() ) -- #SCORING
if GameName then
self.GameName = GameName
else
error( "A game name must be given to register the scoring results" )
end
-- Multipliers
self.MultiplierDestroyScore = 10
self.MultiplierDestroyPenalty = 20
-- Additional Object scores
self.ScoringObjects = {}
-- Additional Zone scores.
self.ScoringZones = {}
-- Configure Messages
self:SetMessagesToAll()
self:SetMessagesHit( true )
self:SetMessagesDestroy( true )
self:SetMessagesScore( true )
self:SetMessagesZone( true )
-- Event handlers
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.Hit, self._EventOnHit )
self:HandleEvent( EVENTS.PlayerEnterUnit )
self:HandleEvent( EVENTS.PlayerLeaveUnit )
--self.SchedulerId = routines.scheduleFunction( SCORING._FollowPlayersScheduled, { self }, 0, 5 )
--self.SchedulerId = SCHEDULER:New( self, self._FollowPlayersScheduled, {}, 0, 5 )
self:OpenCSV( GameName)
return self
end
--- Set the multiplier for scoring valid destroys (enemy destroys).
-- A calculated score is a value between 0.1 and 10.
-- The multiplier magnifies the scores given to the players.
-- @param #SCORING self
-- @param #number Multiplier The multiplier of the score given.
function SCORING:SetMultiplierDestroyScore( Multiplier )
self.MultiplierDestroyScore = Multiplier
return self
end
--- Set the multiplier for scoring penalty destroys (friendly destroys).
-- A calculated score is a value between 0.1 and 10.
-- The multiplier magnifies the scores given to the players.
-- @param #SCORING self
-- @param #number Multiplier The multiplier of the score given.
-- @return #SCORING
function SCORING:SetMultiplierDestroyPenalty( Multiplier )
self.MultiplierDestroyPenalty = Multiplier
return self
end
--- Add a @{Unit} for additional scoring when the @{Unit} is destroyed.
-- Note that if there was already a @{Unit} declared within the scoring with the same name,
-- then the old @{Unit} will be replaced with the new @{Unit}.
-- @param #SCORING self
-- @param Wrapper.Unit#UNIT ScoreUnit The @{Unit} for which the Score needs to be given.
-- @param #number Score The Score value.
-- @return #SCORING
function SCORING:AddUnitScore( ScoreUnit, Score )
local UnitName = ScoreUnit:GetName()
self.ScoringObjects[UnitName] = Score
return self
end
--- Removes a @{Unit} for additional scoring when the @{Unit} is destroyed.
-- @param #SCORING self
-- @param Wrapper.Unit#UNIT ScoreUnit The @{Unit} for which the Score needs to be given.
-- @return #SCORING
function SCORING:RemoveUnitScore( ScoreUnit )
local UnitName = ScoreUnit:GetName()
self.ScoringObjects[UnitName] = nil
return self
end
--- Add a @{Static} for additional scoring when the @{Static} is destroyed.
-- Note that if there was already a @{Static} declared within the scoring with the same name,
-- then the old @{Static} will be replaced with the new @{Static}.
-- @param #SCORING self
-- @param Wrapper.Static#UNIT ScoreStatic The @{Static} for which the Score needs to be given.
-- @param #number Score The Score value.
-- @return #SCORING
function SCORING:AddStaticScore( ScoreStatic, Score )
local StaticName = ScoreStatic:GetName()
self.ScoringObjects[StaticName] = Score
return self
end
--- Removes a @{Static} for additional scoring when the @{Static} is destroyed.
-- @param #SCORING self
-- @param Wrapper.Static#UNIT ScoreStatic The @{Static} for which the Score needs to be given.
-- @return #SCORING
function SCORING:RemoveStaticScore( ScoreStatic )
local StaticName = ScoreStatic:GetName()
self.ScoringObjects[StaticName] = nil
return self
end
--- Specify a special additional score for a @{Group}.
-- @param #SCORING self
-- @param Wrapper.Group#GROUP ScoreGroup The @{Group} for which each @{Unit} a Score is given.
-- @param #number Score The Score value.
-- @return #SCORING
function SCORING:AddScoreGroup( ScoreGroup, Score )
local ScoreUnits = ScoreGroup:GetUnits()
for ScoreUnitID, ScoreUnit in pairs( ScoreUnits ) do
local UnitName = ScoreUnit:GetName()
self.ScoringObjects[UnitName] = Score
end
return self
end
--- Add a @{Zone} to define additional scoring when any object is destroyed in that zone.
-- Note that if a @{Zone} with the same name is already within the scoring added, the @{Zone} (type) and Score will be replaced!
-- This allows for a dynamic destruction zone evolution within your mission.
-- @param #SCORING self
-- @param Core.Zone#ZONE_BASE ScoreZone The @{Zone} which defines the destruction score perimeters.
-- Note that a zone can be a polygon or a moving zone.
-- @param #number Score The Score value.
-- @return #SCORING
function SCORING:AddZoneScore( ScoreZone, Score )
local ZoneName = ScoreZone:GetName()
self.ScoringZones[ZoneName] = {}
self.ScoringZones[ZoneName].ScoreZone = ScoreZone
self.ScoringZones[ZoneName].Score = Score
return self
end
--- Remove a @{Zone} for additional scoring.
-- The scoring will search if any @{Zone} is added with the given name, and will remove that zone from the scoring.
-- This allows for a dynamic destruction zone evolution within your mission.
-- @param #SCORING self
-- @param Core.Zone#ZONE_BASE ScoreZone The @{Zone} which defines the destruction score perimeters.
-- Note that a zone can be a polygon or a moving zone.
-- @return #SCORING
function SCORING:RemoveZoneScore( ScoreZone )
local ZoneName = ScoreZone:GetName()
self.ScoringZones[ZoneName] = nil
return self
end
--- Configure to send messages after a target has been hit.
-- @param #SCORING self
-- @param #boolean OnOff If true is given, the messages are sent.
-- @return #SCORING
function SCORING:SetMessagesHit( OnOff )
self.MessagesHit = OnOff
return self
end
--- If to send messages after a target has been hit.
-- @param #SCORING self
-- @return #boolean
function SCORING:IfMessagesHit()
return self.MessagesHit
end
--- Configure to send messages after a target has been destroyed.
-- @param #SCORING self
-- @param #boolean OnOff If true is given, the messages are sent.
-- @return #SCORING
function SCORING:SetMessagesDestroy( OnOff )
self.MessagesDestroy = OnOff
return self
end
--- If to send messages after a target has been destroyed.
-- @param #SCORING self
-- @return #boolean
function SCORING:IfMessagesDestroy()
return self.MessagesDestroy
end
--- Configure to send messages after a target has been destroyed and receives additional scores.
-- @param #SCORING self
-- @param #boolean OnOff If true is given, the messages are sent.
-- @return #SCORING
function SCORING:SetMessagesScore( OnOff )
self.MessagesScore = OnOff
return self
end
--- If to send messages after a target has been destroyed and receives additional scores.
-- @param #SCORING self
-- @return #boolean
function SCORING:IfMessagesScore()
return self.MessagesScore
end
--- Configure to send messages after a target has been hit in a zone, and additional score is received.
-- @param #SCORING self
-- @param #boolean OnOff If true is given, the messages are sent.
-- @return #SCORING
function SCORING:SetMessagesZone( OnOff )
self.MessagesZone = OnOff
return self
end
--- If to send messages after a target has been hit in a zone, and additional score is received.
-- @param #SCORING self
-- @return #boolean
function SCORING:IfMessagesZone()
return self.MessagesZone
end
--- Configure to send messages to all players.
-- @param #SCORING self
-- @return #SCORING
function SCORING:SetMessagesToAll()
self.MessagesAudience = 1
return self
end
--- If to send messages to all players.
-- @param #SCORING self
-- @return #boolean
function SCORING:IfMessagesToAll()
return self.MessagesAudience == 1
end
--- Configure to send messages to only those players within the same coalition as the player.
-- @param #SCORING self
-- @return #SCORING
function SCORING:SetMessagesToCoalition()
self.MessagesAudience = 2
return self
end
--- If to send messages to only those players within the same coalition as the player.
-- @param #SCORING self
-- @return #boolean
function SCORING:IfMessagesToCoalition()
return self.MessagesAudience == 2
end
--- Add a new player entering a Unit.
-- @param #SCORING self
-- @param Wrapper.Unit#UNIT UnitData
function SCORING:_AddPlayerFromUnit( UnitData )
self:F( UnitData )
if UnitData:IsAlive() then
local UnitName = UnitData:GetName()
local PlayerName = UnitData:GetPlayerName()
local UnitDesc = UnitData:GetDesc()
local UnitCategory = UnitDesc.category
local UnitCoalition = UnitData:GetCoalition()
local UnitTypeName = UnitData:GetTypeName()
self:T( { PlayerName, UnitName, UnitCategory, UnitCoalition, UnitTypeName } )
if self.Players[PlayerName] == nil then -- I believe this is the place where a Player gets a life in a mission when he enters a unit ...
self.Players[PlayerName] = {}
self.Players[PlayerName].Hit = {}
self.Players[PlayerName].Destroy = {}
self.Players[PlayerName].Mission = {}
-- for CategoryID, CategoryName in pairs( SCORINGCategory ) do
-- self.Players[PlayerName].Hit[CategoryID] = {}
-- self.Players[PlayerName].Destroy[CategoryID] = {}
-- end
self.Players[PlayerName].HitPlayers = {}
self.Players[PlayerName].Score = 0
self.Players[PlayerName].Penalty = 0
self.Players[PlayerName].PenaltyCoalition = 0
self.Players[PlayerName].PenaltyWarning = 0
end
if not self.Players[PlayerName].UnitCoalition then
self.Players[PlayerName].UnitCoalition = UnitCoalition
else
if self.Players[PlayerName].UnitCoalition ~= UnitCoalition then
self.Players[PlayerName].Penalty = self.Players[PlayerName].Penalty + 50
self.Players[PlayerName].PenaltyCoalition = self.Players[PlayerName].PenaltyCoalition + 1
MESSAGE:New( "Player '" .. PlayerName .. "' changed coalition from " .. _SCORINGCoalition[self.Players[PlayerName].UnitCoalition] .. " to " .. _SCORINGCoalition[UnitCoalition] ..
"(changed " .. self.Players[PlayerName].PenaltyCoalition .. " times the coalition). 50 Penalty points added.",
2
):ToAll()
self:ScoreCSV( PlayerName, "COALITION_PENALTY", 1, -50, self.Players[PlayerName].UnitName, _SCORINGCoalition[self.Players[PlayerName].UnitCoalition], _SCORINGCategory[self.Players[PlayerName].UnitCategory], self.Players[PlayerName].UnitType,
UnitName, _SCORINGCoalition[UnitCoalition], _SCORINGCategory[UnitCategory], UnitData:getTypeName() )
end
end
self.Players[PlayerName].UnitName = UnitName
self.Players[PlayerName].UnitCoalition = UnitCoalition
self.Players[PlayerName].UnitCategory = UnitCategory
self.Players[PlayerName].UnitType = UnitTypeName
self.Players[PlayerName].UNIT = UnitData
if self.Players[PlayerName].Penalty > 100 then
if self.Players[PlayerName].PenaltyWarning < 1 then
MESSAGE:New( "Player '" .. PlayerName .. "': WARNING! If you continue to commit FRATRICIDE and have a PENALTY score higher than 150, you will be COURT MARTIALED and DISMISSED from this mission! \nYour total penalty is: " .. self.Players[PlayerName].Penalty,
30
):ToAll()
self.Players[PlayerName].PenaltyWarning = self.Players[PlayerName].PenaltyWarning + 1
end
end
if self.Players[PlayerName].Penalty > 150 then
local ClientGroup = GROUP:NewFromDCSUnit( UnitData )
ClientGroup:Destroy()
MESSAGE:New( "Player '" .. PlayerName .. "' committed FRATRICIDE, he will be COURT MARTIALED and is DISMISSED from this mission!",
10
):ToAll()
end
end
end
--- Registers Scores the players completing a Mission Task.
-- @param #SCORING self
-- @param Tasking.Mission#MISSION Mission
-- @param Wrapper.Unit#UNIT PlayerUnit
-- @param #string Text
-- @param #number Score
function SCORING:_AddMissionTaskScore( Mission, PlayerUnit, Text, Score )
local PlayerName = PlayerUnit:GetPlayerName()
local MissionName = Mission:GetName()
self:E( { Mission:GetName(), PlayerUnit.UnitName, PlayerName, 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.Mission[MissionName] then
PlayerData.Mission[MissionName] = {}
PlayerData.Mission[MissionName].ScoreTask = 0
PlayerData.Mission[MissionName].ScoreMission = 0
end
self:T( PlayerName )
self:T( PlayerData.Mission[MissionName] )
PlayerData.Score = self.Players[PlayerName].Score + Score
PlayerData.Mission[MissionName].ScoreTask = self.Players[PlayerName].Mission[MissionName].ScoreTask + Score
MESSAGE:New( "Player '" .. PlayerName .. "' has " .. Text .. " in Mission '" .. MissionName .. "'. " ..
Score .. " task score!",
30 ):ToAll()
self:ScoreCSV( PlayerName, "TASK_" .. MissionName:gsub( ' ', '_' ), 1, Score, PlayerUnit:GetName() )
end
end
--- Registers Mission Scores for possible multiple players that contributed in the Mission.
-- @param #SCORING self
-- @param Tasking.Mission#MISSION Mission
-- @param Wrapper.Unit#UNIT PlayerUnit
-- @param #string Text
-- @param #number Score
function SCORING:_AddMissionScore( Mission, Text, Score )
local MissionName = Mission:GetName()
self:E( { Mission, Text, Score } )
self:E( self.Players )
for PlayerName, PlayerData in pairs( self.Players ) do
self:E( PlayerData )
if PlayerData.Mission[MissionName] then
PlayerData.Score = PlayerData.Score + Score
PlayerData.Mission[MissionName].ScoreMission = PlayerData.Mission[MissionName].ScoreMission + Score
MESSAGE:New( "Player '" .. PlayerName .. "' has " .. Text .. " in Mission '" .. MissionName .. "'. " ..
Score .. " mission score!",
60 ):ToAll()
self:ScoreCSV( PlayerName, "MISSION_" .. MissionName:gsub( ' ', '_' ), 1, Score )
end
end
end
--- Handles the OnPlayerEnterUnit event for the scoring.
-- @param #SCORING self
-- @param Core.Event#EVENTDATA Event
function SCORING:OnEventPlayerEnterUnit( Event )
if Event.IniUnit then
self:_AddPlayerFromUnit( Event.IniUnit )
local Menu = MENU_GROUP:New( Event.IniGroup, 'Scoring' )
local GroupReport = MENU_GROUP_COMMAND:New( Event.IniGroup, 'Overview Report', Menu, SCORING.ReportScoreGroup, self, Event.IniGroup )
--local DetailedReport = MENU_GROUP_COMMAND:New( Event.IniGroup, 'Detailed Report', self.Menu, SCORING.ReportDetailed, self )
self:SetState( Event.IniUnit, "ScoringMenu", Menu )
end
end
--- Handles the OnPlayerLeaveUnit event for the scoring.
-- @param #SCORING self
-- @param Core.Event#EVENTDATA Event
function SCORING:OnEventPlayerLeaveUnit( Event )
if Event.IniUnit then
local Menu = self:GetState( Event.IniUnit, "ScoringMenu" ) -- Core.Menu#MENU_GROUP
if Menu then
Menu:Remove()
end
end
end
--- Handles the OnHit event for the scoring.
-- @param #SCORING self
-- @param Core.Event#EVENTDATA Event
function SCORING:_EventOnHit( Event )
self:F( { Event } )
local InitUnit = nil
local InitUNIT = nil
local InitUnitName = ""
local InitGroup = nil
local InitGroupName = ""
local InitPlayerName = nil
local InitCoalition = nil
local InitCategory = nil
local InitType = nil
local InitUnitCoalition = nil
local InitUnitCategory = nil
local InitUnitType = nil
local TargetUnit = nil
local TargetUNIT = nil
local TargetUnitName = ""
local TargetGroup = nil
local TargetGroupName = ""
local TargetPlayerName = nil
local TargetCoalition = nil
local TargetCategory = nil
local TargetType = nil
local TargetUnitCoalition = nil
local TargetUnitCategory = nil
local TargetUnitType = nil
if Event.IniDCSUnit then
InitUnit = Event.IniDCSUnit
InitUNIT = Event.IniUnit
InitUnitName = Event.IniDCSUnitName
InitGroup = Event.IniDCSGroup
InitGroupName = Event.IniDCSGroupName
InitPlayerName = Event.IniPlayerName
InitCoalition = Event.IniCoalition
--TODO: Workaround Client DCS Bug
--InitCategory = InitUnit:getCategory()
--InitCategory = InitUnit:getDesc().category
InitCategory = Event.IniCategory
InitType = Event.IniTypeName
InitUnitCoalition = _SCORINGCoalition[InitCoalition]
InitUnitCategory = _SCORINGCategory[InitCategory]
InitUnitType = InitType
self:T( { InitUnitName, InitGroupName, InitPlayerName, InitCoalition, InitCategory, InitType , InitUnitCoalition, InitUnitCategory, InitUnitType } )
end
if Event.TgtDCSUnit then
TargetUnit = Event.TgtDCSUnit
TargetUNIT = Event.TgtUnit
TargetUnitName = Event.TgtDCSUnitName
TargetGroup = Event.TgtDCSGroup
TargetGroupName = Event.TgtDCSGroupName
TargetPlayerName = Event.TgtPlayerName
TargetCoalition = Event.TgtCoalition
--TODO: Workaround Client DCS Bug
--TargetCategory = TargetUnit:getCategory()
--TargetCategory = TargetUnit:getDesc().category
TargetCategory = Event.TgtCategory
TargetType = Event.TgtTypeName
TargetUnitCoalition = _SCORINGCoalition[TargetCoalition]
TargetUnitCategory = _SCORINGCategory[TargetCategory]
TargetUnitType = TargetType
self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType, TargetUnitCoalition, TargetUnitCategory, TargetUnitType } )
end
if InitPlayerName ~= nil then -- It is a player that is hitting something
self:_AddPlayerFromUnit( InitUNIT )
if self.Players[InitPlayerName] then -- This should normally not happen, but i'll test it anyway.
if TargetPlayerName ~= nil then -- It is a player hitting another player ...
self:_AddPlayerFromUnit( TargetUNIT )
end
self:T( "Hitting Something" )
-- What is he hitting?
if TargetCategory then
-- A target got hit, score it.
-- Player contains the score data from self.Players[InitPlayerName]
local Player = self.Players[InitPlayerName]
-- Ensure there is a hit table per TargetCategory and TargetUnitName.
Player.Hit[TargetCategory] = Player.Hit[TargetCategory] or {}
Player.Hit[TargetCategory][TargetUnitName] = Player.Hit[TargetCategory][TargetUnitName] or {}
-- PlayerHit contains the score counters and data per unit that was hit.
local PlayerHit = Player.Hit[TargetCategory][TargetUnitName]
PlayerHit.Score = PlayerHit.Score or 0
PlayerHit.Penalty = PlayerHit.Penalty or 0
PlayerHit.ScoreHit = PlayerHit.ScoreHit or 0
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0
PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0
PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT
-- Only grant hit scores if there was more than one second between the last hit.
if timer.getTime() - PlayerHit.TimeStamp > 1 then
PlayerHit.TimeStamp = timer.getTime()
if TargetPlayerName ~= nil then -- It is a player hitting another player ...
-- Ensure there is a Player to Player hit reference table.
Player.HitPlayers[TargetPlayerName] = true
end
local Score = 0
if InitCoalition then -- A coalition object was hit.
if InitCoalition == TargetCoalition then
Player.Penalty = Player.Penalty + 10
PlayerHit.Penalty = PlayerHit.Penalty + 10
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1
if TargetPlayerName ~= nil then -- It is a player hitting another player ...
MESSAGE
:New( "Player '" .. InitPlayerName .. "' hit friendly player '" .. TargetPlayerName .. "' " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.PenaltyHit .. " times. " ..
"Penalty: -" .. PlayerHit.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty,
2
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
else
MESSAGE
:New( "Player '" .. InitPlayerName .. "' hit a friendly target " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.PenaltyHit .. " times. " ..
"Penalty: -" .. PlayerHit.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty,
2
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
end
self:ScoreCSV( InitPlayerName, "HIT_PENALTY", 1, -25, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
else
Player.Score = Player.Score + 1
PlayerHit.Score = PlayerHit.Score + 1
PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1
if TargetPlayerName ~= nil then -- It is a player hitting another player ...
MESSAGE
:New( "Player '" .. InitPlayerName .. "' hit enemy player '" .. TargetPlayerName .. "' " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.ScoreHit .. " times. " ..
"Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty,
2
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
else
MESSAGE
:New( "Player '" .. InitPlayerName .. "' hit an enemy target " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.ScoreHit .. " times. " ..
"Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty,
2
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
end
self:ScoreCSV( InitPlayerName, "HIT_SCORE", 1, 1, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
end
else -- A scenery object was hit.
MESSAGE
:New( "Player '" .. InitPlayerName .. "' hit a scenery object.",
2
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
self:ScoreCSV( InitPlayerName, "HIT_SCORE", 1, 1, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Scenery", TargetUnitType )
end
end
end
end
elseif InitPlayerName == nil then -- It is an AI hitting a player???
end
end
--- Track DEAD or CRASH events for the scoring.
-- @param #SCORING self
-- @param Core.Event#EVENTDATA Event
function SCORING:_EventOnDeadOrCrash( Event )
self:F( { Event } )
local TargetUnit = nil
local TargetGroup = nil
local TargetUnitName = ""
local TargetGroupName = ""
local TargetPlayerName = ""
local TargetCoalition = nil
local TargetCategory = nil
local TargetType = nil
local TargetUnitCoalition = nil
local TargetUnitCategory = nil
local TargetUnitType = nil
if Event.IniDCSUnit then
TargetUnit = Event.IniDCSUnit
TargetUnitName = Event.IniDCSUnitName
TargetGroup = Event.IniDCSGroup
TargetGroupName = Event.IniDCSGroupName
TargetPlayerName = Event.IniPlayerName
TargetCoalition = Event.IniCoalition
--TargetCategory = TargetUnit:getCategory()
--TargetCategory = TargetUnit:getDesc().category -- Workaround
TargetCategory = Event.IniCategory
TargetType = Event.IniTypeName
TargetUnitCoalition = _SCORINGCoalition[TargetCoalition]
TargetUnitCategory = _SCORINGCategory[TargetCategory]
TargetUnitType = TargetType
self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType } )
end
-- Player contains the score and reference data for the player.
for PlayerName, Player in pairs( self.Players ) do
if Player then -- This should normally not happen, but i'll test it anyway.
self:T( "Something got destroyed" )
-- Some variables
local InitUnitName = Player.UnitName
local InitUnitType = Player.UnitType
local InitCoalition = Player.UnitCoalition
local InitCategory = Player.UnitCategory
local InitUnitCoalition = _SCORINGCoalition[InitCoalition]
local InitUnitCategory = _SCORINGCategory[InitCategory]
self:T( { InitUnitName, InitUnitType, InitUnitCoalition, InitCoalition, InitUnitCategory, InitCategory } )
-- What is he hitting?
if Player and Player.Hit and Player.Hit[TargetCategory] and Player.Hit[TargetCategory][TargetUnitName] then -- Was there a hit for this unit for this player before registered???
Player.Destroy[TargetCategory] = Player.Destroy[TargetCategory] or {}
Player.Destroy[TargetCategory][TargetType] = Player.Destroy[TargetCategory][TargetType] or {}
-- PlayerDestroy contains the destroy score data per category and target type of the player.
local TargetDestroy = Player.Destroy[TargetCategory][TargetType]
Player.Destroy[TargetCategory][TargetType] = {}
TargetDestroy.Score = TargetDestroy.Score or 0
TargetDestroy.ScoreDestroy = TargetDestroy.ScoreDestroy or 0
TargetDestroy.Penalty = TargetDestroy.Penalty or 0
TargetDestroy.PenaltyDestroy = TargetDestroy.PenaltyDestroy or 0
TargetDestroy.UNIT = TargetDestroy.UNIT or Player.Hit[TargetCategory][TargetUnitName].UNIT
if TargetCoalition then
if InitCoalition == TargetCoalition then
local ThreatLevelTarget, ThreatTypeTarget = TargetDestroy.UNIT:GetThreatLevel()
local ThreatLevelPlayer = Player.UNIT:GetThreatLevel()
local ThreatPenalty = math.ceil( ( ThreatLevelTarget / ThreatLevelPlayer ) * self.MultiplierDestroyPenalty )
self:E( { ThreatLevel = ThreatPenalty, ThreatLevelTarget = ThreatLevelTarget, ThreatTypeTarget = ThreatTypeTarget, ThreatLevelPlayer = ThreatLevelPlayer } )
Player.Penalty = Player.Penalty + ThreatPenalty
TargetDestroy.Penalty = TargetDestroy.Penalty + ThreatPenalty
TargetDestroy.PenaltyDestroy = TargetDestroy.PenaltyDestroy + 1
if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player
MESSAGE
:New( "Player '" .. PlayerName .. "' destroyed friendly player '" .. TargetPlayerName .. "' " ..
TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. TargetDestroy.PenaltyDestroy .. " times. " ..
"Penalty: -" .. TargetDestroy.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty,
15
)
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
else
MESSAGE
:New( "Player '" .. PlayerName .. "' destroyed a friendly target " ..
TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. TargetDestroy.PenaltyDestroy .. " times. " ..
"Penalty: -" .. TargetDestroy.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty,
15
)
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
end
self:ScoreCSV( PlayerName, "DESTROY_PENALTY", 1, ThreatPenalty, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
else
local ThreatLevelTarget, ThreatTypeTarget = TargetDestroy.UNIT:GetThreatLevel()
local ThreatLevelPlayer = Player.UNIT:GetThreatLevel()
local ThreatScore = math.ceil( ( ThreatLevelTarget / ThreatLevelPlayer ) * self.MultiplierDestroyScore )
self:E( { ThreatLevel = ThreatScore, ThreatLevelTarget = ThreatLevelTarget, ThreatTypeTarget = ThreatTypeTarget, ThreatLevelPlayer = ThreatLevelPlayer } )
Player.Score = Player.Score + ThreatScore
TargetDestroy.Score = TargetDestroy.Score + ThreatScore
TargetDestroy.ScoreDestroy = TargetDestroy.ScoreDestroy + 1
if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player
MESSAGE
:New( "Player '" .. PlayerName .. "' destroyed enemy player '" .. TargetPlayerName .. "' " ..
TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. TargetDestroy.ScoreDestroy .. " times. " ..
"Score: " .. TargetDestroy.Score .. ". Score Total:" .. Player.Score - Player.Penalty,
15
)
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
else
MESSAGE
:New( "Player '" .. PlayerName .. "' destroyed an enemy " ..
TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. TargetDestroy.ScoreDestroy .. " times. " ..
"Score: " .. TargetDestroy.Score .. ". Total:" .. Player.Score - Player.Penalty,
15
)
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
end
self:ScoreCSV( PlayerName, "DESTROY_SCORE", 1, ThreatScore, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
local UnitName = TargetDestroy.UNIT:GetName()
local Score = self.ScoringObjects[UnitName]
if Score then
Player.Score = Player.Score + Score
TargetDestroy.Score = TargetDestroy.Score + Score
MESSAGE
:New( "Special target '" .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. " destroyed! " ..
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! Total: " .. Player.Score - Player.Penalty,
15
)
:ToAllIf( self:IfMessagesScore() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesScore() and self:IfMessagesToCoalition() )
self:ScoreCSV( PlayerName, "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
end
-- Check if there are Zones where the destruction happened.
for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do
self:E( { ScoringZone = ScoreZoneData } )
local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE
local Score = ScoreZoneData.Score
if ScoreZone:IsVec2InZone( TargetDestroy.UNIT:GetVec2() ) then
Player.Score = Player.Score + Score
TargetDestroy.Score = TargetDestroy.Score + Score
MESSAGE
:New( "Target hit in zone '" .. ScoreZone:GetName() .. "'." ..
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " ..
"Total: " .. Player.Score - Player.Penalty,
15 )
: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, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
end
end
end
else
-- Check if there are Zones where the destruction happened.
for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do
self:E( { ScoringZone = ScoreZoneData } )
local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE
local Score = ScoreZoneData.Score
if ScoreZone:IsVec2InZone( TargetDestroy.UNIT:GetVec2() ) then
Player.Score = Player.Score + Score
TargetDestroy.Score = TargetDestroy.Score + Score
MESSAGE
:New( "Scenery hit in zone '" .. ScoreZone:GetName() .. "'." ..
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " ..
"Total: " .. Player.Score - Player.Penalty,
15
)
: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 )
end
end
end
end
end
end
end
--- Produce detailed report of player hit scores.
-- @param #SCORING self
-- @param #string PlayerName The name of the player.
-- @return #string The report.
function SCORING:ReportDetailedPlayerHits( PlayerName )
local ScoreMessage = ""
local PlayerScore = 0
local PlayerPenalty = 0
local PlayerData = self.Players[PlayerName]
if PlayerData then -- This should normally not happen, but i'll test it anyway.
self:T( "Score Player: " .. PlayerName )
-- Some variables
local InitUnitCoalition = _SCORINGCoalition[PlayerData.UnitCoalition]
local InitUnitCategory = _SCORINGCategory[PlayerData.UnitCategory]
local InitUnitType = PlayerData.UnitType
local InitUnitName = PlayerData.UnitName
local ScoreMessageHits = ""
for CategoryID, CategoryName in pairs( _SCORINGCategory ) do
self:T( CategoryName )
if PlayerData.Hit[CategoryID] then
self:T( "Hit scores exist for player " .. PlayerName )
local Score = 0
local ScoreHit = 0
local Penalty = 0
local PenaltyHit = 0
for UnitName, UnitData in pairs( PlayerData.Hit[CategoryID] ) do
Score = Score + UnitData.Score
ScoreHit = ScoreHit + UnitData.ScoreHit
Penalty = Penalty + UnitData.Penalty
PenaltyHit = UnitData.PenaltyHit
end
local ScoreMessageHit = string.format( "%s:%d ", CategoryName, Score - Penalty )
self:T( ScoreMessageHit )
ScoreMessageHits = ScoreMessageHits .. ScoreMessageHit
PlayerScore = PlayerScore + Score
PlayerPenalty = PlayerPenalty + Penalty
else
--ScoreMessageHits = ScoreMessageHits .. string.format( "%s:%d ", string.format(CategoryName, 1, 1), 0 )
end
end
if ScoreMessageHits ~= "" then
ScoreMessage = "Hits: " .. ScoreMessageHits .. "\n"
end
end
return ScoreMessage, PlayerScore, PlayerPenalty
end
--- Produce detailed report of player destroy scores.
-- @param #SCORING self
-- @param #string PlayerName The name of the player.
-- @return #string The report.
function SCORING:ReportDetailedPlayerDestroys( PlayerName )
local ScoreMessage = ""
local PlayerScore = 0
local PlayerPenalty = 0
local PlayerData = self.Players[PlayerName]
if PlayerData then -- This should normally not happen, but i'll test it anyway.
self:T( "Score Player: " .. PlayerName )
-- Some variables
local InitUnitCoalition = _SCORINGCoalition[PlayerData.UnitCoalition]
local InitUnitCategory = _SCORINGCategory[PlayerData.UnitCategory]
local InitUnitType = PlayerData.UnitType
local InitUnitName = PlayerData.UnitName
local ScoreMessageDestroys = ""
for CategoryID, CategoryName in pairs( _SCORINGCategory ) do
if PlayerData.Destroy[CategoryID] then
self:T( "Destroy scores exist for player " .. PlayerName )
local Score = 0
local ScoreDestroy = 0
local Penalty = 0
local PenaltyDestroy = 0
for UnitName, UnitData in pairs( PlayerData.Destroy[CategoryID] ) do
Score = Score + UnitData.Score
ScoreDestroy = ScoreDestroy + UnitData.ScoreDestroy
Penalty = Penalty + UnitData.Penalty
PenaltyDestroy = PenaltyDestroy + UnitData.PenaltyDestroy
end
local ScoreMessageDestroy = string.format( " %s:%d ", CategoryName, Score - Penalty )
self:T( ScoreMessageDestroy )
ScoreMessageDestroys = ScoreMessageDestroys .. ScoreMessageDestroy
PlayerScore = PlayerScore + Score
PlayerPenalty = PlayerPenalty + Penalty
else
--ScoreMessageDestroys = ScoreMessageDestroys .. string.format( "%s:%d ", string.format(CategoryName, 1, 1), 0 )
end
end
if ScoreMessageDestroys ~= "" then
ScoreMessage = ScoreMessage .. " Destroys: " .. ScoreMessageDestroys .. "\n"
end
end
return ScoreMessage, PlayerScore, PlayerPenalty
end
--- Produce detailed report of player penalty scores because of changing the coalition.
-- @param #SCORING self
-- @param #string PlayerName The name of the player.
-- @return #string The report.
function SCORING:ReportDetailedPlayerCoalitionChanges( PlayerName )
local ScoreMessage = ""
local PlayerScore = 0
local PlayerPenalty = 0
local PlayerData = self.Players[PlayerName]
if PlayerData then -- This should normally not happen, but i'll test it anyway.
self:T( "Score Player: " .. PlayerName )
-- Some variables
local InitUnitCoalition = _SCORINGCoalition[PlayerData.UnitCoalition]
local InitUnitCategory = _SCORINGCategory[PlayerData.UnitCategory]
local InitUnitType = PlayerData.UnitType
local InitUnitName = PlayerData.UnitName
local ScoreMessageCoalitionChangePenalties = ""
if PlayerData.PenaltyCoalition ~= 0 then
ScoreMessageCoalitionChangePenalties = ScoreMessageCoalitionChangePenalties .. string.format( " -%d (%d changed)", PlayerData.Penalty, PlayerData.PenaltyCoalition )
PlayerPenalty = PlayerPenalty + PlayerData.Penalty
end
if ScoreMessageCoalitionChangePenalties ~= "" then
ScoreMessage = ScoreMessage .. " Coalition Penalties: " .. ScoreMessageCoalitionChangePenalties .. "\n"
end
end
return ScoreMessage, PlayerScore, PlayerPenalty
end
--- Produce detailed report of player penalty scores because of changing the coalition.
-- @param #SCORING self
-- @param #string PlayerName The name of the player.
-- @return #string The report.
function SCORING:ReportDetailedPlayerMissions( PlayerName )
local ScoreMessage = ""
local PlayerScore = 0
local PlayerPenalty = 0
local PlayerData = self.Players[PlayerName]
if PlayerData then -- This should normally not happen, but i'll test it anyway.
self:T( "Score Player: " .. PlayerName )
-- Some variables
local InitUnitCoalition = _SCORINGCoalition[PlayerData.UnitCoalition]
local InitUnitCategory = _SCORINGCategory[PlayerData.UnitCategory]
local InitUnitType = PlayerData.UnitType
local InitUnitName = PlayerData.UnitName
local PlayerScore = 0
local PlayerPenalty = 0
local ScoreMessageMission = ""
local ScoreMission = 0
local ScoreTask = 0
for MissionName, MissionData in pairs( PlayerData.Mission ) do
ScoreMission = ScoreMission + MissionData.ScoreMission
ScoreTask = ScoreTask + MissionData.ScoreTask
ScoreMessageMission = ScoreMessageMission .. "'" .. MissionName .. "'; "
end
PlayerScore = PlayerScore + ScoreMission + ScoreTask
if ScoreMessageMission ~= "" then
ScoreMessage = ScoreMessage .. " Tasks: " .. ScoreTask .. " Mission: " .. ScoreMission .. " ( " .. ScoreMessageMission .. ")\n"
end
end
return ScoreMessage, PlayerScore, PlayerPenalty
end
--- Report Group Score
-- @param #SCORING self
-- @param Wrapper.Group#GROUP PlayerGroup The player group.
function SCORING:ReportScoreGroup( PlayerGroup )
local PlayerMessage = ""
self:T( "Report Score Group" )
local PlayerUnits = PlayerGroup:GetUnits()
for UnitID, PlayerUnit in pairs( PlayerUnits ) do
local PlayerUnit = PlayerUnit -- Wrapper.Unit#UNIT
local PlayerName = PlayerUnit:GetPlayerName()
if PlayerName then
local ReportHits, ScoreHits, PenaltyHits = self:ReportDetailedPlayerHits( PlayerName )
self:E( { ReportHits, ScoreHits, PenaltyHits } )
local ReportDestroys, ScoreDestroys, PenaltyDestroys = self:ReportDetailedPlayerDestroys( PlayerName )
self:E( { ReportDestroys, ScoreDestroys, PenaltyDestroys } )
local ReportCoalitionChanges, ScoreCoalitionChanges, PenaltyCoalitionChanges = self:ReportDetailedPlayerCoalitionChanges( PlayerName )
self:E( { ReportCoalitionChanges, ScoreCoalitionChanges, PenaltyCoalitionChanges } )
local ReportMissions, ScoreMissions, PenaltyMissions = self:ReportDetailedPlayerMissions( PlayerName )
self:E( { ReportMissions, ScoreMissions, PenaltyMissions } )
local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreMissions
local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + PenaltyMissions
PlayerMessage =
string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties ):%s%s%s%s",
PlayerName,
PlayerScore - PlayerPenalty,
PlayerScore,
PlayerPenalty,
ReportHits,
ReportDestroys,
ReportCoalitionChanges,
ReportMissions
)
MESSAGE:New( PlayerMessage, 30, "Player '" .. PlayerName .. "'" ):ToGroup( PlayerGroup )
end
end
end
function SCORING:SecondsToClock(sSeconds)
local nSeconds = sSeconds
if nSeconds == 0 then
--return nil;
return "00:00:00";
else
nHours = string.format("%02.f", math.floor(nSeconds/3600));
nMins = string.format("%02.f", math.floor(nSeconds/60 - (nHours*60)));
nSecs = string.format("%02.f", math.floor(nSeconds - nHours*3600 - nMins *60));
return nHours..":"..nMins..":"..nSecs
end
end
--- Opens a score CSV file to log the scores.
-- @param #SCORING self
-- @param #string ScoringCSV
-- @return #SCORING self
-- @usage
-- -- Open a new CSV file to log the scores of the game Gori Valley. Let the name of the CSV file begin with "Player Scores".
-- ScoringObject = SCORING:New( "Gori Valley" )
-- ScoringObject:OpenCSV( "Player Scores" )
function SCORING:OpenCSV( ScoringCSV )
self:F( ScoringCSV )
if lfs and io and os then
if ScoringCSV then
self.ScoringCSV = ScoringCSV
local fdir = lfs.writedir() .. [[Logs\]] .. self.ScoringCSV .. " " .. os.date( "%Y-%m-%d %H-%M-%S" ) .. ".csv"
self.CSVFile, self.err = io.open( fdir, "w+" )
if not self.CSVFile then
error( "Error: Cannot open CSV file in " .. lfs.writedir() )
end
self.CSVFile:write( '"GameName","RunTime","Time","PlayerName","ScoreType","PlayerUnitCoaltion","PlayerUnitCategory","PlayerUnitType","PlayerUnitName","TargetUnitCoalition","TargetUnitCategory","TargetUnitType","TargetUnitName","Times","Score"\n' )
self.RunTime = os.date("%y-%m-%d_%H-%M-%S")
else
error( "A string containing the CSV file name must be given." )
end
else
self:E( "The MissionScripting.lua file has not been changed to allow lfs, io and os modules to be used..." )
end
return self
end
--- Registers a score for a player.
-- @param #SCORING self
-- @param #string PlayerName The name of the player.
-- @param #string ScoreType The type of the score.
-- @param #string ScoreTimes The amount of scores achieved.
-- @param #string ScoreAmount The score given.
-- @param #string PlayerUnitName The unit name of the player.
-- @param #string PlayerUnitCoalition The coalition of the player unit.
-- @param #string PlayerUnitCategory The category of the player unit.
-- @param #string PlayerUnitType The type of the player unit.
-- @param #string TargetUnitName The name of the target unit.
-- @param #string TargetUnitCoalition The coalition of the target unit.
-- @param #string TargetUnitCategory The category of the target unit.
-- @param #string TargetUnitType The type of the target unit.
-- @return #SCORING self
function SCORING:ScoreCSV( PlayerName, ScoreType, ScoreTimes, ScoreAmount, PlayerUnitName, PlayerUnitCoalition, PlayerUnitCategory, PlayerUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
--write statistic information to file
local ScoreTime = self:SecondsToClock( timer.getTime() )
PlayerName = PlayerName:gsub( '"', '_' )
if PlayerUnitName and PlayerUnitName ~= '' then
local PlayerUnit = Unit.getByName( PlayerUnitName )
if PlayerUnit then
if not PlayerUnitCategory then
--PlayerUnitCategory = SCORINGCategory[PlayerUnit:getCategory()]
PlayerUnitCategory = _SCORINGCategory[PlayerUnit:getDesc().category]
end
if not PlayerUnitCoalition then
PlayerUnitCoalition = _SCORINGCoalition[PlayerUnit:getCoalition()]
end
if not PlayerUnitType then
PlayerUnitType = PlayerUnit:getTypeName()
end
else
PlayerUnitName = ''
PlayerUnitCategory = ''
PlayerUnitCoalition = ''
PlayerUnitType = ''
end
else
PlayerUnitName = ''
PlayerUnitCategory = ''
PlayerUnitCoalition = ''
PlayerUnitType = ''
end
TargetUnitCoalition = TargetUnitCoalition or ""
TargetUnitCategory = TargetUnitCategory or ""
TargetUnitType = TargetUnitType or ""
TargetUnitName = TargetUnitName or ""
if lfs and io and os then
self.CSVFile:write(
'"' .. self.GameName .. '"' .. ',' ..
'"' .. self.RunTime .. '"' .. ',' ..
'' .. ScoreTime .. '' .. ',' ..
'"' .. PlayerName .. '"' .. ',' ..
'"' .. ScoreType .. '"' .. ',' ..
'"' .. PlayerUnitCoalition .. '"' .. ',' ..
'"' .. PlayerUnitCategory .. '"' .. ',' ..
'"' .. PlayerUnitType .. '"' .. ',' ..
'"' .. PlayerUnitName .. '"' .. ',' ..
'"' .. TargetUnitCoalition .. '"' .. ',' ..
'"' .. TargetUnitCategory .. '"' .. ',' ..
'"' .. TargetUnitType .. '"' .. ',' ..
'"' .. TargetUnitName .. '"' .. ',' ..
'' .. ScoreTimes .. '' .. ',' ..
'' .. ScoreAmount
)
self.CSVFile:write( "\n" )
end
end
function SCORING:CloseCSV()
if lfs and io and os then
self.CSVFile:close()
end
end
--- Produce detailed report of player hit scores.
-- @param #SCORING self
-- @param #string PlayerName The name of the player.
-- @return #string The report.
function SCORING:_ReportDetailedPlayerHits( PlayerName )
local ScoreMessage = ""
local PlayerMessage = ""
self:T( "Score Report" )
local PlayerData = self.Players[PlayerName]
if PlayerData then -- This should normally not happen, but i'll test it anyway.
self:T( "Score Player: " .. PlayerName )
-- Some variables
local InitUnitCoalition = _SCORINGCoalition[PlayerData.UnitCoalition]
local InitUnitCategory = _SCORINGCategory[PlayerData.UnitCategory]
local InitUnitType = PlayerData.UnitType
local InitUnitName = PlayerData.UnitName
local PlayerScore = 0
local PlayerPenalty = 0
ScoreMessage = ":\n"
local ScoreMessageHits = ""
for CategoryID, CategoryName in pairs( _SCORINGCategory ) do
self:T( CategoryName )
if PlayerData.Hit[CategoryID] then
local Score = 0
local ScoreHit = 0
local Penalty = 0
local PenaltyHit = 0
self:T( "Hit scores exist for player " .. PlayerName )
for UnitName, UnitData in pairs( PlayerData.Hit[CategoryID] ) do
Score = Score + UnitData.Score
ScoreHit = ScoreHit + UnitData.ScoreHit
Penalty = Penalty + UnitData.Penalty
PenaltyHit = UnitData.PenaltyHit
end
local ScoreMessageHit = string.format( "%s:%d ", CategoryName, Score - Penalty )
self:T( ScoreMessageHit )
ScoreMessageHits = ScoreMessageHits .. ScoreMessageHit
PlayerScore = PlayerScore + Score
PlayerPenalty = PlayerPenalty + Penalty
else
--ScoreMessageHits = ScoreMessageHits .. string.format( "%s:%d ", string.format(CategoryName, 1, 1), 0 )
end
end
if ScoreMessageHits ~= "" then
ScoreMessage = ScoreMessage .. " Hits: " .. ScoreMessageHits .. "\n"
end
local ScoreMessageDestroys = ""
for CategoryID, CategoryName in pairs( _SCORINGCategory ) do
self:T( "Destroy scores exist for player " .. PlayerName )
if PlayerData.Destroy[CategoryID] then
local Score = 0
local ScoreDestroy = 0
local Penalty = 0
local PenaltyDestroy = 0
for UnitName, UnitData in pairs( PlayerData.Destroy[CategoryID] ) do
Score = Score + UnitData.Score
ScoreDestroy = ScoreDestroy + UnitData.ScoreDestroy
Penalty = Penalty + UnitData.Penalty
PenaltyDestroy = PenaltyDestroy + UnitData.PenaltyDestroy
end
local ScoreMessageDestroy = string.format( " %s:%d ", CategoryName, Score - Penalty )
self:T( ScoreMessageDestroy )
ScoreMessageDestroys = ScoreMessageDestroys .. ScoreMessageDestroy
PlayerScore = PlayerScore + Score
PlayerPenalty = PlayerPenalty + Penalty
else
--ScoreMessageDestroys = ScoreMessageDestroys .. string.format( "%s:%d ", string.format(CategoryName, 1, 1), 0 )
end
end
if ScoreMessageDestroys ~= "" then
ScoreMessage = ScoreMessage .. " Destroys: " .. ScoreMessageDestroys .. "\n"
end
local ScoreMessageCoalitionChangePenalties = ""
if PlayerData.PenaltyCoalition ~= 0 then
ScoreMessageCoalitionChangePenalties = ScoreMessageCoalitionChangePenalties .. string.format( " -%d (%d changed)", PlayerData.Penalty, PlayerData.PenaltyCoalition )
PlayerPenalty = PlayerPenalty + PlayerData.Penalty
end
if ScoreMessageCoalitionChangePenalties ~= "" then
ScoreMessage = ScoreMessage .. " Coalition Penalties: " .. ScoreMessageCoalitionChangePenalties .. "\n"
end
local ScoreMessageMission = ""
local ScoreMission = 0
local ScoreTask = 0
for MissionName, MissionData in pairs( PlayerData.Mission ) do
ScoreMission = ScoreMission + MissionData.ScoreMission
ScoreTask = ScoreTask + MissionData.ScoreTask
ScoreMessageMission = ScoreMessageMission .. "'" .. MissionName .. "'; "
end
PlayerScore = PlayerScore + ScoreMission + ScoreTask
if ScoreMessageMission ~= "" then
ScoreMessage = ScoreMessage .. " Tasks: " .. ScoreTask .. " Mission: " .. ScoreMission .. " ( " .. ScoreMessageMission .. ")\n"
end
PlayerMessage = PlayerMessage .. string.format( "Player '%s' Score:%d (%d Score -%d Penalties)%s", PlayerName, PlayerScore - PlayerPenalty, PlayerScore, PlayerPenalty, ScoreMessage )
end
MESSAGE:New( PlayerMessage, 30, "Player Scores" ):ToAll()
end