diff --git a/.gitattributes b/.gitattributes index 594bf50ed..7affbdbf9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,2 @@ -*.sh text eol=lf -*.txt text eol=lf +* text=auto eol=lf diff --git a/freeciv-web/src/main/webapp/css/bluecurve.css b/freeciv-web/src/main/webapp/css/bluecurve.css index 18e9f2080..c5218c1a8 100644 --- a/freeciv-web/src/main/webapp/css/bluecurve.css +++ b/freeciv-web/src/main/webapp/css/bluecurve.css @@ -1,79 +1,79 @@ -/* - back: rgb(230,230,230) - dark: rgb(90,97,90) - medium rgb(189,190,189) - */ - -.dynamic-slider-control { - position: relative; - -moz-user-focus: normal; - -moz-user-select: none; - cursor: default; -} - -.horizontal { - width: 200px; - height: 27px; -} - -.vertical { - width: 29px; - height: 200px; -} - -.dynamic-slider-control input { - display: none; -} - -.dynamic-slider-control .handle { - position: absolute; - font-size: 1px; - overflow: hidden; - -moz-user-select: none; - cursor: default; -} - -.dynamic-slider-control.horizontal .handle { - width: 31px; - height: 14px; - background-image: url("/images/handle.horizontal.png"); -} - -.dynamic-slider-control.horizontal .handle div {} -.dynamic-slider-control.horizontal .handle.hover {} - -.dynamic-slider-control.vertical .handle { - width: 15px; - height: 31px; - background-image: url("/images/handle.vertical.png"); -} - -.dynamic-slider-control.vertical .handle.hover {} - -.dynamic-slider-control .line { - position: absolute; - font-size: 0.01mm; - overflow: hidden; - border: 1px solid rgb(90,97,90); - background: rgb(189,190,189); - - behavior: url("css/boxsizing.htc"); /* ie path bug */ - box-sizing: content-box; - -moz-box-sizing: content-box; -} -.dynamic-slider-control.vertical .line { - width: 3px; -} - -.dynamic-slider-control.horizontal .line { - height: 3px; -} - -.dynamic-slider-control .line div { - width: 1px; - height: 1px; - - border: 1px solid; - border-color: rgb(230,230,230) rgb(189,190,189) - rgb(189,190,189) rgb(230,230,230); -} +/* + back: rgb(230,230,230) + dark: rgb(90,97,90) + medium rgb(189,190,189) + */ + +.dynamic-slider-control { + position: relative; + -moz-user-focus: normal; + -moz-user-select: none; + cursor: default; +} + +.horizontal { + width: 200px; + height: 27px; +} + +.vertical { + width: 29px; + height: 200px; +} + +.dynamic-slider-control input { + display: none; +} + +.dynamic-slider-control .handle { + position: absolute; + font-size: 1px; + overflow: hidden; + -moz-user-select: none; + cursor: default; +} + +.dynamic-slider-control.horizontal .handle { + width: 31px; + height: 14px; + background-image: url("/images/handle.horizontal.png"); +} + +.dynamic-slider-control.horizontal .handle div {} +.dynamic-slider-control.horizontal .handle.hover {} + +.dynamic-slider-control.vertical .handle { + width: 15px; + height: 31px; + background-image: url("/images/handle.vertical.png"); +} + +.dynamic-slider-control.vertical .handle.hover {} + +.dynamic-slider-control .line { + position: absolute; + font-size: 0.01mm; + overflow: hidden; + border: 1px solid rgb(90,97,90); + background: rgb(189,190,189); + + behavior: url("css/boxsizing.htc"); /* ie path bug */ + box-sizing: content-box; + -moz-box-sizing: content-box; +} +.dynamic-slider-control.vertical .line { + width: 3px; +} + +.dynamic-slider-control.horizontal .line { + height: 3px; +} + +.dynamic-slider-control .line div { + width: 1px; + height: 1px; + + border: 1px solid; + border-color: rgb(230,230,230) rgb(189,190,189) + rgb(189,190,189) rgb(230,230,230); +} diff --git a/freeciv-web/src/main/webapp/javascript/libs/bmp_lib.js b/freeciv-web/src/main/webapp/javascript/libs/bmp_lib.js index 34ecb1dc1..b3c069d00 100644 --- a/freeciv-web/src/main/webapp/javascript/libs/bmp_lib.js +++ b/freeciv-web/src/main/webapp/javascript/libs/bmp_lib.js @@ -1,410 +1,410 @@ -/********************************************************************** - Freeciv - Copyright (C) 2009 - Andreas Røsdal andrearo@pvv.ntnu.no - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2, or (at your option) - any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. -***********************************************************************/ - - -/** - * BMP Library for JavaScript - * - * Copyright 2008 Neil Fraser. - * http://neil.fraser.name/software/bmp_lib/ - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Root object for BMP Library. -var bmp_lib = {}; - - -/** - * Replace the contents of an image or a table with a picture. - * @param {string|Element} table The ID of an image or a table, - * or the actual image or table element. - * @param {Array} grid The image data. - * @param {Array} opt_palette Optional palette data. - * @throws {string} If the element is invalid. - */ -bmp_lib.render = function(element, grid, opt_palette) { - if (typeof element == 'string') { - element = document.getElementById(element); - } - if (!element || !element.tagName) { - throw('bmp_lib.render: Invalid element: ' + element); - } else if (element.tagName == 'IMG') { - element.src = this.imageSource(grid, opt_palette); - } else if (element.tagName == 'TABLE') { - var data = this.tableBody(grid, opt_palette); - // IE throws "Unknown runtime error" if one sets table.innerHTML. - // Using insertRow/insertCell works, but takes 10 times longer. - // Instead, build a new table, then swap out the old one. - if ('outerHTML' in element) { - // IE, Safari, Opera. - // Clear existing table. - var rowCount = element.rows.length; - for (var y = rowCount - 1; y >= 0; y--) { - element.deleteRow(y); - } - // Fetch the opening table tag. - var tableTag = element.outerHTML; - tableTag = tableTag.substring(0, tableTag.indexOf('>') + 1); - var tempDiv = document.createElement('DIV'); - tempDiv.innerHTML = tableTag + data + ''; - element.parentElement.replaceChild(tempDiv.firstChild, element); - } else { - // Firefox. - element.innerHTML = data; - } - } else { - throw('bmp_lib.render: Invalid HTML tag: ' + element.tagName); - } -}; - - -/** - * Create a BMP image and encode it so that it may be set directly to the - * 'src' attribute of an HTML image tag. - * @param {Array} grid The image data. - * @param {Array} opt_palette Optional palette data. - * @return {string} Base64-encoded image data with header. - */ -bmp_lib.imageSource = function(grid, opt_palette) { - var a = this.normalize_(grid, opt_palette) - var data = this.createBmp_(a[0], a[1]); - return 'data:image/bmp;base64,' + this.encode64_(data); -}; - - -/** - * Create table contents with each cell depicting one pixel. - * May be written directly between the and
tags. - * @param {Array} grid The image data. - * @param {Array} opt_palette Optional palette data. - * @return {string} HTML soup depicting the image as a table. - */ -bmp_lib.tableBody = function(grid, opt_palette) { - // Note: run-length encoding was attempted but resulted in a slight slowdown. - var a = this.normalize_(grid, opt_palette); - grid = a[0]; - var palette = a[1]; - var height = grid.length; - var width = height && grid[0].length; - var rgb; - var table = []; - for (var y = 0; y < height; y++) { - row = []; - for (var x = 0; x < width; x++) { - if (palette) { - rgb = palette[grid[y].charCodeAt(x)]; - } else { - rgb = grid[y][x]; - } - var colour = this.dec2hex_(rgb[0]) + this.dec2hex_(rgb[1]) + - this.dec2hex_(rgb[2]); - row[x] = ''; - } - row.unshift(''); - row.push(''); - table[y] = row.join(''); - } - return table.join('\n'); -}; - - -/** - * Verifies that the grid and palette are one of the three known types. If - * the grid is a 2D array of numbers, convert it to an array of binary strings. - * @param {Array} grid The image data. - * @param {Array} opt_palette Optional palette data. - * @return {Array.} A tuple containing the a normalized grid and palette. - * @private - */ -bmp_lib.normalize_ = function(grid, opt_palette) { - var palette; - // Check what type of data was provided. - if (grid.length == 0) { - // 0x0 picture. - palette = null; - } else if (typeof grid[0] == 'string' && opt_palette) { - // Array of strings, with palette. - palette = opt_palette; - } else if (typeof grid[0] == 'object' && typeof grid[0][0] == 'number' && - opt_palette) { - // 2D array of numbers, with palette. Convert to array of strings. - grid = this.arrayArrayToArrayStr_(grid); - palette = opt_palette; - } else if (typeof grid[0] == 'object' && typeof grid[0][0] == 'object' && - grid[0][0].length >= 3) { - // 2D array of [r, g, b] tuples, without palette. True-colour mode. - palette = null; - } else { - // WTF? - throw('Invalid argument types.'); - } - return [grid, palette]; -}; - - -/** - * Assemble a BMP based on the image data and an optional palette. - * If a palette is provided and contains 256 or fewer colours, the BMP is - * in 8-bit paletted mode, otherwise it is in 24-bit true-colour mode. - * @param {Array} grid The image data. - * @param {Array?} palette Optional palette data. - * @return {string} BMP as binary string. - * @private - */ -bmp_lib.createBmp_ = function(grid, palette) { - // xxxx and yyyy are placeholders for offsets (computed later). - var bitmapFileHeader = 'BMxxxx\0\0\0\0yyyy'; - - // Assemble the info header. - var height = grid.length; - var width = height && grid[0].length; - var biHeight = this.multixbyteEncode_(height, 4); - var biWidth = this.multixbyteEncode_(width, 4); - var bfOffBits = this.multixbyteEncode_(40, 4); - var bitCount; - if (palette && palette.length <= 256) { - bitCount = 8; - } else { - bitCount = 24; - if (palette) { - // Convert the oversized palette into inline-colours. - grid = this.depalette_(grid, palette); - } - palette = null; - } - var biBitCount = this.multixbyteEncode_(bitCount, 2); - var bitmapInfoHeader = bfOffBits + biWidth + biHeight + '\x01\0' + - biBitCount + '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'; - - // Compute the palette. - var rgbQuad; - if (bitCount != 24) { - var palette_str = String(palette); - if (bmp_lib.createBmp_.palette_str_cache == palette_str) { - // The previously computed palette was identical. Use it. - rgbQuad = bmp_lib.createBmp_.rgbQuad_cache; - } else { - rgbQuad = []; - var r = 0; - var g = 0; - var b = 0; - for (var x = 0; x < 256; x++) { - if (x < palette.length) { - r = palette[x][0]; - g = palette[x][1]; - b = palette[x][2]; - } - rgbQuad[x] = String.fromCharCode(b, g, r, 0); - } - rgbQuad = rgbQuad.join(''); - // Cache this result in case the next call uses the same palette. - bmp_lib.createBmp_.palette_str_cache = palette_str; - bmp_lib.createBmp_.rgbQuad_cache = rgbQuad; - } - } else { - rgbQuad = ''; - } - - var padding; - if (width % 4 == 1) { - padding = '\0\0\0'; - } else if (width % 4 == 2) { - padding = '\0\0'; - } else if (width % 4 == 3) { - padding = '\0'; - } else { - padding = ''; - } - if (bitCount == 24) { - padding = padding + padding + padding; - } - - var data = []; - // BMPs are drawn from the bottom up. - for (var y = 0; y < height; y++) { - var row = grid[height - y - 1]; - if (bitCount == 8) { - data[y] = row + padding; - } else if (bitCount == 24) { - for (var x = 0; x < width; x++) { - data.push(String.fromCharCode(row[x][2], row[x][1], row[x][0])); - } - data.push(padding); - } - } - data = data.join(''); - - var bitmap = bitmapFileHeader + bitmapInfoHeader + rgbQuad + data; - // Specify the offset from the beginning of the file to the bitmap data. - bitmap = bitmap.replace(/yyyy/, this.multixbyteEncode_( - bitmapFileHeader.length + bitmapInfoHeader.length + rgbQuad.length, 4)); - // Insert the size of the bitmap in xbytes. - bitmap = bitmap.replace(/xxxx/, this.multixbyteEncode_(bitmap.length, 4)); - return bitmap; -}; - -// Cached palette to avoid recomputing identical palettes. -bmp_lib.createBmp_.palette_str_cache = ''; -bmp_lib.createBmp_.rgbQuad_cache = ''; - - -/** - * Return a binary string of the specified xbyte length that encodes the - * specified number. LITTLE-ENDIAN! - * @param {number} number The numeric value to be encoded. - * @param {Array?} palette The number of xbytes to use. - * @return {string} BMP as binary string. - * @throws {string} If the number is too big to fit in the xbyte space. - * @private - */ -bmp_lib.multixbyteEncode_ = function(number, xbytes) { - if (number < 0 || xbytes < 0) { - throw('Negative numbers not allowed.'); - } - var oldbase = 1; - var string = ''; - for (var x = 0; x < xbytes; x++) { - var xbyte = 0; - if (number != 0) { - var base = oldbase * 256; - xbyte = number % base; - number = number - xbyte; - xbyte = xbyte / oldbase; - oldbase = base; - } - string += String.fromCharCode(xbyte); - } - if (number != 0) { - throw('Overflow, number too big for string length'); - } - return string; -}; - - -/** - * Converts a 2D array of numbers into an array of binary strings. - * @param {Array.>} grid Array of Arrays of numbers. - * @return {Array.} Array of strings. - * @private - */ -bmp_lib.arrayArrayToArrayStr_ = function(arrayArray) { - var arrayStr = Array(arrayArray.length); - for (var y = 0; y < arrayArray.length; y++) { - line = []; - for (var x = 0; x < arrayArray[y].length; x++) { - line[x] = String.fromCharCode(arrayArray[y][x]); - } - arrayStr[y] = line.join(''); - } - return arrayStr; -}; - - -/** - * Convert a paletted image into a 24-bit true-colour image. - * Used when a palette has more than 256 colours. - * @param {Array.} grid The image data. - * @param {Array.>} palette Palette data. - * @return {Array.>>} 2D array of RGB tuples. - * @private - */ -bmp_lib.depalette_ = function(oldGrid, palette) { - var newGrid = Array(oldGrid.length); - for (var y = 0; y < oldGrid.length; y++) { - newGrid[y] = []; - for (var x = 0; x < oldGrid[y].length; x++) { - newGrid[y][x] = palette[oldGrid[y].charCodeAt(x)]; - } - } - return newGrid; -}; - - -/** - * Converts decimal to hex. Used for HTML colour codes. - * @param {number} decimal 0-255. - * @return {string} '00'-'FF'. - * @private - */ -bmp_lib.dec2hex_ = function(decimal) { - var a = decimal % 16; - var b = (decimal - a) / 16; - return bmp_lib.dec2hex_.hexChars.charAt(b) + - bmp_lib.dec2hex_.hexChars.charAt(a); -}; - -bmp_lib.dec2hex_.hexChars = '0123456789ABCDEF'; - - -// This code was written by Tyler Akins and has been placed in the -// public domain. It would be nice if you left this header intact. -// Base64 code from Tyler Akins -- http://rumkin.com - -/** - * Encode a binary string as Base64. - * @param {string} input Binary string. - * @return {string} Base64-encoded string. - * @private - */ -bmp_lib.encode64_ = function(input) { - var output = ''; - var i = 0; - - do { - var chr1 = input.charCodeAt(i++); - var chr2 = input.charCodeAt(i++); - var chr3 = input.charCodeAt(i++); - - var enc1 = chr1 >> 2; - var enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); - var enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); - var enc4 = chr3 & 63; - - if (isNaN(chr2)) { - enc3 = enc4 = 64; - } else if (isNaN(chr3)) { - enc4 = 64; - } - - output = output + bmp_lib.encode64_.keyStr.charAt(enc1) + - bmp_lib.encode64_.keyStr.charAt(enc2) + - bmp_lib.encode64_.keyStr.charAt(enc3) + - bmp_lib.encode64_.keyStr.charAt(enc4); - } while (i < input.length); - - return output; -}; - -bmp_lib.encode64_.keyStr = - 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; - -// Some browsers (Gecko, Webkit) have a native function for base64 encoding. -if ('btoa' in window && typeof window.btoa == 'function' && - window.btoa('hello') == 'aGVsbG8=') { - // Overwrite previous function with native call. - bmp_lib.encode64_ = function(input) { - return window.btoa(input); - } -} - +/********************************************************************** + Freeciv - Copyright (C) 2009 - Andreas Røsdal andrearo@pvv.ntnu.no + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +***********************************************************************/ + + +/** + * BMP Library for JavaScript + * + * Copyright 2008 Neil Fraser. + * http://neil.fraser.name/software/bmp_lib/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Root object for BMP Library. +var bmp_lib = {}; + + +/** + * Replace the contents of an image or a table with a picture. + * @param {string|Element} table The ID of an image or a table, + * or the actual image or table element. + * @param {Array} grid The image data. + * @param {Array} opt_palette Optional palette data. + * @throws {string} If the element is invalid. + */ +bmp_lib.render = function(element, grid, opt_palette) { + if (typeof element == 'string') { + element = document.getElementById(element); + } + if (!element || !element.tagName) { + throw('bmp_lib.render: Invalid element: ' + element); + } else if (element.tagName == 'IMG') { + element.src = this.imageSource(grid, opt_palette); + } else if (element.tagName == 'TABLE') { + var data = this.tableBody(grid, opt_palette); + // IE throws "Unknown runtime error" if one sets table.innerHTML. + // Using insertRow/insertCell works, but takes 10 times longer. + // Instead, build a new table, then swap out the old one. + if ('outerHTML' in element) { + // IE, Safari, Opera. + // Clear existing table. + var rowCount = element.rows.length; + for (var y = rowCount - 1; y >= 0; y--) { + element.deleteRow(y); + } + // Fetch the opening table tag. + var tableTag = element.outerHTML; + tableTag = tableTag.substring(0, tableTag.indexOf('>') + 1); + var tempDiv = document.createElement('DIV'); + tempDiv.innerHTML = tableTag + data + ''; + element.parentElement.replaceChild(tempDiv.firstChild, element); + } else { + // Firefox. + element.innerHTML = data; + } + } else { + throw('bmp_lib.render: Invalid HTML tag: ' + element.tagName); + } +}; + + +/** + * Create a BMP image and encode it so that it may be set directly to the + * 'src' attribute of an HTML image tag. + * @param {Array} grid The image data. + * @param {Array} opt_palette Optional palette data. + * @return {string} Base64-encoded image data with header. + */ +bmp_lib.imageSource = function(grid, opt_palette) { + var a = this.normalize_(grid, opt_palette) + var data = this.createBmp_(a[0], a[1]); + return 'data:image/bmp;base64,' + this.encode64_(data); +}; + + +/** + * Create table contents with each cell depicting one pixel. + * May be written directly between the and
tags. + * @param {Array} grid The image data. + * @param {Array} opt_palette Optional palette data. + * @return {string} HTML soup depicting the image as a table. + */ +bmp_lib.tableBody = function(grid, opt_palette) { + // Note: run-length encoding was attempted but resulted in a slight slowdown. + var a = this.normalize_(grid, opt_palette); + grid = a[0]; + var palette = a[1]; + var height = grid.length; + var width = height && grid[0].length; + var rgb; + var table = []; + for (var y = 0; y < height; y++) { + row = []; + for (var x = 0; x < width; x++) { + if (palette) { + rgb = palette[grid[y].charCodeAt(x)]; + } else { + rgb = grid[y][x]; + } + var colour = this.dec2hex_(rgb[0]) + this.dec2hex_(rgb[1]) + + this.dec2hex_(rgb[2]); + row[x] = ''; + } + row.unshift(''); + row.push(''); + table[y] = row.join(''); + } + return table.join('\n'); +}; + + +/** + * Verifies that the grid and palette are one of the three known types. If + * the grid is a 2D array of numbers, convert it to an array of binary strings. + * @param {Array} grid The image data. + * @param {Array} opt_palette Optional palette data. + * @return {Array.} A tuple containing the a normalized grid and palette. + * @private + */ +bmp_lib.normalize_ = function(grid, opt_palette) { + var palette; + // Check what type of data was provided. + if (grid.length == 0) { + // 0x0 picture. + palette = null; + } else if (typeof grid[0] == 'string' && opt_palette) { + // Array of strings, with palette. + palette = opt_palette; + } else if (typeof grid[0] == 'object' && typeof grid[0][0] == 'number' && + opt_palette) { + // 2D array of numbers, with palette. Convert to array of strings. + grid = this.arrayArrayToArrayStr_(grid); + palette = opt_palette; + } else if (typeof grid[0] == 'object' && typeof grid[0][0] == 'object' && + grid[0][0].length >= 3) { + // 2D array of [r, g, b] tuples, without palette. True-colour mode. + palette = null; + } else { + // WTF? + throw('Invalid argument types.'); + } + return [grid, palette]; +}; + + +/** + * Assemble a BMP based on the image data and an optional palette. + * If a palette is provided and contains 256 or fewer colours, the BMP is + * in 8-bit paletted mode, otherwise it is in 24-bit true-colour mode. + * @param {Array} grid The image data. + * @param {Array?} palette Optional palette data. + * @return {string} BMP as binary string. + * @private + */ +bmp_lib.createBmp_ = function(grid, palette) { + // xxxx and yyyy are placeholders for offsets (computed later). + var bitmapFileHeader = 'BMxxxx\0\0\0\0yyyy'; + + // Assemble the info header. + var height = grid.length; + var width = height && grid[0].length; + var biHeight = this.multixbyteEncode_(height, 4); + var biWidth = this.multixbyteEncode_(width, 4); + var bfOffBits = this.multixbyteEncode_(40, 4); + var bitCount; + if (palette && palette.length <= 256) { + bitCount = 8; + } else { + bitCount = 24; + if (palette) { + // Convert the oversized palette into inline-colours. + grid = this.depalette_(grid, palette); + } + palette = null; + } + var biBitCount = this.multixbyteEncode_(bitCount, 2); + var bitmapInfoHeader = bfOffBits + biWidth + biHeight + '\x01\0' + + biBitCount + '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'; + + // Compute the palette. + var rgbQuad; + if (bitCount != 24) { + var palette_str = String(palette); + if (bmp_lib.createBmp_.palette_str_cache == palette_str) { + // The previously computed palette was identical. Use it. + rgbQuad = bmp_lib.createBmp_.rgbQuad_cache; + } else { + rgbQuad = []; + var r = 0; + var g = 0; + var b = 0; + for (var x = 0; x < 256; x++) { + if (x < palette.length) { + r = palette[x][0]; + g = palette[x][1]; + b = palette[x][2]; + } + rgbQuad[x] = String.fromCharCode(b, g, r, 0); + } + rgbQuad = rgbQuad.join(''); + // Cache this result in case the next call uses the same palette. + bmp_lib.createBmp_.palette_str_cache = palette_str; + bmp_lib.createBmp_.rgbQuad_cache = rgbQuad; + } + } else { + rgbQuad = ''; + } + + var padding; + if (width % 4 == 1) { + padding = '\0\0\0'; + } else if (width % 4 == 2) { + padding = '\0\0'; + } else if (width % 4 == 3) { + padding = '\0'; + } else { + padding = ''; + } + if (bitCount == 24) { + padding = padding + padding + padding; + } + + var data = []; + // BMPs are drawn from the bottom up. + for (var y = 0; y < height; y++) { + var row = grid[height - y - 1]; + if (bitCount == 8) { + data[y] = row + padding; + } else if (bitCount == 24) { + for (var x = 0; x < width; x++) { + data.push(String.fromCharCode(row[x][2], row[x][1], row[x][0])); + } + data.push(padding); + } + } + data = data.join(''); + + var bitmap = bitmapFileHeader + bitmapInfoHeader + rgbQuad + data; + // Specify the offset from the beginning of the file to the bitmap data. + bitmap = bitmap.replace(/yyyy/, this.multixbyteEncode_( + bitmapFileHeader.length + bitmapInfoHeader.length + rgbQuad.length, 4)); + // Insert the size of the bitmap in xbytes. + bitmap = bitmap.replace(/xxxx/, this.multixbyteEncode_(bitmap.length, 4)); + return bitmap; +}; + +// Cached palette to avoid recomputing identical palettes. +bmp_lib.createBmp_.palette_str_cache = ''; +bmp_lib.createBmp_.rgbQuad_cache = ''; + + +/** + * Return a binary string of the specified xbyte length that encodes the + * specified number. LITTLE-ENDIAN! + * @param {number} number The numeric value to be encoded. + * @param {Array?} palette The number of xbytes to use. + * @return {string} BMP as binary string. + * @throws {string} If the number is too big to fit in the xbyte space. + * @private + */ +bmp_lib.multixbyteEncode_ = function(number, xbytes) { + if (number < 0 || xbytes < 0) { + throw('Negative numbers not allowed.'); + } + var oldbase = 1; + var string = ''; + for (var x = 0; x < xbytes; x++) { + var xbyte = 0; + if (number != 0) { + var base = oldbase * 256; + xbyte = number % base; + number = number - xbyte; + xbyte = xbyte / oldbase; + oldbase = base; + } + string += String.fromCharCode(xbyte); + } + if (number != 0) { + throw('Overflow, number too big for string length'); + } + return string; +}; + + +/** + * Converts a 2D array of numbers into an array of binary strings. + * @param {Array.>} grid Array of Arrays of numbers. + * @return {Array.} Array of strings. + * @private + */ +bmp_lib.arrayArrayToArrayStr_ = function(arrayArray) { + var arrayStr = Array(arrayArray.length); + for (var y = 0; y < arrayArray.length; y++) { + line = []; + for (var x = 0; x < arrayArray[y].length; x++) { + line[x] = String.fromCharCode(arrayArray[y][x]); + } + arrayStr[y] = line.join(''); + } + return arrayStr; +}; + + +/** + * Convert a paletted image into a 24-bit true-colour image. + * Used when a palette has more than 256 colours. + * @param {Array.} grid The image data. + * @param {Array.>} palette Palette data. + * @return {Array.>>} 2D array of RGB tuples. + * @private + */ +bmp_lib.depalette_ = function(oldGrid, palette) { + var newGrid = Array(oldGrid.length); + for (var y = 0; y < oldGrid.length; y++) { + newGrid[y] = []; + for (var x = 0; x < oldGrid[y].length; x++) { + newGrid[y][x] = palette[oldGrid[y].charCodeAt(x)]; + } + } + return newGrid; +}; + + +/** + * Converts decimal to hex. Used for HTML colour codes. + * @param {number} decimal 0-255. + * @return {string} '00'-'FF'. + * @private + */ +bmp_lib.dec2hex_ = function(decimal) { + var a = decimal % 16; + var b = (decimal - a) / 16; + return bmp_lib.dec2hex_.hexChars.charAt(b) + + bmp_lib.dec2hex_.hexChars.charAt(a); +}; + +bmp_lib.dec2hex_.hexChars = '0123456789ABCDEF'; + + +// This code was written by Tyler Akins and has been placed in the +// public domain. It would be nice if you left this header intact. +// Base64 code from Tyler Akins -- http://rumkin.com + +/** + * Encode a binary string as Base64. + * @param {string} input Binary string. + * @return {string} Base64-encoded string. + * @private + */ +bmp_lib.encode64_ = function(input) { + var output = ''; + var i = 0; + + do { + var chr1 = input.charCodeAt(i++); + var chr2 = input.charCodeAt(i++); + var chr3 = input.charCodeAt(i++); + + var enc1 = chr1 >> 2; + var enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + var enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + var enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + + output = output + bmp_lib.encode64_.keyStr.charAt(enc1) + + bmp_lib.encode64_.keyStr.charAt(enc2) + + bmp_lib.encode64_.keyStr.charAt(enc3) + + bmp_lib.encode64_.keyStr.charAt(enc4); + } while (i < input.length); + + return output; +}; + +bmp_lib.encode64_.keyStr = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + +// Some browsers (Gecko, Webkit) have a native function for base64 encoding. +if ('btoa' in window && typeof window.btoa == 'function' && + window.btoa('hello') == 'aGVsbG8=') { + // Overwrite previous function with native call. + bmp_lib.encode64_ = function(input) { + return window.btoa(input); + } +} + diff --git a/freeciv-web/src/main/webapp/javascript/libs/range.js b/freeciv-web/src/main/webapp/javascript/libs/range.js index f7d390dec..c36ee8c9b 100644 --- a/freeciv-web/src/main/webapp/javascript/libs/range.js +++ b/freeciv-web/src/main/webapp/javascript/libs/range.js @@ -1,121 +1,121 @@ -/********************************************************************** - Freeciv-web - the web version of Freeciv. http://play.freeciv.org/ - Copyright (C) 2009-2015 The Freeciv-web project - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -***********************************************************************/ - - - -function Range() { - this._value = 0; - this._minimum = 0; - this._maximum = 100; - this._extent = 0; - - this._isChanging = false; -} - -Range.prototype.setValue = function (value) { - value = Math.round(parseFloat(value)); - if (isNaN(value)) return; - if (this._value != value) { - if (value + this._extent > this._maximum) - this._value = this._maximum - this._extent; - else if (value < this._minimum) - this._value = this._minimum; - else - this._value = value; - if (!this._isChanging && typeof this.onchange == "function") - this.onchange(); - } -}; - -Range.prototype.getValue = function () { - return this._value; -}; - -Range.prototype.setExtent = function (extent) { - if (this._extent != extent) { - if (extent < 0) - this._extent = 0; - else if (this._value + extent > this._maximum) - this._extent = this._maximum - this._value; - else - this._extent = extent; - if (!this._isChanging && typeof this.onchange == "function") - this.onchange(); - } -}; - -Range.prototype.getExtent = function () { - return this._extent; -}; - -Range.prototype.setMinimum = function (minimum) { - if (this._minimum != minimum) { - var oldIsChanging = this._isChanging; - this._isChanging = true; - - this._minimum = minimum; - - if (minimum > this._value) - this.setValue(minimum); - if (minimum > this._maximum) { - this._extent = 0; - this.setMaximum(minimum); - this.setValue(minimum); - } - if (minimum + this._extent > this._maximum) - this._extent = this._maximum - this._minimum; - - this._isChanging = oldIsChanging; - if (!this._isChanging && typeof this.onchange == "function") - this.onchange(); - } -}; - -Range.prototype.getMinimum = function () { - return this._minimum; -}; - -Range.prototype.setMaximum = function (maximum) { - if (this._maximum != maximum) { - var oldIsChanging = this._isChanging; - this._isChanging = true; - - this._maximum = maximum; - - if (maximum < this._value) - this.setValue(maximum - this._extent); - if (maximum < this._minimum) { - this._extent = 0; - this.setMinimum(maximum); - this.setValue(this._maximum); - } - if (maximum < this._minimum + this._extent) - this._extent = this._maximum - this._minimum; - if (maximum < this._value + this._extent) - this._extent = this._maximum - this._value; - - this._isChanging = oldIsChanging; - if (!this._isChanging && typeof this.onchange == "function") - this.onchange(); - } -}; - -Range.prototype.getMaximum = function () { - return this._maximum; -}; +/********************************************************************** + Freeciv-web - the web version of Freeciv. http://play.freeciv.org/ + Copyright (C) 2009-2015 The Freeciv-web project + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +***********************************************************************/ + + + +function Range() { + this._value = 0; + this._minimum = 0; + this._maximum = 100; + this._extent = 0; + + this._isChanging = false; +} + +Range.prototype.setValue = function (value) { + value = Math.round(parseFloat(value)); + if (isNaN(value)) return; + if (this._value != value) { + if (value + this._extent > this._maximum) + this._value = this._maximum - this._extent; + else if (value < this._minimum) + this._value = this._minimum; + else + this._value = value; + if (!this._isChanging && typeof this.onchange == "function") + this.onchange(); + } +}; + +Range.prototype.getValue = function () { + return this._value; +}; + +Range.prototype.setExtent = function (extent) { + if (this._extent != extent) { + if (extent < 0) + this._extent = 0; + else if (this._value + extent > this._maximum) + this._extent = this._maximum - this._value; + else + this._extent = extent; + if (!this._isChanging && typeof this.onchange == "function") + this.onchange(); + } +}; + +Range.prototype.getExtent = function () { + return this._extent; +}; + +Range.prototype.setMinimum = function (minimum) { + if (this._minimum != minimum) { + var oldIsChanging = this._isChanging; + this._isChanging = true; + + this._minimum = minimum; + + if (minimum > this._value) + this.setValue(minimum); + if (minimum > this._maximum) { + this._extent = 0; + this.setMaximum(minimum); + this.setValue(minimum); + } + if (minimum + this._extent > this._maximum) + this._extent = this._maximum - this._minimum; + + this._isChanging = oldIsChanging; + if (!this._isChanging && typeof this.onchange == "function") + this.onchange(); + } +}; + +Range.prototype.getMinimum = function () { + return this._minimum; +}; + +Range.prototype.setMaximum = function (maximum) { + if (this._maximum != maximum) { + var oldIsChanging = this._isChanging; + this._isChanging = true; + + this._maximum = maximum; + + if (maximum < this._value) + this.setValue(maximum - this._extent); + if (maximum < this._minimum) { + this._extent = 0; + this.setMinimum(maximum); + this.setValue(this._maximum); + } + if (maximum < this._minimum + this._extent) + this._extent = this._maximum - this._minimum; + if (maximum < this._value + this._extent) + this._extent = this._maximum - this._value; + + this._isChanging = oldIsChanging; + if (!this._isChanging && typeof this.onchange == "function") + this.onchange(); + } +}; + +Range.prototype.getMaximum = function () { + return this._maximum; +}; diff --git a/freeciv-web/src/main/webapp/javascript/libs/slider.js b/freeciv-web/src/main/webapp/javascript/libs/slider.js index 0f81c995a..4f10d4fda 100644 --- a/freeciv-web/src/main/webapp/javascript/libs/slider.js +++ b/freeciv-web/src/main/webapp/javascript/libs/slider.js @@ -1,469 +1,469 @@ -/********************************************************************** - Freeciv - Copyright (C) 2009 - Andreas Røsdal andrearo@pvv.ntnu.no - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2, or (at your option) - any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. -***********************************************************************/ - - -Slider.isSupported = typeof document.createElement != "undefined" && - typeof document.documentElement != "undefined" && - typeof document.documentElement.offsetWidth == "number"; - - -function Slider(oElement, oInput, sOrientation) { - if (!oElement) return; - this._orientation = sOrientation || "horizontal"; - this._range = new Range(); - this._range.setExtent(0); - this._blockIncrement = 10; - this._unitIncrement = 1; - this._timer = new Timer(100); - - - if (Slider.isSupported && oElement) { - - this.document = oElement.ownerDocument || oElement.document; - - this.element = oElement; - this.element.slider = this; - this.element.unselectable = "on"; - - // add class name tag to class name - this.element.className = this._orientation + " " + this.classNameTag + " " + this.element.className; - - // create line - this.line = this.document.createElement("DIV"); - this.line.className = "line"; - this.line.unselectable = "on"; - this.line.appendChild(this.document.createElement("DIV")); - this.element.appendChild(this.line); - - // create handle - this.handle = this.document.createElement("DIV"); - this.handle.className = "handle"; - this.handle.unselectable = "on"; - this.handle.appendChild(this.document.createElement("DIV")); - this.handle.firstChild.appendChild( - this.document.createTextNode(String.fromCharCode(160))); - this.element.appendChild(this.handle); - } - - this.input = oInput; - - // events - var oThis = this; - this._range.onchange = function () { - oThis.recalculate(); - if (typeof oThis.onchange == "function") - oThis.onchange(); - }; - - if (Slider.isSupported && oElement) { - this.element.onfocus = Slider.eventHandlers.onfocus; - this.element.onblur = Slider.eventHandlers.onblur; - this.element.onmousedown = Slider.eventHandlers.onmousedown; - this.element.onmouseover = Slider.eventHandlers.onmouseover; - this.element.onmouseout = Slider.eventHandlers.onmouseout; - this.element.onkeydown = Slider.eventHandlers.onkeydown; - this.element.onkeypress = Slider.eventHandlers.onkeypress; - this.element.onmousewheel = Slider.eventHandlers.onmousewheel; - this.handle.onselectstart = - this.element.onselectstart = function () { return false; }; - - this._timer.ontimer = function () { - oThis.ontimer(); - }; - - // extra recalculate for ie - window.setTimeout(function() { - oThis.recalculate(); - }, 1); - } - else { - this.input.onchange = function (e) { - oThis.setValue(oThis.input.value); - }; - } -} - -Slider.eventHandlers = { - - // helpers to make events a bit easier - getEvent: function (e, el) { - if (!e) { - if (el) - e = el.document.parentWindow.event; - else - e = window.event; - } - if (!e.srcElement) { - var el = e.target; - while (el != null && el.nodeType != 1) - el = el.parentNode; - e.srcElement = el; - } - if (typeof e.offsetX == "undefined") { - e.offsetX = e.layerX; - e.offsetY = e.layerY; - } - - return e; - }, - - getDocument: function (e) { - if (e.target) - return e.target.ownerDocument; - return e.srcElement.document; - }, - - getSlider: function (e) { - var el = e.target || e.srcElement; - while (el != null && el.slider == null) { - el = el.parentNode; - } - if (el) - return el.slider; - return null; - }, - - getLine: function (e) { - var el = e.target || e.srcElement; - while (el != null && el.className != "line") { - el = el.parentNode; - } - return el; - }, - - getHandle: function (e) { - var el = e.target || e.srcElement; - var re = /handle/; - while (el != null && !re.test(el.className)) { - el = el.parentNode; - } - return el; - }, - // end helpers - - onfocus: function (e) { - var s = this.slider; - s._focused = true; - s.handle.className = "handle hover"; - }, - - onblur: function (e) { - var s = this.slider - s._focused = false; - s.handle.className = "handle"; - }, - - onmouseover: function (e) { - e = Slider.eventHandlers.getEvent(e, this); - var s = this.slider; - if (e.srcElement == s.handle) - s.handle.className = "handle hover"; - }, - - onmouseout: function (e) { - e = Slider.eventHandlers.getEvent(e, this); - var s = this.slider; - if (e.srcElement == s.handle && !s._focused) - s.handle.className = "handle"; - }, - - onmousedown: function (e) { - e = Slider.eventHandlers.getEvent(e, this); - var s = this.slider; - if (s.element.focus) - s.element.focus(); - - Slider._currentInstance = s; - var doc = s.document; - - if (doc.addEventListener) { - doc.addEventListener("mousemove", Slider.eventHandlers.onmousemove, true); - doc.addEventListener("mouseup", Slider.eventHandlers.onmouseup, true); - } - else if (doc.attachEvent) { - doc.attachEvent("onmousemove", Slider.eventHandlers.onmousemove); - doc.attachEvent("onmouseup", Slider.eventHandlers.onmouseup); - doc.attachEvent("onlosecapture", Slider.eventHandlers.onmouseup); - s.element.setCapture(); - } - - if (Slider.eventHandlers.getHandle(e)) { // start drag - Slider._sliderDragData = { - screenX: e.screenX, - screenY: e.screenY, - dx: e.screenX - s.handle.offsetLeft, - dy: e.screenY - s.handle.offsetTop, - startValue: s.getValue(), - slider: s - }; - } - else { - var lineEl = Slider.eventHandlers.getLine(e); - s._mouseX = e.offsetX + (lineEl ? s.line.offsetLeft : 0); - s._mouseY = e.offsetY + (lineEl ? s.line.offsetTop : 0); - s._increasing = null; - s.ontimer(); - } - }, - - onmousemove: function (e) { - e = Slider.eventHandlers.getEvent(e, this); - - if (Slider._sliderDragData) { // drag - var s = Slider._sliderDragData.slider; - - var boundSize = s.getMaximum() - s.getMinimum(); - var size, pos, reset; - - if (s._orientation == "horizontal") { - size = s.element.offsetWidth - s.handle.offsetWidth; - pos = e.screenX - Slider._sliderDragData.dx; - reset = Math.abs(e.screenY - Slider._sliderDragData.screenY) > 100; - } - else { - size = s.element.offsetHeight - s.handle.offsetHeight; - pos = s.element.offsetHeight - s.handle.offsetHeight - - (e.screenY - Slider._sliderDragData.dy); - reset = Math.abs(e.screenX - Slider._sliderDragData.screenX) > 100; - } - s.setValue(reset ? Slider._sliderDragData.startValue : - s.getMinimum() + boundSize * pos / size); - return false; - } - else { - var s = Slider._currentInstance; - if (s != null) { - var lineEl = Slider.eventHandlers.getLine(e); - s._mouseX = e.offsetX + (lineEl ? s.line.offsetLeft : 0); - s._mouseY = e.offsetY + (lineEl ? s.line.offsetTop : 0); - } - } - - }, - - onmouseup: function (e) { - e = Slider.eventHandlers.getEvent(e, this); - var s = Slider._currentInstance; - var doc = s.document; - if (doc.removeEventListener) { - doc.removeEventListener("mousemove", Slider.eventHandlers.onmousemove, true); - doc.removeEventListener("mouseup", Slider.eventHandlers.onmouseup, true); - } - else if (doc.detachEvent) { - doc.detachEvent("onmousemove", Slider.eventHandlers.onmousemove); - doc.detachEvent("onmouseup", Slider.eventHandlers.onmouseup); - doc.detachEvent("onlosecapture", Slider.eventHandlers.onmouseup); - s.element.releaseCapture(); - } - - if (Slider._sliderDragData) { // end drag - Slider._sliderDragData = null; - } - else { - s._timer.stop(); - s._increasing = null; - } - Slider._currentInstance = null; - }, - - onkeydown: function (e) { - e = Slider.eventHandlers.getEvent(e, this); - //var s = Slider.eventHandlers.getSlider(e); - var s = this.slider; - var kc = e.keyCode; - switch (kc) { - case 33: // page up - s.setValue(s.getValue() + s.getBlockIncrement()); - break; - case 34: // page down - s.setValue(s.getValue() - s.getBlockIncrement()); - break; - case 35: // end - s.setValue(s.getOrientation() == "horizontal" ? - s.getMaximum() : - s.getMinimum()); - break; - case 36: // home - s.setValue(s.getOrientation() == "horizontal" ? - s.getMinimum() : - s.getMaximum()); - break; - case 38: // up - case 39: // right - s.setValue(s.getValue() + s.getUnitIncrement()); - break; - - case 37: // left - case 40: // down - s.setValue(s.getValue() - s.getUnitIncrement()); - break; - } - - if (kc >= 33 && kc <= 40) { - return false; - } - }, - - onkeypress: function (e) { - e = Slider.eventHandlers.getEvent(e, this); - var kc = e.keyCode; - if (kc >= 33 && kc <= 40) { - return false; - } - }, - - onmousewheel: function (e) { - e = Slider.eventHandlers.getEvent(e, this); - var s = this.slider; - if (s._focused) { - s.setValue(s.getValue() + e.wheelDelta / 120 * s.getUnitIncrement()); - // windows inverts this on horizontal sliders. That does not - // make sense to me - return false; - } - } -}; - - - -Slider.prototype.classNameTag = "dynamic-slider-control", - -Slider.prototype.setValue = function (v) { - this._range.setValue(v); - this.input.value = this.getValue(); -}; - -Slider.prototype.getValue = function () { - return this._range.getValue(); -}; - -Slider.prototype.setMinimum = function (v) { - this._range.setMinimum(v); - this.input.value = this.getValue(); -}; - -Slider.prototype.getMinimum = function () { - return this._range.getMinimum(); -}; - -Slider.prototype.setMaximum = function (v) { - this._range.setMaximum(v); - this.input.value = this.getValue(); -}; - -Slider.prototype.getMaximum = function () { - return this._range.getMaximum(); -}; - -Slider.prototype.setUnitIncrement = function (v) { - this._unitIncrement = v; -}; - -Slider.prototype.getUnitIncrement = function () { - return this._unitIncrement; -}; - -Slider.prototype.setBlockIncrement = function (v) { - this._blockIncrement = v; -}; - -Slider.prototype.getBlockIncrement = function () { - return this._blockIncrement; -}; - -Slider.prototype.getOrientation = function () { - return this._orientation; -}; - -Slider.prototype.setOrientation = function (sOrientation) { - if (sOrientation != this._orientation) { - if (Slider.isSupported && this.element) { - // add class name tag to class name - this.element.className = this.element.className.replace(this._orientation, - sOrientation); - } - this._orientation = sOrientation; - this.recalculate(); - - } -}; - -Slider.prototype.recalculate = function() { - if (!Slider.isSupported || !this.element) return; - - var w = this.element.offsetWidth; - var h = this.element.offsetHeight; - var hw = this.handle.offsetWidth; - var hh = this.handle.offsetHeight; - var lw = this.line.offsetWidth; - var lh = this.line.offsetHeight; - - // this assumes a border-box layout - - if (this._orientation == "horizontal") { - this.handle.style.left = (w - hw) * (this.getValue() - this.getMinimum()) / - (this.getMaximum() - this.getMinimum()) + "px"; - this.handle.style.top = (h - hh) / 2 + "px"; - - this.line.style.top = (h - lh) / 2 + "px"; - this.line.style.left = hw / 2 + "px"; - //this.line.style.right = hw / 2 + "px"; - this.line.style.width = Math.max(0, w - hw - 2)+ "px"; - this.line.firstChild.style.width = Math.max(0, w - hw - 4)+ "px"; - } - else { - this.handle.style.left = (w - hw) / 2 + "px"; - this.handle.style.top = h - hh - (h - hh) * (this.getValue() - this.getMinimum()) / - (this.getMaximum() - this.getMinimum()) + "px"; - - this.line.style.left = (w - lw) / 2 + "px"; - this.line.style.top = hh / 2 + "px"; - this.line.style.height = Math.max(0, h - hh - 2) + "px"; //hard coded border width - //this.line.style.bottom = hh / 2 + "px"; - this.line.firstChild.style.height = Math.max(0, h - hh - 4) + "px"; //hard coded border width - } -}; - -Slider.prototype.ontimer = function () { - var hw = this.handle.offsetWidth; - var hh = this.handle.offsetHeight; - var hl = this.handle.offsetLeft; - var ht = this.handle.offsetTop; - - if (this._orientation == "horizontal") { - if (this._mouseX > hl + hw && - (this._increasing == null || this._increasing)) { - this.setValue(this.getValue() + this.getBlockIncrement()); - this._increasing = true; - } - else if (this._mouseX < hl && - (this._increasing == null || !this._increasing)) { - this.setValue(this.getValue() - this.getBlockIncrement()); - this._increasing = false; - } - } - else { - if (this._mouseY > ht + hh && - (this._increasing == null || !this._increasing)) { - this.setValue(this.getValue() - this.getBlockIncrement()); - this._increasing = false; - } - else if (this._mouseY < ht && - (this._increasing == null || this._increasing)) { - this.setValue(this.getValue() + this.getBlockIncrement()); - this._increasing = true; - } - } - - this._timer.start(); +/********************************************************************** + Freeciv - Copyright (C) 2009 - Andreas Røsdal andrearo@pvv.ntnu.no + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +***********************************************************************/ + + +Slider.isSupported = typeof document.createElement != "undefined" && + typeof document.documentElement != "undefined" && + typeof document.documentElement.offsetWidth == "number"; + + +function Slider(oElement, oInput, sOrientation) { + if (!oElement) return; + this._orientation = sOrientation || "horizontal"; + this._range = new Range(); + this._range.setExtent(0); + this._blockIncrement = 10; + this._unitIncrement = 1; + this._timer = new Timer(100); + + + if (Slider.isSupported && oElement) { + + this.document = oElement.ownerDocument || oElement.document; + + this.element = oElement; + this.element.slider = this; + this.element.unselectable = "on"; + + // add class name tag to class name + this.element.className = this._orientation + " " + this.classNameTag + " " + this.element.className; + + // create line + this.line = this.document.createElement("DIV"); + this.line.className = "line"; + this.line.unselectable = "on"; + this.line.appendChild(this.document.createElement("DIV")); + this.element.appendChild(this.line); + + // create handle + this.handle = this.document.createElement("DIV"); + this.handle.className = "handle"; + this.handle.unselectable = "on"; + this.handle.appendChild(this.document.createElement("DIV")); + this.handle.firstChild.appendChild( + this.document.createTextNode(String.fromCharCode(160))); + this.element.appendChild(this.handle); + } + + this.input = oInput; + + // events + var oThis = this; + this._range.onchange = function () { + oThis.recalculate(); + if (typeof oThis.onchange == "function") + oThis.onchange(); + }; + + if (Slider.isSupported && oElement) { + this.element.onfocus = Slider.eventHandlers.onfocus; + this.element.onblur = Slider.eventHandlers.onblur; + this.element.onmousedown = Slider.eventHandlers.onmousedown; + this.element.onmouseover = Slider.eventHandlers.onmouseover; + this.element.onmouseout = Slider.eventHandlers.onmouseout; + this.element.onkeydown = Slider.eventHandlers.onkeydown; + this.element.onkeypress = Slider.eventHandlers.onkeypress; + this.element.onmousewheel = Slider.eventHandlers.onmousewheel; + this.handle.onselectstart = + this.element.onselectstart = function () { return false; }; + + this._timer.ontimer = function () { + oThis.ontimer(); + }; + + // extra recalculate for ie + window.setTimeout(function() { + oThis.recalculate(); + }, 1); + } + else { + this.input.onchange = function (e) { + oThis.setValue(oThis.input.value); + }; + } +} + +Slider.eventHandlers = { + + // helpers to make events a bit easier + getEvent: function (e, el) { + if (!e) { + if (el) + e = el.document.parentWindow.event; + else + e = window.event; + } + if (!e.srcElement) { + var el = e.target; + while (el != null && el.nodeType != 1) + el = el.parentNode; + e.srcElement = el; + } + if (typeof e.offsetX == "undefined") { + e.offsetX = e.layerX; + e.offsetY = e.layerY; + } + + return e; + }, + + getDocument: function (e) { + if (e.target) + return e.target.ownerDocument; + return e.srcElement.document; + }, + + getSlider: function (e) { + var el = e.target || e.srcElement; + while (el != null && el.slider == null) { + el = el.parentNode; + } + if (el) + return el.slider; + return null; + }, + + getLine: function (e) { + var el = e.target || e.srcElement; + while (el != null && el.className != "line") { + el = el.parentNode; + } + return el; + }, + + getHandle: function (e) { + var el = e.target || e.srcElement; + var re = /handle/; + while (el != null && !re.test(el.className)) { + el = el.parentNode; + } + return el; + }, + // end helpers + + onfocus: function (e) { + var s = this.slider; + s._focused = true; + s.handle.className = "handle hover"; + }, + + onblur: function (e) { + var s = this.slider + s._focused = false; + s.handle.className = "handle"; + }, + + onmouseover: function (e) { + e = Slider.eventHandlers.getEvent(e, this); + var s = this.slider; + if (e.srcElement == s.handle) + s.handle.className = "handle hover"; + }, + + onmouseout: function (e) { + e = Slider.eventHandlers.getEvent(e, this); + var s = this.slider; + if (e.srcElement == s.handle && !s._focused) + s.handle.className = "handle"; + }, + + onmousedown: function (e) { + e = Slider.eventHandlers.getEvent(e, this); + var s = this.slider; + if (s.element.focus) + s.element.focus(); + + Slider._currentInstance = s; + var doc = s.document; + + if (doc.addEventListener) { + doc.addEventListener("mousemove", Slider.eventHandlers.onmousemove, true); + doc.addEventListener("mouseup", Slider.eventHandlers.onmouseup, true); + } + else if (doc.attachEvent) { + doc.attachEvent("onmousemove", Slider.eventHandlers.onmousemove); + doc.attachEvent("onmouseup", Slider.eventHandlers.onmouseup); + doc.attachEvent("onlosecapture", Slider.eventHandlers.onmouseup); + s.element.setCapture(); + } + + if (Slider.eventHandlers.getHandle(e)) { // start drag + Slider._sliderDragData = { + screenX: e.screenX, + screenY: e.screenY, + dx: e.screenX - s.handle.offsetLeft, + dy: e.screenY - s.handle.offsetTop, + startValue: s.getValue(), + slider: s + }; + } + else { + var lineEl = Slider.eventHandlers.getLine(e); + s._mouseX = e.offsetX + (lineEl ? s.line.offsetLeft : 0); + s._mouseY = e.offsetY + (lineEl ? s.line.offsetTop : 0); + s._increasing = null; + s.ontimer(); + } + }, + + onmousemove: function (e) { + e = Slider.eventHandlers.getEvent(e, this); + + if (Slider._sliderDragData) { // drag + var s = Slider._sliderDragData.slider; + + var boundSize = s.getMaximum() - s.getMinimum(); + var size, pos, reset; + + if (s._orientation == "horizontal") { + size = s.element.offsetWidth - s.handle.offsetWidth; + pos = e.screenX - Slider._sliderDragData.dx; + reset = Math.abs(e.screenY - Slider._sliderDragData.screenY) > 100; + } + else { + size = s.element.offsetHeight - s.handle.offsetHeight; + pos = s.element.offsetHeight - s.handle.offsetHeight - + (e.screenY - Slider._sliderDragData.dy); + reset = Math.abs(e.screenX - Slider._sliderDragData.screenX) > 100; + } + s.setValue(reset ? Slider._sliderDragData.startValue : + s.getMinimum() + boundSize * pos / size); + return false; + } + else { + var s = Slider._currentInstance; + if (s != null) { + var lineEl = Slider.eventHandlers.getLine(e); + s._mouseX = e.offsetX + (lineEl ? s.line.offsetLeft : 0); + s._mouseY = e.offsetY + (lineEl ? s.line.offsetTop : 0); + } + } + + }, + + onmouseup: function (e) { + e = Slider.eventHandlers.getEvent(e, this); + var s = Slider._currentInstance; + var doc = s.document; + if (doc.removeEventListener) { + doc.removeEventListener("mousemove", Slider.eventHandlers.onmousemove, true); + doc.removeEventListener("mouseup", Slider.eventHandlers.onmouseup, true); + } + else if (doc.detachEvent) { + doc.detachEvent("onmousemove", Slider.eventHandlers.onmousemove); + doc.detachEvent("onmouseup", Slider.eventHandlers.onmouseup); + doc.detachEvent("onlosecapture", Slider.eventHandlers.onmouseup); + s.element.releaseCapture(); + } + + if (Slider._sliderDragData) { // end drag + Slider._sliderDragData = null; + } + else { + s._timer.stop(); + s._increasing = null; + } + Slider._currentInstance = null; + }, + + onkeydown: function (e) { + e = Slider.eventHandlers.getEvent(e, this); + //var s = Slider.eventHandlers.getSlider(e); + var s = this.slider; + var kc = e.keyCode; + switch (kc) { + case 33: // page up + s.setValue(s.getValue() + s.getBlockIncrement()); + break; + case 34: // page down + s.setValue(s.getValue() - s.getBlockIncrement()); + break; + case 35: // end + s.setValue(s.getOrientation() == "horizontal" ? + s.getMaximum() : + s.getMinimum()); + break; + case 36: // home + s.setValue(s.getOrientation() == "horizontal" ? + s.getMinimum() : + s.getMaximum()); + break; + case 38: // up + case 39: // right + s.setValue(s.getValue() + s.getUnitIncrement()); + break; + + case 37: // left + case 40: // down + s.setValue(s.getValue() - s.getUnitIncrement()); + break; + } + + if (kc >= 33 && kc <= 40) { + return false; + } + }, + + onkeypress: function (e) { + e = Slider.eventHandlers.getEvent(e, this); + var kc = e.keyCode; + if (kc >= 33 && kc <= 40) { + return false; + } + }, + + onmousewheel: function (e) { + e = Slider.eventHandlers.getEvent(e, this); + var s = this.slider; + if (s._focused) { + s.setValue(s.getValue() + e.wheelDelta / 120 * s.getUnitIncrement()); + // windows inverts this on horizontal sliders. That does not + // make sense to me + return false; + } + } +}; + + + +Slider.prototype.classNameTag = "dynamic-slider-control", + +Slider.prototype.setValue = function (v) { + this._range.setValue(v); + this.input.value = this.getValue(); +}; + +Slider.prototype.getValue = function () { + return this._range.getValue(); +}; + +Slider.prototype.setMinimum = function (v) { + this._range.setMinimum(v); + this.input.value = this.getValue(); +}; + +Slider.prototype.getMinimum = function () { + return this._range.getMinimum(); +}; + +Slider.prototype.setMaximum = function (v) { + this._range.setMaximum(v); + this.input.value = this.getValue(); +}; + +Slider.prototype.getMaximum = function () { + return this._range.getMaximum(); +}; + +Slider.prototype.setUnitIncrement = function (v) { + this._unitIncrement = v; +}; + +Slider.prototype.getUnitIncrement = function () { + return this._unitIncrement; +}; + +Slider.prototype.setBlockIncrement = function (v) { + this._blockIncrement = v; +}; + +Slider.prototype.getBlockIncrement = function () { + return this._blockIncrement; +}; + +Slider.prototype.getOrientation = function () { + return this._orientation; +}; + +Slider.prototype.setOrientation = function (sOrientation) { + if (sOrientation != this._orientation) { + if (Slider.isSupported && this.element) { + // add class name tag to class name + this.element.className = this.element.className.replace(this._orientation, + sOrientation); + } + this._orientation = sOrientation; + this.recalculate(); + + } +}; + +Slider.prototype.recalculate = function() { + if (!Slider.isSupported || !this.element) return; + + var w = this.element.offsetWidth; + var h = this.element.offsetHeight; + var hw = this.handle.offsetWidth; + var hh = this.handle.offsetHeight; + var lw = this.line.offsetWidth; + var lh = this.line.offsetHeight; + + // this assumes a border-box layout + + if (this._orientation == "horizontal") { + this.handle.style.left = (w - hw) * (this.getValue() - this.getMinimum()) / + (this.getMaximum() - this.getMinimum()) + "px"; + this.handle.style.top = (h - hh) / 2 + "px"; + + this.line.style.top = (h - lh) / 2 + "px"; + this.line.style.left = hw / 2 + "px"; + //this.line.style.right = hw / 2 + "px"; + this.line.style.width = Math.max(0, w - hw - 2)+ "px"; + this.line.firstChild.style.width = Math.max(0, w - hw - 4)+ "px"; + } + else { + this.handle.style.left = (w - hw) / 2 + "px"; + this.handle.style.top = h - hh - (h - hh) * (this.getValue() - this.getMinimum()) / + (this.getMaximum() - this.getMinimum()) + "px"; + + this.line.style.left = (w - lw) / 2 + "px"; + this.line.style.top = hh / 2 + "px"; + this.line.style.height = Math.max(0, h - hh - 2) + "px"; //hard coded border width + //this.line.style.bottom = hh / 2 + "px"; + this.line.firstChild.style.height = Math.max(0, h - hh - 4) + "px"; //hard coded border width + } +}; + +Slider.prototype.ontimer = function () { + var hw = this.handle.offsetWidth; + var hh = this.handle.offsetHeight; + var hl = this.handle.offsetLeft; + var ht = this.handle.offsetTop; + + if (this._orientation == "horizontal") { + if (this._mouseX > hl + hw && + (this._increasing == null || this._increasing)) { + this.setValue(this.getValue() + this.getBlockIncrement()); + this._increasing = true; + } + else if (this._mouseX < hl && + (this._increasing == null || !this._increasing)) { + this.setValue(this.getValue() - this.getBlockIncrement()); + this._increasing = false; + } + } + else { + if (this._mouseY > ht + hh && + (this._increasing == null || !this._increasing)) { + this.setValue(this.getValue() - this.getBlockIncrement()); + this._increasing = false; + } + else if (this._mouseY < ht && + (this._increasing == null || this._increasing)) { + this.setValue(this.getValue() + this.getBlockIncrement()); + this._increasing = true; + } + } + + this._timer.start(); }; \ No newline at end of file diff --git a/freeciv-web/src/main/webapp/javascript/libs/timer.js b/freeciv-web/src/main/webapp/javascript/libs/timer.js index 2904171ce..601d8cfe3 100644 --- a/freeciv-web/src/main/webapp/javascript/libs/timer.js +++ b/freeciv-web/src/main/webapp/javascript/libs/timer.js @@ -1,55 +1,55 @@ -/********************************************************************** - Freeciv-web - the web version of Freeciv. http://play.freeciv.org/ - Copyright (C) 2009-2015 The Freeciv-web project - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -***********************************************************************/ - - - -function Timer(nPauseTime) { - this._pauseTime = typeof nPauseTime == "undefined" ? 1000 : nPauseTime; - this._timer = null; - this._isStarted = false; -} - -Timer.prototype.start = function () { - if (this.isStarted()) - this.stop(); - var oThis = this; - this._timer = window.setTimeout(function () { - if (typeof oThis.ontimer == "function") - oThis.ontimer(); - }, this._pauseTime); - this._isStarted = false; -}; - -Timer.prototype.stop = function () { - if (this._timer != null) - window.clearTimeout(this._timer); - this._isStarted = false; -}; - -Timer.prototype.isStarted = function () { - return this._isStarted; -}; - -Timer.prototype.getPauseTime = function () { - return this._pauseTime; -}; - -Timer.prototype.setPauseTime = function (nPauseTime) { - this._pauseTime = nPauseTime; +/********************************************************************** + Freeciv-web - the web version of Freeciv. http://play.freeciv.org/ + Copyright (C) 2009-2015 The Freeciv-web project + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +***********************************************************************/ + + + +function Timer(nPauseTime) { + this._pauseTime = typeof nPauseTime == "undefined" ? 1000 : nPauseTime; + this._timer = null; + this._isStarted = false; +} + +Timer.prototype.start = function () { + if (this.isStarted()) + this.stop(); + var oThis = this; + this._timer = window.setTimeout(function () { + if (typeof oThis.ontimer == "function") + oThis.ontimer(); + }, this._pauseTime); + this._isStarted = false; +}; + +Timer.prototype.stop = function () { + if (this._timer != null) + window.clearTimeout(this._timer); + this._isStarted = false; +}; + +Timer.prototype.isStarted = function () { + return this._isStarted; +}; + +Timer.prototype.getPauseTime = function () { + return this._pauseTime; +}; + +Timer.prototype.setPauseTime = function (nPauseTime) { + this._pauseTime = nPauseTime; }; \ No newline at end of file diff --git a/freeciv-web/src/main/webapp/javascript/pbem.js b/freeciv-web/src/main/webapp/javascript/pbem.js index 6735d34eb..ef53906b1 100644 --- a/freeciv-web/src/main/webapp/javascript/pbem.js +++ b/freeciv-web/src/main/webapp/javascript/pbem.js @@ -1,634 +1,634 @@ -/********************************************************************** - Freeciv-web - the web version of Freeciv. http://play.freeciv.org/ - Copyright (C) 2009-2015 The Freeciv-web project - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -***********************************************************************/ - -var opponents = []; -var pbem_phase_ended = false; - -var invited_players = []; - -/************************************************************************** - Shows the Freeciv play-by-email dialog. -**************************************************************************/ -function show_pbem_dialog() -{ - var title = "Welcome to Freeciv-web"; - var message = ""; - - if ($.getUrlVar('invited_by') != null) { - var invited = $.getUrlVar('invited_by').replace(/[^a-zA-Z]/g,''); - message = "You have been invited by " + invited + " for a Play-by-Email game of Freeciv-web. " - + "You and " + invited + " will play alternating turns, and you will get an e-mail every time " - + "it is your turn to play. First you can create a new user or log-in, then you will play " - + "the first turn."; - } else if ($.getUrlVar('savegame') != null) { - message = "It is now your turn to play this Play-by-Email game. Please login to play your turn."; - if (pbem_duplicate_turn_play_check()) return; - - } else { - message = "You are about to start a Play-by-Email game, where you " - + "can challenge other players, and each player will be notified when " - + "it is their turn to play through e-mail. If you are a new player, then click the sign up button below. These are the game rules:
" - + "
  • The game will have between 2 and 4 human players playing alternating turns. Each player will get an e-mail when it is their turn to play.
  • " - + "
  • Standard Freeciv-web rules are used with some changes to map size, research speed, start units and gold to speed up games.
  • " - + "
  • Please complete your turn as soon as possible, and use at no longer than 7 days until you complete your turn.
  • " - + "
  • Results of games with 2 players are stored to rank players.
  • " - + "
  • Please post feedback and arrange new games on the forum.
  • " - + "
"; - } - - // reset dialog page. - $("#dialog").remove(); - $("
").appendTo("div#game_page"); - $("#dialog").html(message); - $("#dialog").attr("title", title); - $("#dialog").dialog({ - bgiframe: true, - modal: true, - width: is_small_screen() ? "95%" : "60%", - buttons: - { - "Signup new user": function() { - show_new_user_account_dialog("pbem"); - }, - "Log In" : function() { - login_pbem_user(); - }, - "Account...": function() { - close_pbem_account(); - } - - - } - - }); - - $("#dialog").dialog('open'); - - $.ajax({ - type: 'POST', - url: "/user_count" , - success: function(data, textStatus, request){ - $("#user_count").html("We are now " + data + " players available for Play-By-Email games."); - } }); - - var stored_username = simpleStorage.get("username", ""); - var stored_password = simpleStorage.get("password", ""); - if (stored_username != null && stored_username != false && stored_password != null && stored_password != false) { - // Not allowed to create a new user account when already logged in. - $(".ui-dialog-buttonset button").first().button("disable"); - } - -} - -/************************************************************************** -... -**************************************************************************/ -function login_pbem_user() -{ - - var title = "Log in"; - var message = "Log in to your Freeciv-web user account:

" - + "" - + "
Username:
Password:   Forgot password?


" - + "

"; - - // reset dialog page. - $("#dialog").remove(); - $("
").appendTo("div#game_page"); - - $("#dialog").html(message); - $("#dialog").attr("title", title); - $("#dialog").dialog({ - bgiframe: true, - modal: true, - width: is_small_screen() ? "80%" : "40%", - buttons: - { - "Cancel" : function() { - show_pbem_dialog(); - }, - "Login" : function() { - login_pbem_user_request(); - } - } - }); - - var stored_username = simpleStorage.get("username", ""); - if (stored_username != null && stored_username != false) { - $("#username").val(stored_username); - } - var stored_password = simpleStorage.get("password", ""); - if (stored_password != null && stored_password != false) { - $("#password").val(stored_password); - } - - $("#dialog").dialog('open'); - - $('#dialog').keyup(function(e) { - if (e.keyCode == 13) { - login_pbem_user_request(); - } - }); - - $(".pwd_reset").click(forgot_pbem_password); - -} - -/************************************************************************** -... -**************************************************************************/ -function login_pbem_user_request() -{ - - username = $("#username").val().trim(); - var password = $("#password").val().trim(); - if (password != null && password.length > 2) { - var shaObj = new jsSHA("SHA-512", "TEXT"); - shaObj.update(password); - var sha_password = encodeURIComponent(shaObj.getHash("HEX")); - - $.ajax({ - type: 'POST', - url: "/login_user?username=" + encodeURIComponent(username) + "&sha_password=" + sha_password, - success: function(data, textStatus, request){ - if (data != null && data == "OK") { - simpleStorage.set("username", username); - simpleStorage.set("password", password); - if ($.getUrlVar('savegame') != null) { - handle_pbem_load(); - } else { - challenge_pbem_player_dialog(null); - } - } else { - $("#username_validation_result").html("Incorrect username or password. Please try again!"); - $("#username_validation_result").show(); - } - }, - error: function (request, textStatus, errorThrown) { - swal("Login user failed. "); - } - }); - } else { - swal("Invalid password"); - } -} - - -/************************************************************************** - Shows the Freeciv play-by-email dialog. -**************************************************************************/ -function challenge_pbem_player_dialog(extra_intro_message) -{ - - var title = "Find other players to play with!"; - var message = ""; - if (extra_intro_message != null) message = extra_intro_message += "
"; - - message += "Total number of human players: " + - "

" + - "
"; - - // reset dialog page. - $("#dialog").remove(); - $("
").appendTo("div#game_page"); - $("#dialog").html(message); - $("#dialog").attr("title", title); - $("#dialog").dialog({ - bgiframe: true, - modal: true, - width: is_small_screen() ? "80%" : "40%", - buttons: {"Invite random player": function() { - $.ajax({ - type: 'POST', - url: "/random_user" , - success: function(data, textStatus, request){ - var playercount = parseFloat($('#playercount').val()); - if (playercount == 2) { - $("#opponent").val(data) - } else { - $("#opponent_1").val(data) - } - } - }); - }, - "Cancel" : function() { - show_pbem_dialog(); - }, - - "Start game": function() { - create_new_pbem_game(); - } - } - - }); - - var invited = $.getUrlVar('invited_by'); - if (invited != null) { - $("#opponent").val(invited); - $(".ui-dialog-buttonset button").first().hide(); - } - prepare_pbem_opponent_selection(); - $('#playercount').change(prepare_pbem_opponent_selection); - - $("#dialog").dialog('open'); -} - -/************************************************************************** - TODO: validate all usernames when game has 3 or more players. -**************************************************************************/ -function prepare_pbem_opponent_selection() { - var playercount = parseFloat($('#playercount').val()); - opponents = []; - if (playercount == 2) { - var msg = "Enter the username or e-mail address of the other player: " - + "" - + "
Username/E-Mail:

" - + "If the other player already has an account, then you will play the first turn.
If you enter the e-mail " - + "address of a new player, then we will send an invitation e-mail to that player " - + "and the other player will play the first turn." - $("#opponent_box").html(msg); - - } else if (playercount == 3 || playercount == 4) { - var msg = "Enter the usernames of the other human players: " - + "" - + "" - + "" - + ""; - if (playercount == 4) { - msg += ""; - } - - msg += "
You:" + username + "
Username 2:
Username 3:
Username 4:

" - + "Before you fill in this form, the other players have to create their account and tell you their usernames.
" + - "You will play the first turn. The other players will get an e-mail when it is their turn to play.
" + - "This type of game can have human players only." - $("#opponent_box").html(msg); - - } - } - -/************************************************************************** - Determines if the email is valid -**************************************************************************/ -function validateEmail(email) { - var checkemail = email; - if (checkemail != null) checkemail = checkemail.replace("+", ""); // + is allowed. - var re = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i; - return re.test(checkemail); -} - -/************************************************************************** -... -**************************************************************************/ -function forceLower(strInput) -{ - strInput.value=strInput.value.toLowerCase(); -} - -/************************************************************************** -... -**************************************************************************/ -function create_new_pbem_game() -{ - opponents = []; - var playercount = parseFloat($('#playercount').val()); - - if (playercount == 2) { - var check_opponent = $("#opponent").val(); - if (check_opponent != null) check_opponent = check_opponent.trim(); - - if (invited_players.indexOf(check_opponent) != -1) { - swal("You have already invited " + check_opponent + ". There is no " - + "need to invite multiple times. It can take some time before " - + "the invitation email arrives to " + check_opponent); - return; - } - - if (check_opponent == username) { - swal("No playing with yourself please."); - return; - } - - invited_players.push(check_opponent); - - $.ajax({ - type: 'POST', - url: "/validate_user?userstring=" + check_opponent + "&invited_by=" + username, - success: function(data, textStatus, request){ - if (data == "invitation") { - send_pbem_invitation(check_opponent); - - } else if (data == "user_does_not_exist") { - swal("Opponent does not exist. Please try another username."); - - } else if (data != null && data.length > 3) { - opponents.push(data); - network_init(); - $("#dialog").dialog('close'); - setTimeout(create_pbem_players, 3500); - show_dialog_message("Game ready", "Play-By-Email game is now ready to start. " + - "Click the start game button to play the first turn. You can also configure some " + - "game settings before the game begins. The default settings are recommended. " + - "Some settings are not supported in PBEM games, " + - "such as more than four players, AI players or diplomacy. " + - "As the first player, you can choose nation for both players. " + - "Have a fun Play-By-Email game!" - ); - - - } else { - swal("Problem starting new pbem game."); - } - }, - error: function (request, textStatus, errorThrown) { - swal("Opponent does not exist. Please try another username."); - } - }); - - } else { - // 3 or 4 players. - var pbem_ready_players = 1; - var game_start_ready = true; - opponents.push($("#opponent_1").val()); - opponents.push($("#opponent_2").val()); - if (playercount == 4) { - opponents.push($("#opponent_3").val()); - } - - for (var i = 0; i < opponents.length; i++) { - if (opponents[i].length < 1) { - swal("Please fill inn all players names."); - return; - } else if (opponents[i].indexOf("@") != -1) { - swal("Please specify the username of the other player, not their e-mail address."); - return; - } else { - $.ajax({ - async: false, - type: 'POST', - url: "/validate_user?userstring=" + opponents[i] + "&invited_by=" + username, - success: function(data, textStatus, request) { - if (data == "user_does_not_exist") { - swal("Opponent does not exist. Please try another username."); - game_start_ready = false; - } else { - pbem_ready_players = pbem_ready_players + 1; - } - } - }); - } - } - - // start new pbem game - if (game_start_ready && pbem_ready_players == playercount) { - network_init(); - $("#dialog").dialog('close'); - setTimeout(create_pbem_players, 3500); - show_dialog_message("Game ready", "Play-By-Email game is now ready to start. " + - "Click the start game button to play the first turn. You can also configure some " + - "game settings before the game begins. The default settings are recommended. " + - "Some settings are not supported in PBEM games, " + - "such as more than four players, AI players or diplomacy. " + - "As the first player, you can choose nation for all players. " + - "Have a fun Play-By-Email game!" - ); - } - } -} - -/************************************************************************** -... -**************************************************************************/ -function send_pbem_invitation(email) -{ - $.ajax({ - type: 'POST', - url: "/mailstatus?action=invite&to=" + email + "&from=" + username, - success: function(data, textStatus, request){ - swal(email + " has been invited to Freeciv-web. You will " - + "receive an e-mail when it is your turn to play. Now " - + "you can wait for the other player."); - $("#opponent").val("") - }, - error: function (request, textStatus, errorThrown) { - swal("Error: Unable to invite the opponent."); - } - }); - - -} - -/************************************************************************** -... -**************************************************************************/ -function create_pbem_players() -{ - if (ws != null && ws.readyState === 1) { - if (opponents != null && opponents.length > 0) { - for (var i = 0; i < opponents.length; i++) { - send_message("/create " + opponents[i]); - } - setTimeout(pbem_init_game, 1200); - - } else { - swal("Error: invalid opponent selected."); - } - } else { - setTimeout(create_pbem_players, 500); - } -} - -/************************************************************************** -... -**************************************************************************/ -function set_human_pbem_players() -{ - for (var player_id in players) { - var pplayer = players[player_id]; - if (pplayer['flags'].isSet(PLRF_AI) == true - && pplayer['name'].toUpperCase() != username.toUpperCase()) { - send_message("/aitoggle " + pplayer['name']); - } - } -} - -/************************************************************************** - Is this a Play-By-Email game? -**************************************************************************/ -function is_pbem() -{ - return ($.getUrlVar('action') == "pbem"); -} - -/************************************************************************** -... -**************************************************************************/ -function pbem_end_phase() -{ - if (pbem_phase_ended) return; - pbem_phase_ended = true; - send_message("/save"); - - show_dialog_message("Play By Email turn over", - "Your turn is now over in this Play By Email game. Now the next player " + - "will get an email with information about how to complete their turn. " + - "You will also get an email about when it is your turn to play again. " + - "See you again soon!" ); - if ($.getUrlVar('savegame') != null) { - simpleStorage.set("pbem_" + $.getUrlVar('savegame'), "true"); - } - $(window).unbind('beforeunload'); - setTimeout("window.location.href ='/';", 5000); -} - -/************************************************************************** -... -**************************************************************************/ -function handle_pbem_load() -{ - network_init(); - var savegame = $.getUrlVar('savegame'); - $("#dialog").dialog('close'); - $.blockUI(); - - wait_for_text("You are logged in as", function () { - load_game_real(savegame); - }); - wait_for_text("Load complete", activate_pbem_player); - -} - -/************************************************************************** - called when starting a new PBEM game. -**************************************************************************/ -function pbem_init_game() -{ - set_human_pbem_players(); - $.post("/freeciv_time_played_stats?type=pbem").fail(function() {}); -} - - -/************************************************************************** - called when loading a PBEM game. -**************************************************************************/ -function activate_pbem_player() -{ - send_message("/take " + username); - send_message_delayed("/start", 50); -} - - -/************************************************************************** - Dialog for the user to close their user accounts. -**************************************************************************/ -function close_pbem_account() -{ - - var title = "Close account"; - var message = "To deactivate your account, please enter your username and password:

" - + "" - + "
Username:
Password:


" - + "

"; - - // reset dialog page. - $("#dialog").remove(); - $("
").appendTo("div#game_page"); - - $("#dialog").html(message); - $("#dialog").attr("title", title); - $("#dialog").dialog({ - bgiframe: true, - modal: true, - width: is_small_screen() ? "80%" : "50%", - buttons: - { - "Cancel" : function() { - show_pbem_dialog(); - }, - "Unsubscribe" : function() { - request_deactivate_account(); - } - } - }); - - $("#dialog").dialog('open'); -} - - - -/************************************************************************** - Will request the user to be deactivated (activated='0' in DB). -**************************************************************************/ -function request_deactivate_account() -{ - var usr = $("#username").val().trim(); - var password = $("#password").val().trim(); - - var shaObj = new jsSHA("SHA-512", "TEXT"); - shaObj.update(password); - var sha_password = encodeURIComponent(shaObj.getHash("HEX")); - - $.ajax({ - type: 'POST', - url: "/deactivate_user?username=" + usr + "&sha_password=" + sha_password, - success: function(data, textStatus, request){ - swal("User account has been deactivated!"); - - }, - error: function (request, textStatus, errorThrown) { - swal("deactivate user failed."); - } - }); - -} - -/************************************************************************** - Checks for user playing same turn twice. -**************************************************************************/ -function pbem_duplicate_turn_play_check() -{ - var pbem_savegame = $.getUrlVar('savegame'); - if (pbem_savegame != null) { - var previously_played = simpleStorage.get("pbem_" + pbem_savegame, ""); - if (previously_played != null) { - swal("This Play-By-Email turn has already been played and can't be played again. Sorry!"); - return true; - } else { - return false; - } - - } - -} - -/************************************************************************** -... -**************************************************************************/ -function get_pbem_game_key() -{ - return "pbem_tech_" + client.conn.username + players[0]['name'] + players[1]['name']; -} +/********************************************************************** + Freeciv-web - the web version of Freeciv. http://play.freeciv.org/ + Copyright (C) 2009-2015 The Freeciv-web project + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +***********************************************************************/ + +var opponents = []; +var pbem_phase_ended = false; + +var invited_players = []; + +/************************************************************************** + Shows the Freeciv play-by-email dialog. +**************************************************************************/ +function show_pbem_dialog() +{ + var title = "Welcome to Freeciv-web"; + var message = ""; + + if ($.getUrlVar('invited_by') != null) { + var invited = $.getUrlVar('invited_by').replace(/[^a-zA-Z]/g,''); + message = "You have been invited by " + invited + " for a Play-by-Email game of Freeciv-web. " + + "You and " + invited + " will play alternating turns, and you will get an e-mail every time " + + "it is your turn to play. First you can create a new user or log-in, then you will play " + + "the first turn."; + } else if ($.getUrlVar('savegame') != null) { + message = "It is now your turn to play this Play-by-Email game. Please login to play your turn."; + if (pbem_duplicate_turn_play_check()) return; + + } else { + message = "You are about to start a Play-by-Email game, where you " + + "can challenge other players, and each player will be notified when " + + "it is their turn to play through e-mail. If you are a new player, then click the sign up button below. These are the game rules:
" + + "
  • The game will have between 2 and 4 human players playing alternating turns. Each player will get an e-mail when it is their turn to play.
  • " + + "
  • Standard Freeciv-web rules are used with some changes to map size, research speed, start units and gold to speed up games.
  • " + + "
  • Please complete your turn as soon as possible, and use at no longer than 7 days until you complete your turn.
  • " + + "
  • Results of games with 2 players are stored to rank players.
  • " + + "
  • Please post feedback and arrange new games on the forum.
  • " + + "
"; + } + + // reset dialog page. + $("#dialog").remove(); + $("
").appendTo("div#game_page"); + $("#dialog").html(message); + $("#dialog").attr("title", title); + $("#dialog").dialog({ + bgiframe: true, + modal: true, + width: is_small_screen() ? "95%" : "60%", + buttons: + { + "Signup new user": function() { + show_new_user_account_dialog("pbem"); + }, + "Log In" : function() { + login_pbem_user(); + }, + "Account...": function() { + close_pbem_account(); + } + + + } + + }); + + $("#dialog").dialog('open'); + + $.ajax({ + type: 'POST', + url: "/user_count" , + success: function(data, textStatus, request){ + $("#user_count").html("We are now " + data + " players available for Play-By-Email games."); + } }); + + var stored_username = simpleStorage.get("username", ""); + var stored_password = simpleStorage.get("password", ""); + if (stored_username != null && stored_username != false && stored_password != null && stored_password != false) { + // Not allowed to create a new user account when already logged in. + $(".ui-dialog-buttonset button").first().button("disable"); + } + +} + +/************************************************************************** +... +**************************************************************************/ +function login_pbem_user() +{ + + var title = "Log in"; + var message = "Log in to your Freeciv-web user account:

" + + "" + + "
Username:
Password:   Forgot password?


" + + "

"; + + // reset dialog page. + $("#dialog").remove(); + $("
").appendTo("div#game_page"); + + $("#dialog").html(message); + $("#dialog").attr("title", title); + $("#dialog").dialog({ + bgiframe: true, + modal: true, + width: is_small_screen() ? "80%" : "40%", + buttons: + { + "Cancel" : function() { + show_pbem_dialog(); + }, + "Login" : function() { + login_pbem_user_request(); + } + } + }); + + var stored_username = simpleStorage.get("username", ""); + if (stored_username != null && stored_username != false) { + $("#username").val(stored_username); + } + var stored_password = simpleStorage.get("password", ""); + if (stored_password != null && stored_password != false) { + $("#password").val(stored_password); + } + + $("#dialog").dialog('open'); + + $('#dialog').keyup(function(e) { + if (e.keyCode == 13) { + login_pbem_user_request(); + } + }); + + $(".pwd_reset").click(forgot_pbem_password); + +} + +/************************************************************************** +... +**************************************************************************/ +function login_pbem_user_request() +{ + + username = $("#username").val().trim(); + var password = $("#password").val().trim(); + if (password != null && password.length > 2) { + var shaObj = new jsSHA("SHA-512", "TEXT"); + shaObj.update(password); + var sha_password = encodeURIComponent(shaObj.getHash("HEX")); + + $.ajax({ + type: 'POST', + url: "/login_user?username=" + encodeURIComponent(username) + "&sha_password=" + sha_password, + success: function(data, textStatus, request){ + if (data != null && data == "OK") { + simpleStorage.set("username", username); + simpleStorage.set("password", password); + if ($.getUrlVar('savegame') != null) { + handle_pbem_load(); + } else { + challenge_pbem_player_dialog(null); + } + } else { + $("#username_validation_result").html("Incorrect username or password. Please try again!"); + $("#username_validation_result").show(); + } + }, + error: function (request, textStatus, errorThrown) { + swal("Login user failed. "); + } + }); + } else { + swal("Invalid password"); + } +} + + +/************************************************************************** + Shows the Freeciv play-by-email dialog. +**************************************************************************/ +function challenge_pbem_player_dialog(extra_intro_message) +{ + + var title = "Find other players to play with!"; + var message = ""; + if (extra_intro_message != null) message = extra_intro_message += "
"; + + message += "Total number of human players: " + + "

" + + "
"; + + // reset dialog page. + $("#dialog").remove(); + $("
").appendTo("div#game_page"); + $("#dialog").html(message); + $("#dialog").attr("title", title); + $("#dialog").dialog({ + bgiframe: true, + modal: true, + width: is_small_screen() ? "80%" : "40%", + buttons: {"Invite random player": function() { + $.ajax({ + type: 'POST', + url: "/random_user" , + success: function(data, textStatus, request){ + var playercount = parseFloat($('#playercount').val()); + if (playercount == 2) { + $("#opponent").val(data) + } else { + $("#opponent_1").val(data) + } + } + }); + }, + "Cancel" : function() { + show_pbem_dialog(); + }, + + "Start game": function() { + create_new_pbem_game(); + } + } + + }); + + var invited = $.getUrlVar('invited_by'); + if (invited != null) { + $("#opponent").val(invited); + $(".ui-dialog-buttonset button").first().hide(); + } + prepare_pbem_opponent_selection(); + $('#playercount').change(prepare_pbem_opponent_selection); + + $("#dialog").dialog('open'); +} + +/************************************************************************** + TODO: validate all usernames when game has 3 or more players. +**************************************************************************/ +function prepare_pbem_opponent_selection() { + var playercount = parseFloat($('#playercount').val()); + opponents = []; + if (playercount == 2) { + var msg = "Enter the username or e-mail address of the other player: " + + "" + + "
Username/E-Mail:

" + + "If the other player already has an account, then you will play the first turn.
If you enter the e-mail " + + "address of a new player, then we will send an invitation e-mail to that player " + + "and the other player will play the first turn." + $("#opponent_box").html(msg); + + } else if (playercount == 3 || playercount == 4) { + var msg = "Enter the usernames of the other human players: " + + "" + + "" + + "" + + ""; + if (playercount == 4) { + msg += ""; + } + + msg += "
You:" + username + "
Username 2:
Username 3:
Username 4:

" + + "Before you fill in this form, the other players have to create their account and tell you their usernames.
" + + "You will play the first turn. The other players will get an e-mail when it is their turn to play.
" + + "This type of game can have human players only." + $("#opponent_box").html(msg); + + } + } + +/************************************************************************** + Determines if the email is valid +**************************************************************************/ +function validateEmail(email) { + var checkemail = email; + if (checkemail != null) checkemail = checkemail.replace("+", ""); // + is allowed. + var re = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i; + return re.test(checkemail); +} + +/************************************************************************** +... +**************************************************************************/ +function forceLower(strInput) +{ + strInput.value=strInput.value.toLowerCase(); +} + +/************************************************************************** +... +**************************************************************************/ +function create_new_pbem_game() +{ + opponents = []; + var playercount = parseFloat($('#playercount').val()); + + if (playercount == 2) { + var check_opponent = $("#opponent").val(); + if (check_opponent != null) check_opponent = check_opponent.trim(); + + if (invited_players.indexOf(check_opponent) != -1) { + swal("You have already invited " + check_opponent + ". There is no " + + "need to invite multiple times. It can take some time before " + + "the invitation email arrives to " + check_opponent); + return; + } + + if (check_opponent == username) { + swal("No playing with yourself please."); + return; + } + + invited_players.push(check_opponent); + + $.ajax({ + type: 'POST', + url: "/validate_user?userstring=" + check_opponent + "&invited_by=" + username, + success: function(data, textStatus, request){ + if (data == "invitation") { + send_pbem_invitation(check_opponent); + + } else if (data == "user_does_not_exist") { + swal("Opponent does not exist. Please try another username."); + + } else if (data != null && data.length > 3) { + opponents.push(data); + network_init(); + $("#dialog").dialog('close'); + setTimeout(create_pbem_players, 3500); + show_dialog_message("Game ready", "Play-By-Email game is now ready to start. " + + "Click the start game button to play the first turn. You can also configure some " + + "game settings before the game begins. The default settings are recommended. " + + "Some settings are not supported in PBEM games, " + + "such as more than four players, AI players or diplomacy. " + + "As the first player, you can choose nation for both players. " + + "Have a fun Play-By-Email game!" + ); + + + } else { + swal("Problem starting new pbem game."); + } + }, + error: function (request, textStatus, errorThrown) { + swal("Opponent does not exist. Please try another username."); + } + }); + + } else { + // 3 or 4 players. + var pbem_ready_players = 1; + var game_start_ready = true; + opponents.push($("#opponent_1").val()); + opponents.push($("#opponent_2").val()); + if (playercount == 4) { + opponents.push($("#opponent_3").val()); + } + + for (var i = 0; i < opponents.length; i++) { + if (opponents[i].length < 1) { + swal("Please fill inn all players names."); + return; + } else if (opponents[i].indexOf("@") != -1) { + swal("Please specify the username of the other player, not their e-mail address."); + return; + } else { + $.ajax({ + async: false, + type: 'POST', + url: "/validate_user?userstring=" + opponents[i] + "&invited_by=" + username, + success: function(data, textStatus, request) { + if (data == "user_does_not_exist") { + swal("Opponent does not exist. Please try another username."); + game_start_ready = false; + } else { + pbem_ready_players = pbem_ready_players + 1; + } + } + }); + } + } + + // start new pbem game + if (game_start_ready && pbem_ready_players == playercount) { + network_init(); + $("#dialog").dialog('close'); + setTimeout(create_pbem_players, 3500); + show_dialog_message("Game ready", "Play-By-Email game is now ready to start. " + + "Click the start game button to play the first turn. You can also configure some " + + "game settings before the game begins. The default settings are recommended. " + + "Some settings are not supported in PBEM games, " + + "such as more than four players, AI players or diplomacy. " + + "As the first player, you can choose nation for all players. " + + "Have a fun Play-By-Email game!" + ); + } + } +} + +/************************************************************************** +... +**************************************************************************/ +function send_pbem_invitation(email) +{ + $.ajax({ + type: 'POST', + url: "/mailstatus?action=invite&to=" + email + "&from=" + username, + success: function(data, textStatus, request){ + swal(email + " has been invited to Freeciv-web. You will " + + "receive an e-mail when it is your turn to play. Now " + + "you can wait for the other player."); + $("#opponent").val("") + }, + error: function (request, textStatus, errorThrown) { + swal("Error: Unable to invite the opponent."); + } + }); + + +} + +/************************************************************************** +... +**************************************************************************/ +function create_pbem_players() +{ + if (ws != null && ws.readyState === 1) { + if (opponents != null && opponents.length > 0) { + for (var i = 0; i < opponents.length; i++) { + send_message("/create " + opponents[i]); + } + setTimeout(pbem_init_game, 1200); + + } else { + swal("Error: invalid opponent selected."); + } + } else { + setTimeout(create_pbem_players, 500); + } +} + +/************************************************************************** +... +**************************************************************************/ +function set_human_pbem_players() +{ + for (var player_id in players) { + var pplayer = players[player_id]; + if (pplayer['flags'].isSet(PLRF_AI) == true + && pplayer['name'].toUpperCase() != username.toUpperCase()) { + send_message("/aitoggle " + pplayer['name']); + } + } +} + +/************************************************************************** + Is this a Play-By-Email game? +**************************************************************************/ +function is_pbem() +{ + return ($.getUrlVar('action') == "pbem"); +} + +/************************************************************************** +... +**************************************************************************/ +function pbem_end_phase() +{ + if (pbem_phase_ended) return; + pbem_phase_ended = true; + send_message("/save"); + + show_dialog_message("Play By Email turn over", + "Your turn is now over in this Play By Email game. Now the next player " + + "will get an email with information about how to complete their turn. " + + "You will also get an email about when it is your turn to play again. " + + "See you again soon!" ); + if ($.getUrlVar('savegame') != null) { + simpleStorage.set("pbem_" + $.getUrlVar('savegame'), "true"); + } + $(window).unbind('beforeunload'); + setTimeout("window.location.href ='/';", 5000); +} + +/************************************************************************** +... +**************************************************************************/ +function handle_pbem_load() +{ + network_init(); + var savegame = $.getUrlVar('savegame'); + $("#dialog").dialog('close'); + $.blockUI(); + + wait_for_text("You are logged in as", function () { + load_game_real(savegame); + }); + wait_for_text("Load complete", activate_pbem_player); + +} + +/************************************************************************** + called when starting a new PBEM game. +**************************************************************************/ +function pbem_init_game() +{ + set_human_pbem_players(); + $.post("/freeciv_time_played_stats?type=pbem").fail(function() {}); +} + + +/************************************************************************** + called when loading a PBEM game. +**************************************************************************/ +function activate_pbem_player() +{ + send_message("/take " + username); + send_message_delayed("/start", 50); +} + + +/************************************************************************** + Dialog for the user to close their user accounts. +**************************************************************************/ +function close_pbem_account() +{ + + var title = "Close account"; + var message = "To deactivate your account, please enter your username and password:

" + + "" + + "
Username:
Password:


" + + "

"; + + // reset dialog page. + $("#dialog").remove(); + $("
").appendTo("div#game_page"); + + $("#dialog").html(message); + $("#dialog").attr("title", title); + $("#dialog").dialog({ + bgiframe: true, + modal: true, + width: is_small_screen() ? "80%" : "50%", + buttons: + { + "Cancel" : function() { + show_pbem_dialog(); + }, + "Unsubscribe" : function() { + request_deactivate_account(); + } + } + }); + + $("#dialog").dialog('open'); +} + + + +/************************************************************************** + Will request the user to be deactivated (activated='0' in DB). +**************************************************************************/ +function request_deactivate_account() +{ + var usr = $("#username").val().trim(); + var password = $("#password").val().trim(); + + var shaObj = new jsSHA("SHA-512", "TEXT"); + shaObj.update(password); + var sha_password = encodeURIComponent(shaObj.getHash("HEX")); + + $.ajax({ + type: 'POST', + url: "/deactivate_user?username=" + usr + "&sha_password=" + sha_password, + success: function(data, textStatus, request){ + swal("User account has been deactivated!"); + + }, + error: function (request, textStatus, errorThrown) { + swal("deactivate user failed."); + } + }); + +} + +/************************************************************************** + Checks for user playing same turn twice. +**************************************************************************/ +function pbem_duplicate_turn_play_check() +{ + var pbem_savegame = $.getUrlVar('savegame'); + if (pbem_savegame != null) { + var previously_played = simpleStorage.get("pbem_" + pbem_savegame, ""); + if (previously_played != null) { + swal("This Play-By-Email turn has already been played and can't be played again. Sorry!"); + return true; + } else { + return false; + } + + } + +} + +/************************************************************************** +... +**************************************************************************/ +function get_pbem_game_key() +{ + return "pbem_tech_" + client.conn.username + players[0]['name'] + players[1]['name']; +} diff --git a/freeciv-web/src/main/webapp/javascript/scorelog.js b/freeciv-web/src/main/webapp/javascript/scorelog.js index bf0dcc556..c00d57a22 100644 --- a/freeciv-web/src/main/webapp/javascript/scorelog.js +++ b/freeciv-web/src/main/webapp/javascript/scorelog.js @@ -1,187 +1,187 @@ -/********************************************************************** - Freeciv-web - the web version of Freeciv. http://play.freeciv.org/ - Copyright (C) 2009-2015 The Freeciv-web project - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -***********************************************************************/ - - -/**************************************************************************** - Shows the game scores dialog. -****************************************************************************/ -function view_game_scores() { - set_default_mapview_active(); - $("#scores_dialog").remove(); - $("
").appendTo("div#game_page"); - - - var dialog_html = "
Please wait while generating score graphs...
" - +"
    "; - - $("#scores_dialog").html(dialog_html); - $("#scores_dialog").attr("title", "Game Scores"); - $("#scores_dialog").dialog({ - bgiframe: true, - modal: true, - width: is_small_screen() ? "95%" : "80%", - height: is_small_screen() ? 560 : 710, - buttons: { - Ok: function() { - $("#scores_dialog").dialog('close'); - $("#scores_tabs").remove(); - $("#scores_dialog").remove(); - } - } - }); - - $("#scores_dialog").dialog('open'); - - - $.ajax({ - url: "/data/scorelogs/score-" + civserverport + ".log", - dataType: "html", - cache: false, - async: true - }).fail(function() { - $("#scores_wait").html("Score graphs not shown, because server 'scorelog' variable is disabled," - + " or because of problem loading the score file."); - $("#game_scores_button").button( "option", "disabled", true); - }).done(function( data ) { - handle_scorelog(data); - }); - - -} - -/**************************************************************************** - Handles the scorelog file -****************************************************************************/ -function handle_scorelog(scorelog) { - var start_turn = 0; - var scoreitems = scorelog.split("\n"); - var scoreplayers = {}; - var playerslist = []; - var playernames = []; - var scoretags = {}; - var resultdata = {}; - var scorecolors = []; - for (var i = 0; i < scoreitems.length; i++) { - var scoreitem = scoreitems[i]; - var scoredata = scoreitem.split(" "); - if (scoredata.length >= 3) { - if (scoredata[0] == "addplayer") { - var pname = scoredata[3]; - for (var s = 4; s < scoredata.length; s++) { - pname += " " + scoredata[s]; - } - scoreplayers[scoredata[2]] = pname; - playerslist.push(scoredata[2]); - playernames.push(pname); - var pplayer = player_by_name(pname); - if (pplayer == null) { - scorecolors.push("#ff0000"); - } else { - scorecolors.push(nations[pplayer['nation']]['color']); - } - - } else if (scoredata[0] == "turn") { - if (start_turn==0) start_turn = scoredata[1]; - - } else if (scoredata[0] == "tag") { - scoretags[scoredata[1]] = scoredata[2]; - - } else if (scoredata[0] == "data") { - var turn = scoredata[1]; - var tag = scoredata[2]; - var player = scoredata[3]; - var value = scoredata[4]; - if (resultdata[tag] == null) { - var s = {}; - s["turn"] = turn; - s[player] = parseInt(value); - resultdata[tag] = []; - resultdata[tag][turn - start_turn] = s; - } else if (resultdata[tag] != null && resultdata[tag][turn - start_turn] == null) { - var s = {}; - s["turn"] = turn; - s[player] = parseInt(value); - resultdata[tag][turn - start_turn] = s; - } else if (resultdata[tag][turn - start_turn] != null) { - resultdata[tag][turn - start_turn][player] = parseInt(value); - } - } - } - } - if (is_small_screen()) scoretags = {"0" : "score"}; - - for (var key in scoretags) { - var tagname = scoretags[key]; - $("#scores_ul").append("
  • " + get_scorelog_name(tagname) + "
  • "); - $("#scores_tabs").append("
    " - + "
    " + get_scorelog_name(tagname) + "
    "); - } - - var ps = 4; - if (scoreitems.length >1000) ps = 0; - - for (var key in scoretags) { - try { - Morris.Line({ - element: 'scoreschart-' + key, - data: resultdata[key], - xkey: 'turn', - ykeys: playerslist, - labels: playernames, - parseTime: false, - lineColors : scorecolors, - pointSize: ps - }); - } catch(err) { - console.log("Problem showing score log graph: " + err); - } - } - - $("#scores_tabs").tabs(); - $(".scores_tabber").css("padding", "1px"); - $("#scores_wait").hide(); - if (is_small_screen()) { - $(".scorechart").height($("#scores_dialog").height() - 10); - } -} - -/**************************************************************************** -... -****************************************************************************/ -function get_scorelog_name(tag) { - var names = { - "score" : "Score", - "pop" : "Population", - "bnp" : "Economics", - "mfg" : "Production", - "cities" : "Cities", - "techs" : "Techs", - "munits" : "Military units", - "wonders" : "Wonders", - "techout" : "Tech output", - "landarea" : "Land area", - "settledarea" : "Settled area", - "gold" : "Gold", - "unitsbuilt" : "Units built", - "unitskilled" : "Units killed", - "unitslost" : "Units lost" - }; - return names[tag]; -} - +/********************************************************************** + Freeciv-web - the web version of Freeciv. http://play.freeciv.org/ + Copyright (C) 2009-2015 The Freeciv-web project + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +***********************************************************************/ + + +/**************************************************************************** + Shows the game scores dialog. +****************************************************************************/ +function view_game_scores() { + set_default_mapview_active(); + $("#scores_dialog").remove(); + $("
    ").appendTo("div#game_page"); + + + var dialog_html = "
    Please wait while generating score graphs...
    " + +"
      "; + + $("#scores_dialog").html(dialog_html); + $("#scores_dialog").attr("title", "Game Scores"); + $("#scores_dialog").dialog({ + bgiframe: true, + modal: true, + width: is_small_screen() ? "95%" : "80%", + height: is_small_screen() ? 560 : 710, + buttons: { + Ok: function() { + $("#scores_dialog").dialog('close'); + $("#scores_tabs").remove(); + $("#scores_dialog").remove(); + } + } + }); + + $("#scores_dialog").dialog('open'); + + + $.ajax({ + url: "/data/scorelogs/score-" + civserverport + ".log", + dataType: "html", + cache: false, + async: true + }).fail(function() { + $("#scores_wait").html("Score graphs not shown, because server 'scorelog' variable is disabled," + + " or because of problem loading the score file."); + $("#game_scores_button").button( "option", "disabled", true); + }).done(function( data ) { + handle_scorelog(data); + }); + + +} + +/**************************************************************************** + Handles the scorelog file +****************************************************************************/ +function handle_scorelog(scorelog) { + var start_turn = 0; + var scoreitems = scorelog.split("\n"); + var scoreplayers = {}; + var playerslist = []; + var playernames = []; + var scoretags = {}; + var resultdata = {}; + var scorecolors = []; + for (var i = 0; i < scoreitems.length; i++) { + var scoreitem = scoreitems[i]; + var scoredata = scoreitem.split(" "); + if (scoredata.length >= 3) { + if (scoredata[0] == "addplayer") { + var pname = scoredata[3]; + for (var s = 4; s < scoredata.length; s++) { + pname += " " + scoredata[s]; + } + scoreplayers[scoredata[2]] = pname; + playerslist.push(scoredata[2]); + playernames.push(pname); + var pplayer = player_by_name(pname); + if (pplayer == null) { + scorecolors.push("#ff0000"); + } else { + scorecolors.push(nations[pplayer['nation']]['color']); + } + + } else if (scoredata[0] == "turn") { + if (start_turn==0) start_turn = scoredata[1]; + + } else if (scoredata[0] == "tag") { + scoretags[scoredata[1]] = scoredata[2]; + + } else if (scoredata[0] == "data") { + var turn = scoredata[1]; + var tag = scoredata[2]; + var player = scoredata[3]; + var value = scoredata[4]; + if (resultdata[tag] == null) { + var s = {}; + s["turn"] = turn; + s[player] = parseInt(value); + resultdata[tag] = []; + resultdata[tag][turn - start_turn] = s; + } else if (resultdata[tag] != null && resultdata[tag][turn - start_turn] == null) { + var s = {}; + s["turn"] = turn; + s[player] = parseInt(value); + resultdata[tag][turn - start_turn] = s; + } else if (resultdata[tag][turn - start_turn] != null) { + resultdata[tag][turn - start_turn][player] = parseInt(value); + } + } + } + } + if (is_small_screen()) scoretags = {"0" : "score"}; + + for (var key in scoretags) { + var tagname = scoretags[key]; + $("#scores_ul").append("
    • " + get_scorelog_name(tagname) + "
    • "); + $("#scores_tabs").append("
      " + + "
      " + get_scorelog_name(tagname) + "
      "); + } + + var ps = 4; + if (scoreitems.length >1000) ps = 0; + + for (var key in scoretags) { + try { + Morris.Line({ + element: 'scoreschart-' + key, + data: resultdata[key], + xkey: 'turn', + ykeys: playerslist, + labels: playernames, + parseTime: false, + lineColors : scorecolors, + pointSize: ps + }); + } catch(err) { + console.log("Problem showing score log graph: " + err); + } + } + + $("#scores_tabs").tabs(); + $(".scores_tabber").css("padding", "1px"); + $("#scores_wait").hide(); + if (is_small_screen()) { + $(".scorechart").height($("#scores_dialog").height() - 10); + } +} + +/**************************************************************************** +... +****************************************************************************/ +function get_scorelog_name(tag) { + var names = { + "score" : "Score", + "pop" : "Population", + "bnp" : "Economics", + "mfg" : "Production", + "cities" : "Cities", + "techs" : "Techs", + "munits" : "Military units", + "wonders" : "Wonders", + "techout" : "Tech output", + "landarea" : "Land area", + "settledarea" : "Settled area", + "gold" : "Gold", + "unitsbuilt" : "Units built", + "unitskilled" : "Units killed", + "unitslost" : "Units lost" + }; + return names[tag]; +} + diff --git a/pbem/pbem.py b/pbem/pbem.py index 5208f97ae..f8db156be 100644 --- a/pbem/pbem.py +++ b/pbem/pbem.py @@ -1,281 +1,281 @@ -#! /usr/bin/env python - -'''********************************************************************** - Copyright (C) 2009-2016 The Freeciv-web project - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -***********************************************************************''' - -import os, os.path -import sys -import time -import lzma -import datetime -import mysql.connector -from mailsender import MailSender -from mailstatus import * -import shutil -import random -import configparser -import json -import traceback - -game_expire_time = 60 * 60 * 24 * 7; # 7 days until games are expired. -game_remind_time = 60 * 60 * 24 * 6; # 6 days until games are sent a reminder. -testmode = False; - -settings = configparser.ConfigParser() -settings.read("settings.ini") - -mysql_user=settings.get("Config", "mysql_user"); -mysql_database=settings.get("Config", "mysql_database"); -mysql_password=settings.get("Config", "mysql_password"); - -savedir = settings.get("Config", "savegame_directory"); -rankdir = settings.get("Config", "ranklog_directory"); - -host = settings.get("Config", "host"); - -# load game status from file. -loaded_games = {}; -try: - if (os.path.isfile('pbem-games.json')): - with open('pbem-games.json') as data_file: - loaded_games = json.load(data_file) -except Exception as e: - print(e); - -mail = MailSender(); - -status = MailStatus() -status.savegames_read = 0; -status.emails_sent = 0; -status.reminders_sent = 0; -status.ranklog_emails_sent = 0; -status.invitation_emails_sent = 0; -status.retired = 0; -status.games = loaded_games; -status.expiry = game_expire_time; - -# send reminder where game is about to expire -def remind_old_games(): - for key, value in status.games.items(): - if (value['reminder_sent'] == False and (value['time_int'] + game_remind_time) < (time.time())): - status.games[key]['reminder_sent'] = True; - status.reminders_sent += 1; - mail.send_game_reminder(status.games[key]['active_player_email'], status.games[key]['url']); - -# expire old games -def cleanup_expired_games(): - for key, game in status.games.copy().items(): - if ((game['time_int'] + game_expire_time) < (time.time())): - looser = game['players'][game['phase']]; - winner = game['players'][(game['phase']+1) % len(game['players'])]; - print("Expiring game: " + key + " winner:" + winner + ", looser:" + looser); - # at least 3 turns must be completed before it will effect ranking. - if (game['turn'] > 4 and len(game['players']) <= 2): save_game_result(winner, winner, looser); - del status.games[key]; - status.retired += 1; - -# parse Freeciv savegame and collect information to include in e-mail. -def handle_savegame(root, file): - time.sleep(1); - filename = os.path.join(root,file) - print("Handling savegame: " + filename); - txt = None; - with lzma.open(filename, mode="rt") as f: - txt = f.read().split("\n"); - status.savegames_read += 1; - - new_filename = "pbem_processed_" + str(random.randint(0,10000000000)) + ".xz"; - f.close(); - if not testmode: shutil.move(filename, os.path.join(root,new_filename)) - print("New filename will be: " + new_filename); - players = list_players(txt); - phase = find_phase(txt); - turn = find_turn(txt); - game_id = find_game_id(txt); - state = find_state(txt); - print("game_id=" + str(game_id)); - print("phase=" + str(phase)); - print("turn=" + str(turn)); - print("state=" + str(state)); - print("players=" + str(players)); - - if (len(players) <= phase): - print("skipping savegame, game is over."); - return; - active_player = players[phase]; - print("active_player=" + active_player); - active_email = find_email_address(active_player); - game_url = "https://" + host + "/webclient/?action=pbem&savegame=" + new_filename.replace(".xz", ""); - if (active_email != None): - status.games[game_id] = {'turn' : turn, 'phase': phase, 'players' : players, 'time_str' : time.ctime(), - 'time_int' : int(time.time()), 'state' : state, 'url' : game_url, - 'active_player_email' : active_email, 'reminder_sent' : False}; - print("active email=" + active_email); - mail.send_email_next_turn(active_player, players, active_email, game_url, turn); - status.emails_sent += 1; - - #store games status in file - with open('pbem-games.json', 'w') as outfile: - json.dump(status.games, outfile); - -#Returns the phase (active player number), eg 1 -def find_phase(lines): - for l in lines: - if ("phase=" in l): return int(l[6:]); - return None; - -#Returns the current turn -def find_turn(lines): - for l in lines: - if ("turn=" == l[0:5]): return int(l[5:]); - return None; - -#Returns the current server state -def find_state(lines): - for l in lines: - if ("server_state=" == l[0:13]): return l[18:].replace("\"",""); - return None; - - -#Returns the game id -def find_game_id(lines): - for l in lines: - if ("id=" == l[0:3]): return l[4:]; - return None; - - -# Returns a list of active players -def list_players(lines): - players = []; - for l in lines: - if (l[:4] == "name"): players.append(l[5:].replace("\"", "")); - return players; - -# Returns the emailaddress of the given username -def find_email_address(user_to_find): - result = None; - cursor = None; - cnx = None; - try: - cnx = mysql.connector.connect(user=mysql_user, database=mysql_database, password=mysql_password) - cursor = cnx.cursor() - query = ("select email from auth where lower(username)=lower(%(usr)s) and activated='1' limit 1") - cursor.execute(query, {'usr' : user_to_find}) - for email in cursor: - result = email[0]; - finally: - cursor.close() - cnx.close() - return result; - -# store game result in database table 'game_results' -def save_game_result(winner, playerOne, playerTwo): - print("saving game result: " + winner + "," + playerOne + "," + playerTwo); - - if (playerOne == playerTwo): - print("Ignoring game result."); - return; - - cursor = None; - cnx = None; - try: - cnx = mysql.connector.connect(user=mysql_user, database=mysql_database, password=mysql_password) - cursor = cnx.cursor() - - # check if game result has already been stored, for example expiring game when ranklog already read in previously. - query = ("select count(*) as count from game_results where playerOne=%(one)s and playerTwo=%(two)s and endDate>= NOW() - INTERVAL 7 DAY") - cursor.execute(query, {'one' : playerOne, 'two' : playerTwo}); - for count in cursor: - if (count[0] != 0): - print("game result already exists."); - return; - - # insert new game result - query = ("insert ignore into game_results (playerOne, playerTwo, winner, endDate) values (%(playerOne)s, %(playerTwo)s, %(winner)s, NOW())"); - cursor.execute(query, {'playerOne' : playerOne, 'playerTwo' : playerTwo, 'winner' : winner}) - cnx.commit() - finally: - cursor.close() - cnx.close() - return; - - -def process_savegames(): - for root, subFolders, files in os.walk(savedir): - for file in files: - if (file.endswith(".xz") and file.startswith("pbem") and not file.startswith("pbem_processed")): - handle_savegame(root, file); - - -def handle_ranklog(root, file): - filename = os.path.join(root,file) - print("Handling ranklog: " + filename); - openfile = open(filename, 'r') - lines = ""; - try: - lines = openfile.readlines() - finally: - openfile.close(); - winner = None; - winner_score = None; - winner_email = None; - losers = None; - losers_score = None; - losers_email = None; - - turns = None; - for line in lines: - if (len(line) > 9 and line[:7]=='winners' and ',' in line): - winner = line[9:].split(",")[1]; #FIXME: this is not always correct, if there are multiple winners. - winner_score = line[9:].split(",")[3]; - winner_email = find_email_address(winner); - if (len(line) > 8 and line[:6]=='losers' and ',' in line): - losers = line[8:].split(",")[1]; - losers_score = line[8:].split(",")[3]; - losers_email = find_email_address(losers); - if (losers_email != None and winner_email != None): - mail.send_game_result_mail(winner, winner_score, winner_email, losers, losers_score, losers_email); - if (winner != None and len(winner) > 3 and losers != None and len(losers) > 3): - save_game_result(winner, losers, winner); - status.ranklog_emails_sent += 1; - - else: - print("error: game with winner without email in " + file); - os.remove(filename); - -def process_ranklogs(): - for root, subFolders, files in os.walk(rankdir): - for file in files: - if (file.endswith(".log")): - handle_ranklog(root, file); - -if __name__ == '__main__': - print("Freeciv-PBEM processing savegames"); - status.start(); - - while (1): - try: - time.sleep(5); - process_savegames(); - process_ranklogs(); - remind_old_games(); - cleanup_expired_games(); - time.sleep(60); - except Exception as e: - print(e); - traceback.print_exc(); +#! /usr/bin/env python + +'''********************************************************************** + Copyright (C) 2009-2016 The Freeciv-web project + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +***********************************************************************''' + +import os, os.path +import sys +import time +import lzma +import datetime +import mysql.connector +from mailsender import MailSender +from mailstatus import * +import shutil +import random +import configparser +import json +import traceback + +game_expire_time = 60 * 60 * 24 * 7; # 7 days until games are expired. +game_remind_time = 60 * 60 * 24 * 6; # 6 days until games are sent a reminder. +testmode = False; + +settings = configparser.ConfigParser() +settings.read("settings.ini") + +mysql_user=settings.get("Config", "mysql_user"); +mysql_database=settings.get("Config", "mysql_database"); +mysql_password=settings.get("Config", "mysql_password"); + +savedir = settings.get("Config", "savegame_directory"); +rankdir = settings.get("Config", "ranklog_directory"); + +host = settings.get("Config", "host"); + +# load game status from file. +loaded_games = {}; +try: + if (os.path.isfile('pbem-games.json')): + with open('pbem-games.json') as data_file: + loaded_games = json.load(data_file) +except Exception as e: + print(e); + +mail = MailSender(); + +status = MailStatus() +status.savegames_read = 0; +status.emails_sent = 0; +status.reminders_sent = 0; +status.ranklog_emails_sent = 0; +status.invitation_emails_sent = 0; +status.retired = 0; +status.games = loaded_games; +status.expiry = game_expire_time; + +# send reminder where game is about to expire +def remind_old_games(): + for key, value in status.games.items(): + if (value['reminder_sent'] == False and (value['time_int'] + game_remind_time) < (time.time())): + status.games[key]['reminder_sent'] = True; + status.reminders_sent += 1; + mail.send_game_reminder(status.games[key]['active_player_email'], status.games[key]['url']); + +# expire old games +def cleanup_expired_games(): + for key, game in status.games.copy().items(): + if ((game['time_int'] + game_expire_time) < (time.time())): + looser = game['players'][game['phase']]; + winner = game['players'][(game['phase']+1) % len(game['players'])]; + print("Expiring game: " + key + " winner:" + winner + ", looser:" + looser); + # at least 3 turns must be completed before it will effect ranking. + if (game['turn'] > 4 and len(game['players']) <= 2): save_game_result(winner, winner, looser); + del status.games[key]; + status.retired += 1; + +# parse Freeciv savegame and collect information to include in e-mail. +def handle_savegame(root, file): + time.sleep(1); + filename = os.path.join(root,file) + print("Handling savegame: " + filename); + txt = None; + with lzma.open(filename, mode="rt") as f: + txt = f.read().split("\n"); + status.savegames_read += 1; + + new_filename = "pbem_processed_" + str(random.randint(0,10000000000)) + ".xz"; + f.close(); + if not testmode: shutil.move(filename, os.path.join(root,new_filename)) + print("New filename will be: " + new_filename); + players = list_players(txt); + phase = find_phase(txt); + turn = find_turn(txt); + game_id = find_game_id(txt); + state = find_state(txt); + print("game_id=" + str(game_id)); + print("phase=" + str(phase)); + print("turn=" + str(turn)); + print("state=" + str(state)); + print("players=" + str(players)); + + if (len(players) <= phase): + print("skipping savegame, game is over."); + return; + active_player = players[phase]; + print("active_player=" + active_player); + active_email = find_email_address(active_player); + game_url = "https://" + host + "/webclient/?action=pbem&savegame=" + new_filename.replace(".xz", ""); + if (active_email != None): + status.games[game_id] = {'turn' : turn, 'phase': phase, 'players' : players, 'time_str' : time.ctime(), + 'time_int' : int(time.time()), 'state' : state, 'url' : game_url, + 'active_player_email' : active_email, 'reminder_sent' : False}; + print("active email=" + active_email); + mail.send_email_next_turn(active_player, players, active_email, game_url, turn); + status.emails_sent += 1; + + #store games status in file + with open('pbem-games.json', 'w') as outfile: + json.dump(status.games, outfile); + +#Returns the phase (active player number), eg 1 +def find_phase(lines): + for l in lines: + if ("phase=" in l): return int(l[6:]); + return None; + +#Returns the current turn +def find_turn(lines): + for l in lines: + if ("turn=" == l[0:5]): return int(l[5:]); + return None; + +#Returns the current server state +def find_state(lines): + for l in lines: + if ("server_state=" == l[0:13]): return l[18:].replace("\"",""); + return None; + + +#Returns the game id +def find_game_id(lines): + for l in lines: + if ("id=" == l[0:3]): return l[4:]; + return None; + + +# Returns a list of active players +def list_players(lines): + players = []; + for l in lines: + if (l[:4] == "name"): players.append(l[5:].replace("\"", "")); + return players; + +# Returns the emailaddress of the given username +def find_email_address(user_to_find): + result = None; + cursor = None; + cnx = None; + try: + cnx = mysql.connector.connect(user=mysql_user, database=mysql_database, password=mysql_password) + cursor = cnx.cursor() + query = ("select email from auth where lower(username)=lower(%(usr)s) and activated='1' limit 1") + cursor.execute(query, {'usr' : user_to_find}) + for email in cursor: + result = email[0]; + finally: + cursor.close() + cnx.close() + return result; + +# store game result in database table 'game_results' +def save_game_result(winner, playerOne, playerTwo): + print("saving game result: " + winner + "," + playerOne + "," + playerTwo); + + if (playerOne == playerTwo): + print("Ignoring game result."); + return; + + cursor = None; + cnx = None; + try: + cnx = mysql.connector.connect(user=mysql_user, database=mysql_database, password=mysql_password) + cursor = cnx.cursor() + + # check if game result has already been stored, for example expiring game when ranklog already read in previously. + query = ("select count(*) as count from game_results where playerOne=%(one)s and playerTwo=%(two)s and endDate>= NOW() - INTERVAL 7 DAY") + cursor.execute(query, {'one' : playerOne, 'two' : playerTwo}); + for count in cursor: + if (count[0] != 0): + print("game result already exists."); + return; + + # insert new game result + query = ("insert ignore into game_results (playerOne, playerTwo, winner, endDate) values (%(playerOne)s, %(playerTwo)s, %(winner)s, NOW())"); + cursor.execute(query, {'playerOne' : playerOne, 'playerTwo' : playerTwo, 'winner' : winner}) + cnx.commit() + finally: + cursor.close() + cnx.close() + return; + + +def process_savegames(): + for root, subFolders, files in os.walk(savedir): + for file in files: + if (file.endswith(".xz") and file.startswith("pbem") and not file.startswith("pbem_processed")): + handle_savegame(root, file); + + +def handle_ranklog(root, file): + filename = os.path.join(root,file) + print("Handling ranklog: " + filename); + openfile = open(filename, 'r') + lines = ""; + try: + lines = openfile.readlines() + finally: + openfile.close(); + winner = None; + winner_score = None; + winner_email = None; + losers = None; + losers_score = None; + losers_email = None; + + turns = None; + for line in lines: + if (len(line) > 9 and line[:7]=='winners' and ',' in line): + winner = line[9:].split(",")[1]; #FIXME: this is not always correct, if there are multiple winners. + winner_score = line[9:].split(",")[3]; + winner_email = find_email_address(winner); + if (len(line) > 8 and line[:6]=='losers' and ',' in line): + losers = line[8:].split(",")[1]; + losers_score = line[8:].split(",")[3]; + losers_email = find_email_address(losers); + if (losers_email != None and winner_email != None): + mail.send_game_result_mail(winner, winner_score, winner_email, losers, losers_score, losers_email); + if (winner != None and len(winner) > 3 and losers != None and len(losers) > 3): + save_game_result(winner, losers, winner); + status.ranklog_emails_sent += 1; + + else: + print("error: game with winner without email in " + file); + os.remove(filename); + +def process_ranklogs(): + for root, subFolders, files in os.walk(rankdir): + for file in files: + if (file.endswith(".log")): + handle_ranklog(root, file); + +if __name__ == '__main__': + print("Freeciv-PBEM processing savegames"); + status.start(); + + while (1): + try: + time.sleep(5); + process_savegames(); + process_ranklogs(); + remind_old_games(); + cleanup_expired_games(); + time.sleep(60); + except Exception as e: + print(e); + traceback.print_exc(); diff --git a/publite2/pubscript_pbem.serv b/publite2/pubscript_pbem.serv index 30ac8cc45..5dc7de917 100644 --- a/publite2/pubscript_pbem.serv +++ b/publite2/pubscript_pbem.serv @@ -1,31 +1,31 @@ -cmdlevel ctrl first -set topology WRAPX -set nationset all -set compresstype xs -set maxplayers 32 -set netwait 15 -set nettimeout 120 -set pingtime 30 -set pingtimeout 120 -set maxconnectionsperhost 256 -set threaded_save enabled -set scorelog enabled -set size 3 -set landm 50 -set aifill 1 -set onsetbarbs 5000 -set barbarians disabled -set civilwarsize 1000 -set minp 2 -set maxp 4 -set gold 500 -set sciencebox 50 -set startunits=ccccccwwwwwx -set contactturns=0 -set phasemode player -set ec_chat=enabled -set ec_info=enabled -set ec_max_size=20000 -set ec_turns=32768 -metaconnection persistent -metamessage New Freeciv-web Play-By-Email Game +cmdlevel ctrl first +set topology WRAPX +set nationset all +set compresstype xs +set maxplayers 32 +set netwait 15 +set nettimeout 120 +set pingtime 30 +set pingtimeout 120 +set maxconnectionsperhost 256 +set threaded_save enabled +set scorelog enabled +set size 3 +set landm 50 +set aifill 1 +set onsetbarbs 5000 +set barbarians disabled +set civilwarsize 1000 +set minp 2 +set maxp 4 +set gold 500 +set sciencebox 50 +set startunits=ccccccwwwwwx +set contactturns=0 +set phasemode player +set ec_chat=enabled +set ec_info=enabled +set ec_max_size=20000 +set ec_turns=32768 +metaconnection persistent +metamessage New Freeciv-web Play-By-Email Game