DFU does not support custom backstories right now, but I have a PR pending here: https://github.com/Interkarma/daggerfal ... /pull/2065
These efforts have led me to wonder about adding new macro types in DFU (ex: "You swing your %foo."). Hazel has documented how the system works and how maintainers can improve on this here: viewtopic.php?f=23&t=673
However, as of now, it seems we can only add new macros by modifying DFU itself. While it's not really a blocker, it does mean new macros require sending a pull request and waiting for a new DFU release before mods can use them.
I'm wondering about what could be done to make the MCP system more mod-friendly. There's three main issues I see so far
1) handlers are set in a fixed, private dictionary inside MacroHelper. Mods cannot add new key-value pairs in there yet
2) for macros that require context, macro expansion relies on a "macro data source" (MDS) provided by a "macro context provider" (MCP) object. For example, the backstory strings will be expanded within the CreateCharBiography window with a BiogFile object, which has its own BiogFileMacroDataSource type. Mods cannot change the BiogFile object or its MDS, and therefore cannot store extra context that could be required by new macros, or change the behavior of macros
3) for each macro type, a new fixed function is added to the MacroDataSource interface. Adding new context-dependent macros requires modifying DFU code to modify that interface
Problem #1 is not too much of an issue in itself, just adding a public accessor would be enough.
Problem #2, in the current approach, probably requires work in every MCP class to make these mod-friendly. Not an insurmountable task, but worth noting.
Problem #3 probably cannot be easily worked around for modding without changing the approach.
For reference, here's a list of all the MCP classes: BiogFile, DaggerfallStats, DaggerfallUnityItem, IEntityEffect (implemented by BaseEntityEffect), Quest, and TalkManager.
Suggested solution
The first part of the solution solves both #1 and #3. Simply put, we add a method in MacroDataSource that takes the symbol string (ex: %foo) as a paramter and returns the expanded string (let's name it ExpandString as a placeholder). There's not really a need to have a fixed function for each macro type, only 2 macro handlers require any extra data to expand the string, but I think they can be ignored for this thread. In MacroHelper.GetValue(...), we will now get the MDS from the MCP and call ExpandString on it. If it returns null, we can call the existing macro handlers as a default. Note the order of the operations. This allows the MDS to handle macros that would otherwise be caught by the handlers - this is by design.
But just that is not good enough in itself, of course. For problem #2, we need a new type: IMacroDataSourceFactory. This interface has a factory function for each MCP (see list above). This factory will be set as a new field in DaggerfallUnity (along with similar objects, like textProvider), and mods can implement their own factory type and return their own macro data sources from there.
With these two changes, mods can override "ExpandString" to handle new macros, override existing macro functions to change its behavior, and add any extra data needed by the mod for context-awareness.
As an example, let's say my CustomBackgrounds mod wants to change the MDS used in BiogFile. During the Mod's Awake, it sets its own MacroDataSourceFactory in the DaggerfallUnity instance, which overrides ex: BiogFileDataSource() to return a new type derived from BiogFileMacroDataSource. When the time comes to expand a new character's backstory, BiogFile.GetMacroDataSource is called, which would now return DaggerfallUnity.Instance.macroDataSourceFactory.BiogFileDataSource(). The MacroHelper calls ExpandString on this MDS, and the mod can do whatever it wants there. For macros it does not want to override, it simply returns null, and everything keeps working as before, no extra work needed anywhere.
Design points
- Why keep the old MDS functions at all, then?
- If the MDS factory is a singleton, doesn't that mean only a single mod can override it?
Code: Select all
public void Awake()
{
ITextProvider currentTextProvider = DaggerfallUnity.Instance.TextProvider;
DaggerfallUnity.Instance.TextProvider = new BackgroundTextProvider(currentTextProvider);
}
Conclusion
I hope the problem and the offered solution are clear enough from this post. In any case, I'll start working on an implementation, and will update this thread to link to it, so that readers may get a better idea of where I'm going.
Feel free to ask questions, or discuss any other issues.