Living without pointers to custom structs

Started by Snarky, Thu 28/03/2013 10:34:52

Previous topic - Next topic

Snarky

I'm looking for some advice about how to work around the AGS limitations on object-oriented code.

I'm trying to code up a module for RPG-style combat. The problem seems naturally suited for an object-oriented approach, since it involves a number of entity types (characters, monsters, weapons, armor, equipment, spells, etc.) each with a bunch of different properties and possible actions. And in fact, the explanations of structs in the manual use RPG scenarios as examples.

But very quickly I run into problems because AGS doesn't allow pointers to custom structs, or arrays of structs inside structs. For (a simplified) example:

Code: AGS

struct RpgWeapon{
  String name;
  int damage;
};

struct RpgSpell{
  String name;
  int mpCost;
  int damage;
};

struct RpgChar{
  String name;
  // ...
  RpgSpell spells[10];        // ILLEGAL!
  RpgWeapon* currentWeapon;   // ILLEGAL!
  import int attack(RpgChar* target); // ILLEGAL!
  // ...
};


This complicates things a lot. I need to be able to refer to particular characters, weapons, spells etc. in other data structures and to pass them as arguments to methods. Otherwise, they're not much use.

My current workaround is to create global "master-arrays" for each struct, and store a "pointer" to a struct as an integer index into the array (with -1 for null):

Code: AGS

struct RpgChar{
  String name;
  // ...
  int spells[10];        // entries index into RpgSpells
  int currentWeapon;   // indexes into RpgWeapons
  import int attack(int target); // target indexes into RpgChars
  // ...
};

RpgWeapon RpgWeapons[RPGWEAPONS_MAX];
RpgSpell RpgSpells[RPGSPELLS_MAX];
RpgChar RpgChars[RPGCHARS_MAX];


This works, but it has a number of drawbacks: You have to keep track of the contents of each array yourself (which is OK if you define all the weapons, spells, characters and so on all in one place, like at game start; but if you want to make new ones dynamically it could get messy), you have to think very carefully about how to deal with multiple instances of "the same" struct (e.g. two characters who both have the same weapon; one or two entries in RpgWeapons?), and it adds a step every time you want to dereference a struct. And of course, the arrays take up memory, though I'm not sure that (apart from empty slots in the array) they take any more memory than you would use anyway.

In addition, because the master-arrays store the actual structs rather than pointers, I've found I've had to write methods to "clone" an instance from the array or into it (by going through every field and assigning the same value), though that might be because I haven't been completely disciplined in always using the arrays.

So, am I going about this the wrong way around, trying to force AGS into a programming style it doesn't fit? Is there a better way? If I use this workaround, do you have any tips for how to make it as smooth and non-error-prone as possible? (For example, writing this post made me consider whether I should use Hungarian notation to identify the pseudo-pointers, so instead of "spells[]", "currentWeapon" and "target" it would be "psSpells[]", "pwCurrentWeapon" and "pcTarget".) Should I create an API to manage the master-arrays? Should I have separate arrays for "permanent" and "ephemeral" instances (e.g. for monsters, have one arrays of the "monster prototype" and one of individual monster instances)?

Crimson Wizard

#1
Quote from: Snarky on Thu 28/03/2013 10:34:52
In addition, because the master-arrays store the actual structs rather than pointers, I've found I've had to write methods to "clone" an instance from the array or into it (by going through every field and assigning the same value), though that might be because I haven't been completely disciplined in always using the arrays.
I used this technique, my every struct having Read & Write method to copy data back and forth (also in many cases it allows you to hide actual arrays).
BTW that is in some way similar to how various older APIs work, such as WinAPI or DirectX API: they ask you to provide a struct object for them when you query any internal data, so I haven't had any moral problem here in this particular case :).

If you are serious about writing this thing properly, you may consider either move your RPG implementation to plugin (plugin interface allows you to define your own managed types), or *cough* *cough* use lua *cough*.

Khris

#2
Yeah, the only thing I can contribute here:
-don't create new objects during the game
-use enums for the index numbers

The contents of a chest for instance is stored as an array (or String) of numbers, each referring to a pre-defined game object.

An object in-game is stored by having some variable set to its index number, as opposed to there's an instance of it. That way you shouldn't have to duplicate objects.

Calin Leafshade

#3
Yay! A practical example of why this stuff is bullshit!

In the old days I did it exactly as you described. Basically everywhere you need to reference a struct, you reference an index to a master struct. There is basically no way around it. That's the only way you can do it really.

The only other way you could conceivably do it is by storing information in an array of strings and converting the data in an accessor function but thats really getting beyond the pale.

My serious suggestion is to use Lua.
Remember that the *game* doesnt have to be written in Lua. You could just write your library in lua and provide an interface with AGS in ags script.

Every hour you spend trying to force ags script to do something it can't could be spent making an elegant solution in Lua.

My 2 pence.

(If monkey_05_06 were here he would right about now leap through a window declaring "NOT SO FAST!")

EDIT:

Actually.. the string array thing is not such a bad idea now that i've thought about it because dynamic arrays *can* be passed as parameters.
It would take some work and some understand on the user's part but it's not unworkable..

Crimson Wizard

#4
Another thing, I started using auxiliary "Index Arrays" to define which elements of my arrays are valid for particular case.
For example, for combat sequence I made "Attackable" index array to get the possible attack targets for AI faster and simplier. When character died, I did not remove it from array (because that would be impossible), nor change it position in there, I just modify index array.

As for string arrays, there is a monkey_05_06's Stack module based on this idea.
http://www.adventuregamestudio.co.uk/forums/index.php?topic=37232.0

Snarky

Thanks, it's at least good to know that this approach isn't entirely going in the wrong direction.

Quote from: Crimson Wizard on Thu 28/03/2013 10:57:36
If you are serious about writing this thing properly, you may consider either move your RPG implementation to plugin (plugin interface allows you to define your own managed types), or *cough* *cough* use lua *cough*.
I have an aversion against using plugins for stuff that can be done in-engine, if only out of some vague sense that it reduces compatibility with ports/future engine versions. That said, the complications involved in doing it in AGS script has made me even more receptive to the arguments for switching to a more standard scripting language.

Quote from: Khris on Thu 28/03/2013 11:05:22
-use enums for the index numbers

How do you mean? If let's say the monster list had 128 entries, would I need to make a "monstersIndex" enum with 128 different values? Or is there some loophole in the AGS construct of enums that lets you provide any integer in place of an enum value, even if it's completely out of range?

Quote-don't create new objects during the game
The contents of a chest for instance is stored as an array (or String) of numbers, each referring to a pre-defined game object.

An object in-game is stored by having some variable set to its index number, as opposed to there's an instance of it. That way you shouldn't have to duplicate objects.

OK, so how would you suggest dealing with something like this case: In the game there are magic rings of healing. There are many of them, all functionally identical, and different characters can have carry one or more instances of them at a time. Each ring has ten uses.

So in a chest, I could store an index to "healing ring" in the generic equipment list, but as soon as a character takes it, I'll need to make a separate instance, so I can keep track of the state of that particular ring (the number of uses left, for example).

The simplest seems to be having another array that represents any equipment object that "exists" individually; depending on the game, that might just be all the characters' inventories, or it might also include shop inventories etc. (for example if the game allows you to sell a partially used healing ring, and then buy it back later). In any case, it would have to be a pretty large array (MAX_CHARACTERS * MAX_INVENTORY, at a minimum).

Crimson Wizard

I'd split Item Class struct and Item Instance struct, first having immutable properties, and the second - mutable ones. Instances reference their Types via index.
Character inventory would contain slots of Item Instance type (the instance should have a flag to mark item as "non-existing" or something like that).
When the chest is found, its contents are represented by a Chest struct, it will contain inventory Instance objects, that, when picked up, would be copied to slots in character inventory.

Snarky

Yup, I think that's more or less where I'm heading. The current code is pretty messy since I didn't plan out a comprehensive approach from the beginning, but just dealt with problems as they came up. It will probably have to be completely refactored.

Another thing I regret is that because I couldn't easily use object composition as a form of extension, I tended to put everything into a single struct (either the base type or an inherited one). For example, my RpgCharacter struct doesn't just contain long-term information like the character's stats and inventory contents, but also stuff like how much time it has accumulated towards its next action, what direction it's facing, where it's standing on the battlefield, etc. Now I think that all this should, after all, go into a separate struct/array, in order to keep the basic RpgCharacter struct simple, and to separate the generic RPG concepts from the specifics of the combat system.

Khris

Yeah, number of uses left is an example of where it gets really icky to work with AGS.
One way is having a (or two) generic ItemState array(s) associated with every inventory, and depending on the item's type, they are used for additional info.

Basically,
hero[0].item[index] = eItemHealingRing
hero[0].itemstate1[index] == 10

Regarding enums, you can set its members to arbitrary integer values while declaring it.

Alternatively, the only way is to use master structs.

item[iHealingRing].name = "healing ring";
item[iHealingRing].spell = "health + 50";
...

game_item[i].item = iHealingRing;
game_item[i].uses = 10;

game_item[j].item = iHealingRing;
game_item[j].uses = 7;

The player's inventory keeps references to game_item, thus:
Code: ags
  int uses_left = game_item[hero[current_player].inv[current_inv]].uses;
  String spell = item[game_item[hero[current_player].inv[current_inv]].item].spell;  // go AGS

Monsieur OUXX

Calin, didn't you create a plugin or something to store arrays?

If not, Snarky, just find "EasyPlugin" in the forums and start making your own, biatch. No more limitations caused by pointers!
 

Snarky

Quote from: Snarky on Thu 28/03/2013 11:36:56
I have an aversion against using plugins for stuff that can be done in-engine

Calin Leafshade

Quoting one's own post as a response is a highly passive-aggressive thing to do.

However, you did say that you were more receptive to the idea and that your reason for reticence was compatibility. The Lua plugin is fully compatible with the ports and included in their distribution.

Crimson Wizard

#12
Quote from: Calin Leafshade on Fri 05/04/2013 13:38:21
However, you did say that you were more receptive to the idea and that your reason for reticence was compatibility. The Lua plugin is fully compatible with the ports and included in their distribution.
IIRC it still has some problems with x64 linux build.

But lets be honest, it will take some more time to make everything correctly ported (there are many plugins that are not ported at all).
If you have access to plugin source code (or its author ;)), it should be easier to make any updates there.

Snarky

Quote from: Calin Leafshade on Fri 05/04/2013 13:38:21
Quoting one's own post as a response is a highly passive-aggressive thing to do.

:-\ Any response I had for Mr Ouxx was already adequately expressed by my earlier post. It seemed like he had missed it, so I pointed it out, without malice or animosity. It's no bigger deal than that; no need to stir up drama.

QuoteHowever, you did say that you were more receptive to the idea and that your reason for reticence was compatibility. The Lua plugin is fully compatible with the ports and included in their distribution.

I actually meant that I was more receptive to the idea of AGS switching to a more standard scripting language (I'm fairly agnostic on whether that should be Lua or something else). Support in current ports is a good thing (though as CW points out, I've heard of problems with it), but compatibility with future versions can hardly be guaranteed. In any case, for a module I hope would be usable by AGS users in general, I wouldn't want to impose any unnecessary requirements like learning another programming language, or even how to use plugins. (And because this is a module that will require some customization for each particular use-case, it's not just a matter of providing an AGS-script API to black-box code that's implemented externally.)

Monsieur OUXX

Quote from: Snarky on Fri 05/04/2013 15:07:09
Quote from: Calin Leafshade on Fri 05/04/2013 13:38:21
Quoting one's own post as a response is a highly passive-aggressive thing to do.

:-\ Any response I had for Mr Ouxx was already adequately expressed by my earlier post. It seemed like he had missed it, so I pointed it out, without malice or animosity. It's no bigger deal than that; no need to stir up drama.

Passive-agressiveness is accepted when it was my own fault in the first place ;) Not to mention I have the same arguments as you for not using plugins.
But I do think the Lua plugin could be a good compromise.
 

bush_monkey

#15
Having worked on a RPG in AGS for the last 7 or so years, I feel I can contribute to this :)

For items, I just have them as inventories and use a custom property to link the inventory item to a struct so you only need 1 entry per item in your struct.

For linking chars to equipments, I have an index int in the weapon struct which goes in weaponID in the char struct:

struct type6 //weapons
{
  int str;
  int def;
  int speed;
  int hp;
  int mp;
  int index; //links to weaponID
};

For chars, these are the stats I keep hold of

struct type4 //stats
{
  int level;
  int maxmp;
  int maxhp;
  int defense;
  int strength;
  int speed;
  int exp;
  string name;
  int view; //which char
  int mp;
  int hp;
  int chi;
  int maxchi;
  int weaponID;
  int shieldID;
  int ringID;
  int sprite;   //which sprite slot for gui
  int magictype; //which chakra is set
  //levelup modifiers
  int levelup; //amount of exp needed for levelup
  int mod; //modifier for level up
  int binmod; //binary modifier for levelup
  int nextmod; //next level at which modifier gets changed
  int strmod;
  int hpmod;
  int mpmod;
};

also for magic powers, they are set in my game this way:

struct chakras
{
  int level;
  int exp;
  string chakraname;
  int power[5];//points to which powers are this chakra's
  int weak; //which other chakra is it weak against
};

Chakras are set of magic powers the characters have with the power array linking to an index in the power struct:

struct powers
{
  int str;
  string powername;
  int on; //is it available
  int type;//is str healing,hurting,status_altering 
  int cost; //mp cost of power
};

SMF spam blocked by CleanTalk