1214 lines
31 KiB
PHP
1214 lines
31 KiB
PHP
<?php
|
|
/**
|
|
* phpBB Studio's Dice extension for the phpBB Forum Software package.
|
|
*
|
|
* @copyright (c) 2019 phpBB Studio <https://www.phpbbstudio.com>
|
|
* @license GNU General Public License, version 2 (GPL-2.0)
|
|
*/
|
|
|
|
namespace phpbbstudio\dice\entity;
|
|
|
|
/**
|
|
* Entity for a roll.
|
|
*/
|
|
class roll implements roll_interface
|
|
{
|
|
/**
|
|
* Data for this entity.
|
|
*
|
|
* @var array
|
|
* roll_id
|
|
* roll_notation
|
|
* roll_dices
|
|
* roll_rolls
|
|
* roll_output
|
|
* roll_total
|
|
* roll_successes
|
|
* roll_is_pool
|
|
* roll_time
|
|
* roll_edit_user
|
|
* roll_edit_time
|
|
* roll_edit_count
|
|
* forum_id
|
|
* topic_id
|
|
* post_id
|
|
* user_id
|
|
* @access protected
|
|
*/
|
|
protected $data;
|
|
|
|
/** @var \phpbb\config\config */
|
|
protected $config;
|
|
|
|
/** @var \phpbb\db\driver\driver_interface */
|
|
protected $db;
|
|
|
|
/** @var \phpbbstudio\dice\core\functions_common */
|
|
protected $functions;
|
|
|
|
/** @var \phpbb\language\language */
|
|
protected $lang;
|
|
|
|
/** @var \phpbbstudio\dice\core\functions_regex */
|
|
protected $regex;
|
|
|
|
/** @var \phpbbstudio\dice\core\functions_utils */
|
|
protected $utils;
|
|
|
|
/** @var string Dice rolls table */
|
|
protected $table;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param \phpbb\config\config $config Configuration object
|
|
* @param \phpbb\db\driver\driver_interface $db Database object
|
|
* @param \phpbbstudio\dice\core\functions_common $functions Common functions
|
|
* @param \phpbb\language\language $lang Language object
|
|
* @param \phpbbstudio\dice\core\functions_regex $regex Regex functions
|
|
* @param \phpbbstudio\dice\core\functions_utils $utils Utility functions
|
|
* @param string $table Dice rolls table
|
|
* @return void
|
|
* @access public
|
|
*/
|
|
public function __construct(
|
|
\phpbb\config\config $config,
|
|
\phpbb\db\driver\driver_interface $db,
|
|
\phpbbstudio\dice\core\functions_common $functions,
|
|
\phpbb\language\language $lang,
|
|
\phpbbstudio\dice\core\functions_regex $regex,
|
|
\phpbbstudio\dice\core\functions_utils $utils,
|
|
$table
|
|
)
|
|
{
|
|
$this->config = $config;
|
|
$this->db = $db;
|
|
$this->functions = $functions;
|
|
$this->lang = $lang;
|
|
$this->regex = $regex;
|
|
$this->utils = $utils;
|
|
$this->table = $table;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function load($id)
|
|
{
|
|
$sql = 'SELECT *
|
|
FROM ' . $this->table . '
|
|
WHERE roll_id = ' . (int) $id;
|
|
$result = $this->db->sql_query($sql);
|
|
$this->data = $this->db->sql_fetchrow($result);
|
|
$this->db->sql_freeresult($result);
|
|
|
|
if ($this->data === false)
|
|
{
|
|
// The roll does not exist
|
|
throw new \phpbbstudio\dice\exception\out_of_bounds(['ROLL_ID', 'ROLL_NOT_EXIST']);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function import(array $data)
|
|
{
|
|
$this->data = [];
|
|
|
|
foreach ($data as $key => $value)
|
|
{
|
|
$this->data[$key] = $value;
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function insert()
|
|
{
|
|
if (!empty($this->data['roll_id']))
|
|
{
|
|
// The roll already exists
|
|
throw new \phpbbstudio\dice\exception\out_of_bounds(['ROLL_ID', 'ROLL_ALREADY_EXIST']);
|
|
}
|
|
|
|
// Insert the roll data to the database
|
|
$sql = 'INSERT INTO ' . $this->table . ' ' . $this->db->sql_build_array('INSERT', $this->data);
|
|
$this->db->sql_query($sql);
|
|
|
|
// Set the roll_id using the id created by the SQL insert
|
|
$this->data['roll_id'] = (int) $this->db->sql_nextid();
|
|
|
|
/**
|
|
* And update the table, as we have a different primary key
|
|
* That's for forked topics where we can't use the autoincrement value.
|
|
*/
|
|
$sql = 'UPDATE ' . $this->table . ' SET roll_id = ' . (int) $this->data['roll_id'] . ' WHERE roll_num = ' . (int) $this->data['roll_id'];
|
|
$this->db->sql_query($sql);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function save()
|
|
{
|
|
if (empty($this->data['roll_id']))
|
|
{
|
|
// The roll does not exist
|
|
throw new \phpbbstudio\dice\exception\out_of_bounds(['ROLL_ID', 'ROLL_NOT_EXIST']);
|
|
}
|
|
|
|
/**
|
|
* Copy the data array, filtering out the roll_id identifier
|
|
* so we do not attempt to update the row's identity column.
|
|
*/
|
|
$sql_array = array_diff_key($this->data, ['roll_num' => null, 'roll_id' => null]);
|
|
|
|
// Update the roll data in the database
|
|
$sql = 'UPDATE ' . $this->table . '
|
|
SET ' . $this->db->sql_build_array('UPDATE', $sql_array) . '
|
|
WHERE roll_id = ' . (int) $this->get_id();
|
|
$this->db->sql_query($sql);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function roll()
|
|
{
|
|
// There are no dices to roll!
|
|
if (!$this->get_dices())
|
|
{
|
|
throw new \phpbbstudio\dice\exception\unexpected_value(['ROLL_DICES', 'FIELD_MISSING']);
|
|
}
|
|
|
|
$notation_rolls = [];
|
|
|
|
foreach ($this->get_dices() as $dice)
|
|
{
|
|
$rolls = $re_rolls = [];
|
|
|
|
$sides = $dice['sides'];
|
|
$callback = 'default_dice';
|
|
|
|
// Ensure the roll quantity is valid
|
|
$dice['qty'] = ($dice['qty'] > 0) ? $dice['qty'] : 1;
|
|
|
|
// check for non-numerical dice formats
|
|
if ($dice['fudge'])
|
|
{
|
|
// we have a fudge dice - define the callback to return the `fudge` roll method
|
|
$callback = 'fudge_dice';
|
|
|
|
// set the `sides` to the correct value for the fudge type
|
|
$sides = (isset($dice['fudge'][1]) && $this->utils->is_numeric($dice['fudge'][1])) ? intval($dice['fudge'][1]) : 2;
|
|
}
|
|
else if (gettype($dice['sides']) === 'string')
|
|
{
|
|
if ($dice['sides'] === '%'){
|
|
// convert percentile to 100 sided die
|
|
$sides = 100;
|
|
}
|
|
}
|
|
|
|
// only continue if the number of sides is valid
|
|
if ($sides)
|
|
{
|
|
// loop through and roll for the quantity
|
|
for ($i = 0; $i < $dice['qty']; $i++)
|
|
{
|
|
// the rolls for the current die (only multiple rolls if exploding)
|
|
$re_rolls = [];
|
|
|
|
// count of rolls for this die roll (Only > 1 if exploding)
|
|
$roll_count = 0;
|
|
|
|
/** @noinspection PhpUnusedLocalVariableInspection */
|
|
// the total rolled
|
|
$roll_total = 0;
|
|
|
|
/** @noinspection PhpUnusedLocalVariableInspection */
|
|
// re-roll index
|
|
$roll_index = 0;
|
|
|
|
do
|
|
{
|
|
// the reRolls index to use
|
|
$roll_index = count($re_rolls);
|
|
|
|
// get the total rolled on this die
|
|
$roll_total = $this->utils->{$callback}($sides);
|
|
|
|
// add the roll to our list
|
|
$re_rolls[$roll_index] = isset($re_rolls[$roll_index]) ? $re_rolls[$roll_index] + $roll_total : $roll_total;
|
|
|
|
// subtract 1 from penetrated rolls (only consecutive rolls, after initial roll are subtracted)
|
|
if ($dice['penetrate'] && ($roll_count > 0))
|
|
{
|
|
$re_rolls[$roll_index]--;
|
|
}
|
|
|
|
$roll_count++;
|
|
}
|
|
while ($dice['explode'] && $this->utils->is_compare_point($dice['compare_point'], $roll_total));
|
|
|
|
$rolls = array_merge($rolls, $re_rolls);
|
|
}
|
|
}
|
|
|
|
$notation_rolls[] = $rolls;
|
|
}
|
|
|
|
// Set the rolls, total and output
|
|
$this->set_rolls($notation_rolls)
|
|
->set_total()
|
|
->set_output();
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function get_id()
|
|
{
|
|
return isset($this->data['roll_id']) ? (int) $this->data['roll_id'] : 0;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function get_forum()
|
|
{
|
|
return isset($this->data['forum_id']) ? (int) $this->data['forum_id'] : 0;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function set_forum($id)
|
|
{
|
|
// Enforce data type
|
|
$id = (int) $id;
|
|
|
|
/**
|
|
* If the data is out of range we'll throw an exception. We use 4294967295 as a
|
|
* maximum because it matches the MySQL unsigned large int maximum value which
|
|
* is the lowest amongst the DBMS supported by phpBB.
|
|
*/
|
|
if ($id < 0 || $id > 4294967295)
|
|
{
|
|
throw new \phpbbstudio\dice\exception\out_of_bounds(['ROLL_FORUM_ID', 'ROLL_ULINT']);
|
|
}
|
|
|
|
// Add the identifier to the data array
|
|
$this->data['forum_id'] = $id;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function get_topic()
|
|
{
|
|
return isset($this->data['topic_id']) ? (int) $this->data['topic_id'] : 0;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function set_topic($id)
|
|
{
|
|
// Enforce data type
|
|
$id = (int) $id;
|
|
|
|
/**
|
|
* If the data is out of range we'll throw an exception. We use 4294967295 as a
|
|
* maximum because it matches the MySQL unsigned large int maximum value which
|
|
* is the lowest amongst the DBMS supported by phpBB.
|
|
*/
|
|
if ($id < 0 || $id > 4294967295)
|
|
{
|
|
throw new \phpbbstudio\dice\exception\out_of_bounds(['ROLL_TOPIC_ID', 'ROLL_ULINT']);
|
|
}
|
|
|
|
// Add the identifier to the data array
|
|
$this->data['topic_id'] = $id;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function get_post()
|
|
{
|
|
return isset($this->data['post_id']) ? (int) $this->data['post_id'] : 0;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function set_post($id)
|
|
{
|
|
// Enforce data type
|
|
$id = (int) $id;
|
|
|
|
/**
|
|
* If the data is out of range we'll throw an exception. We use 4294967295 as a
|
|
* maximum because it matches the MySQL unsigned large int maximum value which
|
|
* is the lowest amongst the DBMS supported by phpBB.
|
|
*/
|
|
if ($id < 0 || $id > 4294967295)
|
|
{
|
|
throw new \phpbbstudio\dice\exception\out_of_bounds(['ROLL_POST_ID', 'ROLL_ULINT']);
|
|
}
|
|
|
|
// Add the identifier to the data array
|
|
$this->data['post_id'] = $id;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function get_user()
|
|
{
|
|
return isset($this->data['user_id']) ? (int) $this->data['user_id'] : 0;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function set_user($id)
|
|
{
|
|
// Enforce data type
|
|
$id = (int) $id;
|
|
|
|
// User identifier is a required field
|
|
if (empty($id))
|
|
{
|
|
throw new \phpbbstudio\dice\exception\unexpected_value(['ROLL_USER_ID', 'FIELD_MISSING']);
|
|
}
|
|
|
|
/**
|
|
* If the data is out of range we'll throw an exception. We use 4294967295 as a
|
|
* maximum because it matches the MySQL unsigned large int maximum value which
|
|
* is the lowest amongst the DBMS supported by phpBB.
|
|
*/
|
|
if ($id < 0 || $id > 4294967295)
|
|
{
|
|
throw new \phpbbstudio\dice\exception\out_of_bounds(['ROLL_USER_ID', 'ROLL_ULINT']);
|
|
}
|
|
|
|
// Add the identifier to the data array
|
|
$this->data['user_id'] = $id;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function get_notation()
|
|
{
|
|
return isset($this->data['roll_notation']) ? (string) $this->data['roll_notation'] : '';
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function set_notation($notation)
|
|
{
|
|
// Enforce a data type
|
|
$notation = (string) $notation;
|
|
|
|
// Notation is a required field
|
|
if ($notation === '')
|
|
{
|
|
throw new \phpbbstudio\dice\exception\unexpected_value(['ROLL_NOTATION', 'FIELD_MISSING']);
|
|
}
|
|
|
|
// We limit the notation length to 255 characters
|
|
if (truncate_string($notation, 255) !== $notation)
|
|
{
|
|
throw new \phpbbstudio\dice\exception\unexpected_value(['ROLL_NOTATION', 'TOO_LONG']);
|
|
}
|
|
|
|
// Add the notation to the data array
|
|
$this->data['roll_notation'] = $notation;
|
|
|
|
// Set the all the dices
|
|
$this->set_dices();
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function get_dices()
|
|
{
|
|
return isset($this->data['roll_dices']) ? (array) json_decode($this->data['roll_dices'], true) : [];
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function set_dices()
|
|
{
|
|
if (!$this->get_notation())
|
|
{
|
|
throw new \phpbbstudio\dice\exception\unexpected_value(['ROLL_NOTATION', 'FIELD_MISSING']);
|
|
}
|
|
|
|
$dices = [];
|
|
$percentage = $fudge = 0;
|
|
$quantity = $exploding = 0;
|
|
$penetrating = $compounding = 0;
|
|
|
|
$pattern = $this->regex->get('notation');
|
|
$notation = $this->get_notation();
|
|
|
|
$matches_found = preg_match_all($pattern, $notation, $matches, PREG_SET_ORDER);
|
|
|
|
if (!$matches_found)
|
|
{
|
|
throw new \phpbbstudio\dice\exception\unexpected_value((['ROLL_NOTATION', 'ROLL_NO_MATCHES']));
|
|
}
|
|
|
|
foreach ($matches as $match)
|
|
{
|
|
$dice = [
|
|
'operator' => $match[1] ? $match[1] : '+',
|
|
'qty' => $match[2] ? intval($match[2]) : 1,
|
|
'sides' => $match[3] ? ($this->utils->is_numeric($match[3]) ? intval($match[3]) : $match[3]) : 1,
|
|
'fudge' => false,
|
|
'explode' => (bool) $match[5],
|
|
'penetrate' => ((bool) (($match[5] === '!p') || ($match[5] === '!!p'))),
|
|
'compound' => ((bool) (($match[5] === '!!') || ($match[5] === '!!p'))),
|
|
'compare_point' => false,
|
|
'additions' => [],
|
|
];
|
|
|
|
// Check dice quantity limit
|
|
if ($this->config['dice_qty_per_dice'] && ($dice['qty'] > $this->config['dice_qty_per_dice']))
|
|
{
|
|
throw new \phpbbstudio\dice\exception\unexpected_value(['ROLL_DICE_QTY', 'TOO_HIGH']);
|
|
}
|
|
|
|
// Check dice sides limit, if limit is set and sides is not 100 or % (percentage dice)
|
|
if (!in_array($dice['sides'], ['F', 'F.1', 'F.2', '%', 100]))
|
|
{
|
|
if ($this->config['dice_sides_only'])
|
|
{
|
|
$allowed_sides = $this->functions->get_dice_sides();
|
|
if (!in_array($dice['sides'], $allowed_sides))
|
|
{
|
|
throw new \phpbbstudio\dice\exception\unexpected_value(['ROLL_SIDES', 'NOT_ALLOWED']);
|
|
}
|
|
}
|
|
|
|
if ($this->config['dice_sides_per_dice'] && ($dice['sides'] > $this->config['dice_sides_per_dice']))
|
|
{
|
|
throw new \phpbbstudio\dice\exception\unexpected_value(['ROLL_SIDES', 'TOO_HIGH']);
|
|
}
|
|
}
|
|
|
|
// Add the dice quantity to the overall count
|
|
$quantity += $dice['qty'];
|
|
$exploding = $dice['explode'] ? ++$exploding : $exploding;
|
|
$penetrating = $dice['penetrate'] ? ++$penetrating : $penetrating;
|
|
$compounding = $dice['compound'] ? ++$compounding : $compounding;
|
|
$percentage = in_array($dice['sides'], ['%', 100]) ? ++$percentage : $percentage;
|
|
|
|
// Check if it's a fudge dice
|
|
if (gettype($dice['sides']) === 'string')
|
|
{
|
|
$dice['fudge'] = preg_match($this->regex->get('fudge', true), $dice['sides'], $fudge_matches) ? $fudge_matches : false;
|
|
$fudge = $dice['fudge'] ? ++$fudge : $fudge;
|
|
}
|
|
|
|
// Check if we have a compare point
|
|
if ($match[6])
|
|
{
|
|
$dice['compare_point'] = [
|
|
'operator' => $match[6],
|
|
'value' => intval($match[7]),
|
|
];
|
|
}
|
|
else if ($dice['explode'])
|
|
{
|
|
// we are exploding the dice so we need a compare point, but none has been defined
|
|
$dice['compare_point'] = [
|
|
'operator' => '=',
|
|
'value' => $dice['fudge'] ? 1 : ($dice['sides'] === '%') ? 100 : $dice['sides'],
|
|
];
|
|
}
|
|
|
|
// Check if we have additions
|
|
if (isset($match[8]))
|
|
{
|
|
// we have additions (ie. +2, -L)
|
|
preg_match_all($this->regex->get('addition'), $match[8], $additions, PREG_SET_ORDER);
|
|
|
|
foreach ($additions as $addition)
|
|
{
|
|
// add the addition to the list
|
|
$dice['additions'][] = [
|
|
// addition operator for concatenating with the dice (+, -, /, *)
|
|
'operator' => $addition[1],
|
|
// addition value - either numerical or string 'L' or 'H'
|
|
'value' => $this->utils->is_numeric($addition[2]) ? intval($addition[2]) : $addition[2],
|
|
];
|
|
}
|
|
}
|
|
|
|
// Add the dice to the list
|
|
$dices[] = $dice;
|
|
}
|
|
|
|
// Check dice limit
|
|
if ($this->config['dice_per_notation'] && (count($dices) > $this->config['dice_per_notation']))
|
|
{
|
|
throw new \phpbbstudio\dice\exception\unexpected_value(['ROLL_DICES', 'TOO_HIGH']);
|
|
}
|
|
|
|
// Check dice quantity limit
|
|
if ($this->config['dice_qty_dice_per_notation'] && ($quantity > $this->config['dice_qty_dice_per_notation']))
|
|
{
|
|
throw new \phpbbstudio\dice\exception\unexpected_value(['ROLL_DICES_QTY', 'TOO_HIGH']);
|
|
}
|
|
|
|
// Check percentage dice limit
|
|
if ($this->config['dice_pc_dice_per_notation'] && ($percentage > $this->config['dice_pc_dice_per_notation']))
|
|
{
|
|
throw new \phpbbstudio\dice\exception\unexpected_value(['ROLL_DICE_PERCENT', 'TOO_HIGH']);
|
|
}
|
|
|
|
// Check fudge dice limit
|
|
if ($this->config['dice_fudge_dice_per_notation'] && ($fudge > $this->config['dice_fudge_dice_per_notation']))
|
|
{
|
|
throw new \phpbbstudio\dice\exception\unexpected_value(['ROLL_DICE_FUDGE', 'TOO_HIGH']);
|
|
}
|
|
|
|
// Check exploding dice limit
|
|
if ($this->config['dice_exploding_dice_per_notation'] && ($exploding > $this->config['dice_exploding_dice_per_notation']))
|
|
{
|
|
throw new \phpbbstudio\dice\exception\unexpected_value(['ROLL_DICE_EXPLODE', 'TOO_HIGH']);
|
|
}
|
|
|
|
// Check penetrating dice limit
|
|
if ($this->config['dice_penetration_dice_per_notation'] && ($penetrating > $this->config['dice_penetration_dice_per_notation']))
|
|
{
|
|
throw new \phpbbstudio\dice\exception\unexpected_value(['ROLL_DICE_PENETRATE', 'TOO_HIGH']);
|
|
}
|
|
|
|
// Check compounding dice limit
|
|
if ($this->config['dice_compound_dice_per_notation'] && ($compounding > $this->config['dice_compound_dice_per_notation']))
|
|
{
|
|
throw new \phpbbstudio\dice\exception\unexpected_value(['ROLL_DICE_COMPOUND', 'TOO_HIGH']);
|
|
}
|
|
|
|
// JSON encode
|
|
$dices = json_encode($dices);
|
|
|
|
// Add the dices to the data array
|
|
$this->data['roll_dices'] = $dices;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function get_rolls()
|
|
{
|
|
return isset($this->data['roll_rolls']) ? (array) json_decode($this->data['roll_rolls']) : [0];
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function set_rolls(array $rolls)
|
|
{
|
|
// Enforce data type
|
|
$rolls = (array) $rolls;
|
|
|
|
// JSON encode
|
|
$rolls = json_encode($rolls);
|
|
|
|
// Add the rolls to the data array
|
|
$this->data['roll_rolls'] = $rolls;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function get_display($skin, $dir, $ext = '')
|
|
{
|
|
$display = [];
|
|
|
|
// loop through and build the string for die rolled
|
|
foreach ($this->get_dices() as $index => $dice)
|
|
{
|
|
$rolls = isset($this->get_rolls()[$index]) ? $this->get_rolls()[$index] : [];
|
|
$has_compare_point = !empty($dice['compare_point']);
|
|
|
|
// Current roll total - used for totalling compounding rolls
|
|
$current_roll = 0;
|
|
$compounded_rolls = [];
|
|
|
|
$display_dice = ['OPERATOR' => $dice['operator']];
|
|
|
|
// Output the rolls
|
|
foreach ($rolls as $roll_index => $roll)
|
|
{
|
|
$display_roll = [];
|
|
|
|
// get the roll value to compare to (If penetrating and not the first roll, add 1, to compensate for the penetration)
|
|
$roll_val = ($dice['penetrate'] && $current_roll) ? $roll + 1 : $roll;
|
|
$has_matched_cp = $has_compare_point && $this->utils->is_compare_point($dice['compare_point'], $roll_val);
|
|
|
|
$delimit = true;
|
|
|
|
if ($dice['explode'] && $has_matched_cp)
|
|
{
|
|
// this die roll exploded (Either matched the explode value or is greater than the max - exploded and compounded)
|
|
|
|
// add the current roll to the roll total
|
|
$current_roll += $roll;
|
|
|
|
if ($dice['compound'])
|
|
{
|
|
$compounded_rolls[] = $roll;
|
|
|
|
// do NOT add the delimiter after this roll as we're not outputting it
|
|
$delimit = false;
|
|
}
|
|
else
|
|
{
|
|
$display_roll['ROLL'] = $roll;
|
|
$display_roll['TOOLTIP']['S_EXPLODE'] = $this->lang->lang('DICE_ROLL_EXPLODED');
|
|
if ($dice['penetrate'])
|
|
{
|
|
$display_roll['TOOLTIP']['S_PENETRATE'] = $this->lang->lang('DICE_ROLL_PENETRATED');
|
|
} }
|
|
}
|
|
else if ($has_matched_cp)
|
|
{
|
|
// not exploding but we've matched a compare point - this is a pool dice (success or failure)
|
|
$display_roll['ROLL'] = $roll;
|
|
$display_roll['TOOLTIP']['S_SUCCESS'] = $this->lang->lang('SUCCESS');
|
|
}
|
|
else if ($dice['compound'] && $current_roll)
|
|
{
|
|
// last roll in a compounding set (This one didn't compound)
|
|
$display_roll['ROLL'] = ($roll + $current_roll);
|
|
$display_roll['TOOLTIP']['S_EXPLODE'] = $this->lang->lang('DICE_ROLL_EXPLODED');
|
|
$display_roll['TOOLTIP']['S_COMPOUND'] = $this->lang->lang('DICE_ROLL_COMPOUNDED');
|
|
if ($dice['penetrate'])
|
|
{
|
|
$display_roll['TOOLTIP']['S_PENETRATE'] = $this->lang->lang('DICE_ROLL_PENETRATED');
|
|
}
|
|
$display_roll['COMPOUNDED_ROLLS'] = $compounded_rolls;
|
|
|
|
// Reset current roll total
|
|
$current_roll = 0;
|
|
$compounded_rolls = [];
|
|
}
|
|
else
|
|
{
|
|
// Just a normal roll
|
|
$display_roll['ROLL'] = $roll;
|
|
}
|
|
|
|
if ($delimit)
|
|
{
|
|
// Check if an image exists.
|
|
if ($skin !== 'text')
|
|
{
|
|
$img_alt = $this->functions->get_dice_image_notation($dice['sides'], $display_roll['ROLL'], $ext);
|
|
$img_src = $dir . $skin . '/' . $img_alt;
|
|
|
|
$display_roll['IMAGE'] = $this->functions->check_dice_dir($img_src) ? $this->functions->update_dice_img_path($img_src) : '';
|
|
}
|
|
|
|
$display_dice['ROLLS'][] = $display_roll;
|
|
}
|
|
}
|
|
|
|
// Add any additions
|
|
if (!empty($dice['additions']))
|
|
{
|
|
$display_dice['ADDITIONS'] = array_reduce($dice['additions'], function($prev, $current)
|
|
{
|
|
return $prev . $current['operator'] . $current['value'];
|
|
}, '');
|
|
|
|
// Actual values of the rolls for the purposes of L/H modifiers
|
|
$rolls_values = $dice['compound'] ? array_reduce($rolls, function($a, $b)
|
|
{
|
|
return $a + $b;
|
|
}, 0) : $rolls;
|
|
|
|
foreach ($dice['additions'] as $addition)
|
|
{
|
|
switch ($addition['value'])
|
|
{
|
|
case 'H':
|
|
$value_highest = max($rolls_values);
|
|
|
|
$key = array_search($value_highest, $rolls_values);
|
|
$display_dice['ROLLS'][$key]['TOOLTIP']['S_HIGHEST'] = $this->lang->lang('DICE_ROLL_HIGHEST');
|
|
break;
|
|
case 'L':
|
|
$value_lowest = min($rolls_values);
|
|
|
|
$key = array_search($value_lowest, $rolls_values);
|
|
$display_dice['ROLLS'][$key]['TOOLTIP']['S_LOWEST'] = $this->lang->lang('DICE_ROLL_LOWEST');
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
$display[] = $display_dice;
|
|
}
|
|
|
|
return $display;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function get_output()
|
|
{
|
|
return isset($this->data['roll_output']) ? (string) $this->data['roll_output'] : '';
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function set_output()
|
|
{
|
|
$output = '';
|
|
|
|
if ($this->get_dices() && is_array($this->get_rolls()) && $this->get_rolls())
|
|
{
|
|
// loop through and build the string for die rolled
|
|
foreach ($this->get_dices() as $index => $dice)
|
|
{
|
|
$rolls = isset($this->get_rolls()[$index]) ? $this->get_rolls()[$index] : [];
|
|
$has_compare_point = !empty($dice['compare_point']);
|
|
|
|
// Current roll total - used for totalling compounding rolls
|
|
$current_roll = 0;
|
|
|
|
$output .= (($index > 0) ? $dice['operator'] : '') . '[';
|
|
|
|
// Output the rolls
|
|
foreach ($rolls as $roll_index => $roll)
|
|
{
|
|
// get the roll value to compare to (If penetrating and not the first roll, add 1, to compensate for the penetration)
|
|
$roll_val = ($dice['penetrate'] && $current_roll) ? $roll + 1 : $roll;
|
|
$has_matched_cp = $has_compare_point && $this->utils->is_compare_point($dice['compare_point'], $roll_val);
|
|
|
|
$delimit = $roll_index !== (count($rolls) - 1);
|
|
|
|
if ($dice['explode'] && $has_matched_cp)
|
|
{
|
|
// this die roll exploded (Either matched the explode value or is greater than the max - exploded and compounded)
|
|
|
|
// add the current roll to the roll total
|
|
$current_roll += $roll;
|
|
|
|
if ($dice['compound'])
|
|
{
|
|
// do NOT add the delimiter after this roll as we're not outputting it
|
|
$delimit = false;
|
|
}
|
|
else
|
|
{
|
|
$output .= $roll . '!' . ($dice['penetrate'] ? 'p' : '');
|
|
}
|
|
}
|
|
else if ($has_matched_cp)
|
|
{
|
|
// not exploding but we've matched a compare point - this is a pool dice (success or failure)
|
|
$output .= $roll . '*';
|
|
}
|
|
else if ($dice['compound'] && $current_roll)
|
|
{
|
|
// last roll in a compounding set (This one didn't compound)
|
|
$output .= ($roll + $current_roll) . '!!' . ($dice['penetrate'] ? 'p' : '');
|
|
|
|
// Reset current roll total
|
|
$current_roll = 0;
|
|
}
|
|
else
|
|
{
|
|
// Just a normal roll
|
|
$output .= $roll;
|
|
}
|
|
|
|
if ($delimit)
|
|
{
|
|
$output .= $this->lang->lang('COMMA_SEPARATOR');
|
|
}
|
|
}
|
|
|
|
$output .= ']';
|
|
|
|
// Add any additions
|
|
if (!empty($dice['additions']))
|
|
{
|
|
$output .= array_reduce($dice['additions'], function($prev, $current)
|
|
{
|
|
return $prev . $current['operator'] . $current['value'];
|
|
}, '');
|
|
}
|
|
}
|
|
|
|
// Add the total
|
|
$output .= ' = ' . $this->get_total();
|
|
}
|
|
else
|
|
{
|
|
$output .= $this->lang->lang('DICE_ROLL_NO_ROLL');
|
|
}
|
|
|
|
// Add the result to the data array
|
|
$this->data['roll_output'] = $output;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function get_total()
|
|
{
|
|
return isset($this->data['roll_total']) ? ((int) $this->data['roll_total'] / 100) : 0;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function set_total()
|
|
{
|
|
$total = $successes = 0;
|
|
$overall_is_pool = false;
|
|
|
|
if ($this->get_dices() && is_array($this->get_rolls()) && $this->get_rolls())
|
|
{
|
|
// Loop through each roll and calculate the totals
|
|
foreach ($this->get_dices() as $index => $dice)
|
|
{
|
|
$rolls = isset($this->get_rolls()[$index]) ? $this->get_rolls()[$index] : [];
|
|
|
|
/** @noinspection PhpUnusedLocalVariableInspection */
|
|
$dice_total = 0;
|
|
|
|
// Actual values of the rolls for the purposes of L/H modifiers
|
|
$rolls_values = $dice['compound'] ? array_reduce($rolls, function($a, $b)
|
|
{
|
|
return $a + $b;
|
|
}, 0) : $rolls;
|
|
|
|
$is_pool = !$dice['explode'] && !empty($dice['compare_point']);
|
|
|
|
if ($is_pool)
|
|
{
|
|
/**
|
|
* Pool dice are success/failure so we don't want the actual dice roll
|
|
* we need to convert each roll to 1 (success) or 0 (failure)
|
|
*/
|
|
$rolls = array_map(function($value) use ($dice)
|
|
{
|
|
return $this->utils->get_success_state_value($value, $dice['compare_point']);
|
|
}, $rolls);
|
|
}
|
|
|
|
// add all the rolls together to get the total
|
|
$dice_total = $this->utils->sum_array($rolls);
|
|
|
|
if (!empty($dice['additions']))
|
|
{
|
|
// loop through the additions and handle them
|
|
foreach ($dice['additions'] as $addition)
|
|
{
|
|
$addition_value = $addition['value'];
|
|
$is_pool_modifier = false;
|
|
|
|
// run any necessary addition value modifications
|
|
if ($addition_value === 'H')
|
|
{
|
|
// 'H' is equivalent to the highest roll
|
|
$addition_value = max($rolls_values);
|
|
// flag that this value needs to be modified to a success/failure value
|
|
$is_pool_modifier = true;
|
|
}
|
|
else if ($addition_value === 'L')
|
|
{
|
|
// 'L' is equivalent to the lowest roll
|
|
$addition_value = min($rolls_values);
|
|
// flag that this value needs to be modified to a success/failure value
|
|
$is_pool_modifier = true;
|
|
}
|
|
|
|
if ($is_pool && $is_pool_modifier)
|
|
{
|
|
// pool dice are either success or failure, so value is converted to 1 or 0
|
|
$addition_value = $this->utils->get_success_state_value($addition_value, $dice['compare_point']);
|
|
}
|
|
|
|
// run the actual mathematical equation
|
|
$dice_total = $this->utils->equate_numbers($dice_total, $addition_value, $addition['operator']);
|
|
}
|
|
}
|
|
|
|
// Total the value
|
|
$total = $this->utils->equate_numbers($total, $dice_total, $dice['operator']);
|
|
|
|
// if this is a pool dice, add it's success count to the count
|
|
if ($is_pool)
|
|
{
|
|
$successes = $this->utils->equate_numbers($successes, $dice_total, $dice['operator']);
|
|
}
|
|
|
|
// Update if this entire roll had any pool dice.
|
|
$overall_is_pool = $overall_is_pool ? $overall_is_pool : $is_pool;
|
|
}
|
|
}
|
|
|
|
// Set the successes
|
|
$this->set_successes($successes);
|
|
|
|
// Set the is_pool status
|
|
$this->set_is_pool($overall_is_pool);
|
|
|
|
// Round to two decimals
|
|
$total = round($total, 2, PHP_ROUND_HALF_UP);
|
|
|
|
// Make it an integer
|
|
$total = $total * 100;
|
|
|
|
// Enforce data type
|
|
$total = (int) $total;
|
|
|
|
// Add the total to the data array
|
|
$this->data['roll_total'] = $total;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function get_successes()
|
|
{
|
|
return isset($this->data['roll_successes']) ? (int) $this->data['roll_successes'] : 0;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function set_successes($successes)
|
|
{
|
|
// Enforce data type
|
|
$successes = (int) $successes;
|
|
|
|
/**
|
|
* If the data is out of range we'll throw an exception. We use 65535 as a
|
|
* maximum because it matches the MySQL unsigned small int maximum value which
|
|
* is the lowest amongst the DBMS supported by phpBB.
|
|
*/
|
|
if ($successes < 0 || $successes > 65535)
|
|
{
|
|
throw new \phpbbstudio\dice\exception\out_of_bounds(['ROLL_SUCCESSES', 'ROLL_USINT']);
|
|
}
|
|
|
|
// Add the successes to the data array
|
|
$this->data['roll_successes'] = $successes;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function get_is_pool()
|
|
{
|
|
return isset($this->data['roll_is_pool']) ? (bool) $this->data['roll_is_pool'] : false;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function set_is_pool($is_pool)
|
|
{
|
|
// Enforce data type
|
|
$is_pool = (bool) $is_pool;
|
|
|
|
// Add the is_pool status to the data array
|
|
$this->data['roll_is_pool'] = $is_pool;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function get_time()
|
|
{
|
|
return isset($this->data['roll_time']) ? (int) $this->data['roll_time'] : 0;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function set_time($time)
|
|
{
|
|
// Enforce data type
|
|
$time = (int) $time;
|
|
|
|
/*
|
|
* If the data is out of range we'll throw an exception. We use 4294967295 as a
|
|
* maximum because it matches the MySQL unsigned large int maximum value which
|
|
* is the lowest amongst the DBMS supported by phpBB. ULINT equals UNIX TIMESTAMP.
|
|
*/
|
|
if ($time < 0 || $time > 4294967295)
|
|
{
|
|
throw new \phpbbstudio\dice\exception\out_of_bounds(['ROLL_TIME', 'ROLL_ULINT']);
|
|
}
|
|
|
|
// Add the time to the data array
|
|
$this->data['roll_time'] = $time;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function get_edit_user()
|
|
{
|
|
return isset($this->data['roll_edit_user']) ? (int) $this->data['roll_edit_user'] : 0;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function set_edit_user($id)
|
|
{
|
|
// Enforce data type
|
|
$id = (int) $id;
|
|
|
|
/**
|
|
* If the data is out of range we'll throw an exception. We use 4294967295 as a
|
|
* maximum because it matches the MySQL unsigned large int maximum value which
|
|
* is the lowest amongst the DBMS supported by phpBB.
|
|
*/
|
|
if ($id < 0 || $id > 4294967295)
|
|
{
|
|
throw new \phpbbstudio\dice\exception\out_of_bounds(['ROLL_EDIT_USER', 'ROLL_ULINT']);
|
|
}
|
|
|
|
// Add the identifier to the data array
|
|
$this->data['roll_edit_user'] = $id;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function get_edit_time()
|
|
{
|
|
return isset($this->data['roll_edit_time']) ? (int) $this->data['roll_edit_time'] : 0;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function set_edit_time($time)
|
|
{
|
|
// Enforce data type
|
|
$time = (int) $time;
|
|
|
|
/**
|
|
* If the data is out of range we'll throw an exception. We use 4294967295 as a
|
|
* maximum because it matches the MySQL unsigned large int maximum value which
|
|
* is the lowest amongst the DBMS supported by phpBB. ULINT equals UNIX TIMESTAMP.
|
|
*/
|
|
if ($time < 0 || $time > 4294967295)
|
|
{
|
|
throw new \phpbbstudio\dice\exception\out_of_bounds(['ROLL_EDIT_TIME', 'ROLL_ULINT']);
|
|
}
|
|
|
|
// Add the time to the data array
|
|
$this->data['roll_edit_time'] = $time;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function get_edit_count()
|
|
{
|
|
return isset($this->data['roll_edit_count']) ? (int) $this->data['roll_edit_count'] : 0;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function set_edit_count($count)
|
|
{
|
|
// Enforce data type
|
|
$count = (int) $count;
|
|
|
|
/**
|
|
* If the data is out of range we'll throw an exception. We use 4294967295 as a
|
|
* maximum because it matches the MySQL unsigned large int maximum value which
|
|
* is the lowest amongst the DBMS supported by phpBB. ULINT equals UNIX TIMESTAMP.
|
|
*/
|
|
if ($count < 0 || $count > 4294967295)
|
|
{
|
|
throw new \phpbbstudio\dice\exception\out_of_bounds(['ROLL_EDIT_COUNT', 'ROLL_ULINT']);
|
|
}
|
|
|
|
// Add the count to the data array
|
|
$this->data['roll_edit_count'] = $count;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function increment_edit_count()
|
|
{
|
|
$count = $this->get_edit_count();
|
|
|
|
$count++;
|
|
|
|
$this->set_edit_count($count);
|
|
|
|
return $this;
|
|
}
|
|
}
|