Extension:Dice

The Dice extension adds the ability to automatically substitute random dice when an edit is saved, and logs the dice being rolled to a special page.

When an edit is saved, requests for dice rolls in the form  cause dice to be "rolled" and logged, and are substituted with a tag that displays the result.

Roll results are intended to be tamper-proof, and easily verifiable. The extension provides a special page where dice roll logs can be examined.

Usage
Provides a magic keyword that is substituted when a page is saved with the appropriate &lt;dice> tag to display the result of a roll. The dice tag can be moved around, but remains attached to a log entry and will resist modification of the result.

Dice roll keywords are "standard" roll specifications prefixed with @@; specific accepted formats are as below:

Installation

 * 1) From your  directory, check out the Dice extension with
 * 2) Add the following line to the bottom of LocalSettings.php (though not after trailing ?>, if it is present):
 * 3) This extension adds a table to the database to hold the results of dice rolls, so you need to run the update script (instructions).
 * 4) Installation can now be verified through Special:Version of your wiki.
 * 1) This extension adds a table to the database to hold the results of dice rolls, so you need to run the update script (instructions).
 * 2) Installation can now be verified through Special:Version of your wiki.

User rights
rolldice: user can roll dice by saving a page with the magic keyword, and can use the Special:Dice special page to check their roll history. checkrolls: user can use Special:Dice to check the history of rolls of users other than themselves.

E.g., if you want everyone to be able to roll dice and check rolls:

Dice.alias.php
 array( 'Dice' ), );

Dice.i18n.php
 'Dice', // Ignore        'dice-desc' => "Dice roller",	'dice-special-desc' => "Show log of dice rolls",	'dice-special-title' => "Dice roll log",	'dice-special-baduser' => "That is not a valid user.",	'dice-special-notself' => "You are only allowed to check your own dice rolls.",	'dice-special-search' => "Search for rolls",	'dice-special-username' => "Username:",	'dice-tag-desc' => "Adds &lt;dice> tags and @@xdy syntax for making and displaying dice rolls",	'dice-bad' => "Incorrect or modified dice roll!",	'dice-noid' => "Not a logged roll",	'dice-error' => "Incorrect roll requested", ); /** Message documentation * @author */ $messages['qqq'] = array(       'dice-desc' => "", );

Dice.php
 __FILE__,	'name' => 'Dice',	'author' => 'Marc A. Pelletier',	'descriptionmsg' => 'dice-tag-desc',	'url' => 'http://www.mediawiki.org/wiki/Extension:Dice' );

$wgExtensionCredits['specialpage'][] = array(	'path' => __FILE__,	'name' => 'Dice',	'author' => 'Marc A. Pelletier',	'descriptionmsg' => 'dice-special-desc',	'url' => 'http://www.mediawiki.org/wiki/Extension:Dice' );

$wgAutoloadClasses['Dice'] = dirname(__FILE__). "/Dice_body.php"; $wgAutoloadClasses['SpecialDice'] = dirname(__FILE__). "/SpecialDice.php"; $wgExtensionMessagesFiles['Dice'] = dirname(__FILE__). '/Dice.i18n.php'; # Location of a messages file (Tell MediaWiki to load this file) $wgExtensionMessagesFiles['DiceAlias'] = dirname(__FILE__). '/Dice.alias.php'; # Location of an aliases file (Tell MediaWiki to load this file) $wgSpecialPageGroups['Dice'] = 'changes'; $wgSpecialPages['Dice'] = 'SpecialDice';

function wfDice( $parser ) { return Dice::setHooks($parser); }

$wgHooks['ParserFirstCallInit'][] = 'wfDice'; $wgHooks['ArticleSave'][] = 'Dice::parse'; $wgHooks['ArticleSaveComplete'][] = 'Dice::saved';

$wgResourceModules['ext.Dice.special'] = array(   'styles' => 'css/Dice_special.css',    'localBasePath' => dirname(__FILE__),    'remoteExtPath' => 'Dice',    'position' => 'top' );

$wgResourceModules['ext.Dice.tag'] = array(   'styles' => 'css/Dice_tag.css',    'localBasePath' => dirname(__FILE__),    'remoteExtPath' => 'Dice',    'position' => 'top' );

$wgHooks['LoadExtensionSchemaUpdates'][] = 'wfDiceSchema'; function wfDiceSchema( DatabaseUpdater $updater ) { $updater->addExtensionTable('dice_rolls', dirname(__FILE__) . '/Dice_rolls.sql'); return true; }

Dice_body.php
rev = $r; $this->uid = $u; $this->source = $s; }

function roll($match) { $dice = (isset($match[1])&&$match[1]!='')? 0+$match[1]: 1; $die = (isset($match[2])&&$match[2]!='')? $match[2]: 100; // for 00 = d100 $best = (isset($match[3])&&$match[3]!='')? 0+substr($match[3], 1): $dice; $plus = isset($match[4])? 0+((substr($match[4], 0, 1)=='+')? substr($match[4], 1): $match[4]): 0; if($dice > 0 && $dice < 30 && $die > 1 && $die <= 100 && $best > 0 && $best <= $dice) { $roll = ($dice>1)? "$dice": ""; $roll .= "d$die"; $roll .= ($best!=$dice)? "b$best": ""; $roll .= ($plus>0)? "+$plus": (($plus<0)? "$plus": ""); $result = array;

for($i=0; $i<$dice; $i++) $result[] = mt_rand(1, $die); rsort($result, SORT_NUMERIC); $total = $plus; $rlist = ''; for($i=0; $i<$dice; $i++) { if($i>0) $rlist .= ','; if($i<$best) $total += $result[$i]; if($i==$best) $rlist .= '&lt;s&gt;'; $rlist .= $result[$i]; }	   if($best < $dice) $rlist .= "&lt;/s&gt;";

$dbw = wfGetDB(DB_MASTER); $id = $dbw->nextSequenceValue('dice_rolls_roll_id_seq'); $res = $dbw->insert('dice_rolls', array( 'roll_id' => $id, 'roll_rev' => null, 'roll_user' => $this->uid, 'roll_timestamp' => $dbw->timestamp, 'roll_dice' => $roll, 'roll_result' => $rlist, 'roll_total' => $total, 'roll_source' => $this->source)); $id = $dbw->insertId; $this->id[] = $id; return ""; }	return " : ". substr($match[0], 2) ." "; }

function save($rev) { $dbw = wfGetDB(DB_MASTER); foreach($this->id as $id) { $dbw->update('dice_rolls', array('roll_rev' => $rev), array("roll_id=$id")); }   } }

class Dice { static protected $hooksInstalled = false;

static protected $rollArticle; static protected $rollUser;

function diceTag($input, array $args, Parser $parser, PPFrame $frame) { global $wgLang;

$id = isset($args['id'])? $args['id']: null; $dice = isset($args['dice'])? $args['dice']: null; $result = isset($args['result'])? $args['result']: null; $total = isset($args['total'])? $args['total']: null; $timestamp = '(never)'; $user = '(nobody)'; $source = '(nowhere)'; $false = 1;

$parser->getOutput->addModules('ext.Dice.tag');

if(isset($input) && $input<>'') return "".wfMessage('dice-error')->inContentLanguage->parse."$input "; if(!isset($id)) return "".wfMessage('dice-noid')->inContentLanguage->parse." ";

$dbr = wfGetDB(DB_SLAVE); $res = $dbr->select('dice_rolls',			   array('roll_user', 'roll_timestamp', 'roll_dice', 'roll_result', 'roll_total', 'roll_source'), 			    'roll_id='.(0+$id));

if($res->numRows==1) { $row = $res->fetchObject; // This trickery is required because the on-page result is decoded $rr = str_replace('&gt;', '>', str_replace('&lt;', '<', $row->roll_result)); if($dice==$row->roll_dice && $result==$rr && $total==$row->roll_total) { $user = User::newFromId($row->roll_user)->getName; $timestamp = $wgLang->timeanddate(wfTimestamp(TS_MW, $row->roll_timestamp), true); $false = 0; $source = $row->roll_source; }	}	$res->free;

if($false) return "". wfMessage('dice-bad')->inContentLanguage->parse. " ";

if ($source == "Blades For Blackbird") { $outcome = ""; if ($total >= 13) { $outcome = "advanced"; } else if ($total >= 11) { $outcome = "success"; } else if ($total >= 8) { $outcome = "partial"; } else { $outcome = "failure"; }

$output = ""; $output .= "$dice ($result) = $total: $outcome "; $output .= " "; }       else { // ($source == "Low Estate") $output = ""; $output .= "$dice ". "(" . $result . ") = ". "$total "; $output .= " "; }	return $output; }

function diceRoll($dice) { $dbw->insert('dice_rolls', array('roll_id' => $id)); }

static function parse(&$article, &$user, &$text, &$summary, $minor, $watchthis, $sectionanchor, &$flags, &$status) { if($user->isAllowed('rolldice')) { $roller = new DiceRoller(0, $user->getId, $article->getTitle->getRootText); $article->dice_roller = $roller; $text = preg_replace_callback('/@@([0-9]{0,2})[Dd]([0-9]{1,3})([bB][1-9][0-9]?)?([+-][0-9]+)?/', array($roller, 'roll'), $text); }	return true; }

static function saved(&$article, &$user, $text, $summary,			   $minoredit, $watchthis, $sectionanchor, &$flags,			    $revision, &$status, $baseRevId) { if(isset($revision) && isset($article->dice_roller)) { $article->dice_roller->save($revision->getID); }	return true; }

static function setHooks($parser) { if(!Dice::$hooksInstalled) { Dice::$hooksInstalled = true; $parser->extDice = new self; $parser->setHook('dice', array($parser->extDice, 'diceTag')); }	return true; } }

Dice_rolls.sql
CREATE TABLE /*_*/dice_rolls (	roll_id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,	roll_rev INT NOT NULL REFERENCES /*_*/revision(rev_id) ON DELETE CASCADE,	roll_user INT NOT NULL REFERENCES /*_*/mwuser(user_id) ON DELETE CASCADE,	roll_timestamp binary(14) NOT NULL,	roll_dice text NOT NULL,	roll_result text NOT NULL,	roll_total INT NOT NULL,	roll_source varchar(150) NULL, ) /*$wgDBTableOptions*/;

CREATE INDEX /*i*/dice_user_idx ON /*_*/dice_rolls(roll_user); CREATE INDEX /*i*/dice_timestamp_idx ON /*_*/dice_rolls(roll_timestamp);

SpecialDice.php
uid = $u; $this->context = $c; }

function getIndexField { return 'roll_id'; }

function formatRow($row) { $sk = $this->getSkin; $lang = $this->context->getLanguage;

$title = Title::newFromID($row->rev_page); $user = User::newFromID($row->roll_user); $id = "<td class=\"dice-log-id\">". $row->roll_id. " ";	$who = ''; if(!isset($this->uid)) { $who = "<td class=\"dice-log-user\">" . $sk->link($user->getUserPage, $user->getName) . " ";	}       $where = "<td class=\"dice-log-user\">". $row->roll_source. " ";	$when = "<td class=\"dice-log-rev\">" . $sk->link($title, null). "; "	   . $sk->link($title, $lang->timeanddate(wfTimestamp(TS_MW, $row->roll_timestamp), true),			array,			array('oldid' => $row->roll_rev, 'diff' => 'prev')) . " ";	$roll = "<td class=\"dice-log-roll\"><b>" . $row->roll_dice. "</b> (" . $row->roll_result . ") = ". $row->roll_total . " ";	return "<tr class=\"dice-log-row\">$id$who$where$when$roll "; }

function getQueryInfo { $conds = array( 0 => 'rev_id=roll_rev'); if(isset($this->uid) && $this->uid>0) $conds['roll_user'] = $this->uid;

return array(		    'tables' => array('dice_rolls', 'revision'),		     'fields' => 'roll_id, roll_user, roll_rev, roll_timestamp, rev_page, roll_dice, roll_result, roll_total, roll_source',		     'conds' => $conds); } }

class SpecialDice extends SpecialPage { function __construct { parent::__construct('Dice', 'rolldice'); }

function execute($par) { global $wgScript;

$sk = $this->getSkin; $out = $this->getOutput;

$out->addModules('ext.Dice.special'); $this->setHeaders;

$out->setPageTitle($this->msg('dice-special-title')->parse);

if(!$this->userCanExecute($this->getUser)) { $this->displayRestrictionError; return; }

$error = ''; $who = 0; $target = isset($par)? $par: $this->getRequest->getVal('target'); if(isset($target)) { $who = User::newFromName($target); if(!$who || $who->getId==0) { $who = 0; $error = $this->msg('dice-special-baduser')->parse; }	}

if($who && $this->getUser->getId!=$who->getId) { if(!$this->getUser->isAllowed('checkrolls')) { $error = $this->msg('dice-special-notself')->parse; $who = 0; }	}

if($who) $out->setSubTitle($this->msg('contribsub2', array($who->getName, "talk")));

$opt = array('size' => '20'); $opt['required'] = ''; if(!$who) $opt['autofocus'] = ''; $f = Xml::openElement('form', array('method' => 'get', 'action' => $wgScript)); $f .= Xml::openElement('fieldset') . Html::hidden('title', str_replace(' ', '_', $this->getTitle)) . Xml::element('legend', array, wfMsg('dice-special-search')) . Xml::tags('label', array('for' => 'target'), wfMsgExt('dice-special-username', 'parseinline')). ' '	   . Html::input('target', ($who? $who->getName: ''), 'text', $opt) . Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' ); $out->addHTML($f);

if($error == '') { $pager = new DicePager($who? $who->getId: null, $this); $out->addHTML(			   $pager->getNavigationBar .			    ' ' .			    $pager->getNavigationBar			   ); } else { $out->addHTML($error); }   } }

css/Dice_special.css
TABLE.dice-log	{ }

TABLE.dice-log TR:nth-child(odd) { background: #F8FCFF; }

TABLE.dice-log TD { }

TABLE.dice-log TD.dice-log-id { width: 5em; text-align: right; padding-right: .5em; }

TABLE.dice-log TD.dice-log-user { padding-right: .5em; }

TABLE.dice-log TD.dice-log-rev { padding-right: .5em; }

TABLE.dice-log TD.dice-log-roll { }

css/Dice_tag.css
SPAN.dice-ok { background: #F8FFF8; border: 1px solid #008000; padding: 2px 1ex; } SPAN.dice-bad { background: #FFF8F8; border: 1px solid #800000; padding: 2px 1ex; } SPAN.dice-dice { font-weight: bolder; } SPAN.dice-result { font-style: italic; font-size: smaller; margin-left: 1ex; margin-right: 1ex; } SPAN.dice-total { font-weight: bolder; font-size: larger; }

SPAN.dice2-outer { border: 1px solid; padding: 2px 1ex; font-weight: bold; } SPAN.dice2-outer-advanced { border-color: blue; color: blue; } SPAN.dice2-outer-success { border-color: green; color: green; } SPAN.dice2-outer-partial { border-color: black; color: black; } SPAN.dice2-outer-failure { border-color: red; color: red; } SPAN.dice2-outcome { font-size: 20px; font-variant: small-caps; }