The biggest thing I'm thinking about for how I would actually use this function is that, in most scenarios, there will be one or two structures you want to demand are put in a particular place. Smelters come to mind here, as well as enemy structures that are mission objectives in some way. If you use the manual structure placement you mentioned before, will it know to build a tube from the final autolayout-ed base to wherever the manually placed structure is, or does it simply know not to cause unit overlaps with it?
You make a good point.
I just added a "MaxTubes" attribute to UnitData that defaults to -1 (unlimited). This can be used to control the number of tubes to any structure passed to BaseGenerator. Tokamaks, Light Towers, Mines, etc will default to 0 if the value is -1, so you will be able to connect those by tubes as well, if desired. This attribute applies even with IgnoreLayout set to true. Same with CreateWall.
If all else fails, you can always use the PathFinder to generate a path between structures for creating tubes. It's a little more complicated, but can help create extra tubes if the BaseGenerator isn't putting them where you want them. All generated units are put in a list, so you can scan the list for the auto structure you want to connect.
It's pretty easy and standard to write 0-based array code that is start point inclusive, and end point exclusive.
[snip]
Saying this made me rethink using max inclusive. I looked at some other frameworks, and apparently you are right, it is standard to use max exclusive. It seems strange to me that a rect contains a point on its min edge, but not its max edge. However, I also think that a rect of zero size shouldn't be able to contain a point, even if it is infinitely small. Anyway, it's been standardized.
Just kind of want to outline how I'm going to set up the AI.
ManagersThe AI is going to be set up as three managers. Managers assess the situation and prioritize goals using heuristics and goal weights. Goals are top-level tasks. Managers have no direct dependencies on each other. They assess the situation and perform their own goals to the best of their ability.
The managers are independent to prevent task blocking. For example, if the BaseManager needs a spaceport, but Space Program has not been researched, it will get stuck waiting for the research to complete rather than pursue other goals. CombatManager would block the other two managers if it's performing a raid, and so on.
BaseManager - Handles deployment, morale, and economic tasks. Also builds military.
ResearchManager - Chooses the next research topic for the colony. Base manager assigns the number of scientists to the lab.
CombatManager - Directs combat tactics with the military units available.
TasksTasks represent what an AI must do to complete a goal. They are arranged as a tree. The current goal task is at the top. When a manager wants to achieve a goal, it calls PerformTask on it.
Tasks contain a list of dependent tasks underneath it that are required to complete the current task. Perform task first checks these tasks to see if they are complete (IsTaskComplete()). If all are true, the task performs itself. If any are false, those unfinished tasks get PerformTask called on them. This can continue all the way down to bottom-level tasks that have no dependencies.
Examples of top-level tasks include "LaunchSpaceportEvac", "CreateMilitaryUnits", "HarassEnemy", and "FixDisconnectedStructure".
If, for example, the goal task is "FixDisconnectedStructure", it would check IsTaskComplete on the "CreateEarthworker" task. "CreateEarthworker" would check "CreateVehicleFactory".
Some tasks can't be completed, and the AI needs to give up on the goal to pursue other things. PerformTask returns false if a task cannot be performed.
In the example above, "CreateVehicleFactory" may not be able to complete its task. If there is no structure factory (or convec with it) or Cybernetic Teleoperation is not researched, the task cannot be completed. The manager is informed via the return, and chooses a different goal.
Bot TypesBot types or "personalities" will be based on goal weights. I intend to support the following predefined types:
PopulationGrowth, // Bot focuses on growing population. Keeps enough defense to avoid being killed. Will build Recreation, DIRT and other optional structures.
LaunchStarship, // Bot focuses on launching starship. Keeps enough defense to avoid being killed.
EconomicGrowth, // Bot focuses on resource acquisition. Keeps enough defense to avoid being killed.
Passive, // Bot does not build new structures. Keeps enough defense to avoid being killed.
Defender, // Bot will build military units and defend itself and allies. Does not attack.
Balanced, // Bot will build military units and defend itself and allies. Attacks with best available strategy.
Aggressive, // Bot will build military units and won't defend itself or allies. Attacks with best available strategy.
Harassment, // Bot will build military units and harass trucks, power plants, and unescorted or poorly defended utility vehicles.
Wreckless, // Bot will build military units and send them to attack even against overwhelming odds.
A predefined type can be selected, and then the weights tweaked to get the desired effect. For example, you can choose "Aggressive" and tweak how much it focuses on population and economic growth to either confine it to its starting base, or have it attempt to build all over the map. You can also change these weights at any time, so a dormant AI can be triggered to suddenly turn Aggressive or Wreckless.
The greatest obstacle is going to be suboptimal access to player state. I'm thinking of creating a PlayerInfo class to start pooling information in an efficient manner at the beginning of each update.
For example, I am working on the LaunchStarshipTask, and I need to know if the evac module has been deployed. I believe I can iterate over the player units to find it, but I need to also check if the phoenix module has been deployed in another task, the orbital module in another, and... well.. you can see where this is going. Hundreds of loops to pull single items from the player state.
There are other things that are harder to get due to limited API - Beacon states (was it surveyed?), structure demand, and possibly many other things.