PDA

View Full Version : Writing a dynamic cycler



irpagan
May 24th, 2015, 21:26
Hello,

I am attempting to write a dynamic cycler, because of the free form nature of how a skill can be entered the only thing I can count on is that it will be tagged as skilltype = "combat". Weapons attacks are linked to a combat skill so the thought process I had was to look at how the button_cycler.lua processed information.

their is labels {}, and values {}

given that I went with the following;

Creating a dummy control with blank placeholders as;


<template name="cycler_weapon_skill">
<button_stringcycler>
<font>altsheetlabel</font>
<center />
<parameters>
<defaultlabel> - </defaultlabel>
<labels></labels>
<values></values>
</parameters>
</button_stringcycler>
</template>

Then I wrote the onInit() within the control as;


<cycler_weapon_skill name="weapon_skill">
<anchored width="70" height="20">
<top offset="20" />
<right parent="rightanchor" anchor="left" relation="relative" offset="-40" />
</anchored>
<script>
function onInit()
Debug.chat(" onInit() called");

local dbSkill = window.getDatabaseNode().getChild("skillslist");
Debug.chat(" ... dbSkill ... ", dbSkill);

local sListing = {};
local cVar = 0;

for _,s in ipairs(dbSkill) do
Debug.chat("s.skilltype.getValue(); is ", s.skilltype.getValue(), "\n");

if s.skilltype.getValue == "combat" then
cVar = cVar + 1;

sListing[cVar] = s.name.getValue();
end
end

Debug.chat("sListing is :: ", sListing);

-- in this case both labels and values are the same
parameters.labels = sListing;
parameters.values = sListing;

end
</script>
</cycler_weapon_skill>

dbSkill points to databasenode = { charsheet.id-00001.skillslist }

The error I'm getting at the moment, can't seem to get into the loop is Script Error: [string "weapon_skill"]:1: bad argument #1 to 'ipairs' (table expected, got userdata)

What is the error I am making? I'm not even positive this will work to be honest.

Regards,
-d0gb0y

irpagan
May 25th, 2015, 00:19
Alright :) Solved my issues. Best of all it works. Now to see if it actually holds its value after being populated and saved to the DB.

I figured out that

A). labels becomes an array, it begins life as a string.
B). I needed to work with getChildren()

After that it was fairly simple logic and working through what button_cycler was expecting to receive. Here is my code;



function onInit()
local dbSkill = window.getDatabaseNode().getChild("skillslist").getChildren();
local sListing = "";

if dbSkill then
for _,s in pairs(dbSkill) do
if string.lower(s.getChild("skilltype").getValue()) == "combat" then
local sSkill = s.getChild("name").getValue();
sListing = sListing .. sSkill .. "|";
end
end
end

sListing = string.sub(sListing, 1, string.len(sListing)-1);

self.parameters[1].defaultlabel[1] = "???";
self.parameters[1].labels[1] = sListing;
self.parameters[1].values[1] = sListing;

local sLabels = self.parameters[1].labels[1];
local sValues = self.parameters[1].values[1];
local sDefaultLabel = self.parameters[1].defaultlabel[1];

super.onInit();
end


If you see anything I could do to tighten this up a bit please feel free to comment and let me know. So now I know you can create a blank control and populate it on init.

Regards,
-d0gb0y

irpagan
May 25th, 2015, 00:51
I forgot to mention attach the script to the template not the created control. Opposite of what I started with in message #1

damned
May 25th, 2015, 01:02
Well done irpagan.

irpagan
May 25th, 2015, 01:44
Thank you.

irpagan
May 25th, 2015, 03:42
One final point if I may as this just bit me;

This advice is for people like *me* new to Fantasy Grounds and wanting to write scripts etc. When doing anything where calling data you have to pay attention to how far down your nested. When I initially tested this out I was in a separate windowclass named character_combat and everything performed correctly as intended. However; once I moved the control to where it would ultimately belong which was a list named weaponslist with a window class of char_weapons inside character_combat it stopped working with a cannot index a global control with a value of nil.

Luckily I had listened to Trenloe's advice and used the following debug statements;


Debug.chat("window", window);
Debug.chat("window.getDatabaseNode()", window.getDatabaseNode());
Debug.chat("window.getDatabaseNode().getParent()", window.getDatabaseNode().getParent());
Debug.chat("window.getDatabaseNode().getParent().getParent()", window.getDatabaseNode().getParent().getParent());
Debug.chat("window.getDatabaseNode().getParent().getParent().g etChild(\"skillslist\")", window.getDatabaseNode().getParent().getParent().g etChild("skillslist"));
Debug.chat("window.getDatabaseNode().getParent().getParent().g etChild(\"skillslist\").getChildren()", window.getDatabaseNode().getParent().getParent().g etChild("skillslist").getChildren());

This allowed me to find the correct path to the data. Essentially I ended up changing;



local dbSkill = window.getDatabaseNode().getChild("skillslist").getChildren();
to local dbSkill = window.getDatabaseNode().getParent().getParent().g etChild("skillslist").getChildren();


Hope this helps someone :)

Regards,
-d0gb0y

irpagan
May 25th, 2015, 06:08
One more gotcha, when attempting to use the value of the cycler to set the associated skill rank, or grab any information really you need to skill id. While the first cycler works and saves nicely I can't do something like this without modifying my underlying values;



<!-- @comment: Weapon Skill to use for this weapon -->
<cycler_weapon_skill name="weapon_skill">
<anchored width="100" height="20">
<top />
<right parent="rightanchor" anchor="left" relation="relative" offset="-40" />
</anchored>
<script>
function onValueChanged()
if window.weapon_skill.getStringValue() ~= "" then
window.rank.setValue(DB.getValue(window.getDatabas eNode(), "...skillslist." .. window.weapon_skill.getStringValue() .. ".skilltotal", 0));
else
window.rank.setValue(0);
end
end
</script>
</cycler_weapon_skill>

Since this is a learning curve for me I'll update my code journey for a dynamic cycler. The value I need is already available in the generic for used to populate the values. The value is contained in _ so by updating the code like so;



<!-- @Comment: Cycler(cycler_weapon_skill) What Skill to Use? -->
<template name="cycler_weapon_skill">
<button_stringcycler>
<font>altsheetlabel</font>
<center />
<parameters>
<defaultlabel></defaultlabel>
<labels></labels>
<values></values>
</parameters>
<script>
function onInit()
local dbSkill = window.getDatabaseNode().getParent().getParent().g etChild("skillslist").getChildren();
local myLabels = "";
local myValues = "";

if dbSkill then
for _,s in pairs(dbSkill) do
if string.lower(s.getChild("skilltype").getValue()) == "combat" then
local sSkill = s.getChild("name").getValue();
myLabels = myLabels .. sSkill .. "|";
myValues = myValues .. _ .. "|";
end
end
end

myLabels = string.sub(myLabels, 1, string.len(myLabels)-1);
myValues = string.sub(myValues, 1, string.len(myValues)-1);

self.parameters[1].defaultlabel[1] = "???";
self.parameters[1].labels[1] = myLabels;
self.parameters[1].values[1] = myValues;

local sLabels = self.parameters[1].labels[1];
local sValues = self.parameters[1].values[1];
local sDefaultLabel = self.parameters[1].defaultlabel[1];

super.onInit();
end
</script>
</button_stringcycler>
</template>


Now I can get my values in the onValueChanged()

Learning, slowly but learning I am.

Regards,
-d0gb0y

irpagan
May 25th, 2015, 23:15
In the interest of continuing the experiment, and because I had other functionality I wanted in the weapon's list I decided to consolidate the functionality into it's very own .lua file. Probably should have done this in the beginning but I've learned a lot so no issues.

There were some modifications I needed to make, this makes way number three for accomplishing the task;

Here's the latest set of code (remember to register the handlers);

Template Code

<!-- @Comment: Cycler(cycler_weapon_skill) What Skill to Use? -->
<template name="cycler_weapon_skill">
<button_stringcycler>
<font>altsheetlabel</font>
<center />
<parameters>
<defaultlabel></defaultlabel>
<labels></labels>
<values></values>
</parameters>
<script>
function onInit()
local rWindow = window.getDatabaseNode();
window.setStringCycler(rWindow);
super.onInit();
end
</script>
</button_stringcycler>
</template>

char_weapons.lua file function


function setStringCycler(rWindow)
local dbSkill = rWindow.getParent().getParent().getChild("skillslist").getChildren();
local myLabels = "";
local myValues = "";

if dbSkill then
for _,s in pairs(dbSkill) do
if string.lower(s.getChild("skilltype").getValue()) == "combat" then
local sSkill = s.getChild("name").getValue();
myLabels = myLabels .. sSkill .. "|";
myValues = myValues .. _ .. "|";
end
end
end

myLabels = string.sub(myLabels, 1, string.len(myLabels)-1);
myValues = string.sub(myValues, 1, string.len(myValues)-1);

weapon_skill.parameters[1].defaultlabel[1] = "???";
weapon_skill.parameters[1].labels[1] = myLabels;
weapon_skill.parameters[1].values[1] = myValues;

local sLabels = weapon_skill.parameters[1].labels[1];
local sValues = weapon_skill.parameters[1].values[1];
local sDefaultLabel = weapon_skill.parameters[1].defaultlabel[1];

end

All this may be old hat for a lot of folks but for anyone new (like me) sharing the love.

-d0gb0y

chillybilly
May 26th, 2015, 13:23
I've been following this thread and for the life of me, I can't figure out what this does. I'm a total noob in coding, I've just done very minor, rudimentary modification to existing rulests but I'm trying to pick up little things along the way.

irpagan
May 26th, 2015, 14:06
I'm not a lua coder either, trying hard to pick it up as I go along. I do have some other coding experience which helps me relate somewhat -

I'll try and explain my thought process, basically because of the free-form way skills can get entered on the character sheet, and not being able to count on how the individual will type the skill (e.g. weapons(slings) vs. slings) I needed a way to populate a cycler dynamically where skills of type combat existed. In the Weapons Listing I have an associated skill which is where this cycler comes into play.

I started with the theory that if it is in xml then there is generally a way to do the same thing in script. While this may not always be true it was for the cycler.

The first piece is the predefined empty blank in template_char. The second piece is the code which pulls the skills out of the db.xml character sheet . If it is a skill of type combat adds it to the cycler. The parameters is what the button cycler is looking for. That script is in commons\scripts\button_cycler.lua and its template is in commons\template_buttons.xml. I went and put debug statements in each function to trace out the flow. Once the parameters are set I called super.onInit() which invokes the onInit() method in button_cycler kicking it all off. Once you add handler's it refreshes itself whenever a new combat skill is changed, added or deleted. That part isn't here -- this was all about the creating a cycler dynamically instead of declaring it statically.

To be honest it's probably a terrible waste of resources and there may be an easier way to do something like it, I only know what I've discovered so far.



a really good example I found for handlers is in the 3.5.pak dealing with abilities updating associated skills.

chillybilly
May 26th, 2015, 16:23
Hey, thanks for taking the time to explain this to me. I'll have to reread it a couple times but I do think I'm getting the jist of what you're saying. Fantasy Grounds is such an amazing tool and it only gets better when one becomes proficient enough to make some minor tweaks to customize a ruleset.

irpagan
May 26th, 2015, 21:36
I do have a question regarding handlers;

the code fires fine once onInit() but when I modify the underlying skill, or remove it in the skills window, it never gets picked up by the handler (that I can tell). I initially thought this would be similar to what I did for skills which are dependent upon an underlying ability. That code works fine and doesn't seem to cause me any sort of pain at all.

I wrote my handler as;



function onInit()
local nodeChar = getDatabaseNode().getParent();
recalculateWeaponSkills();
DB.addHandler(DB.getPath(nodeChar, "skills"), "onChildUpdate", onStatUpdate);
end

function onClose()
local nodeChar = getDatabaseNode().getParent();
DB.removeHandler(DB.getPath(nodeChar, "skills"), "onChildUpdate", onStatUpdate);
end


Which leads me to a couple of questions;

1. I may not be understanding how the handler determines what it is looking for, I assumed "skills" which is my list is what I would want to
watch for using onChildUpdate in order to trigger the changes.

2. do handlers for Lists operate differently? e.g. should I be doing something more like
DB.addHandler(DB.getPath(nodeChar, "*.skillrank"), "onChildUpdate", onStatUpdate);

3. I am assuming the following are valid handlers for lists (onChildAdded, onChildDeleted, onChildUpdated, onDelete,
onIntegrityChange, onObserverUpdate, onUpdate)


What I am attempting is to trigger reset and recalc on certain fields in the weaponslist if the underlying skill changes.

Any ideas or pointers would be greatly appreciated.

Regards,
-d0gb0y

Moon Wizard
May 26th, 2015, 23:28
1. The "onChildUpdate" event is fired when one of the values is changed in a leaf node under the watched node. There are separate events for "onChildAdded" and "onChildDeleted", but these only go one level down.

2. Every node in the database is either a value, or a parent node (with or without children). The example handler registration you provided may or may not work depending on what your data record fields look like. (i.e. If skillrank is a parent node with child value nodes underneath, then it should work. If skillrank is a value node, then it won't work since value nodes have no children.)

3.
The valid list events are: onFilter, onGainFocus, onListChanged, onListRearranged, onLoseFocus, onSortCompare
https://www.fantasygrounds.com/refdoc/windowlist.xcp

The valid database handlers are: onUpdate, onAdd, onDelete, onObserverUpdate, onChildAdded, onChildUpdate, onChildDeleted, onIntegrityChange
https://www.fantasygrounds.com/refdoc/DB.xcp#addHandler

They are sort of the same but different. The list events are tied to a control in the UI and will be called if that function exists in any scripts attached to the list control, whereas the database handlers have to be explicitly registered.

NOTE: Try checking out the passive Perception skill on the front page of the 5E character sheet, which I think does something similar to what you are trying to do.

Regards,
JPG

irpagan
May 27th, 2015, 00:21
Ah, then if I understand correctly that is the problem. I need to use onListChanged since it's a list; I wasn't sure whether to use that since it's a list not a windowlist. Should have known better.

My data looks like this in the test campaign (I pulled one record);


<skillslist>
<id-00007>
<attribname type="string">rangedcombatrating</attribname>
<name type="string">Bow</name>
<preferredskill type="number">0</preferredskill>
<skillrank type="number">9</skillrank>
<skillshortcut type="windowreference">
<class></class>
<recordname></recordname>
</skillshortcut>
<skilltotal type="number">11</skilltotal>
<skilltype type="string">Combat</skilltype>
<skilltype_hdn type="string">Combat</skilltype_hdn>
<stat type="number">2</stat>
<used type="number">0</used>
</id-00007>
</skillslist>

So onListChanged would cover any changes to attribname, skillrank and skilltotal fields within the list?

I will check out the example you mentioned. Thank you kindly for the quick response.

Regards,
-d0gb0y

irpagan
May 27th, 2015, 02:31
After doing some quick looks at code, and some quick reading;

I believe I'll need to do a onListChanged() for the driving list to update another list in another tab. Now that seems like a bit of a challenge for me linked list wise as I'm not quite sure to go about calling a function in another window (tab) which hasn't been activated yet. The other option and one that might be more viable is to do a data pull and compare the pulled result against the window result, and if the window result is different update the dataset. It's three fields to sync or is that going to cause me similar issues with what's in the updated tab being out of synch with what's in the db?

Opinions?

Regards,
-d0gb0y

Moon Wizard
May 27th, 2015, 07:14
If the data is in the database, you are better off using a DB.addHandler call to set up the handling.

You can do cross window updating, but it's a little messier generally (i.e. use Interface.findWindow specifying the class/data path you are looking for, then drill down to the control to update)

The onListChanged event of a windowlist control is only triggered when a child is added or deleted from the database node linked to the windowlist control.

Given the ruleset coding I have done so far, you were on the right track using DB.addHandler. There are lots of examples in the 3.5E, 4E and 5E rulesets.

Regards,
JPG

Valarian
May 27th, 2015, 07:50
If you want to refer to a value on another tab you could always put the value in as a read-only, hidden, field on the other tab. I've done this on occasion and it makes things easier.

irpagan
May 27th, 2015, 11:06
Thank you both for the advice, ideas. I'll play with it a bit and study some examples and see what works best. I wish there was a way to make the handler say "I just triggered" but then, that would be no fun. :)

irpagan
May 28th, 2015, 04:12
Okay so I figured out I was matching on the wrong thing first off, I initially assumed I should match the list which was named "skills" when I needed to match on the datasource "skillslist" so that was a part of the issue. Secondarily, I wrote a bunch of dummy functions and worked the driving list, the only handlers I could get to trigger were onChildUpdate and onListChanged().

I should be able to work with these now that I have a better understanding of what is happening when. I'm not sure when onGainFocus and onLoseFocus trigger - I assumed that was when the list itself received focus or lost it. I have yet to figure out how to trigger those. nor onChildAdded , onChildDeleted , onUpdate or onDelete. I suspect perhaps those need to be specific to the calling list versus a "listening" list if that makes sense.

Regards,
-d0gb0y

irpagan
June 9th, 2015, 02:10
I am not sure this matters except in my little OCD code world.

I wrote a dynamic string cycler and it works fine with one exception; previously created controls never update to reflect newly added labels and values. Now, if I exit the campaign and come back the values are displayed in every weapon skill cycler. I believe this is because the control has been created previously and I'm missing my window for updating it or I don't know how. I don't know why someone would want to scroll through an existing weapon to use a newly learned skill with it, I suspect they wouldn't. I just feel it's not complete without updating all the existing weapon skill controls.

I did try using onChildUpdate with a function to re-call the weapon_skill.onInit(), this works a little too well - I believe I got a control over a control one with new values, and one with the old values. It was terribly ugly and unreadable :) (see attached screen shot for a laugh)

I also tried using mergerules replace, and resetandadd and add for labels and values in the parameters. That did nothing I could see per se - I am not sure if I could use delete at the template level, the documentation at https://www.fantasygrounds.com/modguide/templates.xcp doesn't seem to indicate I can. Is there a way to either update an existing cycler's values or test for its existance and replace / re-create it if necessary? I would not want to recreate a newly created cycler with the default value for example. I know I can recreate the parameters but I am having issue with replacing the existing with the previously created.

My template char code is as follows;



<template name="cycler_weapon_skill">
<button_stringcycler>
<font>altsheetlabel</font>
<center />
<parameters>
<defaultlabel></defaultlabel>
<labels mergerule="resetandadd"></labels>
<values mergerule="resetandadd"></values>
</parameters>
<script>
function onInit()
Debug.chat(" === TEMPLATE_CHAR cycler_weapon_skill onInit() FIRING === ");
window.setWeaponStringCycler();
super.onInit();
end

function onDrop(x, y, draginfo)
if draginfo.getType() == "string" then
local w = addEntry(true);
w.name.setValue(draginfo.getStringData());
return true;
end
end

</script>
</button_stringcycler>
</template>


The pertinent part (I believe) of char_weapon.lua is;



function setWeaponStringCycler()

Debug.chat(" === setting setWeaponStringCycler() === ")

local myValues = "";
local myLabels = "";
local myDefault = "???";
local mySkills = {};

mySkills = createSkillParameters();

-- NIL Check
if not mySkills then
return;
end

-- Sort the results into something aesthetically pleasing
for k,v in pairsByKeys(mySkills) do
myLabels = myLabels .. k .. "|";
myValues = myValues .. v .. "|";
end

-- Assign strings to variables and remove the trailing pipe symbol
myLabels = string.sub(myLabels, 1, string.len(myLabels) - 1);
myValues = string.sub(myValues, 1, string.len(myValues) - 1);

-- create parameters which will be used onInit() of weapon_skill
-- and passed to the super (button string cycler) as onInit();
weapon_skill.parameters[1].labels[1] = myLabels;
weapon_skill.parameters[1].values[1] = myValues;
weapon_skill.parameters[1].defaultlabel[1] = myDefault;

Debug.chat(" === @OUT >>> weapon_skill.parameters ===", weapon_skill.parameters[1], "\n");
end

-- Create skill parameters
function createSkillParameters()
local dSkill = getDatabaseNode().getParent().getParent().getChild ("skillslist").getChildren();
local mySkills = {};

if dSkill then
for _,s in pairs(dSkill) do
-- NIL Check
if not s.getChild("skilltype") then
return;
end

if string.lower(s.getChild("skilltype").getValue()) ~= "" and string.lower(s.getChild("skilltype").getValue()) == "combat" then
local sSkill = s.getChild("name").getValue();
local sSk = string.lower(sSkill);
--[[
@comment: Alright, because we set DataSkill.skilldata as Weapon ( ... ) and we can't be sure others won't put in something funk-tatsic like Wpns (, Wpn: so we try to catch the most prevelant patterns and go from there. This may need adjusting down the road. The patterns below will also account for artillerist(Trebuchet) etc, and Tactics too except we are denying Tactics in our list since no weapon every devised is dependant upon a tactic.

]]--

if sSk ~= "assassinate" and sSk ~= "command" and sSk ~= "evade" and sSk ~= "guard" and sSk ~= "tactics" then

local x = "%("; --pattern 1
local y = ":"; --pattern 2

local i = string.find(sSkill, x); --search for pattern 1
local j = string.find(sSkill, y); --search for pattern 2

-- Did we get a hit on one of our patterns?
if i ~= nil or j ~= nil then
-- Handle pattern 1 (
if i ~= nil then
sSkill = string.sub(sSkill, i+1, string.len(sSkill)-1);
end

-- Handle pattern 2 :
if j ~= nil then
sSkill = string.sub(sSkill, j+1, string.len(sSkill));
end
end
-- Put value into the array
mySkills[sSkill] = _;

end
end
end
end

return mySkills;
end

-- Use as an Alpha Sort
function pairsByKeys (t, f)
local a = {}
for n in pairs(t) do table.insert(a, n) end
table.sort(a, f)
local i = 0 -- iterator variable
local iter = function () -- iterator function
i = i + 1
if a[i] == nil then
return nil
else
return a[i], t[a[i]]
end
end
return iter
end



Any advice? Am I being to anal?

Regards,
-d0gb0y

irpagan
June 9th, 2015, 04:12
Double Post

irpagan
June 9th, 2015, 04:13
I forgot to add this piece of code to the puzzle; this is the function fired onChildUpdate (triggered by a change in skillslist)


function updWeaponSkills()

for _,wnd in pairs(getWindows()) do

local newSkills = {};
local myLabels = "";
local myValues = "";

Debug.chat(" >>> >>> >>> index ", _, " of wnd ", wnd, " <<< <<< <<< \n");

-- If name is blank then its a new entry doesn't need updated
if wnd.name.getValue() ~= "" then

Debug.chat(" Weapon ", wnd.name.getValue());

newSkills = wnd.createSkillParameters();
Debug.chat(" newSkills ", newSkills);

if not newSkills then
return;
end

-- Sort the results into something aesthetically pleasing
for k,v in wnd.pairsByKeys(newSkills) do
myLabels = myLabels .. k .. "|";
myValues = myValues .. v .. "|";
end

-- Assign strings to variables and remove the trailing pipe symbol
myLabels = string.sub(myLabels, 1, string.len(myLabels) - 1);
myValues = string.sub(myValues, 1, string.len(myValues) - 1);

-- create parameters which will be passed to labels and values
-- hopefully updating our exisiting cycler
wnd.weapon_skill.parameters[1].labels[1] = myLabels;
wnd.weapon_skill.parameters[1].values[1] = myValues;

end
end
onStatUpdate();
end
regards,
-d0gb0y

irpagan
June 9th, 2015, 13:02
Solved the problem, I was missing my call to re-initialize the underlying parameters. setting them for the window is necessary but I still needed to access the super's initialize method.

Setting a variable for the default and then Adding wnd.weapon_skill.super.initialize(myLabels, myValues, myDefault); to the function in post 22 resolves the issue. The cycler now updates like it should whenever a change occurs to the list.

jreddin1
July 3rd, 2015, 16:46
@irpagan

If you're interested, I completely re-wrote the button_string_cycler for my Chatnomicon extension. Honestly, the original is fairly cryptic, and can be difficult to work with. Mine is definitely shorter, and I think a bit clearer. It's not quite what you're doing, but you may find it of interest, academically.

I haven't read all the posts here, but one suggestion is to get rid of sLabel and sValue (if you haven't). It didn't look like you needed it in the XML since you were going to parse the DB, and then you could just go straight to the arrays instead of converting the pipe delimited string.

In lua, you can do stuff like:

aValues[#aValues+1] = new_value
to append to a table, rather than messing with the ".." concantenation operator.

I'm new to lua, too, but learning a lot...

irpagan
July 6th, 2015, 01:15
I'll take a look if you don't mind then. Always eager to learn something new.

In the end I ended up trimming it down to this;

for k,v in pairsByKeys(mySkills) do
weapon_skill.parameters[1].labels[1] = weapon_skill.parameters[1].labels[1] .. k .. "|";
weapon_skill.parameters[1].values[1] = weapon_skill.parameters[1].values[1] .. v .. "|";
end
and then doing the trim on ...[1] to remove the last pipe. Essentially I removed the myLabels and mySkills var's and streamlined a bit. The issue I was attempting to resolve involved a user entered skill which could be in an unwanted format as well as the cycler could potentially already pre-exist. In the template I had to pre-populate a blank list otherwise I got a really ugly error. Side note: I did use the method you describe elsewhere in the ruleset and I'll go back and look at this code now again and see if I can get it a bit tighter.

Regards,
-d0gb0y