PDA

View Full Version : More onDrop.....



Tenian
August 13th, 2008, 04:47
Background:
In 4E there are Feats that give you access to powers.
The current ruleset allows you to drop a feat onto your character sheet (on the feat section) and it adds the feat to the window list.
On another part of the sheet you can drop a power and it places it correctly in the power list
I've recently modified the windowclass used to store links so it has a <link> element which points to the power. This all works fine.

What I'm trying to do:
When I drop the feat onto the feat list, I want to add the feat AND add the power to the power list.

The problem:
The power list and the feat list are controlled by two separate scripts. Also the code for adding a power is rather long. I'd prefer not to have to duplicate the code. Is there some way I can call the onDrop of another object?

Foen
August 13th, 2008, 08:26
I think you can call the onDrop function directly, provided it is on the same character sheet tab (and hence has been instantiated). Otherwise you can either repeat the code, or abstract it to a code module and try to use the same code for both purposes.

Code modules (like chatmanager.lua and gmidentitymanager.lua) provide global access to common code and are initialised from the base.xml as global objects. They also feature some neat additional events (such as onLogin).

Hope that helps

Stuart

Tenian
August 13th, 2008, 16:01
No they are on different tabs (naturally!).

At the moment I'm stuck...I'm just not seeing what I'm doing wrong (although it's probably obvious)...

The object I drag looks like this:


<featArmorOfBahamut>
<name type="string">Armor of Bahamut</name>
<source type="string">Feat </source>

.....
<link type="windowreference">
<class>powerdesc</class>
<recordname>powerdesc.featpowerChannelDivinity_ArmorOfBahamut@ 4E Players Handbook</recordname>
</link>
</featArmorOfBahamut>


My onDrop looks like:


function onDrop(x, y, draginfo)
if draginfo.isType("shortcut") then
local class, datasource = draginfo.getShortcutData();
local source = draginfo.getDatabaseNode();

if source and class == "powerdesc" then
local srcvalnode = source.getChild("name");
local name = "";
if srcvalnode then
name = srcvalnode.getValue();
end

if name ~= "" then
local newwin = createWindow();
newwin.value.setValue(name);
newwin.shortcut.setValue(draginfo.getShortcutData( ))
--This is what I added---
if source.getChild("link") then
local powernode = source.getChild("link").getTargetDatabaseNode()
print(powernode.getChild("name"));
end
end
end

return true;
end
end



It goes into the source.getChild("link") branch but the getTargetDatabaseNode() fails....

More sleep would probably help too!

Foen
August 13th, 2008, 18:06
Well, you are missing a semi-colon from the end of that line...

Tenian
August 13th, 2008, 18:11
Nope that didn't change the error:

attempt to call field 'getTargetDatabaseNode' (a nil value)

I added:
print(source.getChild("link").getType());

and it returns "windowreference"

I'm just not seeing what I missed.....

Foen
August 13th, 2008, 23:50
Hmm, I think you are trying to access a windowreferencecontrol property from a databasenode.

Try getValue() instead, I think it returns two values: the class and the database path.

Stuart

Tenian
August 14th, 2008, 01:12
Well that explains why getValue() was giving me the class (powerdesc).

Okay, now I have the database node of the power linked inside the feat. Now I just need to add it to the proper place on the power sheet...

I'm just not sure how to do that. Heh

Foen
August 14th, 2008, 06:05
That shouldn't be too difficult, provided you know the database path of the powers list. All tabs share the same top-level database node, which is the character's top-level node, and you can find the node which lists the powers from that.

The easy answer would then be to use the handy copyNode function we prepared earlier to add the new powers to the list.

A better, but more complex, answer would be to check the power doesn't already exist befoe adding it, and when you add it also flag its source (for example that it arises from a Feat, and the Feat is called Armor Of Bahamut). This would allow you to include powers which derived from items and spells, and to remove them when the item is dropped or the spell expires.

Stuart

Tenian
August 15th, 2008, 00:12
Code modules (like chatmanager.lua and gmidentitymanager.lua) provide global access to common code and are initialised from the base.xml as global objects. They also feature some neat additional events (such as onLogin).

Hope that helps

Stuart

Right now the functions I am interested in are associated with one windowlist (the power lists). I want to use them on another windowlist (the feature list).

I think what I want to do is create abstracted versions of the functions on the character sheet object (two levels up from the lists). Those would be in scope for all the tabs and lists on said tabs...correct?

Foen
August 15th, 2008, 06:37
I think a better place to put them would be a global script module, say utilities.lua, which you add to the base.xml file like this:



<script name="Utilities" file="scripts/utilities.lua" />


Then you can call it from almost anywhere using the syntax Utilities.copyNode() etc.

Stuart

Tenian
August 15th, 2008, 15:07
I wondered what the name element did....
guess that explains why just moving the function into those files and calling it unqualified didn't work :)

Foen
August 15th, 2008, 17:08
Script modules are really neat, and they allow you to tap into additional events, like onLogin, as they have persistent scope while the session is running.

A very simple example:



local usercount = 0;

function onInit()
if not User.isHost() then
return; -- only want the host to run this module
end
usercount = 1;
User.onLogin = onLogin;
end

function onLogin(username, activated)
if activated then
usercount = usercount + 1;
else
usercount = usercount - 1;
end
ChatManager.addMessage({font="",sender="ghost in the machine:",text=usercount.." user(s) connected"});
end


This has to be placed in a module which is declared after ChatManager in the base.xml file, because it uses that module.

Stuart

Tenian
August 15th, 2008, 22:55
Okay I understand what the current addPower function is doing ... the problem is I don't know how to abstract it. The existing function looks to be based, heavily, on the windowlist. Unfortunately the windowlist is on a different sheet so I don't think I can access it directly?

So that would leave me changing everything to database calls..but I'm not at all familiar with the character sheet within the database (it looks to be in the campaign folder\db.xml?). If that is it, I see it's full of lines similar to
<holder name="X" owner="true" />. But I don't know what to do with those....

Anyhow here are the sections of the existing code I think I need to rewrite....


function addPower(draginfo)
local sourcenode = draginfo.getDatabaseNode();

local newwin = createWindow();
newwin.toggleDetail();
newwin.setDetailVisible(false);
......
Whole bunch of code to set and format newwin based on the shortcut
........
-- Finally, change our state to visible, since we just added a power
setVisible(true);
window.icon.setState(true);
if window.modenode then
window.modenode.setValue(1);
end
end

Foen
August 16th, 2008, 06:20
It is worth taking a look at the db.xml and comparing its contents to the windows and lists with which you are already familiar. There is a strong correlation between them, with a database node for every stringfield or numberfield.

You may not be able to abstract all the functionality, as anything to do with visibility isn't applicable when you are dealing with database nodes for a hidden tab, but you might be able to abstract enough to make it worth while.

I'd suggest trying to split the node creation/copying functionality from the window/control visibility functionality, by having addPower calling the abstract routine copyPowerNode:



function addPower(draginfo)
local sourcenode = draginfo.getDatabaseNode();
local newwin = createWindow();
local newnode = newwin.getDatabaseNode();
Utilities.copyPowerNode(source, newnode); -- does all the reusable database manipulation
...
Whole bunch of window/control visibility stuff which isn't needed
when dealing with stuff on a hidden sheet tab
...
end


In respect of the holder nodes, they are not strictly part of the database structure. FG uses them to keep track of diferent user permissions (which users can read and/or write to a node) and it adds them and removes them on the fly. As you don't interact with them directly and they don't represent any of the window data, you can ignore them for the time being.

Stuart

Tenian
August 16th, 2008, 17:53
You ever wake up...look at something you wrote the day before and wonder.."How could I be so stupid?"

I finished my abstracted function, it only needs some minor clean up and it will work for all Feat/Power combinations.

Then I'll go back to the original routine and modify it to use the abstracted function.

Tenian
August 16th, 2008, 21:57
It works and stuff. (https://dnd-4e.blogspot.com/2008/08/ever-wake-up-and-realize-youre-stupid.html)

Foen
August 16th, 2008, 22:13
It's looking really good, is your stuff being incorporated in JPG's 4e ruleset?

I'm not following 4e ATM, but imagine I'll take a good look when our group is ready for it.

Stuart

Tenian
August 16th, 2008, 22:54
Yeah, everything I write ends up in 4E_JPG after moon reviews it. Most of my previous onDrop code for making new magic items made it into the latest release (1.0.2)

I assume if no one finds any bugs with this, it'll show up in 1.0.3 or whatever is next :)

Moon Wizard
August 18th, 2008, 20:26
OK, I ended up finding an issue with this approach based on new features I was adding.

Each power now has multiple attacks that can be associated with it (used to be only one), so there is now a windowlist associated with each power entry.

I rebuilt Tenian's code to add the correct child nodes for each attack power to the database, and the data looks fine. However, the visible windowlist that holds the attacks is not updating based on the background database additions. So, I end up with 1+ attack entries in the DB, but 0 attack windows in the list.

If I close the character window completely and re-open, the data and windows are fine. However, if I try to interact with the list before closing/opening, then it generates errors as new windows conflict with the data already in the database.

Any ideas on how to force a windowlist to update based on additions of children to the underlying data?

Thanks,
JPG

Foen
August 18th, 2008, 22:43
Hmm, any chance you could share some pseudo-code or a bit more detail on how this is implemented?

Cheers

Stuart

Tenian
August 19th, 2008, 02:15
In 3 parts due to length:

Okay here's the abstracted function I created (and then modified by moon_wizard):

function addPowerDB(charnode, sourcenode)
-- Validate parameters
if not charnode or not sourcenode then
return;
end

-- Get the powers node
local powertypenode = charnode.createChild("powers").createChild(getPowerType(sourcenode));
local newpower = powertypenode.createChild("power").createChild();

-- Set up the shortcut link
newpower.createChild("shortcut", "windowreference").setValue({class = "powerdesc", recordname = sourcenode.getName()});

-- Set up the basic power fields
newpower.createChild("name", "string").setValue(NodeManager.getSafeChildValue(sourcenod e, "name", ""));
newpower.createChild("source", "string").setValue(NodeManager.getSafeChildValue(sourcenod e, "source", ""));
newpower.createChild("recharge", "string").setValue(NodeManager.getSafeChildValue(sourcenod e, "recharge", "-"));
newpower.createChild("keywords", "string").setValue(NodeManager.getSafeChildValue(sourcenod e, "keywords", "-"));
newpower.createChild("range", "string").setValue(NodeManager.getSafeChildValue(sourcenod e, "range", "-"));

-- Set up the action field
local poweraction = NodeManager.getSafeChildValue(sourcenode, "action", "-");
if poweraction == "Standard Action" then
poweraction = "Standard";
elseif poweraction == "Move Action" then
poweraction = "Move";
elseif poweraction == "Minor Action" then
poweraction = "Minor";
elseif poweraction == "Free Action" then
poweraction = "Free";
elseif poweraction == "Immediate Interrupt" then
poweraction = "Interrupt";
elseif poweraction == "Immediate Reaction" then
poweraction = "Reaction";
end
newpower.createChild("action", "string").setValue(poweraction);

-- Get the description field, which contains any clauses not covered above
local powerdesc = NodeManager.getSafeChildValue(sourcenode, "shortdescription", "");

-- Make sure that the attacks node is empty to start with
local attacknode = newpower.getChild("attacks");
if attacknode then
attacknode.delete();
end
attacknode = newpower.createChild("attacks");

-- Parse the description field for target, attack and hit clauses
local clauses = PowersManager.parseClauses(powerdesc);
local lastAttack = nil;
local finalstr = "";
for i, v in ipairs(clauses) do
local addstr = v.label .. ": " .. v.value;

-- Handle the target clauses
if v.label == "Target" then
if v.value == "One creature" or v.value == "Each creature in blast" or v.value == "Each creature in burst" then
addstr = "";
end
-- Handle the attack clauses
elseif (v.label == "Attack" or v.label == "Secondary Attack" or v.label == "Tertiary Attack") then
-- Find any attack entries in this clause
local attacks = PowersManager.parseAttacks(v.value);
local iFirst = 0;
for i2, v2 in ipairs(attacks) do
if iFirst == 0 then
iFirst = i2;
elseif attacks[iFirst].startpos > v2.startpos then
iFirst = i2;
end
end

-- If we have an attack in this clause, then create a new attack entry
if iFirst > 0 then
lastAttack = attacknode.createChild();

lastAttack.createChild("attackstat", "string").setValue(attacks[iFirst].atkstat);
lastAttack.createChild("attackdef", "string").setValue(attacks[iFirst].defense);
lastAttack.createChild("attackstatmodifier", "number").setValue(attacks[iFirst].bonus);

-- Don't add the string to the final description string, if we already got all the information out of this clause
if attacks[iFirst].endpos > #v.value then
addstr = "";
end
end
-- Handle the hit clause, and make sure we have an attack to link the damage to
elseif lastAttack and v.label == "Hit" then
-- Find any damage entries in this clause
local damages = PowersManager.parseDamages(v.value);
local iFirst = 0;
for i2, v2 in ipairs(damages) do
if iFirst == 0 then
iFirst = i2;
elseif damages[iFirst].startpos > v2.startpos then
iFirst = i2;
end
end

-- If we have a damage entry in this clause, then add the damage information to the current attack entry
if iFirst > 0 then
lastAttack.createChild("damagestat", "string").setValue(damages[iFirst].dmgstat);

if damages[iFirst].damage then
local dice, modifier = ChatManager.parseDiceString(damages[iFirst].damage);
lastAttack.createChild("damagebasicdice", "dice").setValue(dice);
lastAttack.createChild("damagestatmodifier", "number").setValue(modifier);
elseif damages[iFirst].damagemult then
lastAttack.createChild("damageweaponmult", "number").setValue(damages[iFirst].damagemult);
end

-- Clear the attack entry, since we have already added the damage
lastAttack = nil;

-- Remove the part of the clause we already handled,
-- then make sure there is something left to add
local sRemainder = string.sub(v.value, damages[iFirst].endpos);
local sSep = string.sub(sRemainder, 1, 1);
if sSep == "," or sSep == "." then
sRemainder = string.sub(sRemainder, 3);
if string.sub(sRemainder, 1, 4) == "and " then
sRemainder = string.upper(string.sub(sRemainder, 5, 5)) .. string.sub(sRemainder, 6);
end

local sDisplay = string.sub(v.value, 1, damages[iFirst].startpos - 1) .. sRemainder;
if sDisplay == "" then
addstr = "";
else
addstr = v.label .. ": " .. sDisplay;
end
end
end
end

-- Build the final description string
if addstr ~= "" then
if finalstr ~= "" then
finalstr = finalstr .. "; ";
end
finalstr = finalstr .. addstr;
end
end

-- If no attacks found, then make sure we're not empty
if attacknode.getChildCount() == 0 then
attacknode.createChild();
end

-- Space savers
finalstr = string.gsub(finalstr, "vs. Fortitude", "vs. Fort");
finalstr = string.gsub(finalstr, "vs. Reflex", "vs. Ref");
finalstr = string.gsub(finalstr, "Strength vs.", "STR vs.");
finalstr = string.gsub(finalstr, "Constitution vs.", "CON vs.");
finalstr = string.gsub(finalstr, "Dexterity vs.", "DEX vs.");
finalstr = string.gsub(finalstr, "Intelligence vs.", "INT vs.");
finalstr = string.gsub(finalstr, "Wisdom vs.", "WIS vs.");
finalstr = string.gsub(finalstr, "Charisma vs.", "CHA vs.");
finalstr = string.gsub(finalstr, "Strength modifier", "STR");
finalstr = string.gsub(finalstr, "Constitution modifier", "CON");
finalstr = string.gsub(finalstr, "Dexterity modifier", "DEX");
finalstr = string.gsub(finalstr, "Intelligence modifier", "INT");
finalstr = string.gsub(finalstr, "Wisdom modifier", "WIS");
finalstr = string.gsub(finalstr, "Charisma modifier", "CHA");

-- Finally, set the description field with whatever is left
if finalstr == "" then
finalstr = "-";
end
newpower.createChild("shortdescription", "string").setValue(finalstr);
end

Tenian
August 19th, 2008, 02:16
Here's moon_wizard's non-abstracted function:


function addPower(draginfo)
local sourcenode = draginfo.getDatabaseNode();

local newwin = createWindow();
newwin.toggleAttack();
newwin.toggleDetail();
newwin.setAttackVisible(false);
newwin.setDetailVisible(false);

newwin.shortcut.setValue(draginfo.getShortcutData( ));

newwin.name.setValue(NodeManager.getSafeChildValue (sourcenode, "name", ""));
newwin.source.setValue(NodeManager.getSafeChildVal ue(sourcenode, "source", "-"));
newwin.recharge.setValue(NodeManager.getSafeChildV alue(sourcenode, "recharge", "-"));
newwin.keywords.setValue(NodeManager.getSafeChildV alue(sourcenode, "keywords", "-"));
newwin.range.setValue(NodeManager.getSafeChildValu e(sourcenode, "range", "-"));

local srcvalue = NodeManager.getSafeChildValue(sourcenode, "action", "-");
if srcvalue == "Standard Action" then
newwin.action.setValue("Standard");
elseif srcvalue == "Move Action" then
newwin.action.setValue("Move");
elseif srcvalue == "Minor Action" then
newwin.action.setValue("Minor");
elseif srcvalue == "Free Action" then
newwin.action.setValue("Free");
elseif srcvalue == "Immediate Interrupt" then
newwin.action.setValue("Interrupt");
elseif srcvalue == "Immediate Reaction" then
newwin.action.setValue("Reaction");
else
newwin.action.setValue(srcvalue);
end

-- Clear the attack list
newwin.attacks.deleteAll();

-- Get the description to parse
srcvalue = NodeManager.getSafeChildValue(sourcenode, "shortdescription", "");

-- Parse the clauses, and perform special handling on the target/attack/hit clauses
local clauses = PowersManager.parseClauses(srcvalue);
local lastAttack = nil;
local finalstr = "";
for i, v in ipairs(clauses) do
local addstr = v.label .. ": " .. v.value;

if v.label == "Target" then
if v.value == "One creature" or v.value == "Each creature in blast" or v.value == "Each creature in burst" then
addstr = "";
end
elseif (v.label == "Attack" or v.label == "Secondary Attack" or v.label == "Tertiary Attack") then
local attacks = PowersManager.parseAttacks(v.value);

local iFirst = 0;
for i2, v2 in ipairs(attacks) do
if iFirst == 0 then
iFirst = i2;
elseif attacks[iFirst].startpos > v2.startpos then
iFirst = i2;
end
end
if iFirst > 0 then
lastAttack = newwin.attacks.createWindow();

lastAttack.attackstatlabel.setSourceValue(attacks[iFirst].atkstat);
lastAttack.attackdeflabel.setSourceValue(attacks[iFirst].defense);
lastAttack.attackstatlabel.setModifier(attacks[iFirst].bonus);

if attacks[iFirst].endpos > #v.value then
addstr = "";
end
end
elseif lastAttack and v.label == "Hit" then
local damages = PowersManager.parseDamages(v.value);

local iFirst = 0;
for i2, v2 in ipairs(damages) do
if iFirst == 0 then
iFirst = i2;
elseif damages[iFirst].startpos > v2.startpos then
iFirst = i2;
end
end
if iFirst > 0 then
lastAttack.damagestatlabel.setSourceValue(damages[iFirst].dmgstat);
if damages[iFirst].damage then
local dice, modifier = ChatManager.parseDiceString(damages[iFirst].damage);
lastAttack.damagebasicdice.setDice(dice);
lastAttack.damagestatlabel.setModifier(modifier);
elseif damages[iFirst].damagemult then
lastAttack.damageweaponmult.setValue(damages[iFirst].damagemult);
end
lastAttack = nil;

local sRemainder = string.sub(v.value, damages[iFirst].endpos);
local sSep = string.sub(sRemainder, 1, 1);
if sSep == "," or sSep == "." then
sRemainder = string.sub(sRemainder, 3);
if string.sub(sRemainder, 1, 4) == "and " then
sRemainder = string.upper(string.sub(sRemainder, 5, 5)) .. string.sub(sRemainder, 6);
end

local sDisplay = string.sub(v.value, 1, damages[iFirst].startpos - 1) .. sRemainder;
if sDisplay == "" then
addstr = "";
else
addstr = v.label .. ": " .. sDisplay;
end
end
end
end

if addstr ~= "" then
if finalstr ~= "" then
finalstr = finalstr .. "; ";
end
finalstr = finalstr .. addstr;
end
end

-- If no attacks found, then make sure we're not empty
newwin.attacks.checkForEmpty();

-- Space savers
finalstr = string.gsub(finalstr, "vs. Fortitude", "vs. Fort");
finalstr = string.gsub(finalstr, "vs. Reflex", "vs. Ref");
finalstr = string.gsub(finalstr, "Strength vs.", "STR vs.");
finalstr = string.gsub(finalstr, "Constitution vs.", "CON vs.");
finalstr = string.gsub(finalstr, "Dexterity vs.", "DEX vs.");
finalstr = string.gsub(finalstr, "Intelligence vs.", "INT vs.");
finalstr = string.gsub(finalstr, "Wisdom vs.", "WIS vs.");
finalstr = string.gsub(finalstr, "Charisma vs.", "CHA vs.");
finalstr = string.gsub(finalstr, "Strength modifier", "STR");
finalstr = string.gsub(finalstr, "Constitution modifier", "CON");
finalstr = string.gsub(finalstr, "Dexterity modifier", "DEX");
finalstr = string.gsub(finalstr, "Intelligence modifier", "INT");
finalstr = string.gsub(finalstr, "Wisdom modifier", "WIS");
finalstr = string.gsub(finalstr, "Charisma modifier", "CHA");

if finalstr == "" then
finalstr = "-";
end
newwin.shortdescription.setValue(finalstr);

-- Finally, change our state to visible, since we just added a power
setVisible(true);
window.icon.setState(true);
if window.modenode then
window.modenode.setValue(1);
end
end

Tenian
August 19th, 2008, 02:17
And finally...the code that breaks:

Now when powers that add attacks are created via addPowerDB() they work if you open and close the sheet. However if you immediately go to the power section and expand the power's attack area you get nil value errors in this function:



function onInit()
-- Get any custom fields
local labeltext = "";
local valuetext = "";
local srcnodename = "";
if sourcefields then
if sourcefields[1].values then
valuetext = sourcefields[1].values[1];
end
if sourcefields[1].labels then
labeltext = sourcefields[1].labels[1];
end
if sourcefields[1].srcnode then
srcnodename = sourcefields[1].srcnode[1];
end
end

-- Parse the labels to determine the options we should show
for v in string.gmatch(labeltext, "[^|]+") do
labels[#labels+1] = v;
end

-- Parse the labels to determine the options we should show
for v in string.gmatch(valuetext, "[^|]+") do
values[#values+1] = v;
end

-- Get the data node set up and synched
if srcnodename ~= "" then
srcnode = window.getDatabaseNode().createChild(srcnodename, "string");
srcnode.onUpdate = update;
setSourceValue(srcnode.getValue());
end

-- Set the label
setLabel();
end


It throws the error in the:
if srcnodename ~= "" then...
section

Oddly the error doesn't occur if the power added is the first one added to the list.

Also the shortcut portion of addPowerDB doesn't work either...just if you're bored and want to fix it all :)

Tenian
August 19th, 2008, 17:39
if srcnodename ~= "" then
srcnode = window.getDatabaseNode().createChild(srcnodename, "string");
srcnode.onUpdate = update;
setSourceValue(srcnode.getValue());
end


For powers created from addPowerDB() the window.getDatabaseNode() returns nothing. window.getClass() returns the correct value. From reading the help, this means the window is unbound.....is there a way to bind the window back to the database?

Foen
August 19th, 2008, 18:25
I think it depends on how the window is being created. It isn't clear from the posted code, but there are a couple of ways:

- use parentnode.createChild() to create a db node in the list: a window will automatically be created to reflect the db node; or
- use list.createWindow() and then fetch the db node from the newly created window

The second method has the advantage of also returning a handle to the new window, but I think it can sometimes invoke onInit for the window before it is bound. The first method creates the window from the node, and I _think_ that means it is bound from the outset.

Which method did you use?

Stuart

Tenian
August 19th, 2008, 18:45
moon altered my code somewhat....

I assume you are not referring to :

addPowerDB()


-- Get the powers node
local powertypenode = charnode.createChild("powers").createChild(getPowerType(sourcenode));
local newpower = powertypenode.createChild("power").createChild();


addPower()

local newwin = createWindow();



Are you looking for the functions that call addPower and addPowerDB?

Foen
August 19th, 2008, 19:19
Hi Tenian

I'm looking for the context in which charnode is created (the calling routine for addPowerDB, but within the routine for addPower).

I may be barking up the wrong tree anyway.

Stuart

Tenian
August 19th, 2008, 19:51
This is part of what moon changed, I believe..at least it looks slightly different, maybe he just renamed things.

This script is for the ability sheet. It is on a different tab than the powers. Based on previous posts, I understood the best way to add the power would be to stick it into the database.


function onDrop(x, y, draginfo)
if draginfo.isType("shortcut") then
local class, sourcenodename = draginfo.getShortcutData();
local source = draginfo.getDatabaseNode();

if source and class == "powerdesc" then
local name = NodeManager.getSafeChildValue(source, "name", "");

if name ~= "" then
local newwin = createWindow();
newwin.value.setValue(name);
newwin.shortcut.setValue(draginfo.getShortcutData( ))

if source.getChild("link") then
local powerclass, powernodename = source.getChild("link").getValue();

if powerclass == "powerdesc" then
CharSheetCommon.addPowerDB(window.getDatabaseNode( ), DB.findNode(powernodename));
end
end
end
end

return true;
end
end

addPower() is called in 2 places...The drop on a specific power list:

function onDrop(x, y, draginfo)
if draginfo.isType("shortcut") then
local class, datasource = draginfo.getShortcutData();
local source = draginfo.getDatabaseNode();

if source and class == "powerdesc" then
addPower(draginfo);
end

return true;
end
end


And the drop on the power sheet itself (which just finds the correct list and calls it's addPower()):


function onDrop(x, y, draginfo)
if draginfo.isType("shortcut") then
local class, datasource = draginfo.getShortcutData();
local sourcenode = draginfo.getDatabaseNode();

if sourcenode and class == "powerdesc" then
local powertype = getPowerType(sourcenode);

for k,v in pairs(getWindows()) do
if v.getDatabaseNode().getName() == powertype then
v.powerlist.addPower(draginfo);
break;
end
end
end
return true;
end
end

Foen
August 19th, 2008, 21:53
I'm not quite sure about this: your first onDrop looks for a shortcut (class = powerdesc) to a database node. It then looks for a windowreferencefield called link in the original source DB node, and checks again if that link points to a powerdesc node. Finally you set powernodename equal to the path to the node, but I think you already have that path in sourcenodename.

It may be that none of this is causing the problem, but I think there is some unnecessary code in here.

I gues what it boils down to is: the code looks sufficiently complex that it is difficult to determine what the problem is. Sorry I'm not much help :confused:

Stuart

Tenian
August 19th, 2008, 23:02
I fixed the shortcut :)

I think I'm on to another piece of the puzzle as well....

Tenian
August 21st, 2008, 18:04
I'm still convinced this SHOULD be possible:

I changed the addPowerDB() and removed the section where the attack is parsed. Now everything works, except the attack lines do not get added (no kidding).

Next I created a new function in the onInit of the powerlistitem. This new function is designed to take care of parsing the attacks and creating the needed subwindows.

It looks like everything is working, until it gets to the point where it needs to create a new window.....

The code that works (in addPower a few posts back) reads:

lastAttack = newwin.attacks.createWindow();

My code which breaks is

lastAttack = attacks.createWindow();

I just don't understand why....

Tenian
August 21st, 2008, 18:14
I understand now :) A few lines before it there's
local attacks = PowersManager.parseAttacks(v.value);
When my reference to an unqualified attacks occurs...it assumes I mean the local variable named attacks instead of the windowlist named attacks.

I changed the name of the local variable...and all is well....just one last thing to fix.

Foen
August 21st, 2008, 19:02
It's frustrating when that happens - it would be good to have an intelligent development environment for FG which spotted that kind of stuff.

Stuart

Tenian
August 21st, 2008, 19:07
Considering I knock out my code by having somewhere between 1 and 5 different copies of notepad open...any development tool would probably be an improvement :)

I keep telling myself, it's not worth looking into one because I'll be done after I just finish adding this one more thing.....

Foen
August 21st, 2008, 19:13
:D

Stuart

Tenian
August 22nd, 2008, 01:12
It was a pain but I got it working.

I re-abstracted the addPowerDB() function.

the addPower() function now does some setup and then calls addPowerDB()

I made a new function that handles adding the attack items to the windowlist (addAttacks())

addAttacks is called in 3 different places to handle various conditions:

1) If you open the character sheet then go to the power tab and drop a power. It calls addAttacks right away

2) If you open the character sheet and go right to the ability tab and drop a linked feat/power combo. addAttacks() gets called in the onInit() of the power's window list display class

3) If you open the character sheet go to the power tab, and then at some later point in time drop a linked feat/combo onto the ability page. addAttacks() gets called on the onListRearranged of the powerlist. This one took a while to figure out.
The problem was if the powerlists had been instanced then the onInit was called before the database item was fully populated. Which meant the values weren't available to be parsed. Using onListRearranged triggers after the item is fully entered (because the items are sorted based on the name).

Oh and setting the shortcut on the items from another sheet was another problem. I just figured out a way to do that in addAttacks()

Foen
August 22nd, 2008, 06:00
Well done, what's your next "one more thing..."?

Tenian
August 22nd, 2008, 10:48
Who knows? :) Every time I finish a project I think I'm done...then I play a session or read a source book and I have a new project :)

Bidmaron
August 22nd, 2008, 11:39
So this is now in the 4e ruleset on FUM?

Tenian
August 22nd, 2008, 11:44
It will be when the next revision is released (provided it makes it through testing).

I don't know how the other developers work, but I usually grab a the latest version of 4E_JPG, make modifications, and then email them to moon_wizard.

He tests them, makes any changes he desires, and then rolls them into the next release.

So the linked feat/powers and multi attacks will probably appear in 1.0.3

Tenian
August 23rd, 2008, 00:49
Apparently my new one more thing is the inclusion of "Quirks" on magic items. WoTC decided to add this in the FR Campaign Guide.

Foen
August 23rd, 2008, 06:48
I can only guess what a "quirk" is, having no 4e FR material and not having read my 4e rule books yet.

Sounds like more fun lies ahead :)

Stuart

Tenian
August 23rd, 2008, 10:38
Its like a minor curse. For example one of the items in the book is great, but it's quirk is NPCs with a reptile keyword get certain bonuses against you.

You can see the quirk text here (https://4.bp.blogspot.com/_uDFpksWt8LQ/SK9qeTvhIII/AAAAAAAAAt0/g3XHXPA9Wfg/s1600-h/quirks.jpg)

Foen
August 23rd, 2008, 10:53
Its like a minor curse.

A bit like the FG programming urge.