Last active
June 3, 2019 21:02
-
-
Save plexsoup/6fb407c51d3ded60119f914ea82781de to your computer and use it in GitHub Desktop.
Roll20 Script to flipH side-view tokens automatically. Also provides a directional flashlight for isometric tokens.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// psIsoFacingAndView | |
// Purpose: provide a directional flashlight which rotates independently of the parent token. | |
// Also: flip the parent token on the horizontal axis because players can't fliph themselves. | |
// Designed for side-view tokens on isometric or 8bit maps, but should work for top-down as well. | |
// Once installed, turn on the yellow status marker for your token to turn on the flashlight. | |
// turn on the boot (tread) status marker to turn on auto-facing. | |
// For menu: type !psIsoFacing in chat. | |
// **** TODO | |
// BUGS | |
// something's weird about the install routine.. sometime's a character doesn't switch direction until the second move. | |
var psIsoFacing = psIsoFacing || (function plexsoupIsoFacing() { | |
"use strict"; | |
var debug = false; | |
var defaultConfig = { | |
scriptIsActive: true, | |
whisperErrors: true, | |
useTreadStatusForFacing: true, // true: tokens are active when they have a yellow status; false: tokens have to be registered through the gui. | |
useYellowStatusForLight: true, | |
light_radius: 40, | |
light_dimradius: 20, | |
light_angle: 90 | |
}; | |
var config = _.clone(defaultConfig); | |
var info = { | |
scriptName: "psIsoFacing", | |
version: 0.15, | |
authorName: "plexsoup" | |
}; | |
var travellers = {}; // dictionary of objects for side-view tokens actively moving and changing directions. key: tokenID, value: travellerObj | |
var flashlights = {}; // dictionary of objects for flashlights (or navi from Legend of Zelda) which follow the characters around and rotate based on vector from tokens lastmove | |
var ICONS = { // note: Roll20 has particular rules about imgsrc. See https://wiki.roll20.net/API:Objects#imgsrc_and_avatar_property_restrictions | |
torch: "https://s3.amazonaws.com/files.d20.io/images/17247606/Wbr841_bq9ka1FmamWo38w/thumb.png?1458169656", | |
transparent: "https://s3.amazonaws.com/files.d20.io/images/27023509/jDIhDrjr_RyUxq5ldQj1uw/thumb.png?1483683924", | |
scifi: "https://s3.amazonaws.com/files.d20.io/images/27025190/VrSyw_oHrsM8cfdBPcfLHw/thumb.png?1483690176" | |
}; | |
/* | |
_/_/_/ _/_/ _/ _/ _/_/_/ _/_/_/_/_/ _/_/_/ _/ _/ _/_/_/ _/_/_/_/_/ | |
_/ _/ _/ _/_/ _/ _/ _/ _/ _/ _/ _/ _/ _/ | |
_/ _/ _/ _/ _/ _/ _/_/ _/ _/_/_/ _/ _/ _/ _/ | |
_/ _/ _/ _/ _/_/ _/ _/ _/ _/ _/ _/ _/ _/ | |
_/_/_/ _/_/ _/ _/ _/_/_/ _/ _/ _/ _/_/ _/_/_/ _/ | |
*/ | |
var travellerObj = function travellerClass(tokenID) { // note: roll20 state variable can only store simple properties. No functions | |
this.direction = 1; // right is 1, left is -1 | |
this.tokenID = tokenID; | |
}; | |
var instantiateTravellerMethods = function(traveller) { // note: only operates on a single instance at a time. Call with _.each(travellers) if you need them all. | |
traveller.changeDirection = function() { | |
traveller.direction *= -1; | |
var token = getObj("graphic", traveller.tokenID); | |
if (token !== undefined ) { | |
token.set("fliph", !token.get("fliph") ); | |
} | |
}; | |
traveller.remove = function() { | |
// Note: there's no js mechanism to destroy myself, so we'll assume that chrome will manage memory when the reference is gone. | |
// delete traveller; // take myself out of the dictionary | |
}; | |
// note: we store travellerObj's in state, but state can only handle simple objects with simple datatypes: ie: no methods. | |
// let's use the prototype property to add methods back to all travellerObj's once we rebuild travellers from state. | |
}; | |
var flashlightObj = function flashlightClass(tokenID) { // note: roll20 state variable can only store simple properties. No functions | |
this.unitVector = [0,1]; | |
this.parentID = tokenID; | |
this.turnedOn = false; | |
this.lightID = ""; | |
this.deleteMe = false; | |
}; | |
var instantiateFlashlightMethods = function(flashlight) { // note: only operates on a single instance at a time. Call with _.each if you need more. | |
flashlight.changeOrientation = function() { | |
}; | |
/* | |
flashlight.turnOff = function() { | |
var lightGraphic = getObj("graphic", flashlight.lightID); | |
if (lightGraphic) { | |
flashlight.deleteMe = true; // **** TODO **** Needs work.. when are you actually going to do this cleanup? | |
lightGraphic.remove(); | |
//mark for deletion and then do cleanup / garbage collection from outside the object. | |
//delete flashlights[flashlight.parentID]; // doesn't work from inside the object | |
} | |
}; | |
*/ | |
// flashlight.remove = flashlight.turnOff; | |
flashlight.follow = function() { | |
var lightGraphic = getObj("graphic", flashlight.lightID); | |
var parentGraphic = getObj("graphic", flashlight.parentID); | |
if (lightGraphic && parentGraphic) { // error checking in case someone deleted either of those tokens | |
// lights don't follow tokens in realtime, they kind of float after them like ghosts. | |
// instead of moving them, we're going to destroy the old one and create a new one. | |
lightGraphic.remove(); // remove the old graphic object | |
flashlight.lightID = createFlashlightToken(flashlight.parentID); | |
var desiredRotation = flashlight.getRotation(); | |
if (debug) psLog("desiredRotation = " + desiredRotation); | |
var newLightGraphic = getObj("graphic" , flashlight.lightID); | |
if (newLightGraphic) { | |
newLightGraphic.set("rotation", desiredRotation ); | |
} | |
} else { | |
psLog("==> Error: flashlight.follow cannot execute because one of the tokens no longer exists."); | |
psLog("light token: " + lightGraphic); | |
psLog("parent token: " + parentGraphic); | |
// reset(); // **** TODO **** This is a little heavy handed, don't you think? | |
} | |
}; | |
flashlight.getRotation = function() { | |
var lightObj = getObj("graphic", flashlight.lightID); | |
var parentObj = getObj("graphic", flashlight.parentID); | |
var lastMoveStr = parentObj.get("lastmove"); | |
var rotation = 0; // straight up | |
if (lastMoveStr !== "") { | |
var lastLocationArr = lastMoveStr.split(","); // Note: these are strings. | |
if (debug) psLog("lastLocationArr = " + lastLocationArr); | |
if (debug) psLog("currentLocation = " + [parentObj.get("left"), parentObj.get("top")]); | |
var lastLocation = new psPoint( Number(lastLocationArr[0]), Number(lastLocationArr[1]) ); | |
var point1 = new psPoint(lastLocation.x, lastLocation.y); | |
var point2 = new psPoint(parentObj.get("left"), parentObj.get("top")); | |
if (debug) psLog("point1 = " + JSON.stringify(point1) + ", point2 = " + JSON.stringify(point2)); | |
var directionVector = makeVector(point1, point2); | |
if (debug) psLog("directionVector == " + JSON.stringify(directionVector), "turquoise"); | |
var degrees = vectorToRoll20Rotation(directionVector); | |
if (debug) psLog("flashlight.getRotation("+ JSON.stringify(point1) + ", " + JSON.stringify(point2) + ") says degrees = " + degrees, "LightBlue"); | |
rotation = degrees; | |
} | |
return rotation; | |
}; | |
}; | |
var lightFlashlight = function(tokenID) { | |
flashlights[tokenID] = new flashlightObj(tokenID); | |
instantiateFlashlightMethods(flashlights[tokenID]); | |
flashlights[tokenID].lightID = createFlashlightToken(tokenID); | |
flashlights[tokenID].turnedOn = true; | |
return flashlights[tokenID].lightID; | |
}; | |
var extinguishFlashlight = function(tokenID) { | |
// this removes the token and removes the flashlightObj from the list of "flashlights" | |
if (flashlights[tokenID]) { | |
var flashlightGraphic = getObj("graphic", flashlights[tokenID].lightID); | |
if (flashlightGraphic !== undefined && flashlightGraphic.get("gmnotes").indexOf(tokenID) !== -1 ) { | |
flashlightGraphic.remove(); | |
delete flashlights[tokenID]; | |
} | |
} | |
}; | |
var createFlashlightToken = function flashlightTokenCreator(parentTokenID) { // returns tokenID of new flashlight graphic object | |
var parentToken = getObj("graphic", parentTokenID); | |
var parentTokenName = parentToken.get("name"); | |
if (debug) psLog("entered createFlashlightToken for "+ parentTokenName + " - " + parentTokenID); | |
var token = createObj("graphic", { | |
imgsrc: ICONS.transparent, | |
light_radius: config.light_radius, | |
light_dimradius: config.light_dimradius, | |
light_otherplayers: true, | |
light_hassight: false, | |
light_angle: config.light_angle, | |
pageid: parentToken.get("pageid"), | |
layer: parentToken.get("layer"), | |
left: parentToken.get("left"), | |
top: parentToken.get("top"), | |
name: "flashlight", | |
showname: false, | |
aura1_radius: "", | |
showplayers_aura1: false, | |
width: parentToken.get("width")/4, | |
height: parentToken.get("height")/4 | |
}); | |
if (token) { | |
token.set("gmnotes", parentTokenID); | |
if (debug) psLog("createFlashlightToken Just created a token: " + token.get("_id") + " for " + parentTokenName); | |
if (debug) psLog("Flashlight Token: " + token.get("_id") ); | |
} | |
if (parentToken === undefined) { | |
if (debug) psLog("==> Error in createFlashlightToken. parentTokenID = " + parentTokenID); | |
} | |
//toBack(token); | |
var tokenID = token.get("_id"); | |
if (debug) psLog("leaving createFlashlightToken. Returning " + tokenID); | |
return tokenID; | |
}; | |
var whisper = function chatMessageSender(playerName, message) { | |
// sends a chat message to a specific player. Can use gm as playerName | |
//sendChat(playerName, '/w ' + playerName + " " + message); | |
sendChat("psIsoFacing", '/w ' + playerName + " " + message); | |
}; | |
var ch = function (c) { | |
// This function will take a single character and change it to it's equivalent html encoded value. | |
// psNote: I tried alternate methods of regexps to encode the entire string, but I always ran into problems with | and [] characters. | |
var entities = { | |
'<' : 'lt', | |
'>' : 'gt', | |
"'" : '#39', | |
'@' : '#64', | |
'{' : '#123', | |
'|' : '#124', | |
'}' : '#125', | |
'[' : '#91', | |
']' : '#93', | |
'"' : 'quot', | |
'-' : 'mdash', | |
' ' : 'nbsp' | |
}; | |
if(_.has(entities,c) ){ | |
return ('&'+entities[c]+';'); | |
} | |
return ''; | |
}; | |
var inString = function stringFinder(stringToSearch, textToLookFor) { | |
// I use this instead of indexof because I hate type converting indexOf, which produces -1 instead of false. | |
if (!stringToSearch || !textToLookFor) { | |
// log("==> Error: inString() missing params" + stringToSearch + ", " + textToLookFor); | |
return undefined; | |
} | |
if ( stringToSearch.indexOf(textToLookFor) == -1 ) { | |
return false; | |
} else { | |
return true; | |
} | |
}; | |
var makeButton = function buttonMakerForChat(title, command) { // expects two strings. Returns encoded html for the chat stream | |
var output=""; | |
output += '['+title+']('+command+')'; | |
return output; | |
}; | |
var psLog = function ErrorLogger(message, backgroundColor) { | |
if (!backgroundColor) { | |
backgroundColor = "Gainsboro"; | |
} | |
var simpleOutputStr = info.scriptName + ": " + message; | |
log(simpleOutputStr); | |
var styledOutputStr = "<div style='font-size: smaller; background-color: " + backgroundColor + ";'>" + info.scriptName + ": " + message + "</div>"; | |
if (config.whisperErrors) | |
whisper("gm", styledOutputStr ); | |
}; | |
/* | |
_/ _/ _/_/_/_/ _/ _/_/_/ | |
_/ _/ _/ _/ _/ _/ | |
_/_/_/_/ _/_/_/ _/ _/_/_/ | |
_/ _/ _/ _/ _/ | |
_/ _/ _/_/_/_/ _/_/_/_/ _/ | |
*/ | |
var showDetailedHelp = function showDetailedHelpTextInChat(playerName) { | |
if (!playerName) { playerName = "gm";} | |
var exampleStyle = '"background-color: #eee; font-size: smaller; margin-left: 40px; margin-bottom: 3px; "'; | |
var warningStyle = '"background-color: AntiqueWhite; font-size: smaller;"'; | |
var exampleTokenSelect = ch('@') + ch('{') + 'selected' + ch('|') + 'token_id' + ch('}'); | |
var helpText = ''; | |
helpText += '<div style="font-size: smaller;">'; | |
helpText += 'psIsoFacing is a script to automatically change facing (flip horizontal) for tokens on isometric maps.'; | |
helpText += "This makes movement look more realistic. Otherwise, players wouldn't have access to flipH."; | |
helpText += "</div>"; | |
helpText += '<div style="font-size: smaller;">'; | |
helpText += "To start, enter !psIsoFacing in the chat window."; | |
helpText += "</div>"; | |
helpText += '<div style="font-size: smaller;">'; | |
helpText += "Configuration options to be aware of: "; | |
helpText += "<ul>"; | |
helpText += "<li>Tokens only change facing if they represent a character and they're controlled by someone.</li>"; | |
helpText += "<li>By default, they won't change facing unless their 'tread' status indicator is on (it looks like a boot).</li>"; | |
helpText += "<li>If you prefer not to use the tread status indicator, you can 'Toggle Use Tread Status Indicator'. Then, you'll have to 'Register token for Travelling'</li>"; | |
helpText += "</ul>"; | |
helpText += "</div>"; | |
helpText += "<div style='font-size: smaller'>"; | |
helpText += "In addition to the gui buttons, you can make macros to activate the features. Here are some commands to play with."; | |
helpText += '<div style='+ exampleStyle +'> !psIsoFacing</div>'; | |
helpText += '<div style='+ exampleStyle +'> !psIsoFacing --register ' + exampleTokenSelect + '</div>'; | |
helpText += '<div style='+ exampleStyle +'> !psIsoFacing --deregister ' + exampleTokenSelect + '</div>'; | |
helpText += '<div style='+ exampleStyle +'> !psIsoFacing --fliph ' + exampleTokenSelect + '</div>'; | |
helpText += '<div style='+ exampleStyle +'> !psIsoFacing --reset</div>'; | |
//whisper(playerName, helpText ); | |
var helpHandouts; | |
helpHandouts = findObjs({ | |
_type: "handout", | |
name: "psIsoFacing Help" | |
}); | |
var helpHandout = helpHandouts[0]; | |
//log("helpHandout = " + helpHandout); | |
if (!helpHandout) { // create it | |
helpHandout = createObj('handout', { | |
name: 'psIsoFacing Help', | |
inplayerjournals: 'all' | |
}); | |
helpHandout.set("notes", helpText); | |
} else { // it exists, set it's contents to match the latest version of the script | |
helpHandout.set("notes", helpText); | |
} | |
var handoutID = helpHandout.get("_id"); | |
var chatMessage = ""; | |
var buttonStyle = "'background-color: AntiqueWhite; text-align: center'"; | |
chatMessage += "<div style="+buttonStyle+"><a href='http://journal.roll20.net/handout/" + handoutID + "'>Additional Information</a></div>"; | |
return(chatMessage); | |
}; | |
var getHelp = function helpGetter() { | |
var helpText = ""; | |
helpText += "<div style='text-align: center;'>"; | |
helpText += makeButton("Status", "!psIsoFacing --status"); | |
helpText += makeButton("Toggle On/Off for all", "!psIsoFacing --toggle all"); | |
var tokenSelect = ch('@') + ch('{') + 'selected' + ch('|') + 'token_id' + ch('}'); | |
var tokenTarget = ch('@') + ch('{') + 'target' + ch('|') + 'token_id' + ch('}'); | |
helpText += makeButton("Register token for Travelling", "!psIsoFacing --register " + tokenTarget ); | |
helpText += makeButton("De-Register token for Travelling", "!psIsoFacing --deregister " + tokenTarget ); | |
helpText += makeButton("Toggle Use Tread Status Indicator", "!psIsoFacing --toggle tread"); | |
helpText += makeButton("Flip Horizontal", "!psIsoFacing --fliph " + tokenTarget); | |
helpText += makeButton("Reset", "!psIsoFacing --reset "); | |
helpText += "</div>"; | |
helpText += showDetailedHelp("gm"); | |
whisper("gm", helpText); | |
}; | |
var hasPermissions = function permissionIdentifier(tokenID, playerID) { | |
var tokenObj = getObj("graphic", tokenID); | |
var characterID = tokenObj.get("represents"); | |
var playerHasPermission = false; | |
if (playerIsGM(playerID)) { | |
playerHasPermission = true; | |
} else if (tokenObj.get("controlledby").indexOf(playerID) !== -1) { | |
playerHasPermission = true; | |
} else if ( characterID !== undefined ) { | |
var character = getObjs("character", characterID); | |
if (character.get("controlledby").indexOf(playerID) !== -1 ) { | |
playerHasPermission = true; | |
} | |
} else { | |
playerHasPermission = false; | |
} | |
if (debug) log("hasPermissions("+tokenID+", "+ playerID + ") is returning: " + playerHasPermission); | |
return playerHasPermission; | |
}; | |
var flipTokenIfPermitted = function tokenFlipper(tokenID, playerID) { | |
if (hasPermissions( tokenID, playerID)) { // make sure players can't flip someone else's token | |
var tokenObj = getObj("graphic", tokenID); | |
tokenObj.set("fliph",!tokenObj.get("fliph")); | |
} | |
}; | |
/* | |
_/_/_/ _/ _/ _/_/_/ _/ _/ _/_/_/_/_/ | |
_/ _/_/ _/ _/ _/ _/ _/ _/ | |
_/ _/ _/ _/ _/_/_/ _/ _/ _/ | |
_/ _/ _/_/ _/ _/ _/ _/ | |
_/_/_/ _/ _/ _/ _/_/ _/ | |
*/ | |
var handleInput = function inputHandler(msg) { | |
if (msg.type == "api" && msg.content.indexOf("!psIsoFacing") !== -1 ) { | |
var argsFromUser, | |
who, | |
errors=[], | |
playerID, | |
playerName, | |
pageID, | |
tokenID, | |
requestedToggle, | |
userCommand; | |
playerName = msg.who; | |
playerID = msg.playerid; | |
argsFromUser = msg.content.split(/ +/); | |
userCommand = argsFromUser[1]; | |
tokenID = argsFromUser[2]; | |
requestedToggle = argsFromUser[2]; | |
//whisper("gm", "heard: " + userCommand); | |
//whisper("gm", "heard: " + tokenID); | |
switch(userCommand) { | |
case "--on": | |
config.resizeOnAdd = true; | |
break; | |
case "--off": | |
config.resizeOnAdd = false; | |
break; | |
case "--status": | |
getStatus(); | |
break; | |
case "--toggle": | |
if ( requestedToggle == "all") { | |
config.scriptIsActive = !config.scriptIsActive; | |
whisper("gm", "config.scriptIsActive = " + config.scriptIsActive); | |
} else if (requestedToggle == "tread") { | |
config.useTreadStatusForFacing = !config.useTreadStatusForFacing; | |
whisper("gm", "config.useTreadStatusForFacing = " + config.useTreadStatusForFacing); | |
} else { // assume requested toggle is tokenID | |
if (_.has(travellers, tokenID) ) { | |
travellers[tokenID].remove(); | |
} else { | |
registerTokenForFacing(tokenID); | |
} | |
} | |
break; | |
case '--fliph': | |
flipTokenIfPermitted(tokenID, playerID); | |
break; | |
case '--register': | |
registerTokenForFacing(tokenID); | |
break; | |
case '--deregister': | |
deregisterTokenForFacing(tokenID); | |
break; | |
case '--help': | |
getHelp(); | |
break; | |
case '--reset': | |
reset(getCurrentPage(playerID)); | |
break; | |
case '--resetall': | |
_.each(allActivePages(), function(pageID) { | |
reset(pageID); | |
}); | |
break; | |
case undefined: | |
getHelp(); | |
break; | |
case '--inspect': | |
if (getObj("graphic", tokenID) !== undefined) { | |
if (debug) psLog("properties of " + JSON.stringify(getObj("graphic", tokenID)), "DarkKhaki"); | |
} | |
break; | |
} | |
} | |
}; | |
/* | |
_/_/_/ _/_/_/_/ _/_/_/ _/_/_/ _/_/_/ _/_/_/_/_/ _/_/_/_/ _/_/_/ | |
_/ _/ _/ _/ _/ _/ _/ _/ _/ _/ | |
_/_/_/ _/_/_/ _/ _/_/ _/ _/_/ _/ _/_/_/ _/_/_/ | |
_/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ | |
_/ _/ _/_/_/_/ _/_/_/ _/_/_/ _/_/_/ _/ _/_/_/_/ _/ _/ | |
*/ | |
// **** What are you trying to achieve here? Is this supposed to be an on/off toggle, or what? | |
// If you rely on the tread or yellow status indicator, then you don't need a register routine. | |
// But, too many status indicators makes the token look ugly. | |
var registerTokenForFacing = function tokenFacingRegistrar(tokenID) { | |
if (debug) psLog("in registerTokenForFacing with " + tokenID); | |
if (tokenID === undefined || getObj("graphic", tokenID) === undefined ) { | |
log("Error: registerTokenForFacing called without a tokenID parameter."); | |
return false; | |
} else if ( _.has(travellers, tokenID) === false ) { // this token is not yet registered | |
travellers[tokenID] = new travellerObj(tokenID); | |
instantiateTravellerMethods(travellers[tokenID]); | |
whisper("gm", "registered " + tokenID); | |
whisper("gm", "travellers = " + _.keys(travellers) ); | |
return travellers[tokenID]; // travellerObj | |
} else { // this token is already registered. | |
whisper("gm", "token: " + tokenID + " is already registered"); | |
return travellers[tokenID]; // returning a travellerObj | |
} | |
}; | |
/* This never gets called? | |
var registerTokenForFlashlight = function flashlightRegistrar(tokenID) { | |
if (debug) psLog("in registerTokenForFlashlight with " + tokenID); | |
if (tokenID === undefined || getObj("graphic", tokenID) === undefined ) { | |
log("Error: registerTokenForFlashlight called without a tokenID parameter."); | |
return false; | |
} else if ( _.has(flashlights, tokenID) === false ) { // this token is not yet registered | |
//flashlights[tokenID] = new flashlightObj(tokenID); | |
lightFlashlight(tokenID); | |
return flashlights[tokenID]; // travellerObj | |
} else { // this token is already registered. | |
whisper("gm", "token: " + tokenID + " is already registered"); | |
return flashlights[tokenID]; // returning a travellerObj | |
} | |
}; | |
*/ | |
var deregisterTokenForFacing = function tokenDeRegistrar(tokenID){ | |
log("in deregisterTokenForFacing with " + tokenID); | |
if (_.has(travellers, tokenID) ) { | |
var thisTraveller = travellers[tokenID]; | |
thisTraveller.remove(); | |
} | |
}; | |
var deregisterTokenForFlashlight = function flashlightDeRegistrar(tokenID){ | |
if (debug) psLog("in deregisterTokenForFlashlight with " + tokenID); | |
if (_.has(flashlights, tokenID) ) { | |
var thisFlashlight = flashlights[tokenID]; | |
//thisFlashlight.remove(); | |
extinguishFlashlight(tokenID); | |
} | |
}; | |
var getLastLocation = function lastLocationGetter(token) { | |
//if (debug) psLog("lastmove for " + token.get("name") + ", " + token.get("_id") + " = " + token.get("lastmove") ); | |
var movementPath = token.get("lastmove").split(","); | |
var lastLocation = []; | |
lastLocation[0] = Number(movementPath[0]); // important to type convert them into numbers | |
lastLocation[1] = Number(movementPath[1]); | |
return lastLocation; | |
}; | |
var isTokenOwned = function ownerChecker(tokenObj) { | |
var controlledby = tokenObj.get("controlledby"); | |
var represents = tokenObj.get("represents"); | |
// log("name: " + tokenObj.get("name") + ", controlledby: " + controlledby + ", represents: " + represents); | |
if ( !controlledby && !represents ) { | |
// log("tokenIsOwned === false"); | |
return false; | |
} else { | |
// log("tokenIsOwned === true"); | |
return true; | |
} | |
}; | |
/* | |
_/_/_/ _/ _/ _/_/_/ _/_/_/ _/_/_/_/ _/_/_/ _/_/_/_/_/ | |
_/ _/_/ _/ _/ _/ _/ _/ _/ _/ | |
_/ _/ _/ _/ _/_/ _/_/_/ _/_/_/ _/ _/ | |
_/ _/ _/_/ _/ _/ _/ _/ _/ | |
_/_/_/ _/ _/ _/_/_/ _/ _/_/_/_/ _/_/_/ _/ | |
*/ | |
var getStatus = function statusGetter() { | |
whisper("gm", "Info: " + JSON.stringify(info) ); | |
whisper("gm", "Config: " + JSON.stringify(config) ); | |
whisper("gm", "Active Travellers: " + _.keys(travellers) ); | |
whisper("gm", "Active Flashlights: " + _.keys(flashlights) ); | |
}; | |
var isTokenMarching = function activeChecker(tokenObj) { | |
//log("entered function isTokenMarching with " + JSON.stringify(tokenObj)); | |
var result = false; | |
if (!config.scriptIsActive) { // don't do anything. The user turned psIsoTraveller off for everyone. | |
//log("config.scriptIsActive == " + config.scriptIsActive); | |
result = false; | |
} else if (isTokenOwned(tokenObj) === false ) { | |
result = false; | |
} else if (config.useTreadStatusForFacing === true) { // see if the token has a tread (boot) status marker | |
var currentTreadStatus = inString(tokenObj.get("statusmarkers"), "tread"); | |
result = currentTreadStatus; | |
//log("status: " + tokenObj.get("statusmarkers") + ". currentTreadStatus == " + currentTreadStatus); | |
} else { // see if the token is "registered" from the gui. | |
var isRegisteredInTravellersList = _.has(travellers, tokenObj.get("_id") ); | |
//log("isRegisteredInTravellersList: " + isRegisteredInTravellersList); | |
result = isRegisteredInTravellersList; | |
} | |
//log("leaving isTokenMarching. Returning " + result); | |
return result; | |
}; | |
var shouldTokenBeLit = function flashlightChecker(tokenObj) { | |
var result = false; | |
var tokenID = tokenObj.get("_id"); | |
if (config.scriptIsActive && isTokenOwned(tokenObj) ) { // has owner | |
if (config.useYellowStatusForLight === true && inString(tokenObj.get("statusmarkers"), "yellow") === true ) { // user wants it on | |
result = true; | |
} else if ( _.has(flashlights, tokenObj.get("_id")) === true && config.useYellowStatusForLight === false ) { // user wants it on but they don't like the yellow status indicators | |
result = true; | |
} | |
} | |
if (debug) psLog("shouldTokenBeLit: " + tokenObj.get("name") + " = " + result, "yellow"); | |
return result; | |
}; | |
var isTokenLit = function tokenLightChecker(tokenObj) { | |
var result = false; | |
var tokenID = tokenObj.get("_id"); | |
if ( _.has(flashlights, tokenID) === true && flashlights[tokenID].lightID !== "" ) { // token is lit | |
result = true; | |
} else { | |
result = false; | |
} | |
if (debug) psLog("isTokenLit: " + tokenObj.get("name") + " = " + result, "yellow"); | |
return result; | |
}; | |
/* | |
_/_/_/ _/_/ _/ _/ _/_/_/ _/_/_/ _/_/_/ _/_/_/_/ _/_/_/ | |
_/ _/ _/ _/_/ _/ _/ _/ _/ _/ _/ _/ _/ | |
_/ _/ _/ _/ _/ _/ _/_/ _/ _/ _/ _/_/_/ _/_/_/ | |
_/ _/ _/ _/ _/_/ _/ _/ _/ _/ _/ _/ _/ | |
_/_/_/ _/_/ _/ _/ _/_/_/ _/_/_/ _/_/_/ _/_/_/_/ _/ _/ | |
*/ | |
var considerChangingDirection = function directionChangeDeterminator(tokenMoved) { | |
if (isTokenMarching(tokenMoved) === true) { | |
//log("entered considerChangingDirection with " + JSON.stringify(tokenMoved) ); | |
var lastLocation = getLastLocation(tokenMoved); | |
var currentLocation = [tokenMoved.get("left"), tokenMoved.get("top")]; | |
var tokenID = tokenMoved.get("_id"); | |
var thisTraveller = travellers[tokenID]; | |
if (thisTraveller === undefined) { | |
log("==> Error: currentTraveller is undefined: " + tokenID); | |
if (isTokenMarching(tokenMoved) ) { | |
thisTraveller = registerTokenForFacing(tokenID); | |
} | |
return false; | |
} | |
if (!lastLocation) { // this token hasn't ever moved.. just use the current location. | |
lastLocation = [tokenMoved.get("left"), tokenMoved.get("top")]; | |
} else { // Hey, we're moving! Check the direction and change facing. | |
var previousDirection; | |
var currentDirection; | |
previousDirection = thisTraveller.direction; | |
if ( lastLocation[0] > currentLocation[0] ) { // face right? | |
currentDirection = 1; | |
} else if ( lastLocation[0] < currentLocation[0] ) { //face left? | |
currentDirection = -1; | |
} else { // token graphic changed, but it hasn't moved on the x axis. Maybe someone updated the status markers or something. | |
return; | |
} | |
if ( currentDirection !== previousDirection ) { | |
// log("thisTraveller = " + JSON.stringify(thisTraveller)); | |
if (_.has(thisTraveller, "changeDirection") ) { | |
thisTraveller.changeDirection(); | |
} else { | |
log("thisTraveller has no changeDirection. " + JSON.stringify(thisTraveller) ); | |
} | |
} | |
} | |
} | |
}; | |
var considerLightingLight = function lightLighter(tokenMoved) { | |
var tokenID = tokenMoved.get("_id"); | |
var shouldBeLit = shouldTokenBeLit(tokenMoved); | |
var isLit = isTokenLit(tokenMoved); | |
if (debug) psLog("considerLightingLight: " + tokenMoved.get("name") , "LightGrey"); | |
if (shouldTokenBeLit(tokenMoved) === true && isTokenLit(tokenMoved) === false ) { // light this token | |
if (debug) psLog("tokenID needs flashlight: " + tokenID, "DarkGoldenRod"); | |
if (debug) psLog("flashlights: " + JSON.stringify(_.keys(flashlights)), "DarkGoldenRod"); | |
if (debug) psLog("considerLightingLight says: turn it on", "LightGrey"); | |
lightFlashlight(tokenID); | |
} else if ( shouldTokenBeLit(tokenMoved) === false && isTokenLit(tokenMoved) === true) { // turn it off | |
//flashlights[tokenID].turnOff(); // can't delete the object from inside the object. | |
extinguishFlashlight(tokenID); | |
if (debug) psLog("turn it off", "LightGrey"); | |
} | |
}; | |
var considerMovingLight = function lightMover(tokenMoved) { | |
var tokenID = tokenMoved.get("_id"); | |
if ( shouldTokenBeLit(tokenMoved) === true && isTokenLit(tokenMoved) === true ) { // that character doesn't have a flashlightObj yet | |
flashlights[tokenID].follow(); | |
} | |
/* else { // the token shouldn't be lit. | |
if (debug) psLog("considerMovingLight says this token either isn't lit, or shouldn't be lit"); | |
considerLightingLight(tokenMoved); | |
} */ | |
}; | |
/* | |
_/ _/ _/_/ _/ _/ _/_/_/_/ | |
_/_/ _/_/ _/ _/ _/ _/ _/ | |
_/ _/ _/ _/ _/ _/ _/ _/_/_/ | |
_/ _/ _/ _/ _/ _/ _/ | |
_/ _/ _/_/ _/ _/_/_/_/ | |
*/ | |
var handleTokenMove = function tokenMoveHandler(tokenMoved) { | |
if (debug) psLog("entering handleTokenMoved with: tokenObj for " + tokenMoved.get("name")); | |
considerLightingLight(tokenMoved); | |
considerMovingLight(tokenMoved); | |
considerChangingDirection(tokenMoved); | |
}; | |
var handleTokenStatusMarkersChange = function tokenStatusMarkerChangeHandler(tokenChanged) { | |
considerLightingLight(tokenChanged); | |
}; | |
/* | |
_/ _/ _/_/_/_/_/ _/_/_/ _/ _/_/_/ | |
_/ _/ _/ _/ _/ _/ | |
_/ _/ _/ _/ _/ _/_/ | |
_/ _/ _/ _/ _/ _/ | |
_/_/ _/ _/_/_/ _/_/_/_/ _/_/_/ | |
*/ | |
var psPoint = function(x, y) { | |
this.x = x; | |
this.y = y; | |
}; | |
var makeVector = function(point1, point2) { | |
if (debug) psLog("makeVector received: " + JSON.stringify(point1) + ", " + JSON.stringify(point2) , "pink"); | |
var resultVector = new psPoint( (point2.x - point1.x) , (point2.y - point1.y) ); | |
if (debug) psLog("makeVector returning: " + JSON.stringify(resultVector) , "pink"); | |
return resultVector; | |
}; | |
var vectorLength = function(point) { | |
var distance = Math.sqrt( Math.pow(point.x, 2) + Math.pow(point.y, 2) ); | |
return distance; | |
}; | |
var makeUnitVector = function(point) { | |
if (debug) psLog("makeUnitVector received " + JSON.stringify(point) ); | |
var distance = vectorLength(point); | |
if (distance !== 0) { | |
return new psPoint( (point.x/distance), (point.y/distance) ); | |
} else { | |
return NaN; | |
} | |
}; | |
var vectorToRoll20Rotation = function(point) { | |
if (debug) psLog("vectorToRoll20Rotation received " + JSON.stringify(point), "yellow"); | |
var theta = Math.atan2(point.y, point.x); | |
var roll20rotation = (theta * 180/3.1415) + 90; | |
if (debug) psLog("vectorToRoll20Rotation returning: " + roll20rotation); | |
return roll20rotation; | |
}; | |
var radiansToRoll20Degrees = function(rads) { | |
return 360 - (rads * 180/3.1415); | |
}; | |
var getCurrentPage = function currentPageGetter(playerID) { | |
var currentPage; | |
var playerSpecificPages = Campaign().get("playerspecificpages"); | |
var playerRibbon = Campaign().get("playerpageid"); | |
if (playerID === undefined || getObj("player", playerID) === undefined) { | |
currentPage = false; | |
} else { | |
if (playerIsGM(playerID)) { | |
var lastPage = getObj("player", playerID).get("_lastpage"); | |
if ( lastPage !== "" ) { | |
currentPage = lastPage; | |
} else { | |
currentPage = playerRibbon; | |
} | |
} else { // non-gm player | |
if (playerSpecificPages !== false && playerSpecificPages[playerID] !== undefined ) { | |
currentPage = playerSpecificPages[playerID]; | |
} else { | |
currentPage = playerRibbon; | |
} | |
} | |
} | |
if (debug) log("getCurrentPage is returning "+ currentPage); | |
return currentPage; | |
}; | |
var allActivePages = function activePagesGetter() { | |
var allPages = []; | |
var playerRibbon = Campaign().get("playerpageid"); // pageid | |
allPages.push(playerRibbon); | |
var playerSpecificPages = _.values(Campaign().get("playerspecificpages")); // pageids | |
_.each(_.uniq(playerSpecificPages), function(pageID) { | |
allPages.push(pageID); | |
}); | |
var players = findObjs({type: "player"}); | |
_.each(players, function(player) { | |
if (playerIsGM(player.get("_id"))) { | |
var gmPlayer = player; | |
var gmPage = gmPlayer.get("_lastpage"); // pageid | |
if (gmPage !== "") { | |
allPages.push(gmPage); | |
} | |
} | |
}); | |
return allPages; | |
}; | |
/* | |
_/_/_/ _/ _/ _/_/_/ _/_/_/_/_/ | |
_/ _/_/ _/ _/ _/ | |
_/ _/ _/ _/ _/ _/ | |
_/ _/ _/_/ _/ _/ | |
_/_/_/ _/ _/ _/_/_/ _/ | |
*/ | |
var reconnectOldFlashlights = function flashlightReconnector(pageID) { | |
if (pageID === undefined) { | |
pageID = Campaign().get("playerpageid"); | |
} | |
var activeFlashlights = findObjs({ | |
_pageid: pageID, | |
name: "flashlight", | |
}); | |
_.each( activeFlashlights, function(flashlightGraphic) { | |
var parentTokenID = flashlightGraphic.get("gmnotes"); | |
if (getObj("graphic", parentTokenID) !== undefined ) { | |
var reconnectedFlashlight = new flashlightObj(parentTokenID); | |
reconnectedFlashlight.lightID = flashlightGraphic.get("_id"); | |
flashlights[parentTokenID] = reconnectedFlashlight; | |
} | |
}); | |
}; | |
var reset = function resetter(pageID) { // remove all flashlights | |
if (pageID === undefined) { // screw it, we'll reset every page that has a player on it. | |
log("==> Error: reset function received no pageID"); | |
} | |
config = _.clone(defaultConfig); | |
travellers = {}; | |
flashlights = {}; | |
var activeFlashlights = findObjs({ | |
_pageid: pageID, | |
name: "flashlight", | |
}); | |
_.each( activeFlashlights, function(flashlightGraphic) { | |
flashlightGraphic.remove(); | |
}); | |
if (debug) psLog("reset to defaults"); | |
}; | |
var checkInstall = function installChecker() { | |
if ( !_.has(state, "psIsoFacing") || state.psIsoFacing.info.version !== info.version ) { // populate state from default values | |
state.psIsoFacing = { | |
info: info, | |
config: config, | |
travellers: _.keys(travellers), // state will hold a simple list of traveller IDs. It can't hold complex objects | |
flashlights: _.keys(flashlights) | |
}; | |
} else { // use values from roll20 persistent state object for the campaign. | |
travellers = {}; | |
_.each(state.psIsoFacing.travellers, function(travellerID) { | |
travellers[travellerID] = new travellerObj(); | |
instantiateTravellerMethods(travellers[travellerID]); | |
} ); | |
//travellers = state.psIsoFacing.travellers; // state simplifies objects and strips out methods. Hopefully they're still instances of travellerObj | |
flashlights = {}; | |
_.each(state.psIsoFacing.flashlights, function(flashlightID) { | |
flashlights[flashlightID] = new flashlightObj(); | |
instantiateFlashlightMethods(flashlights[flashlightID]); | |
}); | |
reconnectOldFlashlights(); | |
//flashlights = state.psIsoFacing.flashlights; | |
config = _.clone(state.psIsoFacing.config); | |
info = _.clone(state.psIsoFacing.info); | |
} | |
_.each(travellers, function(traveller) { | |
instantiateTravellerMethods(traveller); | |
}); | |
_.each(flashlights, function(flashlight) { | |
instantiateFlashlightMethods(flashlight); | |
}); | |
log("psIsoFacing is ready."); | |
}; | |
var registerEventHandlers = function eventHandlerRegistrar() { | |
if (debug) psLog("psIsoFacing registered event handlers"); | |
on('chat:message', handleInput ); | |
on('change:graphic', handleTokenMove ); | |
on("change:graphic:statusmarkers", handleTokenStatusMarkersChange); | |
}; | |
return { // expose functions for outside calls | |
RegisterEventHandlers: registerEventHandlers, | |
CheckInstall: checkInstall, | |
Reset: reset, | |
ShowDetailedHelp: showDetailedHelp, | |
psLog: psLog | |
}; | |
}()); | |
on("ready",function(){ | |
psIsoFacing.psLog("on('ready') just fired.", "burlywood"); | |
psIsoFacing.CheckInstall(); // instantiate all the function expressions | |
psIsoFacing.RegisterEventHandlers(); // instantiate all the listeners | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment