Saturday, December 5, 2009

A programming aside: SRP in World's code

It's a bit different than most recent posts, but I felt like talking about about good code, bad code, and some of the World's code design that has paid off, in theory.

Divide-and-conquer is a pretty well known strategy, in any field. Any task which is sufficiently complicated has to be broken into pieces to be completed. Needless to say, almost all programming tasks are beyond the mental capacity of an individual to solve. Breaking the problem into pieces is a fundamental task in programming, and most advances in programming, historically, have been to make this break easier.

Now, there are many ways to divide things up. Or rather, there are many ways to combine the pieces together. Some of them are bad. For example, introductory object oriented programming teaches us to split things up by classes, and further by their respective nouns (data) and verbs (functions). This is bad! Very bad. In the long run, you end up with a class with a dizzying variety of tasks, data, and code, none of which may be terribly related to each other. Furthermore, the noun/verb class abstraction often breaks down in terrible ways. They say a square is a rectangle, but try and implement a square and a rectangle class with that relationship embedded and you'll have trouble, I promise you.

Anyway. We all agree that dividing the work up is a necessity. However, as soon as we did the work, we assumedly integrate it back together to form the whole. But, and here's the point, if we have to go back to it, how to we redivide it? We broke up the work, but only once, and then we threw the division away! That seems silly. Why not retain the division somehow, so that if we have to go back to code a year later, it is already divided into understandable components?

This has been codified into a programming principle called SRP, the Single Responsibility Principle. When following this principle, the rule is that any given piece of code must only 'care' about a single aspect of the program. Defining 'care' is tricky of course, but I define it to mean anything I can understand without reference. If the code brings two complicated systems together, and each is too complicated to keep in my brain, that's too much responsibility, and the offending code must be further divided (and conquered).

An anecdotal example. In Twilight 1, each potential action a unit could perform was a discrete class. Each individual class had a large set of knowledge required to perform its job. A 'rob that building' task action needed to know how to move, how to respond to danger, how to rob, and how to return with the results. Not only was each action very difficult to get working, but they inevitably had bugs. The code was too complicated, because it knew too much.

To fix this, in Twilight 2 I implemented an action sequencing system. Robbery was defined as 'Move to Building', 'Rob', and 'Return', and these were reusable pieces. How could this single element, the sequence, possibly be incorrect? It can't. There are no possible bugs. The sub-components can be wrong, but that's not the worry of this action, so it MUST work. I never had to touch the sequences again. (Actually, since I implemented my own scripting language to build these sequences, I overdid it greatly and suffered from second-system-syndrome, but that's a talk for another post).

So yes, Single-Responsibility is good. It makes for a HUGE number of classes, but each one is individually understandable, debug-able, and iterate-able. Ironically, the only good way to get SRP out of traditional Object-Oriented Programming, is to invert the traditional approach. Classes are no longer nouns with function verbs. Classes are now verbs, that operate on classes that are nouns. This separates data storage and activity. Each potentially complicated verb is split into its own responsibility, and life is good.

World's design, entirely on accident, absolutely enforces this paradigm. I didn't even realize it till a year past I had been working with it.

When working with the game entities in World, there are only 3 concepts available for use: Attributes (just data), States (usually implemented as a State Machine), and Actions (which are just functions, but with some attached metadata). When constructing a new action, there is a set of action classes already implemented, which can be used, or a new one can be implemented. An action implementation has a known definition and limited data availability. It cannot possibly do more than an Action is supposed to do. Each action definition, therefore, is extremely easy to understand, debug, or copy.

States are slightly more complicated, since state machines are not a simple construct. A state machine is a set of states (just a number, really), each of which has a set of transitions (conditions for moving to a new state), and behaviors (just some operation again, like an Action). If new code is needed, the only places to hook in are at these two types. Either you make a new transition, or a new behavior. And, like actions, these consist of a very small set of functions and responsibilities. They are simple. There are a lot of them, but each one is simple.

So as it turns out, actions can only act, and transitions can only transit. But perhaps more importantly, only actions can act, and only transitions can transit. This means that nothing else can possibly care about acting or transiting, and that, in effect, simplifies everything else.

Perhaps I should redefine SRP. Single Responsibility Principle: Any given piece of code must only 'care' about a single aspect of the program, and ONLY that piece of code is allowed to 'care' about it. Divided and Conquered.

Wednesday, December 2, 2009

Laziness

Some have said that laziness is a virtue of a good programmer. Perhaps this because a lazy programmer will often do a bit of extra work to be sure he doesn't have to work hard later. We'd rather write an automatic solution than exert manual effort.

Now, I think that's kind of a fallacy really. A really lazy programmer won't bother with the efficient solution, and just plods along doing whatever works. Perhaps there is a Ballmer-peak-esque point where laziness yields maximal efficiency, however. Indulge me in a brief allegory from the engagement implementation.

In Skirmish, there are two tables of hostile-entity information retained. First is engagement, which is a constant value, and is recalculated every 2 seconds. Second is damage-threat, which is additive, but decays at a rate of 5% per second. The only important yield from all this data is knowing which entity is on top of each table, and then combining the two tables to know which entity is the overall 'winner'. Remember, the entire system is event based, so we won't be asking who is on top and reacting. Rather, we need the system to tell us when the top entity changes. Furthermore, we apply a threshold, and define this threshold to be 10%. Only when a new entity reaches 110% of the current maximal entity, will the event fire.

So there are three cases. First, a newly applied engagement value trumps the current top. This is extremely easy. We are informed when engagement changes, so we can easily fire the event if the math is correct. The damage-decay is easy to manage, since at any given time we can compute the new value correctly and easily. Equally easy, is when a newly applied engagement value is the previous top, and is now no longer top. Very easy to implement.

Second, a newly incoming damage-threat rises above. Also easy for the same reason.

The only tricky case is handling transitions due to damage-threat decay. Herein there are several cases as well. First, one damage-threat drops below another due to decay. But HA! This is easy. Each threat decays the same exponential rate, so they are guaranteed to never cross, however long time passes! Piece of cake, unless someone decides they want damage-threat to decay at different rates per class. We'll just pretend that'll never happen for now.

And now the point. What if damage-threat decays below 90.9% of the top engagement threat, so that engagement is now 10% greater than damage-threat? Since the decay is implicit (that is, it is never calculated and assigned a new value), we would have to compute the future time where the decay reaches 90.9% of max engagement, set a timer, and wait till then. While the scheduling system World is written on makes that sort of thing easy, I think you can guess my reaction. I decided that that was too much effort, got lazy, and forgot about it.

Later I grumbled a bit and decided I polish the implementation off and get it right. But I realized a curious thing. The timer will NEVER FIRE. That's right. If I implemented the delayed decay transit system, it would never be relevant.

Two simple facts combine to put a constraint on the time the decay would require. First, know we have a 10% threshold for the top damage-threat to be relevant. Then know that there is a 5% decay per second on damage-threat, and then note the 2 second recalculation of engagement.
In the 2 seconds before engagement is reassigned, there is only marginally more than 10% of decay possible. So it'll never transit, or if it did, it'd only be minutely before the engagement ping would fire anyway. Horray for laziness!

Actually, all that is wrong.

The 10% threshold is for overcoming threat. It could be that at the 2 second mark, entity A has 100 damage threat, and entity B has 109 engagement, and entity A is currently the 'top' entity, since B has not crossed the threshold. In this case the transit would need to fire in about .2 seconds, in order to be correct.

But you know what? It doesn't matter. The end effect is that in extremely rare cases, damage-threat will not be overcome by engagement immediately, and instead will need to wait a very rare worst case of 2 seconds. If that becomes a gameplay disaster, then you can call me wrong and make me write the bloody algorithm.

Guess I'm Lazy.

Skirmish Update

  • Implemented engagement value calculations and broadcast, and nullification when unconscious.
  • Added per-class engagement data.
  • Implemented Engagement table, which tallies up incoming engagement values, and tracks which entity is on top of the list.
  • Implemented Threat-by-damage tracking, with an appropriate table and top-tracking as well.
  • Added a new type of AI state called a Behavior. The only existing one is called 'Attack by Aggro'. The main scenario has been rewritten to start this state on the hostile NPCs. As expected, this state starts idle, and invokes an Attack Target whenever the top entity in threat (taking the greater of damage and engagement threat) changes.
  • Aggro works! The game is fundamentally different to play, and works masssively better.

Wednesday, November 18, 2009

Commander Skill Design

Well, as promised (or perhaps dreaded by few?), let's talk about commanders.

As (maybe) previously mentioned, a player enters a skirmish with a single squad. A squad consists of a set of 'primary units', which use a class-based advancement system, and are led by a single commander. The commander is responsible for managing unit resources, morale, and formations, and is intended to be the director of combat. He is not necessarily a combative unit, but with a small squad the commander will necessarily become part of the action.

The primary goal, from the design perspective, of the commander, is to be a focal point for group management and development, and provide an advancement and customization path for different players. The commander is the player's avatar in skirmish, and perhaps also in the exploration and development modes of the game. The commander is where the player decides how he wants to play.

So let's look at how the commander develops. That's been the primary focus of our design, since implementation of formations and combat implications of command are not yet implemented.

Primary units are numerous, and therefore use a 'simple' development scheme, which requires only very rare user interaction. Primaries can change their current class at any time, but new ranks of the class are automatic, and new class availability is relatively rare. Commanders are single, and warrant more interesting decision making. We've decided to go with a skillpoint based system, where at set quantities of experience, a commander will gain a skillpoint, to be used whenever and however the commander wishes.

Commanders purchase skills from 1 of 3 categories. The first category is called the Resource tree, and covers skills that manipulate resources, equipment, and often morale. The second is called Tactics, and covers actions and maneuvers. Tactics may grant new abilities or buffs to the commander and/or his squad. The last category is called Command, and governs the leadership of the commander. Command may grant new formation slots, new maneuvers, or introduce a bias towards defending, or perhaps improve the commander's ability in certain situations, for example, being outnumbered, or defending an outpost.

Each category is arranged in into a set of skill 'Cells'. A cell is like a small skill tree, broken into 3 conceptual areas. The first row is the entry point of a cell, and there is only a single skill to be learned. Learning this skill grants access to the cell. Rows 2, 3, and 4 are other skills, and may have interdependencies, or not. Typically we expect to have 2 to 3 options per cell row. Requirements to advance down a tree are not set in stone yet (or maybe I just do not remember?). The last row, row 5, is the advancement row. Skills in row 5 match the row 1, access skills, of a new cell. When this is taken the new cell is unlocked. Furthermore, access skills are restricted. It is intended that selecting which cell to advance into is an important decision, and maps to how the commander is developing. The intent is that a commander uses skill points to develop within a cell, then occasionally picks a new cell to advance into.

The method of restriction is still relatively undecided, though several possibilities are under consideration. A simple option is to make all row-5 skills exclusive, so only 1 in any cell may be taken. This prevents breadth-first development, and forces a 'downward' momentum, which may force specialization, depending on cell structure. The next possibility considered would be to introduce a new type of skill point, rarely granted, which may only be used to select a new access skill, but may be used on any available access cell when granted. This allows more freedom at the expense of some simplicity.

Now, each category of skills (Resource, Tactics, Command) is not weighted the same, nor will they become available at the same time. At the moment I'm considering having Tactics be available from the onset, introducing Command later, and adding in Resource last. Introducing new concepts to players in a controlled way has its advantages. Hmm, perhaps we should actually write down the 'Player training experience' sometime....

To implement the separation of categories, we may introduce 3 types of skillpoints. Each is granted at different XP values. Say tactics points are granted every 200 XP, starting at 200. We could introduce Command points every 400 XP, starting at 2000, and easily control the rate of skill gain.

For reference, since it hasn't really been discussed, Kol is been in charge of Primary Unit development, and Eirikr is in charge of Commander design. We all discuss the possibilities of course. I'm in charge of implementation, so they are both doing more of that themselves as time goes on. Maybe I'll pester them into posting sometime too.

Wednesday, November 11, 2009

More on Engagement, Threat, and leading to Formations


After this last TOAST we came to some conclusions about the engagement and threat mechanics from last post.

First, the relationship between engagement threat and damage threat. Balancing the lines between these two is a tricky thing! One we think will be simpler if the current maximum of the two is used, rather than the sumation. This lets us balance 'tank' unit threat versus 'nuke' unit threat without having to account for the nuker engagement, and the tank damage.

Second, adding a health modifier to engagement threat. Wounded or weakened targets are more desirable to be attacked. At least by smart enemies. This could be implemented by modifying engagement threat magnitude based on current health. However, I think this aspect needs to be kept separate from the engagement itself. A bit more detail here:

Consider a line of attackers and defenders, the defenders being equal in threat. The units will tend to disperse along the line evenly because of this. Say the attackers nukers focus their efforts on one section of the wall. The defender there weakens, and the attackers then focus on this section of the wall, breaching the line at a weak point. A good mechanic! Also, something for the player to counter by adjusting the formations, or by using some sort of temporary boost on the weakened area.

Now, I do NOT think that this should be baked into engagement threat. I think this factor should be managed by the enemy, since its an AI decision, more than a gameplay mechanic. Doing so lets us make stupid enemies that behave more boringly and predictably. These types of AIs are crucial for teaching and making the game fun! If enemies are smart, we have a problem with the player... isn't. Undoubtedly in the long run we'll need NPCs to behave differently. Scouts may partially ignore engagement threat, for example. These effects needs to be applied in the threat-response logic, rather than the threat logic itself. Moving on!

Third, threat thresholds. Without a threashold to change current targets, pingponging is way too easy. It's a common practice for good reason. WoW has a 10% threshold for melee and 30% for ranged. Seems like a good place to start.

Fourth, engagement range decay must be greater than 0. There was some mathematical reason I had for this, but I can't remember the details. I remember that the gradient of the engagement threat must point outwards, even if the value is fairly small. I'll pull a Fermat here and say I'll show why later.

Fifth, morale modifications to engagement. This seems like an obvious way to tie in morale to formation effectiveness. When tank units get less happy their engagement threat goes down. This has a side effect of balancing out incoming damage, but is dangerous beyond a certain level. Let morale drop too low, and the line disintegrates immediately.

And lastly, pondering formations. Engagement encourages flanking. The more radii the cover the enemy, the slower they become and their weaknesses increase as they become more surrounded. It just works.

I like it.
Commander design next time.

Monday, November 2, 2009

Combat speed and Threat mechanics


Two topics I want to expand upon, both to say it 'out loud', and to keep a record of thoughts. First, movement speed in relation to combat, and second, threat mechanics. Both with respect to the skirmish game design, naturally.

First, Speed.

Ever notice how in games everyone runs everywhere, all the time? This is because walking is very, very, very slow. So slow that no one ever walks in games, unless it's a social thing. Those quests where you escort some NPC who decides to walk? Very irritating. But equally irritating are escorts where they move faster than you can and leave you in the dust. There's a reason all the escorts in WoW move at 95% run speed. So you can catch up.

Now take the perma-run speed and bring it into combat. For a game without positioning being important, no problem. For a game where you control one person, no problem. But in a game where managing formations, lines, and special maneuvers are important, and where you control a plethora of peeps, run speed is just too fast. Nothing is understandable. Small breaks in the line are immediately filled, to the point where the line break isn't noticeable. For formations, motion, and knockback to be relevant, units need to SLOW DOWN. A lot.

So we slow them down. Now we have the irritating dilemna of the slow escort. Waiting for your units to get in position is intolerable. Crossing a skirmish to setup a trap is agonizing. Exploration is, well, a slide show rather than a whirlwind tour. It's meh.

We get around one problem in the Skirmish system by the nature of its design. Exploration is not done with combat units. We can have exploring units move at a run pace, or even substantially faster if we wish, with no balance concerns, since out-of-skirmish speed has no advantageous effect in combat. But this is a digression, easily solved and set aside.

The current plan is to introduce the concept of engagement. When a unit is engaged, they are considered in combat, and their movement speed slows dramatically. Now, how engagement works depends on the offenders and defenders. Shieldmen have a small engagement radius, but may slow their opponents more. Scouts may have a large engagement radius but only slow their opponents slightly. On the receiving end, a scout may reduce the amount of motion slowed. This makes scouts faster and more nimble in combat, but all units match outside of combat, when travelling.

There are lots of open questions here. Do engagements stack? Are there disadvantages to being multiply engaged? This is sort of a flanking determination. If you're engaged by 5 enemies it is certainly worse than being engaged by 1. But this does not account for support. If you are engaged by 5 archers, but surrounded by 15 shieldmen, you are still at an advantage. So perhaps we should integrate the concept of support, which is a sort of anti-engagement.

Or perhaps this is getting too complicated. The design may need to be simplified down to understandability.

So let me review the basic tenets: In combat, units move slower. This is to promote the efficacy of formations, breaking formations, in addition to increasing the 'parsability' of combat. That is, the ability of the player to understand what is happening and make interesting combat decisions.

The second point is related: Threat mechanics. The holy trinity of MMOs is the tank/healer/dps role distinction. Tanks manage threat, Healers manage tanks, dps manage damage. Threat mechanics are imperative to this design working, even if they are quite artificial. It doesn't make much sense, really, but it makes the game fun, and makes the roles possible. Without them each role blends together. Compare class roles in Diablo 2 to WoW, for example. Diablo has no threat mechanics, and therefore you have no control over combat, except to kill things faster. In its defense, that works quite well for a brawler, but less so for a game intended to be tactical in nature.

So, threat. That is, how the AI units decide who to strike. The most basic threat mechanic is based on incoming damage. Units attack whoever hits them hardest. The next simplest is that they attack whoever is nearest. Let's consider our goals first then see what tools we need to implement to reach them.

AI behavior needs to be predictable. The user needs to be able to understand them and setup a formation and units to overcome the enemy 'strategy'. In skirmish, there are 2 seriously differing cases to consider. First, let's ponder the simple case; independent units. Secondly we'll address enemy squads, which should behave at a group level.

Certain types of player units are built to be the wall. They are the tanks of the squad. The first of these is the shieldman, naturally. We need to make sure the shieldman can do his job. His job really is to block units from getting behind him. We could enforce a sort of literal interpretation of this and eschew threat entirely, but just requiring a solid wall of shieldmen. Any independent unit that decides to get through to the nukers just keeps trying to walk around until they get there. But really, that just looks ugly. It means walls don't work at all. In reality, the shieldman would be easily able to prevent a single unit from passing, just by staying in the way. But if the AI routines and movement system cannot handle that by itself, we'll have to cheese it using something else. One solution (clearly) is a threat system. A basic proximity system would both get incoming units to stop at the shieldwall and appear to engage.

But, well, that's boring. Everything will stick to your closest units and strategy is too simple. If there are 3 incoming units and one defender, that should be a problem, and the 3 shouldn't merely stick to the 1 defender while the 15 archers plug away.

So how bout this. Earlier we introduced the concept of engagement, as part of a combat speed mechanic. Let us reintroduce that. Give each unit type an engagement radius and an engagement magnitude. Furthermore, let's make the engagement magnitude decay (optionally) with distance. Secondly, and importantly, have the magnitude of the engagement drop off by a function of the number of units engaged. Now, what do to with the engagement magnitude calculation? Let's say every tick, we recalculate engagement, perform a weighted combat-speed adjustment as before, and introduce a threat mechanic. The more engagement, the higher the threat ticks up. An example:

Let us define a shieldman's engagement: Short radius, high magnitude, but high decay. It's very strong in melee but even 10 feet away is almost nonexistant. Let us also define an archer's. Wide radius, low magnitude; they don't really slow you down since they aren't in your face.

So a unit attacks your formation from the front; the shieldman is closest and the first tick produces a lot of threat, the shieldman stays in melee with the unit and easily maintains a threat lead. Second example, 4 units attack from the front. The shieldman is closest and manages in get in melee with 2 of them. Each gets a moderate amount of threat. The decay rate on the other 2 means they go for the archer. The shieldman is able to hold a few but is overwhelmed as intended. One more case to ponder: 4 shieldmen in a line that isn't touching but overlaps engagement radii, and many attackers. As each attacker comes in, if they engage anywhere in the center of the line, they will be slowed substantially by each defender's engagement, and will likely stick to the shieldmen. However, if enough pile in on one location, they can overrun that shieldman's engagement by splitting it among themselves, and break through the line. Or perhaps they have enough knockbacks to split up the shield line, reduce engagement count in the breach, and suddenly those units are at full speed.

But... why did they try and break through at all? Well, the nukers, of course. But, we haven't accounted for them at all! We need to make sure they're on the threat table and known to be the highest threat, and therefore the intended target. So, we introduce a damage-to-threat mechanic as well. Balancing the intensity of the two will be tricky of course. Maybe there's an alternate thought here...

Consider keeping engagement threat and damage threat distinct. Damage threat will continually accrue from damage and decay slowly, while engagement threat is only based on the current state, and has no history at all. The current target is still the greater of all the threat entries, but this gives a new desirable behavior. When the line is broken, engagement threat falls off and damage threat dominates. The line falls apart and the attackers flood the nukers. Dramatic! Or, if your wall is solid, nothing will ever escape, and your formation does its job.

Food for thought!


World development update!

Yes, yes, things are actually getting done, isn't it odd?

  • Directory methods are implemented. The directory is responsible for keeping track of which servers are running, where they are, and what zone they are running. Servers use this to contact each other, clients use it to find their server, and the directory uses this to find prospective servers to create new zones on. Everything but that last one is implemented. This basically means that the Skirmish client can connect to any running zone without needing to know IPs or anything. It's important structurally, even though noone cares.
  • Morale partial implementation. The morale system is in place and works, although most of the morale modifiers are not running. We can set up classes to behave with respect to their morale, but the morale value itself isn't really changed. That's coming soon.
  • Unit details window. A first cut at a sub-window to show unit statistics is in place. It also lets primary units change classes, which is useful.
  • Commander design. Lots of work on the development and operational design of the commander units. Beginning implementation of this will probably start once morale is more polished up.
Going's been slow recently; been too busy for additional programming time. But it does continue. We're at the point where the game is playable, but it's kind of too ugly and cumbersome to want to. I may take another development detour to actually implement some graphics and effects.

On the list now: Morale modifiers, commander skill basic implementation, client-side data, server-servers, combat-speed mechanics, threat and engagement mechanics, graphics engine integration...

Might be a bit.