Wednesday, July 11, 2012

EPI #17: Battles, part 3

If you remember from EPI #15, all Spells have one or more effect that they convey to their target.  This is pretty much where all of the flavor of battles comes from.  It's where designers should be able to get super creative and have a lot of fun making up horrible things for Monsters and PlayerCharacters to do to each other.  I'm taking the same approach to defining and parsing these as I am with LAVs.

For the same reasons, then, if I wanted to do these right, I'd jump through the hoops of coming up with a grammar and writing a parser and generally treating Spell effects like a light scripting language.  However, I'm running a little low on time, so I'm gonna stick with regular expressions and if-elif-elif-... blocks.  (If you remember from EPI #1, I made it my goal to have some kind of playable release done by July 16th.  That's Monday. Ha ha ha.)

But Spell effects pose a problem for that approach.  How am I supposed to handle something like

<effect>MODIFY_STATISTIC -1*(1-("target.defense"/100))*"caster.attack_power"+5 TO "target.hit_points"</effect>

In what amounts to a switch statement?  Regular expressions and switches are definitely not suited to evaluating arbitrarily complicated mathematical expressions, so I need a different solution.

Enter Python's eval() function.  eval() takes a string of text and asks the Python interpreter to treat it as if it were Python code.  Using eval() on raw user input (for example, an expression written by a designer as part of an Spell effect) is an unspeakably bad idea.  This is because you leave yourself wide open to code injection attacks.  The Python interpreter does what it's told, and doesn't think twice about whether the code it's interpreting might be catastrophically harmful.  For example, what if a clever designer wrote a Spell that looked like this:

<effect>MODIFY_STATISTIC import os; os.system("rm -rf /") TO "target.hit_points"</effect>

If the player were running EPI as root on a Linux machine, casting that Spell would delete his entire hard drive.  Not good.  This example is a little dramatic, yes, and strictly speaking people should only use their machines as root or an administrative user for exactly as long as they need to, but the point is that I don't want to leave my code open to these kind attacks, but I don't want to write a top-down parser either.

So I choose to treat these expressions very carefully instead.  First, I match them against the regular expression /import/i, which makes sure that the expression I'm about to evaluate doesn't try to import any modules.  Then, using more regular expressions, I substitute occurrences of things like "target.defense" for self.target.statistics['defense'], replacing those quoted quantities with their numerical equivalents.  Then, I pass the new formula to eval( formula, {'__builtins__':None}, {} ). The two optional arguments, {'__builtins__':None} and {}, prevent eval() from using any built-in, global, or local variables.  This, combined with the /import/i filter, permit eval() to operate only on literal expressions.  It's not allowed to reference any variables, import any modules, or make any function calls.

It may be possible that a designer could obfuscate their module import somehow and slip it past my regular expression filter; I don't know.  But I'm not really too concerned about that.  I plan on releasing the engine open-source anyways, and anyone with enough experience with Python to know how to do such a thing would have a much easier time just modifying the engine to do something malicious in the first place.

So I guess you make sure you only get the engine from a trustworthy source (I'll probably throw it up on Sourceforge when it's ready), and definitely don't run it as root/Administrator.

EPI #16: Battles, part 2

More about Battles!  Last time I talked about Spells, and while spell-flinging is exciting and accounts for most of the action in battles, there's a lot more that I need to talk about.  So I'll start with Monsters and PlayerCharacters, the two kinds of things that Spells act upon.  class Monster:
  • Monster
    • art - A string containing the path to the sprite to display in battle.
    • statistics - a dictionary. {'stat_1:'val_1', 'stat_2':'val_2', ...}
    • moveset - a list containing the Spell.ids that the Monster is allowed to use.
    • pixbuf - a gtk.gdk.Pixbuf of the image indicated by Monster.art
    • resolution - an OrderedPair
    • pick_move() - chooses a spell to cast (at random, currently)
    • pick_target( targets ) - selects a target for the spell (at random, currently)
Pretty standard stuff.  These are so standard, in fact, that they should probably be related to PlayerCharacters via a common superclass somehow, but they're not.  This is a flaw in my class design, because it means that I'm not reusing as much code as I could be.  class PlayerCharacter:
  • PlayerCharacter(MapEntity)
    • statistics - Same as Monster.statistics.
    • equipment_slots - a dictionary. {'slot_1':Item, 'slot_2':Item, ...}
    • default_equipment - a dictionary {'slot_1':Spell, 'slot_2':Spell, ...}
    • spellbook - Same as Monster.moveset.
    • party - A reference to the Party to which the PlayerCharacter belongs.
    • battle_art - Same as Monster.art.
    • battle_pixbuf - Same as Monster.pixbuf.
    • battle_resolution - Same as Monster.resolution.
    • make_option_list() - Creates a dictionary of dictionaries representing the options a player has to choose from when acting in battle.  Right now, this is constructed from spellbook, equipment_slots, and default_equipment.
The dictionary returned by make_option_list() is passed to a MenuDrawer, which is like a TextDrawer, except it allows the user to select a value.  I'm not going to go into too much detail about MenuDrawers because they're not that interesting.  I haven't implemented Items yet, but the idea is to have players choose a specific Item to use each round in combat rather than generic verbs like "Attack" or "Defend".  make_option_list() loops over the equipment_slots making a list of all the useable Items therein, or, if an equipment slot is None, it uses the same key to see if there's a Spell in default_equipment.  If there is, it adds that to the list, too.  PlayerCharacters are defined in global.xml like so:

<playercharacter id='0' name='Kenfold'>
     <path type='map_sprite' direction='ALL'>./content/test/sprites/kenfold.png</path>
     <path type='battle_sprite'>./content/test/sprites/kenfold_battle.png</path>
     <statistic name='move_power' default='0' />
     <statistic name='attack_power' default='10' />
     <statistic name='hit_points' default='25' />
     ...
     <equipment_slot name='head' />
     <equipment_slot name='left_hand' default='5' />
     ...
     <spellbook>4</spellbook>
</playercharacter>

Looking at this, I realize I've forgotten to specify maximum values for statistics.  That is, Kenfold has default hit_points of 25, but there's no statement about how many maximum hit_points he can have.  This is also an example of how default equipment works.  If the PlayerCharacter has nothing equipped in his left_hand, he can still cast Spell id=5, which is "Unarmed" in my test scenario.  There are no technical barriers preventing me from adding a default to the head equipment_slot, either.  If I wanted to, I could give Kenfold a headbutt Spell there by default.

Monsters look much the same as PlayerCharacters in their XML, except instead of a spellbook, they have a moveset.  They're also defined in bestiary.xml instead of global.xml.  A quick example:

<monster id='1' name='Bearwolf'>
     <path type='battle_sprite'>./content/test/sprites/bearwolf.png</path>
     <statistic attack_power='15' />
     <statistic hit_points='35' />
     ...
     <moveset>1,2,3</moveset>
</monster>

Nothing new here.

BattleWidgets and Battles work together much in the same way that MapWidgets and Maps do.  Maps and Battles are responsible for keeping track of the actual game data, while their MapWidget and BattleWidget counterparts adjust their Maps and Battles according to input and represent them on the screen appropriately.  Here's Kenfold mid-battle:


It's not... riveting.  Not yet, anyways.  You can probably tell that, although the battle algorithm implementation is functional, there's still a lot of work to be done.  One thing that would have an immediate positive effect would be to create battle scene (background) art, and render it to the screen based on where (what part of what Map) the Battle is happening.  It would also be nice to take care of Items, Buffs, and battle rewards (think experience and/or gold).  I also need to implement random encounters, as well as test multi-Monster and multi-PlayerCharacter Battles.  I'll take care of one or more of those things tomorrow.

Hopefully the last couple of blog posts have given you a general idea about how Battles work.  I'll go into more detail next time about the single most important function call in Battles: Spell.eval().

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.

Monday, July 9, 2012

EPI #14: Battles, part 0

Sorry for the lull in updating.  I spent most of the daylight hours on Saturday playing Ultimate in 104 degree heat, and most of the nighttime hours, well, sleeping.  And today I've been working on battles!  Yeah!

Battles are the other white meat of JRPGs.  What doesn't happen on a map generally happens in combat, so I need to make a combat system that's at least a little bit entertaining and a lot bit flexible.  They're also enormously complex.  In order to flesh out a battle system, I need more or less to finalize concepts for Monsters, Items, Spells, and PlayerCharacters, among other things.  I've spent most of today bludgeoning my way through this tangled mess of interactions, and I'm surprisingly close to having something that works.   I'm gonna keep on keepin' on, and when I've got a functional example, I'll write it up and fill you all in on how it works.

In the meantime, here are a couple of 128x128 sprites I drew for battle art:



The one on the left is Kenfold's fearsome enemy, the Bearwolf.  Think of the bearwolf as a combination of a bear and a werewolf and you're on the right track.  The figure on the right is Kenfold, in battle mode.  What do you mean you didn't recognize his stylish red shoes?

Friday, July 6, 2012

EPI #13: Dialog

I had so much fun with listener action verbs (henceforth, LAVs) the other day that I decided to implement another one:  the "SAY" verb.  This does pretty much what you'd expect-- it draws text to the screen.  The concept shouldn't be foreign to anyone who's ever played an RPG:  You go up to an NPC, press a button, and read through someone's entire life story.

Even though it's a simple idea, getting arbitrary text to appear on an arbitrarily-sized screen turns out to be a remarkably difficult task.  The part of the implementation that concerns the event system is straightforward enough-- mostly it amounts to adding a SAY case to EPIEventHandler.eval_listener() and EPIEventListener.parse().  This isn't hard.  The hard part is that little niggling detail of actually getting text to appear on the screen.

It's challenging mostly because I have to pay attention to all of the little details about fonts and text that I usually take for granted.  Things like:
  • How many characters fit on a line?
  • How many lines fit in a given area?
  • Where is an appropriate place a line break?
  • What happens if the whole text to display won't fit in the display area?
In practice, answering these questions boils down to learning a few key details about the context I'm rendering fonts in.  For a given font face, point size, slant, and weight, I need to know:
  • The average dimensions of a glyph (width, ascent - descent)
  • The dimensions of my display area.
I say "average" because, unless I'm working with a monospaced font, the width and height will change slightly from glyph to glyph.

I started my solution with (surprise!) a class definition. class TextDrawer:
  • TextDrawer
    • context - the gtk.gdk.CairoContext I want to draw with.
    • origin - an OrderedPair representing the upper-left corner of the display area.
    • extents - an OrderedPair representing the width and height of the display area.
    • text - the text to draw.
    • padding - an integer number of pixels to offset the text from each border of the display area (the same idea as CSS padding).
    • is_drawing_text - a boolean indicating whether or not the TextDrawer is currently drawing text.
    • width_per_char - integer
    • height_per_line - integer
    • chars_per_line - integer
    • max_lines - integer
    • set_font(self, face, size, slant, weight)
    • draw_text(self)
    • advance_text(self)
It turns out that getting width_per_char, height_per_line, chars_per_line, and max_lines is really easy because gtk.gdk.CairoContexts know a text_extents() method, which tells you all the information you need to derive these values.  The TextDrawer performs the derivation in set_font(), and then uses these values to determine and draw one "page" of text in draw_text().  advance_text() discards the current page of text, allowing draw_text() to draw the next page.  is_drawing_text is True for as long as the TextDrawer has text to draw, and is changed to False by advance_text() when it's called with no more text to draw.

I use the term "page" here because it's a convenient abstraction.  In my implementation, all I'm doing is chopping up text into carefully chosen substrings and rendering those to the screen.  If you're really curious about how the algorithm for doing that works, I'll tell you in the comments if you ask.

So how does TextDrawer integrate with the rest of the EPI graphics infrastructure?  It's pretty simple, actually.  A MapWidget has a TextDrawer, and as part of its "expose_event" handler, it tells its TextDrawer to draw_text(), which then does or does not depending on whether or not it has text to draw.  The final bit of integration happens in MapWidget.key_press_handler().  This function's behavior depends on TextDrawer.is_drawing_text.  If is_drawing_text is True, all keypresses call TextDrawer.advance_text().  Otherwise, they behave normally.

To test this, I added the following listener to mapentity id=1:

<listener type='use' state='0'>SAY "It's empty!"</listener>

So then if I maneuver Kenfold next to my treasure chest, face him the appropriate way, and press 'e' (the default use/talk/select key)...
Being trolled by game developers is never fun.
From here, pressing any key closes the dialog and returns control to the player.  I also had an example with Ariel and 500 words of lorem ipsum to test multi-page text rendering, but my mspaint art for Ariel will actually give you nightmares, and lorem ipsum isn't all that exciting anyways.  You'll just have to take my word that it that functions correctly.

Thursday, July 5, 2012

EPI #12.5: Events, part 2

Okay!  I implemented the changes I mentioned last post:
  • EPIEventListener
    • event - The exact event the listener listens for.
    • verb - the category of action to take
    • direct object(s), depending on the verb
    • prepositional phrase(s), depending on the verb
Now, to see if it needs to interpret an EPIEventListener, the EPIEventHandler compares the EPIEvent it's handling to the EPIEventListener.event in question.  If they're the same, it calls eval_listener() on that EPIEventListener.  This also means that I needed to override EPIEvent.__eq__().  I don't care that the EPIEvent instances are identical, only that they carry the exact same data.  This will probably come back to haunt me later when I implement new event types and find out that their listeners don't work because I've implemented EPIEvent.__eq__() wrong.

So, in order to test my event system, I added a listener to my test map:

<listener type='move' x='0' y='1'>CHANGE_MAP TO 11 2 IN ./content/test/zone_01/map_02.xml</listener>

I also added a new map, ./content/test/zone_01/map_02.xml, which also has a listener:

<listener type='move' x='11' y='2'>CHANGE_MAP TO 0 1 IN ./content/test/zone_01/map_01.xml</listener>

These two listeners allow the player to move back and forth between maps as he pleases.  So, if we position Kenfold at (1, 1) on map_01:


And move him one step west to (0, 1) on map_01...


He teleports to (11, 2) on map_02!  Cool stuff!  You'll have to take my word for it, but when he moves away and then moves back to (11, 2), he'll teleport back to (0, 1) on map_01.  He doesn't immediately enter a teleporting loop because the CHANGE_MAP action doesn't itself emit a move event.  Move events are only caused by movement as a result of player keystrokes.

This example might seem trivial, but it's actually all the infrastructure I need to design the EPI's entire map system.

Side note:  you may have noticed that these maps don't appear to be rectangles, and you may be confused because I said in my post about maps that they were required to be rectangular.  Well, actually, these maps are rectangles.  I lined the necessary edges with impassable transparent tiles.  This lets me emphasize which tiles will take the player to a new area.

One other thing:  I've mentioned before that the event system is the heart and soul of compelling scenarios.  The more events and listener verbs I implement, the more creative designers can be with their scenarios.  More creativity leads to more interesting stuff, which leads to more fun.  This is where you, dear readers, come in.  I can't think of all the different events or verbs designers are going to want to be able to work with.  What I'm trying to say is that your ideas are welcome.  Think about what you, as a designer, would like to be able to manipulate, and what events you, as a designer would like to be able to work with.  Let me know in the comments!

EPI #12: Events, part 1

Before I'm done with my maps, I need to implement an event system.  What is that, exactly?  The event system is the interface by which designers will direct the game engine to do things in response to player actions.  In simpler English, the event system is how designers tell the game when to do stuff and what stuff to do.  The event system is made up of two main parts: events and listeners.  Events are (generally) generated by player activity.  Listeners specify a kind of event to listen for, and an action to take.

So, naturally, I made a class EPIEvent:
  • EPIEvent
    • source - the object the event originated from
    • type - the kind of event
    • target - the event's target (if it's a targeted event)
I'll get to Listeners in a moment.  EPIEvents are really simple, but this will probably make more sense if I give an example.  The only kind of event I have implemented so far is the "move" event.  This occurs whenever the player attempts to move on a map.  Whenever the player press a key that's bound to a direction to move in, the key_press_handler figures out the direction, calls the move() function, and then emits an EPIEvent.  This EPIEvent's source is the map that handled the move() call, its type is "move", and its target is the player party's new location.  The move event is an example of a targeted event-- it's only relevant to the target tile.  In this case, the move event is conveyed to the map on which it occurred, which may or may not have an EPIEventListener attached to the appropriate tile.  Other events, such as the "level_up" event, are untargeted.  Untargeted events are conveyed to every entity in the game.  If you're wondering how exactly the event "emission" is handled, don't worry (you're in good company; so did I), I'll get to that in a moment.  But first I need to talk about EPIEventListeners.

Events are only one half of the event system.  While events tell the engine when to do something, listeners receive events and tell the engine what to do.  class EPIEventListener:
  • EPIEventListener
    • verb - the category of action to take
    • direct object(s), depending on the verb
    • prepositional phrase(s), depending on the verb
Listeners also have two parts-- the event they're listening for, and the action they take.  EPIEventListeners don't actually keep track of event they're listening for themselves because they're stored in a dictionary of EPIEventListener lists indexed by event type.  It's a bad idea not to have EPIEventListeners keep track of the conditions under which they evaluate their action.  I'll probably fix this as soon as I'm done writing this post.

As far as actions are concerned, I've chosen a natural language approach to defining listener actions.  There will be a finite list of verbs designers can specify, and the rest of the listener action definition depends on the verb.  An example of this is the CHANGE_MAP verb (which, coincidentally, is the only one I've implemented so far).  The archetype for a CHANGE_MAP action looks like this:  CHANGE_MAP AT [src_x] [src_y] TO [dest_x] [dest_y] IN [path].  This has the effect of moving the player party to (dest_x, dest_y) on the map specified by path when they're at (src_x, src_y) in the current map.

This is a terrible example for a couple of reasons.  First, the CHANGE_MAP verb doesn't have a direct object.  Instead, it has three prepositional phrases.  This is unusual; I expect most of the verbs I implement to take a direct object of some sort.  Second (and more important), this CHANGE_MAP archetype tries to assert something about the condition under which it happens in the AT [src_x] [src_y] prepositional phrase.  This is problematic because what if, for example, I want to CHANGE_MAP in response to an event which has no location associated with it?  At best, the CHANGE_MAP would never happen because it wouldn't be able to confirm that the player party was at (src_x, src_y).  At worst, it would crash the game.

A note on listener action definition parsing:  Since listener action definitions essentially amount to a very simple scripting language, they're an ideal application for a grammar.  If I wanted to parse them right (and make the most of formal Computer Science training), I'd define a grammar for them and then write a class to parse that grammar and attach semantics to it.  However, given the scope of my project, this approach requires way too much effort. Instead, I parse these with a combination of switch statements and regular expressions.

Currently, XML listener definitions look like this:

<listener type='EVENT_TYPE'>ACTION_ARCHETYPE</listener>

An example:

<listener type='move'>CHANGE_MAP AT 0 1 TO 11 2 IN ./content/test/map_02/map_02.xml</listener>

Sorry about the line wrapping.  In order to fix the problem I mentioned last paragraph, I'll need to change the XML listener definition to look more like this:

<listener type='EVENT_TYPE' [arg_key_1='ARG_VAL_1' arg_key_2='ARG_VAL_2' ...]>ACTION_ARCHETYPE</listener>

The stuff in brackets is optional and depends on EVENT_TYPE.  The example, translated to the new format:

<listener type='move' x='0' y='1'>CHANGE_MAP TO 11 2 IN ./content/test/map_02/map_02.xml</listener>

Again, I'll implement this change when I'm done writing.

Throughout this post I've been talking about how player actions cause events to be "emitted" and how listeners "receive" events and act on them.  Even though these concepts are relatively simple, implementing them in code turns out to be a challenging task.  One of the hallmarks of good class design is keeping the class aware only of the information it needs to do its job.  Events, however, need to be communicated to every single thing in the game that's capable of having a listener attached to it.  These two goals are somewhat at odds with each other.  One option is to make a global list of everything that can listen for events.  That way, whatever's signaling an event can just reach into the list and pass its event on to each object in turn.  However, this also requires all of these objects to be capable of handling the events they receive.  Also, global variables are generally frowned upon.  Instead, I wrote class EPIEventHandler:
  • EPIEventHandler
    • entities - a list of all the MapEntitys in the game.  As I realize that there are more things that can receive events, I'll need to add them.
    • handle_event(EPIEvent)
    • eval_listener( EPIEvent, EPIEventListener)
After the MGEET initializes the scenario, it creates an EPIEventHandler and gives it to things which can emit events as it creates them.  This way, when different widgets need to emit an event, they can just access their reference to the EPIEventHandler and pass an appropriate EPIEvent to handle_event().  Also, this approach abstracts listener interpretation into the EPIEventHandler.  Where before entities would have had to be responsible for handling their own events, now the EPIEventHandler is responsible for evaluating listeners, and entities only need to keep track of which listeners are their own.

Right.  So that's an introduction to the events.  I apologize if is confusing to the point of making no sense.  I'm still a little confused about it myself.  I'm gonna go implement the changes I talked about in this post, and come back with a working example and some screenshots!