Basic airbase land command gadget

Basic airbase land command gadget

Discuss Lua based Spring scripts (LuaUI widgets, mission scripts, gaia scripts, mod-rules scripts, scripted keybindings, etc...)

Moderator: Moderators

Locked
hokomoko
Spring Developer
Posts: 593
Joined: 02 Jun 2014, 00:46

Basic airbase land command gadget

Post by hokomoko »

Will only work correctly on 100.0.1-1155-g9894a29 or later

Two new commands actually:
1) Force Land (anywhere)
2) Land at a specific base

Code: Select all

function gadget:GetInfo()
	return {
		name      = "Airbase Repair Command",
		desc      = "Add command to return to airbase for repairs",
		author    = "ashdnazg",
		date      = "12 February 2016",
		license   = "GNU GPL, v2 or later",
		layer     = 1,
		enabled   = true  --  loaded by default?
	}
end

---------------------------------------------------------------------------------
local FORCE_LAND_CMD_ID = 35430
local LAND_CMD_ID = 35431

if (gadgetHandler:IsSyncedCode()) then

--------------------------------------------------------------------------------
-- Synced
--------------------------------------------------------------------------------

local airbaseDefIDs = {}

local planes = {}
local airbases = {} -- airbaseID = { int pieceNum = unitID reservedFor }
local pendingLanders = {}
local landingPlanes = {}
local landedPlanes = {}

local forceLandCmd = {
	id      = FORCE_LAND_CMD_ID,
	name    = "ForceLand",
	action  = "forceland",
	type    = CMDTYPE.ICON,
	tooltip = "Return to base: Force the plane to return to base immediately",
}

local landCmd = {
	id      = LAND_CMD_ID,
	name    = "Land",
	action  = "land",
	cursor  = 'Repair',
	type    = CMDTYPE.ICON_UNIT,
	tooltip = "Land at a specific airbase",
	hidden  = true,
}


-- add logic for deciding which pad to use here
function AddAirBase(unitID)
	local airBasePads = {}
	local pieceMap = Spring.GetUnitPieceMap(unitID)
	for pieceName, pieceNum in pairs(pieceMap) do
		if pieceName:find("pad") then
			airBasePads[pieceNum] = false
		end
	end
	airbases[unitID] = airBasePads
end


-- returns either false or the piece number of the free pad
function CanLandAt(unitID, airbaseID)
	local airbasePads = airbases[airbaseID]
	if not airbasePads then
		return false
	end

	local unitTeamID = Spring.GetUnitTeam(unitID)
	local airbaseTeamID = Spring.GetUnitTeam(airbaseID)
	if not Spring.AreTeamsAllied(unitTeamID, airbaseTeamID) then
		return false
	end

	local padPieceNum = false

	for pieceNum, reservedBy in pairs(airbasePads) do
		-- if somehow no pad expects you, find a vacant one
		if not reservedBy then
			padPieceNum = pieceNum
		end
		if reservedBy == unitID then
			padPieceNum = pieceNum
			break
		end
	end
	return padPieceNum
end


function FindAirBase(unitID)
	local minDist = math.huge
	local closestAirbaseID
	local closestPieceNum
	for airbaseID, _ in pairs(airbases) do
		local pieceNum = CanLandAt(unitID, airbaseID)
		if pieceNum then
			local dist = Spring.GetUnitSeparation(unitID, airbaseID)
			if dist < minDist then
				minDist = dist
				closestAirbaseID = airbaseID
				closestPieceNum = pieceNum
			end
		end
	end

	return closestAirbaseID, closestPieceNum
end

function RemoveLander(unitID)
	if landingPlanes[unitID] then
		local airbaseID, pieceNum = landingPlanes[unitID][1], landingPlanes[unitID][2]
		local airbasePads = airbases[airBaseID]
		if airbasePads then
			airbasePads[pieceNum] = false
		end
		landingPlanes[unitID] = nil
		return
	end
end

function NeedsRepair(unitID)
	local health, maxHealth = Spring.GetUnitHealth(unitID)
	local landAtState = Spring.GetUnitStates(unitID).autorepairlevel
	return health < maxHealth * landAtState;
end


function gadget:UnitCreated(unitID, unitDefID, unitTeam, builderID)
	if UnitDefs[unitDefID].canFly then
		Spring.InsertUnitCmdDesc(unitID, landCmd)
		Spring.InsertUnitCmdDesc(unitID, forceLandCmd)
	end

	local _, _, _, _, buildProgress = Spring.GetUnitHealth(unitID)
	if buildProgress == 1.0 then
		gadget:UnitFinished(unitID, unitDefID, unitTeam)
	end
end

function gadget:UnitDestroyed(unitID, unitDefID, unitTeam)
	RemoveLander(unitID)
	planes[unitID] = nil
	airbases[unitID] = nil
	landingPlanes[unitID] = nil
	landedPlanes[unitID] = nil
	pendingLanders[unitID] = nil
end

function gadget:UnitFinished(unitID, unitDefID, unitTeam)
	if airbaseDefIDs[unitDefID] then
		AddAirBase(unitID)
	end
	if UnitDefs[unitDefID].canFly then
		planes[unitID] = true
	end
end

function gadget:AllowCommand(unitID, unitDefID, teamID, cmdID, cmdParams, cmdOptions)
	if cmdID == LAND_CMD_ID then
		local airbaseID = cmdParams[1]
		return CanLandAt(unitID, airbaseID)
	end
	return true
end

function gadget:CommandFallback(unitID, unitDefID, teamID, cmdID, cmdParams, cmdOptions)
	if cmdID == LAND_CMD_ID then
		-- clear old pad
		RemoveLander(unitID)
		-- stop if we've landed
		if landedPlanes[unitID] then
			return true, true
		end

		local airbaseID = cmdParams[1]

		local padPieceNum = CanLandAt(unitID, airbaseID)

		-- failed to land
		if not padPieceNum then
			return true, true
		end

		-- update new pad
		airbases[airbaseID][padPieceNum] = unitID
		landingPlanes[unitID] = {airbaseID, padPieceNum}
		return true, false
	end
	if cmdID == FORCE_LAND_CMD_ID then
		pendingLanders[unitID] = true
		return true, true
	end

	return false
end

function gadget:UnitCommand(unitID, unitDefID, teamID, cmdID, cmdParams, cmdOptions)
	if not planes[unitID] then
		return
	end
	landedPlanes[unitID] = nil
	RemoveLander(unitID)
	local airBaseID = Spring.GetUnitTransporter(unitID)
	if not airBaseID then
		return
	end
	local airbasePads = airbases[airBaseID]
	if not airbasePads then
		return
	end
	Spring.UnitDetach(unitID)

	for pieceNum, reservedBy in pairs(airbasePads) do
		if reservedBy == unitID then
			airbasePads[pieceNum] = false
		end
	end
end

function gadget:GameFrame()
	for unitID, _ in pairs(planes) do
		if not landingPlanes[unitID] and not landedPlanes[unitID] and NeedsRepair(unitID) then
			pendingLanders[unitID] = true
		end
	end

	for unitID, t in pairs(landingPlanes) do
		local airbaseID, padPieceNum = t[1], t[2]
		local px, py, pz = Spring.GetUnitPiecePosDir(airbaseID, padPieceNum)
		local ux, uy, uz = Spring.GetUnitPosition(unitID)
		local dx, dy ,dz = ux - px, uy - py, uz - pz
		local r = Spring.GetUnitRadius(unitID)
		local dist = dx * dx + dy * dy + dz * dz
		-- check if we're close enough
		if dist < 4 * r * r then
			Spring.GiveOrderToUnit(unitID, CMD.STOP,{},{})
			Spring.UnitAttach(airbaseID, unitID, padPieceNum)
			landingPlanes[unitID] = nil
			landedPlanes[unitID] = true
		else
			Spring.SetUnitLandGoal(unitID, px, py, pz, r)
		end
	end


	for unitID, _ in pairs(pendingLanders) do
		local closestAirbaseID, closestPieceNum = FindAirBase(unitID)
		if closestAirbaseID then
			Spring.GiveOrderToUnit(unitID, CMD.INSERT,{0, LAND_CMD_ID, 0, closestAirbaseID},{"alt"})
			landingPlanes[unitID] = {closestAirbaseID, closestPieceNum}
			pendingLanders[unitID] = nil
		end
	end
end

function gadget:Initialize()
	for unitDefID, unitDef in pairs(UnitDefs) do
		if unitDef.isAirBase then
			airbaseDefIDs[unitDefID] = true
		end
	end

	-- Fake UnitCreated events for existing units. (for '/luarules reload')
	local allUnits = Spring.GetAllUnits()
	for i=1,#allUnits do
		local unitID = allUnits[i]
		gadget:UnitCreated(unitID, Spring.GetUnitDefID(unitID))
	end
end

function gadget:UnitLoaded(unitID, unitDefID, unitTeam, transportID, transportTeam)
    if (planes[unitID]) then
        Spring.SetUnitNoDraw(unitID, false)
        Spring.SetUnitStealth(unitID, false)
        Spring.SetUnitSonarStealth(unitID, false)
    end
end

--------------------------------------------------------------------------------
-- Unsynced
--------------------------------------------------------------------------------
else

function gadget:Initialize()
	Spring.AssignMouseCursor("Land for repairs", "cursorrepair", false, false)
	Spring.SetCustomCommandDrawData(LAND_CMD_ID, "Land for repairs", {1,0.5,0,.8}, false)
end

function gadget:DefaultCommand()
	local mx, my = Spring.GetMouseState()
	local s, targetID = Spring.TraceScreenRay(mx, my)
	if s ~= "unit" then
		return false
	end

	if not Spring.AreTeamsAllied(Spring.GetMyTeamID(), Spring.GetUnitTeam(targetID)) then
		return false
	end

	if not UnitDefs[Spring.GetUnitDefID(targetID)].isAirBase then
		return false
	end


	for _, unitID in pairs(Spring.GetSelectedUnits()) do
		if UnitDefs[Spring.GetUnitDefID(unitID)].canFly then
			return LAND_CMD_ID
		end
	end
	return false
end

end
This gadget doesn't have any game specific logic so it should probably be possible to drop it into any game with planes and repair pads to see how it works.
It's nowhere near complete, and it probably contains bugs. I publish this mostly to give a reference about the new air+transport stuff and how to use them.

NeedsRepair is where you decide whether a unit needs repair
CanLandAt may need tweaking according to your units and the names of their pieces
and I'm not doing anything currently with the planes on the pad, just iterate on landedPlanes or use UnitLoaded/UnitUnloaded
Super Mario
Posts: 823
Joined: 21 Oct 2008, 02:54

Re: Basic airbase land command gadget

Post by Super Mario »

I know that you remove the hardcore fuel logic from the game engine for aircraft, so the questions are.
1. Can this be re-implemented easily from a game dev side?
2. Is there a genetic replacement for it, like a container for units that stores ammo, fuel, etc?
hokomoko
Spring Developer
Posts: 593
Joined: 02 Jun 2014, 00:46

Re: Basic airbase land command gadget

Post by hokomoko »

Super Mario wrote:I know that you remove the hardcore fuel logic from the game engine for aircraft, so the questions are.
1. Can this be re-implemented easily from a game dev side?
2. Is there a genetic replacement for it, like a container for units that stores ammo, fuel, etc?
1. yes
2. https://springrts.com/wiki/Lua_SyncedCtrl#RulesParams
User avatar
PepeAmpere
Posts: 591
Joined: 03 Jun 2010, 01:28

Re: Basic airbase land command gadget

Post by PepeAmpere »

Awesome hokomoko! New commands are cool and attaching example gadget, lol, what a service. :twisted:
User avatar
Jools
XTA Developer
Posts: 2816
Joined: 23 Feb 2009, 16:29

Re: Basic airbase land command gadget

Post by Jools »

There is the xta version as well, a staggering 671 lines of code for this (sometimes more is less):

https://github.com/xta-springrts/xta-sp ... airpad.lua
User avatar
PepeAmpere
Posts: 591
Joined: 03 Jun 2010, 01:28

Re: Basic airbase land command gadget

Post by PepeAmpere »

Jools wrote:There is the xta version as well, a staggering 671 lines of code for this (sometimes more is less):

https://github.com/xta-springrts/xta-sp ... airpad.lua
Of course jools, I was stalking your code once you released the fixed XTA few days ago :twisted: And I was wondering what thor is doing in author field of given gadget and then it remind me old problems with landings...
User avatar
Jools
XTA Developer
Posts: 2816
Joined: 23 Feb 2009, 16:29

Re: Basic airbase land command gadget

Post by Jools »

I just started by using his gadget and it still has some code left from it. If you have an axe, and your brother replaces the blade, and then you replace the shaft: is it still the same axe?
User avatar
Silentwings
Posts: 3720
Joined: 25 Oct 2008, 00:23

Re: Basic airbase land command gadget

Post by Silentwings »

What mechanism should now be used for landing onto a pad?

After fixing a few bugs, the result of trying to land on a pad (which seems to be what the code is meant to do) is that the aircraft gets somewhere close-ish and then just snaps onto it in an instant. But this can't be what was actually intended?!
hokomoko
Spring Developer
Posts: 593
Joined: 02 Jun 2014, 00:46

Re: Basic airbase land command gadget

Post by hokomoko »

I was slightly generous with the snap distance, you can probably reduce it somewhat.
(probably checking for sane distance + very very close height should be the trick)
User avatar
Silentwings
Posts: 3720
Joined: 25 Oct 2008, 00:23

Re: Basic airbase land command gadget

Post by Silentwings »

Ok, I'll write a fully working update of this gadget - will take a few days since busy.
hokomoko
Spring Developer
Posts: 593
Joined: 02 Jun 2014, 00:46

Re: Basic airbase land command gadget

Post by hokomoko »

make sure you call SetUnitLoadingTransport because I've forgotten to :P

SetUnitLoadingTransport(unitID, transportID) < every fallback or something like that
SetUnitLoadingTransport(unitID, nil) < on cleanup/when landed
gajop
Moderator
Posts: 3051
Joined: 05 Aug 2009, 20:42

Re: Basic airbase land command gadget

Post by gajop »

Probably a much bigger problem is in how UnitAttach works. You have no control over units rotation, and it will always be rotated depending on the piece you're attaching to. This means that if a plane comes at a certain angle it will be *snapped* once it lands unless you also rotate the airpad piece it's being attached to.
User avatar
Silentwings
Posts: 3720
Joined: 25 Oct 2008, 00:23

Re: Basic airbase land command gadget

Post by Silentwings »

I also don't have a ready solution to this. Is it possible to know the position/rotation that the transportee will be in once attached (assuming the transporter remained stationary), without actually attaching it?
User avatar
Jools
XTA Developer
Posts: 2816
Joined: 23 Feb 2009, 16:29

Re: Basic airbase land command gadget

Post by Jools »

I already worked quite a bit on this gadget and have therefore discovered some issues. One issue is how to actually do the repairing, because if you use the repair command, you can only do one thing at a time. If you lua it, you'd also have to implement the nano spray (if you're a perfectionist).

I don't think the angle and snapping is so bad, it looks silly, but with a too small snap distance the plane used to get stuck in the wrong place.
User avatar
Silentwings
Posts: 3720
Joined: 25 Oct 2008, 00:23

Re: Basic airbase land command gadget

Post by Silentwings »

Good point, I can't see how to do nano spray for units with multiple pads either (imo forcing use of lups nanospray would be overkill & non-portable).
User avatar
Jools
XTA Developer
Posts: 2816
Joined: 23 Feb 2009, 16:29

Re: Basic airbase land command gadget

Post by Jools »

Yes, but it's not even just that. For instance, if you have a carrier that has a repair pad, it can also move. The move order cancels the repair order, unless you do some kind of lua witchcraft
Super Mario
Posts: 823
Joined: 21 Oct 2008, 02:54

Re: Basic airbase land command gadget

Post by Super Mario »

Jools wrote:Yes, but it's not even just that. For instance, if you have a carrier that has a repair pad, it can also move. The move order cancels the repair order, unless you do some kind of lua witchcraft
Move with x function then?
User avatar
Silentwings
Posts: 3720
Joined: 25 Oct 2008, 00:23

Re: Basic airbase land command gadget

Post by Silentwings »

Fully functional airbase gadget: viewtopic.php?f=23&t=34473

-> locking this, pm me/mods if unlock wanted.
Locked

Return to “Lua Scripts”