Dungeons & Dragons 2024 Core Rulebooks Pre-Order
View RSS Feed


A Neophyte Tackles the FG Extension - Reducing Copied Ruleset Script

Rate this Entry
This time Iíll look at reducing the size of the manager_effect.lua file in my extension. Recall from last time, I copied that lua file, in its entirety, to my extension then added a single line of code to change the default behavior of an effectís initial visibility. Having nearly 1500 lines of duplicate code troubles me.

Detour: Why is duplicating code bad practice? For one it bloats the size of your program, and for certain languages that can cause decreased execution speed. But, in my mind, even worse, it creates a maintenance nightmare. If one simply copies code, later on if new functionality is being added, one must remember where those copies are in order to change them. If one is new to the code, one doesnít even know the copies exist. An example: say youíve written a program that manages elevator buttons. The building has five floors and you just copy the code five times, once for each button. Now suppose your customer wants lit buttons. Thatís five places in code you have to modify. Later on you get a contract to use your program in a building with three elevators servicing 350 floors. Thatís 1050 copies of that code! What if, your new customer later wants flashing button lights?

In many of my prior posts Iíve typically only reported processes that produced positive results. This time Iíll include those that yield negative or failed results. Iím doing this to illustrate my tactics as I try to solve a problem.

Each time, after I perform code modifications Iíll do the following: Iíll save the edited files, reload the ruleset in FG, bring up the Combat Tracker, remove all existing effects, bring up the Effects Manager and paralyze the Orc. Iíll abbreviate this as: ďsave Ö and paralyzeĒ.

There are nearly 40 functions in the copied manager_effect.lua file. Previously, I added one line of code to one of those functions. All the other functions in the extension are identical to their corresponding function in the 5E ruleset. Recall how an extension works, code in the extension overrides code in the ruleset. If the ruleset code is still accessible, the extension might be able to call it, thereby eliminating the need for the duplicate code.

Theory: The easiest way to eliminate duplicate code is to simply delete all functions in the extension that are identical to those in the ruleset.

Since I know how to invoke the addEffect( ) function I can use it as a test case. Iíll delete it from manager_effect.lua in my extension. If this is a workable solution the code in the 5E ruleset will execute in place of the missing extension function. Iíll place a Debug.console( ) call in the 5Eís addEffect( ) function, delete addEffect( ) from the extensionĎs lua file and ďsave Ö and paralyzeĒ. The console reports an error ĎÖaddEffect a nil valueí. I believe this somewhat cryptic message means there is no addEffect( ) function to execute. Fail! I undo my edits and resave the file.

Theory: Iíll keep the function definitions in my extension lua file but forward execution to the ruleset. To do this Iíll replace the guts of the function with a call to the 5E function.

To force access to the 5E functions Iíll add a <script> file reference in my extension.xml file, that ďcreatesď a ď5E Effects Managerď. Notice that the reference to the 5E manager precedes the other reference. Iíve done this because I donít want any initialization done by my extension to be overridden or changed by the 5E managerís initialization.
    <!-- As of FG v3.2.0 the following should no longer be necessary -->
    <script name="EffectManager5E" file="../../rulesets/5E/scripts/manager_effect.lua" />
    <script name="EffectManager" file="./scripts/manager_effect.lua" />
As the test case, Iíll add a line of code to the extensionís addEffect( ) function that utilizes the new ď5E Effect ManagerĒ:
function addEffect(sUser, sIdentity, nodeCT, rNewEffect, bShowMsg, sEffectTargetNode)
  Debug.console("TWD|manager_effect.lua|addEffect()|status", "EXT Adding");
  rNewEffect.nGMOnly = 1;
  return EffectManager5E.addEffect(sUser, sIdentity, nodeCT, rNewEffect, bShowMsg, sEffectTargetNode);
  if not nodeCT or not rNewEffect or not rNewEffect.sName then
  . . .
I saved the files, and during ruleset reload an error was thrown to the console: ď..end expected..Ē I believe the message means that the lua interpreter recognized that my ďreturnĒ statement prevents any of the rest of the code in the addEffect( ) function from executing. My quick hack failed! So Iíll comment out the rest of the function all the way down to, but not including, end. After I ďsave Ö and paralyzeĒ, it works!

It appears as though I have found a solution. I have almost 40 ďforwardingĒ functions to implement, but it will reduce the size of my lua file from ~1500 lines to ~160 and remove all duplicated code. I am going to do this a few functions at a time, just in case there are unforeseen affects.

I am going to leave the onInit( ) function alone because I donít want the extension to initialize then call 5E to initialize over the top of it. After implementing the forwarding calls in five functions I ďsave Ö and paralyzeĒ and it fails - the initial visibility comes up ďVSBLĒ not ďGMĒ! I restore the functions with undos.

Okay then, one function at a time . The first one, handleApplyEffect( ) causes a fail! An undo and a look at the functionís codeÖ The last line is an addEffect( ) call! Here is my guess as to what is happening. The extensionís handleApplyEffect( ) is called, the execution is forwarded to the 5E ruleset function then the addEffect( ) in the ruleset is called instead of the one in my extension, the default visibility isnít changed. Iíll verify this by putting Debug.console( ) calls in the onInit( ), handleApplyEffect( ) and addEffect( ) functions in both the extension and the ruleset, plus put the forwarding call back in.

Runtime Notice: Reloading ruleset
Runtime Notice: s'TWD|manager_effect.lua|onInit()|status' | s'5E Initializing'
Runtime Notice: s'TWD|manager_effect.lua|onInit()|status' | s'EXT Initializing'
Runtime Notice: s'TWD|manager_effect.lua|handleApplyEffect()|statu s' | s'EXT Handling'
Runtime Notice: s'TWD|manager_effect.lua|handleApplyEffect()|statu s' | s'5E Handling'
Runtime Notice: s'TWD|manager_effect.lua|addEffect()|status' | s'5E Adding'
Sure enough, thatís it. Note the initializers occur in the proper order, 5E first then the extension (and they occur as FG is loading up). When I paralyze, the extension forwards execution to 5E and then 5E calls its own addEffect( ) not the one in the extension!

The handleApplyEffect( ) cannot forward execution to the ruleset. It has to keep the duplicated code. A quick search of the file for addEffect( shows that there are no more of those calls to worry about.

After implementing the forwarding calls, a few at a time, in the remaining functions, a test of the extension by paralyzing the Orc yields the desired default visibility behavior.

I have attached my manager_effect.lua (as a .txt) file for your perusal.

Some explanation about the methodology and the forwarding call statements: First: Did I accomplish what I set out to do? Most assuredly. I eliminated almost 1000 lines of duplicate code! Second: By eliminating the duplicated code using this methodology, should the 5E rulesetís manager_effect.lua file be modified in a later version of FG it is very unlikely that I will have to ďfixĒ my extension to be compatible with the new version. Three exceptions are: (a) if more arguments are added to any of the functions, (b) if new functions are added, or (c) if an addEffect( ) call is added anywhere in the 5E lua file. Third: Lua is very forgiving, a reduction of arguments wonít matter. Also, if one paid close attention to the code that was being replaced by the forwarding function statements, one would have noted that many of the functions did not return values to their callers. Luaís forgiveness ignores returned values if theyíre not used and returns nil if thereís nothing to return. Iíve used consistency in coding the forwarding statements for ease of maintenance.

I havenít noticed anyone else using this methodology in other extensions, but then again I havenít looked at every extension out there. I would really be appreciative of comments by other programmers and especially by the developers at Smiteworks, as to the general reusability of this methodology in FG extensions.

I canít wait to play test my extension!

Until next time keep on role playing.

Submit "A Neophyte Tackles the FG Extension - Reducing Copied Ruleset Script" to Digg Submit "A Neophyte Tackles the FG Extension - Reducing Copied Ruleset Script" to del.icio.us Submit "A Neophyte Tackles the FG Extension - Reducing Copied Ruleset Script" to Google Submit "A Neophyte Tackles the FG Extension - Reducing Copied Ruleset Script" to Facebook Submit "A Neophyte Tackles the FG Extension - Reducing Copied Ruleset Script" to Twitter

Updated June 16th, 2016 at 00:36 by Minty23185Fresh



  1. Minty23185Fresh's Avatar
    AARRGH!!!! A problem cropped up!

    I was loading up FG with my extension to play test it later today and received multiple error messages in the console. At the moment it appears to me as though this line of code in the extension.xml file is not being resolved:
    <script name="EffectManager5E" file="../../rulesets/5E/scripts/manager_effect.lua" />
    My initial supposition is: in the development environment the 5E ruleset is unpacked. In my game play environment the 5E ruleset exists as a .pak file. Lua is unable to resolve the path into a .pak file.

    I'm investigating... (a solution exists, see comments below).
    Updated June 27th, 2016 at 05:09 by Minty23185Fresh
  2. damned's Avatar
    The duplicated code is read twice on load time but only the second instance is kept and only the second instance is executed.
    The big challenge with duplicating so much code is you are more likely to have to update the extension more often as there is more code that could be changed in future versions.
  3. Minty23185Fresh's Avatar
    It took a few days, but I did work out a solution to the issue I found. Please see the revisitation of this post, here.

Log in

Log in