Collecting Items from Group Members

Tony Tobianski
1/2006

Contents


Introduction

While boning up on some of the c-script features of GS I began to investigate the use of scan-entity(). Initially, my intent was to determine a player's proximity to a door prior to open or closing. Then, I thought scan-entity() might be useful for querying entities of interest for properties or inventory items they might have in their possession. My mock up of the basic requirements of this scenario worked out fine. But, being the programmer that I am, I thought there might be a better way of solving the problem. "Scanning" carries a high price! The obvious alternative was to manage a collection of entities that could be queried for the desired information. What follows is a description of a short example that demonstates such an alternative to scan-entity() for querying entities for their possessions. I've also included some problems that relate to building on to the presented example, along with my versions of the solutions. So, if you breeze through the material in the main part of this document, you might find that the problem/solution section more challenging or interesting.

Example Program Requirements

Before we can all be on the same page, we need to look at the requirements we will be working from. Generally speaking, we want the leader of a group of entities to be able to collect the gold from their underlings. Obviously, this means we need a way to add or remove members from the leader's group, due to an untimely demise, or because of a parting of ways. We will also assume that the leader is a little greedy and will take your gold before you leave... after all, they found or earned it while under his direction. This is really enough to get us started. Another premise we will be working under is, we do not care about the proximity of the members when we gather their gold.

Getting Ready

Now, if you want to follow along, it's time to do some prep. Start WED and create a new script. Open up the new script in a text editor editor and clear things out so you have the following items:

var video_mode = 7; // 800x600 pixels
var video_depth = 32; // 32 bit mode

string your_wmb = <your.wmb>

function main()
{
	fps_max = 30;
	level_load (your_wmb);
	wait (3);
	camera.x = -100; // set a static position for the camera
	camera.y = 0;
	camera.z = 70;
}

One important item left out here is the "path" to the template directory. You may leave this in your script, or choose to move an entity over into your current map directory. Apart from this, I don't believe you'll require any items from your templates directory. I chose to move an entity into my project directory. Save your script!

Next, return to WED, close the map, then reopen it. Then, create a small room and place three actors into it. Remember, the three actors must be able to have behaviors and skills connected to them, so prefabs will not do! Finally, save this work. This will meet our needs to continue on with our coding exercise.

It's time to lay down some skeleton code to begin fleshing out the code to meet our program requirements! In this implementation, we know that we need a collection of group members, a "leader" action, and a "group member" action. So, plug in the following items into your script; we will obviously be doing a good deal of coding, so leave your text editor accessible.

// Placed near the top, before main()...
var group[10];				// container for group_member entities

// Place actions under main()...
action group_member
{
	while (1)
	{
		wait(1);
	}
}

action leader
{
	while(1)
	{
		wait(1);
	}
}

The "group" array, as well as both actions, are certainly "do nothing" items at the moment, but we'll be addressing these very shortly.

To keep things in sync, return the WED level and assign the actions to your entities. Assign "leader" to one of your entities, and "group_member" to the remaining actors.

Now for the Real Work

We are now all preped for the real brain work. Its time to conceptualize what we want to carry out under the rules of c-script.

First, let's concentrate on constructing a group. We need to find a way to collect our leader's members into a group. We have group[] waiting for us to use, but how and where can we insert the code to construct this group?

In c-script, we do not have the luxury of using an entity name as in the following:


group[1] = entity_mdl_001;
Though it may not seem like it at first, we have a more flexible way to access an entity, by using the built-in my within the member's action. But, there will be some extra things to take into account when doing so, so there is a price for the flexibiltiy.

First, we know that an action is called on each of the entities it is assigned to. We can easily assign my to an array position. However, how do we determine the position to assign it to? You have a couple choices. You can create a global variable with the initial value of 0, increasing it each time to add a new member. Another alternative is to assign a value to one of the entity's skills that represents an ID and serve as an array index. In this example, I've chosen the "skill-value-as-index" method. The added code concerns action group_member. I've placed a new listing for this action below, with additions in bold:

// Place this "define" near the top of your script, before main()...
define member_id,skill2;   // member identification: 1 - group_member_count

...

// uses member_id
action group_member
{
	// Use member_id as index to store entity into group[].
	var id = my.member_id;
	group[id] = my;
	

	while (1)
	{
		wait(1);
	}
}

Let's take a quick walkthough of the additions! First, we've "defined" a meaningful name to one of our group_member skills, SKILL2, via the define statement at the top of the listing. To use this named skill in WED you need the special uses comment, placed just above the action to which it applies. All named skills you wish to appear for the action must appear after "uses", separated by commas.

Now, in the action, the ID stored in my.member_id is moved to the local variable, id. This, in turn, is used as the index for group[] to designate where the current my should be stored.

Now, let's think about how our indexing has to work. Usually one would start storing things in an array at position 0. However, if we don't physically change member_id within WED, it will have a value of 0. Therefore, to ensure valid membership, I have chosen to start my member_id at 1, and work up consecutively, from one group member entity to the next. With this in mind, you should return to WED, select each of your group_member entities, go to their properties dialog, and assign their member_id property accordingly.

Once you've completed the steps above, you should be able to test your script. Try placing a breakpoint; line at the top of group_member and watch the values as it stores the items into group[].

Show Me the Money

Well, we wont see the money, but we sure need to determine how we are going to represent, store, and manage that all important gold! The gold should obviously be represented numerically. Where is it stored? Going back to our requirements, it is assumed that each of the group members may have a piece of the pie, so each needs a holding. I've chosen to use an entities skill property as the purse. These additions are simple...

// Place this line with the other define for "member_id"...
define gold_owned, skill1;     // "group_member" gold cache
...
// Change the "uses" comment for group_member to include the "gold_owned" skill...
// uses member_id, gold_owned
action group_member
{
...

Having made these simple changes to your code, you can now return to WED, visit each of your group_member entities and assign some reasonable values to their gold_owned property.

Okay, now we have some booty to count! Let's determine how we're going to accomplish the tally! First, we know that something has to signal that a collection must be made. In a real game, most anything could kick off the collection, but we only want this to be simple here. The easiest way to signal the count would be an "on_key" assignment. Below you will see my activate_gold_collection function and its assingment to on_g:

// Place this somewhere above your action definitions...
function activate_gold_collection()
{
}

...

// Add this line to the bottom of your script...
on_g = activate_gold_collection;    // collect gold from members

With this work complete, we have a way to gather gold at will. We can now flesh out the code in the activate_gold_collection function to do the collecting.

There are quite a few things to account for in this new function. First, it would seem obvious that we will need to loop through group[], get each member, get its gold_owned property, add it to a running total, then reset the member's gold_owned back to 0.

The above sets up the basic logic, but there are some sub-requirements to account for. First, what will our "while" condition be? Remember, our storage in group[] starts at 1. But how many members do we have? In this instance we know we have 2, but we really didn't address determining this in a dynamic way. Let's solve this problem by creating a global variable, group_member_count at the top of your listing:

var group_member_count = 0;	// number of group members

Now, let's return to the group_member action and make use of this change:

	// Add this anywhere above the "while" loop in "group_member"...
	group_member_count+=1;

From our discussion of sub-requirments for our gold counting function, we determined we need a place to hold a running total. We will need the result outside of this method, so this calls for another global variable:

	// Add at the top of your listing witn "group_member_count" and "group[]"...
	var gold_sum = 0.0;	// temp for gold summation from group members

Now, let's add what we've learned to activate_gold_collection():

function activate_gold_collection(){
	var index = 1; // Set the starting index into group[].
	gold_sum = 0;  // Reset global gold_sum to 0.

	// While index is 1 to number of group members...
	while (index <= group_member_count )
	{
		// Add gold to running count...
		gold_sum += group[index].gold_owned;

		// Remove gold from member's cache...
		group[index].gold_owned = 0;
		index+=1;
	}
}

This code is very close to being complete. There is one issue, however, we have to overcome, as this code probably will not compile. I don't believe the compiler will understand group[index].gold_owned. The problem is, it may not understand that "group[index]" is holding a value of type entity. To make this clear we need to add a global entity pointer to use the value in "group[index]". Here are the additions and changes:

// Place with the other global variables at the top of the script...
entity* current;	// temp to examine member stored in group[]
...
// Changes/additions are in bold...
function activate_gold_collection(){
	var index = 1; // Set the starting index into group[].
	gold_sum = 0;  // Reset global gold_sum to 0.

	// While index is 1 to number of group members...
	while (index <= group_member_count )
	{
		// Add gold to running count...
		current = group[index];
		gold_sum += current.gold_owned;

		// Remove gold from member's cache...
		current.gold_owned = 0;
		index+=1;
	}
}

There are still a couple of logic issues to contend with in our gold counting function. It may not be apparent, but we need a way to set a flag that will notify the rest of the program that the summation has been completed. Notice, we totalled the gold holding for the group, but these holding need to be moved over to the "leader", and we can't give the gold to the leader until we know that it has been gathered. Thus, we need an additinal global value to serve as a true/false flag to signal whether or not the gold has been money bagged:

// Place with the other globals at the top of the script...
var leader_has_gold = 0;  // has gold been collected?
...
// Changes/additions are in bold...
function activate_gold_collection(){
	var index = 1;		// Set the starting index into group[].
	gold_sum = 0;		// Reset global gold_sum to 0.
	leader_has_gold = 0;	// Reset gold_counted.
...

Further clarification is probably needed. leader_has_gold is not so much used to say, "I'm done counting.", but "Leader, you don't have the collection yet.", which is a clue to who will be using this value!

You may have wondered what happeded to our "do nothing" leader action. For the sake of this exercise, the leader only needs to collect the gold that was gathered. However, as simple as this action is, I should point out that the gold is being moved into a predefined "skill" property of the "leader" entity. Here's a listing for you to look at, then we'll do a quick walkthrough:

// Placed with the other "defines" at the top of the listing...
define gold_collected, skill1; // "leader" gold cache
...
// I've bolded the additions to "leader", and its "uses" comment...

// uses gold_collected
action leader
{
	// First time collection of gold from team members.
	// Caution: May not get all gold, because all members
	// may not have been evaluated yet!!! (See group_member action)...
	activate_gold_collection();

	while(1)
	{

		// If we haven't collected gold_sum into "me.gold_collected"...
		if ( !leader_has_gold )
		{

			// Collect global gold_sum into group leader's cache...
			me.gold_collected += gold_sum;

			// Reset global gold_counted flag to avoid needless attemp to regather gold.
			leader_has_gold = 1;
		}
		wait(1);
	}
}

The leader action is very simple. The first line activates gold collection for the very first time, prior to entering it's terminal loop. Once inside the loop, a check is made to see if leader_has_gold is false. If it is false, then our global gold_sum is moved into the predetermined "skill" property of the leader, represended by me.gold_collected. Finally, leader_has_gold is set to 1, or "true", to avoid the group-to-leader transfer until leader_has_gold is set to 0 when the gold collection is triggered again.

Death and Taxes

We have constructed a basic framework for hoarding our gold, but we still need to address the need to handle the demise of a group member, or one's desire to strike off on their own.

First, let's analyze where membership termination fits logically within c-script. An action that is assigned to an entity extends our access to that entity's details. Since we want to remove a member, the first place to look for the incorporation of this new functionality should be our existing group_member action. So, for now, keep in mind that this is the action we will be returning to. First, we must come up with a quick and dirty way to trigger a member's removal from the clan. An "on_key" function worked great for kicking off our gold collection, so it should fit nice here, as well. Here is the associated code:

// Place this with our other global variables at the top to the script...
var remove_activated = 0; // request to remove member flag

// Place this function anywhere prior to the next "on_r" assignment...
function activate_remove(){
	remove_activated = 1;
}

// Place this line by the other "on_key" assignment statement...
on_r = activate_remove;				// remove a member

As a quick walkthrough, remove_activated is needed to signal the remove request. We will use a value of 1 to signal removal. Note that it is initially set to 0, or "no remove request". Next, we simply need a function, activate_remove() that sets remove_activated to 1, "remove requested". Finally, we assign activate_remove() to the on_r key event; "r" for "remove", of course.

Now we are ready to implement the actual "remove" processing. However, let me admit up-front that the code I'll present is a "hack". Despite the shortcomings of the methodology I'll present here, the code still serves as a good starting point. Almost any code can be polished up, given time and attention. So let's get on with it!

Let's look at what "remove" really mean in terms of our existig code. Since membership is represented by storage in group[] your attentions should turn here. So, how do we remove a member from "group"? Well, as in many instances, there is a hard way, and their is an easy way. The "hard way" involves finding the group member in the array and shifting values to fill the void that has been made, and the member count is reduced by one. Here we will be using the easy way. We will simply use the member's member_id to find it's corresponding place in group[] and set that position equal to 0. Now, to make things a little easier, the code I use also presumes the member is not only leaving the group, but physically being removed from the level. Since the "remove" logic will be placed in the member entity group_member action, only members that physically exist and are connected to this action will ever query for removal.

Before addressing the group_member action, let's come up with a function it can call to carry out the requirements of our remove; collect the member's gold, remove them from the group, and banish them from the game. Though we really don't need it, it would be clearer if the function take an "ID" for a member. Here is my version of such a function, remove_member(id):

// Place this whole function above "action group_member", 
// but after "activate_cold_collection"...
function remove_member( id )
{
	// Pretending the removal request if for "member_id == 1"
	if ( id == 1 )
	{
		activate_gold_collection();
		group[id] = 0;
		wait(1);
		remove(my);  // c-scripts built-in remove()
		remove_activated = 0;
	}

}

First, the member's id is checked against the ID of the member to be removed from the group. Notice that I've hard-coded "1" as the member for removal. If this isn't the member to remove, nothing is done. But, if the member's ID (my.member_id) is equal to 1, then we do our work. We already have our tools to collect gold, so I just use what I already have, activate_gold_collection(). Then, as stated earlier, I set group[id] equal to 0 to clear the entity pointer that was stored there. Before calling remove(), wait() must be called, per the manuals instructions for proper operation. Next, the pre-existing, built-in remove() function is used to physically remove the player from our level. You are familiar with "my", but you might be wondering why we can use it here. Well, we know that this function is going to be called from within the group_member action, which we've connected each of our members to. When a function is called from within an action, the "my" representation of the entity carries down to the function like magic! This is a very useful and important thing to remember! Finally we have to signal the end of the removal process by setting remove_activated to 0. Remember, remove_member() is invoked by the action when remove_activated is set to 1.

Now, finally, we can turn our attention to the group_member action. If you recall, we have a "do-nothing-and-wait" loop in this action. It serves as our listener. In this case, it needs to listen for a removal request. If it recieves the request, we need to call our remove_member() function. Here is the code to add within the "while loop" of group_member:

// Add this code within "action group_member",
// inside the "while", prior to the "wait(1)"...

		if ( remove_activated )
		{
			remove_member( id );
		}

This code is pretty straightforward, since we've dicussed what it does. The only thing you might need to be reminded of is, local variable id is set to my.member_id near the top of the action, prior to the "while loop", so its passed as a parameter to remove_member()

We are almost done, but we have one last detail to address, since we changed the conditions of our program near the end by introducing the removal of an entity from group[]. If you look back at the activate_gold_collection() we currently expect all members to still be present in our list. When current = group[index]; is executed, we expect group[index] to contain a pointer to an entity. Now, the "1" slot will actually be 0 if its removal took place. The statements that follow assume current to be a pointer to an entity, certainly not 0! We must fix this with a check so we will only collect gold when group[index] != 0:

// Additions are in bold...

function activate_gold_collection(){
	var index = 1; // Set the starting index into group[].
	gold_sum = 0;  // Reset global gold_sum to 0.

	// While index is 1 to number of group members...
	while (index <= group_member_count )
	{
		if ( group[index] != 0 )
		{
     		// Add gold to running count...
     		current = group[index];
     		gold_sum += current.gold_owned;

     		// Remove gold from member's cache...
     		current.gold_owned = 0;
		}
		index+=1;
	}
}

Now, as a small exercise, you can create a panel with a "digits" member and use this to display the gold collected by the leader of the group, or any other runtime info you'd like to examine. You may also find it useful to place a breakpoint; in the functions and actions. I've included a full code listing at the very end of this document, in the event that I may have missed a detail during our incremental construction of our code. Remember to change the name of the level and include the "path" designation for template items you want to include when playing with this code. The code is heavily commented to aid your study of the code. If you feel you understand the code, the comments may only clutter things up for you. If you remove them, you will actually see what little actual code was needed to accomplish this exercise.

Conclusion

There are plenty of places in a level where scan_entity() would be a required tool. To avoid the overhead, we can find ways to avoid the use of scan_entity() for tasks, such as searching for entities that have gold we want to collect. We've shown that its very possible to use an array to manage a group of entities. If you are relatively new to c-script, I hope the analysis, design, and development of this bit of code helped you gain a clearer perspective of how the working parts fit together. Code does not have to be perfect the first time through, but rather a working foundation for further improvements.

Further Study

As stated earlier, we took some short-cuts to develop some foundational logic for group management, collecting gold and removing group members. There are a number of places where improvements can be made. Furthermore, you could add the other logic pieces required to make up a whole finished game level. But let's focus on some of the items that are truly lacking in the example code:

1) Gold counting will still occur if the leader is the only one in the group. What could be done to avoid this from happening?

2) The example code does not reduce the group_member_count value when an entity is deleted. What would have to be done in the code to be able to keep an accurate member count, without affecting the reading of group[] (not miss members still in the list)?

3) Program in the capability to add gold to a player and trigger collection by the leader; you need not be concerned with which player to give the gold to.

4) Explore the possibility of listening for the activate_gold_collection request from within the group_member action and implement this in your code. Does this remove the need for group[]? What possible benefits do you see when using an array for storing group members?

5) There is a good chance that there would be more than one group in a good adventure game. Change the code developed thus far to include logic that handles more than a single group, including the ability to rob another group of its gold.

6) Come up with a way to spawn new players and add them to the groups. New group members must possess a member ID, group ID, and able to own gold.

The above is a sizable list of problems and ideas to explore. And, to be quite honest, I didn't list them because I have the answers, but because they are truly items for exploration and learning... they come from my own observations and curiosity. Have great fun coding and exploring!

Code Listing

var video_mode = 7; // 800x600 pixels
var video_depth = 32; // 32 bit mode

/////////// DOOR PROCESSING /////////////////////////////////////////////////////////////

string work18_wmb = <work18.wmb>
 
/////////////////////////////////////////////////////////////////////////////////////////
// GLOGALS:
var group[10];				// container for group_member entities
entity* current;			// temp to examine member stored in group[]
var gold_sum = 0.0;			// temp for gold summation from group members
var group_member_count = 0;	// number of group members
var leader_cache;			// for panel display of leader's gold_collected property

// FLAGS:
var leader_has_gold = 0;  // has gold been collected from each "group_member" for "leader"?
var remove_activated = 0; // request to remove a member?

// SKILLS USED:
define gold_owned, skill1;     // "group_member" gold cache
define member_id,skill2;       // member identification: 1 - group_member_count
define gold_collected, skill1; // "leader" gold cache

panel display_leaders_cache
{
	pos_x = 0;
	pos_y = 0;
	digits = 410, 200, 2, _a4font, 1, leader_cache;
	flags = overlay, refresh, visible;
}

function main()
{
	fps_max = 30;
	level_load (work18_wmb);
	wait (3);
	camera.x = -100; // set a static position for the camera
	camera.y = 0;
	camera.z = 70;
}

// FUNCTIONS/ACTIONS ///////////////////////////////////////////////////////////////

// Process a request to gather gold from members...
function activate_gold_collection(){
	// Use members stored in group[] via action group_member to count
	// the amount of gold owned by the group...
	var index = 1;

	// Reset global gold_sum to 0...
	gold_sum = 0;

	// Set global gold_counted flag to false...
	leader_has_gold = 0;

	// While index is 1 to number of POSSIBLE group members...
	while (index <= group_member_count )
	{

		// Only examine group[] members that are not 0;
		// see remove_member() below for "group[id] = 0"...
		if ( group[index] != 0 )
		{
			// Set entity pointer, current, to stored group member
			// and add its "gold_owned" to global "gold_sum"...
			current = group[index];
			gold_sum += current.gold_owned;

			// Remove gold from member's cache...
			current.gold_owned = 0;
		}
		index+=1;
	}
//	NOTE: You cannot subtract 1 from global group_members count,
//      when deleting a member.  If member with ID of 1 was remove, 
//      subtracting from 1 from group_members would reduce group_members to 1, 
//      thus we would not find the still existing group[2] (member with ID of 2).
}

// Process a request to remove the member with "member_id == id".
function remove_member( id )
{
	// Pretending the removal request if for "member_id == 1"
	if ( id == 1 )
	{
		// Make sure we get their gold first!!!...
		activate_gold_collection();

		// Set group[] slot to 0; test this during sum so we don't attempt
		// to process an entity that doesn't really exist.
		group[id] = 0;

		// built-in remove() of "my"...  
		remove(my);

		// Signal the end of the "remove"...
		remove_activated = 0;

//	NOTE: You cannot subtract 1 from global group_members count,
//      because if member with ID of 1 was remove, subtracting from
//      1 from group_members would reduce group_members to 1, 
//      thus we would not find the still existing group[2]
//      (member with ID of 2).
	}

}

// GROUP MEMBER ACTION: responsible for adding itself to global group[]
//    based on member_id and processing a removal from membership.
//
// GROUP_MEMBER Skills for WED...
// uses gold_owned, member_id
action group_member
{
	// Use member_id as index to store entity into group[].
	// Being in an action, group[] is always current.
	group_member_count+=1;
	var id = my.member_id;
	group[id] = my;

	while (1)
	{
		// Readjust group[] and re-sum gold upon member removal...
		if ( remove_activated )
		{
			remove_member( id );
		}
		wait(1);
	}
}

// LEADER ACTION: Responsible for collecting gold collected from its group members.
// 
// LEADER Skills for WED...
// uses gold_collected
action leader
{
	// First time collection of gold from team members.
	// Caution: May not get all gold, because all members
	// may not have been evaluated yet!!! (See group_member action)...
	activate_gold_collection();

	while(1)
	{
		// If we haven't collected gold_sum into "me.gold_collected"...
		if ( !leader_has_gold )
		{

			// Collect global gold_sum into group leader's cache...
			me.gold_collected += gold_sum;

			// Uses display_leaders_cache panel number to display collected gold...
			leader_cache = me.gold_collected;

			// Reset global gold_counted flag to avoid needless attemp to regather gold.
			leader_has_gold = 1;
		}
		wait(1);
	}
}

// Sets global remove_activated flag to invoke membership removal processing.
function activate_remove(){
	remove_activated = 1;
}

// TEST KEY EVENTS TO INVOKE GOLD COLLECTION AND REMOVAL FROM GROUP...
on_g = activate_gold_collection;	// collect gold from members
on_r = activate_remove;				// remove a member

Problem Solutions

Problem 1 & 2 Solutions

Exercise 1 & 2 are very much related, because both involve group_member_count. Therefore, I've included a solution that covers both problems. I've placed "***" in front of the applicable change description comments, as well as put them in uppercase. I've removed many of the comments that existed in the first version of the code.

var video_mode = 7; // 800x600 pixels
var video_depth = 32; // 32 bit mode

/////////// DOOR PROCESSING /////////////////////////////////////////////////////////////

string work18_wmb = <collect1.wmb>
 
/////////////////////////////////////////////////////////////////////////////////////////
// GLOGALS:
var group[10];				// container for group_member entities
entity* current;			// temp to examine member stored in group[]
var gold_sum = 0.0;			// temp for gold summation from group members
var group_member_count = 0;	// number of group members
var leader_cache;			// for panel display of leader's gold_collected property


// FLAGS:
var leader_has_gold = 0;  // gold collected from each "group_member" for "leader"?
var remove_activated = 0; // request to remove a member?

// SKILLS USED:
define gold_owned, skill1;     // "group_member" gold cache
define member_id,skill2;       // member identification: 1 - group_member_count
define gold_collected, skill1; // "leader" gold cache

panel display_leaders_cache
{
	pos_x = 0;
	pos_y = 0;
	digits = 410, 200, 6, _a4font, 1, leader_cache;
	flags = overlay, refresh, visible;
}

function main()
{
	fps_max = 30;
	level_load (work18_wmb);
	wait (3);
	camera.x = -100; // set a static position for the camera
	camera.y = 0;
	camera.z = 70;
}

// FUNCTIONS/ACTIONS ///////////////////////////////////////////////////////////////

// Process a request to gather gold from members...
function activate_gold_collection(){
	var index = 1;
	gold_sum = 0;
	leader_has_gold = 0;
	var num_counted = 0;   // *** COUNT ACTUAL MEMBERS FOUND IN GROUP[]

	// *** WHILE GROUP_MEMBER_COUNT NOT 0 && NUM_COUNTED < GROUP_MEMBER_COUNT ...
	while ( group_member_count > 0 && 
			num_counted < group_member_count )
	{
		if ( group[index] != 0 )
		{
			current = group[index];
			gold_sum += current.gold_owned;
			current.gold_owned = 0;
			num_counted += 1;      // *** ADD 1 TO FOUND MEMBER
		}
		index+=1;
	}
}

// Process a request to remove the member with "member_id == id".
function remove_member( id )
{
	// *** CHANGED TO TEST REMOVAL OF ENTITIES WITH ID 1 OR 2;
	// *** ALLOWS MULTIPLE PRESSES OF KEY "R".
	if ( id  == 2 || 
		id  == 1)	
	{
		activate_gold_collection();
		group[id] = 0;

		// *** ENSURE THAT GROUP_MEMBER_COUNT DOES NOT GO NEGATIVE
		// *** SUBTRACT 1 FROM MEMBER COUNT
		if ( group_member_count > 0)
		{
			group_member_count-=1;
		}
		wait(1);		// *** BUG FIX: NEEDED FOR PROPER FUNCTIONING
		remove(my); 
		remove_activated = 0;
	}
}

// uses gold_owned, member_id
action group_member
{
	var id = my.member_id;
	group_member_count+=1;
	group[id] = my;

	while (1)
	{
		// *** ADD CHECK FOR GROUP_MEMBER_COUNT NOT EQUAL 0
		if ( remove_activated && 
			 group_member_count != 0 )
		{
			remove_member( my.member_id );
		}
		wait(1);
	}
}

// uses gold_collected
action leader
{
	activate_gold_collection();

	while(1)
	{
		// If we haven't collected gold_sum into "me.gold_collected"...
		if ( !leader_has_gold )
		{
			me.gold_collected += gold_sum;
			leader_cache = me.gold_collected;  // ... to display panel
			leader_has_gold = 1;
		}
		wait(1);
	}
}

function activate_remove(){
	remove_activated = 1;
}

on_g = activate_gold_collection;    // collect gold from members
on_r = activate_remove;				// remove a member

Problem 3 Solution

The solution to this problem is composed most of new pieces, rather than changes to existing cold, so the following are the additions, and only the actions that have changed.

var found_gold_activated = 0;	// *** TRIGGER PLAYER'S GOLD COLLECTION
var more_gold = 0;		  // *** INDICATE THAT PLAYER COLLECTED GOLD
...
// *** PLAYER COLLECTS GOLD
function get_gold_found( amount ){
	me.gold_owned+=amount;
	found_gold_activated = 0;
	more_gold = 1;		// SIGNAL LEADER THAT MEMBER HAS NEW GOLD
}
...
// uses gold_owned, member_id
action group_member
{
	var id = my.member_id;
	group_member_count+=1;
	group[id] = my;

	while (1)
	{
		if ( remove_activated && 
			 group_member_count != 0 )
		{
			remove_member( my.member_id );
		}
		// *** PLAYER FINDS 50 GOLD
		if ( found_gold_activated )
		{
			get_gold_found( 50 );
		}
		// ************************
		wait(1);
	}
}
...
// uses gold_collected
action leader
{
	activate_gold_collection();

	while(1)
	{
		// *** IF MEMBER FOUND AND COLLECTED GOLD, GET IT...
		if ( more_gold )
		{
			activate_gold_collection();
			more_gold=0;			
		}
		// **************************************************
		if ( !leader_has_gold )
		{
			me.gold_collected += gold_sum;
			leader_cache = me.gold_collected;  // ... to display panel
			leader_has_gold = 1;
		}
		wait(1);
	}
}
...
// *** SET NOTIFICATION OF INITIAL FIND
function activate_found_gold(){
	found_gold_activated = 1;
}
...
// *** SET KEY TO TRIGGER "FOUND GOLD" NOTIFICATION 
on_a = activate_found_gold;			

When programming, there are usually a variety of ways to meet the requirements for the task. If you approached the problem from a different angle and it proved successful, that's great. The above only depicts how I chose to solve it.

Problem 4 Solution

The purpose of these questions was to stir you toward thinking about the use a function with a global array and how this differs from how an action actually gets executed. Understanding the distinction between function and action is fundamental in your being to implement c-script solutions. If the difference has escaped you, I'll give you a description below.

A function is a unit of re-usable code that is called on demand, does its job and then ends. There is only one copy of each defined function in a program. It usually is not tied to any entity, except indirectly when called from an action, which magically passes "my" to the function. In contract, an action is generally meant to be a "listener" that is always present and looking for conditions to that triggers special processing, only continue listening. Furthermore, and most important, their can be muliple copies of the same action running simulaneously. The number of copies is determined by the number of entities you've connected the action to. In our example, there are two entities that have the group_member action connected them, so their are two group_member "listeners" running simultaneously during the execution of the script. An action is literally the brains of an entity, and each entity it's connected to. Because of this, each entity will behave the same. The only way to make group_member entities different is to give each a different attributes throught the WED properties panel (skills and flags), and program in the conditions for behavior in the single commonly used action; "if my intellegence is 90, then do this. If my intellegence is 50, then do that."

In turning back to our example program, heavy use of global indicators were used to signal the completion of a task by one of the running copies of the action, so other running copies of the action would not attempt to carry out the task again; one "remove_activated" was set to 0, no other group_member will attempt to remove the entity they are connected to.

So, in exploring the possibility of processing the collection of all the gold from each of the members, we know that each group_member action knows who it is connected to (my), so can determine the gold of each. We already have a global variable to collect the gold into, which is then passed on to the leader. So, the real problem is, "How will the running group_member action listeners know when they should stop gathering gold? From which member did the collection begin, and on which member should it end?" In other words, "Have all the entities to gather gold from been visited?" To help answer this question, we have the existing group_member_count global variable that tells us how many entities we have. The activate_gold_collection() function also has a num_counted variable that's used to track the number of members found and examined. Therefore, modifications could be made to activate_gold_collection() to add 1 to num_counted, with num_counted changed to a global variable. If num_counted equals group_member_count we know we've visited all entities, and we would need to add a flag that's visible to the group_member listeners so they know not to keep collecting gold from my.

To complete the discussion of the alternative presented above, we would in fact be able to remove the use of the group[], including lines of code used to assign each entity to a place in group[] located at the top of group_member, prior to the "while loop". But, remember, by removing group[] you affect the logic used in remove_member().

What are the benefits of group[]? The only real plus that I personally see is, it allows up to separate all group members out of their actions, allowing us to visit them all within a single function call. However, the processing of group[] requires that we check to see if a slot is blank, which many could be if their were a lot of entities and a good number of them were killed off. If we process the gold collection via the group_member action using the exposed my, we wouldn't need to wonder if the member still exists.

If you haven't done so already, try your hand at making the changes using the major clues I've give you. You should benefit greatly from this exercise. I've sited my code changes for my solution below:

// *** NO LONGER NEEDED
// *** var group[10];
// *** entity* current;

...

var num_counted = 0;		// *** NEW GLOBAL COLLECTION MEMBER TRACKER
var collect_gold = 0;		// *** NEW GROUP GOLD COLLECTION FLAG

...

// *** CHANGED NAME FROM activate_gold_collection() TO get_players_gold()
// *** REMOVED ALL ARRAY PROCESSING CODE, ETC.
function get_players_gold(){
	gold_sum += me.gold_owned;
	me.gold_owned = 0;
	num_counted += 1;
}

// *** CHANGED PURPOSE OF activate_gold_collection()
// *** NOW ONLY SETS FLAG FOR CONDITION IN GROUP_MEMBER ACTION
function activate_gold_collection(){
	collect_gold = 1;
}

function remove_member( rem_id )
{
// *** REMOVED UNNEED REFERENCES TO GROUP[]
// *** REMOVED CHECK FOR group_member_count > 0
	if ( rem_id  == 2 || 
		 rem_id  == 1)	
	{
		// *** REPLACES EXCESSIVE CALL TO activate_gold_collection()
		// *** SINCE WE CAN GATHER GOLD HERE.
		if ( my.gold_owned > 0){		
			gold_sum+=my.gold_owned;
			leader_has_gold=0;
		}
		group_member_count-=1;
		wait(1);
		remove(my); 
		remove_activated = 0;
	}
}

action group_member
{
	// *** NO LONGER NEEDED 
	// var id = my.member_id;
	//	group[id] = my;

	group_member_count+=1;

	while (1)
	{
		...

		// *** NEW GROUP GOLD COLLECTION LOGIC ***********
		// *** ADDED BELOW OTHER "IF" BLOCKS
		if ( collect_gold )
		{
			get_players_gold();
			if ( num_counted == group_member_count )
			{
				collect_gold = 0;		// RESET COLLECTION FLAG
				num_counted = 0;		// RESET NUM_COUNTED
				if ( gold_sum > 0 )     // DON'T SET FLAG FOR LEADER IF 0 GOLD
				{
					leader_has_gold = 0;	// RESET FOR TRANSFER OF GOLD TO LEADER
				}
			}
		}
		// ************************************************
		wait(1);
	}
}

action leader
{
	...

	while(1)
	{
		if ( more_gold )
		{
			activate_gold_collection();
			more_gold=0;
		}			
    	if ( !leader_has_gold )
    	{
    		me.gold_collected += gold_sum;
    		leader_cache = me.gold_collected;  // ...to display panel
    		gold_sum = 0;       // *** MOVED THIS RESET TO HERE
     		leader_has_gold = 1;
   		}
		wait(1);
	}
}

You should be able to make the above changes to your preceding version of the code to reach a working script. The most interesting thing about this code is the new use of activate_gold_collection(). This function used to contain processing that looped through the group[] array and summed the gold found. Now, it only sets a flag that signals the group_member action to start collecting the gold.

Problem 5 Solution

I would approach solving this problem in two steps. First, alter the code to recognize and track an additional group. Next, you would add "rob" or "take" processing.

Step 1: The code changes required to recognize and track additional groups are quite numerous, but relatively easy to make. The map requires another set of entities to make up the second group; 1 "leader", and a couple "group_member" entities. Once these are added, you need to add an additional defined skill value for "group_id", allowing you to set "group_id" from within the properties dialog of each entity; make sure both "group_member" and "leader" can use "group_id". If you add the defined skill while the map is open, you may need to close and re-open the level for "group_id" to show up. Each leader, and its respective members, need to have their "group_id" properties set to match one another. Finally, give the new group members of the new group some initial "gold_owned" values.

Most of the changes to the code to transform current processing to handle multiple groups relates to our notification flag (i.e., collect_gold ) and counting (i.e., gold_sum) variables. Each group needs its own set of notification flags and counting variables. You want the code to be flexible, so it doesn't make sense to use additional variables for each group like, "gold_sum_group_1", "gold_sum_group_2", etc. Arrays would work nicely for this purpose, with the group ID used to index to the desired value; "gold_sums[my.group_id] = my.gold_owned", with group IDs being set from 0 to N. So, create arrays for your state flags and accumulators, and do away with their corresponding value variables. Then, replace all usages the old variables in all actions and functions to the flag[my.group_id] and counter[my.group_id] array syntax. It would be a good idea to count the number of groups used in the game by adding a global variable, "num_of_groups", then add 1 to this each time a "leader" is encountered (prior to the "while loop" of the "leader" action). Group members would still be counted in "group_member" in much the same way, but you will need to use the array syntax with a new group_member_counts[]; "group_member_counts[my.group_id]+=1;".

Next, you need a way to control which group you are triggering an action for. For instance, we used the "a" key to tell a group member to pick up some gold. Now we want to control which group's member to process the "pick up gold" request. Likewise, we want to be able to do the same with the "remove member" request. I personally built a small function to toggle between groups 0 and 1 each time one of these requests is made. This is acceptable here, because we only want a way to test the functionality of our code with our two test groups.

One problem I ran into after making all the above changes, was my code was failing to pull in all the gold from each group during the initial collection that occurs at startup. This was a hard problem to solve at first, but I solved it by checking to see if "number_of_groups" equals 2 (that both leaders have been loaded) in the "group_member" action. If both "leaders" were present, I called a function that set all collect_gold_flags[] to 1, thus being found later by the "group_member" test for these.

Finally, if you didn't do so already, you will want an additional panal element to print the new group's gold sum, as held by the leader. You will need to have a condition statement to determine which gold sum you are updating in the "leader" action.

I've include my source for "Step 1" below. If I lost you in my explanation, the code listing should pull it all together for you. You may notice that I made a few minor adjustments. For instance, I moved many of my flag reset statements to be the very first instructions being executed when processing the request. This just makes things safer, ensuring we don't get a double process hit for a single request. Also, I defined "ON" and "OFF" for 1 and 0 respectively, making flag sets/resets more readable. Because the changes are so significant, the listing is a full one this time. Once again, "***" with uppercase comments stress additions and changes to my previous code.


var video_mode = 7; // 800x600 pixels
var video_depth = 32; // 32 bit mode

/////////// DOOR PROCESSING /////////////////////////////////////////////////////////////

string work18_wmb = ;
 
/////////////////////////////////////////////////////////////////////////////////////////
// GLOGALS:

var leader_cache;			// for panel display of 1st leader's gold_collected

// *** ADDITIONAL VARIABLE FOR DISPLAY OF ADDITIONAL LEADER'S GOLD
var leader_cache_2;			// for panel display of 2nd leader's gold_collected

// *** NEW ACCUMULATORS; COUNTS PER GROUP ARRAYS (GROUP ID AS INDEX)
define MAX_GROUPS,10;			// *** NEW MAXIMUM NUMBER OF GROUPS ALLOWED CONSTANT
var gold_sums[MAX_GROUPS];		// *** NEW ARRAY FOR GROUPS GOLD SUMS
var group_member_counts[MAX_GROUPS];	// *** NEW ARRAY FOR GROUPS MEMBERS COUNTS
var num_counteds[MAX_GROUPS];	// *** NEW ARRAY FOR GROUPS NUMBER MEMBERS COUNTED

// FLAGS:
// *** NEW FLAG ARRAYS TO TRACK EVENT BY BY GROUP
var collect_gold_flags[MAX_GROUPS];		// flags for collect group's cold
var found_gold_activated_flags[MAX_GROUPS];	// triggers player picking up gold
var more_gold_flags[MAX_GROUPS];		// indicate that player picked up gold
var leader_has_gold_flags[MAX_GROUPS]; 	// indicate leader can collected gold
var remove_activated_flags[MAX_GROUPS];	// indicates a remove request

// *** NEW TEST VARIABLE TO SET GROUP FOR REQUEST
var fake_out_group = 0;  // *** FAKE WHICH GROUP THE TASK SHOULD OCCUR ON

// *** NEW DEFINES FOR ON/OFF STATES
define ON, 1;
define OFF, 0;

// *** NEW DEFINE FOR NUMBER OF INITIAL GROUPS
define INITIAL_GROUP_COUNT, 2;

// *** NEW NUMBER OF GROUPS ACCUMULATOR
var number_of_groups=0;	  // incremented when a group leader is loaded		

// SKILLS USED:
define gold_owned, skill1;	// "group_member" gold cache
define member_id,skill2;	// member identification: 1 - group_member_count
define gold_collected, skill1;	// "leader" gold cache
define group_id,skill3;		// *** NEW GROUP ID (0 TO n)

panel display_leaders_cache
{
	pos_x = 0;
	pos_y = 0;
	digits = 470, 200, 6, _a4font, 1, leader_cache;
	digits = 410, 200, 6, _a4font, 1, leader_cache_2;
	flags = overlay, refresh, visible;
}

// *** INITIALIZE COUNTS FOR ALL GROUPS
function init_accumulators()
{
	var i = 0;
	while( i < MAX_GROUPS ){
		gold_sums[i]=0;
		group_member_counts[i]=0;
		num_counteds[i]=0;
		i+=1;
	}

}

// *** INITIALIZE STATE FLAGS FOR ALL GROUPS
function init_flags()
{
	var i = 0;
	while( i < MAX_GROUPS ){
		collect_gold_flags[i]=OFF;
		found_gold_activated_flags[i]=OFF;
		more_gold_flags[i]=OFF;
		leader_has_gold_flags[i]=OFF;
		remove_activated_flags[i]=OFF;
		i+=1;
	}
}

function main()
{
	fps_max = 30;
	
	// *** INITIALIZE OUR VALUES PRIOR TO LEVEL LOAD
	init_accumulators();
	init_flags();

	level_load (work18_wmb);
	wait (3);
	camera.x = -100; // set a static position for the camera
	camera.y = 0;
	camera.z = 70;

}

// FUNCTIONS/ACTIONS ///////////////////////////////////////////////////////////////

// *** GROUP USAGE TESTING: ALTERNATES BETWEEN 2 GROUPS PER REQUEST
function simulate_group_selection(){
	if ( fake_out_group == 0 )
	{
		fake_out_group = 1;
	} else {
		fake_out_group = 0;
	}
	return( fake_out_group );
}

// *** CHANGED NAME FROM activate_gold_collection() TO get_players_gold()
// *** REMOVED ALL ARRAY PROCESSING CODE, ETC.
function get_players_gold(){
	gold_sums[my.group_id] += my.gold_owned;
	me.gold_owned = 0;
	num_counteds[my.group_id] += 1;
}


function remove_member( )
{
// *** REMOVED UNNEED REFERENCES TO GROUP[]
// *** REMOVED CHECK FOR group_member_count > 0
	gold_sums[my.group_id]+=my.gold_owned;
	leader_has_gold_flags[my.group_id]=OFF;
	if ( group_member_counts[my.group_id] > 0 )
	{
		group_member_counts[my.group_id]-=1;
	}
	wait(1);
	remove(my);
}

function get_gold_found( amount ){
	// *** NOW USES GROUP FLAGS AND ACCUMULATORS
	found_gold_activated_flags[my.group_id] = OFF;
	me.gold_owned+=amount;
	more_gold_flags[my.group_id] = ON;
}

// *** NEW FUNCTION TO DO INITIAL GOLD COLLECTION AT THE
// *** START OF THE GAME; DIRECTLY CHANGES COLLECT FLAG
// *** FOR ALL GROUPS.
function signal_initial_collection()
{
	var i = 0;
	while( i < INITIAL_GROUP_COUNT )
	{
		collect_gold_flags[i]=ON;		
		i+=1;
	}
	return;
}

// uses gold_owned,member_id,group_id
action group_member
{
	// *** NOW COUNTS MEMBERS FOR EACH GROUP
	group_member_counts[my.group_id]+=1;

	// *** NEED TO WAIT UNTIL BOTH GROUP LEADERS HAVE BEEN FOUND
	// *** THEN FORCE INITIALIZATION BY BRUTE FORCE.
	if ( number_of_groups == INITIAL_GROUP_COUNT)
	{
		signal_initial_collection();
	}

	// *** NOW CHECKS STATES BY GROUP ID...
	while (1)
	{
   		if ( remove_activated_flags[my.group_id] )
   		{
			remove_activated_flags[my.group_id]=OFF;
			remove_member();
   		}
		if ( found_gold_activated_flags[my.group_id] )
		{
			get_gold_found( 50 );
		}
		if ( collect_gold_flags[my.group_id] )
		{
			get_players_gold();
			if ( num_counteds[my.group_id] == group_member_counts[my.group_id] )
			{
				collect_gold_flags[my.group_id] = OFF;
				num_counteds[my.group_id] = 0;
				if ( gold_sums[my.group_id] > 0 )
				{
					leader_has_gold_flags[my.group_id] = OFF;
				}
			}
		}
		wait(1);
	}
}

// uses gold_collected,group_id
action leader
{

	// *** COUNTS NUMBER OF GROUPS; 1 GROUP PER LEADER ASSUMED
	// *** WOULD NEED THIS LATER FOR ADD/DELETE GROUP PROCESSING
	number_of_groups+=1;		

	while(1)
	{
		if ( more_gold_flags[my.group_id] )
		{
			more_gold_flags[my.group_id] = OFF;
			collect_gold_flags[my.group_id] = ON;
		}			
		if ( ! leader_has_gold_flags[my.group_id] )
		{
			leader_has_gold_flags[my.group_id] = ON;
			me.gold_collected += gold_sums[my.group_id];

			// *** NEED TO DETERMINE WHICH GOLD DISPLAY TO UPDATE
			if ( my.group_id == 0 )
			{
				leader_cache = me.gold_collected;  // ... display group 1 gold
			} else {
				leader_cache_2 = me.gold_collected;  // ... display group 2 gold
			}
			gold_sums[my.group_id] = 0; 
		}
		wait(1);
	}
}

// *** NEW TRIGGER FOR COLLECTING GOLD
function manual_gold_collection(){
	var grp = 0;
	grp = simulate_group_selection();
	collect_gold_flags[grp] = ON;
}


// *** CHANGED TO USE GROUP PICKING FUNCTION AND FLAG ARRAY
function activate_remove(){
	var grp = 0;
	grp = simulate_group_selection();
	// YOU DON'T WANT TO TURN THE FLAG ON IF IT IS NOT REALLY GOING TO BE USED, BECAUSE
	// IT WILL STAY ON, THUS A SPONED PLAYER WILL IMMEDIATELY BE REMOVED!!!
	if ( group_member_counts[grp] > 0)
	{
		remove_activated_flags[grp] = ON;
	}
}

// *** CHANGED TO USE GROUP PICKING FUNCTION AND FLAG ARRAY
function activate_found_gold(){
	var grp = 0;
	grp = simulate_group_selection();
	if ( group_member_counts[grp] > 0)
	{
		found_gold_activated_flags[grp] = ON;
	}
}


on_g = manual_gold_collection;  // *** CORRECTION: SET TO NEW FUNCTION
on_r = activate_remove;			// remove a member
on_a = activate_found_gold;		// player finds gold

Step 2: With step 1 out of the way, this step doesn't require a lot of additions, especially since all the gold is always being transferred to the leader; at the start of the level and when any members find it. This is a key fact to remember, because this means we only have to transfer from leader to leader, as opposed to collecting it from all the group members to the leader before it can be robbed from that group's leader. Therefore, I didn't get fancy and used the fact that each leader has all the gold at all times to my advantage. Here's my approach to solving the "robbery" part of the problem.

First, we need a couple more flag arrays, one for "rob", and another for "lose". The "rob" flags are used to begin the robbery, while the "lose" flags signal for the actual transfer of gold. You may be wondering why the robbery needs more than one flag. Let's look at this necessity.

First, realize that since the robbery is from one leader to another, the processing of the robbery needs to take place in leader. Now, as expected we look for the "rob" flag set for the group leader that's doing the robbing... no sweat! But, now what? This current leader knows nothing of the other one and we don't have access to them yet, but only my or me. This makes it obvious that we have to somehow set another flag that targets the leader we want to rob. So, on with the question of, "How?"

Having hit the "rob" flag for the group that's robbing, we save that me as "robber", in the form of a global entity* (pointer to an entity). Now, above, we determined that we had to somehow wait for one of the other "leader" listeners to hit a flag condition, so it can use the "robber" to give its money to. So, how is this flag set? If we know the group we want to rob from, we simply use this as the index into our lose_flags[] array, setting this flag to "ON". Remember, the "rob" flag must be turned off for the current group. Now, the next condition your "leader" listener tests for is lose_flags[my.group_id] being true, or "ON", just like the syntax we used in all the other places. When the group leader to rob is found by this flag, you simply have to reset lose_flags[my.group_id] back to "OFF", perform robber.gold_collected += my.gold_collected, and set my.gold_collected equal to 0. In this implementation, I have no direct way to designate the group to be robbed, but I know I have two group, so I just alternate; if 0 is robbing, then 1 is robbed, or if 1 is robbing, then 0 is robbed. I use asmall function to determine this during runtime, so I can set the appropriate "lose flag". I also use simulate_group_selection() to pick the group that's doing the robbing.

The code for "Step 2" is made up up of minor changes, when compared to "Step 1", so I've just provided the additions to Step 1 here. There are only 20-25 lines of added code!

// *** NEW ROBBERY VARIABLES
entity* give_to;		// saves who to give gold to
var rob_flags[MAX_GROUPS];	// triggers a rob request
var lose_gold_flags[MAX_GROUPS];// triggers lose due to robbery

function init_flags()
{
	var i = 0;
	while( i < MAX_GROUPS ){
		...
		// *** ADDED TO INITIALIZE ADDITIONAL ROBBERY FLAGS
		rob_flags[i]=OFF;
		lose_gold_flags[i]=OFF;
		// ***************************************
		...
	}
}

action leader
{
	// *** ADDED GROUP ID OF GROUP TO BE ROBBED
	var temp_group = 0;

	...

	while(1)
	{
		...
		// *** SETUP ROBBERY
		if ( rob_flags[my.group_id] )
		{
			rob_flags[my.group_id]=OFF;
			give_to = me;         // *** SAVE ROBBER INTO GLOBAL ENTITY POINTER
			temp_group = get_other_group(my.group_id);  // *** GET GROUP TO ROB
			lose_gold_flags[temp_group]=ON;
		}
		// *** DO ROBBERY AND UPDATE DISPLAY.
		if ( lose_gold_flags[my.group_id] )
		{ 
			lose_gold_flags[my.group_id]=OFF;
			give_to.gold_collected += my.gold_collected;
			my.gold_collected = 0;
			if ( give_to.group_id == 0)
			{
   				leader_cache = give_to.gold_collected;
				leader_cache_2 = me.gold_collected;
			} else {
   				leader_cache = me.gold_collected;
				leader_cache_2 = give_to.gold_collected;
			}
		}
		...
	}
}

// *** SET ROBBERY FLAG: USES simulate_group_selection() FOR
// *** GROUP SELECTION FOR TESTING PURPOSES
function activate_robbery(){
	var grp = 0;
	grp = simulate_group_selection();
	rob_flags[grp] = ON;
}

// *** NEW ROBBERY ACTIVATOR
on_t = activate_robbery;			// group robs the other

Problem 6 Solution

As it turns out, spawning a player is really quite easy. But, you have to make some of the same decisions you had to make in the previous exercises. How do I want to signal for the action? Which action should I signal? What are the consequences of spawning a new player? How do I inject properties into the new group member?

Before we go any further, you may have discovered the problem of "placement" with spawning a player. Eventually, you would have to determine how not spawn a player on top of another. However, this is not part of this problem. We need to spawn a group member, then give the newborn some personality.

Since there are two steps in solving the problem, as just stated, I would use 2 signals. One signal would create the group member and another to signal that a new player needs to be IDed and get equiped. It seemed to make sense to send my first signal to the "leader", who would then create the player. The "leader" would then signal "group_member" for the handling of the newborn.

First, I used the same strategy to fire off the initial spawn signal as the other triggers, which gives us some control over what group gets the new member (which group leader should request the creation). This would also call for the same type of flags we've been using for both signals; creation and prep... and don't forget to initialize them! The create() function can be found in the manual and its usage is straight forward. However, there are two unseen bumps in the road.

We've ben using "my.group_id" to set the appropriate flag for a signal to be recieve by a "leader" of a group or a "group_member" of a group. This works just fine for the "leader", because he already has a group identity (group_id). However, our entity that is about to be birthed will not. So, how do we set our flag for the "group_member" action so it knows what group the new member will belong to? This is the first bump in the road, and I simply set a global variable specifically for this task, such as SPAWN_GROUP; c-script is not case sensitive, but the uppercase helps it stand out.

What's the next hurdle? The next mini-challenge is to determine how, or where, to place the "member prep" handling code in "group_member". The manual states that the specified action will be attached to the entity immediately after it is created, with it being set to "me". So, we certainly won't be "listening" and "preping" within the "while loop". The signal to prep the member must be caught at the very beginning of the action, prior to the loop. So, we will basically have two starting conditions for the "group_member" action; "If you are a new player do this, but if you were built into the game do that." So, our check for the flag will be at the very top of "group_member", and the pre-determined flag will need to use the global group indicator as its index to do the checking. This same global is also used to turn the flag "off" and set the new member's "group_id". The "group_member_counts[]" can be used to determine and set the entity's "member_id" property. Then, you simply set "me.gold_owned" to whatever you want.

As a side note, I did one other thing to my actions that may optimize them. Rather than keeping the series of IF blocks, I transformed these to IF-ELSE, with each ELSE contain the remaining conditions:

if ( one ) {
	do_one();
} else {
	if ( two ) {
		do_two();
	} else {
		if ( three ) {
			do_three();
		}
	}
}

I don't know if this is a great thing to do, but it would seem to cut down the amount of processing done each time the program cycles through the "while loop". When a flag is set, only the code related to that request will be processed, then cycle back through the loop. When using a simple series of IF blocks, a lot of conditions may be caught through each cycle through the loop. I would think that this could cause big problems down the road as the program becomes more comples. Some languages have a rule, as to how deep IFs can be nested, but I don't recall seeing any information concerning a limit in c-script.

If you notice, most of our processing relates to gold collection. One way to clean up your action listener loops would be to take the chunk of code out of the loop and place it in a function, so a single function call will appear within the action's loop:

function handle_players_gold() {
    if ( one ) {
    	do_one();
    } else {
    	if ( two ) {
    		do_two();
    	} else {
    		if ( three ) {
    			do_three();
    		}
    	}
    }
}

action player {
	while(1){
		handle_players_gold();
		...
		wait(1);
	}

}

I've not tried this functional restructuring on my code yet, but I can see where it would be of great benefit once you start adding more functionality!

The following listing only provides the changes to the existing code, with comments for guidence:

var init_spawn_flags[MAX_GROUPS]; 	// triggers initial spawn request
var spawn_flags[MAX_GROUPS];		// signal to set properties
var _SPAWN_GROUP_ID;			// group to be spawned to

function init_flags()
{
	var i = 0;
	while( i < MAX_GROUPS ){
	...
		// *** ADDITIONAL SPAWN FLAGS INITIALIZATION
		init_spawn_flags[i]=OFF;
		spawn_flags[i]=OFF;
		// ****************************************
		i+=1;
	}
	...
}

function spawn_member()
{
	spawn_flags[_SPAWN_GROUP_ID]=OFF;
	group_member_counts[_SPAWN_GROUP_ID]+=1;
   	my.group_id=_SPAWN_GROUP_ID;
   	my.member_id = group_member_counts[_SPAWN_GROUP_ID];
	my.gold_owned=35;
}

action group_member
{
   	if ( spawn_flags[_SPAWN_GROUP_ID] )
   	{
		spawn_member();
   	} 
	else
	{
		group_member_counts[my.group_id]+=1;
	}
	...
}


action leader
{
	var myvec[3];
	... 
	while(1) {
		...

		// *** LEADER DOES THE SPAWN ********
		if ( init_spawn_flags[my.group_id] )
		{
 			init_spawn_flags[my.group_id]=OFF;
			spawn_flags[my.group_id]=ON;
   			if ( my.group_id == 0 )
   			{
				myvec.x=0.000;
				myvec.y=-100.000;
				myvec.z=130.000;
				wait(1);
   				create( <ball_1.mdl>,myvec,group_member);
   			}
			else
			{
				myvec.x=0.000;
				myvec.y=100.000;
   				myvec.z=130.000;
   				wait(1);
				create( <box.mdl>,myvec,group_member);
			}
		}
	...
	}
}

// *** NEW PLAYER SPAWN ACTIVATOR
function activate_spawn(){
	var grp = 0;
	grp = simulate_group_selection();
	_SPAWN_GROUP_ID = grp;
	init_spawn_flags[grp] = ON;
}

on_s = activate_spawn;			// *** NEW SPAWN TRIGGER