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 "API:Short Community Scripts"

From Roll20 Wiki

Jump to: navigation, search
m (AddCustomInvisibleTurn)
m
 
Line 530: Line 530:
  
 
=More Community APIs=
 
=More Community APIs=
 +
* [https://app.roll20.net/forum/permalink/10934769/ sort GroupCheck buttons in alphabetic order]
 
* https://app.roll20.net/forum/post/10820404/script-note-to-tooltip
 
* https://app.roll20.net/forum/post/10820404/script-note-to-tooltip
 
* [https://app.roll20.net/forum/post/10593263/script-simple-sound Simple Sound] (in the install menu) - more simple than [[Roll20AM]]
 
* [https://app.roll20.net/forum/post/10593263/script-simple-sound Simple Sound] (in the install menu) - more simple than [[Roll20AM]]

Latest revision as of 10:51, 23 June 2022

Main Page: API:Script Index

This is a collection of shorter API scripts and snippets created the community, which haven't ended up in the one-click menu for for being so situational or small.



Contents


[edit] Chat

[edit] Whisper Self

by Oosh, Andreas J.

A shorthand to whisper messages, rolls & rolltemplates to yourself. Trying to be an equivalent to /w gm(/whisper to GM), or like a one-line version of //talktomyself.

Command: !wself

Great for using with Chat Menus, which people often make to whisper to GM with /w gm and means GMs no longer need to see these unnecessary rolls.

It doesn't archive self-whispers.

Example:

Whisper a Chat Menus to yourself, with buttons to some of your often used rolls:

!wself &{template:default} {{name=Chat Menu }} {{[Attack (d20+4)](`/r d20+4)= [Damage (1d8+2)](`/r 1d8+2)}} {{[End turn](`I END MY TURN)=[Macro Guide](https://wiki.roll20.net/Complete_Guide_to_Macros_%26_Rolls)}}


on('ready', () => {
  on('chat:message', (msg) => {
    if (msg.type === 'api' && /^!wself\s/i.test(msg.content)) {
      const pid = msg.playerid,
      player = getObj('player', pid);
      if (!player) return;
        const playerName = player.get('displayname'),
        template = msg.rolltemplate,
        newMsg = msg.content.replace(/^!wself\s*/i, template ? `&{template:${template}}` : '').replace(/\//g, '/').replace(/\`/g, '`');
        sendChat(`${playerName}`, `/w "${playerName}" ${newMsg}`, null, {noarchive: true});
    }
  });
});

Update Tips:

It might be better to just use Tim's DiscreteWhisper and throw an extra flag in it - I think that already reconstructs inline rolls, and probably handles the API buttons as well. The forward slashes look like they need to be escaped for the /r commands, so just a newMsg = newMsg.replace(/\//g, '/'); before the sendChat should do the trick. The inline rolls either need libInline to put them back in, or just use DiscreteWhisper.

Todo:

  • preserve Inline Rolls
  • option to exclude/include individual self-whispers from archive, like !wself --noarchive/!wself --archive, along with option to change the default setting between the two. (currently archives no selfwhispers)

[edit] Blind Roll (TheAaron)


Example:

!blindroll [[@{selected|skill}+1]]d20+[[ [[@{selected|attack]]+@{target|ac} ]] Weird attack..

Results in player seeing:

Blind roll sent to GM
3d20+8 Weird attack..
Blind Roll API code
/*******************************************************************************
* rollers.js - Provides specialized dice rolling alternatives.
*
* Dependencies: none
*******************************************************************************
*
* Commands for performing specialty dice rolling.
*
* !blindroll roll
* Players may issue this command to have a roll of the dice generated in
* secret to the GM only. This is useful when the player should not know
* the result of the roll (e.g., a Stealth check).
*
* Example:
* !blindroll 1d20+7 Stealth check
* This example would be rolled as a GM roll so that the results are only
* visible to the GM.
*
* !rigroll <num dice>d<num sides>:<modifier>:<actual rolls (comma-separated)>
* Occasionally, a GM may want to exert control over fate and have the dice
* fall a certain way (for cinematic reasons or to avoid a TPK). This command
* allows a GM to roll "in the open" and still get the desired outcome.
* This command cannot currently deal with complex roll types that are
* available generally in Roll20.
* Whenever this command is issued, the GM also receives a whisper informing
* him of the rigged roll. This is favored over simply denying players the
* ability to rig rolls because there may be times when the GM and a player
* conspire together for story purposes and require a particular outcome.
*
* Examples:
* !rigroll 1d20:+3:18
* !rigroll 5d4:+8:2,3,1,3,4
* The first example would produce a result of 21 (showing that an 18 was
* rolled). The second example would also produce a result of 21 (showing
* that a 2, 3, 1, 3, and 4 were rolled.
*
*******************************************************************************
* Copyright (C) 2014 Aaron Garrett (aaron.lee.garrett@gmail.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*******************************************************************************/
if(!('contains' in String.prototype)) {
    String.prototype.contains = function(str, startIndex) {
        return ''.indexOf.call(this, str, startIndex) !== -1;
    };
}
var inspired = inspired || {};
on("chat:message", function(msg_orig) {
    var msg=_.clone(msg_orig);
    if(msg.type != "api") return;


    if(_.has(msg,'inlinerolls')){
        msg.content = _.chain(msg.inlinerolls)
        .reduce(function(m,v,k){
            m['$[['+k+']]']=v.results.total || 0;
            return m;
        },{})
        .reduce(function(m,v,k){
            return m.replace(k,v);
        },msg.content)
        .value();
    }


    if(msg.content.contains("!blindroll ")) {
        var roll = msg.content.replace("!blindroll ", "").trim();
        if(roll.length > 0) {
            sendChat(msg.who, "Blind roll sent to GM <br/><small>(" + roll + ")</small>.");
            sendChat(msg.who, "/gmroll " + roll + " from " + msg.who);
        }
    }
});
on("chat:message", function(msg) {
    if(msg.type != "api") return;
    if(msg.content.contains("!rigroll ")) {
        var parts = msg.content.replace("!rigroll ", "").split(":");
        var roll = parts[0];
        var temp = roll.split("d");
        var numdice = temp[0];
        var faces = temp[1];
        var modifier = parts[1];
        var values = parts[2].split(",");
        var formulaStyle = "font-size:inherit;display:inline;padding:4px;background:white;border-radius:3px;";
        var totalStyle = formulaStyle;
        totalStyle += "border:1px solid #d1d1d1;cursor:move;font-size:1.4em;font-weight:bold;color:black;line-height:2.0em;";
        formulaStyle += "border:1px solid #d1d1d1;font-size:1.1em;line-height:2.0em;word-wrap:break-word;";
        var clearStyle = "clear:both";
        var formattedFormulaStyle = "display:block;float:left;padding:0 4px 0 4px;margin:5px 0 5px 0";
        var dicegroupingStyle = "display:inline";
        var uisortableStyle = "cursor:move";
        var rolledStyle = "cursor:move;font-weight:bold;color:black;font-size:1.4em";
        var uidraggableStyle = "cursor:move";
        var html = "<div style=\"" + formulaStyle + "\"> rolling " + numdice + "d" + faces + modifier + " </div>";
        html += "<div style=\"" + clearStyle + "\"></div>";
        html += "<div style=\"" + formulaStyle + ";" + formattedFormulaStyle + "\">";
        html += " <div style=\"" + dicegroupingStyle + ";" + uisortableStyle + "\" data-groupindex=\"0\">";
        var total = 0;
        html += " (";
            for(var i = 0; i < numdice; i++) {
                var value = values[i];
                var color="black";
                if (value == "1") {
                    color="#730505";
                }
                else if (value == faces) {
                    color="#247305";
                }
                var didrollStyle = "text-shadow:-1px -1px 1px #fff,1px -1px 1px #fff,-1px 1px 1px #fff,1px 1px 1px #fff;z-index:2;position:relative;color:"+color+";height:30px;min-height:29px;margin-top:-3px;top:0;text-align:center;font size=16px;font-family:sans-serif;";
                var dicerollStyle = "display:inline-block;font-size:1.2em;font-family:san-sarif" + faces;
                var diconStyle = "display:inline-block;min-width:30px;text-align:center;position:relative";
                var backingStyle = "position:absolute;top:-2px;left:0;width:100%;text-align:center;font-size:30px;color:#8fb1d9;text-shadow:0 0 3px #8fb1d9;opacity:.75;pointer-events:none;z-index:1";
                html += " <div data-origindex=\"0\" style=\"" + dicerollStyle + "\" class=\"diceroll d" + faces + "\">";
                html += " <div style=\"" + diconStyle + "\">";
                html += " <div class=\"backing\"></div>"
                html += " <div style=\"" + didrollStyle + "\">"
                total += eval(value);
                if ((value=="1")||(value==faces)){
                    html+= "<strong>"
                }
                html += value;
                if ((value=="1")||(value==faces)){
                    html+= "</strong>"
                }
                html += "</div>";
                html += " <div style=\"" + backingStyle + "\"></div>";
                html += " </div>";
                html += " </div>";
                if(i == numdice - 1) html += ")";
                else html += "+";
            }
            html += " </div>";
            total = eval(total + modifier);
            html += modifier;
            html += "</div>";
            html += "<div style=\"" + clearStyle + "\"></div><strong> = </strong><div style=\"" + totalStyle + ";" + uidraggableStyle + "\"><strong><font size=\"6\"> " + total + "</strong> </div>";
            sendChat(msg.who, "/direct " + html);
            sendChat("Roll20", "/w gm " + roll + " was rigged to have values " + values.join() + ".");
    }
});

[edit] Blind Roll (Stephen)

Works just like a GM Roll.... only the player doesn't see the dice roll result.

!broll 1d100+10 to pick pocket Jimmy<pre>

Would result in the players seeing:

:''"Secret roll sent to GM (1d100+10 to picket pocket Jimmy)"'', with no dice roll result.

<pre data-language="javascript">
on("chat:message", function(msg) {
    var cmdName = "!broll ";
	var msgTxt = msg.content;
	var msgWho = msg.who;
	var msgFormula = msgTxt.slice(cmdName.length);

	if(msg.type == "api" && msgTxt.indexOf(cmdName) !== -1) {
		sendChat(msgWho, "/gmroll " + msgFormula);
		sendChat(msgWho, "/w " + msgWho + " secret roll sent to GM (" + msgFormula + ")");
  	};
});

[edit] Cards

[edit] Hide Tooltips on face down cards

[edit] Combat

[edit] Move to Map Layer on Death

Move to Map Layer on Death by Adam J.

Used in combination with TokenMod & Health Tracker API.

on('ready', () => {
    const HPBarNum = 3;
    const bar = `bar${HPBarNum}_value`;
    const max = `bar${HPBarNum}_max`;
    const constrainHPBarToMax = (obj) => {
        const hpMax = parseInt(obj.get(max),10);
        if(!isNaN(hpMax) && 'token' === obj.get('subtype') && !obj.get('isdrawing') ){
            let hp = parseInt(obj.get(bar),10);
            let changes = {};

            if(hp < hpMax) {
                hp < hpMax;
                changes[bar] = hp;
                changes.status_dead = false;
                changes.layer = "objects";
            } else if (hp = hpMax) {
                changes[bar] = hp;
                changes.status_dead = false;
            } else if(hp <= 0) {
                hp=0;
                changes[bar] = hp;
                changes.status_dead = true;
                changes.order = "tofront"
                changes.layer = "map";
            } else {
                changes.status_dead = false;
            }
            obj.set(changes);
        }
    };
    on("change:token", constrainHPBarToMax);
    if('undefined' !== typeof TokenMod && TokenMod.ObserveTokenChange){
        TokenMod.ObserveTokenChange(constrainHPBarToMax);
    }
    if('undefined' !== typeof ApplyDamage && ApplyDamage.registerObserver){
        ApplyDamage.registerObserver('change',constrainHPBarToMax);
    }
});


[edit] Add Custom Invisible Turn

[edit] Token


[edit] Make Rollable Table Tokens

!make-rtt(Forum) by Aaron

Little script for making Rollable Table Tokens. Just select a bunch of graphics on the page and run:

!make-rtt

and it will zip them up in a Rollable Table Token. it sorts them in order from top to bottom, left to right. If any graphics are from the marketplace, it will put the dead X on them and not include them. It places the new token at the lop left of the tokens (or at (0,0 if you have some bizzare selected tokens that are somewhat off screen..)


on('ready',()=>{

  const s = {
    err: "padding: 1px 1em; size:.8em; font-weight:bold; background: #cccccc; border:2px solid black; border-radius:1em; color: #990000;"
  };

  const getCleanImgsrc = (imgsrc) => {
    let parts = imgsrc.match(/(.*\/images\/.*)(thumb|med|original|max)([^?]*)(\?[^?]+)?$/);
    if(parts) {
      return parts[1]+'thumb'+parts[3]+(parts[4]?parts[4]:`?${Math.round(Math.random()*9999999)}`);
    }
    return;
  };


  const positionalSorter = (a,b) => {
    let at = Math.round((a.get('top')+17)/35);
    let bt = Math.round((b.get('top')+17)/35);
    let al = Math.round((a.get('left')+17)/35);
    let bl = Math.round((b.get('left')+17)/35);
    let abt = at-bt;
    let abl = al-bl;
    return (0 === abt ? abl : abt);
  };

  const findTraits = (b) => (o) => {
    let x = parseFloat(o.get('left'));
    let y = parseFloat(o.get('top'));
    let w = parseFloat(o.get('width'));
    let h = parseFloat(o.get('height'));
    b.minX = Math.min(b.minX,x-(w/2));
    b.minY = Math.min(b.minY,y-(h/2));
    b.maxX = Math.max(b.minX,x+(w/2));
    b.maxY = Math.max(b.minY,y+(h/2));
    b.layer = o.get('layer');
    b.pageid = o.get('pageid');
    return o;
  };

  on('chat:message',msg=>{
    if('api'===msg.type && /^!make-rtt(\b\s|$)/i.test(msg.content) && playerIsGM(msg.playerid)){
      let who = (getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname');

      let traits = {
        minX: Number.MAX_SAFE_INTEGER,
        minY: Number.MAX_SAFE_INTEGER,
        maxX: -Number.MAX_SAFE_INTEGER,
        maxY: -Number.MAX_SAFE_INTEGER,
        layer: 'objects',
        pageid: ''
      };


      if(0===(msg.selected||[].length)){
          sendChat('',`/w "${who}" <div style="${s.err}">Please selected some tokens.</div>`);
          return;
      }

      let images = (msg.selected || [])
        .map(o=>getObj('graphic',o._id))
        .filter(g=>undefined !== g)
        .sort(positionalSorter)
        .map(findTraits(traits))
        .reduce((m,g)=>{
          let i = getCleanImgsrc(g.get('imgsrc'));
          if(i){
            m.push(i);
          } else {
            g.set('status_dead',true);
          }
          return m;
        },[])
        ;

        if(images.length){
          let token = createObj('graphic',{
            pageid: traits.pageid,
            layer: traits.layer,
            left: traits.minX||0,
            top: traits.minY||0,
            width: 70,
            height: 70,
            imgsrc: images[0],
            sides: images.map(encodeURIComponent).join('|')
          });
          if(token){
            toFront(token);
          } else {
            sendChat('',`/w "${who}" <div style="${s.err}">Failed to create token!</div>`);
          }
          
        } else {
          sendChat('',`/w "${who}" <div style="${s.err}">Only marketplace images found!</div>`);
        }
    }
  });
});

[edit] Show Tooltip

Showtooltip(Forum) by Aaron

Shows Token Tooltip of selected tokens in the chat.

!show-tip //show in chat to all
!wshow-tip   //whisper to self


on('ready',()=>{


  const s = {
    container: `display:inline-block;border:1px solid #999;border-radius:.2em; padding: .1em;background-color:white;width:100%;`,
    img: `max-width: 5em;max-height:5em;display:block;overflow:auto;background-color:transparent;float:left;margin:.5em;`,
    quote: `font-weight: bold;font-style:italic;padding:.3em;`,
    clear: `clear:both;`
  };
  const f = {
    container: (d,q) => `<div style="${s.container}">${d}${q}${f.clear()}</div>`,
    item: (d)=>`<img src="${d}" style="${s.img}">`,
    quote: (q)=>q?`<div style="${s.quote}">${q}</div>`:'',
    clear: () => `<div style="${s.clear}"></div>`
  };

  on('chat:message',msg=>{
    if('api'===msg.type && /^![w]?show-tip(\b\s|$)/i.test(msg.content) && playerIsGM(msg.playerid)){
      let who = (getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname');
      let whisper = /^!w/i.test(msg.content);
      let msgs = (msg.selected || [])
        .map(o=>getObj('graphic',o._id))
        .filter(g=>undefined !== g)
        .filter(g=>0 !== g.get('tooltip').length)
        .map(t=>f.container(f.item(t.get('imgsrc')),f.quote(t.get('tooltip'))))
        ;
      if(msgs){
        sendChat('',`${whisper ? `/w "${who}" `: ''}${msgs.join('')}`);
      }
    }
  });
});


[edit] DropTorch

DropTorch(Forum), A little script that lets you and your players drop their light source on the ground.

[edit] RetrieveTokens

RetrieveTokens(Forum) -- get graphics that are off the page

[edit] HealthState

as a tokens HP changes it will add a colored status marker to the token indicating it's general health; starting with green down to red and when they drop below 1 it marks them with the death X marker.

!hs - when used without a selection it will toggle the mod functionality ON or OFF and display this as a whisper in chat.

If you have tokens selected when using this command it will cycle through them all and update them with the current color status (useful if you have a whole page of tokens not yet marked). Tokens will update when you change their HP value automatically.

[edit] Map

[edit] ToggleDaylight

ToggleDaylight(Forum) turn daylight mode on, off, or toggle it for LDL or UDL on the current page.

[edit] LightningFX

LightningFX script(Forum) by David M.

Create a flash of lighting, temporally illuminating map. Can be used for simulate lighting from weather, signal flare, or a fireball giving off light on impact.

[edit] Flash

Flash by Aaron simulates a lightning strike by turning on daylight mode for 300ms. Trigger it with: !flash

on('ready',()=>{
  const FLASH_TIME = 300; // milliseconds

  const getPageForPlayer = (playerid) => {
    let player = getObj('player',playerid);
    if(playerIsGM(playerid)){
      return player.get('lastpage');
    }

    let psp = Campaign().get('playerspecificpages');
    if(psp[playerid]){
      return psp[playerid];
    }

    return Campaign().get('playerpageid');
  };

  const flash = (page) => {
    if(page.get('dynamic_lighting_enabled') && !page.get('daylight_mode_enabled')){
      page.set({daylight_mode_enabled: true});
      setTimeout(()=>page.set({daylight_mode_enabled: false}), FLASH_TIME);
    }
  }

  on('chat:message',(msg)=>{
    if('api' === msg.type && /^!flash(\b\s|$)/i.test(msg.content) && playerIsGM(msg.playerid)){
      let page = getObj('page', getPageForPlayer(msg.playerid));
      if(page){
        flash(page);
        let who = (getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname');
        sendChat('',`/w "${who}" ⚡ FLASH ⚡`);
      }
    }
  });

});

[edit] More Community APIs