Character Vault
Any Concept / Any System
Compendium
Your System Come To Life
Roll20 for Android
Streamlined for your Tablet
Roll20 for iPad
Streamlined for your Tablet

Personal tools

Difference between revisions of "Script:Marking Conditions"

From Roll20 Wiki

Jump to: navigation, search
(Code)
(updated to use @target/@selected instead of poorly-implemented descriptors, and added means to remove singular markers)
Line 1: Line 1:
The following script creates the API command <code>!mark</code>
+
The following script creates the API commands <code>!mark</code>, <code>!unmark</code>, and <code>!clearmark</code>
  
 
==== Syntax ====
 
==== Syntax ====
<blockquote style="border:1px #0088ee solid;background:#eee;padding:0.5em">!mark ''tokenset'' [''status'' [''type'']]</blockquote>
+
<blockquote style="border:1px #0088ee solid;background:#eee;padding:0.5em">!mark ''tokenid'' [''status'' [''type'']]<br>
 +
!unmark ''tokenid'' [''status'' [''type'']]<br>
 +
!clearmark ''tokenid''</blockquote>
  
 
{| class="wikitable"
 
{| class="wikitable"
Line 9: Line 11:
 
! Values
 
! Values
 
|-
 
|-
| ''tokenset''
+
| ''tokenid''
| Defines the set of tokens on the map to mark. One of:
+
| The ID of the token to mark. Use <code><nowiki>@{target|token_id}</nowiki></code> or <code><nowiki>@{selected|token_id}</nowiki></code> to get this value.
* The name of a token
+
* !all
+
* !key=value[,key=value[,key=value...etc]]
+
 
|-
 
|-
 
| ''status''
 
| ''status''
 
| (Optional) A D&D 4e status (except for dying, helpless, unconscious, insubstantial, or surprised) or the name of a [[API:Objects#Graphic (Token/Map/Card/Etc.)|statusmarker icon]]. The script has several aliases for the statuses. For example, immobilized, immobile, and immob all correspond to the same marker.
 
| (Optional) A D&D 4e status (except for dying, helpless, unconscious, insubstantial, or surprised) or the name of a [[API:Objects#Graphic (Token/Map/Card/Etc.)|statusmarker icon]]. The script has several aliases for the statuses. For example, immobilized, immobile, and immob all correspond to the same marker.
  
If ''status'' is not specified, the purple statusmarker will be used (which corresponds to the Marked status).
+
If ''status'' is not specified, the purple statusmarker will be used.
 
|-
 
|-
 
| ''type''
 
| ''type''
Line 35: Line 34:
 
|}
 
|}
  
==== The ''tokenset'' Parameter ====
+
The <code>!mark</code> command will set a marker, while the <code>!unmark</code> command will clear it. <code>!clearmark</code> will clear all markers from the token.
The ''tokenset'' parameter requires some additional notice. If a token's name is used, obviously that token will be the only token in the set. Alternatively, if <code>!all</code> is used, all tokens on the map will be in the set. The value that requires explanation is the <code>!key=value</code> list.
+
 
+
''key'' may be one of:
+
* distance
+
* controlledby
+
* bar1
+
* bar2
+
* bar3
+
 
+
Values for the <code>distance</code> key may be:
+
* nearest
+
* furthest
+
* ''N''
+
* ''>N''
+
* ''<N''
+
 
+
If you use any <code>distance</code> key, you must have a token selected, and that token will not be part of the <code>tokenset</code>. ''N'' looks for tokens '''exactly''' N squares from the selected token. ''>N'' and ''<N'' look for tokens N or more, or else N or less squares away from the selected token, as appropriate. ''nearest'' and ''furthest'' calculate the rest of the <code>tokenset</code>, and then select the nearest or furthest tokens within that set. (There might be multiple results, if there are multiple equidistant tokens.)
+
 
+
Values for the <code>controlledby</code> key may be:
+
* none
+
* ''name''
+
 
+
If you use <code>none</code>, the <code>tokenset</code> will include tokens with no controlling player, or which represent characters with no controlling player (generally, NPC tokens). Otherwise, the mark will look for a token controlled by a player named ''name'', or a token representing a character named ''name''.
+
 
+
Values for the <code>barN</code> keys may be:
+
* ''N''
+
* ''>N''
+
* ''<N''
+
 
+
If ''N'' is used as the value, the <code>tokenset</code> will look for tokens with a current value of exactly N; the token does not need a maximum value set. Otherwise, N should be a value in the [0..1] range, the token needs both current and maximum values, and the <code>tokenset</code> will search for tokens with the relevant bar at or above (or at or below) the given percentage.
+
  
 
==== Code ====
 
==== Code ====
 
<pre data-language="javascript">
 
<pre data-language="javascript">
/**
+
on('chat:message', function(msg) {
* Provides commands for marking enemies.
+
     if(msg.type != 'api') return;
*/
+
var conditions = conditions || {};
+
conditions.markingCharacters = [];
+
on("chat:message", function(msg) {
+
     if(msg.type != 'api' || msg.content.toLowerCase().indexOf('!mark') != 0) return;
+
 
      
 
      
     var args = msg.content.toLowerCase().split(' ');
+
     var args = msg.content.split(' ');
     args.shift();
+
     var command = args.shift().toLowerCase().substring(1);
 
      
 
      
    var mark = 'purple'; // default to defender's "mark" condition
+
     if (!(command == 'mark' || command == 'clearmark' || command == 'unmark')) return;
     if(args[1]) switch(args[1])
+
    if (args.length == 0) {
    {
+
        sendChat('ERROR', '/w ' + msg.who + ' Please supply a token target with @{target|token_id}');
        case 'blinded':
+
         return;
        case 'blind':
+
            mark = 'bleeding-eye';
+
            break;
+
        case 'dazed':
+
        case 'daze':
+
            mark = 'pummeled';
+
            break;
+
        case 'deafened':
+
        case 'deaf':
+
            mark = 'screaming';
+
            break;
+
        case 'dominated':
+
        case 'dominate':
+
            mark = 'chained-heart';
+
            break;
+
        case 'immobilized':
+
        case 'immobile':
+
        case 'immob':
+
            mark = 'fishing-net';
+
            break;
+
        case 'marked':
+
        case 'mark':
+
            mark = 'purple';
+
            break;
+
        case 'petrified':
+
        case 'petrify':
+
        case 'stone':
+
            mark = 'white-tower';
+
            break;
+
        case 'prone':
+
            mark = 'back-pain';
+
            break;
+
        case 'restrained':
+
            mark = 'aura';
+
            break;
+
        case 'slowed':
+
        case 'slow':
+
            mark = 'snail';
+
            break;
+
        case 'stunned':
+
        case 'stun':
+
            mark = 'lightning-helix';
+
            break;
+
        case 'weakened':
+
        case 'weak':
+
            mark = 'broken-heart';
+
            break;
+
        case 'ongoing':
+
        case 'damage':
+
        case 'dam':
+
            if(args[2]) switch(args[2])
+
            {
+
                case 'acid':
+
                    mark = 'chemical-bolt';
+
                    break;
+
                case 'cold':
+
                    mark = 'frozen-orb';
+
                    break;
+
                case 'fire':
+
                    mark = 'half-haze';
+
                    break;
+
                case 'force':
+
                    mark = 'blue';
+
                    break;
+
                case 'lightning':
+
                    mark = 'edge-crack';
+
                    break;
+
                case 'necrotic':
+
                    mark = 'death-zone';
+
                    break;
+
                case 'poison':
+
                    mark = 'skull';
+
                    break;
+
                case 'psychic':
+
                    mark = 'pink';
+
                    break;
+
                case 'radiant':
+
                    mark = 'angel-outfit';
+
                    break;
+
                case 'thunder':
+
                    mark = 'yellow';
+
                    break;
+
                default:
+
                    sendChat('ERROR', '/w ' + msg.who + ' No damage type called ' + args[2]
+
                            + '. If the damage has no type, do not include a type!');
+
                    return;
+
            }
+
            else mark = 'all-for-one'; // untyped ongoing damage
+
            break;
+
         case 'dying':
+
        case 'helpless':
+
        case 'unconscious':
+
        case 'insubstantial':
+
        case 'surprised':
+
            sendChat('ERROR', '/w ' + msg.who + ' The ' + args[1]
+
                            + ' status is not implemented for the !mark command.');
+
            return;
+
            break;
+
        default:
+
            mark = args[1]; // allows for direct status setting
+
            break;
+
 
     }
 
     }
 
      
 
      
     var so = msg.selected || {};
+
     var mark = 'purple'; // default to defender's "mark" condition
    var tgtList = conditions.targetList(args[0], so, msg.who);
+
    if(args[1]) {
    if(mark == 'none') conditions.unmarkTokens(tgtList, msg.who);
+
        args[1] = args[1].toLowerCase();
    else conditions.markTokens(tgtList, msg.who, mark);
+
         switch(args[1])
});
+
 
+
/**
+
* Parses the input to search for tokens which need to be marked (or unmarked).
+
* The tokens are returned as an array. The descriptor begins with an
+
* exclamation point to differentiate it from polling a single token, and is a
+
* comma-separated set of key=value pairs. All pairs will be true for each
+
* token selected. For example, !distance=<1,controlledby=none,bar1=<.5 would
+
* find only those tokens which are within 1 square of the currently selected
+
* token, are controlled by nobody (ie, NPCs), and have 50% or less remaining
+
* on bar1.
+
*
+
* all                  All tokens on the current page
+
* distance=N          A token exactly N squares from the currently selected token
+
* distance=<N          A token N squares or less from the currently selected token
+
* distance=>N          A token N squares or more from the currently selected token
+
* distance=nearest    The nearest token to the currently selected token
+
* distance=furthest    The furthest token from the currently selected token
+
* controlledby=none    A token not controlled by any player -- generally, the option to use for finding NPCs
+
* controlledby=player  A token controlled by the player named `player'
+
* bar1=N              A token whose current bar1 value is N
+
* bar1=<N              A token whose current bar1 value is N% or less
+
* bar1=>N              A token whose current bar1 value is N% or more
+
* bar2=N              A token whose current bar2 value is N
+
* bar2=<N              A token whose current bar2 value is N% or less
+
* bar2=>N              A token whose current bar2 value is N% or more
+
* bar3=N              A token whose current bar3 value is N
+
* bar3=<N              A token whose current bar3 value is N% or less
+
* bar3=>N              A token whose current bar3 value is N% or more
+
*
+
* The `distance' options will never return the currently selected token, only
+
* other tokens based on their distance from the currently selected token.
+
*
+
* The `all' option does not take any value, is ignored if it isn't the first
+
* option, and ignores all other options if it's first. It's a loner, used
+
* mostly for cleaning things up (eg, !mark !all none to clear all marks on the
+
* page).
+
*
+
* @param descriptor    arg0 of the user input. This should be either a
+
*                      formatted search for tokens, or the name of a single
+
*                      token.
+
* @param selectedObjs  an array of selected objects
+
* @param who          the player to send whispers about errors
+
* @return  an array of token objects which will have their statusmarkers modified
+
*/
+
conditions.targetList = function(descriptor, selectedObjs, who)
+
{
+
    var selected;
+
    var targets = [];
+
   
+
    for(var i = 0; i < selectedObjs.length; i++)
+
    {
+
         if(selectedObjs[i]._type == 'graphic')
+
 
         {
 
         {
             var tmp = getObj(selectedObjs[i]._type, selectedObjs[i]._id);
+
             case 'blinded':
             if(tmp && tmp.get('_subtype') == 'token')
+
             case 'blind':
             {
+
                mark = 'bleeding-eye';
                 selected = tmp; // use the first token we find as reference for 'nearest'/'furthest'
+
                break;
 +
             case 'dazed':
 +
            case 'daze':
 +
                 mark = 'pummeled';
 +
                break;
 +
            case 'deafened':
 +
            case 'deaf':
 +
                mark = 'screaming';
 
                 break;
 
                 break;
             }
+
             case 'dominated':
        }
+
            case 'dominate':
    }
+
                mark = 'chained-heart';
   
+
                break;
    if(descriptor.indexOf('!') == 0) // descriptor describes a group of tokens
+
            case 'immobilized':
    {
+
            case 'immobile':
        var parts = descriptor.substring(1).split(',');
+
            case 'immob':
        var filters = [];
+
                mark = 'fishing-net';
        var error = false;
+
                break;
       
+
            case 'marked':
        if(parts[0] == 'all') filters.push(function(tgt) { return true; });
+
            case 'mark':
        else parts.forEach(function(kvp) {
+
                mark = 'purple';
             var key = kvp.substring(0, kvp.indexOf('='));
+
                break;
             var value = kvp.substring(kvp.indexOf('=')+1);
+
            case 'petrified':
            var func;
+
            case 'petrify':
              
+
            case 'stone':
             switch(key)
+
                mark = 'white-tower';
            {
+
                break;
                 case 'distance':
+
            case 'prone':
                    if(!selected)
+
                mark = 'back-pain';
 +
                break;
 +
            case 'restrained':
 +
                mark = 'aura';
 +
                break;
 +
            case 'slowed':
 +
             case 'slow':
 +
                mark = 'snail';
 +
                break;
 +
             case 'stunned':
 +
            case 'stun':
 +
                mark = 'lightning-helix';
 +
                break;
 +
             case 'weakened':
 +
             case 'weak':
 +
                mark = 'broken-heart';
 +
                 break;
 +
            case 'ongoing':
 +
            case 'damage':
 +
            case 'dam':
 +
                if(args[2]) {
 +
                    args[2] = args[2].toLowerCase();
 +
                    switch(args[2])
 
                     {
 
                     {
                         sendChat('ERROR', '/w ' + who + ' There is no token selected.');
+
                         case 'acid':
                        error = true;
+
                            mark = 'chemical-bolt';
                        return;
+
                            break;
 +
                        case 'cold':
 +
                            mark = 'frozen-orb';
 +
                            break;
 +
                        case 'fire':
 +
                            mark = 'half-haze';
 +
                            break;
 +
                        case 'force':
 +
                            mark = 'blue';
 +
                            break;
 +
                        case 'lightning':
 +
                            mark = 'edge-crack';
 +
                            break;
 +
                        case 'necrotic':
 +
                            mark = 'death-zone';
 +
                            break;
 +
                        case 'poison':
 +
                            mark = 'skull';
 +
                            break;
 +
                        case 'psychic':
 +
                            mark = 'pink';
 +
                            break;
 +
                        case 'radiant':
 +
                            mark = 'angel-outfit';
 +
                            break;
 +
                        case 'thunder':
 +
                            mark = 'yellow';
 +
                            break;
 +
                        default:
 +
                            sendChat('ERROR', '/w ' + msg.who + ' No damage type called ' + args[2]
 +
                                    + '. If the damage has no type, do not include a type!');
 +
                            return;
 
                     }
 
                     }
                    var sL = selected.get('left');
 
                    var sT = selected.get('top');
 
                   
 
                    if(value == 'nearest' || value == 'furthest') func = function(tgt) {
 
                        // near/far can't be decided until after all other filters have run;
 
                        // using dumy function until then
 
                        return tgt != selected;
 
                    };
 
                    else if(value.indexOf('<') == 0) func = function(tgt) {
 
                        // find tokens <= value spaces away
 
                        var squares = (+value.substring(1)) * 70;
 
                        var distL = Math.abs(sL - tgt.get('left'));
 
                        var distT = Math.abs(sT - tgt.get('top'));
 
                       
 
                        return tgt != selected && distL <= squares && distT <= squares;
 
                    }
 
                    else if(value.indexOf('>') == 0) func = function(tgt) {
 
                        // find tokens >= value spaces away
 
                        var squares = (+value.substring(1)) * 70;
 
                        var distL = Math.abs(sL - tgt.get('left'));
 
                        var distT = Math.abs(sT - tgt.get('top'));
 
                       
 
                        return tgt != selected && (distL >= squares || distT >= squares);
 
                    }
 
                    else func = function(tgt) {
 
                        // find tokens exactly value spaces away
 
                        var squares = (+value) * 70;
 
                        var distL = Math.abs(sL - tgt.get('left'));
 
                        var distT = Math.abs(sT - tgt.get('top'));
 
                       
 
                        return tgt != selected && ((distL == squares && distT == squares)
 
                                                || (distL == squares && distT < squares)
 
                                                || (distL < squares && distT == squares));
 
                    }
 
                    break;
 
                case 'controlledby':
 
                    if(value == 'none') func = function(tgt) {
 
                        // find NPC tokens
 
                        if(tgt.get('_represents') == '')
 
                            return tgt.get('controlledby') == '';
 
                        else
 
                        {
 
                            var character = getObj('character', tgt.get('_represents'));
 
                            return character.get('controlledby') == '';
 
                        }
 
                    }
 
                    else func = function(tgt) {
 
                        // find tokens controlled by value
 
                        var players = findObjs({_type: 'player', _displayname: value},
 
                                                {caseInsensitive: true});
 
                        if(players[0]) return tgt.get('controlledby').indexOf(players[0].id) != -1;
 
                        players = findObjs({_type: 'player', _displayname: value.replace('-', ' ')},
 
                                            {caseInsensitive: true});
 
                        if(players[0]) return tgt.get('controlledby').indexOf(players[0].id) != -1;
 
                       
 
                        var characters = findObjs({_type: 'character', name: value},
 
                                                    {caseInsensitive: true});
 
                        if(characters[0]) return tgt.get('_represents') == characters[0].id;
 
                        characters = findObjs({_type: 'character', name: value.replace('-', ' ')},
 
                                                {caseInsensitive: true});
 
                        if(characters[0]) return tgt.get('_represents') == characters[0].id;
 
                       
 
                        return false;
 
                    }
 
                    break;
 
                case 'bar1':
 
                case 'bar2':
 
                case 'bar3':
 
                    if(value.indexOf('<') == 0) func = function(tgt) {
 
                        // find tokens with bar# at or below value%
 
                        var barCur = tgt.get(key+'_value');
 
                        var barMax = tgt.get(key+'_max');
 
                        var percent = barCur / barMax;
 
                        if(barMax == NaN || barCur == NaN) return false;
 
                       
 
                        return percent <= (+value.substring(1));
 
                    }
 
                    else if(value.indexOf('>') == 0) func = function(tgt) {
 
                        // find tokens with bar# at or above value%
 
                        var barCur = tgt.get(key+'_value');
 
                        var barMax = tgt.get(key+'_max');
 
                        var percent = barCur / barMax;
 
                        if(barMax == NaN || barCur == NaN) return false;
 
                       
 
                        return percent >= (+value.substring(1));
 
                    }
 
                    else func = function(tgt) {
 
                        // find tokens with bar# at value
 
                        var barCur = tgt.get(key+'_value');
 
                        if(barCur == NaN) return false;
 
                       
 
                        return barCur == (+value);
 
                    }
 
                    break;
 
                default:
 
                    sendChat('ERROR', '/w ' + who + ' ' + key + ' is not a valid token filter key.');
 
                    return;
 
            }
 
           
 
            filters.push(func);
 
        });
 
       
 
        if(error) return [];
 
       
 
        targets = filterObjs(function(obj) {
 
            if(obj.get('_pageid') != Campaign().get('playerpageid')) return false;
 
            if(obj.get('_type') != 'graphic') return false;
 
            if(obj.get('_subtype') != 'token') return false;
 
           
 
            var matchAll = filters.length > 0;
 
            filters.forEach(function(f) {
 
                if(!matchAll) return;
 
                if(!f(obj)) matchAll = false;
 
            });
 
           
 
            return matchAll;
 
        });
 
       
 
        // if we have distance=nearest or distance=furthest, trim `targets' as appropriate
 
        // do nothing if $selected is null
 
        if(selected)
 
        parts.forEach(function(kvp) {
 
            var key = kvp.substring(0, kvp.indexOf('='));
 
            var value = kvp.substring(kvp.indexOf('=')+1);
 
           
 
            if(key == 'distance')
 
            {
 
                var sL = selected.get('left');
 
                var sT = selected.get('top');
 
                   
 
                if(value == 'nearest')
 
                {
 
                    var minDist = -1;
 
                    var toKeep = [];
 
                    targets.forEach(function(tgt) {
 
                        var distL = Math.abs(tgt.get('left') - sL);
 
                        var distT = Math.abs(tgt.get('top') - sT);
 
                       
 
                        var distance = Math.max(distL, distT);
 
                        if(minDist == -1 || minDist > distance)
 
                        {
 
                            toKeep = [tgt];
 
                            minDist = distance;
 
                        }
 
                        else if(minDist == distance)
 
                        {
 
                            toKeep.push(tgt);
 
                        }
 
                    });
 
                   
 
                    targets = toKeep;
 
 
                 }
 
                 }
                 else if(value == 'furthest')
+
                 else mark = 'all-for-one'; // untyped ongoing damage
                 {
+
                 break;
                    var maxDist = -1;
+
            case 'dying':
                    var toKeep = [];
+
            case 'helpless':
                    targets.forEach(function(tgt) {
+
            case 'unconscious':
                        var distL = Math.abs(sL - tgt.get('left'));
+
            case 'insubstantial':
                        var distT = Math.abs(sT - tgt.get('top'));
+
            case 'surprised':
                       
+
                sendChat('ERROR', '/w ' + msg.who + ' The ' + args[1]
                        var distance = Math.max(distL, distT);
+
                                + ' status is not implemented for the !mark command.');
                        if(maxDist == -1 || maxDist < distance)
+
                return;
                        {
+
                break;
                            toKeep = [tgt];
+
            default:
                            maxDist = distance;
+
                mark = args[1]; // allows for direct status setting
                        }
+
                 break;
                        else if(maxDist == distance)
+
         }
                        {
+
                            toKeep.push(tgt);
+
                        }
+
                    });
+
                   
+
                    targets = toKeep;
+
                 }
+
            }
+
         });
+
 
     }
 
     }
     else // descriptor is a token name
+
      
 +
    var target = getObj('graphic', args[0]);
 +
    if (!target) {
 +
        sendChat('ERROR', '/w ' + msg.who + ' Please specify a token to mark with @{target|token_id}.');
 +
        return;
 +
    }
 +
   
 +
    switch (command)
 
     {
 
     {
         var target = findObjs({
+
         default:
            _type: 'graphic',
+
        case 'mark':
            name: descriptor,
+
             target.set('status_'+mark, true);
             _pageid: Campaign().get('playerpageid')
+
            break;
        }, {caseInsensitive: true})[0];
+
         case 'unmark':
        if(!target)
+
             target.set('status_'+mark, false);
         {
+
             break;
             sendChat('ERROR', '/w ' + who + ' Cannot find a token named ' + args[0] + '.');
+
         case 'clearmark':
             return;
+
            target.set('statusmarkers', '');
         }
+
            break;
       
+
        targets.push(target);
+
 
     }
 
     }
   
+
});
    return targets;
+
</pre>
}
+
  
/**
+
==== Examples ====
* Marks all given targets and keeps track of who marked it.
+
'''!mark @{target|token_id} brown'''
*
+
<blockquote>This will set the brown marker on the target token.</blockquote>
* @param targets  an array of target token objects to mark
+
* @param character the character performing the mark
+
* @param mark      optional. The type of statusmarker to use for the mark.
+
*/
+
conditions.markTokens = function(targets, character, mark)
+
{
+
    targets.forEach(function(tgt) { // tgt is a token object
+
        tgt.set('status_'+mark, true);
+
    });
+
}
+
  
/**
+
'''!mark @{target|token_id} immob'''
* Removes all marks on the target tokens that were placed there by the calling
+
<blockquote>This will set the 'immobilize' icon to the target token.</blockquote>
* player.
+
*
+
* @param targets  an array of target token objects to clean up
+
* @param character the player who's removing marks -- only marks placed by the player will be removed
+
*/
+
conditions.unmarkTokens = function(targets, character)
+
{
+
    targets.forEach(function(tgt) {
+
        tgt.set('statusmarkers', '');
+
    });
+
}
+
</pre>
+
  
==== Examples ====
+
'''!mark @{selected|token_id} ongoing fire'''
'''!mark !distance=nearest,bar1<.5 brown'''
+
<blockquote>This will mark the selected token with ongoing fire damage.</blockquote>
<blockquote>This will set the brown marker on the nearest token with 50% or less of its bar1.</blockquote>
+
  
'''!mark !distance=<1,controlledby=none immob'''
+
'''!unmark @{selected|token_id}'''
<blockquote>This will set the 'immobilize' icon to all NPC tokens 1 square away (or closer!)</blockquote>
+
<blockquote>This will remove the purple (default) statusmarker from the selected token.</blockquote>
  
'''!mark Wulfgar none'''
+
'''!clearmark @{selected|token_id}'''
<blockquote>This will remove all statusmarkers from the token named Wulfgar</blockquote>
+
<blockquote>This will remove all markers from the selected token.</blockquote>
  
 
[[Category:User API Scripts|Marking Conditions]]
 
[[Category:User API Scripts|Marking Conditions]]
 
[[Category:API Commands|Marking Conditions]]
 
[[Category:API Commands|Marking Conditions]]

Revision as of 10:43, 15 June 2014

The following script creates the API commands !mark, !unmark, and !clearmark

Syntax

!mark tokenid [status [type]]
!unmark tokenid [status [type]]
!clearmark tokenid
Parameter Values
tokenid The ID of the token to mark. Use @{target|token_id} or @{selected|token_id} to get this value.
status (Optional) A D&D 4e status (except for dying, helpless, unconscious, insubstantial, or surprised) or the name of a statusmarker icon. The script has several aliases for the statuses. For example, immobilized, immobile, and immob all correspond to the same marker.

If status is not specified, the purple statusmarker will be used.

type (Optional) A D&D 4e damage type. Only used if status is ongoing, damage, or dam. One of:
  • acid
  • cold
  • fire
  • force
  • lightning
  • necrotic
  • poison
  • psychic
  • radiant
  • thunder

If type is not specified, a statusmarker to represent untyped damage will be used.

The !mark command will set a marker, while the !unmark command will clear it. !clearmark will clear all markers from the token.

Code

on('chat:message', function(msg) {
    if(msg.type != 'api') return;
    
    var args = msg.content.split(' ');
    var command = args.shift().toLowerCase().substring(1);
    
    if (!(command == 'mark' || command == 'clearmark' || command == 'unmark')) return;
    if (args.length == 0) {
        sendChat('ERROR', '/w ' + msg.who + ' Please supply a token target with @{target|token_id}');
        return;
    }
    
    var mark = 'purple'; // default to defender's "mark" condition
    if(args[1]) {
        args[1] = args[1].toLowerCase();
        switch(args[1])
        {
            case 'blinded':
            case 'blind':
                mark = 'bleeding-eye';
                break;
            case 'dazed':
            case 'daze':
                mark = 'pummeled';
                break;
            case 'deafened':
            case 'deaf':
                mark = 'screaming';
                break;
            case 'dominated':
            case 'dominate':
                mark = 'chained-heart';
                break;
            case 'immobilized':
            case 'immobile':
            case 'immob':
                mark = 'fishing-net';
                break;
            case 'marked':
            case 'mark':
                mark = 'purple';
                break;
            case 'petrified':
            case 'petrify':
            case 'stone':
                mark = 'white-tower';
                break;
            case 'prone':
                mark = 'back-pain';
                break;
            case 'restrained':
                mark = 'aura';
                break;
            case 'slowed':
            case 'slow':
                mark = 'snail';
                break;
            case 'stunned':
            case 'stun':
                mark = 'lightning-helix';
                break;
            case 'weakened':
            case 'weak':
                mark = 'broken-heart';
                break;
            case 'ongoing':
            case 'damage':
            case 'dam':
                if(args[2]) {
                    args[2] = args[2].toLowerCase();
                    switch(args[2])
                    {
                        case 'acid':
                            mark = 'chemical-bolt';
                            break;
                        case 'cold':
                            mark = 'frozen-orb';
                            break;
                        case 'fire':
                            mark = 'half-haze';
                            break;
                        case 'force':
                            mark = 'blue';
                            break;
                        case 'lightning':
                            mark = 'edge-crack';
                            break;
                        case 'necrotic':
                            mark = 'death-zone';
                            break;
                        case 'poison':
                            mark = 'skull';
                            break;
                        case 'psychic':
                            mark = 'pink';
                            break;
                        case 'radiant':
                            mark = 'angel-outfit';
                            break;
                        case 'thunder':
                            mark = 'yellow';
                            break;
                        default:
                            sendChat('ERROR', '/w ' + msg.who + ' No damage type called ' + args[2]
                                    + '. If the damage has no type, do not include a type!');
                            return;
                    }
                }
                else mark = 'all-for-one'; // untyped ongoing damage
                break;
            case 'dying':
            case 'helpless':
            case 'unconscious':
            case 'insubstantial':
            case 'surprised':
                sendChat('ERROR', '/w ' + msg.who + ' The ' + args[1]
                                + ' status is not implemented for the !mark command.');
                return;
                break;
            default:
                mark = args[1]; // allows for direct status setting
                break;
        }
    }
    
    var target = getObj('graphic', args[0]);
    if (!target) {
        sendChat('ERROR', '/w ' + msg.who + ' Please specify a token to mark with @{target|token_id}.');
        return;
    }
    
    switch (command)
    {
        default:
        case 'mark':
            target.set('status_'+mark, true);
            break;
        case 'unmark':
            target.set('status_'+mark, false);
            break;
        case 'clearmark':
            target.set('statusmarkers', '');
            break;
    }
});

Examples

!mark @{target|token_id} brown

This will set the brown marker on the target token.

!mark @{target|token_id} immob

This will set the 'immobilize' icon to the target token.

!mark @{selected|token_id} ongoing fire

This will mark the selected token with ongoing fire damage.

!unmark @{selected|token_id}

This will remove the purple (default) statusmarker from the selected token.

!clearmark @{selected|token_id}

This will remove all markers from the selected token.