Advertisement
Guest User

Objectives.lua

a guest
Jan 13th, 2017
39
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 19.28 KB | None | 0 0
  1. -- Global variables
  2. bluePlanes = 60
  3. redPlanes = 60
  4.  
  5. bluePilots = 30
  6. redPilots = 30
  7.  
  8. blueTargetCount = 0
  9. redTargetCount = 0
  10.  
  11. blueTargetZones = {}
  12. redTargetZones = {}
  13.  
  14. gameOver = false;
  15.  
  16. TIME_BETWEEN_ATTACK_MESSAGES = 15
  17. TIME_BETWEEN_MISSION_STATS = 180
  18.  
  19. publishStatsFunction = nil
  20.  
  21. -- FUNCTION DEFINITIONS ====================================
  22.  
  23. -- When a plane becomes occupied, update that group on the mission status.
  24. -- I'd prefer to go by plane, but this is the best I can do. I guess it's nice
  25. -- to know when somebody joins your flight.
  26. function OnPlayerEnterUnit(event)      
  27.  
  28.     -- Sanity check to make sure player isn't entering an empty unit or something I dunno.
  29.     if event.id == world.event.S_EVENT_PLAYER_ENTER_UNIT and event.initiator ~= nil then
  30.        
  31.         local playerName = event.initiator:getPlayerName()
  32.         local callsign = event.initiator:getCallsign()
  33.         local unitName = event.initiator:getDesc().displayName
  34.         local groupID = event.initiator:getGroup():getID()
  35.        
  36.         -- Full status text
  37.         local stats = string.format("%s has occupied %s %s.\nBlue team has %u planes and %u pilots left.\nRed team has %u planes and %u pilots left", playerName, unitName, callsign, bluePlanes, bluePilots, redPlanes, redPilots)
  38.         trigger.action.outTextForGroup(groupID, stats, 15.0, false)
  39.     end
  40.    
  41. end
  42.  
  43. -- When a vehicle gets destroyed, remove it from the appropriate side.
  44. -- Note that everything other than planes are "dead" when they are destroyed, not "crashed."
  45. function OnVehicleDestroyed(event)
  46.  
  47.     -- Sanity to make sure the initiator exists. Sometimes a vehicle gets destroyed twice.
  48.     if event.id == world.event.S_EVENT_DEAD and event.initiator ~= nil and event.initiator:getCoalition() ~= nil then
  49.         DestroyGroundTarget(event.initiator, event.initiator:getCoalition())
  50.     end
  51.    
  52. end
  53.  
  54. -- When a plane is destroyed, remove it from the appropriate side.
  55. -- Note that all aicraft are "crashed" when they are destroyed, not "dead."
  56. function OnAircraftCrashed(event)
  57.    
  58.     -- Sanity check to make sure the initiator exists. Weird things can happen.
  59.     if event.id == world.event.S_EVENT_CRASH and event.initiator ~= nil then
  60.         DestroyPlane(event.initiator:getCoalition())
  61.     end
  62.  
  63. end
  64.  
  65. -- When a pilot is killed, remove them from the appropriate side.
  66. function OnPilotKilled(event)
  67.  
  68.     -- Sanity check to make sure the initiator exists. Weird things can happen.
  69.     if event.id == world.event.S_EVENT_PILOT_DEAD and event.initiator ~= nil then
  70.         DestroyPilot(event.initiator:getCoalition())
  71.     end
  72.  
  73. end
  74.  
  75. -- If a player leaves a plane while in the air, count it as a lost plane.
  76. -- When they leave a plane that's on the ground, they won't get penalized.
  77. function OnPlayerExitUnit(event)
  78.    
  79.     -- Initiator can be nil when the player crashes or dies.
  80.     if event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT and event.initiator ~= nil and event.initiator:inAir() then
  81.        
  82.         local callsign = event.initiator:getCallsign()
  83.         local unitName = event.initiator:getDesc().displayName
  84.        
  85.         -- Remove the appropriate side's plane and pilot.
  86.         DestroyPlane(event.initiator:getCoalition())
  87.         DestroyPilot(event.initiator:getCoalition())
  88.        
  89.         -- Tell everybody what happened. Sometimes you just need to disconnect,
  90.         -- but if you're being a poor sport, everybody will knooooooooooooooow.
  91.         local outText = string.format("%s left their slot %s while flying.", callsign, unitName)               
  92.        
  93.     end
  94.    
  95. end
  96.  
  97. -- Remove the appropriate side's plane and notify all the players.
  98. function DestroyPlane(side)
  99.  
  100.     if side == coalition.side.RED then
  101.         redPlanes = redPlanes - 1
  102.         trigger.action.outText("Red team aircraft destroyed, " .. redPlanes .. " remaining.", 15.0, false)
  103.         CheckObjectiveWinConditions()
  104.        
  105.     elseif side == coalition.side.BLUE then
  106.         bluePlanes = bluePlanes - 1
  107.         trigger.action.outText("Blue team aircraft destroyed, " .. bluePlanes .. " remaining.", 15.0, false)
  108.         CheckObjectiveWinConditions()
  109.        
  110.     else
  111.         trigger.action.outText("Invalid faction for plane destroy.", 15.0, false)
  112.     end
  113.    
  114. end
  115.  
  116. -- When a ground target is destroyed, they get removed from that zone's unit count.
  117. function DestroyGroundTarget(unit, side)
  118.    
  119.     local reportText = ""
  120.     local zoneDestroyedName = ""
  121.     local zoneUnderAttackMGRS = ""
  122.     local targetZones
  123.     local messagePublished = true
  124.     local attackedZone
  125.    
  126.     -- Determine which target zone table to use.
  127.     if side == coalition.side.RED then
  128.         targetZones = redTargetZones
  129.     elseif side == coalition.side.BLUE then
  130.         targetZones = blueTargetZones
  131.     end
  132.    
  133.     -- Go through that side's target zones to determine which zone this one was in.
  134.     for i, zone in ipairs(targetZones)
  135.     do
  136.         -- If this is the zone, then save its name and position.
  137.         if WithinDistance(unit:getPoint().x, unit:getPoint().z, zone.point.x, zone.point.z, zone.radius) then
  138.             zoneDestroyedName = zone.name
  139.            
  140.             -- Convert from engine coordinates to MGRS through lat/long.
  141.             local zoneLat, zoneLong = coord.LOtoLL(zone.point)
  142.             local zoneMGRS = coord.LLtoMGRS(zoneLat, zoneLong)
  143.            
  144.             -- DCS's raw MGRS northing and easting are numbers, not strings. So if they are printed
  145.             -- directly, you get incorrect values. (e.g. 035 prints to 35) This will print out the
  146.             -- whole thing like MG1234512345, then use substrings of that to construct something with
  147.             -- the format MG11.
  148.             zoneUnderAttackMGRS = string.format("%s%05u%05u", zoneMGRS.MGRSDigraph, zoneMGRS.Easting, zoneMGRS.Northing)
  149.             zoneUnderAttackMGRS = string.sub(zoneUnderAttackMGRS, 1, 3) .. string.sub(zoneUnderAttackMGRS, 8, 8)
  150.            
  151.             -- Save this to fire a flare later on.
  152.             attackedZone = zone
  153.                        
  154.             break
  155.         end            
  156.     end
  157.  
  158.     -- Only do target destroyed logic if they were in a target zone.
  159.     if zoneDestroyedName ~= "" then
  160.            
  161.         -- Red target destroyed.
  162.         if side == coalition.side.RED then
  163.             redTargetZones[zoneDestroyedName] = redTargetZones[zoneDestroyedName] -1
  164.             reportText = string.format("RED target destroyed in \"%s\" at MGRS %s!\nZone status is now %s (%u/%u).", zoneDestroyedName, zoneUnderAttackMGRS, PercentTargetsLeftInZone(redTargetZones, zoneDestroyedName), redTargetZones[zoneDestroyedName], redTargetZones[zoneDestroyedName .. "init"])
  165.             redTargetCount = redTargetCount - 1
  166.            
  167.             -- Only print attack messages if the last attack was TIME_BETWEEN_ATTACK_MESSAGES seconds ago.
  168.             messagePublished = PublishTargetDestroyedText(reportText, redTargetZones[zoneDestroyedName .. "lastAttackTime"])
  169.             redTargetZones[zoneDestroyedName .. "lastAttackTime"] = timer.getTime()
  170.            
  171.             -- If this zone was completely destroyed, publish a special message.
  172.             if redTargetZones[zoneDestroyedName] == 0 then
  173.                 trigger.action.outText("\nAll units in red target area " .. zoneDestroyedName .. " have been completely destroyed!\n", 30.0, false)
  174.             end
  175.  
  176.         -- Blue target destroyed.
  177.         elseif side == coalition.side.BLUE then
  178.             blueTargetZones[zoneDestroyedName] = blueTargetZones[zoneDestroyedName] -1
  179.             reportText = string.format("BLUE target destroyed in zone \"%s\" at MGRS %s!\nZone status is now %s (%u/%u).", zoneDestroyedName, zoneUnderAttackMGRS, PercentTargetsLeftInZone(blueTargetZones, zoneDestroyedName), blueTargetZones[zoneDestroyedName], blueTargetZones[zoneDestroyedName .. "init"])
  180.             blueTargetCount = blueTargetCount - 1
  181.            
  182.             -- Only print attack messages if the last attack was TIME_BETWEEN_ATTACK_MESSAGES seconds ago.
  183.             messagePublished = PublishTargetDestroyedText(reportText, blueTargetZones[zoneDestroyedName .. "lastAttackTime"])
  184.             blueTargetZones[zoneDestroyedName .. "lastAttackTime"] = timer.getTime()
  185.  
  186.             -- If this zone was completely destroyed, publish a special message.
  187.             if blueTargetZones[zoneDestroyedName] == 0 then
  188.                 trigger.action.outText("\nAll units in blue target area " .. zoneDestroyedName .. " have been completely destroyed!\n", 30.0, false)
  189.             end
  190.            
  191.         else
  192.             reportText = "Invalid faction for ground object destroy."
  193.         end
  194.        
  195.         -- Shoot a flare if a message was published.
  196.         if messagePublished == true then
  197.             --trigger.action.signalFlare(mist.utils.makeVec3GL(mist.getRandPointInCircle(attackedZone.point, attackedZone.radius)), 3, 0)
  198.             trigger.action.signalFlare(unit:getPoint(), 3, 0)
  199.         end    
  200.        
  201.         -- Check if there's nothing else and if so, this is a win.
  202.         CheckObjectiveWinConditions()
  203.        
  204.     end
  205.    
  206. end
  207.  
  208. function PublishTargetDestroyedText(text, lastAttackTime)  
  209.    
  210.     local newMessagePublished = false;
  211.    
  212.     if (timer.getTime() - lastAttackTime > TIME_BETWEEN_ATTACK_MESSAGES) then      
  213.         trigger.action.outText(text, 15.0, false)
  214.         newMessagePublished = true
  215.     end
  216.        
  217.     return newMessagePublished
  218.    
  219. end
  220.  
  221. -- Remove the appropriate side's plane and notify all the players.
  222. function DestroyPilot(side)
  223.  
  224.     if side == coalition.side.RED then
  225.         redPilots = redPilots - 1
  226.         trigger.action.outText("Red team pilot killed, " .. redPilots .. " remaining.", 15.0, false)
  227.         CheckObjectiveWinConditions()
  228.  
  229.     elseif side == coalition.side.BLUE then
  230.         bluePilots = bluePilots - 1
  231.         trigger.action.outText("Blue team pilot killed, " .. bluePilots .. " remaining.", 15.0, false)
  232.         CheckObjectiveWinConditions()
  233.  
  234.     else
  235.         trigger.action.outText("Invalid faction for pilot kill.", 15.0, false)
  236.     end
  237.  
  238. end
  239.  
  240. -- Returns a string of the format 27% (usual case) or 100% (when 100% of units are left in zone).
  241. function PercentTargetsLeftInZone(targetZones, zoneDestroyedName)
  242.    
  243.     local percentKilled = (targetZones[zoneDestroyedName] / targetZones[zoneDestroyedName .. "init"]) * 100
  244.     percentKilled = string.format("%0.0f%%", percentKilled)
  245.  
  246.     return percentKilled
  247.  
  248. end
  249.  
  250. -- Get total ground targets.
  251. function InitGroundTargetCounts()
  252.  
  253.     local blueTargetAreaCount = 0
  254.     local redTargetAreaCount = 0
  255.  
  256.     -- Save zones by their color.
  257.     for i, zone in ipairs(mist.DBs.zonesByNum)
  258.     do     
  259.         if ZoneIsBlue(zone.color) then
  260.             table.insert(blueTargetZones, zone)
  261.             blueTargetZones[zone.name] = CountUnitsInZone(zone, "blue")
  262.             blueTargetZones[zone.name .. "init"] = blueTargetZones[zone.name]
  263.             blueTargetAreaCount = blueTargetAreaCount + 1
  264.             blueTargetCount = blueTargetCount + blueTargetZones[zone.name]         
  265.             blueTargetZones[zone.name .. "lastAttackTime"] = timer.getTime()
  266.            
  267.             -- Spawn a blue smoke flare on the zone.
  268.             SpawnFlaresInCircle(mist.utils.zoneToVec3(zone), zone.radius, trigger.smokeColor.Blue)
  269.  
  270.             -- DEBUG
  271.             --trigger.action.outText("Initialized zone " .. zone.name .. " with " .. blueTargetZones[zone.name] .. " units.", 15.0, false)
  272.        
  273.         elseif ZoneIsRed(zone.color) then
  274.             table.insert(redTargetZones, zone)
  275.             redTargetZones[zone.name] = CountUnitsInZone(zone, "red")
  276.             redTargetZones[zone.name .. "init"] = redTargetZones[zone.name]
  277.             redTargetAreaCount = redTargetAreaCount + 1
  278.             redTargetCount = redTargetCount + redTargetZones[zone.name]
  279.             redTargetZones[zone.name .. "lastAttackTime"] = timer.getTime()
  280.            
  281.             -- Spawn a red smoke flare on the zone.
  282.             SpawnFlaresInCircle(mist.utils.zoneToVec3(zone), zone.radius, trigger.smokeColor.Red)
  283.            
  284.             -- DEBUG
  285.             --trigger.action.outText("Initialized zone " .. zone.name .. " with " .. redTargetZones[zone.name] .. " units.", 15.0, false)
  286.  
  287.         end
  288.        
  289.     end
  290.  
  291.     trigger.action.outText("Mission initialized with " .. blueTargetAreaCount .. " blue and " .. redTargetAreaCount .. " red target areas, with " .. redTargetCount .. " red targets, and " .. blueTargetCount .. " blue targets.", 15.0, false)
  292.    
  293. end
  294.  
  295. -- Creates 6 flares in a circle pattern.
  296. function SpawnFlaresInCircle(point, radius, color)
  297.  
  298.     local offset = {x, y, z}
  299.    
  300.     for i = 0, 5, 1 do
  301.         offset.x = point.x + (math.cos(math.rad(i * 60)) * radius)
  302.         offset.y = 0
  303.         offset.z = point.z + (math.sin(math.rad(i * 60)) * radius)
  304.        
  305.         -- Smoke flares aren't persistent, so create the flares repeatedly every 5 minutes.
  306.         mist.scheduleFunction(trigger.action.smoke, {mist.utils.makeVec3GL(offset), color}, timer.getTime(), 300)
  307.     end
  308.    
  309. end
  310.  
  311. function CountUnitsInZone(zone, side)
  312.  
  313.     countInZone = 0
  314.  
  315.     for i, unit in ipairs(mist.DBs.unitsByNum)
  316.     do
  317.         -- Only check ground units and units of the appropriate side.
  318.         -- Also exclude FARPs because they're indestructible.      
  319.         if unit.coalition == side and (unit.category == "vehicle" or (unit.category == "static" and unit.type ~= "FARP") or unit.category == "ship") then
  320.             if WithinDistance(unit.point.x, unit.point.y, zone.point.x, zone.point.z, zone.radius) then
  321.                 countInZone = countInZone + 1
  322.             end
  323.         end        
  324.     end
  325.  
  326.     return countInZone
  327. end
  328.  
  329. function WithinDistance(x1, y1, x2, y2, distance)
  330.  
  331.     local xd = x1 - x2
  332.     local yd = y1 - y2
  333.     local distanceToZoneCenter = math.sqrt(xd*xd + yd*yd)
  334.    
  335.     if distanceToZoneCenter < distance then
  336.         return true
  337.     else
  338.         return false
  339.     end
  340.  
  341. end
  342.  
  343. function ZoneIsRed(color)
  344.    
  345.   if color[1] == 1 and color[2] == 0 and color[3] == 0 then
  346.         return true
  347.   else
  348.         return false   
  349.   end
  350.  
  351. end
  352.  
  353. function ZoneIsBlue(color)
  354.    
  355.   if color[1] == 0 and color[2] == 0 and color[3] == 1 then
  356.         return true
  357.   else
  358.         return false   
  359.   end
  360.  
  361. end
  362.  
  363. -- Mission update text.
  364. function PublishMissionStatsToAll()
  365.  
  366.     -- Timer.
  367.     local stats = "Mission time: " .. SecondsToClock(timer.getTime())
  368.     trigger.action.outText(stats, 30.0, false)
  369.    
  370.     -- Blue team report.   
  371.     stats = "Blue team report:\n\n"
  372.     stats = stats .. GenerateSideReport(blueTargetZones, bluePilots, bluePlanes)
  373.     trigger.action.outText(stats, 30.0, false)
  374.    
  375.     -- Red team report.
  376.     stats = "Red team report:\n\n"
  377.     stats = stats .. GenerateSideReport(redTargetZones, redPilots, redPlanes)
  378.     trigger.action.outText(stats, 30.0, false)
  379.    
  380.     -- Publish the final string and queue up the next update.
  381.     publishStatsFunction = mist.scheduleFunction(PublishMissionStatsToAll, {}, timer.getTime() + TIME_BETWEEN_MISSION_STATS)
  382. end
  383.  
  384. -- Generates a string of the format:
  385. -- Soviet FARP is at 100%
  386. -- 25 pilots and 50 planes are available.
  387. function GenerateSideReport(targetZones, pilots, planes)
  388.    
  389.     local stats = ""
  390.     local percentTGT = ""
  391.  
  392.     for i, zone in ipairs(targetZones) do
  393.         percentTGT = PercentTargetsLeftInZone(targetZones, zone.name)
  394.  
  395.         if percentTGT == "0%" then
  396.             stats = stats .. "\"" .. zone.name .. "\" has been completely destroyed!\n"
  397.         else
  398.             stats = stats .. string.format("\"%s\" is at %s effectiveness. (%u/%u)\n", zone.name, percentTGT, targetZones[zone.name], targetZones[zone.name .. "init"])
  399.         end
  400.     end
  401.    
  402.     return stats .. string.format("\n%u pilots and %u planes are available.", pilots, planes)
  403.  
  404. end
  405.  
  406. -- Adapted from TJ_Tigger post from http://www.indigorose.com/forums/archive/index.php/t-14669.html
  407. function SecondsToClock(sSeconds)
  408.  
  409.     local nSeconds = tonumber(sSeconds)
  410.    
  411.     --return nil;
  412.     if nSeconds == 0 then
  413.     return "00:00:00";
  414.    
  415.     else
  416.         nHours = string.format("%02.f", math.floor(nSeconds/3600));
  417.         nMins = string.format("%02.f", math.floor(nSeconds/60 - (nHours*60)));
  418.         nSecs = string.format("%02.f", math.floor(nSeconds - nHours*3600 - nMins *60));
  419.         return nHours..":"..nMins..":"..nSecs
  420.     end
  421.    
  422. end
  423.  
  424. function CheckObjectiveWinConditions()
  425.    
  426.     local winText = "" 
  427.    
  428.     -- When a pilot and plane die at the same time, they can generate duplicate game over screens.
  429.     -- Protect against that.
  430.     if gameOver == false then
  431.  
  432.         -- DEBUG
  433.         --outString = string.format("Performing test: Red has %u units left", redTargetCount)
  434.         --trigger.action.outText(outString, 30.0, false)
  435.        
  436.         -- Blue win conditions
  437.         if redTargetCount == 0 or redPilots == 0 or redPlanes == 0 then
  438.             gameOver = true
  439.            
  440.             if (redPilots == 0) then
  441.                 winText = "============================================\n============================================\n\nBLUE HAS WON THE ROUND!\nAll red pilots were killed!\n\n============================================\n============================================"
  442.             elseif (redPlanes == 0) then
  443.                 winText = "============================================\n============================================\n\nBLUE HAS WON THE ROUND!\nAll red aircaft were destroyed!\n\n============================================\n============================================"
  444.             else
  445.                 winText = "============================================\n============================================\n\nBLUE HAS WON THE ROUND!\nAll red ground targets were destroyed!\n\n============================================\n============================================"    
  446.             end
  447.         end
  448.        
  449.         -- Red win conditions
  450.         if blueTargetCount == 0 or bluePilots == 0 or bluePlanes == 0 then
  451.             gameOver = true
  452.            
  453.             if (bluePilots == 0) then
  454.                 winText = "============================================\n============================================\n\nRED HAS WON THE ROUND!\nAll blue pilots were killed!\n\n============================================\n============================================"
  455.             elseif (bluePlanes == 0) then
  456.                 winText = "============================================\n============================================\n\nRED HAS WON THE ROUND!\nAll blue aircaft were destroyed!\n\n============================================\n============================================"
  457.             else
  458.                 winText = "============================================\n============================================\n\nRED HAS WON THE ROUND!\nAll blue ground targets were destroyed!\n\n============================================\n============================================"            
  459.             end
  460.         end
  461.        
  462.         -- End the mission if somebody won.
  463.         if gameOver then
  464.        
  465.             -- Win text
  466.             trigger.action.outText(winText, 30.0, true)
  467.        
  468.             -- Mission end stats
  469.             --local stats = "Mission time: " .. SecondsToClock(timer.getTime()) .. "\Final mission report:\n"
  470.             --trigger.action.outText(stats, 30.0, false)
  471.             local stats = "\Final mission stats:"
  472.             trigger.action.outText(stats, 30.0, false)
  473.            
  474.             -- Blue team report.   
  475.             stats = "Blue team:\n\n"
  476.             stats = stats .. GenerateSideReport(blueTargetZones, bluePilots, bluePlanes)
  477.             trigger.action.outText(stats, 30.0, false)
  478.            
  479.             -- Red team report.
  480.             stats = "Red team:\n\n"
  481.             stats = stats .. GenerateSideReport(redTargetZones, redPilots, redPlanes)
  482.             trigger.action.outText(stats, 30.0, false)
  483.        
  484.             mist.removeFunction(publishStatsFunction)
  485.             mist.scheduleFunction(trigger.action.outText, {"Ending the mission in 30 seconds.", 30.0, false}, timer.getTime() + 5)
  486.             mist.scheduleFunction(EndTheMission, {}, timer.getTime() + 35)
  487.         end
  488.  
  489.  
  490.     end
  491.    
  492. end
  493.  
  494. -- Set the flag "1" to true. In the mission itself, a trigger needs to check when this is set to true
  495. -- and then end the mission.
  496. function EndTheMission()
  497.     trigger.action.setUserFlag("1", true)
  498. end
  499.  
  500. function NumberOfElementsInTable(iTable)
  501.  
  502.     local count = 0
  503.    
  504.     for i, element in ipairs(iTable) do
  505.         count = count + 1
  506.     end
  507.  
  508.     return count
  509.    
  510. end
  511.  
  512. -- END FUNCTION DEFINITIONS ================================
  513.  
  514. -- MISSION START
  515. mist.addEventHandler(OnPlayerEnterUnit)
  516. mist.addEventHandler(OnPlayerExitUnit)
  517. mist.addEventHandler(OnVehicleDestroyed)
  518. mist.addEventHandler(OnAircraftCrashed)
  519. mist.addEventHandler(OnPilotKilled)
  520.  
  521. InitGroundTargetCounts()
  522.  
  523. -- Every minute, update mission stats.
  524. publishStatsFunction = mist.scheduleFunction(PublishMissionStatsToAll, {}, timer.getTime())
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement