Object Interaction
Overcat
07 Jan 2006, 15:35
Okay, so here's what I've been working on. I'd like to know from all of you whether I might be on a right track here, or if there is a better way.
Objects: PC, NPC's, Items, Triggers, Events, etc. Anything in Quest.
Objects interact with one another by 'emitting' their actions and 'listening' for actions within a given 'locale'. An emitted action has three components...
Origin: The object that's emitting the action
Action: The type of action being emitted
Locale: The area or room in which the action is being emitted
For example, Bob enters the Living Room from the Hallway. He broadcasts the action BOB:ENTER:LIVINGROOM into the Living Room. Any objects within Living Room that are 'listening' and set up to respond to the action BOB:ENTER:LIVINGROOM fire their response code. Similarily, Bob broadcasts the action BOB:EXIT:HALLWAY in the Hallway, where objects there respond in the same way.
An object's response code can of itself contain actions to emit. For example, when Bob broadcast BOB:ENTER:LIVINGROOM, Susie fired her response code 'msg <Susie stands up.>' and broadcast SUSIE:STANDUP:LIVINGROOM.
Each emit action is emitted through a single emit procedure (we'll call it the Emitter), so additional emit actions must be stored in a Queue to be processed once preceding emit actions have completed. Once the Emitter has finished broadcasting BOB:ENTER:LIVINGROOM and BOB:EXIT:HALLWAY, it checks for any emit actions that may have been added to its Queue as a result. If it finds any, it broadcasts those.
Emitted actions can beget emitted actions indefinately. This places some additional responsibility on the coder to make sure they avoid infinite loops.
I currently have this working. In my test project, three NPC's inhabit the start room. NPC 3 is set to follow NPC 2 wherever he goes. NPC 2 is set to follow NPC 1 wherever she goes. NPC 1 is set to follow no one until I (the player) hire her to follow me. So I hire her and leave to another room. NPC 1 follows me, NPC 2 follows NPC 1, and NPC 3 follows NPC 2.
Any thoughts?
paul_one
07 Jan 2006, 16:20How do you do this emmit code?
I guess it's in a "for every object in <#room#>" which consequently triggers off an object-action of some sort?
I haven't really thought too much into this as 'clever NPCs' really aren't all that needed, and there are much easier ways just to form a following code than to go through that much.
But it is a very scaleable and clever idea!
Overcat
08 Jan 2006, 01:17From this procedure I was attempting to build dynamic, moveable trigger objects. Then I thought, why not make every object a trigger, able to receive emitted actions?
I wrote this because it gives some flexibility in writing a game: whatever I come up with, story/plot-wise, I can implement easily and quickly. This also allows me to make the game mechanics as simple or as complex as I want for each new game, and to tailor-fit objects under the same architecture. Because every object works this way (or can) - from NPCs to items to triggers to events - designing becomes much easier for me.
At least, as far as my limited vision can see at this point. There may be objections and/or complications yet to come.
paul_one
09 Jan 2006, 11:33Which is nice - and it may even be slightly more tidy than it would be otherwise, I'll have to look into it.
It's just not much faster than coding a normal "when player enters room" script.
How do you determin what order these actions are all set off in?
Could you put a priority into the stack of sorts? - Might be quite useful if you need a couple of prequisits or something. There's numerous ways around it - but a simple (and less-coding) way is to be able to order these actions somewhat.
I guess there are a few benefits from your code - for instance dynamic action-strings. That is "strings-of-actions", so say you may or may not have a 'follower'. At one point that 'follower' executes an action only if he's with you in that spot. Then subsequent objects may also action off (right now I have a vision of a nuclear fusion/explosion). Alot easier to code (if you get the interface right) than trying to code in an exhaustive amount it if/then/else's.
The only thing is it may make it a little less-clear when it comes to understanding bigger storylines. You may have an obscure object-action which is declaired in the first object of the game - which might not be used until halfway through. This is all well and good if you remember - but try a few months down the line concentrating on another project and little commenting - you'll need to scour your code for sequences-off-sequences.
Overcat
14 Jan 2006, 14:34I have found that excessive use creates significant delay. So for large games, RPG or otherwise, response actions would have to be aesthetic and computative only: no emit actions of their own, just printed text, property changes, or other internal calculations. For instance, ten puppies that successively move from one room to another, and each emit a PUPPY:EXIT:LASTROOM action and a PUPPY:ENTER:CURRENTROOM action, produce a noticeable delay in the turn. If I just use flavour text in their responses for moving in and out of the rooms, however, the delay disolves to nothing. Same for setting properties, making calculations, etc.
I guess there are a few benefits from your code - for instance dynamic action-strings. That is "strings-of-actions", so say you may or may not have a 'follower'. At one point that 'follower' executes an action only if he's with you in that spot. Then subsequent objects may also action off (right now I have a vision of a nuclear fusion/explosion). Alot easier to code (if you get the interface right) than trying to code in an exhaustive amount it if/then/else's
Yes, that's the basic idea in mind. It would allow one to easily code custom situations and interactions as the story needs them. Your 'action chain' can be monitered for that nuclear explosion. I guess it's called a 'big delay', in practical terms, and you'd see it if it happened. One thing I did to combat the delay was to store the actions waiting to be emitted in an array queue, rather than a concatenated string queue. This improved performance visibly.
The only thing is it may make it a little less-clear when it comes to understanding bigger storylines. You may have an obscure object-action which is declaired in the first object of the game - which might not be used until halfway through. This is all well and good if you remember - but try a few months down the line concentrating on another project and little commenting - you'll need to scour your code for sequences-off-sequences.
I understand what you're saying. Because I quarantine each NPC in their own library file (and other groups of objects in their own files), I can easily determine who is capable of doing what, when, how, where and why. But what do you mean by 'sequences-off-sequences'?
paul_one
14 Jan 2006, 19:24One group would have to take a lock off the bottom of a big gate - the other to turn a cog to reel in the gate-rope, then the first group need to attack monsters that come over attacking the second group after they've unlocked the gate. First group attacking the monsters then spawns off may then spawn off a few other sequences.
So that's one thing that kicks off two sequences - the cog-turning and the monster's attacking.
The monsters attacking in turn kicks off the first group attacking the monsters.
And then comes the other sequences.
Which is understandable here. But when you're looking at code things get a bit more complicated having to dodge here and there trying to figure out what happens where.
To your noticable delay fact - with all those puppies, you could sensibly treat them all as one object. Have the 'leader' emit one action that the others pick up on. If you wish to seperate them then they can always gain an emitting action later on...
So, in this sort of instance you can have 100 people following one 'leader', and they have their enter/exit room stuff disabled - meaning only the 'leader's signal get's emitted.
Only problem then becomes that instead of an external object detecting that a 'follower' object has entered the room - and acting accordingly - you need to somehow have the follower detect an external object.
Overcat
15 Jan 2006, 12:48Hey - thanks for the idea about a 'leader' NPC. That should come in handy. The ten puppies were a test to see how the code would handle ten different NPC's at once, every turn. I put all ten puppies in the same room for simplicity's sake, but more practically they would all be inhabiting independent locations and operating under independent rules.
Only problem then becomes that instead of an external object detecting that a 'follower' object has entered the room - and acting accordingly - you need to somehow have the follower detect an external object.
The external object, or listener, could always check if the emitted action was sent by a 'leader' type object, then scan for any followers.
Additionally, I'm in the process of creating as many 'event hooks' in the NPC lib file as I can. For instance, OnSpeak, OnHire, OnFire, OnAttack, OnMove, etc. (The 'listening' component of an NPC in this whole emit action model is of itself an event hook called Responses.) NPC's are moved via a Move procedure, which calls the NPC's OnMove event hook. Script can be placed there for the follower to detect an external object.
paul_one
15 Jan 2006, 14:49That's the good point - it can handle and deal a great variety of situations.
The only thing is when it comes to looking back over your code trying to debug it etc, when you have about 4 different streams/chains all at once. You find yourself dodging from code-window to code-window, trying to understand what is calling what.
I'm suprised Quest seems to slow down with only a few items doing those emitting actions. - But then again, make an array over 9999 in the startscript and watch it hang

Overcat
15 Jan 2006, 14:59The only thing is when it comes to looking back over your code trying to debug it etc, when you have about 4 different streams/chains all at once. You find yourself dodging from code-window to code-window, trying to understand what is calling what.
True that. I guess I'll just have to be extra-organized. I'm willing to be diligent as long as the payoff continues to be worth it.
I think it slows down because of the for each looping. Each emitted action loops over every object in its locale. For instance, moving something causes it to emit an Exit action and an Enter action, the former looping over every object in the last room, the latter dito for the current room. If any of those objects have responses that include emitted actions of themselves, then a loop occurs again. So those ten puppies emitted a total of twenty actions in one move.
paul_one
15 Jan 2006, 20:09So those ten puppies emitted a total of twenty actions in one move.
I think it's more than that (20 'loops' is more like it).
The first pup would leave executing an exit - looping through 9 objects left in the room (at least - the other pups). I take it that it would then execute the enter script to the objects in the next room.
Then the next pup would do the same - for 8 objects and at least 1 object in the next room.
So it's basically one huge loop of about, erm, 100 repititions. Then you've got any other objects in the room/s to multiply by 10. So one other object would cause 110 rep's.
Overcat
15 Jan 2006, 22:22for each object in <#EmitQueueLocale[1]#> {
if action <#quest.thing#; Responses> then {
if property <#quest.thing#; Responsive> then {
if ( #quest.thing# <> PC ) then {
do <Call(#EmitQueueOrigin[1]#;#EmitQueueAction[1]#;#quest.thing#;Responses)>
}
}
}
}
Does the object being scanned have a Responses action? If yes, is the object set to respond? If yes, is the object NOT the PC? (The PC can respond to actions too, and in the exact same manner, but this is taken care of in another branch of code.) If yes, emit the action to the object via the Call procedure.
The Call procedure is detailed in a previous post, but its usage is:
[list]Call(EMITTING OBJECT; EMITTING ACTION; OBJECT BEING CALLED FOR RESPONSE; ACTION TO CALL FOR THE RESPONSE ON THE OBJECT BEING CALLED TO RESPOND)[/list:u]
paul_one
16 Jan 2006, 12:30Ie:
[details] -> {Call function} -> +object action+
And the object action has multiple if's that it has to scour through?
Or do you go through the possibilities in the call function using properties?
This is also Visual basic, which is ALOT slower than regular compiled code because it is largely interpreted.
Overcat
18 Jan 2006, 23:17And - what's the difference between interpreted code and code that is not interpreted? Curiousity can't kill this cat.
paul_one
19 Jan 2006, 12:38Interpreted is where one program takes source code and runs it line by line - like Quest does. This means it can't take advantage of real binary-compiled code as it needs to interpret each line of code as it comes and make decisions - this takes CPU 'cycles'. Also compiled code can be re-organized in a way interpretted code can't, it can be 'optimized' and is immediately recognisable as computer instructions.
VB 'compiles' it's code into an intermediary space, half-compiled, half sort-of-binary (as far as I have read). This means VB runs slower.
I have also noticed that it doesn't like doing anything fast, and a simple loop can go pretty slow (I think a simple loop of 10,000 in my college class took a few seconds!).
Whereas a C++ program can run a simple loop in perhaps 1 or 2 seconds (I think I have had a LARGE loop print out responses in a minute - while only outputting at the end took 2 seconds, proving that the output window was the slow part).
Hope this helps.