PDA

View Full Version : MoreCore: When rolling an attack on multiple targets



Varsuuk
February 23rd, 2021, 17:01
I added 2 different types of monsters to the CT.
Then I added 2 PCs to test their class bonuses.

The PC (elf) when attacking a goblin or Lich received a +1 to hit. With proper text.
When I try to target both together, it does not use the racial vs target mods.

I then put a printf on the method (onMod) and see it is called only one time and without a target string.
What would I look at to address this? Is it a limitation on the MoreCore roller system? Is there a simple example on multi-target targeting where each target is looked at that I can see if I can make a change to handle this?

44182
Above is the chat log.

The logging on the call to onMod(...):
Runtime Notice: s'onModHandler' | { s'sType' = s'pc', s'sCreatureNode' = s'charsheet.id-00003', s'sCTNode' = s'combattracker.list.id-00002', s'sName' = s'Pete' } | nil | { s'aDice' = { #1 = s'd20' }, s'nMod' = #0, s'sType' = s'swthac0', s'aAttributes' = { }, s'sDesc' = s'[THAC0:19]' }

Varsuuk
February 24th, 2021, 02:20
OK - I loaded up manager_custom_die in MoreCore to trace through and see if there is either a hook or something I can modify so it works as I want/expect.

Then, decided - first trace existing code to check ALL calls and arguments vs just the listing of single mod call without target. So I did and I think I can get around this much easier. And for all I know, it was always intended to be split up this way that I will change to.

This is what I saw (first set is single attack, second is 2 targets and the elf should get a +1 (str) and another +1 for different reasons on each:



Runtime Notice: s'performAction' |
nil |
{ s'sType' = s'pc', s'sCreatureNode' = s'charsheet.id-00002', s'sCTNode' = s'combattracker.list.id-00003', s'sName' = s'Joe' } |
s'| Melee Attack'

Runtime Notice: s'onModHandler' |
{ s'sType' = s'pc', s'sCreatureNode' = s'charsheet.id-00002', s'sCTNode' = s'combattracker.list.id-00003', s'sName' = s'Joe' } |
{ s'sType' = s'npc', s'sCreatureNode' = s'combattracker.list.id-00007', s'sCTNode' = s'combattracker.list.id-00007', s'sName' = s'Acerak' } |
{ s'aDice' = { #1 = s'd20' }, s'nMod' = #0, s'sType' = s'swthac0', s'aAttributes' = { }, s'sDesc' = s'[THAC0:19]' }

Runtime Notice: s'onResultHandler' |
{ s'sType' = s'pc', s'sCreatureNode' = s'charsheet.id-00002', s'sCTNode' = s'combattracker.list.id-00003', s'sName' = s'Joe' } |
{ s'sType' = s'npc', s'sCreatureNode' = s'combattracker.list.id-00007', s'sCTNode' = s'combattracker.list.id-00007', s'sName' = s'Acerak' } |
{ s'aDice' = { #1 = { s'result' = #9, s'type' = s'd20' } }, s'nMod' = #1, s'sType' = s'swthac0', s'aAttributes' = s'', s'bSecret' = bFALSE, s'sDesc' = s'[THAC0:19][Str:1]' }


Runtime Notice: s'performAction' |
nil |
{ s'sType' = s'pc', s'sCreatureNode' = s'charsheet.id-00003', s'sCTNode' = s'combattracker.list.id-00002', s'sName' = s'Pete' } |
s'| Melee Attack'

Runtime Notice: s'onModHandler' |
{ s'sType' = s'pc', s'sCreatureNode' = s'charsheet.id-00003', s'sCTNode' = s'combattracker.list.id-00002', s'sName' = s'Pete' } |
nil |
{ s'aDice' = { #1 = s'd20' }, s'nMod' = #0, s'sType' = s'swthac0', s'aAttributes' = { }, s'sDesc' = s'[THAC0:19]' }

Runtime Notice: s'onResultHandler' |
{ s'sType' = s'pc', s'sCreatureNode' = s'charsheet.id-00003', s'sCTNode' = s'combattracker.list.id-00002', s'sName' = s'Pete' } |
{ s'nOrder' = #1, s'sType' = s'npc', s'sName' = s'Acerak', s'sCTNode' = s'combattracker.list.id-00007', s'sCreatureNode' = s'combattracker.list.id-00007' } |
{ s'aDice' = { #1 = { s'result' = #18, s'type' = s'd20' } }, s'nMod' = #1, s'sType' = s'swthac0', s'aAttributes' = s'', s'bSecret' = bFALSE, s'sDesc' = s'[THAC0:19][Str:1]' }

Runtime Notice: s'onResultHandler' |
{ s'sType' = s'pc', s'sCreatureNode' = s'charsheet.id-00003', s'sCTNode' = s'combattracker.list.id-00002', s'sName' = s'Pete' } |
{ s'nOrder' = #2, s'sType' = s'npc', s'sName' = s'Goblin 3', s'sCTNode' = s'combattracker.list.id-00006', s'sCreatureNode' = s'combattracker.list.id-00006' } |
{ s'bSecret' = bFALSE, s'aDice' = { #1 = { s'result' = #18, s'type' = s'd20' } }, s'nMod' = #1, s'aTotal' = #19, s'sType' = s'swthac0', s'aAttributes' = s'', s'aHitResult' = s'-> [at Acerak] [HIT]', s'sDesc' = s'[THAC0:19][Str:1]' }


Originally, I setup the roll then on onMod I would check source/target for str or dex depending on melee/missle, racial with weapons, racial against target etc modes.
Now, I think I will do it so that onMod ONLY adds in "inherent" mods - like STR/DEX and racial based on SOURCE's attack method.

The onResults gets both source and target so there I can see if I can prior to creatMessage I can look at the source/target and modify rRoll accordingly (ie individually for this target)



function onResultHandler(rSource, rTarget, rRoll)
Debug.console("onResultHandler", rSource, rTarget, rRoll)
----> I would call my mod-appliers here looking at source/taget.
local rMessage = ActionsManager.createActionMessage(rSource, rRoll)

rRoll = getDiceResults(rRoll, rTarget);
rMessage = createChatMessage(rSource, rRoll, rTarget)
rMessage.type = sCmd

Comm.deliverChatMessage(rMessage)
end



I'll update once I can try it out.

Varsuuk
February 24th, 2021, 05:43
Was a good idea... kinda... but forgot changing rRoll will carry to the next results :(

44202

I guess I need to look at how it is called after all for ideas.

Varsuuk
February 24th, 2021, 14:55
OK prior to breakfast I remembered seeing a call to a deep copy and that gave me a solution to try. It worked. So at the start of the results handler I use not the roll passed in but a new copy of it instead.

This will happen for ALL rolls so I will go back and add a flag to indicate multi-targets and copy only when the flag was "true" because I assume a deep copy would be a pointless performance hit. But I wanted to test first and now I am starting my "Day job" ;)



function onResultHandler(rSource, rTarget, rRoll)
Debug.console("onResultHandler", rSource, rTarget, rRoll)
local rNewRoll = UtilityManager.copyDeep(rRoll);

local nodeSourceActor = ActorManager.getCreatureNode(rSource)
local sType, nodeTargetActor = ActorManager.getTypeAndNode(rTarget)

if nodeSourceActor and nodeTargetActor and ActorManager.isPC(rSource) then
...



44230
Example of 3 targets one with no special bonus and one each with different applicable bonuses.


EDIT: I still feel I am missing something though, because of the comments in both CoreRPG and MoreCore on this:


local bModStackUsed = false;
if bMultiTarget then
if vTarget and #vTarget == 1 then
bModStackUsed = applyModifiers(rSource, vTarget[1], rNewRoll);
else
-- Only apply non-target specific modifiers before roll
bModStackUsed = applyModifiers(rSource, nil, rNewRoll);
end
else
bModStackUsed = applyModifiers(rSource, vTarget, rNewRoll);
end


The above bold implies you should add AFTER roll, I guess, and if so - I want to make sure I am doing it in the right "after" spot, if you will.

Varsuuk
February 24th, 2021, 15:45
Please feel free to offer suggestions ;)
I could not determine a way to detect that the action is aimed at multiple targets because those calls are not exposed to me.
Calls like Core/MoreCore's actionRoll(rSource, vTarget, rRolls) and applyModifiersAndRoll(...) both can check if multi target.

If there is no existing way to determine if something is multi target and we are supposed to calculate per target mods in the results handler (not sure assumptions are right) - maybe the code in Core/More can add a flag to the rRoll object?


function applyModifiersAndRoll(rSource, vTarget, bMultiTarget, rRoll)
local rNewRoll = UtilityManager.copyDeep(rRoll);

local bModStackUsed = false;
if bMultiTarget then
if vTarget and #vTarget == 1 then
bModStackUsed = applyModifiers(rSource, vTarget[1], rNewRoll);
else
rNewRoll.bIsMultiTarget = 'true'
-- Only apply non-target specific modifiers before roll
bModStackUsed = applyModifiers(rSource, nil, rNewRoll);
end
else
bModStackUsed = applyModifiers(rSource, vTarget, rNewRoll);
end

roll(rSource, vTarget, rNewRoll, bMultiTarget);

return bModStackUsed;
end


Not doing this because I have a (more costly) workaround and maintaining my replacement code vs Core / MoreCore would be a headache for just one method.
If it were done IN those 2, then it would rock :)

I used a string because of the request to use string->string mapping in Action manager. Also, I didn't add it everywhere (with = 'false') which is fine if you only check against "== 'true'" like I did in my code knowing I wrote it. But in Core, I'd change it so would set to "false" initially or otherwise so user could check however they wished.

Still works as before with my isMultiTarget (local) check:
44231




Debug.console("onModHandler", rSource, rTarget, rRoll)
local nodeSourceActor = ActorManager.getCreatureNode(rSource)
local sType, nodeTargetActor = ActorManager.getTypeAndNode(rTarget)

if nodeSourceActor then
local sModString = ""

if rTarget == nil then
-- I do not know how to detect the difference between NO target
-- (rolling on sheet) and multi-target (passes nil here). So I will assume
-- we are a multi-target and cause perhaps one unneeded deep copy in those
-- cases. I chose to do it prior to checking if this is a PC because I intend
-- on allowing for NPCs to go through similar trait/race checking in future.
rRoll.bIsMultiTarget = 'true'
end

if ActorManager.isPC(rSource) then
...
...


function onResultHandler(rSource, rTarget, rRoll)
local rNewRoll = rRoll
if rRoll.bIsMultiTarget == 'true' then
-- If this is a multi-target roll, the modifications warranted based on
-- target can vary. Use a different rRoll object for each in case.
rNewRoll = UtilityManager.copyDeep(rRoll);
end

local nodeSourceActor = ActorManager.getCreatureNode(rSource)
local sType, nodeTargetActor = ActorManager.getTypeAndNode(rTarget)

if nodeSourceActor and nodeTargetActor and ActorManager.isPC(rSource) then
local sRaceID = Character.getRaceID(nodeSourceActor)
local sModDesc, nModToHit = Character.getToHitBonus(nodeSourceActor,
rNewRoll.bIsMeleeAttack == 'true')

if nModToHit ~= 0 then
rNewRoll.nMod = rNewRoll.nMod + nModToHit
rNewRoll.sDesc = rNewRoll.sDesc .. "[" .. sModDesc .. ":" .. tostring(nModToHit) .. "]"
end

aModTable = RaceCommon.race_attack_bonus_to_hit_for_target_tag[sRaceID]
ModManager.applyVsActor(aModTable, nodeTargetActor, rNewRoll, sModString)
end

...