All pastes #1621724 Raw Edit

unit_morph.lua

public lua v1 · immutable
#1621724 ·published 2009-10-15 08:50 UTC
rendered paste body
--------------------------------------------------------------------------------------------------------------------------------------------------------------------  file:    unit_morph.lua--  brief:   Adds unit morphing command--  author:  Dave Rodgers (improved by jK, Licho and aegis)----  Copyright (C) 2007.--  Licensed under the terms of the GNU GPL, v2 or later.------------------------------------------------------------------------------------------------------------------------------------------------------------------function gadget:GetInfo()  return {    name      = "UnitMorph",    desc      = "Adds unit morphing",    author    = "trepan (improved by jK, Licho and aegis)",    date      = "Jan, 2008",    license   = "GNU GPL, v2 or later",    layer     = 0,    enabled   = true  }end--------------------------------------------------------------------------------------------------------------------------------------------------------------------  Proposed Command ID Ranges:----    all negative:  Engine (build commands)--       0 -   999:  Engine--    1000 -  9999:  Group AI--   10000 - 19999:  LuaUI--   20000 - 29999:  LuaCob--   30000 - 39999:  LuaRules--local CMD_MORPH_STOP = 32210local CMD_MORPH = 31210local MAX_MORPH = 0 --// will increase dynamically----------------------------------------------------------------------------------  COMMON----------------------------------------------------------------------------------[[ // for use with any mod -_-function GetTechLevel(udid)  local ud = UnitDefs[udid];  return (ud and ud.techLevel) or 0end]]---- // for use with mods like CA <_<local function GetTechLevel(UnitDefID)  --return UnitDefs[UnitDefID].techLevel or 0  local cats = UnitDefs[UnitDefID].modCategories  if (cats) then    --// bugfix, cuz lua don't remove uppercase :(    if     (cats["LEVEL1"]) then return 1    elseif (cats["LEVEL2"]) then return 2    elseif (cats["LEVEL3"]) then return 3      elseif (cats["level1"]) then return 1      elseif (cats["level2"]) then return 2      elseif (cats["level3"]) then return 3    end  end  return 0endlocal function isFactory(UnitDefID)  return UnitDefs[UnitDefID].isFactory or falseendlocal function isFinished(UnitID)  local _,_,_,_,buildProgress = Spring.GetUnitHealth(UnitID)  return (buildProgress==nil)or(buildProgress>=1)end----------------------------------------------------------------------------------------------------------------------------------------------------------------if (gadgetHandler:IsSyncedCode()) then----------------------------------------------------------------------------------  SYNCED--------------------------------------------------------------------------------include("LuaRules/colors.h.lua")local stopPenalty  = 0.5local morphPenalty = 1.0local upgradingBuildSpeed = 250local XpScale = 0.50local XpMorphUnits = {}local devolution = true            --// remove upgrade capabilities after factory destruction?local stopMorphOnDevolution = true --// should morphing stop during devolution----------------------------------------------------------------------------------------------------------------------------------------------------------------local morphDefs  = {} --// make it global in Initialize()local morphUnits = {} --// make it global in Initialize()local reqDefIDs  = {} --// all possible unitDefID's, which are used as a requirement for a morph--// per team techlevel and owned MorphReq. units tablelocal teamTechLevel = {}local teamReqUnits  = {}local teamList = Spring.GetTeamList()for i=1,#teamList do  local teamID = teamList[i]  teamReqUnits[teamID]  = {}  teamTechLevel[teamID] = 0endlocal morphCmdDesc = {--  id     = CMD_MORPH, -- added by the calling function because there is now more than one option  type   = CMDTYPE.ICON,  name   = 'Upgrade',  cursor = 'Morph',  -- add with LuaUI?  action = 'morph',}--// will be replaced in Initialize()local RankToXp    = function() return 0 endlocal GetUnitRank = function() return 0 end--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------// translate lowercase UnitNames to real unitname (with upper-/lowercases)local defNamesL = {}for defName in pairs(UnitDefNames) do  defNamesL[string.lower(defName)] = defNameendlocal function DefCost(paramName, udSrc, udDst)  local pSrc = udSrc[paramName]  local pDst = udDst[paramName]  if ((not pSrc) or (not pDst) or      (type(pSrc) ~= 'number') or      (type(pDst) ~= 'number')) then    return 0  end  local cost = (pDst - pSrc) * morphPenalty  if (cost < 0) then    cost = 0  end  return math.floor(cost)endlocal function BuildMorphDef(udSrc, morphData)  local udDst = UnitDefNames[defNamesL[string.lower(morphData.into)] or -1]  if (not udDst) then    Spring.Echo('Morph gadget: Bad morph dst type: ' .. morphData.into)    return  else    local unitDef = udDst    local newData = {}    newData.into = udDst.id    newData.time = morphData.time or math.floor(unitDef.buildTime*7/upgradingBuildSpeed)    newData.increment = (1 / (30 * newData.time))    newData.metal  = morphData.metal  or DefCost('metalCost',  udSrc, udDst)    newData.energy = morphData.energy or DefCost('energyCost', udSrc, udDst)    newData.resTable = {      m = (newData.increment * newData.metal),      e = (newData.increment * newData.energy)    }    newData.tech = morphData.tech or 0    newData.xp   = morphData.xp or 0    newData.rank = morphData.rank or 0    local require = -1    if (morphData.require) then      require = (UnitDefNames[defNamesL[string.lower(morphData.require)] or -1] or {}).id      if (require) then        reqDefIDs[require]=true      else        Spring.Echo('Morph gadget: Bad morph requirement: ' .. morphData.require)        require = -1      end    end    newData.require = require    newData.cmd     = CMD_MORPH      + MAX_MORPH    newData.stopCmd = CMD_MORPH_STOP + MAX_MORPH    MAX_MORPH = MAX_MORPH + 1    return newData  endendlocal function ValidateMorphDefs(mds)  local newDefs = {}  for src,morphData in pairs(mds) do    local udSrc = UnitDefNames[defNamesL[string.lower(src)] or -1]    if (not udSrc) then      Spring.Echo('Morph gadget: Bad morph src type: ' .. src)    else      newDefs[udSrc.id] = {}      if (morphData.into) then        local morphDef = BuildMorphDef(udSrc, morphData)        if (morphDef) then newDefs[udSrc.id][morphDef.cmd] = morphDef end      else        for _,morphData in pairs(morphData) do          local morphDef = BuildMorphDef(udSrc, morphData)          if (morphDef) then newDefs[udSrc.id][morphDef.cmd] = morphDef end        end      end    end  end  return newDefsend------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------local function UnitReqCheck(teamID, reqUnit)  if (reqUnit==-1) then return true end  return ((teamReqUnits[teamID][reqUnit] or 0) > 0)endlocal function GetMorphToolTip(unitID, unitDefID, teamID, morphDef, teamTech, unitXP, unitRank, teamOwnsReqUnit)  local ud = UnitDefs[morphDef.into]  local tt = ''  tt = tt .. WhiteStr  .. 'Morph into a ' .. ud.humanName .. '\n'  tt = tt .. GreenStr  .. 'time: '   .. morphDef.time     .. '\n'  tt = tt .. CyanStr   .. 'metal: '  .. morphDef.metal    .. '\n'  tt = tt .. YellowStr .. 'energy: ' .. morphDef.energy   .. '\n'  if (morphDef.tech > teamTech) or     (morphDef.xp > unitXP) or     (morphDef.rank > unitRank) or     (not teamOwnsReqUnit)  then    tt = tt .. RedStr .. 'needs'    if (morphDef.tech>teamTech) then tt = tt .. ' level: ' .. morphDef.tech end    if (morphDef.xp>unitXP)     then tt = tt .. ' xp: '    .. string.format('%.2f',morphDef.xp) end    if (morphDef.rank>unitRank) then tt = tt .. ' rank: '  .. morphDef.rank .. ' (' .. string.format('%.2f',RankToXp(unitDefID,morphDef.rank)) .. 'xp)' end    if (not teamOwnsReqUnit)	then tt = tt .. ' unit: '  .. UnitDefs[morphDef.require].humanName end  end  return ttendlocal function UpdateMorphReqs(teamID)  local morphCmdDesc = {}  local teamTech  = teamTechLevel[teamID] or 0  local teamUnits = Spring.GetTeamUnits(teamID)  for n=1,#teamUnits do    local unitID   = teamUnits[n]    local unitXP   = Spring.GetUnitExperience(unitID)    local unitRank = GetUnitRank(unitID)    local unitDefID = Spring.GetUnitDefID(unitID)    local morphDefs = morphDefs[unitDefID] or {}    for _,morphDef in pairs(morphDefs) do      local cmdDescID = Spring.FindUnitCmdDesc(unitID, morphDef.cmd)      if (cmdDescID) then        local teamOwnsReqUnit = UnitReqCheck(teamID,morphDef.require)        morphCmdDesc.disabled = (morphDef.tech > teamTech)or(morphDef.rank > unitRank)or(morphDef.xp > unitXP)or(not teamOwnsReqUnit)        morphCmdDesc.tooltip  = GetMorphToolTip(unitID, unitDefID, teamID, morphDef, teamTech, unitXP, unitRank, teamOwnsReqUnit)        Spring.EditUnitCmdDesc(unitID, cmdDescID, morphCmdDesc)      end    end  endendlocal function AddMorphCmdDesc(unitID, unitDefID, teamID, morphDef, teamTech)  local unitXP   = Spring.GetUnitExperience(unitID)  local unitRank = GetUnitRank(unitID)  local teamOwnsReqUnit = UnitReqCheck(teamID,morphDef.require)  morphCmdDesc.tooltip = GetMorphToolTip(unitID, unitDefID, teamID, morphDef, teamTech, unitXP, unitRank, teamOwnsReqUnit)  morphCmdDesc.texture = "#" .. morphDef.into   --//only works with a patched layout.lua or the TweakedLayout widget!  morphCmdDesc.disabled= (morphDef.tech > teamTech)or(morphDef.rank > unitRank)or(morphDef.xp > unitXP)or(not teamOwnsReqUnit)  morphCmdDesc.id = morphDef.cmd  local cmdDescID = Spring.FindUnitCmdDesc(unitID, morphDef.cmd)  if (cmdDescID) then    Spring.EditUnitCmdDesc(unitID, cmdDescID, morphCmdDesc)  else    Spring.InsertUnitCmdDesc(unitID, morphCmdDesc)  end  morphCmdDesc.tooltip = nil  morphCmdDesc.texture = nilend----------------------------------------------------------------------------------------------------------------------------------------------------------------local function ReAssignAssists(newUnit,oldUnit)  local ally = Spring.GetUnitAllyTeam(newUnit)  local alliedTeams = Spring.GetTeamList(ally)  for n=1,#alliedTeams do    local teamID = alliedTeams[n]    local alliedUnits = Spring.GetTeamUnits(teamID)    for i=1,#alliedUnits do      local unitID = alliedUnits[i]      local cmds = Spring.GetCommandQueue(unitID)      for j=1,#cmds do        local cmd = cmds[j]        if (cmd.id == CMD.GUARD)and(cmd.params[1] == oldUnit) then          Spring.GiveOrderToUnit(unitID,CMD.INSERT,{cmd.tag,CMD.GUARD,0,newUnit},{})          Spring.GiveOrderToUnit(unitID,CMD.REMOVE,{cmd.tag},{})        end      end    end  endend----------------------------------------------------------------------------------------------------------------------------------------------------------------local function StartMorph(unitID, unitDefID, teamID, morphDef)  Spring.SetUnitHealth(unitID, { paralyze = 1.0e9 })    --// turns mexes and mm off (paralyze the unit)  Spring.SetUnitResourcing(unitID,"e",0)                --// turns solars off  Spring.GiveOrderToUnit(unitID, CMD.ONOFF, { 0 }, { "alt" }) --// turns radars/jammers off  morphUnits[unitID] = {    def = morphDef,    progress = 0.0,    increment = morphDef.increment,    morphID = morphID,    teamID = teamID,  }  local cmdDescID = Spring.FindUnitCmdDesc(unitID, morphDef.cmd)  if (cmdDescID) then    Spring.EditUnitCmdDesc(unitID, cmdDescID, {id=morphDef.stopCmd, name=RedStr.."Stop"})  end  SendToUnsynced("unit_morph_start", unitID, unitDefID, morphDef.cmd)endlocal function StopMorph(unitID, morphData)  morphUnits[unitID] = nil  Spring.SetUnitHealth(unitID, { paralyze = -1})  local scale = morphData.progress * stopPenalty  local unitDefID = Spring.GetUnitDefID(unitID)  Spring.SetUnitResourcing(unitID,"e", UnitDefs[unitDefID].energyMake)  Spring.GiveOrderToUnit(unitID, CMD.ONOFF, { 1 }, { "alt" })  local usedMetal  = morphData.def.metal  * scale  Spring.AddUnitResource(unitID, 'metal',  usedMetal)  --local usedEnergy = morphData.def.energy * scale  --Spring.AddUnitResource(unitID, 'energy', usedEnergy)  SendToUnsynced("unit_morph_stop", unitID)  local cmdDescID = Spring.FindUnitCmdDesc(unitID, morphData.def.stopCmd)  if (cmdDescID) then    Spring.EditUnitCmdDesc(unitID, cmdDescID, {id=morphData.def.cmd, name=morphCmdDesc.name})  endendlocal function FinishMorph(unitID, morphData)  local udDst = UnitDefs[morphData.def.into]  local defName = udDst.name  local unitTeam = morphData.teamID  local px, py, pz = Spring.GetUnitBasePosition(unitID)  Spring.SetUnitBlocking(unitID, false)  morphUnits[unitID] = nil  local newUnit = Spring.CreateUnit(defName, px, py, pz, 0, unitTeam)    Spring.SetUnitPosition(newUnit, px, py, pz)    local h = Spring.GetUnitHeading(unitID)  Spring.SetUnitRotation(newUnit, 0, -h * math.pi / 32768, 0)  --//copy experience  local newXp = Spring.GetUnitExperience(unitID)*XpScale  local nextMorph = morphDefs[morphData.def.into]  if nextMorph~= nil and nextMorph.into ~= nil then nextMorph = {morphDefs[morphData.def.into]} end  if (nextMorph) then --//determine the lowest xp req. of all next possible morphs    local maxXp = math.huge    for _, nm in pairs(nextMorph) do      local rankXpInto = RankToXp(nm.into,nm.rank)      if (rankXpInto>0)and(rankXpInto<maxXp) then        maxXp=rankXpInto      end      local xpInto     = nm.xp      if (xpInto>0)and(xpInto<maxXp) then        maxXp=xpInto      end    end    newXp = math.min( newXp, maxXp*0.9)  end  Spring.SetUnitExperience(newUnit, newXp)  --//copy some state  local states = Spring.GetUnitStates(unitID)  Spring.GiveOrderArrayToUnitArray({ newUnit }, {    { CMD.FIRE_STATE, { states.firestate },             { } },    { CMD.MOVE_STATE, { states.movestate },             { } },    { CMD.REPEAT,     { states["repeat"] and 1 or 0 },  { } },    { CMD.CLOAK,      { states.cloak     and 1 or udDst.initCloaked },  { } },    { CMD.ONOFF,      { 1 },                            { } },    { CMD.TRAJECTORY, { states.trajectory and 1 or 0 }, { } },  })  --//copy command queue  local cmds = Spring.GetUnitCommands(unitID)  for i = 2, cmds.n do  -- skip the first command (CMD_MORPH)    local cmd = cmds[i]    Spring.GiveOrderToUnit(newUnit, cmd.id, cmd.params, cmd.options.coded)  end  --//reassign assist commands to new unit  ReAssignAssists(newUnit,unitID)  --// copy health  local oldHealth,oldMaxHealth = Spring.GetUnitHealth(unitID)  local _,newMaxHealth         = Spring.GetUnitHealth(newUnit)  local newHealth = (oldHealth / oldMaxHealth) * newMaxHealth  if newHealth<=1 then newHealth = 1 end  Spring.SetUnitHealth(newUnit, newHealth)  local lineage = Spring.GetUnitLineage(unitID)  Spring.SetUnitLineage(newUnit,lineage,true)  --// FIXME: - re-attach to current transport?  --// update selection  SendToUnsynced("unit_morph_finished", unitID, newUnit)  Spring.SetUnitBlocking(newUnit, true)  Spring.DestroyUnit(unitID, false, true) -- selfd = false, reclaim = trueendlocal function UpdateMorph(unitID, morphData)  if (Spring.UseUnitResource(unitID, morphData.def.resTable)) then    morphData.progress = morphData.progress + morphData.increment  end  if (morphData.progress >= 1.0) then    FinishMorph(unitID, morphData)    return false -- remove from the list, all done  end  return trueend------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------function gadget:Initialize()  --// RankApi linking  if (GG.rankHandler) then    GetUnitRank = GG.rankHandler.GetUnitRank    RankToXp    = GG.rankHandler.RankToXp  end  if (type(GG.UnitRanked)~="table") then GG.UnitRanked = {} end  table.insert(GG.UnitRanked, UnitRanked)  --// get the morphDefs  morphDefs = include("LuaRules/Configs/morph_defs.lua")  if (not morphDefs) then gadgetHandler:RemoveGadget(); return; end  morphDefs = ValidateMorphDefs(morphDefs)  --// make it global for unsynced access via SYNCED  _G.morphUnits = morphUnits  _G.morphDefs  = morphDefs  --// Register CmdIDs  for number=0,MAX_MORPH do    gadgetHandler:RegisterCMDID(CMD_MORPH+number)    gadgetHandler:RegisterCMDID(CMD_MORPH_STOP+number)  end  --// check existing ReqUnits+TechLevel  local allUnits = Spring.GetAllUnits()  for i=1,#allUnits do    local unitID    = allUnits[i]    local unitDefID = Spring.GetUnitDefID(unitID)    local teamID    = Spring.GetUnitTeam(unitID)    if (reqDefIDs[unitDefID])and(isFinished(unitID)) then      local teamReq = teamReqUnits[teamID]      teamReq[unitDefID] = (teamReq[unitDefID] or 0) + 1    end    AddFactory(unitID, unitDefID, teamID)  end  --// add the Morph Menu Button to existing units  for i=1,#allUnits do    local unitID    = allUnits[i]    local teamID    = Spring.GetUnitTeam(unitID)    local unitDefID = Spring.GetUnitDefID(unitID)    local morphDefSet  = morphDefs[unitDefID]    if (morphDefSet) then      local useXPMorph = false      for _,morphDef in pairs(morphDefSet) do        if (morphDef) then          local cmdDescID = Spring.FindUnitCmdDesc(unitID, morphDef.cmd)          if (not cmdDescID) then            AddMorphCmdDesc(unitID, unitDefID, teamID, morphDef, teamTechLevel[teamID])          end          useXPMorph = (morphDef.xp>0) or useXPMorph        end      end      if (useXPMorph) then XpMorphUnits[#XpMorphUnits+1] = {id=unitID,defID=unitDefID,team=teamID} end    end  endendfunction gadget:Shutdown()  for i,f in pairs(GG.UnitRanked or {}) do    if (f==UnitRanked) then      table.remove(GG.UnitRanked, i)      break    end  end  local allUnits = Spring.GetAllUnits()  for i=1,#allUnits do    local unitID    = allUnits[i]    local morphData = morphUnits[unitID]    if (morphData) then      StopMorph(unitID, morphData)    end    for number=0,MAX_MORPH do      local cmdDescID = Spring.FindUnitCmdDesc(unitID, CMD_MORPH+number)      if (cmdDescID) then        Spring.RemoveUnitCmdDesc(unitID, cmdDescID)      end    end  endend------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------function gadget:UnitCreated(unitID, unitDefID, teamID)  local morphDefSet = morphDefs[unitDefID]  if (morphDefSet) then    local useXPMorph = false    for _,morphDef in pairs(morphDefSet) do      if (morphDef) then    	AddMorphCmdDesc(unitID, unitDefID, teamID, morphDef, teamTechLevel[teamID])        useXPMorph = (morphDef.xp>0) or useXPMorph      end    end    if (useXPMorph) then XpMorphUnits[#XpMorphUnits+1] = {id=unitID,defID=unitDefID,team=teamID} end  endendfunction gadget:UnitFinished(unitID, unitDefID, teamID)  local bfrTechLevel = teamTechLevel[teamID] or 0  AddFactory(unitID, unitDefID, teamID)  if (reqDefIDs[unitDefID]) then    local teamReq = teamReqUnits[teamID]    teamReq[unitDefID] = (teamReq[unitDefID] or 0) + 1    if (teamReq[unitDefID]==1) then      UpdateMorphReqs(teamID)    end  end  if (bfrTechLevel~=teamTechLevel[teamID]) then    UpdateMorphReqs(teamID)  endendfunction gadget:UnitDestroyed(unitID, unitDefID, teamID)  if (morphUnits[unitID]) then    StopMorph(unitID,morphUnits[unitID])    morphUnits[unitID] = nil  end  local bfrTechLevel = teamTechLevel[teamID] or 0  RemoveFactory(unitID, unitDefID, teamID)  local updateButtons = false  if (reqDefIDs[unitDefID])and(isFinished(unitID)) then    local teamReq = teamReqUnits[teamID]    teamReq[unitDefID] = (teamReq[unitDefID] or 0) - 1    if (teamReq[unitDefID]==0) then      StopMorphsOnDevolution(teamID)      updateButtons = true    end  end  if (bfrTechLevel~=teamTechLevel[teamID]) then    StopMorphsOnDevolution(teamID)    updateButtons = true  end  if (updateButtons) then UpdateMorphReqs(teamID) endendfunction gadget:UnitTaken(unitID, unitDefID, oldTeamID, teamID)  self:UnitCreated(unitID, unitDefID, teamID)  if (isFinished(unitID)) then    self:UnitFinished(unitID, unitDefID, teamID)  endendfunction gadget:UnitGiven(unitID, unitDefID, newTeamID, oldTeamID)  self:UnitDestroyed(unitID, unitDefID, oldTeamID)endfunction UnitRanked(unitID,unitDefID,teamID,newRank,oldRank)  local morphDefSet = morphDefs[unitDefID]  if (morphDefSet) then    local teamTech = teamTechLevel[teamID] or 0    local unitXP   = Spring.GetUnitExperience(unitID)    for _, morphDef in pairs(morphDefSet) do      if (morphDef) then        local cmdDescID = Spring.FindUnitCmdDesc(unitID, morphDef.cmd)        if (cmdDescID) then          local morphCmdDesc = {}          local teamOwnsReqUnit = UnitReqCheck(teamID,morphDef.require)          morphCmdDesc.disabled = (morphDef.tech > teamTech)or(morphDef.rank > newRank)or(morphDef.xp > unitXP)or(not teamOwnsReqUnit)          morphCmdDesc.tooltip  = GetMorphToolTip(unitID, unitDefID, teamID, morphDef, teamTech, unitXP, newRank, teamOwnsReqUnit)          Spring.EditUnitCmdDesc(unitID, cmdDescID, morphCmdDesc)        end      end    end  endend------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------function AddFactory(unitID, unitDefID, teamID)  if (isFactory(unitDefID)) then    local unitTechLevel = GetTechLevel(unitDefID)    if (unitTechLevel > teamTechLevel[teamID]) then      teamTechLevel[teamID]=unitTechLevel    end  endendfunction RemoveFactory(unitID, unitDefID, teamID)  if (devolution)and(isFactory(unitDefID))and(isFinished(unitID)) then    --// check all factories and determine team level    local level = 0    local teamUnits = Spring.GetTeamUnits(teamID)    for i=1,#teamUnits do      local unitID2 = teamUnits[i]      if (unitID2 ~= unitID) then        local unitDefID2 = Spring.GetUnitDefID(unitID2)        if (isFactory(unitDefID2) and isFinished(unitID2)) then          local unitTechLevel = GetTechLevel(unitDefID2)          if (unitTechLevel>level) then level = unitTechLevel end        end      end    end    if (level ~= teamTechLevel[teamID]) then      teamTechLevel[teamID] = level    end  endendfunction StopMorphsOnDevolution(teamID)  if (stopMorphOnDevolution) then    for unitID, morphData in pairs(morphUnits) do      local morphDef = morphData.def      if (morphData.teamID == teamID)and         (           (morphDef.tech>teamTechLevel[teamID])or           (not UnitReqCheck(teamID, morphDef.require))         )      then        StopMorph(unitID, morphData)      end    end  endend------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------function gadget:GameFrame(n)  if ((n+24)%150<1) then    local unitCount = #XpMorphUnits    local i = 1    while (i<=unitCount) do      local unitdata    = XpMorphUnits[i]      local unitID      = unitdata.id      local unitDefID   = unitdata.defID      local morphDefSet = morphDefs[unitDefID]      if (morphDefSet) then        local teamID   = unitdata.team        local teamTech = teamTechLevel[teamID] or 0        local unitXP   = Spring.GetUnitExperience(unitID)        local unitRank = GetUnitRank(unitID)        local xpMorphLeft = false        for _,morphDef in pairs(morphDefSet) do          if (morphDef) then            local cmdDescID = Spring.FindUnitCmdDesc(unitID, morphDef.cmd)            if (cmdDescID) then              local morphCmdDesc = {}              local teamOwnsReqUnit = UnitReqCheck(teamID,morphDef.require)              morphCmdDesc.disabled = (morphDef.tech > teamTech)or(morphDef.rank > unitRank)or(morphDef.xp > unitXP)or(not teamOwnsReqUnit)              morphCmdDesc.tooltip  = GetMorphToolTip(unitID, unitDefID, teamID, morphDef, teamTech, unitXP, unitRank, teamOwnsReqUnit)              Spring.EditUnitCmdDesc(unitID, cmdDescID, morphCmdDesc)              xpMorphLeft = morphCmdDesc.disabled or xpMorphLeft            end          end        end        if (not xpMorphLeft) then          --// remove unit in list (it fullfills all xp requirements)          XpMorphUnits[i] = XpMorphUnits[unitCount]          XpMorphUnits[unitCount] = nil          unitCount = unitCount - 1          i = i - 1        end      end      i = i + 1    end  end  for unitID, morphData in pairs(morphUnits) do    if (not UpdateMorph(unitID, morphData)) then      morphUnits[unitID] = nil    end  endendfunction gadget:AllowCommand(unitID, unitDefID, teamID, cmdID, cmdParams, cmdOptions)  local morphData = morphUnits[unitID]  if (morphData) then    if (cmdID==morphData.def.stopCmd)or(cmdID == CMD.STOP) then      StopMorph(unitID, morphData)      morphUnits[unitID] = nil      return false    elseif (cmdID == CMD.ONOFF) then      return false    --else --// disallow ANY command to units in morph    --  return false    end  elseif (cmdID >= CMD_MORPH and cmdID <= CMD_MORPH+MAX_MORPH) then    local morphDef = (morphDefs[unitDefID] or {})[cmdID]    if ((morphDef)and        (morphDef.tech<=teamTechLevel[teamID])and        (morphDef.rank<=GetUnitRank(unitID))and        (morphDef.xp<=Spring.GetUnitExperience(unitID))and        (UnitReqCheck(teamID, morphDef.require)) )    then      if (isFactory(unitDefID)) then        --// the factory cai is broken and doesn't call CommandFallback(),        --// so we have to start the morph here        StartMorph(unitID, unitDefID, teamID, morphDef)        return false      else        return true      end    end    return false  end  return trueendfunction gadget:CommandFallback(unitID, unitDefID, teamID, cmdID, cmdParams, cmdOptions)  if (cmdID <= CMD_MORPH or cmdID >= CMD_MORPH+MAX_MORPH) then    return false  --// command was not used  end  local morphDef = (morphDefs[unitDefID] or {})[cmdID]  if (not morphDef) then    return true, true  --// command was used, remove it  end  local morphData = morphUnits[unitID]  if (not morphData) then    StartMorph(unitID, unitDefID, teamID, morphDef)    return true, true  end  return true, false  --// command was used, do not remove itend----------------------------------------------------------------------------------  END SYNCED--------------------------------------------------------------------------------else----------------------------------------------------------------------------------  UNSYNCED----------------------------------------------------------------------------------// 75b2 compability (removed it in the next release)if (Spring.GetTeamColor==nil) then  Spring.GetTeamColor = function(teamID) local _,_,_,_,_,_,r,g,b = Spring.GetTeamInfo(teamID); return r,g,b endend---- speed-ups--local gameFrame;local SYNCED = SYNCEDlocal CallAsTeam = CallAsTeamlocal spairs = spairslocal snext = snextlocal GetUnitTeam         = Spring.GetUnitTeamlocal GetUnitHeading      = Spring.GetUnitHeadinglocal GetUnitBasePosition = Spring.GetUnitBasePositionlocal GetGameFrame        = Spring.GetGameFramelocal GetSpectatingState  = Spring.GetSpectatingStatelocal AddWorldIcon        = Spring.AddWorldIconlocal AddWorldText        = Spring.AddWorldTextlocal IsUnitVisible       = Spring.IsUnitVisiblelocal GetLocalTeamID      = Spring.GetLocalTeamIDlocal spAreTeamsAllied    = Spring.AreTeamsAlliedlocal spGetGameFrame      = Spring.GetGameFramelocal glBillboard    = gl.Billboardlocal glColor        = gl.Colorlocal glPushMatrix   = gl.PushMatrixlocal glTranslate    = gl.Translatelocal glRotate       = gl.Rotatelocal glUnitShape    = gl.UnitShapelocal glPopMatrix    = gl.PopMatrixlocal glText         = gl.Textlocal glPushAttrib   = gl.PushAttriblocal glPopAttrib    = gl.PopAttriblocal glBlending     = gl.Blendinglocal glDepthTest    = gl.DepthTestlocal GL_LEQUAL      = GL.LEQUALlocal GL_ONE         = GL.ONElocal GL_SRC_ALPHA   = GL.SRC_ALPHAlocal GL_ONE_MINUS_SRC_ALPHA = GL.ONE_MINUS_SRC_ALPHAlocal GL_COLOR_BUFFER_BIT = GL.COLOR_BUFFER_BITlocal headingToDegree = (360 / 65535)----------------------------------------------------------------------------------------------------------------------------------------------------------------local useLuaUI = falselocal oldFrame = 0        --//used to save bandwidth between unsynced->LuaUIlocal drawProgress = true --//a widget can do this job too (see healthbars)------------------------------------------------------------------------------------------------------------------------------------------------------------------//synced -> unsynced actionslocal function SelectSwap(cmd, oldID, newID)  local selUnits = Spring.GetSelectedUnits()  for i=1,#selUnits do    local unitID = selUnits[i]    if (unitID == oldID) then      selUnits[i] = newID      Spring.SelectUnitArray(selUnits)      break    end  end  if (Script.LuaUI('MorphFinished')) then    if (useLuaUI) then      local readTeam, spec, specFullView = nil,GetSpectatingState()      if (specFullView)        then readTeam = Script.ALL_ACCESS_TEAM        else readTeam = GetLocalTeamID() end      CallAsTeam({ ['read'] = readTeam }, function()        if (IsUnitVisible(oldID)) then          Script.LuaUI.MorphFinished(oldID,newID)        end      end)    end  end  return trueendlocal function StartMorph(cmd, unitID, unitDefID, morphID)  if (Script.LuaUI('MorphStart')) then    if (useLuaUI) then      local readTeam, spec, specFullView = nil,GetSpectatingState()      if (specFullView)        then readTeam = Script.ALL_ACCESS_TEAM        else readTeam = GetLocalTeamID() end      CallAsTeam({ ['read'] = readTeam }, function()        if (unitID)and(IsUnitVisible(unitID)) then          Script.LuaUI.MorphStart(unitID, SYNCED.morphDefs[unitDefID][morphID])        end      end)    end  end  return trueendlocal function StopMorph(cmd, unitID)  if (Script.LuaUI('MorphStop')) then    if (useLuaUI) then      local readTeam, spec, specFullView = nil,GetSpectatingState()      if (specFullView)        then readTeam = Script.ALL_ACCESS_TEAM        else readTeam = GetLocalTeamID() end      CallAsTeam({ ['read'] = readTeam }, function()        if (unitID)and(IsUnitVisible(unitID)) then          Script.LuaUI.MorphStop(unitID)        end      end)    end  end  return trueend----------------------------------------------------------------------------------------------------------------------------------------------------------------function gadget:Initialize()  gadgetHandler:AddSyncAction("unit_morph_finished", SelectSwap)  gadgetHandler:AddSyncAction("unit_morph_start", StartMorph)  gadgetHandler:AddSyncAction("unit_morph_stop", StopMorph)endfunction gadget:Shutdown()  gadgetHandler:RemoveSyncAction("unit_morph_finished")  gadgetHandler:RemoveSyncAction("unit_morph_start")  gadgetHandler:RemoveSyncAction("unit_morph_stop")endfunction gadget:Update()  local frame = spGetGameFrame()  if (frame>oldFrame) then    oldFrame = frame    if snext(SYNCED.morphUnits) then      local useLuaUI_ = Script.LuaUI('MorphUpdate')      if (useLuaUI_~=useLuaUI) then --//Update Callins on change        drawProgress = not Script.LuaUI('MorphDrawProgress')        useLuaUI     = useLuaUI_      end      if (useLuaUI) then        local morphTable = {}        local readTeam, spec, specFullView = nil,GetSpectatingState()        if (specFullView)          then readTeam = Script.ALL_ACCESS_TEAM          else readTeam = GetLocalTeamID() end        CallAsTeam({ ['read'] = readTeam }, function()          for unitID, morphData in spairs(SYNCED.morphUnits) do            if (unitID and morphData)and(IsUnitVisible(unitID)) then              morphTable[unitID] = {progress=morphData.progress, into=morphData.def.into}            end          end        end)        Script.LuaUI.MorphUpdate(morphTable)      end    end  endendlocal teamColors = {}local function SetTeamColor(teamID,a)  local color = teamColors[teamID]  if (color) then    color[4]=a    glColor(color)    return  end  local r, g, b = Spring.GetTeamColor(teamID)  if (r and g and b) then    color = { r, g, b }    teamColors[teamID] = color    glColor(color)    return  endend--//patchs an annoying popup the first time you morph a unittype(+team)local alreadyInit = {}local function InitializeUnitShape(unitDefID,unitTeam)  local iTeam = alreadyInit[unitTeam]  if (iTeam)and(iTeam[unitDefID]) then return end  glPushMatrix()  gl.ColorMask(false)  glUnitShape(unitDefID, unitTeam)  gl.ColorMask(true)  glPopMatrix()  if (alreadyInit[unitTeam]==nil) then alreadyInit[unitTeam] = {} end  alreadyInit[unitTeam][unitDefID] = trueendlocal function DrawMorphUnit(unitID, morphData, localTeamID)  local h = GetUnitHeading(unitID)  if (h==nil) then    return  --// bonus, heading is only available when the unit is in LOS  end  local px,py,pz = GetUnitBasePosition(unitID)  if (px==nil) then    return  end  local unitTeam = morphData.teamID  InitializeUnitShape(morphData.def.into,unitTeam) --BUGFIX  local frac = ((gameFrame + unitID) % 30) / 30  local alpha = 2.0 * math.abs(0.5 - frac)  SetTeamColor(unitTeam,alpha)  glPushMatrix()  glTranslate(px, py, pz)  glRotate(h * headingToDegree, 0, 1, 0)  glUnitShape(morphData.def.into, unitTeam)  glPopMatrix()  --// cheesy progress indicator  if (drawProgress)and(localTeamID)and     ( (spAreTeamsAllied(unitTeam,localTeamID)) or (localTeamID==Script.ALL_ACCESS_TEAM) )  then    glPushMatrix()    glPushAttrib(GL_COLOR_BUFFER_BIT)    glTranslate(px, py+14, pz)    glBillboard()    local progStr = string.format("%.1f%%", 100 * morphData.progress)    gl.Text(progStr, 0, -20, 9, "oc")    glPopAttrib()    glPopMatrix()  endendfunction gadget:DrawWorld()  local morphUnits = SYNCED.morphUnits  if ((not morphUnits) or (snext(morphUnits) == nil)) then    return --//no morphs to draw  end  gameFrame = GetGameFrame()  glBlending(GL_SRC_ALPHA, GL_ONE)  glDepthTest(GL_LEQUAL)  local spec, specFullView = GetSpectatingState()  local readTeam  if (specFullView) then    readTeam = Script.ALL_ACCESS_TEAM  else    readTeam = GetLocalTeamID()  end  CallAsTeam({ ['read'] = readTeam }, function()    for unitID, morphData in spairs(morphUnits) do      if (unitID and morphData)and(IsUnitVisible(unitID)) then        DrawMorphUnit(unitID, morphData,readTeam)      end    end  end)  glDepthTest(false)  glBlending(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)end----------------------------------------------------------------------------------  UNSYNCED--------------------------------------------------------------------------------end----------------------------------------------------------------------------------  COMMON--------------------------------------------------------------------------------