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:Blood And Honor: Automatic blood spatter, pooling and trail effects"

From Roll20 Wiki

Jump to: navigation, search
m
Line 2: Line 2:
 
|name=Blood And Honor
 
|name=Blood And Honor
 
|author={{user profile|242509|John C.}}
 
|author={{user profile|242509|John C.}}
|version=0.3
+
|version=0.6
|lastmodified=2015-01-05}}
+
|lastmodified=2015-01-07}}
<img>https://s3.amazonaws.com/files.d20.io/images/7000553/V1ay9Ot3LVGPprW8KWBKRA/med.jpg?1420449414</img>
+
 
 
Blood and Honor is an automatic way to add lightweight blood effects to your campaign, without any animations or commands.
 
Blood and Honor is an automatic way to add lightweight blood effects to your campaign, without any animations or commands.
  
Line 12: Line 12:
  
 
'''Spatter'''
 
'''Spatter'''
:Any time a token's health bar is changed so that it's total value is half or less of its maximum, a randomly selected, offset and rotated blood graphic will be placed on the map layer beneath it. The exact formula is as such - the system rolls a dice with as many sides as the max health value. If that roll exceeds the current health value, a blood spatter appears.
+
:Any time a token's health bar is changed so that it's total value is half or less of its maximum, a randomly selected, offset and rotated blood graphic will be placed on the map layer beneath it. The exact formula is as such - the system rolls a dice with as many sides as the max health value. If that roll exceeds the current health value, a blood spatter appears. The larger the amount of damage taken at once, the bigger the spatter will be.
  
 
'''Trails'''
 
'''Trails'''
Line 29: Line 29:
 
contact: "echo@TeamAsshat.com",
 
contact: "echo@TeamAsshat.com",
 
},
 
},
version: "0.3",
+
version: "0.7",
 
gist: "https://gist.github.com/SplenectomY/097dac3e427ec50f32c9",
 
gist: "https://gist.github.com/SplenectomY/097dac3e427ec50f32c9",
 
forum: "https://app.roll20.net/forum/post/1477230/",
 
forum: "https://app.roll20.net/forum/post/1477230/",
Line 38: Line 38:
 
// Default is 70 px x 70 px square, Roll20's default.
 
// Default is 70 px x 70 px square, Roll20's default.
 
tokenSize: 70,
 
tokenSize: 70,
 
+
 
+
// If you have it installed, this will plug in TheAaron's isGM auth module,
 +
// which will make it so only the GM can use the !clearblood command
 +
// Change to "true" if you want to check for authorization
 +
useIsGM: false,
 +
 
// YOU MUST ADD YOUR OWN SPATTERS AND POOLS TO YOUR LIBRARY
 
// YOU MUST ADD YOUR OWN SPATTERS AND POOLS TO YOUR LIBRARY
 
// AND GET THE IMAGE LINK VIA YOUR WEB BROWSER.
 
// AND GET THE IMAGE LINK VIA YOUR WEB BROWSER.
Line 53: Line 57:
 
],
 
],
 
chooseBlood: function chooseBlood(type) {
 
chooseBlood: function chooseBlood(type) {
if (type == "spatter") return BloodAndHonor.spatters[randomInteger(BloodAndHonor.spatters.length) - 1]
+
if (type == "spatter") return BloodAndHonor.spatters[randomInteger(BloodAndHonor.spatters.length) - 1];
if (type == "pool") return BloodAndHonor.pools[randomInteger(BloodAndHonor.pools.length) - 1]
+
if (type == "pool") return BloodAndHonor.pools[randomInteger(BloodAndHonor.pools.length) - 1];
 
},
 
},
 
getOffset: function getOffset() {
 
getOffset: function getOffset() {
if (randomInteger(2) == 1) return 1
+
if (randomInteger(2) == 1) return 1;
else return -1
+
else return -1;
 
},
 
},
 
bloodColor: function bloodColor(gmnotes) {
 
bloodColor: function bloodColor(gmnotes) {
if (gmnotes.indexOf("bloodcolor_purple") !== -1) return "#0000ff"
+
if (gmnotes.indexOf("bloodcolor_purple") !== -1) return "#0000ff";
if (gmnotes.indexOf("bloodcolor_blue") !== -1) return "#00ffff"
+
if (gmnotes.indexOf("bloodcolor_blue") !== -1) return "#00ffff";
if (gmnotes.indexOf("bloodcolor_orange") !== -1) return "#ffff00"
+
if (gmnotes.indexOf("bloodcolor_orange") !== -1) return "#ffff00";
 
else return "transparent"
 
else return "transparent"
 
},
 
},
 
createBlood: function createBlood(gPage_id,gLeft,gTop,gWidth,gType,gColor) {
 
createBlood: function createBlood(gPage_id,gLeft,gTop,gWidth,gType,gColor) {
gLeft = gLeft + (randomInteger(Math.floor(gWidth / 2)) * BloodAndHonor.getOffset())
+
gLeft = gLeft + (randomInteger(Math.floor(gWidth / 2)) * BloodAndHonor.getOffset());
gTop = gTop + (randomInteger(Math.floor(gWidth / 2)) * BloodAndHonor.getOffset())
+
gTop = gTop + (randomInteger(Math.floor(gWidth / 2)) * BloodAndHonor.getOffset());
 
setTimeout(function(){
 
setTimeout(function(){
 
toFront(fixedCreateObj("graphic",{
 
toFront(fixedCreateObj("graphic",{
Line 84: Line 88:
 
},50);
 
},50);
 
},
 
},
 +
timeout: 0,
 +
onTimeout: function theFinalCountdown() {
 +
if (BloodAndHonor.timeout > 0) {
 +
BloodAndHonor.timeout--;
 +
} else {
 +
return;
 +
}
 +
}
 
};
 
};
 
  
 
fixedCreateObj = (function () {
 
fixedCreateObj = (function () {
Line 97: Line 108:
 
}());
 
}());
  
 +
on("ready", function(obj) {
 +
 +
setInterval(function(){BloodAndHonor.onTimeout()},1000);
  
on("change:graphic:bar3_value", function(obj, prev) {
+
on("change:graphic:bar3_value", function(obj, prev) {
if(obj.get("bar3_max") === "" || obj.get("layer") != "objects") return;
+
if (obj.get("bar3_max") === "" || obj.get("layer") != "objects" || (obj.get("gmnotes")).indexOf("noblood") !== -1) return;
// Create spatter near token if "bloodied".
+
// Create spatter near token if "bloodied".
// Chance of spatter depends on severity of damage
+
// Chance of spatter depends on severity of damage
else if(obj.get("bar3_value") <= obj.get("bar3_max") / 2 && prev["bar3_value"] > obj.get("bar3_value")) {
+
else if (obj.get("bar3_value") <= obj.get("bar3_max") / 2 && prev["bar3_value"] > obj.get("bar3_value") && obj.get("bar3_value") > 0) {
if (randomInteger(obj.get("bar3_max")) > obj.get("bar3_value") && (obj.get("gmnotes")).indexOf("noblood") == -1) {
+
if (randomInteger(obj.get("bar3_max")) > obj.get("bar3_value")) {
BloodAndHonor.createBlood(obj.get("_pageid"), obj.get("left"), obj.get("top"), BloodAndHonor.tokenSize, BloodAndHonor.chooseBlood("spatter"), BloodAndHonor.bloodColor(obj.get("gmnotes")));
+
var bloodMult = 1 + ((obj.get("bar3_value") - prev["bar3_value"]) / obj.get("bar3_max"));
 +
BloodAndHonor.createBlood(obj.get("_pageid"), obj.get("left"), obj.get("top"), Math.floor(BloodAndHonor.tokenSize * bloodMult), BloodAndHonor.chooseBlood("spatter"), BloodAndHonor.bloodColor(obj.get("gmnotes")));
 +
}
 
}
 
}
}
+
// Create pool near token if health drops below 1.
// Create pool near token if health drops below 1.
+
else if (obj.get("bar3_value") <= 0) {
if(obj.get("bar3_value") <= 0) {
+
BloodAndHonor.createBlood(obj.get("_pageid"), obj.get("left"), obj.get("top"), Math.floor(BloodAndHonor.tokenSize * 1.5), BloodAndHonor.chooseBlood("pool"), BloodAndHonor.bloodColor(obj.get("gmnotes")));
BloodAndHonor.createBlood(obj.get("_pageid"), obj.get("left"), obj.get("top"), Math.floor(BloodAndHonor.tokenSize * 1.5), BloodAndHonor.chooseBlood("pool"), BloodAndHonor.bloodColor(obj.get("gmnotes")));
+
}
}
+
});
});
+
 
+
  
 
//Make blood trails, chance goes up depending on how injured a token is
 
//Make blood trails, chance goes up depending on how injured a token is
on("change:graphic:lastmove", function(obj) {
+
on("change:graphic:lastmove", function(obj) {
if(obj.get("bar3_value") <= obj.get("bar3_max") / 2) {
+
if (BloodAndHonor.timeout == 0) {
if (randomInteger(obj.get("bar3_max")) > obj.get("bar3_value")) {
+
if (obj.get("bar3_value") <= obj.get("bar3_max") / 2 && (obj.get("gmnotes")).indexOf("noblood") == -1) {
BloodAndHonor.createBlood(obj.get("_pageid"), obj.get("left"), obj.get("top"), Math.floor(BloodAndHonor.tokenSize / 2), BloodAndHonor.chooseBlood("spatter"), BloodAndHonor.bloodColor(obj.get("gmnotes")));
+
if (randomInteger(obj.get("bar3_max")) > obj.get("bar3_value")) {
}
+
BloodAndHonor.createBlood(obj.get("_pageid"), obj.get("left"), obj.get("top"), Math.floor(BloodAndHonor.tokenSize / 2), BloodAndHonor.chooseBlood("spatter"), BloodAndHonor.bloodColor(obj.get("gmnotes")));
 +
BloodAndHonor.timeout += 2;
 +
}
 +
}
 +
});
 
}
 
}
 +
 +
on("chat:message", function(msg) {
 +
if (msg.type == "api" && msg.content.indexOf("!clearblood") !== -1) {
 +
if (isGM(msg.playerid) == false && BloodAndHonor.useIsGM == true) {
 +
sendChat(msg.who,"/w " + msg.who + " You are not authorized to use that command!");
 +
return;
 +
} else {
 +
objects = filterObjs(function(obj) {
 +
if(obj.get("type") == "graphic" && obj.get("gmnotes") == "blood") return true;
 +
else return false;
 +
});
 +
_.each(objects, function(obj) {
 +
obj.set("left",0); obj.set("top",0);
 +
});
 +
}
 +
}
 +
});
 
});</pre>
 
});</pre>
  

Revision as of 17:54, 8 January 2015

API ScriptAuthor: John C.
Version: 0.6
Last Modified: 2015-01-07
Code: Blood And Honor
Dependencies: None
Conflicts: None

Blood and Honor is an automatic way to add lightweight blood effects to your campaign, without any animations or commands.

How it works:

Any token in the object layer that has a bar3 (health) value less than or equal to its max has a chance to bleed. The chance goes up depending on how severe the damage is.

Spatter

Any time a token's health bar is changed so that it's total value is half or less of its maximum, a randomly selected, offset and rotated blood graphic will be placed on the map layer beneath it. The exact formula is as such - the system rolls a dice with as many sides as the max health value. If that roll exceeds the current health value, a blood spatter appears. The larger the amount of damage taken at once, the bigger the spatter will be.

Trails

If a token moves while at or below half health, a smaller blood graphic will appear where it moves to based on the chances described above. Less health = more blood.

Pool

If a token's bar3 value falls 0 or below, a large pool of blood graphic appears on the map layer beneath it.

Contents

Code

/////////////////////////////////////////////////
/***********************************************/
var BloodAndHonor = {
	author: {
		name: "John C." || "Echo" || "SplenectomY",
		company: "Team Asshat" || "The Alehounds",
		contact: "echo@TeamAsshat.com",
	},
	version: "0.7",
	gist: "https://gist.github.com/SplenectomY/097dac3e427ec50f32c9",
	forum: "https://app.roll20.net/forum/post/1477230/",
/***********************************************/
/////////////////////////////////////////////////
	
	// This value should match the size of a standard grid in your campaign
	// Default is 70 px x 70 px square, Roll20's default.
	tokenSize: 70,
	
	// If you have it installed, this will plug in TheAaron's isGM auth module,
	// which will make it so only the GM can use the !clearblood command
	// Change to "true" if you want to check for authorization
	useIsGM: false,
	
	// YOU MUST ADD YOUR OWN SPATTERS AND POOLS TO YOUR LIBRARY
	// AND GET THE IMAGE LINK VIA YOUR WEB BROWSER.
	// FOLLOW THE INSTRUCTIONS HERE:
	// https://wiki.roll20.net/API:Objects#imgsrc_and_avatar_property_restrictions
	// You can add as many as you'd like to either category.
	// Spatters are also used for blood trails.
	spatters: [
		//"https://s3.amazonaws.com/files.d20.io/images/6993500/mAA-8agYIwkhEciVVSCFmg/thumb.png?1420411542",
	],
	pools: [
		//"https://s3.amazonaws.com/files.d20.io/images/6993478/77YowTZze57mGAHfSaxwYg/thumb.png?1420411480",
	],
	chooseBlood: function chooseBlood(type) {
		if (type == "spatter") return BloodAndHonor.spatters[randomInteger(BloodAndHonor.spatters.length) - 1];
		if (type == "pool") return BloodAndHonor.pools[randomInteger(BloodAndHonor.pools.length) - 1];
	},
	getOffset: function getOffset() {
		if (randomInteger(2) == 1) return 1;
		else return -1;
	},
	bloodColor: function bloodColor(gmnotes) {
		if (gmnotes.indexOf("bloodcolor_purple") !== -1) return "#0000ff";
		if (gmnotes.indexOf("bloodcolor_blue") !== -1) return "#00ffff";
		if (gmnotes.indexOf("bloodcolor_orange") !== -1) return "#ffff00";
		else return "transparent"
	},
	createBlood: function createBlood(gPage_id,gLeft,gTop,gWidth,gType,gColor) {
		gLeft = gLeft + (randomInteger(Math.floor(gWidth / 2)) * BloodAndHonor.getOffset());
		gTop = gTop + (randomInteger(Math.floor(gWidth / 2)) * BloodAndHonor.getOffset());
		setTimeout(function(){
			toFront(fixedCreateObj("graphic",{
				imgsrc: gType,
				gmnotes: "blood",
				pageid: gPage_id,
				left: gLeft,
				tint_color: gColor,
				top: gTop,
				rotation: randomInteger(360) - 1,
				width: gWidth,
				height: gWidth,
				layer: "map",
			}));
		},50);
	},
	timeout: 0,
	onTimeout: function theFinalCountdown() {
		if (BloodAndHonor.timeout > 0) {
			BloodAndHonor.timeout--;
		} else {
			return;
		}
	}
};

fixedCreateObj = (function () {
	return function () {
		var obj = createObj.apply(this, arguments);
			if (obj && !obj.fbpath) {
				obj.fbpath = obj.changed._fbpath.replace(/([^\/]*\/){4}/, "/");
			}
		return obj;
	};
}());

on("ready", function(obj) {
	
	setInterval(function(){BloodAndHonor.onTimeout()},1000);

	on("change:graphic:bar3_value", function(obj, prev) {
		if (obj.get("bar3_max") === "" || obj.get("layer") != "objects" || (obj.get("gmnotes")).indexOf("noblood") !== -1) return;
		// Create spatter near token if "bloodied".
		// Chance of spatter depends on severity of damage
		else if (obj.get("bar3_value") <= obj.get("bar3_max") / 2 && prev["bar3_value"] > obj.get("bar3_value") && obj.get("bar3_value") > 0) {
			if (randomInteger(obj.get("bar3_max")) > obj.get("bar3_value")) {
				var bloodMult = 1 + ((obj.get("bar3_value") - prev["bar3_value"]) / obj.get("bar3_max"));
				BloodAndHonor.createBlood(obj.get("_pageid"), obj.get("left"), obj.get("top"), Math.floor(BloodAndHonor.tokenSize * bloodMult), BloodAndHonor.chooseBlood("spatter"), BloodAndHonor.bloodColor(obj.get("gmnotes")));
			}
		}
		// Create pool near token if health drops below 1.
		else if (obj.get("bar3_value") <= 0) {
			BloodAndHonor.createBlood(obj.get("_pageid"), obj.get("left"), obj.get("top"), Math.floor(BloodAndHonor.tokenSize * 1.5), BloodAndHonor.chooseBlood("pool"), BloodAndHonor.bloodColor(obj.get("gmnotes")));
		}
	});

//Make blood trails, chance goes up depending on how injured a token is
	on("change:graphic:lastmove", function(obj) {
		if (BloodAndHonor.timeout == 0) {
			if (obj.get("bar3_value") <= obj.get("bar3_max") / 2 && (obj.get("gmnotes")).indexOf("noblood") == -1) {
				if (randomInteger(obj.get("bar3_max")) > obj.get("bar3_value")) {
					BloodAndHonor.createBlood(obj.get("_pageid"), obj.get("left"), obj.get("top"), Math.floor(BloodAndHonor.tokenSize / 2), BloodAndHonor.chooseBlood("spatter"), BloodAndHonor.bloodColor(obj.get("gmnotes")));
					BloodAndHonor.timeout += 2;
				}
			}
		});
	}
	
	on("chat:message", function(msg) {
		if (msg.type == "api" && msg.content.indexOf("!clearblood") !== -1) {
			if (isGM(msg.playerid) == false && BloodAndHonor.useIsGM == true) {
				sendChat(msg.who,"/w " + msg.who + " You are not authorized to use that command!");
				return;
			} else {
				objects = filterObjs(function(obj) {	
					if(obj.get("type") == "graphic" && obj.get("gmnotes") == "blood") return true;
					else return false;
				});
				_.each(objects, function(obj) {
					obj.set("left",0); obj.set("top",0);
				});
			}
		}
	});
});

Configuration

Stop bleeding

Putting noblood in any token's GM Notes will cause it to never bleed.

Change color

There are currently three custom colors, but you can easily add your own in the script. Put one of the following into the GM Notes if you want the token to bleed a color other than red:

bloodcolor_orange, bloodcolor_purple, bloodcolor_blue

Changelog

v.0.3 (2015-01-05)

  • [bugfix] blood pools now appear properly
  • Adding to bar3_value will no longer cause bleeding if the token is still below half health

v.0.2 (2015-01-05)

  • Added layer check to make sure nothing bleeds on the non-object layers.
  • Fixed tabs and spacing

v.0.1 (2015-01-05)

  • Release

See Also