5
\$\begingroup\$

Inspired by the XML format for game data question, I was interested to see how my system works for storing game data. My system works a lot like Halo, where there are a variety of gametypes and configurations vary between them, but some basic items are always configurable.

For example, every game has a time limit, but team deathmatch has points per kill while capture the flag has a flag idle reset time. How should I store and load the universal and variable data? Something to note is that every game type has a variety of different variants with different config values, but the same format.

With the 5 or so gametypes we have, changing data around is pretty easy, but when we add more gametypes, I want to unify as much as possible for easy copy pasting / editing.

Currently my XML storage looks like the following (Yes this is Minecraft data):

Team Deathmatch (Slayer.xml):

<?xml version="1.0" encoding="utf-8"?>
<gametype>
    <!--Core Settings-->
    <name display="Slayer">Slayer</name>
    <description>Kill the enemy team.</description>
    <version>1.0.0</version>

    <!--Team Settings-->
    <teams>
        <team>
            <name>Red Team</name>
            <display>&amp;cRed Team</display>
            <prefix>&amp;c</prefix>
            <suffix>&amp;r</suffix>
            <armor>
                <helmet color="FF0000" >LEATHER_HELMET</helmet>
                <chestplate color="FF0000" >LEATHER_CHESTPLATE</chestplate>
                <leggings color="FF0000" >LEATHER_LEGGINGS</leggings>
                <boots color="FF0000" >LEATHER_BOOTS</boots>
            </armor>
        </team>
        <team>
            <name>Blue Team</name>
            <display>&amp;9Blue Team</display>
            <prefix>&amp;9</prefix>
            <suffix>&amp;r</suffix>
            <armor>
                <helmet color="0000FF" >LEATHER_HELMET</helmet>
                <chestplate color="0000FF" >LEATHER_CHESTPLATE</chestplate>
                <leggings color="0000FF" >LEATHER_LEGGINGS</leggings>
                <boots color="0000FF" >LEATHER_BOOTS</boots>
            </armor>
        </team>
    </teams>

    <!--Slayer Settings-->
    <kill-points>1</kill-points>
    <death-points>0</death-points>
    <suicide-points>-1</suicide-points>
    <betrayal-points>-1</betrayal-points>

    <!--Game Settings-->
    <score-to-win>50</score-to-win>
    <time-limit>10</time-limit>
    <friendly-fire>false</friendly-fire>
    <betrayal-booting threshold="3">true</betrayal-booting>
    <damage-multi>1.0</damage-multi>
    <can-join-late>true</can-join-late>

    <!--Round Settings-->
    <rounds>1</rounds>
    <lives-per-round>0</lives-per-round>

    <!--Item Settings-->
    <items initial="true" respawn="true" personal="true"/>
    <loadout>
        <item slot="0">STONE_SWORD</item>
        <item slot="1">BOW</item>
        <item slot="7" amount="16" name="Arrows">ARROW</item>
    </loadout>
</gametype>

Team Deathmatch Heavies (Heavies.xml):

<?xml version="1.0" encoding="utf-8"?>
<gametype>
    <!--Core Settings-->
    <name display="Heavies">Heavies</name>
    <description>Kill the enemy team.</description>
    <version>1.0.0</version>

    <!--Team Settings-->
    <teams>
        <team>
            <name>Red Team</name>
            <display>&amp;cRed Team</display>
            <prefix>&amp;c</prefix>
            <suffix>&amp;r</suffix>
            <armor>
                <helmet color="FF0000" >LEATHER_HELMET</helmet>
                <chestplate color="FF0000" >LEATHER_CHESTPLATE</chestplate>
                <leggings color="FF0000" >LEATHER_LEGGINGS</leggings>
                <boots color="FF0000" >LEATHER_BOOTS</boots>
            </armor>
        </team>
        <team>
            <name>Blue Team</name>
            <display>&amp;9Blue Team</display>
            <prefix>&amp;9</prefix>
            <suffix>&amp;r</suffix>
            <armor>
                <helmet color="0000FF" >LEATHER_HELMET</helmet>
                <chestplate color="0000FF" >LEATHER_CHESTPLATE</chestplate>
                <leggings color="0000FF" >LEATHER_LEGGINGS</leggings>
                <boots color="0000FF" >LEATHER_BOOTS</boots>
            </armor>
        </team>
    </teams>

    <!--Slayer Settings-->
    <kill-points>1</kill-points>
    <death-points>0</death-points>
    <suicide-points>-1</suicide-points>
    <betrayal-points>-1</betrayal-points>

    <!--Game Settings-->
    <score-to-win>50</score-to-win>
    <time-limit>10</time-limit>
    <friendly-fire>false</friendly-fire>
    <betrayal-booting threshold="3">true</betrayal-booting>
    <damage-multi>1.0</damage-multi>
    <can-join-late>true</can-join-late>

    <!--Round Settings-->
    <rounds>1</rounds>
    <lives-per-round>0</lives-per-round>

    <!--Item Settings-->
    <items initial="true" respawn="true" personal="true"/>
    <loadout>
        <item slot="0">IRON_SWORD</item>
        <item slot="1">BOW</item>
        <item slot="2" name="Lightning 'Nade" amount="2">SNOW_BALL</item>
        <item slot="7" amount="16" name="Arrows">ARROW</item>
    </loadout>
</gametype>

Capture the Flag (CTF.xml):

<?xml version="1.0" encoding="utf-8"?>
<gametype>
    <!--Core Settings-->
    <name display="Capture the Flag">CTF</name>
    <description>Bring the enemy flag back to base.</description>
    <version>1.0.0</version>

    <!--Team Settings-->
    <teams>
        <team>
            <name>Red Team</name>
            <display>&amp;cRed Team</display>
            <prefix>&amp;c</prefix>
            <suffix>&amp;r</suffix>
            <armor>
                <helmet color="FF0000" lore="Loadout Item">LEATHER_HELMET</helmet>
                <chestplate color="FF0000" lore="Loadout Item">LEATHER_CHESTPLATE</chestplate>
                <leggings color="FF0000" lore="Loadout Item">LEATHER_LEGGINGS</leggings>
                <boots color="FF0000" lore="Loadout Item">LEATHER_BOOTS</boots>
            </armor>
            <flag name="&amp;cRed Flag" durability="14">WOOL</flag>
        </team>
        <team>
            <name>Blue Team</name>
            <display>&amp;9Blue Team</display>
            <prefix>&amp;9</prefix>
            <suffix>&amp;r</suffix>
            <armor>
                <helmet color="0000FF" lore="Loadout Item">LEATHER_HELMET</helmet>
                <chestplate color="0000FF" lore="Loadout Item">LEATHER_CHESTPLATE</chestplate>
                <leggings color="0000FF" lore="Loadout Item">LEATHER_LEGGINGS</leggings>
                <boots color="0000FF" lore="Loadout Item">LEATHER_BOOTS</boots>
            </armor>
            <flag name="&amp;9Blue Flag" durability="11">WOOL</flag>
        </team>
    </teams>

    <!--CTF Settings-->
    <flag-at-home>false</flag-at-home>
    <flag-reset>15</flag-reset>
    <flag-recover>5</flag-recover>
    <flag-capture-respawn>1</flag-capture-respawn>
    <flag-respawn-retry>1</flag-respawn-retry>

    <!--Game Settings-->
    <score-to-win>5</score-to-win>
    <time-limit>10</time-limit>
    <friendly-fire>false</friendly-fire>
    <betrayal-booting threshold="3">false</betrayal-booting>
    <damage-multi>1.0</damage-multi>
    <can-join-late>true</can-join-late>

    <!--Round Settings-->
    <rounds>1</rounds>
    <lives-per-round>0</lives-per-round>

    <!--Item Settings-->
    <items initial="true" respawn="true" personal="true"/>
    <loadout>
        <item slot="0" lore="Loadout Item">STONE_SWORD</item>
        <item slot="1" lore="Loadout Item">BOW</item>
        <item slot="2" amount="16">ARROW</item>
    </loadout>
</gametype>

I've edited these files over SSH in vim/pico for about a year now. Seeing them in an actual text editor has immediately brought up some issues. Firstly, comments seems to be used as grouping when parent elements should be used. Teams are almost exactly the same and should be loaded from another file by an id reference. I also feel as if attributes are used in the wrong places.

In terms of loading / saving this data in Java, I read in the contents of the file and then pass the contents to the configuration constructor. It then searches for elements as shown below. eRoot is the top <gametype> element, element is a defined once Element variable that is reassigned for every search: (I use the XOM library)

element = eRoot.getFirstChildElement("rounds");
if (element != null) {
    this.rounds = Integer.parseInt(element.getValue());
} else {
    this.rounds = 1;
}

Different game types have different config classes that inherit the one containing the above code. Each one is responsible for loading its own data. Am I doing this in a somewhat organized manner, or is everything completely wrong?

Also an idea that has sparked my interest is storing these gametypes in SQL tables. Is there any way to make this work nicely there or should I abandon that idea. These files are loaded from a central location that works nicely when we have 1 server, but in a few weeks our servers will be split to multiple boxes and I believe that SQL would be a better platform for pulling the configuration file data.

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

SQL

I'm really not sure what you would gain from storing in SQL honestly. But I guess it would be worth a try. It would likely result in either a few very wide tables, or lots of very small tables that you would have to join. I'll provide a brief example below, please note being that I'm not sure what SQL engine you would be using that the syntax would vary. Below is using PostgreSQL but it would look/work very similar on other SQL engines.

CREATE TABLE core_settings(
    game_name VARCHAR PRIMARY KEY,
    description VARCHAR,
    version VARCHAR
);
CREATE TABLE team_settings(
    game_name VARCHAR REFERENCES core_settings(game_name),
    team_name VARCHAR,
    PRIMARY KEY (game_name,team_name),
    helmet_name VARCHAR,
    helmet_color VARCHAR,
    chestplate_name VARCHAR,
    chestplate_color VARCHAR,
    leggings_name VARCHAR,
    leggings_color VARCHAR,
    boots_name VARCHAR,
    boots_color VARCHAR
);
-- etc. for your other nested sets of data


INSERT INTO core_settings
    (game_name, description, version)
VALUES
    ('Slayer', 'Kill the enemy team.', '1.0.0'),
    ('Heavies', 'Kill the enemy team.', '1.0.0'),
    ('Capture the flag', 'Bring the enemy flag back to base.', '1.0.0')
;
INSERT INTO team_settings(
    game_name,
    team_name,
    helmet_name,
    helmet_color,
    chestplate_name,
    chestplate_color,
    leggings_name,
    leggings_color,
    boots_name,
    boots_color
)
VALUES  (
    'Slayer',
    'Red Team',
    'LEATHER_HELMET',
    'FF0000',
    'LEATHER_CHESTPLACE',
    'FF0000',
    'LEATHER_LEGGINGS',
    'FF0000',
    'LEATHER_BOOTS',
    'FF0000'
    ),
    (
    'Slayer',
    'Blue Team',
    'LEATHER_HELMET',
    '0000FF',
    'LEATHER_CHESTPLACE',
    '0000FF',
    'LEATHER_LEGGINGS',
    '0000FF',
    'LEATHER_BOOTS',
    '0000FF'
    );
-- etc. for each game, team, setting...
\$\endgroup\$
  • \$\begingroup\$ The problem here is that Slayer and Heavies have the same configuration, but ctf doesn't. Would I make a different table for CTF settings and how would I go about relating a game to one table over the other? \$\endgroup\$ – CrypticStorm Jul 28 '14 at 19:48
  • \$\begingroup\$ You could either make another table for CTF, or additional columns in the tables above, whichever you prefer. If the latter then some columns would have NULL values on the records where you don't need them. \$\endgroup\$ – Phrancis Jul 28 '14 at 20:19
  • \$\begingroup\$ To select from multiple tables you would use JOINs, e.g. SELECT * FROM core_settings cs INNER JOIN team_settings ts ON cs.game_name = ts.game_name WHERE cs.game_name = 'Slayer'; \$\endgroup\$ – Phrancis Jul 28 '14 at 20:20
  • \$\begingroup\$ I know how to JOIN, but I was wondering how in 1 statement, I would pull the Slayer table for a Slayer game, and the CTF table for a CTF game. Their settings are in 2 separate tables, but are loaded at the same time through the same method. Using the NULL values would be possible, but with a lot of gametypes that's a lot of NULL data. \$\endgroup\$ – CrypticStorm Jul 28 '14 at 20:23
  • \$\begingroup\$ True. You may in fact be better off sticking with XML, SQL was really designed with that sort of use in mind, methinks. You could also wrap all this up in a stored procedure/function to pull multiple queries into a single transaction. \$\endgroup\$ – Phrancis Jul 28 '14 at 21:05

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.