336 lines
10 KiB
JavaScript
336 lines
10 KiB
JavaScript
var ConanAPI = ConanAPI || (function()
|
|
{
|
|
'use strict';
|
|
|
|
let version = '2.1',
|
|
DeadTokenImgSrc = 'https://s3.amazonaws.com/files.d20.io/images/120063604/QZmTLXMVBSqFoqENP6LTkA/thumb.png?15864718355',
|
|
WoundMarkers = ['Wounds1::1402922', 'Wounds2::1402925', 'Wounds3::1402927','Wounds4::1644476','Wounds5::1644475'],
|
|
TraumaMarkers = ['Trauma1::1644483', 'Trauma2::1644484', 'Trauma3::1644485','Trauma4::1644486','Trauma5::1644482'],
|
|
//BandagedWoundMarkers = ['BNumber1::1644478', 'BNumber2::1644479', 'BNumber3::1644480','BNumber4::1644481','BNumber5::1644482'],
|
|
BandagedWoundMarker = 'BNumber1::1644478',
|
|
combatFlashing = {delay:250, maxCntr:4},
|
|
combatFlashCntr = 0,
|
|
dyingTokenConfig = {delay:25, rotationDirection: -1, rotationValue: 10, beginningRotaton: 0},
|
|
doomTokenName = 'DoomMomentum',
|
|
|
|
getInteger = function(obj)
|
|
{
|
|
if (isInteger(obj))
|
|
return parseInt(obj);
|
|
|
|
return NaN;
|
|
},
|
|
|
|
isInteger = function(obj) {
|
|
if (isNumeric(obj))
|
|
return Number.parseFloat(obj) == Number.parseInt(obj);
|
|
|
|
return false;
|
|
},
|
|
|
|
isNumeric = function(obj) {
|
|
return !isNaN(obj - parseFloat(obj));
|
|
},
|
|
|
|
/**
|
|
* Generic token bar value adjustment
|
|
*/
|
|
updateTokenBar = function(tokenName, barName, barAdjustValue, barMin, barMax)
|
|
{
|
|
var tokens = findObjs({_subtype: 'token'});
|
|
|
|
tokens.forEach(function(token) {
|
|
if (token.get('name') == tokenName) {
|
|
var barValue = getInteger(token.get(barName));
|
|
if ( !isInteger(barValue)) barValue = 0; //forcing bar value to be an integer
|
|
barValue += barAdjustValue;
|
|
if (barValue < barMin) barValue = barMin;
|
|
if (barValue > barMax) barValue = barMax;
|
|
token.set(barName, barValue);
|
|
|
|
//spawnFx(token.get('left'),token.get('top'),'bomb-blood');
|
|
return;
|
|
}
|
|
});
|
|
},
|
|
|
|
|
|
addDegrees = function(currentRotation, degreesAdjustment) {
|
|
var newDegrees = currentRotation += degreesAdjustment;
|
|
|
|
while ((newDegrees < 0) || (newDegrees > 360)) {
|
|
if (newDegrees < 0)
|
|
newDegrees = (360 + newDegrees);
|
|
|
|
if (newDegrees > 360)
|
|
newDegrees = (newDegrees - 360);
|
|
}
|
|
|
|
return newDegrees;
|
|
},
|
|
|
|
distance = function(alpha, beta) {
|
|
var phi = Math.abs(beta - alpha) % 360; // This is either the distance or 360 - distance
|
|
var distance = phi > 180 ? 360 - phi : phi;
|
|
return distance;
|
|
},
|
|
|
|
flashCombatToken = function(token) {
|
|
if (combatFlashCntr < combatFlashing.maxCntr) {
|
|
combatFlashCntr++;
|
|
token.set('fliph', !token.get('fliph'));
|
|
setTimeout(flashCombatToken, combatFlashing.delay, token);
|
|
return;
|
|
}
|
|
combatFlashCntr = 0;
|
|
},
|
|
|
|
|
|
startCombatToken = function(whosToken) {
|
|
var tokens = findObjs({_subtype: 'token'});
|
|
tokens.forEach(function(token) {
|
|
if (token.get('name') == whosToken) {
|
|
setTimeout(flashCombatToken, combatFlashing.delay, token);
|
|
}
|
|
});
|
|
|
|
},
|
|
|
|
|
|
addDeadToken = function(token) {
|
|
toFront(createObj('graphic', {
|
|
_subtype: 'token',
|
|
_pageid: token.get('_pageid'),
|
|
left: token.get('left'),
|
|
top: token.get('top'),
|
|
width: token.get('width'),
|
|
height: token.get('height'),
|
|
layer: 'objects',
|
|
imgsrc: DeadTokenImgSrc,
|
|
name: 'DeadToken'
|
|
}));
|
|
},
|
|
|
|
|
|
killToken = function(token) {
|
|
var rotation = token.get('rotation');
|
|
|
|
rotation = addDegrees(rotation, (dyingTokenConfig.rotationValue * dyingTokenConfig.rotationDirection));
|
|
|
|
if (dyingTokenConfig.rotationDirection < 0) {
|
|
if (distance(dyingTokenConfig.beginningRotaton, rotation) >= 90) {
|
|
rotation = addDegrees(dyingTokenConfig.beginningRotaton, -90);
|
|
dyingTokenConfig.rotationDirection = 1;
|
|
}
|
|
}
|
|
else {
|
|
if (distance(dyingTokenConfig.beginningRotaton, rotation) >= 90) {
|
|
rotation = addDegrees(dyingTokenConfig.beginningRotaton, 90);
|
|
token.set('rotation', rotation);
|
|
dyingTokenConfig.rotationDirection = -1;
|
|
addDeadToken(token);
|
|
return; //dying animation complete
|
|
}
|
|
}
|
|
|
|
token.set('rotation', rotation);
|
|
setTimeout(killToken, dyingTokenConfig.delay, token);
|
|
},
|
|
|
|
startKillToken = function(selectedObect) {
|
|
var token = getObj('graphic', selectedObect._id);
|
|
dyingTokenConfig.beginningRotaton = token.get('rotation') + 0; //force number
|
|
killToken(token);
|
|
},
|
|
|
|
|
|
setTokenStatus = function(token, attrName, attrValue) {
|
|
var statusMarkers = token.get('statusmarkers');
|
|
attrValue--; //adjust for zero offset array; if adjusted to -1, then no marker (zero)
|
|
|
|
if (attrName == 'Wounds') {
|
|
WoundMarkers.forEach(function(item){
|
|
statusMarkers = statusMarkers.replace(new RegExp(item, 'gi'),'');
|
|
});
|
|
if (attrValue > -1 && attrValue <= WoundMarkers.length)
|
|
statusMarkers += ',' + WoundMarkers[attrValue];
|
|
}
|
|
|
|
if (attrName == 'Trauma') {
|
|
TraumaMarkers.forEach(function(item){
|
|
statusMarkers = statusMarkers.replace(new RegExp(item, 'gi'),'');
|
|
});
|
|
if (attrValue > -1 && attrValue <= TraumaMarkers.length)
|
|
statusMarkers += ',' + TraumaMarkers[attrValue];
|
|
}
|
|
|
|
//convert to array, remove empty elements, and set back to status markers on token
|
|
var newMarkers = statusMarkers.split(',')
|
|
.filter(function(marker){ return marker.trim() > ''; })
|
|
.join();
|
|
|
|
token.set('statusmarkers', newMarkers);
|
|
},
|
|
|
|
// on modify character sheet attribute
|
|
// attr_after parm is roll20 attribute object. The attr_before is plain javascript object
|
|
handleChangeCharAttribute = function(attrAfter, attrBefore ) {
|
|
var attrName = attrAfter.get('name');
|
|
var attrValue = attrAfter.get('current');
|
|
var charId = attrAfter.get('_characterid');
|
|
var character = getObj('character', charId);
|
|
var charName = character.get('name');
|
|
|
|
if (attrName.toLowerCase() == 'vigor' ||
|
|
attrName.toLowerCase() == 'wounds' ||
|
|
attrName.toLowerCase() == 'resolve' ||
|
|
attrName.toLowerCase() == 'trauma' ||
|
|
attrName.toLowerCase() == 'fatigue' ||
|
|
attrName.toLowerCase() == 'despair' ||
|
|
attrName.toLowerCase() == 'fortune')
|
|
sendChat(charName,"&{template:conan-default} {{title=" + charName + "'s Attribute Changed}} {{" + attrName+" from " + attrBefore.current + " to " + attrValue + "}}");
|
|
|
|
if (attrName == 'Wounds' || attrName == 'Trauma') {
|
|
//find token and update status markers
|
|
var tokens = findObjs({_subtype: 'token'});
|
|
tokens.forEach(function(token) {
|
|
if (token.get('represents') == charId) {
|
|
setTokenStatus(token, attrName, attrValue);
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
|
|
// on modify graphic
|
|
handleChangeGraphic = function(currentObj, prevObj) {
|
|
//obj is a roll20 object with built in functions
|
|
//prev is plain javascript object
|
|
var currentName = currentObj.get('name');
|
|
|
|
if (currentName == 'DeadToken') {
|
|
var prevName = prevObj.name;
|
|
var currentPoistion = { left: currentObj.get('left'), top: currentObj.get('top') }
|
|
var prevPoistion = { left: prevObj.left, top: prevObj.top }
|
|
|
|
//find token in prev position ... this would be token under the Dead Token icon
|
|
var deadTokens = findObjs({ left: prevObj.left, top: prevObj.top});
|
|
//log('Deadtoken count:' + String(deadTokens.length));
|
|
deadTokens.forEach(function(token) {
|
|
//log('found token:' + token.get('name'));
|
|
token.set('left', currentPoistion.left);
|
|
token.set('top', currentPoistion.top);
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Handle chat events
|
|
*
|
|
* @param {object} msg
|
|
*/
|
|
handleChatMessage = function(msg)
|
|
{
|
|
let content = msg.content;
|
|
|
|
if(msg.type == "api") {
|
|
|
|
if(content.indexOf("!killtoken") > -1) {
|
|
if(msg.selected && msg.selected.length > 0)
|
|
startKillToken(msg.selected[0]);
|
|
}
|
|
|
|
if(content.indexOf("!bandagetoken") > -1) {
|
|
if(msg.selected && msg.selected.length > 0) {
|
|
var token = getObj('graphic', msg.selected[0]._id);
|
|
var markers = token.get('statusmarkers') + ',' + BandagedWoundMarker;
|
|
token.set('statusmarkers', markers);
|
|
}
|
|
}
|
|
|
|
if (content.indexOf("!dmd+") > -1)
|
|
updateTokenBar(doomTokenName, 'bar3_value', 1, 0, 30);
|
|
|
|
if(content.indexOf("!dmd-") > -1)
|
|
updateTokenBar(doomTokenName, 'bar3_value', -1, 0, 30);
|
|
|
|
if(content.indexOf("!dmm+") > -1)
|
|
updateTokenBar(doomTokenName, 'bar1_value', 1, 0, 6);
|
|
|
|
if(content.indexOf("!dmm-") > -1)
|
|
updateTokenBar(doomTokenName, 'bar1_value', -1, 0, 6);
|
|
|
|
}
|
|
else {
|
|
if(msg.rolltemplate == "skill" && msg.inlinerolls) {
|
|
//1 is skilled roll; 2 is unskilled roll
|
|
var ninteens = 0;
|
|
var ninteens1 = -1;
|
|
var ninteens2 = 0;
|
|
var twenties = 0;
|
|
var twenties1 = -1;
|
|
var twenties2 = 0;
|
|
var inlinerolls = msg.inlinerolls;
|
|
inlinerolls.forEach(function(inlineObj){
|
|
if (inlineObj.results.resultType == "success") {
|
|
ninteens = 0;
|
|
twenties = 0;
|
|
var rolls = inlineObj.results.rolls;
|
|
for(var r = 0; r < rolls.length; r++) {
|
|
for(var v = 0; v < rolls[r].results.length; v++) {
|
|
if (rolls[r].results[v].v == 19)
|
|
ninteens++;
|
|
if (rolls[r].results[v].v == 20)
|
|
twenties++;
|
|
}
|
|
}
|
|
if (ninteens1 == -1)
|
|
ninteens1 = ninteens;
|
|
else
|
|
ninteens2 = ninteens
|
|
if (twenties1 == -1)
|
|
twenties1 = twenties;
|
|
else
|
|
twenties2 = twenties
|
|
}
|
|
});
|
|
if((content.indexOf("{{expertise=0}}") == -1 && twenties1 > 1) ||
|
|
(content.indexOf("{{expertise=0}}") !== -1 && (ninteens2 + twenties2) > 1)) {
|
|
sendChat("Conan","&{template:conan-default} {{title=DOUBLE TROUBLE !!!}} {{subtitle1="+msg.who+" rolled MULTIPLE complications!!!}}")
|
|
}
|
|
}
|
|
|
|
if (msg.rolltemplate == "skill" && content.indexOf("skill_name=Melee") > -1) {
|
|
startCombatToken(msg.who);
|
|
}
|
|
}
|
|
},
|
|
|
|
|
|
init = function()
|
|
{
|
|
log('Starting ConanAPI v' + version);
|
|
sendChat('Conan', 'Starting Conan APIs v' + version);
|
|
|
|
on('change:graphic', handleChangeGraphic);
|
|
on('change:attribute', handleChangeCharAttribute);
|
|
on("chat:message", handleChatMessage);
|
|
|
|
};
|
|
|
|
return {
|
|
init: init
|
|
};
|
|
}());
|
|
|
|
/**
|
|
* Fires when the page has loaded.
|
|
*/
|
|
on("ready", function()
|
|
{
|
|
'use strict';
|
|
|
|
ConanAPI.init();
|
|
});
|
|
|