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"