Friday, July 30, 2010

Damage

I just re-read my post about destruction and realised I should show what it took me to implement damage. This might not be my most interesting post, as it is just code, but maybe some of you are interested. Get ready... there are about 200 lines of code coming for you!


public abstract class DamageEvent extends ReplaceableEvent {
    private MagicObject source;
    private int         ammount;
    private boolean     combat, preventable;
    
    public DamageEvent(MagicObject affected, MagicObject source, int ammount, boolean combat, boolean preventable) {
        this(you(ofInstance(affected)), source, ammount, combat, preventable);
    }
    
    public DamageEvent(Player affected, MagicObject source, int ammount, boolean combat, boolean preventable) {
        this(ofInstance(affected), source, ammount, combat, preventable);
    }
    
    public DamageEvent(Supplier affected, MagicObject source, int ammount, boolean combat, boolean preventable) {
        super(affected);
        this.source = source;
        this.ammount = ammount;
        this.combat = combat;
        this.preventable = preventable;
    }
    
    public MagicObject getSource() {
        return source;
    }
    
    /**
     * This method can be used by replacement effects that prevent or increase damage.
     */
    public void setAmmount(int ammount) {
        this.ammount = ammount;
    }
    
    public int getAmmount() {
        return ammount;
    }
    
    public boolean isCombatDamage() {
        return combat;
    }
    
    public boolean isPreventable() {
        return preventable;
    }
}



public class DamagePlayerEvent extends DamageEvent {
    private Player p;
    
    public DamagePlayerEvent(Player p, MagicObject source, int ammount, boolean combat, boolean preventable) {
        super(p, source, ammount, combat, preventable);
        this.p = p;
    }
    
    public Player getPlayer() {
        return p;
    }
    
    @Override
    protected boolean execute0() {
        CompoundEdit ed = new CompoundEdit(getGame(), true, "Deal damage");
        
        //118.3a. Damage dealt to a player causes that player to lose that much life.
        getPlayer().getLifeTotal().loseLife(getAmmount());
        
        ed.end();
        
        return true;
    }
}



public class DamagePermanentEvent extends DamageEvent {
    private static final Predicate legalAffected  = and(isIn(ofInstance(BATTLEFIELD)),
                                                                       card(or(CREATURE, PLANESWALKER)));
    
    private static final Predicate isPlaneswalker = card(has(PLANESWALKER));
    //TODO implement wither and lifelink
    private static final Predicate hasWither      = alwaysFalse();
    private static final Predicate hasLifelink    = alwaysFalse();
    
    private CardObject                          permanent;
    
    public DamagePermanentEvent(CardObject affected, MagicObject source, int ammount, boolean combat, boolean preventable) {
        super(affected, source, ammount, combat, preventable);
        if(!legalAffected.apply(affected)) throw new IllegalArgumentException(
                "affected must be a creature or planeswalker permanent: " + affected);
        permanent = affected;
    }
    
    public CardObject getPermanent() {
        return permanent;
    }
    
    @Override
    protected boolean execute0() {
        CompoundEdit ed = new CompoundEdit(getGame(), true, "Deal damage");
        
        //118.3b. Damage dealt to a planeswalker causes that many loyalty counters to be removed from that
        // planeswalker.
        if(isPlaneswalker.apply(getPermanent())) {
            //TODO remove counters
        }
        //118.3c. Damage dealt to a creature by a source with wither causes that many -1/-1 counters to be put on
        // that creature.
        else if(hasWither.apply(getSource())) {
            //TODO add counters
        }
        //118.3d. Damage dealt to a creature by a source without wither causes that much damage to be marked on
        // that creature.
        else {
            getPermanent().setMarkedDamage(getPermanent().getMarkedDamage() + getAmmount());
        }
        //118.3e. Damage dealt to an object or player by a source with lifelink causes that source's controller to
        // gain that much life, in addition to the damage's other results.
        if(hasLifelink.apply(getSource())) {
            getSource().getController().getLifeTotal().gainLife(getAmmount());
        }
        
        ed.end();
        
        return true;
    }
}


I don't really like this code, because it has the effects of damage hard coded, but I think it's fine for now. extracting these effects later if it's necessary shouldn't be too hard, as the different effects of damage will only be mentioned here, so I can safely delay this task.

2 comments:

nantuko84 said...

putting lifelink, wither, etc. in one place is good idea. at the beginning, I've made a mistake putting checking for various damage aspects in several places - this causes some issues later.

btw, are you planning to use IllegalArgumentException in you workflow or is it just for protection to make sure nothing goes wrong (I mean using runtime exceptions to stop the game whenever it is in wrong state - in this case "damage was dealt to non creature\non planeswalker")?

Silly Freak said...

my point was not putting the checks in several places but to put every of the effects into one place. say sometimes there's a card like:

"damage dealt by warriors causes a +1/+1 counter to be put on that creature in addition to other effects"

I definitely don't want to check for that creature in the damage class.

Exceptions: these checks are just to make sure I call the methods right. In the normal program flow, this exception should never be thrown