xponen

Carrepairer shrunked retreat + overrideable

Sep 23rd, 2013
226
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 17.16 KB | None | 0 0
  1.  
  2.  
  3. function widget:GetInfo()
  4.   return {
  5.     name      = "Retreat",
  6.     desc      = "v0.279 Place 'retreat zones' on the map and order units to retreat to them at desired HP percentages.",
  7.     author    = "CarRepairer",
  8.     date      = "2008-03-17", --2013-9-21
  9.     license   = "GNU GPL, v2 or later",
  10.     handler   = true,
  11.     layer     = 2, --start after unit_start_state.lua (to apply saved initial retreat state)
  12.     enabled   = true
  13.   }
  14. end
  15.  
  16. -- speed-ups
  17. local glDepthTest      = gl.DepthTest
  18. local glAlphaTest      = gl.AlphaTest
  19. local glTexture        = gl.Texture
  20. local glTexRect        = gl.TexRect
  21. local glTranslate      = gl.Translate
  22. local glBillboard      = gl.Billboard
  23. local glColor          = gl.Color
  24. local GL_GREATER       = GL.GREATER
  25.  
  26. local CMD_WAIT          = CMD.WAIT
  27. local CMD_MOVE          = CMD.MOVE
  28. local CMD_PATROL        = CMD.PATROL
  29. local CMD_REPAIR        = CMD.REPAIR
  30. local CMD_STOP          = CMD.STOP
  31.  
  32. local CMD_CLOAK         = CMD.CLOAK
  33.  
  34. local CMD_INSERT        = CMD.INSERT
  35. local CMD_REMOVE        = CMD.REMOVE
  36.  
  37. local CMD_RETREAT       = 10000
  38. local CMD_SETHAVEN      = 10001
  39.  
  40. local GetGameFrame     = Spring.GetGameFrame
  41. local GetLocalTeamID   = Spring.GetLocalTeamID
  42. local GetUnitHealth    = Spring.GetUnitHealth
  43. local GetUnitCommands  = Spring.GetUnitCommands
  44. local GetUnitPosition  = Spring.GetUnitPosition
  45. local GetUnitDefID     = Spring.GetUnitDefID
  46. local GetSelectedUnits = Spring.GetSelectedUnits
  47. local GetUnitIsStunned     = Spring.GetUnitIsStunned
  48. local SelectUnitArray = Spring.SelectUnitArray
  49.  
  50. --local AreTeamsAllied   = Spring.AreTeamsAllied
  51. local GiveOrderToUnit  = Spring.GiveOrderToUnit
  52. --local IsGuiHidden     =   Spring.IsGUIHidden
  53.  
  54. local echo = Spring.Echo
  55.  
  56. local abs, rand       = math.abs, math.random
  57.  
  58. local currentGameFrame = 0
  59.  
  60. local dist = 160 --retreat zone radius
  61. local maxDistSqr = dist * dist
  62. local myTeamID
  63. local tooltips = {}
  64.  
  65. local retreatMoveOrders, wantRetreat, retreatOrdersArray = {}, {}, {}, {}
  66. local mobileUnits,pauseRetreatChecks, havens = {}, {}, {}
  67. local havenCount = 0
  68.  
  69. local retreatedUnits = {} --recently retreating unit that is about to be deselected from user selection
  70. local currentSelection = nil --current unit selection (for deselecting any retreating units. See code for more detail)
  71. local maximumDeselect = 0.59 -- (fraction), threshold of retreating-unit compared to healthy-unit in current selection before auto-deselect turn off.
  72.  
  73. -------------------------------------
  74. options_path = 'Game/Unit AI/Retreat Zone' --//for use 'with gui_epicmenu.lua'
  75. options_order = {'overrideableCommand','removeFromSelection','returnLastPosition'}
  76. options = {
  77.     overrideableCommand = {
  78.         name = 'Overrideable Retreat',
  79.         type = 'bool',
  80.         value = false,
  81.         desc = 'Allow you to suspend unit\'s retreat command by giving it new order. Unit will return to retreat when it become idle, is best use with Auto-Deselect feature below to prevent accidental override.',
  82.     },
  83.     removeFromSelection = {
  84.         name = 'Auto Deselect units',
  85.         type = 'bool',
  86.         value = false,
  87.         desc = 'Automatically remove retreating (damaged) unit from your selection if the majority is healthy units.',
  88.     },
  89.     returnLastPosition = {
  90.         name = 'Return to last position',
  91.         type = 'bool',
  92.         value = false,
  93.         desc = 'Always attempt to return unit to their last known position. Unit will attempt to return to their last idle position or their last location where their previous order expired',
  94.     },
  95. }
  96. --------------------------------------------------------------------------------
  97. --------------------------------------------------------------------------------
  98.  
  99. local function CanReallyMove(unitDefID)
  100.   -- we let factories have the retreat button, but we don't check them
  101.   -- (factory have canMove for rallying new units, but factory speed should be 0)
  102.   local ud = UnitDefs[unitDefID]
  103.   if (ud == nil) then
  104.     return false
  105.   end
  106.   return (ud.speed > 0)
  107. end
  108.  
  109. local function HeadingToHaven(unitID)
  110.     if (havenCount == 0) then
  111.         return false
  112.     end
  113.  
  114.     local dx, dy, dz = retreatMoveOrders[unitID][1], retreatMoveOrders[unitID][2], retreatMoveOrders[unitID][3]
  115.  
  116.     for havenUnitID, havenPosition in pairs(havens) do
  117.         local hx, hy, hz = havenPosition[1], havenPosition[2], havenPosition[3]
  118.         if hx then
  119.             local dSquared = (hx - dx)^2 + (hz - dz)^2
  120.             if (dSquared < maxDistSqr) then
  121.                 return true
  122.             end
  123.         end
  124.     end--for
  125.     return false
  126. end
  127.  
  128. local function FindClosestHaven(sx, _, sz)
  129.   local closestDistSqr = math.huge
  130.   local cx, cy, cz  --  closest coordinates
  131.   for havenID, havenPosition in pairs(havens) do
  132.     local hx, hy, hz = havenPosition[1], havenPosition[2], havenPosition[3]
  133.     if hx then
  134.       local dSquared = (hx - sx)^2 + (hz - sz)^2
  135.       if (dSquared < closestDistSqr) then
  136.         closestDistSqr = dSquared
  137.         cx = hx; cy = hy; cz = hz
  138.     cHavenID = havenID
  139.       end
  140.     end
  141.   end
  142.   if (not cx) then return -1, -1, -1, -1 end  -- should not happen
  143.   return cx, cy, cz, closestDistSqr, cHavenID
  144. end
  145.  
  146. local function FindClosestHavenToUnit(unitID)
  147.   local ux, _, uz = GetUnitPosition(unitID)
  148.   return FindClosestHaven(ux, _, uz)
  149. end
  150.  
  151. local function addHaven(x, y, z)
  152.     havenCount = havenCount + 1
  153.     havens[#havens+1] = {x, y, z}
  154. end
  155.  
  156.  
  157. local function removeHaven(havenID)
  158.     if havens[havenID] then
  159.         havenCount = havenCount - 1
  160.     end
  161.     havens[havenID] = nil
  162. end
  163.  
  164.  
  165. local function setRetreatOrder(unitID, unitDefID, retreatOrder)
  166.     retreatOrdersArray[unitID] = retreatOrder
  167.     mobileUnits[unitID] = mobileUnits[unitID] or CanReallyMove(unitDefID) --note: mobileUnits[unitID] is used to filter only moving object for periodic health checks
  168. end
  169.  
  170.  
  171. function StopRetreating(unitID)
  172.     Spring.SendLuaRulesMsg('retreat|' .. unitID .. '|cancel')
  173.     retreatMoveOrders[unitID] = nil
  174. end
  175.  
  176.  
  177. local function StartRetreat(unitID, force)
  178.     local hx, hy, hz, dSquared = FindClosestHavenToUnit(unitID)
  179.     hx = hx + dist - rand(10, dist*2)
  180.     --hy = hy
  181.     hz = hz + dist - rand(10, dist*2)
  182.  
  183.     if force or dSquared > maxDistSqr then
  184.         local insertIndex = 0
  185.        
  186.         Spring.SendLuaRulesMsg('retreat|' .. unitID .. '|' .. hx .. '|' .. hy .. '|' .. hz .. '|' )
  187.         retreatMoveOrders[unitID] = {hx, hy, hz}
  188.        
  189.         --add last position
  190.         if options.returnLastPosition.value then
  191.             local ux, uy, uz = GetUnitPosition(unitID)
  192.             retreatMoveOrders[unitID][4] = ux
  193.             retreatMoveOrders[unitID][5] = uy
  194.             retreatMoveOrders[unitID][6] = uz
  195.         end
  196.     end
  197. end
  198.  
  199. local function SetWantRetreat(unitID, want)
  200.     if want and (havenCount > 0) and (not pauseRetreatChecks[unitID]) and (not retreatMoveOrders[unitID]) then
  201.         StartRetreat(unitID)
  202.         if options.removeFromSelection.value then
  203.             retreatedUnits[#retreatedUnits+1] = unitID
  204.         end
  205.     elseif retreatMoveOrders[unitID] then
  206.         StopRetreating(unitID)
  207.     end
  208.    
  209.     WG.icons.SetUnitIcon( unitID, {
  210.         name='retreatstate',
  211.         texture= want and 'Anims/cursorrepair_old.png' or nil
  212.     } )
  213. end
  214.  
  215. local function CheckSetWantRetreat(unitID, retreatOrder)
  216.     local health, maxHealth = GetUnitHealth(unitID)
  217.     if (health) then --note: old widget have this check, won't hurt to have it too
  218.         local healthRatio = health / maxHealth
  219.         local threshold = retreatOrder * 0.3
  220.         local _,_,inBuild = GetUnitIsStunned(unitID)
  221.  
  222.         if healthRatio < threshold and (not inBuild) then        
  223.             SetWantRetreat(unitID, true)
  224.         elseif (healthRatio == 1) then
  225.             SetWantRetreat(unitID, nil)
  226.         end
  227.     end
  228. end
  229.  
  230.  
  231. local function RemoveUnitData(unitID)
  232.     SetWantRetreat(unitID, nil)
  233.     pauseRetreatChecks[unitID] = nil
  234.     retreatOrdersArray[unitID] = nil
  235.     retreatMoveOrders[unitID] = nil
  236.     mobileUnits[unitID] = nil
  237. end
  238.  
  239. local function PerformDeselection()
  240.     local commitChange = false
  241.     if currentSelection then --new selection?
  242.         local unitsToDeselect= {}
  243.         local selectionCount = #currentSelection
  244.         local retreatedCount = 0
  245.         for i=1, selectionCount do
  246.             local unitID = currentSelection[i]
  247.             if retreatMoveOrders[unitID] then --selection contain retreating units
  248.                 unitsToDeselect[#unitsToDeselect+1] = i
  249.                 retreatedCount = retreatedCount + 1
  250.             end
  251.         end
  252.         if retreatedCount > 0 and (retreatedCount/selectionCount) < maximumDeselect then --retreating unit is minority?
  253.             table.sort(unitsToDeselect, function(a,b) return a>b end) --sort from biggest index to lowest index
  254.             for i=1, retreatedCount do
  255.                 table.remove(currentSelection, unitsToDeselect[i]) --remove from list
  256.             end
  257.             commitChange = true--update unit selection
  258.         end
  259.     end
  260.     if #retreatedUnits > 0 then -- there is recently retreating units?
  261.         currentSelection = currentSelection or GetSelectedUnits() --get existing selection
  262.         local changes = false
  263.         for i=1, #retreatedUnits do
  264.             local retreatingUnitID = retreatedUnits[i]
  265.             for j=1, #currentSelection do
  266.                 local selectedUnitID = currentSelection[j]
  267.                 if selectedUnitID == retreatingUnitID then --selection contain recently retreating units
  268.                     table.remove(currentSelection, j) --unit remove from existing selection
  269.                     changes = true
  270.                     break;
  271.                 end
  272.             end
  273.         end
  274.         if changes then --have unit to deselect?
  275.             commitChange = true --update unit selection
  276.         end
  277.         retreatedUnits = {} --empty recent-retreat list
  278.     end
  279.     if commitChange then
  280.         SelectUnitArray(currentSelection) --apply selection changes
  281.     end
  282.     currentSelection = nil --flag: finish deselecting units
  283. end
  284.  
  285. --------------------
  286. -- callins
  287.  
  288. function widget:Initialize()
  289.     myTeamID = GetLocalTeamID()
  290.     tooltips[1] = 'Orders: Never retreat.'
  291.     tooltips[2] = 'Orders: Retreat at less than 30% health (right-click to cancel).'
  292.     tooltips[3] = 'Orders: Retreat at less than 60% health (right-click to cancel).'
  293.     tooltips[4] = 'Orders: Retreat at less than 90% health (right-click to cancel).'
  294.  
  295.     WG['retreat'] = {}
  296.     WG['retreat'].addRetreatCommand = function(unitID, unitDefID, retreatOrder)    
  297.         setRetreatOrder(unitID, unitDefID, retreatOrder)
  298.     end
  299.    
  300.     WG.icons.SetOrder( 'retreatstate', 5 )
  301.     WG.icons.SetDisplay( 'retreatstate', true )
  302.     WG.icons.SetPulse( 'retreatstate', true )
  303.    
  304.     WG.retreatingUnits = retreatMoveOrders --make this table global, available to all widget
  305. end
  306.  
  307. function widget:UnitFromFactory(unitID, unitDefID, teamID, builderID, _, _)
  308.     local ud = UnitDefs[unitDefID]
  309.     if ud.canMove and not retreatOrdersArray[unitID] and builderID then --unit can move, unit not given retreat order yet, unit has a builder    
  310.         setRetreatOrder(unitID, unitDefID, retreatOrdersArray[builderID])
  311.         SetWantRetreat(unitID, nil)
  312.     end
  313. end
  314.  
  315. function widget:UnitDestroyed(unitID, _, teamID)
  316.     RemoveUnitData(unitID)
  317. end
  318.  
  319. function widget:UnitTaken(unitID, unitDefID, oldTeam, newTeam)
  320.     RemoveUnitData(unitID)
  321. end
  322.  
  323. function widget:UnitIdle(unitID, unitDefID, teamID)
  324.     pauseRetreatChecks[unitID] = nil --clear ignore list when unit idle
  325. end
  326.  
  327. function widget:CommandNotify(cmdID, cmdParams, cmdOptions)
  328.     if cmdID == CMD_SETHAVEN then
  329.         local x,y,z = cmdParams[1], cmdParams[2], cmdParams[3]
  330.         local _, _, _, dSquared, closestHavenID = FindClosestHaven(x,y,z)
  331.         if dSquared ~= -1 and dSquared < dist*dist then
  332.             removeHaven(closestHavenID)
  333.         else
  334.             addHaven(x,y,z)
  335.         end
  336.         return true
  337.        
  338.     elseif cmdID == CMD_RETREAT then
  339.         local foundValidUnit = false
  340.         local newRetreatOrder = nil
  341.         local selectedUnits = GetSelectedUnits()
  342.         for i=1, #selectedUnits do
  343.             local unitID = selectedUnits[i]
  344.             local unitDefID = GetUnitDefID(unitID)
  345.             local ud = UnitDefs[unitDefID]
  346.  
  347.             if ud.canMove then --Check canmove for mixed selections
  348.  
  349.                 SetWantRetreat(unitID, nil)
  350.                 if not foundValidUnit then
  351.                     foundValidUnit = true
  352.                     -- [[ --scheme1: left-click to cycle between 3 retreat state, right-click to de-activate retreat
  353.                     if not cmdOptions.right then
  354.                         if retreatOrdersArray[unitID] then
  355.                             newRetreatOrder = retreatOrdersArray[unitID] % 3 + 1 --1,2,3,1,2,3
  356.                         else
  357.                             newRetreatOrder = 1
  358.                         end
  359.                     else --right click cancel
  360.                         newRetreatOrder = 0
  361.                     end
  362.                     --]]
  363.                     --[[
  364.                     --scheme2: left-click to cycle 4 retreat state upward, right-click to cycle 4 retreat state downward.
  365.                     newRetreatOrder = retreatOrdersArray[unitID] or 0
  366.                     if cmdOptions.right then
  367.                         newRetreatOrder = newRetreatOrder - 1
  368.                         if newRetreatOrder < 0 then
  369.                             newRetreatOrder = 3
  370.                         end
  371.                     else
  372.                         newRetreatOrder = newRetreatOrder + 1
  373.                         if newRetreatOrder == 4 then
  374.                             newRetreatOrder = 0
  375.                         end
  376.                     end
  377.                     --]]
  378.                 end
  379.  
  380.                 setRetreatOrder(unitID, unitDefID, newRetreatOrder)
  381.                 if newRetreatOrder==0 then --if no-retreat
  382.                     StopRetreating(unitID) --stop retreat instantly
  383.                 end
  384.             end --if canmove
  385.         end --for
  386.         return true
  387.     else
  388.         --exclude currently retreating unit until idle
  389.         if (havenCount > 0) and options.overrideableCommand.value then
  390.             local selectedUnits = GetSelectedUnits()
  391.             for i=1, #selectedUnits do
  392.                 local unitID = selectedUnits[i]
  393.                 if retreatMoveOrders[unitID] then  --currently retreating
  394.                     StopRetreating(unitID) --stop retreat if it is
  395.                     pauseRetreatChecks[unitID] = true --suspend retreat command until unit become idle, "widget:UnitIdle()"
  396.                 end
  397.             end
  398.         end
  399.     end
  400. end
  401.  
  402.  
  403. function widget:CommandsChanged()
  404.     local selectedUnits = GetSelectedUnits()
  405.     local foundRetreatable = false
  406.     local customCommands = widgetHandler.customCommands
  407.  
  408.     --Add retreat-area button
  409.     table.insert(customCommands, {
  410.         id      = CMD_SETHAVEN,
  411.         type    = CMDTYPE.ICON_MAP,
  412.         tooltip = 'Place a retreat zone. Units will retreat there.',
  413.         cursor  = 'Repair',
  414.         action  = 'sethaven',
  415.         params  = { },
  416.         texture = 'LuaUI/Images/commands/Bold/retreat.png',
  417.  
  418.         pos = {CMD_CLOAK},
  419.     })
  420.  
  421.     -- Find out if menu should display retreat-state button
  422.     --(current unit selection may or may not contain a retreatable unit, this code checks for retreatable unit and add button to menu if appropriate)
  423.     for _, unitID in ipairs(selectedUnits) do
  424.  
  425.         local unitID = GetSelectedUnits()[1]
  426.  
  427.         local unitDefID = GetUnitDefID(unitID)
  428.         local ud = UnitDefs[unitDefID]
  429.  
  430.         if not foundRetreatable and ud and ud.canMove then --Note: canMove include factory
  431.             foundRetreatable = true
  432.             local retreatOrder = retreatOrdersArray[unitID] or 0           
  433.             --widgetHandler:AddLayoutCommand({
  434.             table.insert(customCommands, {
  435.                 id      = CMD_RETREAT,
  436.                 type    = CMDTYPE.ICON_MODE,
  437.                 name    = 'Retreat',
  438.                 tooltip = tooltips[retreatOrder + 1],
  439.                 cursor  = 'Retreat',
  440.                 action  = 'retreat',
  441.                 params  = { retreatOrder , 'Retreat Off', 'Retreat 30%', 'Retreat 60%', 'Retreat 90%' },
  442.  
  443.                 pos = {CMD_CLOAK},
  444.             })
  445.    
  446.         end--if canmove
  447.        
  448.         if foundRetreatable then
  449.             return
  450.         end
  451.     end--for
  452. end
  453.  
  454. function widget:SelectionChanged(newSelection)
  455.     if (havenCount > 0) and options.removeFromSelection.value then
  456.         currentSelection = newSelection
  457.     end
  458. end
  459.  
  460. function widget:UnitDamaged(unitID)
  461.     local retreatOrder = retreatOrdersArray[unitID]
  462.    
  463.     if (havenCount > 0)
  464.         and retreatOrder ~= nil
  465.         and retreatOrder > 0
  466.         and mobileUnits[unitID]
  467.         and not (retreatMoveOrders[unitID] or pauseRetreatChecks[unitID])
  468.     then
  469.         CheckSetWantRetreat(unitID, retreatOrder)
  470.        
  471.     end
  472.  
  473. end
  474.  
  475. function widget:GameFrame(gameFrame)
  476.     local frame32 = gameFrame % 32 == 0 -- ~1 second
  477.     local frame160 = gameFrame % 160 == 0 -- ~5 second
  478.     currentGameFrame = gameFrame
  479.  
  480.     if frame32 then
  481.         --check all unit if retreat is needed, apply retreat if needed, remove retreat queue if no longer needed.
  482.         for unitID, retreatOrders in pairs(retreatOrdersArray) do
  483.             if mobileUnits[unitID] then
  484.                 CheckSetWantRetreat(unitID, retreatOrders)
  485.             end
  486.         end -- for
  487.     end --if frame 1/30
  488.    
  489.     --Every 5 seconds, check all unit in case they are heading to a deleted retreat area
  490.     if frame160 then
  491.         for unitID, retreatOrders in pairs(retreatOrdersArray) do
  492.             if retreatMoveOrders[unitID] and not HeadingToHaven(unitID) then
  493.                 StopRetreating(unitID) --remove retreat queue and nullify "retreatMoveOrders[unitID]"
  494.             end
  495.         end
  496.     end
  497.    
  498.     --remove retreating unit from selection (only perform at selection change or when retreat is ordered)
  499.     if options.removeFromSelection.value then
  500.         PerformDeselection()
  501.     end
  502. end
  503.  
  504. function widget:DrawWorld()
  505.     local fade = abs((currentGameFrame % 40) - 20) / 20
  506.  
  507.     --Draw ambulance on havens.
  508.     if (havens) then
  509.  
  510.         glDepthTest(true)
  511.         gl.LineWidth(2)
  512.  
  513.         for havenID, havenPosition in pairs(havens) do
  514.             local x, y, z = havenPosition[1], havenPosition[2], havenPosition[3]
  515.  
  516.             gl.LineWidth(4)
  517.             glColor(1, 1, 1, 0.5)
  518.             gl.DrawGroundCircle(x, y, z, dist, 32)
  519.  
  520.             gl.LineWidth(2)
  521.             glColor(1, 0.1, 0.1, 0.8)
  522.             gl.DrawGroundCircle(x, y, z, dist, 32)
  523.  
  524.         end --for
  525.         glAlphaTest(GL_GREATER, 0)
  526.         glColor(1,fade,fade,fade+0.1)
  527.         glTexture('LuaUI/Images/commands/Bold/retreat.png')
  528.        
  529.         for unitID, havenPosition in pairs(havens) do
  530.             local x, y, z = havenPosition[1], havenPosition[2], havenPosition[3]
  531.             gl.PushMatrix()
  532.             glTranslate(x, y, z)
  533.             glBillboard()
  534.             glTexRect(-10, 0, 10, 20)
  535.             gl.PopMatrix()
  536.         end --for
  537.        
  538.         glTexture(false)
  539.         glAlphaTest(false)
  540.         glDepthTest(false)
  541.     end --if havens
  542.    
  543. end --DrawWorld
  544.  
  545. --Bind a hotkey to "luaui noretreat" for quick bravery
  546. function widget:TextCommand(command)
  547.     if (command == "noretreat") then
  548.         widgetHandler:CommandNotify(CMD_RETREAT, {}, { right=true })
  549.         return true
  550.     end
  551.     return false
  552. end
Advertisement
Add Comment
Please, Sign In to add comment