Log in

View Full Version : getChildren / getChildrenGlobal



Varsuuk
January 23rd, 2020, 07:46
I was trying to collect various subsides which had 1..n attack types and when getChildren returned it was always 0 elements via #operator.

Spent a long time trying several things until switched to getChildrenGlobal.
That worked.

So... when do you you one or the other?
The odd thing to me is that even if I "copy" the node by dragging it from the MOD file it is listed in (monster manual) - it still returns 0. I checked the DB and after slight drag and creating second copy, it is in the campaign/db.xml file now as well as the mm.mod file.

When switch to global, it returns a different value than 0, ... 2 which I will read further to understand since I am retrieving it like:



function onAttacksChanged(nodeRecord)
local aAttacks = DB.getChildrenGlobal(nodeRecord, "attacks");
...


The xml:

<attacks>
<bite>
<name type="string">Bite</name>
<damage type="string">1d6</damage>
<numberOfAttacks>1</numberOfAttacks>
</bite>
</attacks>



Naively at first, I expected a return of 1, 1 child node of attacks. If it counted it deeper, then 4. But I recall something in docs having to do with children nodes and numbering. Will look there tomorrow.

Trenloe
January 23rd, 2020, 08:49
As the API document mentions for getChildrenGlobal: “... any children of that path will be compiled across the campaign database as well as all loaded module databases.”

To check why getChildren was returning 0 records put some debug in the code to output the exact node being used: https://www.fantasygrounds.com/refdoc/databasenode.xcp#getNodeName and make sure your code is executing against the node you think it’s executing against.

Varsuuk
January 23rd, 2020, 12:50
XML -> in a loaded MODULE:


<giantbatvampirebat>
<id type="string">giantbatvampirebat</id>
<nameknown type="string">Giant Bat (Vampire Bat)</nameknown>
<name type="string">Bat, Giant (Vampire Bat)</name>
<nameunidentified type="string">Giant Bat</nameunidentified>
<isidentified type="number">1</isidentified> <!-- TODO SHOULD this always be identified? Should this field be included in module? -->
<hitdice type="string">1</hitdice>
<hitpoints>
<totaldamage type="number">0</totaldamage>
<realdamage type="number">0</realdamage>
<subdualdamage type="number">0</subdualdamage>
<total type="number">0</total>
</hitpoints>
<defenses>
<ac type="number">8</ac>
<acasc type="number">11</acasc>
</defenses>
<attacks>
<bite>
<name type="string">Bite</name>
<damage type="string">1d6</damage>
<numberOfAttacks>1</numberOfAttacks>
</bite>
</attacks>
<savingthrow type="number">17</savingthrow>
<special type="String">Sucks blood</special>
<weaponlist>
...



The code (ran first with getChildren(), then getChildrenGlobal() )



function onAttacksChanged(nodeRecord)
Debug.console("onAttacksChanged", nodeRecord.getNodeName(), nodeRecord);
local aAttacks = DB.getChildrenGlobal(nodeRecord, "attacks");
local nTypesOfAttacks = #aAttacks;
Debug.console("onAttacksChanged", nodeRecord, nTypesOfAttacks);
local sAttacks = "";


for idx,attack in ipairs(aAttacks) do
...





With getChildren:


Runtime Notice: Reloading ruleset
Runtime Notice: s'onAttacksChanged' | s'monster.giantbatvampirebat@Swords & Wizardry Complete - Monsters' | databasenode = { monster.giantbatvampirebat@Swords & Wizardry Complete - Monsters }
Runtime Notice: s'onAttacksChanged' | databasenode = { monster.giantbatvampirebat@Swords & Wizardry Complete - Monsters } | #0



With getChildrenGlobal:


Runtime Notice: Reloading ruleset
Runtime Notice: s'onAttacksChanged' | s'monster.giantbatvampirebat@Swords & Wizardry Complete - Monsters' | databasenode = { monster.giantbatvampirebat@Swords & Wizardry Complete - Monsters }
Runtime Notice: s'onAttacksChanged' | databasenode = { monster.giantbatvampirebat@Swords & Wizardry Complete - Monsters } | #2

Trenloe
January 23rd, 2020, 13:32
Ah, so the node is within a module, not the campaign database.

We'd need MW to confirm if DB.getChildren only works against the campaign database.

As a work around, try <databasenode>.getChildren (https://www.fantasygrounds.com/refdoc/databasenode.xcp#getChildren) - you'll need another step to get the "attacks" node, but see if that works.

Varsuuk
January 23rd, 2020, 13:48
I will do that - just got on train and finally had moment to try what I was thinking of since hitting "post" - I was wondering why it returned 2 vs 1 for the global version.

Well, why not print in loop what the node is?
I did.

Odd:


Runtime Notice: Reloading ruleset
Runtime Notice: s'acchanged-self' | windowinstance = { class = monster_combat, node = monster.giantbatvampirebat@Swords & Wizardry Complete - Monsters, x,y,w,h = 97,326,435,483 }
Runtime Notice: s'ac_ascending_text' | nil
Runtime Notice: s'onAttacksChanged' | s'monster.giantbatvampirebat@Swords & Wizardry Complete - Monsters' | databasenode = { monster.giantbatvampirebat@Swords & Wizardry Complete - Monsters }
Runtime Notice: s'onAttacksChanged' | databasenode = { monster.giantbatvampirebat@Swords & Wizardry Complete - Monsters } | #2
Runtime Notice: s'--' | #1 | databasenode = { monster.giantbatvampirebat.attacks.bite@Swords & Wizardry Complete - Monsters }
Runtime Notice: s'--' | #2 | databasenode = { monster.giantbatvampirebat.attacks.numberattacks_t ext@Swords & Wizardry Complete - Monsters }

But while it was an "aha!" moment (and I realize I cannot place the text version under Attacks because it ruins my other logic...) but now I am on a mad search for WHERE it is finding the field. I've looked at both db.xml and not seeing "number attacks_text" but now that know the problem will find it.

--- Well, the problem of why count was 2 not 1. Not about the global. I read that and thought it too - global includes modules opened and other is only for local campaign - but when "copied" bat by dragging I verified it is in the campaign xml but not finding it either - still 0.


The troublesome "attacks.numberattacks_text" is at the end, I forgot I added it there - didn't original post entire method because hadn't tested and wanted to avoid posting embarrassingly wrong code ;) still haven't tested it but figure now that see the error was from later on in function - should give full info :)


function onAttacksChanged(nodeRecord)
Debug.console("onAttacksChanged", nodeRecord.getNodeName(), nodeRecord);
local aAttacks = DB.getChildrenGlobal(nodeRecord, "attacks");
local nTypesOfAttacks = #aAttacks;
Debug.console("onAttacksChanged", nodeRecord, nTypesOfAttacks);
local sAttacks = "";


for idx,attack in ipairs(aAttacks) do
Debug.console("--", idx, attack);
local nNumberOfAttacks = DB.getValue(attack, "numberOfAttacks", 1);

if nNumberOfAttacks == 1 then
sAttacks = tostring(nNumberOfAttacks) .. DB.getValue(attack, "name");
else
sAttacks = tostring(nNumberOfAttacks) ..
Plural.lookup(DB.getValue(attack, "name"));
end

sAttacks = sAttacks .. " (" .. DB.getValue(attack, "damage") .. ")";

if idx ~= nTypesOfAttacks then
sAttacks = sAttacks .. ", ";
end
end

DB.setValue(nodeRecord, "attacks.numberattacks_text", "string", sAttacks);
end




*** UPDATE ***
Verified with grep on dev directory, no where outside of xml control name and the lua script where it is set, do I find "numberattacks_text"

Loading the db.xml file from Windows into Mac's BBEdit, I see no numberattacks_text either in the "copied" bat (I did /save as well to be sure):


From campaign/db.xml:


...
</abilitynoteslist>
<aligment type="string">Neutrality</aligment>
<attacks>
<bite>
<damage type="string">1d6</damage>
<name type="string">Bite</name>
<numberOfAttacks>
</numberOfAttacks>
</bite>
</attacks>
<challengelevel type="number">3</challengelevel>
...

Trenloe
January 23rd, 2020, 14:13
What's in db.xml only reflects what is in the database when the file is saved - it doesn't represent what is in memory at any particular point. Your debug shows that when the debug ran there was a numberattacks_text node, and you have LUA code that sets a value in that node.

celestian
January 23rd, 2020, 15:39
DB.setValue(nodeRecord, "attacks.numberattacks_text", "string", sAttacks);



I'm curious... is this part of the code run when viewing an NPC?

I ask because I ran into an issue doing something similar and caused me some headache. The problem was if this is run on any NPC when you view them (including modules loaded) after they are viewed they will then be flagged as "changed" (if they are from a module) because the node "numberattacks_text" is changed anytime they are viewed.

You might consider instead of using a node, use a label and then update to "numberattacks_text_label.setValue(sAttacks)".

Varsuuk
January 23rd, 2020, 23:17
(*** I wrote this shortly after Trenloe's reply - but got to work and only now on commute back am I back on laptop ***)
Yeah, I got that. That's why I THOUGHT typing /save on FG chat would make memory reflect it.

Plus, I do /reload before every run, so in theory it is reading campaign/db.xml into men each time? And the code to count the entries is before the first time I set the entries.


Another fly in ointment of sanity:


Runtime Notice: Reloading ruleset
Runtime Notice: s'nACAsc' | #11
Runtime Notice: s'onAttacksChanged' | s'monster.giantbatvampirebat@Swords & Wizardry Complete - Monsters' | databasenode = { monster.giantbatvampirebat@Swords & Wizardry Complete - Monsters }
Runtime Notice: s'onAttacksChanged' | databasenode = { monster.giantbatvampirebat@Swords & Wizardry Complete - Monsters } | #0


Running with get children() not Global(), it looks like refers to the module? in the first ands the local in the second... separated by only a couple lines. Putting a pin in that for now...


========

Celestian, you are correct, and I will try that - remember that my desire always here was to avoid a field - but I did not get syntax right to use a control. So, I will try a "label" type vs the string controls I started on. Will update.

Varsuuk
January 23rd, 2020, 23:31
Deleted the campaign.
Started new (diff name)
Opened PHB/MM mods.
Opened Npcs
Dragged the only defined creature to create local copy.
Opened the local (never having opened the module one)

I saw the following (same code generating from above)



Runtime Notice: s'nACAsc' | #11
Runtime Notice: s'onAttacksChanged' | s'monster.id-00001' | databasenode = { monster.id-00001 }
Runtime Notice: s'onAttacksChanged' | databasenode = { monster.id-00001 } | #0


So this time, local naming as expected. But no "Attacks" found.

Now I hit /save in chat.

After reviewing, I noticed what didn't on commute in, numberofattacks was ill formed. In module forgot when added to place type="number", so it copied incorrectly. Fixed but not out of woods yet, still #0:



<attacks>
<bite>
<damage type="string">1d6</damage>
<name type="string">Bite</name>
<numberOfAttacks type="number">1</numberOfAttacks>
</bite>
</attacks>



Trasin rivning gotta go

Runtime Notice: Host session started
Runtime Notice: s'nACAsc' | #11
Runtime Notice: s'onAttacksChanged' | s'monster.id-00001' | databasenode = { monster.id-00001 }
Runtime Notice: s'onAttacksChanged' | databasenode = { monster.id-00001 } | #0

Trenloe
January 23rd, 2020, 23:53
Oh, I think the issue is that the table of children is not integer indexed, so # (and ipairs) won't actually work.

In your debug, output the complete aAttacks lua table to the console, not #aAttacks.

celestian
January 24th, 2020, 00:24
Something like this might work if I understand your goal.



local sAttacks = "";
for _,nodeAttack in pairs(DB.getChildrenGlobal(nodeRecord, "attacks")) do
local nNumberOfAttacks = DB.getValue(nodeAttack, "numberOfAttacks", 1);
local sSep = "";
if sAttacks ~= "" then
sSep = ", ";
end
sAttacks = sAttacks .. sSep .. tostring(nNumberOfAttacks) .. DB.getValue(nodeAttack , "name") .. " (" .. DB.getValue(nodeAttack , "damage") .. ")";
end

DB.setValue(nodeRecord, "attacks.numberattacks_text", "string", sAttacks);

Varsuuk
January 24th, 2020, 04:42
You guys ABSOLUTELY rock!

1. Yes, I have no CLUE when or IF I'd have figured out the sparse array thing. I am aware of the difference but it never occurred to me to switch to index - at least for a long time. Once I did this, the issue of failing to iterate properly went way. I switched to getChildCount() instead of # operator and that solved the odd numbers returned (Celestian, #ofAttacks as a field was different than numberOfAttkEntries (which isn't what I called it -so that's why it was confusion - #attk referred to overall how many entries so I knew when I was at the last one - will be clearer when done tonight or tomorrow and post the fixed version. Have to put boy late to bed.)

2. Celestian, yeah - I know I started trying a version of "self[numberattacks_text_label].setValue(sAttacks);" because of misunderstanding use of self[control].update(...) code that was already there - BUT I thought I DID try it straight - I must have done something wrong, because it worked when I tried it on a label field. Will redo the control to emulate look of the stringcolumnleft control before finishing.

Seriously, than you for solutions and forever the patience.

Varsuuk
January 24th, 2020, 06:01
Debug printout:
Runtime Notice: Reloading ruleset
...
Runtime Notice: s'AttkStr=' | s'1 Bite (1d6), 2 Claws (1d6)'

I had to leave off on the control template to sleep ;) but the code showing a logging of the output *so far* is:



function onAttacksChanged(nodeRecord)
local aAttacks = DB.getChildren(nodeRecord, "attacks");
local nTypesOfAttacks = DB.getChildCount(nodeRecord, "attacks");
Debug.console("onAttacksChanged", nodeRecord, nTypesOfAttacks);
local sAttacks = "";

local count = 0;
for idx,attack in pairs(aAttacks) do
count = count + 1;
Debug.console(idx, "Attk:", attack.getNodeName(), attack);
local nNumberOfAttacks = DB.getValue(attack, "numberOfAttacks", 1);

if nNumberOfAttacks == 1 then
sAttacks = sAttacks .. tostring(nNumberOfAttacks) .. " " ..
DB.getValue(attack, "name");
else
sAttacks = sAttacks .. tostring(nNumberOfAttacks) .. " " ..
Plural.lookup(DB.getValue(attack, "name"));
end

sAttacks = sAttacks .. " (" .. DB.getValue(attack, "damage") .. ")";
Debug.console("IDX=", count, "#Attks", nTypesOfAttacks)
if count ~= nTypesOfAttacks then
sAttacks = sAttacks .. ", ";
end
end

Debug.console("AttkStr=", sAttacks);
numberattacks_text.setValue(sAttacks);
end



That said, any reason NOT to use a "control" vs calculating it once and storing in the NPC-DB?
I just didn't want to fill it up with fields that are nothing other than combinations of existing fields. But if there is a big cost to the control way, I would reconsider.

In this case (and of "AC Down [Up]") I do not need to ever do anything with the string other than show it to folks.

Trenloe
January 24th, 2020, 07:36
If all the control is ever going to do is show it, it's never going to be accessed by any code and the value is set by code within the same GUI window, then a control rather than a field, is OK.

Are you going to be displaying this data in then combat tracker fields?

Varsuuk
January 24th, 2020, 13:26
Nope, merely a nicer formatted (space-saving) way of displaying the underlying fields that will be referenced.

If I were to use that underline click thing feature and use this string elsewhere I’d save it. I wasn’t sure it was efficient to keep calculating it each time it is loaded due to the concatenation (Leery of string concatenation in the real time apps I work with, habit) but figured it’s a thing to keep in sync if I make it a data field, separately editable.

So making it a derived field actually in the DB is best middle ground other than (IMO) cluttering up the stored npc. But if I need it anywhere else, then yes, I would change it.