Skip to content

Instantly share code, notes, and snippets.

@Bastlifa
Last active August 19, 2020 04:08
Show Gist options
  • Save Bastlifa/0fb83ccd7d1039851b72e472b2178ec3 to your computer and use it in GitHub Desktop.
Save Bastlifa/0fb83ccd7d1039851b72e472b2178ec3 to your computer and use it in GitHub Desktop.
Druid Shift Script for Roll20 D&D 5E OGL Sheet
/**************************************** Druid Shift ****************************************
This is for the 5E OGL sheet in Roll20. It will probably not work with any others without
some modification. Also, I didn't do much in the way of error checking.
The GM must make character named Druid X, where X is the creature being shifted into.
It is suggested that the GM duplicate existing creatures, rename them, and assign to the druid
player. The images for the characters must be uploaded and assigned to the avatar of the druid
form characters. The easiest way I've found it to open the character sheet, edit, right click
the picture in the avatar, copy, paste into your image program, save, upload and put it back in.
Maybe you could just upload it back into the avatar directly. Anyways, this won't work if you
don't first upload the image to your library.
Either the druid player should have a general macro, or the forms characters should
each have a token action with the body !DSBaseChar
It's helpful if the druid character has token actions for each desired form.
If you use multiple druids, or shapeshifters, add another check for !DSCharName for them.
I put in several example forms in the code. Delete or add as needed.
In the !DSBaseChar part of the code, you must replace 'Druid Char Name' with
'<the druid character's name, exactly>'.
If you want to do custom creatures, and not bother updating the API script, do the following:
Make and assign a character, such as Camel, and name it Druid Camel
Have player enter this command: !DSCustom,Camel,Large,0
That's commas with no spaces, first item is for the api to know which command to deal with,
second is the creature (it'll add the Druid to the Camel, so just enter the creature's base name),
third is creature size, last is darkvision range.
*********************************************************************************************/
on("ready", function() {
var getCleanImgsrc = function (imgsrc) {
var 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;
};
on("chat:message", function (msg) {
// Make sure to copy your druid character's name exactly in CharacterGet parameter.
if (msg.type === "api" && msg.content === "!DSBaseChar") {
CharacterGet('Druid Char Name', msg, 'Normal', 0);
}
if (msg.type === "api" && msg.content === "!DSCaveBear") {
CharacterGet('Druid Cave Bear', msg, 'Large', 60);
}
if (msg.type === "api" && msg.content === "!DSAllosaurus") {
CharacterGet('Druid Allosaurus', msg, 'Large', 0);
}
if (msg.type === "api" && msg.content === "!DSCrocodile") {
CharacterGet('Druid Crocodile', msg, 'Large', 0);
}
if (msg.type === "api" && msg.content === "!DSDireWolf") {
CharacterGet('Druid Dire Wolf', msg, 'Large', 0);
}
if (msg.type === "api" && msg.content === "!DSGiantSpider") {
CharacterGet('Druid Giant Spider', msg, 'Large', 60);
}
if (msg.type === "api" && msg.content === "!DSGiantToad") {
CharacterGet('Druid Giant Toad', msg, 'Large', 30);
}
if (msg.type === "api" && msg.content.split(',')[0] === "!DSCustom") {
let parts = msg.content.split(',')
let creatureName = parts[1]
let creatureSize = parts[2]
let creatureDV = parts[3]
CharacterGet(`Druid ${creatureName}`, msg, `${creatureSize}`, Number(creatureDV))
if (!findObjs({_type: 'macro', _playerid: msg.playerid, name: creatureName})[0])
{
createObj('macro', {
name: creatureName,
playerid: msg.playerid,
istokenaction: false,
visibleto: "",
action: msg.content
});
}
}
});
function Size(charSize, msg)
{
if (charSize === "Normal"){
_.each(msg.selected,function (o) {
getObj(o._type,o._id).set("height", 70);
getObj(o._type,o._id).set("width", 70);
});}
if (charSize === "Large"){
_.each(msg.selected,function (o) {
getObj(o._type,o._id).set("height", 140);
getObj(o._type,o._id).set("width", 140);
});}
if (charSize === "Huge"){
_.each(msg.selected,function (o) {
getObj(o._type,o._id).set("height", 210);
getObj(o._type,o._id).set("width", 210);
});}
if (charSize === "Gargantuan"){
_.each(msg.selected,function (o) {
getObj(o._type,o._id).set("height", 280);
getObj(o._type,o._id).set("width", 280);
});}
}
function CharacterGet(characterName, msg, charSize, darkvision)
{
if (!msg.selected || !getObj(msg.selected[0]._type,msg.selected[0]._id).get('represents'))
{
sendChat('Druid Shift Script', 'Please select a representative token to shift');
return;
}
var ShiftCharacter = findObjs({ type: 'character', name: characterName })[0];
if (!ShiftCharacter)
{
sendChat('Druid Shift API', `No character found: ${characterName}`);
return;
}
//sendChat("", "ShiftChar is: " + ShiftCharacter.get('name'));
if(getAttrByName(ShiftCharacter.get('id'), 'npc', 'current') == 1)
{
//sendChat("", "NPC thing happened");
_.each(msg.selected,function (o) {
getObj(o._type,o._id).set({
imgsrc: getCleanImgsrc(ShiftCharacter.get('avatar')),
bar1_link: 'None',
represents: ShiftCharacter.id,
bar1_value: getAttrByName(ShiftCharacter.get('id'), 'hp', 'max'),
bar1_max: getAttrByName(ShiftCharacter.get('id'), 'hp', 'max'),
bar2_link: 'None',
bar2_value: getAttrByName(ShiftCharacter.get('id'), 'npc_ac', 'current'),
light_radius: darkvision,
light_dimradius: 0,
light_otherplayers: false
});
});
}
else
{
//sendChat("", "PC thing happened");
_.each(msg.selected,function (o) {
getObj(o._type,o._id).set({
imgsrc: getCleanImgsrc(ShiftCharacter.get('avatar')),
represents: ShiftCharacter.id,
bar1_value: getAttrByName(ShiftCharacter.get('id'), "hp", 'current'),
bar1_max: getAttrByName(ShiftCharacter.get('id'), "hp", 'max'),
bar1_link: findObjs({type: "attribute", characterid: ShiftCharacter.get('id'), name: 'hp'})[0].id,
bar2_value: getAttrByName(ShiftCharacter.get('id'), 'ac', 'current'),
bar2_link: findObjs({type: "attribute", characterid: ShiftCharacter.get('id'), name: 'ac'})[0].id,
light_radius: darkvision,
light_dimradius: 0,
light_otherplayers: false
});
});
}
Size(charSize, msg);
}
});
/w Einin &{template:npcaction} {{name=Shift}} {{rname=Forms}} {{description=[Einin Form &#x1F9DD](!DSBaseChar)
**Furry**
[Cave Bear &#x1F43B](!DSCaveBear)
[Dire Wolf &#x1F43A](!DSDireWolf)
[Displacer Beast](!DSDisplacerBeast)
**Feathered**
[Giant Eagle &#x1F985](!DSGiantEagle)
[Giant Vulture &#x1F424](!DSGiantVulture)
[Griffon](!DSGriffon)
[Owlbear &#x1F989](!DSOwlbear)
**Chitinous**
[Giant Spider &#x1F577](!DSGiantSpider)
[Giant Scorpion &#x1F982](!DSGiantScorpion)
**Scaled**
[Crocodile &#x1F40A](!DSCrocoile)
[Allosaurus &#x1F996](!DSAllosaurus)
[Giant Toad &#x1F438](!DSGiantToad)
**Elemental**
[Earth Elemental &#x1F30E](!DSEarthElemental)
[Air Elemental &#x1F32A](!DSAirElemental)
[Fire Elemental &#x1F525](!DSFireElemental)
[Water Elemental &#x1F30A](!DSWaterElemental)
}}
@Darkcritop
Copy link

Darkcritop commented May 31, 2020

Hi! I have some issues with the code. My caracters are link : Bar 3 hp, Bar 2 Passifwisdom and Bar 1 AC. Then i change the bar in the script :
bar2_link: 'none',
represents: ShiftCharacter.id,
bar2_value: getAttrByName(ShiftCharacter.get('id'), 'hp', 'max'),
bar2_max: getAttrByName(ShiftCharacter.get('id'), 'hp', 'max'),
Then i can see the Hp remaining of the caracter in Bar :3 and in Bar 1 i see the Hp of the animal wildshape instead of the passifwisdom.

1 Issue : Ac remain of the caracter and not the npc druid animal he turn into.
2 Issue : When i turn back caracter, Bar 2 is link to none now and keep track of remains HP/Max hp of the creature and Bar 1 is now link to HP instead of AC.
3 Issue : Caracter now lose Darkvision (She a Elf) in Advanced parameter.

Thank for help ( i am french sorry haha)

I let you a exemple of the code i got here : I try multiple Shifter and it work but got the same issue with both. If someone could send the code i need i would be gratefull! No programmer skill by the way ! Just a DM trying to have the best tools for his players.

/**************************************** Druid Shift ****************************************
This is for the 5E OGL sheet in Roll20. It will probably not work with any others without
some modification. Also, I didn't do much in the way of error checking.

The GM must make character named Druid X, where X is the creature being shifted into.
It is suggested that the GM duplicate existing creatures, rename them, and assign to the druid
player. The images for the characters must be uploaded and assigned to the avatar of the druid
form characters. The easiest way I've found it to open the character sheet, edit, right click
the picture in the avatar, copy, paste into your image program, save, upload and put it back in.
Maybe you could just upload it back into the avatar directly. Anyways, this won't work if you
don't first upload the image to your library.

Either the druid player should have a general macro, or the forms characters should
each have a token action with the body !DSBaseChar

It's helpful if the druid character has token actions for each desired form.

If you use multiple druids, or shapeshifters, add another check for !DSCharName for them.

I put in several example forms in the code. Delete or add as needed.

In the !DSBaseChar part of the code, you must replace 'Druid Char Name' with
'<the druid character's name, exactly>'.

If you want to do custom creatures, and not bother updating the API script, do the following:

Make and assign a character, such as Camel, and name it Druid Camel
Have player enter this command: !DSCustom,Camel,Large,0
That's commas with no spaces, first item is for the api to know which command to deal with,
second is the creature (it'll add the Druid to the Camel, so just enter the creature's base name),
third is creature size, last is darkvision range.

*********************************************************************************************/

on("ready", function() {

var getCleanImgsrc = function (imgsrc) {
    var 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;
};

on("chat:message", function (msg) {
    // Make sure to copy your druid character's name exactly in CharacterGet parameter.
    if (msg.type === "api" && msg.content === "!DSMae")  {
        CharacterGet('Mae', msg, 'Normal', 0);

}
if (msg.type === "api" && msg.content === "!DSAcolyte") {
CharacterGet('Acolyte', msg, 'Normal', 0);
}
if (msg.type === "api" && msg.content === "!DSGiantHyena") {
CharacterGet('Druid Giant Hyena', msg, 'Large', 0);
}
if (msg.type === "api" && msg.content === "!DSCaveBear") {
CharacterGet('Druid Cave Bear', msg, 'Large', 60);
}
if (msg.type === "api" && msg.content === "!DSAllosaurus") {
CharacterGet('Druid Allosaurus', msg, 'Large', 0);
}
if (msg.type === "api" && msg.content === "!DSCrocodile") {
CharacterGet('Druid Crocodile', msg, 'Large', 0);
}
if (msg.type === "api" && msg.content === "!DSDireWolf") {
CharacterGet('Druid Dire Wolf', msg, 'Large', 0);
}
if (msg.type === "api" && msg.content === "!DSGiantSpider") {
CharacterGet('Druid Giant Spider', msg, 'Large', 60);
}
if (msg.type === "api" && msg.content === "!DSGiantToad") {
CharacterGet('Druid Giant Toad', msg, 'Large', 30);
}
if (msg.type === "api" && msg.content === "!DSHyena") {
CharacterGet('Druid Hyena', msg, 'Normal', 0);
}
if (msg.type === "api" && msg.content.split(',')[0] === "!DSCustom") {
let parts = msg.content.split(',')
let creatureName = parts[1]
let creatureSize = parts[2]
let creatureDV = parts[3]
CharacterGet(Druid ${creatureName}, msg, ${creatureSize}, Number(creatureDV))
if (!findObjs({_type: 'macro', _playerid: msg.playerid, name: creatureName})[0])
{
createObj('macro', {
name: creatureName,
playerid: msg.playerid,
istokenaction: false,
visibleto: "",
action: msg.content
});
}
}

});



function Size(charSize, msg)
{


if (charSize === "Normal"){
    _.each(msg.selected,function (o) {
        getObj(o._type,o._id).set("height", 70);
        getObj(o._type,o._id).set("width", 70);
        
    });}
    
    if (charSize === "Large"){
    _.each(msg.selected,function (o) {
        getObj(o._type,o._id).set("height", 140);
        getObj(o._type,o._id).set("width", 140);
        
    });}
    
    if (charSize === "Huge"){
    _.each(msg.selected,function (o) {
        getObj(o._type,o._id).set("height", 210);
        getObj(o._type,o._id).set("width", 210);
        
    });}
    
    if (charSize === "Gargantuan"){
    _.each(msg.selected,function (o) {
        getObj(o._type,o._id).set("height", 280);
        getObj(o._type,o._id).set("width", 280);
        
    });}
}

function CharacterGet(characterName, msg, charSize, darkvision)
{
var ShiftCharacter = findObjs({ type: 'character', name: characterName })[0];
//sendChat("", "ShiftChar is: " + ShiftCharacter.get('name'));
if(getAttrByName(ShiftCharacter.get('id'), 'npc', 'current') == 1)
{
//sendChat("", "NPC thing happened");
_.each(msg.selected,function (o) {
getObj(o._type,o._id).set({
imgsrc: getCleanImgsrc(ShiftCharacter.get('avatar')),
bar2_link: 'none',
represents: ShiftCharacter.id,
bar2_value: getAttrByName(ShiftCharacter.get('id'), 'hp', 'max'),
bar2_max: getAttrByName(ShiftCharacter.get('id'), 'hp', 'max'),
light_radius: darkvision,
light_dimradius: 0,
light_otherplayers: false
});
});
}
else
{
//sendChat("", "PC thing happened");
_.each(msg.selected,function (o) {
getObj(o._type,o._id).set({
imgsrc: getCleanImgsrc(ShiftCharacter.get('avatar')),
represents: ShiftCharacter.id,
bar1_value: getAttrByName(ShiftCharacter.get('id'), "hp", 'current'),
bar1_max: getAttrByName(ShiftCharacter.get('id'), "hp", 'max'),
bar1_link: findObjs({type: "attribute", characterid: ShiftCharacter.get('id'), name: 'hp'})[0].id,
light_radius: darkvision,
light_dimradius: 0,
light_otherplayers: false
});
});
}

    Size(charSize, msg);
}

});

@Bastlifa
Copy link
Author

Hmm, I wonder why I never had it change AC... It seems like an obvious thing to do. I'll put up the new one, after this comment, with some error checking as well because of errors I ran into. As usual, just change the bars, or remove sections, if you like things in different bars.

Also, I haven't messed around with advanced dynamic lighting, except for a bit on the dev server, because I kept hearing about how it caused a lot of problems for people, so I'll put off working on that.

For Darkcritop, maybe I'll look at advanced dynamic lighting another time, but for bars at least, the CharacterGet function should be something like:

function CharacterGet(characterName, msg, charSize, darkvision)
    {
        if (!msg.selected || !getObj(msg.selected[0]._type,msg.selected[0]._id).get('represents'))
        {
            sendChat('Druid Shift Script', 'Please select a representative token to shift');
            return;
        }
        var ShiftCharacter = findObjs({ type: 'character', name: characterName })[0];
        if (!ShiftCharacter)
        {
            sendChat('Druid Shift API', `No character found: ${characterName}`);
            return;
        }
        //sendChat("", "ShiftChar is: " + ShiftCharacter.get('name'));
            if(getAttrByName(ShiftCharacter.get('id'), 'npc', 'current') == 1)
            {
                //sendChat("", "NPC thing happened");
                _.each(msg.selected,function (o) {
                    getObj(o._type,o._id).set({
                        imgsrc: getCleanImgsrc(ShiftCharacter.get('avatar')),
                        bar3_link: 'None',
                        represents: ShiftCharacter.id,
                        bar3_value: getAttrByName(ShiftCharacter.get('id'), 'hp', 'max'),
                        bar3_max: getAttrByName(ShiftCharacter.get('id'), 'hp', 'max'),
                        bar1_link: 'None',
                        bar1_value: getAttrByName(ShiftCharacter.get('id'), 'npc_ac', 'current'),
                        bar2_link: 'None',
                        bar2_value: Number(getAttrByName(ShiftCharacter.get('id'), 'npc_perception', 'current')) + 10,
                        light_radius: darkvision,
                        light_dimradius: 0,
                        light_otherplayers: false
                    });
                });
            }
            else
            {   
                //sendChat("", "PC thing happened");
                _.each(msg.selected,function (o) {
                    getObj(o._type,o._id).set({
                        imgsrc: getCleanImgsrc(ShiftCharacter.get('avatar')),
                        represents: ShiftCharacter.id,
                        bar3_value: getAttrByName(ShiftCharacter.get('id'), "hp", 'current'),
                        bar3_max: getAttrByName(ShiftCharacter.get('id'), "hp", 'max'),
                        bar3_link: findObjs({type: "attribute", characterid: ShiftCharacter.get('id'), name: 'hp'})[0].id,
                        bar1_value: getAttrByName(ShiftCharacter.get('id'), 'ac', 'current'),
                        bar1_link: findObjs({type: "attribute", characterid: ShiftCharacter.get('id'), name: 'ac'})[0].id,
                        bar2_value: getAttrByName(ShiftCharacter.get('id'), 'passive_wisdom', 'current'),
                        bar2_link: findObjs({type: "attribute", characterid: ShiftCharacter.get('id'), name: 'passive_wisdom'})[0].id,
                        light_radius: darkvision,
                        light_dimradius: 0,
                        light_otherplayers: false
                    });
                });
            }
        
    
        Size(charSize, msg);
    }

I think, for some reason, PC sheets use passive_wisdom for passive perception, but for NPCs, it's different. Cave Bear, for example, has 11 passive wisdom, but 3 perception bonus.

@Darkcritop
Copy link

Thx a million time ! It work great! I have a bit of trouble with the perception code. The code was crashing until i delete only that part. Perception still the same has caracter but i don't mind at all. Thx again!

bar2_link: 'None',
bar2_value: Number(getAttrByName(ShiftCharacter.get('id'), 'npc_perception', 'current')) + 10,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment