Alchemy Day 3: Of monsters, items and effects

Posted: March 14, 2013 in Uncategorized

Today was another day of good progress. The race against time is definitely on – I’ve spent probably too much time on the core engine, which is pretty cool but probably overkill for a 7DRL.

Nevertheless, I’m feeling optimistic that I will be able to finish a working game in time, although it might no be possible to get in all the advanced features I would like.
2013-03-14 More Progress

As you can see, it is now starting to look like a proper Roguelike. I’ve decided to go for a fairly “classic” look.

Item management and actions

I spent a good chunk of time today getting the item management working.

Partly this was my own fault: I spend a couple of hours tracking down a painful bug that was causing the game state to become inconsistent. In Alchemy the game state management is complex (necessarily!) because the entire game data structure is immutable. Any change to an item in the hierarchy requires a rebuilding of potentially large chunks of the game state graph.

As it happens, my bug was caused by holding a reference to an old version of a persistent data structure. When I subsequently used this reference, it was overwriting new (fresh) data in the game tree, causing the inconsistency. A nasty bug to track down indeed.

Anyway, I ended up pretty pleased with the mechanics:

  • Any thing can be located either on the map, or contained within another thing. Nesting is arbitrary (probably not needed, but doing it was easier than not doing it….)
  • You can access items with a nice functional programming style, e.g. to get all the food that the hero is carrying you can do “(filter :is-food (contents hero))”
  • Every Thing has a unique ID
  • The game state maintains a fast index of ID -> Thing, so you can look up a Thing without having to walk the whole game state graph. This is pretty important for performance.
  • I’ve hidden most of the state management complexity behind a small number of well defined functions (add-thing, move-thing, update-thing, remove-thing etc.). Hopefully this means that the complexity of game state management can be hidden away from the rest of the code base.

On top of this I built some simple inventory management commands / GUI display.

2013-04-14 Inventory Screen

Nothing too sophisticated. However I was quite pleased with my implementation of the item selection screen with higher order functions:

(defn item-select-handler [state msg items pos action]
  (let [c (count items)]
    (redraw-item-select-screen state msg items pos)
	  (reset! (:event-handler state)
	          (fn [^String k]
	            (let [sel (.indexOf "abcdefghijklmnopqrstuvwxyz" k)]
	              (cond
	                (and (>= sel 0) (< (+ pos sel) c))
	                  (action (+ sel pos))
	                (and (> pos 0) (.contains "124" k))
                    (item-select-handler state msg items (- pos 26) action)
                  (and (< (+ pos 26) c) (.contains "689" k))
                    (item-select-handler state msg items (+ pos 26) action)
	                (= "Q" k)
	                  (main-handler state)
	                :else
	                  :ignored))))))

The clever point about the code above is that it takes an “action” as a parameter that says what to do when an item is selected. It reconfigures the event handler to perform that action, so that the next keypress does the right thing.

And this is re-usable, so the same code can be used for any command that needs an item to be selected. Here’s the code to handle item dropping for example:

(defn choose-drop [state]
  (let [game @(:game state)
        hero (engine/hero game)
        inv (vec (filter :is-item (contents hero)))]
    (item-select-handler state "Drop an item:"
                      (vec (map (partial engine/base-name game) inv))
                      0
                      (fn [n]
                        (swap! (:game state) world/handle-drop (inv n))
                        (main-handler state)))))

In a similar way you can reconfigure the code for eating, quaffing, picking up items, mixing potions etc. just by passing different lists of items, different messages and a different handler.

Effects

I spent some time today getting the effect system working properly. This was a complex task and is somewhat tied to the game state management (taken together this will end up being the trickiest part of the entire code base by far).

The effect concept is an idea I borrowed from Tyrant, where it works exceptionally well.

  • An effect is just a “Thing” that can be added as a child to another thin (an item can also be an effect source if needed)
  • An effect applies one or more modifiers to its parent
  • Modifiers can change any or all properties of the parent thing
  • Modifiers can be stacked, and perform arbitrary calculations in sequence
  • Effects are first-class things, so they can be active (respond to time ticks) which enables things like effects that wear off over time

Here’s an example of declaring an effect:

    (proclaim "invincible" "base temporary effect"
              {:lifetime 2000
               :parent-modifiers [(modifier :colour-fg (colour (Rand/r 0x1000000)))
                                  (modifier :ARM (+ value 100))]
               }

The effect has modifies two properties:

  • It calls the random number generator whenever the forground colour of the parent is queried. This is a fun effect that causes the player to flash different colours at every step
  • It adds 100 to the armour of the parent (:ARM) to protect it from damage

See how quite a bit of complex functionality got added in very few lines of code? This is going to be key to allow development of a lot of game content in the next few days.

Also a powerful point about this technique is that it avoids having to insert conditional checks / function calls all over the code base to test for effects. You just define directly how properties will be modified. The effects are defined in one place, and the rest of the code base doesn’t need to know anything about the specific effects that might be applied. It’s a bit like dependency injection in that sense.

Monster AI and combat (part 1)

Monster AI got installed, so the monsters chase you around the map and attack you. No damage yet.

Nothing too fancy here. Key points:

  • The monsters use the LOS / visibility code to determine when they can see the player
  • Current strategy is just a direct “charge at player”
  • Monsters and player share the same movement and combat code. I’ve been careful to minimise the differences between the player and the monsters so that this code can be re-used.

I’m probably going to keep AI fairly simple as I know it can be a big time sink. I’d rather put the design time into the variety of monsters, items and effects to make them a strategic challenge rather than fancier AI.

Doors

I put in a quick door implementation. The definition was quite neat so I though worth sharing:

    (proclaim "base door" "base scenery"
              {:is-door true
               :closed-properties {:char (char 0x256C)
                                   :is-open false
                                   :colour-fg (colour 0x000000)
                                   :colour-bg (colour 0xC07020)
                                   :is-blocking true
                                   :is-view-blocking true}
               :open-properties {:char (char 0x2551)
                                 :is-open true
                                 :is-locked false
                                 :colour-bg (colour 0x000000)
                                 :colour-fg (colour 0xC07020)
                                 :is-blocking false
                                 :is-view-blocking false
                                 }
               :on-open (fn [game door actor]
                          (update-thing game (merge door
                                                    (if (? door :is-open)
                                                      (? door :closed-properties)
                                                      (? door :open-properties)))))
               :on-create (fn [door]
                            (merge door ((if (:is-open door) :open-properties :closed-properties) door)))
               :z-order 70})

The clever bit here is that transforming the door from one state to another is just a case of merging in either the :closed-properties or the :open-properties. This is nice and flexible, and allows many variations (e.g. prison gates that are locked but allow vision through the bars etc.)

Planning

I’m pleased with how the engine is shaping up, but still haven’t got as far as I would like on the actual gameplay.  Will have to focus on this tomorrow, maybe with a bit of dungeon generation thrown in.

About these ads
Comments
  1. Febrero 9th says:

    Do you mind if I quote a few of your articles as long as I
    provide credit and sources back to your webpage? My website is in the
    very same niche as yours and my visitors would definitely benefit from a lot of
    the information you present here. Please let me know if this ok with you.
    Thanks a lot!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s