Fenrir wrote:
Could you give one or two examples of this
"content/functionality idea --> implementation
idea" part?
F'r example, here are some gameplay ideas.
One is that some items buff with use, to some extent the same way that characters buff with experience. So, when the character takes experience, the items he or she is using may also take some experience.
Another gameplay idea is that the bonuses, exact range and damage, etc, of particular items should be variable, so that it's possible to find a fire wand that's better (or better for particular uses) than the fire wand you have now, or organize your tactics around the peculiarities of equipment you happen to have now.
Another idea is that "staves" should affect spellcasting, buffing your spells, or maybe particular classes of spells, by making them some combination of easier, faster, longer-range, higher damage, lower mana cost, etc. So your staff isn't a charged item, it's a weapon that you wield while spellcasting in the same way that a sword is a weapon you wield while slicing. And it affects what your spells do, in a way comparable to the way your sword affects what your physical attacks do.
Another idea, is that generally items will have many different uses, and may have disparate power levels in buffs for those uses.
Another idea, this time for a particular item, is that there's a Rarity, a sword named Famine, which is more powerful the more you are hungry (but this is self-limiting, because killing things with it - even things you couldn't normally eat -- nourishes you).
Another idea is that there's a type of object which is enchanted specially against particular species or types of creatures. Examples include a crooked arrow of wumpus slaying, a sword of dragon slaying, etc.
And a final gameplay idea - though it's really more an interface idea - is cross-system, mod-safe, and version-safe save and restore. To the extent possible, installations of the game on different operating systems, installations of locally modded games, and installations of later versions of the game, should understand the savefiles left behind by versions found on all systems, by earlier (or all) versions, and/or by unmodded versions of the game.
Okay, now holding all these ideas in your mind, consider the question: how to implement and represent magical (or normal) items and their properties.
When I'm brainstorming ideas for how to store item bonuses and properties, therefore, I reject out of hand the "rogue" idea that item bonuses and properties are fixed by type and that the appropriate representation is just a reference to the item type, because it doesn't account for individual objects buffing, nor for variability within type.
I consider the pval business from Angband, where an item is buffed by incrementing its Pval number - and the meaning of the buff is a function of item type and pval. At first this doesn't seem like it would work, because it only stores one number. If I want *this* fire wand to have longer range but lower damage than some other fire wand, but the fire wand always interprets pval the same way, then one number for range and one number for damage can't both be the pval. On the other hand, it seems to work okay if I make "foo of range" fireball wands separate from normal fireball wands. And with large multiplication of item types to cover each combination of disparate power-levels in buffs, this covers most cases. But due to the diversity among staves (diverse staves X diverse affected spells = lots of cases) in particular and other weapons somewhat, it requires an astounding multiplication in item types. And it doesn't cover all the cases, because Famine in particular and foo-slaying weapons in general need to refer to their wielder or target, respectively, to figure out just how awesome they are on a particular swing. So, I rejected it.
Angband modified this idea for the benefit of foo-slaying weapons by devoting bits in each creature representation to say which sort of foo it is (of the very limited number of general groups that foo-slaying weapons in Angband target), but I don't think that's general enough, and it still doesn't account for the specific wielder-hunger query of Famine. So, I reject that too.
I consider the idea of having a separate pval'ish enchant value for each of several attributes - to-hit, to-damage, to-defend, etc, recorded on each item - along with a number for antagonist-type to determine the species or class that this is a bane against. But then I realize that with possibly several different enchants on a given item, this too is a non-starter. While it abstracts the reference to the target and returns enough info for the foo-banes, it still can't resolve that the user is hungry enough to unlock Famine's totally-awesome-hits-of-over-the-topness. And it's ridiculous to have code in your hit routine to check the hunger level of the wielder when 99.999% of all items won't use it and Famine is a Rarity anyway so it won't even appear in most games.
Hmmm, code. Here comes another implementation approach. The code that checks the hunger level of the wielder .... It has to "belong" to Famine, doesn't it? Nothing else uses a check like that. It is what OO aficionados call a "member function" of the object, right? And the code that checks the species, class, type, etc of the target has to belong to (but can be shared by all) foo-banes. So, if there's code associated with particular items, when and how does it get called?
And then I'm back to the idea of "Events." Each event has actor roles, including four corresponding to subject/attacker, agent/weapon, agency/ammo, object/target. What if there's a moment when the event-processing code looks at each actor for "methods" corresponding to the current event-type and the current role of the actor within that event, and calls them if they exist? The argument given that code, under the assumption that it needs to key on some combination of properties of the actors in that event, would be a reference to the event itself, where it can see references to all the actors. There's a practical problem with this, which is the sheer number of "methods" involved. Every combination of event and role within that event would be many thousands of function prototypes, most never overridden, so it would be really ugly in C++ or one of the obligate-OO languages where you have to have a prototype for each overridable method. But I'm writing in C not C++, so I'm free to use a Data-Directed Design instead of an Object-Oriented Design. if I take a data-directed approach, I can query a hash table for a code pointer, returning NULL if there's no such method, and then check for and just not call NULL, so I never have to write a prototype at all - only the specializations.
So.... each item has a hash-table of attributes. If there aren't very many, it won't waste the space for very many. NULL return is always an option, so there's no need to store values for attributes that don't exist. Some attributes have code-pointer values, and the keys of those attributes are "Triggers" that the event processing code will compute from item type and role, then query for, and if non-null, call. Famine can check the hunger level of the wielder, Foo-slaying swords can check to see if the target is a foo, and no event whose actors are uninterested in the results of those checks will ever cause such code to be called. The code can express its results by writing back into particular fields of the "event", changing, for example, hit probabilities, range, damage levels, costs, etc before the event gets executed. Number of possible attributes can increase without limit, and each item uses space proportional to the attributes it actually has. And items can have an "experience" attribute that keeps track of their level of buffing. In fact, it can be the same "experience" attribute that monsters have, and an add-experience event can call the same incrementing/buffing/leveling code if the items, like creatures, have the takes-experience attribute set to that code pointer. Meanwhile, this leaves room for both "ordinary" items and creatures that take no experience (undead, golems, etc, just don't have a takes-experience attrbute).
I check this last approach against the ideas I have for items, and find that it works. It covers everything I want to do, several things I hadn't thought of under the heading of "items" before, and sometimes with mild abuse, everything I can imagine wanting to do with representing the special properties of items and monsters in my game.
But when I check it against other ideas, I reject it because it violates save-and-restore. The same code pointer value won't mean the same thing in the restored game that it meant in the saved game, because there's little chance of the same routines being stored at the same addresses. But it's close. Can I make it pass save-and-restore by adding an indirection to it, where instead of code pointers I have an index number that I use to access one from an array of code pointers? The index numbers would refer to the same code pointer, across different operating systems, installations, etc.... but this still violates version-proof save-and-restore because later versions of the game will add some code pointers and rearrange the numbers that refer to them.
So, this new approach still doesn't pass version-proof save-and-restore either, but it's very close. Okay, I say, make the numbers into named constants, and save the symbolic names in the savefile rather than the number. Does it pass version-proof save-and-restore now? At first I thought so. But I was wrong. I realized I was wrong when I added another actor reference to the structure of the event. I had to change the code that figures out what key to associate with each software method stored in objects. I was clever enough to make the code indices version-stable, but I hadn't made the keys under which they were stored version-stable. So this still violated version-proof save and restore, and I had to fix it.
One more revision to the basic idea, then. The keys that the code triggers are stored under are also saved as symbolic names in the savefile, and these names will also mean the same thing across different versions, even if it doesn't happen to mean the same index number.
So, after rejecting several approaches, I hit on one that was "close" in terms of enabling all the gameplay ideas I had for items. But it still failed save/restore. With a few more refinements it passed save/restore, but if I hadn't found the appropriate refinements, I'd have had to reject it too.
So, after writing most of a book chapter.... It's not really the case that a gameplay idea refines itself into a design idea. What it comes down to is brainstorming design ideas, then *REJECTING* design ideas that don't allow your gameplay ideas to be expressed. Sometimes you can rehabilitate an implementation idea by a refinement that permits it to express a gameplay idea that it failed in its initial form, but sometimes, if you can't see this problem coming in the design phase, you have to scrap something you've already implemented and redesign that subsystem (which in the worst case involves redesigning other subsystems too). Or, the alternative, which is scrapping a gameplay idea (Not a bad thing if implementing it really is intractable or not worth the effort; is Famine an "unreasonable" item? Could you implement it in your design? I could have gone with a simpler idea if I'd chucked it. )