Party Composition

Today’s goal was to balance the summons and basic monsters (rats and skeletons) to the point where a diverse party composition is more effective than only using one or two of the unit types.

This is not true in all situations: there are some layouts where three archers is definitely superior to any other combination–but there should be situations like this choke point where a variety of unit strengths wins the day.

For a refresher on what the units in Soulcaster do:

  • Shaedu (green archer) does the most damage, but needs a clear line of sight to the target.
  • Aeox (blue knight) does the least damage, but has very high HP and takes reduced damage. Melee only.
  • Bloodfire (red bomber) can lob bombs over walls. They are weaker than Shaedu’s arrows against single foes, but because of a large blast radius, he can hit multiple enemies with each shot and decimate monsters when they group closely.

party_composition_sc3

  • Ranged units alone can deal good damage, but as soon as the enemies close the distance, they can’t survive at close range.
  • A stack of three tanky units (knights) does a great job of keeping the choke point closed, Spartan army style, but simply can’t deal enough damage to survive the entire wave of enemies.
  • Two of the ranged units combined with a single tank work well, but just barely–the line is broken just as the final monsters are taken out.

 

Summoner AI and Balancing

With all three summons operational again, now begins the task of finding a good starting point for balancing.

The obvious things to tune are the values for creature health, damage, attack rate, etc. I use spreadsheets to do this sort of thing:

spreadsheetThe orange headings are the creature’s attack stats, which can be used to compute the outcome of an encounter. Those are in green. For example, with these stats, Shaedu can slay 0.44 skeletons per second provided direct shots and the ability to attack at full range.

The mitigated damage value is how much the creature would damage an armored foe. I’m currently just using an armor value of 2, which reduces damage of each attack by 2. This means lower attack power is severely reduced compared with strong attack power (compare the skeleton’s DPS drop from 4.50 to 3.00 with the rat’s DPS, which is being slashed from 4.80 to 2.40).

This spreadsheet is only really useful as a starting point, because there are so many other variables that impact on a creature’s effectiveness and survivability. Choke points are where Bloodfire does best, because the bombs hit multiple foes if they are clumped up. Shaedu does great in corridors where the monster has to head directly into the line of fire–given a slow enough release rate, she can actually take out an infinite number of skeletons or rats. And Aeox is most effective in 1v1 attacks, foe example, when he is guarding a doorway.

Before fine tuning these values, I have to decide on which AI features the summons have. For example:

  • Can Aeox start an attack on an approaching monster before they are in range to strike back? This makes an enormous difference if he is just cleaning up wounded foes by last-hitting them. It means that if monsters come in one at a time with less than 6 HP, he won’t be damaged by them. (I do plan to add this pre-emptive strike, because I think this is strategically cool).
  • Do Shaedu and Bloodfire lead their shots? Until today, they just fired at the monster’s current position. Fast-moving rats, when they are running laterally, become impossible for her to hit, because in the 10 frames it takes the arrow to arrive, the rat’s already moved away.

To calculate the shot lead, it just looks at the monster’s current velocity, calculates how long it will take for the arrow to reach the target, and looks ahead that far in the future to shoot where the monster will be.

Of course, if the monster changes directions or stops, the shot might still miss. This is so effective, I’m curious if it should be a powerup, or just natural behavior.  In a scenario like this, see how much difference it makes:

Without leading the shot
Without leading the shot
With leading - See all those direct hits
With leading – See all those direct hits

The next step is to create a few test maps and see how well the summons do in a variety of room layouts. It’s important that they be considered roughly equivalent in their effectiveness.  The synergy is even more important–Aeox and Shaedu working together, with proper placement, will take out twice as many foes as they would if they were placed one after the other.

Feature restored: Aeox and Shaedu Attacking

As is usually the case with a major code overhaul, I disabled many features so I could test stuff out and get it working with each existing system, one at a time. One of the final things I need to restore is the summon AI for targeting and attacking enemies. Their behavior is slightly different from monsters, because they never move to reach another target, but it uses the same code at least.

attacks1

So far, Aeox attacks properly (now with 8-way attacking!) and Shaedu also aims and hits properly. There’s still an issue with her firing trick shots at moving targets between obstacles (like through a doorway if the monster walks past it), which probably has to do with her search interval and vision check code. Something to fix tomorrow, along with the Bloodfire attacks.

Pathfinding Exhibition

I’ve gotten things to a state where it’s good enough that I want to show it off.  This video shows all the features I’ve put in place, and what happens when they’re removed. It’s kind of like that elementary school science experiment where you bake several batches of cookies and leave out a different ingredient each time, to see the results.

The pathfinding AI is a collection of features:

  1. Gauntlet Movement: Monsters move towards the summoner. They will slide along walls with diagonal movement, but can’t navigate around corners and will get stuck easily.
  2. Sidestepping: Even without any maze solving code, the Gauntlet movement can be improved a bit. When monsters sense that they have been blockaded for about one second, they randomly try moving in a diagonal towards the summoner, which helps them get around walls.
  3. Raw Pathfinding: A maze solving algorithm points the monsters towards the closest viable path to the summoner. If the monster has a direct, visible path to the summoner, it switches back to Gauntlet movement, because that tends to be faster than using the path. Notice how the rats on the right side get stuck because they get to the doorway at precisely the same time. I call this the Three Stooges Effect, and I came up with a pretty cool solution for it:
  4. Traffic Fields: A traffic director data type watches the monster movement, and adds vectors to each floor tile.  If a monster tries to walk on a tile that has a vector pointing in the opposite direction, it gets blockaded just like a wall. This way, whoever gets there first sets up the flow of traffic for that particular tile. Each vector times out after a second or so of inactivity, which lets the patiently waiting rats through. Look at the rats on the right now–they are behaving so orderly!  Even though both rats reach the doorway at the same time, the top one got evaluated first, got its movement vector placed there, and successfully repelled the lower rat (and all his friends).  The visualization lets you see the tile vectors as arrows.
  5. Creature Weight: The numbers you see on each tile in the Traffic Field visualization are the “weights” of each tile. These are used in the maze solving algorithm. They actually represent the number of steps it takes to get to that particular tile from the point of origin: in this case, the summoner. The fastest route is easily generated by starting at the monster’s location, and just picking the lowest number from the eight available directions.  With creature weight taken into account, any tile occupied by a creature gets its weight bumped up by a couple points, or 5 points if the creature is blockaded. Notice how the rats don’t all take the same path: once a few have started using a particular route, the weight to cross it is now larger than another route. Not only does this decrease the time it takes for a large crowd to solve the maze, but it also has the great side effect of flanking.
  6. Uncrowding: This last one is subtle, but makes a difference. When a monster has been blocked for more than a couple full seconds, it picks a random direction to move which is roughly in the opposite direction they were originally trying to go. This breaks up congestion and slightly improves the maze solving time.

At this point, the pathfinding AI is good enough to leave alone for a while. I just wanted to get it to a point where the monsters don’t get stuck, and you won’t be able to cheese them (at least not easily).