Making the NPC characters walk around the rooms.

Started by Professor Plum, Sat 10/10/2009 18:02:12

Previous topic - Next topic

Professor Plum

For my Clue: Master Detective game, I wanted to make the characters feel less static by moving around the mansion over a period of time.

How would I script it properly so that say,

e.g. Colonel Mustard starts off in the Library. After 10 seconds, he will move to the Billiard Room. After another 10 seconds he will move to the Studio......ect.....ect......eventually returns to the Library.

I'm assuming it would involve a timer, but I'm not certain where to go from there.

Thanks.


RickJ

Well here is a bit of untested code that may be of some help.

Code: ags

*** Script Header ***
// Define the max number of locations a NPC can be in
#define MAX_LOCATIONS 10

// Define the max number of suspects 
#define MAX_SUSPECTS   10

import function InitializeSuspect(this Character*, int idx);
import SetSuspectLocation(this Character*, int dwell_time, in room, int x, int y);
import function MoveSuspect(this Character*, bool sequential);


*** Global Script ***
// Define the info needed for each suspect (npc) and their possible locations 
struct suspect {
   Character *Ptr;                              // Pointer to character
   int Location;                                   // Current location index  
   int DwellCount;                              // Time in current location  
   int DwellTime[MAX_LOCATIONS];   // Time to stay in each location
   int Room[MAX_LOCATIONS];          // Room number for each location
   int X[MAX_LOCATIONS];                 //  X coordniate for each location
   int Y[MAX_LOCATIONS];                 //  Y coordniate for each location
}

// Create an array of suspects, one for each character.
suspect Suspect[MAX_SUSPECTS];

// Create some functions that use or manipulate the suspect info

// This function returns the index of specified suspect character 
int chridx(Character *ptr) {
   int i, idx;

   idx = -1;   // Return -1 if not found
   i = 0;
   while (i<MAX_SUSPECTS) {
      if (Suspect[i].Ptr==ptr) {
         idx = i;                        // return the index of found character
         i = MAX_SUSPECTS;    // Break loop
      }
      i++;
   }
   return idx;
}

// This function initializes each of the suspect characters
function InitializeSuspect(this Character*, int idx) {
   int i;

   if ((idx<0)||(idx>=MAX_SUSPECTS)) {
      Display("*** Error-InitializeSuspect, idx must be in the range 0 - %d".MAX_SUSPECTS-1);
   }
   else {
      Suspect[idx].Ptr = this;
      Suspect[idx].Location = 0;
      Suspect[idx].DwellCount = 0;
 
      i = 0;
      while (i<MAX_LOCATIONS) {
         Suspect[idx].DwellTime[i] = 0;
         Suspect[idx].Room[i] = -1;
         Suspect[idx].X[i] = 0;
         Suspect[idx].Y[i] = 0;
         i++;
      }
   } 
}

// This function sets up a suspect character location 
function SetSuspectLocation(this Character*, int dwell_time, in room, int x, int y) {
   int idx;  
   int location;

   // Find the character 
   idx = chridx(this);
   if (idx<0) {
      Display("*** Error-SetSuspectLocation, idx must be in the range 0 - %d".MAX_SUSPECTS-1);
   }
   else if (Suspect[idx].Location<0) {
      Display("*** Error-SetSuspectLocation, not initialized, call InitializeSuspect(() befor calling this function");
   }
   else if (Suspect[idx].Location>=MAX_LOCATIONS) {
      Display("*** Error-SetSuspectLocation, max number of locations, %d,  exceeded".MAX_LOCATIONS-1);
   }
   else {
      Suspect[idx].Ptr = this;
      Suspect[idx].Location = 0;
      Suspect[idx].DwellCount = 0;
      Suspect[idx].DwellTime[ Suspect[idx].Location] = dwell_time;
      Suspect[idx].Room[ Suspect[idx].Location] = room;
      Suspect[idx].X[ Suspect[idx].Location] = x;
      Suspect[idx].Y[ Suspect[idx].Location] = y;
       Suspect[idx].Location++;
   }
}

// This function moves the suspect character from location to location
function MoveSuspect(this Character*, bool sequential) { 
   int idx;  
   int next_location;

   // Find the character 
   idx = chridx(this);
   if (idx<0) {
      Display("*** Error-MoveSuspectLocation, idx must be in the range 0 - %d".MAX_SUSPECTS-1);
   }
   else if (Suspect[idx].DwellCount>=0) {
      Suspect[idx].DwellCount--;
   }
   else {
      // Go to next location either sequentially or randomly
      if (sequential) {
         Suspect[idx].Location++;
         if (Suspect[idx].Location>=MAX_SUSPECTS) Suspect[idx].Location = 0;
      }
      else {
         Suspect[idx].Location = Random(MAX_SUSPECTS-1);
      }
      // Setup the dwell timer for the new location
      Suspect[idx].DwellCount = Suspect[idx].DwellTime[Suspect[idx].Location]; 

      if ((Suspect[idx].Ptr.Room==player.Room) )||(Suspect[idx].Room[Suspect[idx].Location]==player.Room)) {
         // Don't allow suspects to vanish from room where player character is
         // Also don't allow suspects to materialize in same room as player character
      }
      else {
         // Setup the character's room and position for the new location
         Suspect[idx].Ptr.Room = Suspect[idx].Room[Suspect[idx].Location]; 
         Suspect[idx].Ptr.X = Suspect[idx].X[Suspect[idx].Location]; 
         Suspect[idx].Ptr.Y = Suspect[idx].Y[Suspect[idx].Location]; 
      }
   }
}

function game_start() {
   cMustard.InitializeSuspect(0);
   cMustard.SetSuspectLocation(200, 1, 100, 100);  // dwell_time, room, x, y 
   cMustard.SetSuspectLocation(200, 2, 100, 100);
   cMustard.SetSuspectLocation(200, 3, 100, 100);
   cMustard.SetSuspectLocation(200, 4, 100, 100);
   cMustard.SetSuspectLocation(200, 5, 100, 100);
   cMustard.SetSuspectLocation(200, 6, 100, 100);

   cPlum.InitializeSuspect(1);
   cPlum.SetSuspectLocation(200, 1, 100, 100);  // dwell_time, room, x, y
   cPlum.SetSuspectLocation(200, 2, 100, 100);
   cPlum.SetSuspectLocation(200, 3, 100, 100);
   cPlum.SetSuspectLocation(200, 4, 100, 100);
   cPlum.SetSuspectLocation(200, 5, 100, 100);
   cPlum.SetSuspectLocation(200, 6, 100, 100);
}

funcrtion repeatedly_execute() {
   cMustard.MoveSuspect(false);   // Move him randomly
   cPlum.MoveSuspect(true);         // Move him sequentially
}


Professor Plum

Oh wow. That's rather complicated to figure out at the time being. Although your incode explanations help a little, I find myself still lost.

RickJ

Ok, we'll take it one step at a time if you like.   We'll start by creating a variable space that can hold all the information we need about the suspect characters and their possible locations.  The first thing to do is to define a couple of constants, one for the number of suspects, and one for the number of possible locations.

Code: ags

** Script Header ***
// Define the max number of locations a NPC can be in
#define MAX_LOCATIONS 10

// Define the max number of suspects 
#define MAX_SUSPECTS   10


A simple text substitution is performed at the beginning of the compile process.  For example, wherever the string "MAX_LOCATIONS" appears in the code it will be replaced by the string "10".    Typically such constants appear multiple times in the code and so if it at some point it is decided that there needs to be more or fewer locations it is a simple matter to change the definition of MAX_LOCATIONS.  This is much much easier than modifying a numerical value in each and every line of code where it is used.  The definitions are placed in the Script Header so that they can be used anywhere.

The next thing to do figure out how to associate a list of locations with a suspect character.  In the script language a list is usually represented by an array variable.  An array variable contains a list of values that are accessed using the variable name and an index.  Ok so what is a location?  In this case it is primarily a room but we will also ned to know the x,y location of the suspect within the room.  We will also need to know how long the suspect should stay in each location.  So for a suspect we could define the possible locations as shown in the code below.  So for example, the room number of location 0 would be stored in Room[0] and the x,y positions would be stored in X[0] and Y[0].   Note that the first array index is zero and the last index is MAX_LOCATIONS - 1.   
Code: ags

*** Global Script ***
   int Room[MAX_LOCATIONS];          // Room where suspect is located
   int X[MAX_LOCATIONS];                 // Suspect's X position within room
   int Y[MAX_LOCATIONS];                 // Suspect's Y position within room
   int DwellTime[MAX_LOCATIONS];   // How many game cycles to stay in the room


Now if I wanted to write a function that moved a character from location to location it would be necessary to remember the current location and how long the suspect character was there.  So we will need to define a variable for each of these quantities like this.  The variable Location is an array index to be used with the array variables defined above.   It's value indicates the current room.

The variable DwellCount is used to keep track of how long the suspect has been in the current location.  This can be done by initially setting it to the DwellTime (i.e. DwellCount = DwellTine[Location]) and then decrementing it every game cycle.  When it reaches zero it is time to move the suspect to the next location.
Code: ags

*** Global Script ***
   int Location;
   int DwellCount;


So now we have all we need for a single suspect. But there are many suspects; what do we do about them?  We could just append the suspects name on to the front of each of the above variables and define one set for each suspect.   If we did this, however,  it would be necessary to write a set of functions for each suspect that explicitly (i.e. by name) accessed the correct variable names.  Fortunately there is a better way of creating a set of variables for multiple suspects.

We are going to create a custom data type that contains all the info required to move a given suspect from location to location.  The keyword "struct" is shorthand for structure and it allows us to use a collection of variables as if they were one single variable.   In the example below a custom data type named "suspect" is defined.  We can now use "suspect" to define variables the same way "int" is used to define variables.   
Code: ags

*** Global Script ***
// Define the info needed for each suspect (npc) and their possible locations 
struct suspect {
   Character *Ptr;                              // Pointer to character
   int Location;                                   // Current location index  
   int DwellCount;                              // Time in current location  
   int DwellTime[MAX_LOCATIONS];   // Time to stay in each location
   int Room[MAX_LOCATIONS];          // Room number for each location
   int X[MAX_LOCATIONS];                 //  X coordniate for each location
   int Y[MAX_LOCATIONS];                 //  Y coordniate for each location
};


There is one last detail to take care of before we go on.  It will be necessary to know who the suspect character is.  The variable Ptr (pointer to character object) is added for this purpose.   A pointer to the character object is used as identification because a pointer gives us access to all the character's properties and functions.  This is illustrated in the example below
Code: ags

   Character *Ptr = cEgo;   // If a pointer to a character is defined like this ...
   Ptr..Say("Hello");            // then this line of code does exactly the same thing ...
   cEgo.Say("Hello");          // as this line of code


Now we can use our new data type to define a variable for each possible suspect like this
Code: ags

// Create an array of suspects, one for each character.
suspect Suspect[MAX_SUSPECTS];


Now for clarity's sake, the resulting code would be something like this.   I hope the explanation of this part helps you to understand my previous post a little better.  I'll give you some time to digest and respond to this and then perhaps I'll give an explanation of the functions.
Code: ags

*** Script Header ***
// Define the max number of locations a NPC can be in
#define MAX_LOCATIONS 10

// Define the max number of suspects 
#define MAX_SUSPECTS   10

*** Global Script ***
// Define the info needed for each suspect (npc) and their possible locations 
struct suspect {
   Character *Ptr;                              // Pointer to character
   int Location;                                   // Current location index  
   int DwellCount;                              // Time in current location  
   int DwellTime[MAX_LOCATIONS];   // Time to stay in each location
   int Room[MAX_LOCATIONS];          // Room number for each location
   int X[MAX_LOCATIONS];                 //  X coordniate for each location
   int Y[MAX_LOCATIONS];                 //  Y coordniate for each location
}

// Create an array of suspects, one for each character.
suspect Suspect[MAX_SUSPECTS];

densming

Nice post RickJ!  ;D  Very well laid out and informative.

Professor Plum

#6
Agreed. Thank you.

I haven't implemented your suggestions yet (and I will), but I'd like to kindly thank you Rick for the time you've invested in helping me out on this issue.


Professor Plum

#7
Is this script advisable to be tested while my work is in progress? Or should I wait till I have all my rooms and characters before playing around?

I just figure it would be good practice to start working around with it for further knowledge. From the looks of your original post, there's a huge bulk that I'm assuming is to be copied and pasted into the original script.

RickJ

#8
Quote
Is this script advisable to be tested while my work is in progress? Or should I wait till I have all my rooms and characters before playing around?
Sure but why don't I go through it with you first.  There are a couple of refinements I would like to make along the way as well.

The first thing we will need to do is clear out our the Suspect array and put in some default values.  AGS currently put zeros into all variables it creates but it hasn't always done that and we can't be certain that it always will.  It's usually considered bad practice to rely on an observed behavior of a particular compiler rather than programming language specification.  We also need a way to know when a data item is empty as it's possible to use less than the maximum number of suspects and/or locations.  To that end we will create a function that will be called from game_start() and that will fill the Suspect array with default data.

In my previous post this function was designed to set the default values for only one suspect character and so would have to be called once for each suspect.   We can improve this design so that the function will only need to be called once and will set default values for the entire data space. 

In the function below we use a while loop write data to each of the elements of the array variable Suspect.  The variable i is used as the array index.  The statement i++ increments it to the next element.

Code: ags

function SuspectInitialize() {
   int i;
   
   // Sequence through each element in the suspect array 
   i = 0;
   while (i<MAX_SUSPECTS) {
      Suspect[i].Ptr = null;                // null is used for pointers that don't point to anything
      Suspect[i].Location = 0;           // set the location index to the first location
      Suspect[i].DwellCount = -1;     // set the current elapsed time to -1 which means dwell time has elapsed
      i++;
   }
}


Ok but there isn't there something missing?  Yes, we have to add the part that initializes the Location, X, Y, and DwellTime arrays.  To do this it will be necessary to add another while loop inside the first one.  Only this loop will use the variable j to index the location array variables.
 
Code: ags

*** Global Script ***
function SuspectInitialize() {
   int i;
   int j;
   
   // Sequence through each element in the suspect array 
   i = 0;
   while (i<MAX_SUSPECTS) {
      // Write default data to individual variables
      Suspect[i].Ptr = null;                // null is used for pointers that don't point to anything
      Suspect[i].Location = 0;           // set the location index to the first location
      Suspect[i].DwellCount = -1;     // set the current elapsed time to -1 which means dwell time has elapsed

      // Write default data to array variables
      j = 0;
      while (<MAX_LOCATIONS) {
         Suspect[i].Room[j] = -1;       // Since -1 can't actually be a room, this will tell use there are no more locations
         Suspect[i].X[j] = -1;              // We may as well set the rest to -1 as well
         Suspect[i].Y[j] = -1;
         Suspect[i].DwellTime[j] = -1;
         j++;
      }
      i++;
   }
}


Cool, the first function is done.  Now all we need to do is call it once from the game_start() event handler function like this..

Code: ags

*** Global Script ***
function game_start() {
   // Initialize Suspect array 
   SuspectInitialize();
}


Now we will need another function that will allow us to write location data to the Suspect array variable.  Since there many possible locations it would be impractical to pass data for all locations in a single function call.  Instead it would be easier if a single function call wrote the data for a single location.  Such a function would then need to be called once for each location. 

So our function would need to have parameters that specified the character, the room number, the x an y positions, and the dwell time, perhaps something lke this ...

Code: ags

function SuspectSetLocation(Character *suspect, int dwell_time, in room, int x, int y) {
}


Now there is a really neat feature of the script language that allows us to define functions that extend AGS characters (and other built in objects) and that are called the same as other character functions.   The way we do this is to use a tricky declaration of the first parameter like this ...   (you may want to lookup "extender functions" in the help file)

Code: ags

// Define an extender function like this ...
function SuspectSetLocation(this Character*, int dwell_time, in room, int x, int y) {
   // There is a special pointer called "this", which is only valid inside the function ...
   // that contains the pointer to the specific character.  For example to make the ...
   // character stop moving we would write the following code
   this.StopMoving();
}

// and use it like this
cEgo.SuspectSetLocation(dwell_time, room, x, y);


Ok so the first thing we need to do in our function is to find out which element of the Suspect array corresponds to the character.  We can do this by examining each element of the array and compare it's Ptr value to the character's pointer.  That sounds like we will need to use a while loop again and we will.

Code: ags

// Define an extender function like this ...
function SuspectSetLocation(this Character*, int dwell_time, in room, int x, int y) {
   int i, idx;

   // Find the character
   i = 0;
   idx = -1;
   while (i<MAX_SUSPECTS) {
      if (Suspect[i].Ptr==this) {         // We found the character
         idx = i;                                   // Remember where
         i = MAX_SUSPECTS;               // Quit looking
      }
      else if (Suspect[i].Ptr==null) {  // We found an empty slot instead of our character ...
         Suspect[i].Ptr = this;             // so we can just put our character here
         idx = i;                                   // Remember where
         i = MAX_SUSPECTS;               // Quit looking          
      }
      i++;
   }
   // idx now contains the index of the Suspect array where our 
   // character can be found or it contains -1 meaning that our 
   // character is not in there and that we are out of space
}


Ok, that isn't so difficult but aren't we going to have to do this searching thing again in the next function?  So why don't we make a utility function that does the search and that can be used by both functions.  That way we will only need to test and debug it once; a two for.    There I went and done it, used a term not in the manual, "utility function".  We may as well take a second and talk about some things.

AGS uses a single pass complier which means that it reads the script code once and then generates the byte code used by the runtime engine.   The good thing about this kind of compiler is that it is fast or faster than a 2 pass compiler.   The bad thing is that forward references are not allowed.   For example if you define two functions in the Global Script,  the second function can call the first function but the first one can't call the second.  This is because when the compiler is reading the first function the second one is not yet defined. 

Now, if you have many many functions it could get confusing and difficult to keep organized so that it's clear which things can call  which other things.   Let's say for example you had a script with 100 functions and it became difficult to find a specific function to work on.   It would be perfectly natural to organize these functions in alphabetical order to make them easier to find.  But this could potentially cause your script to not work.  So then what can be done?  I have developed my own programming style and programming conventions (as all programmers have or will at some point) that helps me keep track of such things which I will explain after this disclaimer.

Quote
The one thing to remember is that there is no best programming convention/style and that everybody has their own preference.  It's more important to adopt a programming convention/style, that serves one's purpose, than it is to fret about which programming convention/style is best or ought to adopted.   

The way I keep things straight is to categorize functions as being either Utility Functions, Application Functions, or Event Handler Functions.  These functions are organized as shown below. 

Utility Functions perform low level operations such as making calculations,  string manipulations, searching, etc.  Generally speaking utility functions shouldn't call any other user defined functions.  As a practical matter, it's sometimes necessary to call other utility functions.  A lower case naming convention is used to make them distinct from other user created functions.   

Application Functions perform high level operations that have more of a direct relationship to the operation of the game logic.   These functions can call utility functions but not each other. 

Event Handler Functions are the standard events created by the AGS editor and called by the runtime engine in response to user actions and other game events.  These functions can call application functions but not each other.  They are also allowed to call utility functions if necessary but not usually  done.

Code: ags

*** Room or Global Script ***
======================================
   Utility Functions
======================================
function do_something() {
}

======================================
   Application Functions 
======================================
function DoSomething() {
   do_something();
}

======================================
   Standard AGS Event Handler Functions 
======================================
function roomLoad() {
   DoSomething()
}
 

Sorry about the long winded explanation but perhaps some folks over may find it helpful.  so where were we before I got into this crap?  Right, we are going to make a utility function that will search the Suspect array for a specific character and return the index.  So here it is.

Code: ags

// This function searches through the Suspect array for the specified character
// It returns an index if the character or an empty slot is found and -1 otherwise
int find_suspect(Character *ptr)  {
   int i, idx;

   // Find the character
   i = 0;
   idx = -1;
   while (i<MAX_SUSPECTS) {
      if ((Suspect[i].Ptr==ptr)||(Suspect[i].Ptr==null))  {   // We found the character or an empty slot
         idx = i;                                                                    // Remember where
         i = MAX_SUSPECTS;                                                // Quit looking
      }
      i++;
   }
   return idx;
}


So now we can rewrite our previous function to use the new find_suspect() function above to get the following. 

Code: ags

// Define an extender function like this ...
function SuspectSetLocation(this Character*, int dwell_time, in room, int x, int y) {
   int i;

   // Find the character
   i = find_suspect(this);
   if (i==-1) {
      Display("*** Error-SuspectSetLocation, max number of suspects, %d, exceeded", MAX_SUSPECTS);
   }
   else {
      // If slot is empty the initialize some things
      if (Suspect[i].Ptr==null) {
         Suspect[i].Ptr = this;               // Associate Suspect[i] with "this" character
         Suspect[i].Location = 0;
         Suspect[i].DwellCount = 0;
      }
      if (Suspect[i].Location<MAX_LOCATIONS) {

         // Write the location data to the array
         Suspect[i].DwellTime[Suspect[i].Location];
         Suspect[i].Room[Suspect[i].Location];
         Suspect[i].X[Suspect[i].Location];
         Suspect[i].Y[Suspect[i].Location];

         // Advance to next location
         Suspect[i].Location++;
      }
      else {
         Display("*** Error-SuspectSetLocation, max number of locations , %d, exceeded", MAX_LOCATIONS);
      }
   }
}


This code may a bit confusing  so I'll explain it.
Code: ags

   Suspect[i].Room[Suspect[i].Location];

Remember that i is the index of the Suspect array where our character was located.   Also remember that Room is an array of locations and that it needs an index as well.  So what is happening here is that
Code: ags
Suspect[i].Location
is being used as the location index; that's it's purpose after all.

Now to see our new function in use.   Likely we would call this function from the game_start() event handler.  Note that we haven't made any provisions to modify location data once we have it in place.  To make changes we would have to call SuspectInitialize() and start over.

Code: ags

*** Global Script ***
function game_start() {
   // Initialize Suspect array 
   SuspectInitialize();

   // Set Prof Plum's locations
   cPlum.SuspectSetLocation(400, 10, 100, 160);  // Move character to RM=10, X=100,Y=160 for 400 game cycles
   cPlum.SuspectSetLocation(800, 22, 125, 226); 
   cPlum.SuspectSetLocation(600, 15, 200, 230); 
   cPlum.SuspectSetLocation(500, 19, 212, 190); 
   cPlum.SuspectSetLocation(100, 08, 300, 160); 

   // Set Col Mustard's  locations
   cMustard.SuspectSetLocation(800, 22, 100, 160); 
   cMustard.SuspectSetLocation(400, 10, 125, 226); 
   cMustard.SuspectSetLocation(300, 16, 200, 230); 
   cMustard.SuspectSetLocation(500, 21, 212, 190); 
   cMustard.SuspectSetLocation(700, 18, 300, 160); 
}


Now for the final function that moves everyone around.  In my previous post this function was implemented a Character extender function that would need to be called once for each suspect.  Thinking it over, however, we may as well just move them all around at once and have done with it.

Code: ags

*** Global Script ***
function SuspectMove() {
   int i;
   
   // Sequence through each element in the suspect array 
   i = 0;
   while (i<MAX_SUSPECTS) {

      // Wait for dwell time to expire
      if  (Suspect[i].DwellCount>0) {
         // Decrement the dwell counter
         Suspect[i].DwellCount--;  
      }

      // Dwell time to expired, move character
      else {

         // Go to the next location
         Suspect[i].Location++;
         if (Suspect[i].Location>=MAX_LOCATIONS) Suspect[i].Location = 0;

         // Reset the dwell counter
         Suspect[i].DwellCount =  Suspect[i].DwellTime[Suspect[i].Location];

         // Move the character to the new room if the player character is 
         // in neither the current room or the next room
         if ((player.Room!=Suspect[i].Ptr.Room)&&
             (player.Room!=Suspect[i].DwellTime[Suspect[i].Location])) {

            // Move the character
            Suspect[i].Ptr.StopMoving();
            Suspect[i].Ptr.ChangeRoom(Suspect[i].Room[Suspect[i].Location],
                                                         Suspect[i].X[Suspect[i].Location],
                                                         Suspect[i].Y[Suspect[i].Location]);
         }
      }
      i++;
   }
}


You will also notice that this time I first stop the character moving and then correctly use the ChangeRoom() function to move the character to another room instead of trying to muck around with it's properties;  What was I thinking.  Now to put it all together we get the following.  It's untested, I'll leave that to you ;)

Code: ags

*** Script Header ***
// Define the max number of locations a NPC can be in
#define MAX_LOCATIONS 10

// Define the max number of suspects 
#define MAX_SUSPECTS   10

*** Global Script ***
// Define the info needed for each suspect (npc) and their possible locations 
struct suspect {
   Character *Ptr;                              // Pointer to character
   int Location;                                   // Current location index  
   int DwellCount;                              // Time in current location  
   int DwellTime[MAX_LOCATIONS];   // Time to stay in each location
   int Room[MAX_LOCATIONS];          // Room number for each location
   int X[MAX_LOCATIONS];                 //  X coordniate for each location
   int Y[MAX_LOCATIONS];                 //  Y coordniate for each location
}

// Create an array of suspects, one for each character.
suspect Suspect[MAX_SUSPECTS];

// This function searches through the Suspect array for the specified character
// It returns an index if the character or an empty slot is found and -1 otherwise
int find_suspect(Character *ptr)  {
   int i, idx;

   // Find the character
   i = 0;
   idx = -1;
   while (i<MAX_SUSPECTS) {
      if ((Suspect[i].Ptr==ptr)||(Suspect[i].Ptr==null))  {   // We found the character or an empty slot
         idx = i;                                                                    // Remember where
         i = MAX_SUSPECTS;                                                // Quit looking
      }
      i++;
   }
   return idx;
}

// This is a Character extender function that writes location info into the 
// Suspect array for one location.
function SuspectSetLocation(this Character*, int dwell_time, in room, int x, int y) {
   int i;

   // Find the character
   i = find_suspect(this);
   if (i==-1) {
      Display("*** Error-SuspectSetLocation, max number of suspects, %d, exceeded", MAX_SUSPECTS);
   }
   else {
      // If slot is empty the initialize some things
      if (Suspect[i].Ptr==null) {
         Suspect[i].Ptr = this;               // Associate Suspect[i] with "this" character
         Suspect[i].Location = 0;
         Suspect[i].DwellCount = 0;
      }
      if (Suspect[i].Location<MAX_LOCATIONS) {

         // Write the location data to the array
         Suspect[i].DwellTime[Suspect[i].Location];
         Suspect[i].Room[Suspect[i].Location];
         Suspect[i].X[Suspect[i].Location];
         Suspect[i].Y[Suspect[i].Location];

         // Advance to next location
         Suspect[i].Location++;
      }
      else {
         Display("*** Error-SuspectSetLocation, max number of locations , %d, exceeded", MAX_LOCATIONS);
      }
   }
}

// This function initializes the Suspect array with default values
function SuspectInitialize() {
   int i;
   int j;
   
   // Sequence through each element in the suspect array 
   i = 0;
   while (i<MAX_SUSPECTS) {
      // Write default data to individual variables
      Suspect[i].Ptr = null;                // null is used for pointers that don't point to anything
      Suspect[i].Location = 0;           // set the location index to the first location
      Suspect[i].DwellCount = -1;     // set the current elapsed time to -1 which means dwell time has elapsed

      // Write default data to array variables
      j = 0;
      while (<MAX_LOCATIONS) {
         Suspect[i].Room[j] = -1;       // Since -1 can't actually be a room, this will tell use there are no more locations
         Suspect[i].X[j] = -1;              // We may as well set the rest to -1 as well
         Suspect[i].Y[j] = -1;
         Suspect[i].DwellTime[j] = -1;
         j++;
      }
      i++;
   }
}

// This function moves suspects from one location to another
function SuspectMove(ibool sequentially) {
   int i;
   
   // Sequence through each element in the suspect array 
   i = 0;
   while (i<MAX_SUSPECTS) {

      // Wait for dwell time to expire
      if  (Suspect[i].DwellCount>0) {
         // Decrement the dwell counter
         Suspect[i].DwellCount--;  
      }

      // Dwell time to expired, move character
      else {

         // Go to the next location
         if (sequentially) {
            Suspect[i].Location++;
            if (Suspect[i].Location>=MAX_LOCATIONS) Suspect[i].Location = 0;
         }

         // Go to a random location 
         else {
            Suspect[i].Location = Random(MAX_LOCAATIONS-1);
         }

         // Reset the dwell counter
         Suspect[i].DwellCount =  Suspect[i].DwellTime[Suspect[i].Location];

         // Move the character to the new room if the player character is 
         // in neither the current room or the next room
         if ((player.Room!=Suspect[i].Ptr.Room)&&
             (player.Room!=Suspect[i].DwellTime[Suspect[i].Location])) {

            // Move the character
            Suspect[i].Ptr.StopMoving();
            Suspect[i].Ptr.ChangeRoom(Suspect[i].Room[Suspect[i].Location],
                                                         Suspect[i].X[Suspect[i].Location],
                                                         Suspect[i].Y[Suspect[i].Location]);
         }
      }
      i++;
   }
}

// *** Event Handler Functions ***
function game_start() {
   // Initialize Suspect array 
   SuspectInitialize();

   // Set Prof Plum's locations
   cPlum.SuspectSetLocation(400, 10, 100, 160);  // Move character to RM=10, X=100,Y=160 for 400 game cycles
   cPlum.SuspectSetLocation(800, 22, 125, 226); 
   cPlum.SuspectSetLocation(600, 15, 200, 230); 
   cPlum.SuspectSetLocation(500, 19, 212, 190); 
   cPlum.SuspectSetLocation(100, 08, 300, 160); 

   // Set Col Mustard's  locations
   cMustard.SuspectSetLocation(800, 22, 100, 160); 
   cMustard.SuspectSetLocation(400, 10, 125, 226); 
   cMustard.SuspectSetLocation(300, 16, 200, 230); 
   cMustard.SuspectSetLocation(500, 21, 212, 190); 
   cMustard.SuspectSetLocation(700, 18, 300, 160); 
}

function repeatedly _execute()  {
   // Move suspects from location to location randomly
   SuspectMove(false);
}


I appologize fior such a long post.  This was an interesting problem that and I thought it would be a good opportunity to make a sort of scripting tutorial of it.  Hopefully I haven't made too many errors or bored too many people ... :=

Professor Plum

Okay, so I am going to start working around with your code you've generously created.

I've created the custom functions under the Script Header. However, I had to fix one detail and that was to add in function after import. Otherwise the script would not allow me to proceed with testing:

Code: ags
import function SetSuspectLocation(this Character*, int dwell_time, in room, int x, int y);


So far, so good.

Now... Here's where I'm getting my first error.

Code: ags
// Define the info needed for each suspect (npc) and their possible locations 
struct suspect {
   Character *Ptr;                              // Pointer to character
   int Location;                                   // Current location index  
   int DwellCount;                              // Time in current location  
   int DwellTime[MAX_LOCATIONS];   // Time to stay in each location
   int Room[MAX_LOCATIONS];          // Room number for each location
   int X[MAX_LOCATIONS];                 //  X coordniate for each location
   int Y[MAX_LOCATIONS];                 //  Y coordniate for each location
}



Under
   int Room[MAX_LOCATIONS];   the error box is telling me that Room is already defined. I'm guessing that means I will have to choose something other than Room. However, won't that screw the rest of your script up?

RickJ

Just replace every occurrence of Suspect[].Room[]with Suspect[].Rm[].   Things like player.Room and Suspect[].Ptr.Room should stay as they are.

I also remembered that I forgot to check for valid suspect characters and valid location information  in the SuspectMove() function.  So I added some code to the previous version to do that. Here is the new version.  Let me know how you make out.

Code: ags

// This function moves suspects from one location to another
function SuspectMove(ibool sequentially) {
   int i, j;
   
   // Sequence through each element in the suspect array 
   i = 0;
   while ((i<MAX_SUSPECTS)&&(Suspect[i].Ptr!=null)) {

      // Wait for dwell time to expire
      if  (Suspect[i].DwellCount>0) {
         // Decrement the dwell counter
         Suspect[i].DwellCount--;  
      }

      // Dwell time to expired, move character
      else {

         // Go to the next location
         if (sequentially) {
            Suspect[i].Location++;
            if ((Suspect[i].Location>=MAX_LOCATIONS)||(Suspect[i].Rm[Suspect[i].Location]==-1)) {
               Suspect[i].Location = 0;
            }
         }

         // Go to a random location 
         else {
            // Pick a location at random
            Suspect[i].Location = Random(MAX_LOCATIONS-1);

            // If there is no location information defined try again
            j = 0;
            while ((Suspect[i].Rm[Suspect[i].Location]==-1)&&(j<MAX_LOCATIONS)) {
               Suspect[i].Location = Random(MAX_LOCATIONS-1);
               j++;
            }
             // Check for an error 
             if (j>=MAX_LOCATIONS) {
                Display("*** Error-SuspectMove(), no location information defined for Suspect(%d) %s", i,Suspect[i].Ptr.Name);
                Suspect[i].Location = 0;
             }
         }

         // Reset the dwell counter
         Suspect[i].DwellCount =  Suspect[i].DwellTime[Suspect[i].Location];

         // Move the character to the new room if the player character is 
         // in neither the current room or the next room
         if ((player.Room!=Suspect[i].Ptr.Room)&&
             (player.Room!=Suspect[i].DwellTime[Suspect[i].Location])) {

            // Move the character
            Suspect[i].Ptr.StopMoving();
            Suspect[i].Ptr.ChangeRoom(Suspect[i].Rm[Suspect[i].Location],
                                                         Suspect[i].X[Suspect[i].Location],
                                                         Suspect[i].Y[Suspect[i].Location]);
         }
      }
      i++;
   }
}

Professor Plum

Alright, well I changed Room to Rm and I'm no longer getting the "Room is already defined" error. Although, I assumed that it was important to have it say Room as we're trying to encompass all the available rooms as the locations.

However, now I'm getting a different error involving that same bit I posted before. The error will indicate the scripting directly below the character struct and say "missing semicolon after struct declaration".

So, I go back to see where there is a semicolon still needed and I can't seem to find anything specific within the character struct. As you say the script is untested, I figure it would make sense that errors will pop up here and there, and hopefully, they will be fixable. Right now, I'm baffled as to where that semicolon is supposed to go.

Here's a visual reference to what I'm talking about:




monkey0506

As per the manual struct definitions must be followed by a semi-colon:
struct suspect {
  Character *Ptr;
  int Location;
  int DwellCount;
  // ...
};
;)

Snake

Grim: "You're making me want to quit smoking... stop it!;)"
miguel: "I second Grim, stop this nonsense! I love my cigarettes!"

Professor Plum

Quote from: monkey_05_06 on Mon 12/10/2009 18:29:38
As per the manual struct definitions must be followed by a semi-colon:
struct suspect {
  Character *Ptr;
  int Location;
  int DwellCount;
  // ...
};
;)

Ack! Your right. I should have known that. Bear in mind this is the biggest piece of scripting I've worked with on AGS and sometimes I stupidly forget simple logic like that.

Sorry about that. I'll get back to you once I encounter any real errors.

Professor Plum

Okay, everything up to this point appears to be working. However, I'm getting a parse error at this line. I'm not really sure how to fix this:


NsMn

My guess is you're not allowed to insert spaces between the points.

monkey0506

#17
Well AGS is expecting you to be doing something. suspect.DwellTime as per the above struct definition is an int array. suspect.Location is an int. So when you're accessing the index at Suspect[i].Location in the Suspect[i].DwellTime array, you're not doing anything with it! This would be similar to just typing:

Code: ags
if (Suspect[i].Location<MAX_LOCATIONS) {
  i;
}


Just typing i; by itself doesn't do anything. I'm not sure what is supposed to be taking place here, but you're probably supposed to be assigning a value to these variables.

Edit: Upon further inspection of the above function, I think it's supposed to be like this, but Rick may need to verify:

Code: ags
if (Suspect[i].Location < MAX_LOCATIONS) {
  // Write the location data to the array
  Suspect[i].DwellTime[Suspect[i].Location] = dwell_time;
  Suspect[i].Rm[Suspect[i].Location] = room;
  Suspect[i].X[Suspect[i].Location] = x;
  Suspect[i].Y[Suspect[i].Location] = y;

  // Advance to next location
  Suspect[i].Location++;
}

Professor Plum

#18
Thank you monkey, that allowed the script to continue running.

Now I'm at one final error after trying to implement a character test in the form of:

Code: ags
function game_start() 
function InitializeSuspect() {
   cMustard.SetSuspectLocation(800, 3, 330, 500); 

.....


Here's what I'm getting when I attempt to run.




I went back to the Script Header functions as I assumed there was something wrong with it. The only difference I can see is that the imported function reads...

Code: ags
import function SetSuspectLocation(this Character*, int dwell_time, int room, int x, int y);


...with the character inside the bracket, while the Global Script function has the character placed outside next to the function....

Code: ags
cMustard.SetSuspectLocation(800, 3, 330, 500); 




Hopefully this will be the last issue to be resolved as I seem to getting close to the desired effect needed.

Lufia

Nah, that's written correctly.

Is the import written in the header of the script where the function is defined ? Do the coordinates (330, 500) in room 3 actually exist ? Just random ideas, I haven't studied the script in detail.

SMF spam blocked by CleanTalk