I have been trying t make a mobile factory, but for some reason instead of being a factory its a builder unit, how do I change it to a mobile factory?
Hello nightovizard,
Spring doesn't have mobile factory, but modder have tried a lot of hax to make mobile factory.
The following is my method,
First, you must intercept build command issued by player, this is done by Widget or probably in Gadget Unsynced (did not test the later), like this:
Code: Select all
function widget:CommandNotify(cmdID, cmdParams, cmdOptions)
if haveRelevantUnit == 0 or cmdID >= 0 or (cmdParams[1] and cmdParams[2] and cmdParams[3]) then --can't handle other command.
return false
end
local selectedUnits = Spring.GetSelectedUnits()
for i=1, #selectedUnits do
if constructionShipDef[Spring.GetUnitDefID(selectedUnits[i])] then
BuildThisUnit(-1*cmdID)
return true
end
end
return false
end
*I copied pasted from my WIP Widget, some variable could be undefined in that code snippet, but the important thing is the concept*
The "BuildThisUnit()" function will send LUA message to Gadget which will spawn the unit, here's the detail:
Code: Select all
function widget:GameFrame()
if haveRelevantUnit>0 then
rmbG = rmbG2
_,_, _, _, rmbG2 = Spring.GetMouseState()
end
end
function BuildThisUnit(unitDefID)
local alt, ctrl, meta, shift = Spring.GetModKeyState()
local _,_, lmb, mmb, rmb = Spring.GetMouseState()
local selectedUnits = Spring.GetSelectedUnits()
if #selectedUnits == 0 then
return
end
local selectedUnitsString = '{'
for i=1,#selectedUnits do
local mobile_factory = constructionShipDef[Spring.GetUnitDefID(selectedUnits[i])]
if mobile_factory and mobile_factory.buildOptions[unitDefID] then
selectedUnitsString = selectedUnitsString .. '[' .. i .. '] =' .. selectedUnits[i] .. ','
end
end
selectedUnitsString = selectedUnitsString .. '}' --eg: {unitID1,unitID2,unitID3,}
local options = '{'
local count = 1
if alt then options = options .. '[' .. count .. '] = \"alt\",'; count= count + 1 end
if ctrl then options = options .. '[' .. count .. '] = \"ctrl\",'; count= count + 1 end
if meta then options = options .. '[' .. count .. '] = \"meta\",'; count= count + 1 end
if shift then options = options .. '[' .. count .. '] = \"shift\",'; count= count + 1 end
if rmb or rmbG then options = options .. '[' .. count .. '] = \"right\",' end
options = options .. '}' --eg: {'shift','meta','ctrl',}
Spring.SendLuaRulesMsg('mbfc' .. '{[1] =' .. selectedUnitsString .. ',[2] =' .. unitDefID .. ',[3] =' .. options .. ',}' )
Spring.Echo('{'.. selectedUnitsString .. ',' .. unitDefID .. ',' .. options .. ',' .. '}' )
end
Then, the gadget must read this message, decipher it, and perform any synced action like spawning unit, or consume resources (to emulate mobile factory), like this: (NOTE this is in Synced portion of gadget)
Code: Select all
function gadget:RecvLuaMsg(msg, playerID)
if msg:sub(1,4) == 'mbfc' then
local tableString = msg:sub(5)
local chunk, err = loadstring("return "..tableString) --This code is from cawidgets.lua
local cmdTable = chunk and chunk() or {}
if #cmdTable == 3 and
type(cmdTable[1]) == 'table' and
type(cmdTable[2]) == 'number' and
type(cmdTable[3]) == 'table' then
local unitDefID = cmdTable[2]
if UnitDefs[unitDefID] then --valid unitDefID
local shift, meta, ctrl, alt, right
local options = cmdTable[3]
for i=1, #options do
local option = options[i]
if option == "shift" then
shift = true
elseif option == "alt" then
alt = true
elseif option == "ctrl" then
ctrl = true
elseif option == "right" then
right = true
elseif option == "meta" then
meta = true
end
end
local playerTeamID = select(4,Spring.GetPlayerInfo(playerID))
local selectedUnits = cmdTable[1]
for i=1, #selectedUnits do
local unitID = selectedUnits[i]
if Spring.GetUnitTeam(unitID) ~= playerTeamID then --is giving order to enemy unit?
break;
end
local consShipData = constructionShipData[unitID]
if consShipData and consShipData.typedata.buildOptions[unitDefID] then --buildable unitDefID
if right then
if shift then
RemoveTargettedUnitDefIDFromQueue(unitDefID,consShipData,unitID,5)
elseif ctrl then
RemoveTargettedUnitDefIDFromQueue(unitDefID,consShipData,unitID,20)
else
RemoveTargettedUnitDefIDFromQueue(unitDefID,consShipData,unitID,1)
end
else
local consShipQueue = consShipData.status.buildQueue
if shift then
local tableIndex = #consShipQueue
for i=1,5 do
consShipQueue[tableIndex+i] = unitDefID
end
elseif ctrl then
local tableIndex = #consShipQueue
for i=1,20 do
consShipQueue[tableIndex+i] = unitDefID
end
else
consShipQueue[#consShipQueue+1] = unitDefID
end
end
end
end
end
end
end
end
function RemoveTargettedUnitDefIDFromQueue(unitDefID, consShipData,consShipID, count)
Spring.Echo("REMOVING QUEUE " .. count)
local consShipQueue = consShipData.status.buildQueue
local isBuildingSame = (consShipData.status.isBuildingA[2] == unitDefID)
local isBuildingIndex
local countRemove = count
local countTableIndex = 1
local toRemoveIndex = {nil}
--Remove all targetted UnitDefID excluding currently built unit:
for j=1,#consShipQueue do
if consShipQueue[j] ==unitDefID then
if not isBuildingIndex and isBuildingSame then
isBuildingIndex = j
Spring.Echo("Current build to be cancelled")
else
toRemoveIndex[countTableIndex] = j
countTableIndex = countTableIndex + 1
countRemove = countRemove - 1
if countRemove == 0 then
break;
end
end
end
end
for j=#toRemoveIndex, 1,-1 do --remove from topmost index first
table.remove(consShipQueue,toRemoveIndex[j])
end
--If not reached targetted quota, remove currently built unit too:
if countRemove>0 and isBuildingIndex then
table.remove(consShipQueue,isBuildingIndex)
if constructionShipData[consShipID].status.isBuildingA[1] then
CancelNanoframeConstruction(constructionShipData[consShipID].status.isBuildingA[1],consShipID)
end
end
end
Note, the code added unitDefID to a table called "consShipQueue", this can be a global table (but in my case I put it in some other table to index it by UnitID), this table can be read later in "function gadget:GameFrame()" which loop every frame. Here you can do anything, like spawning unit or consume resource, like this:
Code: Select all
function gadget:GameFrame(n)
....<some other messy stuff>.....
for unitID,buildInfo in pairs(consShipStatus.unitsOnPadInfo) do
local health,maxHealth,_,_,buildProgress = Spring.GetUnitHealth(unitID)
if buildProgress and buildProgress <1 then
consShipStatus.isBuildingA[1] = unitID
consShipStatus.isBuildingA[2] = buildInfo.unitDefID
Spring.SetUnitRulesParam(consShipID,"currentlyBuilding",unitID) --hint widgets
if isWait then
break
end
local metalCost = consShipTypedata.buildOptions[buildInfo.unitDefID].metalCost
local maxMetalPerFrame = (consShipTypedata.buildPower/30)
local maxEnergyPerFrame = maxMetalPerFrame
local buildProgressPerFrame = maxMetalPerFrame/metalCost
local remainingBuildProgress = 1-buildProgress
if remainingBuildProgress > buildProgressPerFrame then
consShipStatus.constructing = true
local healthPerFrame = buildProgressPerFrame*maxHealth
Spring.SetUnitHealth(unitID,{ health = health+healthPerFrame, build = buildProgress+buildProgressPerFrame })
Spring.UseUnitResource(consShipID, 'energy', maxEnergyPerFrame)-- + energyMake - energyUse)
Spring.UseUnitResource(consShipID, 'metal', maxMetalPerFrame)-- + metalMake - metalUse)
consShipStatus.unitsOnPadInfo[unitID].usedEnergy = maxEnergyPerFrame + buildInfo.usedEnergy
consShipStatus.unitsOnPadInfo[unitID].usedMetal = maxMetalPerFrame + buildInfo.usedMetal
else --FINISH BUILD:
local remainingMetalToSpend = remainingBuildProgress*metalCost
local remainingEnergyToSpend = remainingMetalToSpend
local remainingHealthToAdd = remainingBuildProgress*maxHealth
Spring.SetUnitHealth(unitID,{ health = health+remainingHealthToAdd, build = 1.0 })
Spring.UseUnitResource(consShipID, 'energy', remainingEnergyToSpend)-- + energyMake - energyUse)
Spring.UseUnitResource(consShipID, 'metal', remainingMetalToSpend)-- + metalMake - metalUse)
consShipStatus.unitsOnPadInfo[unitID].usedEnergy = remainingEnergyToSpend + buildInfo.usedEnergy
consShipStatus.unitsOnPadInfo[unitID].usedMetal = remainingMetalToSpend + buildInfo.usedMetal
GG.StopMiscPriorityResourcing(consShipID,teamID) --is using unit_priority.lua gadget to handle priority.
consShipStatus.constructing = false
consShipStatus.isBuildingA[1] = nil
consShipStatus.isBuildingA[2] = nil
local isRepeat = Spring.GetUnitStates(consShipID)["repeat"]
if isRepeat then
consShipStatus.buildQueue[#consShipStatus.buildQueue+1] = consShipStatus.buildQueue[1]
end
table.remove(consShipStatus.buildQueue,1)
Spring.SetUnitRulesParam(consShipID,"currentlyBuilding",-1) --hint widgets
if UnitDefs[buildInfo.unitDefID].canFly then --if unit can fly, then undock immediately..
DetachUnit(unitID, consShipID)
Spring.GiveOrderToUnit(unitID, CMD.IDLEMODE, {0}, {}) --fly away
end
end
break; --only build 1 unit at a time
end
end
end
There are other misc stuff to make mobile factory not look silly, like constantly repositioning nanoframe to appear on the unit and add command to allow undocking, that make a lot more codes. Currently my gadget is WIP, its not yet added to ZK mod (which I intended for)
-----
In summary, my method were:
a) intercept build command using widget
b) send build command to gadget using LUArule message
c) read LUArule message and spawn unit
EDIT:
Knorker method is to attach factory to a unit, this could work too!
Instead of spawning unit, just give build order to the factory attached to a mobile unit.