var BattleMaster = BattleMaster || (function() { 'use strict'; var bInCombat, bHasTakenAction, bHasTakenBonusAction, bIsWaitingOnRoll, bIsWaitingOnResponse, responseCallbackFunction, selectedTokenCallbackFunction, iMoveSpeedTotal, iMoveSpeedRemaining, iXStart, iYStart, iXCurrent, iYCurrent, currentPlayerDisplayName, currentTurnPlayer, currentTurnCharacter, currentTurnToken, currentlyCastingSpellRoll, target, reticleTokenId, direction, range, listTokensInEncounter = [], listTokensWaitingOnSavingThrowsFrom = [], sPreviousAction, sPreviousBonusAction, listRollCallbackFunctions = [], listPlayerIDsWaitingOnRollFrom = [], listSelectableGraphics = [], defaults = { css: { button: { 'border': '1px solid #cccccc', 'border-radius': '1em', 'background-color': '#006dcc', 'margin': '0 .1em', 'font-weight': 'bold', 'padding': '.1em 1em', 'color': 'white' } } }, templates = {}; function rollData(rollMsg){ var inlineData = rollMsg.inlinerolls; this.bRequiresSavingThrow = (universalizeString(rollMsg.content).indexOf("saveattr") != -1); this.playerid = rollMsg.playerid; this.d20Rolls = []; this.dmgRolls = []; this.dmgTypes = []; this.critRolls = []; this.critTypes = []; this.rangeString = ""; this.saveType = ""; this.saveEffects = ""; var r1Index = parseInt(rollMsg.content.substring(rollMsg.content.indexOf("{{r1=$[[") + 8, firstIndexAfter(rollMsg.content,rollMsg.content.indexOf("{{r1=$[[") + 8,"]]")),10), r2Index = parseInt(rollMsg.content.substring(rollMsg.content.indexOf("{{r2=$[[") + 8, firstIndexAfter(rollMsg.content,rollMsg.content.indexOf("{{r2=$[[") + 8,"]]")),10), saveDCIndex = parseInt(rollMsg.content.substring(rollMsg.content.indexOf("{{savedc=$[[") + 12, firstIndexAfter(rollMsg.content,rollMsg.content.indexOf("{{savedc=$[[") + 12,"]]")),10), dmg1Index = parseInt(rollMsg.content.substring(rollMsg.content.indexOf("{{dmg1=$[[") + 10, firstIndexAfter(rollMsg.content,rollMsg.content.indexOf("{{dmg1=$[[") + 10,"]]")),10), //dmg2Index = parseInt(rollMsg.content.substring(rollMsg.content.indexOf("{{savedc=$[[") + 12, firstIndexAfter(rollMsg.content,rollMsg.content.indexOf("{{savedc=$[[") + 12,"]]")),10), crit1Index = parseInt(rollMsg.content.substring(rollMsg.content.indexOf("{{crit1=$[[") + 11, firstIndexAfter(rollMsg.content,rollMsg.content.indexOf("{{crit1=$[[") + 11,"]]")),10), //crit2Index = parseInt(rollMsg.content.substring(rollMsg.content.indexOf("{{savedc=$[[") + 12, firstIndexAfter(rollMsg.content,rollMsg.content.indexOf("{{savedc=$[[") + 12,"]]")),10), dmgType1 = rollMsg.content.substring(rollMsg.content.indexOf("dmg1type=") + 9, firstIndexAfter(rollMsg.content,rollMsg.content.indexOf("dmg1type=") + 9,"}}")); //dmgType2 = rollMsg.content.substring(rollMsg.content.indexOf("dmg2type=") + 9, firstIndexAfter(rollMsg.content,rollMsg.content.indexOf("dmg2type=") + 9,"}}")), this.d20Rolls.push(inlineData[r1Index]); this.d20Rolls.push(inlineData[r2Index]); this.dc = inlineData[saveDCIndex]; this.dmgRolls.push(inlineData[dmg1Index]);//this.dmgRolls.push(inlineData[dmg2Index]); this.dmgTypes.push(dmgType1.trim()); this.rangeString = rollMsg.content.substring(rollMsg.content.indexOf("{{range=") + 8, firstIndexAfter(rollMsg.content, rollMsg.content.indexOf("{{range=") + 8, "}}")); this.saveType = rollMsg.content.substring(rollMsg.content.indexOf("{{saveattr=") + 11, firstIndexAfter(rollMsg.content,rollMsg.content.indexOf("saveattr=") + 11,"}}")); this.saveEffects = rollMsg.content.substring(rollMsg.content.indexOf("savedesc=") + 9, firstIndexAfter(rollMsg.content,rollMsg.content.indexOf("savedesc=") + 9,"}}")); } //**UTILITY SCRIPTS** var buildTemplates = function() { templates.cssProperty =_.template( '<%=name %>: <%=value %>;' ); templates.style = _.template( 'style="<%='+ '_.map(css,function(v,k) {'+ 'return templates.cssProperty({'+ 'defaults: defaults,'+ 'templates: templates,'+ 'name:k,'+ 'value:v'+ '});'+ '}).join("")'+ ' %>"' ); templates.button = _.template( ' href="<%= command %>"><%= label||"Button" %>' ); }, firstIndexAfter = function(string, preIndex, search){ return (preIndex + string.substring(preIndex).indexOf(search)); }, /*Makes the API buttons used throughout the script*/ makeButton = function(command, label, backgroundColor, color){ return templates.button({ command: command, label: label, templates: templates, defaults: defaults, css: { color: color, 'background-color': backgroundColor } }); }, promptButtonArray = function(promptName, listPromptableItems,listCommandNames){ var stringToSend, buttonArray = []; /* for(var i = 0; i < listPromptableItems.length; i++){ var tempString = listPromptableItems[i]; while(tempString.indexOf(' ') != -1){ tempString = tempString.slice(0,tempString.indexOf(' ')) + tempString.slice(tempString.indexOf(' ') + 1); } tempString = tempString.toLowerCase(); listCommandNames[i] = tempString; log(tempString); } */ for(var i = 0; i < listPromptableItems.length; i++){ buttonArray[i] = makeButton('!combat ' + listCommandNames[i], listPromptableItems[i], '#CDAE88', 'black'); } stringToSend = '/w "' + currentPlayerDisplayName + '" ' +'
' +'
' +promptName +'
'; for(var i = 0; i < buttonArray.length; i++){ stringToSend += buttonArray[i]; } stringToSend += '
'; sendChat('BattleMaster', stringToSend); }, promptTarget = function(){ reticleTokenId = createObj("graphic", { controlledby: (currentTurnPlayer.id), _pageid: Campaign().get('playerpageid'), left: (currentTurnToken.get('left')), top: (currentTurnToken.get('top') - distanceToPixels(5)), layer: "objects", imgsrc: "https://s3.amazonaws.com/files.d20.io/images/35946928/hC9eLBaOaso0aa9kldO9Jg/thumb.png?1500001934", width: distanceToPixels(5), height: distanceToPixels(5), }).id; sendPing(currentTurnToken.get('left'), currentTurnToken.get('top')- distanceToPixels(5), null, true); log("Reticle token ID: " + reticleTokenId); promptButtonArray("Move the target to where you would like to attack", ["Target selected"], ["selectedTarget"]) }, findCurrentTurnToken = function(turnorder) { if (!turnorder) {turnorder = Campaign().get('turnorder');} if (!turnorder) {return undefined;} if (typeof(turnorder) === 'string') {turnorder = JSON.parse(turnorder);} if (turnorder && turnorder.length > 0 && turnorder[0].id !== -1) {return getObj('graphic',turnorder[0].id);} return; }, findWhoIsControlling = function(character){ var whoIsControlling; var listPlayerIDs; _.each(character.get('controlledby').split(','), function(player){ if(!playerIsGM(player)){ log('Player ID ' + player + ' is controlling this character!'); whoIsControlling = player; } else if(whoIsControlling === undefined){ log('Player ID ' + player + ' is a GM, setting them to controlling anyways!'); whoIsControlling = player; } else{ log('Player ID ' + player + ' is a GM, not controlling!'); } }); return whoIsControlling; }, findTokenAtTarget = function(){ var reticleToken = getObj("graphic",reticleTokenId); if(reticleToken){ log("Reticle token isn't null!"); _.each(JSON.parse(Campaign().get('turnorder')), function(token){ token = getObj('graphic', token.id); log("Testing token " + token.id); log("Token coords: (" + token.get('left') + ", " + token.get('top')); log("Reticle coords: (" + reticleToken.get('left') + ", " + reticleToken.get('top')); if(token.get('left') + (token.get('width')/2) >= reticleToken.get('left') && token.get('left') - (token.get('width')/2) <= reticleToken.get('left') && token.get('top') + (token.get('height')/2) >= reticleToken.get('top') && token.get('top') - (token.get('height')/2) <= reticleToken.get('top')) { listSelectableGraphics.push(getObj('graphic', token.id)) } }); log('List of selectable graphics: ' + listSelectableGraphics); reticleToken.remove(); if(listSelectableGraphics.length > 1){ var listTokenNames = [], listCommandNames = []; for(var i = 0; i 1 && rollData.dmgRolls[1].results.total != 0){ applyDamage(rollData.dmgRolls[1].results.total, rollData.dmgTypes[1], target, getObj('character', target.get('represents'))); } spawnFx(target.get('left'), target.get('top'), 'glow-blood',getObj('page', Campaign().get('playerpageid'))); } else{ log("Miss! Enemy AC is " + ac + " and roll result was " + rollData.d20Rolls[0].results.total); sendChat("BattleMaster", '/w "' + currentPlayerDisplayName + '" Miss!'); } }, DirectSpellAttack = function(){ if(target != undefined){ log('Direct spell attacking at ' + target.get('name')); sendChat("BattleMaster", '/w "' + currentPlayerDisplayName + '" ' + "Now attempting to attack " + target.get('name') + ". Please roll your spell attack from your character sheet."); listRollCallbackFunctions.push(DirectSpellRollCallback); log("Current turn player: " + currentTurnPlayer); listPlayerIDsWaitingOnRollFrom.push(currentTurnPlayer.id); bIsWaitingOnRoll = true; } else{ log('Tried to attack with direct spell, but no target was selected!'); sendChat("BattleMaster", '/w "' + currentPlayerDisplayName + '" No target is selected! Please select a target!'); promptTarget(); selectedTokenCallbackFunction = DirectSpellAttack; } }, DirectSpellRollCallback = function(rollData){ bIsWaitingOnRoll = (listPlayerIDsWaitingOnRollFrom.length != 0); //Check if we're still waiting on another roll if(rollData.bRequiresSavingThrow){ currentlyCastingSpellRoll = rollData; log("Saving throw spell!"); var playerID = findWhoIsControlling(getObj('character',target.get('represents'))); sendChat("BattleMaster", '/w "' + getObj('player',playerID).get("displayname") + '" Please roll a ' + rollData.saveType + ' saving throw for ' + target.get("name")); listPlayerIDsWaitingOnRollFrom.push(playerID); listRollCallbackFunctions.push(SavingThrowAgainstDamageRollCallback); listTokensWaitingOnSavingThrowsFrom.push(target); } else{ log("Spell attack!"); var ac = getAttrByName(target.get('represents'),'npcd_ac'); if(ac === "" || ac === undefined){ log('Couldn\'t find npcd_ac, looking for just ac') ac = getAttrByName(target.get('represents'),'ac'); } if(ac <= rollData.d20Rolls[0].results.total){ log("Hit! Enemy AC is " + ac + " and roll result was " + rollData.d20Rolls[0].results.total); sendChat("BattleMaster", '/w "' + currentPlayerDisplayName + '" Hit! Applying damage to ' + target.get('name')); applyDamage(rollData.dmgRolls[0].results.total, rollData.dmgTypes[0], target, getObj('character', target.get('represents'))); if(rollData.dmgRolls.length > 1 && rollData.dmgRolls[1].results.total != 0){ applyDamage(rollData.dmgRolls[1].results.total, rollData.dmgTypes[1], target, getObj('character', target.get('represents'))); } } else{ log("Miss! Enemy AC is " + ac + " and roll result was " + rollData.d20Rolls[0].results.total); sendChat("BattleMaster", '/w "' + currentPlayerDisplayName + '" Miss!'); } } }, AOESpellAttack = function(){ sendChat('BattleMaster', '/w "' + currentPlayerDisplayName + '" Roll your AOE spell from your character sheet!'); listPlayerIDsWaitingOnRollFrom.push(currentTurnPlayer.id); listRollCallbackFunctions.push(AOESpellRollCallback); bIsWaitingOnRoll = true; }, AOESpellRollCallback = function(rollData){ currentlyCastingSpellRoll = rollData; var rangeString = rollData.rangeString, x = currentTurnToken.get('left'), y = currentTurnToken.get('top'), args = rangeString.toLowerCase().split(/\s+/); if(args[0]!= "self"){ log("Not self targeted!"); } else{ switch(args[1]){ case "cone": promptButtonArray("Select a direction", ["North","South","East","West","Northeast","Northwest","Southeast","Southwest"], ["up","down","right","left","upright","upleft","downright","downleft"]); bIsWaitingOnResponse = true; responseCallbackFunction = coneDirectionPromptCallback; range = args[2]; log("Spell is a cone!"); break; case "line": promptButtonArray("Select a direction", ["North","South","East","West","Northeast","Northwest","Southeast","Southwest"], ["up","down","right","left","upright","upleft","downright","downleft"]); bIsWaitingOnResponse = true; responseCallbackFunction = lineDirectionPromptCallback; range = args[2]; break; case "sphere": var effectType = "burst-"+dmgTypeToFXName(rollData.dmgTypes[0]); log("Spawning fx: " + effectType); spawnFx(x,y,effectType,getObj('page', Campaign().get('playerpageid'))); _.each(findAllTokensInSphere(x,y,args[2]), spellEffects) break; case "cube": break; case "cylinder": break; } } }, distanceBetween = function(originX, originY, finalX, finalY){ var deltaX = originX - finalX, deltaY = originY - finalY return Math.sqrt(Math.pow(deltaX,2) + Math.pow(deltaY,2)); }, coneDirectionPromptCallback = function(){ log("Casting " + direction); var xMod = 0, yMod = 0, x = currentTurnToken.get("left"), y = currentTurnToken.get("top"); if(direction.toLowerCase().indexOf('up') != -1){ yMod = -35; } else if(direction.toLowerCase().indexOf('down') != -1){ yMod = 35; } if(direction.toLowerCase().indexOf('left') != -1){ xMod = -35; } else if (direction.toLowerCase().indexOf('right') != -1){ xMod = 35; } //spawnFxBetweenPoints({x:(x+xMod),y:(y+yMod)},{}) var effectType = "breath-"+dmgTypeToFXName(currentlyCastingSpellRoll.dmgTypes[0]); log("Spawning fx: " + effectType); spawnFxBetweenPoints({x:(x+xMod), y:(y+yMod)},{x:(x+xMod+xMod), y:(y+yMod+yMod)},effectType,getObj('page', Campaign().get('playerpageid'))); _.each(findAllTokensInCone(x + xMod, y + yMod, direction, range), spellEffects); }, lineDirectionPromptCallback = function(){ var xMod = 0, yMod = 0, x = currentTurnToken.get("left"), y = currentTurnToken.get("top"); if(direction.toLowerCase().indexOf('up') != -1){ yMod = -35; } else if(direction.toLowerCase().indexOf('down') != -1){ yMod = 35; } if(direction.toLowerCase().indexOf('left') != -1){ xMod = -35; } else if (direction.toLowerCase().indexOf('right') != -1){ xMod = 35; } var effectType = "beam-"+dmgTypeToFXName(currentlyCastingSpellRoll.dmgTypes[0]); log("Spawning fx: " + effectType); spawnFxBetweenPoints({x:(x+xMod), y:(y+yMod)},{x:(x+xMod+xMod), y:(y+yMod+yMod)},effectType,getObj('page', Campaign().get('playerpageid'))); _.each(findAllTokensInLine(x+xMod,y+yMod,direction,range), spellEffects); }, spellEffects = function(token){ var playerID = findWhoIsControlling(getObj('character',token.get('represents'))); sendChat("BattleMaster", '/w "' + getObj('player',playerID).get("displayname") + '" Please roll a ' + currentlyCastingSpellRoll.saveType + ' saving throw for ' + token.get("name")); listPlayerIDsWaitingOnRollFrom.push(playerID); listRollCallbackFunctions.push(SavingThrowAgainstDamageRollCallback); listTokensWaitingOnSavingThrowsFrom.push(token); }, distanceToPixels = function(dist) { var PIX_PER_UNIT = 70; var page = getObj('page', Campaign().get('playerpageid')); return PIX_PER_UNIT * (dist/page.get('scale_number')); }, findAllTokensInCone = function(originX, originY, direction, range){ var listTokensToReturn = [], line1YofX, line2YofX, line1XofY, line2XofY, bLine1XNeg, bLine2XNeg, bLine1YNeg, bLine2YNeg; var tokenIsConstrainedByLines = function(token, line1XofY, line1YofX, line2XofY, line2YofX, bLine1XNeg, bLine1YNeg, bLine2XNeg, bLine2YNeg, range){ var bValueToReturn, tokenX = token.get('left'), tokenY = token.get('top'); bValueToReturn = (bLine1XNeg && tokenX <= line1XofY(tokenY) || (!bLine1XNeg) && tokenX >= line1XofY(tokenY)); bValueToReturn = bValueToReturn && (bLine1YNeg && tokenY <= line1YofX(tokenX) || (!bLine1YNeg) && tokenY >= line1YofX(tokenX)); bValueToReturn = bValueToReturn && (bLine2XNeg && tokenX <= line2XofY(tokenY) || (!bLine2XNeg) && tokenX >= line2XofY(tokenY)); bValueToReturn = bValueToReturn && (bLine2YNeg && tokenY <= line2YofX(tokenX) || (!bLine2YNeg) && tokenY >= line2YofX(tokenX)); bValueToReturn = bValueToReturn && (distanceBetween(originX, originY, tokenX, tokenY) <= distanceToPixels(range)); return bValueToReturn; } switch (direction){ case "up": bLine1XNeg = false; bLine1YNeg = true; bLine2XNeg = true; bLine2YNeg = true; line1YofX = function(x){ return ((x - originX)*2) + originY; } line2YofX = function(x){ return -((x - originX)*2) + originY; } line1XofY = function(y){ return ((y - originY)/2) + originX; } line2XofY= function(y){ return -((y - originY)/2) + originX; } break; case "down": bLine1XNeg = false; bLine1YNeg = false; bLine2XNeg = true; bLine2YNeg = false; line1YofX = function(x){ return -((x - originX)*2) + originY; } line2YofX = function(x){ return ((x - originX)*2) + originY; } line1XofY = function(y){ return -((y - originY)/2) + originX; } line2XofY= function(y){ return ((y - originY)/2) + originX; } break; case "left": bLine1XNeg = true; bLine1YNeg = true; bLine2XNeg = true; bLine2YNeg = false; line1YofX = function(x){ return -((x - originX)/2) + originY; } line2YofX = function(x){ return ((x - originX)/2) + originY; } line1XofY = function(y){ return -((y - originY)*2) + originX; } line2XofY= function(y){ return ((y - originY)*2) + originX; } break; case "right": bLine1XNeg = false; bLine1YNeg = false; bLine2XNeg = false; bLine2YNeg = true; line1YofX = function(x){ return -((x - originX)/2) + originY; } line2YofX = function(x){ return ((x - originX)/2) + originY; } line1XofY = function(y){ return -((y - originY)*2) + originX; } line2XofY= function(y){ return ((y - originY)*2) + originX; } break; case "upleft": bLine1XNeg = false; bLine1YNeg = true; bLine2XNeg = true; bLine2YNeg = false; line1YofX = function(x){ return ((x - originX)/3) + originY; } line2YofX = function(x){ return ((x - originX)*3) + originY; } line1XofY = function(y){ return ((y - originY)*3) + originX; } line2XofY= function(y){ return ((y - originY)/3) + originX; } break; case "upright": bLine1XNeg = false; bLine1YNeg = false; bLine2XNeg = true; bLine2YNeg = true; line1YofX = function(x){ return -((x - originX)*3) + originY; } line2YofX = function(x){ return -((x - originX)/3) + originY; } line1XofY = function(y){ return -((y - originY)/3) + originX; } line2XofY= function(y){ return -((y - originY)*3) + originX; } break; case "downleft": bLine1XNeg = true; bLine1YNeg = true; bLine2XNeg = true; bLine2YNeg = false; line1YofX = function(x){ return -((x - originX)*3) + originY; } line2YofX = function(x){ return -((x - originX)/3) + originY; } line1XofY = function(y){ return -((y - originY)/3) + originX; } line2XofY= function(y){ return -((y - originY)*3) + originX; } break; case "downright": bLine1XNeg = true; bLine1YNeg = false; bLine2XNeg = false; bLine2YNeg = true; line1YofX = function(x){ return ((x - originX)/3) + originY; } line2YofX = function(x){ return ((x - originX)*3) + originY; } line1XofY = function(y){ return ((y - originY)*3) + originX; } line2XofY= function(y){ return ((y - originY)/3) + originX; } break; } _.each(listTokensInEncounter, function(token){ log("Looking for token" + token.get("name")); if(tokenIsConstrainedByLines(token, line1XofY, line1YofX, line2XofY, line2YofX, bLine1XNeg, bLine1YNeg, bLine2XNeg, bLine2YNeg, range)){ listTokensToReturn.push(token); log(token.get("name") + " is within the cone!"); } else{ log(token.get('name') + " is outside the cone."); } }); return listTokensToReturn; }, findAllTokensInSphere = function(x,y,range){ var listTokensToReturn = []; _.each(listTokensInEncounter, function(token){ log("Looking for token" + token.get("name")); if(distanceBetween(x,y,token.get('left'), token.get('top')) <= distanceToPixels(range)){ listTokensToReturn.push(token); log(token.get("name") + " is inside the sphere"); } else{ log(token.get('name') + " is outside the sphere"); } }); return listTokensToReturn; }, findAllTokensInLine = function(x,y,direction,range){ var listTokensToReturn = []; _.each(listTokensInEncounter, function(token){ var tokenX = token.get('left'), tokenY = token.get('top'); switch (direction){ case "up": if(tokenX + 20 >= x && tokenX - 20 <= x && tokenY < y && distanceBetween(x,y,tokenX,tokenY) <= distanceToPixels(range)){ listTokensToReturn.push(token); } break; case 'right': if(tokenY + 20 >= y && tokenY - 20 <= y && tokenX >= x && distanceBetween(x,y,tokenX,tokenY) <= distanceToPixels(range)){ listTokensToReturn.push(token); } break; case 'down': if(tokenX + 20 >= x && tokenX - 20 <= x && tokenY > y && distanceBetween(x,y,tokenX,tokenY) <= distanceToPixels(range)){ listTokensToReturn.push(token); } break; case 'left': if(tokenY + 20 >= y && tokenY - 20 <= y && tokenX <= x && distanceBetween(x,y,tokenX,tokenY) <= distanceToPixels(range)){ listTokensToReturn.push(token); } break; case 'upright': if(tokenX-x + 20 >= -(tokenY-y) && tokenX-x - 20 <= -(tokenY-y) && tokenX >= x && distanceBetween(x,y,tokenX,tokenY) <= distanceToPixels(range)){ listTokensToReturn.push(token); } break; case 'downright': if(tokenX-x + 20 >= tokenY-y && tokenX-x - 20 <= tokenY-y && tokenX >= x && distanceBetween(x,y,tokenX,tokenY) <= distanceToPixels(range)){ listTokensToReturn.push(token); } break; case 'downleft': if(tokenX-x + 20 >= tokenY-y && tokenX-x - 20 <= tokenY-y && tokenX <= x && distanceBetween(x,y,tokenX,tokenY) <= distanceToPixels(range)){ listTokensToReturn.push(token); } break; case 'upleft': if(tokenX-x + 20 >= -(tokenY-y) && tokenX-x - 20 <= -(tokenY-y) && tokenX <= x && distanceBetween(x,y,tokenX,tokenY) <= distanceToPixels(range)){ listTokensToReturn.push(token); } break; } }); return listTokensToReturn; }, findAllTokensInCube = function(x,y,range){ }, findAllTokensInCylinder = function(x,y,range,height){ var listTokensToReturn = []; _.each(listTokensInEncounter, function(token){ log("Looking for token" + token.get("name")); if(distanceBetween(x,y,token.get('left'), token.get('top')) <= distanceToPixels(range)){ listTokensToReturn.push(token); log(token.get("name") + " is inside the sphere"); } else{ log(token.get('name') + " is outside the sphere"); } }); return listTokensToReturn; }, SavingThrowAgainstDamageRollCallback = function(rollData){ log("Waiting on rolls for the following tokens: "); _.each(listTokensWaitingOnSavingThrowsFrom, function(token){ log(token.get('name')); }); for(var i = 0; i < listTokensWaitingOnSavingThrowsFrom.length; i++){ if(findWhoIsControlling(getObj('character', listTokensWaitingOnSavingThrowsFrom[i].get('represents'))) === rollData.playerid){ var token = listTokensWaitingOnSavingThrowsFrom[i]; listTokensWaitingOnSavingThrowsFrom.splice(i,1); log("Found correct token, breaking out of the loop!"); break; } } log("Waiting on rolls for the following tokens: "); _.each(listTokensWaitingOnSavingThrowsFrom, function(token){ log(token.get('name')); }); sendChat("BattleMaster",'/w "' + currentPlayerDisplayName +'" Recieved roll for ' + token.get("name")); var rollAttribute = currentlyCastingSpellRoll.saveType, rollEffectsDesc = currentlyCastingSpellRoll.saveEffects, rollDC = currentlyCastingSpellRoll.dc.results.total, rollDmg = currentlyCastingSpellRoll.dmgRolls[0].results.total, rollDmgType = currentlyCastingSpellRoll.dmgTypes[0]; log("Potential damage: " + rollDmg); log("Damage type: "+ rollDmgType); var savingThrowRoll = rollData.d20Rolls[0].results.total; log("Saving throw roll: " + savingThrowRoll); log("rollDC: " + rollDC); if(savingThrowRoll >= rollDC){ log("Succeeded on saving throw roll! Effects: " + universalizeString(rollEffectsDesc)); //SAVING THROW EFFECTS GO HERE switch(universalizeString(rollEffectsDesc)){ case "halfdamage": applyDamage(rollDmg/2, rollDmgType, token, getObj('character', token.get("represents"))); break; default: break; } } else{ applyDamage(rollDmg, rollDmgType, token, getObj('character', token.get("represents"))); } }, applyDamage = function(dmgAmt, dmgType, targetToken, targetCharacter){ log("Applying " + dmgAmt +" " + dmgType + " damage to " + targetToken.get('name')); var immunitiesRaw = targetCharacter.get("npc_immunities"), resistancesRaw = targetCharacter.get("npc_resistances"), vulnerabilitiesRaw = targetCharacter.get("npc_vulnerabilities"); if(immunitiesRaw != undefined && universalizeString(immunitiesRaw).indexOf(universalizeString(dmgType)) != -1){ //UpdateDeathMarkers(targetToken); return; } else if(vulnerabilitiesRaw != undefined && universalizeString(vulnerabilitiesRaw).indexOf(universalizeString(dmgType)) != -1){ targetToken.set('bar3_value', targetToken.get('bar3_value') - Math.round(2*dmgAmt)); //UpdateDeathMarkers(targetToken); return; } else if(resistancesRaw != undefined && universalizeString(resistancesRaw).indexOf(universalizeString(dmgType)) != -1){ targetToken.set('bar3_value', targetToken.get('bar3_value') - Math.round(dmgAmt/2)); //UpdateDeathMarkers(targetToken); return; } else{ targetToken.set('bar3_value', targetToken.get('bar3_value') - Math.round(dmgAmt)); //UpdateDeathMarkers(targetToken); return; } }, dmgTypeToFXName = function(dmgType){ switch(universalizeString(dmgType)){ case "fire": return "fire"; case "necrotic": return "death"; case "radiant": return "holy"; case "force": return "magic"; case "cold": return "frost"; case "acid": return "slime"; case "psychic": return "magic"; case "lightning": return "smoke"; case "poison": return "slime"; case "thunder": return "smoke"; } }, generateTurnOptions = function(){ //Add class specific options as well! var optionsToReturn = [ 'Weapon Attack', 'Direct Spell', 'AOE Spell', 'Move' ]; return optionsToReturn; }, generateTurnOptionCommands = function(){ var optionsToReturn = [ 'weaponattack', 'directspell', 'aoespell', 'move' ]; return optionsToReturn; }, RegisterEventHandlers = function(){ buildTemplates(); on('chat:message', HandleInput); on('change:campaign:turnorder', function(){ if(bInCombat){ TurnChange(); } }); }; return { RegisterEventHandlers: RegisterEventHandlers, }; }()); on('ready',function(){ 'use strict'; BattleMaster.RegisterEventHandlers(); })