diff --git a/Admin/Application.csproj b/Admin/Application.csproj
index bc9bc9ed2..9bcdf6b0b 100644
--- a/Admin/Application.csproj
+++ b/Admin/Application.csproj
@@ -165,6 +165,9 @@
PreserveNewest
+
+ PreserveNewest
+ PreserveNewest
@@ -363,7 +366,6 @@ copy /Y "$(ProjectDir)lib\Kayak.dll" "$(SolutionDir)BUILD\lib"
xcopy /Y /I /E "$(ProjectDir)webfront\*" "$(SolutionDir)BUILD\Webfront"
-
if $(ConfigurationName) == Release-Nightly powershell.exe -file "$(SolutionDir)DEPLOY\publish_nightly.ps1" 1.4
if $(ConfigurationName) == Release-Stable powershell.exe -file "$(SolutionDir)DEPLOY\publish_stable.ps1" 1.4
diff --git a/Admin/Server.cs b/Admin/Server.cs
index 94bf29560..f87809dac 100644
--- a/Admin/Server.cs
+++ b/Admin/Server.cs
@@ -362,7 +362,7 @@ namespace IW4MAdmin
if ((lastCount - playerCountStart).TotalMinutes >= SharedLibrary.Helpers.PlayerHistory.UpdateInterval)
{
- while (PlayerHistory.Count > ((60 / SharedLibrary.Helpers.PlayerHistory.UpdateInterval) * 12 )) // 12 times a hour for 12 hours
+ while (PlayerHistory.Count > ((60 / SharedLibrary.Helpers.PlayerHistory.UpdateInterval) * 12)) // 12 times a hour for 12 hours
PlayerHistory.Dequeue();
PlayerHistory.Enqueue(new SharedLibrary.Helpers.PlayerHistory(ClientNum));
playerCountStart = DateTime.Now;
@@ -593,8 +593,9 @@ namespace IW4MAdmin
else // Not a command
{
E.Data = E.Data.StripColors().CleanChars();
- if (E.Data.Length > 50)
- E.Data = E.Data.Substring(0, 50) + "...";
+ // this should not be done for all messages.
+ //if (E.Data.Length > 50)
+ // E.Data = E.Data.Substring(0, 50) + "...";
ChatHistory.Add(new Chat(E.Origin.Name, E.Data, DateTime.Now));
}
diff --git a/Admin/lib/SharedLibrary.dll b/Admin/lib/SharedLibrary.dll
index df9a7c688..55a33d81f 100644
Binary files a/Admin/lib/SharedLibrary.dll and b/Admin/lib/SharedLibrary.dll differ
diff --git a/Admin/webfront/chat.html b/Admin/webfront/chat.html
index 5f282702b..f7b1c03ab 100644
--- a/Admin/webfront/chat.html
+++ b/Admin/webfront/chat.html
@@ -1 +1,31 @@
-
\ No newline at end of file
+
+
+
+
\ No newline at end of file
diff --git a/Admin/webfront/players.html b/Admin/webfront/players.html
index 70eb83117..461cf0abf 100644
--- a/Admin/webfront/players.html
+++ b/Admin/webfront/players.html
@@ -1,134 +1,113 @@
-
-
+
+
-
Name
-
Aliases
-
IP
-
Level
-
Connections
-
V2
-
Last Seen
+
Name
+
Aliases
+
IP
+
Level
+
Connections
+
Chat
+
Last Seen
-
+
diff --git a/Admin/webfront/scripts/wordcloud2.js b/Admin/webfront/scripts/wordcloud2.js
new file mode 100644
index 000000000..35fd5d6e2
--- /dev/null
+++ b/Admin/webfront/scripts/wordcloud2.js
@@ -0,0 +1,2369 @@
+'use strict';
+
+
+
+// setImmediate
+
+if (!window.setImmediate) {
+
+ window.setImmediate = (function setupSetImmediate() {
+
+ return window.msSetImmediate ||
+
+ window.webkitSetImmediate ||
+
+ window.mozSetImmediate ||
+
+ window.oSetImmediate ||
+
+ (function setupSetZeroTimeout() {
+
+ if (!window.postMessage || !window.addEventListener) {
+
+ return null;
+
+ }
+
+
+
+ var callbacks = [undefined];
+
+ var message = 'zero-timeout-message';
+
+
+
+ // Like setTimeout, but only takes a function argument. There's
+
+ // no time argument (always zero) and no arguments (you have to
+
+ // use a closure).
+
+ var setZeroTimeout = function setZeroTimeout(callback) {
+
+ var id = callbacks.length;
+
+ callbacks.push(callback);
+
+ window.postMessage(message + id.toString(36), '*');
+
+
+
+ return id;
+
+ };
+
+
+
+ window.addEventListener('message', function setZeroTimeoutMessage(evt) {
+
+ // Skipping checking event source, retarded IE confused this window
+
+ // object with another in the presence of iframe
+
+ if (typeof evt.data !== 'string' ||
+
+ evt.data.substr(0, message.length) !== message/* ||
+
+ evt.source !== window */) {
+
+ return;
+
+ }
+
+
+
+ evt.stopImmediatePropagation();
+
+
+
+ var id = parseInt(evt.data.substr(message.length), 36);
+
+ if (!callbacks[id]) {
+
+ return;
+
+ }
+
+
+
+ callbacks[id]();
+
+ callbacks[id] = undefined;
+
+ }, true);
+
+
+
+ /* specify clearImmediate() here since we need the scope */
+
+ window.clearImmediate = function clearZeroTimeout(id) {
+
+ if (!callbacks[id]) {
+
+ return;
+
+ }
+
+
+
+ callbacks[id] = undefined;
+
+ };
+
+
+
+ return setZeroTimeout;
+
+ })() ||
+
+ // fallback
+
+ function setImmediateFallback(fn) {
+
+ window.setTimeout(fn, 0);
+
+ };
+
+ })();
+
+}
+
+
+
+if (!window.clearImmediate) {
+
+ window.clearImmediate = (function setupClearImmediate() {
+
+ return window.msClearImmediate ||
+
+ window.webkitClearImmediate ||
+
+ window.mozClearImmediate ||
+
+ window.oClearImmediate ||
+
+ // "clearZeroTimeout" is implement on the previous block ||
+
+ // fallback
+
+ function clearImmediateFallback(timer) {
+
+ window.clearTimeout(timer);
+
+ };
+
+ })();
+
+}
+
+
+
+(function(global) {
+
+
+
+ // Check if WordCloud can run on this browser
+
+ var isSupported = (function isSupported() {
+
+ var canvas = document.createElement('canvas');
+
+ if (!canvas || !canvas.getContext) {
+
+ return false;
+
+ }
+
+
+
+ var ctx = canvas.getContext('2d');
+
+ if (!ctx) {
+
+ return false;
+
+ }
+
+ if (!ctx.getImageData) {
+
+ return false;
+
+ }
+
+ if (!ctx.fillText) {
+
+ return false;
+
+ }
+
+
+
+ if (!Array.prototype.some) {
+
+ return false;
+
+ }
+
+ if (!Array.prototype.push) {
+
+ return false;
+
+ }
+
+
+
+ return true;
+
+ }());
+
+
+
+ // Find out if the browser impose minium font size by
+
+ // drawing small texts on a canvas and measure it's width.
+
+ var minFontSize = (function getMinFontSize() {
+
+ if (!isSupported) {
+
+ return;
+
+ }
+
+
+
+ var ctx = document.createElement('canvas').getContext('2d');
+
+
+
+ // start from 20
+
+ var size = 20;
+
+
+
+ // two sizes to measure
+
+ var hanWidth, mWidth;
+
+
+
+ while (size) {
+
+ ctx.font = size.toString(10) + 'px sans-serif';
+
+ if ((ctx.measureText('\uFF37').width === hanWidth) &&
+
+ (ctx.measureText('m').width) === mWidth) {
+
+ return (size + 1);
+
+ }
+
+
+
+ hanWidth = ctx.measureText('\uFF37').width;
+
+ mWidth = ctx.measureText('m').width;
+
+
+
+ size--;
+
+ }
+
+
+
+ return 0;
+
+ })();
+
+
+
+ // Based on http://jsfromhell.com/array/shuffle
+
+ var shuffleArray = function shuffleArray(arr) {
+
+ for (var j, x, i = arr.length; i;
+
+ j = Math.floor(Math.random() * i),
+
+ x = arr[--i], arr[i] = arr[j],
+
+ arr[j] = x) {}
+
+ return arr;
+
+ };
+
+
+
+ var WordCloud = function WordCloud(elements, options) {
+
+ if (!isSupported) {
+
+ return;
+
+ }
+
+
+
+ if (!Array.isArray(elements)) {
+
+ elements = [elements];
+
+ }
+
+
+
+ elements.forEach(function(el, i) {
+
+ if (typeof el === 'string') {
+
+ elements[i] = document.getElementById(el);
+
+ if (!elements[i]) {
+
+ throw 'The element id specified is not found.';
+
+ }
+
+ } else if (!el.tagName && !el.appendChild) {
+
+ throw 'You must pass valid HTML elements, or ID of the element.';
+
+ }
+
+ });
+
+
+
+ /* Default values to be overwritten by options object */
+
+ var settings = {
+
+ list: [],
+
+ fontFamily: '"Trebuchet MS", "Heiti TC", "?????", ' +
+
+ '"Arial Unicode MS", "Droid Fallback Sans", sans-serif',
+
+ fontWeight: 'normal',
+
+ color: 'random-dark',
+
+ minSize: 0, // 0 to disable
+
+ weightFactor: 1,
+
+ clearCanvas: true,
+
+ backgroundColor: '#fff', // opaque white = rgba(255, 255, 255, 1)
+
+
+
+ gridSize: 8,
+
+ drawOutOfBound: false,
+
+ origin: null,
+
+
+
+ drawMask: false,
+
+ maskColor: 'rgba(255,0,0,0.3)',
+
+ maskGapWidth: 0.3,
+
+
+
+ wait: 0,
+
+ abortThreshold: 0, // disabled
+
+ abort: function noop() {},
+
+
+
+ minRotation: - Math.PI / 2,
+
+ maxRotation: Math.PI / 2,
+
+ rotationSteps: 0,
+
+
+
+ shuffle: true,
+
+ rotateRatio: 0.1,
+
+
+
+ shape: 'circle',
+
+ ellipticity: 0.65,
+
+
+
+ classes: null,
+
+
+
+ hover: null,
+
+ click: null
+
+ };
+
+
+
+ if (options) {
+
+ for (var key in options) {
+
+ if (key in settings) {
+
+ settings[key] = options[key];
+
+ }
+
+ }
+
+ }
+
+
+
+ /* Convert weightFactor into a function */
+
+ if (typeof settings.weightFactor !== 'function') {
+
+ var factor = settings.weightFactor;
+
+ settings.weightFactor = function weightFactor(pt) {
+
+ return pt * factor; //in px
+
+ };
+
+ }
+
+
+
+ /* Convert shape into a function */
+
+ if (typeof settings.shape !== 'function') {
+
+ switch (settings.shape) {
+
+ case 'circle':
+
+ /* falls through */
+
+ default:
+
+ // 'circle' is the default and a shortcut in the code loop.
+
+ settings.shape = 'circle';
+
+ break;
+
+
+
+ case 'cardioid':
+
+ settings.shape = function shapeCardioid(theta) {
+
+ return 1 - Math.sin(theta);
+
+ };
+
+ break;
+
+
+
+ /*
+
+
+
+ To work out an X-gon, one has to calculate "m",
+
+ where 1/(cos(2*PI/X)+m*sin(2*PI/X)) = 1/(cos(0)+m*sin(0))
+
+ http://www.wolframalpha.com/input/?i=1%2F%28cos%282*PI%2FX%29%2Bm*sin%28
+
+ 2*PI%2FX%29%29+%3D+1%2F%28cos%280%29%2Bm*sin%280%29%29
+
+
+
+ Copy the solution into polar equation r = 1/(cos(t') + m*sin(t'))
+
+ where t' equals to mod(t, 2PI/X);
+
+
+
+ */
+
+
+
+ case 'diamond':
+
+ // http://www.wolframalpha.com/input/?i=plot+r+%3D+1%2F%28cos%28mod+
+
+ // %28t%2C+PI%2F2%29%29%2Bsin%28mod+%28t%2C+PI%2F2%29%29%29%2C+t+%3D
+
+ // +0+..+2*PI
+
+ settings.shape = function shapeSquare(theta) {
+
+ var thetaPrime = theta % (2 * Math.PI / 4);
+
+ return 1 / (Math.cos(thetaPrime) + Math.sin(thetaPrime));
+
+ };
+
+ break;
+
+
+
+ case 'square':
+
+ // http://www.wolframalpha.com/input/?i=plot+r+%3D+min(1%2Fabs(cos(t
+
+ // )),1%2Fabs(sin(t)))),+t+%3D+0+..+2*PI
+
+ settings.shape = function shapeSquare(theta) {
+
+ return Math.min(
+
+ 1 / Math.abs(Math.cos(theta)),
+
+ 1 / Math.abs(Math.sin(theta))
+
+ );
+
+ };
+
+ break;
+
+
+
+ case 'triangle-forward':
+
+ // http://www.wolframalpha.com/input/?i=plot+r+%3D+1%2F%28cos%28mod+
+
+ // %28t%2C+2*PI%2F3%29%29%2Bsqrt%283%29sin%28mod+%28t%2C+2*PI%2F3%29
+
+ // %29%29%2C+t+%3D+0+..+2*PI
+
+ settings.shape = function shapeTriangle(theta) {
+
+ var thetaPrime = theta % (2 * Math.PI / 3);
+
+ return 1 / (Math.cos(thetaPrime) +
+
+ Math.sqrt(3) * Math.sin(thetaPrime));
+
+ };
+
+ break;
+
+
+
+ case 'triangle':
+
+ case 'triangle-upright':
+
+ settings.shape = function shapeTriangle(theta) {
+
+ var thetaPrime = (theta + Math.PI * 3 / 2) % (2 * Math.PI / 3);
+
+ return 1 / (Math.cos(thetaPrime) +
+
+ Math.sqrt(3) * Math.sin(thetaPrime));
+
+ };
+
+ break;
+
+
+
+ case 'pentagon':
+
+ settings.shape = function shapePentagon(theta) {
+
+ var thetaPrime = (theta + 0.955) % (2 * Math.PI / 5);
+
+ return 1 / (Math.cos(thetaPrime) +
+
+ 0.726543 * Math.sin(thetaPrime));
+
+ };
+
+ break;
+
+
+
+ case 'star':
+
+ settings.shape = function shapeStar(theta) {
+
+ var thetaPrime = (theta + 0.955) % (2 * Math.PI / 10);
+
+ if ((theta + 0.955) % (2 * Math.PI / 5) - (2 * Math.PI / 10) >= 0) {
+
+ return 1 / (Math.cos((2 * Math.PI / 10) - thetaPrime) +
+
+ 3.07768 * Math.sin((2 * Math.PI / 10) - thetaPrime));
+
+ } else {
+
+ return 1 / (Math.cos(thetaPrime) +
+
+ 3.07768 * Math.sin(thetaPrime));
+
+ }
+
+ };
+
+ break;
+
+ }
+
+ }
+
+
+
+ /* Make sure gridSize is a whole number and is not smaller than 4px */
+
+ settings.gridSize = Math.max(Math.floor(settings.gridSize), 4);
+
+
+
+ /* shorthand */
+
+ var g = settings.gridSize;
+
+ var maskRectWidth = g - settings.maskGapWidth;
+
+
+
+ /* normalize rotation settings */
+
+ var rotationRange = Math.abs(settings.maxRotation - settings.minRotation);
+
+ var rotationSteps = Math.abs(Math.floor(settings.rotationSteps));
+
+ var minRotation = Math.min(settings.maxRotation, settings.minRotation);
+
+
+
+ /* information/object available to all functions, set when start() */
+
+ var grid, // 2d array containing filling information
+
+ ngx, ngy, // width and height of the grid
+
+ center, // position of the center of the cloud
+
+ maxRadius;
+
+
+
+ /* timestamp for measuring each putWord() action */
+
+ var escapeTime;
+
+
+
+ /* function for getting the color of the text */
+
+ var getTextColor;
+
+ function random_hsl_color(min, max) {
+
+ return 'hsl(' +
+
+ (Math.random() * 360).toFixed() + ',' +
+
+ (Math.random() * 30 + 70).toFixed() + '%,' +
+
+ (Math.random() * (max - min) + min).toFixed() + '%)';
+
+ }
+
+ switch (settings.color) {
+
+ case 'random-dark':
+
+ getTextColor = function getRandomDarkColor() {
+
+ return random_hsl_color(10, 50);
+
+ };
+
+ break;
+
+
+
+ case 'random-light':
+
+ getTextColor = function getRandomLightColor() {
+
+ return random_hsl_color(50, 90);
+
+ };
+
+ break;
+
+
+
+ default:
+
+ if (typeof settings.color === 'function') {
+
+ getTextColor = settings.color;
+
+ }
+
+ break;
+
+ }
+
+
+
+ /* function for getting the classes of the text */
+
+ var getTextClasses = null;
+
+ if (typeof settings.classes === 'function') {
+
+ getTextClasses = settings.classes;
+
+ }
+
+
+
+ /* Interactive */
+
+ var interactive = false;
+
+ var infoGrid = [];
+
+ var hovered;
+
+
+
+ var getInfoGridFromMouseTouchEvent =
+
+ function getInfoGridFromMouseTouchEvent(evt) {
+
+ var canvas = evt.currentTarget;
+
+ var rect = canvas.getBoundingClientRect();
+
+ var clientX;
+
+ var clientY;
+
+ /** Detect if touches are available */
+
+ if (evt.touches) {
+
+ clientX = evt.touches[0].clientX;
+
+ clientY = evt.touches[0].clientY;
+
+ } else {
+
+ clientX = evt.clientX;
+
+ clientY = evt.clientY;
+
+ }
+
+ var eventX = clientX - rect.left;
+
+ var eventY = clientY - rect.top;
+
+
+
+ var x = Math.floor(eventX * ((canvas.width / rect.width) || 1) / g);
+
+ var y = Math.floor(eventY * ((canvas.height / rect.height) || 1) / g);
+
+
+
+ return infoGrid[x][y];
+
+ };
+
+
+
+ var wordcloudhover = function wordcloudhover(evt) {
+
+ var info = getInfoGridFromMouseTouchEvent(evt);
+
+
+
+ if (hovered === info) {
+
+ return;
+
+ }
+
+
+
+ hovered = info;
+
+ if (!info) {
+
+ settings.hover(undefined, undefined, evt);
+
+
+
+ return;
+
+ }
+
+
+
+ settings.hover(info.item, info.dimension, evt);
+
+
+
+ };
+
+
+
+ var wordcloudclick = function wordcloudclick(evt) {
+
+ var info = getInfoGridFromMouseTouchEvent(evt);
+
+ if (!info) {
+
+ return;
+
+ }
+
+
+
+ settings.click(info.item, info.dimension, evt);
+
+ evt.preventDefault();
+
+ };
+
+
+
+ /* Get points on the grid for a given radius away from the center */
+
+ var pointsAtRadius = [];
+
+ var getPointsAtRadius = function getPointsAtRadius(radius) {
+
+ if (pointsAtRadius[radius]) {
+
+ return pointsAtRadius[radius];
+
+ }
+
+
+
+ // Look for these number of points on each radius
+
+ var T = radius * 8;
+
+
+
+ // Getting all the points at this radius
+
+ var t = T;
+
+ var points = [];
+
+
+
+ if (radius === 0) {
+
+ points.push([center[0], center[1], 0]);
+
+ }
+
+
+
+ while (t--) {
+
+ // distort the radius to put the cloud in shape
+
+ var rx = 1;
+
+ if (settings.shape !== 'circle') {
+
+ rx = settings.shape(t / T * 2 * Math.PI); // 0 to 1
+
+ }
+
+
+
+ // Push [x, y, t]; t is used solely for getTextColor()
+
+ points.push([
+
+ center[0] + radius * rx * Math.cos(-t / T * 2 * Math.PI),
+
+ center[1] + radius * rx * Math.sin(-t / T * 2 * Math.PI) *
+
+ settings.ellipticity,
+
+ t / T * 2 * Math.PI]);
+
+ }
+
+
+
+ pointsAtRadius[radius] = points;
+
+ return points;
+
+ };
+
+
+
+ /* Return true if we had spent too much time */
+
+ var exceedTime = function exceedTime() {
+
+ return ((settings.abortThreshold > 0) &&
+
+ ((new Date()).getTime() - escapeTime > settings.abortThreshold));
+
+ };
+
+
+
+ /* Get the deg of rotation according to settings, and luck. */
+
+ var getRotateDeg = function getRotateDeg() {
+
+ if (settings.rotateRatio === 0) {
+
+ return 0;
+
+ }
+
+
+
+ if (Math.random() > settings.rotateRatio) {
+
+ return 0;
+
+ }
+
+
+
+ if (rotationRange === 0) {
+
+ return minRotation;
+
+ }
+
+
+
+ if (rotationSteps > 0) {
+
+ // Min rotation + zero or more steps * span of one step
+
+ return minRotation +
+
+ Math.floor(Math.random() * rotationSteps) *
+
+ rotationRange / (rotationSteps - 1);
+
+ }
+
+ else {
+
+ return minRotation + Math.random() * rotationRange;
+
+ }
+
+ };
+
+
+
+ var getTextInfo = function getTextInfo(word, weight, rotateDeg) {
+
+ // calculate the acutal font size
+
+ // fontSize === 0 means weightFactor function wants the text skipped,
+
+ // and size < minSize means we cannot draw the text.
+
+ var debug = false;
+
+ var fontSize = settings.weightFactor(weight);
+
+ if (fontSize <= settings.minSize) {
+
+ return false;
+
+ }
+
+
+
+ // Scale factor here is to make sure fillText is not limited by
+
+ // the minium font size set by browser.
+
+ // It will always be 1 or 2n.
+
+ var mu = 1;
+
+ if (fontSize < minFontSize) {
+
+ mu = (function calculateScaleFactor() {
+
+ var mu = 2;
+
+ while (mu * fontSize < minFontSize) {
+
+ mu += 2;
+
+ }
+
+ return mu;
+
+ })();
+
+ }
+
+
+
+ var fcanvas = document.createElement('canvas');
+
+ var fctx = fcanvas.getContext('2d', { willReadFrequently: true });
+
+
+
+ fctx.font = settings.fontWeight + ' ' +
+
+ (fontSize * mu).toString(10) + 'px ' + settings.fontFamily;
+
+
+
+ // Estimate the dimension of the text with measureText().
+
+ var fw = fctx.measureText(word).width / mu;
+
+ var fh = Math.max(fontSize * mu,
+
+ fctx.measureText('m').width,
+
+ fctx.measureText('\uFF37').width) / mu;
+
+
+
+ // Create a boundary box that is larger than our estimates,
+
+ // so text don't get cut of (it sill might)
+
+ var boxWidth = fw + fh * 2;
+
+ var boxHeight = fh * 3;
+
+ var fgw = Math.ceil(boxWidth / g);
+
+ var fgh = Math.ceil(boxHeight / g);
+
+ boxWidth = fgw * g;
+
+ boxHeight = fgh * g;
+
+
+
+ // Calculate the proper offsets to make the text centered at
+
+ // the preferred position.
+
+
+
+ // This is simply half of the width.
+
+ var fillTextOffsetX = - fw / 2;
+
+ // Instead of moving the box to the exact middle of the preferred
+
+ // position, for Y-offset we move 0.4 instead, so Latin alphabets look
+
+ // vertical centered.
+
+ var fillTextOffsetY = - fh * 0.4;
+
+
+
+ // Calculate the actual dimension of the canvas, considering the rotation.
+
+ var cgh = Math.ceil((boxWidth * Math.abs(Math.sin(rotateDeg)) +
+
+ boxHeight * Math.abs(Math.cos(rotateDeg))) / g);
+
+ var cgw = Math.ceil((boxWidth * Math.abs(Math.cos(rotateDeg)) +
+
+ boxHeight * Math.abs(Math.sin(rotateDeg))) / g);
+
+ var width = cgw * g;
+
+ var height = cgh * g;
+
+
+
+ fcanvas.setAttribute('width', width);
+
+ fcanvas.setAttribute('height', height);
+
+
+
+ if (debug) {
+
+ // Attach fcanvas to the DOM
+
+ document.body.appendChild(fcanvas);
+
+ // Save it's state so that we could restore and draw the grid correctly.
+
+ fctx.save();
+
+ }
+
+
+
+ // Scale the canvas with |mu|.
+
+ fctx.scale(1 / mu, 1 / mu);
+
+ fctx.translate(width * mu / 2, height * mu / 2);
+
+ fctx.rotate(- rotateDeg);
+
+
+
+ // Once the width/height is set, ctx info will be reset.
+
+ // Set it again here.
+
+ fctx.font = settings.fontWeight + ' ' +
+
+ (fontSize * mu).toString(10) + 'px ' + settings.fontFamily;
+
+
+
+ // Fill the text into the fcanvas.
+
+ // XXX: We cannot because textBaseline = 'top' here because
+
+ // Firefox and Chrome uses different default line-height for canvas.
+
+ // Please read https://bugzil.la/737852#c6.
+
+ // Here, we use textBaseline = 'middle' and draw the text at exactly
+
+ // 0.5 * fontSize lower.
+
+ fctx.fillStyle = '#000';
+
+ fctx.textBaseline = 'middle';
+
+ fctx.fillText(word, fillTextOffsetX * mu,
+
+ (fillTextOffsetY + fontSize * 0.5) * mu);
+
+
+
+ // Get the pixels of the text
+
+ var imageData = fctx.getImageData(0, 0, width, height).data;
+
+
+
+ if (exceedTime()) {
+
+ return false;
+
+ }
+
+
+
+ if (debug) {
+
+ // Draw the box of the original estimation
+
+ fctx.strokeRect(fillTextOffsetX * mu,
+
+ fillTextOffsetY, fw * mu, fh * mu);
+
+ fctx.restore();
+
+ }
+
+
+
+ // Read the pixels and save the information to the occupied array
+
+ var occupied = [];
+
+ var gx = cgw, gy, x, y;
+
+ var bounds = [cgh / 2, cgw / 2, cgh / 2, cgw / 2];
+
+ while (gx--) {
+
+ gy = cgh;
+
+ while (gy--) {
+
+ y = g;
+
+ singleGridLoop: {
+
+ while (y--) {
+
+ x = g;
+
+ while (x--) {
+
+ if (imageData[((gy * g + y) * width +
+
+ (gx * g + x)) * 4 + 3]) {
+
+ occupied.push([gx, gy]);
+
+
+
+ if (gx < bounds[3]) {
+
+ bounds[3] = gx;
+
+ }
+
+ if (gx > bounds[1]) {
+
+ bounds[1] = gx;
+
+ }
+
+ if (gy < bounds[0]) {
+
+ bounds[0] = gy;
+
+ }
+
+ if (gy > bounds[2]) {
+
+ bounds[2] = gy;
+
+ }
+
+
+
+ if (debug) {
+
+ fctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
+
+ fctx.fillRect(gx * g, gy * g, g - 0.5, g - 0.5);
+
+ }
+
+ break singleGridLoop;
+
+ }
+
+ }
+
+ }
+
+ if (debug) {
+
+ fctx.fillStyle = 'rgba(0, 0, 255, 0.5)';
+
+ fctx.fillRect(gx * g, gy * g, g - 0.5, g - 0.5);
+
+ }
+
+ }
+
+ }
+
+ }
+
+
+
+ if (debug) {
+
+ fctx.fillStyle = 'rgba(0, 255, 0, 0.5)';
+
+ fctx.fillRect(bounds[3] * g,
+
+ bounds[0] * g,
+
+ (bounds[1] - bounds[3] + 1) * g,
+
+ (bounds[2] - bounds[0] + 1) * g);
+
+ }
+
+
+
+ // Return information needed to create the text on the real canvas
+
+ return {
+
+ mu: mu,
+
+ occupied: occupied,
+
+ bounds: bounds,
+
+ gw: cgw,
+
+ gh: cgh,
+
+ fillTextOffsetX: fillTextOffsetX,
+
+ fillTextOffsetY: fillTextOffsetY,
+
+ fillTextWidth: fw,
+
+ fillTextHeight: fh,
+
+ fontSize: fontSize
+
+ };
+
+ };
+
+
+
+ /* Determine if there is room available in the given dimension */
+
+ var canFitText = function canFitText(gx, gy, gw, gh, occupied) {
+
+ // Go through the occupied points,
+
+ // return false if the space is not available.
+
+ var i = occupied.length;
+
+ while (i--) {
+
+ var px = gx + occupied[i][0];
+
+ var py = gy + occupied[i][1];
+
+
+
+ if (px >= ngx || py >= ngy || px < 0 || py < 0) {
+
+ if (!settings.drawOutOfBound) {
+
+ return false;
+
+ }
+
+ continue;
+
+ }
+
+
+
+ if (!grid[px][py]) {
+
+ return false;
+
+ }
+
+ }
+
+ return true;
+
+ };
+
+
+
+ /* Actually draw the text on the grid */
+
+ var drawText = function drawText(gx, gy, info, word, weight,
+
+ distance, theta, rotateDeg, attributes) {
+
+
+
+ var fontSize = info.fontSize;
+
+ var color;
+
+ if (getTextColor) {
+
+ color = getTextColor(word, weight, fontSize, distance, theta);
+
+ } else {
+
+ color = settings.color;
+
+ }
+
+
+
+ var classes;
+
+ if (getTextClasses) {
+
+ classes = getTextClasses(word, weight, fontSize, distance, theta);
+
+ } else {
+
+ classes = settings.classes;
+
+ }
+
+
+
+ var dimension;
+
+ var bounds = info.bounds;
+
+ dimension = {
+
+ x: (gx + bounds[3]) * g,
+
+ y: (gy + bounds[0]) * g,
+
+ w: (bounds[1] - bounds[3] + 1) * g,
+
+ h: (bounds[2] - bounds[0] + 1) * g
+
+ };
+
+
+
+ elements.forEach(function(el) {
+
+ if (el.getContext) {
+
+ var ctx = el.getContext('2d');
+
+ var mu = info.mu;
+
+
+
+ // Save the current state before messing it
+
+ ctx.save();
+
+ ctx.scale(1 / mu, 1 / mu);
+
+
+
+ ctx.font = settings.fontWeight + ' ' +
+
+ (fontSize * mu).toString(10) + 'px ' + settings.fontFamily;
+
+ ctx.fillStyle = color;
+
+
+
+ // Translate the canvas position to the origin coordinate of where
+
+ // the text should be put.
+
+ ctx.translate((gx + info.gw / 2) * g * mu,
+
+ (gy + info.gh / 2) * g * mu);
+
+
+
+ if (rotateDeg !== 0) {
+
+ ctx.rotate(- rotateDeg);
+
+ }
+
+
+
+ // Finally, fill the text.
+
+
+
+ // XXX: We cannot because textBaseline = 'top' here because
+
+ // Firefox and Chrome uses different default line-height for canvas.
+
+ // Please read https://bugzil.la/737852#c6.
+
+ // Here, we use textBaseline = 'middle' and draw the text at exactly
+
+ // 0.5 * fontSize lower.
+
+ ctx.textBaseline = 'middle';
+
+ ctx.fillText(word, info.fillTextOffsetX * mu,
+
+ (info.fillTextOffsetY + fontSize * 0.5) * mu);
+
+
+
+ // The below box is always matches how s are positioned
+
+ /* ctx.strokeRect(info.fillTextOffsetX, info.fillTextOffsetY,
+
+ info.fillTextWidth, info.fillTextHeight); */
+
+
+
+ // Restore the state.
+
+ ctx.restore();
+
+ } else {
+
+ // drawText on DIV element
+
+ var span = document.createElement('span');
+
+ var transformRule = '';
+
+ transformRule = 'rotate(' + (- rotateDeg / Math.PI * 180) + 'deg) ';
+
+ if (info.mu !== 1) {
+
+ transformRule +=
+
+ 'translateX(-' + (info.fillTextWidth / 4) + 'px) ' +
+
+ 'scale(' + (1 / info.mu) + ')';
+
+ }
+
+ var styleRules = {
+
+ 'position': 'absolute',
+
+ 'display': 'block',
+
+ 'font': settings.fontWeight + ' ' +
+
+ (fontSize * info.mu) + 'px ' + settings.fontFamily,
+
+ 'left': ((gx + info.gw / 2) * g + info.fillTextOffsetX) + 'px',
+
+ 'top': ((gy + info.gh / 2) * g + info.fillTextOffsetY) + 'px',
+
+ 'width': info.fillTextWidth + 'px',
+
+ 'height': info.fillTextHeight + 'px',
+
+ 'lineHeight': fontSize + 'px',
+
+ 'whiteSpace': 'nowrap',
+
+ 'transform': transformRule,
+
+ 'webkitTransform': transformRule,
+
+ 'msTransform': transformRule,
+
+ 'transformOrigin': '50% 40%',
+
+ 'webkitTransformOrigin': '50% 40%',
+
+ 'msTransformOrigin': '50% 40%'
+
+ };
+
+ if (color) {
+
+ styleRules.color = color;
+
+ }
+
+ span.textContent = word;
+
+ for (var cssProp in styleRules) {
+
+ span.style[cssProp] = styleRules[cssProp];
+
+ }
+
+ if (attributes) {
+
+ for (var attribute in attributes) {
+
+ span.setAttribute(attribute, attributes[attribute]);
+
+ }
+
+ }
+
+ if (classes) {
+
+ span.className += classes;
+
+ }
+
+ el.appendChild(span);
+
+ }
+
+ });
+
+ };
+
+
+
+ /* Help function to updateGrid */
+
+ var fillGridAt = function fillGridAt(x, y, drawMask, dimension, item) {
+
+ if (x >= ngx || y >= ngy || x < 0 || y < 0) {
+
+ return;
+
+ }
+
+
+
+ grid[x][y] = false;
+
+
+
+ if (drawMask) {
+
+ var ctx = elements[0].getContext('2d');
+
+ ctx.fillRect(x * g, y * g, maskRectWidth, maskRectWidth);
+
+ }
+
+
+
+ if (interactive) {
+
+ infoGrid[x][y] = { item: item, dimension: dimension };
+
+ }
+
+ };
+
+
+
+ /* Update the filling information of the given space with occupied points.
+
+ Draw the mask on the canvas if necessary. */
+
+ var updateGrid = function updateGrid(gx, gy, gw, gh, info, item) {
+
+ var occupied = info.occupied;
+
+ var drawMask = settings.drawMask;
+
+ var ctx;
+
+ if (drawMask) {
+
+ ctx = elements[0].getContext('2d');
+
+ ctx.save();
+
+ ctx.fillStyle = settings.maskColor;
+
+ }
+
+
+
+ var dimension;
+
+ if (interactive) {
+
+ var bounds = info.bounds;
+
+ dimension = {
+
+ x: (gx + bounds[3]) * g,
+
+ y: (gy + bounds[0]) * g,
+
+ w: (bounds[1] - bounds[3] + 1) * g,
+
+ h: (bounds[2] - bounds[0] + 1) * g
+
+ };
+
+ }
+
+
+
+ var i = occupied.length;
+
+ while (i--) {
+
+ var px = gx + occupied[i][0];
+
+ var py = gy + occupied[i][1];
+
+
+
+ if (px >= ngx || py >= ngy || px < 0 || py < 0) {
+
+ continue;
+
+ }
+
+
+
+ fillGridAt(px, py, drawMask, dimension, item);
+
+ }
+
+
+
+ if (drawMask) {
+
+ ctx.restore();
+
+ }
+
+ };
+
+
+
+ /* putWord() processes each item on the list,
+
+ calculate it's size and determine it's position, and actually
+
+ put it on the canvas. */
+
+ var putWord = function putWord(item) {
+
+ var word, weight, attributes;
+
+ if (Array.isArray(item)) {
+
+ word = item[0];
+
+ weight = item[1];
+
+ } else {
+
+ word = item.word;
+
+ weight = item.weight;
+
+ attributes = item.attributes;
+
+ }
+
+ var rotateDeg = getRotateDeg();
+
+
+
+ // get info needed to put the text onto the canvas
+
+ var info = getTextInfo(word, weight, rotateDeg);
+
+
+
+ // not getting the info means we shouldn't be drawing this one.
+
+ if (!info) {
+
+ return false;
+
+ }
+
+
+
+ if (exceedTime()) {
+
+ return false;
+
+ }
+
+
+
+ // If drawOutOfBound is set to false,
+
+ // skip the loop if we have already know the bounding box of
+
+ // word is larger than the canvas.
+
+ if (!settings.drawOutOfBound) {
+
+ var bounds = info.bounds;
+
+ if ((bounds[1] - bounds[3] + 1) > ngx ||
+
+ (bounds[2] - bounds[0] + 1) > ngy) {
+
+ return false;
+
+ }
+
+ }
+
+
+
+ // Determine the position to put the text by
+
+ // start looking for the nearest points
+
+ var r = maxRadius + 1;
+
+
+
+ var tryToPutWordAtPoint = function(gxy) {
+
+ var gx = Math.floor(gxy[0] - info.gw / 2);
+
+ var gy = Math.floor(gxy[1] - info.gh / 2);
+
+ var gw = info.gw;
+
+ var gh = info.gh;
+
+
+
+ // If we cannot fit the text at this position, return false
+
+ // and go to the next position.
+
+ if (!canFitText(gx, gy, gw, gh, info.occupied)) {
+
+ return false;
+
+ }
+
+
+
+ // Actually put the text on the canvas
+
+ drawText(gx, gy, info, word, weight,
+
+ (maxRadius - r), gxy[2], rotateDeg, attributes);
+
+
+
+ // Mark the spaces on the grid as filled
+
+ updateGrid(gx, gy, gw, gh, info, item);
+
+
+
+ // Return true so some() will stop and also return true.
+
+ return true;
+
+ };
+
+
+
+ while (r--) {
+
+ var points = getPointsAtRadius(maxRadius - r);
+
+
+
+ if (settings.shuffle) {
+
+ points = [].concat(points);
+
+ shuffleArray(points);
+
+ }
+
+
+
+ // Try to fit the words by looking at each point.
+
+ // array.some() will stop and return true
+
+ // when putWordAtPoint() returns true.
+
+ // If all the points returns false, array.some() returns false.
+
+ var drawn = points.some(tryToPutWordAtPoint);
+
+
+
+ if (drawn) {
+
+ // leave putWord() and return true
+
+ return true;
+
+ }
+
+ }
+
+ // we tried all distances but text won't fit, return false
+
+ return false;
+
+ };
+
+
+
+ /* Send DOM event to all elements. Will stop sending event and return
+
+ if the previous one is canceled (for cancelable events). */
+
+ var sendEvent = function sendEvent(type, cancelable, detail) {
+
+ if (cancelable) {
+
+ return !elements.some(function(el) {
+
+ var evt = document.createEvent('CustomEvent');
+
+ evt.initCustomEvent(type, true, cancelable, detail || {});
+
+ return !el.dispatchEvent(evt);
+
+ }, this);
+
+ } else {
+
+ elements.forEach(function(el) {
+
+ var evt = document.createEvent('CustomEvent');
+
+ evt.initCustomEvent(type, true, cancelable, detail || {});
+
+ el.dispatchEvent(evt);
+
+ }, this);
+
+ }
+
+ };
+
+
+
+ /* Start drawing on a canvas */
+
+ var start = function start() {
+
+ // For dimensions, clearCanvas etc.,
+
+ // we only care about the first element.
+
+ var canvas = elements[0];
+
+
+
+ if (canvas.getContext) {
+
+ ngx = Math.ceil(canvas.width / g);
+
+ ngy = Math.ceil(canvas.height / g);
+
+ } else {
+
+ var rect = canvas.getBoundingClientRect();
+
+ ngx = Math.ceil(rect.width / g);
+
+ ngy = Math.ceil(rect.height / g);
+
+ }
+
+
+
+ // Sending a wordcloudstart event which cause the previous loop to stop.
+
+ // Do nothing if the event is canceled.
+
+ if (!sendEvent('wordcloudstart', true)) {
+
+ return;
+
+ }
+
+
+
+ // Determine the center of the word cloud
+
+ center = (settings.origin) ?
+
+ [settings.origin[0]/g, settings.origin[1]/g] :
+
+ [ngx / 2, ngy / 2];
+
+
+
+ // Maxium radius to look for space
+
+ maxRadius = Math.floor(Math.sqrt(ngx * ngx + ngy * ngy));
+
+
+
+ /* Clear the canvas only if the clearCanvas is set,
+
+ if not, update the grid to the current canvas state */
+
+ grid = [];
+
+
+
+ var gx, gy, i;
+
+ if (!canvas.getContext || settings.clearCanvas) {
+
+ elements.forEach(function(el) {
+
+ if (el.getContext) {
+
+ var ctx = el.getContext('2d');
+
+ ctx.fillStyle = settings.backgroundColor;
+
+ ctx.clearRect(0, 0, ngx * (g + 1), ngy * (g + 1));
+
+ ctx.fillRect(0, 0, ngx * (g + 1), ngy * (g + 1));
+
+ } else {
+
+ el.textContent = '';
+
+ el.style.backgroundColor = settings.backgroundColor;
+
+ el.style.position = 'relative';
+
+ }
+
+ });
+
+
+
+ /* fill the grid with empty state */
+
+ gx = ngx;
+
+ while (gx--) {
+
+ grid[gx] = [];
+
+ gy = ngy;
+
+ while (gy--) {
+
+ grid[gx][gy] = true;
+
+ }
+
+ }
+
+ } else {
+
+ /* Determine bgPixel by creating
+
+ another canvas and fill the specified background color. */
+
+ var bctx = document.createElement('canvas').getContext('2d');
+
+
+
+ bctx.fillStyle = settings.backgroundColor;
+
+ bctx.fillRect(0, 0, 1, 1);
+
+ var bgPixel = bctx.getImageData(0, 0, 1, 1).data;
+
+
+
+ /* Read back the pixels of the canvas we got to tell which part of the
+
+ canvas is empty.
+
+ (no clearCanvas only works with a canvas, not divs) */
+
+ var imageData =
+
+ canvas.getContext('2d').getImageData(0, 0, ngx * g, ngy * g).data;
+
+
+
+ gx = ngx;
+
+ var x, y;
+
+ while (gx--) {
+
+ grid[gx] = [];
+
+ gy = ngy;
+
+ while (gy--) {
+
+ y = g;
+
+ singleGridLoop: while (y--) {
+
+ x = g;
+
+ while (x--) {
+
+ i = 4;
+
+ while (i--) {
+
+ if (imageData[((gy * g + y) * ngx * g +
+
+ (gx * g + x)) * 4 + i] !== bgPixel[i]) {
+
+ grid[gx][gy] = false;
+
+ break singleGridLoop;
+
+ }
+
+ }
+
+ }
+
+ }
+
+ if (grid[gx][gy] !== false) {
+
+ grid[gx][gy] = true;
+
+ }
+
+ }
+
+ }
+
+
+
+ imageData = bctx = bgPixel = undefined;
+
+ }
+
+
+
+ // fill the infoGrid with empty state if we need it
+
+ if (settings.hover || settings.click) {
+
+
+
+ interactive = true;
+
+
+
+ /* fill the grid with empty state */
+
+ gx = ngx + 1;
+
+ while (gx--) {
+
+ infoGrid[gx] = [];
+
+ }
+
+
+
+ if (settings.hover) {
+
+ canvas.addEventListener('mousemove', wordcloudhover);
+
+ }
+
+
+
+ var touchend = function (e) {
+
+ e.preventDefault();
+
+ };
+
+
+
+ if (settings.click) {
+
+ canvas.addEventListener('click', wordcloudclick);
+
+ canvas.addEventListener('touchstart', wordcloudclick);
+
+ canvas.addEventListener('touchend', touchend);
+
+ canvas.style.webkitTapHighlightColor = 'rgba(0, 0, 0, 0)';
+
+ }
+
+
+
+ canvas.addEventListener('wordcloudstart', function stopInteraction() {
+
+ canvas.removeEventListener('wordcloudstart', stopInteraction);
+
+
+
+ canvas.removeEventListener('mousemove', wordcloudhover);
+
+ canvas.removeEventListener('click', wordcloudclick);
+
+ canvas.removeEventListener('touchstart', wordcloudclick);
+
+ canvas.removeEventListener('touchend', touchend);
+
+ hovered = undefined;
+
+ });
+
+ }
+
+
+
+ i = 0;
+
+ var loopingFunction, stoppingFunction;
+
+ if (settings.wait !== 0) {
+
+ loopingFunction = window.setTimeout;
+
+ stoppingFunction = window.clearTimeout;
+
+ } else {
+
+ loopingFunction = window.setImmediate;
+
+ stoppingFunction = window.clearImmediate;
+
+ }
+
+
+
+ var addEventListener = function addEventListener(type, listener) {
+
+ elements.forEach(function(el) {
+
+ el.addEventListener(type, listener);
+
+ }, this);
+
+ };
+
+
+
+ var removeEventListener = function removeEventListener(type, listener) {
+
+ elements.forEach(function(el) {
+
+ el.removeEventListener(type, listener);
+
+ }, this);
+
+ };
+
+
+
+ var anotherWordCloudStart = function anotherWordCloudStart() {
+
+ removeEventListener('wordcloudstart', anotherWordCloudStart);
+
+ stoppingFunction(timer);
+
+ };
+
+
+
+ addEventListener('wordcloudstart', anotherWordCloudStart);
+
+
+
+ var timer = loopingFunction(function loop() {
+
+ if (i >= settings.list.length) {
+
+ stoppingFunction(timer);
+
+ sendEvent('wordcloudstop', false);
+
+ removeEventListener('wordcloudstart', anotherWordCloudStart);
+
+
+
+ return;
+
+ }
+
+ escapeTime = (new Date()).getTime();
+
+ var drawn = putWord(settings.list[i]);
+
+ var canceled = !sendEvent('wordclouddrawn', true, {
+
+ item: settings.list[i], drawn: drawn });
+
+ if (exceedTime() || canceled) {
+
+ stoppingFunction(timer);
+
+ settings.abort();
+
+ sendEvent('wordcloudabort', false);
+
+ sendEvent('wordcloudstop', false);
+
+ removeEventListener('wordcloudstart', anotherWordCloudStart);
+
+ return;
+
+ }
+
+ i++;
+
+ timer = loopingFunction(loop, settings.wait);
+
+ }, settings.wait);
+
+ };
+
+
+
+ // All set, start the drawing
+
+ start();
+
+ };
+
+
+
+ WordCloud.isSupported = isSupported;
+
+ WordCloud.minFontSize = minFontSize;
+
+
+
+ // Expose the library as an AMD module
+
+ if (typeof define === 'function' && define.amd) {
+
+ global.WordCloud = WordCloud;
+
+ define('wordcloud', [], function() { return WordCloud; });
+
+ } else if (typeof module !== 'undefined' && module.exports) {
+
+ module.exports = WordCloud;
+
+ } else {
+
+ global.WordCloud = WordCloud;
+
+ }
+
+
+
+})(this); //jshint ignore:line
\ No newline at end of file
diff --git a/IW4MAdmin.sln b/IW4MAdmin.sln
index e6bc44a46..b5be328f8 100644
--- a/IW4MAdmin.sln
+++ b/IW4MAdmin.sln
@@ -75,8 +75,8 @@ Global
{DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Release-Nightly|x64.Build.0 = Release-Nightly|Any CPU
{DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Release-Nightly|x86.ActiveCfg = Release-Nightly|Any CPU
{DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Release-Nightly|x86.Build.0 = Release-Nightly|Any CPU
- {DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Release-Stable|Any CPU.ActiveCfg = Release-Stable|Any CPU
- {DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Release-Stable|Any CPU.Build.0 = Release-Stable|Any CPU
+ {DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Release-Stable|Any CPU.ActiveCfg = Debug|Any CPU
+ {DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Release-Stable|Any CPU.Build.0 = Debug|Any CPU
{DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Release-Stable|Mixed Platforms.ActiveCfg = Release-Stable|x86
{DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Release-Stable|Mixed Platforms.Build.0 = Release-Stable|x86
{DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Release-Stable|x64.ActiveCfg = Release-Stable|Any CPU
@@ -99,8 +99,8 @@ Global
{4785AB75-66F3-4391-985D-63A5A049A0FA}.Release-Nightly|x64.Build.0 = Release-Nightly|Any CPU
{4785AB75-66F3-4391-985D-63A5A049A0FA}.Release-Nightly|x86.ActiveCfg = Release-Nightly|Any CPU
{4785AB75-66F3-4391-985D-63A5A049A0FA}.Release-Nightly|x86.Build.0 = Release-Nightly|Any CPU
- {4785AB75-66F3-4391-985D-63A5A049A0FA}.Release-Stable|Any CPU.ActiveCfg = Release-Stable|Any CPU
- {4785AB75-66F3-4391-985D-63A5A049A0FA}.Release-Stable|Any CPU.Build.0 = Release-Stable|Any CPU
+ {4785AB75-66F3-4391-985D-63A5A049A0FA}.Release-Stable|Any CPU.ActiveCfg = Debug|Any CPU
+ {4785AB75-66F3-4391-985D-63A5A049A0FA}.Release-Stable|Any CPU.Build.0 = Debug|Any CPU
{4785AB75-66F3-4391-985D-63A5A049A0FA}.Release-Stable|Mixed Platforms.ActiveCfg = Release-Stable|x86
{4785AB75-66F3-4391-985D-63A5A049A0FA}.Release-Stable|Mixed Platforms.Build.0 = Release-Stable|x86
{4785AB75-66F3-4391-985D-63A5A049A0FA}.Release-Stable|x64.ActiveCfg = Release-Stable|Any CPU
@@ -123,8 +123,8 @@ Global
{D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Release-Nightly|x64.Build.0 = Release-Nightly|Any CPU
{D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Release-Nightly|x86.ActiveCfg = Release-Nightly|Any CPU
{D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Release-Nightly|x86.Build.0 = Release-Nightly|Any CPU
- {D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Release-Stable|Any CPU.ActiveCfg = Release-Stable|Any CPU
- {D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Release-Stable|Any CPU.Build.0 = Release-Stable|Any CPU
+ {D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Release-Stable|Any CPU.ActiveCfg = Debug|Any CPU
+ {D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Release-Stable|Any CPU.Build.0 = Debug|Any CPU
{D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Release-Stable|Mixed Platforms.ActiveCfg = Release-Stable|x86
{D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Release-Stable|Mixed Platforms.Build.0 = Release-Stable|x86
{D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Release-Stable|x64.ActiveCfg = Release-Stable|Any CPU
@@ -147,8 +147,8 @@ Global
{AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Release-Nightly|x64.Build.0 = Release-Nightly|Any CPU
{AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Release-Nightly|x86.ActiveCfg = Release-Nightly|Any CPU
{AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Release-Nightly|x86.Build.0 = Release-Nightly|Any CPU
- {AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Release-Stable|Any CPU.ActiveCfg = Release-Stable|Any CPU
- {AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Release-Stable|Any CPU.Build.0 = Release-Stable|Any CPU
+ {AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Release-Stable|Any CPU.ActiveCfg = Debug|Any CPU
+ {AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Release-Stable|Any CPU.Build.0 = Debug|Any CPU
{AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Release-Stable|Mixed Platforms.ActiveCfg = Release-Stable|x86
{AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Release-Stable|Mixed Platforms.Build.0 = Release-Stable|x86
{AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Release-Stable|x64.ActiveCfg = Release-Stable|Any CPU
@@ -164,14 +164,13 @@ Global
{428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Debug|x86.ActiveCfg = Debug|x86
{428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Debug|x86.Build.0 = Debug|x86
{428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Release-Nightly|Any CPU.ActiveCfg = Release-Nightly|Any CPU
- {428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Release-Nightly|Any CPU.Build.0 = Release-Nightly|Any CPU
{428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Release-Nightly|Mixed Platforms.ActiveCfg = Release-Nightly|x86
{428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Release-Nightly|Mixed Platforms.Build.0 = Release-Nightly|x86
{428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Release-Nightly|x64.ActiveCfg = Release-Nightly|Any CPU
{428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Release-Nightly|x64.Build.0 = Release-Nightly|Any CPU
{428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Release-Nightly|x86.ActiveCfg = Release-Nightly|Any CPU
{428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Release-Nightly|x86.Build.0 = Release-Nightly|Any CPU
- {428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Release-Stable|Any CPU.ActiveCfg = Release-Stable|Any CPU
+ {428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Release-Stable|Any CPU.ActiveCfg = Debug|Any CPU
{428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Release-Stable|Mixed Platforms.ActiveCfg = Release-Stable|x86
{428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Release-Stable|Mixed Platforms.Build.0 = Release-Stable|x86
{428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Release-Stable|x64.ActiveCfg = Release-Stable|Any CPU
@@ -184,13 +183,12 @@ Global
{E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Debug|x64.ActiveCfg = Release-Stable|Any CPU
{E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Debug|x86.ActiveCfg = Debug|x86
{E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Debug|x86.Build.0 = Debug|x86
- {E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Release-Nightly|Any CPU.ActiveCfg = Release-Stable|Any CPU
- {E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Release-Nightly|Any CPU.Build.0 = Release-Stable|Any CPU
+ {E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Release-Nightly|Any CPU.ActiveCfg = Release-Nightly|Any CPU
{E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Release-Nightly|Mixed Platforms.ActiveCfg = Release-Stable|Any CPU
{E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Release-Nightly|x64.ActiveCfg = Release-Stable|Any CPU
{E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Release-Nightly|x86.ActiveCfg = Release-Nightly|Any CPU
{E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Release-Nightly|x86.Build.0 = Release-Nightly|Any CPU
- {E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Release-Stable|Any CPU.ActiveCfg = Release-Stable|Any CPU
+ {E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Release-Stable|Any CPU.ActiveCfg = Debug|Any CPU
{E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Release-Stable|Mixed Platforms.ActiveCfg = Release-Stable|Any CPU
{E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Release-Stable|x64.ActiveCfg = Release-Stable|Any CPU
{E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Release-Stable|x86.ActiveCfg = Release-Stable|Any CPU
@@ -211,8 +209,8 @@ Global
{C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Release-Nightly|x64.Build.0 = Release-Nightly|Any CPU
{C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Release-Nightly|x86.ActiveCfg = Release-Nightly|Any CPU
{C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Release-Nightly|x86.Build.0 = Release-Nightly|Any CPU
- {C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Release-Stable|Any CPU.ActiveCfg = Release-Stable|Any CPU
- {C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Release-Stable|Any CPU.Build.0 = Release-Stable|Any CPU
+ {C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Release-Stable|Any CPU.ActiveCfg = Debug|Any CPU
+ {C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Release-Stable|Any CPU.Build.0 = Debug|Any CPU
{C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Release-Stable|Mixed Platforms.ActiveCfg = Release-Stable|x86
{C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Release-Stable|Mixed Platforms.Build.0 = Release-Stable|x86
{C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Release-Stable|x64.ActiveCfg = Release-Stable|Any CPU
@@ -228,14 +226,13 @@ Global
{1479DE87-ACB5-4046-81C8-A0BA5041227D}.Debug|x86.ActiveCfg = Debug|x86
{1479DE87-ACB5-4046-81C8-A0BA5041227D}.Debug|x86.Build.0 = Debug|x86
{1479DE87-ACB5-4046-81C8-A0BA5041227D}.Release-Nightly|Any CPU.ActiveCfg = Release-Nightly|Any CPU
- {1479DE87-ACB5-4046-81C8-A0BA5041227D}.Release-Nightly|Any CPU.Build.0 = Release-Nightly|Any CPU
{1479DE87-ACB5-4046-81C8-A0BA5041227D}.Release-Nightly|Mixed Platforms.ActiveCfg = Release-Nightly|x86
{1479DE87-ACB5-4046-81C8-A0BA5041227D}.Release-Nightly|Mixed Platforms.Build.0 = Release-Nightly|x86
{1479DE87-ACB5-4046-81C8-A0BA5041227D}.Release-Nightly|x64.ActiveCfg = Release-Nightly|Any CPU
{1479DE87-ACB5-4046-81C8-A0BA5041227D}.Release-Nightly|x64.Build.0 = Release-Nightly|Any CPU
{1479DE87-ACB5-4046-81C8-A0BA5041227D}.Release-Nightly|x86.ActiveCfg = Release-Nightly|Any CPU
{1479DE87-ACB5-4046-81C8-A0BA5041227D}.Release-Nightly|x86.Build.0 = Release-Nightly|Any CPU
- {1479DE87-ACB5-4046-81C8-A0BA5041227D}.Release-Stable|Any CPU.ActiveCfg = Release-Stable|Any CPU
+ {1479DE87-ACB5-4046-81C8-A0BA5041227D}.Release-Stable|Any CPU.ActiveCfg = Debug|Any CPU
{1479DE87-ACB5-4046-81C8-A0BA5041227D}.Release-Stable|Mixed Platforms.ActiveCfg = Release-Stable|x86
{1479DE87-ACB5-4046-81C8-A0BA5041227D}.Release-Stable|Mixed Platforms.Build.0 = Release-Stable|x86
{1479DE87-ACB5-4046-81C8-A0BA5041227D}.Release-Stable|x64.ActiveCfg = Release-Stable|Any CPU
@@ -251,15 +248,13 @@ Global
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Debug|x86.ActiveCfg = Debug|x86
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Debug|x86.Build.0 = Debug|x86
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release-Nightly|Any CPU.ActiveCfg = Release-Nightly|Any CPU
- {B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release-Nightly|Any CPU.Build.0 = Release-Nightly|Any CPU
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release-Nightly|Mixed Platforms.ActiveCfg = Release-Nightly|x86
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release-Nightly|Mixed Platforms.Build.0 = Release-Nightly|x86
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release-Nightly|x64.ActiveCfg = Release-Nightly|Any CPU
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release-Nightly|x64.Build.0 = Release-Nightly|Any CPU
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release-Nightly|x86.ActiveCfg = Release-Nightly|Any CPU
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release-Nightly|x86.Build.0 = Release-Nightly|Any CPU
- {B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release-Stable|Any CPU.ActiveCfg = Release-Stable|Any CPU
- {B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release-Stable|Any CPU.Build.0 = Release-Stable|Any CPU
+ {B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release-Stable|Any CPU.ActiveCfg = Debug|Any CPU
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release-Stable|Mixed Platforms.ActiveCfg = Release-Stable|x86
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release-Stable|Mixed Platforms.Build.0 = Release-Stable|x86
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release-Stable|x64.ActiveCfg = Release-Stable|Any CPU
diff --git a/Plugins/SimpleStats/Chat/ChatDatabase.cs b/Plugins/SimpleStats/Chat/ChatDatabase.cs
index 4da3f03f6..2ef091476 100644
--- a/Plugins/SimpleStats/Chat/ChatDatabase.cs
+++ b/Plugins/SimpleStats/Chat/ChatDatabase.cs
@@ -12,6 +12,86 @@ namespace StatsPlugin
{
public class ChatDatabase : Database
{
+ private string[] CommonWords = new string[] { "for",
+"with",
+"from",
+"about",
+"into",
+"over",
+"after",
+"that",
+"not",
+"you",
+"this",
+"but",
+"his",
+"they",
+"her",
+"she",
+"will",
+"one",
+"all",
+"would",
+"there",
+"their",
+"have",
+"say",
+"get",
+"make",
+"know",
+"take",
+"see",
+"come",
+"think",
+"look",
+"want",
+"give",
+"use",
+"find",
+"tell",
+"ask",
+"work",
+"seem",
+"feel",
+"try",
+"leave",
+"call",
+"good",
+"new",
+"first",
+"last",
+"long",
+"great",
+"little",
+"own",
+"other",
+"old",
+"right",
+"big",
+"high",
+"small",
+"large",
+"next",
+"early",
+"young",
+"important",
+"few",
+"public",
+"same",
+"able",
+"the",
+"and",
+"that",
+"have",
+"this",
+"one",
+"would",
+ "yeah",
+ "yah",
+ "why",
+ "who" ,
+ "when"};
+
public ChatDatabase(string FN) : base(FN)
{
}
@@ -65,6 +145,9 @@ namespace StatsPlugin
public void AddChatHistory(int clientID, int serverID, string message)
{
+ if (message.Length < 3)
+ return;
+
var chat = new Dictionary()
{
{ "ClientID", clientID },
@@ -75,20 +158,22 @@ namespace StatsPlugin
Insert("CHATHISTORY", chat);
- message.Split(' ').Where(word => word.Length >= 3).Any(word =>
- {
- word = word.ToLower();
- Insert("WORDSTATS", new Dictionary() { { "Word", word } }, true);
- // shush :^)
- ExecuteNonQuery($"UPDATE WORDSTATS SET Count = Count + 1 WHERE Word='{word.CleanChars()}'");
- return true;
- }
- );
+ var eachWord = message.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)
+ .Where (word => word.Length >= 3)
+ .Where(word => CommonWords.FirstOrDefault(c => c == word.ToLower()) == null)
+ .ToList();
+
+ foreach (string _word in eachWord)
+ {
+ string word = _word.ToLower();
+ Insert("WORDSTATS", new Dictionary() { { "Word", word } }, true);
+ UpdateIncrement("WORDSTATS", "Count", new Dictionary() { { "Count", 1 } }, new KeyValuePair("Word", word));
+ }
}
public KeyValuePair[] GetWords()
{
- var result = GetDataTable("SELECT * FROM WORDSTATS ORDER BY Count desc LIMIT 100");
+ var result = GetDataTable("SELECT * FROM WORDSTATS ORDER BY Count desc LIMIT 200");
return result.Select().Select(w => new KeyValuePair(w["Word"].ToString(), Convert.ToInt32(w["Count"].ToString()))).ToArray();
}
}
diff --git a/Plugins/SimpleStats/Chat/ChatHistoryPage.cs b/Plugins/SimpleStats/Chat/ChatHistoryPage.cs
index 1d44ee0c6..be0ed5e3c 100644
--- a/Plugins/SimpleStats/Chat/ChatHistoryPage.cs
+++ b/Plugins/SimpleStats/Chat/ChatHistoryPage.cs
@@ -11,8 +11,6 @@ namespace StatsPlugin.Chat
{
public class ChatPage : HTMLPage
{
- public ChatPage() : base(false) { }
-
public override string GetContent(NameValueCollection querySet, IDictionary headers)
{
StringBuilder S = new StringBuilder();
@@ -27,7 +25,7 @@ namespace StatsPlugin.Chat
return S.ToString();
}
- public override string GetName() => "Chat Stats";
+ public override string GetName() => "Word Cloud";
public override string GetPath() => "/chat";
}
@@ -68,11 +66,20 @@ namespace StatsPlugin.Chat
public HttpResponse GetPage(NameValueCollection querySet, IDictionary headers)
{
+ int clientID = Convert.ToInt32(querySet["clientid"]);
+ var client = Stats.ManagerInstance.GetClientDatabase().GetPlayer(clientID);
HttpResponse resp = new HttpResponse()
{
contentType = GetContentType(),
- content = Stats.ChatDB.GetChatForPlayer(Convert.ToInt32(querySet["clientid"])).ToArray(),
+ content = Stats.ChatDB.GetChatForPlayer(clientID).ToArray().Select(c => new
+ {
+ ClientID = c.ClientID,
+ ServerID = c.ServerID,
+ Message = c.Message,
+ TimeSent = c.TimeSent,
+ Client = client
+ }),
additionalHeaders = new Dictionary()
};
diff --git a/Plugins/SimpleStats/Plugin.cs b/Plugins/SimpleStats/Plugin.cs
index d454ffb8b..48093dcd4 100644
--- a/Plugins/SimpleStats/Plugin.cs
+++ b/Plugins/SimpleStats/Plugin.cs
@@ -316,7 +316,6 @@ namespace StatsPlugin
//S.Logger.WriteInfo($"{E.Origin.Name} killed {E.Target.Name} with a {killEvent.Weapon} from a distance of {Vector3.Distance(killEvent.KillOrigin, killEvent.DeathOrigin)} with {killEvent.Damage} damage, at {killEvent.HitLoc}");
var cs = statLists.Find(x => x.Port == S.GetPort());
cs.playerStats.AddKill(killEvent);
- return;
}
Player Killer = E.Origin;
diff --git a/Plugins/Tests/Tests.csproj b/Plugins/Tests/Tests.csproj
index 293f6b680..d8b8646c7 100644
--- a/Plugins/Tests/Tests.csproj
+++ b/Plugins/Tests/Tests.csproj
@@ -91,6 +91,6 @@
- if $(ConfigurationName) == Debug copy /Y "$(TargetDir)$(TargetName).dll" "$(SolutionDir)BUILD\plugins\"
+ if $(ConfigurationName) == Debug (copy /Y "$(TargetDir)$(TargetName).dll" "$(SolutionDir)BUILD\plugins\")
\ No newline at end of file
diff --git a/SharedLibrary/Database.cs b/SharedLibrary/Database.cs
index 2eca9f9b5..0f08f5391 100644
--- a/SharedLibrary/Database.cs
+++ b/SharedLibrary/Database.cs
@@ -64,6 +64,42 @@ namespace SharedLibrary
}
+ protected void UpdateIncrement(String tableName, string columnName, Dictionary data, KeyValuePair where)
+ {
+ string parameters = "";
+ foreach (string key in data.Keys)
+ {
+ parameters += $"{key}={key}+1,";
+ }
+
+ parameters = parameters.Substring(0, parameters.Length - 1);
+ var Con = GetNewConnection();
+
+ SQLiteCommand updatecmd = new SQLiteCommand()
+ {
+ Connection = Con,
+ CommandText = String.Format("UPDATE `{0}` SET {1} WHERE {2}=@{2}", tableName, parameters, where.Key)
+ };
+ foreach (string key in data.Keys)
+ {
+ updatecmd.Parameters.AddWithValue('@' + key, data[key]);
+ }
+
+ updatecmd.Parameters.AddWithValue('@' + where.Key, where.Value);
+
+ try
+ {
+ Con.Open();
+ updatecmd.ExecuteNonQuery();
+ Con.Close();
+ }
+
+ catch (Exception E)
+ {
+ Console.WriteLine($"Line 96: {E.Message}");
+ }
+ }
+
protected bool Update(String tableName, Dictionary data, KeyValuePair where)
{
string parameters = "";
diff --git a/SharedLibrary/SharedLibrary.csproj b/SharedLibrary/SharedLibrary.csproj
index cac026b9b..76563899e 100644
--- a/SharedLibrary/SharedLibrary.csproj
+++ b/SharedLibrary/SharedLibrary.csproj
@@ -143,9 +143,11 @@ copy /Y "$(TargetDir)Newtonsoft.Json.dll" "$(SolutionDir)Admin\lib"
- if not exist "$(SolutionDir)BUILD" mkdir "$(SolutionDir)BUILD"
+ if exist "$(SolutionDir)BUILD\Plugins" rmdir /Q /S "$(SolutionDir)BUILD\Plugins"
+mkdir "$(SolutionDir)BUILD\Plugins"
+
+if not exist "$(SolutionDir)BUILD" mkdir "$(SolutionDir)BUILD"
if not exist "$(SolutionDir)BUILD\Lib" mkdir "$(SolutionDir)BUILD\Lib"
-if not exist "$(SolutionDir)BUILD\Plugins" mkdir "$(SolutionDir)BUILD\Plugins"
if not exist "$(SolutionDir)BUILD\userraw\scripts" mkdir "$(SolutionDir)BUILD\userraw\scripts"