0

It's been awhile since my last post but I have decided to move on with trying my hand at game development. So right now something I am looking at is actual data storage. Probably not something that will be used while the game is necessarily running but on load, save and exit use.. Probably. Fact is I'm not sure yet. However, that is not the question of the hour for me. The real question I am facing is in a database design aspect. I can't for some reason wrap my head around how to design the items portion of the database. I am looking to do something efficient yet robust. Let me give some things I am reflecting on..

  • Assuming that this is a Fantasy RPG even possibly MMO (though that part is not important)
  • Trying to keep the OOP approach to this.

Now the questions:

  1. If an Item can be multiple things (i.e. a potion/food (which I get consumables), a sword/staff (weapons)) what would be the database approach to this?
  2. Going as an add on to the above question... So a Sword can have different specs.. depending on Material based specs (base stats) to the potential augment based stats (buffs or debuffs).. How can I encapsulate that in the database without every single item needing that information there too. (Idea being if it isn't important to this item then don't include it at all.
    • I am thinking the idea to this is.. if the item has a fire augment then in a table (probably some sort of junction table) define the: StatID: primary key for this stat Item_ID: the item being affected Augment_ID: augment i.e. fire damage AugmentEffect: would be +/- value

If someone can shed light on this would be great or... if someone knows a great location to find this information.

I have attempted to look through the search.. sadly I don't know what to exactly search for that returns useful results to this topic and genre. Thanks in advance.

~ Edit 1 ~ This is kinda the approach I was thinking towards segregating the different types of "items" this way based on a flag is which table to look at. Would this work in practice? Game Database Example

Maxs728
  • 591
  • 2
  • 8
  • 18
  • 1
    Having seen quite a few of these games up close, what I can say is that you need to create a flexible internal representation. Using inheritance for different types of objects is therefore usually a bad idea. Composition works much better. And you should only start to worry about your database once you get the internal representation right. – biziclop Apr 09 '17 at 10:43
  • 1
    I have a custom implemention that I am going to show you. I can't post an answer right now though, so it won't be available until tomorrow. Basically, though, I have a way where you can store anything you want, regardless of item type, and retrieve it with a single integer ID which uses bitmasking to find the internal array, and the index in that array. You can then pass around integer IDs to everything in your game. I'll post an example tomorrow, including code. – Krythic Nov 14 '17 at 05:35
  • I also noticed that you mentioned customizable and "augmented" weapons in a comment below. My system allows dynamic item creation. All you would do is find the next available index within the database, or if the database is full, resize it and then return the location+index pseudo Bitmask, and boom, you now can copy and edit a new item into that index in the database. – Krythic Nov 14 '17 at 05:40
  • As an example of my system in action until I can create an answer tomorrow, here is a screenshot and of my engine using my system. http://imgur.com/jkSs3OE – Krythic Nov 14 '17 at 05:57

4 Answers4

0

If you don't have a predefined schema, or want to be flexible in the future about your schema, you may consider noSQL databases. If you consider using cloud provider, Azure has Table storage, which is a noSQL storage (Disclosure: I work for Microsoft, there are other options from other cloud providers, as well as for no-cloud local deployment).

oldbam
  • 2,397
  • 1
  • 16
  • 24
  • Ok so after reading what you sent. Why is NoSqL a better choice. I'm not seeing a clear reason. And if it was a better choice how would that solve my design issue? – Maxs728 Apr 09 '17 at 17:53
  • @Maxs728 I partially indicated that in my response, "If you don't have a predefined schema, or want to be flexible in the future about your schema". You may read more on relational vs. non-relational storage on the web, one of SO answers I like is http://stackoverflow.com/a/17955601/403431 – oldbam Apr 10 '17 at 08:25
0

Alright, for my implementation, I wanted to store various item types, this included Weapons, Apparel, Items, GameEvents, LootTables, Abilities, etc. I didnt want to use a large amount of polymorphism, because I consider it to be a weakness in a large system, especially the one that my game has become. As you can imagine, this would make a database mechanism for non-polymorphic items very difficult to create.

What I ended up doing was having a static GameDatabase class, which is initialized at runtime, loaded from a directory, and used throughout the entire game engine without having to pass around references to the Database. The Database consists of one List of each item type, and an associated enumeration, which is combined with the index of the item to create a pseudo bitmask key. The key is a single integer. What this means is that now we can pass around an integer ID for everything in the game, instead of the item itself.

Here is an example of my GameDatabase with the file loading trimmed out. I am also using C# because I absolutely abhor Java.

using System;
using System.Collections.Generic;
using VoidwalkerEngine.Framework.Collections;
using VoidwalkerEngine.Framework.DataTypes;
using VoidwalkerEngine.Framework.Entities;
using VoidwalkerEngine.Framework.Game.Items;
using VoidwalkerEngine.Framework.Rendering.OpenGL.Modeling;

namespace VoidwalkerEngine.Framework.Game.Managers
{

    public enum DatabaseLocation
    {
        Abilities,
        Apparel,
        Actors,
        Classes,
        Events,
        Items,
        LootTables,
        LootTablePools,
        Models,
        Sounds,
        StatusEffects,
        Weapons
    }

    public static class GameDatabase
    {

        public static List<Ability> Abilities;
        public static List<Actor> Actors;
        public static List<Birthsign> Classes;
        public static List<GameEvent> Events;
        public static List<GameItem> Items;
        public static List<Weapon> Weapons;
        public static List<Apparel> Apparel;
        public static List<LootTable> LootTables;
        public static List<LootTablePool> LootPools;
        public static List<VoidwalkerModel> Models;
        public static List<GameSound> Sounds;
        public static List<StatusEffect> StatusEffects;

        public static void Create()
        {
            Abilities = new List<Ability>();
            Actors = new List<Actor>();
            Classes = new List<Birthsign>();
            Events = new List<GameEvent>();
            Items = new List<GameItem>();
            Weapons = new List<Weapon>();
            Apparel = new List<Apparel>();
            LootTables = new List<LootTable>();
            LootPools = new List<LootTablePool>();
            Models = new List<VoidwalkerModel>();
            Sounds = new List<GameSound>();
            StatusEffects = new List<StatusEffect>();
        }

        public static void Initialize(int identifier)
        {
            Initialize(new DatabaseKey(identifier));
        }

        /// <summary>
        /// Initializes the Database location with a new Object of that type.
        /// The identifier for the object is automatically added.
        /// </summary>
        /// <param name="key"></param>
        public static void Initialize(DatabaseKey key)
        {
            int identifier = key.Identifier;
            int index = key.Index;
            switch (key.Location)
            {
                case DatabaseLocation.Abilities:
                    Abilities[index] = new Ability(identifier);
                    break;
                case DatabaseLocation.Apparel:
                    Apparel[index] = new Apparel(identifier);
                    break;
                case DatabaseLocation.Actors:
                    Actors[index] = new Actor(identifier);
                    break;
                case DatabaseLocation.Classes:
                    Classes[index] = new Birthsign(identifier);
                    break;
                case DatabaseLocation.Events:
                    Events[index] = new GameEvent(identifier);
                    break;
                case DatabaseLocation.Items:
                    Items[index] = new GameItem(identifier);
                    break;
                case DatabaseLocation.LootTables:
                    LootTables[index] = new LootTable(identifier);
                    break;
                case DatabaseLocation.LootTablePools:
                    LootPools[index] = new LootTablePool(identifier);
                    break;
                case DatabaseLocation.Models:
                    Models[index] = new VoidwalkerModel(identifier);
                    break;
                case DatabaseLocation.Sounds:
                    Sounds[index] = new GameSound(identifier);
                    break;
                case DatabaseLocation.StatusEffects:
                    StatusEffects[index] = new StatusEffect(identifier);
                    break;
                case DatabaseLocation.Weapons:
                    Weapons[index] = new Weapon(identifier);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        public static object Query(int identifier)
        {
            return Query(new DatabaseKey(identifier));
        }

        public static object Query(DatabaseKey key)
        {
            int index = key.Index;
            switch (key.Location)
            {
                case DatabaseLocation.Abilities:
                    return Abilities[index];
                case DatabaseLocation.Apparel:
                    return Apparel[index];
                case DatabaseLocation.Actors:
                    return Actors[index];
                case DatabaseLocation.Classes:
                    return Classes[index];
                case DatabaseLocation.Events:
                    return Events[index];
                case DatabaseLocation.Items:
                    return Items[index];
                case DatabaseLocation.LootTables:
                    return LootTables[index];
                case DatabaseLocation.LootTablePools:
                    return LootPools[index];
                case DatabaseLocation.Models:
                    return Models[index];
                case DatabaseLocation.Sounds:
                    return Sounds[index];
                case DatabaseLocation.StatusEffects:
                    return StatusEffects[index];
                case DatabaseLocation.Weapons:
                    return Weapons[index];
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
    }
}

In order to access an item in the database, you need to use this class to create a key for said item:

using System;
using VoidwalkerEngine.Framework.Game.Managers;

namespace VoidwalkerEngine.Framework.DataTypes
{
    public struct DatabaseKey : IEquatable<DatabaseKey>
    {
        #region Fields
        /// <summary>
        /// The Location within the Database
        /// </summary>
        public DatabaseLocation Location { get; }

        /// <summary>
        /// The Index of the Database List
        /// </summary>
        public int Index { get; }

        /// <summary>
        /// The Packed Identifier of this DatabaseKey
        /// </summary>
        public int Identifier
        {
            get
            {
                return Pack(Location, Index);
            }
        }
        #endregion

        #region Constants
        /// <summary>
        /// The Masking Multiplier. This is 10,000
        /// which means the maximum number of Items,
        /// Weapons,Apparel, etc are 9,999 for each
        /// Database List. If this number is increased
        /// it will also increase the maximum number of items.
        /// MaskMultiplier = 100,000 > 99,999 maximum items.
        /// </summary>
        public const int MaskMultiplier = 10000;
        public const int MinimumIndex = 0;
        public const int MaximumIndex = MaskMultiplier - 1;
        #endregion

        #region Constructors
        public DatabaseKey(string hexString) : this(Convert.ToInt32(hexString, 16)) { }
        public DatabaseKey(int identifier) : this(UnpackLocation(identifier), UnpackIndex(identifier)) { }
        public DatabaseKey(DatabaseKey other) : this(other.Location, other.Index) { }
        public DatabaseKey(DatabaseLocation location, int index)
        {
            this.Location = location;
            this.Index = index;
        }
        #endregion

        #region Functions
        /// <summary>
        /// Unpacks the Location from a packed HexCode.
        /// </summary>
        /// <param name="hexCode"></param>
        /// <returns></returns>
        public static DatabaseLocation UnpackLocation(int hexCode)
        {
            return (DatabaseLocation)(hexCode / MaskMultiplier);
        }

        /// <summary>
        /// Unpacks the Index within a packed HexCode.
        /// </summary>
        /// <param name="hexCode"></param>
        /// <returns></returns>
        public static int UnpackIndex(int hexCode)
        {
            return ((hexCode - ((hexCode / MaskMultiplier) * MaskMultiplier)) - 1);
        }

        /// <summary>
        /// Packs a Location and Index into an Identifier.
        /// </summary>
        /// <param name="location"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        public static int Pack(DatabaseLocation location, int index)
        {
            return ((((int)location * MaskMultiplier) + index) + 1);
        }
        #endregion

        #region Overrides
        public bool Equals(DatabaseKey other)
        {
            return Identifier == other.Identifier;
        }

        public override bool Equals(object obj)
        {
            return obj is DatabaseKey reference && Equals(reference);
        }

        public override int GetHashCode()
        {
            return Identifier;
        }

        public override string ToString()
        {
            return "0x" + Identifier.ToString("X");
        }
        #endregion
    }
}

The DatabaseKey is declared as an immutable struct. In order to create a key for an item you do something like:

DatabaseKey key = new DatabaseKey(DatabaseLocation.Items,24);

This results in an Integer identifier of: 50025 or in hex: 0xC369. What that means is that basic game items are located within the range of 50000-59999. So you take 50,000 + array index. This gives us a single integer in which to map items. Passing items around internally would then use an ItemStack implementation; here is mine:

using System;
using VoidwalkerEngine.Framework.Maths;

namespace VoidwalkerEngine.Framework.DataTypes
{
    [Serializable]
    public class ItemStack
    {

        /// <summary>
        /// The ID of the Associated Item.
        /// </summary>
        public int Identifier { get; set; }

        private int _quantity;

        /// <summary>
        /// The Quantity of this ItemStack.
        /// </summary>
        public int Quantity
        {
            get
            {
                return _quantity;
            }
            set
            {
                this._quantity = VoidwalkerMath.Clamp(value, MinQuantity, MaxQuantity);
            }
        }

        private int _maxQuantity = Int32.MaxValue;

        /// <summary>
        /// The Maximum Quantity of this Stack.
        /// </summary>
        public int MaxQuantity
        {
            get
            {
                return _maxQuantity;
            }
            set
            {
                this._maxQuantity = VoidwalkerMath.Clamp(value, MinQuantity, Int32.MaxValue);
                this.Quantity = this.Quantity;
            }
        }

        public const int MinQuantity = 0;

        public bool IsStackable
        {
            get
            {
                return this.MaxQuantity != 1;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public bool IsEmpty
        {
            get
            {
                return this.Quantity == MinQuantity;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public bool IsFull
        {
            get
            {
                return this.Quantity == this.MaxQuantity;
            }
        }

        public ItemStack()
        {

        }

        public ItemStack(int identifier, int quantity = 1, int maxQuantity = Int32.MaxValue)
        {
            this.Identifier = identifier;
            this.Quantity = quantity;
            this.MaxQuantity = maxQuantity;
        }

        /// <summary>
        /// Adds the specified quantity to this ItemStack. If
        /// the ItemStack is already full or close to being full,
        /// this ItemStack will overflow into a new ItemStack as the
        /// return value.
        /// </summary>
        /// <param name="quantity"></param>
        /// <returns></returns>
        public ItemStack Add(int quantity)
        {
            if (quantity <= MinQuantity)
            {
                return null;
            }
            int overflow = ComputeOverflow(quantity);
            if (overflow > MinQuantity)
            {
                this.Quantity += quantity;
                return new ItemStack(this.Identifier, overflow, this.MaxQuantity);
            }
            this.Quantity += quantity;
            return null;
        }

        /// <summary>
        /// Adds the specified quantity to this ItemStack. If
        /// the ItemStack is already full or close to being full,
        /// this ItemStack will overflow into a new ItemStack as the
        /// return value.
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public ItemStack Add(ItemStack other)
        {
            ItemStack stack = Add(other.Quantity);
            other.Subtract(other.Quantity);
            return stack;
        }

        /// <summary>
        /// Subtracts the specified quantity from this ItemStack. If
        /// the ItemStack is already empty or close to being empty,
        /// this ItemStack will underflow into a new ItemStack as the
        /// return value.
        /// </summary>
        /// <param name="quantity"></param>
        /// <returns></returns>
        public ItemStack Subtract(int quantity)
        {
            if (quantity <= MinQuantity)
            {
                return null;
            }
            int underflow = ComputeUnderflow(quantity);
            if (underflow > MinQuantity)
            {
                this.Quantity -= (quantity - underflow);
            }
            this.Quantity -= quantity;
            return new ItemStack(this.Identifier, quantity, this.MaxQuantity);
        }

        /// <summary>
        /// Subtracts the specified quantity from this ItemStack. If
        /// the ItemStack is already empty or close to being empty,
        /// this ItemStack will underflow into a new ItemStack as the
        /// return value.
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public ItemStack Subtract(ItemStack other)
        {
            ItemStack stack = Subtract(other.Quantity);
            other.Subtract(stack.Quantity);
            return stack;
        }

        /// <summary>
        /// Clears the Quantity of this ItemStack to 0. MaxValue, however, remains the same.
        /// </summary>
        public void Clear()
        {
            this._quantity = MinQuantity;
        }

        /// <summary>
        /// Makes the currect Quantity of this ItemStack equal to the MaxValue of this ItemStack.
        /// </summary>
        public void Fill()
        {
            this._quantity = MaxQuantity;
        }

        /// <summary>
        /// Splits this ItemStack into another, giving half to both stacks.
        /// If the split amount is an odd number, the result gets +1 so no
        /// loss of items happens due to rounding errors.
        /// </summary>
        /// <returns></returns>
        public ItemStack Split()
        {
            if (this.Quantity <= (MinQuantity + 1))
            {
                return null; // A split is impossible.
            }
            int splitResult = (this.Quantity / 2);
            if (this.Quantity % 2 == 0)
            {
                this.Quantity = splitResult;
                return new ItemStack(this.Identifier, splitResult, this.MaxQuantity);
            }
            this.Quantity = splitResult;
            return new ItemStack(this.Identifier, splitResult + 1, this.MaxQuantity);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="other"></param>
        public void Copy(ItemStack other)
        {
            this.Identifier = other.Identifier;
            this.Quantity = other.Quantity;
            this.MaxQuantity = other.MaxQuantity;
        }

        /// <summary>
        /// Creates a new ItemStack which is an exact copy of this ItemStack.
        /// </summary>
        /// <returns></returns>
        public ItemStack MakeCopy()
        {
            return new ItemStack(this.Identifier, this.Quantity, this.MaxQuantity);
        }

        /// <summary>
        /// Determines if this ItemStack is stackable with another ItemStack. This function tests
        /// for a match between the string Identifiers, and whether or not this ItemStack is Stackable.
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public bool IsStackableWith(ItemStack other)
        {
            return this.Identifier == other.Identifier && this.IsStackable;
        }

        /// <summary>
        /// Calculates the amount of overflow that will take place
        /// if the desired ItemStack is added to this one.
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public int ComputeOverflow(ItemStack other)
        {
            return ComputeOverflow(other.Quantity);
        }

        /// <summary>
        /// Calculates the amount of overflow that will take place
        /// if the desired amount is added to this one.
        /// </summary>
        /// <param name="amount"></param>
        /// <returns></returns>
        public int ComputeOverflow(int amount)
        {
            if (amount <= MinQuantity)
            {
                return MinQuantity;
            }
            int total = ((this.Quantity + amount) - this.MaxQuantity);
            if (total < MinQuantity)
            {
                return MinQuantity;
            }
            return total;
        }

        /// <summary>
        /// Calculates the amount of underflow that will take place
        /// if the desired ItemStack is subtracted from this one.
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public int ComputeUnderflow(ItemStack other)
        {
            return ComputeUnderflow(other.Quantity);
        }

        /// <summary>
        /// Calculates the amount of underflow that will take place
        /// if the desired amount is subtracted from this one.
        /// </summary>
        /// <param name="amount"></param>
        /// <returns></returns>
        public int ComputeUnderflow(int amount)
        {
            if (amount <= MinQuantity)
            {
                return MinQuantity;
            }
            int total = (this.Quantity - amount);
            if (total > MinQuantity)
            {
                return MinQuantity;
            }
            return Math.Abs(total);
        }

        /// <summary>
        /// Determines if this ItemStack has the specified quantity.
        /// If the quantity is less than or equal to 0, this function
        /// will always return false.
        /// </summary>
        /// <param name="quantity"></param>
        /// <returns></returns>
        public bool HasQuantity(int quantity)
        {
            if (quantity <= MinQuantity)
            {
                return false;
            }
            return this.Quantity >= quantity;
        }

        /// <summary>
        /// Determines if this ItemStack can still fit the specified amount
        /// without overflowing into a new ItemStack. If the quantity is less
        /// than or equal to 0, this function will always return false.
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public bool CanHold(ItemStack other)
        {
            return CanHold(other.Quantity);
        }

        /// <summary>
        /// Determines if this ItemStack can still fit the specified amount
        /// without overflowing into a new ItemStack. If the quantity is less
        /// than or equal to 0, this function will always return false.
        /// </summary>
        /// <param name="amount"></param>
        /// <returns></returns>
        public bool CanHold(int amount)
        {
            if (amount <= MinQuantity)
            {
                return false;
            }
            return this.Quantity + amount <= MaxQuantity;
        }

        /// <summary>
        /// Determines if this ItemStack can subtract the specified amount
        /// without underflowing into a new ItemStack. If the quantity is less
        /// than or equal to 0, this function will always return false.
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public bool CanSubtract(ItemStack other)
        {
            return CanSubtract(other.Quantity);
        }

        /// <summary>
        /// Determines if this ItemStack can subtract the specified amount
        /// without underflowing into a new ItemStack. If the quantity is less
        /// than or equal to 0, this function will always return false.
        /// </summary>
        /// <param name="amount"></param>
        /// <returns></returns>
        public bool CanSubtract(int amount)
        {
            if (amount <= MinQuantity)
            {
                return false;
            }
            return this.Quantity - amount >= MinQuantity;
        }

        public bool Equals(ItemStack other)
        {
            if (other != null)
            {
                return 
                    this.Identifier == other.Identifier &&
                    this.Quantity == other.Quantity &&
                    this.MaxQuantity == other.MaxQuantity;
            }
            return false;
        }

        public override string ToString()
        {
            return $"{this.Identifier},{this.Quantity},{this.MaxQuantity}";
        }
    }
}

For more information on this ItemStack implementation, please see another answer I posted here: https://gamedev.stackexchange.com/questions/133000/how-do-inventories-work/133024

We do not pass around items. We pass around the integer ID's. This means we don't need to do any fancy polymorphism at all. When you need to get an item from the database, you just feed in the ID, use the location to cast it to the correct item(or other things that can achieve the cast on a dynamic level) and there you have it. This system also allows dynamic item generation. All you have to do is add another database List for Procedural Weapons, or Apparel. In the game when you augment an item, you're really just creating a new item, adding it to a list in the Database, removing the associated ItemStack from the players inventory, and then adding the new ItemStack with the new generated ID, which points to the created item in the database.

And finally, I believe a picture is worth a thousand words, so here is a screenshot of my engine using the Database system I have mentioned:

enter image description here

It should also be noted that this system scales very well with SQLite databases for actual storage and retrieval later down the road in your development. Essentially, you can rewrite the Database class that I have shown you to be a Query-Cache facade. So you first test to see if the item has been queried from the SQLite file already, and if it has, you just return the copy that you have already queried, else you query the item, cache it in the in-memory database, and then return it. This can greatly speed up your performance. The internal SQLite database is also very good at handling your custom integer indexes. All you would need to do for that is have a table in the SQLite file for each item type, then use the DatabaseKey struct to find out what type of item you're after, then use that to select the table from the SQLite file.

If you have any questions, I would be happy to answer them.

Krythic
  • 4,184
  • 5
  • 26
  • 67
-1

Polymorphic class model is a natural fit for your question.

Since you tagged Java; JPA supports inheritance via @javax.persistence.Inheritance annotation.

JPA stands for java persistence API. It provides an abstraction layer for dealing with databases. You can just annotate your model classes and have the framework build the tables - that is if you're to use a RDB at all as there are implementations that work with NoSQL

Lev Kuznetsov
  • 3,520
  • 5
  • 20
  • 33
  • I know and understand polymophism.. buy you have lost me to how that would benefit me within data storage. I like the idea of have a database to store peoples accounts, character profiles, etc. – Maxs728 Apr 09 '17 at 17:19
-1

I would probably extend this for high scalability by having 3 values stored instead of 2 per modifier.

This would allow you to 'type' modifiers first, with one type being 'augment', then the second value would be a category, like the 'fire' category of the 'augment' type, then finally a value.

I'd probably go

ItemId (key)

ImageId (since your items probably want to map to an image)

ModType1

ModCategory1

ModValue1

ModType2

ModCategory2

ModValue2

ModType3

ModCategory3

ModValue3

...

You could simplify this in your DB (and in turn speed your server up) by deciding on a very specific size of all these values.

I'd say a good model is this:

up to 256 categories of modifiers. (2 byte)

Up to 256 types of modifiers (2 bytes) (since if this modifier is a stat we will probably have more than 16 stats)

Values probably want to go up to 65,536 if you want big stats, so lets give it 4 bytes. Actually though we'll want this to be a signed int so we have values -32,768 to 32,768.

So now we can take a single int64 and convert that into a complex modifier.

We'll want to split it up into bytes and grab [2][2][4] of them for the three values.

Now each Stat is just one column of int64s!

  • so I see what you are doing here which is part of the problem I am trying to avoid.. Your example limits the (for all intensive purposes) weapon to three and exactly three possible augments. If now I wanted to add 2 more augments I need to scale the fields to include 2 more augments. What I would be trying to approach is instead of field based make it some how more record based. But trying to think through the most efficient ERD is hurting my mind at the moment. – Maxs728 Apr 09 '17 at 17:55
  • You would need to just have a second table of modifiers, many to one relationship between modifiers and items. I will note this is probably unnecessary, as its probably poor game design for your items to have a mountain of 'stat vomit' on them. Makes it hard to process what they actually do. I'd strongly recommend only having like 8 stats max on an item, more than that and it becomes hard to process. – Steffen Cole Blake Apr 09 '17 at 18:07
  • You can also make blanket stats though to add the illusion of more than 8. You could make a stat that gives str/dex/int/mnd/wis/chr +1 each, so setting that special stat to 8 would give +8 to all stats. Then if you want a one or two to be a bit higher add the difference on as a second stat – Steffen Cole Blake Apr 09 '17 at 18:10
  • I mean I guess that is something that could work. I mean there is base stats to every weapon but augments would add to or remove those base stats. But this leaves another issue in the database. The OOP approach being everything is an object. "Items" would be initially entities. I am not sure how to differentiate between an item versus a weapon. Weapons have stats.. A clock item (just an example) would not have these stats so it would be useless in the same table as the weapon. See Edit 1 as to my thought process. Excuse the fact I am using MS Access. This is just for prototyping at the moment. – Maxs728 Apr 10 '17 at 05:03
  • A clock item in this case would still indeed have modifiers. For example, how much does it sell for to NPCs? Is it tradeable? Is it mailable? Is it sellable? etc etc. – Steffen Cole Blake Apr 13 '17 at 01:31
  • But I mean then we are talking more OOP than database at the moment. We can safely assume all entities have some base attributes that could be in the entity table. But still break out into different sub tables where the data is more specific to the entity type... right? – Maxs728 Apr 28 '17 at 16:28
  • Probably unnecessary. You can severaly compress it by just having an 'item Type' flag. Then the properties are just a lookup table of pairs of Types and Values. Property Type 08 might be one thing for furniture items, and something else for armor, and something else for weapons. But they can all just have a generic typing, then in your logic you just fetch the property from the correct table based on that ItemType flag. – Steffen Cole Blake Apr 29 '17 at 05:55
  • IE: If ItemType = Armor, then for each Property type get its info from the ArmorProperty table. But if its a furniture ItemType, fetch properties from the FurnitureProperties table. Furthermore you could do something like, say, for the first half of the property values all be on one shared table, then all values over the half mark are on their own special tables. – Steffen Cole Blake Apr 29 '17 at 05:56