Previously, the Refinery placement check would first collect all cells
with resources inside the MaxBaseRadius, then perform a full findPos check
for each cell until one of them would not return null. The problem was
that if all of the cells returned null (for example if there wasn't enough
space between base center and resource, or if all suitable space was
otherwise occupied), it would basically check all tiles with resources
only to fail finding a suitable position and fall back to the normal
findPos check.
Since nearbyResources already performs a shuffle, I simply made the check
use the first cell from the list and use the basic findPos fallback if
that cell is null.
Closes#4717.
In places where arrays were being treated as a set, just create a set directly. This reveals the intention of such collections better, and also improves performance by allowing set based methods to be used.
- Cache the shroud projection even for flat maps to avoid allocating single element arrays.
- Avoid LINQ in shroud and map projection queries to avoid enumerator allocations.
- Avoid LINQ in calculation of sync values.
- Cache enumerables in ProductionQueue.
- Cache delegate in HackyAI.
The AI code runs on only one hosts, so by having the AI use SharedRandom values, the host's random gets out of sync with the other players' and crashes the game.
Moved water checks before --waitTicks.
Use Water enum instead of multiple booleans.
Check for BaseProvider rather than BaseBuilding.
Move expensive ClosEnoughToWater check to last position for naval
production override.
Early game AI usually follows the same build order (power plant first, then refinery), which also means they all start producing them at the same tick. This adds a random factor to the production delay, so not all AIs produce on the same tick.
Since naval structures have their own safety measures now and therefore shouldn't count towards failCount under normal circumstances, we can now assume that 3 consecutive placement failures mean lack of space. Therefore, rather than unconditionally resetting the failCount and retry every N ticks, we now cache the number of buildings and construction yards at the time of the 3rd consecutive failure and if the number of buildings hasn't decreased and number of construction yards not increased, we assume there is still not enough space and reset the retry delay instead.
If not enough water space can be found inside the base perimeter, stop the AI from trying to build naval production buildings permanently until it deploys another construction yard.
If enough water is available within the base perimeter, check whether any
building that provides buildable area (for adjacency) is close enough to
water, otherwise don't even start producing this naval structure.
This was extremely borked:
The priority overrides for refinery, production and silo ignored power completely and never checked whether the structure might drive the AI into low power state; whereas the check for regular building checked whether the structure's power -exceeded- MinimumExcessPower (previously just 0). The catch is that power draw is represented by negative numbers, so the only buildings that would trigger this were - power plants.
Now it checks whether the sum of building power draw (negative) and AI's current power level are lower than MinimumExcessPower, if not, build the structure, if yes, build a power plant instead.
Additionally, this check is now performed for the early-game priority overrides as well.
Last but not least the AI would not(!) give power plants priority if it was already low on power. This has been fixed as well.
To prevent the AI generating lag spikes by issuing too many orders in a single tick, it is now limited in how many orders can be issued per tick. Extra orders are queued. This allows the performance hit of issuing multiple orders to be spread out over several ticks.
Randomize the initial tick used for each AI so they don't cause lag but all flooding the same tick with orders, but instead (try) and use a different tick for each AI. Order processing is expensive due to the need to sync world state, so it is best to spread out the performance cost of issuing these orders over different ticks.