Once again, we are back to knife-fighting in the pit. This is the traditional game design approach at Gaslamp Games; we fight to defend our ideas, using oversized weapons and our bare hands. Recently, however, somebody has been seen fashioning a rudimentary lathe – a troubling development that will either upset the balance of power or be absolutely useless.
So what have we been fighting about? Well, all sorts of things. Today, let’s talk about the AI. The AI Cabal – Nicholas, Chris Whitman, and myself – have been hashing things out, and what we have is a data-driven, XML-based monstrosity that is sure to please everybody. The whole goal of Clockwork Empires’ AI is to provide characters in the game (currently referred to, in-engine, as Citizens, although this is not something that makes David happy; after all, we are a monarchy) with unique, rational, and relatable behaviours. The plan is to start simply, and add layers of complexity to the game until the goals and aspirations of characters appear to the player naturally and gracefully.
A citizen job consists of three basic parts: a collection of requirements, a utility function, and a finite state machine that consists of a collection of other, smaller, finite state machines. Hooray for complexity.
When a citizen has no job, they poll the job blackboard (an invisible construct, as far as the player is concerned) asking for work. The jobs dispatcher looks at the list of all jobs that a citizen can perform (based on what, we don’t know – right now, it’s basically just a whitelist), and returns one job that it would like the citizen to do. The citizen then looks at all the jobs that it wants to do that aren’t on the blackboard – things like eating, sleeping, and ingesting opium pods – and ranks each of these jobs according to two things:
- Do I have enough resources to complete the job? If not, this job is invalid. (i.e. if there is no lumber, I can’t cut trees; if there are no opium pods, I cannot ingest opium pods.)
- How happy will this job make me?
The happiness of a job is determined using utility functions. The utility of a task is determined by a number of factors – basic constraints, variable constraints (how hungry am I? how mad am I?), and distance constraints (how far am I from the match factory?) Characters then color their utility function based on their own individual preferences: favourite pub, peaceful disposition, distrust of honeybees, general laziness, piousness, or whatever else we want. Some of these pieces of information come from a character traits system; characters with certain traits may be more likely (or less likely) to do certain things.
When we can do a job with more than one type of resource, we pick the best resource – the combination of resources which produces the highest utility. If a character can choose between drinking whiskey and gin, and the gin is closer but the whiskey is better, which one he chooses depends on how we set the weights of the utility function elements.
The character will then base their actions on these values. The FSM starts running, going through stages – walking to required objects, collecting required objects, walking to a job site, playing animations, and doing things. At any time, a citizen’s job may be interrupted – either by receiving a message requiring the citizen to re-evaluate his situation (“You are on fire now.” “You are being eaten by a bear.”) or if one of a certain category of possible jobs, which are evaluated sporadically during FSM execution, kicks in and is determined to be more useful than what he or she is currently doing. For instance, if a character is starving, at some point the “must find food” job becomes more useful than the “make widgets” job. At this point, we stop what we’re doing and switch tacts.
A job program (or FSM) consists of a collection of tasks, each of which (internally) are handled by other, smaller, finite state machines. Yay, progress.
Jobs also have event blocks attached to their XML. This tells the game some basic information about how the event affects the world. Events are tagged with a longevity and an importance; longevity indicates how long the event is remembered, and importance indicates how important the event is. (Importance scales exponentially.)
Since we’re creating a moddable game, we’re eating our own dog-food. All of this is data driven. A sample job might look like this:
<job name="Pick Berries" display_name="Pick Berries" mandatory_eval="0"> <requires_resource_source type="plant" flag="BERRIES" amount="1" tag="bush"/> <requires_stockpile type="food" tag="stockpile"/> <utility> <base_job_utility weight="1"/> <distance_from tag="bush" weight="-1"/> </utility> <fsm> <walk_to tag="bush"/> <harvest tag="bush"/> <walk_to tag="stockpile"/> <drop_item_in_stockpile tag="stockpile"/> </fsm> <event importance="1" longevity="1"> <newspaper headline="[CITIZEN] Engages in Leisurely Berry-Picking" subline="Grand Day of Not Starving Reportedly Enjoyed by All" norton="about the unsightly picking of berries by [CITIZEN]"/> <poem type="commemorating the plucking of berries from a bush by [CITIZEN]"/> <art type="the plucking of berries from a #TAG bush# by [CITIZEN]"/> <element name="berries" plural="1"/> <element name="[CITIZEN]" plural="0"/> </event> </job>
And there you have it. Clearly, berry-picking is not an important event; however, if nothing else is going on, it might show up in the Empire Times. The “norton” tag is used to indicate the name of the gentleman who consistently writes to the Empire Times; his primary function is to complain about things. This is based on, and named after, a real chronic letter-writer to the Times of London circa 1898. Element tags in the event block indicate what the things are in the event. For instance, this event involves berries, and whoever executes the event [CITIZEN]. Consequently, you might produce art, commemorating the plucking of berries by Jeremiah Widgetsocket, where Jeremiah’s expression is plaintive, and the berries are… berries, I guess.
Internally, this is all handled by our message-passing and scheduling architecture to try to make these decisions as efficient and as parallelizable as possible. It turns out that making useful decisions about what a character should do in any given frame is difficult and expensive; fortunately, we have the technology to make things faster. But that’s a story for another day.
(Also, Sean drew these. This can’t be good.)