5E Product Walkthrough Playlist
  1. #1
    Minty23185Fresh's Avatar
    Join Date
    Dec 2015
    Location
    Goldstone, CA, USA
    Posts
    1,211
    Blog Entries
    29

    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.
    Last edited by Minty23185Fresh; September 6th, 2017 at 05:00. Reason: missed words, typos, added [Code] delimiters,importance,guidelines
    Current Projects:
    Always...
    Community Contributions:
    Extensions: Bardic Inspiration, Druid Wild Shapes, Local Dice Tower, Library Field Filters
    Tutorial Blog Series: "A Neophyte Tackles (coding) the FG Extension".

  2. #2
    Minty23185Fresh's Avatar
    Join Date
    Dec 2015
    Location
    Goldstone, CA, USA
    Posts
    1,211
    Blog Entries
    29

    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.
    Attached Files Attached Files
    Last edited by Minty23185Fresh; September 2nd, 2017 at 20:49. Reason: provide information in the reserved post, couple minor typos
    Current Projects:
    Always...
    Community Contributions:
    Extensions: Bardic Inspiration, Druid Wild Shapes, Local Dice Tower, Library Field Filters
    Tutorial Blog Series: "A Neophyte Tackles (coding) the FG Extension".

  3. #3
    Minty23185Fresh's Avatar
    Join Date
    Dec 2015
    Location
    Goldstone, CA, USA
    Posts
    1,211
    Blog Entries
    29

    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!
    Attached Files Attached Files
    Last edited by Minty23185Fresh; September 6th, 2017 at 03:57. Reason: Added content
    Current Projects:
    Always...
    Community Contributions:
    Extensions: Bardic Inspiration, Druid Wild Shapes, Local Dice Tower, Library Field Filters
    Tutorial Blog Series: "A Neophyte Tackles (coding) the FG Extension".

  4. #4

    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
    Attached Files Attached Files

  5. #5
    Minty23185Fresh's Avatar
    Join Date
    Dec 2015
    Location
    Goldstone, CA, USA
    Posts
    1,211
    Blog Entries
    29
    @bratch9 Interesting, I'm looking at this. I have both FGC and FGU licenses. The issue you're observing in FGU is also present in FGC.

  6. #6
    Minty23185Fresh's Avatar
    Join Date
    Dec 2015
    Location
    Goldstone, CA, USA
    Posts
    1,211
    Blog Entries
    29
    @bratch9 I have some information for you. I think I know what is causing this but I don't know why.
    I unpacked the CoreRPG and 5E rulesets, added some Debug statements and here is the console output
    Code:
    Runtime Notice: s'5E | scripts/manager_action_attack.lua | onInit() | status=' | s'arrived'
    Runtime Notice: s'5E | scripts/manager_action_damage.lua | onInit() | status=' | s'arrived'
    Runtime Notice: s'MECA_onInit() | status=' | s'arrived'
    Runtime Notice: s'MECD_onInit() | status=' | s'arrived'
    Runtime Notice: s'5E | scripts/manager_action_attack.lua | performRoll() | status=' | s'arrived'
    Runtime Notice: s'MECA_applyAttack() | status=' | s'arrived - extension A'
    Runtime Notice: s'MECA_applyAttack() | status=' | s'caling helper (saved by A)'
    Runtime Notice: s'5E | scripts/manager_action_attack.lua | applyAttack() | status=' | s'arrived'
    Runtime Notice: s'5E | scripts/manager_action_damage.lua | performRoll() | status=' | s'arrived'
    Runtime Notice: s'5E | scripts/manager_action_damage.lua | onInit() | status=' | s'arrived'
    When FG starts up the AttackManager and DamageManager are loaded and initialized (first two statements).
    Then MECA and MECD were loaded and initialized (second two statements).

    When I dragged the attack roll (die) from the attacker to the target in the Combat Manager the drag triggered the performRoll() in the AttackManager (statement 5).
    The overriding MECA extension script was triggered which called the original (helper) ruleset function (statements 6 through 8).

    When I dragged the damage roll (die) from the attacker to the target the drag triggered a performRoll() in the DamageManager, (statement 9) similar to what happened in the AttackManager for the attack roll (statement 5).
    But then, for some reason the DamageManager was reinitialized (statement 10).

    I'm not sure why the Damage Manager was reinitialized, or if it is actually a second instance, encapsulated under some event handling code in the Combat Tracker's XML <script>.

    I find it interesting that the DamageManager is reinitialized. It might be a coding error in the ruleset, I don't know. I might explore this further, but I really have other things to do.

    So why does your MECC extension work? This is even more perplexing. A second instance or reinitializing should not be affected by the reload in your extension.xml. Further, because the reload occurs after the initialization of MECC (and therefore the replacement of the ruleset function) the replacing function of MECC should not be available.

    Here is the console output using MECC instead of MECD. The order that things are occurring per the console output makes me believe there's some asynchronicity going on here. It would really be great if some from Smiteworks would weigh in here, but they have the FGU thing going on.
    Code:
    Runtime Notice: s'5E | scripts/manager_action_attack.lua | onInit() | status=' | s'arrived'
    Runtime Notice: s'MECA_onInit() | status=' | s'arrived'
    Runtime Notice: s'MECC_onInit() | status=' | s'arrived'
    Runtime Notice: s'5E | scripts/manager_action_damage.lua | onInit() | status=' | s'arrived'
    Runtime Notice: s'5E | scripts/manager_action_attack.lua | performRoll() | status=' | s'arrived'
    Runtime Notice: s'MECA_applyAttack() | status=' | s'arrived - extension A'
    Runtime Notice: s'MECA_applyAttack() | status=' | s'caling helper (saved by A)'
    Runtime Notice: s'5E | scripts/manager_action_attack.lua | applyAttack() | status=' | s'arrived'
    Runtime Notice: s'5E | scripts/manager_action_damage.lua | performRoll() | status=' | s'arrived'
    Runtime Notice: s'MECC_modDamage() | status=' | s'arrived - extension C'
    Runtime Notice: s'MECC_modDamage() | status=' | s'some new functionality - extension C'
    Runtime Notice: s'MECC_modDamage() | status=' | s'calling helper (saved by C)'
    Runtime Notice: s'5E | scripts/manager_action_damage.lua | onInit() | status=' | s'arrived'
    Runtime Notice: s'MECC_modDamage() | status=' | s'more new functionality - extension C'
    Database Notice: Campaign saved.
    Database Notice: Campaign saved.
    One last thing bratch9. Thank you for even looking at this thread. I posted it a long time ago, when I was first getting a handle on extension construction. I kind of wrote it for myself, but also to document and share what I had learned. Interest was pretty minimal. I thought this had been lost to obscurity.

  7. #7
    Thanks for taking a look at this, i found it very odd and as new to making changes had a look around and thought your method interesting..

    Its odd if you take out the reference in 'c' then neither c or d are run, both initialize.. ( as if ActionDamage is initialized later and so override the function overrides.. ) , and if you add the line into 'd' then both initialized and worked ok. If you move the line up the xml to above the mecc script line that it also does not work... So its clearly causing some load order issue. I just assumed extensions were loaded 'after' the rulesets, so the base scripts would be setup and ready for the extension override...

    Your method is much better than just replacing the full file, but I guess it depends what you are trying to do. If you are just adding an extra entry to a character you would use the xml and 'join' and add some new bits of script.

    To be fair I assumed others were doing this, I read a couple of extensions that did. But a lot are now encrypted on the dmguild so can not be looked at to see what they are doing etc..

    I'm looking at 2 different issues, to play with, one is the incorrect dice grouping on a critical roll caused by effects/critical extra dice. The dice get duplicated, but the clause are added to the end, so a weapon with 2 lines of damage say 1d12 slashing, and 2d6 piercing.. on critical ends up with dice list of 1d12,1d12,2d6,2d6, but will read off the dice as 1d12,2d6,1d12,2d6 as the critical clauses are added at the end. Causing a sum issue if you hit something with 'slashing resist' etc, as it will consider the second 1d12 and one of the 2d6 to be part of the piercing non critical total 2d6... I fix this by rebuilding the dice roll list from the clauses as a post fix item for that function..

    The other is a change of effect type system, that allows effects onto the damage line of weapons and onto the character sheet weapon attack section. So that a weapon could have a damage line '1d6 slashing, 1d6 radiant TYPE(undead)', with an assumed IFT: type effect line, but I dont apply it to the combat tracker effects list. Its just processed in the applyDamage section automatically, making it so much easier.. ( I know something like advanced_effects, can do this sort of thing, but it forces it into the combat tracker line as a custom effect and you just end up with so many effects in the list to manage etc... )

    Been new I just had a play with the code and had a read around the forum, and liked your idea ( to be fair I was not looking at dates of posts... ). As I developed my code I just had the full source for the ActionDamage and just coded it direct in an extracted 5e folder, then moved it to an extension file on its own and then split it up as per your suggestion method to try and make it more ruleset code update safe. At which point it failed to work, and hence why I made the cut down zip provided as I found it odd and thought you might have seen this before and just said 'do this bit different and it will work' as I assumed it was some noob thing I had missed/done wrong.

    While I'm an experienced coder, mainly c++ graphics stuff, Lua is new to me and obviously FGU extensions. ( Started playing on FGU due to lockdown, normally our group does face to face table top... ) As i spotted a couple issues, thought I'd have a play with the code just as a bit of fun between real paid code work !!

    I mainly had issues with the Lua side, and no real debugger, and the fact /reload is not working on FGU slows this even more. ( I'm used to good tools/debuggers... )

    But was not expecting some 'odd' load sequence type issues to cause me even more problems !!

    I'm happy with my 'fix', but probably should be looked into further.. I've also tried the 'loadorder' tag in the xml and this does not seem to solve the issue either... it is very odd. ( As you say, maybe a race condition, but not sure on this, as its consistently broke the same way between runs and rolls.. )

    If you do get change to look/think further on this, then do update the thread. If you think its worth me putting the issue into the 5E bug thread forum section, then I'm happy to do that.

    Thanks for your response and looking it to it a bit..
    -pete

  8. #8
    this also talks about the same system, but uses varargs to pass values and results about.... but basically the same style. ( from a different game.. )

    https://wowwiki.fandom.com/wiki/Hooking_functions

  9. #9
    This is a really interesting thread. I appreciate the discussion!

  10. #10
    So I had some time to take a look at the issue of why some functions work ( your example ), and some functions do not work ( my example. )

    By looking at the code I can see that replacing 'ActionAttack.applyAttack' in your example works as a lua level function.

    This is the same for another example that works, 'ActionDamage.applyDamage' again its a basic lua level function.

    My issue function 'ActionDamage.modDamage', does not work, and this caused an issue for me and my extension code.

    But I have figured out why and how to resolve it.

    If you look at the 'manager_action_damage.lua' source in the rule set, you will notice one small difference between 'ActionDamage.applyDamage' and 'ActionDamage.modDamage', in the onInit function the use of

    'ActionsManager.registerModHandler("damage", modDamage);'

    So when the original 'ActionDamage.modDamage' function is registered its function pointer/address is cached in the 'ActionsManager' for "damage" callbacks.

    When you override the function at a lua level this callback is not updated in the 'ActionsManager', and on an action for "damage" the original 'ActionDamage.modDamage' is called and not the extension version.

    The solution is to re-register the call back in the extension, like this..


    function onInit()
    ActionDamage.ext_modDamage = ActionDamage.modDamage;
    ActionDamage.modDamage = extension_modDamage;

    ActionsManager.registerModHandler("damage", extension_modDamage);
    end


    In this case due to the code in the 'ActionsManager.registerModHandler' only one "damage" callback can be registered into its table entry for "damage", and so its safe to update this a second time by the extension.

    But it could cause issues for more complex registered lua functions that you might want to override.

    Something to watch out for...

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
FG Spreadshirt Swag

Log in

Log in