Developers Iteration I of 2023

18

Find out about the last bits and twigs for the first development iteration of the game in 2023. Everything in this topic is merged - you can experiment with these changes yourself by playing the FAF Develop game type. You can choose this game type as you host your game:

a724abb1-a5db-4d84-8734-4fddcbb638ec-image.png

With that said, let us dive right in the latest developments!

A work of art is never finished, merely abandoned

21

Improved structure terrain interactions (#4584 on Github)

Combined with the knowledge of Balthazar we managed to significantly reduce a common source of confusion and disappointment: bad terrain deformations. As you build structures the terrain was flattened underneath, with a bit of (bad) luck this could create sharp edges in the terrain that end up blocking pathing or projectiles all together.

As a few examples:

Seton's Clutch

f3787cae-d488-4666-bd87-d3b226c59470-image.png

Long John Silver

4f5ab8ef-e4dd-47b8-94e3-cd28554a266d-image.png

b1fd76bc-7812-49fe-ac45-98bab33276ef-image.png

We're all too familiar with them, to the degree that it would even limit the creativity of map authors. Terrain has to be flat, or it will cause bad deformations that result in significantly worse gameplay.

But - no more! With the changes we're making to how structures interact with terrain we no longer create a flat plane that needs to be completely horizontal. Instead, we create a gradient between the four points of the build skirt and slightly orient the structure to match the gradient as required. As a result the number of bad deformations is reduced significantly, to the degree that it is really difficult to create a bad deformation.

As a few examples:

Seton's clutch

ba35b039-cd3b-43c5-8f7f-1394cb7ca57b-image.png

Long John Silver

60aa05b9-0a24-4d27-a37d-88c6967e0c0f-image.png

bcd20260-3fec-4aea-b0ed-a02fc3f3788a-image.png

This change is significant - not only does it help you as a player to just enjoy the game. It will also increase the creative possibilities for map authors as terrain no longer necessarily needs to be flat.

Significant performance improvements (#4584 on Github)

We've found one more large performance hiccup and managed to resolve it. The problem and solution is rather technical. Tthroughout the entire game you can expect 10% to 30% more performance on average, depending on what is happening.

I'll try and explain it - I've not found a way to keep it simple. Therefore I'll just write it out and make comparisons where possible.

Table trashing

The first issue was during instantiation of a table with a C reference. Instantiation means the creation (allocation) of something. And the table can reference quite literally anything:

  • A damage instance: any damage in general, regardless whether it hits a unit or prop
  • A decal: tread marks or the small decals we generate when projectiles hit the ground
  • An effect: terrain effects, projectile trail effects, projectile impact effects, build effects - the list goes on
  • A blip: the radar blips that are created for units
  • A manipulator: these are applied to units for animations, and the sliding of barrels as they fire
  • An entity: units, weapons, projectiles, bare entities, props, beams, trails, ...
  • ..., the list goes on but we'll stick with these examples

When we create an instance they inherit functionality and data from a Class. This was done via a table called a ClassFactory, which was defined as:

ClassFactory = {
    __call = function(self, ...)
        -- create the new entity with us as its meta table
        local instance = {&1 &0}
        setmetatable(instance, self)

        -- call class initialisation functions, if they exist
        local initfn = self.__init
        if initfn then
            initfn(instance, unpack(arg))
        end
        local postinitfn = self.__post_init
        if postinitfn then
            postinitfn(instance, unpack(arg))
        end

        return instance
    end
}

The problem is at the very start:

    __call = function(self, ...) <-- note the '...'

The argument ... is called a varargs, and the idea is that it allows a function to be more flexible: you can pass any amount of data to the function and the function can then iterate over that data, as the data is stored in a table. The table is created regardless of whether there is any data to pass along. And that is exactly what the issue was: every example we just described does not use this approach to pass data to the instance. Therefore every example described has an overhead of creating a 80 byte table, just to trash it out again!

And the overhead is significant: 80 bytes sounds like nothing. But let us take a single event as an example: when a weapon fires a projectile. At this event the game creates:

  • 1x manipulator: A slider to mimic recoil
  • 6x effects: two effects for firing the weapon (a flash and smoke for example), two effects for the projectile itself and two impact effects
  • 1x projectile
  • 1x damage
  • 1x decal
  • 1x other things

In total, on average:

  • 11x varargs table created just to trash it again

For this single event we've trashed up to 880 bytes worth of memory. The average unit fires about 1 projectile per second. That means during a battle a single unit can trash up to almost a kilobyte of memory per second! Multiply that by 200 units for the average battle and we're talking about hundreds of kilobytes of data being generated per second, just to trash it again. To put that into perspective:

  • This post is about 5 kilobytes of text, at the moment of writing this sentence. We'd trash the same amount of memory when a Hoplite fires one salvo
  • The average JPEG image is about 50 to 500 kilobytes. It is not unreasonable to trash as much memory as the average JPEG image per second during a relatively small battle

We can continue on - but the impact is quite significant when you take into account the garbage collector and how the CPU cache works. For example, we drastically reduce trashing the caches and increase the chance of a cache hit.

For those that like a puzzle: there are a lot of other very common events that no longer create this dummy table. Can you find some based on this information? I'll add them to a list in this post as they are found 🙂

Pre allocate tables

The second issue is about how tables grow in memory as more elements as attached to it. We'll take the example of the creation of a projectile again. When a projectile is created, we at least add the following fields to it:

OnCreate = function(self, inWater)
    -- store information to prevent engine calls
    self.Blueprint = EntityGetBlueprint(self)
    self.Army = EntityGetArmy(self)
    self.Launcher = ProjectileGetLauncher(self)
    self.Trash = TrashBag()
end,

Just like lists in C# do, a table in Lua starts with no allocated memory by default. As we add elements to the table (the self instance, in other words: the projectile) the table grows accordingly. This is done by logic similar to the following:

static void resize (lua_State *L, Table *t, int nasize, int nhsize) {
  int i;
  int oldasize = t->sizearray;
  int oldhsize = t->lsizenode;
  Node *nold;
  Node temp[1];
  if (oldhsize)
    nold = t->node;  /* save old hash ... */
  else {  /* old hash is `dummynode' */
    lua_assert(t->node == G(L)->dummynode);
    temp[0] = t->node[0];  /* copy it to `temp' */
    nold = temp;
    setnilvalue(gkey(G(L)->dummynode));  /* restate invariant */
    setnilvalue(gval(G(L)->dummynode));
    lua_assert(G(L)->dummynode->next == NULL);
  }
  if (nasize > oldasize)  /* array part must grow? */
    setarrayvector(L, t, nasize);
  /* create new hash part with appropriate size */
  setnodevector(L, t, nhsize);  
  /* re-insert elements */
  if (nasize < oldasize) {  /* array part must shrink? */
    t->sizearray = nasize;
    /* re-insert elements from vanishing slice */
    for (i=nasize; i<oldasize; i++) {
      if (!ttisnil(&t->array[i]))
        setobjt2t(luaH_setnum(L, t, i+1), &t->array[i]);
    }
    /* shrink array */
    luaM_reallocvector(L, t->array, oldasize, nasize, TObject);
  }
  /* re-insert elements in hash part */
  for (i = twoto(oldhsize) - 1; i >= 0; i--) {
    Node *old = nold+i;
    if (!ttisnil(gval(old)))
      setobjt2t(luaH_set(L, t, gkey(old)), gval(old));
  }
  if (oldhsize)
    luaM_freearray(L, nold, twoto(oldhsize), Node);  /* free old array */
}

That is a lot of code, but more importantly: it allocates new memory, re-inserts the existing elements into the new memory and prepares the old memory for deallocation! That is a relatively expensive operation, but it all depends on how often it is run. To understand that, these are the resize thresholds for the hash-based array:

Resize threshold Resizes to Bytes hash part occupies Total bytes
0 or 1 2 40 80
2 4 80 120
4 8 160 200
8 16 320 360
16 32 640 680
... ... ... ...
n 2 * n n * 20 n * 20 + 40

That means when we create a projectile we have to resize at least three times! And in practice it is four times, where as the average projectile can take up to 8 hash entries. That causes it to just resize to 16. This is something we can try and optimize in the future too.

Usually in Lua you can not pre-allocate a table. That is not normal syntax. But the GPG devs introduced that syntax in the Moho engine. And using that syntax, we can size the table as it is created. As an example, this is a special class instantiation factory for projectiles:

ProjectileFactory = {
    ---@param self any
    ---@return table
    __call = function (self)
        -- LOG(string.format("%s -> %s", "ProjectileFactory", tostring(self.__name)))
        -- needs a hash part of one for the _c_object field
        local instance = {&15 &0}
        return setmetatable(instance, self)
    end
}

Where the important line is this:

        local instance = {&15 &0}

Which states that we want to pre-allocate the hash part of the table so that it can at least hold up to 15 elements.

This same principle applies to any instance mentioned earlier, where we properly pre-allocate the table for units, shields, weapons, projectiles, damage instances, effects, decals and all the other parts of this game. We now properly pre-allocate them all, drastically reducing the frequency at which the engine ends up calling the resize function.

Control ... (#4587 on Github)

A Discord user asked in the general chat if there is an easy way to split up your selection. The answer is no - but the question is why? Why are there no tools to manage your current selection?

With this patch we're introducing a first batch of hotkeys that you can use to create subgroups of your selection, through which you can navigate. You can find them in the hotkeys menu:

f8605648-3467-4938-9b38-e4a3e9a444cc-image.png

All these hotkeys divide your selection over a series of subgroups. You can then use an additional hotkey (in the screenshot it is Tab) to navigate through your subgroups. We'll take two examples:

Divides a selection by the line through your mouse position and the center of the selection
bdcf1864-79e4-4011-9dbc-1da63a65096c-image.png

Divides a selection orthogonally to the line from your mouse position to the center of the selection
be226567-5f0a-4d0e-a689-c633a831d31b-image.png

These two hotkeys allows you to divide your selection into two subgroups, which you can then quickly navigate between. The command mode (when the cursor changes to issue orders, for example reclaim, ground attack, launch orders ...) are not reset as you navigate between subgroups.

With this patch we at least introduce the following divisions:

  • Divide over mouse axis
  • Divide over orthogonal mouse axis
  • Divide over major axis
  • Divide over minor axis
  • Divide over tech
  • Divide over layer
  • Divide over tech, but only include engineers
  • Divide over subgroups of size 1
  • Divide over subgroups of size 2
  • Divide over subgroups of size 4
  • Divide over subgroups of size 8
  • Divide over subgroups of size 16

We hope this provides you with more control over your selection, and therefore with more control over your army. If you have ideas of other divisions or selection manipulation: feel free to jump into the suggestions channel in Discord and we can discuss them accordingly!

... and Command! (#4577 on Github)

On top of that we are introducing various quality of life features. The first two features we'll reveal are about adding optional side effects when you issue an assist order. Specifically we're talking about these options:

ad1c2ce5-8b55-44c4-9936-d4c9f542e4ab-image.png

The option Assist to upgrade allows you to immediately queue up the upgrade of a tech 1 mass extractor as you issue the assist order. The option Assist to Unpause allows allows you to unpause extractors and radars as your units start assisting them. The former is useful for quickly queue up (assisted) extractor upgrades. The second makes it easier to focus your build power.

A work of art is never finished, merely abandoned

9

You can find out about all the other changes for this development iteration by reviewing the milestone on Github.

<reserved for changelog>

A work of art is never finished, merely abandoned

1

Hello Jip,

this is fantastic news. I didn't think it could be fixed, wow great job.

2

I understand the attraction of reducing deformity for gameplay sake, but I can't say I am a fan of this.

Having the buildings at different angles is going to make some bases look like a smurf village, the aesthetic should be gritty industrial, not hobbiton.

You will end up with taller buildings like the perimeter system looking like the tower of pizza.

I know the game is not going for hyper-realism, but ask any engineer and they can tell you why building foundations are levelled.

If there was a way to perhaps 'bury' some of the building model in the ground, so that the structure itself is still horizontal, that would be perfect with this.

1

What about defensive structures and their shooting angle limits?

0

this also prevents terraforming now?

@black_wriggler said in Developers Iteration I of 2023:

If there was a way to perhaps 'bury' some of the building model in the ground

wouldn't this make some buildings hitboxes harder to hit by weapons?

0

@mach wouldn't this make some buildings hitboxes harder to hit by weapons?

well looks like this proposal does still form the terrain, just less than currently, having a corner of a structure slightly underneath the level of a slope could be problematic for small buildings perhaps, would require testing

2

@black_wriggler I think it will look much better with terrain being smooth than some really sharp edges that scream "I'm a 2000s game with bad graphics" Plus less sharp edges might result in better pathfinding so I'm all for this over sharp edges.

FAF Website Developer

1

@Black_Wriggler / @Melanol / @Mach

The changes are there for you to test and experience on the FAF Develop branch. I highly encourage you to do so and report back with what you find. There are quite some parameters that we can adjust to make it work better for specific units, as an example:

  • Physics.MaxGroundVariation: this is set to 1 for default, but for very high structures we could reduce this to 0.5. The Soothsayer would be a good candidate for this.
  • Physics.StandUpright: this prevents the structure from being tilted

And options that are available for longer units are:

  • Being able to slide them slightly into the ground

Note that the sliding will cause issues for smaller units. Therefore it is only a solution for units higher than, say, a tech 1 radar.

Having the buildings at different angles is going to make some bases look like a smurf village, the aesthetic should be gritty industrial, not hobbiton.

The average map is quite flat, and will still have the same look as before. What this will primarily prevent is large terrain deformations occurring near ramps and slight hills.

On top of that, the average view direction is from top to bottom. A structure being slightly tilted should not be that noticeable.

this also prevents terraforming now?

Terraforming terrain is considered a glitch, see also point 1 and 2 of:

A work of art is never finished, merely abandoned

5

I've added the second section about performance, see also the second post starting at 'Significant performance improvements'.

A work of art is never finished, merely abandoned

0

Terraforming comes to Supcom pog

put the xbox units in the game pls u_u

2

@Jip The new terrain looks awsome great job.

0

I guess Physics.MaxGroundVariation 0.5 causes the unit to only tilt half the angle of the ground? Have you tried how setting all structures to a value like 0.5 or 0.8 looks like? I think it would be nice to still keep a hint of a "platform" for the structures. With the new feature it can look kind of wonky sometimes, it deprives the buildings of any sense of weight or heavyness.

0

Wow I wouldn't have thought terrain deformation was fixable. Very cool.
And Lua continues to be a voodoo language that I refuse to touch.
Good work and all.

1

@blackyps MaxGroundVariation controls just how much deviation there can be, from edge to edge. This is the value the keeps you from building a factory on the side of mountain, for example.

2

Why not something like this (assume image is split into image 1, 2, 3 and 4, left to right):
terrain flat.png

How it is

When attempting to build on an incline (figure 1), currently the game finds some average height, plops the building on that height, and proceeds to flatten the terrain around the building (figure 1). This results in these tiny portions of terrain to get extreme angles (red in figure 2), which can prevent further building and/or even prevent units from moving through previously passable terrain.

Currently proposed solution

In figure 3, we can see the 'skew the building to follow the terrain' that has been worked on. As most will agree, it looks odd. But it does fix the problem of not altering the topography.

Maybe this

Figure 4 holds my idea. The idea is you pick the highest edge-point of a structure, and use that as the structure Y position. Rather than highest edge-point, you could also find an average which is heavily biased towards higher-Y points (meaning it will be partially sunk, but mostly 'floating' above the lower parts of the slope). You then add a 'base' to the structure (blue in figure 4), so as it would not look like it floats - kinda like what naval factories do atm, with their 'infinite' legs that go down as deep as need.

Pros are you benefit from 'not altering terrain' while also not skewing the buildings.
A big con is you somehow need to model to 'extend' below it's usual lowest Y value. But I thin that, with diligent work, just extending the base of all non-floaty buildings to go down shouldn't be a big problem. The only problematic buildings are the unit-dispensing ones, where there would be an odd ledge the unit would have to cross - one solution is to just extend the existing ramp to also cover negative Y space, but it might look awkward if the ramp is on a similar angle as the terrain (it might extend like 3 unit widths away from it's usual terminating point, which would look like it's going under other buildings).

0

Honestly, as a mod this feature would be neat, but as the main FAF branch? I would hate it. So what if it screams "I'm from 2000". One of my favorite parts about FAF is how it improves the quality of life while maintaining the visual fidelity and core gameplay integrity from SC:FA. To make this change would ruin that visual fidelity to me and honestly change so many little things about this game, like attack angles of PD and such. I just don't see this being the best way forward aesthetically for FAF. I liked how I could still make cool, level city-esc bases while still being effective for war. But honestly I would rather see this as a modification or separate branch for FAF because I love the visuals that it has maintained since the original game. You could argue that they've made many visual changes over the years, but those changes at least fit the SupCom feel for the most part.

2

Do people realize that this won't affect how 95% of structures look? They will be placed on flat ground anyways and will look the same. On the other hand this could reward creative placement of point defense

put the xbox units in the game pls u_u

1

@BlackYps As Sprouto describes: it is the maximum height difference over the build skirt. Tech 1 power generators are small, therefore the maximum angle would be larger. Factories are large, maximum angle would be smaller. I'm sure this can be computed using trigonometry.

@Fichom You propose a solution with a ton of edge cases for something that is an edge case itself. The majority of the terrain is flat - just generate any map and the majority is flat. Look at any competitive map and the majority is flat. It only matters for those moments where you build near or on top of a ramp / hill.

@GoodRaptor See what I wrote to Fichom - the majority of structures placed in a game are completely unchanged.

@Zeldafanboy thank you, finally someone that understands how niche the impact is in practice. Except for the fact that terrain deformations can be a real pain when they happen, and now it becomes virtually impossible to create terrain deformations that negatively impact gameplay.

For those that have worries: please take the time to experiment on the FAF Develop. We can discuss individual cases and adjust parameters if a structure can indeed end up looking silly. Describing hypothetical situations is - with all due respect - not helping anyone, especially as you can just start the FAF Develop game type as described in the first post:

With all of that said - I'm eagerly looking for a few representative lobbies to play on FAF Develop. I've seen a few replays of the past week and they feel a lot smoother performance wise - but they weren't exactly representative. It also helps a lot with testing the stability 🙂

A work of art is never finished, merely abandoned