Fix line endings

Force LF as EOL, so that it is not changed in Windows and the files can be used from the vagrant shared folder in the VM.

Current users may need to run these commands afterwards to normalize:
```
git rm --cached -r .
git reset --hard
```
This commit is contained in:
Máximo Castañeda
2018-08-20 21:22:39 +02:00
parent 3059122cc0
commit d335098a86
10 changed files with 2266 additions and 2267 deletions

3
.gitattributes vendored
View File

@@ -1,3 +1,2 @@
*.sh text eol=lf
*.txt text eol=lf
* text=auto eol=lf

View File

@@ -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);
}

View File

@@ -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 + '</table>';
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 <TABLE> and </TABLE> 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] = '<TD BGCOLOR=#' + colour + '><img width=1 height=1></TD>';
}
row.unshift('<TR>');
row.push('</TR>');
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.<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.<Array.<number>>} grid Array of Arrays of numbers.
* @return {Array.<string>} 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.<string>} grid The image data.
* @param {Array.<Array.<number>>} palette Palette data.
* @return {Array.<Array.<Array.<number>>>} 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 + '</table>';
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 <TABLE> and </TABLE> 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] = '<TD BGCOLOR=#' + colour + '><img width=1 height=1></TD>';
}
row.unshift('<TR>');
row.push('</TR>');
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.<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.<Array.<number>>} grid Array of Arrays of numbers.
* @return {Array.<string>} 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.<string>} grid The image data.
* @param {Array.<Array.<number>>} palette Palette data.
* @return {Array.<Array.<Array.<number>>>} 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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
***********************************************************************/
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 <http://www.gnu.org/licenses/>.
***********************************************************************/
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;
};

View File

@@ -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();
};

View File

@@ -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 <http://www.gnu.org/licenses/>.
***********************************************************************/
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 <http://www.gnu.org/licenses/>.
***********************************************************************/
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;
};

File diff suppressed because it is too large Load Diff

View File

@@ -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 <http://www.gnu.org/licenses/>.
***********************************************************************/
/****************************************************************************
Shows the game scores dialog.
****************************************************************************/
function view_game_scores() {
set_default_mapview_active();
$("#scores_dialog").remove();
$("<div id='scores_dialog'></div>").appendTo("div#game_page");
var dialog_html = "<center><div id='scores_wait'>Please wait while generating score graphs...</div></center>"
+"</div><div id='scores_tabs'><ul id='scores_ul'></ul></div>";
$("#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("<li><a href='#scores-tabs-" + key + "' class='scores_tabber'>" + get_scorelog_name(tagname) + "</a></li>");
$("#scores_tabs").append("<div id='scores-tabs-" + key + "'><div id='scoreschart-" + key + "' class='scorechart'></div>"
+ "<center><b>" + get_scorelog_name(tagname) + "</b></center></div>");
}
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 <http://www.gnu.org/licenses/>.
***********************************************************************/
/****************************************************************************
Shows the game scores dialog.
****************************************************************************/
function view_game_scores() {
set_default_mapview_active();
$("#scores_dialog").remove();
$("<div id='scores_dialog'></div>").appendTo("div#game_page");
var dialog_html = "<center><div id='scores_wait'>Please wait while generating score graphs...</div></center>"
+"</div><div id='scores_tabs'><ul id='scores_ul'></ul></div>";
$("#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("<li><a href='#scores-tabs-" + key + "' class='scores_tabber'>" + get_scorelog_name(tagname) + "</a></li>");
$("#scores_tabs").append("<div id='scores-tabs-" + key + "'><div id='scoreschart-" + key + "' class='scorechart'></div>"
+ "<center><b>" + get_scorelog_name(tagname) + "</b></center></div>");
}
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];
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
***********************************************************************'''
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 <http://www.gnu.org/licenses/>.
***********************************************************************'''
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();

View File

@@ -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