Having Issues With Construction, and Placement?

Taken from this spring forum post on the C++ API and building construction, it may be useful for those wondering about the build functions

For those with a little lua know how, the taskqueuebehaviour ( in dun dun dun taskqueuebehaviour.lua ) uses shards Build function to build things, but it passes no position parameter, and assumes shard will figure out where to build things on its own.

If however, you pass a second parameter for the position, you can call the API yourself with your own values, bypassing the borked values in the shard c++ code completely. The LuaAI version of Shards API uses a different mechanism.

To get the position, one would need to call this function, specified in preload/api.lua:

function map:FindClosestBuildSite(unittype,builderpos, searchradius, minimumdistance) -- returns Position

So, if we look in taskqueuebehaviour.lua, here is the code that does the actual building:

self.progress = not self.unit:Internal():Build(utype)

here, the progress value if true, means to execute the progress queue function and attempt the next task. If Build is successful it will return true, and since ‘not true’ is the same as ‘false’, progress is set to false and we can safely wait until the unit is idle again and progress is set to true and the cycle repeats.

You’ll notice that the unit value isn’t the one handed over by the Shard API. There’s a Shard framework unit which is a lua object, and then there’s the object passed from C++. To access this, you will do local u = self.unit:Internal(), and then you can perform all the operations that are listed in the api docs on ‘u’.

Where did this self.unit come from? It isn’t defined in the behaviour, rather when the behaviour is created, the behaviourmanager object then inserts the value afterwards.

So back to building. First you’ll notice we already have a unit type object called utype which we got several lines up using:

utype = game:GetTypeByName(value)
if utype ~= nil then
	-- stuff that uses utype

GetTypeByName takes a string, for example, to grab the unittype object for a core commander ( unitname “corcom” ) we would do:

local corecommandertype = game:GetTypeByName("corcom")

Then to build it we would do:

local u = self.unit:Internal()
-- or using the other version:

Here Shard will figure out where to build it, so to tell it where we want it to be built, we would pass in a position:


Where did positiontobuild come from? Before this line we need to define it and then set its value using map:FindClosestBuildSite.

So we have our unit type ‘utype‘. To get out builder position we do:

local builderposition = u:GetPosition()

Giving us this to put on line 102 :

local builderposition = u:GetPosition()
local searchradius = 600
local minimumdistance = 2
local positiontobuild = map:FindClosestBuildSite(utype,builderposition , searchradius, minimumdistance)
self.progress = not self.unit:Internal():Build(utype,positiontobuild)

This will search within 600 points of the builders location for somewhere that a unit of type ‘utype’ can be built. This unit must be a minimum of 2/minimumdistance footprints away from any other unit. These 2 values need to be fiddled to find the best ratio. To determine distance 1 footprint square is 8 points, and if you mouse over the terrain ingame, the coordinates in the tooltip are in the same units.

Some work best for big units, others work best for small units. Remember, tiny minimum distance results in LLTs blocking factory exits and gaps too small to fit units through, but good placement for large structures. Big values means nice spacious bases that are a dream to live in, but result in a hellish time trying to find places to put large structures. The obvious answer is to raise the search radius, but this has a hefty performance cost, and is the reason 0.29 spikey was, well, spikey.

If you want to experiment with taskqueuebehaviours.lua, you can put a copy in your overrides folder for your game. If you meet with success, send me a copy so I can replace the default copy, as well as ammend the default values in the C++ code! This also allows you to use nondefault values that work better for your game, or to use them on a per unit basis for maximum effect.

Also of note is that the Build method has the following incarnations:

-- the Build methods now return true if it worked, false if the command was bad
function Build(UnitType)
function Build(typeName)
function Build(typeName, Position)
function Build(UnitType, Position)

There is also CanBuildHere, which can be used to test if a unit type can be built at a certain position.

function map:CanBuildHere(unittype,position) -- returns boolean

Possible improvements I would suggest from NTai experience with this kind of system, is to randomly offset the builders location by a small amount, say 40 or 50, this way the builder can turn and start building immediately for small structures, whereas keeping the builders location could lead to that position being a valid spot, and the builder having to spend time moving out of the way.