How to Avoid or Mitigate Extension Conflicts (or Collisions)
I am proposing a methodology that should help mitigate or eliminate extension conflicts or collisions.
Oftentimes, when a user decides to employ more than one extension conflicts might occur if the extensions deal with similar subject matter. In my case it's usually Effect handling or augmentation.
Typically when I am developing and implementing an extension I replace ruleset functionality with my extension functionality by doing something like this in my extension's onInit():
Code:
function onInit()
-- replace the ruleset functionality with my extension functionality
rulesetManager.rulesetFunction = myFunction;
end
Instead of doing it this way, I propose a methodology, I'll call it encapsulation or isolation, where you save the ruleset function as a "helper function" and then replace the ruleset function with your own. Then within your function you add the functionality your extension needs and call the original ruleset function via the saved helper. The onInit() would look like this:
Code:
function onInit()
-- save the original ruleset functionality in a helper function
rulesetManager.helperFunction = rulesetManager.rulesetFunction;
-- replace the ruleset functionality
rulesetManager.rulesetFunction = myFunction;
end
And my extension's replacing function would look this this:
Code:
function myFunction(arg1, arg2, arg3)
-- my extension functionality
statement one;
statement two;
-- call the helper
returnVal = rulesetManager.helperFunction(arg1, arg2, arg3);
-- more extension functionality could be here if needed
return returnVal;
end
Importance of this methodology:
(1) By employing this technique if someone else has an extension that "reprograms" a function that your extension needs to "reprogram", this encapsulation prevents collisions or conflicts because each developer's code is isolated from the other's.
(2) I suspect, but have yet to confirm, that by compartmentalizing your code in this manner, extension maintenance due to new releases of Fantasy Grounds may be easier or possibly unnecessary.
(3) There is much less duplicated ruleset code which, in my opinion makes the intent of the extension's code easier to read and understand.
Implementation Guidelines (I'll add to this list as I discover more):
(1) Typically, don't mess with the arguments, changing their content will affect downstream functionality. If the argument is an object, adding properties or functions to the object is okay if it needs to be consumed downstream.
(2) Similarly with the return value. Any values returned by the helper (the original function) should not be messed with. Again, adding functionality to a return value that is an object should be okay.
(3) The helper function must be "saved" in the manager that contained the original, that way if the functionality within it calls any other functions in the manager (.lua file) the links aren't broken. If you were to create the helper within your extension you could get script errors.
In the next post to this thread I'll provide a zip file with a couple of example extensions that use this methodology.
1 Attachment(s)
A Generic Example of Using this Mitigation Technique
I have attached a zip file with two extensions in it. Both extensions are in folder form instead of .ext file form. Install them as you would any other extension, by placing them in the extensions subfolder.
The two extensions are nearly identical. Extension A (ExtA) will load first followed by Extension B (ExtB). This could be changed if desired using the "loadorder" option in their extension.xml files.
ExtA saves the ruleset's ActionAttack.applyAttack() in a helper function in the ActionAttack manager and then replaces the original function with its own.
ExtB then saves the current ActionAttack.applyAttack() function as another, differently named, helper function, within the ActionAttack manager. Because ExtA loaded first, it replaced applyAttack() with its replacement function, so ExtB actually saved ExtA's replacement as ExtB's helper. ExtB then replaces the current applyAttack() function (again ExtA's replacement) with its replacement.
Note how successive loads of extensions preserve prior loaded functionality, isolating the functionality by encapsulating it!!
This example can be run if desired. It will post messages to the debug console. To see all the messages you should add the /console command line argument.
In case you haven't done this before, right click the Fantasy Ground shortcut on your desktop and select Properties. Then in the Target field add "/console" to end of what is already there. It should look sometime like this when you're done (don't change anything else just add to it):
"C:\Program Files (x86)\Fantasy Grounds\FantasyGrounds.exe" /console
Now start up Fantasy Grounds with your modified shortcut. The Console should immediately pop up. Create a new campaign, or load an existing one. Select the Mitigating Extension Conflicts Extensions (both Extension A and Extension B) and Start.
Put a couple PCs or NPCs (say ogres or goblins) in the Combat Tracker then attack one with the other. This will force a call to ActionAttack.applyAttack(). The console should look like this:
Code:
Runtime Notice: Host session started
Runtime Notice: s'MECA_onInit() | status=' | s'arrived'
Runtime Notice: s'MECB_onInit() | status=' | s'arrived'
Runtime Notice: s'MECB_applyAttack() | status=' | s'arrived - extension B'
Runtime Notice: s'MECB_applyAttack() | status=' | s'some new functionality - extension B'
Runtime Notice: s'MECA_applyAttack() | status=' | s'calling helper (saved by B)'
Runtime Notice: s'MECA_applyAttack() | status=' | s'arrived - extension A'
Runtime Notice: s'MECA_applyAttack() | status=' | s'some new functionality - extension A'
Runtime Notice: s'MECA_applyAttack() | status=' | s'caling helper (saved by A)'
Runtime Notice: s'MECA_applyAttack() | status=' | s'more new functionality - extension A'
Runtime Notice: s'MECB_applyAttack() | status=' | s'more new functionality - extension B'
Note how Extension A loaded followed by Extension B.
When the attack ensued, ActionAttack.applyAction() was called. Its "contents" were those of Extension B. The replacing function (of ExtB) does some stuff, then calls the helper, which is actually Extension A's replacing function. Extension A now does some stuff, then calls its helper function which is the original ruleset code that it saved. After the ruleset code executes, it returns execution to ExtA which can do some more stuff, and subsequently returns execution to Extension B which can also do some more stuff.
1 Attachment(s)
Real World Example of using this Extension Conflict Mitigation Methodology
I recently posted an extension to the forums (EoSSE).
I have reworked EoSSE to employ the mitigation technique described in this thread. As it turns out it was a lot easier to do that I had thought it might be.
A quick, simplified, explanation on how EoSSE accomplishes its task of accurately applying effects for a successful save when the save will result in half damage (and no effect). When the player clicks a spell's Cast or Save button in the Combat Tracker, Fantasy Grounds' code saves a "half damage" flag in the host or DM's instance of FG for each target that successfully saved. When the player clicks the Dmg button, FG looks for the flag, if it exists for a particular target, only half damage is applied. The EoSSE Extension's code does the same thing for effects. When the "half damage" flag is saved, EoSSE saves a "no effect" flag. When the Effect button is clicked by the player, EoSSE looks for the flag for each target, if the target saved, there will be a flag and no effect is applied.
I've attached a zip file with two versions of EoSSE in it. v0.0.0 does not adhere to the mitigation methodology described in this thread but v0.1.0 does.
As mentioned, it was really quite easy to do for many of the routines in EoSSE. It usually just required a simple "factoring" out of the extension functionality into a "replacing" function. A perfect example is the EoSSE_decodeEffect() function in the scripts\eosse_manager_action_effect.lua files. Compare that function in both EoSSE versions. It becomes obvious just how simple it was.
There was one instance of my original EoSSE code where this simple factoring of functionality was not possible. It happened with a couple OOB messaging functions. OOB messaging always employs a pair functions: A "notify" function, typically running in the client (or player's) instance of FG which sends the message. And a "handler" function running in the host instance which receives and acts on the message. If you examine the EoSSE_notifyApplyEffect() and EoSSE_handleApplyEffect() functions in v0.0.0 you'll notice that I added (in notify) and intercepted (in handle) a spell label flag. Unfortunately the notify function creates the OOB message then sends it. The handler intercepts and decodes the message then passes data on to other routines. There is no way to inject the message or receive and act upon the message outside those routines and still maintain the mitigation methodology. What I did instead is create additional OOB messaging that just handles the flag. EoSSE launches its own notify (via v0.1.0 EoSSE_setEffectSpell() and EoSSE_applyEffectSpell() ) and then handles the tiny OOB message (EoSSE_handleApplyEffectSpell() ) in the host's instance. All to save the flag, as it awaits the ruleset's messaging, which uses the flag to apply the effect or to ignore the effect depending on whether or not the target's saving throw was successful.
Aargh!! While writing this post I realized that this extension might act sporadically. Here's why. As described above two OOB messages are sent from the client to the host. A message with just the spell name flag and another with all the other data. As long as the spell name message is received and acted upon before the other data message is received then things are fine. However if the other data gets there first, the extension will fail to operate properly.
NOTE: This is not a failure of this methodology, it's a failure on my part to implement it correctly.
Our gaming group has not used this extension enough to really put it to test. So far it seems to be working fine. But there is the possibility that I might be revisiting v0.1.0!
1 Attachment(s)
ActionDamage.modDamage not working with this method.
Hi,
I have an issue with this method that just seems very odd.... and not sure why..
I've attached a zip file with 2 extra modified version of your source, called 'C' and 'D' !!
In these I have changed the 'ActionAttack.applyAttack' usage into 'ActionDamage.modDamage'. ( etc.. )
While your 'ActionAttack.applyAttack' work on a targeted npc on an attack as expected, the 'ActionDamage.modDamage' ones do not.
The odd thing is, that the only way I could get these to work was to add a script reference in the 'extension.xml' to the 'ActionDamage' ruleset file, you do not need the actual source code just the line in the xml.
The zip file, has this added for version 'C' and if you roll damage and check console it will show it as been used/called, if you add it to 'D' you can see both been called, remove it from both and nothing gets called.
Only changes to the 'extension.xml', and no actual original source from the ruleset, makes it work or not.
Its such an odd situation, I thought I'd ask if you have seen this sort of issue before and have any suggestions ?
Note, this is FGU, I dont have classic to test it on, so you have to close/edit/restart FGU between changes...
[ Its as if 'ActionDamage' is loaded before the extensions some how, and having the xml reference in the extension causes a second load of it maybe ? ]
Thanks, Pete