Mob Taxonomy
Goto page Previous  1, 2, 3  Next
 
Post new topic   Reply to topic    mudlab.org Forum Index -> Design
View previous topic :: View next topic  
Author Message
Vopisk



Joined: 22 Aug 2005
Posts: 99
Location: Golden Valley, Arizona, USA

PostPosted: Wed May 03, 2006 11:54 am    Post subject: Reply with quote

shasarak wrote:
Kjartan wrote:
shasarak wrote:
Where this gets tricky is that a system like this not only has to be multi-dimensional (something can be both a demon and spider) it would also have to be dynamic.

It is probably ok to have for each being a base type and a bunch of templates, where the list of templates is dynamic (just a linked list of pointers to template objects) and where you simply apply the patches from each template whenever anyone asks a question about the critter. For instance, whenever any creature moves, you can check if it's got the "damaged by sunlight" trait by checking its base type and then traversing its list of templates, and if so then check if the new position is sunlit, and if so take the appropriate action. This sounds inefficient, but really a bunch of walks of shortish linked lists (I doubt anybody will be walking around with more than four or five templates) is not going to be a big cycle hog.

No, that doesn't work. Well, okay, it does work Smile but it's not a nice way for it to work.

The problem with doing it this way is that every single effect that might be influenced by the presence of templates has to be coded in a way that anticipates the possibility of a template affecting the outcome. In your example you've got code that determines whether or not a creature is damaged by sunlight; that code has to be specifically written to check for the presence of templates which have an influence on that.

This is bad encapsulation. What should happen is that you can add a "template" or "effect" to the creature which overrides the creature object's default behaviour in a way that doesn't require either the creature code or the sun-damage code to be aware of the template's existence.

So, by default, creatures are not damaged by sunlight. If you query the creature object:

Code:
creature.damaged_by_sunlight()

that returns false.

You add a "vampirism" object to the creature's list of overrides, and that changes the behaviour of the creature's code. So if you now evaluate:

Code:
creature.damaged_by_sunlight()

it returns true. But the code that calls the damaged_by_sunlight() function is entirely unaware of the existence of templates, and the code that returns false from that function in the default creature code is also unaware of the existence of templates.

With a system like this you can arbitrarily modify any aspect of any object's normal behaviour with a newly coded "effect" without ever having to make any changes to the default code, or to the code that invokes it.

This is something you really can't easily do in Fortran, or in C++. You can do it in a language like Smalltalk or Ruby, but you'd need to hack about with proxies, which still isn't the nicest way of handling it. I think a language like Self might be neater: something that does away with the idea of an object being a member of a class for its entire lifetime, and regards each object as possessing a unique (and dynamic) combination of methods and properties inherited from an arbitrary selection of other objects.


This might be an unelegant solution I'm proposing but... Assuming that on the basis of your vampirism example, we simply give each creature a toggle-able variable to determine whether or not they are damaged by sunlight, which is what the function you described would do, check the creature/player file/class/instanced object/whathaveyou and return either true or false. So basically, for things like this, it would be best if all animals could inherintly be damaged by sunlight (sunlight allergies?) and so they simply contain a Sunlight_Allergy variable that can be either one or zero in a simple true/false comparison.

This can be done easily and with little overhead as far as memory usage and whatnot is concerned so as to make it meaningless, even if we create large numbers of these things. So basically, in addition to your "Super" and "Sub" templates(for lack of a better term) you could also inherit, catch or generally gain any other number of "effects" during the object's lifetime within the game world.

I suppose the same principle could be used, but becomes far more cludgy in attempting to find a way to give a creature new functions (attacks, movements, skills, etc...) that they can call, on the fly, as a result of these "effects". Having all creatures have a huge list of possible commands and toggling them on and off just doesn't seem like a good idea at all, but if our bat becomes a vampire bat, then it goes without saying that they must become able to feed on the blood of the living.

So that's my random, disconnected thoughts for this morning...

Vopisk
Back to top
View user's profile Send private message AIM Address MSN Messenger
Author Message
shasarak



Joined: 29 Jun 2005
Posts: 134
Location: Emily's Shop

PostPosted: Wed May 03, 2006 1:34 pm    Post subject: Reply with quote

Vopisk wrote:
This might be an unelegant solution I'm proposing but... Assuming that on the basis of your vampirism example, we simply give each creature a toggle-able variable to determine whether or not they are damaged by sunlight, which is what the function you described would do, check the creature/player file/class/instanced object/whathaveyou and return either true or false. So basically, for things like this, it would be best if all animals could inherintly be damaged by sunlight (sunlight allergies?) and so they simply contain a Sunlight_Allergy variable that can be either one or zero in a simple true/false comparison.

That suffers from the same problem I was complaining about before: it's bad encapsulation. In fact, it's even worse than the previous suggestion. Smile For this to work, there has to be code in the default creature code to support every possible effect we might want to try and introduce. If I introduce some new effect like "takes damage from holy places" the only way to enable it under your paradigm would be to change the code that is used by ALL creatures to include a "damaged by holy places" boolean property.

Not only does that mean you keep having to modify the standard creature code every time you add something, you're also going to end up with hundreds or even thousands of properties on every single creature in the game, most of which have no effect most of the time.

Quote:
This can be done easily and with little overhead as far as memory usage and whatnot is concerned so as to make it meaningless

Hundreds of boolean flags on tens of thousands of monsters in the MUD? That's hardly a "meaningless" amount of memory.

The way this should work, as I've said, is that you need to be able to arbitrarily override any aspect of any object's behaviour at any time, without the object's default code having to know about the possibility in advance.

For example, a human becoming a vampire will experience a change in dietary requirements; let's say we implement this a little crudely by saying that vampires can eat corpses, but not anything else. The code for a human might have a function in it that determines whether or not any given object is edible. Conceptually it might look like this:

Code:
can_eat(object)

if (object->is_foodstuff()) {return true}
else {return false}

On the vampire template/overrider there wll be a corresponding function that looks like this:

Code:
can_eat(object)

if (object->is_corpse()) {return true}
else {return false}


At the point where you dynamically assign the vampire template to a human creature, the function as it is defined on the template replaces the function defined on the human for as long as the human remains a vampire. Thus, the result of something within the code for a human calling
Code:
me.can_eat(something)
actually executes different code. Before he becomes a vampire the code executed by
Code:
me.can_eat(something)
is part of the code for a human; after he becomes a vampire the code executed by the same expression is part of the vampire template. If he ceases to be a vampire, and the vampirism effect is removed, then executing
Code:
me.can_eat(something)
will once again execute the normal human code.
Back to top
View user's profile Send private message
Author Message
KaVir



Joined: 11 May 2005
Posts: 565
Location: Munich

PostPosted: Wed May 03, 2006 3:18 pm    Post subject: Reply with quote

shasarak wrote:
Conceptually it might look like this:

Code:
can_eat(object)

if (object->is_foodstuff()) {return true}
else {return false}

On the vampire template/overrider there wll be a corresponding function that looks like this:

Code:
can_eat(object)

if (object->is_corpse()) {return true}
else {return false}


At the point where you dynamically assign the vampire template to a human creature, the function as it is defined on the template replaces the function defined on the human for as long as the human remains a vampire. Thus, the result of something within the code for a human calling
Code:
me.can_eat(something)
actually executes different code.


Whether you handle that through overloaded methods, properties, or something else, I don't really see the above as a problem - it's solvable in a number of ways.

The difficulty I see is when you start encountering mixtures of templates on the same creature (I know this has been mentioned already, but I've not yet seen a solution). If a vampire can only drink blood, and a werewolf can only eat meat, what happens if you're both a vampire and a werewolf? Can you consume both? Neither? Specific combinations only (bloody meat)?

That doesn't really demonstrate my point so well, so lets extend it. Let's say that werewolves automatically transform into wolf form during a full moon, and cannot change back until they've eaten raw flesh, while vampires cannot swallow solid food and gain the ability to transform themselves between human, wolf and bat form at will.

What happens to the character during a full moon? Does the bat suddenly transform into a wolf and plummet out of the sky? Can they change back at will (using the vampire shapechanging power)? If not, how do they change back, if they can't stomach solid food?

You could just say "lycanthropy is a disease, and vampires are immune to diseases, so you can't be both" - but the above is only one possible example. If dragons are immune to fire, and liches can only be harmed by fire, what hurts a dracolich? And if you're on a quest to collect the eye and tooth of a dragon, how do you specify that you can get the latter but not the former from a dracolich? And how does the mud know that I can cut a skeletal arm off a lich, and a wing off a dragon, therefore I can cut a skeletal wing off a dracolich?
Back to top
View user's profile Send private message Visit poster's website
Author Message
shasarak



Joined: 29 Jun 2005
Posts: 134
Location: Emily's Shop

PostPosted: Wed May 03, 2006 3:50 pm    Post subject: Reply with quote

KaVir wrote:
Whether you handle that through overloaded methods, properties, or something else, I don't really see the above as a problem - it's solvable in a number of ways.

Sure, I'm just saying you can't solve it nicely in a language like C++. Once an object is created as an instance of a particular class it has to remain an instance of that class for its entire lifetime: you can't easily transform it into an instance of a different class on the fly. If you use some sort of containment or aggregation approach, it's still trick to avoid breaking encapsulation.

Quote:
The difficulty I see is when you start encountering mixtures of templates on the same creature (I know this has been mentioned already, but I've not yet seen a solution). If a vampire can only drink blood, and a werewolf can only eat meat, what happens if you're both a vampire and a werewolf? Can you consume both? Neither? Specific combinations only (bloody meat)?

Well, that's just a design/balance issue. Not handling situations like that is sloppy programming. Smile Either you have to not allow someone to be both a vampire and a werewolf at all, or you have to explicitly cater for that combination. My instinct would be (as you suggest) to treat both vampirism and lycanthropy as diseases, and make either condition confer immunity from all other diseases.

However, in general, templates should obviously be consciously coded in such a way that they don't conflict with one another.

KaVir wrote:
If dragons are immune to fire, and liches can only be harmed by fire, what hurts a dracolich?

I guess the default would be to make templated effects cumulative, so the answer would be "nothing" - one template gives immunity to a non-fire-based attack, and the other to fire.

A not very good possiblity would be to insist that only one template can modify any given attribute or behaviour at a time, and assign them an order of precedence. For example, a dracolich might be a lich first, and a dragon second. Only one template is allowed to affect immunity to attack types, and the lich template takes precedence, so the dragon template is ignored for the purposes of immunity. But this isn't very satisfactory, because the chances are you'll want one template to predominate for certain aspects, but another for others.

With something as specialised as a dracolich I think you probably are going to have to take a more traditional class/inheritance route rather than playing about with dynamically assigned overrides. In this case you could create the creature as an instance of a dracolich class which inherits from dragon and lich classes, and explicitly lays down which method or property is inherited from which superclass. This kind of makes sense anyway: it's hard to imagine that "being a dragon" is something that could affect someone on a dynamic basis.
Back to top
View user's profile Send private message
Author Message
KaVir



Joined: 11 May 2005
Posts: 565
Location: Munich

PostPosted: Wed May 03, 2006 6:38 pm    Post subject: Reply with quote

Quote:
Sure, I'm just saying you can't solve it nicely in a language like C++. Once an object is created as an instance of a particular class it has to remain an instance of that class for its entire lifetime: you can't easily transform it into an instance of a different class on the fly. If you use some sort of containment or aggregation approach, it's still trick to avoid breaking encapsulation.


Well you could store the species-specific information in a separate object, and give each creature a pointer to one such object. If they change species, you just discard the old one and create a new one.

As the species data is almost certainly going to be constant you could even share it, rather than creating an instance for each creature. For mixed-species creatures (vampire-werewolf, dracolich, etc) you could either use a hardcoded class which inherits from both and overrides as desired, or (for unexpected combinations) generate a custom species on the fly based on the dominance of each attribute for the different species.

Quote:
The difficulty I see is when you start encountering mixtures of templates on the same creature (I know this has been mentioned already, but I've not yet seen a solution). If a vampire can only drink blood, and a werewolf can only eat meat, what happens if you're both a vampire and a werewolf? Can you consume both? Neither? Specific combinations only (bloody meat)?

Well, that's just a design/balance issue. Not handling situations like that is sloppy programming.


If I've got several hundred different types of creature (which is not at all unreasonable), I can't realistically hand-write a different template for every possible combination - there would be (quite literally) millions.

Quote:
A not very good possiblity would be to insist that only one template can modify any given attribute or behaviour at a time, and assign them an order of precedence. For example, a dracolich might be a lich first, and a dragon second. Only one template is allowed to affect immunity to attack types, and the lich template takes precedence, so the dragon template is ignored for the purposes of immunity. But this isn't very satisfactory, because the chances are you'll want one template to predominate for certain aspects, but another for others.


You could give each attribute of each template its own precedence - and perhaps also include whether or not it allows other attributes to be applied. For example:

Dragon traits: breath attack, fiery energy, fire immunity, cold resistance, winged, tough hide.

Lich traits: life-stealing energy, physical immunity, cold resistance, fire vulnerability, fleshless.


The 'life-stealing energy' would have a higher priority than the 'fiery energy', so the dracolich would use the former for its breath attack. The 'fire vulnerability' would have a higher priority than the 'fire immunity', and the former would be classified as 'blocking', so the latter wouldn't be applied. Both would have 'cold resistance' at the same priority, and both would be classified as 'non-blocking', so they'd stack. The 'fleshless' would also block the 'tough hide', while the 'winged' and 'physical immunity' would apply normally.

The resulting dracolich would then be much like a regular lich, except with a breath attack (using the life-stealing energy instead of fire), wings, and a better cold resistance.

Quote:
With something as specialised as a dracolich I think you probably are going to have to take a more traditional class/inheritance route rather than playing about with dynamically assigned overrides.


Perhaps, but with so many different combinations it'd be nice if you could automatically handle as much as possible.

Quote:
This kind of makes sense anyway: it's hard to imagine that "being a dragon" is something that could affect someone on a dynamic basis.


In the Malazan Book of the Fallen novels, by Steven Erikson, there are a race of undead called the T'lan Imass. Their bonecasters are very lich-like, and some of them possess the ability to transform into dragons (and end up looking very much like a dracolich).

I think it'd be a very nice feature for a mud to support such dynamic shapechanging. Imagine having a shapechanger who could specifically choose to assume hybrid forms.
Back to top
View user's profile Send private message Visit poster's website
Author Message
Vopisk



Joined: 22 Aug 2005
Posts: 99
Location: Golden Valley, Arizona, USA

PostPosted: Wed May 03, 2006 11:26 pm    Post subject: Reply with quote

I think I understand what you're proposing KaVir, but I'm getting the impression that your suggestion so far only covers creating the creature the first time, so to speak.

In order for something to "change" species and skip pointers, we would basically just be creating a new, but different creature and inserting it in place of the old one wouldn't we?

But, however, I think what's more important to note, is that all in all, creatures will not often change species dynamically, but in such case as shapechangers, how do you persist the data for their original form while switching them into a new form?

I suppose you could just save the original form of the creature, so that if they gain some temporary affect that alters their appearance or whatnot, they can be reverted once it wears off, but that seems like even more overhead than having my suggestion of a whole slew of booleans, considering we might need to have 2x (or more) the number of MOBs potentially, further complicating things if we need to know what the bat looked and acted like, then what the vampire bat looked like, and finally we have our lycanthropic, vampire bat lich...

I do like your proposal, it seems feasible, I'm more I suppose asking for explanation of how exactly it would work rather than trying to refute it...

Vopisk
Back to top
View user's profile Send private message AIM Address MSN Messenger
Author Message
KaVir



Joined: 11 May 2005
Posts: 565
Location: Munich

PostPosted: Thu May 04, 2006 8:07 am    Post subject: Reply with quote

Quote:
In order for something to "change" species and skip pointers, we would basically just be creating a new, but different creature and inserting it in place of the old one wouldn't we?


No, not a whole new creature - just a new 'species' object.

Quote:
But, however, I think what's more important to note, is that all in all, creatures will not often change species dynamically, but in such case as shapechangers, how do you persist the data for their original form while switching them into a new form?


I suppose you could keep track of an 'original form' species object as well.

Quote:
I suppose you could just save the original form of the creature, so that if they gain some temporary affect that alters their appearance or whatnot, they can be reverted once it wears off, but that seems like even more overhead than having my suggestion of a whole slew of booleans, considering we might need to have 2x (or more) the number of MOBs potentially, further complicating things if we need to know what the bat looked and acted like, then what the vampire bat looked like, and finally we have our lycanthropic, vampire bat lich...


Nope, I think you misunderstand what I'm proposing.

Each mob consists of a Creature object, which stores things like attributes, skills, name, etc. Each Creature also stores a pointer to a Species object, which contains constant data like the name of the species, which body parts it has, whether it can eat food, what vulnerabilities or immunities it has, etc. Because the Species data is constant you wouldn't need an instance for each creature, except in the case of customised hybrids.

Thus my human wizard would have a pointer to the human Species object (several hundred other Creatures might also point to the same instance). When I cast a polymorph spell to turn into a frog, the pointer in my Creature object would change to point to the frog Species object. If I used a special power to turn into a frogman hybrid, a new Species object would actually be allocated and initialised based on the precedence of human and frog Species attributes.

The per-Creature memory overhead would therefore be around 4 bytes (for the pointer to the Species) - or 8, if you also wanted to keep track of their original form. The Species would also take up memory, but you wouldn't need to keep repeating it - if there are 500 humans, 1000 goblins and 20 dragons in the mud, they'll only need a total of 3 Species objects between them. Shapechangers might well require their own customised Species objects, but I can't imagine a high percentage of the mobs being shapechanged into unique forms at any one time.
Back to top
View user's profile Send private message Visit poster's website
Author Message
shasarak



Joined: 29 Jun 2005
Posts: 134
Location: Emily's Shop

PostPosted: Thu May 04, 2006 9:18 am    Post subject: Reply with quote

KaVir wrote:
Each mob consists of a Creature object, which stores things like attributes, skills, name, etc. Each Creature also stores a pointer to a Species object, which contains constant data like the name of the species, which body parts it has, whether it can eat food, what vulnerabilities or immunities it has, etc. Because the Species data is constant you wouldn't need an instance for each creature, except in the case of customised hybrids.

That sounds eminently reasonable: I've argued before that this sort of aggregation approach is desirable in many contexts. Newbie OO programmers tend to assume that one MUD "object" such as a sword or a monster must, of necessity, be represented by one code object, but this is a dangerous assumption: often one game "object" ahould be an aggregation of many code objects, each one modelling a different aspect of the overall behaviour.

Of course, the class that a creature's species is derived from could quite easily inherit attributes from more than one other species superclass, so we can mix in some of the other aspects we've been talking about. For example, you might have a DragonSpecies class, which has a subclass RedDragonSpecies, which, in turn, has a subclass TransformedWereDragonSpecies. DragonSpecies in turn might well inherit from superclasses like ReptileSpecies, WingedSpecies and BreathWeaponSpecies.
Back to top
View user's profile Send private message
Author Message
Vopisk



Joined: 22 Aug 2005
Posts: 99
Location: Golden Valley, Arizona, USA

PostPosted: Thu May 04, 2006 11:32 am    Post subject: Reply with quote

Thanks for the explanation KaVir, I get what you were saying now and it is even better than I imagined! Smile

In all seriousness, I think this system would probably trump any other as far making mobiles steadily and consistently be relatively similar, while still being dynamic rather than static, assuming that the actual statistic a mob has are dependent upon their level or other abstract modifier that will increase their skill percentages or stats or whathaveyou.

The only thing I can think of that's really missing at this point is an idea as to how to blend names together for hybrid species in a cosistent and logical manner.

I also like the idea that instead of having every single creature store species-specific information, they all share, I imagine that's a significant drop in memory overhead when the amount of mobs gets to be huge. But I wonder, would there be a speed performance penalty when all 500 humans or 1000 goblins are accessing species-information at the same time? Especially since all these seperate calls are calling for the same piece of memory?

As to Shasarak's response, so the case could be that the creature object, which is rather bland and all together worthless on its own, would inherit superclass pointers to super-class objects that tell it about that part of it's special origin (i.e. the Demon side of our original Demon Spider) and then the Demon would also get a pointer to the Spider sub-class which would blend the two "templates"(for lack of a better term) together to create the final end-result of a Demon Spider? And all of these inherited species objects would have prioritized lists of attributes which would be compared and either stacked or blocked (as per KaVir's suggestion) in order to determine exactly which effects and abilities the resulting creature has?

Anyway, my new concerns, probably trivial,

Vopisk
Back to top
View user's profile Send private message AIM Address MSN Messenger
Author Message
KaVir



Joined: 11 May 2005
Posts: 565
Location: Munich

PostPosted: Thu May 04, 2006 12:11 pm    Post subject: Reply with quote

Quote:
The only thing I can think of that's really missing at this point is an idea as to how to blend names together for hybrid species in a cosistent and logical manner.


Yeah, it's the same problem with all descriptive text - body part names, etc. It wouldn't be an issue for hand-crafted species classes, but for generated on-the-fly ones you're going to end up with the occasional strange result. I can't think of any real solution other than constantly testing and fine-tuning, and thinking carefully about each species as you add it.

Quote:
But I wonder, would there be a speed performance penalty when all 500 humans or 1000 goblins are accessing species-information at the same time? Especially since all these seperate calls are calling for the same piece of memory?


No, that won't make any difference at all in terms of access speed. If anything, the overall performance of the mud may even improve (very) slightly, because you won't be allocating and destroying memory as often.

Quote:
As to Shasarak's response, so the case could be that the creature object, which is rather bland and all together worthless on its own, would inherit superclass pointers to super-class objects that tell it about that part of it's special origin (i.e. the Demon side of our original Demon Spider) and then the Demon would also get a pointer to the Spider sub-class which would blend the two "templates"(for lack of a better term) together to create the final end-result of a Demon Spider? And all of these inherited species objects would have prioritized lists of attributes which would be compared and either stacked or blocked (as per KaVir's suggestion) in order to determine exactly which effects and abilities the resulting creature has?


Sort of...for example you might create a 'giant spider' mob with the 'spider' Species, and a 'pit lord' mob with the 'demon' Species. If you then wanted a 'Bubba the Brutal' spider demon mob, you'd give it a new 'spider demon' Species.

The mud would then create a new Species object, and generate its attributes based on the the precedence of each of the demon and spider Species' attributes. Thus, for example, it'd probably be immune to fire (from demon), have eight legs (from spider), and so on. Your new Bubba the Brutal mob would then use the newly created Species as its own. Like all mobs he'd only have one Species, but it would be a customised one which combines elements from two others.

Alternatively, if you decided that the spider demon should have specific traits (eg perhaps it should have four legs and four arms, which it wouldn't get from either of the child Species) you could hardcode that combination specifically. Anyone assigned (or shapechanged) into a spider demon would then get your hardcoded hybrid solution, rather than the default one.
Back to top
View user's profile Send private message Visit poster's website
Author Message
shasarak



Joined: 29 Jun 2005
Posts: 134
Location: Emily's Shop

PostPosted: Thu May 04, 2006 1:50 pm    Post subject: Reply with quote

Vopisk wrote:
But I wonder, would there be a speed performance penalty when all 500 humans or 1000 goblins are accessing species-information at the same time? Especially since all these seperate calls are calling for the same piece of memory?

KaVir wrote:
No, that won't make any difference at all in terms of access speed. If anything, the overall performance of the mud may even improve (very) slightly, because you won't be allocating and destroying memory as often.

Not only that, but you also improve the chances of the necessary data already being in the CPU's cache rather than requiring vast amounts of prefetching from all over the place. If you can look up the same piece of data ten times that will be quicker than looking up ten separate pieces of data.

Vopisk, would I be correct in assuming that aren't you very familiar with Object-Oriented programming? If so, you really need to do some reading up on it, as a lot of basic OO concepts are applicable here.
Back to top
View user's profile Send private message
Author Message
Vopisk



Joined: 22 Aug 2005
Posts: 99
Location: Golden Valley, Arizona, USA

PostPosted: Fri May 05, 2006 1:07 am    Post subject: Reply with quote

Most of my "coding" experience is in scripting and other non-oo languages it's true. I understand the concepts of it, but not the implementation, which is why sometimes I have a hard time wrapping my head around something such as this.

Luckily there's two helpful people to highlight the keypoints a little better though eh? That would be why I'm asking so many questions about this particular thread though, most other things can be linearly scripted and work fine, but with something like characters and mobs, we need the functionality of OO in order to not get dragged down into having to hardcode all 8 billion different combinations of MOBs that we want to make available.

Furthermore, I'm working on designing a dynamic description type system, so once again, the OO is going to come in handy... I'm learning, as fast as I can for someone who has to spend eight or more hours a day on the roof...
Back to top
View user's profile Send private message AIM Address MSN Messenger
Author Message
thyrr



Joined: 18 Apr 2006
Posts: 7

PostPosted: Fri May 05, 2006 8:59 am    Post subject: Reply with quote

On the topic of code polymorphism and such, I'm not a fan of is_XXX or can_XXX methods, because they're pretty much glorified flags. If you want fine-grained behavior, like say "mildly allergic to reflected sunlight", you'd have to do something like if(vampire.vuln_indirect_sunlight()) then damage vampire.sunlight_damage(brightness); which is just ugly namespace pollution.

Instead, it might be better to pass off the handling to the creature. If you wanted to make blueberries poisonous to smorfs, instead of smorf.is_poisonous(blueberry), why not generalize it to smorf.after_eating(blueberries) and leave the poisoning code to the smorf?

Then if you make a race of loompa-oompas that explode after eating blueberries, you don't need to modify on_eat for another case -- you make the loompa-oompas's after_eating method handle the explosion. Even better, you could again define a precedence order, so if you have a hybrid smorf-oompa, it becomes poisoned and then explodes in a shower of toxic flesh. The smorf after_eating code could also signal to stop processing if it thinks it's handled the situation sufficiently on its own.

Likewise, with the vampire code, you could have a more general light_level_change() method/event that's sent when it gets dark or bright, and the vampire would check for the appropriate conditions. A werewolf might also use this method but only check for full-moonlight. A were-vampire might check both.

Now actually implementing this kind of dispatch handling sort of system could be a major pain. Like dealing with inheritance -- if you wanted to override your ancestor's stat loss from eating egg sprouts but not your other ancestor's affinity for banana cake cold remedies, which is one issue with having a single, general method. Maybe inter-object calls should be general, but subclasses would have to deal with specifics like snarf.eww_egg_sprouts() although again it could be nasty with multiple inheritance. Lots of details to get right. But I think it could be very flexible in the end.


Last edited by thyrr on Fri May 05, 2006 9:14 am; edited 1 time in total
Back to top
View user's profile Send private message
Author Message
shasarak



Joined: 29 Jun 2005
Posts: 134
Location: Emily's Shop

PostPosted: Fri May 05, 2006 9:14 am    Post subject: Reply with quote

Vopisk wrote:
Most of my "coding" experience is in scripting and other non-oo languages it's true. I understand the concepts of it, but not the implementation, which is why sometimes I have a hard time wrapping my head around something such as this.

Well, a very quick lecture in OO basic principles - probably a bit too easy-peasy for you. Smile

An "object", in OO programming terms, is a self-contained "something" that has associated with it some data (properties) and/or some code. In an OO context one normally refers to functions that are defined on an object as the object's "methods". You will sometimes hear "invoking a method" on an object referred to as a "message send", although this is not universal.


The three most important aspects of OO are: Encapsulation, Inheritance, and Polymorphism.


"Encapsulation" basically means "isolation". An object should have a specific function or purpose; it should know what it needs to know in order to fulfil that purpose; and it should know as little as possible about anything else. To put it in engineering terms, objects should be as "decoupled" as possible: one object should make as few assumptions as possible about the internal workings of another object.

The reason encapsulation is important is that it simplifies the process of enhancing the system. Suppose, for example, we have a system on a MUD where some code associated with a room knows whether or not the room is in daylight. When a monster or player enters the room, the room code checks to see if the monster is vulnerable to daylight and, if it is, inflicts some damage on it.

This is an example of VERY BAD encapsulation. Why? Well, imagine that someone wants to introduce some armour that disintegrates when exposed to daylight. In order for this to work correctly you would have to modify some of the code associated with the ROOM in order to make your new armour, which is crazy.

The correct way to do this is that when a player object enters the room it relays a message to all of the objects it is carrying saying "environment has changed to room X". Any object the player is carrying then responds to that message in whatever way it sees fit. So the light-sensitive armour would check if room X was in daylight and, if it was, cause the armour to disintegrate. Other objects wouldn't bother checking.

So, once you have your entering-the-room notification system working, if something needs to be affected by entering a room, the thing itself deals with all the consequences of doing so; the player code and the room code don't need to know anything about the armour being light-sensitive, and the only thing that has to be changed to make armour light-sensitive is the code for the armour itself.


"Inheritance" refers to code being reused. Essentially, if a piece of code serves a particular purpose, then any other piece of code which requires to do the exactly same thing should normally reuse the same bit of code rather than duplicating it.

This helps with things like memory footprint and performance but it also (again) helps with maintenance and changes to the code. For example, suppose we decide that all lightning-based, damage-causing spells are going to receive a +5 damage bonus if cast out-of-doors on a rainy day. If each lightning spell is coded entirely separately, then every lightning spell will have to be separately updated to reflect this change: more work, and it would be easy to miss one. However, if all lightning spells inherit some common lightning spell code, and merely extend or specialise it slightly, then you only have to change one lot of code, and you've got what you want.

Different languages deal with inheritance in different ways, but it's often handled in terms of "classes". A class represents the definition of a type of object. A class may inherit code and properties from a "superclass" (or, in some languages, several superclasses). And a class may, in turn, be inherited by "subclasses".

In MUD terms, we might (rather crudely) imagine that there is a class Weapon, which has subclasses like EdgedWeapon, PointedWeapon and BluntWeapon. EdgedWeapon might have a subclass Sword, which might, in turn, have a subclass BastardSword.

Any actual bastard sword in the game would be an "instance" of the class "BastardSword". It would therefore have certain behaviours that are the result of it being an instance of that class. It would also have behaviours that are inherited from Sword, EdgedWeapon, and Weapon.

Subclasses extend, specialise, and sometimes override superclass behaviour. If we had a class Mammal, it might implement a method has_fur() which returns true, a method suckles_young() which returns true, a method lays_egs() which returns false. Thus, by default, instances of all classes that inherit from Mammal will return true if asked whether they have fur. A naked molerat would alter that behaviour to return false. A duck-billed platypus would alter the default mammalian behaviour to return true if asked whether or not it lays eggs. But both animals suckle their young (after a fashion) so neither the NakedMoleRat class nor the DuckBilledPlatypus class would have any young-suckling code defined at that level. Instead, they would both inherit the fact that they suckle their young from the Mammal superclass.


"Polymorphism" refers to the abililty of different objects to respond to the same message (or method invokation) in different ways. So, for example, if you send a human the message "exposedToSunlight" then nothing very much will happen. If you send a vampire the message "exposedToSunlight" then it takes damage. Exactly the same message is sent in both cases - or, if you like, the same function is called in both cases - but the two objects respond to the same function call in two different ways.
Back to top
View user's profile Send private message
Author Message
KaVir



Joined: 11 May 2005
Posts: 565
Location: Munich

PostPosted: Fri May 05, 2006 1:51 pm    Post subject: Reply with quote

Quote:
Instead, it might be better to pass off the handling to the creature. If you wanted to make blueberries poisonous to smorfs, instead of smorf.is_poisonous(blueberry), why not generalize it to smorf.after_eating(blueberries) and leave the poisoning code to the smorf?

Then if you make a race of loompa-oompas that explode after eating blueberries, you don't need to modify on_eat for another case -- you make the loompa-oompas's after_eating method handle the explosion. Even better, you could again define a precedence order, so if you have a hybrid smorf-oompa, it becomes poisoned and then explodes in a shower of toxic flesh. The smorf after_eating code could also signal to stop processing if it thinks it's handled the situation sufficiently on its own.


The problem is that you're making the behaviour code specific to a certain species. The smurf is poisoned by blueberries, and that has to be supported somewhere - but if the code is in the smurf class, what do you do about the vampire being poisoned by garlic, and the werewolf being poisoned by wolfsbane? Not only does this result in redundant code, it also makes it very difficult for the mud to mix two different species on-the-fly.

Instead, I would propose each species store which substances can poison it, along with the severity of the reaction and the means of application. You could still make the methods part of the species class for encapsulation purposes, but they shouldn't be overloaded except in very specific cases. For example when Bubba eats the blueberry pie you could call "Bubba->getSpecies()->eats(pie)", which then calls the eats() method in the base class, checks whether the pie is poisonous, and responds accordingly.

This means that you'd only have one piece of code for handling what happens when you eat food, and the specific responses (exploding, poisoned, turning to stone, etc) would be handled in the same place - but they'd be based on the attributes of the species object being called.

Now I can just add "blueberries:poisonous:non-blocking:low" to the smurf species and "blueberries:exploding:non-blocking:medium" to the loompa-oompa species, and the mud will be able to automatically generate the appropriate crossbreed from those attributes, without the need for any further code, let alone new methods. Equally, when I add "solidfood:sick:blocking:high" to the vampire species (to indicate that vampires throw up when they eat food), it means that vampire loompa-oompas will no longer explode when they eat blueberries (because they're no longer digesting them), instead responding in the same way as any other vampire. However they'd still explode from drinking blueberry juice - and once again this would all be handled automatically, as part of the design, without needing any additional code to be added.
Back to top
View user's profile Send private message Visit poster's website
Display posts from previous:   
Post new topic   Reply to topic    mudlab.org Forum Index -> Design All times are GMT
Goto page Previous  1, 2, 3  Next
Page 2 of 3

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

Powered by phpBB © 2001, 2002 phpBB Group
BBTech Template by © 2003-04 MDesign