Learning more about Act/React from Thief

On a recent pre-stream (day 204) Casey was asked about gameplay code architecture, and mentioned two things offhand: finite state machines and Act/React. He also mentioned that as far as he understands it, it sounds as though the "Entity Component System" hullabaloo is a reinvention or rephrasing of ideas from Act/React (and I guess the Thief's object system in general).

Everyone and their mother talks about finite state machines, but I hadn't heard of Act/React. So I went digging around for info on it. Was not surprised to see it had originated from Looking Glass. The internet tells me Marc LeBlanc designed it, and the object system for Thief:

http://www.giantbomb.com/marc-leblanc/3040-3749/credits/
http://thief.wikia.com/wiki/TG_CREDITS

The only time I ever spent playing Thief was a short demo of Thief: Deadly Shadows that came with a graphics card I bought ten years ago, so I have little knowledge of the games, much less the modding scene for them.

Here's more stuff I dug up:

LG/Doc 9/24/1999: Act/React requirements
http://ttlg.com/Forums/showthread.php?t=47822

DromEd FAQ
http://www.ttlg.com/forums/showthread.php?t=87962

Sources and Receptrons, for teleporting and timing
http://www.thief-thecircle.com/teg-old/guides/twg/srctut.html

Sources & Receptron Basics
http://dromed.whoopdedo.org/dromed/s_and_r_basics

ShockEd for Dummies Pt III
https://www.systemshock.org/index.php?topic=5798.0

Postmortem: Thief: The Dark Project
http://www.gamasutra.com/view/fea...hief_the_dark_project.php?print=1

I'm interested in learning about this and related systems since i've been iterating on the design tools for the project at work, and it can be useful to look at ways people have solved related problems. There's a lot out there to read about it, and I feel like I should maybe get Thief on Gog and try to run the mod tools to get a better understanding of it.

Has anyone here done any scripting for Thief, System Shock, or other Looking Glass games that used this system or a derivative of it? What was it like to program levels/characters/encounters? What useful ideas or techniques do you think modern game authoring or modding tools could glean from there (or are they all already in use)? What wasn't good about it (the postmortem suggests there were some hiccups)? Know of any other good articles about this or other approaches?

I'm very behind on HMH, so apologies if this has been discussed in detail on streams that I've missed :pinch:

Edited by drjeats on
I can't say anything about those games, but in the game I'm currently developing I've designed a system that is very similar to this act/react system (simpler of course), only that I called it with a different name.

I'm using it only for AI decision making basically, but the concepts are the same I guess.
The only concern I have is that you end up with basically a giant switch statement, there's no way around it...

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
HandleStimulus( Entity, Stimulus )
{
   switch( stimulus->type )
   {
      case Stimulus_SomeoneAttackedMe:
      {
          if( enemyTooStrong )
          {
               Flee;
          }
          else
          {
               Attack him;
          }
      }

      case Stimulus_MurderHappened:
      {
           Entity* killed = stimulus->destEntity;
           Role role = GetRoleof( killed );
           switch( role )
           {
                case Sun:
                {
                     if( entity->gender == female )
                     {
                          SpawnStimulus(CallGuards);
                     }
                     else
                     {
                         StartAIPlan( DefendSun );
                     }
                }

                case Friend: ...
                case LandOwner: ...
           }
      }
   }
}


As you can see the problem is that if you start this route you will surely end up with nested switch statement (at least for an rpg like my game is), and I'm a little nervous about them... you start switching on "role", "relationship", "personality", "race"... It's just a "sparse" combinatorial explosion... But it's too effective for me to pass on... I rather prefer to sacrifice something else, because this allows me to define a database of act/react stuff that can be defined at compile time, or loaded from files for that matter:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
BeginRace( Monsters );
OnActionSpawn( Attack, Stimulus_Attack );
CanReactTo( Stimulus_Murder, Role_Child, Protect );

BeginSubRace( Goblin );
OnActionSpawn( Attack_Stimulus_AttackSpecial );
CanReactTo( Stimulus_Murder, Role_EveryOne, Ignore );
EndSubRace();

BeginSubRace( Ghoul );
...
...
EndSubRace();

EndRace();


For me being able to define entities hierarchically this way is too valuable.
Yes you loose the ability to have "organic" entities that continuosly change, but as an rpg is concerned, a wolf will remain a wolf forever, and so that's my choice.

Has someone implemented this sort of stuff as a giant switch statement? how are the performances?




Edited by erpeo93 on
erpeo93
I can't say anything about those games, but in the game I'm currently developing I've designed a system that is very similar to this act/react system (simpler of course), only that I called it with a different name.

I'm using it only for AI decision making basically, but the concepts are the same I guess.
The only concern I have is that you end up with basically a giant switch statement, there's no way around it...



This system will likely end up a mess if your rpg is anything beyond modest in scope and scale. A far simpler and more powerful approach would be to use a stack-based goal architecture where you use only a single switch statement for the state at the top of the npc's behavior stack.

Say an npc is currently sleeping, and the player wakes him up. You'd simply push an "awoken_aistate" onto his behavior stack (along with the relevant data like expiration and who did it, etc.)...

|------|ai stack|------|
[1] awoken_aistate (expired in 2 min., player) <---- current state
[0] sleep_aistate (no expire)

then, if then if he happens to witness a crime while awoken (and he's flagged to respond to crimes against the perpetrator) just push a report_crime_aistate...

|------|ai stack|------|
[2] report_crime_aistate (expire: 3 min) <---- current state
[1] awoken_aistate (expired in 2 min.)
[0] sleep_aistate (no expire)

...you can probably already see the potential for sophisticated-looking emergent behavior. Also if your RPG has any kind of a scheduling system for npcs, it'll integrate easily and flawlessly with a system like this - as scheduling becomes just a matter of manipulating the behavior stack.



NelsonMandella
erpeo93
I can't say anything about those games, but in the game I'm currently developing I've designed a system that is very similar to this act/react system (simpler of course), only that I called it with a different name.

I'm using it only for AI decision making basically, but the concepts are the same I guess.
The only concern I have is that you end up with basically a giant switch statement, there's no way around it...



This system will likely end up a mess if your rpg is anything beyond modest in scope and scale. A far simpler and more powerful approach would be to use a stack-based goal architecture where you use only a single switch statement for the state at the top of the npc's behavior stack.

Say an npc is currently sleeping, and the player wakes him up. You'd simply push an "awoken_aistate" onto his behavior stack (along with the relevant data like expiration and who did it, etc.)...

|------|ai stack|------|
[1] awoken_aistate (expired in 2 min., player) <---- current state
[0] sleep_aistate (no expire)

then, if then if he happens to witness a crime while awoken (and he's flagged to respond to crimes against the perpetrator) just push a report_crime_aistate...

|------|ai stack|------|
[2] report_crime_aistate (expire: 3 min) <---- current state
[1] awoken_aistate (expired in 2 min.)
[0] sleep_aistate (no expire)

...you can probably already see the potential for sophisticated-looking emergent behavior. Also if your RPG has any kind of a scheduling system for npcs, it'll integrate easily and flawlessly with a system like this - as scheduling becomes just a matter of manipulating the behavior stack.





Sorry but I can't really see why having stimuli "suggesting" my entities what they should/could do would end up being a mess.
(Assuming I don't end up with a huge number of them of course)

edit: I should clarify that this is not the "main" AI system, I mean entities decides what to do in a totally separated way.
The "flee" and "attack him" were a bit misleading I have to say. What I wanted to say is that I want these stimuli to _possibly_ suggest some actions to entities, not that the entire ai should run this way :)


The decisions they take are already need-oriented, evaluating everything they can do around them and picking the best thing that matches their needs. Surely having a stack like you suggest (based on the current "routine" of the entity I guess?) is another totally viable option :)

Now on the example you've made: what if I want the npc's sun to perceive that his dad is awake and prepare him the breakfast?
How would you go to handle it without having a concept similar to a stimulus or "act"?


Edited by erpeo93 on
erpeo93


Sorry but I can't really see why having stimuli "suggesting" my entities what they should/could do would end up being a mess.
(Assuming I don't end up with a huge number of them of course)

edit: I should clarify that this is not the "main" AI system, I mean entities decides what to do in a totally separated way.
The "flee" and "attack him" were a bit misleading I have to say. What I wanted to say is that I want these stimuli to _possibly_ suggest some actions to entities, not that the entire ai should run this way :)


The decisions they take are already need-oriented, evaluating everything they can do around them and picking the best thing that matches their needs. Surely having a stack like you suggest (based on the current "routine" of the entity I guess?) is another totally viable option :)

Now on the example you've made: what if I want the npc's sun to perceive that his dad is awake and prepare him the breakfast?
How would you go to handle it without having a concept similar to a stimulus or "act"?



I probably could have been more clear in my qualification. If your game has 20 NPCs (not including monsters/foes) you should be fine, but if it has over 200 and their behavior is sufficiently sophisticated, then explicitly spelling out behaviors in a nested way like that is probably going to lead to a lot of unnecessary and hard to maintain complexity down the road.

Obviously this also depends on the kind of game your doing. If your game is more like fable 2 than skyrim then this sort of need-oriented approach certainly makes sense (in theory) but if its more like skyrim, then it's probably over-engineering. And if what you're doing is more akin to skyrim, in terms of ai, then it's totally unnecessary to explicitly spell out father/son relationships - instead just assign them each a tag indicating that they're part of the same group (or something like that) so that they can respond to crimes against one another/conversation requests, etc.

I get that your system is essentially for responding to "messages", or at least making behavioral response suggestions based on them. The only thing I'd suggest is that if your game is large in scale, shift as much of the responsibility for responding to stimuli to the actual behavior routines themselves - and then allow for a set of "overrides", like responding to a crime, or a conversation request, etc., that can "override" the npc's current behavior. This simplifies things massively, because you only ever have to think in the context of a single behavior at one time.

Stack-based doesn't have to be based on routines (I assume you mean routine as in schedule, not as in a function), though it can be. The advantage is that it remembers and can return to previous behaviors. Also building certain kind of complex and compounding behaviors becomes very natural. And above all it allows you to keep things "flat", there is an implied hierarchy for certain behaviors but you don't mentally have to step through it - and if you do need to understand what's going on hierarchically, just glance at the stack.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
void ai_goto(bot_t *bot, aistate_t *state)
{
    if (dist(bot->loc, state->goal) < state->radius)
        pop_aistate(bot);
    else
        bot_set_goal(bot, state->goal));       
}

void ai_chase_nearest_guard(bot_t *bot, aistate_t *state)
{
    if (dist(bot, get_nearest_guard() < state->radius)
        pop_aistate(bot);
    else
        push_aistate(bot, GOTO_AISTATE);
}


void ai_report_crime(bot_t *bot, aistate_t state)
{
    if (dist(bot, get_nearest_guard() > state->radius){
        push_aistate(bot, CHASE_NEAREST_GUARD_AISTATE);
    }
    else{
        report_crime();
        pop_aistate(bot);
    }
}

void run_bot_ai(bot_t *bot)
{
     // you could check messages here for overriding the current behavior if permissible
     // obviously you could register npcs to listen for and respond to only particular
     // stimuli/events that they'd need to know about
     if (bot_receieved_crime_stimuli and can_override_curr_aistate)
        push_aistate(bot, REPORT_CRIME_AISTATE);

    aistate_t *state = get_top_aistack(bot);
    switch (state->type) {
        case GOTO_AISTATE:
            ai_goto(bot, state);
            break;
            
        case GOTO_AISTATE:
            ai_chase_nearest_guard(bot, state);
            break;
            
        case REPORT_CRIME_AISTATE:
            ai_report_crime(bot, state);
            break;
            
        default:
            break;
    }
} 


For the dad and son example, you'd have the son listen for a message that his dad has awoken, and you'd immediately push a MAKE_BREAKFAST_FOR_DAD_AISTATE onto his stack (if allowed by the son's current state). The actual make_breakfast() routine would be built out of the existing more general aistate building blocks like goto, just as with REPORT_CRIME_AISTATE example.

It's not perfect for everything, but it's a great fit for a large scale RPG which requires reasonably sophisticated and emergent looking npc behavior.


Edited by NelsonMandella on

I probably could have been more clear in my qualification. If your game has 20 NPCs (not including monsters/foes) you should be fine, but if it has over 200 and their behavior is sufficiently sophisticated, then explicitly spelling out behaviors in a nested way like that is probably going to lead to a lot of unnecessary and hard to maintain complexity down the road.

Obviously this also depends on the kind of game your doing. If your game is more like fable 2 than skyrim then this sort of need-oriented approach certainly makes sense (in theory) but if its more like skyrim, then it's probably over-engineering. And if what you're doing is more akin to skyrim, in terms of ai, then it's totally unnecessary to explicitly spell out father/son relationships - instead just assign them each a tag indicating that they're part of the same group (or something like that) so that they can respond to crimes against one another/conversation requests, etc.

My system is basically this:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
UpdateAI( World, Entity, deltaTime )
{
     UpdateNeeds( Entity, deltaTime );
     
     ListOfPossibleThingsToDo list = {};
     for( everyEntity in sight )
     {
          for( everything I am able to do with it )
          {
               EvaluateAction;
               AddActionToList( list );
          }
     }

     SortList( list );
     entity->action = list[0].action;
     entity->destEntity = list[0].entity;
}


For sure it probably won't scale well, and I will have to make some tradeoff (reduce range of sight, evaluate less actions, evaluate them quickly ecc) but honestly it doesn't seem like over-engineering to me. For sure the behavior stack is a really good option, and surely one doesn't exclude the other :)

I get that your system is essentially for responding to "messages", or at least making behavioral response suggestions based on them. The only thing I'd suggest is that if your game is large in scale, shift as much of the responsibility for responding to stimuli to the actual behavior routines themselves - and then allow for a set of "overrides", like responding to a crime, or a conversation request, etc., that can "override" the npc's current behavior. This simplifies things massively, because you only ever have to think in the context of a single behavior at one time.

Here I totally agree with you, obviously the goal is to obtain the best results with as less stimuli as I can: they effectively represents "deviations" and there is a considerable overhead in mantaining them.

Stack-based doesn't have to be based on routines (I assume you mean routine as in schedule, not as in a function), though it can be. The advantage is that it remembers and can return to previous behaviors. Also building certain kind of complex and compounding behaviors becomes very natural. And above all it allows you to keep things "flat", there is an implied hierarchy for certain behaviors but you don't mentally have to step through it - and if you do need to understand what's going on hierarchically, just glance at the stack.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
void ai_goto(bot_t *bot, aistate_t *state)
{
    if (dist(bot->loc, state->goal) < state->radius)
        pop_aistate(bot);
    else
        bot_set_goal(bot, state->goal));       
}

void ai_chase_nearest_guard(bot_t *bot, aistate_t *state)
{
    if (dist(bot, get_nearest_guard() < state->radius)
        pop_aistate(bot);
    else
        push_aistate(bot, GOTO_AISTATE);
}


void ai_report_crime(bot_t *bot, aistate_t state)
{
    if (dist(bot, get_nearest_guard() > state->radius){
        push_aistate(bot, CHASE_NEAREST_GUARD_AISTATE);
    }
    else{
        report_crime();
        pop_aistate(bot);
    }
}

void run_bot_ai(bot_t *bot)
{
     // you could check messages here for overriding the current behavior if permissible
     // obviously you could register npcs to listen for and respond to only particular
     // stimuli/events that they'd need to know about
     if (bot_receieved_crime_stimuli and can_override_curr_aistate)
        push_aistate(bot, REPORT_CRIME_AISTATE);

    aistate_t *state = get_top_aistack(bot);
    switch (state->type) {
        case GOTO_AISTATE:
            ai_goto(bot, state);
            break;
            
        case GOTO_AISTATE:
            ai_chase_nearest_guard(bot, state);
            break;
            
        case REPORT_CRIME_AISTATE:
            ai_report_crime(bot, state);
            break;
            
        default:
            break;
    }
} 


yes but with this approach don't you loose the unpredictability of an npc that while it's going to grab some food encounters an old friend and start talking with him? how do you handle something like that?


For the dad and son example, you'd have the son listen for a message that his dad has awoken, and you'd immediately push a MAKE_BREAKFAST_FOR_DAD_AISTATE onto his stack (if allowed by the son's current state). The actual make_breakfast() routine would be built out of the existing more general aistate building blocks like goto, just as with REPORT_CRIME_AISTATE example.

It's not perfect for everything, but it's a great fit for a large scale RPG which requires reasonably sophisticated and emergent looking npc behavior.

ok so the son is listening for a message, that means that the father has to send that message.
And that message could be seen as a stimulus, can't it? :)
The problem I see with "just send a message" is that now the son has to be inside the house to receive the message (or at least in the range of sight of the father), unless you send the message repeatedly for a certain amount of time...
At that point isn't better to just have an explicit stimulus that represents the fact that the father has awaken? This way the message has to be "created" only one time, and even if the son enters the home a couple of minutes later, is able to "perceive" that the father has aweken and goes prepare the breakfast. (of course not before having checked that the other son hasn't preceeded him :) )
erpeo93
ok so the son is listening for a message, that means that the father has to send that message.
And that message could be seen as a stimulus, can't it? :)
The problem I see with "just send a message" is that now the son has to be inside the house to receive the message (or at least in the range of sight of the father), unless you send the message repeatedly for a certain amount of time...
At that point isn't better to just have an explicit stimulus that represents the fact that the father has awaken? This way the message has to be "created" only one time, and even if the son enters the home a couple of minutes later, is able to "perceive" that the father has aweken and goes prepare the breakfast. (of course not before having checked that the other son hasn't preceeded him :) )


You by no means need to use messages/stimuli, that was just one example. But this really depends on the underlying system that's creating these behaviors. Are these behaviors determined by a scheduler or are they semi-randomly selected based on needs and/or desires? If the son makes or attempts to make breakfast for the dad each and every morning, then no message is necessary, as you'd simply pass the dad's info/id/ptr along through the make_breakfast_for_npc() behavior function and that's that - you'd just need a scheduler to push that state onto the son's stack. Polling is just fine, as long as its on a single entity like the dad and you're not combing through every npc looking for one who you can make breakfast for.

Take for example the simplest possible scheduler...

[11pm-7am][100%: sleep]
[10am-11pm][90%: go to school][10% fish[
[7am-10am] [50%: make breakfast for dad] [50%: go fishing]

...and then on top of that layer your overrides (crime reporting, conversation requests, etc.) and you've got a very sophisticated looking system that's already beyond what most AAA games are doing in terms of the perceived end result.

You can create very sophisticated looking behavior using simple behavioral building blocks in a stack-based system without having to explicitly spell out relationships between npcs.

Ultima 7 took these kinds of behaviors to the Nth degree, making food, serving patrons, etc. etc. and there's a reverse engineered open source version of the engine available here.
NelsonMandella
erpeo93
ok so the son is listening for a message, that means that the father has to send that message.
And that message could be seen as a stimulus, can't it? :)
The problem I see with "just send a message" is that now the son has to be inside the house to receive the message (or at least in the range of sight of the father), unless you send the message repeatedly for a certain amount of time...
At that point isn't better to just have an explicit stimulus that represents the fact that the father has awaken? This way the message has to be "created" only one time, and even if the son enters the home a couple of minutes later, is able to "perceive" that the father has aweken and goes prepare the breakfast. (of course not before having checked that the other son hasn't preceeded him :) )


You by no means need to use messages/stimuli, that was just one example. But this really depends on the underlying system that's creating these behaviors. Are these behaviors determined by a scheduler or are they semi-randomly selected based on needs and/or desires? If the son makes or attempts to make breakfast for the dad each and every morning, then no message is necessary, as you'd simply pass the dad's info/id/ptr along through the make_breakfast_for_npc() behavior function and that's that - you'd just need a scheduler to push that state onto the son's stack. Polling is just fine, as long as its on a single entity like the dad and you're not combing through every npc looking for one who you can make breakfast for.

Take for example the simplest possible scheduler...

[11pm-7am][100%: sleep]
[10am-11pm][90%: go to school][10% fish[
[7am-10am] [50%: make breakfast for dad] [50%: go fishing]

...and then on top of that layer your overrides (crime reporting, conversation requests, etc.) and you've got a very sophisticated looking system that's already beyond what most AAA games are doing in terms of the perceived end result.

You can create very sophisticated looking behavior using simple behavioral building blocks in a stack-based system without having to explicitly spell out relationships between npcs.

Ultima 7 took these kinds of behaviors to the Nth degree, making food, serving patrons, etc. etc. and there's a reverse engineered open source version of the engine available here.


Do we agree that whatever is the way you choose to "manage" the ai, you have to "poll" the environment from time to time to know if there's something more interesting to do that what you're doing?

A stack seems to me like an optimization: instead of considering "all the possible things that you can do on everything you see" you just consider "the last thing you did" to be the best choice. For sure it's a really sane and good optimization, but I can't see how that can be more expressive than just mantaining a list of "active" behaviors, and you just switch on what you think is the best of them. Obviously a stack is easy to implement, faster, and easier to debug, but in my opinion it can't be more expressive than my option... And as I said you have to examine the sorrounding environment anyway, because otherwise your entities will be unable to react to sudden changes.

I didn't understood the part where you say "without having to explicitly spell out relationships between npcs"... what does it means? How can you make it without having the son storing whom is father is?

erpeo93
Do we agree that whatever is the way you choose to "manage" the ai, you have to "poll" the environment from time to time to know if there's something more interesting to do that what you're doing?


Not necessarily, you could in theory rely solely on message passing.

erpeo93
A stack seems to me like an optimization: instead of considering "all the possible things that you can do on everything you see" you just consider "the last thing you did" to be the best choice. For sure it's a really sane and good optimization, but I can't see how that can be more expressive than just mantaining a list of "active" behaviors, and you just switch on what you think is the best of them. Obviously a stack is easy to implement, faster, and easier to debug, but in my opinion it can't be more expressive than my option... And as I said you have to examine the sorrounding environment anyway, because otherwise your entities will be unable to react to sudden changes.


I wouldn't consider it an optimization, in the sense of improving performance. A stack is a natural and intuitive model for goal oriented behavior because it approximates nicely the way that our minds actually deal with goals. When someone wants to make take a drive they do something like...

1
2
3
4
5
6
7
push(drive_to_store)
     push(get_keys)
         push(check_pocket)
         pop(check_pocket)
    pop(get_keys)
    push(walk to door)
    etc...


erpeo93
I didn't understood the part where you say "without having to explicitly spell out relationships between npcs"... what does it means? How can you make it without having the son storing whom is father is?


In the vast majority of cases it would not be necessary to explicitly spell out father-son relationship. They live in the same house, they're probably tagged as being part of the same group, they're particular ai schedules may reference one another so as to interlock for behaviors like making breakfast, they may have customized dialogue that they shout to one another - none of this requires creating a set of enums for father/son. What about the son's relationships to everyone else? Is he also a citizen in relation to a mayor? Is he a shopper in relation to a shopkeeper? You're drawing explicit relationships between npcs instead of letting the behaviors do the talking. If an npc is making breakfast for another npc, and the other npc is letting him do it, and they live together and one is a young man and the other is an old man then their behaviors become the expressions of these implicit relationships.
Not necessarily, you could in theory rely solely on message passing.

Yep I meant "something" that allows the environment to communicates, I've not specified it well enough. :)


I wouldn't consider it an optimization, in the sense of improving performance. A stack is a natural and intuitive model for goal oriented behavior because it approximates nicely the way that our minds actually deal with goals. When someone wants to make take a drive they do something like...

1
2
3
4
5
6
7
push(drive_to_store)
     push(get_keys)
         push(check_pocket)
         pop(check_pocket)
    pop(get_keys)
    push(walk to door)
    etc...

Sorry but I disagree.
I see it more like this:

1
2
3
4
struct Plan
{
    ACtion* firstAction;
};


And so the drive to store Plan is just a linked list of action or subplans: getkeys->checkpocket->walktodoor.
But maybe we're talking about the same thing in different terms, because when my entities encounters a "subplan" in a plan, they're effectively "pushing" onto the stack that plan... but I prefer to see a plan like a simple sequence of actions that have to be taken in series.
The role of the ai is to choose the best plan among some number that we mantain active.

In the vast majority of cases it would not be necessary to explicitly spell out father-son relationship. They live in the same house, they're probably tagged as being part of the same group, they're particular ai schedules may reference one another so as to interlock for behaviors like making breakfast, they may have customized dialogue that they shout to one another - none of this requires creating a set of enums for father/son. What about the son's relationships to everyone else? Is he also a citizen in relation to a mayor? Is he a shopper in relation to a shopkeeper? You're drawing explicit relationships between npcs instead of letting the behaviors do the talking. If an npc is making breakfast for another npc, and the other npc is letting him do it, and they live together and one is a young man and the other is an old man then their behaviors become the expressions of these implicit relationships.

This is absolutely true. As many behaviors as possible should "emerge" from attributes of the entities relative to their "community" and the entities that surround them. (age, personality, ecc)
But for certain things I think you're forced to have roles: for example the village has to know exactly whom is the king of the village: that has to be stored somewhere, and furthermore the members of the community have to have a way to know who the king his... you know maybe they want to spawn a quest that is like "kill the king" or something.

Obviously entities store only roles that are relative to them: the son will only remember father, mother, and maybe a couple of friends. The father could store also something like blacksmith, wife, innkeeper, king.

How would you go about a father that has broke his sword killing some goblins, and now has to go to the blacksmith to buy a new sword? I mean if the village is small enough, he _has_ to know exactly who the blacksmith is and where he lives. Without the blacksmith explicitly declaring "I'm a blacksmith" (or alternatively declaring "I offer swords" ), how can the father know where to go?

Edited by erpeo93 on
Sorry but I disagree.
I see it more like this:

1
2
3
4
struct Plan
{
    ACtion* firstAction;
};


And so the drive to store Plan is just a linked list of action or subplans: getkeys->checkpocket->walktodoor.
But maybe we're talking about the same thing in different terms, because when my entities encounters a "subplan" in a plan, they're effectively "pushing" onto the stack that plan... but I prefer to see a plan like a simple sequence of actions that have to be taken in series.
The role of the ai is to choose the best plan among some number that we mantain active.


You're talking about building a plan from the top-down, whereas I'm talking about generating one automatically from the ground up using simple building block behaviors that push one another. With a stack-based approach, suppose your high level ai system wants some npc to go talk to another npc inside a locked room, just push talk_to_npc, which will in turn push walk_to_door which will push unlock_door, etc. and before you know it the stack is the plan - all done informally and automatically.

In the vast majority of cases it would not be necessary to explicitly spell out father-son relationship. They live in the same house, they're probably tagged as being part of the same group, they're particular ai schedules may reference one another so as to interlock for behaviors like making breakfast, they may have customized dialogue that they shout to one another - none of this requires creating a set of enums for father/son. What about the son's relationships to everyone else? Is he also a citizen in relation to a mayor? Is he a shopper in relation to a shopkeeper? You're drawing explicit relationships between npcs instead of letting the behaviors do the talking. If an npc is making breakfast for another npc, and the other npc is letting him do it, and they live together and one is a young man and the other is an old man then their behaviors become the expressions of these implicit relationships.

This is absolutely true. As many behaviors as possible should "emerge" from attributes of the entities relative to their "community" and the entities that surround them. (age, personality, ecc)
But for certain things I think you're forced to have roles: for example the village has to know exactly whom is the king of the village: that has to be stored somewhere, and furthermore the members of the community have to have a way to know who the king his... you know maybe they want to spawn a quest that is like "kill the king" or something.

Obviously entities store only roles that are relative to them: the son will only remember father, mother, and maybe a couple of friends. The father could store also something like blacksmith, wife, innkeeper, king.

How would you go about a father that has broke his sword killing some goblins, and now has to go to the blacksmith to buy a new sword? I mean if the village is small enough, he _has_ to know exactly who the blacksmith is and where he lives. Without the blacksmith explicitly declaring "I'm a blacksmith" (or alternatively declaring "I offer swords" ), how can the father know where to go?

Offload as much of the relationship expression and customized behaviors to scripting as possible. Create a scheduler, where you can assign an entire script in place of a behavior...

[10pm-7am][custom_script.lua]
[7am-10pm] [fish]

That way you can do explicit stuff like check your weapon condition, and go to a nearby blacksmith if it needs repair. Or else do something else. Even if you're not doing dialogue trees and are just keeping to "barking" or shouting, you absolutely need scripting in my opinion to do the highly customized stuff. You could use "triggers" on the floors to flag stuff like blacksmith's shop, town center, church, etc.
NelsonMandella
Sorry but I disagree.
I see it more like this:

1
2
3
4
struct Plan
{
    ACtion* firstAction;
};


And so the drive to store Plan is just a linked list of action or subplans: getkeys->checkpocket->walktodoor.
But maybe we're talking about the same thing in different terms, because when my entities encounters a "subplan" in a plan, they're effectively "pushing" onto the stack that plan... but I prefer to see a plan like a simple sequence of actions that have to be taken in series.
The role of the ai is to choose the best plan among some number that we mantain active.


You're talking about building a plan from the top-down, whereas I'm talking about generating one automatically from the ground up using simple building block behaviors that push one another. With a stack-based approach, suppose your high level ai system wants some npc to go talk to another npc inside a locked room, just push talk_to_npc, which will in turn push walk_to_door which will push unlock_door, etc. and before you know it the stack is the plan - all done informally and automatically.

In the vast majority of cases it would not be necessary to explicitly spell out father-son relationship. They live in the same house, they're probably tagged as being part of the same group, they're particular ai schedules may reference one another so as to interlock for behaviors like making breakfast, they may have customized dialogue that they shout to one another - none of this requires creating a set of enums for father/son. What about the son's relationships to everyone else? Is he also a citizen in relation to a mayor? Is he a shopper in relation to a shopkeeper? You're drawing explicit relationships between npcs instead of letting the behaviors do the talking. If an npc is making breakfast for another npc, and the other npc is letting him do it, and they live together and one is a young man and the other is an old man then their behaviors become the expressions of these implicit relationships.

This is absolutely true. As many behaviors as possible should "emerge" from attributes of the entities relative to their "community" and the entities that surround them. (age, personality, ecc)
But for certain things I think you're forced to have roles: for example the village has to know exactly whom is the king of the village: that has to be stored somewhere, and furthermore the members of the community have to have a way to know who the king his... you know maybe they want to spawn a quest that is like "kill the king" or something.

Obviously entities store only roles that are relative to them: the son will only remember father, mother, and maybe a couple of friends. The father could store also something like blacksmith, wife, innkeeper, king.

How would you go about a father that has broke his sword killing some goblins, and now has to go to the blacksmith to buy a new sword? I mean if the village is small enough, he _has_ to know exactly who the blacksmith is and where he lives. Without the blacksmith explicitly declaring "I'm a blacksmith" (or alternatively declaring "I offer swords" ), how can the father know where to go?

Offload as much of the relationship expression and customized behaviors to scripting as possible. Create a scheduler, where you can assign an entire script in place of a behavior...

[10pm-7am][custom_script.lua]
[7am-10pm] [fish]

That way you can do explicit stuff like check your weapon condition, and go to a nearby blacksmith if it needs repair. Or else do something else. Even if you're not doing dialogue trees and are just keeping to "barking" or shouting, you absolutely need scripting in my opinion to do the highly customized stuff. You could use "triggers" on the floors to flag stuff like blacksmith's shop, town center, church, etc.


I've been thinking about this during the morning, and I think that now I see what your point is.
Let's leave apart fom one second "how" the behaviors are handled, (stack, linked list, whatever)... you're saying that it's bad to define plans in "data", they are better defined "in code": in every moment every entity can only be doing "one thing". If we switch on that state, we arrive at the big switch statement that you were talking about:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
HandleAIState(Entity)
{
   switch(entity->aiState)
   {
       case SearchingForFood:
       {
           if( there is food around)
           {
               ChooseBestFood();
               AddState( entity, Eating, food );
               RemoveState( entity, SearchingForFood );
           }
       }

       case Eating:
       {
           if( nearEnough )
           {
               entity->action = eat;
           }
           else
           {
               entity->acceleration = food->position - entity->position;
           }
       }
   }
}


Then it's just a matter of choosing the right state in every moment.
And that can be done in different ways, depending on requirements/context.
(With a stack for example, you just assume that whatever is the top of the stack is your current state, while my proposal is to have a "list" of possible state, you evaluate them and you pick the best, and pass it to the switch statement).

And yeah with this switch we have a lot more freedom to also check attributes inside entities ecc:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
...

case NeedMoney:
{
     if( IsBad( entity->personality)
     {
         PushState( entity, SearchSomeoneToStealMoneyFrom );
     }
}

case SearchSomeoneToStealMoneyFrom:
{
    if( someoneElsePresent )
    {
        PushState( StealFrom, destEntity );
    }
    else
    {
        PushState( WanderAround );
    }
}


And with this approach Stimuli/messages can just be implemented as simple pushes onto the stack/list.

I now absolutely agree with you on that, what I was not thinking is that at the end the entity can only be in a single ai state, so thank you :)
The cool thing is that it's a lot easier to debug and mantain, and also it's trivial to expand and "improve" a single ai state if you want.

Edited by erpeo93 on
erpeo93
I've been thinking about this during the morning, and I think that now I see what your point is.
Let's leave apart fom one second "how" the behaviors are handled, (stack, linked list, whatever)... you're saying that it's bad to define plans in "data", they are better defined "in code": in every moment every entity can only be doing "one thing". If we switch on that state, we arrive at the big switch statement that you were talking about:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
HandleAIState(Entity)
{
   switch(entity->aiState)
   {
       case SearchingForFood:
       {
           if( there is food around)
           {
               ChooseBestFood();
               AddState( entity, Eating, food );
               RemoveState( entity, SearchingForFood );
           }
       }

       case Eating:
       {
           if( nearEnough )
           {
               entity->action = eat;
           }
           else
           {
               entity->acceleration = food->position - entity->position;
           }
       }
   }
}


Then it's just a matter of choosing the right state in every moment.
And that can be done in different ways, depending on requirements/context.
(With a stack for example, you just assume that whatever is the top of the stack is your current state, while my proposal is to have a "list" of possible state, you evaluate them and you pick the best, and pass it to the switch statement).


Exactly, every entity is only ever in one state at a time, so there's almost never need to think outside the context of the current state, either in terms of the parent or children states but obviously just check the stack to see how it got there if need be. As much as possible you want to shift the burden of "plan building" from higher level ai systems to lower levels behavioral systems (having talk_to_npc be responsible for all the steps needed to make that happen so your high level system who pushed that state doesn't need to know or care about the details of how it actually works). And yeah as much as possible try and draw a clear distinction between the higher level ai systems that are in charge of actual high level decision making (like a scheduler where you're telling an npc to go fish in the morning) and the lower level behavioral systems that are in charge of actually executing behaviors. And if you do have something like a scheduler (composed of aistate nodes) in charge of controlling an npc's daily routines, then you can treat the schedule itself as data, which you could then edit visually or in a text file.


erpeo93

And yeah with this switch we have a lot more freedom to also check attributes inside entities ecc:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
...

case NeedMoney:
{
     if( IsBad( entity->personality)
     {
         PushState( entity, SearchSomeoneToStealMoneyFrom );
     }
}

case SearchSomeoneToStealMoneyFrom:
{
    if( someoneElsePresent )
    {
        PushState( StealFrom, destEntity );
    }
    else
    {
        PushState( WanderAround );
    }
}


And with this approach Stimuli/messages can just be implemented as simple pushes onto the stack/list.

I now absolutely agree with you on that, what I was not thinking is that at the end the entity can only be in a single ai state, so thank you :)
The cool thing is that it's a lot easier to debug and mantain, and also it's trivial to expand and "improve" a single ai state if you want.


No problem :) AI systems for RPGs in particular can get pretty nasty once you start accommodating special cases and specific instances of stuff so keeping everything "flat" and being able to think in terms of a single behavioral context, as you said, significantly simplifies things from a maintainability standpoint. Only other thing I'd throw out there is that, if your game requires highly granular control over the ai, then you've almost gotta use some kind of scripting language to implement custom ai logic for specific instances of NPCs.

Of course as an aesthetic alternative to a giant switch statement you could create a single dispatch table at the top of the file to bind the AI_STATE_ENUMS to their corresponding function pointers - assuming that the behavior functions all share the same parameters like (bot_t*, *aistate).

Edited by NelsonMandella on