7
\$\begingroup\$

I'm working on the inventory system for my game, and am interested in how do I implement additional weapon and armor properties that are not just damage or armor?

If all I have is damage and armor, the equation is simple: damage = weapon damage - armor;

But I'm looking for a more interesting user experience - namely items that have 2-3 properties that modify various game mechanics.

For example, I can use a bunch of flags and write a big if/else statement in my attack method and check for all these at appropriate points:

if (weapon is vampiric) { heal attacker for x }

if(weapon is holy) { check defender type, add extra damage }

if(armor absorbs first x damage) { reduce damage to defender by x }

Another way that I thought might be better is the decorator design pattern - progressively wrapping the output of one weapon creation into another :

public class Main {

    public static final void main(String[] args) {
    Weapon w = new MeleeWeapon();

        w = new Holy(w);
        w = new Vampiric;
        w = new Enchanted(w,1);

        Damage damage = w.damage();

    }

}

It seems to me that decorator would allow me to write more flexible code and add more types later, but I'm not sure if it will just move the if/else statements into the weapons special property method.

\$\endgroup\$
7
\$\begingroup\$

The words themselves helps you : a weapon is just a carrier for effects. Some are instantaneous (damage), other are longer (poison).

The scope of the effects is also quite large : some effects affect the weapon owner, some others the opponent. And most of the time, you need to take all of them into account (nevertheless, you can easily fasten the search to know which ones are applicable).

def Attack( attacker, receiver) :
    status = receiver.status() # original LP, MP, etc
    impacts = [] # the list of impacts (poison, speed, etc) to be applied
                 # impact need to be sortable by category, so we can 
                 # enhance them or cancel them
    for effect in attacker.effects:
        # accumulation the instantaneous impacts
         if effect.instantaneous:
             impacts.append( effects.apply(status, impacts) )
        # some effects last a while, an active instance will be carried by the receiver
        # those instance have a tick method and will stop after a moment and be removed
         elif effect.lasting:
             receiver.effects.append( effect.get_copy() )
         else:
             continue
    # we need to apply receiver effects as some may cancel some impacts of the attacker
    for effect in receiver.effects:        
         if effect.on_impacts: # effects that can modify impacts
             impacts.append( effects.apply(status, impacts) )
         elif effect.on_effects: # effects than can modify lasting effects
             receiver.effects = effect.filter(receiver.effects, status )
         else:
             continue


    # lastly, we apply all the impacts.
    status.change( impacts )
    receiver.status = status

For example, the computation of a warrior carrying a sword with poison protection stung by a giant scorpion would something like :

Warrior.effects = [ PoisonProtection(.30), #effect.on_impact,
                    CannotBeStun(), #effect.on_effects,
                    FireDamage(+15%), #effect.instantaneous
                    SensibleToIce(+15%), #effect.instantaneous ]

Scorpion.effects = [ SimpleDamage(min=10, max=30),
                     PoisonDamage( 30),
                     LastingPoisonDamage( dmg_per_sec=3, length=15) ]

This needs to be extented to manage area effects but you get the general idea.

\$\endgroup\$
  • \$\begingroup\$ I really like your answer, especially how effects can be either instantaneous or affect other effects. I've been thinking on how to implement things like "cannot be stunned", and here you have it! \$\endgroup\$ – Alex Stone Dec 19 '13 at 4:06
2
\$\begingroup\$

newing repeatedly and the jumps this will incur between memory locations where the original object and its various wrappers are located, will be more costly than the extensive conditionals you mention or the approach offered below. Also, I would hesitate at using the Decorator Pattern in this instance because there is no clear order in which decorators would apply, thus making code potentially confusing to debug; preferable to have all options listed linearly, but encapsulated individually.

What I would suggest would be function pointers, functors or the Strategy Pattern for languages that do not support first-class functions or where there is otherwise a strong desire to work to fixed interfaces.

In your weapon class, hold a series of references to your possible function pointers, functors or strategies representing the various possible effects, and call each (non-null) one at the time when you need to do weapon processing. You can hold these references either as members, or as entries in an array or dictionary (more dynamic and workable with a scripting engine, but also a little slower). Try to maintain an update phase where you are resolving all weapon effects, not only damage.

P.S. This is quite closely related to how we often build OO-based entity-component systems, though the distinction ought to be made here that we are not considering each of these to be a component of an entity, but rather to be a component of a weapon which is usually referenced by the weapon or hand component of an entity. That said, a weapon may itself be a type of entity.

P.P.S. Be careful applying traditional (read: business) software development design patterns when writing (real-time) games. Not all of them are nearly as applicable here as in the wider world, as game architecture necessitates a very different set of concerns.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.