1
\$\begingroup\$

Diablo 2 has a very sophisticated item system and I want to create something similar. But I am having a hard time getting the architecture right. In Diablo 2 most different kind of gear has different kind of attributes and they can all hold X amount of modifiers depending on the quality.

However my server creates items randomly based on several factors. And each item is pretty much unique. To lower the amount of records a player can only have X amount of items. I tried to get some thought on "paper" and would like some insight in how this can be done properly. These are my findings:

Having all my different kinds of gear separated since weapons have damage, boots alter speed and armor, shield give armor and block rate, etc. A modifier table holds all the modifiers and has a FK to weaponId, bootsId, shieldId and only one should be set where the others are null. I could add requirements in the main tables as well but in the diagram below I opted for a separate 1 to 1 relation table.

enter image description here

And this could be a variation of it where there is a single table linking them together.

enter image description here

To clarify table modifiers, the column modifies should hold something like undead and percentage could hold 10 to make the armor or weapon 10% more effective to undead. Or strength & 5 to increase strength by 5.

But I might be over complicating things. I could have a single item class that defines the type, damage, armor, etc and keep them null if that attribute does not belong to the item. I could even throw in the requirements since thats a 1 on 1 relation anyway. The modifier relation would stay the same where a single item can have more modifiers. Something like this:

enter image description here

But this made me wonder, why would I store any attributes in the actual item table? Damage could be a modifier too and so can defense. I would handle it like so:

modifies = "baseDamage"
amount = "10"

modifies = "bonusDamage"
amount = "3"

Now I can show the player what the base damage of the weapon is and how much the bonus is. Heck, I could even handle the item name and description using this and combine them all by coding in there proper classes. This would make it really flexible where a stat like strength in code is just a String and an int.

The downfall is a lot more coding and calculations and a large table since each item has at least a 3 or 4 of modifiers. With 10.000 players all holding 100 good items with like 10 modifiers this would translate into 10.000.000 records. Nothing that MySQL could not handle but it does make me wonder about performance a bit.

I really need some advice or maybe a whole different view on the topic to make this work properly.

Edit

I was thinking about something a bit different. If I would make a class to hold a property, or even stats and store that as a binary field alongside the item? Would this be a good solution? Take a look at the following code:

public class WeaponExample {
    Weapon weapon = new Weapon();

    public WeaponExample()
    {
        weapon.name = "sword";
        weapon.damage = 10;
        weapon.levelRequirement = 4;

        //create a modifier that adds extra strength to the character
        Modifier modifier1 = new Modifier();
        modifier1.type = "strength";
        modifier1.amount = 5;

        //create a modifier for 10% extra damage against undead
        Modifier modifier2 = new Modifier();
        modifier2.type = "undead";
        modifier2.percentage = 10;

        //add them to the weapon
        weapon.modifiers.add(modifier1);
        weapon.modifiers.add(modifier2);

        //serialize weapon.modifiers to store in database.
        //..
    }    
}

class Weapon {
    //Basic weapon data
    public String name;
    public int damage;
    public int levelRequirement;
    //... etc

    //Data to be serialized to binary and stored
    List<Modifier> modifiers;
}

class Modifier {
    public String type;
    public int percentage;
    public int amount;
}

Serializing those 2 modifiers generates a byte array of 183 bytes. This will likely be a bit larger as I add some extra stuff. I will do some more testing. Now I am by far an expert on these things but if I would have a separate table I would end up with:

  • 2 varchars of 16 bytes (14 characters + 2 overheat).
  • 2 ints of 8 bytes (since the others will be null?).

That makes 24 bytes instead of the whopping 283. So I'm thinking this is not such a good idea. This does save me a lot of looking up in the SQL table and if I deserialize on the clients end then my server does not have to do the job of deserializing all those items when requested.

\$\endgroup\$
2
\$\begingroup\$

Given your requirements I would recommend not using a RDBMS. I'd look at a JSON type document database for storing your data. It tends to be me more flexible and matches the data structures you'll design in a cleaner way.

If you do want to use an RDBMS though I'd look at PostgreSQL rather than MySQL. PostgreSQL 9.4+ has JSON binary support. You can essentially design a table that looks like characters(id serial, inventory jsonb). With the database module plv8 you can write Javascript functions to operate on the objects. As an example your inventory data could just look like:

[
    {
        "name": "sword",
        "damage": 10,
        "levelrequirement": 4,
        "modifiers":
        [
            {                
                "type": "strength",
                "amount": 5
            },
            {                
                "type": "undead",
                "percentage": 10
            }
        ]
    }
]

In plv8 you can implement trade operations between players and modify or delete items. It's up to you though how much you do on the database and how much you do on the server.

create or replace function add_item(character integer, item json) returns json as
$$
    // No error handling is done here. Good idea to try/catch
    plv8.subtransaction(function()
    {
        var inventory = JSON.parse(plv8.execute('select inventory from characters where id=$1', [character])[0].inventory);
        inventory.push(item);
        plv8.execute('update characters set inventory=$2 where id=$1', [character, JSON.stringify(inventory)]);

        // Can even setup database notifications so others can listen for events
        plv8.execute('notify inventorychanged, \'{ "character": ' + character + '}\'');
    });
    return { success: true };
$$
language plv8 stable strict;

I should mention you can still setup indexes and query json binary objects in postgresql. Also since jsonb is binary it's fairly compact. Not as compact as you'd probably like, but it's manageable.

\$\endgroup\$
  • \$\begingroup\$ I only have experience with relational databases and I think I will stick to it at least for this project. I will however read some more about non relational databases. \$\endgroup\$ – Madmenyo Sep 18 '15 at 19:55
  • \$\begingroup\$ Well PostgreSQL is a relational database. It just has more features for handling varied data sets like the ones you're using. For all other purposes you can treat it like a relational database. (That said even for things like login it has pgcrypto as an extension to simplify things).Also you can query jsonb data just there was a relationship combining items to the character. postgresql.org/docs/9.4/static/functions-json.html Specifically in my example:'select * from jsonb_to_recordset(select inventory from characters where id=$1) as x(name text, damage int, levelrequirement int)' \$\endgroup\$ – Sirisian Sep 18 '15 at 20:14
  • \$\begingroup\$ Thanks, I will certainly look into that. Kinda wished my previous post was not closed here where I asked for a suitable database and named my exact needs. I have been busy on this for a week. But I guess migrating from MySQL to postgreSQL should not be a lot of work. I just hope I can get jdbc to work with it if thats possible, \$\endgroup\$ – Madmenyo Sep 18 '15 at 20:19
  • \$\begingroup\$ Have you read my edit at the bottom? I just cut down the serialization in between 10 and 20 bytes depending on the string I pass. I could even go below 10 if I use a integer as an id for the modifier type. I think that is pretty good to store as a binary in any database. \$\endgroup\$ – Madmenyo Sep 18 '15 at 20:58
  • \$\begingroup\$ 'I just cut down the serialization in between 10 and 20 bytes', - c2.com/cgi/wiki?PrematureOptimization \$\endgroup\$ – NiCk Newman Jan 19 '16 at 4:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.