I am always reluctant to share code, because there is so much to unpack, but let's try. First the user presses a button in the sheet that reads like this (T, NAC, SK, SPM, and WPN are codes that we use that are unique for each repeating section and are shorter way to refer to it that the whole repeating_xxx (so for example, an Attribute name could be repeating_talents_rOwId_T_Name)) value="!Earthdawn~ charID: @{character_id}~ Chatmenu: Link: T : @{T_RowID}" This sends a command to the API Paser (Chatmenu: Link: ) that is processed here case "link": { // chatmenu: Link: (code - T, NAC, SK, SPM, WPN): rowID // List all existing links for this rowID and ask if to be removed. And a button to add a link. let pre = Earthdawn.buildPre( ssa[ 2 ], ssa[ 3 ]); s = "Managing links for " + Earthdawn.codeToName( ssa[ 2 ] ) + " " + Earthdawn.getAttrBN( this.charID, pre + "Name", "") + ". " + Earthdawn.texttip( "(hover)", "Links are used to automatically integrate a value from another ability in the calculation of this ability. " + "For example, a Free Talent or Free Matrix should be linked to a Discipline so that its Rank automatically updates when the Circle is updated. " + "A Knack would be linked to the referenced Talent, so that the Knack rank automatically uses the Talent Rank. " + "Links can also be used to create combos between elements, such as Surprise Short Sword, " + "that could be linked with the Talent Surprise Strike and a Weapon Short Sword to get the total damage roll, " + "or Tiger Dance that could be linked to Talents Tiger Spring and Air Dance to roll the combination of the 2 Talents)"); addButtonWithCharID( "Add a Link", "ChatMenu: Linkadd1: " + ssa[ 2 ] + ": " + ssa[ 3 ] + ": ?{What are we linking this to|Talent,T|Discipline,DSP|Skill,SK|Weapon,WPN|Thread Item,TI|Attribute}: " + "?{Name to link to (Make sure substring is an exact match, including case. Example - Melee)}", "Link this to an Talent, Discipline Circle, Skill, Weapon, Thread Item or Attribute, such that this item will use that items Rank." , Earthdawn.Colors.param, Earthdawn.Colors.paramfg); lst = Earthdawn.getAttrBN( this.charID, pre + "LinksGetValue", "" ); // repeating_talents_xxx_T_name+repeating_talents_xxx_T_name2,another let lst2 = Earthdawn.getAttrBN( this.charID, pre + "LinksGetValue_max", "" ); if( lst2.length < 5 ) lst2 = ""; // strip out any (0) that was put here. if( lst ) lst = lst.split( "," ); if( lst2 ) lst2 = lst2.split( "," ); if( !lst || !lst2 || lst.length == 0 ) s += "Existing links: None."; else for( let i = 0; i < lst.length; ++i ) { let t = lst2[ i ], att = !t.startsWith( "repeating_" ), code = Earthdawn.repeatSection( 3, t ); s += "Remove "; if( att ) addButtonWithCharID( lst[ i ].trim(), "ChatMenu: LinkRemove: " + ssa[ 2 ] + ": " + ssa[ 3 ] + ": Attribute: " + lst2[ i ], "Make this item no longer linked to this Attribute.", Earthdawn.Colors.param2, Earthdawn.Colors.param2fg); else { let tmp = "item."; tmp = Earthdawn.codeToName( code ) + "."; addButtonWithCharID( lst[ i ].trim(), "ChatMenu: LinkRemove: " + ssa[ 2 ] + ": " + ssa[ 3 ] + ": " + code + ": " + Earthdawn.repeatSection( 2, t ), "Make this item no longer linked to this " + tmp, Earthdawn.Colors.param2, Earthdawn.Colors.param2fg); } } this.chat( s.trim(), Earthdawn.whoTo.player | Earthdawn.whoFrom.api | Earthdawn.whoFrom.noArchive, "API" ); } break; // end link Result is : The button in "Add a Link" is a new chat command to the API linkadd1: case "linkadd1": { // ChatMenu: linkadd1: (code: T, NAC, SK, SPM, WPN): (rowID): (Attribute, T, SK, DSP, TI, or WPN) : (name string to search for)
// User has given us a string to attempt to link to. Search all entries to see if there is a match. Confirm the match.
if( ssa.length < 6 || ssa[ 5 ].length < 2 ) // Make sure got a valid command line.
return;
let attrib = (ssa[ 4 ] === "Attribute"), skipmost = false;
if( attrib ) { // Check for the "real" attributes first, and if you find one, don't search for any more.
let tar = [ "dex", "str", "tou", "per", "wil", "cha" ],
t = Earthdawn.safeString( ssa[ 5 ] ).toLowerCase(),
effect = ( t.indexOf( "-effect" ) !== -1 ),
step = ( t.indexOf( "-step" ) !== -1 );
for( let i = 0; i < tar.length; ++i )
if( t.indexOf( tar[ i ] ) !== -1 ) {
ssa[ 5 ] = Earthdawn.safeString( tar[ i ].slice(0, 1)).toUpperCase() + tar[ i ].slice( 1 ) + (effect ? "-Effect" : "") + (step ? "-Step" : "");
skipmost = true;
}
}
if( !skipmost ) {
let attributes = findObjs({ _type: "attribute", _characterid: this.charID }),
found = [];
// if( ssa[ 4 ] === "T" ) {
// let tar = [ "SP-Spellcasting-Effective-Rank", "SP-Patterncraft-Effective-Rank", "SP-Elementalism-Effective-Rank", "SP-Illusionism-Effective-Rank",
// "SP-Nethermancy-Effective-Rank", "SP-Shamanism-Effective-Rank", "SP-Wizardry-Effective-Rank", "SP-Power-Effective-Rank", "SP-Willforce-Effective-Rank" ];
// for( let i = 0; i < tar.length; ++i )
// if( tar[ i ].indexOf( ssa[ 5 ] ) !== -1 ) {
// attrib = true;
// ssa[ 4 ] = "Attribute";
// ssa[ 5 ] = tar[ i ];
// }
// }
if( attrib ) {
_.each( attributes, function (att) {
if ( att.get( "name" ).indexOf( ssa[ 5 ] ) != -1)
found.push( att );
}); // End for each attribute.
} else { // It is a repeating section, check each name.
let lookfor = "_" + ssa[ 4 ] + "_Name",
strMatch = Earthdawn.safeString( ssa[ 5 ] ).toLowerCase();
_.each( attributes, function (att) {
if( Earthdawn.safeString( att.get("name")).endsWith( lookfor ) && Earthdawn.safeString( att.get( "current")).toLowerCase().indexOf( strMatch ) != -1)
found.push( att );
}); } // End for each attribute.
if( found.length == 0 ) {
this.chat( "No matches found.", Earthdawn.whoFrom.apiWarning);
break;
} else if ( found.length == 1 )
ssa[ 5 ] = attrib ? found[ 0 ].get( "name" ) : Earthdawn.repeatSection( 2, found[ 0 ].get( "name" ) );
// This option immediately falls into lindadd2.
else {
s = found.length + " matches found. Select which to Link: ";
for( let i = 0; i < found.length; ++i )
addButtonWithCharID( ssa[ 4 ] + " " + ( attrib ? found[ i ].get( "name" ) + " " : "") + found[ i ].get( "current" )
, "ChatMenu: Linkadd2: " + ssa[ 2 ] + ":" + ssa[ 3 ] + ": " + ssa[ 4 ] + ": "
+ (attrib ? found[ i ].get( "name" ) : Earthdawn.repeatSection( 2, found[ i ].get( "name" ) )),
"Add this Link." , Earthdawn.Colors.param, Earthdawn.Colors.paramfg);
this.chat( s.trim(), Earthdawn.whoFrom.apiWarning | Earthdawn.whoFrom.noArchive, "API" );
break;
} } } // end linkAdd1
This button will via query ask for name and code of another item to link, and will look how many it finds. If it finds just one, it will create the link, If it finds >1 it will post a message with yet another button linkadd2 to chose from. The 2 possibilities are in the screenshot below case "linkadd2": { // User has pressed a button telling us which exact one to link to OR we fell through from above due to only finding one candidate.
// Here we do the actual linking.
// ChatMenu: linkadd2: (code: T, NAC, SK, SPM, WPN): (rowID): (Attribute, T, SK, DSP, TI, or WPN): link rowID or attribute name
// First code/rowID combo is row that is making link (getting a value).
// Second code/rowID combo is row that is being linked to (providing a value).
// Can make a link T, NAC, SK, SPM, WPN
// Can link to T, SK, WPN, DSP, TI, attributes???. Maybe static talents. // note removed NAC
if( ssa.length < 6 || ssa[ 5 ].length < 2 ) // Make sure got a valid command line.
return;
// walk links. Check to make sure that nothing links back to this (or anything else in the tree).
function walkLinks( nextCode, nextRow, badLink, nameList ) { // Check to make sure row being linked does not reference row adding link.
let links = Earthdawn.getAttrBN( edParse.charID, Earthdawn.buildPre( nextCode, nextRow) + "LinksGetValue_max" );
if( links ) {
let alinks = links.split();
for( let i = 0; i < alinks.length; ++i ) {
if( alinks[ i ].indexOf( badLink ) !== -1 ) {
edParse.chat( "Error! attempted circular reference: " + nameList + " to "
+ Earthdawn.getAttrBN( edParse.charID, Earthdawn.buildPre( nextCode, nextRow) + "Name", Earthdawn.whoFrom.apiWarning ));
return false;
} else if( alinks[ i ].startsWith( "repeating_" ))
if ( !walkLinks( Earthdawn.repeatSection( 3, alinks[ i ]), Earthdawn.repeatSection( 2, alinks[ i ]), badLink,
nameList + " to " + Earthdawn.getAttrBN( edParse.charID, Earthdawn.buildPre( nextCode, nextRow) + "Name", "" )))
return false;
} }
return true;
}; // end walkLinks()
if( !walkLinks( ssa[ 4 ], ssa[ 5 ], ssa[ 3 ], Earthdawn.getAttrBN( this.charID, Earthdawn.buildPre( ssa[ 2 ], ssa[ 3 ]) + "Name", "" )))
return;
let att = (ssa[ 4 ] === "Attribute");
// Given a fully qualified argument name, it will Add the Display and Links.
function linkAdd( linkName, dispAdd, linkToAdd) {
let obj = Earthdawn.findOrMakeObj({ _type: "attribute", _characterid: edParse.charID, name: linkName });
let dlst = obj.get( "current" ),
llst = obj.get( "max" );
if( !llst || llst.length < 5 ) { // there are no existing links, so we are adding the first one.
llst = [];
dlst = [];
} else { // There are existing links.
llst = llst.split( "," );
dlst = dlst.split( "," );
if( llst.length != dlst.length ) {
edParse.chat( "Warning! internal data mismatch in linkadd2.", Earthdawn.whoFrom.apiError);
Earthdawn.errorLog( "Warning! internal data mismatch in linkadd2.", edParse);
log( dlst );
log( llst);
llst = [];
dlst = [];
}
}
dlst.push( dispAdd );
llst.push( linkToAdd );
Earthdawn.set( obj, "max", llst.join(), linkToAdd );
//log( "setww " + obj.get( "name" ) + " from: " + obj.get( "current" ) + " to: " + dlst.join());
Earthdawn.setWithWorker( obj, "current", dlst.join(), dispAdd );
let sflag = false;
if( linkName.startsWith( "repeating_" ) ) {
let code = Earthdawn.repeatSection( 3, linkName )
if( code === "T" || code === "NAC" || code === "SK" || code === "WPN" || code === "SPM" )
sflag - true;
} else sflag = true;
if( sflag )
edParse.addSWflag( "Trigger2", linkName );
} // end linkAdd
let t = att ? ssa[ 5 ] : Earthdawn.getAttrBN( this.charID, Earthdawn.buildPre( ssa[ 4 ], ssa[ 5 ] ) + "Name", "" ),
l;
function lnk() {
l = "";
for( let i = 0; i < arguments.length; ++i ) {
let tmp = (att ? arguments[ i ] : Earthdawn.buildPre( ssa[ 4 ], ssa[ 5 ] )) + arguments[ i ];
l += ((i === 0) ? "" : "+" ) + tmp;
}
};
// repeating_talents_xxx_T_name+repeating_talents_xxx_T_name2,another
switch( ssa[ 4 ] ) {
case "Attribute": lnk( ssa[ 5 ] ); break;
case "DSP": lnk( "Circle" ); break;
case "NAC": lnk( "Step" ); break;
case "TI": lnk( "Rank" ); break;
default: lnk( "Effective-Rank" ); break; // T, SK, WPN
}
linkAdd( Earthdawn.buildPre( ssa[ 2 ], ssa[ 3 ]) + "LinksGetValue",
t.replace( /[\,|\+]/g, ""), // absolutely can't have any commas or plus signs in the name.
l); // LinksGetValue are of form comma delimited list of fully qualified attributes, maybe more than one seperated by plus signs.
if( !att ) // LinksProviceValue are comma delimited lists of form (code);(rowID).
linkAdd( Earthdawn.buildPre( ssa[ 4 ], ssa[ 5 ]) + "LinksProvideValue",
Earthdawn.getAttrBN( this.charID, Earthdawn.buildPre( ssa[ 2 ], ssa[ 3 ] ) + "Name", "" ).replace( /[\,|\+]/g, ""),
ssa[ 2 ] + ";" + ssa[ 3 ]);
this.chat( "Linked " + ssa[ 4 ] + "-" + t, Earthdawn.whoFrom.apiWarning | Earthdawn.whoFrom.noArchive, "API" );
this.sendSWflag();
} break; // end linkAdd2