donky
Conjurer

Group: Members
Posts: 230
Joined: Jul 16, 2009
|
#151 id:43913 Posted Mar 15, 2010, 6:43 pm
|
Deimos said:It's not that they are tangential. I don't really mind that, given that most of what's being discussed in this thread has started as tangential at some point or another (I mean, what does any of this have to do with programming languages anyway?).
What I don't like is this attitude of "I'm only going to answer direct, specific questions and nothing more." This isn't the best medium for Q&A. It's simply too frustrating for that kind of back and forth banter. We come here to share elaborate ideas, descriptions, and examples that, more often than not, take a lot of time to construct.
What I didn't say, is that this system is in use in a commercial game and I signed an NDA while working on it. So while it isn't necessarily wrong for me to go into detail about it, if I reveal bits as asked specifically about them, then it is easier for me to limit and carefully consider what I disclose.
Deimos said:Now, you claim to have solved some of the problems we were discussing (whether they're tangential or not), and I'd personally like to know how. If you don't want to explain yourself and help us (me) to understand how exactly you solved them, that's certainly your prerogative, but it isn't going to help anything or anyone for us to continue down this path without that clarification.
I am happy to give you clarification, but you're going to have to get it out of me question by question, in the same manner you did before. Although whether you consider it worth your time and effort, is another matter.
|
|
......................... Ding a ding ding dang doo. Or something.
|
|
David Haley
Wizard


Group: Members
Posts: 6,874
Joined: Jun 30, 2007
|
#152 id:43914 Posted Mar 15, 2010, 6:44 pm
|
donky said:What I didn't say, is that this system is in use in a commercial game and I signed an NDA while working on it. So while it isn't necessarily wrong for me to go into detail about it, if I reveal bits as asked specifically about them, then it is easier for me to limit and carefully consider what I disclose.
Well, that's certainly useful information to know about why you were responding the way you were.
|
|
|
Deimos
Conjurer


Group: Members
Posts: 220
Joined: Mar 8, 2010
|
#153 id:43915 Posted Mar 15, 2010, 6:58 pm
|
shasarak said:
You're thinking like a C++ programmer.
Hah! Am I really that transparent?
shasarak said:
Depending on how much late-binding you are comfortable with, you could have as little as one Effect class that handles absolutely every effect in the game. Instead of hard-coding an Effect subclass which achieves each effect, you simply intialise it with a block from the source spell or item, and that block is evaluated by the proxy to determine what the effect does.
(I'm using the word "block" in its Smalltalk sense, here - I can't for the moment remember what the Ruby equivalent is called; the equivalent in C# would be an anoymous delegate).
Hrm. Well, I don't necessarily have a problem with late binding, but it would seem that this kind of system would (could) produce repeated code blocks all across your spells/items/whatever, and break encapsulation by having an effect block tied to its source like that. Still, I like it more than any other method so far. Also, I think the Ruby equivalent is also called a block. I only read about them in passing, though, so I could be wrong.
|
|
......................... Any sufficiently advanced magic will run circles around technology.
|
|
Runter
Wizard


Group: Members
Posts: 1,850
Joined: Jun 1, 2006
|
#154 id:43917 Posted Mar 15, 2010, 7:23 pm
|
Quote:I can't for the moment remember what the Ruby equivalent is called
It's also called block.
|
......................... CoralMud project
For once you have tasted flight Ruby you will walk the earth with your eyes turned skywards,
for there you have been and there you will long to return. --
Leonardo Da Vinci Yukihiro Matsumoto
|
|
donky
Conjurer

Group: Members
Posts: 230
Joined: Jul 16, 2009
|
#155 id:43920 Posted Mar 15, 2010, 9:36 pm
|
shasarak said:David Haley said:Less facetiously, your suggestion might not sit will with Shasarak, because to implement your proposal one needs to know an awful lot again about what one can modify, which is exactly what he has been trying to avoid. So your suggestion would sit well in my camp, but really doesn't address Shasarak's goals here. I'm still curious to know how he'd handle all this in practice.
I don't feel I have a sufficiently good understanding of what donky is suggesting to be able to comment, really. If he would like to help me out by elaborating somewhat, I will try to produce an intelligent response; but I am not demanding that he do so.

I will try and describe it without describing the actual implementation of the existing system in too much detail. But given how many times I have tried to do this so far, it looks like I am going to have to give some degree of insight into implementation.
As I have said, it is data-driven. Everything is referred to in terms of abstraction, by ID. An expression uses attributes from given entities (the actor, the entity the effect is authored on, the target of the action, ..), and applies them to attributes from other entities. It is possible to say, is this attribute on this object currently being used in the calculation of something? And if it is, to cascade changes in that attribute, to anything that is dependent on it. And attributes may be more than just simple values, for instance you might not need to increase the character's "hp" attribute, the "hp" would instead be defined as a type of 'curve'. Whenever its value is queried, the value at the current position of the curve for the current time is returned. If the value changes, perhaps an amount of damage applied, then the curve is recalculated. A curve might be defined by a rate of change and a maximum value, but not with the actual values for these things. Rather, with what attributes to lookup values for. In this way, almost anything can be modified by anything else, simply because only attributes have values, and everything else works in terms of attributes. Stacking is defined on a per-attribute basis as well, so anything can modify any attribute within a reachable scope, but how the attribute is modified is up to that attribute. Additionally, you do not go to the instance of a class to get attribute values, you go to a generic decoupled handler which encompasses all this state and behaviour. In fact, you can do away with classes and instances for both specific effects, item types and more.
Now a concrete case, to hopefully give some context. If a player has a sword in their inventory, then this is an item of type sword located in the player's character. The sword type would a swing action associated with it, this would be linked to a swing effect. An effect defines how expressions are applied by items. It would define how many times the expressions are applied, and how long each application lasts. So for a sword, you would swing and it would by default have a "how many" of 1 and perhaps a "how long" of 1 tick. But it wouldn't have these values on the effect, it would lookup attributes on the item the expression was operating on behalf of (in this case the sword). Then the swing expression attached to the effect would check if the swing hit (referring to attributes providing values used in the calculation), and if it did it would apply damage (referring to attributes providing values used in the calculation).
Any of these attributes used at the effect level, or the expression level, can be modified within reason by almost anything else. Maybe the wielder of the sword has modifiers from his sword wielding skill. Maybe he also has a per-tick decrease to one or more of them from a cursed potion he just drank. Maybe his opponent is so fierce that anyone they engage who has heard of them, gets a percentage decrease to a range of attributes.
Now, this is a general description of the heart of the system I worked on. I have left out a range of particulars about how exactly it worked, so it also contains flexibilities which I have not described. But this system handles multiple concurrent effects being applied to something, when each of those effects is being effected by other effects and so forth. You can have periodic decreases or increases working together on an attribute with other effects having different effects. And it does not need to be coupled to code, you don't need to limit or shape your effect system to fit how you can make classes or use inheritance, or whatever the limitations of the programming language you might be using are. This system could be implemented in any language, in much the same way.
And the advantage of being data-driven is that you have the ability to introspect and manipulate that data. Want to see what effects are not attached to any item type? This is a simple SQL query. Want to see what expressions are not used by any effects? This is a simple SQL query. Want to see what attributes are not being used at all? This is a simple SQL query. You can generate reports for these things. Much more interesting is just being able to drill down through the data. What item types are attached to effects that are marked as offensive? What are the different things that use this attribute? What are the range of values for this attribute on this subset of types? Edit the attribute values with a grid to make it easier to get an idea of how balanced things are. Something wrong with the data? Run a query to fix it up. Is the handler running a little slow? Add code generation.
|
|
......................... Ding a ding ding dang doo. Or something.
|
|
shasarak
Sorcerer


Group: Members
Posts: 421
Joined: Nov 28, 2007
|
#156 id:43956 Posted Mar 16, 2010, 9:07 am
|
Deimos said:shasarak said:
Depending on how much late-binding you are comfortable with, you could have as little as one Effect class that handles absolutely every effect in the game. Instead of hard-coding an Effect subclass which achieves each effect, you simply intialise it with a block from the source spell or item, and that block is evaluated by the proxy to determine what the effect does.
(I'm using the word "block" in its Smalltalk sense, here - I can't for the moment remember what the Ruby equivalent is called; the equivalent in C# would be an anoymous delegate).
Hrm. Well, I don't necessarily have a problem with late binding, but it would seem that this kind of system would (could) produce repeated code blocks all across your spells/items/whatever, and break encapsulation by having an effect block tied to its source like that. Still, I like it more than any other method so far. Also, I think the Ruby equivalent is also called a block. I only read about them in passing, though, so I could be wrong.
There's no obvious need for repetition. If the same class of spell is cast twice and thus creates two different effects, each of which is initialised using a block, that block is defined only once (within the class definition of the spell), so each effect ends up with a reference to what is actually the same block object (albeit potentially with a different execution context). If two or more related spells pass the same block to an effect, the block could perhaps be returned from a method defined on something that is a common superclass to both spell classes.
In cases where you want to do something a little more complicated, it might make sense to have some of the effect logic defined on an Effect subclass; but the logic could easily involve parameters that can be set differently on different instances of the same effect class.
Suppose we have an effect which boosts a character's strength by an amount equal to a percentage of his intelligence. We could define the #strength method on the effect like this:
Code (text): 1
2
3
4
5 | strength: baseStrength
^baseStrength + (self effectTarget intelligence * self effectPercentage / 100) |
You might then instantiate the effect in question like this:
Code (text): 1
2
3
4
5
6
7
8
9 | castOn: target
| strengthEffect |
strengthEffect := StrengthIntelligenceLinkage new.
strengthEffect effectPercentage: 50.
target addEffect: strengthEffect |
So here we've got an effect which is hard-coded to connect strength to intelligence, but by a configurable amount. (Note that the effect's effectTarget property is set by the proxy as part of the addEffect: process).
Alternatively, you could inline absolutely everything. Go back to the generic Effect which takes a block as its action:
Code (text): 1
2
3
4
5
6
7
8
9
10
11 |
castOn: target
| strengthEffect |
strengthEffect := Effect new.
strengthEffect overrides: #strength.
strengthEffect action:
[:baseStrength | baseStrength + (target intelligence * 0.5)] |
Here you'll note that the block being passed into the effect object contains a reference to the target variable - something that is in scope within the context of the spell's castOn: method, not a variable that the effect class itself is aware of. But the block executes happily because it remembers what context it was defined in.
Either one of these approaches might be equally valid - it depends how often an effect of a particular degree of specificity is used. If spells which boost strength by a percentage of intelligence are very common, differing only in magnitude, then it might make sense to have a dedicated Effect subclass for handling them; if not, it may be quicker to inline it. More complicated behavour is probably best handled by an Effect subclass with appropriate code and properties, but even then different instances of the class can have their properties initialised differently. There's no inherent need for code duplication.
There's no encapsulation issue, either. If you think about what a spell actually is, it's simply a way for a player to apply an effect to a target. In order to do that, clearly the spell has to know which effect it is supposed to apply; telling it that doesn't break encapsulation, it's information that it needs to have to do its job.
|
|
......................... Hand over the chocolate and nobody gets hurt.
Last edited Mar 16, 2010, 9:08 am by shasarak
|
|
Deimos
Conjurer


Group: Members
Posts: 220
Joined: Mar 8, 2010
|
#157 id:43957 Posted Mar 16, 2010, 10:00 am
|
shasarak said:
Here you'll note that the block being passed into the effect object contains a reference to the target variable - something that is in scope within the context of the spell's castOn: method, not a variable that the effect class itself is aware of. But the block executes happily because it remembers what context it was defined in.
I don't quite understand how two similar effects could override the same superclass method, though. Using the same example we've been talking about, both strength-modifying effects would need to override #strength behavior with different blocks, yes? But how would this happen? Are you planning on doing some kind of block chaining or something?
shasarak said:
If two or more related spells pass the same block to an effect, the block could perhaps be returned from a method defined on something that is a common superclass to both spell classes.
[...]
There's no encapsulation issue, either. If you think about what a spell actually is, it's simply a way for a player to apply an effect to a target. In order to do that, clearly the spell has to know which effect it is supposed to apply; telling it that doesn't break encapsulation, it's information that it needs to have to do its job.
I see what you're saying, but I was looking at effects in a much broader sense. If other things in the game (weapons, potions, special attacks, etc.) could cause the same effects as spells do (which is fairly common practice), and you're keeping these blocks in the spell classes/superclasses, then my Sword of Weakening needs to know about the StrengthReducingSpell class in order to tell the effect how to function, right?
|
|
......................... Any sufficiently advanced magic will run circles around technology.
|
|
Runter
Wizard


Group: Members
Posts: 1,850
Joined: Jun 1, 2006
|
#158 id:43958 Posted Mar 16, 2010, 10:02 am
|
Runter said:Quote:I can't for the moment remember what the Ruby equivalent is called
It's also called block.
But I think the specific term in Ruby when using it like this is a closure.
|
......................... CoralMud project
For once you have tasted flight Ruby you will walk the earth with your eyes turned skywards,
for there you have been and there you will long to return. --
Leonardo Da Vinci Yukihiro Matsumoto
|
|
shasarak
Sorcerer


Group: Members
Posts: 421
Joined: Nov 28, 2007
|
#159 id:43986 Posted Mar 16, 2010, 2:01 pm
|
Deimos said:I don't quite understand how two similar effects could override the same superclass method, though.
I got a little careless about my use of the word "override" there. In that specific context I was using it in the sense of a subclass implementing a method which therefore "overrides" the equivalent method on its superclass, not in the sense of an effect overriding the default method implementation on its target.
Deimos said:Using the same example we've been talking about, both strength-modifying effects would need to override #strength behavior with different blocks, yes? But how would this happen? Are you planning on doing some kind of block chaining or something?
Well, you don't have to use blocks if you don't want to! You could have a type of Effect where what it does is defined simply as "execute your effectBlock". Or you could have a type of effect where what it does is defined in terms of one or more permanent methods on the corresponding effect class. Or you could mix and match in any way that's useful.
In the context of discussing more than one class of effect inheriting from a common superclass, we're probably talking about the effect's action being a defined method rather than a passed-in block.
Deimos said:shasarak said:There's no encapsulation issue, either. If you think about what a spell actually is, it's simply a way for a player to apply an effect to a target. In order to do that, clearly the spell has to know which effect it is supposed to apply; telling it that doesn't break encapsulation, it's information that it needs to have to do its job.
I see what you're saying, but I was looking at effects in a much broader sense. If other things in the game (weapons, potions, special attacks, etc.) could cause the same effects as spells do (which is fairly common practice), and you're keeping these blocks in the spell classes/superclasses, then my Sword of Weakening needs to know about the StrengthReducingSpell class in order to tell the effect how to function, right?
Well, that might be a case where it is helpful for the effect class to use a method rather than a block to determine its action. So you have a StrengthReducingEffect class, an instance of which is created and activated by both the sword and the spell. The StrengthReducingEffect would most likely have at least one property which you set on it to tell it how much to reduce strength by. Depending on how you wanted to do it, you could have different effect classes for handling immediate and per-tick reductions, or you could handle both on the same class, and give it separate immediateReduction and perTickReduction properties.
|
|
......................... Hand over the chocolate and nobody gets hurt.
|
|