· 5 years ago · Mar 30, 2020, 12:04 PM
1/* jshint freeze:false, bitwise:false, unused:false */
2/* global Game, GPWindowMgr, ngettext, Timestamp, DebugTranslations, GameData */
3
4// TODO separate polyfills from common functions and native object extensions (should be undone if possible)
5
6if (!Date.now) { // Emulate Date.now() for IE8
7 Date.now = function() {
8 return new Date().valueOf();
9 };
10}
11
12if (!String.prototype.camelCase) {
13 String.prototype.camelCase = function() {
14 return this.replace(/(?:\b|_)(.)/g, function (x, chr) {
15 return chr.toUpperCase();
16 });
17 };
18}
19
20if (!String.prototype.snakeCase) {
21 String.prototype.snakeCase = function() {
22 return this.replace(/([A-Z])/g, function(capital) {
23 return '_' + capital.toLowerCase();
24 }).replace(/^_/, '');
25 };
26}
27
28//inheritance wrapper
29if (!Function.prototype.inherits) {
30 Function.prototype.inherits = function(F) {
31 this.prototype = new F();
32 this.prototype.constructor = this;
33 this.prototype.parent = F.prototype;
34 };
35}
36
37if (!Number.prototype.between) {
38 Number.prototype.between = function(a, b) {
39 return (a < b ? this >= a && this <= b : this >= b && this <= a);
40 };
41}
42
43if (!Array.prototype.searchFor) {
44 Array.prototype.searchFor = function(attr, value) {
45 return this.filter(function(el) {
46 return el[attr] == value; // jshint ignore:line
47 });
48 };
49}
50
51if (!String.prototype.truncate) {
52 String.prototype.truncate = function(limit) {
53 var len = this.length;
54
55 return len > limit ? this.substr(0, limit) + '...' : this;
56 };
57}
58
59if (!Array.prototype.remove) {
60 //Some array functions - By John Resig (MIT Licensed)
61 Array.prototype.remove = function(from, to) {
62 var rest = this.slice((to || from) + 1 || this.length);
63 this.length = from < 0 ? this.length + from : from;
64 return this.push.apply(this, rest);
65 };
66}
67
68if (!Array.prototype.max) {
69 Array.prototype.max = function(array){
70 return Math.max.apply(Math, array);
71 };
72}
73
74String.prototype.repeat = function (times) {
75 'use strict';
76
77 var out = '', i;
78
79 for (i = 0; i < times; i++) {
80 out += this;
81 }
82
83 return out;
84};
85
86/**
87 * us.min([1, 2]);
88 *
89 * @deprecated
90 */
91if (!Array.prototype.min) {
92 Array.prototype.min = function(array) {
93 return Math.min.apply(Math, array );
94 };
95}
96
97if (!Array.prototype.clone) {
98 Array.prototype.clone = function() {
99 return this.slice(0);
100 };
101}
102
103/**
104 * Strip HTML-Tags from a String
105 */
106if (!String.prototype.strip) {
107 String.prototype.strip = function () {
108 return this.replace(/<(.|\n)*?>/g,'');
109 };
110}
111
112/**
113 * Needed for IE10 because it doesn't support group function
114 */
115
116if (window.console && !window.console.group) {
117 window.console.group = function(str) {
118 return str;
119 };
120
121 window.console.groupEnd = function() {
122 return '';
123 };
124}
125
126/**
127 * Checks if a string is lte than another string. If both strings start with a number, those values are parsed and then compared.
128 *
129 * @param y {String}
130 */
131
132if (!String.prototype.isLTE) {
133 String.prototype.isLTE = function (y) {
134 //parse
135 var x = this,
136 x_m = x.match(/^\s+\d+|^\d+/),
137 y_m = y.match(/^\s+\d+|^\d+/),
138 x_i = x_m !== null ? parseInt(x_m.shift(),10) : NaN,
139 y_i = y_m !== null ? parseInt(y_m.shift(),10) : NaN;
140 //compare strings if one isNaN
141 if (isNaN(x_i) || isNaN(y_i)) {
142 x = x.toLowerCase();
143 y = y.toLowerCase();
144 } else {
145 x = x_i;
146 y = y_i;
147 }
148 //compare
149 return x <= y;
150 };
151}
152
153/**
154 * Returns the Date as a short string
155 */
156if (!Date.prototype.toShortString) {
157 Date.prototype.toShortString = function(){
158 var h, m, d, mn;
159 h = ( ( h = this.getUTCHours()) < 10 ? '0' + h : h );
160 m = ( ( m = this.getUTCMinutes()) < 10 ? '0' + m : m );
161 d = ( ( d = this.getUTCDate()) < 10 ? '0' + d : d );
162 mn = ( ( mn = this.getUTCMonth()+1) < 10 ? '0' + mn : mn );
163 return ( h + ':' + m + ' ' + d + '/' + mn + '/' + this.getUTCFullYear());
164 };
165}
166
167function utf8Encode(string) {
168 string = string.replace(/\r\n/g, '\n');
169 var utftext = '',
170 n;
171
172 for (n = 0; n < string.length; n++) {
173
174 var c = string.charCodeAt(n);
175
176 if (c < 128) {
177 utftext += String.fromCharCode(c);
178 }
179 else if((c > 127) && (c < 2048)) {
180 utftext += String.fromCharCode((c >> 6) | 192);
181 utftext += String.fromCharCode((c & 63) | 128);
182 }
183 else {
184 utftext += String.fromCharCode((c >> 12) | 224);
185 utftext += String.fromCharCode(((c >> 6) & 63) | 128);
186 utftext += String.fromCharCode((c & 63) | 128);
187 }
188
189 }
190
191 return utftext;
192}
193
194//private method for UTF-8 decoding
195function utf8Decode(utftext) {
196 var string = '';
197 var i = 0,
198 c, c3, c2;
199 c = c3 = c2 = 0;
200
201 while ( i < utftext.length ) {
202 c = utftext.charCodeAt(i);
203
204 if (c < 128) {
205 string += String.fromCharCode(c);
206 i++;
207 } else if ((c > 191) && (c < 224)) {
208 c2 = utftext.charCodeAt(i+1);
209 string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
210 i += 2;
211 } else {
212 c2 = utftext.charCodeAt(i+1);
213 c3 = utftext.charCodeAt(i+2);
214 string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
215 i += 3;
216 }
217 }
218
219 return string;
220}
221
222/**
223 * base64 decode / encode
224 *
225 * Modern browser provide us with a btoa and atob function, but these do not support unicode
226 * @see https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/btoa
227 *
228 * for browsers with native btoa, we use a unicode alternative from the MDN
229 * else we mock with a custom function (that works with unicode as well)
230 *
231 * the original btoa and atob are provided via window.__btoa and __atob
232 */
233
234if (window.btoa && window.encodeURIComponent) {
235 window.__btoa = window.btoa;
236 window.btoa = function(str) {
237 return window.__btoa(window.unescape(encodeURIComponent(str)));
238 };
239} else {
240 window.btoa = function(str) {
241 var key_str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
242 output = '',
243 chr1, chr2, chr3, enc1, enc2, enc3, enc4,
244 i = 0;
245
246 str = utf8Encode(str);
247
248 while (i < str.length) {
249
250 chr1 = str.charCodeAt(i++);
251 chr2 = str.charCodeAt(i++);
252 chr3 = str.charCodeAt(i++);
253
254 enc1 = chr1 >> 2;
255 enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
256 enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
257 enc4 = chr3 & 63;
258
259 if (isNaN(chr2)) {
260 enc3 = enc4 = 64;
261 } else if (isNaN(chr3)) {
262 enc4 = 64;
263 }
264
265 output +=
266 key_str.charAt(enc1) + key_str.charAt(enc2) +
267 key_str.charAt(enc3) + key_str.charAt(enc4);
268
269 }
270
271 return output;
272 };
273}
274
275if (window.atob && window.decodeURIComponent) {
276 window.__atob = window.atob;
277 window.atob = function(str) {
278 return decodeURIComponent(window.escape(window.__atob(str)));
279 };
280} else {
281 window.atob = function(str) {
282 var key_str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
283 output = '',
284 i = 0,
285 len = str.length;
286
287 var enc1, enc2, enc3, enc4;
288
289 var chr1, chr2, chr3;
290
291 str = str.replace(/[^A-Za-z0-9\+\/\=]/g, '');
292
293 while(i < len){
294 enc1 = key_str.indexOf(str.charAt(i++));
295 enc2 = key_str.indexOf(str.charAt(i++));
296 enc3 = key_str.indexOf(str.charAt(i++));
297 enc4 = key_str.indexOf(str.charAt(i++));
298
299 chr1 = (enc1 << 2) | (enc2 >> 4);
300 chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
301 chr3 = ((enc3 & 3) << 6) | enc4;
302
303 output += String.fromCharCode(chr1);
304
305 if (enc3 !== 64) {
306 output = output + String.fromCharCode(chr2);
307 }
308 if (enc4 !== 64) {
309 output = output + String.fromCharCode(chr3);
310 }
311 }
312
313 return utf8Decode(output);
314 };
315}
316
317window.cycle = function() {
318 var toggle = false;
319
320 return function(a, b) {
321 return (toggle = !toggle) ? a : b;
322 };
323}();
324
325/**
326 * jQuery.blocker
327 *
328 * @param object options
329 * @return {self}
330 *
331 * minimum example:
332 * $.blocker({html: $('<div>This is an (visual) blocking note</div>')});
333 */
334jQuery.blocker = function(options){
335 var settings = jQuery.extend({
336 id: '', // rename to caching
337 caching: options.id,
338 html: 'f00',
339 width: 520,
340 height: 200,
341 gameloader: false,
342 bgClickable: true,
343 success: '',
344 cssClass : '',
345 onSuccess: function(){},
346 cancel: '',
347 onCancel: function(){},
348 callback: void(0)
349 }, options);
350
351 var block_wrapper = '<div class="gpwindow_frame ' + settings.cssClass + '" style="position: absolute; top: ' + (($(window).height() - settings.height) / 2) + 'px;' +
352 'left: ' + (($(window).width() - settings.width) / 2) + 'px; width: ' + settings.width + 'px; height: ' + settings.height + 'px; z-index: 5000; display: block;">' +
353 '<div class="gpwindow_left"></div><div class="gpwindow_right"></div><div class="gpwindow_bottom"><div class="gpwindow_left corner"></div><div class="gpwindow_right corner">'+
354 '</div></div><div class="gpwindow_top"><div class="gpwindow_left corner"></div><div class="gpwindow_right corner"></div></div>' +
355 '<div class="gpwindow_content"></div>';
356 var frame = $(block_wrapper);
357
358 var elm = {
359 box: frame,
360 bg: $('<div id="blockbox_bg"></div>'),
361 body: $('body')
362 };
363
364 elm.content = elm.box.find('div.gpwindow_content');
365
366 // show
367 this.blocker.block = function(){
368 //save location ... otherwise use document body
369 var tmp = settings.html.parent();
370 elm.original_parent = tmp.length ? tmp : $('body');
371
372 elm.html = settings.html.detach();
373 elm.content.append(elm.html.show());
374
375 elm.box.appendTo(elm.body).show();
376 elm.bg.appendTo(elm.body).show();
377 };
378
379 this.blocker.handleEvents = function(){
380 // set handlers
381 if(settings.bgClickable){
382 elm.bg.bind('click.block', function(){
383 jQuery.blocker.unblock();
384 });
385 }
386 // bind user defined functions to elements
387 $(settings.success).unbind('click').click(function(){
388 settings.onSuccess();
389 jQuery.blocker.unblock();
390 });
391 $(settings.cancel).unbind('click').click(function(){
392 settings.onCancel();
393 jQuery.blocker.unblock();
394 });
395 };
396
397 this.blocker.unblock = function(){
398 elm.box.hide().detach();
399 elm.bg.hide().detach();
400 if(settings.gameloader){
401 elm.html.remove();
402 } else {
403 elm.html.appendTo(elm.original_parent).hide();
404 }
405 if(settings.callback && typeof settings.callback === 'function'){
406 settings.callback();
407 }
408 };
409
410 this.blocker.block();
411 this.blocker.handleEvents();
412
413 return this.blocker;
414};
415
416/**
417 * @param controller String
418 * @param parameters Object
419 */
420function buildLink(controller, parameters) {
421 var params = [],
422 key;
423 for(key in parameters) {
424 params.push(key + '=' + escape(parameters[key]));
425 }
426 // always append the CSRF token
427 params.push('h=' + encodeURIComponent(Game.csrfToken));
428
429 return '/game/' + controller + '?' + params.join('&');
430}
431/**
432 * Generates a link from the given parameters.
433 *
434 * @param controller String
435 * @param action String
436 * @param parameters Object
437 * @return String
438 */
439function url(controller, action, parameters) {
440 if (controller && controller.substr(0, 1) === '/') {
441 return controller;
442 }
443 controller = controller || Game.controller;
444 parameters = parameters || {};
445
446 if (typeof action !== 'undefined' && action !== '') {
447 parameters.action = action;
448 }
449 parameters.town_id = parameters.town_id || Game.townId;
450
451 return buildLink(controller, parameters);
452}
453
454/**
455 * Transforms seconds to hours:minutes:seconds
456 * @param {number} duration in seconds
457 * @param {boolean} only_non_zero - if true, omits any leading 0s
458 * @return {String}
459 */
460function day_hr_min_sec(duration, only_non_zero, options) {
461 var result, days, hours, minutes, seconds, hours_str, minutes_str, seconds_str,
462 lang = {
463 days : __('day|d'),
464 hours : __('hour|h'),
465 minutes : __('minute|m'),
466 seconds : __('second|s')
467 };
468
469 function getSecondsHtml() {
470 return with_seconds ? (seconds_str + '<span>' + lang.seconds+'</span>') : '';
471 }
472
473 options = options || {};
474 var with_seconds = typeof options.with_seconds === "undefined" ? true : options.with_seconds;
475
476 if (duration === 0) {
477 return _('Now');
478 }
479
480 days = parseInt(duration / 86400, 10);
481 hours = parseInt((duration / 3600) % 24, 10);
482 minutes = parseInt((duration / 60) % 60, 10);
483 seconds = parseInt(duration % 60, 10);
484
485 //Days won't be displayed when == 0
486 result = days > 0 ? days + '<span>' + lang.days + '</span> ' : '';
487
488 //Add 0 at the beginning if number is smaller than 10
489 hours_str = (hours < 10) ? '0' + hours : hours;
490 minutes_str = (minutes < 10) ? '0' + minutes : minutes;
491 seconds_str = (seconds < 10) ? '0' + seconds : seconds;
492
493 if (only_non_zero) {
494 result += (hours > 0 || days > 0) ? (hours_str + '<span>' + lang.hours + '</span> ') : '';
495 result += (minutes > 0 || hours > 0 || days > 0) ? (minutes_str + '<span>' + lang.minutes + '</span> ') : '';
496 result += getSecondsHtml();
497 } else {
498 result += hours_str + '<span>' + lang.hours + '</span> ' +
499 minutes_str + '<span>' + lang.minutes + '</span> ' +
500 getSecondsHtml();
501 }
502
503 return result;
504}
505
506/**
507 * Transforms seconds to x hours (y minutes) (z seconds)
508 * primary used for inline texts e.g. in power effects
509 *
510 * @param {integer} duration
511 * @see TimeFormat.php formatTime()
512 * @return {string}
513 */
514function hours_minutes_seconds(duration) {
515 var weeks, days, hours, minutes, seconds,
516 str = [];
517
518 weeks = Math.floor(duration / 604800);
519 days = Math.floor((duration / 86400) % 7);
520 hours = Math.floor((duration / 3600) % 24);
521 minutes = Math.floor((duration / 60) % 60);
522 seconds = Math.floor(duration % 60);
523
524 if (weeks > 0) {
525 str.push(weeks + ' ' + ngettext('week', 'weeks', weeks));
526 }
527 if (days > 0) {
528 str.push(days + ' ' + ngettext('day', 'days', days));
529 }
530 if (hours > 0) {
531 str.push(hours + ' ' + ngettext('hour', 'hours', hours));
532 }
533 if (minutes > 0) {
534 str.push(minutes + ' ' + ngettext('minute', 'minutes', minutes));
535 }
536 if (seconds > 0) {
537 str.push(seconds + ' ' + ngettext('second', 'seconds', seconds));
538 }
539
540 return str.join(' ');
541}
542
543/**
544 * Transforms date object to readable format
545 *
546 * @param object date
547 * @param boolean date is utc
548 * @param boolean ext_date - flag if a date with day and month should be returned
549 * @param boolean with_seconds - flag if a date with seconds should be returned
550 * @return String
551 */
552function readableDate(date, utc, ext_date, with_seconds) {
553 return readableUnixTimestamp(date.getTime(), (utc === true ? 'no_offset' : 'player_timezone'), {extended_date: ext_date, with_seconds: with_seconds});
554}
555
556/**
557 * Transforms a unix timestamp to readable format
558 * This readable format could depend on a timezone:
559 * - player_timezone := player timezone from his settings (default)
560 * - lc_timezone := servers lc_timezone
561 * - no_offset := no timezone offset
562 *
563 * The options object can hold of those two boolean flags
564 * - extended_date := flag if a date with day and month should be returned
565 * - with_seconds := flag if a date with seconds should be returned
566 *
567 * @param {integer} timestamp
568 * @param {string} timezone_type
569 * @param {object} options
570 * @return String
571 */
572function readableUnixTimestamp(timestamp, timezone_type, options) {
573 options = options === undefined ? {} : options;
574
575 var with_seconds = options.with_seconds === undefined ? true : options.with_seconds;
576 var extended_date = options.extended_date === undefined ? false : options.extended_date;
577
578 var ts = (timezone_type === 'no_offset' ? timestamp : Timestamp.shiftUnixTimestampByTimezoneOffset(timestamp, timezone_type));
579
580 var date = new Date(ts * 1E3);
581 var hours, minutes, seconds, days, months;
582 var result;
583
584 hours = date.getUTCHours();
585 minutes = date.getUTCMinutes();
586 seconds = date.getUTCSeconds();
587
588 if (minutes < 10) {
589 minutes = '0' + minutes;
590 }
591
592 if (seconds < 10) {
593 seconds = '0' + seconds;
594 }
595
596 if (extended_date) {
597 days = date.getUTCDate();
598 months = date.getUTCMonth() + 1;
599
600 if (days < 10) {
601 days = '0' + days;
602 }
603
604 if (months < 10) {
605 months = '0' + months;
606 }
607
608 result = days + '.' + months + '.|' + hours + ':' + minutes + (with_seconds ? (':' + seconds) : '');
609 } else {
610 result = hours + ':' + minutes + (with_seconds ? (':' + seconds) : '');
611 }
612
613 return result;
614}
615/**
616 * Returns a ratio string from a given float.
617 * One side of the ratio is 1.
618 * The other side is always >=1.
619 * e.g.
620 * 1.2 -> '1.2':1
621 * 0.8 -> '1:1.2'
622 *
623 * @param {number} fraction
624 * @return {string}
625 */
626function readableRatio(fraction) {
627 var bigger_part = Math.round( (fraction >= 1 ? fraction : (1/fraction)) * 100 ) / 100;
628 if (fraction < 1) {
629 return '1:' + bigger_part;
630 } else {
631 return bigger_part + ':1';
632 }
633}
634
635// Translation tool, return s
636// Strings wrapped with this will get translated
637function _(s) {
638 if (DebugTranslations.isEnabled()) {
639 return DebugTranslations.markString(s);
640 }
641
642 return s;
643}
644
645// Translation tool, return s
646// Strings wrapped with this will NOT get translated
647function _literal(s) {
648 return s;
649}
650
651// context aware gettext alias - only used for dev
652// and fallback in case of missing translation
653function __(s) {
654 if (DebugTranslations.isEnabled()) {
655 return DebugTranslations.markString(s.substr(s.indexOf('|')+1));
656 }
657
658 return s.substr(s.indexOf('|')+1);
659}
660
661function s(text) {
662 var i;
663 if(!text){
664 return '';
665 }
666 for(i = 1; i<arguments.length; i++) {
667 text = text.split('%'+i).join(arguments[i]);
668 }
669
670 return text;
671}
672
673/**
674 * Debugging function.
675 *
676 * @param whatever Object
677 */
678function debug(whatever){
679 var i, s;
680 if(!Game.dev) {
681 return;
682 }
683 try {
684 if(arguments.length > 1){
685 console.group();
686 for(i = 0; i < arguments.length; i++){
687 console.debug(arguments[i]);
688 }
689 console.groupEnd();
690 } else {
691 console.log(whatever);
692 }
693 } catch(x){
694 try {
695 opera.postError(whatever);
696 } catch(y){
697 if ('object' === typeof(whatever)) {
698 s = '';
699 for (var i in whatever){
700 s += i + ': ' + whatever[i] + '\n';
701 }
702 alert(s);
703 } else {
704 alert(whatever);
705 }
706 }
707 }
708}
709
710jQuery.extend(jQuery.easing,{
711 // t: current time, b: beginning value, c: change in value, d: duration
712 bounce: function (x, t, b, c, d) {
713 if ((t/=d) < (1/2.75)) {
714 return c*(7.5625*t*t) + b;
715 } else if (t < (2/2.75)) {
716 return c*(7.5625*(t-=(1.5/2.75))*t + 0.75) + b;
717 } else if (t < (2.5/2.75)) {
718 return c*(7.5625*(t-=(2.25/2.75))*t + 0.9375) + b;
719 } else {
720 return c*(7.5625*(t-=(2.625/2.75))*t + 0.984375) + b;
721 }
722 }
723});
724
725/**
726 * @deprecated, use .bind()/.click()/.whatever() instead.
727 */
728function w(foo){
729 var elm = foo || this;
730 if (elm.wnd) {
731 return elm.wnd;
732 }
733
734 while (elm.parentNode &&
735 // if element has a tagName different from 'DIV', we skip it. This is mainly a workaround
736 // for <form>-elements, which cause serious problems when used with this function, e. g.:
737 // an error is thrown with something like: form#[object HTMLInputElement]
738 ((elm = elm.parentNode).tagName !== 'DIV' ? elm = elm.parentNode : elm).id.indexOf('gpwnd') === -1){
739 null;
740 }
741
742 var id = (~~elm.id.match(/\d+/));
743 elm.wnd = GPWindowMgr.GetByID(id);
744 return elm.wnd;
745}
746
747function attr(attribs){
748 var res = '',
749 att;
750
751 for (att in attribs) {
752 if (attribs[att]) {
753 res = res + ' ' + att + '="' + attribs[att] + '"';
754 }
755 }
756 return res;
757}
758
759/**
760 * function to create a button as string or jquery object
761 *
762 * @param string text
763 * @param object html_options
764 * @param boolean as_string (optional: default false)
765 * @return object either string or jquery object
766 */
767function button(text, html_options, as_string, wnd) {
768 var btn,
769 button_text,
770 callback,
771 css_class = 'button';
772
773 if (html_options.hasOwnProperty('class')) {
774 css_class += ' ' + html_options['class'];
775 }
776
777 if (!html_options.href) {
778 html_options.href = '#';
779 }
780 if ((callback = html_options.onclick) && typeof callback === 'function') {
781 // if we bind a function, we cannot return a string
782 as_string = false;
783 delete html_options.onclick;
784 }
785
786 html_options = attr(html_options);
787
788 button_text = '<a class="' + css_class + '" ' + html_options + '>'+
789 '<span class="left"><span class="right">'+
790 '<span class="middle">' + text + '</span>'+
791 '</span></span>'+
792 '<span style="clear:both;"></span>' +
793 '</a>';
794
795 if (!as_string) {
796 btn = $(button_text);
797 }
798 // if callback insted of onclick:
799 if (callback && !as_string) {
800 btn.click(function() {
801 callback.apply(wnd);
802 });
803 }
804
805 return as_string ? button_text : btn;
806}
807
808
809/**
810 * checks if argument is a number
811 *
812 * @param object val
813 * @return boolean
814 */
815function isNumber(val){
816 return typeof val === 'number' && isFinite(val);
817}
818
819/**
820 * checks if argument is an array
821 *
822 * @param {object} o
823 * @return boolean
824 */
825function is_array(o){
826 return typeof o === 'object' && (o instanceof Array);
827}
828
829/**
830 * adds slashes to string
831 *
832 * @param str string
833 * @return string
834 */
835function addslashes(str) {
836 var result = str || '';
837 result = result.replace(/\\/g,'\\\\');
838 result = result.replace(/\'/g,'\\\'');
839 result = result.replace(/\"/g,'\\"');
840 result = result.replace(/\0/g,'\\0');
841 return result;
842}
843
844/**
845 * get human readable time and date format: hours:minutes:seconds day/month/year
846 */
847function getHumanReadableTimeDate(time) {
848 // http://www.hunlock.com/blogs/Javascript_Dates-The_Complete_Reference
849 var hours = time.getUTCHours(),
850 minutes = time.getUTCMinutes(),
851 seconds = time.getUTCSeconds(),
852 day = time.getUTCDate(),
853 month = time.getUTCMonth() + 1,
854 year = time.getUTCFullYear();
855
856 if (hours < 10) {
857 hours = '0' + hours;
858 }
859 if (minutes < 10) {
860 minutes = '0' + minutes;
861 }
862 if (seconds < 10) {
863 seconds = '0' + seconds;
864 }
865 if (day < 10) {
866 day = '0' + day;
867 }
868 if (month < 10) {
869 month = '0' + month;
870 }
871 return hours + ':' + minutes + ':' + seconds + ' ' + day + '/' + month + '/' + year;
872}
873
874/**
875 * get human readable date format: day/month/year
876 */
877function getHumanReadableDate(timestamp) {
878 var day = timestamp.getUTCDate(),
879 month = timestamp.getUTCMonth() + 1,
880 year = timestamp.getUTCFullYear();
881
882 if (day < 10) {
883 day = '0' + day;
884 }
885 if (month < 10) {
886 month = '0' + month;
887 }
888 return day + '/' + month + '/' + year;
889}
890
891
892/**
893 * creates a date object by the date arguments supplied. A concrete timezone
894 * for the date supplied can be specified
895 *
896 * @param {integer} year
897 * @param {integer} month
898 * @param {integer} day
899 * @param {integer} hours
900 * @param {integer} minutes
901 * @param {integer} seconds
902 * @param {integer} timezone_offset (in seconds) GMT +02 = 7200
903 *
904 * @return object date
905 */
906function newDateByTimezone(year, month, day, hours, minutes, seconds, timezone_offset){
907 var date = new Date();
908 date.setUTCFullYear(year, month, day);
909 date.setUTCHours(hours, minutes, seconds - timezone_offset);
910
911 return date;
912}
913
914
915/**
916 * Only used in wndhandler_attack and building_place
917 * TODO: get rid of this method in common.js
918 * @param units Array
919 * @param data Object unit data for current town
920 * @param pop jQuery element for display of selected population
921 * @param cap jQuery element for display of total capacity
922 * @param progbar jQuery progressbar element
923 */
924function recalcCapacity(units, researches, pop, cap, progbar, slow_boats_needed, fast_boats_needed) {
925 researches = researches || 0;
926
927 var q, progress,
928 total_capacity = 0,
929 total_population = 0,
930 // TODO!
931 pwidth = 460,
932 berth = researches.berth || 0,
933 gdunits = GameData.units,
934 big_transporter_cap = gdunits.big_transporter.capacity,
935 small_transporter_cap = gdunits.small_transporter.capacity;
936
937 units.each(function() {
938 var i = this.name,
939 count = ~~this.value,
940 cap;
941
942 // don't do anything if 0 or a flying unit
943 if (count && !gdunits[i].flying) {
944 if (gdunits[i].is_naval) {
945 // add research-modifiers if they exist
946 total_capacity += ((cap = gdunits[i].capacity) + (cap ? researches.berth || 0 : 0)) * count;
947 } else {
948 total_population += gdunits[i].population * count;
949 }
950 }
951 });
952
953 q = total_population / total_capacity;
954 progress = ( q >= 1 || !total_capacity) ? 0 : (1 - q) * -pwidth;
955
956 pop.text(total_population);
957 cap.text(total_capacity);
958 progbar.stop(true,true).animate({backgroundPosition: progress + 'px'});
959
960 if (slow_boats_needed) {
961 slow_boats_needed.text(Math.ceil(total_population / (big_transporter_cap + berth)));
962 }
963
964 if (fast_boats_needed) {
965 fast_boats_needed.text(Math.ceil(total_population / (small_transporter_cap + berth)));
966 }
967
968 return {
969 'capacity': total_capacity,
970 'population': total_population
971 };
972}
973
974function isSomeThing(checks, anyOrAll) {
975 var isSomeThing, uagent;
976
977 return function() {
978 var check_idx, checks_length = checks.length, check;
979
980 if (typeof isSomeThing === 'boolean') {
981 return isSomeThing;
982 }
983 else {
984 uagent = navigator.userAgent.toLowerCase();
985 if (anyOrAll === 'any') {
986 for (check_idx = 0; check_idx < checks_length; ++check_idx) {
987 check = checks[check_idx];
988 isSomeThing = isSomeThing || uagent.indexOf(check) > -1;
989 }
990 } else {
991 isSomeThing = true;
992 for (check_idx = 0; check_idx < checks_length; ++check_idx) {
993 check = checks[check_idx];
994 isSomeThing = isSomeThing && uagent.indexOf(check) > -1;
995 }
996 }
997
998 return isSomeThing;
999 }
1000 };
1001}
1002
1003var isiOs = isSomeThing(['iphone', 'ipad', 'ipod'], 'any');
1004var isIeTouch = isSomeThing(['touch', 'trident', 'tablet'], 'all');
1005var isMsApp = isSomeThing(['trident', 'msapphost'], 'all');
1006
1007function isSmallScreen() {
1008
1009 if (isSmallScreen.result) {
1010 return isSmallScreen.result;
1011 }
1012
1013 if ($(window).height() <= 800) {
1014 isSmallScreen.result = true;
1015 } else {
1016 isSmallScreen.result = false;
1017 }
1018
1019 return isSmallScreen.result;
1020}
1021
1022/**
1023 * Generated a DWORD (32-bit-integer) from 2 WORDS (16 bit ints)
1024 *
1025 * @param {Number} lo Lower Half WORD
1026 * @param {Number} hi Higher Half WORD
1027 * @return {Number} generated DWORD
1028 */
1029function makeDWord(lo, hi) {
1030 return (lo & 0xffff ) | ((hi & 0xffff) << 16);
1031}
1032
1033var Sort = {
1034 sort_by: null,
1035
1036 /**
1037 * Setter method for the values by which the list should be sorted
1038 *
1039 * @param {String} string classname of any sortable value
1040 */
1041 sortBy: function (string) {
1042 this.sort_by = string;
1043 },
1044 /**
1045 * checks if list is already sorted by a value
1046 */
1047 sortedBy: function(string) {
1048 return this.sort_by === string;
1049 },
1050
1051 /**
1052 * Sorts an array via quicksort. sortBy() must be used first.
1053 *
1054 * @param {Array} array The array which has to be sorted
1055 */
1056 qsort: function(array) {
1057 var greater = [], less = [];
1058 if (!array || array.length <= 1) {
1059 return array;
1060 }
1061 else {
1062 var index = Math.floor(Math.random() * (array.length - 1));
1063 var pivot = array[index], i;
1064 array.splice(index,1);
1065 for (i = 0; i < array.length; i++) {
1066 var obj = array[i];
1067 var x = $(obj).find('span.sortable.' + this.sort_by).text();
1068 var y = $(pivot).find('span.sortable.' + this.sort_by).text();
1069
1070 if (x.isLTE(y)) {
1071 less.push(obj);
1072 }
1073 else {
1074 greater.push(obj);
1075 }
1076 }
1077 return (this.qsort(less).concat(pivot)).concat(this.qsort(greater));
1078 }
1079 }
1080};
1081
1082/**
1083 * change digit to roman numerals (up to 4000)
1084 */
1085function romanNumerals(val) {
1086 var values = [1000, 900, 500, 400, 100,90, 50, 40, 10, 9, 5, 4, 1],
1087 numerals = ['min', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I'],
1088 vl = values.length,
1089 i = 0,
1090 rest = val,
1091 return_val = '';
1092
1093 for (; i < vl; ++i) {
1094 while (rest > values[i]) {
1095 rest -= values[i];
1096 return_val += numerals[i];
1097 }
1098 }
1099
1100 return return_val;
1101}
1102
1103if (!window.getComputedStyle) {
1104 window.getIEComputedStyle = function(el, pseudo) {
1105 this.el = el;
1106 this.getPropertyValue = function(prop) {
1107 var re = /(\-([a-z]){1})/g;
1108 if (prop === 'float') {
1109 prop = 'styleFloat';
1110 }
1111 if (re.test(prop)) {
1112 prop = prop.replace(re, function () {
1113 return arguments[2].toUpperCase();
1114 });
1115 }
1116 return el.currentStyle[prop] ? el.currentStyle[prop] : null;
1117 };
1118 this.cssText = function() {
1119 if(!el) {
1120 return false;
1121 }
1122 var elStyle = el.currentStyle,
1123 cssText = '';
1124 for (var k in elStyle) { cssText += k + ':' + elStyle[k] + ';';}
1125 return cssText.replace(/([A-Z])/g, function($1){return '-'+$1.toLowerCase();});
1126 }();
1127
1128 return this;
1129 };
1130}
1131
1132function difference(template, override) {
1133 var ret = {};
1134 for (var name in template) {
1135 if (template.hasOwnProperty(name) && name in override) {
1136 if (us.isObject(override[name]) && !us.isArray(override[name])) {
1137 var diff = difference(template[name], override[name]);
1138 if (!us.isEmpty(diff)) {
1139 ret[name] = diff;
1140 }
1141 } else if (!us.isEqual(template[name], override[name])) {
1142 ret[name] = override[name];
1143 }
1144 }
1145 }
1146 return ret;
1147}
1148
1149/**
1150 * Shortens a string exceeding the total_max_length with an ellipsis (3 dots)
1151 * total_max_length includes the length of the ellipsis!
1152 *
1153 * @param {String} string_to_shorten
1154 * @param {number} total_max_length
1155 */
1156function ellipsis(string_to_shorten, total_max_length) {
1157 if (string_to_shorten.length <= total_max_length) {
1158 return string_to_shorten;
1159 }
1160
1161 return string_to_shorten.slice(0, total_max_length - 1) + '\u2026';
1162}
1163/**
1164 * Wrapper for the native number.toLocaleString() in order to make sure the game locale is used.
1165 * @param number
1166 * @return {string|*}
1167 */
1168function numberToLocaleString(number) {
1169 var toLocaleStringSupportsLocales = function() {
1170 var test_number = 0;
1171 try {
1172 test_number.toLocaleString('i');
1173 } catch (e) {
1174 return e.name === 'RangeError';
1175 }
1176 return false;
1177 },
1178 locale = Game.locale_lang.replace('_','-');
1179
1180 return toLocaleStringSupportsLocales() ?
1181 number.toLocaleString(locale) :
1182 number;
1183}
1184
1185(function($) {
1186 'use strict';
1187
1188 /**
1189 * When element is hidden with display:none; we can not get its proper size
1190 * in this case, we have to show him for a while somewhere out of the viewport,
1191 * take the size, and restore values
1192 */
1193 $.fn.hiddenOuterWidth = function(include_margin) {
1194 include_margin = include_margin ? true : false;
1195
1196 var $el = $(this),
1197 old_styles = {
1198 left : $el.css('left'),
1199 display : $el.css('display'),
1200 position : $el.css('position'),
1201 visibility : $el.css('visibility'),
1202 width : $el.css('width')
1203 }, outer_width;
1204
1205 $el.css({left : 300, display : 'block', position : 'absolute', visibility: 'visible'});
1206
1207 outer_width = $el.outerWidth(include_margin);
1208
1209 $el.css(old_styles);
1210
1211 return outer_width;
1212 };
1213
1214 /**
1215 *
1216 */
1217 $.fn.setOffsetWidth = function(value) {
1218 var $el = $(this),
1219 margins_width = $el.outerWidth(true) - $el.outerWidth();
1220
1221 $el.width(value - margins_width);
1222
1223 return this;
1224 };
1225}(jQuery));