-
Notifications
You must be signed in to change notification settings - Fork 158
Code documentation
This page contains various basic information about the code structure and how things are designed here.
-- WIP --
The editor uses MVVM (Model-view-view model) pattern, in particular it uses View Model first
(instead of View first
) approach. Explaining MVVM is out of this wiki scope, if you want to read more about it, just google for MVVM
, e.g. https://docs.avaloniaui.net/guides/basics/mvvm .
Regarding the 'View Model first' approach - it means everywhere in the code we are referring to the view model. And the view
is created automatically underneath. E.g. if you want to display a new dialog window, you should create a View Model for the window and the view will be created automatically. In the other approach (View first) you would create a view instance first and the view model would be created automatically. This however couples your UI framework, meanwhile, view models in WoW Database Editor are view agnostic. In the past there were two versions - one using WPF, the other one using Avalonia, both reusing the same Models and View Models, while only views were different.
Wow Database Editor is built in a modular way, that means projects are de-coupled from each other and they don't know about other projects whenever possible. It is even possible to load an external .dll as a plugin (even though it is not used now). Things are resolved dynamically, usually using shared interfaces, defined in the project WDE.Common
.
The core project (sometimes referred as a "shell") doesn't even know what particular things can be edited, all the editors like 'Creature script', 'WoW Sniff', 'Creature texts' are resolved dynamically, all the tool windows like 'Tables', 'Sessions', 'History' are resolved dynamically.
By default, when you are about to display some View Model, View is resolved automatically by name convention - ViewModel
in class full name is replaced with View
. E.g. if you have class WDE.SmartScriptEditor.ViewModels.EditorViewModel
, the editor will try to instantiate class WDE.SmartScriptEditor.Views.EditorView
as a view.
Alternatively, you can manually assign a view to your viewmodel, in the method public override void RegisterViews(IViewLocator viewLocator)
in your module class (class that extends ModuleBase
)
This chapter briefly describes the common interfaces used to implement editing features.
Implement this interface, to add a new tool window, like 'Tables', 'Sessions', 'History'. Once you add a class that implements this interface, the editor will automatically find it and add it to 'Views' menu, so that the user is able to open it.

Note: please don't confuse the interface with Dock.Model.Controls.ITool
which is also defined in the solution.
The most important and basic interface representing a basic document that can be opened in the editor. It contains basic data like Title, Icon of the document, Cut, Paste, Copy commands implementation for this document. It doesn't have to be wow-related, it doesn't have to be save-able. I.e. "Quick start" is a document, "Settings" is a document. But also each editor (smart script editor, table editor) is a document.
Note: please don't confuse the interface with Dock.Model.Controls.IDocument
which is also defined in the solution.
This is the most basic interface, the most basic unit that represents an editable thing, that represents a thing that can be opened
, edited
, saved
, a thing that can be stored in a solution
or in a session
.

The Solution Explorer tool contains ISolutionItems
. E.g. a folder is implemented by WDE.Solutions.SolutionFolderItem
class, smart scripts are implemented by WDE.SmartScriptEditor.SmartScriptSolutionItem
class, table editors are implemented by WDE.DatabaseEditors.Solution.DatabaseTableSolutionItem
class.
Those ISolutionItem
are serialized automatically by Newtonsoft.Json library, so if there is something you don't want to be serialized, you might need to use additional attributes on fields/properties, e.g. look at SmartScriptSolutionItem
class ([JsonIgnore]
attributes) or DatabaseTableSolutionItem
class ([JsonProperty]
attributes).
There is a series of ISolutionItem related interfaces that provide additional data regarding the solution item, e.g. name or icon.
note: T there will be your particular ISolutionItem implementation
-
ISolutionNameProvider<T>
- implement this interface and methodstring GetName(T item)
to provide a name for a solution item (must have). -
ISolutionItemSqlProvider<T>
- implement this interface and methodTask<IQuery> GenerateSql(T item)
to provide a method that generates a query for this item (must have). -
ISolutionItemIconProvider<T>
- implement this interface and methodImageUri GetIcon(T item)
to provide an icon for a solution item. E.g. icons in the solution explorer tool are resolved using this interface (pretty much must have). -
ISolutionItemSerializer<T>
andISolutionItemDeserializer<T>
- implement this interfaces as an alternative method of serializing and deserializing solution items (must have). Note:Type
field inISmartScriptProjectItem
must be unique per solution item and also is now an arbitrary number, which is not the best design decision (considering we support modularity). -
ISolutionItemEditorProvider<T>
- implement this interface and methodIDocument GetEditor(T item)
to construct a view model (which implementsIDocument
) for this solution item -
ISolutionItemRemoteCommandProvider<T>
- implement this interface and methodIRemoteCommand[] GenerateCommand(T item)
to generate a series of server commands that will be remotely executed to reload this solution item. E.g. editing creature_template of entry X, can return.reload creature_template X
command (optional) -
ISolutionItemRelatedProvider<T>
- implement this interface and methodTask<RelatedSolutionItem?> GetRelated(T item)
to provide information what 'related' things can be opened (icons in the top right corner) (optional)

-
ISolutionItemDocument
- this is an extension ofIDocument
interface, it additionally containsISolutionItem SolutionItem { get; }
property andTask<IQuery> GenerateQuery()
. All the 'document editors' view models should implement this interface. _todo: I guessISolutionItemEditorProvider<T>
should returnISolutionItemDocument
instead ofIDocument
then.
Implement this interface, to provide a "factory" class for your T : ISolutionItem
. It contains a "name", "description" and "icon" of the thing that can be created using this interface. It has a method Task<ISolutionItem?> CreateSolutionItem()
used to create a new solution item. This method returns a Task
, which means you can open additional async windows, e.g. open a window to pick a creature entry, which later will be used to construct your ISolutionItem
instance.
In this interface you can also decide whether your particular ISolutionItem
makes sense with currently selected ICoreVersion
(= emulator version/type). E.g. Smart Scripts are not supported by (c)mangos, they are supported by Trinity based emulators tho. Therefore a provider for SmartScriptSolutionItem
can decide whether the current core version is Trinity or Mangos and allow creating this type of solution item or not.

(Quick load
and File -> New/Open
are added dynamically based on ISolutionItemProvider
implementations).

To sum this up, the fact that Creature Script
is visible in the Quick Load panel means, there is some ISolutionItemProvider
implementation for this.
When I click on the creature script
button, it will execute ISolutionItemProvider::CreateSolutionItem()
method, which will create a new ISolutionItem
. In case of SAI Creature Script, it will create a new SmartScriptSolutionItem
instance with chosen "entryorguid" and SmartScriptType.Creature
enum as type.
Once the SmartScriptSolutionItem
is created, in order to display an editor for this item, a document-editor needs to be created. This means
the editor will look up for a class that implements ISolutionItemEditorProvider<SmartScriptSolutionItem>
and it will invoke GetEditor()
method, in case of SAI, check the WDE.TrinitySmartScriptEditor.Providers.SmartScriptEditorProvider
class.

In some cases it makes sense to create a specialized editor, e.g. smart_scripts
internal structure (in the database) is a little bit compound (e.g. a way to link multiple actions), therefor SmartScripts
has a special editor displaying events and actions.
However, in some cases there is no way to create anything but a generic view for table, columns and row. In this case WDE.DatabaseEditors
project is used. Both the tables view on the left and the game_event
editor is provided by this project.
It is fully generic, i.e. it loads the tables definition from json
files. The json
files are stored in WDE.DatabaseEditors/DbDefinitions
folder.
You might ask: if this is generic, why do we even need a json definition, why not generate it automatically using database columns? The thing is, this is a generic view, but thanks to the definition, the editor can interpret the data in a specific way. I.e. TrinityCore's holiday_dates
table has a column date_value
which is a number - seconds since 1st of January 2000 (a unix epoch time with an offset). Thanks to the provided definition, the editor will display the date in a human readable form:
The generic table editor can work in three different modes, depending on a field record_mode
in the json definition. Depending on the mode, the view is different and the generated query may vary.

Template
mode can be used for tables that have a primary key consisting of one column, i.e. creature_template
.
In this mode, the user has to pick the key that one wants to edit.
This view presents table "column" in a vertical form - as rows. This is mainly due to the fact that _template
tables usually have a lot of columns and scrolling horizontally is extremely inconvenient while vertical view makes much more sense.

you can also use search
option to quickly find a field by name.
The generated query will always be update
, unless you insert a completely new key. This is due to the fact, that we shall never delete + insert from _template
tables (for retail-like content). If however you are creating custom stuff, you can insert a new key and it will correctly generate insert query.

SingleRow
mode is the most universal, it can be used for tables with any primary key, as long as the columns in the key are numbers only (so no strings in table key). The view is very similar to a generic SQL viewers, but it makes use of the provided definition and presents the fields in a special way. I.e. in the screenshot above, Start/End Time
columns are unix timestamp in the database, here displayed in a human-friendly form, Holiday
is an entry from a DBC, here the corresponding name is shown.
In this mode, the user don't select any key to display, this view always displays all the rows, with pagination (in the bottom right corner) and with an ability to write a custom SQL query to filter.
The user can delete rows (DELETE query will be generated), can insert new rows (INSERT query will be generated), can edit non-key columns (UPDATE query will be generated) and can edit key columns (DELETE + INSERT will be generated, because changing a key is treated as a deletion + insertion in the editor).

MultiRecord
mode is the most specific one. It can be used for tables whose primary key contains at least two columns, yet one of them is usually the one the user want to edit it by. I.e. Trinity's creature_text
has a primary key of (CreatureId, GroupId, Id)
. Creature id
is a creature entry, while group id
and id
are just some consecutive numbers per entry. In other words: it doesn't make sense to edit (at once) 'all the creature texts, that has group id 1', yet it makes a lot of sense to edit (at once) 'all the creature texts, that belongs to creature id XYZ'
In this mode, the user has to select the key one wants to edit. And despite the primary key having multiple columns, the user selects just one column, e.g. in the creature_text
example, the user has to select the creature entry whose texts we want to edit.
In the view there is no pagination, there is no filtering, because by design we already see the rows which matches our selected key.
Also, when it comes to the query generation, it never generates UPDATE query, it always generates DELETE + INSERT query by the selected key.