Extendable inventory and world objects

Started by
18 comments, last by hplus0603 13 years, 9 months ago
So quite every game has a set of objects and features and usually you can find some parts like:

void useInventoryObject(int obj, int count)
{
if(obj == APPLE)
addParam(ENERGY, 20);
else if(obj == MEAT)
addParam(ENERGY, 50);
else if(isClothes(obj))
wearClothes(obj);
}

Because the game designed with some abilities and needs no changes. Maybe some constants can be in config file, like APPLE_ENERGY = 20, MEAT_ENERGY = 50 etc.

In my project I try to make some extendable model to allow external programmers to add some features, objects, or just objects' skins.

For example you want to add some gun. You can write some data in some file, eg. "model=gun23.mod, power=23, bullets=simple, weight=21".

Another problem is that there are many general object types:
1. Parameter-change objects. Like medicine, fruits, vegetables. So on create another object you just write in config "Name=Potato, param_to_change=Hunger, amount=30, skin=potato23.mod".
2. Guns. Already mentioned - you define model, power, what bullets to use etc.
3. Tables. Objects that can be positioned and used for place for some food.
4. Cars-keys. You click and car arrives.
5. Unique portraits. You can uppload some picture and then place it in your room.

This list usually consists of about 50 different objects classes. There is no problem to make list of 50 static functions to create objects when they are read from database, or first creation.

The problem is how to manage them in inventory also? Because some objects have a huge amount of parameters. For example the table has a set of objects on it, position, orientation, visible list (who watches it). When you place it into inventory you loose all those parameters.

Maybe there's a need to create class InventoryItem to store just object type and amount? But how to manage actions in inventory, like fruit eating? So it can be done using some object-specified factory. So you have Apple - a 3D object and static AppleManager that has functions create3DApple(), inventoryUse() etc. Too much static ones are used to be a bad practice - minimize singletons is told everywhere.

Next idea is to make AppleInventoryItem - funcional object for inventory. But there's some user-defined data for this type of "Parameter-change objects" which must be static too, or you'll need something like ParamObjectData* data = (ParamObjectData*) getWorld()->getObjectData(PARAM_OBJECT, OBJECT_APPLE); because getObjectData returns all the data for all types of objects, that's the bad practice too.
VATCHENKO.COM
Advertisement
So your question is really two-fold:

1) How to manage the difference between objects in world (where they are networked) and objects in inventory (where only the holder knows about the object, and you really don't need the additional parameters).

2) How to apply all the parameters of an object when it's used out of inventory, where it's only stored as type/count.

I would recommend that you separate "object types" versus "object instances." You can load all the object types from script when the server/game starts, because there will only be a limited number of object types. Each type has an ID.

Then you have a "generic object object" which contains an object type, and does the networking necessary (who can see it, pick it up, etc). This is configured with the object type ID. To render the object, you get the position/orientation from the networked generic object, but look up the model/sprite/texture/particle-effects/whatever from the object type by ID.

When the object is picked up, you destroy the in-world generic object, and instead increment the inventory for the object type ID in question for the picker-upper.

Finally, when an object is used out of inventory, you decrement the count from inventory, and apply the object actions as specified by the object type, not the object instance!

This means that you separate different classes of data into different containers, because the classes of data are operated on differently. Presentation and effects go in the object type; networking goes into the networked-object object, and inventory goes into the inventory-count object.
enum Bool { True, False, FileNotFound };
Ok, I'll try to figure out with code. Because your object type is equal (as I understood) to my object factory.

class AbstractType{    // When user wants to drop an item    AbstractObject* create(Point pos);    char* name;};class AbstractObject{    int m_subtype;    Point pos;    std::list<AbstractObject*> knownList;    void onClick(Player p) {}};// It's a box where you click and sell-buy some itemsclass MarketType : public AbstractType{    // Every market has some list of possible items to sell/buy    // It's just a list for owner to select what to sell and to buy    std::list<ItemType> canSellBuy;};// It's a real market on the groundclass MarketInstance : public AbstractObject{    // This is a list of things that market sells/buys with some count, price    std::list<MarketItem> marketList;};


So the user can create many market types, ancient, modern, what he likes. And there's a need to create files like:

Market0.txt:
name=Ancient market
can_sell_buy=APPLE,MANGO,WOOD

Market1.txt:
name=Modern market
can_sell_buy=BREAD,BUTTER,PISTOL

So there's need no difficulty for users to create new market that they like.

First problem is how to link those object types and instances, because when user want to add something, there must be something like:

void MarketInstance::doAddItemToMarket(ItemType item, int price){    ObjectType* type = getWorld()->getObjectType(TYPE_MARKET, m_subtype);    if(((MarketType*) type)->canSellBuy(item))    {        Do_something();    }    else    {        alert("This market doesn't support this thing.");    }}
VATCHENKO.COM
Maybe we need to add after AbstractObject* create(Point pos); the function void use(int amount) to solve the problem of eating fruits, using medicine etc? Or I'm wrong?

Also the question is to organize unique objects like post cards, images, texts. Because the addition of new market or new fruit is config-based feature, it can be updated after game restart or even smarter, don't care. But images/photos are stored in database, and I have no idea to implement it as a part of ObjectType/ObjectInstance ideology.
VATCHENKO.COM
In a generic model, there would be only one single type, crudely defined as:
std::map<std::string, void *> object;

map is the actual instance of an object.
string is the property name.
void * is data specific to property.

And that is it. What the logic does with that is entirely up to user. There is no class hierarchy, no inheritance, polymorphism, etc...

Want to make something a shop - put (is_shopkeeper,true) and (sell_list,[a,b,c]) into the map.

When interacting with an object, one can then query if it has is_shopkeeper property and it's set to true and then display whatever GUI is needed by querying the sell_list and interpreting the parameter as std::list<Item>.

Eventually this would evolve into full-blown key-value store.

But unless closely controlled and regularly tested, such systems can quickly devolve into unmaintainable mess, since they lack any kind of constraints usually imposed by language syntax as well tool support, type safety and dependency checking.

Most of these can be constructed in C++ using various techniques.
A probably much faster way is to use a scripting language, &#106avascript would be ideal due to its prototype-based design, and just define everything in there, no need for separate configuration and such.<br><br>This type of design is basically C++'s arch nemesis, the exact opposite of what it's best at, namely painfully static design.
Yes, map of properties is a universal thing - different object types have different properties. But those types have different actions. Yes, for those actions we have object instances in 3D world, but what about inventory? Because every INVENTORY_USE_ITEM and other inventory actions should be processed differently. For example:

1. Apple on use adds some parameter(s).
2. Car keys call the car near person.
3. Gun can shoot only some bullets.
4. Stick can be used for near attack.
5. Shields have other actions.
6. Parachute bags can be used when falling.
7. Other bags can be used for reactive fly.

All those actions are inventory actions (you don't need to create 3D object with a list of visible objects, position etc.)

Yes, those actions could be processed simply by IF/ELSE, but who knows if someone will add something new?
VATCHENKO.COM
You can divide your object types to several main categories, e.g. "NPCs", "PCs", "things", "boats" etc. All objects in a category has a lot in common, so avoiding those numerous if's, e.g. objects can be movable or not etc. Consider "things", they are world objects that can be placed in inventory etc. "Usage" can have some effects that can be stored as properties of recommended by hplus0603 "object type". E.g. object type "apple", "usage" will have effects: "energy" "+20" (+ maybe some others). When object instance is used, all its parent "object type" usage properties are enumerated and its effects are applied.

it's a trade-off between more straightforward approaches and more universal ones
advantage: still quite simple and straightforward
disadvantage: still requires some amount of auxiliary code writing
You probably also want to look into the concept of interfaces in C++ (generally implemented as abstract base classes).

class IInventoryCategory {  public:    virtual Bitmap *getBitmap() = 0;    virtual char const *getName() = 0;    virtual int getSortOrder() = 0;};class IInventoryObject {  public:    virtual IObject *baseObject() = 0;    virtual IInventoryCategory *getCategory() = 0;    virtual Bitmap *getBitmap() = 0;    virtual char const *getName() = 0;    virtual char const *getActivationName() = 0;    virtual bool needsTarget() = 0;    virtual bool consumesOnActivation() = 0;    virtual bool canActivate(IPlayer *player, IObject *target) = 0;    virtual void activate(IPlayer *player, IObject *target) = 0;};


The Inventory is simply a sorted list of IInventoryObject pointers and counts, displayed/grouped by IInventoryCategory. When displaying, you can display enable/disable state based on the return values from canActivate() for example.

The actual implementation can vary -- a CWeapon doesn't need to be the same implementation as a CConsumable; they just both need to derive from IInventoryObject.
enum Bool { True, False, FileNotFound };
As I understand you advise to process all the inventory operations in inventory module according to parameters and type of the object?

Because I thought that objects in game is like some plugins. We have some engine and if we want to add some non-core abilities, it can be added to objects functionality.

Even if I'll will process param-change items, clothes, rocket bags, swords, pistols then once someone will need to add some object-plugin with other functions.
VATCHENKO.COM
Also there is a problem with inventory objects that are represented by one 3D object class, for example, fruits, guns, meditine. Even if they have different actions in inventory, they have everything in common in 3D space. All of them are simply staying on the ground and put themselves in inventory on user click near them.

As I understand, they must have something like:

class InventoryObjectInstance : public AbstractObject{public:    void onClick(Player* p)    {        p->getInventory()->addItem(getWorld()->getPlugin(type)->getObjectType(subtype), count);    }private:    int type, subtype, count;};


What to do when object is being stored? Do I need to save it as an InventoryObject or instantly Fruit? Because if I save InventoryObject, I need to read is an inventory object, so there must be a plugin/object-type.

Also please help me how to manage with unique objects like images, sounds etc.?
VATCHENKO.COM

This topic is closed to new replies.

Advertisement