Good Robot #38: Spec’ing a Feature

By Shamus Posted Wednesday Nov 25, 2015

Filed under: Good Robot 78 comments

When you’ve got more than one person working on a complex bit of software, you generally need a specification (spec) for new features. The bigger the team, the more you need a spec. The more complex a feature, the more you need a spec.

According to stereotypes, big firms usually lean too hard on specs, to the point where they might spend more time writing the specs than coding the feature:

“The button will be ten pixels from the left margin and will conform to the usability guidelines sheet 201-a. It will be labeled “Join Game” and will – after a confirmation popup as outlined in the interface framework – begin polling the designated server in request for an open slot. If no slot is found, then the fallback behavior […]”

Meanwhile, little indie houses have a slightly less formal approach:

Bruce: Can you add a button that will let players join the game?

Barbara: Sure.

Stuff gets done either way, but sometimes indies are a little slapdash and sometimes big firms are a little too bureaucratic. On Good Robot, our spec is usually a sentence or two in the shared Google doc that we use as a universal to-do list.

But this week I ran into something that I realized was too complicated for that. It was one of those features that sounded obvious and simple in the meeting, but then became mysterious when I sat down to write the damn thing. (This is the point of a spec: To reveal the unknowns BEFORE coding begins. This is important in big firms, since once you’ve begun coding you’ve ALREADY been allotted a fixed time budget, which means this is a bad time to begin figuring out what you need to do.)

So I proposed a spec. And then I thought I’d post it here, just to show what the non-exciting bits of game development look like. This is pretty informal as these things go, but it should give you an idea of what needs to be hammered out before you start coding.

Also note that I’ve replaced the names of the stuff in our game with DOOM references to make it easier to follow.

Drop Tables Discussion

I'll bet this is SUPER distracting when you're trying to read.
I'll bet this is SUPER distracting when you're trying to read.

“Add a drop table” has been lurking on my to-do list for several weeks now. This is actually a complex enough topic that we need to define a spec. It turns out this is a bigger feature than we might have anticipated in meetings. Every time I’ve tried to start on it, I ended up with more questions on what we needed and what I was supposed to be doing.

Abstract:

We will be adding a new file called drops.ini or drops.xml, which will allow artists to design things to be dropped in the game. (Things that the player can pick up.) We want the system to be robust enough that it feels unpredictable and varied on repeated playthroughs, yet controllable enough that the game doesn’t feel like random chaos. Specifically, we want to be able to ensure things like, “Player will be offered at least 1 half-decent weapon before reaching point N.”

  1. Right now there are two main things that need to result in drops. We should go ahead and assume that more “causes” might be added later.
    • When you destroy a robot.
    • When you destroy a machine. (Boxes are “machines”, in the parlance of the game’s internal workings.)
  2. A drop can deliver a number of things. Given our discussions so far, It seems like it should be possible for a single drop to create multiple things:
    • Weapon pickup. (Of a specific type. Ex: “AlienBlaster”) This might be too naive a design. I’ll talk more about this at the end.
    • Money. (Of a certain range of values. Ex: Between $10 and $20.)
    • Robots. (Of a given type and number. Ex: 5 instances of “Imp”)
    • MAYBE projectiles? This would let a boss release a swarm of missiles as it dies. I don’t know how these would be aimed, but it’s here for discussion.
  3. A drop must have some sort of probability associated with it.
    • This probability ought to work on a per-item basis. So, maybe the “Cyberdemon” boss has 100% chance of dropping some imps, a 50% chance of dropping some PinkyDemons, and a 1% chance of dropping the BFG 9000.
    • For simplicity of parsing, I THINK this probability should be expressed as a percentage (50%) and not a ratio (1:2). The first is simply much easier to write, but the latter is better if we plan to have very very small chances (say 1 in 10,000’s), since percentages like “0.01%” aren’t as intuitive. (And I worry about floating-point shenanigans.)
  4. Drops will be named.
    • So in our file, the artist will name a drop something descriptive. “Level1Trash”, “Level2MiniBoss”, “BellaAndJacobOTP”, or “foo”. Whatever.
    • The name will be referred to by the robots and machines in their respective files: “drop=Level2MiniBoss”
    • If we go crazy, I suppose we could add something insane like recursive drops. So “Level2MiniBoss” has a 50% chance to create “Level1Trash”, which itself might contain references to other drops. This is powerful, but it’s also an exercise in juggling dynamite. It’s only slightly more effort to code, but you can create a lot of needless complexity and bugs with this. Protip: I’m not going to build in a system to detect infinite recursion, so if you screw up you might crash the game. Choose wisely.

Proposal

Go on. Just TRY to look away. Ignore all this motion and just focus on the static words that OH YOU'RE LOOKING AT THE ANIMATED GIF AGAIN, AREN'T YOU?
Go on. Just TRY to look away. Ignore all this motion and just focus on the static words that OH YOU'RE LOOKING AT THE ANIMATED GIF AGAIN, AREN'T YOU?

My proposal, which needs to be discussed before I begin work on it:

I’m 99% sure that this needs to be in XML. The problem is hierarchical, which means using .ini files would be clumsy. Let’s just start with this assumption.

The definition starts with the name of the drop. It is then followed by one or more entries of things that might be dropped. Example:

1
2
3
4
5
<PhatLewt>
<AlienBlaster chance="50"/>
<Imp Min="0" Max="6" Chance="50"/>
<Cash Min="0" Max="100" Chance="100"/>
</PhatLewt>

For each entry:

There is the name of the drop: Either the name of a robot, the name of a weapon, or the word “cash”.

There is a minimum and maximum number to drop. If you omit these, it will default to a min and max of 1. (This is good for weapons, since it doesn’t make any sense to drop more than one of the same weapon.)

Then there is the value “chance”, which will probably be a percent. Note that it will roll this chance and THEN roll again for min and max. So in the example above, it might roll the dice for Imps. The roll comes up positive, so it rolls again to see HOW MANY. Then it might roll zero, meaning no imps actually appear. Keep this in mind.

You can use this system to create bell curves. For example, if you don’t want cash drops to be completely random, then you could do this:

1
2
3
4
<ThrowMoney>
<Cash Min="1" Max="6" Chance="100"/>
<Cash Min="1" Max="6" Chance="100"/>
</ThrowMoney>

Both cash drops will always happen, and will always drop between 1 and 6 coins. This is the exact thing you get when rolling 2d6 in a tabletop setting: Most values will be in the mid range, and values of 2 or 12 will be rare.

Robots already have a value for how much money they will drop. I don’t see any reason to REMOVE that system, but we might simply transition to using this new system for that. Or maybe we’ll just limit this new system for specials and bosses.

One final note is that this doesn’t allow us to drop weapons from lists of possibilities, which is what vending machines use. Maybe we also need to add a systems of “weapon pools” of similar-powered weapons. So each entity can be a weapon, a weapon pool, money, or a robot. (And maybe projectiles.)

This needs a lot of discussion. It’s probably the most complex item on my list right now, so the sooner we hammer this out, the better.

Conclusion

So this is what I sent to the rest of the team (minus the animated Gifs) this morning. This afternoon we’ll have our meeting and we’ll see if what I’ve proposed fits with the needs of the project.

 


From The Archives:
 

78 thoughts on “Good Robot #38: Spec’ing a Feature

  1. DanMan says:

    Finally something that I have more than enough experience to comment on. Your first example of corporate overlordship is a good one because it defines something else you don’t specifically mention: Spec Standards. At my company, we have Specs (we call them Use Cases) which follow their own standards to such a point where it’s almost ANOTHER programming language. I actually sometimes (as a developer) have a hard time reading the specs and need our BSA (person who writes the specs) to read it for me because there’s some obscure rarely-used logic to the spec notation.

    This leads us to writing SUMMARIES in plain English of our SPECS…wee paperwork….

    1. Robyrt says:

      I write those ridiculous use cases for a living and I’m sorry that they are so annoying to read. You’re quite right that this stuff is basically code, but the language it’s written in is legalese. The other guy’s Contracts team is looking through the specs, trying to find vague, undocumented or half-baked features, so they can get you to build more for the same amount of money. Your testing team is trying to write test cases, ideally automated ones which can expect the same output 100% of the time, so they need everything in exacting detail.

      Once we spent almost a week in meetings to determine what printing options would be supported. A one-line comment like “The Print button opens the system print options dialog, depending on the user’s current printer” is enough for a developer to understand what you mean, but when the customer submits a support ticket requesting that you add an image rotation feature because you said you supported printing in portrait or landscape, you will need those 3 pages of nonsense.

      1. DanMan says:

        We get those all the times. Customer opens a Production Defect saying that the system isn’t doing a thing that sounds completely reasonable. The first response is to pass it off to the BSA to comb the requirements.

        Technically, the system isn’t supposed to do anything NOT specified in the Use Case, so if it’s not in the Use Case, then it’s a New Business Request (read: pay us more money to do it). If it IS in the Use Case, then it’s a defect (read: we don’t get paid to fix it).

        And, of course, the BSAs get dinged for too many “missed requirement” NBRs and the Testers get dinged for too many “missed defects”, so then they fight back and forth over if it’s an NBR or a DEF…Mmmm…politics…

  2. arnsholt says:

    For point 4 and infinite recursion, detecting it shouldn’t be too hard. Since this is (presumably) data that’s loaded once at start-up, you can traverse the loot drop graph after loading all the data with a topological sort algorithm, ensuring that there are no cycles. Way simpler than detecting loops in the graph when generating loot.

    1. Ingvar says:

      Detecting during run-time “merely” requires passing around a set of all nodes you have visited so far and simply stop the current traversal whenever you see you’ve visited before.

      It does require to be thought of when you start writing the code and is greatly simplified by already having a decent set implementation hanging around, but can usually be faked by abusing a hash table.

    2. droid says:

      Even if there are loops, infinite recursion has zero probability if the total outgoing probabilities for each node are less than 1. So no loops would be more restrictive than absolutely necessary.

      1. Ayegill says:

        On the other hand, you probably do want to drop some things with probability 100%.

        1. Corpital says:

          I want a phoenix with 100% chance to drop itself after defeat.

          1. MichaelGC says:

            But only if you beat it with The Good Incinerator.

      2. guy says:

        It can still have a nonzero probability for any finite number of recursions. Which is effectively the same thing because it runs out of memory in finite time.

    3. Sord says:

      In the spirit of the original frame driven processing, I’d put the drops on a stack and on each frame process 1 (or N) of them. An infinite drop recursion would prevent the stack from shrinking, but the game would continue to run. To guard against exponential stack growth, I’d check the stack size when adding drops, logging an error at some small size and skip adding the drop at some much larger size.

  3. Zoe M. says:

    Ooh, fun! I’ve written systems like this before (and sometimes gone overboard and tried to make everything modular and interchangeable, complete with object inheritance and custom scripting code to execute).

    I’d say let a drop include the option of dropping an item chosen from a set. That lets you organize objects on hierarchies of rarity, giving (say) a miniboss a 1:100 chance of dropping a legendary weapon, which is then chosen from a set of legendaries based on the rarity and power of that weapon. Plus you can simplify the system and only define weapons in a single place (the containing set).

    I’d do this with a drop-wide “all or one” setting – if its a one-item set, add up the probabilities, take a random polling, and see which item you polled. If it’s a multi-item set, work as before.
    Also let drops drop other drops. Makes things simpler. (Though still limitedly recursive)

    1. AileTheAlien says:

      You could re-use the same logic, to have monsters dropped from pools. Anything really. Maybe the level-1 boss drops either X cash, or something from the fire-pool, where the fire-pool is either a fire-based weapon, a handful of fire-imps, or a fire-shield.

  4. MrGuy says:

    Obligatory’);DROP TABLE Robots;

  5. Duffy says:

    Boring?! This is the best part of software development! Solving the puzzles!

    I get bored about 40% of the way through coding a similar but different feature for the 173rd time. But figuring out a tricky problem or speccing a new feature is always a nice fun little puzzle to solve.

  6. alfa says:

    One final note is that this doesn't allow us to drop weapons from lists of possibilities, which is what vending machines use. Maybe we also need to add a systems of “weapon pools” of similar-powered weapons.

    Why not add logical hierarchies, i.e. <OR Chance=70 < AlienBlaster Chance=70 /> < BetterAlienBlaster Chance=30 /> />?

    This would also allow you to define certain sets of things that should drop together via an AND.

    1. Tuck says:

      What you’ve got there is invalid XML, but here’s a valid version of the same idea:


      <WeaponPool RandomTotal="100">
      <AlienBlaster Min="1" Max="69" />
      <SuperBlaster Min="70" Max="100" />
      </WeaponPool>

      Roll a random number from 1 to RandomTotal, and select the weapon dropped depending on which Min/Max it falls within.

      1. Lanthanide says:

        Allow a result for “no drop” and you’ve got an easy way to simply express a 1 in 10,000 chance:
        total = 10000
        awesome item = min=1, max = 1
        no drop, min = 2, max = 10000

        You could go further and just omit the ‘no drop’ line altogether.

  7. MrGuy says:

    Some thoughts….

    Rather than specify chance as a number for each drop, you might want to use an enum (e.g. “very rare”, “rare”, “uncommon”, “average”, “common”, “likely”, “very likely”, “certain”). The advantage of this is that it’s a LOT easier to tune a playtesting issue like “it seems like the drops are way too generous” if you didn’t have to twiddle 100 different numeric values one at a time. Might also consider this with the cash values.

    Separately, every drop could in theory happen on every event. Is that the way you want it to work? For example, a boss can drop a weapon AND money. A possible extension would be some kind of “wheel” system (at a level above the individual drops), where exactly one of N possible drops will be triggered.

    Example (angle brackets don’t seem to work easily in wordpress, nor does indenting…)

    [PhatLewt]
    [Imp Min=”0″ Max=”6″ Chance=”50″/]
    [Cash Min=”0″ Max=”20″ Chance=”100″/]
    [wheel]
    [AlienBlaster weight=”1″ chance=”100″/]
    [BFG9000 weight=”1″ chance=”100″/]
    [Cash weight=”8″ Min=”0″ Max=”500″ Chance=”100″/]
    [/wheel]
    [/PhatLewt]

    This would yield a 50% chance of imps, a 100% chance of a small amount of cash, and ONE of the (AlienBlaster, BFG9000,Big pile o’ cash) set. The “weight” param would only matter in a wheel, and determine how likely each option is (with these weights, 10% chance of blaster, 10% chance of BFG, 80% chance big cash pile).

    This might be useful in a situation where “I want to drop exactly 1 weapon, but I want it random which one gets dropped”

    1. Mephane says:

      I really think nested drop tables are the way to go, and I would generalize between the drop table and a wheel. Need not even be recursive, just allow a top-level drop table to contain drop tables as regular entries, but no deeper. Also, I would make it so


      <DropTable Name="Lvl1Boss" Max=2 Roll=10 >
      <Stack Archetype="Bomb_01" Min=1 Max=3 Weight=1 />
      <DropTable Weight=5 Roll="Auto" >
      <Stack Archetype="Laser_lvl10" />
      <Stack Archetype="Minigun_lvl10" />
      <Stack Archetype="Shield_lvl10" Weight=2 />
      <Stack Archetype="Nothing" />
      </DropTable>
      <Stack Archetype="Money" Min=100 Max=500 Weight=10 />
      </DropTable>

      This is how it would work at runtime:
      An enemy assigned this drop table dies. The drop table starts at the top element, rolls a number between 1 and the value of the “roll” attribute, and if the result is lower or equal the weight of the item, the drop table yields this item (or multiples according to the defined min and max of the item, which both default to 1).
      The drop table then proceeds to the next item, rolls again, compares the weight to the roll. It stops once the maximum number of drops has been met, or the end of the table is reached. The attribute “max” for drop tables defaults to 1.

      This gives us a few nice opportunities to play with. Drop tables are evaluated top to bottom. In my example, if the first roll yields a 1, 1-3 bombs drop. This consumes one of the “Max=2” possible items this table can produce. The next item is a nested drop table, which is then evaluated by the same rules that all drop tables follows, minus the option to contain another nested table.
      If both some bombs have been dropped and the nested drop table has produced something, then the outer drop table has reached its maximum and no money is dropped. A nested drop table may itself yield empty, depending on the roll and weight values of the table and its items; if weight of the inner table itself is not met, or none of its contained items is met, then no “Max=2” drop from the outer table is consumed. If the special “Nothing” element is met, however, this does consume a drop.
      If only bomb or a weapon, or neither, has dropped, the player is guaranteed some money as a consolation price. Jackpot is when no bomb drops, an item drops, and money drops.
      The inner drop table has the special attribute value “Roll=Auto”. This means it will automatically determine its Roll-value by the sum of all weights contained within its own entries, and it only rolls a single time, not each time for each entry. Since it contains 5 items, 3 with a weight defaulted to 1, 1 with an explicit weight of 2, the game rolls 1-5: 1 yields a laser, 2 yield a minigun, 3 and 4 yield a shield; a 5 yields nothing. The Archetype=”Nothing” entry is no accident, it is special value with weight and everything, when its value is rolled it yields nothing, but unlike a roll that does not meet any defined item, meeting an Archetype=”Nothing” item does consume a drop. Key to all of this is the top-to-bottom evaluation.

      You can of course also basic things like having a special rare drop and a common normal drop, like this 1% chance to drop a level 10 laser:

      <DropTable Name=”RareMeansRare” Roll=Auto>
      <Stack Archetype=”Laser_lvl10″ />
      <Stack Archetype=”Laser_lvl5″ Weight=99 />
      </DropTable>

      Insert this into another drop table and you have an entry that says “in this place, drop a level 5 laser, only very rarely a level 10 one”.

      Shamus’ idea of using two similar entries to emulate the effects of a 2d6 are possible in this system, too:

      <DropTable Name=”2d6_cash” Max=2 Roll=1>
      <Stack Archetype=”Money” Max=6 />
      <Stack Archetype=”Money” Max=6 />
      </DropTable>

      It rolls 1-1, which always meets the default weight=1, and will output at most 2 items, each of which are defined as 1-6 units of cash. :)

      Also, some of the options, especial the “Nothing” element, may seem redundant at first glance, as you could produce similar effects by different means. The following drop tables would evaluate to the same results, but they can express different designer intention, or just modes of thought (everyone of us has a different way to wrap their head around probabilities). Each of the following has a 1/3 chance to drop 1-100 units of cash.

      <DropTable Name=”A” Max=1 Roll=3>
      <Stack Archetype=”Money” Max=100 Weight=1/>
      </DropTable>

      <DropTable Name=”B” Max=1 Roll=Auto>
      <Stack Archetype=”Money” Max=100 />
      <Stack Archetype=”Nothing” Weight=2 />
      </DropTable>

      <DropTable Name=”C” Max=1 Roll=Auto>
      <Stack Archetype=”Nothing”/>
      <Stack Archetype=”Nothing”/>
      <Stack Archetype=”Money” Max=100 />
      </DropTable>

      Also, keep in mind, as I said above, yielding a “Nothing” result consumes a drop, but failing to meet a weight does not do so, therefore allowing to produce exceptions where a table mostly meant to drop a given number of items, may sometimes yield less. We can thus emulate a probability for a table’s own Max=N value:

      <DropTable Name=”1-3d6_cash” Max=3 Roll=100>
      <Stack Archetype=”Nothing” Weight=50 />
      <Stack Archetype=”Nothing” Weight=50 />
      <Stack Archetype=”Money” Max=6 Weight=100 />
      <Stack Archetype=”Money” Max=6 Weight=100 />
      <Stack Archetype=”Money” Max=6 Weight=100 />
      </DropTable>

      If the first “Nothing” weight is met, one of the Max=3 drops is consumed. If the second one is met, only a single drop remains, which is consumed by the first money drop already. So you can have 1d6, 2d6, or 3d6, and by adjusting the weight of the nothing element, you can directly control the probability for each number of dice, independently of the sizes of the dice.

      1. Mephane says:

        Addendum: To further illustrate the flexibility of this system, here is the first example, but changed so that when bombs drop, the chance of 3 is very low only:

        <DropTable Name=”Lvl1Boss” Max=2 Roll=10 >
        <DropTable Weight=1 Max=3 Roll=100>
        <Stack Archetype=”Bomb_01″ Weight=100 />
        <Stack Archetype=”Bomb_01″ Weight=50 />
        <Stack Archetype=”Bomb_01″ Weight=1 />
        </DropTable>
        <DropTable Weight=5 Roll=”Auto” >
        <Stack Archetype=”Laser_lvl10″ />
        <Stack Archetype=”Minigun_lvl10″ />
        <Stack Archetype=”Shield_lvl10″ Weight=2 />
        <Stack Archetype=”Nothing” />
        </DropTable>
        <Stack Archetype=”Money” Min=100 Max=500 Weight=10 />
        </DropTable>

        The inner drop table for the bombs always yields 1 bomb, with 50% a second bomb, with 1% another bomb (technically it can drop the third but not the second, but you get the idea). Like in the original example, it is the weight of the drop table for the bombs that determines whether bombs drop at all. And if 3 bombs drop, it still consumes only 1 drop of the outer table, as the inner table is only regarded as a binary “yields”/”no yield” situation (again, with the special “Nothing” element counting as a “yield”). If you want to have multiple bombs count as individual items, you justput them into the outer table as individual, separate items. :)

  8. Tim Keating says:

    Re: floating point — just because it’s WRITTEN as a decimal percentage doesn’t mean you have to STORE it as floating point. Personally, I’d probably parse it into an int value equating to parts per thousand or per ten thousand, depending on how granular you want to get.

    1. Shamus says:

      Yeah, but then I’d have to climb down into the XML reader and write my own “text float to int” parsing code, and that’s a lot of effort for a single special case.

      1. Dev Null says:

        As a rule of thumb, for probabilities in code I just work out the smallest probability that I _think_ I want (say, 1 in 1,000) and add a digit (1 in 10,000) because you will always later wish you could cut it in half, and then code in integer numbers of these. You never have to convert it to a float, because you just code your “die roller” to generate an int from 1 to DIE_SIZE and compare it to your integer value. Because yeah; I hate floating-point maths on computers. We’ve got a print billing system here at work that keeps trying to tell me that Bob from Accounting owes 1.100000007 dollars, which is just dumb.

        1. Treesong says:

          If I had a dollar for every time it was a good idea to store monetary values as floats, I’d have 0.00000000003 dollars.

          1. guy says:

            COBOL actually has a special data type for storing monetary amounts. Which is part of why people still use it for accounting systems.

          2. Bodyless says:

            You can totally use floats for monetary values, because rounding errors can be fixed.
            On the other hand, overflow exceptions cannot be fixed. For example when someone is trying to calculate a bigger monetary value in YEN.

            1. Richard says:

              And this is why people keep making the same mistake.

              Floats have a lot more ways of going wrong than you think.

              For example, the value “0.10” cannot be precisely stored in a float – or a double, or indeed any size floating point you care to choose!

              So paying in ten cents a few thousand times gives a different answer depending on whether you are working in floating point or fixed point arithmetic!

              That’s before you start considering what happens when you add a small number to a big number…

              1. Ingvar says:

                Or you hit the limit where the remaining precision in your floats are larger than the smallest monetary unit you want to handle. If you’re handling cents, that happens at around 2^46 dollars.

                If you instead decided to use the Maltese Lira (I think they use the Euro, now), you would hit the limit somewhere around 2^43 Maltese Lira (their smallest coin was the milli, thousand millis to the Lira).

                1. nm says:

                  Incidentally, since you mentioned source, here’s some:

                  #include <stdio.h>

                  int main(int argc, char *argv[])
                  {
                  float a = 9000000000.;
                  float b = 2.;

                  printf(“%fn”, a + b);

                  return 0;
                  }

            2. nm says:

              Fixing rounding errors implies detecting rounding errors, which is hard when you can’t store the number 1/10 precisely. Try using single precision floating point on a 32-bit computer to calculate 9 billion + 2. (hint: 8999999488.000000) The solution isn’t to bury your errors. The solution is to use larger, more precise types.

              Complaining that single precision floating point on 32-bit systems is a straw man? You bet it is. But then, so is a 32-bit integer. I can do 64-bit math on a 486, and the largest 63-bit number (don’t forget the sign bit) is 18,446,744,073,709,551,615. That’s 18 quintillion, which is more than the GDP of Earth in ZWD (about 8 quintillion).

              1. Bodyless says:

                I can only talk about .NET here, but it is definitely doable using its DOUBLE and DECIMAL types.
                But I doubt i am allowed to post the source code here, so the rest is left as an exercise for the reader.

                1. guy says:

                  You’re never going to be able to represent things to arbitrary precision with perfect accuracy. There are a finite number of possible representable values and an uncountably infinite number of points between zero and one.

                  1. Bodyless says:

                    I think you went a bit off topic there, i was talking about monatary values here and there are definitely a finite number of cents between 0 and 1 dollar :)

                    1. guy says:

                      Uh, firstly finance often does track fractions of a cent. It adds up. Secondly, if you’re using a generic floating-point data type instead of a specialized fixed-precision one, it will not be able to accurately represent all of your possible values. Additionally, when dealing with large numbers, precision errors will creep to the left of the decimal point. If you’d have overflow errors in dealing with Yen as an integer, a floating-point representation of dollars is going to have massive precision errors because one yen is about equal to one cent. The rate fluctuates, but that’s a good heuristic if you want a general impression of how much a price denominated in yen is (IIRC it’s about 80 yen to one dollar at the moment).

                      Your double type is going to be more precise than a float, but it’s still mapping an infinite space to a finite number of values, and the fact that you’re only concerned about a finite number of points in that infinite space does not mean that the data format will map the exact set of finite points you want. Additionally, a double takes up as much space in memory as a 64-bit integer.

                2. nm says:

                  DOUBLE, no. DECIMAL, yes. Don’t try to use floating point to represent money. That’s the short version of what I said above.

            3. Alexander The 1st says:

              That’s what longs are for.

    2. Mephane says:

      I’d abandon the notion 1.0 = 100% entirely. Just define an arbitrary value to roll, and which values yield which results. If the roll value is defined in the XML, designer can then pick a value that makes sense for their case, as opposed to cramming some values where they don’t fit. E.g. instead of having to assign 33%, 33% and 34% for 3 items meant to drop with equal probability in a system from 1-100, just let them define the roll as 1-3, and assign a weight of 1 to each item. :)

  9. MichaelGC says:

    Why is this post two gifs with no text?

    ;D

    1. Bubble181 says:

      I was just going to make the opposite remark. Both here and in previous Good Robot entries, Shamus has implied us being distracted by the moving gifs. While I appreciate them as looks at the game as a work in progress, they don’t distract for a second, and I hardly notice them unless I decide to look at the pretty pictures.
      Shamus, you underestimate the amount of advertisement we have to ignore on a daily basis; I’m by now completely programmed to read a text and not notice the flashy screaming colorful “attracting” images or videos surrounding it.

      1. Also, mouse scroll wheels. :)

  10. Tom H. says:

    (1) you seem to have lost track of “Specifically, we want to be able to ensure things like, “Player will be offered at least 1 half-decent weapon before reaching point N.”” Isn’t *that* the hard part? To some extent it’s going to imply inversion of control: rather than explicit spawns, you’ll be dropping “30% chance of a tier 1 weapon”, and then you’ll have a system to guarantee that at least 3 in 10 possible tier 1 weapon drops *actually* happens, as well as that at least 1 in 5 native tier 1 weapon drops is promoted to tier 2.

    (2) it doesn’t at all follow that XML is necessary here, although I guess if your choices are that and .ini files then XML is the better of the two.

    1. Zoe M. says:

      The easy approach is to have a drop set of ‘half decent weapons’ whose probability of dropping goes up over time and approaches 100% at point A.

    2. Darren Matthews says:

      I’d probably put in an incremental percentage value that gets multiplied by the number of previous drops that haven’t paid out. And zero out the previous drops number when you have a successful drop. I think this plays into having to classify the individual dropped items into categories and specify drops by categories rather than specific items.

      1. nm says:

        Make the gambler’s fallacy come true! That’ll screw with ’em.

    3. Primogenitor says:

      The other approach is to treat drops not as a percentage, but each source as a deck of cards – one way or the other, the ace will come up eventually….

    4. Viktor says:

      I’d just make a certain enemy that shows up in one specific room that’s guaranteed to drop a random decent weapon. If you stick it in a room with a bunch of the base random-loot versions of the enemy, players won’t notice that that’s why they always get a good drop in the first 2 levels. It’s a brute-force solution, but it solves the problem.

    5. Tektotherriggen says:

      This could be solved with logic (IF player has no good weapon THEN drop random good weapon). But then your loot table becomes its own programming language…

  11. swenson says:

    Technical specs can be annoying, but man, do they ever make your life easier when you get into something and realize it’s more complex than you initially thought. I was a fly-by-the-seat-of-my-pants sort of girl when I was in college and when I first started working, but I’ve been thoroughly converted. Seat-of-my-pants works fine when you’re making some minor change, but when you’re doing BIG stuff, complicated problems or dealing with different interacting systems or having multiple developers work on the same system at the same time… yeah, it falls apart pretty fast.

    Plus, when you’re like me and you’re making stuff for internal customers in your company, it’s reeeeeal nice six months down the road to be able to pull out the document that THEY approved and go, “Well, the reason I didn’t implement Feature X is that you jolly well didn’t ASK for Feature X!” And because they’re internal customers, I’m allowed to say that. :)

    (this week’s infuriating customer conversation: “But when I click the delete button, it DELETES THE DATA, leaving no record!” “Yes… yes that’s what you asked for… you said there was too much unnecessary data so you wanted to be able to clean it up…” “But it DELETES it!” Luckily they were appeased by adding dire warnings to the “are you sure you want to delete” dialog. I wanted to add a laughing skull on the side for extra warning-ness, but I managed to restrain myself.)

    1. WJS says:

      Ugh, that kind of bullshit is the worst. I recently saw someone had left a negative review of the Firefox NoScript addon that basically boiled down to “it stops scripts from working”. No shit. The fact that that will break some websites had apparently not occurred to them.

  12. Naota says:

    Fun fact: I read this spec here before I saw it in person among our other development files. Making games is weird sometimes.

    In other news, this looks very usable and I like the implications it has for rarer item drops – …I say as I realize the one filling out the game’s worth of tables will be me, probably in just a few hours.

  13. guy says:

    You know how I keep talking about planning things out in advance before coding them? This is that in action, and people are already suggesting changes to avoid technical issues or features you might want to add well before having a nice, entirely finished system you will need to knock holes in.

  14. RCN says:

    Your first weapon reference was “alien blaster” instead of “minigun” or “plasma rifle”.

    For shame, Shamus. For shame! :p

  15. Khizan says:

    You’ll definitely want weapon pools, IMO.

    It’s such a nice thing to be able to say “Okay, this enemy type has a chance of dropping a common laser weapon, and THIS type has a chance of dropping an uncommon projectile weapon” without having to go through and manually set the groups every time you want to do this. If you don’t implement it now, you’ll almost certainly do it later when you get tired of specifiying drops like this:

    [PhatLoot]
    [exclusive]
    commonBlastLaser=0.05
    commonBeamLaser=0.05
    commonBurstLaser=0.05
    commonSpreadLaser=0.05
    [/exclusive]
    [/PhatLoot]

    On literally every object that can drop a weapon.

    1. Decius says:

      That doesn’t even work. Four independent 5% chances is not the same thing as a 20% chance of one.

      1. guy says:

        Who says it’s supposed to be a 20% chance? I’m not sure what the net chance there actually is, which is part of the problem, and the exclusive block there presumably means there may be at most one drop so they aren’t independent. Which may mean they don’t have equal probability, depending on implementation (they could be rolled independently and have one of the successes selected randomly with probability based on relative chance, or they could be rolled in order until one succeeds, for instance).

        1. WJS says:

          The odds there are actually about 18.5%. Some “intuitive” probability errors are actually not that far off.

  16. Primogenitor says:

    I can’t tell if your abusing XML for simplicity of explanantion, or you are genuinely using it that way? Names should NOT be element types, you should have an attribute name=”fat loot” instead.

    Sorry, but this irritates my inner programmer like writing “Pi is exactly 3” in front of a mathematician.

    1. Dirk Destiny says:

      That was my immediate response too. To make an XML vocabulary scalable it makes sense to use the element names (structure) for class information, and attributes for instance values.

      However, the virtue of XML is its simplicity, so if this usage doesn’t introduce any ambiguity and will never become more complex, then why not? What’s abuse in one context can be simplicity in another.

    2. Syal says:

      The .14 is silent, everybody knows that.

      1. Decius says:

        as is the .00159….

    3. Shamus says:

      I assume the correct format would be something like:

      {item name=”PhatLewt” /}

      instead?

      Which means you’re going to have all of these “item” entries where the word “item” is completely superfluous. Like, items are the only thing that can go there. This contributes to the ongoing verbosity of XML. It doesn’t do anything to help artists maintain the files.

      1. guy says:

        I’d expect “weapondrop name=’phat loot'”. That would be useful for stuff like a special ability that boosts weapon drop rates. Probably the most maintainable way of adding that.

        Of course, that isn’t in the spec.

      2. sheer_falacy says:

        {weapon name=”alienblaster/}
        {robot name=”imp”/}
        {cash/}

        Or even {currency name=”cash”/} in case you want to add other currencies in the future.

        This is part of the value of XSDs – they can be annoying to write and excessively picky, but the version you include in the post would require a new schema every time you added a weapon or monster to the game, while using a more general form wouldn’t. Also it lets all of the enemy and weapon tags share the same allowed sub attributes rather than needing to duplicate it every time. And whatever library you’re using for reading XML is going to be really good at getting a list of nodes and make it a bit harder to iterate over all nodes.

        Another way to think about it – do you have a class (or struct or whatever) for every robot and weapon type? I imagine you have a robot struct with a name property. Why should your xml be different?

        Also incidentally it’d make it possible to have weapons and enemies with the same name, which someone might do accidentally (unless you prevent that somehow). For that matter, how do you know if something refers to a weapon, a robot, or cash? What if someone names a robot cash because it reminds them of the singer?

        1. Ant says:

          Another way to look at it : in C#, i prefer that the class which will interpret the xml for the program to be able to generate it. So my version would look something like that :
          {Loots}
          {Loot name=”ImportantLoot”}
          {Robots}
          {Robot}
          {Name}”Louis”{/Name}
          {Number}3{/Number}
          {Probability}50{/Probability}
          {GroupLoot}1{/GroupLoot}
          {/Robot}
          {Robot}
          {Name}”John the mighty Archidemon”{/Name}
          {Number}1{/Number}
          {Probability}20{/Probability}
          {GroupLoot}2{/GroupLoot}
          {/Robot}
          {Robot}
          {Name}”Patrick the medium Archidemon”{/Name}
          {Number}1{/Number}
          {Probability}10{/Probability}
          {GroupLoot}2{/GroupLoot}
          {/Robot}
          {/Robots}
          {Weapons}
          {Weapon}
          {type}”Laser”{/type}
          {power}
          {min}2{/min}
          {max}5{/max}
          {/power}
          {spread}3.5{/spread}
          {Probability}30{/Probability}
          {GroupLoot}3{/GroupLoot}
          {/Weapon}
          {/Weapons}
          {Money}
          {min}300{/min}
          {max}300{/max}
          {Probability}30{/Probability}
          {GroupLoot}3{/GroupLoot}
          {/Money}
          {Loots}
          {Loot}
          {name=”MediumLoot”/}
          {number=”2″}
          {Probability}30{/Probability}
          {GroupLoot}3{/GroupLoot}
          {/Loot}
          {/Loots}
          {/Loot}
          {/Loots}

          I add a new property, GroupLoot, to know if the loots are exclusive or not. In this example, you can have Louis and John the mighty Archidemon gang up on you, but not John the mighty Archidemon and Patrick the medium Archidemon.

          The Loot class is quite easy to parse :
          an array of class weaponLoot (properties enum type, power power, double Spread, int Probability, int GroupLoot).
          an array of robotsDropped
          a class of MoneyDropped
          an array of LootDropped (not the same class as Loot because you need the probability, number and group loot property)

          I recommand making an interface/Abstract class IDrop, which implement the properties Probability, number and GroupLoot.

          1. WJS says:

            Good god, that’s hard to parse without indentation. Seriously, I can barely make it out at all.

      3. nm says:

        I know I’m really late to this, but I’m hoping you still read comments on old-ish posts as they come in. XML is awful to write by hand, so don’t require your artists to do it. Make a nice little editor tool that knows the schema and what kinds of things can go in what kinds of fields. Have it validate its input so that when your artists make typos there’s a better chance they get caught, and then make it spit out XML in all its verbosity.

        In fact, you don’t have to write the little editor program yourself. There are already programs out there that will take a schema and auto-generate generators. I can’t recommend any because I don’t really do XML-y work, but Google ought to be able to.

  17. Decius says:

    I think that you absolutely need a way to specify “Exactly one of the following:”
    The golden example will be “We need this boss to drop one level 5-10 weapon.”

    Then you create a table called Weapon_Tier2, that has a row for each weapon in that level range, and a row (with a small weight) for Weapon_Tier3.

    (That's also why you need to have drop tables recurse, so that you can create suitably trivial chances for something to drop well outside of its weight class.)

    It also expands well for when the sequel or expansion adds things other than weapons to the drop table; you just add Equipment_Tier2, which weights between Weapon_Tier2, Engine_Tier2, and Shield_Tier2.

    1. guy says:

      Actually, point four can be used to do that. Be a bit clunky, though.

      1. Decius says:

        How would you use that to produce always exactly one item from the list, rather than merely on average one?

  18. 1) These posts are my absolute favourite thing on the site. I’ve been reading for…wow, for over 6 years now. I don’t comment much, but I just had to point out what a fan I am of your posts about programming.

    2) I’m not a programmer, but just as you removed “min” and “max” when it’s exactly one, wouldn’t it also be cleaner to remove “chance” if it’s definitely going to drop?

  19. Da Mage says:

    Cant….take….eyes….away….from….gifs….

  20. HiEv says:

    I’d be tempted to have something like a list of items and you can guarantee one (or more) items from that list would drop. For example:

    {WeaponReward}
    {List Drops=”1″ Chance=”100″}
    {LaserBlaster}
    {MissleLauncher}
    {LightningShield}
    {/List}
    {Cash Min=”0″ Max=”100″ Chance=”100″/}
    {/WeaponReward}

    That would give you a 100% chance of getting only one of those three weapons, plus 0-100 cash.

  21. Jsor says:

    Tying swan song attacks like projectiles into the drop system sounds a little hacky. Something that works really well until something terrible you didn’t think of comes up a couple months later. Or if you decide to change the drop system later, it may break swan songs (e.g. if you decide for balance reasons enemies can only drop one thing, suddenly it can’t fire missiles on death anymore without refactoring).

    But it’s game dev, sometimes these things happen (and I know it was just a thought and not a done deal in the proposal).

  22. kdansky says:

    If you like XML, use JSON. It’s the same thing for 99% of all applications, but just less verbose.

    1. nm says:

      JSON is so much harder to parse in a language like C++ that it may as well be impossible. Or so I’ve been told by people trying to do it.

      1. Ant says:

        In C#, java and probably C++, you should not parse XML, html or JSON by yourself. There are good libraries for that which allow you to write a code which will be more readable, with fewer bug and probably faster than the hack that an average programmer will do in a few hours.

  23. WJS says:

    Wow, I would have figured you’d have better understanding of chance, Shamus. It doesn’t matter whether you roll chance or number first for your imps, because it’s commutative. (If you roll number first, you can still roll 0)
    Also, 2d6 isn’t a bell curve, it’s triangular :P

    And man, reading this now is uncanny. I’m screwing around with modding Oblivion, and have just been working heavily with the levelled lists. (And really missing the “drop everything” flag from FO3, btw)

Thanks for joining the discussion. Be nice, don't post angry, and enjoy yourself. This is supposed to be fun. Your email address will not be published. Required fields are marked*

You can enclose spoilers in <strike> tags like so:
<strike>Darth Vader is Luke's father!</strike>

You can make things italics like this:
Can you imagine having Darth Vader as your <i>father</i>?

You can make things bold like this:
I'm <b>very</b> glad Darth Vader isn't my father.

You can make links like this:
I'm reading about <a href="http://en.wikipedia.org/wiki/Darth_Vader">Darth Vader</a> on Wikipedia!

You can quote someone like this:
Darth Vader said <blockquote>Luke, I am your father.</blockquote>

Leave a Reply to Jsor Cancel reply

Your email address will not be published.