1. #1
    Trenloe's Avatar
    Join Date
    May 2011
    Location
    Denver, Colorado, USA (for a bit)
    Posts
    23,539

    Fantasy Grounds v3.X CoreRPG based Actions (dice rolling)

    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" },
    };
    Most rulesets will override this with their own scripts\manager_gamesystem.lua file.

    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.
    Last edited by Trenloe; December 14th, 2016 at 22:54.


    FG Product Development status: Pathfinder Playtest Ruleset and add-ons: In development. Pathfinder Bestiary, Pathfinder Bestiary 2, Pathfinder Bestiary 3 (in store).

    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!

  2. #2
    Trenloe's Avatar
    Join Date
    May 2011
    Location
    Denver, Colorado, USA (for a bit)
    Posts
    23,539
    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: http://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)
    Any action will have a LUA record - usually called rRoll with a number of parameters that have specific meaning within the FG code - the base ones are listed in the "ROLL" section in the Action Flow above: sType (action type), sDesc (description to show in the chat window), aDice (dice to roll - includes their result after rolling), nMod (modifier to the roll). These are the base four things needs to define a roll to enable an action to be initiated. There are more parameters, but these are the main four.

    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";
    Confused yet? Still with me? I hope so, because we're nearly there...

    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
    A base rMessage record is created - this is what will get sent to the chat window and the message data structure is detailed here: http://www.fantasygrounds.com/refdoc/Comm.xcp

    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.


    FG Product Development status: Pathfinder Playtest Ruleset and add-ons: In development. Pathfinder Bestiary, Pathfinder Bestiary 2, Pathfinder Bestiary 3 (in store).

    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!

  3. #3
    Trenloe's Avatar
    Join Date
    May 2011
    Location
    Denver, Colorado, USA (for a bit)
    Posts
    23,539
    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
    The first step in this small function is getRoll which basically puts all of the roll data together in the rRoll record (remember rRoll from post #2?):
    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
    This is where the type is set = "ability" in this case, what dice to roll, what modifier to use (calls a helper function to get the ability bonus based off sAbilityStat). Then sets up the roll description - this is key (see post #2, Step 3). A couple of other parameters are added and then control passes back to performRoll with the rRoll record being returned and ActionsManager.performAction(draginfo, rActor, rRoll); is called to initiate the full action sequence, which is detailed in post #2.

    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>
    Of interest here is the <target> parameter. This is used by the template to specify which ability should be used for the roll - allowing the number_charabilitybonus template to be re-used for different abilities. This "target" parameter should not be confused with rTarget in the general action process - rTarget is completely different and is used to store the targeting information of an action - i.e. other PCs/NPCs that are being targeted for an attack, damage, heal, etc. action.

    See "Accessing XML parameters from script" here: http://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
    Either a double-click on the control or a drag start event will call with action function - note that draginfo is only relevant if a drag event is being initiated. The action function gets the rActor record from the ActorManager.getActor helper function (telling the function that it is a "pc" and passing the database record associated with the character sheet). Then ActionAbility.performRoll(draginfo, rActor, self.target[1]); is called to start the whole "ability" action process - self.target[1] retrieves the name of the ability to use for the roll. This kicks off the whole process - see the start of this post for the specifics of the ability action and then post #2 for more general action processing and the final result handler for the ability action.
    Attached Images Attached Images
    Last edited by Trenloe; December 14th, 2016 at 19:10.


    FG Product Development status: Pathfinder Playtest Ruleset and add-ons: In development. Pathfinder Bestiary, Pathfinder Bestiary 2, Pathfinder Bestiary 3 (in store).

    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!

  4. #4
    Trenloe's Avatar
    Join Date
    May 2011
    Location
    Denver, Colorado, USA (for a bit)
    Posts
    23,539
    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
    We can save this script as a package - let's call it manager_action_mytest.lua and initialise it with <script name="ActionMyTest" file="scripts/manager_action_mytest.lua" /> - usually in the ruleset base.xml or the extension extension.xml file. To start this custom action all we need to do is call: ActionMyTest.performRoll(draginfo, rActor, bSecretRoll);

    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.

    Attached Images Attached Images
    Attached Files Attached Files
    Last edited by Trenloe; December 14th, 2016 at 19:59.


    FG Product Development status: Pathfinder Playtest Ruleset and add-ons: In development. Pathfinder Bestiary, Pathfinder Bestiary 2, Pathfinder Bestiary 3 (in store).

    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!

  5. #5
    Trenloe's Avatar
    Join Date
    May 2011
    Location
    Denver, Colorado, USA (for a bit)
    Posts
    23,539
    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:
    Last edited by Trenloe; December 14th, 2016 at 19:51.


    FG Product Development status: Pathfinder Playtest Ruleset and add-ons: In development. Pathfinder Bestiary, Pathfinder Bestiary 2, Pathfinder Bestiary 3 (in store).

    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!

  6. #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!

  7. #7
    Trenloe's Avatar
    Join Date
    May 2011
    Location
    Denver, Colorado, USA (for a bit)
    Posts
    23,539
    Quote Originally Posted by ronalmb View Post
    The only problem I have is that when I use your extension, my dice results are not coming out the same as yours.
    You have to sign up to Secret University to learn how to do that...


    FG Product Development status: Pathfinder Playtest Ruleset and add-ons: In development. Pathfinder Bestiary, Pathfinder Bestiary 2, Pathfinder Bestiary 3 (in store).

    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!

  8. #8
    dulux-oz's Avatar
    Join Date
    Jan 2012
    Location
    Brisbane, Australia
    Posts
    3,949
    Blog Entries
    14
    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).

    Cheers
    Dulux-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

    Read my FG Blog here.

    Watch our games on Twitch: www.twitch.tv/dulux_oz

    Support Ongoing Video, Ruleset & Extension Development: via PayPal (Send To: [email protected]grineit.net)

    YouTube Channel/Tutorial Playlists: www.youtube.com/c/duluxoz

Thread Information

Users Browsing this Thread

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

Posting Permissions

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

Log in

Log in