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!

Wednesday, July 4, 2012

EPI #11: Movement

Now that my roadblocks related to determining game state and handling MapEntitys are all taken care of, it's time draw the PC and the MapEntity to the map, and make the PC move around.  In order to do this, I need to:
  1. Load config.ini
  2. Get a path to a scenario if I don't already have one.
  3. Load the scenario's global.xml
  4. Load the scenario's master entity list (I keep this in entities.xml).
  5. (Load the scenario's master item list.)
  6. (Load the scenario's bestiary.)
  7. Initialize the window.
  8. Initialize the game.
The steps in parentheses aren't strictly necessary for displaying a Map in the appropriate game context, so I put placeholders where those things need to happen instead.  At this point I've taken care of everything except step 8.  That's going to be handled by the magical function init_game(), which I discussed in a previous post.  The gist of it is that it takes the main gtk.Window, the master entity list, the scenario properties, and optionally a save game file to load, and identifies the appropriate map to load, sets appropriate MapEntity states, etc.  It does all the things necessary to either resume a game where it was left off, or start a new game entirely.  Look what it does:


There he is!  A little man, in the appropriate default location on the default map!  A new game started.  Making him move around is pretty simple.  I do a window.connect("key_press_event", lambda w,e: mw.key_press(e)), which tells the my gtk.Window to handle key press events by calling MapWidget.key_press() function, and passing it the gtk.gdk.Event it received originally.  There's almost certainly a better way to do this, and I'm going to need to figure it out as I begin changing around which widgets are attached to my window.  I'll probably end up implementing a class EPIWindow(gtk.Window), which will be capable of managing multiple different widgets (e.g. MapWidget, BattleWidget, PauseWidget, TitleWidget, etc), and keeping its event handlers straight.  I don't want to call MapWidget.key_press() if I'm displaying a BattleWidget, after all.

For the curious, MapWidget.key_press(event) derives a direction from the button that was pressed, calls Map.move(target, dir), and then redraws itself with queue_draw().  Map.move(target, dir) sets the target's direction to dir, and attempts to move the target in that direction.  A move is successful unless one of the following conditions is met:

  1. The new location (which I get from the sum of the target's current location plus the movement vector [which is derived from the movement direction]) is out of the bounds of the map.
  2. A MapEntity is already in the new location.
  3. The new location's tile's passability is greater than the target's move_power.
Cool.  I'd upload a video of me moving Kenfold around the beach, but I don't have any video capture software installed on my computer.  I might get some later, when I have something more exciting to share.

Adding the line <entity id='1' x='7' y='2' /> to my map specification adds entity 1 at (7, 2) on my map:

Looks like I didn't get the shading quite right on the chest.

It's a treasure chest!  Wow!  Kenfold is rich!

Next up, I'm going to start thinking about which events I'm going to support, and how to handle them.  Or maybe I'll write that EPIWindow class...  There's so much to do!  And I start work in 12 days!

EPI #10: MapEntitys

In order to finalize my concept for MapEntitys, I need to figure out how MapEntitys, player characters (PCs), and non-player characters (NPCs) are related to each other.  Since a MapEntity (per my definition from last post) is anything the player can interact with, PCs and NPCs must both be related to them in some way.

My original idea was to write a class Character(MapEntity) to define extra resources needed to differentiate a Character from a MapEntity, and then write a class PlayerCharacter(Character), to differentiate player characters from other characters.  (For those of you who aren't fluent in Python syntax, Character extends MapEntity, and PlayerCharacter extends Character.)  This is a textbook example of why it's important to understand inheritance.

While this approach is neat, logical, and takes advantage of the object-oriented paradigm, it's problematic in a couple of ways.  If you look back to the definition for class Map, you'll notice that Maps have an actors property.  actors is a list of all the MapEntitys on the Map, and it's how the MapWidget knows what to draw other than the Map's MapTiles.  The MGEET also asks each entry in the actors list every so often if it wants to move somewhere.  This is how NPC movement is implemented.  What I'm getting at is that if I have a mix of Characters and MapEntitys in my actors list, I have to handle each one differently.  This is a little messy, but not a huge problem.

The real problem comes in the XML definitions.  What's the best way to distinguish MapEntitys from Characters in entities.xml?  And then what's the best way to parse them differently?  I thought about this for awhile, and came up with several more or less elegant ways to do this, but I wasn't really happy with any of them.  There had to be a better way.

And there is.  I made an assumption at the beginning of this post that it is necessary for NPCs to be logically distinct from MapEntitys.  Thinking harder about it, this assumption isn't valid.  There's nothing an NPC needs to be able to do that a MapEntity shouldn't also be able to do.  So I threw out class Character(MapEntity), and changed PlayerCharacter to class PlayerCharacter(MapEntity).  This decision means that the distinction between, say, a treasure chest and a shopkeeper rests in the definition for their respective behaviors.

A sample MapEntity definition in entities.xml might look like this:

<mapentity id='1' name='treasure_chest'>
     <default state='0' direction='SOUTH'/>
     <path type='map_sprite' direction='ALL'>./content/test/sprites/chest_01.png</path>
     <event type='use' state='0'>(GRANT ITEMS 1,2,3 AND CHANGE STATE id=1 TO 1) ELSE SHOW TEXT "You can't carry all the items!"</event>
     <event type='use' state='1'>SHOW TEXT "It's empty!"</event>
</mapentity>

(I apologize for the line wrapping.)  This is pretty simple.  direction indicates the direction the MapEntity is facing.  This is used in conjunction with the map_sprite definitions.  Designers can specify up to four sprites to display when the MapEntity is facing NORTH, SOUTH, EAST, or WEST.  Designers can also use the special keyword "ALL" to assign all four directions at once.  Events, for the time being, are specified in event elements.  The type and state attributes indicate which event to listen for and which state the MapEntity must be in for the event logic to be interpreted.  I'm still thinking about how I'm going to define event logic, but for the moment I'm thinking about a system that looks a little bit like SQL.

Tuesday, July 3, 2012

EPI #9: States

After yesterday's whirlwind of coding and tangible results, I started out this morning with the intention of adding a little man to my map and having him move around in response to keyboard input.  From a purely technical point of view, this is not a hard thing to do.  However, a little bit of thinking made two things clear to me: 1) When I write a particular component of the engine, I need to do it in the context of the entire engine; and 2) this can be very hard to do.  When I skipped steps in my flowchart yesterday, I did myself a bit of a disservice, because now I need to modify code I've already written in order to un-skip those steps.  There's a trade-off at work here.  On the one hand, it's important for me to be able to generalize my way past things I don't want or need to implement eo ipso tempore, but on the other hand, I can't just ignore inconvenient stuff.

So I decided to warm up by taking care of a smaller detail I mentioned yesterday: handling window resizes.  A little bit of Googling revealed my main gtk.Window receives a check_resize event every time it's resized.  I attached a handler to this to update the MapWidget's geometry and tell it to redraw itself, and it worked.  Immediately afterward, I realized that the an expose_event was being received by the MapWidget every time I resized the gtk.Window.  So I changed my approach and inserted logic to update the MapWidget's geometry to match that of its parent gtk.Window every time it tries to redraw itself.  This turns out to be a much more elegant solution than attaching a handler to the gtk.Window.  I'd put of a video of me resizing windows like crazy and everything being drawn correctly, but it's actually not that exciting.

Having warmed up, I thought some more about what needs to happen before I'd be able to draw a map with a man on it to the screen.  Basically, before anything gets drawn, the MGEET needs to determine the game state.  What do I mean by this?  I'll explain with an example.  Think back to Pokemon Red (or Blue, or Yellow, or Green, if you're Japanese).  In Pewter City, if you try to advance to route 3 (the approach to Mt. Moon) before defeating Brock, there's a trainer who blocks your way.  After you defeat Brock, when you get to route 3, the trainer has mysteriously moved, and you are free to continue your quest to catch 'em all.  What's happening behind the scenes here is pretty straightforward, but I'll explain it anyways:
  • The trainer has a state (probably just a number) associated with him.
  • When you enter Pewter City, he's in a particular state.  Let's call it state '1'.  For as long as he's in state 1, he stands in a certain place (exactly in your way), and speaks certain dialog when you talk to him.
  • When you defeat Brock, his state changes to a new state (we'll call it '2').  In state 2, he has a different set of attributes-- he stands in a different place and says different things when you talk to him.
When you save your game after beating Brock (because he's the first boss!  That was hard!  You don't want to have to do that again!), the game records Brock's defeat, as well as our trainer friend's new state, so that when you return to Kanto the next morning, your progress is exactly as you remember it.  Now, I don't know that this is exactly how GameFreak implemented Pokemon, but it's about as reasonable a guess as anyone could make.

Returning to Prospero's Island, the game state is the collection of the states of every single stateful thing in the game.  For my purposes in EPI, I define a MapEntity as a thing with which the player can interact.  Since players can interact with them, every MapEntity must have behavior associated with at least one state.  MapEntity state changes happen as a result of player actions and manifest themselves as changes in the game world.  NPCs, treasure chests, and wall switches are all examples of potential MapEntitys.

The reason, then, that I need to know the game state before I try to draw anything is because the game state carries with it almost all of the information I need to draw things.

But how can I know the game state without knowing about all the MapEntitys in the game?  I can't.  So I thought harder about how scenarios need to be structured.  Here's what I decided on:
  • global.xml contains a section for defaults and a section for settings.  Defaults contain paths to XML specifications for the bestiary, the master item list, the default map to draw, and the master entity list.
  • Settings affect the way the MGEET conducts its business behind the scenes.  A good example might be the damage formula, which is used to calculate the amount of damage something receives after it suffers an attack.
  • The bestiary, master item list, and master entity list contain specifications for every monster, item, and MapEntity, respectively, in the game.  Each one of these carries a unique numeric ID, by which other resources (such as maps) identify them.
With all that straightened out, then, it's fairly straightforward to create a game state.  Basically, I'll define a function init_game(), which, in exchange for certain arguments (the main window, a master dictionary entities, a dictionary of scenario-specific settings, and optionally any relevant save data), initializes the game state and draws the necessary map to the screen.

So maybe later tonight but probably tomorrow I'll finalize how I want to represent MapEntitys internally, and implement init_game().  Once that's done, I should be able to draw a little man on the screen and move around him around with the arrow keys.



Bonus:  I did actually draw the little man today.  His name is Kenfold:


He's blurry because he's only 32x32.

EPI #8: Maps, part 2

So I wanted to write a class that would take interpret an XML map specification and draw it to the screen.  Well, it didn't make much sense to me to dive right into without any context, so I sat down with a pen and a ruler and drew another flowchart.  This flowchart goes into detail about the steps the MGEET needs to go through before it draws a map:


I made this a couple of days before I wrote the last post, so some things don't line up.  In particular the "manifest.xml" I mention here is actually the "global.xml" I talked about last post.

I implemented everything up to "Is a scenario specified?"  Then I hard-coded a scenario into my config.ini and pretended that "Is a scenario specified?" pointed straight to "Draw map".  I'm not really concerned about the title screen right now because I have bigger and more exciting things to do, like have things move around on the map and get into fights with each other.

config.ini is a simple flat file containing one "key = value" statement per line.  These are then parsed into a dictionary called engine_settings.  Currently, I have the keys resolution_x, resolution_y, bg_r, bg_g, bg_b, scenario_dir, and last_savegame implemented, although I will certainly add more keys as they become necessary.  Users aren't meant to modify config.ini directly.  Instead, I'll have an interface to changing config.ini from within the application.

This afternoon, as I was still without power, I went to Starbucks, ordered a Grande Decaf Iced Mocha Latte (henceforth "GDIML") because I figured I needed to patronize them in exchange for free internet and free power, and started coding.  Five hours of banging my head against PyGTK documentation and tutorials later, I had a class that would draw a map correctly, and a driver to make it go:

This is ugly because I tried to do Art.
Astute readers will have noticed that this is the same map I described in the XML example I gave last post.  Thrilling, I know.  Implementation wise, it makes neat use of object inheritance-- I wrote a MapWidget class which extends the gtk.DrawingArea class provided by PyGTK.  MapWidget basically combines gtk.DrawingArea with the Map class I wrote about earlier.  There's also a fair amount of coordinate geometry that goes into centering the map in the window, and figuring out which tiles are appropriate to draw based on where the player character is and how much window space the application has available to it.  I'll attach scans of my notes in a jump at the end of this post.

While I was implementing MapWidget, several modifications to the Map and MapTile classes became necessary.  In a nutshell, the classes now look like this:

  • Map
    • tileset - dictionary
    • tiles_resolution - OrderedPair
    • default - OrderedPair
    • actors - list
    • map - integer matrix
    • specials - dictionary
    • bg - (red, green, blue) tuple
  • MapTile
    • art_filename - string
    • passability - integer
    • pixel_buffer - gtk.gdk.Pixbuf
    • resolution - OrderedPair

Each Map keeps track of its tiles in its tileset dictionary, which is indexed by tile id.  The actual map matrix consists only of integers-- each tile can be accessed as Map.tileset[map[x][y]].  This technique, known as deduplication, saves a lot of memory as long as the tileset is small compared to the total number of tiles in the map (and this condition should pretty much always hold true).  I'm not really sure why both Maps and MapTiles keep track of their resolutions.  This is redundant, and I'll fix it when I feel like fixing little things.

It would be nice still to dynamically redraw the map every time the window is resized, and also maybe add a zoom feature.

Replacing my bad mspaint tile art with results from Google image searches for sand and stone textures has an immediate positive impact on the map's appearance:

Looking better!
A little creativity with the tileset definition goes a long way.  For my next trick, I'll use more than two tiles to create a beach:

The very essence of summer!
It's primitive, I know, but I hope I'm demonstrating that the returns scale with the amount of effort artists put in to creating environments.

With MapWidget more or less implemented, it's a good time to mention some pros and cons of the strictly tile-based system I've adopted.
  • Pros 
    • It's easy to implement.
  • Cons
    • It's fugly.
    • Character movement is unsmooth.
    • Tile-to-position ratio is approximately 1:1.
    • XML Maps are hard to manage.
    • One MapEntity per tile.
Tile-to-position ratio is ratio of tiles on the map to unique positions a character can be in.

A slightly better approach would be to define and draw maps using tiles, but rather than express MapEntity (look at EPI #6 if you don't remember these) locations in terms of MapTile coordinates, express them as pixel coordinates.  Then, if we give each MapEntity a "speed" (the number of pixels it moves at once), we solve many of the aforementioned cons:
  • Character movement becomes much smoother, since they move fractions of a tile at a time.
  • Tile-to-position ratio skyrockets-- (tile_res_x * tile_res_y)/speed:1.  With a 32x32 tiles and a speed of 2, this approach takes us from 1:1 to 512:1!
  • The increase in tile-to-position ratio also helps with managing XML Maps.  The above example essentially allows a designer to replace a 32x32 grid of tiles with a single tile!
  • Large tiles become more attractive-- they're bigger, more detailed, and you need to keep track of fewer of them to create a whole map.
The only real downside to this approach is that it's a little bit harder to implement.  And I do mean "little bit". It's really not that bad.  I'll probably take this approach as I start to worry about drawing MapEntitys later this week.

As promised, see below the break for the coordinate geometry notes I made by candlelight.  I know you all are probably very excited for this.