-
December 14th, 2016, 17:01 #1
Coding dice rolls (actions) in CoreRPG
I seem to keep discussing how to roll dice as part of the CoreRPG action manager flow, so it's high time I actually spent the time and put a thread together regarding the subject.
This thread aims to provide details of the CoreRPG actions manager package which is used as the basis for the majority of dice rolling (actions) in CoreRPG and ruleset layered on top of CoreRPG (5E, Pathfinder, 3.5E, 4E, Call of Cthulhu, Castles & Crusades, etc., etc.).
Before you start! Actions can get pretty complex fast. They can use a lot of advanced techniques/procedures (targeting, effects, etc.) and you can soon lose yourself in the complex nested code and give up. Your first custom action doesn't have to be complex (see the basic example in post #4). Start off without targeting, without effects, without success/failure reporting. Then, once you have the base action sorted out, you can slowly add these in. Even if you're an experienced programmer, you can soon get lost in the specifics of FG and it's layered code. Take it in small steps...
Action - this is a term FG uses to describe a specific piece of the RPG system's mechanics that usually requires a dice roll. Common actions are: dice, attack, damage, save, init, skill, etc. and depend on the RPG system in question.
As Fantasy Grounds dice rolling is asynchronous - i.e. FG code doesn't sit waiting for the dice to land before continuing (this would be disastrous to performance), there has to be a number of steps to putting the action together and then processing the result of the action once the dice roll land. These are accomplished in the specific ruleset through an Action Manager.
In it's simplest form, an action could just be a simple dice roll - no recipient (target) of the dice roll, no intended success/fail threshold, etc.. Just roll the dice, add modifiers if necessary, and then display the result. Such an action would have the action type set to "dice".
In a more complex form, say a d20 based attack, there would be one or more targets of the attack, there could be many modifiers (including effects both on the source of the action and the target/s), there would be a threshold the roll needs to meet to succeed (the AC of the target/s), and there could be additional processing needed for certain rolls (critical or fumble). All of this is handled by the ActionAttack package (and the relevant built-in helper functions).
The following posts won't go into the full detail of such a complex action as a d20 RPG attack, but will break down a slightly simpler action to illustrate how the process works. Examples of how to create your own actions will follow.
Note: When you create a new action type, you must let FG know that a new action is present. FG will not process actions with a type it is not aware of. CoreRPG comes with three default action types: dice, table and effect which are stored in the actions table in the GameSystem package (scripts\manager_gamesystem.lua):
Code:-- Ruleset action types actions = { ["dice"] = { bUseModStack = "true" }, ["table"] = { }, ["effect"] = { sIcon = "action_effect", sTargeting = "all" }, };
Actions can also be added (handy for extensions) using the GameSystem.actions["XXX_actionname_XXX"] = { XXX_parameters_XXX }; command in the action manager onInit function. See post #4 for an example.
Targetable actions (drag/drop to Combat Tracker entry) In order to make an action drag/droppable it needs to be added to the GameSystem.targetactions LUA table.Last edited by Trenloe; February 1st, 2021 at 16:48.
Private Messages: My inbox is forever filling up with PMs. Please don't send me PMs unless they are actually private/personal messages. General FG questions should be asked in the forums - don't be afraid, the FG community don't bite and you're giving everyone the chance to respond and learn!
-
December 14th, 2016, 17:02 #2
Action Flow
This example looks at the D&D 3.5E ruleset ActionAbility action handler, which is in the scripts\manager_action_ability.lua file in the 3.5E ruleset. And is initialised in the ruleset base.xml: <script name="ActionAbility" file="scripts/manager_action_ability.lua" /> Thus, any function within this script can be accessed from outside the package with ActionAbility.<function name> See "Script Package" here: https://www.fantasygrounds.com/modguide/scripting.xcp
This is a good example as it contains some complexity, but not too much, which gives a good idea of how an action works and the possibilities. Later in this thread a very simple action will be presented to give an idea of what the action system in it's most base form can be. The "ability" action used in this example is basically a d20 roll modified by the relevant ability bonus - with the ability being strength, dexterity, constitution, intelligence, wisdom or charisma.
All actions should follow the same flow. This is briefly outlined in the header of scripts\manager_actions.lua in the CoreRPG ruleset:
Code:-- ACTION FLOW -- -- 1. INITIATE ACTION (DRAG OR DOUBLE-CLICK) -- 2. DETERMINE TARGETS (DROP OR TARGETING SUBSYSTEM) -- 3. APPLY MODIFIERS -- 4. PERFORM ROLLS (IF ANY) -- 5. RESOLVE ACTION -- ROLL -- .sType -- .sDesc -- .aDice -- .nMod -- (Any other fields added as string -> string map, if possible)
Step 1 usually consists of putting the action details together and then initiating the action with ActionsManager.performAction. In this example, it all starts with an ActionAbility.performRoll function call.
Step 2 will be done automatically by the underlying FG code which checks for any targets for the roll - this is targeting in the combat tracker and has nothing to do with any "target" number or ability parameters in the FG control XML or code (more on XML <target> parameters later). In the case of an ability check, there won't be any targets, or if there are (the roller has something targeted in the combat tracker), it won't be used.
Step 3 is where we apply modifiers other than the base ability bonus - this is done with the action modifier Handler. So we go back to manager_action_ability.lua and we see that the onInit() function has a mod handler registered with ActionsManager.registerModHandler("ability", modRoll); So, the modRoll function is our modifier handler. Note: there is also another handler registered here - the ResultHandler of onRoll (more on that later - this is step 5 in our action flow.
Back to the modifier handler - this is where FG sees if there are any additional modifiers to apply to the roll. We've already added the base ability modifier to the roll in step 1 (stored in rRoll.nMod). The modRoll function is more to handle effects that could modify this type of roll. In this case there are a few: ABIL effects, Conditions, STAT effects, negative levels, etc..
Note that the way modRoll gets which ability to use appears somewhat strange at first glance. It parses it out of the roll description using local sAbility = string.match(rRoll.sDesc, "%[ABILITY%] (%w+) check"); It isn't passed as a parameter in rRoll. This is pretty standard practice in FG rulesets - it allows data to be passed asynchronously and also allows the roll to keep it's data well after the roll has finished (so that the result can be drag/dropped from the chat window, for example). The data is usually identified by a keyword in square brackets [] - you've probably seen this a few times in FG roll results in the chat window.
This data passing in the description is setup in the getRoll function, in this case:
Code:rRoll.sDesc = "[ABILITY]"; rRoll.sDesc = rRoll.sDesc .. " " .. StringManager.capitalize(sAbilityStat); rRoll.sDesc = rRoll.sDesc .. " check";
Step 4 After the modRoll handler has calculated the final modifications to the roll, the actions manager performs the actual roll. It throws rRoll.aDice (set in the getRoll function) and once the 3D dice land the final final step occurs...
Step 5 The dice land and FG raises a result event and the result handler registered in the manager_action_ability.lua onInit function is executed. This was registered with ActionsManager.registerResultHandler("ability", onRoll); so the onRoll function executes.
This is a fairly straightforward script - it basically gets the result of the roll and works out what the final description displayed in the chat window should be. At this point (the beginning of the onRoll function) no data has been outputted to the chat window, the dice have just landed...
Code:function onRoll(rSource, rTarget, rRoll) local rMessage = ActionsManager.createActionMessage(rSource, rRoll); if rRoll.nTarget then local nTotal = ActionsManager.total(rRoll); local nTargetDC = tonumber(rRoll.nTarget) or 0; rMessage.text = rMessage.text .. " (vs. DC " .. nTargetDC .. ")"; if nTotal >= nTargetDC then rMessage.text = rMessage.text .. " [SUCCESS]"; else rMessage.text = rMessage.text .. " [FAILURE]"; end end Comm.deliverChatMessage(rMessage); end
Then the message description has additional data added to it (the description from step 1 comes through and is used as the basis of the description here). So, in this case, we already have a description of "[ABILITY] XXXX check", say "[ABILITY] strength check" for example - see the screenshot in post #3.
Then, if we have a target DC (stored in rRoll.nTarget) we check that against the result of the roll nTotal (returned from ActionsManager.total(rRoll)) and add " (vs. DC XX" (where XX is the target DC) and add [SUCCESS] or [FAILURE] to the description. Then the message is sent to the chat window. This includes the dice roll result, which is stored in rMessage.dice in the message data structure.
That's it!
This might seem very complex, but all you have to do is take manager_action_ability.lua as a template, or a similar action manager in your chosen ruleset, or a simple template (available in post 4 of this thread):
- use it to create the manager for your new action
- modify getRoll to setup the roll - set the type, the dice, add the base modifier to rRoll.nMod and include any data to be passed in square brackets - this might be your best way of passing the target number needed.
- use a modifier handler if needed - to handle effects, etc.. I don't know if you'll need this for the kingdom rolls.
- Modify the result handler onRoll to output the appropriate message to the chat window.
In it's most basic form, the action manager might be four functions: onInit (sets up the result handler and possibly the action name registration), getRoll (sets up the initial info needed to start the roll for this action), performRoll (used to start the whole process - usually called from outside the package - see post #3 below) and onRoll (handles the result of the action once the dice have landed). onInit and performRoll are pretty simple, the main coding will be in getRoll and onRoll.Last edited by Trenloe; December 14th, 2016 at 19:10.
Private Messages: My inbox is forever filling up with PMs. Please don't send me PMs unless they are actually private/personal messages. General FG questions should be asked in the forums - don't be afraid, the FG community don't bite and you're giving everyone the chance to respond and learn!
-
December 14th, 2016, 17:02 #3
Initiating an action
Usually there is a function within the specific action manager that starts the whole action process. For our example (3.5E ability check) this is performRoll in manager_action_ability.lua. We see: function performRoll(draginfo, rActor, sAbilityStat, nTargetDC, bSecretRoll)
The minimum we need here is draginfo (the base action information created by FG when a drag action starts - needed for the whole drag/drop process to function correctly), rActor (a LUA record containing details of the PC or NPC that started the action - they are known as the actor and this record can be obtained using the ActorManager.getActor helper function) and sAbilityStat which needs to be the ability to roll the check against - "strength", "dexterity", etc.. There's a couple of arguments (nTargetDC and bSecretRoll) that aren't passed in this case.
So, from this, we could initiate an strength ability check with: ActionAbility.performRoll(draginfo, rActor, "strength"); This would be initiated from a control on a character/NPC sheet or the Party Sheet, so that the Actor record is available, via the control's onDoubleClick or onDragStart events. If double-click is used draginfo would be blank: ActionAbility.performRoll(, rActor, "strength");
Looking at the ActionAbility.performRoll function:
Code:function performRoll(draginfo, rActor, sAbilityStat, nTargetDC, bSecretRoll) local rRoll = getRoll(rActor, sAbilityStat, nTargetDC, bSecretRoll); ActionsManager.performAction(draginfo, rActor, rRoll); end
Code:function getRoll(rActor, sAbilityStat, nTargetDC, bSecretRoll) local rRoll = {}; rRoll.sType = "ability"; rRoll.aDice = { "d20" }; rRoll.nMod = ActorManager2.getAbilityBonus(rActor, sAbilityStat); rRoll.sDesc = "[ABILITY]"; rRoll.sDesc = rRoll.sDesc .. " " .. StringManager.capitalize(sAbilityStat); rRoll.sDesc = rRoll.sDesc .. " check"; rRoll.bSecret = bSecretRoll; rRoll.nTarget = nTargetDC; return rRoll; end
So, where does the 3.5E ruleset do this? Looking at the Ability control XML in the 3.5E (campaign\template_char.xml) we have a template that is used to create the ability bonus fields: <template name="number_charabilitybonus"> These appear in the GUI as shown below:
This screenshot shows the six different ability bonus controls (one for each ability) and an example strength ability check in the chat window.
Each of these controls is created using the <template name="number_charabilitybonus"> template in campaign\record_char_main.xml. For example, the strength bonus field:
Code:<number_charabilitybonus name="strengthbonus" source="abilities.strength.bonus"> <anchored to="strength" /> <target>strength</target> <modifierfield>abilities.strength.bonusmodifier</modifierfield> <description textres="char_tooltip_strbonus" /> </number_charabilitybonus>
See "Accessing XML parameters from script" here: https://www.fantasygrounds.com/modguide/scripting.xcp for more info on how to store information in a control's XMl and access it from a script.
The ability action uses the XML parameter <target> in the XML <script> section of the number_charabilitybonus control template as follows:
Code:function action(draginfo) local rActor = ActorManager.getActor("pc", window.getDatabaseNode()); ActionAbility.performRoll(draginfo, rActor, self.target[1]); return true; end function onDragStart(button, x, y, draginfo) return action(draginfo); end function onDoubleClick(x,y) return action(); end
Last edited by Trenloe; December 14th, 2016 at 19:10.
Private Messages: My inbox is forever filling up with PMs. Please don't send me PMs unless they are actually private/personal messages. General FG questions should be asked in the forums - don't be afraid, the FG community don't bite and you're giving everyone the chance to respond and learn!
-
December 14th, 2016, 17:24 #4
The minimum needed for a custom action!
The following is an example framework for an action manager. In this case, this will handed the "mytestaction" action, rolling a d20 and displaying the result with the description "My Test Roll!"
Code:function onInit() -- Register the new action we're creating. We'll allow use of the modifier stack for this action type. GameSystem.actions["mytestaction"] = { bUseModStack = true }; -- Register the result handler - called after the dice have stopped rolling ActionsManager.registerResultHandler("mytestaction", onRoll); end function getRoll(rActor, bSecretRoll) -- Initialise a blank rRoll record local rRoll = {}; -- Add the 4 minimum parameters needed: -- the action type. rRoll.sType = "mytestaction"; -- the dice to roll. rRoll.aDice = { "d20" }; -- A modifier to apply to the roll. rRoll.nMod = 0; -- The description to show in the chat window rRoll.sDesc = "My Test Roll!"; -- For GM secret rolls. rRoll.bSecret = bSecretRoll; return rRoll; end function performRoll(draginfo, rActor, bSecretRoll) local rRoll = getRoll(rActor, bSecretRoll); ActionsManager.performAction(draginfo, rActor, rRoll); end function onRoll(rSource, rTarget, rRoll) -- Create the base message based off the source and the final rRoll record (includes dice results). local rMessage = ActionsManager.createActionMessage(rSource, rRoll); -- Display the message in chat. Comm.deliverChatMessage(rMessage); end
Try it yourself!
I've packaged the above code into a simple extension (attached - MyTestAction.ext). download this, put it in your <FG app data>\extensions directory and load up one of the main CoreRPG based rulesets (or CoreRPG itself) and enable the "My Test Action" extension. the, when the campaign loads, you can run this actions by typing /mytestaction in the chat window.
Example shown below. The second roll used a modifier entered in the Modifier Stack.
Last edited by Trenloe; December 14th, 2016 at 19:59.
Private Messages: My inbox is forever filling up with PMs. Please don't send me PMs unless they are actually private/personal messages. General FG questions should be asked in the forums - don't be afraid, the FG community don't bite and you're giving everyone the chance to respond and learn!
-
December 14th, 2016, 17:51 #5
OK, that all makes sense - show me more!
Or...
You completely lost me, can we look at a different action?
More Examples
The FG rulesets are littered with many examples. Just look in the ruleset scripts directory, any .lua (script) file beginning with manager_action_ should be an action manager for a specific action. manager_action_attack.lua is the action manager for the "attack" action type, manager_action_damage.lua is the action manager for the "damage" action type, etc., etc.. All of these action types should have been defined in the actions table in scripts\manager_gamesystem.lua
But, these mainstream ruleset actions can still be quite complex and difficult to follow 100% - they usually involve targeting, effects, extra processes (critical damage, for example), etc.. So, I'd recommend looking at some of the dice rolling mechanics basic extensions created by the community:
- The count successes extension is similar to the example in post #4 (you can test it from the chat window). It get's a little more advanced in that is passes parameters from the initial roll through to the result handler via square bracket parameters (mentioned in post #2) and then via a custom rRoll variable. Available here: https://www.fantasygrounds.com/forum...ting-extension
- Roll and Keep dice mechanics: https://www.fantasygrounds.com/forum...l=1#post199129
- Custom dice results: https://www.fantasygrounds.com/forum...l=1#post197855 Not really an action handler, but shows a way to create custom dice results.
- The MoreCore ruleset has a number of custom dice rollers built in. Find this excellent addition to the CoreRPG generic functionality here: https://www.fantasygrounds.com/forum...on-for-CoreRPG
Last edited by Trenloe; December 14th, 2016 at 19:51.
Private Messages: My inbox is forever filling up with PMs. Please don't send me PMs unless they are actually private/personal messages. General FG questions should be asked in the forums - don't be afraid, the FG community don't bite and you're giving everyone the chance to respond and learn!
-
December 14th, 2016, 22:44 #6
- Join Date
- Jun 2008
- Location
- Spring Hill, Florida
- Posts
- 110
This has been extremely helpful. Very informative. Thank you! Explained in a way that I grasped it.
The only problem I have is that when I use your extension, my dice results are not coming out the same as yours.
... crickets ...
Ok, really, I'm not that much of a noob. Now I'm off to put your lessons to the test!
-
December 14th, 2016, 22:52 #7Private Messages: My inbox is forever filling up with PMs. Please don't send me PMs unless they are actually private/personal messages. General FG questions should be asked in the forums - don't be afraid, the FG community don't bite and you're giving everyone the chance to respond and learn!
-
December 15th, 2016, 00:41 #8
This needs to be on the Wiki as well (if its not already) - Trenloe, if you send me a wiki-marked-up file I'll put it up (assuming you don't have permission too).
CheersDulux-Oz
√(-1) 2^3 Σ Π
...And it was Delicious!
Alpha-Geek
ICT Professional
GMing Since 1982
NSW, Australia, UTC +10
LinkedIn Profile: www.linkedin.com/in/mjblack
Watch our games on Twitch: www.twitch.tv/dulux_oz
Support Me on Patreon: www.patreon.com/duluxoz
Past Games, etc, on my YouTube Channel: www.youtube.com/c/duluxoz
-
October 20th, 2019, 11:52 #9
All Hail Trenloe, High Prince of the Understatement!
Having said that...time for some genuine thanks. With the help of this tutorial, after nearly 4 weeks of painstakingly picking apart the 3.5e and coreRPG code, repeatedly working through it in conjunction with this tutorial, i finally managed to get trait tests and skill checks working on my CoreRPG Pendragon extension. I should also throw out a special thanks to Damned and the MoreCore crew for the Pendragon roller code in morecore that I shamelessly stole and repurposed. This has been quite a journey teaching myself how to do this. I have to say I very nearly gave up several times, it must have taken me 40+ hours to get my brain around actions enough to make these simple trait rolls work. I don't entirely understand why the last change worked and the 50 before that didn't, but that has been a consistent feature of my FG customisation learning journey. Frankly I don't understand 2/3 of the nested code that spits out my trait rolls but I'm here to tell you that you can make your custom system work too without understanding it all. Just so long as you can persevere long enough to string other folks work together so that it works for you.
In a broader sense, the process of learning rudimentary XML and even more rudimentary lua to get this far has been both satisfying and humbling. Starting from a rock bottom 'zero coding experience' I can assure you I am definitely not a natural born coder. My first attempt at setting up Pendragon in MoreCore started out with much faster initial progress obviously but then hit a wall as I reached the limit of the standard morecore. The feature rich nature of moreCore meant i wasn't learning the XML/lua at all and it is so rich and layered that frankly my brain couldn't cope with the complexity of it as I tried to understand how it fit together. That's a testament to the brilliance of moreCore and a critical assessment of my own powers of comprehension. So I went back to the start, beginning with vanilla coreRPG, slowly layering tiny change after tiny change. I now have a customised Pendragon character sheet with a good amount of automation set up, an extra history tab and have taught myself a number of FG techniques such as rudimentary windowlists, actions, autocalculating stats, a goodly amount of XML positioning, and so forth. This is pretty good progress for a guy who took perhaps 2 weeks to understand what 'extensible' actually meant...even though it's right there 'on the box' in the name XML...
I relate this story in the hope that it encourages all those folks who read this thread and like me stood staring the screen thinking "WTF does that mean?" Over the course of the last 4 weeks i must have read this through 10-15 times in the hope of having a new epiphany on each read through. Sometimes I did, and eventually after having enough of them, i finally cracked it. So hang in there, take a day off from it if it's getting you down, and just keep going. I'm (almost) taking for granted things that completely stumped me 6 or so weeks ago, like what a template was, or how anchoring works. Half of the challenge is learning to recognise the syntax enough to know where to go searching for stuff. Lua was 100% incomprehensible for a long time, now i understand perhaps 20% what i'm reading and I can stumble through the basic stuff.
There is much still to be done for my Pendragon extension and I keep an ever growing list of features to implement. Each one is a new challenge that helps me to learn more about how this magnificant collaborative undertaking, Fantasy Grounds, works and supports this wonderful hobby that I've loved for 35 years now.
In Pendragon terms, try to crit your Energetic Trait test, and stick with it. Never having coded before does not preclude you from success...although there is no denying it makes it bloody hard...
Tubel
-
June 22nd, 2020, 03:01 #10
- Join Date
- Nov 2017
- Location
- Western NY
- Posts
- 139
Hiya,
I have been trying to wrap my head around coding dice/actions but I am having some oddities occur. If I replace "d20" in the above with "3d6" it does not seem to work. The instructions talk about aDice being the dice you roll, not sure why this does not work. It will work with "d6" however. Any help or pointers on this behavior would be helpful!
Thanks!
Thread Information
Users Browsing this Thread
There are currently 1 users browsing this thread. (0 members and 1 guests)
Bookmarks