Help with structs, pointers, data organization

Started by pslim, Wed 18/05/2011 22:11:46

Previous topic - Next topic

pslim

My partner and I have been trying to design an engine for our game. It is somewhat inspired by Chris Crawford and his "verb thinking" and storyworlds, but takes the form of a point and click adventure.

I've been modeling the data organization in c++ with a fair amount of success, at least according to what I set out to do -- we're just learning c++ so there are probably much better ways of doing what we're trying to do, of course. At this point, though, I'm uncertain about how AGS structs work and how to use pointers in AGS, so I need some help figuring out how to translate the logic into AGS.

The system is based on verbs (not unlike the MI interface in some ways). I'm going to post the code I've written up modeling the behavior of one of the verbs, pilfer, although the flow should be pretty much the same with all the verbs.

What it's supposed to do:
1. register which verb has been chosen (in AGS it will be set with mouse clicks, obviously, which isn't a problem for me)
2. register the target of the verb (which character, object, etc)
3. do the chosen verb's function to the target
4. register the target's response (I haven't put that in yet, still working on 2 and 3)

I've posted my c++ model at the very bottom since it's kind of long.

Because of the high number of characters, objects, etc which verbs can potentially act on, I want to set it up so that there is only one function per verb. In my plan (although I'm open to better ones), the function should act on a target pointer, and the thing that's being pointed to should be able to be supplied by an argument to the function at the time that the player clicks on the target character, object, etc.

My questions/uncertainties:
1. I'm not really clear on what can be done with a struct and what can't, in AGS. Apparently you can put functions in them, but can you nest them?
2. If you can't have a struct inside a struct, how could I potentially translate the model below that uses structs in classes to work with AGS?
3. How would I deal with the arguments of the verb functions so when I call the verb function it will be able to operate on the instance supplied in the function call? So that in anyclick on a character or object I can write talk(diane) or talk (table) and then have something like

Code: ags
function talk(target)
{
   if (target.type == object)
   {
      display("It says nothing.");
   } 

   if (target.type == character)
   {
      runDialogScreen(target);
   }

}



Any help in organization or answering the above 3 questions would be deeply appreciated. We're open to suggestions of other ways of approaching it if this way doesn't suit AGS or isn't as efficient as something else, etc.

C++ model follows [yes, I realize I mixed stdio and iostream, I tried stdio first but scanf gave me problems so I switched, but I haven't cleaned it up yet, it's not meant to be a real application].  Note that the verb I was working with here was mainly pilfer and the target was assumed to be the guard (this process should take place after the player selects the guard).
Code: ags


#define MAX_INV 10
#define TOTAL_CHARS 2


#define TALK 1
#define PILFER 2
#define INVENTORY 3

#define TESTNUM1 2


class Items
{
public:
	std::string name[MAX_INV];
	
	Items()
	{		
		name[TESTNUM1] = "frog";

	}

};
//                                                                                       class for Character attributes
//-------------------------------------------------------------------------------------------------
class Characters
{
public:
	bool pilferable;  // can you steal from them?
	bool hasInv[MAX_INV]; // what inventory items do they have?
	std::string name;

	Characters()
	{
		pilferable = false;
		
		for (int i = 0; i < MAX_INV; i++)
		{
			hasInv[i] = false;
		}
	}


};


class Game
{
public:
	Items inv;
	Characters PC;
	Characters guard1;
	bool isRunning;
	int input;
	int interactMode;

	Game()
	{
		isRunning = true;
		interactMode = 0;
		input = 0;
	}

	void showInv()
	{
		bool hasSomething = false;

		for (int i = 0; i < MAX_INV; i++)
		{
			if (PC.hasInv[i] == true)
			{
				std::cout << "You have the " << this->inv.name[i] << ".\n";
				hasSomething = true;
			}
		}

		if (hasSomething == false)
		{
			std::cout << "You have nothing in your inventory.\n";
		}
	}

	void Talk()
	{
		printf("You chat with the guard.\n");
	}
//                                                                                        ZOMG Pilfer function
//-------------------------------------------------------------------------------------------------
	void Pilfer(Characters target)
	{
		if (target.pilferable == true)
		{
			for (int i = 0; i < MAX_INV; i++)
			{
				if (target.hasInv[i] == true)
				{
					PC.hasInv[i] = true;
					std::cout << "You steal the " << inv.name[i] << " from " << target.name << std::endl;
				}
			}
		}

		else
		{
			std::cout << "Target is not pilferable.\n";
		}
		
	}

	void Setup()
	{
		guard1.name = "the guard";
		guard1.pilferable = true;
		guard1.hasInv[TESTNUM1] = true;
	}

	void Menu()
	{
		std::cout << "-------------------------------------------------\n";
	    std::cout << "A guard is here.\n\n";
		printf("1. Talk\n");
		printf("2. Pilfer\n");
		printf("3. Show Inventory\n");
		
		std::cin >> this->interactMode;

		if (this->interactMode == TALK)
		{
			Talk();
		}
//                                                                                        Pilfer is called here
//-------------------------------------------------------------------------------------------------

		if (this->interactMode == PILFER) //what verb has been chosen?
		{
			Pilfer(this->guard1); //argument of guard1
		}

		if (this->interactMode == INVENTORY)
		{
			showInv();
		}
	}
};



int main ()
{
	Game game;
	game.Setup();


	while (game.isRunning)
	{
		game.Menu();


	}


	return 0;
}


 

Khris

First of all, I'm sorry to say, you can pretty much ditch that code entirely.

In AGS, structs aren't really suitable to replace built-in objects.
For instance characters are created inside the Editor and are all instances of the (not editable) Character class.

Now if you want to add boolean attributes and properties to existing script objects, you can do that by using a combination of structs and extender methods:

Code: ags
// header

struct str_char_data {
  bool pilferable;
  ...
};

import void SetPilferable(this Character*, bool p);
import bool IsPilferable(this Character*);

// main script

str_char_data _c[100];

void SetPilferable(this Character*, bool p) {
  _c[this.ID].pilferable = p;
}

bool IsPilferable(this Character*) {
  return _c[this.ID].pilferable;
}


You can now use this like this:

Code: ags
  cGuard.SetPilferable(true);

  if (cGuard.IsPilferable()) ...


As you can see you have to handle this by using the same index number (.ID) for the character and the struct array.

Now on to the verb menu:
From your description of the process it sounds like the player chooses a verb, then a game object i.e. there's a static list of verbs.
Looking at your code though it looks like the game object is chosen first, then the verb is selected from a context-based list.

The first method is pretty easy to implement, the second is very much possible but requires a bit more consideration.

I'd also suggest that (just like the default way) each game object has its own function(s) and the reaction to the verb is handled in there. You don't have to do it this way, but it's much tidier than handling all objects in a few huge verb functions.
If there's no context-based verb list, you can easily add unhandled behavior for nonsensical actions.

pslim

#2
The code was more intended to help me figure out how to organize (and also communicate) what I was trying to do than to actually DO anything, and I'm certainly not attached to that particular method over something more useful.

Also to clarify, yes, the player is intended to choose the verb first, then the object. The problem was, I didn't know how to go about adding attributes and properties to existing objects. Your example is very helpful, thanks a lot. I tried something similar early on but it didn't occur to me to use the character ID numbers and I wrote myself into a corner without really knowing what was unworkable with what I was doing.

The reason I didn't initially try to handle the interactions individually for each object in the mouse click functions for the object was that I wasn't sure how to do that without just pasting essentially the same code in each object, largely because I wasn't sure if you could put functions in a struct or how to accomplish it if you couldn't.

If I understand you, you're saying I can make a struct array for characters, storing their attributes and flags, and put the verb functions in there, then call them from the character interactions? Would that be a good way of handling it?
 

Khris

Quote from: pslim on Wed 18/05/2011 23:55:48The reason I didn't initially try to handle the interactions individually for each object in the mouse click functions for the object was that I wasn't sure how to do that without just pasting essentially the same code in each object.

Unfortunately, that's more or less what you have to do.

The default way is to have one function per useful verb-object combination (e.g. oDoor_Look(), oDoor_Interact()). If no function exists, AGS will automatically try to call unhandled_event in order to display some generic "That doesn't work" message.

Of course you don't have to use this, it's perfectly possible to reorganize the handling code. A good example is the 9-verb template; it uses the "any click on" event and proceeds to handle all verbs in one function per object. The used verb is determined by checking e.g. UsedAction(eActOpen).
Like you said, this more or less boils down to lots of copy-pasting.

QuoteIf I understand you, you're saying I can make a struct array for characters and put the verb functions in there, then call them from the character interactions? Would that be a good way of handling it?

Maybe I'm misunderstanding you, but the problem with this approach is that you can't overwrite member functions. So while you can add functions to structs you can't have different code for different struct instances.
You'd be back at using if-else if-else if-...-else so it's pretty much pointless.


If I had to do a multiple verb game from scratch, I'd probably use this approach:

-One feature of AGS are the so-called Custom properties.
Using a dialog interface in the editor, you can add data to each object, character, hotspot, inventory item and room. The available types are bool, int and String. The only downside is that they are read-only at runtime.
You can specify default values, then edit them for each object.

-Add one String property for each verb. If the action is supposed to generate a generic unhandled response, leave the text field blank.
If the response is supposed to be unique, enter it.
If something else is supposed to happen, enter a word like "process" or simply "p".

-Click handling will read the object's verb property and accordingly call unhandled_event, display the string or call the object's interaction function.
Thus you still have to check for the verb inside the object's function, but you can save a lot of ifs thanks to the reply string without the player having to constantly read the same "that doesn't work" message.
You could even use some kind of separation character and randomize it.

Let's take the guard as an example:
Look   |   It's a security guard.
Push   |   You don't want to get arrested.#You'd rather not.
Pull   |   _
TalkTo |   process
Open   |   _
Close  |   _
Pilfer |   process
Kick   |   Not such a good idea, he is armed.#He'd only overpower you and throw you in jail.

Pull, Open and Close will produce a generic response regarding characters.
Look, Push and Kick will make the player say the message/one of the messages.
TalkTo and Pilfer will store the used verb in a variable, then call clicked_character.RunInteraction(eModeInteract); and inside cGuard_Interact(), the verb is queried and handled.

You can expand this as much as you want; you could add additional properties to specify coordinates and a direction so that whenever a meaningful interaction is going to take place, the player character will first approach and face the object. You can also make this completely optional: the game will only make the PC face the object, but if you put an "@" at the start of the String it'll make the PC walk there first.

You could also code it so that if the TalkTo string contains only a number, AGS will run dialog[number].

The cool thing about AGS is that despite the "weak" structs, it's still very powerful and I have yet to find a situation where I'd be forced to have lots of duplicate code.

SMF spam blocked by CleanTalk