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