Map Rate was written in response to a
request in the Plugin Requests Forum. Its purpose is to allow players to rate a map between 1-5, inclusive, and view the current rating. Naturally, you will probably want to write some sort of external tool that makes use of the data gathered outside of the game. I certainly will be...
Configuration- maprate_db_config (default "default")
- Specifies what configuration in addons/sourcemod/configs/database.cfg to use to connect to a MySQL or SQLite DB.
- maprate_allow_revote (default "1")
- When non-zero, this allows players to change their existing map rating (recommended). When zero, players cannot rate a map more than once.
- maprate_autorate_time (default "0")
- When non-zero, this specifies the time to wait since the player started playing the map before automatically asking the player to rate the map when they die (and only if they haven't rated the map before). For example, if maprate_autorate_time is 180, the plugin will start asking players who die to rate the map 3 minutes after the player starts playing the map. The behavior of this cvar was changed in v0.10.
- maprate_autorate_delay (default "5")
- When maprate_autorate_time is enabled, Map Rate will wait maprate_autorate_delay seconds after a player dies before asking them to rate the map. This is useful if you have another plugin that displays information to a player when they die (e.g. stats) that could interfere with Map Rate.
- maprate_table (default "map_ratings")
- The name of the database table to store map ratings in. If you run multiple servers, you may want to change this for different game types or for individual servers, depending on whether you servers share databases and whether you want maprating data shared across multiple servers.
- maprate_dismiss (default "0")
- If non-zero, a "Dismiss" option will be added to the menu as option #1 (instead of "1 Star"). Changes to this cvar take effect on every map change, not instantly.
- maprate_autoresults (default "1")
- If non-zero, the graph of a map's rating are automatically displayed to a player after the player rates a map.
Commands- !maprate
- This in-game chat trigger will display the Rate Map menu.
If the player has previously rated the map, it will be noted:
After rating the map, the current rating will be displayed, complete with a nice little histogram (new since v0.6):
- !maprating
- This in-game chat trigger will display the in-game rating viewing tool (new since v0.6):
Sample Option 1 Output:
"Rate this map" will change to "Change my rating" if applicable, or will be blank if you've disallowed changing your rating.
Sample Option 2 Output:
Sample Option 3 Output:
- sm_maprate_resetratings (or !maprate_resetratings)
- Delete ratings for the current map. Requires the admin vote flag ("k").
Also accessible via the admin menu is the "Have All Players Rate This Map" command:
Clients will see this:
This command requires the vote admin flag ("k").
Database Schema
For those who will develop their own tools to use the data this plugin collects, here's what you need to know. This information is only valid if you're using MySQL as your database driver.
Code:
CREATE TABLE IF NOT EXISTS map_ratings (
steamid VARCHAR(24),
map VARCHAR(48),
rating INT(4),
rated DATETIME,
UNIQUE KEY (map, steamid)
);
As you can see, it's very simple. The one thing I can envision coders wanting is a field for player name, but that's trickier to track and IMO outside the scope of this plugin.
PHP Viewer (new 2008-05-22)
I wrote up a little PHP app to display the results. Just modify the top DEFINEs as needed. Here's what it looks like:
And here's the code (
This is for PHP5. If you have PHP4, look elsewhere in this thread for a compatible version):
PHP Code:
<?php
/**
* Map Rate Viewer
* Copyright 2008 Ryan Mannion. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define('MR_DBHOST', 'localhost');
define('MR_DBUSER', 'username');
define('MR_DBPASS', 'password');
define('MR_DBNAME', 'sourcemod_db_name');
define('MR_TABLENAME', 'map_ratings');
define('MR_THRESHOLD', 0);
define('MR_COLUMNS', 3);
define('MR_LEFTWIDTH', 200);
define('MR_RIGHTWIDTH', 100);
$sort = (isset($_GET['sort']) ? $_GET['sort'] : 'rating');
$reverse = (isset($_GET['reverse']) ? $_GET['reverse'] : 'no');
?>
<html>
<head>
<style type="text/css">
body {
font-family: Trebuchet MS, Helvetica, sans-serif;
margin-left: auto;
margin-right: auto;
width: <?php echo MR_COLUMNS * (MR_LEFTWIDTH + MR_RIGHTWIDTH); ?>;
}
div.title {
margin: 0px;
padding: 10px;
color: white;
background-color: #000066;;
}
div.title span.title {
font-weight: bold;
font-size: 18pt;
}
div.title a, a:visited, a:hover, a:link {
color: white;
}
div.ratings {
}
table.map_rating {
border: 2px solid DarkBlue;
background-color: white;
margin: 0;
}
table.map_rating span.map_name {
font-weight: bold;
}
table.rating_graph {
background-color: Lavender;
}
table.rating_graph td {
font-size: 10pt;
}
table.rating_graph tr.bars td {
vertical-align: bottom;
}
table.rating_graph tr.labels td {
text-align: center;
font-weight: bold;
font-size: 8pt;
}
table.rating_graph div.rating_1 {
background-color: #B80000;
}
table.rating_graph div.rating_2 {
background-color: #B85800;
}
table.rating_graph div.rating_3 {
background-color: #B89800;
}
table.rating_graph div.rating_4 {
background-color: #99CC33;
}
table.rating_graph div.rating_5 {
background-color: #33CC00;
}
</style>
</head>
<body>
<?php
class MapRating {
public $name;
public $num_ratings = 0;
public $ratings = array(1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0);
private $max_ratings = 0;
function __construct($name) {
$this->name = $name;
}
function add_rating($rating, $count) {
if (!isset($this->ratings[$rating])) {
throw new Exception('Invalid rating');
}
$this->ratings[$rating] += $count;
$this->num_ratings += $count;
$this->max_ratings = max($this->max_ratings, $count);
}
function get_graph($width = 300, $height = 100) {
$row_bars = array();
$row_labels = array();
for ($i = 1; $i <= 5; $i++) {
/* The weird comment inside the DIV is a fix for IE which renders
* 20px height minimum without the comment */
array_push($row_bars,
"\t\t<td width=\"20%\"><div class=\"rating_$i\" style=\"height: "
.$this->get_rating_height($i, $height)
."\"><!-- --></div></td>\n"
);
array_push($row_labels, "\t\t<td>{$this->ratings[$i]}</td>\n");
}
return "<table class=\"rating_graph\" width=\"$width\" height=\"$height\">\n".
"\t<tr class=\"bars\">\n".implode($row_bars)."\t</tr>\n".
"\t<tr class=\"labels\" height=\"15\">\n".implode($row_labels)."\t</tr>\n".
"</table>\n";
}
function get_average() {
if ($this->num_ratings) {
$rating_sum = 0;
foreach (array_keys($this->ratings) as $key) {
$rating_sum += $key * $this->ratings[$key];
}
return round($rating_sum / $this->num_ratings, 2);
}
else {
return 0;
}
}
private function get_rating_height($rating, $height) {
if ($this->max_ratings) {
return (int)($this->ratings[$rating] / $this->max_ratings * $height);
}
else {
return $height;
}
}
function get_num_ratings() {
return "{$this->num_ratings} rating".($this->num_ratings == 1 ? '' : 's');
}
function get_table() {
return "<table class=\"map_rating\">\n"
."\t<tr>\n"
."\t\t<td width=\"".MR_LEFTWIDTH."\"><span class=\"map_name\">{$this->name}</span><br/>{$this->get_average()} "
."({$this->get_num_ratings()})</td>\n"
."\t\t<td>{$this->get_graph(MR_RIGHTWIDTH, 30)}</td>\n"
."\t</tr>\n"
."</table>\n";
}
}
class MapRatings {
private $map_ratings;
private $maps;
private $db;
function __construct() {
$this->db = mysql_pconnect(MR_DBHOST, MR_DBUSER, MR_DBPASS);
if (!$this->db|| !mysql_select_db(MR_DBNAME)) {
throw new Exception('Could not establish connection to the database');
}
$this->populate_ratings();
}
private function populate_ratings() {
$sort = 'ORDER BY rating DESC';
$query = 'SELECT map, rating, COUNT(*) AS count FROM '.MR_TABLENAME.' GROUP BY map, rating';
$result = mysql_query($query);
$this->map_ratings = array();
while ($row = mysql_fetch_object($result)) {
if (!isset($this->map_ratings[$row->map])) {
$this->map_ratings[$row->map] = new MapRating($row->map);
}
$this->map_ratings[$row->map]->add_rating($row->rating, $row->count);
}
foreach (array_keys($this->map_ratings) as $key) {
if ($this->map_ratings[$key]->num_ratings < MR_THRESHOLD) {
unset($this->map_ratings[$key]);
}
}
$this->maps = array_keys($this->map_ratings);
}
function get_links($sort='name', $dir='no') {
$sort_types = array('rating' => 'Rating', 'name' => 'Map Name', 'ratings' => 'Number of Ratings');
$links = array();
foreach (array_keys($sort_types) as $sort_type) {
$link = '';
if ($sort_type == $sort) {
$link = "<strong>{$sort_types[$sort_type]}</strong>";
}
else {
$link = "<a href=\"{$_SERVER['PHP_SELF']}?sort=$sort_type\">{$sort_types[$sort_type]}</a>";
}
array_push($links, $link);
}
return '<strong>Order By: </strong>'.implode(' | ', $links);
}
function set_sort($sort='rating', $dir='no') {
$rating = array();
$ratings = array();
foreach (array_values($this->map_ratings) as $mr) {
array_push($rating, $mr->get_average());
array_push($ratings, $mr->num_ratings);
}
if ($sort == "name") {
sort($this->maps);
}
else if ($sort == "rating") {
array_multisort($rating, SORT_DESC, $this->maps);
}
else if ($sort == "ratings") {
array_multisort($ratings, SORT_DESC, $this->maps);
}
if ($dir == "yes") {
$this->maps = array_reverse($this->maps);
}
}
function get_ratings_table() {
ob_start();
echo "<table>\n";
$cell = 0;
echo "\t<tr>\n";
foreach ($this->maps as $map) {
$mr = $this->map_ratings[$map];
if (!$cell) {
echo "\t</tr>\n";
echo "\t<tr>\n";
}
echo "\t\t<td>".$mr->get_table()."</td>\n";
$cell = ($cell + 1) % MR_COLUMNS;
}
while ($cell) {
echo "\t\t<td> </td>\n";
$cell = ($cell + 1) % MR_COLUMNS;
}
echo "\t</tr>\n";
echo "</table>\n";
$table = ob_get_contents();
ob_end_clean();
return $table;
}
}
$mr = new MapRatings();
echo "<div class=\"title\"><span class=\"title\">Map Ratings</span><br/>";
echo "<span class=\"links\">".$mr->get_links($sort, $reverse)."</span></div>\n";
echo "<div class=\"ratings\">\n";
$mr->set_sort($sort, $reverse);
echo $mr->get_ratings_table();
echo "</div>\n";
?>
</body>
</html>
Notes about maprate.php- Use MR_THRESHOLD to specify a minimum number of ratings for a map to appear
- If there's a map with a long name, increase MR_LEFTWIDTH
- MR_COLUMNS sets the number of columns to organize the maps into
Version History- 2008-04-07 - v0.1
- 2008-04-07 - v0.2
- 2008-04-07 - v0.3
- Added AdminMenu support
- Renamed !rate and !rating commands to !maprate and !maprating, respectively.
- 2008-04-09 - v0.4
- Added AutoRate (via maprate_autorate_time) for automatically asking dead players to rate the map after a certain period of time
- Added ability to disallow re-rating a map
- Added admin command to delete ratings for the current map
- 2008-04-10 - v0.5
- Added cvar maprate_database
- Fixed minor bug involving recognizing cvar settings when the server is first launched
- 2008-04-10 - v0.6
- Added in-game rating viewing tools
- 2008-04-11 - v0.7
- 2008-05-17 - v0.8
- Added internationalization support
- Added maprate_autorate_delay cvar
- 2008-05-19
- Added German translations to maprate.phrases.txt. Thank you to st0rm_r1der for providing the German translations.
- 2008-05-22
- Added maprate.php, a PHP frontend for viewing map ratings
- 2008-05-24 - v0.9
- Fixed autorate bug involving players disconnecting immediately after being killed
- 2008-06-30 - v0.10
- Changed behavior of maprate_autorate_time to be based on when each player connected rather than when the map started
- Added maprate_dismiss
- Added "Dismiss" section to maprate.phrases.txt
- Added maprate_autoresults
Installation- Copy maprate.smx to addons/sourcemod/scripting/
- Copy maprate.phrases.txt to addons/sourcemod/translations/
Note: I whipped this up in about an hour without looking at existing, non-SourceMod map rating plugins, so I don't know if it behaves similarly to what one would expect from a plugin of this type. Also, this plugin was tested on a TF2 server. Please let me know if you experience problems with other mods.
In v0.10, maprate.phrases.txt was updated. You MUST update it or maprate_dismiss will not work.