-
March 12th, 2024, 19:19 #1
Using Substitution Principles in FG
As someone who is not a developer (though I am an applications engineer and IT business analyst, among other things) but who pokes a little bit in the FG community developer role (I'm working on a ruleset).
Would you or someone be so kind to point me to more information on Liskov's substitution principle and how to extend CoreRPG so that a programming idiot like me might use it in my code? i.e. specific to FG and not really a general Google article, I will do that shortly but I'm quite confident it won't give me the practical knowledge to use it in FG.
edit: For reference, I found this article useful (other not so much) but am still not sure how it would be implemented in FG. Liskov Substitution Principle in C# | by Alexandre Malavasi | MediumLast edited by LordEntrails; March 12th, 2024 at 19:39.
Problems? See; How to Report Issues, Bugs & Problems
On Licensing & Distributing Community Content
Community Contributions: Gemstones, 5E Quick Ref Decal, Adventure Module Creation, Dungeon Trinkets, Balance Disturbed, Dungeon Room Descriptions
Note, I am not a SmiteWorks employee or representative, I'm just a user like you.
-
March 12th, 2024, 20:26 #2
- Join Date
- Sep 2020
- Location
- Ballston Lake, NY
- Posts
- 600
You've done coding in FGU already, so I'm not sure how to answer this in a way that's helpful and not condescending or too simple to help. But I'll try, and apologies in advance.
My rules of thumb when doing any kind of development against APIs, etc:
Start with adding on to or enhancing what it does.
Alter what it does only if necessary.
Avoid overriding / replacing that functionality. Changing behavior will end in tears.
The best way to see this in FGU is to look at an official ruleset compared to CoreRPG for how they did it.
In my case, I look at Savage Worlds. It extends a lot. Some D&D behavior is baked into CoreRPG, and SWADE must sometimes work around that, so there are examples of overrides.
The more common things between rule sets like NPCs and items have more overrides and extensions than rules-specific objects like character sheets.
Character sheets are a great example of subtyping.
I wrote an extension that adds a tab to the character sheet that tracks character advances (level-ups). It also allows players to plan out future advances.
This is for the Savage Worlds rule set.
There's already code that advances a character.
What I want to do is, when a character advances, look at the list of planned advances, then add that edge (feat) or skill or attribute increase to the character sheet.
I don't want to rewrite or replace or copy the current CharacterManager.onAdvance. It already does stuff I want on the character sheet.
Instead, I add a listener to my own onAdvance function to do my own thing in conjunction with the base functionality.
My method in no way changes behavior or functionality of the original CharacterManager.onAdvance.
My method doesn't duplicate or affect any work the original CharacterManager.onAdvance does. I only enhance what it does.
I wrote it in a way that it doesn't matter what the original function does, or the order the functions fire in.
Code:function onInit() .... -- add a listener to my function CharacterManager.onAdvance = onAdvance .... end function onAdvance(nodeChar, nGainedAdvances) .... code doing cool stuff here .... end
There are lectures on YouTube that may help.
This is where peer programming is really, really helpful.
-
March 12th, 2024, 20:52 #3
It's quite simple. The idea is that some part of CoreRPG calls a method, defined on a base template or window, via a reference to a window or control. However, since you have extended that control or window, what is actually called is your redefinition of that method, that adds functionality instead of replacing it.
For example, wherever you see a reference to "self" in CoreRPG, the framework developers are creating a hook for you to use Liskov's principle. That method call does not know, in principle, which piece of code will be called, as this will be determined by the derived/extended code. However, the new behaviour you define is seamlessly integrated in the base behaviour of the core classes. If well applied, this produces solid, extensible code that benefits from improvement to the generic components, and does not break when they change.
Practical case. In ct_entry.lua, line 30, the base code for the CoreRPG combat tracker entry calls self.linkPCFields(). My ruleset makes a near complete rehash of the combat tracker, and redefines linkPCFields() to add many more fields to the ct entry. This piece of code produced the one and only glitch in my ruleset after last week's mega-update, ignoring the value of the 3D token even if it was there. The reason? I had not called super.linkPCFields() in the extended method, which is something you should always do when using inheritance, and thus I was overwriting the method in CoreRPG instead of extending it. Adding a call to super (3 lines) did the magic and all the functionalities of the new 2.5D started working in my ruleset. And please note that I should have done it before, so the new functionalities shouldn't have broken anything, had I done things properly.
When things are done the right way in both the core and the derived rulesets, updates don't break functionalities, they enhance them.
-
March 13th, 2024, 15:16 #4
-
March 13th, 2024, 18:58 #5
- Join Date
- Sep 2020
- Location
- Ballston Lake, NY
- Posts
- 600
It doesn't replace. This adds an event handler to the stack. The base handler is still called.
I could add multiple event handlers, and all would be called.
I could add my own even handler and another extension calls its own. (I have two extensions that do that.)
I can't guarantee the call order, however.
There is a way to replace an event handler. I do that in one of my extensions because I want to ensure my code runs before the base code.
It's in my Setting Tweaks extension. When exporting Savage Worlds modules, I want to remove various nodes that are duplicated in db.xml and are then exported to client.xml.
Here's how I do that.
In my main lua script file I declare this exposed variable:
Code:local oldPerformExport = nil
Code:function onInit() -- replace my method with the current export method oldPerformExport = ExportManager.performExport ExportManager.performExport = performExport end
Code:-- main function that performs the export with my injected functionality local function performExport(wExport) prepareModuleExport(wExport) oldPerformExport(wExport) end
This way I control the call order. I still don't replace functionality. I'm adding functionality and controlling the order.
Though, if another extension adds a handler for this same event my extension won't know that and won't control that one.Last edited by Mike Serfass; March 13th, 2024 at 19:00.
Thread Information
Users Browsing this Thread
There are currently 1 users browsing this thread. (0 members and 1 guests)
Bookmarks