mirror of
https://github.com/akaAgar/the-universal-mission-for-dcs-world.git
synced 2025-11-25 19:31:01 +00:00
312 lines
13 KiB
Lua
312 lines
13 KiB
Lua
-- ====================================================================================
|
|
-- TUM.PLAYERCAREER - HANDLES THE PERSISTENT PILOT CAREER IN SINGLE-PLAYER MISSIONS
|
|
-- ====================================================================================
|
|
-- (local const) MAX_RIBBONS
|
|
-- (local const) MEDAL_BOX_DISPLAY_TIME
|
|
-- (local const) OBJECTIVES_PER_RIBBON
|
|
-- (local const) MEDALS
|
|
-- (local const) RANKS
|
|
-- (local table) careerStats
|
|
-- (local) fixIncompleteStats()
|
|
-- (local) getHighestMedal()
|
|
-- (local) getRibbonCount()
|
|
-- TUM.playerCareer.awardScore(score, objectives)
|
|
-- TUM.playerCareer.createMenu()
|
|
-- TUM.playerCareer.displayMedalBox(printSummary)
|
|
-- TUM.playerCareer.getCareerSummary()
|
|
-- TUM.playerCareer.load()
|
|
-- TUM.playerCareer.onStartUp()
|
|
-- TUM.playerCareer.reset()
|
|
-- TUM.playerCareer.save()
|
|
-- ====================================================================================
|
|
|
|
TUM.playerCareer = {}
|
|
|
|
do
|
|
local MAX_RIBBONS = 40 -- Maximum number of ribbons
|
|
local MEDAL_BOX_DISPLAY_TIME = 15 -- in seconds
|
|
local OBJECTIVES_PER_RIBBON = 4 -- How many completed objectives to gain a new ribbon?
|
|
|
|
local MEDALS = {
|
|
{ "Air medal", 150 },
|
|
{ "Bronze star", 225 },
|
|
{ "Airman's medal", 300 },
|
|
{ "Distinguished Flying Cross", 375 },
|
|
{ "Silver Star for Valor", 450 },
|
|
{ "Air Force Cross", 525 },
|
|
{ "Congressional Medal of Honor", 600 },
|
|
}
|
|
|
|
local RANKS = {
|
|
{ "2d Lt.", "Second lieutenant", 0 },
|
|
{ "1st Lt.", "First lieutenant", 400 },
|
|
{ "Capt.", "Captain", 1600 },
|
|
{ "Maj.", "Major", 6400 },
|
|
{ "Lt Col.", "Lieutenant colonel", 16000 },
|
|
{ "Col.", "Colonel", 32000 },
|
|
}
|
|
|
|
local careerStats = {}
|
|
|
|
-------------------------------------
|
|
-- Adds missing fields, if any, to the careerStats table
|
|
-------------------------------------
|
|
local function fixIncompleteStats()
|
|
if not careerStats then careerStats = { } end
|
|
if not careerStats.bestSortie then careerStats.bestSortie = 0 end
|
|
if not careerStats.completedObjectives then careerStats.completedObjectives = 0 end
|
|
if not careerStats.completedSorties then careerStats.completedSorties = 0 end
|
|
if not careerStats.medals then careerStats.medals = 0 end
|
|
if not careerStats.medalWounded then careerStats.medalWounded = false end
|
|
if not careerStats.rank then careerStats.rank = 1 end
|
|
if not careerStats.score then careerStats.score = 0 end
|
|
careerStats.version = TUM.VERSION_NUMBER
|
|
end
|
|
|
|
-------------------------------------
|
|
-- Returns the highest medal a player has obtained for scoring a high number of points during a single sortie
|
|
-- @return A number (index in the MEDALS table)
|
|
-------------------------------------
|
|
local function getHighestMedal()
|
|
local medal = 0
|
|
for i=1,#MEDALS do
|
|
if careerStats.bestSortie >= MEDALS[i][2] then
|
|
medal = i
|
|
end
|
|
end
|
|
|
|
return medal
|
|
end
|
|
|
|
-------------------------------------
|
|
-- Returns the current number a ribbons a player was awarded for completing objectives
|
|
-- @return A number
|
|
-------------------------------------
|
|
local function getRibbonCount()
|
|
return DCSEx.math.clamp(math.floor(careerStats.completedObjectives / OBJECTIVES_PER_RIBBON), 0, MAX_RIBBONS)
|
|
end
|
|
|
|
-------------------------------------
|
|
-- Awards career points. Only works in single-player missions
|
|
-- @param score Number of career points to award
|
|
-- @param objectives Number of completed objectives to award
|
|
-- @return True if a ribbon, medal or promotion was awarded, false otherwise
|
|
-------------------------------------
|
|
function TUM.playerCareer.awardScore(score, objectives)
|
|
if not DCSEx.io.canReadAndWrite() then return false end -- IO disabled, career and scoring disabled
|
|
if TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then return false end -- No career in multiplayer
|
|
score = math.max(0, math.floor(score or 0))
|
|
|
|
fixIncompleteStats()
|
|
|
|
local oldRibbonCount = getRibbonCount()
|
|
careerStats.bestSortie = math.max(careerStats.bestSortie, score)
|
|
careerStats.score = careerStats.score + score
|
|
careerStats.completedObjectives = careerStats.completedObjectives + objectives
|
|
trigger.action.outText(DCSEx.string.toStringThousandsSeparator(score).." xp and "..tostring(objectives).." completed objective(s) were registered in your flight log.", 5)
|
|
local newRibbonCount = getRibbonCount()
|
|
|
|
local somethingWasAwarded = false
|
|
|
|
-- Check for promotions
|
|
if careerStats.rank < #RANKS and careerStats.score >= RANKS[careerStats.rank + 1][3] then
|
|
careerStats.rank = careerStats.rank + 1
|
|
somethingWasAwarded = true
|
|
trigger.action.outText("✪ You have been promoted to the rank of "..RANKS[careerStats.rank][2]..".", MEDAL_BOX_DISPLAY_TIME)
|
|
end
|
|
|
|
-- Check for medals
|
|
for i=1,#MEDALS do
|
|
if i > careerStats.medals and score >= MEDALS[i][2] then
|
|
trigger.action.outText("✪ You have been awarded the "..MEDALS[i][1]..".", MEDAL_BOX_DISPLAY_TIME)
|
|
careerStats.medals = i
|
|
somethingWasAwarded = true
|
|
break
|
|
end
|
|
end
|
|
|
|
-- Check for ribbons
|
|
if newRibbonCount > oldRibbonCount then
|
|
trigger.action.outText("✪ You have been awarded a battle ribbon.", MEDAL_BOX_DISPLAY_TIME)
|
|
somethingWasAwarded = true
|
|
end
|
|
|
|
TUM.playerCareer.save()
|
|
|
|
if somethingWasAwarded then
|
|
TUM.playerCareer.displayMedalBox(false)
|
|
end
|
|
|
|
TUM.playerScore.reset(false)
|
|
|
|
return somethingWasAwarded
|
|
end
|
|
|
|
-------------------------------------
|
|
-- Appends the career menu to the F10 menu. Only works in single-player missions
|
|
-------------------------------------
|
|
function TUM.playerCareer.createMenu()
|
|
local rootMenu = TUM.getOrCreateRootMenu()
|
|
if not DCSEx.io.canReadAndWrite() then return end -- IO disabled, career and scoring disabled
|
|
if TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then return end -- No career in multiplayer
|
|
|
|
missionCommands.addCommand("✪ View pilot career stats", rootMenu, TUM.playerCareer.displayMedalBox, true)
|
|
end
|
|
|
|
-------------------------------------
|
|
-- Displays the player's medal box and carrer summary. Only works in single-player missions
|
|
-------------------------------------
|
|
function TUM.playerCareer.displayMedalBox(printSummary)
|
|
if not DCSEx.io.canReadAndWrite() then return end -- IO disabled, career and scoring disabled
|
|
if TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then return end -- No career in multiplayer
|
|
fixIncompleteStats()
|
|
|
|
if printSummary then
|
|
trigger.action.outText(TUM.playerCareer.getCareerSummary(), MEDAL_BOX_DISPLAY_TIME, true)
|
|
end
|
|
|
|
DCSEx.dcs.outPicture("Pic-MedalBox.png", MEDAL_BOX_DISPLAY_TIME, true, 0, 2, 2, 50, 1)
|
|
DCSEx.dcs.outPicture("Pic-Rank"..tostring(careerStats.rank)..".png", MEDAL_BOX_DISPLAY_TIME, false, 0, 2, 2, 50, 1)
|
|
|
|
local ribbonCount = getRibbonCount()
|
|
for i=1,ribbonCount do
|
|
DCSEx.dcs.outPicture("Pic-Ribbon"..tostring(i)..".png", MEDAL_BOX_DISPLAY_TIME, false, 0, 2, 2, 50, 1)
|
|
end
|
|
|
|
for i=1,careerStats.medals do
|
|
DCSEx.dcs.outPicture("Pic-Medal"..tostring(i)..".png", MEDAL_BOX_DISPLAY_TIME, false, 0, 2, 2, 50, 1)
|
|
end
|
|
trigger.action.outSound("UI-Career.ogg")
|
|
end
|
|
|
|
-------------------------------------
|
|
-- Returns the player career summary as a string. Only works in single-player missions
|
|
-- @return A string
|
|
-------------------------------------
|
|
function TUM.playerCareer.getCareerSummary()
|
|
if not DCSEx.io.canReadAndWrite() then return "" end -- IO disabled, career and scoring disabled
|
|
if TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then return "" end -- No career in multiplayer
|
|
fixIncompleteStats()
|
|
|
|
local playerName = "Player"
|
|
local players = DCSEx.world.getAllPlayers()
|
|
if #players > 0 then
|
|
playerName = players[1]:getPlayerName()
|
|
end
|
|
|
|
local summary = ""
|
|
summary = summary.."CAREER STATS FOR "..RANKS[careerStats.rank][1]:upper().." "..playerName:upper()..":\n"
|
|
summary = summary.."=======================\n"
|
|
summary = summary.."- Rank: "..RANKS[careerStats.rank][2].."\n"
|
|
summary = summary.."- Best sortie XP: "..DCSEx.string.toStringThousandsSeparator(careerStats.bestSortie).."\n"
|
|
summary = summary.."- Total career XP: "..DCSEx.string.toStringThousandsSeparator(careerStats.score).."\n"
|
|
summary = summary.."- Completed objectives: "..tostring(careerStats.completedObjectives).."\n"
|
|
|
|
if careerStats.medals == 0 then
|
|
summary = summary.."- Medals: None"
|
|
else
|
|
summary = summary.."- Medals:"
|
|
for i=1,careerStats.medals do
|
|
summary = summary.."\n - "..MEDALS[i][1]
|
|
end
|
|
end
|
|
|
|
local ribbonCount = getRibbonCount()
|
|
if ribbonCount < MAX_RIBBONS or careerStats.rank < #RANKS or careerStats.medals < #MEDALS then
|
|
summary = summary.."\n"
|
|
|
|
if careerStats.rank < #RANKS then
|
|
summary = summary.."\n- Next promotion: "..DCSEx.string.toStringThousandsSeparator(RANKS[careerStats.rank + 1][3]).." xp"
|
|
end
|
|
|
|
if ribbonCount < MAX_RIBBONS then
|
|
summary = summary.."\n- Next ribbon: "..tostring((ribbonCount + 1) * OBJECTIVES_PER_RIBBON).." objectives"
|
|
end
|
|
|
|
if careerStats.medals < #MEDALS then
|
|
summary = summary.."\n- Next medal: "..DCSEx.string.toStringThousandsSeparator(MEDALS[careerStats.medals + 1][2]).." xp in a single flight"
|
|
end
|
|
end
|
|
|
|
return summary
|
|
end
|
|
|
|
-------------------------------------
|
|
-- Loads the player career from the disk. Only works in single-player missions
|
|
-- @return True if everything worked (or disabled because of MP), false if an error happened
|
|
-------------------------------------
|
|
function TUM.playerCareer.load()
|
|
if TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then return true end -- No career in multiplayer
|
|
if not DCSEx.io.canReadAndWrite() then return false end
|
|
|
|
local jsonString = DCSEx.io.load("TheUniversalMission.sav")
|
|
if jsonString then
|
|
-- TODO: what if Json is malformed?
|
|
careerStats = net.json2lua(jsonString)
|
|
if not careerStats then
|
|
careerStats = {}
|
|
fixIncompleteStats()
|
|
TUM.log("Failed to load player career data, career data reset.")
|
|
else
|
|
TUM.log("Player career data loaded successfully.")
|
|
end
|
|
else
|
|
fixIncompleteStats()
|
|
return false
|
|
end
|
|
|
|
fixIncompleteStats()
|
|
end
|
|
|
|
-------------------------------------
|
|
-- Called on mission start up
|
|
-- @return True if started up properly, false if an error happened
|
|
-------------------------------------
|
|
function TUM.playerCareer.onStartUp()
|
|
fixIncompleteStats()
|
|
if TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then return true end -- No career in multiplayer
|
|
|
|
if DCSEx.io.canReadAndWrite() then
|
|
TUM.log("Lua IO module available, can read and write.")
|
|
TUM.playerCareer.load()
|
|
else
|
|
local msg = "IO module is disabled, CANNOT read and write persistant data. Player progress will NOT be saved.\n"
|
|
msg = msg.."To enable the IO module, comment or remove the \"sanitizeModule('io')\" line in \n"
|
|
msg = msg.."[DCSWorld installation directory]\\Scripts\\MissionScripting.lua and restart the game."
|
|
|
|
TUM.log(msg, TUM.logger.logLevel.WARNING)
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
-------------------------------------
|
|
-- Resets the player career stats and save them. Only works in single-player missions
|
|
-------------------------------------
|
|
function TUM.playerCareer.reset()
|
|
if not DCSEx.io.canReadAndWrite() then return end -- IO disabled, career and scoring disabled
|
|
if TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then return end -- No career in multiplayer
|
|
|
|
careerStats = nil
|
|
fixIncompleteStats()
|
|
TUM.playerCareer.save()
|
|
end
|
|
|
|
-------------------------------------
|
|
-- Save the player career to the disk. Only works in single-player missions
|
|
-- @return True if everything worked (or disabled), false if an error happened
|
|
-------------------------------------
|
|
function TUM.playerCareer.save()
|
|
if TUM.settings.getValue(TUM.settings.id.MULTIPLAYER) then return true end -- No career in multiplayer
|
|
if not DCSEx.io.canReadAndWrite() then return true end -- IO disabled, career and scoring disabled
|
|
|
|
fixIncompleteStats()
|
|
|
|
if DCSEx.io.save("TheUniversalMission.sav", net.lua2json(careerStats)) then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
end
|