Loading...   

[Show Table of Contents]


ยงUnified Inventory System


Persistent Inventory Type - Inventory objects persist through a mob object's life cycle (i.e., player equipment)

Non-peristent Inventory Type - Inventory objects are consumed at the end of a players's transaction process (i.e., player merchant transaction window)


EQEmu::InventoryProfile is conceived to replace all existing Mob class inventory implementations. This will eliminate all specialized inventory handlers in all mob-derived classes. The design is based off of KLS's inv2 branch constructs.


Each inventory instance will be instantiated based on the using derived-class type. Restriction data will be stored locally through the use of the EQEmu::InventoryProfile::impl opaque pointer class.


Initialization of EQEmu::InventoryProfile requires the use of one of the following enum constants:

namespace EQEmu
{
    namespace versions {
        enum class InventoryVersion {
            Unknown = 0,
            Client62,
            Titanium,
            SoF,
            SoD,
            UF,
            RoF,
            RoF2,
            NPC,
            Merc,
            Bot,
            Pet,
            OfflineTitanium,
            OfflineSoF,
            OfflineSoD,
            OfflineUF,
            OfflineRoF,
            OfflineRoF2
        };

    } /*versions*/

} /*EQEmu*/

 

  • InventoryVersion::Unknown is essentially a 'null' setting for unknown or invalid mob types. This is the 'default' setting for any parameter-less instantiation
  • InventoryVersion::Client62 is a placeholder for the defunct client 6.2. Its definition is currently required to alleviate perl translation issues with the hard-coded numeric values in perl scripts. (Adding tokens to the perl api and re-writing the scripts would eliminate this necessity)
  • InventoryVersion::{Titanium..RoF2} are specifically for client-based settings
  • InventoryVersion::{NPC..Pet} are specifically for non-client based settings
  • InventoryVersion::{OfflineTitanium..OfflineRoF2} are for NPC(?) client-replacement objects designed to allow offline trading


Upon initialization of the inventory class, EQEmu::InventoryProfile::impl will retrieve settings data from EQEmuDictionary - a collection of definitions coallesced from the various client translators and EntityLimits (emu_limits files.) Using this method allows a single inventory class to behave such as if each mob-derived class had its own specialized inventory system. Individual calls to EQEmuDictionary could be made..but, would add a little unneeded overhead since the called functions validate the InventoryVersion argument with each call..which is why the implementation class should include local definitions for such entries.


Using a strongly-typed reference to InventoryProfile within any mob-derived class eliminates the need for added nullptr checks and the possibility of dangling pointers - though, the use of a std::unique_ptr would eliminate that issue.


Due to the fact that an instantiated Client class object does not know its version at the time of instantiation, EQEmu::InvetoryProfile::Initialize() can be used to 're-initialize' an existing inventory. Some oversight may be needed to avoid issues of client disconnects where the player drops with one client version and immediately logs back in with another.


The server will be syncronized to client settings through the EQEmu::inventory namespace. A special declaration will link (sync) the server to any declared client's sub-system:

namespace EQEmu
{
    namespace inventory {
        using namespace RoF2::invtype;
        using namespace RoF2::invslot;
        using namespace RoF2::invbag;
        using namespace RoF2::invaug;

    } /*inventory*/

} /*EQEmu*/

 

The :: namespaces are designed to provide only data pertinent to their sub-system. This would allow for the following scenario:

namespace inventory {
    using namespace RoF::invtype;
    using namespace UF::invslot;
    using namespace RoF2::invbag;
    using namespace SoF::invaug;

} /*inventory*/

 

These settings would allow the server to sync to RoF-allowed inventory types, UF-allowed inventory slots (8 general bag slots versus the newer 10,) RoF-allowed bag sizes (greater than 10) and SoF-allowed augment slots (5 versus the newer 6.) Minimal changes, if any, would need to be made to be made to the InventoryProfile inventory code to facilitate this since each instance of the inventory object has its own internal limits. This coding just specifies how the server needs to standardize its internal references. Any changes to the reference namespaces will need to be reviewed and a determination made if a need to alter any database values are required.

 


One aspect of EQEmu::InventoryProfile::impl is the EQEmu::InventoryDatabaseDataModel class. This class defines how each InventoryType will interact with the database. The class is currently empty and needs to be fully designed.


My original proposal defined a derived class for each InventoryVersion:

namespace EQEmu
{
    class InventoryDatabaseDataModel { // abstract function class

    private:

        friend class InventoryProfile; // nothing but factory() past this point
        static InventoryDatabaseDataModel* factory(versions::InventoryVersion inventory_version);
    };

} /*EQEmu*/

class DataModel_Null : public EQEmu::InventoryDatabaseDataModel {};
class DataModel_Titanium : public EQEmu::InventoryDatabaseDataModel {};
class DataModel_SoF : public EQEmu::InventoryDatabaseDataModel {};
class DataModel_SoD : public EQEmu::InventoryDatabaseDataModel {};
class DataModel_UF : public EQEmu::InventoryDatabaseDataModel {};
class DataModel_RoF : public EQEmu::InventoryDatabaseDataModel {};
class DataModel_RoF2 : public EQEmu::InventoryDatabaseDataModel {};
class DataModel_NPC : public EQEmu::InventoryDatabaseDataModel {};
class DataModel_Merc : public EQEmu::InventoryDatabaseDataModel {};
class DataModel_Bot : public EQEmu::InventoryDatabaseDataModel {};
class DataModel_Pet : public EQEmu::InventoryDatabaseDataModel {};
class DataModel_OfflineTitanium : public EQEmu::InventoryDatabaseDataModel {};
class DataModel_OfflineSoF : public EQEmu::InventoryDatabaseDataModel {};
class DataModel_OfflineSoD : public EQEmu::InventoryDatabaseDataModel {};
class DataModel_OfflineUF : public EQEmu::InventoryDatabaseDataModel {};
class DataModel_OfflineRoF : public EQEmu::InventoryDatabaseDataModel {};
class DataModel_OfflineRoF2 : public EQEmu::InventoryDatabaseDataModel {};

EQEmu::InventoryDatabaseDataModel* EQEmu::InventoryDatabaseDataModel::factory(versions::InventoryVersion inventory_version)
{
    // static_cast safety is only ensured by the use of a properly defined derived class..like those above

    switch (inventory_version) {
    case versions::InventoryVersion::Titanium:
        return static_cast(new DataModel_Titanium);
    case versions::InventoryVersion::SoF:
        return static_cast(new DataModel_SoF);
    case versions::InventoryVersion::SoD:
        return static_cast(new DataModel_SoD);
    case versions::InventoryVersion::UF:
        return static_cast(new DataModel_UF);
    case versions::InventoryVersion::RoF:
        return static_cast(new DataModel_RoF);
    case versions::InventoryVersion::RoF2:
        return static_cast(new DataModel_RoF2);
    case versions::InventoryVersion::NPC:
        return static_cast(new DataModel_NPC);
    case versions::InventoryVersion::Merc:
        return static_cast(new DataModel_Merc);
    case versions::InventoryVersion::Bot:
        return static_cast(new DataModel_Bot);
    case versions::InventoryVersion::Pet:
        return static_cast(new DataModel_Pet);
    case versions::InventoryVersion::OfflineTitanium:
        return static_cast(new DataModel_OfflineTitanium);
    case versions::InventoryVersion::OfflineSoF:
        return static_cast(new DataModel_OfflineSoF);
    case versions::InventoryVersion::OfflineSoD:
        return static_cast(new DataModel_OfflineSoD);
    case versions::InventoryVersion::OfflineUF:
        return static_cast(new DataModel_OfflineUF);
    case versions::InventoryVersion::OfflineRoF:
        return static_cast(new DataModel_OfflineRoF);
    case versions::InventoryVersion::OfflineRoF2:
        return static_cast(new DataModel_OfflineRoF2);
    default:
        return static_cast(new DataModel_Null);
    }
}

 


Since the clients work off of the same database tables, the following could serve as a replacement for individualized clients:

 

namespace EQEmu
{
    class InventoryDatabaseDataModel { // abstract function class

    private:

        friend class InventoryProfile; // nothing but factory() past this point
        static InventoryDatabaseDataModel* factory(versions::InventoryVersion inventory_version);
    };

} /*EQEmu*/

class DataModel_Null : public EQEmu::InventoryDatabaseDataModel {};
class DataModel_Client : public EQEmu::InventoryDatabaseDataModel {};
class DataModel_NPC : public EQEmu::InventoryDatabaseDataModel {};
class DataModel_Merc : public EQEmu::InventoryDatabaseDataModel {};
class DataModel_Bot : public EQEmu::InventoryDatabaseDataModel {};
class DataModel_Pet : public EQEmu::InventoryDatabaseDataModel {};
class DataModel_OfflineClient : public EQEmu::InventoryDatabaseDataModel {};

EQEmu::InventoryDatabaseDataModel* EQEmu::InventoryDatabaseDataModel::factory(versions::InventoryVersion inventory_version)
{
    // static_cast safety is only ensured by the use of a properly defined derived class..like those above

    switch (inventory_version) {
    case versions::InventoryVersion::Titanium:
    case versions::InventoryVersion::SoF:
    case versions::InventoryVersion::SoD:
    case versions::InventoryVersion::UF:
    case versions::InventoryVersion::RoF:
    case versions::InventoryVersion::RoF2:
        return static_cast(new DataModel_Client);
    case versions::InventoryVersion::NPC:
        return static_cast(new DataModel_NPC);
    case versions::InventoryVersion::Merc:
        return static_cast(new DataModel_Merc);
    case versions::InventoryVersion::Bot:
        return static_cast(new DataModel_Bot);
    case versions::InventoryVersion::Pet:
        return static_cast(new DataModel_Pet);
    case versions::InventoryVersion::OfflineTitanium:
    case versions::InventoryVersion::OfflineSoF:
    case versions::InventoryVersion::OfflineSoD:
    case versions::InventoryVersion::OfflineUF:
    case versions::InventoryVersion::OfflineRoF:
    case versions::InventoryVersion::OfflineRoF2:
        return static_cast(new DataModel_OfflineClient);
    default:
        return static_cast(new DataModel_Null);
    }
}

 

Using this abbreviated schema would eliminate some class definitions..but, require more internal coding within the consolidated classes' handlers to process database calls correctly.

 


In either of the above schema, the four NPC-based classes would be scripted to use their specific database tables. Any Mob::InventoryProfile::Save() call would the appropriate data model's Save() function to use the appropriate queries and tables.


Examples of non-client data model use:

  • NPC - would use the merchant items table..in the case of a merchant id..and, possibly a few others
  • Merc - would only save to the merc inventory table (post-RoF2 clients support item trades)
  • Bot - would only save to the bot inventories table
  • Pet - would only save to the character pet inventories table..though, we would probably need to implement InventoryVersion::BotPet so that pet discrimination occurs

More options could be implemented to each data model, or even the entire inventory system. (Imagine a quest NPC that is scripted to take items from you, walk over to a forge and actually use it, then trade the finished product back - all without complicated inventory interactions.)


If the data models' functions are designed to accept parameters from EQEmu::InventoryProfile::impl, an externally initialized array could be implemented to return a static reference for each model type, eliminating some memory overhead with each InventoryProfile instance. Something like:

static InventoryDatabaseDataModel* data_model_ptrs[EQEmu::versions::InventoryVersionCount];

EQEmu::InventoryDatabaseDataModel::Initialize()
{
    if (initialized)
        return;
    
    data_model_ptrs[static_cast(InventoryVersion::Unknown)] = new DataModel_Null;
    data_model_ptrs[static_cast(InventoryVersion::Client62)] = data_model_ptrs[static_cast(InventoryVersion::Unknown)];
    
    data_model_ptrs[static_cast(InventoryVersion::Titanium)] = new DataModel_Titanium;
    data_model_ptrs[static_cast(InventoryVersion::SoF)] = new DataModel_SoF;
    ...
    
    //.. or ..
    
    data_model_ptrs[static_cast(InventoryVersion::Titanium)] = new DataModel_Client;
    data_model_ptrs[static_cast(InventoryVersion::SoF)] = data_model_ptrs[static_cast(InventoryVersion::Titanium)];
    ...
}

 

This would allow EQEmu::InventoryDatabaseDataModel* EQEmu::InventoryDatabaseDataModel::factory() to return a pointer to an existing object in the data_model_ptrs array. The same could be done for the EQEmu::InventoryProfile::impl class in regards to a static reference for 'limit' values.

 


Known Issues:

  • post-instantiation client versioning problems
  • slot use varies amongst clients, requiring specialized handlers
    • PossessionsPowerSource is not available in pre-SoF clients
    • PossessionsGeneral9 and PossessionsGeneral10 are not available in pre-RoF(HoT?) clients
  • feature use varies amongst clients
    • only RoF+ allows merchant-based deleted items tab, InvTypeRealEstate, etc...
    • only RoF2+ utilizes InvTypeKrono, etc... 
  • pre-RoF clients use a queued InvTypeLimbo versus the mapped RoF+ version (verified desyncs)
  • perl scripts will have to be re-written to utilize the new inventory system (time to add tokens?)
  • database schema will need to change and import scripts/code added (swap item slots bits 21/22)
  • (I'm sure there's more...)