Macro handlers and how to add them
Posted: Mon Sep 04, 2017 4:05 pm
As you may have seen on Interkarma's twitter feed, I've created a macro handling system to deal with %abc style text macros that Daggerfall uses in the following files: arena2\text.rsc, fall.exe, and arena2\*.qrc. My aim when creating the system is that it's easy for anyone to add macro handlers in the future without needing to worry about how the system works. I think this is important because there are 160 macros in Daggerfall (according to http://www.dfworkshop.net/static_files/ ... qrcsymbols) and the handling of them often has to wait for the appropriate game system to be in place first.
Messages that are not yet handled will show up in game as %abc[tag]. The tag will tell you what is missing to be able to replace the macro with the correct value, as shown in the list below:
%abc[undefined] -> Macro needs adding to the list of macroHandlers found in MacroHelper class.
%abc[unhandled] -> Macro requires a handler method to be defined in MacroHelper class and the name adding to macroHandlers mapping.
%abc[srcDataUnknown] -> Macro needs an appropriate handler method adding to the relevant context provider class.
What complicates macro handling is that some need contextual information which can be based on an object instance (e.g. item), or based on previous macro expansions (e.g. person pronouns). If a message has contextual macros DaggerfallMessageBox has to be setup using the baseic 2 param constructor and then call the required set methods. SetText methods accept an option macro context provider. So there are two sets of steps given below, one for simple macros whose value is globally availiable, and the other for macros that need context of some sort. (note that steps 1 & 2 are common)
Process for adding new macro handlers:
Open the class DaggerfallWorkshop.Utility.MacroHelper.
Does the macro need an object instance to provide context? (e.g. item, quest, stats)
If no context required:
1) Find the macro in macroHandlers dictionary, if the macro isn't in the list then add it at the bottom. e.g. "%gns"
2) Define the handler method name, replacing 'null' with it. e.g. 'GreatNewStuff'
3) Add a suitable handler method to the code region marked "Global macro handlers". (Note that the mcp param will be null so don't try and use it) e.g.
If context required:
1) Find the macro in macroHandlers dictionary, if the macro isn't in the list then add it at the bottom. e.g. '%gns'
2) Define the handler method name, replacing 'null' with it. e.g. 'GreatNewStuff'
3) Add a suitable handler method that delegates to the same method in a MacroDataSource (provided by the passed IMacroContextProvider) to the code region marked "Contextual macro handlers". e.g.
4) Add an unimplemented exception method to the MacroDataSource base class. (this ensures only has to be implemented by data sources where it's relevant) e.g.
5) Find the class that will provide the required context data and override the handler method. (convention is the macro data source implementation lives in a file of the same name but using suffix 'MCP') The variable 'parent' provides access to the context providing class. e.g.
If you need a new macro data source, copy one of the existing ones: QuestMCP, DaggerfallUnityItemMCP, DaggerfallStatsMCP. (MCP = Macro Context Provider)
Hope this make sense.
Messages that are not yet handled will show up in game as %abc[tag]. The tag will tell you what is missing to be able to replace the macro with the correct value, as shown in the list below:
%abc[undefined] -> Macro needs adding to the list of macroHandlers found in MacroHelper class.
%abc[unhandled] -> Macro requires a handler method to be defined in MacroHelper class and the name adding to macroHandlers mapping.
%abc[srcDataUnknown] -> Macro needs an appropriate handler method adding to the relevant context provider class.
What complicates macro handling is that some need contextual information which can be based on an object instance (e.g. item), or based on previous macro expansions (e.g. person pronouns). If a message has contextual macros DaggerfallMessageBox has to be setup using the baseic 2 param constructor and then call the required set methods. SetText methods accept an option macro context provider. So there are two sets of steps given below, one for simple macros whose value is globally availiable, and the other for macros that need context of some sort. (note that steps 1 & 2 are common)
Process for adding new macro handlers:
Open the class DaggerfallWorkshop.Utility.MacroHelper.
Does the macro need an object instance to provide context? (e.g. item, quest, stats)
If no context required:
1) Find the macro in macroHandlers dictionary, if the macro isn't in the list then add it at the bottom. e.g. "%gns"
2) Define the handler method name, replacing 'null' with it. e.g. 'GreatNewStuff'
3) Add a suitable handler method to the code region marked "Global macro handlers". (Note that the mcp param will be null so don't try and use it) e.g.
Code: Select all
private static string GreatNewStuff(IMacroContextProvider mcp)
{ // %gns
return GameManager.Instance.PlayerEntity.NewStuff.GreatStuff;
}
If context required:
1) Find the macro in macroHandlers dictionary, if the macro isn't in the list then add it at the bottom. e.g. '%gns'
2) Define the handler method name, replacing 'null' with it. e.g. 'GreatNewStuff'
3) Add a suitable handler method that delegates to the same method in a MacroDataSource (provided by the passed IMacroContextProvider) to the code region marked "Contextual macro handlers". e.g.
Code: Select all
public static string GreatNewStuff(IMacroContextProvider mcp)
{ // %gns
return mcp.GetMacroDataSource().GreatNewStuff();
}
Code: Select all
public virtual string GreatNewStuff()
{ // %gns
throw new NotImplementedException();
}
Code: Select all
public override string GreatNewStuff()
{
return (parent.LastStuff != null) ? parent.LastStuff.GreatStuff : parent.NewStuff.GreatStuff;
}
Hope this make sense.