PDA

View Full Version : Dice mechanic tutorial for coding under CoreRPG?



Oberoten
February 22nd, 2015, 07:05
Any chance that someone brighter than me might be able to put up a tutorial on how to make action-rolls bound to certain dice? I am sure this could be useful for a lot of people with how advanced (and hence hard to follow for those not in the know) the dice-manager has become.

- Obe

Valarian
February 22nd, 2015, 07:36
From my Yggdrasill community ruleset ...

template_char.xml (campaign folder) has a template attribute field class which calls the die handlers


<!-- Rollable Attribute -->
<template name="char_attribute">
<basicnumber>
<script>
function onDragStart(button, x, y, draginfo)
local label = getName():gsub("^%l", string.upper);
local woundState = window.getDatabaseNode().getChild("hpstate");
local penalty = window.getDatabaseNode().getChild("hppenalty");
if woundState.getValue() == "Severely Wounded" then
draginfo.setType("wounddice");
else
draginfo.setType("skilldice");
end
draginfo.setDescription(label);
draginfo.setNumberData(penalty.getValue());
local dice = {};
for i = 1, getValue(), 1 do
table.insert(dice, "d10");
end
draginfo.setDieList(dice);
return true;
end

function onDoubleClick(x,y)
local label = getName():gsub("^%l", string.upper);
local woundState = window.getDatabaseNode().getChild("hpstate");
local penalty = window.getDatabaseNode().getChild("hppenalty");
local type = "dice";
if woundState.getValue() == "Severely Wounded" then
type = "wounddice";
else
type = "skilldice";
end
local dice = {};
for i = 1, getValue(), 1 do
table.insert(dice, "d10");
end
Comm.throwDice(type, dice, penalty.getValue(), label);
return true;
end
</script>
</basicnumber>
</template>


The actions are declared in the manager_gamesystem.lua script (scripts folder):


-- Ruleset action types
actions = {
["dice"] = { bUseModStack = "true" },
["skilldice"] = { bUseModStack = "true" },
["wounddice"] = { bUseModStack = "true" },
["table"] = { },
["effect"] = { sIcon = "action_effect", sTargeting = "all" },
};


The die handler script is also in the scripts folder:


-- This file is part of the Fantasy Grounds Open Foundation Ruleset project.
-- For the latest information, see https://www.fantasygrounds.com/
--
-- Copyright 2008 SmiteWorks Ltd.
--
-- This file is provided under the Open Game License version 1.0a
-- Refer to the license.html file for the full license text
--
-- All producers of work derived from this material are advised to
-- familiarize themselves with the license, and to take special
-- care in providing the definition of Product Identity (as specified
-- by the OGL) in their products.
--
-- All material submitted to the Open Foundation Ruleset project must
-- contain this notice in a manner applicable to the source file type.

function onInit()
ActionsManager.registerResultHandler("skilldice", handleSkillDice);
end

function handleSkillDice(rSource, rTarget, rRoll)
local reroll = false;
local total = {};

local firstResult = 0;
local secondResult = 0;
local dicetable = rRoll.aDice;
if dicetable then
for n = 1, table.maxn(dicetable) do
if dicetable[n].type == "d10" then
if firstResult > 0 and dicetable[n].result > secondResult then
secondResult = dicetable[n].result;
total[2] = dicetable[n];
elseif dicetable[n].result > firstResult then
firstResult = dicetable[n].result;
total[1] = dicetable[n];
end
end
end
end

if firstResult == 10 or secondResult == 10 then
repeat
local reroll = false;
local dieRoll = math.random(1,10);
local explode = {};
explode.type = "d10";
explode.result = dieRoll;
table.insert(total, explode);
-- Purely for effect - roll a d10
local dice = {};
table.insert(dice, "d10");
Comm.throwDice("explode", dice, 0, "explode");
if dieRoll == 10 then
reroll = true;
end
until not reroll
end

local rMessage = ChatManager.createBaseMessage(rSource, rRoll.sUser);
rMessage.type = rRoll.sType;
rMessage.text = rMessage.text .. rRoll.sDesc;
rMessage.dice = total;
rMessage.diemodifier = rRoll.nMod;

-- Check to see if this roll should be secret (GM or dice tower tag)
if rRoll.bSecret then
rMessage.secret = true;
if rRoll.bTower then
rMessage.icon = "dicetower_icon";
end
elseif User.isHost() and OptionsManager.isOption("REVL", "off") then
rMessage.secret = true;
end

-- Show total if option enabled
if OptionsManager.isOption("TOTL", "on") and total and #(total) > 0 then
rMessage.dicedisplay = 1;
end

Comm.deliverChatMessage(rMessage);
ModifierStack.reset();
return true;
end


Both the game system manager and the die handler are called in the base.xml (as is the template xml):


<!-- Scripts -->
<script name="DataCommon" file="scripts/data_common.lua"/>
<script name="NodeManager" file="scripts/manager_node.lua"/>
<script name="GameSystem" file="scripts/manager_gamesystem.lua" />
<script name="SkillDieHandler" file="scripts/handler_skilldice.lua"/>
<script name="WoundDieHandler" file="scripts/handler_wounddice.lua"/>
<script name="IntroText" file="scripts/chat_intro.lua"/>

Trenloe
February 22nd, 2015, 15:10
As a further example, have a look at manager_action_ability.lua in the 3.5 ruleset. This specifically keeps the roll to a single d20 - you can see this in the getRoll function.

The you start this roll with ActionAbility.performRoll(draginfo, rActor, "strength", 20, false); to roll a strength check with a target DC of 20, and it isn't a secret roll. The target DC and secret roll flag are optional.

The roll is kicked off in the GUI via the code in the number_charabilitybonus template in template_char.xml:


function action(draginfo)
local rActor = ActorManager.getActor("pc", window.getDatabaseNode());
ActionAbility.performRoll(draginfo, rActor, self.target[1]);

return true;
end

As Valarian mentions above, the actions type (in this case "ability") has to be registered in the valid action types for the ruleset.

Some further info on the dice throwing code in this thread: https://www.fantasygrounds.com/forums/showthread.php?21933-Comm-throwDice-Question

radekg
February 22nd, 2015, 16:28
Thanks a lot for the example.

This part:


-- Purely for effect - roll a d10
local dice = {};
table.insert(dice, "d10");
Comm.throwDice("explode", dice, 0, "explode");


saddens me. Is there no way to read result of exploding dice? I don't want this throw to be just "cosmetic". I think Savage Worlds does it the way I want but it extends ActionsManager and I'd like to stay with CoreRPG as much as I can.

Trenloe
February 22nd, 2015, 16:33
Is there no way to read result of exploding dice? I don't want this throw to be just "cosmetic". I think Savage Worlds does it the way I want but it extends ActionsManager and I'd like to stay with CoreRPG as much as I can.
See here for an example extension of how to add this to CoreRPG (and other rulesets based on CaoreRPG): https://www.fantasygrounds.com/forums/showthread.php?23095-7th-Sea-Roll-amp-Keep&p=199129&viewfull=1#post199129

Oberoten
February 22nd, 2015, 19:57
Lets say I want to drop a "40" as a result in any D6 landing a 6, how would I then go about this?
- Obe

Trenloe
February 22nd, 2015, 21:18
Lets say I want to drop a "40" as a result in any D6 landing a 6, how would I then go about this?
- Obe
In your result handler. In the FG 3.0 CoreRPG action management structure you will have an rRoll structure that have a rRoll.aDice table that contains a set of info about they dice type and the result for every dice rolled. See the "dice" section of the message structure here for info on this table: https://www.fantasygrounds.com/refdoc/Comm.xcp

Within your action result handler, before you construct the result message and calculate the total, step through each of the dice in rRoll.aDice, look at the dice result and if it is 6 change it to 40. Do this before nTotal is worked out and it will do the working out for you.

In Valarian's example above, the "handleSkillDice" function is the result handler, in the 3.5E scripts\manager_action_ability.lua script I mention it is the onRoll function:


function onRoll(rSource, rTarget, rRoll)
-- *** insert code here to change dice results ***
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

I've highlighted where you'd want to step through the dice in rRoll.aDice and change the result if it is 6 - this is just before the result message that will be displayed in the chat window is constructed. This code will do that for you:


for k,v in ipairs(rRoll.aDice) do
if v.result == 6 then
v.result = 40;
end
end

Oberoten
February 23rd, 2015, 02:32
Thank you all, this is going to make things a LOT easier for me and by extension for my players as well.

- Obe

Oberoten
February 24th, 2015, 23:35
Ahhhh. :) Thanks ever so much once again. Now it all works and I can move forward on the Ars Magica set as well as the updated Warhammer FRPG v1.0

- Obe

radekg
February 25th, 2015, 08:15
Yeah, thanks to this thread, I also implemented Exploding Dice without throwing fake dice. :D

Thanks!

damned
February 25th, 2015, 09:45
Please do share your code!


https://www.fg-con.com/wp-content/uploads/2015/01/fg-con-6-150-12.jpg (https://www.fg-con.com/events/)
FG Con 6 – April 17-19th 2015 - register at www.fg-con.com (https://www.fg-con.com/) for all the latest info.

radekg
February 25th, 2015, 09:54
It's not completed (with no guarantee it can be completed) and I'm not Lua expert by any stretch of imagination but following shows my way of thinking:

Helper functions:


function serializeTable(val)
local tmp = ""
for k, v in pairs(val) do
tmp = tmp .. v.result .. "," .. v.type .. ";"
end

return tmp
end

function deserializeTable(val)
local result = {}
for k,i in string.gmatch(val, "(%w+),(%w+)") do
local vvv = {}
vvv.result = k
vvv.type = i
table.insert(result, vvv)
end
return result
end


function getReRoll(aDice, iRoll)
local rRoll = {}
rRoll.aDice = aDice
rRoll.nMod = iRoll.nMod
rRoll.sType = iRoll.sType
rRoll.bSecret = iRoll.bSecret
rRoll.nStrain = iRoll.nStrain
rRoll.sDesc = iRoll.sDesc
rRoll.rPreviousResult = serializeTable(iRoll.aDice)
return rRoll
end


ResultHandler:


function onResult(rSource, rTarget, rRoll)

local isMaxResult = function(rDie)
if tonumber(rDie.type:match("^d(%d+)")) == rDie.result then
return rDie.result
else
return 0
end
end

explode = {}
for i,die in ipairs(rRoll.aDice) do
local reroll = isMaxResult(die)
if reroll>0 then
table.insert(explode, die.type)
end
end

if rRoll.rPreviousResult then
prev = deserializeTable(rRoll.rPreviousResult)
for i,roll in ipairs(prev) do
table.insert(rRoll.aDice, 1, roll)
end
end
if table.getn(explode) > 0 then
ActionsManager.performAction(draginfo, rActor, getReRoll(explode, rRoll));
return false;
end;

local rMessage = ActionsManager.createActionMessage(rSource, rRoll);
local nTotal = ActionsManager.total(rRoll);

Comm.deliverChatMessage(rMessage);
end


Basically, it delivers message to chat only if no dice exploded. If exploding dice exist, it serializes aDice table to string, stores it in custom attribute and rolls again with only exploding dice - when it gets to this point again and previous roll result exists, the method deserializes it to aDice table. :)

What is not done is handling of draginfo (I'm not sure - is it lost?) and rActor. I simply had no time to analyze this so it might be a case that it cannot be done. :/ Fantasy Grounds surprised me like that more than once.

Oberoten
February 28th, 2015, 19:59
One last question, where and how would I add in a target value for a roll?

Trenloe
February 28th, 2015, 20:12
One last question, where and how would I add in a target value for a roll?

Have a look at the 3.5e action ability roll. My post in #3 shows a 20 being set as the target of the roll and then my post in #7 shows the rRoll.nTarget value being used to signal success or failure.