Tuesday, July 10, 2012

EPI #15: Battles, part 1

Cool.  Battles.  From beginning to end, battles look like this:
  1. The EPIWindow hears an "init_battle" event and switches out the active MapWidget for a BattleWidget.
  2. The BattleWidget determines positions for sprites and draws them on the battle scene.
  3. For each member of the home team (the player party), the BattleWidget draws a menu to the screen for the player to select an action for that battle round.
  4. After the player has selected a move for each party member, the BattleWidget tells its Battle to take_turn() and gives it an array of the home team's moves.
  5. Battle.take_turn() does these things:
    1. Determine each partipant's status by evaluating any applicable buffs, debuffs or effects from equipped items
    2. Choose a move for each member of the away team.
    3. Put the home and away moves into a list.
    4. Order that list according to who goes first.
    5. Evaluate each move in order.
  6. If there's at least one member of both the home and away teams still standing, go back to (2).
  7. Otherwise, if one side is completely dead, the battle is over, and the BattleWidget emits an "end_battle" event.
  8. The EPIWindow hears the "end_battle" event and switches the active BattleWidget for the MapWidget.
This'll probably make a lot more sense after I talk about some new classes.  Let's start with Spells:
  • Spell
    • id - an integer representing the spell's id.
    • name - a string representing the spell's name.
    • text - the text to display when the spell is cast.
    • effects - a list of strings describing the effects the spell will have.
    • target - a list of all the targets this spell will be cast upon.
    • caster - a reference to the Monster or PlayerCharacter casting this spell.
    • nature - a string.  One of "offensive", "defensive", or "self".
    • get_target_menu( friends, foes ) - returns an appropriate selection of targets to choose from based on the spell's nature.
    • eval() - evaluate's the spell's effects based on its caster and target(s).
Every ability that is castable in battle is contained within a Spell object.  So, when I refer to "casting a spell", what I really mean is "using an ability in battle".  There's no need to code a distinction between, say, melee attacks, ranged attacks, and magic attacks, because any necessary distinction will be attainable through the Spell's effects.  An example XML Spell definition might look like this:

<spell id='4' name='Fireball' nature='offense' target_type='choose'>
     <text>CASTER slung a Fireball at TARGET!  TARGET took DAMAGE damage!</text>
     <effect>MODIFY_STATISTIC -(1-("target.defense"+"target.magic_power"/100))*"caster.magic_power" "target.hit_points"</effect>
     <effect>MODIFY_STATISTIC -10 "caster.mana_points"</effect>
</spell>

Word wrap is messy and nobody likes it.  This spell ("Fireball") has two effects.  One is to reduce the target's hit_points according to a certain formula dependent on the caster's magic_power and the target's magic_power and defense, and the other is to reduce the caster's mana_points by 10.  Since Spells keep track of their own casters and targets, what's really happening in steps (3) and (5) of the battle algorithm above is that I'm creating and operating on complete Spells.  A complete Spell is one for which both a caster and a target have been set.  The Spells are then discarded at the end of the round, and the list is rebuilt and reevaluated appropriately.

All the spells in the game are described in spellbook.xml.  Monsters, PlayerCharacters, and Items are then assigned spells according to Spell.id.

This is a pretty good start on battles for tonight.  I'm going to go to sleep because I need to be up early in the morning, but I'll be back tomorrow with descriptions of Monsters, menu drawing, and maybe I'll even implement Spell.eval() (the one part of the battle algorithm I haven't implemented yet) and talk about that.

No comments:

Post a Comment