Skip to content

WizardData ​

The WizardData system provides persistent storage for all player and game data using RavenDB with a dual-database architecture.

Data is separated into two distinct databases based on access patterns and data ownership:

  • PlayerDatabase: Individual player accounts and character data
  • WorldDatabase: Shared game configuration data used by development teams

TIP

The WorldDatabase fills gaps in client data. Despite the shared data systems philosophy, some data is left server-side only. Some examples include drop tables, NPC inventories, and zone transfer data.

RavenDB Singleton Pattern ​

Both databases use the RavenDatabaseSingleton<T> pattern for connection management:

csharp
public class PlayerDatabase : RavenDatabaseSingleton<PlayerDatabase> {
    protected override string DatabaseName { get; } 
        = ConfigurationManager.Settings["Database.PlayerDatabaseName"];
    protected override string Url { get; } 
        = ConfigurationManager.Settings["Database.PlayerDatabaseUrl"];
}
public class PlayerDatabase : RavenDatabaseSingleton<PlayerDatabase> {
    protected override string DatabaseName { get; } 
        = ConfigurationManager.Settings["Database.PlayerDatabaseName"];
    protected override string Url { get; } 
        = ConfigurationManager.Settings["Database.PlayerDatabaseUrl"];
}

Embedded vs Remote

  • Embedded: Local database file for development and small deployments
  • Remote: External RavenDB server for production environments
  • Automatic fallback to embedded when no URL is configured

Collection Pattern ​

Data access is handled through static collection classes that provide CRUD operations and encapsulate database logic.

Collection Structure ​

csharp
public static class AccountCollection {
    public const string CollectionName = "Accounts";
    private static readonly IDocumentStore s_store;
    
    static AccountCollection() {
        s_store = PlayerDatabase.Instance.Store;
    }
    
    public static bool CreateAccount(Account account) {
        using var session = s_store.OpenSession();
        
        if (session.Query<Account>(collectionName: CollectionName)
                   .Any(c => c.Username == account.Username)) {
            return false;
        }
        
        session.Store(account);
        var metadata = session.Advanced.GetMetadataFor(account);
        metadata[Raven.Client.Constants.Documents.Metadata.Collection] = CollectionName;
        
        session.SaveChanges();
        return true;
    }
}
public static class AccountCollection {
    public const string CollectionName = "Accounts";
    private static readonly IDocumentStore s_store;
    
    static AccountCollection() {
        s_store = PlayerDatabase.Instance.Store;
    }
    
    public static bool CreateAccount(Account account) {
        using var session = s_store.OpenSession();
        
        if (session.Query<Account>(collectionName: CollectionName)
                   .Any(c => c.Username == account.Username)) {
            return false;
        }
        
        session.Store(account);
        var metadata = session.Advanced.GetMetadataFor(account);
        metadata[Raven.Client.Constants.Documents.Metadata.Collection] = CollectionName;
        
        session.SaveChanges();
        return true;
    }
}

Creating New Collections ​

When adding new data types, follow the established collection pattern:

1. Determine Database Target ​

  • PlayerDatabase: User-specific data that varies per player
  • WorldDatabase: Shared configuration data managed by developers

2. Create Collection Class ​

csharp
public static class ExampleCollection {
    public const string CollectionName = "Examples";
    private static readonly IDocumentStore s_store;
    
    static ExampleCollection() {
        s_store = PlayerDatabase.Instance.Store; // or WorldDatabase.Instance.Store
    }
    
    public static bool CreateExample(ExampleModel example) {
        using var session = s_store.OpenSession();
        
        session.Store(example);
        var metadata = session.Advanced.GetMetadataFor(example);
        metadata[Raven.Client.Constants.Documents.Metadata.Collection] = CollectionName;
        
        session.SaveChanges();
        return true;
    }
    
    public static ExampleModel GetExample(string id) {
        using var session = s_store.OpenSession();
        return session.Load<ExampleModel>(id);
    }
}
public static class ExampleCollection {
    public const string CollectionName = "Examples";
    private static readonly IDocumentStore s_store;
    
    static ExampleCollection() {
        s_store = PlayerDatabase.Instance.Store; // or WorldDatabase.Instance.Store
    }
    
    public static bool CreateExample(ExampleModel example) {
        using var session = s_store.OpenSession();
        
        session.Store(example);
        var metadata = session.Advanced.GetMetadataFor(example);
        metadata[Raven.Client.Constants.Documents.Metadata.Collection] = CollectionName;
        
        session.SaveChanges();
        return true;
    }
    
    public static ExampleModel GetExample(string id) {
        using var session = s_store.OpenSession();
        return session.Load<ExampleModel>(id);
    }
}

3. Follow Conventions ​

  • Static class with collection name constant
  • Store assignment in static constructor
  • Metadata setting for proper RavenDB collection organization
  • Session disposal using using statements
  • Consistent method naming (Create, Get, Update, Delete patterns)

Database Utilities ​

The DatabaseUtilities class provides common operations:

csharp
// Create accounts with hashed passwords
var account = DatabaseUtilities.CreateEmbeddedDatabaseAccount(
    username: "player123",
    email: "player@example.com", 
    plaintextPassword: "password",
    auth: AuthLevel.None
);
// Create accounts with hashed passwords
var account = DatabaseUtilities.CreateEmbeddedDatabaseAccount(
    username: "player123",
    email: "player@example.com", 
    plaintextPassword: "password",
    auth: AuthLevel.None
);