Spice Dice
Author: brooklynsinclair
Description Launch Bot Current Users

Short Description:

Dice game - modified for my room.

Full Description

An (ongoing) modified version of ‘A Bettter Dice Roller’ by aety for my room.

Just search “A Bettter Dice Roller” or “aety” above to find the original bot ;)

*Note: NO changes were made to the integrity of game functions. Removed unused “blurbs”, customized notices/titles to my liking, & cleaned up launch interface.

✔️ Latest test: 11.17.23
——————————————
ORIGINAL OPEN SOURCE:

/**
* A Bettter Dice Roller by aety
* Version: 1.3.7
* Author: aety
* Date: 2021-03-08
* Loosely based on work by: kingchris_, zingknaat, milo_bloom, tablesalt90
* assuming anonymous tipping is implemented the way I expect this should add support for anonymous tipping
*/

cb.settings_choices = [
{name: 'settings_section_label', type:'choice', label:'################# Basic Settings #################', required: false},
{
name: 'tokens', type: 'int', minValue: 1,
label: 'How much do you want to charge per roll?',
defaultValue: 33
},
{
name: 'notice_wait_time',
type: 'int',
label: 'In minutes, how often should the bot advertise itself? (choosing 0 will never show the timer bot notices)',
minValue: 0,
defaultValue: 10
},
{
name: 'blurb_size',
type: 'choice',
label: 'Size of the timed bot advertisement blurb.',
choice1: 'Minimal Blurb',
choice2: 'Short Blurb',
choice3: 'Without "all" or Bot Name',
choice4: 'Full Blurb',
choice5: 'Off',
defaultValue: 'Without "all" or Bot Name'
},
{
name: 'entry_blurb_size',
type: 'choice',
label: 'Size of the user entry advertisement blurb.',
choice1: 'Minimal Blurb',
choice2: 'Short Blurb',
choice3: 'Without "all" or Bot Name',
choice4: 'Full Blurb',
choice5: 'Off',
defaultValue: 'Full Blurb'
},
{
name: 'change_room_subject', type: 'choice', label: 'Change room subject when using this bot?',
choice1: 'Yes', choice2: 'No', defaultValue: 'No'
},
{name: 'multirolls_subsection_label', type:'choice', label:'######### Multi Rolls #########', required: false},
{
name: 'max_rolls_at_once', type: 'int', minValue: 1,
label: 'Maximum number of rolls that can be tipped for in a single tip.',
defaultValue: 3
},
{
name: 'perfect_multi', type: 'choice',
label: 'Require exact multiples of tip amount to roll the die? (if you charge 33 per roll, 66 will roll 2x but 69 won\'t)',
choice1: 'Yes', choice2: 'No',
defaultValue: 'Yes'
},
{name: 'prizeodds_subsection_label', type:'choice', label:'######### Prize Odds #########', required: false},
{
name: 'equal_odds', type: 'choice',
label: 'Should all non-rare prizes have equal odds of being rolled? ("No" simulates rolling a pair of dice and "Yes" rolls a single die)',
choice1: 'Yes', choice2: 'No',
defaultValue: 'No'
},
{
name: 'last_prize_rare', type: 'choice',
label: 'Should the last prize in the list be RARE? (RARE prizes will only show up after a minimum number of rolls have been rolled and should be significantly less likely than other prizes)',
choice1: 'Yes', choice2: 'No',
defaultValue: 'Yes'
},
{
name: 'minimum_rolls', type: 'int', minValue: 0,
label: 'Minimum number of rolls that must be rolled before RARE prizes can be won.',
defaultValue: 10
},
{
name: 'rare_chance_mult', type: 'choice',
label: 'Multiplier for the chance of rolling rare prizes. Increase if rare prizes are too uncommon, Decrease if they\'re too common',
choice1: '1/2x', choice2: '3/4x', choice3: '1x', choice4: '1.5x', choice5: '2x', choice6: '4x', choice7: '8x',
defaultValue: '1x'
},
{name: 'prize_section_label', type:'choice', label:'#################### Prizes ####################', required: false},
{name: 'prize_1', type: 'str', label: '(List possible prizes from most common to least.) Prize 1'},
{name: 'prize_2', type: 'str', label: '(if equal odds option is \'Yes\' order doesn\'t matter.) Prize 2'},
{name: 'prize_3', type: 'str', label: '(Fill prize spots in order from top to bottom.) Prize 3', required: false},
{name: 'prize_4', type: 'str', label: '(Leave the remaining fields blank to not use them.) Prize 4', required: false}
];

// 25 because I got tired after uploading 25 gifs ;>_>
// if someone wants to get in touch with me about updating/improving
// or adding more dice gifs feel free to leave a comment etc
// I *should* see it
var maxPrizes = 25;

for(var i=5; i <= maxPrizes; i++) {
cb.settings_choices.push({
name: 'prize_' + i,
type: 'str',
label: 'Prize ' + i,
required: false
});
}

// We're pushing these so that we can put them after the prizes that we want to push with a loop
// there are better ways to deal with and organize this code
cb.settings_choices.push({name: 'miscellaneous_section_label', type:'choice', label:'################ Advanced Settings ################', required: false});
cb.settings_choices.push({name: 'miscellaneous_section_desc_label', type:'choice', label:'##### (Safe to Ignore Everything Below this Point) #####', required: false});
cb.settings_choices.push({
name: 'mod_rolls', type: 'choice',
label: 'Allow mods to use the /diceroll command?',
choice1: 'Yes', choice2: 'No',
defaultValue: 'No'
});
cb.settings_choices.push({name: 'cmd_prefix', type: 'str', minLength: 1, maxLength: 1, label: 'Prefix for bot commands. (Might want to change if you have other bots using the same commands.)', defaultValue: '/'});
cb.settings_choices.push({name: 'die_img_prefix', type: 'str', label: 'Prefix for die gifs ("prefix#" only change if you know what you\'re doing. :80ngenericdie_1 through :80ngenericdie_25)', defaultValue: ':80ngenericdie_'});
cb.settings_choices.push({name: 'miscellaneous_section_colors_label', type:'choice', label:'#################### Colors ####################', required: false});
cb.settings_choices.push({name: 'miscellaneous_section_notice_label', type:'choice', label:'################ Notice ################', required: false});
cb.settings_choices.push({name: 'notice_fg_color', type: 'str', label: 'Bot advertisement text color (hex code for color make sure you include the # in front)', defaultValue: '#15A6B0', required: false});
cb.settings_choices.push({name: 'notice_bg_color', type: 'str', label: 'Bot advertisement background color (hex code for color make sure you include the # in front)', defaultValue: '', required: false});
cb.settings_choices.push({name: 'miscellaneous_section_prizes_label', type:'choice', label:'################ Prizes ################', required: false});
cb.settings_choices.push({name: 'prize_fg_color', type: 'str', label: 'Prize text color (hex code for color make sure you include the # in front)', defaultValue: '#067D00', required: false});
cb.settings_choices.push({name: 'prize_bg_color', type: 'str', label: 'Prize background color (hex code for color make sure you include the # in front)', defaultValue: '#D9FAD7', required: false});
cb.settings_choices.push({name: 'miscellaneous_section_rare_label', type:'choice', label:'############### Rare Prize ##############', required: false});
cb.settings_choices.push({name: 'rare_fg_color', type: 'str', label: 'Rare Prize text color (hex code for color make sure you include the # in front)', defaultValue: '#A805A6', required: false});
cb.settings_choices.push({name: 'rare_bg_color', type: 'str', label: 'Rare Prize background color (hex code for color make sure you include the # in front)', defaultValue: '#FFDBF3', required: false});
cb.settings_choices.push({name: 'miscellaneous_section_info_label', type:'choice', label:'############## Stats and Info ##############', required: false});
cb.settings_choices.push({name: 'miscellaneous_section_info_desc_label', type:'choice', label:'(prizes list, winners, and dice stats etc)', required: false});
cb.settings_choices.push({name: 'info_fg_color', type: 'str', label: 'Prizes and Stats text color (hex code for color make sure you include the # in front)', defaultValue: '#008596', required: false});
cb.settings_choices.push({name: 'info_bg_color', type: 'str', label: 'Prizes and Stats background color (hex code for color make sure you include the # in front)', defaultValue: '#DBFBFF', required: false});
cb.settings_choices.push({name: 'winners_fg_color', type: 'str', label: 'Winners text color (hex code for color make sure you include the # in front)', defaultValue: '#8A4900', required: false});
cb.settings_choices.push({name: 'winners_bg_color', type: 'str', label: 'Winners background color (hex code for color make sure you include the # in front)', defaultValue: '#FFF0DE', required: false});

var rollprice = parseInt(cb.settings.tokens);
var prizeCount = 13;
var equalOdds = cb.settings.equal_odds == 'Yes';
var numberOfDie = equalOdds?1:2;
var die = [
{
die: 1,
label: "One",
sides: Math.floor((equalOdds? ((cb.settings.last_prize_rare == 'Yes')?prizeCount-1:prizeCount) : ((cb.settings.last_prize_rare == 'Yes')?prizeCount:prizeCount+1)/2))
},
{
die: 2,
label: "Two",
sides: Math.ceil((equalOdds? ((cb.settings.last_prize_rare == 'Yes')?prizeCount-1:prizeCount) : ((cb.settings.last_prize_rare == 'Yes')?prizeCount:prizeCount+1)/2))
}
];
var perfectMults = cb.settings.perfect_multi == 'Yes';
var maxRollsPerTip = parseInt(cb.settings.max_rolls_at_once);
var usingRares = cb.settings.last_prize_rare == 'Yes';
var minRollsForRare = parseInt(cb.settings.minimum_rolls);

var rareChance = 1;

switch(cb.settings.rare_chance_mult) {
case '1x':
rareChance = 1;
break;
case '1/2x':
rareChance = 0.5;
break;
case '3/4x':
rareChance = 0.75;
break;
case '1.5x':
rareChance = 1.5;
break;
case '2x':
rareChance = 2;
break;
case '4x':
rareChance = 4;
break;
case '8x':
rareChance = 8;
break;
default:
rareChance = 1;
break;
}

var changeSubject = cb.settings.change_room_subject == 'Yes';
var allow_mod_rolls = cb.settings.mod_rolls == 'Yes';
var cmdPrefix = cb.settings.cmd_prefix;
var diePrefix = cb.settings.die_img_prefix;
var notice_fg = cb.settings.notice_fg_color;
var notice_bg = cb.settings.notice_bg_color;
var lastRoller = '--';
var lastPrizeWon = '--';
var prizes = [];
var stats = {
'tipCounter': 0,
'winners': [],
'rollCounter': 0,
'rollResultCounts': {}
};

cb.onTip(function(tip) {
var tipAmount = parseInt(tip.amount);
stats.tipCounter += tipAmount;
if(tipAmount >= rollprice && (!perfectMults || ((tipAmount <= (maxRollsPerTip*rollprice)) && (tipAmount % rollprice === 0)))) {
var rolls = Math.floor(tipAmount/rollprice);
for(var i = 0; i < rolls; i++) {
var username = "anonymous";
if(!tip.is_anon_tip) {
username = tip.from_user;
}
roll(username, true);
lastRoller = username;
}
} else {
// not rolling the dice
// var textColor = '#000000';
// var bgColor = '#D9FAD7';
cb.drawPanel();
}
});

cb.onDrawPanel(function (user) {
return {
'template': '3_rows_12_22_31',
'row1_label': 'Last prize won:',
'row1_value': lastPrizeWon,
'row2_label': 'Last player:',
'row2_value': lastRoller,
'row3_value': stats.tipCounter + ' ' + ((stats.tipCounter > 1)?'tokens':'token') + ' received / rolled ' + stats.rollCounter + ' time(s)'
};
});

cb.onEnter(function (user) {
adBlurb(cb.settings.entry_blurb_size, user.user);
});

cb.onMessage(function(msg) {
var message = msg.m.split(" ");

switch (message[0]) {
case cmdPrefix+'dicehelp':
msg["X-Spam"] = true; // Don't print command messages to chat.
msg.background = "linear-gradient(to right, #CCCCCC, #FFFFFF)";
if(message[1] == 'all' && isPrivileged(msg))adBlurb('Full Blurb');
else adBlurb('Full Blurb', msg.user);
break;
case cmdPrefix+'winners':
msg["X-Spam"] = true;
msg.background = "linear-gradient(to right, #CCCCCC, #FFFFFF)";
if(message[1] == 'all' && isPrivileged(msg))winnersBlurb();
else winnersBlurb(msg.user);
break;
case cmdPrefix+'prizes':
msg["X-Spam"] = true;
msg.background = "linear-gradient(to right, #CCCCCC, #FFFFFF)";
if(message[1] == 'all' && isPrivileged(msg))prizesBlurb();
else prizesBlurb(msg.user);
break;
case cmdPrefix+'dicestats':
msg["X-Spam"] = true;
msg.background = "linear-gradient(to right, #CCCCCC, #FFFFFF)";
if(message[1] == 'all' && isPrivileged(msg))statsBlurb();
else statsBlurb(msg.user);
break;
case cmdPrefix+'diceroll':
if(msg.user == cb.room_slug || (allow_mod_rolls && isPrivileged(msg))) {
msg["X-Spam"] = true;
msg.background = "linear-gradient(to right, #CCCCCC, #FFFFFF)";

var times_to_roll = 1;
if(message.length > 1) {
times_to_roll = Math.min(parseInt(message[1]), maxRollsPerTip);
}

while(times_to_roll > 0) {
roll(msg.user, false);
--times_to_roll;
}
} else if(msg.is_mod === true && !allow_mod_rolls) {
msg["X-Spam"] = true;
msg.background = "linear-gradient(to right, #FF6666, #FFFFFF)";
cb.sendNotice('This model has not allowed mods to use /diceroll.\nThe setting "Allow mods to use the /diceroll command?" needs to be set to "Yes".', msg.user, 'linear-gradient(to right, #FF6666, #FFFFFF)', '#000000', 'bold');
}
break;
default:
break;
}

return msg;
});

function getRandomIntInclusive(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}

function getRandomFloatInclusive(min, max) {
return Math.random() * (max - min + 1) + min;
}

function isPrivileged(msg) {
return (msg.is_mod === true) || (msg.user == cb.room_slug);
}

function roll(username, count_towards_rare, send_roll_notice_to) {
var prize = 'A Thank You!';

if(count_towards_rare)stats.rollCounter += 1;

var dieResults = [];
var total = -numberOfDie;
for(var i = 0; i < numberOfDie; i++) {
dieResults[i] = getRandomIntInclusive(1, die[i].sides);
total += dieResults[i];
}

if(usingRares && (stats.rollCounter > minRollsForRare) && (getRandomFloatInclusive(0,100) <= rareChance)) {
//RARE PRIZE WON!!!
if(numberOfDie == 1) {
dieResults[0] = die[0].sides+1;
total = dieResults[0] - numberOfDie;
} else {
if(getRandomIntInclusive(0,1) === 0) {
dieResults[0] = die[0].sides+1;
dieResults[1] = die[1].sides;
total = dieResults[0] + dieResults[1] - numberOfDie;
} else {
dieResults[0] = die[0].sides;
dieResults[1] = die[1].sides+1;
total = dieResults[0] + dieResults[1] - numberOfDie;
}
}
}

var winner = false;

if (total >= 0 && total < prizeCount) {
if((total+numberOfDie) in stats.rollResultCounts)stats.rollResultCounts[(total+numberOfDie)] += 1;
else stats.rollResultCounts[(total+numberOfDie)] = 1;
winner = true;
prize = prizes[total];
} else {
winner = false;
}

var msg = ((numberOfDie == 1)?diePrefix + dieResults[0]: diePrefix + dieResults[0] + " " + diePrefix + dieResults[1]) + "\n";
msg += username + " rolled " + (total+numberOfDie) + "! \n".toUpperCase();
msg += "Roll #" + stats.rollCounter + " | Prize: " + prize;

var textColor = '#000000';
var bgColor = cb.settings.prize_bg_color; // '#D9FAD7';

if (winner) textColor = cb.settings.prize_fg_color; // '#067D00';
if (total == prizeCount-1) {
bgColor = cb.settings.rare_bg_color; // '#FFDBF3';
textColor = cb.settings.rare_fg_color; // '#A805A6';
}

cb.sendNotice(msg, send_roll_notice_to, bgColor, textColor, 'bold');
lastPrizeWon = prize;
stats.winners.push("Roll #" + stats.rollCounter + " (" + (total+numberOfDie) + "): " + username + " - " + prize);
if(stats.winners.length > 20)stats.winners = stats.winners.slice(-21);
cb.drawPanel();
}

function initPrizes() {
for (var i = 1; i <= maxPrizes; i++) {
if(!('prize_' + i in cb.settings) || cb.settings['prize_' + i] === '' || cb.settings['prize_' + i] == '.' || cb.settings['prize_' + i] == ' ')continue;
if(i%2 == 1)prizes.push(cb.settings['prize_' + i]);
else prizes.unshift(cb.settings['prize_' + i]);
}

prizeCount = prizes.length;
if(usingRares) {
if(prizeCount%2 === 0)prizes.push(prizes.shift());
prizes[prizes.length-1] += " (RARE)";
}

for(var j = 0; j < prizeCount; j++) {
stats.rollResultCounts[(j+numberOfDie)] = 0;
}

die[0].sides = Math.floor(((numberOfDie == 1)? ((cb.settings.last_prize_rare == 'Yes')?prizeCount-1:prizeCount) : ((cb.settings.last_prize_rare == 'Yes')?prizeCount:prizeCount+1)/2));
die[1].sides = Math.ceil(((numberOfDie == 1)? ((cb.settings.last_prize_rare == 'Yes')?prizeCount-1:prizeCount) : ((cb.settings.last_prize_rare == 'Yes')?prizeCount:prizeCount+1)/2));
cb.log(die[0].sides + " " + die[1].sides);

// basically this is +2 because only using +1 for unequal dice odds consistently made the rare prize feel too close in likelyhood by default to just one of the least likely prizes
rareChance *= equalOdds?100/(prizeCount*1.35):100/((die[0].sides+2)*die[1].sides);
cb.log(rareChance);
}

function statsBlurb(username) {
var msg = "##### Dice Roll Statistics #####\n";
for(var rollval in stats.rollResultCounts) {
msg += "Roll " + rollval + ": " + stats.rollResultCounts[rollval] + "\n";
}

msg += "Total Rolls: " + stats.rollCounter;

cb.sendNotice(msg, username, cb.settings.info_bg_color, cb.settings.info_fg_color, 'bold'); // '#DBFBFF', '#008596'
}

function prizesBlurb(username) {
var msg = "No Prizes!! D:";
if (prizes.length) {
msg = "##### POSSIBLE PRIZES #####";
for (var i = 0; i < prizeCount; i++) {
msg += "\nRoll " + (i+numberOfDie) + " - " + prizes[i];
}
}
cb.sendNotice(msg, username, cb.settings.info_bg_color, cb.settings.info_fg_color, 'bold'); // '#DBFBFF', '#008596'
}

function winnersBlurb(username) {
var msg = "##### LAST 20 WINNERS #####";
msg += "\nList sorted in chronological order";
if (stats.winners.length === 0) {
cb.sendNotice('No one has won anything yet. Roll the dice to win a prize!', username, '', '', 'bold');
} else {
var recentWinners = stats.winners.slice(-21);
for (var i = 0; i < recentWinners.length; i++) msg += "\n" + recentWinners[i];

cb.sendNotice(msg, username, cb.settings.winners_bg_color, cb.settings.winners_fg_color, 'bold'); // '#FFF0DE', '#8A4900'
}
}

function adBlurb(blurb_size, username) {
var msg = "";

switch(blurb_size) {
case 'Off':
break;
case 'Without "all" or Bot Name':
msg += "Tip " + rollprice + " " + ((rollprice>1)?'tokens':'token') + " to roll the dice. ";
msg += "There are " + prizes.length + " possible prizes.\n";
if(maxRollsPerTip>1) {
msg += "You can roll up to " + maxRollsPerTip + " times with a single tip (" + (maxRollsPerTip*rollprice) + " tokens).\n";
if(perfectMults && rollprice>1) msg += "You must tip exact amounts to roll the dice.\n(1 roll for " + rollprice + " tokens, 2 for " + rollprice*2 + " tokens, but " + Math.ceil(rollprice*1.5) + " tokens won't roll the dice).\n";
}
msg += "Type "+cmdPrefix+"prizes to see the list of prizes. \n";
msg += "Type "+cmdPrefix+"winners to see a list of the last 20 winners.";
break;

case 'Short Blurb':
msg += "Tip " + rollprice + " " + ((rollprice>1)?'tokens':'token') + " to roll the dice. ";
msg += "There are " + prizes.length + " possible prizes.\n";
if(maxRollsPerTip>1) {
msg += "You can roll up to " + maxRollsPerTip + " times with a single tip (" + (maxRollsPerTip*rollprice) + " tokens).\n";
if(perfectMults && rollprice>1) msg += "You must tip exact amounts to roll the dice.\n";
}
msg += "Type "+cmdPrefix+"prizes to see the list of prizes. \n";
msg += "Type "+cmdPrefix+"winners to see a list of the last 20 winners.";
break;

case 'Minimal Blurb':
msg += "Tip " + rollprice + " " + ((rollprice>1)?'tokens':'token') + " to roll the dice. ";
break;

case 'Full Blurb':
default:
msg += "A Bettter Dice Roller by aety.\n";
msg += (username)?"(Loosely based on work by: kingchris_, zingknaat, milo_bloom, tablesalt90.)\n":"";
msg += "Tip " + rollprice + " " + ((rollprice>1)?'tokens':'token') + " to roll the dice. ";
msg += "There are " + prizes.length + " possible prizes.\n";
if(maxRollsPerTip>1) {
msg += "You can roll up to " + maxRollsPerTip + " times with a single tip (" + (maxRollsPerTip*rollprice) + " tokens).\n";
if(perfectMults && rollprice>1) msg += "You must tip exact amounts to roll the dice.\n(1 roll for " + rollprice + " tokens, 2 for " + rollprice*2 + " tokens, but " + Math.ceil(rollprice*1.5) + " tokens won't roll the dice).\n";
}
msg += "Type "+cmdPrefix+"prizes to see the list of prizes. \n";
msg += "Type "+cmdPrefix+"prizes all to send the list to all viewers if you're a mod or the broadcaster.\n";
msg += "Type "+cmdPrefix+"winners to see a list of the last 20 winners.";
break;
}

if (msg !== "") {
cb.sendNotice(msg, username, notice_bg, notice_fg, 'bold');
}
}

function advertise() {
adBlurb(cb.settings.blurb_size);
if(cb.settings.notice_wait_time > 0)cb.setTimeout(advertise, parseInt(cb.settings.notice_wait_time) * 60 * 1000);
}

function init() {
initPrizes();
advertise();
if (changeSubject) {
cb.changeRoomSubject('Tip ' + rollprice + ' ' + ((rollprice>1)?'tokens':'token') + ' to roll the dice and win a prize!');
}
}

init();

© Copyright Chaturbate 2011- 2026. All Rights Reserved.