Moods and Stances
Overview
In many games it may be enough (or more than enough) to track what various actors know and believe, but human beings have affective as well as cognitive properties, and if we're trying to create lifelike NPCs in our game, it may be that we want to track something about their emotional lives too. Moods and Stances in adv3Lite provide a framework for tracking each Actor's current emotional state (Mood) and (positive or negative) attitude towards the player character (and, if we so wish, towards other NPCs).
An Actor's mood and/or stance can be tested (and changed) in game code to affect the way the Actor behaves, for example in his or her converational responses or AgendaItems.
Moods
A Mood is a simple representation of an Actor's current emotional state. Moods are objects of the Mood class. Each Mood object has a programmatic name ending in Mood (e.g., happyMood) with a corresponding name< property giving the name of the Mood as a single-quoted string (e.g., 'happy'). A number of Moods come predefined in the language dependent part of the library (english.t): neutral, calm, happy, euphoric, contented, sad, depressed, angry, furious, afraid, terrified, confident, bold. lonely, bored, and excited. You can easily define more if you want them. For example, to define an irritable Mood you'd just use:
DefMood(irritable);
This would create the following object:
irritableMood:Mood name = 'irritable' ;
The Actor class defines the following properties and methods to support the use of Moods:
- mood: The Actor's current mood (e.g. neutralMood). This should be treated as read-only, since it returns the mood defined on the actor's current ActorState (if there is one) or else the value of actorMood. To change the Actor's mood either set actorMood or use the setMood() method.
- actorMood: The mood this actor is in if and when he or she isn't in any ActorState, or if his/her current ActorState's mood is nil (the default), or the Actor's stateDependentMoods property is set to nil.
- setMood(mood_): sets the Actor's current ActorsState's mood to mood_, or the Actor's actorMood to mood_ if the Actor has no current ActorState. If you want to avoid changing the current ActorState's mood just use actorMood = newMood instead, or set the Actor's stateDependentMoods property to nil. Note thst setMood(mood_) will complain (and not set the mood) if mood_ is not a Mood object.
- stateDependentMoods: If you don't want moods to be related to ActorStates (which would be perfectly reasonable if, for example, you were using ActorStates to model what actors were doing and the like regardless of their mood) then you can set this to nil, either on individual Actors or on the Actor class.
The default initial actorMood for an Actor is the value of libGlobal.defaultMood, which in the english library is neutralMood. It's done this way so that translators (or game authors) working in different languages can readily define their own default mood.
The library also defines the following macros to work with moods:
- gMood: get getActor's current mood.
- gMoodIs(name): test whether getActor's current mood is name
- gMoodIn(name1, name1...): test whether getActor's mood is one of name1, name1...
- gSetMood(name): set getActor's mood to name.
- aMood(actor): get actor's current mood.
- aMoodIs(actor, name): test whether actor's current mood is name
- aMoodIn(actor, name1, name1...): test whether actor's mood is one of name1, name1...
- aSetMood(actor, name): set actor's mood to name.
In each case the name parameter is the name of the Mood without the suffix 'Mood', so that, for example, you would use gMoodIs(happy) rather than gMoodIs(happyMood).
The macros with names starting with g all relate to getActor in contexts where getActor makes sense, i.e. in an ActorTopicEntry, TopicGroup or AgendaItem where getActor is the actor any of these belong to). To get or set the mood of any other actor, or in any other context, you need to use one of th macros beginning a and specify the actor parameter to denote the actor in question.
The macros gMood and aMood(actor) return the Mood object e.g. happyMood.Moods can be used in various ways to customize NPC responses in various ways. Here's one very simple example:
++ AskTopic @bob "<q>How are you today?</q> you enquire.\b <q>I'm quite <<gMood.name>>,</q> he replies. " ;
More typically, we might do something like this:
++ AskTopic @bob "<q>How are you today?</q> you enquire.\b <q>I'm <<if gMoodIn(happy, euphoric, excited, confident)>>great<<else if gMoodIn( sad, depressed, lonely, bored)>>not so good<<else>>okay<<end>>,</q> he tells you. " ;
You can also extend the Mood framework in any way you think fit. For example, if you wanted to add strength and affect properties to Mood (so that Moods with the same affect could, for example, be grouped together and compared for their strength) you could do something like this:
#define ModMood(mood_, strength_, affect_)\
modify mood_ ## Mood\
strength = strength_\
affect = #@affect_
modify Mood
strength = 0
afffect = 'none'
;
ModMood(furious, 50, anger);
ModMood(angry, 25, anger);
ModMood(happy, 25, happiness);
ModMood(euphoric, 50, happiness);
;
// and so on
The macro expansion would then result in:
modify furiousMood
strength = 50
affect = 'anger'
;
// and so on
We don't do this in the library since we assume that different game authors will want to adapt the moods framework in different ways. The above example would allow embedded expressions (or other such tests) like "<<if gMood.affect == 'happines'>>happy text<<else if gMood.affect == 'anger'>> angry response<<else ...>>".
Stances provide a simple representation of the the (positive or negative) attitude an NPC has towards the Player Character (and also, if you wish to track it, towards other NPCs). Stances are not ActorState dependent, but they are Actor-dependent in the sense that an NPC's stance towards other actors in the game may obviously vary by actor. Unlike Moods, stances can be ranked by score, a crude measure of the degree of positivity or negativity an actor has towards the Player Character (and possible other actors). Stances are objects of the Stance class. Each Stance object has a programmatic name ending in Stance(e.g., friendly) with a corresponding name< property giving the name of the Stance as a single-quoted string (e.g., 'friendly'), along with an associated score<, e.g. 20. A number of Stances come predefined in the language dependent part of the library (english.t); in ascending order of score (from -50 to 50) these are: loathing, rancorous, hostile, unfriendly, cool, neutral, lukewarm, friendly, warm, loving, and amorous. You can easily define more if you want them. For example, to define a cordial Stance with a score 25 of you'd just use: This would create the following object: The Actor class defines the following properties and methods to support the use of Stances: In the case of the final four of these, the Actor is excluded from the list under consideration (consider the case where Bob has a positive stance towards everyone else but his stance towards himself has been left at neutralStance; to learn that Bob likes himself the least probably isn't the information we'd be looking for here). As with Mood, there are a number of macros that can be used with Stance; the following all concern other actors' stances towards the Player Character. In each case the name parameter is the name of the Stance without the suffix 'Stance', so that, for example, you would use gStanceIs(friendly) rather than gStanceIs(friendlyStance).
The macros with names starting with g all relate to getActor in contexts where getActor makes sense, i.e. in an ActorTopicEntry, TopicGroup or AgendaItem where getActor is the actor any of these belong to). To get or set the stance of any other actor towards the player character, or in any other context, you need to use one of th macros beginning a and specify the actor parameter to denote the actor in question. Because Stances have an associated score, they can be ranked and compared by score. The following operators are available to compare Stances:
There are a further trio of Stance operators which may perhaps be useful to some game authors: If you need to set up a fair number of attitudes at the start of play, you can make use of the stanceInitializer object, modifying it to define a list of stances, each stance entry being a list of the form [actor1, stance, actor2], meaning actor1 starts out holding stance towards actor2. To avoid having to keep typing 'Stance' at the end of each stance name, you can make use of the Sta macro, for example: Note that you don't need to add any entries for the neutralStance, since this is the default. A simple example of what can be done with Stances might be varying greeting responses according to stance: Or you could do something like this: Or as an extreme example of using a Stance in connection with an AgendaItem: No doubt game authors will be able to come up with many more ingenious uses for Moods and Stances than the examples given here, but hopefully we have suggested enough to get you started.Stances
DefStance(cordial, 25);
cordialStance: Stance
name = 'cordial'
score = 25
;
(The last three of these operators were chosen in place of the more conventional ones as being the closest available operators TADS can override).
modify stanceInitializer
stances = [
Sta(me, friendly, bob),
Sta(bob, warm, me),
Sta(bob, amorous, mavis),
Sta(mavis, loving, bob),
Sta(me, lukewarm, mavis),
Sta(mavis, cool, me)
]
;
++ HelloTopic
"<q>Hello, Bob,</q> you say.\b
<q>Hello,</q> he replies. "
;
+++ AltTopic
"<q>Hi there, Bob,</q> you say.\b
<q>Hi, great to see you!</q> he declares. "
isActive = gStance >> neutralStance
;
+++ AltTopic
"
Hello,
you say.\b
In reply Bob merely grunts. "
isActive = gStance << coolStance
;
++ QueryTopic 'how Mavis feels about you; does feel me'
"<q><<if aStanceIn(mavis, neutral, lukewarm, warm, friendly, loving)>>I think she quite likes
you<<else>>I'd rather not say<<end>>,</q> he tells you. "
;
bobLoathingAgenda: AgendaItem
invokeItem()
{
"Bob snarls as you enter the room. <q>I <i>hate</i> you</q>!\b
So saying, he snatches up the nearest heavy blunt object and smashes
it down on your head with all the force he can muster.\b";
finishGameMsg(ftDeath, [finishOptionUndo]);
}
agendaOrder = 50
initiallyActive = true
isReady = gStanceIs(loathing) && gRoom == getActor.getOutermostRoom
;