Skip to content

Instantly share code, notes, and snippets.

@Geritz
Last active December 16, 2023 20:35
Show Gist options
  • Save Geritz/9f275da9728d0907fdab65fcc20fde5e to your computer and use it in GitHub Desktop.
Save Geritz/9f275da9728d0907fdab65fcc20fde5e to your computer and use it in GitHub Desktop.
Lancer Game Master Toolkit
// LancerGMToolkit.js by Spencer Moran
// V. 1.3.05 - 2/4/2022
//Toolset for building abilities and token macros to use with NPCs with weird sheets
// Intended for use with LancerCoreHelper.js
// Also includes a turntracker for Lancer Combat including Activations!
// To Start enter the command:
// !lncr_gmtk_init
// into the chat window
//
// NOTE: Some buttons require the LancerCoreHelper.js script to be installed. See this gist to pick it up:
// https://gist.github.com/Geritz/f250255d2718477f61ee6ff276d1576a
var LancerGmToolkit = {
//Contains test to avoid duplications.
"roll20objcontains": function (value,type='macro'){
let test = findObjs({
_type: type,
name: value
});
return ((test.length <= 0)? false : true);
},
//Turn order object
"turn_order":{
//Clear the turn order and end combat.
//GM Only.
"clear": function(msg) {
try {
if (!playerIsGM(msg.playerid)){
return;
}
if (typeof state.lncrgmtk === 'undefined'){
sendChat("LANCER GM TOOLS", "ERROR: no state object for gmtk, did you forget to call the init function?");
return;
}
state.lncrgmtk.turns = {};
state.lncrgmtk.round = 1;
sendChat("LANCER GM TOOLS", "&{template:default}{{name=COMBAT HAS ENDED}}");
} catch (e) {
sendChat("LANCER GM TOOLS", "Error hit while attempting to clear state object.");
this.debug();
}
},
//Add the currently selected token by token name.
"add": function(msg,activations,targetname,pos) {
try{
if (!(typeof state.lncrgmtk === 'undefined')){
if (!(typeof state.lncrgmtk.turns === 'undefined')){
log("Adding target to turn order");
state.lncrgmtk.turns[targetname] = {
"max_act": parseInt(activations),
"curr_act": parseInt(activations),
"tokid":msg.selected[pos]._id // HERE IS THE PROBLEM
};
sendChat("LANCER GM TOOLS", "&{template:default}{{name=CHARACTER TURN ADDED}}{{MESSAGE="+targetname+" has been added to the turn order}}");
log("Success");
return;
}
}
sendChat("LANCER GM TOOLS", "ERROR: no state object for gmtk, did you forget to call the init function?");
} catch (e) {
sendChat("LANCER GM TOOLS", "Error hit while attemptign to add a character to the turn order");
sendChat("LANCER GM TOOLS", "TurnOrderStateObject= = " + state.lncrgmtk.turns);
this.debug();
}
},
//Removes an existing entry from the turn order.
//GM Only
"delete": function(msg,targetname) {
try{
if (!playerIsGM(msg.playerid)){
return;
}
if (!(typeof state.lncrgmtk === 'undefined')){
if (!(typeof state.lncrgmtk.turns === 'undefined')){
log("removing from turns");
delete state.lncrgmtk.turns[targetname];
log("success");
sendChat("LANCER GM TOOLS", "&{template:default}{{name=CHARACTER REMOVED}}{{MESSAGE="+targetname+" has been removed from the turn order}}");
return;
}
}
sendChat("LANCER GM TOOLS", "ERROR: no state object for gmtk, did you forget to call the init function?");
} catch (e) {
sendChat("LANCER GM TOOLS", "Error hit while attemptign to delete a character from the turn order");
sendChat("LANCER GM TOOLS", "TurnOrderStateObject= = " + state.lncrgmtk.turns);
this.debug();
}
},
//Display the current turn order and relevant buttons.
"display": function() {
try{
let turn_order = "&{template:default}{{name=Turn Tracker - Round "+state.lncrgmtk.round+"}}";
let current = state.lncrgmtk.turns;
log(current);
for (entry in current){
log(current[entry].curr_act);
let status = (current[entry].curr_act <= 0) ? "DONE" : "ACTIVATE";
let actnum = (current[entry].curr_act <= 0) ? "" : " "+current[entry].curr_act.toString();
log(actnum);
if (current[entry].curr_act <= 0){
turn_order += "{{"+entry+" = ["+status+"](!lncr_stub)[DELETE](!lncr_gmtk_turn del "+entry+")}}";
log(turn_order);
}
else {
turn_order += "{{"+entry+" = ["+status+""+actnum+"](!lncr_gmtk_turn act "+entry+")[DELETE](!lncr_gmtk_turn del "+entry+")}}";
log(turn_order);
}
}
turn_order += "{{[END ROUND](!lncr_gmtk_turn end_round)=[END ENCOUNTER](!lncr_gmtk_turn clear)}}";
sendChat("LANCER GM TOOLS", turn_order);
} catch (e) {
sendChat("LANCER GM TOOLS", "Encountered an error while trying to display the current turn order.");
this.debug();
}
},
//Consume one activation of a unit, marking them as having acted and display current token status effects.
"act": function(targetname) {
try{
log(targetname + " Acting");
if (!(typeof state.lncrgmtk === 'undefined')){
if (!(typeof state.lncrgmtk.turns === 'undefined')){
state.lncrgmtk.turns[targetname].curr_act --;
log(state.lncrgmtk.turns[targetname]);
let tokenid = state.lncrgmtk.turns[targetname].tokid;
let actorObj = getObj('graphic',tokenid);
let activeEffects = "{{ACTIVE EFFECTS=}}"
try{
if(typeof actorObj !== 'undefined'){
let allMarkers = JSON.parse(Campaign().get("token_markers"));
let currentMarkers = actorObj.get("statusmarkers").split(',');
for (let i = 0; i < currentMarkers.length; i++){
_.each(allMarkers, item => {
if(item.tag.toLowerCase() === currentMarkers[i].toLowerCase()){
activeEffects += "{{["+item.name.toUpperCase()+"](!lncr_reference status-"+item.name+")=[**SET / CLEAR**](!set_token_marker "+item.name+")}}";
}
});
}
}
} catch{
log("Caught Exception from act block.");
}
sendChat("LANCER GM TOOLS", "&{template:default}{{name=TURN START}}{{MESSAGE="+targetname+" has started their turn!}}{{Ability Recharge Roll = [[1d6]]}}"+activeEffects);
return;
}
}
sendChat("LANCER GM TOOLS", "ERROR: no state object for gmtk, did you forget to call the init function?");
} catch(e) {
sendChat("LANCER GM TOOLS", "Encountered an error while attempting to activate character "+targetname);
this.debug();
}
},
//Reset all actors to their maximum activations and redisplay turn order.
//GM Only.
"end_round": function(msg) {
try{
if (!playerIsGM(msg.playerid)){
return;
}
let current = state.lncrgmtk.turns;
for (entry in current){
current[entry].curr_act = current[entry].max_act;
}
state.lncrgmtk.turns = current;
state.lncrgmtk.round = state.lncrgmtk.round + 1;
sendChat("LANCER GM TOOLS", "&{template:default}{{name=ROUND "+state.lncrgmtk.round+"}}{{MESSAGE=A new round has started!}}");
this.display();
} catch (e) {
sendChat("LANCER GM TOOLS", "Encountered an error when atttempting to start the next round.");
this.debug();
}
},
"debug": function() {
log(state.lncrgmtk);
}
}, //end of turn_order block.
//Returns an array of strings for a given input.
"extract_string_blocks" : function (inputstring, delimiter = "'"){
let arry = [];
let index = inputstring.indexOf(delimiter,0)
while(inputstring.indexOf(delimiter,index+1) !== -1){
let index2 = inputstring.indexOf(delimiter,index+1);
let content = inputstring.substring(index+1, index2);
if (content !== " "&&content !== ""){
arry.push(inputstring.substring(index+1, index2));
}
index = index2;
}
return arry;
},
"build_command_obj" : function (stringparse){
//log("BCO: "+stringparse);
let stringtoparse = stringparse;
let blocks = this.extract_string_blocks(stringtoparse);
let commandobj = {};
for (i=0;i<blocks.length;i++){
let index = blocks[i].indexOf(":");
let key = blocks[i].substring(0,index)
let value = blocks[i].substring(index+1);
commandobj[key] = value;
}
//log(commandobj);
return commandobj;
},
"add_npc_weapon_ability" : function (msg,stringparse){
if (typeof msg.selected === 'undefined') {
sendChat("LANCER GM TOOLS", "&{template:default}{{name=NO TOKEN SELECTED}}{{You must select a token for this macro}}");
return;
}
let obj = getObj(msg.selected[0]._type,msg.selected[0]._id);
let characterID = obj.get('represents');
let abilityaction = "";
let dict = this.build_command_obj(stringparse);
abilityaction += "&{template:default}";
abilityaction += "{{name="+dict.name.toUpperCase()+" ("+dict.type.toUpperCase()+")}}";
abilityaction += "{{Attack = [[1d20+"+dict.bonus+"]]}}{{**ACC/DIFF** = [[1d6]][[1d6]][[1d6]][[1d6]][[1d6]][[1d6]]}}";
if (dict.range !== ""){
abilityaction += "{{Range ="+dict.range+"}}";
}
if (dict.threat !== ""){
abilityaction += "{{Threat ="+dict.threat+"}}";
}
if (dict.cone !== ""){
abilityaction += "{{Cone ="+dict.cone+"}}";
}
if (dict.line !== ""){
abilityaction += "{{Line ="+dict.line+"}}";
}
if (dict.blast !== ""){
abilityaction += "{{Blast ="+dict.blast+"}}";
}
if (dict.burst !== ""){
abilityaction += "{{Burst ="+dict.burst+"}}";
}
if (dict.explosive !== ""){
abilityaction += "{{EXPLOSIVE ="+dict.explosive+"}}"
}
if (dict.kinetic !== ""){
abilityaction += "{{KINETIC ="+dict.kinetic+"}}";
}
if (dict.energy !== ""){
abilityaction += "{{ENERGY ="+dict.energy+"}}";
}
if (dict.burn !== ""){
abilityaction += "{{BURN ="+dict.burn+"}}";
}
if (dict.heat !== ""){
abilityaction += "{{HEAT ="+dict.heat+"}}";
}
if (dict.effect !== ""){
abilityaction += "{{Effect ="+dict.effect+"}}";
}
if (dict.tag !== ""){
stubs = (dict.tag).split(",");
stubstr = "";
for (stub in stubs) {
stubstr += "["+stubs[stub]+"](!lncr_stub)";
}
abilityaction +="{{Tags =**"+stubstr+"**}}";
}
createObj("ability", {
name: dict.name,
characterid: characterID,
description: "a description",
action: abilityaction,
istokenaction: true
});
sendChat("LANCER GM TOOLS", "/w "+msg.who+" &{template:default}{{name=CUSTOM WEAPON ADDED}}{{"+dict.name+" added, refresh your abilities and token actions.}}");
},
"add_npc_system_ability" : function (msg,stringparse){
if (typeof msg.selected === 'undefined') {
sendChat("LANCER GM TOOLS", "&{template:default}{{name=NO TOKEN SELECTED}}{{You must select a token for this macro}}");
return;
}
let obj = getObj(msg.selected[0]._type,msg.selected[0]._id);
let characterID = obj.get('represents');
let abilityaction = "";
let dict = this.build_command_obj(stringparse);
abilityaction += "&{template:default}{{name="+dict.name.toUpperCase()+" ("+dict.type.toUpperCase()+")}}";
if (dict.trigger !== ""){
abilityaction += "{{Trigger ="+dict.trigger+"}}";
}
if (dict.effect !== ""){
abilityaction += "{{Effect ="+dict.effect+"}}";
}
if (dict.tag !== ""){
stubs = (dict.tag).split(",");
stubstr = "";
for (stub in stubs) {
stubstr += "["+stubs[stub].toUpperCase()+"](!lncr_stub)";
}
abilityaction +="{{Tags =**"+stubstr+"**}}";
}
createObj("ability", {
name: dict.name,
characterid: characterID,
description: "a description",
action: abilityaction,
istokenaction: true
});
sendChat("LANCER GM TOOLS", "/w "+msg.who+" &{template:default}{{name=CUSTOM SYSTEM ADDED}}{{"+dict.name+" added, refresh your abilities and token actions.}}");
},
"pass_through_command" : function (msg, stringparse){ //Used to call ability macros from API Buttons. I don't know a better way to do this.
if (!playerIsGM(msg.playerid)) return;
//Roll20 is processing the table when it is being passed and removing the templating instructions. We need to add them back.
let output = "&{template:default}"+stringparse;
sendChat("LANCER GM TOOLS", output);
},
//Adds a custom weapon to a character sheet as an ability. /DEPRECATED - Here for backwards compatibility
"addCustomWeapon" : function (msg,name,range,damage1,damtype1,damage2,damtype2,bonus,accdiff,wepclass,desc) {
if (typeof msg.selected === 'undefined') {
sendChat("LANCER GM TOOLS", "&{template:default}{{name=NO TOKEN SELECTED}}{{You must select a token for this macro}}");
return;
}
let obj = getObj(msg.selected[0]._type,msg.selected[0]._id);
let characterID = obj.get('represents');
let abilityaction = "";
//Present as table
abilityaction += "&{template:default}";
//Attack Name
abilityaction += "{{name="+name+"}}";
abilityaction += "{{Weapon Class = "+wepclass+"}}";
abilityaction += "{{Attack Roll = [[1d20+"+bonus+"]]}}";
if (!(accdiff.toLowerCase() === "none")){
abilityaction += "{{A/D BONUS = "+accdiff+"}}";
}
abilityaction +="{{ACC/DIFF = [[1d6]][[1d6]][[1d6]][[1d6]][[1d6]][[1d6]][[1d6]]}}";
if (!(range === "0")){
abilityaction +="{{Range = "+range+"}}";
}
if (!(damage1 === "0")){
abilityaction +="{{Damage1 = [["+damage1+"]] "+damtype1+"}}";
}
if (!(damage2 === "0")){
abilityaction +="{{Damage2 = [["+damage2+"]] "+damtype2+"}}";
}
abilityaction += "{{"+desc+"}}";
createObj("ability", {
name: name,
characterid: characterID,
description: "a description",
action: abilityaction,
istokenaction: true
});
sendChat("LANCER GM TOOLS", "/w "+msg.who+" &{template:default}{{name=CUSTOM WEAPON ADDED}}{{"+name+" added, refresh your abilities and token actions.}}");
}, //AddCustomWeapon
//Adds a custom system to a character sheet as an ability. /DEPRECATED - Here for backwards compatibility
"addCustomSystem" : function (msg,name,content) {
if (typeof msg.selected === 'undefined') {
sendChat("LANCER GM TOOLS", "&{template:default}{{name=NO TOKEN SELECTED}}{{You must select a token for this macro}}");
return;
}
let obj = getObj(msg.selected[0]._type,msg.selected[0]._id);
let characterID = obj.get('represents');
let abilityaction = "";
//Present as table
abilityaction += "&{template:default}";
//Attack Name
abilityaction += "{{name="+name+"}}";
abilityaction += "{{Effect="+content+"}}";
createObj("ability", {
name: name,
characterid: characterID,
description: "a description",
action: abilityaction,
istokenaction: true
});
sendChat("LANCER GM TOOLS", "/w "+msg.who+" &{template:default}{{name=CUSTOM SYSTEM ADDED}}{{"+name+" added, refresh your abilities and token actions.}}");
}, //AddCustomSystem
//Adds a number after the name of each selected token to differentiate them prior to adding them to the turn order.
"enumerateTokens" : function (msg) {
if (!playerIsGM(msg.playerid)){
return;
}
if (typeof msg.selected === 'undefined'){
sendChat("LANCER GM TOOLKIT", "NO TOKEN SELECTED");
return;
}
for (let i = 0; i < msg.selected.length; i++){
let obj = getObj('graphic', msg.selected[i]._id);
if (typeof obj !== 'undefined'){
log(obj);
let enumname = obj.get('name');
obj.set('name',enumname + " " + (i+1));
}
}
},
//Initializes the script by creating the state object needed as well as relevant macros.
'initialize' : function(msg){
state.lncrgmtk = {
"turns": {}
};
let chatstring = "/w " + msg.who + " &{template:default}{{name=Generating LANCER GM Toolkit Macros...}}";
let numMacros = 0;
if (!playerIsGM(msg.playerid)){
return;
}
if (!this.roll20objcontains("Display-Turn-Order")){
createObj('macro', {
_playerid: msg.playerid,
name: "Display-Turn-Order",
action: "!lncr_gmtk_turn order",
visibleto: '',
istokenaction: false
});
chatstring += "{{Display-Turn-Order}}";
numMacros++;
}
if (!this.roll20objcontains("0-Add-Turn")){
createObj('macro', {
_playerid: msg.playerid,
name: "0-Add-Turn",
action: "!lncr_gmtk_turn add ?{Turns?|1|2|3} @{selected|token_name}",
visibleto: '',
istokenaction: true
});
chatstring += "{{0-Add-Turn}}";
numMacros++;
}
if (!this.roll20objcontains("Add-Custom-Weapon")){
createObj('macro', {
_playerid: msg.playerid,
name: "Add-Custom-Weapon",
action: "!lncr_gmtk_add_npc_weapon_ability 'name:?{Name?|}' 'bonus:?{Attack bonus?|}' 'type:?{Type|Aux|Main|Heavy|Superheavy} ?{Class|Cannon|CQB|Rifle|Launcher|Melee|Nexus|Other}' 'range:?{Range|}' 'threat:?{Threat|}' 'cone:?{Cone|}' 'line:?{Line|}' 'blast:?{Blast|}' 'burst:?{Burst|}' 'explosive:?{Explosive|}' 'kinetic:?{Kinetic|}' 'energy:?{Energy|}' 'burn:?{Burn|}' 'heat:?{Heat|}' 'effect:?{Effect|}' 'tag:?{Comma Delimited Tags|}'",
visibleto: '',
istokenaction: false
});
chatstring += "{{Add-Custom-Weapon}}";
numMacros++;
}
if (!this.roll20objcontains('Add-Custom-System')){
createObj('macro', {
_playerid: msg.playerid,
name: "Add-Custom-System",
action: "!lncr_gmtk_add_npc_system_ability 'name:?{Name?|}' 'type:?{Type|Trait|Quick Tech|Full Tech|System|Reaction}' 'trigger:?{Trigger|}' 'effect:?{Effect|}' 'tag:?{Comma Delimited Tags|}'",
visibleto: '',
istokenaction: false
});
chatstring += "{{Add-Custom-System}}";
numMacros++;
}
if (!this.roll20objcontains('Enumerate-Tokens')){
createObj('macro', {
_playerid: msg.playerid,
name: "Enumerate-Tokens",
action: "!lncr_gmtk_enumTok",
visibleto: '',
istokenaction: false
});
chatstring += "{{Enumerate-Tokens}}";
numMacros++;
}
if (numMacros == 0) {
chatstring += "{{All macros are already defined}}";
}
sendChat("LANCER GM TOOLS",chatstring);
}
};
//Command handler
on('chat:message', function(msg){
"use strict";
if('api' !== msg.type) {
return;
}
var args = msg.content.split(/\s+/);
let stragg = function(startpos=1){
let aggstring = ""
for (let i = startpos; i < args.length; i++)
{
aggstring += args[i]
if (i+1 !== args.length){
aggstring += " ";
}
}
return aggstring.toLowerCase();
};
switch (args[0]){
case '!lncr_gmtk_addWep':
LancerGmToolkit.addCustomWeapon(msg,args[1],args[2],args[3],args[4],args[5],args[6],args[7],args[8],args[9],stragg(10));
break;
case '!lncr_gmtk_addSys':
LancerGmToolkit.addCustomSystem(msg,args[1],stragg(2));
break;
case '!lncr_gmtk_init':
LancerGmToolkit.initialize(msg);
break;
case '!lncr_gmtk_turn':
if(args[1].toLowerCase() === "order"){
LancerGmToolkit.turn_order.display();
} else if (args[1].toLowerCase() === "act") {
LancerGmToolkit.turn_order.act(stragg(2));
} else if (args[1].toLowerCase() === "debug"){
LancerGmToolkit.turn_order.debug();
} else if (args[1].toLowerCase() === "add"){
for (let itr = 0; itr < msg.selected.length; itr++){
let tokobj = getObj('graphic', msg.selected[itr]._id);
if (typeof tokobj !== 'undefined'){
LancerGmToolkit.turn_order.add(msg,args[2],tokobj.get('name').toLowerCase(),itr);
} else {
sendChat("LANCER GM TOOLS", "No token selected for add operation.");
}
}
//LancerGmToolkit.turn_order.add(msg,args[2],stragg(3));
} else if (args[1].toLowerCase() === "del"){
LancerGmToolkit.turn_order.delete(msg,stragg(2));
} else if (args[1].toLowerCase() === "clear"){
LancerGmToolkit.turn_order.clear(msg);
} else if (args[1].toLowerCase() === "end_round"){
LancerGmToolkit.turn_order.end_round(msg);
}
break;
case '!lncr_gmtk_enumTok':
LancerGmToolkit.enumerateTokens(msg);
break;
case '!lncr_gmtk_tokSwap':
LancerGmToolkit.table_token_sawp(msg, args[1]);
break;
case '!lncr_gmtk_add_npc_weapon_ability':
LancerGmToolkit.add_npc_weapon_ability(msg, stragg(1));
break;
case '!lncr_gmtk_add_npc_system_ability':
LancerGmToolkit.add_npc_system_ability(msg,stragg(1));
break;
case '!lncr_gmtk_pass_through_command':
LancerGmToolkit.pass_through_command(msg,stragg(1));
break;
default:
break;
}
return;
});
on('ready', function() {
"use strict";
if(state.lncrgmtk) {
log("lncrgmtk exists");
if (state.lncrgmtk.turns){
log("lncrgmtk.turns exists");
}
} else {
log("lncr_gmtk object is not initialized!");
}
if(!LancerGmToolkit.roll20objcontains('LANCER GM TOOLKIT','handout')){
createObj("handout",{
name: 'LANCER GM TOOLKIT',
showplayers: false,
});
}
let handoutobj = findObjs({_type: "handout", name: "LANCER GM TOOLKIT"})[0];
let content = "Welcome to the LANCER GM TOOLKIT! To get started using this tool type \"!lncr_gmtk_init\" into the chat window";
handoutobj.get('notes', notes => handoutobj.set('notes', content));
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment