PDA

View Full Version : Recorded for Posterity - The secret tales of data_library.lua



SilentRuin
February 10th, 2021, 18:41
Today I spent a horrible amount of time deciphering where Spells window was generated in the right side toolbar of FG.

So I never forget, though I desperately want to, I shall for posterity record how those right toobar buttons are defined.

The tale will be told through the eyes of the 5E ruleset...

It all starts with CoreRPG\scripts\data_library.lua and its function overrideRecordTypeInfo.

Any ruleset that wants to define the buttons along the right side of the applications to their own way of doing things must play with the aRecords structure format and rules. Not sure where or if these are documented - but I have only delved into them as far as I needed to go. In my case, that was how to get some new buttons in the main 5E Spells window showing the group/lists/etc. of spells in the DB. The aRecords structure defines how the code will define and display these buttons. And rulesets want to override this CoreRPG data by providing their own set of aRecords structure to override the default CorRPG data - via the function function overrideRecordTypeInfo.

The 5E ruleset does this in 5E\scripts\data_library_5E.lua in the aRecordOverrides structure which it will pass onto the CoreRPG function overrideRecordTypeInfo to override the default data. Here is an example of one of the pieces of data - items - in that structure - I show it because it seems to be the most complex one...

(all my examples are from TEST server FYI because that is where my extension work is happening)


["item"] = {
fIsIdentifiable = isItemIdentifiable,
aDataMap = { "item", "reference.equipmentdata", "reference.magicitemdata" },
fRecordDisplayClass = getItemRecordDisplayClass,
aRecordDisplayClasses = { "item", "reference_magicitem", "reference_armor", "reference_weapon", "reference_equipment", "reference_mountsandotheranimals", "reference_waterbornevehicles", "reference_vehicle" },
aGMListButtons = { "button_item_armor", "button_item_weapons", "button_item_templates", "button_forge_item" },
aPlayerListButtons = { "button_item_armor", "button_item_weapons" },
aCustomFilters = {
["Type"] = { sField = "type" },
["Rarity"] = { sField = "rarity", fGetValue = getItemRarityValue },
["Attunement?"] = { sField = "rarity", fGetValue = getItemAttunementValue },
},
},


Looking at the actual Items window I have deduced that this data structure has magical mysterious means (and I choose to ignore finding out the how/why of them) to build out the standard DB data window appearing on the right toolbar in FGU. You can see where the aGMListButtons seems list out all the buttons that will be displayed (I'm guessing to GM only) which is what I'm interested in. You can deduce what the others do on your own.

Now I'm interested in spells so I look to the current 5E definition in aRecordOverrides for that one...



["spell"] = {
bExport = true,
aDataMap = { "spell", "reference.spelldata" },
sRecordDisplayClass = "power",
aCustomFilters = {
["Source"] = { sField = "source", fGetValue = getSpellSourceValue },
["Level"] = { sField = "level", sType = "number" },
["Ritual"] = { sField = "ritual", sType = "boolean" },
},
},


Now my plan is to add my new buttons by adding into this data structure...



aGMListButtons = { "button_thing_ONE", "button_thing_TWO" },


Where I look to the item buttons like this one to define those buttons...

5E\campaign\template_campaign.xml


<template name="button_item_armor">
<button_text_sm>
<anchored to="buttonanchor" width="80">
<top />
<left anchor="right" relation="relative" offset="5" />
</anchored>
<state textres="item_button_armor" />
<script>
function onButtonPress()
local w = Interface.findWindow("reference_groupedlist", "reference.armor");
if w then
Interface.toggleWindow("reference_groupedlist", "reference.armor");
else
w = Interface.openWindow("reference_groupedlist", "reference.armor");
w.init({ sRecordType = "item", sListView = "armor" });
end
end
</script>
</button_text_sm>
</template>


While I will keep the anchor point rules and other things I'll naturally have my own definitions in here to do what I want todo.

Now, I don't know if this is going to work - but I felt that there was high probability that when I take a break shortly - my brain will reflexively BLOT OUT this entire data tracking adventure via keyword searches in notepad++ as a defensive attempt to preserve my sanity. So I'm recording it down here for posterity - so I won't lose the memory of how to add DB related buttons and how they operate in the right hand side of the FGU application.

Now, if you will pardon me, I'm going to go purge my mind. :hurt:

SilentRuin
February 10th, 2021, 22:25
Update - adding the template buttons I created worked. Except for some reason I have to press the button twice before it will actually trigger the onButtonPress() logic - which mystifies me.

SilentRuin
February 10th, 2021, 22:47
Superteddy57 solved it for me in discord - THANK YOU. I had put the new functions inside an older version of the function. Who knew lua could call a function in a function? Anyway I never would have spotted that as I would have thought the parser would drop dead on a mistake like that not just carry on requiring me to click it twice. Just goes to show my stupid is boundless. I miss IDE's :(

Minty23185Fresh
February 11th, 2021, 17:11
I did this work a few years ago, for my Field Filters extension.
I wish I had known, I might have been able to help.
Maybe I should pay a little more attention to the forums.

(The guy says, sliding into home plate, after the game is over.)

Now that you're an expert, it's all pretty slick, isn't it?

SilentRuin
February 11th, 2021, 17:34
I did this work a few years ago, for my Field Filters extension.
I wish I had known, I might have been able to help.
Maybe I should pay a little more attention to the forums.

(The guy says, sliding into home plate, after the game is over.)

Now that you're an expert, it's all pretty slick, isn't it?

As most things - super easy and slick to do some things you want - and near impossible to do other things you want :) As it turns out it worked fine for what I wanted, and I shared to hopefully provide some light on this for anyone else who attempts something like it. If you have more info on this area feel free to add it! I only really told where to start looking and described one of the data structure fields (all I needed to learn about to do my task) :)

Varsuuk
February 13th, 2021, 01:13
This is of course extremely useful from CoreRPG:



-- RECORD TYPE FORMAT
-- ["recordtype"] = {
-- aDataMap = <table of strings>, (required)
-- aDisplayIcon = <table of 2 strings>, (required)
--
-- bExport = <bool>, (optional)
-- nExport = <number>, (optional; overriden by bExport)
-- bExportNoReadOnly = <bool>, (optional; overrides bExport)
-- sExportPath = <string>, (optional)
-- bExportListSkip = <bool>, (optional)
-- sExportListDisplayClass = <string>, (optional)
--
-- bHidden = <bool>, (optional)
-- bID = <bool>, (optional)
-- bNoCategories = <bool>, (optional)
--
-- sListDisplayClass = <string>, (optional)
-- sRecordDisplayClass = <string>, (optional)
-- aRecordDisplayCLasses = <table of strings>, (optional; overrides sRecordDisplayClass)
-- fRecordDisplayClass = <function>, (optional; overrides sRecordDisplayClass)
-- fGetLink = <function>, (optional)
--
-- aGMListButtons = <table of template names>, (optional)
-- aPlayerListButtons = <table of template names>, (optional)
--
-- aCustomFilters = <table of custom filter table records>, (optional)
-- },
--

-- FIELDS ADDED FROM STRING DATA
-- sDisplayText = Interface.getString(library_recordtype_label_ .. sRecordType)
-- sSingleDisplayText = Interface.getString(library_recordtype_single_ .. sRecordType)
-- sEmptyNameText = Interface.getString(library_recordtype_empty_ .. sRecordType)
-- sExportDisplayText = Interface.getString(library_recordtype_export_ .. sRecordType)
-- FIELDS ADDED FROM STRING DATA (only when bID set)
-- sEmptyUnidentifiedNameText = Interface.getString(library_recordtype_empty_nonid _ .. sRecordType)
--

-- RECORD TYPE LEGEND
-- aDataMap = Required. Table of strings. defining the valid data paths for records of this type
-- NOTE: For bExport/nExport, that number of data paths from the beginning of the data map list will be used as the source for exporting
-- and the target data paths will be the same in the module. (i.e. default campaign data paths, editable).
-- The next nExport data paths in the data map list will be used as the export target data paths for read-only data paths for the
-- matching source data path.
-- EX: { "item", "armor", "weapon", "reference.items", "reference.armors", "reference.weapons" } with a nExport of 3 would mean that
-- the "item", "armor" and "weapon" data paths would be exported to the matching "item", "armor" and "weapon" data paths in the module by default.
-- If the reference data path option is selected, then "item", "armor" and "weapon" data paths would be exported to
-- "reference.items", "reference.armors", and "reference.weapons", respectively.
-- aDisplayIcon = Required. Table of strings. Provides icon resource names for sidebar/library buttons for this record type (normal and pressed icon resources)
--
-- bExport = Optional. Same as nExport = 1. Boolean indicating whether record should be exportable in the library export window for the record type.
-- nExport = Optional. Overriden by bExport. Number indicating number of data paths which are exportable in the library export window for the record type.
-- NOTE: See aDataMap for bExport/nExport are handled for target campaign data paths vs. reference data paths (editable vs. read-only)
-- bExportNoReadOnly = Optional. Similar to bExport. Boolean indicating whether record should be exportable in the library export window for the record type, but read only option in export is ignored.
-- sExportPath = Optional. When exporting records to a module, use this alternate data path when storing into a module, instead of the base data path for this record.
-- sExportListDisplayClass = Optional. When exporting records, the list link created for records to be accessed from the library will use this display class. (Default is reference_list)
-- bExportListSkip = Optional. When exporting records, a list link is normally created for the records to be accessed from the library. This option skips creation of the list and link.
--
-- bHidden = Optional. Boolean indicating whether record should be displayed in library, and sidebar options.
-- bID = Optional. Boolean indicating whether record is identifiable or not (currently only items and images)
-- bNoCategories = Optional. Disable display and usage of category information.
-- sEditMode = Optional. Valid values are "play" or "none". If "play" specified, then both players and GMs can add/remove records of this record type. Note, players can only remove records they have created. If "none" specified, then neither player nor GM can add/remove records. If not specified, then only GM can add/remove records.
-- NOTE: The character selection dialog handles this in the custom character selection window class historically, so does not use this option.
--
-- sListDisplayClass = Optional. String. Class to use when displaying this record in a list. If not defined, a default class will be used.
-- sRecordDisplayClass = Optional. String. Class to use when displaying this record in detail. (Defaults to record type key string)
-- aRecordDisplayClasses = Optional. Table of strings. List of valid display classes for records of this type. Use fRecordDisplayClass to specify which one to use for a given path.
-- fRecordDisplayClass = Optional. Function. Function called when requesting to display this record in detail.
-- fGetLink = Optional. Function. Function called to determine window class and data path to use when pressing or dragging sidebar button.
--
-- aGMListButtons = Optional. Table of template names. A list of control templates created and added to the master list window for this record type in GM mode.
-- aPlayerListButtons = Optional. Table of template names. A list of control templates created and added to the master list window for this record type in Player mode.
--
-- aCustomFilters = Optional. Table of custom filter table records. Key = Label string to display for filter;
-- Filter table record format is:
-- sField = Required. String. Child data node that contains data to use to build filter value list; and to apply filter to.
-- fGetValue = Optional. Function. Returns string or table of strings containing filter value(s) for the record passed as parameter to the function.
-- sType = Optional. String. Valid values are: "boolean", "number".
-- NOTE: If no type specified, string is assumed. If empty string value returned, then the string resource (library_recordtype_filter_empty) will be used for display if available.
-- NOTE 2: For boolean type, these string resources will be used for labels (library_recordtype_filter_yes, library_recordtype_filter_no).
--
-- sDisplayText = Required. String Resource. Text displayed in library and tooltips to identify record type textually.
-- sEmptyNameText = Optional. String Resource. Text displayed in name field of record list and detail classes, when name is empty.
-- sEmptyUnidentifiedNameText = Optional. String Resource. Text displayed in nonid_name field of record list and detail classes, when nonid_name is empty. Only used if bID flag set.
--



I was GOING to post a simple example of adding class/race controls since those were two easy ones added in my extension for people to compare against:



function onInit()

aRecords = {
["pcclass"] = {
bExport = true,
sEditMode = "play",
aDataMap = { "pcclass", "reference.pcclass" },
aDisplayIcon = { "button_pcclass", "button_pcclass_down" },
sRecordDisplayClass = "pcclass",
},
["pcrace"] = {
bExport = true,
sEditMode = "play",
aDataMap = { "pcrace", "reference.pcrace" },
aDisplayIcon = { "button_pcrace", "button_pcrace_down" },
sRecordDisplayClass = "pcrace",
},
...
}



NOTE: I have not updated my FG in quite a couple months since when I work slowly, it is a pain to download and git commit all the FG changes to look at etc. I prefer to do it every "epoch" in FG or my work... cos I move at geological speed ;)

SilentRuin
February 13th, 2021, 05:39
That was excellent information! Thank you very much!

SilentRuin
February 23rd, 2021, 19:57
Thanks to Superteddy57 for showing me how to handle this...

Update on an additional problem I had (corrected more semicolons in first post) as I had to override spells which has a function in it its data structure as shown in red below:



aRecordOverrides = {
["spell"] = {
bExport = true,
aDataMap = { "spell", "reference.spelldata" },
sRecordDisplayClass = "power",
aGMListButtons = { "button_Stuff_1", "button_Stuff_2" },
aCustomFilters = {
["Source"] = { sField = "source", fGetValue = getSpellSourceValue },
["Level"] = { sField = "level", sType = "number" },
["Ritual"] = { sField = "ritual", sType = "boolean" },
},
},
};


Feeding this structure to



for kRecordType,vRecordType in pairs(aRecordOverrides) do
LibraryData.overrideRecordTypeInfo(kRecordType, vRecordType);
end


Will not give errors - but also will not work for the filters and will mess up your source filter searches (in this example). You need to provide your own copies of this function in order for the filters to work as it originally did. But this example applies to any function data you are overriding. In my case this function required me to provide two local copies of the original LibraryData5E code.



function getSpellSourceValue(vNode)
return StringManager.split(DB.getValue(vNode, "source", ""), ",", true);
end

function getSpellLevelValue(vNode)
local v = DB.getValue(vNode, "level", 0);
if (v >= 1) and (v <= 9) then
v = tostring(v);
else
v = Interface.getString("library_recordtype_value_spell_level_0");
end
return v;
end


Don't ask me why the getSpellLevelValue as I did not look into the full reason why - it was just the solution Superteddy57 provided and after so many failed attempts by me I was just happy to have it working :)

Xarxus
January 23rd, 2022, 15:46
Okay, I'm also studying the sidebar buttons and I have many doubts, I'm not understanding them.

I created a data_library_personalized.lua file with the following code.

aRecordOverrides = {
["vocation"] = {
bExport = true,
aDataMap = { "vocation", "reference.vocationdata" },
sRecordDisplayClass = "reference_vocation",
sSidebarCategory = "create",
},
};

function onInit()
LibraryData.overrideRecordTypes(aRecordOverrides);
end

The button I get is devoid of writing. First problem, how do I add it and how do I change the icon?

https://www.fantasygrounds.com/forums/attachment.php?attachmentid=51132&stc=1&d=1642952484


My second problem is that pressing the button opens a generic window. I don't know what it is, I don't know how to implement it, and I don't even know how to make a title appear on it.

https://www.fantasygrounds.com/forums/attachment.php?attachmentid=51133&stc=1&d=1642952650

SilentRuin
January 23rd, 2022, 16:01
Without doing the work myself I have no idea. Basically, I find out stuff by trial and error and looking in the 5E and CoreRPG code for how things were done there. All the sidebar stuff has been redone also so I'm not an expert on any of that (This was all written before those changes). Look at what their code does and experiment around with it. Note, my examples were changing existing things not creating a new one. Obviously the new one would need to be reference by something in the broader code also.

Maybe someone else is familiar with what it takes to create a fully functional new entry - I am not. My only advice is to search the rulesets that use one of the current ones to see EVERYWHERE they are used.

Moon Wizard
January 23rd, 2022, 16:52
In addition to the table registration, you also need to define string assets to give the information for what text to display.

<string name="library_recordtype_label_npc">NPCs</string>
<string name="library_recordtype_empty_npc">&#171; New Personality &#187;</string>

Regards,
JPG

Trenloe
January 23rd, 2022, 16:58
Okay, I'm also studying the sidebar buttons and I have many doubts, I'm not understanding them.

I created a data_library_personalized.lua file with the following code.

aRecordOverrides = {
["vocation"] = {
bExport = true,
aDataMap = { "vocation", "reference.vocationdata" },
sRecordDisplayClass = "reference_vocation",
sSidebarCategory = "create",
},
};

function onInit()
LibraryData.overrideRecordTypes(aRecordOverrides);
end

The button I get is devoid of writing. First problem, how do I add it and how do I change the icon?


My second problem is that pressing the button opens a generic window. I don't know what it is, I don't know how to implement it, and I don't even know how to make a title appear on it.
What Moon Wizard said, and also some related info here: https://www.fantasygrounds.com/forums/showthread.php?71797-Adding-New-Campaign-Data-Record-Types-to-the-LibraryData-Script

Xarxus
January 23rd, 2022, 18:41
Great Trenloe, that's what exactly what I was looking for.

Ty all!