Raw Source
Adrriii / Die2Nite Enhancer

// ==UserScript==
//
// @name Die2Nite Enhancer
// @version 0.2.4-beta33
// @description Die2Nite Enhancer improves your game experience on Die2Nite.
// @author Aymeric Beaumet <aymeric@beaumet.me>
// @license Zlib; http://opensource.org/licenses/Zlib
// @icon http://www.die2nite.com/gfx/forum/smiley/h_city_up.gif
// @downloadURL https://d2ne.datw.tf/beta.d2ne.user.js
// @updateURL https://d2ne.datw.tf/beta.d2ne.user.js
// @require https://code.jquery.com/jquery-3.3.1.min.js
// @require https://code.jquery.com/ui/1.12.1/jquery-ui.min.js
//
// @match http://www.die2nite.com/*
// @match http://www.hordes.fr/*
// @match http://www.zombinoia.com/*
// @match http://www.dieverdammten.de/*
//
// @grant GM_xmlhttpRequest
// @match http://chatty.d2ne.beaumet.me/*
// @exclude http://chatty.d2ne.beaumet.me/*
// @connect http://chatty.d2ne.beaumet.me/*
// @connect chatty.d2ne.beaumet.me
// @match http://d2n.duskdawn.net/*
// @exclude http://d2n.duskdawn.net/*
// @connect http://d2n.duskdawn.net/*
// @connect d2n.duskdawn.net
// @match http://die2nite.gamerz.org.uk/*
// @exclude http://die2nite.gamerz.org.uk/*
// @connect http://die2nite.gamerz.org.uk/*
// @connect die2nite.gamerz.org.uk
// @match http://bbh.fred26.fr/*
// @exclude http://bbh.fred26.fr/*
// @connect http://bbh.fred26.fr/*
// @connect bbh.fred26.fr
// @match http://hordes-la-loi.fr/*
// @exclude http://hordes-la-loi.fr/*
// @connect http://hordes-la-loi.fr/*
// @connect hordes-la-loi.fr
// @match http://gest-hordes.eragaming.fr/*
// @exclude http://gest-hordes.eragaming.fr/*
// @connect http://gest-hordes.eragaming.fr/*
// @connect gest-hordes.eragaming.fr
// @match http://data.zenoo.fr/*
// @exclude http://data.zenoo.fr/*
// @connect http://data.zenoo.fr/*
// @connect data.zenoo.fr
// @match http://qaz.wtf/*
// @exclude http://qaz.wtf/*
// @connect http://qaz.wtf/*
// @connect qaz.wtf
//
// ==/UserScript==

/* jshint ignore:start */
/*!
 * Socket.IO v2.1.1
 * (c) 2014-2018 Guillermo Rauch
 * Released under the MIT License.
 */
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.io=e():t.io=e()}(this,function(){return function(t){function e(n){if(r[n])return r[n].exports;var o=r[n]={exports:{},id:n,loaded:!1};return t[n].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var r={};return e.m=t,e.c=r,e.p="",e(0)}([function(t,e,r){"use strict";function n(t,e){"object"===("undefined"==typeof t?"undefined":o(t))&&(e=t,t=void 0),e=e||{};var r,n=i(t),s=n.source,h=n.id,p=n.path,u=c[h]&&p in c[h].nsps,f=e.forceNew||e["force new connection"]||!1===e.multiplex||u;return f?r=a(s,e):(c[h]||(c[h]=a(s,e)),r=c[h]),n.query&&!e.query&&(e.query=n.query),r.socket(n.path,e)}var o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=r(1),s=r(4),a=r(9);r(3)("socket.io-client");t.exports=e=n;var c=e.managers={};e.protocol=s.protocol,e.connect=n,e.Manager=r(9),e.Socket=r(34)},function(t,e,r){(function(e){"use strict";function n(t,r){var n=t;r=r||e.location,null==t&&(t=r.protocol+"//"+r.host),"string"==typeof t&&("/"===t.charAt(0)&&(t="/"===t.charAt(1)?r.protocol+t:r.host+t),/^(https?|wss?):\/\//.test(t)||(t="undefined"!=typeof r?r.protocol+"//"+t:"https://"+t),n=o(t)),n.port||(/^(http|ws)$/.test(n.protocol)?n.port="80":/^(http|ws)s$/.test(n.protocol)&&(n.port="443")),n.path=n.path||"/";var i=n.host.indexOf(":")!==-1,s=i?"["+n.host+"]":n.host;return n.id=n.protocol+"://"+s+":"+n.port,n.href=n.protocol+"://"+s+(r&&r.port===n.port?"":":"+n.port),n}var o=r(2);r(3)("socket.io-client:url");t.exports=n}).call(e,function(){return this}())},function(t,e){var r=/^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,n=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];t.exports=function(t){var e=t,o=t.indexOf("["),i=t.indexOf("]");o!=-1&&i!=-1&&(t=t.substring(0,o)+t.substring(o,i).replace(/:/g,";")+t.substring(i,t.length));for(var s=r.exec(t||""),a={},c=14;c--;)a[n[c]]=s[c]||"";return o!=-1&&i!=-1&&(a.source=e,a.host=a.host.substring(1,a.host.length-1).replace(/;/g,":"),a.authority=a.authority.replace("[","").replace("]","").replace(/;/g,":"),a.ipv6uri=!0),a}},function(t,e){"use strict";t.exports=function(){return function(){}}},function(t,e,r){function n(){}function o(t){var r=""+t.type;if(e.BINARY_EVENT!==t.type&&e.BINARY_ACK!==t.type||(r+=t.attachments+"-"),t.nsp&&"/"!==t.nsp&&(r+=t.nsp+","),null!=t.id&&(r+=t.id),null!=t.data){var n=i(t.data);if(n===!1)return m;r+=n}return r}function i(t){try{return JSON.stringify(t)}catch(t){return!1}}function s(t,e){function r(t){var r=l.deconstructPacket(t),n=o(r.packet),i=r.buffers;i.unshift(n),e(i)}l.removeBlobs(t,r)}function a(){this.reconstructor=null}function c(t){var r=0,n={type:Number(t.charAt(0))};if(null==e.types[n.type])return u("unknown packet type "+n.type);if(e.BINARY_EVENT===n.type||e.BINARY_ACK===n.type){for(var o="";"-"!==t.charAt(++r)&&(o+=t.charAt(r),r!=t.length););if(o!=Number(o)||"-"!==t.charAt(r))throw new Error("Illegal attachments");n.attachments=Number(o)}if("/"===t.charAt(r+1))for(n.nsp="";++r;){var i=t.charAt(r);if(","===i)break;if(n.nsp+=i,r===t.length)break}else n.nsp="/";var s=t.charAt(r+1);if(""!==s&&Number(s)==s){for(n.id="";++r;){var i=t.charAt(r);if(null==i||Number(i)!=i){--r;break}if(n.id+=t.charAt(r),r===t.length)break}n.id=Number(n.id)}if(t.charAt(++r)){var a=h(t.substr(r)),c=a!==!1&&(n.type===e.ERROR||d(a));if(!c)return u("invalid payload");n.data=a}return n}function h(t){try{return JSON.parse(t)}catch(t){return!1}}function p(t){this.reconPack=t,this.buffers=[]}function u(t){return{type:e.ERROR,data:"parser error: "+t}}var f=(r(3)("socket.io-parser"),r(5)),l=r(6),d=r(7),y=r(8);e.protocol=4,e.types=["CONNECT","DISCONNECT","EVENT","ACK","ERROR","BINARY_EVENT","BINARY_ACK"],e.CONNECT=0,e.DISCONNECT=1,e.EVENT=2,e.ACK=3,e.ERROR=4,e.BINARY_EVENT=5,e.BINARY_ACK=6,e.Encoder=n,e.Decoder=a;var m=e.ERROR+'"encode error"';n.prototype.encode=function(t,r){if(e.BINARY_EVENT===t.type||e.BINARY_ACK===t.type)s(t,r);else{var n=o(t);r([n])}},f(a.prototype),a.prototype.add=function(t){var r;if("string"==typeof t)r=c(t),e.BINARY_EVENT===r.type||e.BINARY_ACK===r.type?(this.reconstructor=new p(r),0===this.reconstructor.reconPack.attachments&&this.emit("decoded",r)):this.emit("decoded",r);else{if(!y(t)&&!t.base64)throw new Error("Unknown type: "+t);if(!this.reconstructor)throw new Error("got binary data when not reconstructing a packet");r=this.reconstructor.takeBinaryData(t),r&&(this.reconstructor=null,this.emit("decoded",r))}},a.prototype.destroy=function(){this.reconstructor&&this.reconstructor.finishedReconstruction()},p.prototype.takeBinaryData=function(t){if(this.buffers.push(t),this.buffers.length===this.reconPack.attachments){var e=l.reconstructPacket(this.reconPack,this.buffers);return this.finishedReconstruction(),e}return null},p.prototype.finishedReconstruction=function(){this.reconPack=null,this.buffers=[]}},function(t,e,r){function n(t){if(t)return o(t)}function o(t){for(var e in n.prototype)t[e]=n.prototype[e];return t}t.exports=n,n.prototype.on=n.prototype.addEventListener=function(t,e){return this._callbacks=this._callbacks||{},(this._callbacks["$"+t]=this._callbacks["$"+t]||[]).push(e),this},n.prototype.once=function(t,e){function r(){this.off(t,r),e.apply(this,arguments)}return r.fn=e,this.on(t,r),this},n.prototype.off=n.prototype.removeListener=n.prototype.removeAllListeners=n.prototype.removeEventListener=function(t,e){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var r=this._callbacks["$"+t];if(!r)return this;if(1==arguments.length)return delete this._callbacks["$"+t],this;for(var n,o=0;o<r.length;o++)if(n=r[o],n===e||n.fn===e){r.splice(o,1);break}return this},n.prototype.emit=function(t){this._callbacks=this._callbacks||{};var e=[].slice.call(arguments,1),r=this._callbacks["$"+t];if(r){r=r.slice(0);for(var n=0,o=r.length;n<o;++n)r[n].apply(this,e)}return this},n.prototype.listeners=function(t){return this._callbacks=this._callbacks||{},this._callbacks["$"+t]||[]},n.prototype.hasListeners=function(t){return!!this.listeners(t).length}},function(t,e,r){(function(t){function n(t,e){if(!t)return t;if(s(t)){var r={_placeholder:!0,num:e.length};return e.push(t),r}if(i(t)){for(var o=new Array(t.length),a=0;a<t.length;a++)o[a]=n(t[a],e);return o}if("object"==typeof t&&!(t instanceof Date)){var o={};for(var c in t)o[c]=n(t[c],e);return o}return t}function o(t,e){if(!t)return t;if(t&&t._placeholder)return e[t.num];if(i(t))for(var r=0;r<t.length;r++)t[r]=o(t[r],e);else if("object"==typeof t)for(var n in t)t[n]=o(t[n],e);return t}var i=r(7),s=r(8),a=Object.prototype.toString,c="function"==typeof t.Blob||"[object BlobConstructor]"===a.call(t.Blob),h="function"==typeof t.File||"[object FileConstructor]"===a.call(t.File);e.deconstructPacket=function(t){var e=[],r=t.data,o=t;return o.data=n(r,e),o.attachments=e.length,{packet:o,buffers:e}},e.reconstructPacket=function(t,e){return t.data=o(t.data,e),t.attachments=void 0,t},e.removeBlobs=function(t,e){function r(t,a,p){if(!t)return t;if(c&&t instanceof Blob||h&&t instanceof File){n++;var u=new FileReader;u.onload=function(){p?p[a]=this.result:o=this.result,--n||e(o)},u.readAsArrayBuffer(t)}else if(i(t))for(var f=0;f<t.length;f++)r(t[f],f,t);else if("object"==typeof t&&!s(t))for(var l in t)r(t[l],l,t)}var n=0,o=t;r(o),n||e(o)}}).call(e,function(){return this}())},function(t,e){var r={}.toString;t.exports=Array.isArray||function(t){return"[object Array]"==r.call(t)}},function(t,e){(function(e){function r(t){return n&&e.Buffer.isBuffer(t)||o&&(t instanceof e.ArrayBuffer||i(t))}t.exports=r;var n="function"==typeof e.Buffer&&"function"==typeof e.Buffer.isBuffer,o="function"==typeof e.ArrayBuffer,i=function(){return o&&"function"==typeof e.ArrayBuffer.isView?e.ArrayBuffer.isView:function(t){return t.buffer instanceof e.ArrayBuffer}}()}).call(e,function(){return this}())},function(t,e,r){"use strict";function n(t,e){if(!(this instanceof n))return new n(t,e);t&&"object"===("undefined"==typeof t?"undefined":o(t))&&(e=t,t=void 0),e=e||{},e.path=e.path||"/socket.io",this.nsps={},this.subs=[],this.opts=e,this.reconnection(e.reconnection!==!1),this.reconnectionAttempts(e.reconnectionAttempts||1/0),this.reconnectionDelay(e.reconnectionDelay||1e3),this.reconnectionDelayMax(e.reconnectionDelayMax||5e3),this.randomizationFactor(e.randomizationFactor||.5),this.backoff=new f({min:this.reconnectionDelay(),max:this.reconnectionDelayMax(),jitter:this.randomizationFactor()}),this.timeout(null==e.timeout?2e4:e.timeout),this.readyState="closed",this.uri=t,this.connecting=[],this.lastPing=null,this.encoding=!1,this.packetBuffer=[];var r=e.parser||c;this.encoder=new r.Encoder,this.decoder=new r.Decoder,this.autoConnect=e.autoConnect!==!1,this.autoConnect&&this.open()}var o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=r(10),s=r(34),a=r(5),c=r(4),h=r(36),p=r(37),u=(r(3)("socket.io-client:manager"),r(33)),f=r(38),l=Object.prototype.hasOwnProperty;t.exports=n,n.prototype.emitAll=function(){this.emit.apply(this,arguments);for(var t in this.nsps)l.call(this.nsps,t)&&this.nsps[t].emit.apply(this.nsps[t],arguments)},n.prototype.updateSocketIds=function(){for(var t in this.nsps)l.call(this.nsps,t)&&(this.nsps[t].id=this.generateId(t))},n.prototype.generateId=function(t){return("/"===t?"":t+"#")+this.engine.id},a(n.prototype),n.prototype.reconnection=function(t){return arguments.length?(this._reconnection=!!t,this):this._reconnection},n.prototype.reconnectionAttempts=function(t){return arguments.length?(this._reconnectionAttempts=t,this):this._reconnectionAttempts},n.prototype.reconnectionDelay=function(t){return arguments.length?(this._reconnectionDelay=t,this.backoff&&this.backoff.setMin(t),this):this._reconnectionDelay},n.prototype.randomizationFactor=function(t){return arguments.length?(this._randomizationFactor=t,this.backoff&&this.backoff.setJitter(t),this):this._randomizationFactor},n.prototype.reconnectionDelayMax=function(t){return arguments.length?(this._reconnectionDelayMax=t,this.backoff&&this.backoff.setMax(t),this):this._reconnectionDelayMax},n.prototype.timeout=function(t){return arguments.length?(this._timeout=t,this):this._timeout},n.prototype.maybeReconnectOnOpen=function(){!this.reconnecting&&this._reconnection&&0===this.backoff.attempts&&this.reconnect()},n.prototype.open=n.prototype.connect=function(t,e){if(~this.readyState.indexOf("open"))return this;this.engine=i(this.uri,this.opts);var r=this.engine,n=this;this.readyState="opening",this.skipReconnect=!1;var o=h(r,"open",function(){n.onopen(),t&&t()}),s=h(r,"error",function(e){if(n.cleanup(),n.readyState="closed",n.emitAll("connect_error",e),t){var r=new Error("Connection error");r.data=e,t(r)}else n.maybeReconnectOnOpen()});if(!1!==this._timeout){var a=this._timeout,c=setTimeout(function(){o.destroy(),r.close(),r.emit("error","timeout"),n.emitAll("connect_timeout",a)},a);this.subs.push({destroy:function(){clearTimeout(c)}})}return this.subs.push(o),this.subs.push(s),this},n.prototype.onopen=function(){this.cleanup(),this.readyState="open",this.emit("open");var t=this.engine;this.subs.push(h(t,"data",p(this,"ondata"))),this.subs.push(h(t,"ping",p(this,"onping"))),this.subs.push(h(t,"pong",p(this,"onpong"))),this.subs.push(h(t,"error",p(this,"onerror"))),this.subs.push(h(t,"close",p(this,"onclose"))),this.subs.push(h(this.decoder,"decoded",p(this,"ondecoded")))},n.prototype.onping=function(){this.lastPing=new Date,this.emitAll("ping")},n.prototype.onpong=function(){this.emitAll("pong",new Date-this.lastPing)},n.prototype.ondata=function(t){this.decoder.add(t)},n.prototype.ondecoded=function(t){this.emit("packet",t)},n.prototype.onerror=function(t){this.emitAll("error",t)},n.prototype.socket=function(t,e){function r(){~u(o.connecting,n)||o.connecting.push(n)}var n=this.nsps[t];if(!n){n=new s(this,t,e),this.nsps[t]=n;var o=this;n.on("connecting",r),n.on("connect",function(){n.id=o.generateId(t)}),this.autoConnect&&r()}return n},n.prototype.destroy=function(t){var e=u(this.connecting,t);~e&&this.connecting.splice(e,1),this.connecting.length||this.close()},n.prototype.packet=function(t){var e=this;t.query&&0===t.type&&(t.nsp+="?"+t.query),e.encoding?e.packetBuffer.push(t):(e.encoding=!0,this.encoder.encode(t,function(r){for(var n=0;n<r.length;n++)e.engine.write(r[n],t.options);e.encoding=!1,e.processPacketQueue()}))},n.prototype.processPacketQueue=function(){if(this.packetBuffer.length>0&&!this.encoding){var t=this.packetBuffer.shift();this.packet(t)}},n.prototype.cleanup=function(){for(var t=this.subs.length,e=0;e<t;e++){var r=this.subs.shift();r.destroy()}this.packetBuffer=[],this.encoding=!1,this.lastPing=null,this.decoder.destroy()},n.prototype.close=n.prototype.disconnect=function(){this.skipReconnect=!0,this.reconnecting=!1,"opening"===this.readyState&&this.cleanup(),this.backoff.reset(),this.readyState="closed",this.engine&&this.engine.close()},n.prototype.onclose=function(t){this.cleanup(),this.backoff.reset(),this.readyState="closed",this.emit("close",t),this._reconnection&&!this.skipReconnect&&this.reconnect()},n.prototype.reconnect=function(){if(this.reconnecting||this.skipReconnect)return this;var t=this;if(this.backoff.attempts>=this._reconnectionAttempts)this.backoff.reset(),this.emitAll("reconnect_failed"),this.reconnecting=!1;else{var e=this.backoff.duration();this.reconnecting=!0;var r=setTimeout(function(){t.skipReconnect||(t.emitAll("reconnect_attempt",t.backoff.attempts),t.emitAll("reconnecting",t.backoff.attempts),t.skipReconnect||t.open(function(e){e?(t.reconnecting=!1,t.reconnect(),t.emitAll("reconnect_error",e.data)):t.onreconnect()}))},e);this.subs.push({destroy:function(){clearTimeout(r)}})}},n.prototype.onreconnect=function(){var t=this.backoff.attempts;this.reconnecting=!1,this.backoff.reset(),this.updateSocketIds(),this.emitAll("reconnect",t)}},function(t,e,r){t.exports=r(11),t.exports.parser=r(18)},function(t,e,r){(function(e){function n(t,r){if(!(this instanceof n))return new n(t,r);r=r||{},t&&"object"==typeof t&&(r=t,t=null),t?(t=h(t),r.hostname=t.host,r.secure="https"===t.protocol||"wss"===t.protocol,r.port=t.port,t.query&&(r.query=t.query)):r.host&&(r.hostname=h(r.host).host),this.secure=null!=r.secure?r.secure:e.location&&"https:"===location.protocol,r.hostname&&!r.port&&(r.port=this.secure?"443":"80"),this.agent=r.agent||!1,this.hostname=r.hostname||(e.location?location.hostname:"localhost"),this.port=r.port||(e.location&&location.port?location.port:this.secure?443:80),this.query=r.query||{},"string"==typeof this.query&&(this.query=p.decode(this.query)),this.upgrade=!1!==r.upgrade,this.path=(r.path||"/engine.io").replace(/\/$/,"")+"/",this.forceJSONP=!!r.forceJSONP,this.jsonp=!1!==r.jsonp,this.forceBase64=!!r.forceBase64,this.enablesXDR=!!r.enablesXDR,this.timestampParam=r.timestampParam||"t",this.timestampRequests=r.timestampRequests,this.transports=r.transports||["polling","websocket"],this.transportOptions=r.transportOptions||{},this.readyState="",this.writeBuffer=[],this.prevBufferLen=0,this.policyPort=r.policyPort||843,this.rememberUpgrade=r.rememberUpgrade||!1,this.binaryType=null,this.onlyBinaryUpgrades=r.onlyBinaryUpgrades,this.perMessageDeflate=!1!==r.perMessageDeflate&&(r.perMessageDeflate||{}),!0===this.perMessageDeflate&&(this.perMessageDeflate={}),this.perMessageDeflate&&null==this.perMessageDeflate.threshold&&(this.perMessageDeflate.threshold=1024),this.pfx=r.pfx||null,this.key=r.key||null,this.passphrase=r.passphrase||null,this.cert=r.cert||null,this.ca=r.ca||null,this.ciphers=r.ciphers||null,this.rejectUnauthorized=void 0===r.rejectUnauthorized||r.rejectUnauthorized,this.forceNode=!!r.forceNode;var o="object"==typeof e&&e;o.global===o&&(r.extraHeaders&&Object.keys(r.extraHeaders).length>0&&(this.extraHeaders=r.extraHeaders),r.localAddress&&(this.localAddress=r.localAddress)),this.id=null,this.upgrades=null,this.pingInterval=null,this.pingTimeout=null,this.pingIntervalTimer=null,this.pingTimeoutTimer=null,this.open()}function o(t){var e={};for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r]);return e}var i=r(12),s=r(5),a=(r(3)("engine.io-client:socket"),r(33)),c=r(18),h=r(2),p=r(27);t.exports=n,n.priorWebsocketSuccess=!1,s(n.prototype),n.protocol=c.protocol,n.Socket=n,n.Transport=r(17),n.transports=r(12),n.parser=r(18),n.prototype.createTransport=function(t){var e=o(this.query);e.EIO=c.protocol,e.transport=t;var r=this.transportOptions[t]||{};this.id&&(e.sid=this.id);var n=new i[t]({query:e,socket:this,agent:r.agent||this.agent,hostname:r.hostname||this.hostname,port:r.port||this.port,secure:r.secure||this.secure,path:r.path||this.path,forceJSONP:r.forceJSONP||this.forceJSONP,jsonp:r.jsonp||this.jsonp,forceBase64:r.forceBase64||this.forceBase64,enablesXDR:r.enablesXDR||this.enablesXDR,timestampRequests:r.timestampRequests||this.timestampRequests,timestampParam:r.timestampParam||this.timestampParam,policyPort:r.policyPort||this.policyPort,pfx:r.pfx||this.pfx,key:r.key||this.key,passphrase:r.passphrase||this.passphrase,cert:r.cert||this.cert,ca:r.ca||this.ca,ciphers:r.ciphers||this.ciphers,rejectUnauthorized:r.rejectUnauthorized||this.rejectUnauthorized,perMessageDeflate:r.perMessageDeflate||this.perMessageDeflate,extraHeaders:r.extraHeaders||this.extraHeaders,forceNode:r.forceNode||this.forceNode,localAddress:r.localAddress||this.localAddress,requestTimeout:r.requestTimeout||this.requestTimeout,protocols:r.protocols||void 0});return n},n.prototype.open=function(){var t;if(this.rememberUpgrade&&n.priorWebsocketSuccess&&this.transports.indexOf("websocket")!==-1)t="websocket";else{if(0===this.transports.length){var e=this;return void setTimeout(function(){e.emit("error","No transports available")},0)}t=this.transports[0]}this.readyState="opening";try{t=this.createTransport(t)}catch(t){return this.transports.shift(),void this.open()}t.open(),this.setTransport(t)},n.prototype.setTransport=function(t){var e=this;this.transport&&this.transport.removeAllListeners(),this.transport=t,t.on("drain",function(){e.onDrain()}).on("packet",function(t){e.onPacket(t)}).on("error",function(t){e.onError(t)}).on("close",function(){e.onClose("transport close")})},n.prototype.probe=function(t){function e(){if(u.onlyBinaryUpgrades){var t=!this.supportsBinary&&u.transport.supportsBinary;p=p||t}p||(h.send([{type:"ping",data:"probe"}]),h.once("packet",function(t){if(!p)if("pong"===t.type&&"probe"===t.data){if(u.upgrading=!0,u.emit("upgrading",h),!h)return;n.priorWebsocketSuccess="websocket"===h.name,u.transport.pause(function(){p||"closed"!==u.readyState&&(c(),u.setTransport(h),h.send([{type:"upgrade"}]),u.emit("upgrade",h),h=null,u.upgrading=!1,u.flush())})}else{var e=new Error("probe error");e.transport=h.name,u.emit("upgradeError",e)}}))}function r(){p||(p=!0,c(),h.close(),h=null)}function o(t){var e=new Error("probe error: "+t);e.transport=h.name,r(),u.emit("upgradeError",e)}function i(){o("transport closed")}function s(){o("socket closed")}function a(t){h&&t.name!==h.name&&r()}function c(){h.removeListener("open",e),h.removeListener("error",o),h.removeListener("close",i),u.removeListener("close",s),u.removeListener("upgrading",a)}var h=this.createTransport(t,{probe:1}),p=!1,u=this;n.priorWebsocketSuccess=!1,h.once("open",e),h.once("error",o),h.once("close",i),this.once("close",s),this.once("upgrading",a),h.open()},n.prototype.onOpen=function(){if(this.readyState="open",n.priorWebsocketSuccess="websocket"===this.transport.name,this.emit("open"),this.flush(),"open"===this.readyState&&this.upgrade&&this.transport.pause)for(var t=0,e=this.upgrades.length;t<e;t++)this.probe(this.upgrades[t])},n.prototype.onPacket=function(t){if("opening"===this.readyState||"open"===this.readyState||"closing"===this.readyState)switch(this.emit("packet",t),this.emit("heartbeat"),t.type){case"open":this.onHandshake(JSON.parse(t.data));break;case"pong":this.setPing(),this.emit("pong");break;case"error":var e=new Error("server error");e.code=t.data,this.onError(e);break;case"message":this.emit("data",t.data),this.emit("message",t.data)}},n.prototype.onHandshake=function(t){this.emit("handshake",t),this.id=t.sid,this.transport.query.sid=t.sid,this.upgrades=this.filterUpgrades(t.upgrades),this.pingInterval=t.pingInterval,this.pingTimeout=t.pingTimeout,this.onOpen(),"closed"!==this.readyState&&(this.setPing(),this.removeListener("heartbeat",this.onHeartbeat),this.on("heartbeat",this.onHeartbeat))},n.prototype.onHeartbeat=function(t){clearTimeout(this.pingTimeoutTimer);var e=this;e.pingTimeoutTimer=setTimeout(function(){"closed"!==e.readyState&&e.onClose("ping timeout")},t||e.pingInterval+e.pingTimeout)},n.prototype.setPing=function(){var t=this;clearTimeout(t.pingIntervalTimer),t.pingIntervalTimer=setTimeout(function(){t.ping(),t.onHeartbeat(t.pingTimeout)},t.pingInterval)},n.prototype.ping=function(){var t=this;this.sendPacket("ping",function(){t.emit("ping")})},n.prototype.onDrain=function(){this.writeBuffer.splice(0,this.prevBufferLen),this.prevBufferLen=0,0===this.writeBuffer.length?this.emit("drain"):this.flush()},n.prototype.flush=function(){"closed"!==this.readyState&&this.transport.writable&&!this.upgrading&&this.writeBuffer.length&&(this.transport.send(this.writeBuffer),this.prevBufferLen=this.writeBuffer.length,this.emit("flush"))},n.prototype.write=n.prototype.send=function(t,e,r){return this.sendPacket("message",t,e,r),this},n.prototype.sendPacket=function(t,e,r,n){if("function"==typeof e&&(n=e,e=void 0),"function"==typeof r&&(n=r,r=null),"closing"!==this.readyState&&"closed"!==this.readyState){r=r||{},r.compress=!1!==r.compress;var o={type:t,data:e,options:r};this.emit("packetCreate",o),this.writeBuffer.push(o),n&&this.once("flush",n),this.flush()}},n.prototype.close=function(){function t(){n.onClose("forced close"),n.transport.close()}function e(){n.removeListener("upgrade",e),n.removeListener("upgradeError",e),t()}function r(){n.once("upgrade",e),n.once("upgradeError",e)}if("opening"===this.readyState||"open"===this.readyState){this.readyState="closing";var n=this;this.writeBuffer.length?this.once("drain",function(){this.upgrading?r():t()}):this.upgrading?r():t()}return this},n.prototype.onError=function(t){n.priorWebsocketSuccess=!1,this.emit("error",t),this.onClose("transport error",t)},n.prototype.onClose=function(t,e){if("opening"===this.readyState||"open"===this.readyState||"closing"===this.readyState){var r=this;clearTimeout(this.pingIntervalTimer),clearTimeout(this.pingTimeoutTimer),this.transport.removeAllListeners("close"),this.transport.close(),this.transport.removeAllListeners(),this.readyState="closed",this.id=null,this.emit("close",t,e),r.writeBuffer=[],r.prevBufferLen=0}},n.prototype.filterUpgrades=function(t){for(var e=[],r=0,n=t.length;r<n;r++)~a(this.transports,t[r])&&e.push(t[r]);return e}}).call(e,function(){return this}())},function(t,e,r){(function(t){function n(e){var r,n=!1,a=!1,c=!1!==e.jsonp;if(t.location){var h="https:"===location.protocol,p=location.port;p||(p=h?443:80),n=e.hostname!==location.hostname||p!==e.port,a=e.secure!==h}if(e.xdomain=n,e.xscheme=a,r=new o(e),"open"in r&&!e.forceJSONP)return new i(e);if(!c)throw new Error("JSONP disabled");return new s(e)}var o=r(13),i=r(15),s=r(30),a=r(31);e.polling=n,e.websocket=a}).call(e,function(){return this}())},function(t,e,r){(function(e){var n=r(14);t.exports=function(t){var r=t.xdomain,o=t.xscheme,i=t.enablesXDR;try{if("undefined"!=typeof XMLHttpRequest&&(!r||n))return new XMLHttpRequest}catch(t){}try{if("undefined"!=typeof XDomainRequest&&!o&&i)return new XDomainRequest}catch(t){}if(!r)try{return new(e[["Active"].concat("Object").join("X")])("Microsoft.XMLHTTP")}catch(t){}}}).call(e,function(){return this}())},function(t,e){try{t.exports="undefined"!=typeof XMLHttpRequest&&"withCredentials"in new XMLHttpRequest}catch(e){t.exports=!1}},function(t,e,r){(function(e){function n(){}function o(t){if(c.call(this,t),this.requestTimeout=t.requestTimeout,this.extraHeaders=t.extraHeaders,e.location){var r="https:"===location.protocol,n=location.port;n||(n=r?443:80),this.xd=t.hostname!==e.location.hostname||n!==t.port,this.xs=t.secure!==r}}function i(t){this.method=t.method||"GET",this.uri=t.uri,this.xd=!!t.xd,this.xs=!!t.xs,this.async=!1!==t.async,this.data=void 0!==t.data?t.data:null,this.agent=t.agent,this.isBinary=t.isBinary,this.supportsBinary=t.supportsBinary,this.enablesXDR=t.enablesXDR,this.requestTimeout=t.requestTimeout,this.pfx=t.pfx,this.key=t.key,this.passphrase=t.passphrase,this.cert=t.cert,this.ca=t.ca,this.ciphers=t.ciphers,this.rejectUnauthorized=t.rejectUnauthorized,this.extraHeaders=t.extraHeaders,this.create()}function s(){for(var t in i.requests)i.requests.hasOwnProperty(t)&&i.requests[t].abort()}var a=r(13),c=r(16),h=r(5),p=r(28);r(3)("engine.io-client:polling-xhr");t.exports=o,t.exports.Request=i,p(o,c),o.prototype.supportsBinary=!0,o.prototype.request=function(t){return t=t||{},t.uri=this.uri(),t.xd=this.xd,t.xs=this.xs,t.agent=this.agent||!1,t.supportsBinary=this.supportsBinary,t.enablesXDR=this.enablesXDR,t.pfx=this.pfx,t.key=this.key,t.passphrase=this.passphrase,t.cert=this.cert,t.ca=this.ca,t.ciphers=this.ciphers,t.rejectUnauthorized=this.rejectUnauthorized,t.requestTimeout=this.requestTimeout,t.extraHeaders=this.extraHeaders,new i(t)},o.prototype.doWrite=function(t,e){var r="string"!=typeof t&&void 0!==t,n=this.request({method:"POST",data:t,isBinary:r}),o=this;n.on("success",e),n.on("error",function(t){o.onError("xhr post error",t)}),this.sendXhr=n},o.prototype.doPoll=function(){var t=this.request(),e=this;t.on("data",function(t){e.onData(t)}),t.on("error",function(t){e.onError("xhr poll error",t)}),this.pollXhr=t},h(i.prototype),i.prototype.create=function(){var t={agent:this.agent,xdomain:this.xd,xscheme:this.xs,enablesXDR:this.enablesXDR};t.pfx=this.pfx,t.key=this.key,t.passphrase=this.passphrase,t.cert=this.cert,t.ca=this.ca,t.ciphers=this.ciphers,t.rejectUnauthorized=this.rejectUnauthorized;var r=this.xhr=new a(t),n=this;try{r.open(this.method,this.uri,this.async);try{if(this.extraHeaders){r.setDisableHeaderCheck&&r.setDisableHeaderCheck(!0);for(var o in this.extraHeaders)this.extraHeaders.hasOwnProperty(o)&&r.setRequestHeader(o,this.extraHeaders[o])}}catch(t){}if("POST"===this.method)try{this.isBinary?r.setRequestHeader("Content-type","application/octet-stream"):r.setRequestHeader("Content-type","text/plain;charset=UTF-8")}catch(t){}try{r.setRequestHeader("Accept","*/*")}catch(t){}"withCredentials"in r&&(r.withCredentials=!0),this.requestTimeout&&(r.timeout=this.requestTimeout),this.hasXDR()?(r.onload=function(){n.onLoad()},r.onerror=function(){n.onError(r.responseText)}):r.onreadystatechange=function(){if(2===r.readyState)try{var t=r.getResponseHeader("Content-Type");n.supportsBinary&&"application/octet-stream"===t&&(r.responseType="arraybuffer")}catch(t){}4===r.readyState&&(200===r.status||1223===r.status?n.onLoad():setTimeout(function(){n.onError(r.status)},0))},r.send(this.data)}catch(t){return void setTimeout(function(){n.onError(t)},0)}e.document&&(this.index=i.requestsCount++,i.requests[this.index]=this)},i.prototype.onSuccess=function(){this.emit("success"),this.cleanup()},i.prototype.onData=function(t){this.emit("data",t),this.onSuccess()},i.prototype.onError=function(t){this.emit("error",t),this.cleanup(!0)},i.prototype.cleanup=function(t){if("undefined"!=typeof this.xhr&&null!==this.xhr){if(this.hasXDR()?this.xhr.onload=this.xhr.onerror=n:this.xhr.onreadystatechange=n,t)try{this.xhr.abort()}catch(t){}e.document&&delete i.requests[this.index],this.xhr=null}},i.prototype.onLoad=function(){var t;try{var e;try{e=this.xhr.getResponseHeader("Content-Type")}catch(t){}t="application/octet-stream"===e?this.xhr.response||this.xhr.responseText:this.xhr.responseText}catch(t){this.onError(t)}null!=t&&this.onData(t)},i.prototype.hasXDR=function(){return"undefined"!=typeof e.XDomainRequest&&!this.xs&&this.enablesXDR},i.prototype.abort=function(){this.cleanup()},i.requestsCount=0,i.requests={},e.document&&(e.attachEvent?e.attachEvent("onunload",s):e.addEventListener&&e.addEventListener("beforeunload",s,!1))}).call(e,function(){return this}())},function(t,e,r){function n(t){var e=t&&t.forceBase64;h&&!e||(this.supportsBinary=!1),o.call(this,t)}var o=r(17),i=r(27),s=r(18),a=r(28),c=r(29);r(3)("engine.io-client:polling");t.exports=n;var h=function(){var t=r(13),e=new t({xdomain:!1});return null!=e.responseType}();a(n,o),n.prototype.name="polling",n.prototype.doOpen=function(){this.poll()},n.prototype.pause=function(t){function e(){r.readyState="paused",t()}var r=this;if(this.readyState="pausing",this.polling||!this.writable){var n=0;this.polling&&(n++,this.once("pollComplete",function(){--n||e()})),this.writable||(n++,this.once("drain",function(){--n||e()}))}else e()},n.prototype.poll=function(){this.polling=!0,this.doPoll(),this.emit("poll")},n.prototype.onData=function(t){var e=this,r=function(t,r,n){return"opening"===e.readyState&&e.onOpen(),"close"===t.type?(e.onClose(),!1):void e.onPacket(t)};s.decodePayload(t,this.socket.binaryType,r),"closed"!==this.readyState&&(this.polling=!1,this.emit("pollComplete"),"open"===this.readyState&&this.poll())},n.prototype.doClose=function(){function t(){e.write([{type:"close"}])}var e=this;"open"===this.readyState?t():this.once("open",t)},n.prototype.write=function(t){var e=this;this.writable=!1;var r=function(){e.writable=!0,e.emit("drain")};s.encodePayload(t,this.supportsBinary,function(t){e.doWrite(t,r)})},n.prototype.uri=function(){var t=this.query||{},e=this.secure?"https":"http",r="";!1!==this.timestampRequests&&(t[this.timestampParam]=c()),this.supportsBinary||t.sid||(t.b64=1),t=i.encode(t),this.port&&("https"===e&&443!==Number(this.port)||"http"===e&&80!==Number(this.port))&&(r=":"+this.port),t.length&&(t="?"+t);var n=this.hostname.indexOf(":")!==-1;return e+"://"+(n?"["+this.hostname+"]":this.hostname)+r+this.path+t}},function(t,e,r){function n(t){this.path=t.path,this.hostname=t.hostname,this.port=t.port,this.secure=t.secure,this.query=t.query,this.timestampParam=t.timestampParam,this.timestampRequests=t.timestampRequests,this.readyState="",this.agent=t.agent||!1,this.socket=t.socket,this.enablesXDR=t.enablesXDR,this.pfx=t.pfx,this.key=t.key,this.passphrase=t.passphrase,this.cert=t.cert,this.ca=t.ca,this.ciphers=t.ciphers,this.rejectUnauthorized=t.rejectUnauthorized,this.forceNode=t.forceNode,this.extraHeaders=t.extraHeaders,this.localAddress=t.localAddress}var o=r(18),i=r(5);t.exports=n,i(n.prototype),n.prototype.onError=function(t,e){var r=new Error(t);return r.type="TransportError",r.description=e,this.emit("error",r),this},n.prototype.open=function(){return"closed"!==this.readyState&&""!==this.readyState||(this.readyState="opening",this.doOpen()),this},n.prototype.close=function(){return"opening"!==this.readyState&&"open"!==this.readyState||(this.doClose(),this.onClose()),this},n.prototype.send=function(t){if("open"!==this.readyState)throw new Error("Transport not open");this.write(t)},n.prototype.onOpen=function(){this.readyState="open",this.writable=!0,this.emit("open")},n.prototype.onData=function(t){var e=o.decodePacket(t,this.socket.binaryType);this.onPacket(e)},n.prototype.onPacket=function(t){this.emit("packet",t)},n.prototype.onClose=function(){this.readyState="closed",this.emit("close")}},function(t,e,r){(function(t){function n(t,r){var n="b"+e.packets[t.type]+t.data.data;return r(n)}function o(t,r,n){if(!r)return e.encodeBase64Packet(t,n);
var o=t.data,i=new Uint8Array(o),s=new Uint8Array(1+o.byteLength);s[0]=v[t.type];for(var a=0;a<i.length;a++)s[a+1]=i[a];return n(s.buffer)}function i(t,r,n){if(!r)return e.encodeBase64Packet(t,n);var o=new FileReader;return o.onload=function(){t.data=o.result,e.encodePacket(t,r,!0,n)},o.readAsArrayBuffer(t.data)}function s(t,r,n){if(!r)return e.encodeBase64Packet(t,n);if(g)return i(t,r,n);var o=new Uint8Array(1);o[0]=v[t.type];var s=new w([o.buffer,t.data]);return n(s)}function a(t){try{t=d.decode(t,{strict:!1})}catch(t){return!1}return t}function c(t,e,r){for(var n=new Array(t.length),o=l(t.length,r),i=function(t,r,o){e(r,function(e,r){n[t]=r,o(e,n)})},s=0;s<t.length;s++)i(s,t[s],o)}var h,p=r(19),u=r(20),f=r(21),l=r(22),d=r(23);t&&t.ArrayBuffer&&(h=r(25));var y="undefined"!=typeof navigator&&/Android/i.test(navigator.userAgent),m="undefined"!=typeof navigator&&/PhantomJS/i.test(navigator.userAgent),g=y||m;e.protocol=3;var v=e.packets={open:0,close:1,ping:2,pong:3,message:4,upgrade:5,noop:6},b=p(v),k={type:"error",data:"parser error"},w=r(26);e.encodePacket=function(e,r,i,a){"function"==typeof r&&(a=r,r=!1),"function"==typeof i&&(a=i,i=null);var c=void 0===e.data?void 0:e.data.buffer||e.data;if(t.ArrayBuffer&&c instanceof ArrayBuffer)return o(e,r,a);if(w&&c instanceof t.Blob)return s(e,r,a);if(c&&c.base64)return n(e,a);var h=v[e.type];return void 0!==e.data&&(h+=i?d.encode(String(e.data),{strict:!1}):String(e.data)),a(""+h)},e.encodeBase64Packet=function(r,n){var o="b"+e.packets[r.type];if(w&&r.data instanceof t.Blob){var i=new FileReader;return i.onload=function(){var t=i.result.split(",")[1];n(o+t)},i.readAsDataURL(r.data)}var s;try{s=String.fromCharCode.apply(null,new Uint8Array(r.data))}catch(t){for(var a=new Uint8Array(r.data),c=new Array(a.length),h=0;h<a.length;h++)c[h]=a[h];s=String.fromCharCode.apply(null,c)}return o+=t.btoa(s),n(o)},e.decodePacket=function(t,r,n){if(void 0===t)return k;if("string"==typeof t){if("b"===t.charAt(0))return e.decodeBase64Packet(t.substr(1),r);if(n&&(t=a(t),t===!1))return k;var o=t.charAt(0);return Number(o)==o&&b[o]?t.length>1?{type:b[o],data:t.substring(1)}:{type:b[o]}:k}var i=new Uint8Array(t),o=i[0],s=f(t,1);return w&&"blob"===r&&(s=new w([s])),{type:b[o],data:s}},e.decodeBase64Packet=function(t,e){var r=b[t.charAt(0)];if(!h)return{type:r,data:{base64:!0,data:t.substr(1)}};var n=h.decode(t.substr(1));return"blob"===e&&w&&(n=new w([n])),{type:r,data:n}},e.encodePayload=function(t,r,n){function o(t){return t.length+":"+t}function i(t,n){e.encodePacket(t,!!s&&r,!1,function(t){n(null,o(t))})}"function"==typeof r&&(n=r,r=null);var s=u(t);return r&&s?w&&!g?e.encodePayloadAsBlob(t,n):e.encodePayloadAsArrayBuffer(t,n):t.length?void c(t,i,function(t,e){return n(e.join(""))}):n("0:")},e.decodePayload=function(t,r,n){if("string"!=typeof t)return e.decodePayloadAsBinary(t,r,n);"function"==typeof r&&(n=r,r=null);var o;if(""===t)return n(k,0,1);for(var i,s,a="",c=0,h=t.length;c<h;c++){var p=t.charAt(c);if(":"===p){if(""===a||a!=(i=Number(a)))return n(k,0,1);if(s=t.substr(c+1,i),a!=s.length)return n(k,0,1);if(s.length){if(o=e.decodePacket(s,r,!1),k.type===o.type&&k.data===o.data)return n(k,0,1);var u=n(o,c+i,h);if(!1===u)return}c+=i,a=""}else a+=p}return""!==a?n(k,0,1):void 0},e.encodePayloadAsArrayBuffer=function(t,r){function n(t,r){e.encodePacket(t,!0,!0,function(t){return r(null,t)})}return t.length?void c(t,n,function(t,e){var n=e.reduce(function(t,e){var r;return r="string"==typeof e?e.length:e.byteLength,t+r.toString().length+r+2},0),o=new Uint8Array(n),i=0;return e.forEach(function(t){var e="string"==typeof t,r=t;if(e){for(var n=new Uint8Array(t.length),s=0;s<t.length;s++)n[s]=t.charCodeAt(s);r=n.buffer}e?o[i++]=0:o[i++]=1;for(var a=r.byteLength.toString(),s=0;s<a.length;s++)o[i++]=parseInt(a[s]);o[i++]=255;for(var n=new Uint8Array(r),s=0;s<n.length;s++)o[i++]=n[s]}),r(o.buffer)}):r(new ArrayBuffer(0))},e.encodePayloadAsBlob=function(t,r){function n(t,r){e.encodePacket(t,!0,!0,function(t){var e=new Uint8Array(1);if(e[0]=1,"string"==typeof t){for(var n=new Uint8Array(t.length),o=0;o<t.length;o++)n[o]=t.charCodeAt(o);t=n.buffer,e[0]=0}for(var i=t instanceof ArrayBuffer?t.byteLength:t.size,s=i.toString(),a=new Uint8Array(s.length+1),o=0;o<s.length;o++)a[o]=parseInt(s[o]);if(a[s.length]=255,w){var c=new w([e.buffer,a.buffer,t]);r(null,c)}})}c(t,n,function(t,e){return r(new w(e))})},e.decodePayloadAsBinary=function(t,r,n){"function"==typeof r&&(n=r,r=null);for(var o=t,i=[];o.byteLength>0;){for(var s=new Uint8Array(o),a=0===s[0],c="",h=1;255!==s[h];h++){if(c.length>310)return n(k,0,1);c+=s[h]}o=f(o,2+c.length),c=parseInt(c);var p=f(o,0,c);if(a)try{p=String.fromCharCode.apply(null,new Uint8Array(p))}catch(t){var u=new Uint8Array(p);p="";for(var h=0;h<u.length;h++)p+=String.fromCharCode(u[h])}i.push(p),o=f(o,c)}var l=i.length;i.forEach(function(t,o){n(e.decodePacket(t,r,!0),o,l)})}}).call(e,function(){return this}())},function(t,e){t.exports=Object.keys||function(t){var e=[],r=Object.prototype.hasOwnProperty;for(var n in t)r.call(t,n)&&e.push(n);return e}},function(t,e,r){(function(e){function n(t){if(!t||"object"!=typeof t)return!1;if(o(t)){for(var r=0,i=t.length;r<i;r++)if(n(t[r]))return!0;return!1}if("function"==typeof e.Buffer&&e.Buffer.isBuffer&&e.Buffer.isBuffer(t)||"function"==typeof e.ArrayBuffer&&t instanceof ArrayBuffer||s&&t instanceof Blob||a&&t instanceof File)return!0;if(t.toJSON&&"function"==typeof t.toJSON&&1===arguments.length)return n(t.toJSON(),!0);for(var c in t)if(Object.prototype.hasOwnProperty.call(t,c)&&n(t[c]))return!0;return!1}var o=r(7),i=Object.prototype.toString,s="function"==typeof e.Blob||"[object BlobConstructor]"===i.call(e.Blob),a="function"==typeof e.File||"[object FileConstructor]"===i.call(e.File);t.exports=n}).call(e,function(){return this}())},function(t,e){t.exports=function(t,e,r){var n=t.byteLength;if(e=e||0,r=r||n,t.slice)return t.slice(e,r);if(e<0&&(e+=n),r<0&&(r+=n),r>n&&(r=n),e>=n||e>=r||0===n)return new ArrayBuffer(0);for(var o=new Uint8Array(t),i=new Uint8Array(r-e),s=e,a=0;s<r;s++,a++)i[a]=o[s];return i.buffer}},function(t,e){function r(t,e,r){function o(t,n){if(o.count<=0)throw new Error("after called too many times");--o.count,t?(i=!0,e(t),e=r):0!==o.count||i||e(null,n)}var i=!1;return r=r||n,o.count=t,0===t?e():o}function n(){}t.exports=r},function(t,e,r){var n;(function(t,o){!function(i){function s(t){for(var e,r,n=[],o=0,i=t.length;o<i;)e=t.charCodeAt(o++),e>=55296&&e<=56319&&o<i?(r=t.charCodeAt(o++),56320==(64512&r)?n.push(((1023&e)<<10)+(1023&r)+65536):(n.push(e),o--)):n.push(e);return n}function a(t){for(var e,r=t.length,n=-1,o="";++n<r;)e=t[n],e>65535&&(e-=65536,o+=k(e>>>10&1023|55296),e=56320|1023&e),o+=k(e);return o}function c(t,e){if(t>=55296&&t<=57343){if(e)throw Error("Lone surrogate U+"+t.toString(16).toUpperCase()+" is not a scalar value");return!1}return!0}function h(t,e){return k(t>>e&63|128)}function p(t,e){if(0==(4294967168&t))return k(t);var r="";return 0==(4294965248&t)?r=k(t>>6&31|192):0==(4294901760&t)?(c(t,e)||(t=65533),r=k(t>>12&15|224),r+=h(t,6)):0==(4292870144&t)&&(r=k(t>>18&7|240),r+=h(t,12),r+=h(t,6)),r+=k(63&t|128)}function u(t,e){e=e||{};for(var r,n=!1!==e.strict,o=s(t),i=o.length,a=-1,c="";++a<i;)r=o[a],c+=p(r,n);return c}function f(){if(b>=v)throw Error("Invalid byte index");var t=255&g[b];if(b++,128==(192&t))return 63&t;throw Error("Invalid continuation byte")}function l(t){var e,r,n,o,i;if(b>v)throw Error("Invalid byte index");if(b==v)return!1;if(e=255&g[b],b++,0==(128&e))return e;if(192==(224&e)){if(r=f(),i=(31&e)<<6|r,i>=128)return i;throw Error("Invalid continuation byte")}if(224==(240&e)){if(r=f(),n=f(),i=(15&e)<<12|r<<6|n,i>=2048)return c(i,t)?i:65533;throw Error("Invalid continuation byte")}if(240==(248&e)&&(r=f(),n=f(),o=f(),i=(7&e)<<18|r<<12|n<<6|o,i>=65536&&i<=1114111))return i;throw Error("Invalid UTF-8 detected")}function d(t,e){e=e||{};var r=!1!==e.strict;g=s(t),v=g.length,b=0;for(var n,o=[];(n=l(r))!==!1;)o.push(n);return a(o)}var y="object"==typeof e&&e,m=("object"==typeof t&&t&&t.exports==y&&t,"object"==typeof o&&o);m.global!==m&&m.window!==m||(i=m);var g,v,b,k=String.fromCharCode,w={version:"2.1.2",encode:u,decode:d};n=function(){return w}.call(e,r,e,t),!(void 0!==n&&(t.exports=n))}(this)}).call(e,r(24)(t),function(){return this}())},function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children=[],t.webpackPolyfill=1),t}},function(t,e){!function(){"use strict";for(var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",r=new Uint8Array(256),n=0;n<t.length;n++)r[t.charCodeAt(n)]=n;e.encode=function(e){var r,n=new Uint8Array(e),o=n.length,i="";for(r=0;r<o;r+=3)i+=t[n[r]>>2],i+=t[(3&n[r])<<4|n[r+1]>>4],i+=t[(15&n[r+1])<<2|n[r+2]>>6],i+=t[63&n[r+2]];return o%3===2?i=i.substring(0,i.length-1)+"=":o%3===1&&(i=i.substring(0,i.length-2)+"=="),i},e.decode=function(t){var e,n,o,i,s,a=.75*t.length,c=t.length,h=0;"="===t[t.length-1]&&(a--,"="===t[t.length-2]&&a--);var p=new ArrayBuffer(a),u=new Uint8Array(p);for(e=0;e<c;e+=4)n=r[t.charCodeAt(e)],o=r[t.charCodeAt(e+1)],i=r[t.charCodeAt(e+2)],s=r[t.charCodeAt(e+3)],u[h++]=n<<2|o>>4,u[h++]=(15&o)<<4|i>>2,u[h++]=(3&i)<<6|63&s;return p}}()},function(t,e){(function(e){function r(t){for(var e=0;e<t.length;e++){var r=t[e];if(r.buffer instanceof ArrayBuffer){var n=r.buffer;if(r.byteLength!==n.byteLength){var o=new Uint8Array(r.byteLength);o.set(new Uint8Array(n,r.byteOffset,r.byteLength)),n=o.buffer}t[e]=n}}}function n(t,e){e=e||{};var n=new i;r(t);for(var o=0;o<t.length;o++)n.append(t[o]);return e.type?n.getBlob(e.type):n.getBlob()}function o(t,e){return r(t),new Blob(t,e||{})}var i=e.BlobBuilder||e.WebKitBlobBuilder||e.MSBlobBuilder||e.MozBlobBuilder,s=function(){try{var t=new Blob(["hi"]);return 2===t.size}catch(t){return!1}}(),a=s&&function(){try{var t=new Blob([new Uint8Array([1,2])]);return 2===t.size}catch(t){return!1}}(),c=i&&i.prototype.append&&i.prototype.getBlob;t.exports=function(){return s?a?e.Blob:o:c?n:void 0}()}).call(e,function(){return this}())},function(t,e){e.encode=function(t){var e="";for(var r in t)t.hasOwnProperty(r)&&(e.length&&(e+="&"),e+=encodeURIComponent(r)+"="+encodeURIComponent(t[r]));return e},e.decode=function(t){for(var e={},r=t.split("&"),n=0,o=r.length;n<o;n++){var i=r[n].split("=");e[decodeURIComponent(i[0])]=decodeURIComponent(i[1])}return e}},function(t,e){t.exports=function(t,e){var r=function(){};r.prototype=e.prototype,t.prototype=new r,t.prototype.constructor=t}},function(t,e){"use strict";function r(t){var e="";do e=s[t%a]+e,t=Math.floor(t/a);while(t>0);return e}function n(t){var e=0;for(p=0;p<t.length;p++)e=e*a+c[t.charAt(p)];return e}function o(){var t=r(+new Date);return t!==i?(h=0,i=t):t+"."+r(h++)}for(var i,s="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_".split(""),a=64,c={},h=0,p=0;p<a;p++)c[s[p]]=p;o.encode=r,o.decode=n,t.exports=o},function(t,e,r){(function(e){function n(){}function o(t){i.call(this,t),this.query=this.query||{},a||(e.___eio||(e.___eio=[]),a=e.___eio),this.index=a.length;var r=this;a.push(function(t){r.onData(t)}),this.query.j=this.index,e.document&&e.addEventListener&&e.addEventListener("beforeunload",function(){r.script&&(r.script.onerror=n)},!1)}var i=r(16),s=r(28);t.exports=o;var a,c=/\n/g,h=/\\n/g;s(o,i),o.prototype.supportsBinary=!1,o.prototype.doClose=function(){this.script&&(this.script.parentNode.removeChild(this.script),this.script=null),this.form&&(this.form.parentNode.removeChild(this.form),this.form=null,this.iframe=null),i.prototype.doClose.call(this)},o.prototype.doPoll=function(){var t=this,e=document.createElement("script");this.script&&(this.script.parentNode.removeChild(this.script),this.script=null),e.async=!0,e.src=this.uri(),e.onerror=function(e){t.onError("jsonp poll error",e)};var r=document.getElementsByTagName("script")[0];r?r.parentNode.insertBefore(e,r):(document.head||document.body).appendChild(e),this.script=e;var n="undefined"!=typeof navigator&&/gecko/i.test(navigator.userAgent);n&&setTimeout(function(){var t=document.createElement("iframe");document.body.appendChild(t),document.body.removeChild(t)},100)},o.prototype.doWrite=function(t,e){function r(){n(),e()}function n(){if(o.iframe)try{o.form.removeChild(o.iframe)}catch(t){o.onError("jsonp polling iframe removal error",t)}try{var t='<iframe src="javascript:0" name="'+o.iframeId+'">';i=document.createElement(t)}catch(t){i=document.createElement("iframe"),i.name=o.iframeId,i.src="javascript:0"}i.id=o.iframeId,o.form.appendChild(i),o.iframe=i}var o=this;if(!this.form){var i,s=document.createElement("form"),a=document.createElement("textarea"),p=this.iframeId="eio_iframe_"+this.index;s.className="socketio",s.style.position="absolute",s.style.top="-1000px",s.style.left="-1000px",s.target=p,s.method="POST",s.setAttribute("accept-charset","utf-8"),a.name="d",s.appendChild(a),document.body.appendChild(s),this.form=s,this.area=a}this.form.action=this.uri(),n(),t=t.replace(h,"\\\n"),this.area.value=t.replace(c,"\\n");try{this.form.submit()}catch(t){}this.iframe.attachEvent?this.iframe.onreadystatechange=function(){"complete"===o.iframe.readyState&&r()}:this.iframe.onload=r}}).call(e,function(){return this}())},function(t,e,r){(function(e){function n(t){var e=t&&t.forceBase64;e&&(this.supportsBinary=!1),this.perMessageDeflate=t.perMessageDeflate,this.usingBrowserWebSocket=p&&!t.forceNode,this.protocols=t.protocols,this.usingBrowserWebSocket||(u=o),i.call(this,t)}var o,i=r(17),s=r(18),a=r(27),c=r(28),h=r(29),p=(r(3)("engine.io-client:websocket"),e.WebSocket||e.MozWebSocket);if("undefined"==typeof window)try{o=r(32)}catch(t){}var u=p;u||"undefined"!=typeof window||(u=o),t.exports=n,c(n,i),n.prototype.name="websocket",n.prototype.supportsBinary=!0,n.prototype.doOpen=function(){if(this.check()){var t=this.uri(),e=this.protocols,r={agent:this.agent,perMessageDeflate:this.perMessageDeflate};r.pfx=this.pfx,r.key=this.key,r.passphrase=this.passphrase,r.cert=this.cert,r.ca=this.ca,r.ciphers=this.ciphers,r.rejectUnauthorized=this.rejectUnauthorized,this.extraHeaders&&(r.headers=this.extraHeaders),this.localAddress&&(r.localAddress=this.localAddress);try{this.ws=this.usingBrowserWebSocket?e?new u(t,e):new u(t):new u(t,e,r)}catch(t){return this.emit("error",t)}void 0===this.ws.binaryType&&(this.supportsBinary=!1),this.ws.supports&&this.ws.supports.binary?(this.supportsBinary=!0,this.ws.binaryType="nodebuffer"):this.ws.binaryType="arraybuffer",this.addEventListeners()}},n.prototype.addEventListeners=function(){var t=this;this.ws.onopen=function(){t.onOpen()},this.ws.onclose=function(){t.onClose()},this.ws.onmessage=function(e){t.onData(e.data)},this.ws.onerror=function(e){t.onError("websocket error",e)}},n.prototype.write=function(t){function r(){n.emit("flush"),setTimeout(function(){n.writable=!0,n.emit("drain")},0)}var n=this;this.writable=!1;for(var o=t.length,i=0,a=o;i<a;i++)!function(t){s.encodePacket(t,n.supportsBinary,function(i){if(!n.usingBrowserWebSocket){var s={};if(t.options&&(s.compress=t.options.compress),n.perMessageDeflate){var a="string"==typeof i?e.Buffer.byteLength(i):i.length;a<n.perMessageDeflate.threshold&&(s.compress=!1)}}try{n.usingBrowserWebSocket?n.ws.send(i):n.ws.send(i,s)}catch(t){}--o||r()})}(t[i])},n.prototype.onClose=function(){i.prototype.onClose.call(this)},n.prototype.doClose=function(){"undefined"!=typeof this.ws&&this.ws.close()},n.prototype.uri=function(){var t=this.query||{},e=this.secure?"wss":"ws",r="";this.port&&("wss"===e&&443!==Number(this.port)||"ws"===e&&80!==Number(this.port))&&(r=":"+this.port),this.timestampRequests&&(t[this.timestampParam]=h()),this.supportsBinary||(t.b64=1),t=a.encode(t),t.length&&(t="?"+t);var n=this.hostname.indexOf(":")!==-1;return e+"://"+(n?"["+this.hostname+"]":this.hostname)+r+this.path+t},n.prototype.check=function(){return!(!u||"__initialize"in u&&this.name===n.prototype.name)}}).call(e,function(){return this}())},function(t,e){},function(t,e){var r=[].indexOf;t.exports=function(t,e){if(r)return t.indexOf(e);for(var n=0;n<t.length;++n)if(t[n]===e)return n;return-1}},function(t,e,r){"use strict";function n(t,e,r){this.io=t,this.nsp=e,this.json=this,this.ids=0,this.acks={},this.receiveBuffer=[],this.sendBuffer=[],this.connected=!1,this.disconnected=!0,this.flags={},r&&r.query&&(this.query=r.query),this.io.autoConnect&&this.open()}var o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=r(4),s=r(5),a=r(35),c=r(36),h=r(37),p=(r(3)("socket.io-client:socket"),r(27)),u=r(20);t.exports=e=n;var f={connect:1,connect_error:1,connect_timeout:1,connecting:1,disconnect:1,error:1,reconnect:1,reconnect_attempt:1,reconnect_failed:1,reconnect_error:1,reconnecting:1,ping:1,pong:1},l=s.prototype.emit;s(n.prototype),n.prototype.subEvents=function(){if(!this.subs){var t=this.io;this.subs=[c(t,"open",h(this,"onopen")),c(t,"packet",h(this,"onpacket")),c(t,"close",h(this,"onclose"))]}},n.prototype.open=n.prototype.connect=function(){return this.connected?this:(this.subEvents(),this.io.open(),"open"===this.io.readyState&&this.onopen(),this.emit("connecting"),this)},n.prototype.send=function(){var t=a(arguments);return t.unshift("message"),this.emit.apply(this,t),this},n.prototype.emit=function(t){if(f.hasOwnProperty(t))return l.apply(this,arguments),this;var e=a(arguments),r={type:(void 0!==this.flags.binary?this.flags.binary:u(e))?i.BINARY_EVENT:i.EVENT,data:e};return r.options={},r.options.compress=!this.flags||!1!==this.flags.compress,"function"==typeof e[e.length-1]&&(this.acks[this.ids]=e.pop(),r.id=this.ids++),this.connected?this.packet(r):this.sendBuffer.push(r),this.flags={},this},n.prototype.packet=function(t){t.nsp=this.nsp,this.io.packet(t)},n.prototype.onopen=function(){if("/"!==this.nsp)if(this.query){var t="object"===o(this.query)?p.encode(this.query):this.query;this.packet({type:i.CONNECT,query:t})}else this.packet({type:i.CONNECT})},n.prototype.onclose=function(t){this.connected=!1,this.disconnected=!0,delete this.id,this.emit("disconnect",t)},n.prototype.onpacket=function(t){var e=t.nsp===this.nsp,r=t.type===i.ERROR&&"/"===t.nsp;if(e||r)switch(t.type){case i.CONNECT:this.onconnect();break;case i.EVENT:this.onevent(t);break;case i.BINARY_EVENT:this.onevent(t);break;case i.ACK:this.onack(t);break;case i.BINARY_ACK:this.onack(t);break;case i.DISCONNECT:this.ondisconnect();break;case i.ERROR:this.emit("error",t.data)}},n.prototype.onevent=function(t){var e=t.data||[];null!=t.id&&e.push(this.ack(t.id)),this.connected?l.apply(this,e):this.receiveBuffer.push(e)},n.prototype.ack=function(t){var e=this,r=!1;return function(){if(!r){r=!0;var n=a(arguments);e.packet({type:u(n)?i.BINARY_ACK:i.ACK,id:t,data:n})}}},n.prototype.onack=function(t){var e=this.acks[t.id];"function"==typeof e&&(e.apply(this,t.data),delete this.acks[t.id])},n.prototype.onconnect=function(){this.connected=!0,this.disconnected=!1,this.emit("connect"),this.emitBuffered()},n.prototype.emitBuffered=function(){var t;for(t=0;t<this.receiveBuffer.length;t++)l.apply(this,this.receiveBuffer[t]);for(this.receiveBuffer=[],t=0;t<this.sendBuffer.length;t++)this.packet(this.sendBuffer[t]);this.sendBuffer=[]},n.prototype.ondisconnect=function(){this.destroy(),this.onclose("io server disconnect")},n.prototype.destroy=function(){if(this.subs){for(var t=0;t<this.subs.length;t++)this.subs[t].destroy();this.subs=null}this.io.destroy(this)},n.prototype.close=n.prototype.disconnect=function(){return this.connected&&this.packet({type:i.DISCONNECT}),this.destroy(),this.connected&&this.onclose("io client disconnect"),this},n.prototype.compress=function(t){return this.flags.compress=t,this},n.prototype.binary=function(t){return this.flags.binary=t,this}},function(t,e){function r(t,e){var r=[];e=e||0;for(var n=e||0;n<t.length;n++)r[n-e]=t[n];return r}t.exports=r},function(t,e){"use strict";function r(t,e,r){return t.on(e,r),{destroy:function(){t.removeListener(e,r)}}}t.exports=r},function(t,e){var r=[].slice;t.exports=function(t,e){if("string"==typeof e&&(e=t[e]),"function"!=typeof e)throw new Error("bind() requires a function");var n=r.call(arguments,2);return function(){return e.apply(t,n.concat(r.call(arguments)))}}},function(t,e){function r(t){t=t||{},this.ms=t.min||100,this.max=t.max||1e4,this.factor=t.factor||2,this.jitter=t.jitter>0&&t.jitter<=1?t.jitter:0,this.attempts=0}t.exports=r,r.prototype.duration=function(){var t=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var e=Math.random(),r=Math.floor(e*this.jitter*t);t=0==(1&Math.floor(10*e))?t-r:t+r}return 0|Math.min(t,this.max)},r.prototype.reset=function(){this.attempts=0},r.prototype.setMin=function(t){this.ms=t},r.prototype.setMax=function(t){this.max=t},r.prototype.setJitter=function(t){this.jitter=t}}])});
//# sourceMappingURL=socket.io.slim.js.map
/* jshint ignore:end */
/*jshint browser:true */
/*jshint scripturl:true */

(function(undefined) {

/*jshint validthis:true */

'use strict';

/******************************************************************************
 *                                                                            *
 *  Die2Nite helpers class                                                    *
 *                                                                            *
 ******************************************************************************/

var D2N = (function() {

/*
 * private:
 */

    var pages_url_ = {
        // in town
        overview: 'city/enter',
        home: 'home',
        well: 'city/well',
        bank: 'city/bank',
        citizens: 'city/co',
        buildings: 'city/buildings',
        doors: 'city/door',
        upgrades: 'city/upgrades',
        tower: 'city/tower',
        refine: 'city/refine',
        guard: 'city/guard',

        // in/out of town
        ghost: 'ghost/user',
        ghost_exp: 'ghost/heroUpgrades',
        settings: 'ghost/options',

        // Not in a game
        maps: 'ghost/maps'
    };

    var websites_language_ = {
        'www.die2nite.com': 'en',
        'www.hordes.fr': 'fr',
        'www.zombinoia.com': 'es',
        'www.dieverdammten.de': 'de'
    };

    var session_key_cache_ = null;

    /**
     * Emit a gamebody reload event.
     */
    function emit_gamebody_reloaded_event()
    {
        JS.dispatch_event('d2n_gamebody_reload');
    }

    /**
     * Wait for a gamebody reload and emit the corresponding event then.
     */
    function add_gamebody_reload_event()
    {
        if (D2N.is_on_forum()) {
            return;
        }

        var watch_for_gamebody_reload = function(limit) {
            limit = (typeof limit === 'undefined') ? 20 : limit;

            // If the limit isn't reached and the hash isn't defined, call this
            // function again (this is done to wait the first load)
            if (limit > 0 && window.location.hash === '') {
                return setTimeout(function() { watch_for_gamebody_reload(limit - 1); }, 200);
            }

            JS.wait_for_tag('body', function(nodes) {
                var body_observer = new MutationObserver(function(mutations) {
                    for (var i = 0, max = mutations.length; i < max; i += 1) {
                        if (mutations[i].type !== 'attributes' ||
                            mutations[i].attributeName !== 'style') {
                            continue;
                        }

                        if (mutations[i].target.style.cursor === 'default') {
                            emit_gamebody_reloaded_event();
                            break;
                        }
                    }
                });

                // 1. If the cursor is 'default' it means the page is loaded
                // (and that we missed the cursor change). In this case, just
                // emit an event.
                if (nodes[0].style.cursor === 'default') {
                    emit_gamebody_reloaded_event();
                }

                // 2. Then watch for a cursor style change on the body tag,
                // which means the end of a content load for the page
                body_observer.observe(nodes[0], { attributes: true });
            });
        };
        watch_for_gamebody_reload();
    }

    /**
     * Emit an AP change event.
     */
    function emit_ap_change_event()
    {
        JS.dispatch_event('d2n_apchange');
    }

    /**
     * Wait for an ap change and call emit the corresponding event then.
     */
    function add_ap_change_event()
    {
        // only watch AP change in town
        D2N.is_in_town(function(in_town) {
            if (in_town) {

                emit_ap_change_event(); // dispatch event on first load

                if (D2N.is_on_forum()) {
                    return;
                }

                // Store the observer to always have at least 1 active
                var ap_observer = null;
                var ap_old_observer = null;

                var watch_for_ap_change = function() {
                    JS.wait_for_id('movesCounter', function(node) {
                        ap_old_observer = ap_observer;

                        // Watch for AP change
                        ap_observer = new MutationObserver(function(mutations) {
                            emit_ap_change_event();
                        });

                        ap_observer.observe(node, { childList: true, subtree: true });

                        if (ap_old_observer !== null) {
                            ap_old_observer.disconnect();
                            ap_old_observer = null;
                        }
                    });
                };
                watch_for_ap_change(); // watch on first load

                // watch again when the page change
                window.addEventListener('hashchange', function() {
                    watch_for_ap_change();
                }, false);

            }
        });
    }

    /**
     * Emit a forum topic event.
     */
    function emit_forum_topic_event()
    {
        JS.dispatch_event('d2n_forum_topic');
    }

    /**
     * Wait for a forum topic to load and emit the corresponding event then.
     */
    function add_forum_topic_event()
    {
        if (!D2N.is_on_forum()) {
            return;
        }

        JS.wait_for_id('tid_forum_right', function(node) {
            // if the posts are already loaded, emit the event
            if (document.getElementsByClassName('tid_post').length > 0) {
                emit_forum_topic_event();
            }

            var observer = new MutationObserver(function(mutations) {
                mutations.forEach(function(mutation) {
                    if (mutation.removedNodes.length <= 0) {
                        return;
                    }

                    for (var i = 0, max = mutation.removedNodes.length; i < max; i++) {
                        if (mutation.removedNodes[i].className === 'tid_loading') {
                            emit_forum_topic_event();
                            return;
                        }
                    }
                });
            });
            observer.observe(node, { childList: true });
        }, 10);
    }

/*
 * public:
 */

    return {

        /**
         * Check whether the user is logged.
         * @param callback callback The callback to call with the result
         */
        is_logged: function(callback)
        {
            JS.wait_for_id('tid_sidePanel_user', function(node) {
                callback(true); // logged if the side panel exists
            }, 20, function(node) {
                callback(false); // else not logged
            });
        },

        /**
         * Check if the player is on the forum.
         * @return bool true if on the forum, false otherwise
         */
        is_on_forum: function()
        {
            return (window.location.pathname === '/tid/forum');
        },

        /**
         * Check if the player is on a forum topic.
         * @return bool true if on a forum topic, false otherwise
         */
        is_on_forum_topic: function()
        {
            return D2N.is_on_forum() && /^#!view\/\d+\|thread\/\d+(?:\?p=\d+)?$/.test(window.location.hash);
        },

        /**
         * Check if the player is on the gazette.
         * @return bool true if on the gazette, false otherwise
         */
        is_on_gazette: function()
        {
            return JS.regex_test('^#news', window.location.hash);
        },

        /**
         * Check if the user is playing in a town or not (do not confound with
         * `is_in_city`). Call the function `callback` with the result
         */
        is_in_town: function(callback)
        {
            JS.wait_for_id('clock', function if_found() {
                callback(true);
            }, 5, function if_not_found() {
                callback(false);
            });
        },

        /**
         * Check if the player is in the city. Return false if the user is in the city
         * but on the forum.
         * @return bool true if inside the city, false otherwise
         */
        is_in_city: function()
        {
            return JS.regex_test('^#city', window.location.hash);
        },

        /**
         * Check if the player is outside the city. Return false if the user is
         * outside but on the forum.
         * @return string true if outside, false otherwise
         */
        is_outside: function()
        {
            return (/^#outside\?(?:go=outside\/(?:doors|refresh);)?sk=[a-z0-9]{5}$/).test(window.location.hash);
        },

        /**
         * Check if the player is outside the city, but still at the doors. Return false if user is
         * outside but on the forum
         * @return string true if outside and at the doors, false otherwise
         */
        is_outside_at_doors: function(){
        return (/^#outside\?(?:go=outside\/doors;)?sk=[a-z0-9]{5}$/).test(window.location.hash);
        },

        /**
         * Check if the given page is loaded (in or out of city).
         * @param string page The page to check (a key from pages_url_)
         * @return string true if on the selected page
         */
        is_on_page: function(page)
        {
            return (D2N.is_on_page_in_city(page) || D2N.is_on_page_out_of_city(page));
        },

        /**
         * Check if the given city page is loaded.
         * @param string page The page to check (a key from pages_url_)
         * @return string true if on the selected city page
         */
        is_on_page_in_city: function(page)
        {
            return JS.regex_test('^#city\\/enter\\?go=' + pages_url_[page].replace('/', '\\/') + ';sk=[a-z0-9]{5}$', window.location.hash) ||
                JS.regex_test('^#ghost\\/city\\?go=' + pages_url_[page].replace('/', '\\/') + ';sk=[a-z0-9]{5}$', window.location.hash);
        },

        /**
         * Check if the given page is loaded (not in city).
         * @param string page The page to check (a key from pages_url_)
         * @return string true if on the selected page (not in city)
         */
        is_on_page_out_of_city: function(page)
        {
            return JS.regex_test('^#ghost\\?go=' + pages_url_[page].replace('/', '\\/') + '.*;sk=[a-z0-9]{5}$', window.location.hash);
        },

        /**
         * Load a specific city page.
         * @param string page The page to go (a key from pages_url_)
         */
        go_to_city_page: function(page)
        {
            // if not in a town, outside city or already on the page, abort
            D2N.is_in_town(function(in_town) {
                if (!(in_town || D2N.is_outside() || D2N.is_on_page_in_city(page))) {
                    return;
                }

                D2N.get_session_key(function(sk) {
                    var page_url = pages_url_[page];

                    JS.redirect('/#city/enter?go=' + page_url + ';sk=' + sk);
                });
            });
        },

        /**
         * Find the session key.
         * @param callback callback The function to call once the sk is fetched
         */
        get_session_key: function(callback)
        {
            // if the cache defined, give it immediately
            if (typeof session_key_cache_ === 'string' && session_key_cache_ !== '') {
                return callback(session_key_cache_);
            }

            // else fetch it
            JS.wait_for_selector('a.mainButton.newsButton', function(node) {
                var arr = node.href.split('=');

                // store the key in the cache
                session_key_cache_ = arr[arr.length - 1];

                // pass it to the callback
                return callback(session_key_cache_);
            });
        },

        /**
         * Give the website language (specific to D2N/Hordes). Return 'en' by
         * default.
         * @return string The language of the current page ('en' / 'fr')
         * @return null if no corresponding language can be found
         */
        get_website_language: function()
        {
            var hostname = window.location.hostname;

            if (JS.is_defined(hostname) &&
                JS.is_defined(websites_language_[hostname])) {
                return websites_language_[hostname];
            }

            return null;
        },

        /**
         * Call the given callback with the number of AP (Action Points)
         * @param callback callback The function to be called
         */
        get_number_of_ap: function(callback)
        {
            JS.wait_for_selector('#movesCounter > div img[src*="small_pa.gif"]', function(node) {
                var ap = parseInt(node.parentNode.textContent.split('/')[0]);
                callback(ap);
            });
        },

        /**
         * Call the given callback with the number of CP (Construction Points).
         * @param callback callback The function to be called, pass null if the
         * player doesn't have a CP div (it means he/she's not a Technician)
         */
        get_number_of_cp: function(callback)
        {
            JS.wait_for_selector('#movesCounter > div img[src*="small_pc.gif"]', function(node) {
                var cp = parseInt(node.parentNode.textContent.split('/')[0]);
                callback(cp);
            }, 1, function onNotfound() {
                callback(null);
            });
        },

        /**
         * Call the given callback with the total number of AP + CP.
         * @param callback callback The function to be called
         */
        get_number_of_ap_cp: function(callback)
        {
            D2N.get_number_of_ap(function(ap) {
                D2N.get_number_of_cp(function(cp) {
                    var total = ap;
                    if (typeof cp === 'number') {
                        total += cp;
                    }
                    callback(total);
                });
            });
        },

        /**
         * Check if the user is outside and camping.
         * @return boolean true if the user is outside and camping, otherwise false
         */
        is_camping: function()
        {
            return D2N.is_outside() && document.getElementsByClassName('left').length < 1;
        },

        /**
         * Add custom events on the interface:
         * - to watch when the gamebody is reloaded: 'd2n_gamebody_reload'
         * - to watch the number of AP: 'd2n_apchange'
         */
        add_custom_events: function()
        {
            add_gamebody_reload_event();
            add_ap_change_event();
            add_forum_topic_event();
        },

        /**
         * Check if on Die2Nite.
         * @return boolean true if on Die2Nite, else false
         */
        is_on_die2nite: function() {
            return window.location.hostname === 'www.die2nite.com';
        },

        /**
         * Check if on Hordes.
         * @return boolean true if on Hordes, else false
         */
        is_on_hordes: function() {
            return window.location.hostname === 'www.hordes.fr';
        },

        /**
         * Check if on Zombinoia.
         * @return boolean true if on Zombinoia, else false
         */
        is_on_zombinoia: function()
        {
            return window.location.hostname === 'www.zombinoia.com';
        },

        /**
         * Check if on Dieverdammten.
         * @return boolean true if on Dieverdammten, else false
         */
        is_on_dieverdammten: function()
        {
            return window.location.hostname === 'www.dieverdammten.de';
        },

        /**
         * Check if on one of the 4 game websites.
         * @return boolean true if on one of the game websites, else false
         */
        is_on_game_website: function()
        {
            return D2N.is_on_die2nite() ||
                D2N.is_on_hordes() ||
                D2N.is_on_zombinoia() ||
                D2N.is_on_dieverdammten();
        },

        /**
         * Get an external tool api key and pass it to the given callback
         * @param integer directory_id The external tool id
         * @param Function callback The callback to pass the api key
         */
        get_api_key: function(directory_id, callback_success, callback_failure)
        {
            // Fetch the session key
            D2N.get_session_key(function(sk) {
                JS.network_request('GET', '/disclaimer?id=' + directory_id + ';sk=' + sk, null, null,
                    function on_success(data, context) {
                        var match = data.match(/<input type="hidden" name="key" value="([a-f0-9]{38,39})"\/>/);
                        if (JS.is_defined(match) && match.length === 2) {
                            callback_success(match[1]);
                        } else {
                            callback_failure();
                        }
                    },
                    function onfailure() {
                        callback_failure();
                    }
                );
            });
        },

        /**
         * Remove a player action (in house or outside).
         * @param string/RegExp pattern The action name
         */
        remove_player_action: function(pattern)
        {
            // if not at home or outside (the two only places where a player
            // can use an object), abort
            if (!(D2N.is_on_page_in_city('home') || D2N.is_outside())) {
                return;
            }

            // else list all the possible objects usable by the player
            JS.wait_for_selector_all('a.toolAction > span > strong', function(nodes) {
                nodes.forEach(function(node) {
                    // Skip the node if not a 'strong' element
                    if (node.nodeName !== 'STRONG') {
                        return;
                    }

                    // Hide the node if matching the pattern
                    if ((typeof pattern === "string" && node.textContent === pattern) ||
                        (pattern instanceof RegExp && pattern.test(node.textContent))) {

                        var action = node.parentNode.parentNode;
                        action.style.display = 'none';
                    }
                });
            }, 5);
        },

        /**
         * Redirect to a citizen soul.
         * @param integer citizen_id
         * @param string random (optional) A random string to be appended
         */
        redirect_to_citizen_soul: function(citizen_id, random)
        {
            D2N.get_session_key(function(session_key) {
                var url = '/#ghost/city?go=ghost/user?uid=' + citizen_id + ';sk=' + session_key;
                if (typeof random === 'string' && random.length > 0) {
                    url += '?' + random;
                }
                JS.redirect(url);
            });
        },

        /**
         * Display a notification in the native D2N way.
         * @param DOMElement el The element to insert in the notification. If a
         * string is given, it is wrapped into a simple div.
         */
        notification: function(new_element)
        {
            var el;

            // Wrapped the new element if needed
            if (typeof new_element === 'string') {
                el = JS.jsonToDOM(['div', {}, new_element], document);
            } else {
                el = new_element;
            }

            // Get notification div
            var notif = document.getElementById('notificationText');

            // Empty it
            JS.delete_all_children(notif);

            // Add the new content
            notif.appendChild(el);

            // Get the notification container
            var notif_container = document.getElementById('notification');

            // Scroll to the top
            scroll(0, 0);

            // Display the notification
            notif_container.classList.add('showNotif');
        },

        /**
         * Show an empty notification.
         */
        show_empty_notification: function()
        {
            document.getElementById('notification').classList.add('showNotif');
        },

        /**
         * Hide empty notification.
         */
        hide_empty_notification: function()
        {
            document.getElementById('notification').classList.remove('showNotif');
        },

        /**
         * Return the city name.
         */
        get_city_name: function(callback)
        {
            var selector = '#clock .name';

            // async
            if (typeof callback === 'function') {
                JS.wait_for_selector(selector, function(node) {
                    callback(node.textContent.trim());
                }, 5, function() {
                    callback(null);
                });

            // sync
            } else {
                var el = document.querySelector(selector);
                if (!el) { return null; }
                return el.textContent.trim();
            }
        },

        /**
         * Return the server name.
         */
        get_server_name: function()
        {
            return window.location.hostname;
        },

        /**
         * Return the player name.
         */
        get_player_name: function()
        {
            var el = document.querySelector('#tid_openRight .tid_name');

            if (!el) {
                return null;
            }
            return el.textContent.trim();
        }

    };

})();

/******************************************************************************
 *                                                                            *
 *  Die2Nite Enhancer class                                                   *
 *                                                                            *
 ******************************************************************************/

var D2NE = (function() {

/*
 * private:
 */

    var RESTRICTED_MODE_KEY = 'restricted_mode';

    /**
     * The different module types (the order matters).
     */
    var MODULE_TYPES = [
        'BUSINESS_LOGIC', // Used by business logic modules
        'CONTAINER', // Used by the containers modules
        'INTERFACE_ENHANCEMENT', // Used to customise the interface
        'EXTERNAL_TOOL', // Used to synchronise with external tools
        'EXTERNAL_SITE' // Used to add external sites
    ];

    /**
     * The different module property categories (the order matters).
     */
    var MODULE_PROPERTY_CATEGORIES = [
        'GENERAL',
        'SITES',
        'BANK',
        'CONSTRUCTION',
        'SOUL',
        'CITIZENS',
        'OUTSIDE',
        'EXTERNAL_TOOL',
        'FORUM',
        'INTERFACE'
    ];

    function configure_module_class()
    {
        // Define the types
        MODULE_TYPES.forEach(function(type) {
            Module.add_type(type);
        });

        // The types will be loaded in this order
        Module.set_type_loading_order(MODULE_TYPES);

        // Define the property categories
        MODULE_PROPERTY_CATEGORIES.forEach(function(property_category) {
            Module.add_property_category(property_category);
        });

        // Define the property categories order
        Module.set_property_category_priority_order(MODULE_PROPERTY_CATEGORIES);
    }


    function configure_storage_class()
    {
        // Set storage prefix
        Storage.set_key_prefix('extensions.d2ne.');
    }

    function configure_internationalisation_class()
    {
        // Set default language
        I18N.set_language(D2N.get_website_language());
    }

    /**
     * Initialise every modules.
     */
    function initialise_modules()
    {
        Module.init();
    }

    /**
     * Send the 'all_modules_loaded' if needed.
     */
    function emit_all_modules_loaded_event_if_needed(processed_modules, total_modules)
    {
        if (processed_modules >= total_modules) {
            // Dispatch an event when all the modules are loaded
            JS.dispatch_event('d2ne_all_modules_loaded');
        }
    }

    /**
     * Load every enabled modules.
     */
    function load_modules()
    {
        var processed_modules = 0;
        var total_modules = Module.count();

        // For each module
        Module.iterate_in_priority_order(function(module) {
            // Load it only if it is enabled
            if (module.is_enabled()) {

                // If the module has a 'load' method, call it and provide
                // 'module' as the context to be able to reach its private
                // members and methods via 'this'
                if (typeof module.actions.load !== 'undefined') {
                    module.actions.load.call(module);
                }
            }
            emit_all_modules_loaded_event_if_needed((processed_modules += 1), total_modules);
        });
    }



/*
 * public:
 */

    return {

        /**
         * Initialise the script.
         */
        init: function()
        {
            configure_module_class();
            configure_storage_class();
            configure_internationalisation_class();

            // Will be executed once all the modules will be loaded
            document.addEventListener('d2ne_all_modules_loaded', function() {
                D2N.is_logged(function(is_logged) { if (is_logged) {
                    D2N.add_custom_events();
                }});
            });

            initialise_modules();
            load_modules(); // The modules are loaded here
        },

        /**
         * Set the restricted mode.
         * @param boolean value true if enabled, false otherwise
         */
        set_restricted_mode: function(value)
        {
            Storage.set(RESTRICTED_MODE_KEY, value);
        },

        /**
         * Check if the restricted mode is enabled.
         * @return boolean true if the mode is enabled.
         */
        is_restricted_mode: function()
        {
            var v = Storage.get(RESTRICTED_MODE_KEY) || 'true';
            return v === 'true';
        }

    };

})();

/******************************************************************************
 *                                                                            *
 *  I18N class                                                                *
 *                                                                            *
 ******************************************************************************/

function I18N()
{
}


/*************
 * Constants *
 *************/

/**
 * Languages constants, you should use them when adding or retrieving
 * internationalised strings.
 */

I18N.LANG = {};
I18N.LANG.EN = 'en';
I18N.LANG.FR = 'fr';
I18N.LANG.ES = 'es';
I18N.LANG.DE = 'de';


/********************************
 * Static methods and variables *
 ********************************/

/**
 * This language will be provided if the requested language is not available.
 */
I18N.default_language_ = I18N.LANG.EN;

/**
 * This language will be used when the user does a get request without
 * specifying the language to fetch.
 */
I18N.language_ = I18N.default_language;

/**
 * Store all the keys of the extension. Use the accessors  `I18N.add` and
 * `I18N.get` to register (a) new key(s) or get key.
 */
I18N.strings_ = {};

/**
 * Set the language that will be used when strings are requested.
 * @param string language The default language
 */
I18N.set_language = function(language)
{
    I18N.language_ = language;
};

/**
 * Add some strings to the strings_ object. The `keys` should be of the following
 * form:
 *
 *     var i18n = {};
 *     i18n[I18N.LANG.EN] = {};
 *     i18n[I18N.LANG.EN]['say_hello'] = 'Hello';
 *     i18n[I18N.LANG.FR] = {};
 *     i18n[I18N.LANG.FR]['say_hello'] = 'Bonjour';
 *     i18n[I18N.LANG.ES] = {};
 *     i18n[I18N.LANG.ES]['say_hello'] = 'Hola';
 *     i18n[I18N.LANG.DE] = {};
 *     i18n[I18N.LANG.DE]['say_hello'] = 'Hallo';
 *
 *     I18N.set(i18n);
 */
I18N.set = function(new_strings)
{
    JS.merge(I18N.strings_, new_strings);
};

/**
 * Retrieve a internationalised string.
 * @param string key The key to fetch
 * @param string lang (optional) The lang to fetch (use the class const)
 * @return string The internationalised string
 * @return null If an error occurs (unknown language/key)
 */
I18N.get = function(key, lang)
{
    lang = lang || I18N.language_;

    var languages = [lang, I18N.default_language_];

    for (var i = 0, max = languages.length; i < max; i += 1) {
        var language = languages[i];

        if (typeof I18N.strings_[language] !== 'undefined') {
            if (typeof I18N.strings_[language][key] === 'string') {
                return I18N.strings_[language][key];
            }
        }
    }

    return null;
};

/******************************************************************************
 *                                                                            *
 *  JavaScript helpers class                                                  *
 *                                                                            *
 ******************************************************************************/

var JS = (function() {

/*
 * private:
 */

    /**
     * Time to wait between two self-call of `wait_for_*` functions.
     */
    var wait_for_retry_time_ = 250; //ms

    /**
     * Maximum number of retry for the `wait_for_*` functions.
     */
    var wait_for_max_retry_ = 10;

    /**
     * Store the Safari callbacks.
     */
    var safari_callbacks_ = null;

    /**
     * Safely insert code through JSON.
     * @link https://developer.mozilla.org/en-US/Add-ons/Overlay_Extensions/XUL_School/DOM_Building_and_HTML_Insertion
     */
    var jsonToDOM = function(xml, doc, nodes)
    {
        function namespace(name) {
            var m = /^(?:(.*):)?(.*)$/.exec(name);
            return [jsonToDOM.namespaces[m[1]], m[2]];
        }

        function tag(name, attr) {
            if (Array.isArray(name)) {
                var frag = doc.createDocumentFragment();
                Array.forEach(arguments, function (arg) {
                    if (!Array.isArray(arg[0])) {
                        frag.appendChild(tag.apply(null, arg));
                    } else {
                        arg.forEach(function (arg) {
                            frag.appendChild(tag.apply(null, arg));
                        });
                    }
                });
                return frag;
            }

            var args = Array.prototype.slice.call(arguments, 2);
            var vals = namespace(name);
            var elem = doc.createElementNS(vals[0] || jsonToDOM.defaultNamespace, vals[1]);

            for (var key in attr) {
                if (attr.hasOwnProperty(key)) {
                    var val = attr[key];
                    if (nodes && key === "key") {
                        nodes[val] = elem;
                    }

                    vals = namespace(key);
                    if (typeof val === "function") {
                        elem.addEventListener(key.replace(/^on/, ""), val, false);
                    } else {
                        elem.setAttributeNS(vals[0] || "", vals[1], val);
                    }
                }
            }
            args.forEach(function(e) {
                elem.appendChild(typeof e === "object" ? tag.apply(null, e) :
                                 e instanceof Node    ? e : doc.createTextNode(e));
            });
            return elem;
        }
        return tag.apply(null, xml);
    };
    jsonToDOM.namespaces = {
        html: "http://www.w3.org/1999/xhtml",
        xul: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
    };
    jsonToDOM.defaultNamespace = jsonToDOM.namespaces.html;

    var keydown_event_ = {
        previous_keycode: null,
        previous_keycode_timestamp: 0, // ms
        already_bind: false
    };

    /**
     * Generic wait_for_ function. See wait_for_id for the purpose of this
     * function.
     * @param function search A closure returning the searched element
     * @param function is_found A closure taking the searched element and
     * returning true or false whether it was found or not
     * @param function callback The function to call if the element was found
     * @param integer max The maximum number of retry if the first search fails
     * @param function not_found_callback The function to call if the element
     * was not found
     */
    var wait_for_ = function(search, is_found, callback, max, not_found_callback) {
            max = (typeof max === "number") ? max : wait_for_max_retry_;

            // try to find it
            var el = search();
            if (is_found(el)) {
                return callback(el);
            }

            // if max is defined and is reached, stop research
            if (max <= 0) {
                // if a callback has been given, call it
                if (JS.is_defined(not_found_callback) && typeof not_found_callback === 'function') {
                    not_found_callback();
                }
            } else { // else try again
                setTimeout(function() {
                    wait_for_(search, is_found, callback, max - 1, not_found_callback);
                }, wait_for_retry_time_);
            }
    };

/*
 * public:
 */

    return {

        /**
         * Execute an asynchronous network request.
         * @param string method POST, GET...
         * @param string urn path
         * @param string data query string
         * @param JSON headers
         * @param callback on_success in case of success
         * @param callback on_failure in case of failure
         */
        network_request: function(method, urn, data, headers, on_success, on_failure) {

            var uri = JS.form_uri(null, urn);

            // Google Chrome script / GreaseMonkey
            if (typeof GM_xmlhttpRequest !== 'undefined') {
                if(typeof GM_xmlhttpRequest === 'function') {
                    return GM_xmlhttpRequest({
                        method: method,
                        url: uri,
                        data: '' + data,
                        headers: headers,
                        onload: function(r) { on_success(r.responseText, r.responseHeaders); },
                        onerror: function(r) { on_failure(); }
                    });
                } else {
                    return new GM_xmlhttpRequest({
                        method: method,
                        url: uri,
                        data: '' + data,
                        headers: headers,
                        onload: function(r) { on_success(r.responseText, r.responseHeaders); },
                        onerror: function(r) { on_failure(); }
                    });
                }
            }

            // Safari needs to dispatch the request to the global page if the
            // request is Cross Domain
            if (typeof safari !== 'undefined' && JS.is_cross_domain(uri)) {
                // Only register the listener once
                if (safari_callbacks_ === null) {
                    safari.self.addEventListener('message', function(event) {
                        var request_id = event.message.request_id;

                        // if the callback for the given URI can't be found, abort
                        if (!(request_id in safari_callbacks_)) {
                            return;
                        }

                        switch (event.name) {
                            case 'network_request_succeed':
                                safari_callbacks_[request_id].on_success(event.message.response_text);
                                break;

                            case 'network_request_failed':
                                safari_callbacks_[request_id].on_failure();
                                break;
                        }

                        // Delete the callback
                        safari_callbacks_[request_id] = null;
                        delete safari_callbacks_[request_id];
                    }, false);
                }

                var request_unique_id = +new Date() + Math.random() + uri;

                // Save callbacks to keep the context
                safari_callbacks_ = safari_callbacks_ || {};
                safari_callbacks_[request_unique_id] = {
                    on_success: on_success,
                    on_failure: on_failure
                };

                // Ask to the global page to do the request
                return safari.self.tab.dispatchMessage('do_network_request', {
                    method: method,
                    url: uri,
                    data: '' + data,
                    headers: headers,
                    request_id: request_unique_id
                });
            }

            // All other cases
            var xmlhttp = new XMLHttpRequest();
            xmlhttp.open(method, uri, true);
            for (var header in headers) {
                if (headers.hasOwnProperty(header)) {
                    xmlhttp.setRequestHeader(header, headers[header]);
                }
            }
            xmlhttp.onreadystatechange = function() {
                if (xmlhttp.readyState === 4) {
                    if (xmlhttp.status >= 200 && xmlhttp.status < 300) {
                        return on_success(xmlhttp.responseText, xmlhttp.responseHeaders);
                    }
                    return on_failure();
                }
            };
            xmlhttp.send(data);
        },

        /**
         * Check if a given variable is defined and is not null.
         * @param mixed variable The variable to check
         * @return bool true if the variable is defined and is not null, otherwise
         * false
         */
        is_defined: function(variable)
        {
            return (typeof variable !== 'undefined' && variable !== null);
        },

        /**
         * Reset the keydown_event_ object. Forget about the last key stroke.
         */
        reset_previous_keycode: function()
        {
            keydown_event_.previous_keycode = null;
            keydown_event_.previous_keycode_timestamp = 0;
        },

        /**
         * Catch a keydown event (abort if the cursor is in an input field). Call
         * the callback `callback` with the current keycode and the last one (if it
         * exists).
         * @param callback callback The function to call, should look like the
         * following prototype: `function(keycode, previous_keycode){};`.
         * previous_keycode will be null if it doesn't exists.
         * @param integer time_limit The maximum amount of time (in ms) to wait
         * between two binds.
         */
        keydown_event: function(callback, time_limit)
        {
            // Update/set the callback
            keydown_event_.callback = callback;

            // defaut 1000ms between two key strokes
            keydown_event_.time_limit = (typeof time_limit === "number") ? time_limit : 1000;

            // Ensure it can only be bound once (though the callback can still
            // be updated)
            if (keydown_event_.already_bind) {
                return;
            }
            keydown_event_.already_bind = true;

            document.addEventListener('keydown', function(event) {
                // Cancel event if the cursor is in an input field or textarea
                if (event.target.nodeName === 'INPUT' || event.target.nodeName === 'TEXTAREA') {
                    return;
                }

                var current_timestamp = +new Date(); // ms

                // Cancel previous keycode if the elapsed time is too long
                // between the last two keystrokes
                if (current_timestamp - keydown_event_.previous_keycode_timestamp > keydown_event_.time_limit) {
                    JS.reset_previous_keycode();
                }

                // Invoke callback
                keydown_event_.callback(event.keyCode, keydown_event_.previous_keycode);

                // Save keycode
                keydown_event_.previous_keycode = event.keyCode;
                keydown_event_.previous_keycode_timestamp = current_timestamp;
            }, false);
        },

        /**
         * Inject CSS code in the page context.
         * @param string code The CSS code to inject
         */
        injectCSS: function(code)
        {
            var css = document.createElement('style');
            css.setAttribute('type', 'text/css');
            css.textContent = code;

            JS.wait_for_tag('head', function(nodes) {
                nodes[0].appendChild(css);
            });
        },

        /**
         * Inject and execute JavaScript code in the page context.
         * @link http://wiki.greasespot.net/Content_Script_Injection
         * @param string/callback source The JS code to inject
         */
        injectJS: function(source)
        {
            // Check for function input.
            if ('function' === typeof source) {
                // Execute this function with no arguments, by adding parentheses.
                // One set around the function, required for valid syntax, and a
                // second empty set calls the surrounded function.
                source = '(' + source + ')();';
            }

            // Create a script node holding this  source code.
            var script = document.createElement('script');
            script.setAttribute('type', 'application/javascript');
            script.textContent = source;

            // Insert the script node into the page, so it will run, and immediately
            // remove it to clean up.
            JS.wait_for_selector('html > body', function(node) {
                node.appendChild(script);
                node.removeChild(script);
            });
        },

        /**
         * Remove a DOM node.
         * @link http://stackoverflow.com/a/14782/1071486
         * @param DOMNode node The DOM node to delete
         */
        remove_DOM_node: function(node)
        {
            if (JS.is_defined(node)) {
                node.parentNode.removeChild(node);
            }
        },

        /*
         * Recursively merge properties of 2 objects. The first object properties
         * will be erased by the second ones.
         * @link http://stackoverflow.com/a/383245/1071486
         * @param Object obj1 The first object which will receive the merge
         * @param Object obj2 The second object to merge
         * @return Object The first object
         */
        merge: function(obj1, obj2) {
            for (var p in obj2) {
                if (obj2.hasOwnProperty(p)) {
                    try {
                        // Property in destination object set; update its value.
                        if (obj2[p].constructor === Object) {
                            obj1[p] = JS.merge(obj1[p], obj2[p]);
                        } else {
                            obj1[p] = obj2[p];
                        }
                    } catch(e) {
                        // Property in destination object not set; create it and set
                        // its value.
                        obj1[p] = obj2[p];
                    }
                }
            }

            return obj1;
        },

        /**
         * Execute a callback when a node with the given $id is found.
         * @param string id The id to search
         * @param callback callback The function to call when a result is found
         * @param integer max The maximum number of try
         * @param callback not_found_callback The function called if the element
         *                                    isn't found
         */
        wait_for_id: function(id, callback, max, not_found_callback)
        {
            return wait_for_(function search() {
                    return document.getElementById(id);
                }, function is_found(result) {
                    return JS.is_defined(result);
                }, callback, max, not_found_callback);
        },

        /**
         * Execute a callback with an array containing all the nodes matching the
         * given class.
         * @param string class The class to search
         * @param callback callback The function to call when a result is found
         * @param integer max The maximum number of try
         * @param callback not_found_callback The function called if the element
         *                                    isn't found
         */
        wait_for_class: function(class_name, callback, max, not_found_callback)
        {
            return wait_for_(function search() {
                    return JS.nodelist_to_array(document.getElementsByClassName(class_name));
                }, function is_found(result) {
                    return JS.is_defined(result) && result.length > 0;
                }, callback, max, not_found_callback);
        },

        /**
         * Execute a callback with an array containing all the nodes matching the
         * given tag.
         * @param string tag The tag to search
         * @param callback callback The function to call when a result is found
         * @param integer max The maximum number of try
         * @param callback not_found_callback The function called if the element
         *                                    isn't found
         */
        wait_for_tag: function(tag, callback, max, not_found_callback)
        {
            return wait_for_(function search() {
                    return JS.nodelist_to_array(document.getElementsByTagName(tag));
                }, function is_found(result) {
                    return JS.is_defined(result) && result.length > 0;
                }, callback, max, not_found_callback);
        },

        /**
         * Execute a callback with the first node matching the given selector.
         * @param string selector The selector to execute
         * @param callback callback The function to call when a result is found
         * @param integer max The maximum number of try
         * @param callback not_found_callback The function called if the element
         *                                    isn't found
         */
        wait_for_selector: function(selector, callback, max, not_found_callback)
        {
            return wait_for_(function search() {
                    return document.querySelector(selector);
                }, function is_found(result) {
                    return JS.is_defined(result);
                }, callback, max, not_found_callback);
        },

        /**
         * Execute a callback with an array containing all the nodes matching the
         * given selector.
         * @param string selector The selector to execute
         * @param callback callback The function to call when a result is found
         * @param integer max The maximum number of try
         * @param callback not_found_callback The function called if the element
         *                                    isn't found
         */
        wait_for_selector_all: function(selector, callback, max, not_found_callback)
        {
            return wait_for_(function search() {
                    return JS.nodelist_to_array(document.querySelectorAll(selector));
                }, function is_found(result) {
                    return JS.is_defined(result) && result.length > 0;
                }, callback, max, not_found_callback);
        },

        /**
         * Redirect to the given urn.
         * @param string urn The URN to redirect to
         */
        redirect: function(urn)
        {
            window.location.href = JS.form_uri(null, urn);
        },

        /**
         * Reload the current page.
         */
        reload: function()
        {
            location.reload();
        },

        /**
         * Instanciate a Regex object and test to see if the given string matches
         * it. Useful when the Regex should be constructed from a string.
         * @param string/RegExp The regex to match the string with
         * @param string The string to test
         * @return bool true if the regex matches the string, false otherwise
         */
        regex_test: function(regex, string)
        {
            var r;

            if (regex instanceof RegExp) {
                r = regex;
            } else {
                r = new RegExp(regex);
            }

            return r.test(string);
        },

        /**
         * Iterate over an object and pass the key/value to a callback.
         */
        each: function(object, callback)
        {
            for (var key in object) {
                if (object.hasOwnProperty(key)) {
                    callback(key, object[key]);
                }
            }
        },

        /**
         * Dispatch a custom event on the desired DOM Node
         * @param string key The event key
         * @param Object detail (optional) The event details
         * @param DOMNode node (optional) The node to dispatch the event on
         */
        dispatch_event: function(key, detail, node)
        {
            detail = detail || null;
            node = node || document;
            var bubbles = true;
            var cancelable = true;

            var event;

            if (typeof CustomEvent === "function") {
                event = new CustomEvent(key, {
                    detail: detail,
                    bubbles: bubbles,
                    cancelable: cancelable
                });
            } else { // deprecated
                event = document.createEvent("CustomEvent");
                event.initCustomEvent(key, bubbles, cancelable, detail);
            }

            node.dispatchEvent(event);
        },

        /**
         * Assign an attribute to the current object. This function is only
         * relevant if you call it by specifying a `this` context (with bind(),
         * call() or apply()).
         * @param string key The specific key where to assign the value
         * @param string value The value to store
         */
        assign_attribute: function(key, value)
        {
            this[key] = value;
        },

        /**
         * Insert a DOM node after another.
         * @link http://stackoverflow.com/a/4793630/1071486
         * @param Node reference_node
         * @param Node new_node
         */
        insert_after: function(reference_node, new_node)
        {
            reference_node.parentNode.insertBefore(new_node, reference_node.nextSibling);
        },

        /**
         * Parse a XML string.
         * @param string xml The XML to parse
         */
        parse_xml: function(xml)
        {
            var parser = new DOMParser();

            return parser.parseFromString(xml, "text/xml");
        },

        /**
         * Convert a nodelist to an array.
         * @link http://stackoverflow.com/a/2735133/1071486
         * @param Object obj The object to convert.
         */
        nodelist_to_array: function(obj) {
            var array = [];
            for (var i = 0, max = obj.length; i < max; i++) {
                array[i] = obj[i];
            }
            return array;
        },

        /**
         * Form the complete URI from the URL and the URN.
         * @param string url Can be null, in this case fetched from
         * window.location
         * @param string urn The URN
         * @return string The URN prefixed by the URL (if needed)
         */
        form_uri: function(url, urn)
        {
            // if the URN is relative, prefix it with the URL
            if (/^\/[^\/]/.test(urn)) {
                url = url || window.location.protocol + '//' + window.location.host;
                return url + urn;
            }

            // else leave it as is
            return urn;
        },

        /**
         * Check if the given URI is another domain.
         * @param string uri The URI to check.
         * @return boolean true if on a different domain, else false
         */
        is_cross_domain: function(uri)
        {
            var regex = "^(?:/.+|" + window.location.protocol + "//" + window.location.host + ")";
            return !JS.regex_test(regex, uri);
        },

        /**
         * Delete all the children of the given node.
         * @link http://stackoverflow.com/a/3955238/1071486
         * @param DOMElement node
         */
        delete_all_children: function(node)
        {
            while (node.firstChild) {
                node.removeChild(node.firstChild);
            }
        },

        /**
         * Scroll to the bottom of the given element.
         * @link http://stackoverflow.com/a/270628/1071486
         * @param DOMElement node
         */
        scroll_to_bottom: function(node)
        {
            node.scrollTop = node.scrollHeight;
        },

        jsonToDOM: jsonToDOM

    };

})();

/******************************************************************************
 *                                                                            *
 *  Module class                                                              *
 *                                                                            *
 ******************************************************************************/

/**
 * Module class constructor.
 * @param Object param The parameters to construct the module
 * @return Module The newly created module
 */
function Module(param)
{
    var f = JS.assign_attribute.bind(this);

    // Copy all the param into this
    JS.each(param, function(key, value) {
        f(key, value);
    });

    // Fill the type with the appropriate const if not defined
    this.type = (typeof param.type === 'undefined') ? Module.TYPE.UNKNOWN : param.type;

    // Fetch module properties from the Storage
    this.fetch_properties();
}


/******************
 * Public methods *
 ******************/

/**
 * Return the storage key for the module.
 * @return string The storage key
 */
Module.prototype.get_storage_key = function()
{
    return 'module.' + this.name;
};

/**
 * Disable the module.
 */
Module.prototype.disable = function()
{
    this.properties.enabled = false;
    this.save_properties();
};

/**
 * Enable the module.
 */
Module.prototype.enable = function()
{
    this.properties.enabled = true;
    this.save_properties();
};

/**
 * Toggle the module.
 */
Module.prototype.toggle = function()
{
    this.properties.enabled = !this.properties.enabled;
    this.save_properties();
};

/**
 * Check if the module is enabled.
 * @return bool true if the module is enabled, false otherwise
 */
Module.prototype.is_enabled = function()
{
    return !!this.properties.enabled;
};

/**
 * Save the module properties in the Storage.
 */
Module.prototype.save_properties = function()
{
    Storage.set(this.get_storage_key(), JSON.stringify(this.properties));
};


/**
 * Fetch the properties from the Storage. Update the module with the fetched
 * ones.
 */
Module.prototype.fetch_properties = function()
{
    // Fetch its properties from the Storage
    var storage_properties = JSON.parse(Storage.get(this.get_storage_key()));

    // Merge the storage properties into the default properties
    JS.merge(this.properties, storage_properties);
};


/*************
 * Constants *
 *************/

/**
 * The different property types.
 */

Module.PROPERTY = {};
Module.PROPERTY.BOOLEAN = 0;


/********************************
 * Static methods and variables *
 ********************************/

/**
 * Stores all the registered modules, before they are initialised. Never access
 * it directly.
*/
Module.registered_ = [];

/**
 * Stores all the modules. Never access it directly.
 */
Module.modules_ = {};

/**
 * Indexes all the modules by type. Never access it directly.
 */
Module.modules_by_type_ = {};

/**
 * The different module types. You can read this variable directly.
 */
Module.TYPE = {};
Module.TYPE.UNKNOWN_TYPE = 1;

/**
 * The order in which the module types should be browsed.
 */
Module.TYPE_LOADING_ORDER = [];

/**
 * The different properties categories (optional).
 */
Module.PROPERTY_CATEGORY = {};
Module.PROPERTY_CATEGORY.UNKNOWN_CATEGORY = 1;

/**
 * The property categories priority order (optional).
 */
Module.PROPERTY_CATEGORY_PRIORITY_ORDER = [];

/**
 * Register a function returning a configuration object which will be used to
 * instanciate a new module.
 *
 *         Module.add(function() {
 *             var private_var;
 *
 *             return {
 *                 param1: 'hello',
 *                 param2: 'world'
 *             };
 *         });
 *
 * @param Function param_constructor A function returning the configuration object
 */
Module.register = function(param_constructor)
{
    Module.registered_.push(param_constructor);
};

/**
 * Allocate and initialise all the modules
 */
Module.init = function()
{
    // Loop to initialise any registered module
    Module.registered_.forEach(function(param_constructor) {
        var param = param_constructor();

        // Check if the module can run in the current environment / website
        // language
        if (typeof param === 'undefined' ||
            typeof param.actions === 'undefined' ||
            typeof param.actions.can_run !== 'function' ||
            !param.actions.can_run()) {

            return;
        }

        // Create the module
        var module = new Module(param);

        // Add module to the list
        Module.modules_[module.name] = module;

        // Insert it in the type index
        if (typeof Module.modules_by_type_[module.type] === 'undefined') {
            Module.modules_by_type_[module.type] = [];
        }
        Module.modules_by_type_[module.type].push(module);

        // Call the 'init' method if any
        if (typeof module.actions.init === 'function') {
            module.actions.init.call(module);
        }
    });

    // Empty the registered modules list
    Module.registered_ = null;
};

/**
 * Return the number of modules.
 * @return integer The number of modules
 */
Module.count = function()
{
    return Object.keys(Module.modules_).length;
};

/**
 * Return the number of modules for a specific type.
 * @param Const type The module type
 * @return integer The number of module
 */
Module.count_on_type = function(type)
{
    return Module.modules_by_type_[type].length;
};

/**
 * Iterate over all the modules.
 * @param Function callback The function to call, it takes one parameter: a
 *                          module.
 */
Module.iterate = function(callback)
{
    var modules = Module.modules_;

    JS.each(modules, function(key, value) {
        callback(value);
    });
};

/**
 * Iterate over a specific module type.
 * @param Const type The module type
 * @param Function callback The function to call, it takes one parameter: a
 *                          module.
 */
Module.iterate_on_type = function(type, callback)
{

    // if the type is not defined, abort
    if (typeof Module.modules_by_type_[type] === 'undefined') {
        return;
    }

    Module.modules_by_type_[type].forEach(function(module) {
        callback(module);
    });
};

/**
 * Iterate over all the modules in the priority order.
 * @param Function callback The function to call, it takes one parameter: a
 *                          module.
 */
Module.iterate_in_priority_order = function(callback)
{
    Module.TYPE_LOADING_ORDER.forEach(function(type) {
        Module.iterate_on_type(Module.TYPE[type], callback);
    });
};

/**
 * Return a specific module from its name.
 * @param string name The module name
 * @return Module The desired module
 * @return null if the module is not found
 */
Module.get = function(name)
{
    if (Module.modules_.hasOwnProperty(name)) {
        return Module.modules_[name];
    }
    return null;
};

/**
 * Define a new module type.
 * @param string type The new type
 */
Module.add_type = function(type)
{
    var biggest = 0;

    // Obtain a unique type id by fetching the biggest id and adding one;
    for (var key in Module.TYPE) {
        if (Module.TYPE.hasOwnProperty(key)) {
            biggest = (Module.TYPE[key] > biggest) ?
                Module.TYPE[key] : biggest;
        }
    }

    Module.TYPE[type] = biggest + 1;
};

/**
 * Define a new types priority order.
 * @param string[] An array of module type name
 */
Module.set_type_loading_order = function(new_order)
{
    Module.TYPE_LOADING_ORDER = new_order;
};

/**
 * Define a new property category.
 * @param string property_category The new category
 */
Module.add_property_category = function(property_category)
{
    var biggest = 0;

    // Obtain a unique type id by fetching the biggest id and adding one;
    for (var key in Module.PROPERTY_CATEGORY) {
        if (Module.PROPERTY_CATEGORY.hasOwnProperty(key)) {
            biggest = (Module.PROPERTY_CATEGORY[key] > biggest) ?
                Module.PROPERTY_CATEGORY[key] : biggest;
        }
    }

    Module.PROPERTY_CATEGORY[property_category] = biggest + 1;
};

/**
 * Define a new property categories priority order.
 * @param string[] An array of property categories name
 */
Module.set_property_category_priority_order = function(new_order)
{
    Module.PROPERTY_CATEGORY_PRIORITY_ORDER = new_order;
};

/******************************************************************************
 *                                                                            *
 *  Storage class                                                       *
 *                                                                            *
 ******************************************************************************/

function Storage()
{
}


/*************
 * Constants *
 *************/


/********************************
 * Static methods and variables *
 ********************************/

/*
 * Will prefix the key every time it is used. Never modify this value directly.
 */
Storage.key_prefix = '';

/**
 * Define a new prefix. A string is mandatory, otherwise the setter aborts.
 * @param string new_prefix The new prefix
 */
Storage.set_key_prefix = function(new_prefix)
{
    if (typeof new_prefix !== "string") {
        return;
    }
    Storage.key_prefix = new_prefix;
};

/**
 * Set a value in the storage.
 * @param string key The key to insert or distort
 * @param mixed value The value to put in
 */
Storage.set = function(key, value)
{
    localStorage[Storage.key_prefix + key] = value + ''; // explicit string cast
};

/**
 * Get a value from the storage.
 * @param string key The key to fetch
 * @return string The related value
 * @return null if the key isn't found
 */
Storage.get = function(key)
{
    var ret = localStorage[Storage.key_prefix + key];

    return (typeof ret === 'undefined') ? null : ret;
};

Module.register(function() {

	var MODULE_NAME = 'anti_abuse_counter';

	/******************
	 * Module context *
	 ******************/

	var ANTI_ABUSE_NOTIFIER_ID = 'd2ne_abuse_counter';

	var _time_interval = null;
	var _oldBackPack = 0;

	/**
	 * Add the i18n strings for this module.
	 */
	function add_i18n()
	{
		var i18n = {};

		i18n[I18N.LANG.EN] = {};
		i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Prevent bank abuse';
		i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Display a an anti-abuse counter on the bank page';
		i18n[I18N.LANG.EN][MODULE_NAME + '_label'] = 'Anti-abuse counter:';
		i18n[I18N.LANG.EN][MODULE_NAME + '_reset_in'] = 'reset in';
		i18n[I18N.LANG.EN][MODULE_NAME + '_prevent'] = 'Please wait until the end of the countdown before to try again.';

		i18n[I18N.LANG.FR] = {};
		i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Activer le compteur anti-abus';
		i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Sur la page de la banque, affiche un compteur pour prévenir les abus et empêche de prendre plus de 5 objets toutes les 15 minutes.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_label'] = 'Compteur anti-abus :';
		i18n[I18N.LANG.FR][MODULE_NAME + '_reset_in'] = 'reset dans';
		i18n[I18N.LANG.FR][MODULE_NAME + '_prevent'] = 'Il serait préférable d\'attendre la fin du décompte avant d\'essayer à nouveau.';


		I18N.set(i18n);
	}

	function get_notifier_div()
	{
		var current_time = Math.floor(+new Date() / 1000);
		var time_left = this.properties.end_of_abuse - current_time + 1;
		var hour_left = Math.floor(time_left / 60);
		var min_left = Math.floor(time_left % 60);

		return JS.jsonToDOM(["div", { "id": ANTI_ABUSE_NOTIFIER_ID, "class": "extractCpt" },
			["img", { "src": "/gfx/icons/tag_1.gif" }],
			' ' + I18N.get(MODULE_NAME + '_label') + ' ',
			["strong", {},
				this.properties.attempt_left
			],
			" (" + I18N.get(MODULE_NAME + '_reset_in') + ' ',
			["strong", {},
				((this.properties.end_of_abuse > 0) ? ('' + hour_left + ':' + ((min_left < 10) ? '0' : '') + min_left) : '∞')
			],
			")"
		], document);
	}

	function refresh_notifier()
	{
		var div = document.getElementById(ANTI_ABUSE_NOTIFIER_ID);
		if (JS.is_defined(div)) {
			div.parentNode.replaceChild(get_notifier_div.call(this), div);
		}
	}

	function updateAttemptLeft()
	{
		if (!D2N.is_on_page_in_city('bank') && !D2N.is_on_page_in_city('well')) {
			// If we are not in the bank
			// NOR in the well
			// our backpack can still be modified (in our home for instance)
			_oldBackPack = $("#myBag li img:not([src*='small_empty_inv.gif'])").length;
			return;
		}

		var newBackPack = $("#myBag li img:not([src*='small_empty_inv.gif'])").length;
		if(newBackPack > _oldBackPack) {
			this.properties.attempt_left -= 1;
			if (this.properties.attempt_left < 0) {
				this.properties.attempt_left = 0;
			}
			this.properties.end_of_abuse = (+new Date() / 1000) + (15 * 60);
			this.save_properties();
			refresh_notifier.call(this);
		}

		_oldBackPack = newBackPack;

		document.removeEventListener('d2n_gamebody_reload', updateAttemptLeft.bind(this));
	}

	function on_object_click(event)
	{
		if (!D2N.is_on_page_in_city('bank') && !D2N.is_on_page_in_city('well')) {
			return;
		}
		
		// The click must occur on the object icon, the object number or the
		// link
		if (['IMG', 'SPAN', 'A'].indexOf(event.target.nodeName) < 0) {
			return;
		}

		if (this.properties.attempt_left < 1) {
			event.cancelBubble = true;
			event.stopPropagation();
			event.preventDefault();
			alert(I18N.get(MODULE_NAME + '_prevent'));
			return;
		}

		// We check after the reload if the backpack has changed
		_oldBackPack = $("#myBag li img:not([src*='small_empty_inv.gif'])").length;

		document.addEventListener('d2n_gamebody_reload', updateAttemptLeft.bind(this));
	}

	function inject_click_listener()
	{
		// Add listener
		if (D2N.is_on_page_in_city('bank')) {
			document.querySelector('.tools.stocks.cityInv').addEventListener('click', on_object_click.bind(this), true);
		} else if (D2N.is_on_page_in_city('well')) {
			var session = null;
			D2N.get_session_key(function(sk) {
				session = sk;
            });

			var el = $(".button[href*='well_water']");
			if(el.attr("onclick").indexOf("confirm") > 0){
				el.attr("onclick", "");
				el.click(function(ev){
					if (this.properties.attempt_left < 1) {
						event.cancelBubble = true;
						event.stopPropagation();
						event.preventDefault();
						alert(I18N.get(MODULE_NAME + '_prevent'));
						return;
					}
					if(confirm('Confirmer ?')){
						on_object_click.bind(this)(ev);
						js.XmlHttp.get('city/well_water?sk=' + session, null);
					}
					return false;
				}.bind(this));
			}
		}
	}

	function on_each_second(event)
	{
		var current_time = Math.floor(+new Date() / 1000);
		if (current_time > this.properties.end_of_abuse) {
			this.properties.attempt_left = this.properties.max_attempts;
			this.properties.end_of_abuse = 0;
			this.save_properties();
		}
		refresh_notifier.call(this);
	}

	function inject_notifier()
	{
		// Add notifier
		var selector = "div.right";
		if (D2N.is_on_page_in_city('well')) {
			selector = "div.wellPane";
		}

		JS.wait_for_selector(selector, function(el) {
			el.insertBefore(get_notifier_div.call(this), el.firstChild);

			if (_time_interval === null) {
				_time_interval = setInterval(on_each_second.bind(this), 1000);
			}
		}.bind(this));
	}


	/************************
	 * Module configuration *
	 ************************/

	return {

		name: MODULE_NAME,
		type: Module.TYPE.INTERFACE_ENHANCEMENT,

		properties: {
			enabled: false,
			max_attempts: 5,
            isProtected: false
		},

		configurable: {
			enabled: {
				category: Module.PROPERTY_CATEGORY.BANK,
				type: Module.PROPERTY.BOOLEAN,
				short_desc_I18N: MODULE_NAME + '_short_desc',
				full_desc_I18N: MODULE_NAME + '_full_desc'
			}
		},

		actions: {
			can_run: function() {
				return true;
			},

			init: function() {
				add_i18n();
			},

			load: function() {
				JS.injectCSS(
					'#' + ANTI_ABUSE_NOTIFIER_ID + ' {' +
						'cursor: auto;' +
					'}');

				if(document.getElementsByClassName("chaos").length !== 0){
					this.properties.max_attempts = 10;
				} else {
					this.properties.max_attempts = 5;
				}

				if (JS.is_defined(this.properties.attempt_left) !== true) {
					this.properties.attempt_left = this.properties.max_attempts;
				}
				if (JS.is_defined(this.properties.end_of_abuse) !== true) {
					this.properties.end_of_abuse = 0;
				}

				this.save_properties();

				document.addEventListener('d2n_gamebody_reload', function() {
					// We must check the end of abuse, even if we are outside (we likely are going to spend more than 15 minutes outside)
					var current_time = Math.floor(+new Date() / 1000);
					if (current_time > this.properties.end_of_abuse) {
						this.properties.attempt_left = this.properties.max_attempts;
						this.properties.end_of_abuse = 0;
						this.save_properties();
					}

					if (!D2N.is_on_page_in_city('bank') && !D2N.is_on_page_in_city('well')) {
						return;
					}

					if(document.getElementsByClassName("chaos").length !== 0){
						this.properties.max_attempts = 10;
					} else {
						this.properties.max_attempts = 5;
					}

					this.save_properties();

					if (JS.is_defined(document.getElementById(ANTI_ABUSE_NOTIFIER_ID))) {
						return;
					}

					inject_notifier.call(this);
					inject_click_listener.call(this);
				}.bind(this), false);
			}
		}

	};
});

Module.register(function() {

    var MODULE_NAME = 'external_tools_bar_autosync';

    /******************
     * Module context *
     ******************/
    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_name'] = 'autosync';
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Enable automatic synchronization';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Allow the script to automatically update external tools at each page refresh.';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_name'] = 'autosync';
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Activer la synchronisation automatique';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Ajoute la possibilité de synchroniser automatiquement les sites externes à chaque rafraîchissement de la page.';

        I18N.set(i18n);
    }

    /************************
     * Module configuration *
     ************************/

    /**
    * Load the module.
    */
    function load_module()
    {
        document.addEventListener('d2n_gamebody_reload', function(event) {
            js.XmlHttp._d2ne_onStart = js.XmlHttp.onStart;
            js.XmlHttp.onStart = function(ev){
                if(ev === undefined) return;
                var url = ev.url;
                if(url.startsWith('/outside/go')) {
                    var module = Module.get("external_tools_bar");
                    JS.wait_for_id(module.container_id, function(node){

                        if (module === null || !module.is_enabled()) {
                            return;
                        }

                        module.actions.refresh();
                    });
                }
                this._d2ne_onStart();
            };
        }.bind(this), true);
        /*document.addEventListener('d2n_gamebody_reload', function(event) {
            if (!D2N.is_on_page_in_city('bank') && !D2N.is_outside()) {
                return;
            }

            // En attendant d'avoir une solution pour synchro uniquement quand on se déplace
            return;

            var module = Module.get("external_tools_bar");
            JS.wait_for_id(module.container_id, function(node){

                if (module === null || !module.is_enabled()) {
                    return;
                }

                module.actions.refresh();
            });
        }.bind(this), false);*/
    }

    return {

        name: MODULE_NAME,
        type: Module.TYPE.CONTAINER,

        properties: {
            enabled: false,
            isProtected: false
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.EXTERNAL_TOOL,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc'
            }
        },

        actions: {
            can_run: function() {
                return false;
            },

            init: function(){
                add_i18n();
            },

            load: function() {
                load_module();
            }
        }

    };
});

Module.register(function() {

    var MODULE_NAME = 'button';

    /******************
     * Module context *
     ******************/

    var D2NE_CONFIG_HASH = '#d2ne/configuration';

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_title'] = 'Die2Nite Enhancer';
        i18n[I18N.LANG.EN][MODULE_NAME + '_description'] = 'Die2Nite Enhancer allows you to enhance your game experience.';
        i18n[I18N.LANG.EN][MODULE_NAME + '_configuration_panel_button'] = 'Configuration Panel';
        i18n[I18N.LANG.EN][MODULE_NAME + '_contact_link'] = 'Contact the developer';
        i18n[I18N.LANG.EN][MODULE_NAME + '_support_link'] = 'Support the developer';
        i18n[I18N.LANG.EN][MODULE_NAME + '_bug_tracker_link'] = 'Bug tracker';
        i18n[I18N.LANG.EN][MODULE_NAME + '_license_link'] = 'License';
        i18n[I18N.LANG.EN][MODULE_NAME + '_version'] = 'Version';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_description'] = 'Die2Nite Enhancer vous permet d\'améliorer votre expérience de jeu.';
        i18n[I18N.LANG.FR][MODULE_NAME + '_configuration_panel_button'] = 'Panneau de configuration';
        i18n[I18N.LANG.FR][MODULE_NAME + '_contact_link'] = 'Contacter le développeur';
        i18n[I18N.LANG.FR][MODULE_NAME + '_support_link'] = 'Supporter le développeur';
        i18n[I18N.LANG.FR][MODULE_NAME + '_bug_tracker_link'] = 'Bug tracker';
        i18n[I18N.LANG.FR][MODULE_NAME + '_license_link'] = 'License';
        i18n[I18N.LANG.FR][MODULE_NAME + '_version'] = 'Version';

        I18N.set(i18n);
    }

    function insert_button_style()
    {
        JS.injectCSS(
            '#sites {' +
                'z-index: 14;' +
            '}' +

            '#d2ne_button {' +
                'margin-top: -210px;' +
                'position: absolute;' +
                'margin-left: 43px;' +
                'z-index: 13;' +
                'background-color: #5c2b20;' +
                'border: 1px solid #000000;' +
                'max-width: 862px;' +
                'font-size: 0.9em;' +
                'line-height: 23px;' +
                'border: 1px solid #f0d79e;' +
                'outline: 1px solid black;' +
                'padding-left: 5px;' +
                'padding-right: 5px;' +
            '}' +

            '#d2ne_button > h1 {' +
                'height: auto;' +
                'font-size: 8pt;' +
                'text-transform: none;' +
                'font-variant: small-caps;' +
                'background: none;' +
                'cursor: help;' +
                'margin: 0;' +
                'padding: 0;' +
            '}' +
            '#d2ne_button:hover > h1 {' +
                'border-bottom: 1px solid #b37c4a;' +
                'margin-bottom: 5px;' +
            '}' +

            '#d2ne_button > h1 > span {' +
                'display: none;' +
            '}' +
            '#d2ne_button:hover > h1 > span {' +
                'display: inline;' +
            '}' +

            '#d2ne_button > div {' +
                'display: none;' +
                'width: 430px;' +
            '}' +
            '#d2ne_button:hover > div {' +
                'display: block;' +
            '}' +

            '#d2ne_button p {' +
                'margin: 0;' +
            '}' +

            '#d2ne_button ul {' +
                'margin: 0;' +
                'padding: 0;' +
                'list-style: none;' +
                'margin-bottom: 8px;' +
                'font-size: 13px;' +
            '}' +

            '#d2ne_button ul li {' +
                'margin: 0;' +
                'padding: 0;' +
            '}' +

            '#d2ne_button li img {' +
                'width: 16px;' +
                'height: 16px;' +
                'margin-right: 5px;' +
                'vertical-align: -11%;' +
            '}' +

            '#d2ne_button li a {' +
                'display: block;' +
                'width: 205px;' +
                'height: 19px;' +
                'overflow: hidden;' +
                'text-decoration: none;' +
            '}' +
            '#d2ne_button li a:hover {' +
                'background-color: #696486;' +
            '}' +

            '#d2ne_button li a span {' +
                'text-decoration: underline;' +
            '}' +

            '#d2ne_button a.button {' +
                'margin: 12px auto;' +
            '}'
        );
    }

    function insert_button_dom()
    {
        var button = JS.jsonToDOM(["div", { "id": "d2ne_button" },
            ["h1", {},
                ["img", { "src": "/gfx/forum/smiley/h_city_up.gif", "alt": "" }],
                ["span", {}, ' ' + I18N.get(MODULE_NAME + '_title')]
            ],

            ["div", {},
                ["p", {}, I18N.get(MODULE_NAME + '_description')],

                ["a", { "class": "button", onclick: function() {
                    JS.dispatch_event('d2ne_load_configuration_panel');
                }}, I18N.get(MODULE_NAME + '_configuration_panel_button') ],

                ["ul", {},
                    ["li", {},
                        ["a", { href: 'mailto:ludofloria@aifesdespets.fr?Subject=[D2NE]%20' },
                            ["img", { src: '/gfx/icons/small_mail.gif' }],
                            ["span", {}, I18N.get(MODULE_NAME + '_contact_link')]
                        ]
                    ],
                    ["li", {},
                        ["a", { href: 'https://www.gittip.com/aymericbeaumet/', target: '_blank' },
                            ["img", { src: '/gfx/icons/item_fest.gif' }],
                            ["span", {}, I18N.get(MODULE_NAME + '_support_link')]
                        ]
                    ],
                    ["li", {},
                        ["a", { href: 'https://github.com/aymericbeaumet/die2nite_enhancer/issues', target: '_blank' },
                            ["img", { src: '/gfx/icons/small_rocket.gif' }],
                            ["span", {}, I18N.get(MODULE_NAME + '_bug_tracker_link')]
                        ]
                    ],
                    ["li", {},
                        ["a", { href: 'http://opensource.org/licenses/Zlib', target: '_blank' },
                            ["img", { src: '/gfx/icons/small_nice_lock.gif' }],
                            ["span", {}, I18N.get(MODULE_NAME + '_license_link')]
                        ]
                    ],
                    ["li", {},
                        ["a", { href: 'https://github.com/aymericbeaumet/die2nite_enhancer/', target: '_blank' },
                            ["img", { src: (D2NE.is_restricted_mode() ? '/gfx/icons/item_tamed_pet.gif' : '/gfx/icons/item_tamed_pet_drug.gif') }],
                            ["span", {}, I18N.get(MODULE_NAME + '_version') + ' 0.2.4-beta33']
                        ]
                    ]
                ]
            ]
        ], document);

        JS.wait_for_id('main2', function(node) {
            node.insertBefore(button, node.firstChild);
        });
    }

    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.CONTAINER,

        properties: {
            enabled: true
        },

        actions: {
            can_run: function() {
                return true;
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                insert_button_style();
                insert_button_dom();
            }
        }

    };
});

Module.register(function() {

	var MODULE_NAME = 'chatty';

	/******************
	 * Module context *
	 ******************/

	var SERVER_URL = 'http://chatty.d2ne.beaumet.me:8080';

	var socket_ = null;

	/**
	 * Add the i18n strings for this module.
	 */
	function add_i18n()
	{
		var i18n = {};

		i18n[I18N.LANG.EN] = {};
		i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Enable the chat module';
		i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Allow you to chat with the players in your town.';
		i18n[I18N.LANG.EN][MODULE_NAME + '_title'] = 'Die2Nite Enhancer - Chatty';
		i18n[I18N.LANG.EN][MODULE_NAME + '_send_button'] = 'Send';
		i18n[I18N.LANG.EN][MODULE_NAME + '_world_room'] = 'World';

		i18n[I18N.LANG.FR] = {};
		i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Activer le module de chat';
		i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Vous permet de parler avec les joueurs de votre ville.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_send_button'] = 'Ecrire';
		i18n[I18N.LANG.FR][MODULE_NAME + '_world_room'] = 'Monde';

		I18N.set(i18n);
	}

	/**************************************************************************/
	// Read from the socket

	/**
	 * Add a new message into the container for the given room. Nothing happen
	 * if the room container can not be found.
	 */
	function add_new_message(room, date, user, content)
	{
		var dateObject = new Date(date);
		var hours = ('00' + dateObject.getHours()).slice(-2);
		var minutes = ('00' + dateObject.getMinutes()).slice(-2);

		var container = document.getElementById('messages-room-' + room);
		var li = JS.jsonToDOM(['li', {},
			['span', { title: dateObject.toLocaleDateString() + ' ' + dateObject.toLocaleTimeString() }, '[' + hours + ':' + minutes + ']'],
			' ' + user + ' : ' + content
		], document);

		if (!container) {
			return;
		}

		container.appendChild(li);
		JS.scroll_to_bottom(container.parentNode);

		$("#notif").text("*");
	}

	/**
	 * Wait for a new message.
	 */
	function socket_wait_for_new_message()
	{
	  socket_.on('newMessage', function(data) {
		  add_new_message(data.room, data.date, data.user, data.content);
	  });
	}

	/**
	 * Wait for a new room to init.
	 */
	function socket_wait_for_new_room()
	{
	  socket_.on('newRoom', function(data) {
		  var m;
		  for (var i = data.messages.length - 1; i >= 0; i -= 1) {
			  m = data.messages[i];
			  add_new_message(data.name, m.date, m.user, m.content);
		  }
	  });
	}

	/**************************************************************************/
	// Write on the socket

	/**
	 * Send a message to the given room.
	 */
	function socket_emit_send_message(room, user, content)
	{
		socket_.emit('sendMessage', {
			room: room,
			user: user,
			content: content
		});
	}

	/**
	 * Leave the given room.
	 */
	function socket_emit_leave_room(roomName)
	{
		socket_.emit('leaveRoom', {
			room: roomName
		});
	}

	/**
	 * Join the given room.
	 */
	function socket_emit_join_room(roomName)
	{
		socket_.emit('joinRoom', {
			room: roomName
		});
	}

	/**************************************************************************/

	/**
	 * Return the full name of the active room.
	 */
	function get_active_room()
	{
		var el = document.querySelector('.d2ne_chatty_tab input[type="radio"]:checked');

		if (!el) {
			return null;
		}
		return el.id.replace(/^tab-room-/, '');
	}

	/**
	 * Display the messages for the active room.
	 */
	function display_active_room_messages()
	{
		// Remove all the d2ne_chatty_active class
		var els = document.getElementsByClassName('d2ne_chatty_active');
		for (var i = 0; i < els.length; i += 1) {
			els[i].classList.remove('d2ne_chatty_active');
		}

		// Add the class for the active room
		var active_room = get_active_room();
		var el = document.getElementById('messages-room-' + active_room);
		el.classList.add('d2ne_chatty_active');

		// Scroll room to top
		JS.scroll_to_bottom(el.parentNode);
	}

	/**
	 * Add the room tab + messages container. Also subscribe to the new
	 * messages.
	 */
	function join_room(room_name)
	{
		var full_name = window.location.hostname + '|' + room_name;

		// Add the room tab
		var is_first_tab = document.getElementsByClassName('d2ne_chatty_tab').length === 0;
		var unique_tab_id = 'tab-room-' + full_name;
		var tab = ['div', { "class": 'd2ne_chatty_tab' },
			['input', { "name": 'tab-room', "type": 'radio', "id": unique_tab_id, "onclick": function() { display_active_room_messages(); }}],
			['label', { "for": unique_tab_id }, room_name]
		];
		if (is_first_tab) {
			tab[2][1].checked = 'checked'; // Check the first radio button
		}
		document.querySelector('.d2ne_chatty_rooms td').appendChild(JS.jsonToDOM(tab, document));

		// Add the messages container
		var unique_messages_id = 'messages-room-' + full_name;
		var container = ['ul', { "class": 'd2ne_chatty_messages_container', id: unique_messages_id }];
		document.querySelector('.d2ne_chatty_messages > td > div').appendChild(JS.jsonToDOM(container, document));

		// Inform the server we want to join the room
		socket_emit_join_room(full_name);
	}

	/**
	 * Return true if the chat is displayed.
	 */
	function is_chat_displayed()
	{
		var chat = document.getElementById('d2ne_chatty');

		if (!chat) {
			return false;
		}

		var style = getComputedStyle(chat);

		return parseInt(style.bottom) === 0;
	}

	/**
	 * Called when the header is clicked.
	 */
	function on_header_click()
	{
		var chat = document.getElementById('d2ne_chatty');

		if (is_chat_displayed()) {
			// Hide it
			chat.style.bottom = -(chat.scrollHeight - 25) + 'px';
		} else {
			// Show it
			chat.style.bottom = 0;
		}
	}

	/**
	 * Called either when the form is submitted (with enter) or when the send
	 * button is clicked.
	 */
	function on_form_submit()
	{
		// Find the input
		var input = document.querySelector('#d2ne_chatty .d2ne_chatty_input input[type="text"]');
		if (!input) { return; }

		// Ensure that the message is not empty
		var content = input.value.trim();
		if (!content || content.length === 0) { return; }

		// Send the message
		var active_room = get_active_room();
		if (!active_room) { // if the active room can not be found, abort
			return;
		}
		socket_emit_send_message(active_room, D2N.get_player_name(), content);

		// Empty the input
		input.value = '';

		// Cancel form submit
		return false;
	}

	function insert_chat_dom()
	{
		// The chat is already here
		if(document.getElementById('d2ne_chatty')) return;

		var json = [
		"table", { id: 'd2ne_chatty' },
			["tr", { "class": 'd2ne_chatty_header', onclick: on_header_click },
				["td", {},
					['span', {}, I18N.get(MODULE_NAME + '_title')],
					['span', {id: 'notif'}, ""]
				]
			],
			["tr", { "class": 'd2ne_chatty_rooms' },
				["td", {}]
			],
			["tr", { "class": 'd2ne_chatty_messages' },
				["td", {},
					["div", {}]
				]
			],
			["tr", { "class": 'd2ne_chatty_input' }, ["td", {},
				["form", { action: 'javascript:void(0);', onsubmit: on_form_submit },
					["input", { type: 'text', "class": 'field' }],
					["a", { "class": 'button', onclick: on_form_submit }, I18N.get(MODULE_NAME + '_send_button')]
				]
			]]
		];

		JS.wait_for_tag('body', function(nodes) {
			nodes[0].appendChild(JS.jsonToDOM(json, document));
		});
	}

	function insert_chat_style()
	{
		JS.injectCSS(
			'#d2ne_chatty {' +
				'position: fixed;' +
				'bottom: -281px;' + // -(input height + messages height + tab height)
				'left: 0;' +
				'right: 0;' +
				'margin: 0 auto;' +
				'z-index: 9;' +
				'width: 500px;' +
				'border-collapse: collapse;' +
				'-webkit-transition: all 0.5s ease;' +
				'-moz-transition: all 0.5s ease;' +
				'transition: all 0.5s ease;' +
				'border: 1px solid orange;' +
				'border-bottom: none;' +
			'}' +

				'#d2ne_chatty .d2ne_chatty_header td {' +
					'height: 25px;' +
					'background: rgb(41, 35, 26);' +
					'text-align: center;' +
					'vertical-align: middle;' +
					'cursor: pointer;' +
				'}' +

				'#d2ne_chatty .d2ne_chatty_rooms td {' +
					'height: 40px;' +
					'background: rgb(63, 56, 32);' +
					'vertical-align: middle;' +
				'}' +
					'#d2ne_chatty .d2ne_chatty_rooms td .d2ne_chatty_tab {' +
						'float: left;' +
					'}' +
					'#d2ne_chatty .d2ne_chatty_rooms td .d2ne_chatty_tab + .d2ne_chatty_tab {' +
						'padding-left: 10px;' +
					'}' +

				'#d2ne_chatty .d2ne_chatty_messages td {' +
					'height: 205px;' +
					'background: rgb(105, 74, 80);' +
				'}' +
					'#d2ne_chatty .d2ne_chatty_messages td div {' +
						'overflow-y: scroll;' +
						'height: 100%;' +
					'}' +
					'#d2ne_chatty .d2ne_chatty_messages td ul {' +
						'margin: 0;' +
						'padding: 0;' +
						'list-style: none;' +
						'display: none;' +
					'}' +
					'#d2ne_chatty .d2ne_chatty_messages td ul.d2ne_chatty_active {' +
						'display: initial;' +
					'}' +

				'#d2ne_chatty .d2ne_chatty_input td {' +
					'height: 30px;' +
					'background: rgb(80, 51, 80);' +
				'}' +
					'#d2ne_chatty .d2ne_chatty_input td a.button {' +
						'float: right;' +
						'margin: 0px;' +
						'padding: 0px;' +
						'width: 85px;' +
						'text-align: center;' +
					'}' +
					'.d2ne_chatty_input input {' +
						'width: 90%;' +
					'}'
		);
	}


	/************************
	 * Module configuration *
	 ************************/

	return {

		name: MODULE_NAME,
		type: Module.TYPE.INTERFACE_ENHANCEMENT,

		properties: {
			enabled: false,
            isProtected: false
		},

		configurable: {
			enabled: {
				category: Module.PROPERTY_CATEGORY.GENERAL,
				type: Module.PROPERTY.BOOLEAN,
				short_desc_I18N: MODULE_NAME + '_short_desc',
				full_desc_I18N: MODULE_NAME + '_full_desc'
			}
		},

		actions: {
			can_run: function() {
				return true;
			},

			init: function() {
				add_i18n();
			},

			load: function() {
				insert_chat_style();
				insert_chat_dom();
				// Only continue when the city name is accessible
				D2N.get_city_name(function() {
					socket_ = io.connect(SERVER_URL);

					socket_wait_for_new_message();
					socket_wait_for_new_room();

					join_room(I18N.get(MODULE_NAME + '_world_room'));
					var city_name = D2N.get_city_name();
					if (city_name) {
						join_room(city_name);
					}

					display_active_room_messages();
				});
			}
		}

	};
});

Module.register(function() {

    var MODULE_NAME = 'citizen_data_extractor';

    /******************
     * Module context *
     ******************/

    var EXTRACT_BUTTON_ID = 'd2ne_extract_citizen_data';

    var SOUL_TIMEOUT = 5000; //ms

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_extract_alive_citizens_data_button_text'] = 'Extract alive citizens data';
        i18n[I18N.LANG.EN][MODULE_NAME + '_extract_soul_data_button_text'] = 'Extract the soul data';
        i18n[I18N.LANG.EN][MODULE_NAME + '_extract_game_history_button_text'] = 'Extract citizens data';
        i18n[I18N.LANG.EN][MODULE_NAME + '_warning'] = 'This could take some time, please do not stop the process. Do you want to continue?';
        i18n[I18N.LANG.EN][MODULE_NAME + '_work_in_progress'] = 'Work in progress...';
        i18n[I18N.LANG.EN][MODULE_NAME + '_citizen'] = 'Citizen';
        i18n[I18N.LANG.EN][MODULE_NAME + '_result_notif'] = 'Results:';
        i18n[I18N.LANG.EN][MODULE_NAME + '_use_die2nite_picto_diff'] = 'You can use this results here.';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_extract_alive_citizens_data_button_text'] = 'Extraire les données des vivants';
        i18n[I18N.LANG.FR][MODULE_NAME + '_extract_soul_data_button_text'] = 'Extraire les données de l\'âme';
        i18n[I18N.LANG.FR][MODULE_NAME + '_extract_game_history_button_text'] = 'Extraire les données des citoyens';
        i18n[I18N.LANG.FR][MODULE_NAME + '_warning'] = 'Cette opération peut prendre quelques temps, merci de ne pas interrompre le processus. Voulez-vous continuer ?';
        i18n[I18N.LANG.FR][MODULE_NAME + '_work_in_progress'] = 'Récupération en cours...';
        i18n[I18N.LANG.FR][MODULE_NAME + '_citizen'] = 'Citoyen';
        i18n[I18N.LANG.FR][MODULE_NAME + '_result_notif'] = 'Résultats :';
        i18n[I18N.LANG.FR][MODULE_NAME + '_use_die2nite_picto_diff'] = 'Vous pouvez utiliser ces résultats ici.';

        I18N.set(i18n);
    }

    function extract_username(ret, next) {
        JS.wait_for_selector('div.tid_userName > span:last-child', function(node) {
            ret.username = node.textContent;
            next();
        });
    }

    function extract_icon(ret, next) {
        JS.wait_for_selector('div.tid_userSheet img.tid_avatarImg', function(node) {
            ret.iconURL = node.src;
            next();
        }, 1, next);
    }

    function extract_title(ret, next) {
        JS.wait_for_selector('span.tid_userTitle', function(node) {
            ret.title = node.textContent.trim();
            if (ret.title.length === 0) {
                delete ret.title;
            }
            next();
        });
    }

    function extract_profile_likes(ret, next) {
        JS.wait_for_selector('span.tid_likeButtonCompact', function(node) {
            ret.profileLikes = parseInt(node.textContent);
            if (typeof ret.profileLikes !== 'number' || isNaN(ret.profileLikes)) {
                ret.profileLikes = 0;
            }
            next();
        });
    }

    function extract_avatar(ret, next) {
        JS.wait_for_selector('div.tid_userSheet img.tid_avatarImg', function(node) {
            ret.avatarURL = node.src;
            next();
        }, 1, next);
    }

    function extract_sex(ret, next) {
        JS.wait_for_selector('div.tid_age > img:first-child', function(node) {
            var m = node.src.match(/\/([a-z]+)\.png$/);
            switch (m) {
                case 'male':
                    ret.sex = 'male'; break;
                case 'female':
                    ret.sex = 'female'; break;
            }
            next();
        });
    }

    function extract_age(ret, next) {
        JS.wait_for_selector('div.tid_age', function(node) {
            ret.age = parseInt(node.textContent);
            if (typeof ret.age !== 'number' || isNaN(ret.age)) {
                delete ret.age;
            }
            next();
        });
    }

    function extract_city(ret, next) {
        JS.wait_for_selector('div.tid_city', function(node) {
            ret.city = node.textContent.trim();
            if (ret.city === '--') {
                delete ret.city;
            }
            next();
        });
    }

    function extract_country(ret, next) {
        JS.wait_for_selector('div.tid_country', function(node) {
            ret.country = node.textContent.trim();
            if (ret.country === '--') {
                delete ret.country;
            }
            next();
        });
    }

    function extract_description(ret, next) {
        JS.wait_for_selector('div.tid_userStatus div.tid_content p', function(node) {
            ret.descriptionHTML = node.innerHTML;
            next();
        }, 1, next);
    }

    function extract_pictos_score(ret, next) {
        JS.wait_for_selector('div.tid_overall', function(node) {
            ret.pictosScore = parseInt(node.textContent);
            next();
        });
    }

    function extract_pictos_top(ret, next) {
        var picto;
        var count;

        JS.wait_for_selector_all('div.tid_top > div.tid_goal.tid_bg3.tid_rare:not([style])', function(nodes) {
            for (var i = 0, max = nodes.length; i < max; i++) {
                ret.pictosTop = ret.pictosTop || {};
                picto = nodes[i].querySelector('div.tid_icon > img').src.match(/\/img\/icons\/(.+)\.gif$/)[1];
                count = parseInt(nodes[i].textContent);
                ret.pictosTop[picto] = count;
            }
            next();
        }, 1, next);
    }

    function extract_pictos_rare(ret, next) {
        var picto;
        var count;

        JS.wait_for_selector_all('div.tid_block.tid_stats div.tid_goal.tid_bg3.tid_tip.tid_rare.tid_parsed', function(nodes) {
            for (var i = 0, max = nodes.length; i < max; i++) {
                ret.pictosRare = ret.pictosRare || {};
                picto = nodes[i].querySelector('img.tid_icon').src.match(/\/img\/icons\/(.+)\.gif$/)[1];
                count = parseInt(nodes[i].querySelector('div.tid_count > span:first-child').textContent);
                ret.pictosRare[picto] = count;
            }
            next();
        }, 1, next);
    }

    function extract_pictos(ret, next) {
        var picto;
        var count;

        JS.wait_for_selector_all('div.tid_block.tid_stats div.tid_goal.tid_bg3.tid_tip.tid_parsed:not(.tid_rare)', function(nodes) {
            for (var i = 0, max = nodes.length; i < max; i++) {
                ret.pictos = ret.pictos || {};
                picto = nodes[i].querySelector('img.tid_icon').src.match(/\/img\/icons\/(.+)\.gif$/)[1];
                count = parseInt(nodes[i].querySelector('div.tid_count > span:first-child').textContent);
                ret.pictos[picto] = count;
            }
            next();
        }, 1, next);
    }

    function extract_soul_score(ret, next) {
        JS.wait_for_selector('div.score > strong', function(node) {
            ret.soulScore = parseInt(node.textContent);
            next();
        });
    }

    function extract_citizen_info(citizen_id, on_finish)
    {
        var timeout = null;

        var listener = function() {
            document.removeEventListener('d2n_gamebody_reload', listener);

            var ret = {};
            ret.D2NEVersion = '0.2.4-beta33';
            ret.rootURL = window.location.host;
            if (citizen_id) {
                ret.id = parseInt(citizen_id);
            }
            ret.time = Math.floor(new Date() / 1000);

            extract_username(ret, function next() {
                extract_icon(ret, function next() {
                    extract_title(ret, function next() {
                        extract_profile_likes(ret, function next() {
                            extract_avatar(ret, function next() {
                                extract_sex(ret, function next() {
                                    extract_age(ret, function next() {
                                        extract_city(ret, function next() {
                                            extract_country(ret, function next() {
                                                extract_description(ret, function next() {
                                                    extract_pictos_score(ret, function next() {
                                                        extract_pictos_top(ret, function next() {
                                                            extract_pictos_rare(ret, function next() {
                                                                extract_pictos(ret, function next() {
                                                                    extract_soul_score(ret, function next() {
                                                                        // Everything has been parsed, clear the timeout
                                                                        clearTimeout(timeout);
                                                                        on_finish(ret);
                                                                    });
                                                                });
                                                            });
                                                        });
                                                    });
                                                });
                                            });
                                        });
                                    });
                                });
                            });
                        });
                    });
                });
            });
        };

        if (citizen_id) {
            // If timeout is reached, try to extract the data again. Except if
            // the account is deleted
            timeout = setTimeout(function() {
                document.removeEventListener('d2n_gamebody_reload', listener);

                // If the account is deleted, abort
                if (document.querySelector('.tid_noTid') !== null) {
                    return on_finish(null);
                }

                return extract_citizen_info(citizen_id, on_finish);
            }, SOUL_TIMEOUT);

            document.addEventListener('d2n_gamebody_reload', listener, false);
            D2N.redirect_to_citizen_soul(citizen_id, '' + (Math.floor(new Date() / 1000)));
        } else {
            listener();
        }
    }

    /****/

    function extract_citizens_info(citizens_id, onFinish)
    {
        var ret = [];
        var max = citizens_id.length;

        var handler = function(i) {
            if (i >= max) {
                return onFinish(ret);
            }

            // Notify of the progress
            D2N.notification(I18N.get(MODULE_NAME + '_work_in_progress') + ' ' +
                I18N.get(MODULE_NAME + '_citizen') + ' ' + (i + 1) + '/' + max);

            return extract_citizen_info(citizens_id[i], function(citizen_info) {
                if (citizen_info !== null) {
                    ret.push(citizen_info);
                }
                handler(i + 1);
            });
        };
        handler(0);
    }

    /****/

    function extract_citizens_id(selector)
    {
        var citizens_link = document.querySelectorAll(selector);
        var ret = [];
        var regex = /uid=(\d+)/;
        var regex_results;

        for (var i = 0, max = citizens_link.length; i < max; i++) {
            regex_results = regex.exec(citizens_link[i].href);
            if (regex_results.length > 1) {
                ret.push(regex_results[1]);
            }
        }

        return ret;
    }

    /****/

    function on_soul_button_click()
    {
        D2N.notification(I18N.get(MODULE_NAME + '_work_in_progress'));

        extract_citizen_info(null, function(citizen_info) {
            D2N.notification(JS.jsonToDOM(['div', {},
                I18N.get(MODULE_NAME + '_result_notif'),
                ['textarea', { readonly: '', onclick: 'this.select()',
                               style: 'width: 100%; max-width: 100%; height: initial; margin: 0 auto; margin-top: 5px; margin-bottom: 3px; padding: 0;' },
                    JSON.stringify(citizen_info)
                ],
                ['a', { href: 'http://beaumet.me/die2nite_picto_diff/', target: '_blank' },
                    I18N.get(MODULE_NAME + '_use_die2nite_picto_diff')
                ]
            ], document));
        });
    }

    function on_citizens_list_button_click(selector, final_redirection)
    {
        if (confirm(I18N.get(MODULE_NAME + '_warning')) === false) {
            return;
        }

        D2N.notification(I18N.get(MODULE_NAME + '_work_in_progress'));

        var citizens_id = extract_citizens_id(selector);
        extract_citizens_info(citizens_id, function(citizens_info) {

            var after_getting_back_to_citizens_list = function() {
                document.removeEventListener('d2n_gamebody_reload', after_getting_back_to_citizens_list, false);
                D2N.notification(JS.jsonToDOM(['div', {},
                    I18N.get(MODULE_NAME + '_result_notif'),
                    ['textarea', { readonly: '', onclick: 'this.select()',
                                   style: 'width: 100%; max-width: 100%; height: initial; margin: 0 auto; margin-top: 5px; margin-bottom: 3px; padding: 0;' },
                        JSON.stringify(citizens_info)
                    ],
                    ['a', { href: 'http://beaumet.me/die2nite_picto_diff/', target: '_blank' },
                        I18N.get(MODULE_NAME + '_use_die2nite_picto_diff')
                    ]
                ], document));
            };
            document.addEventListener('d2n_gamebody_reload', after_getting_back_to_citizens_list, false);

            JS.redirect(final_redirection);
        });
    }

    /****/

    function get_button(i18n_key, on_click, style)
    {
        return JS.jsonToDOM(["a", { id: EXTRACT_BUTTON_ID, "class": "button", style: '' + style,
                                    onclick: on_click },
            ["img", { "src": "/gfx/icons/item_book_gen_letter.gif" }],
            ' ' + I18N.get(MODULE_NAME + i18n_key)
        ], document);
    }

    function inject_game_history_button()
    {
        JS.wait_for_selector('div.ghost div.footer', function(el) {
            el.appendChild(get_button('_extract_game_history_button_text', function() {
                on_citizens_list_button_click('td.name > a[href^="#ghost/user?uid="]', window.location.hash);
            }, 'margin-left: 30px;'));
        });
    }

    function inject_soul_button()
    {
        JS.wait_for_selector('div.footSearch', function(el) {
            el.parentNode.insertBefore(get_button('_extract_soul_data_button_text', on_soul_button_click), el);
        });
    }

    function inject_town_citizens_button()
    {
        JS.wait_for_selector('div.citizens', function(el) {
            el.appendChild(get_button('_extract_alive_citizens_data_button_text', function() {
                on_citizens_list_button_click('a.tid_user[href^="/#ghost/city?go=ghost/user?uid="]', window.location.hash);
            }));
        });
    }


    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: true,
            isProtected: false
        },

        actions: {
            can_run: function() {
                return true;
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                document.addEventListener('d2n_gamebody_reload', function() {
                    if (JS.is_defined(document.getElementById(EXTRACT_BUTTON_ID))) {
                        return;
                    }

                    if (D2N.is_on_page_in_city('citizens')) {
                        inject_town_citizens_button();
                    } else if (/^#ghost\/city\?go=ghost\/user(?:\?uid=\d+)?;sk=/.test(window.location.hash)) {
                        inject_soul_button();
                    } else if (/^#ghost\/city\?go=ghost\/ingame\?id=\d+;sk=/.test(window.location.hash)) {
                        inject_game_history_button();
                    }
                }, false);
            }
        }

    };
});

Module.register(function() {

	var MODULE_NAME = 'configuration_panel';

	/******************
	 * Module context *
	 ******************/

	var D2NE_CONFIG_HASH = '#d2ne/configuration';

	/**
	 * Inject in the input field to store the corresponding module name.
	 */
	var INPUT_DATA_MODULE_KEY = 'data-module';

	/**
	 * Inject in the input field to store the corresponding module name.
	 */
	var INPUT_DATA_MODULE_PROPERTY_KEY = 'data-module-property';

	/**
	 * Add the i18n strings for this module.
	 */
	function add_i18n()
	{
		var i18n = {};

		i18n[I18N.LANG.EN] = {};
		i18n[I18N.LANG.EN][MODULE_NAME + '_title'] = 'Die2Nite Enhancer - Configuration Panel';
		i18n[I18N.LANG.EN][MODULE_NAME + '_help_image_url'] = '/gfx/loc/en/helpLink.gif';
		i18n[I18N.LANG.EN][MODULE_NAME + '_general_category'] = 'General';
		i18n[I18N.LANG.EN][MODULE_NAME + '_sites_category'] = 'External sites';
		i18n[I18N.LANG.EN][MODULE_NAME + '_bank_category'] = 'Bank';
		i18n[I18N.LANG.EN][MODULE_NAME + '_construction_category'] = 'Construction Sites';
		i18n[I18N.LANG.EN][MODULE_NAME + '_citizens_category'] = 'Citizens';
		i18n[I18N.LANG.EN][MODULE_NAME + '_outside_category'] = 'Outside';
		i18n[I18N.LANG.EN][MODULE_NAME + '_external_tool_category'] = 'External Tools';
		i18n[I18N.LANG.EN][MODULE_NAME + '_soul_category'] = 'Soul page';
		i18n[I18N.LANG.EN][MODULE_NAME + '_forum_category'] = 'Forums';
		i18n[I18N.LANG.EN][MODULE_NAME + '_interface_category'] = 'Interface';
		i18n[I18N.LANG.EN][MODULE_NAME + '_various_category'] = 'Various';
		i18n[I18N.LANG.EN][MODULE_NAME + '_save_button'] = 'Save';
		i18n[I18N.LANG.EN][MODULE_NAME + '_close_button'] = 'Close';

		i18n[I18N.LANG.FR] = {};
		i18n[I18N.LANG.FR][MODULE_NAME + '_title'] = 'Die2Nite Enhancer - Panneau de configuration';
		i18n[I18N.LANG.FR][MODULE_NAME + '_help_image_url'] = '/gfx/loc/fr/helpLink.gif';
		i18n[I18N.LANG.FR][MODULE_NAME + '_general_category'] = 'Général';
		i18n[I18N.LANG.FR][MODULE_NAME + '_sites_category'] = 'Sites externes';
		i18n[I18N.LANG.FR][MODULE_NAME + '_bank_category'] = 'Banque';
		i18n[I18N.LANG.FR][MODULE_NAME + '_construction_category'] = 'Chantiers';
		i18n[I18N.LANG.FR][MODULE_NAME + '_citizens_category'] = 'Citoyens';
		i18n[I18N.LANG.FR][MODULE_NAME + '_outside_category'] = 'Outre-Monde';
		i18n[I18N.LANG.FR][MODULE_NAME + '_external_tool_category'] = 'Outils Externes';
		i18n[I18N.LANG.FR][MODULE_NAME + '_soul_category'] = 'Page d\'âme';
		i18n[I18N.LANG.FR][MODULE_NAME + '_forum_category'] = 'Forums';
		i18n[I18N.LANG.FR][MODULE_NAME + '_interface_category'] = 'Interface';
		i18n[I18N.LANG.FR][MODULE_NAME + '_various_category'] = 'Divers';
		i18n[I18N.LANG.FR][MODULE_NAME + '_save_button'] = 'Sauvegarder';
		i18n[I18N.LANG.FR][MODULE_NAME + '_close_button'] = 'Fermer';

		i18n[I18N.LANG.ES] = {};
		i18n[I18N.LANG.ES][MODULE_NAME + '_help_image_url'] = '/gfx/loc/es/helpLink.gif';

		i18n[I18N.LANG.DE] = {};
		i18n[I18N.LANG.DE][MODULE_NAME + '_help_image_url'] = '/gfx/loc/de/helpLink.gif';

		I18N.set(i18n);
	}

	/**
	 * Fetch the configuration from the configuration panel and inject it in the
	 * local storage.
	 */
	function save_configuration()
	{
		var configuration_panel = document.querySelector('#d2ne_configuration_panel > div:nth-child(2)');

		for (var i = 0, max = configuration_panel.childElementCount; i < max; i += 1) {
			var el = configuration_panel.childNodes[i];

			// skip if not div
			if (el.tagName !== 'DIV') {
				continue;
			}

			// Grab input dom element
			var input_node = el.firstChild;
			var module_name = input_node.getAttribute(INPUT_DATA_MODULE_KEY);
			var module = Module.get(module_name);
			var property = input_node.getAttribute(INPUT_DATA_MODULE_PROPERTY_KEY);

			// Get the value
			var input_data;
			switch (module.configurable[property].type) {
				case Module.PROPERTY.BOOLEAN:
					input_data = input_node.checked;
					break;
				default:
					input_data = null;
					break;
			}

			// Inject it into the object and save
			module.properties[property] = input_data;
			module.save_properties();
		}
	}

	/**
	 * Return the category title.
	 * @return Array the title
	 */
	function get_category_title(category_name)
	{
		var icon = null;
		var text = null;

		switch (category_name) {
			case Module.PROPERTY_CATEGORY.GENERAL:
				icon = '/gfx/icons/item_chair.gif';
				text = I18N.get(MODULE_NAME + '_general_category');
				break;
			case Module.PROPERTY_CATEGORY.SITES:
				icon = '/gfx/forum/smiley/h_human.gif';
				text = I18N.get(MODULE_NAME + '_sites_category');
				break;
			case Module.PROPERTY_CATEGORY.BANK:
				icon = '/gfx/icons/item_money.gif';
				text = I18N.get(MODULE_NAME + '_bank_category');
				break;
			case Module.PROPERTY_CATEGORY.CONSTRUCTION:
				icon = '/gfx/forum/smiley/h_refine.gif';
				text = I18N.get(MODULE_NAME + '_construction_category');
				break;
			case Module.PROPERTY_CATEGORY.CITIZENS:
				icon = '/gfx/forum/smiley/h_human.gif';
				text = I18N.get(MODULE_NAME + '_citizens_category');
				break;
			case Module.PROPERTY_CATEGORY.OUTSIDE:
				icon = '/gfx/icons/r_camp.gif';
				text = I18N.get(MODULE_NAME + '_outside_category');
				break;
			case Module.PROPERTY_CATEGORY.EXTERNAL_TOOL:
				icon = '/gfx/icons/item_radio_on.gif';
				text = I18N.get(MODULE_NAME + '_external_tool_category');
				break;
			case Module.PROPERTY_CATEGORY.SOUL:
				icon = '/gfx/icons/small_ghost_blue.gif';
				text = I18N.get(MODULE_NAME + '_soul_category');
				break;
			case Module.PROPERTY_CATEGORY.FORUM:
				icon = '/gfx/icons/r_rp.gif';
				text = I18N.get(MODULE_NAME + '_forum_category');
				break;
			case Module.PROPERTY_CATEGORY.INTERFACE:
				icon = '/gfx/icons/item_iphone.gif';
				text = I18N.get(MODULE_NAME + '_interface_category');
				break;
			default:
				icon = '/gfx/icons/item_pet_chick.gif';
				text = I18N.get(MODULE_NAME + '_various_category');
				break;
		}

		return ["h2", {},
				   ["img", { src: icon }],
				   text
			   ];
	}

	/**
	 * Get and sort the modules into categories.
	 */
	function get_modules_json_in_categories()
	{
		var categories = {};

		Module.iterate(function(module) {

			// if configurable object does not exist, skip the module
			if (typeof module.configurable === 'undefined') {
				return;
			}
			var css = "";
			if(module.properties.isProtected) {
				css = "protected";
			}

			JS.each(module.configurable, function(key, value) {
				var input_id = 'd2ne_module_' + module.name + '_' + key;
				var input_value = module.properties[key];
				var onclick = "";

				if(value.url !== undefined){
					onclick = "window.open('" + value.url + "');";
				}
				var json_node =
					["div", {
						"class": css
					},
						null,
						["label", { "for": input_id }, I18N.get(value.short_desc_I18N)],
						["a", { 
							"class": "helpLink d2ne", 
							"href": "#", "onclick": onclick + "return false;",
							"onmouseover": "js.HordeTip.showHelp(this, " + JSON.stringify(I18N.get(value.full_desc_I18N)) + ");",
							"onmouseout": "js.HordeTip.hide()",
							"tabindex": "-1"
						}, ["img", { "src": I18N.get(MODULE_NAME + '_help_image_url') }]]
					];

				var node = ["/* node_html_type */", { /* node attributes */ }];
				switch (value.type) {
					case Module.PROPERTY.BOOLEAN:
						node[0] = "input";
						node[1].type = "checkbox";

						if (input_value === true) {
							node[1].checked = ''; // declare a checked attribute
						}
						break;

					default:
						return;
				}

				node[1].id = input_id;
				node[1][INPUT_DATA_MODULE_KEY] = module.name;
				node[1][INPUT_DATA_MODULE_PROPERTY_KEY] = key;

				json_node[2] = node;

				// Store the node
				var c = null;
				if (typeof value.category === 'undefined') {
					c = Module.PROPERTY_CATEGORY.UNKNOWN_CATEGORY;
				} else {
					c = value.category;
				}

				categories[c] = categories[c] || [];
				categories[c].push(json_node);
			});
		});

		return categories;
	}

	function insert_configuration_panel_dom()
	{
		var el;
		if ((el = document.getElementById('d2ne_configuration_panel'))) {
			el.style.display = 'block';
			D2N.show_empty_notification();
			return;
		}

		var configuration_panel_json = ["div", {}];

		var buttons = ["table", {},
			["tr", {},
				["td", {},
					["a", { 
						href: "javascript:void(0)", 
						"class": "button", 
						onclick: function() {
							scroll(0, 0);
							D2N.hide_empty_notification();
							document.getElementById('d2ne_configuration_panel').style.display = 'none'; }
						},
						I18N.get(MODULE_NAME + '_close_button')
					]
				],
				["td", {},
					["a", { href: "javascript:void(0)", "class": "button", onclick: function() { save_configuration(); JS.reload(); } },
						I18N.get(MODULE_NAME + '_save_button')
					]
				]
			]
		];

		configuration_panel_json.push(['h1', {}, I18N.get(MODULE_NAME + '_title')]);

		configuration_panel_json.push(buttons);

		// Iterate over each module in each category
		var categories = get_modules_json_in_categories();
		Module.PROPERTY_CATEGORY_PRIORITY_ORDER.forEach(function(category_name) {
			var category_id = Module.PROPERTY_CATEGORY[category_name];
			var category = categories[category_id];

			if (!JS.is_defined(category)) {
				return;
			}

			configuration_panel_json.push(get_category_title(category_id));

			category.forEach(function(json_node) {
				configuration_panel_json.push(json_node);
			});
		});

		configuration_panel_json.push(buttons);

		JS.wait_for_class('bigBg2', function(node) {
			node[0].appendChild(JS.jsonToDOM(["div", { id: 'd2ne_configuration_panel' },
				["div"],
				configuration_panel_json,
				["div"]
			], document));
			D2N.show_empty_notification();
		});
	}

	function insert_configuration_panel_style()
	{
		JS.injectCSS(
			// Mandatory to still see the tooltips above the configuration panel
			'#tooltip {' +
				'z-index: 242 !important;' +
			'}' +

			'#d2ne_configuration_panel {' +
				'width: 598px;' +
				'position: absolute;' +
				'z-index: 142;' +
				'left: 0;' +
				'right: 0;' +
				'top: 70px;' +
				'margin: 0 auto;' +
				'margin-bottom: 70px;' +
				'background-color: rgb(122, 73, 40);' +
			'}' +

			'#d2ne_configuration_panel > div:nth-child(1) {' +
				'background-image: url("/gfx/design/panel_header.gif");' +
				'height: 18px;' +
				'-webkit-border-top-left-radius: 48px;' +
				'-moz-border-top-left-radius: 48px;' +
				'border-top-left-radius: 48px;' +
				'-webkit-border-top-right-radius: 48px 30px;' +
				'-moz-border-top-right-radius: 48px 30px;' +
				'border-top-right-radius: 48px 30px;' +
				'border: none;' +
			'}' +

			'#d2ne_configuration_panel > div:nth-child(3) {' +
				'background-image: url("/gfx/design/panel_footer.gif");' +
				'height: 16px;' +
				'-webkit-border-bottom-left-radius: 48px;' +
				'-moz-border-bottom-left-radius: 48px;' +
				'border-bottom-left-radius: 48px;' +
				'-webkit-border-bottom-right-radius: 48px 30px;' +
				'-moz-border-bottom-right-radius: 48px 30px;' +
				'border-bottom-right-radius: 48px 30px;' +
				'border: none;' +
			'}' +

			'#d2ne_configuration_panel > div:nth-child(2) {' +
				'padding: 0px 25px;' +
				'padding-left: 27px;' +
				'background: url("/gfx/design/panel_bg.gif");' +
			'}' +

			'#d2ne_configuration_panel h1 {' +
				'margin: 0 auto;' +
				'height: initial;' +
				'padding-bottom: 15px;' +
				'padding-left: 51px;' +
			'}' +

			'#d2ne_configuration_panel h2 {' +
				'padding-bottom: 3px;' +
				'width: 100%;' +
				//'border: 1px dotted rgba(179, 124, 74, 0.5);' +
				'border-bottom: 1px solid rgba(179, 124, 74, 0.6);' +
				'border-right: 1px solid rgba(179, 124, 74, 0.6);' +
				'background-color: rgb(112, 62, 33);' +
			'}' +

			'#d2ne_configuration_panel h2 img {' +
				'margin-left: 5px;' +
				'margin-right: 5px;' +
				'vertical-align: -20%;' +
			'}' +

			'#d2ne_configuration_panel div div {' +
				'margin-bottom: 1px;' +
			'}' +

			'#d2ne_configuration_panel a.helpLink {' +
				'float: right;' +
			'}' +

			'#d2ne_configuration_panel table {' +
				'margin: 0 auto;' +
			'}' +

			'#d2ne_configuration_panel a.button {' +
				'margin: 0 auto;' +
				'margin-top: 11px;' +
				'text-align: center;' +
			'}' +

			'#d2ne_configuration_panel .protected {' +
				'color: orange;' +
			'}'
		);
	}

	/************************
	 * Module configuration *
	 ************************/

	return {

		name: MODULE_NAME,
		type: Module.TYPE.CONTAINER,

		properties: {
			enabled: true
		},

		actions: {
			can_run: function() {
				return true;
			},

			init: function() {
				add_i18n();
			},

			load: function() {
				insert_configuration_panel_style();

				document.addEventListener('d2ne_load_configuration_panel', function() {
					insert_configuration_panel_dom();
				}, false);

				document.addEventListener('d2ne_hide_configuration_panel', function() {
					var el = document.getElementById('d2ne_configuration_panel');
					el.style.display = 'none';
				}, false);
			}
		}
	};
});

Module.register(function() {

    var MODULE_NAME = 'construction_max_ap';

    /******************
     * Module context *
     ******************/

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Use max AP in constructions';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'While in the construction page, use your actual number of AP instead of the default 1 AP.';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Construire et réparer avec un maximum de PA';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Pré-remplit la case pour construire ou réparer un chantier avec le maximum de PA disponible au lieu de 1 par défaut.';

        I18N.set(i18n);
    }

    function change_ap()
    {
        D2N.get_number_of_ap_cp(function(ap) {
            var constructions = JS.nodelist_to_array(document.querySelectorAll('tr.building'));

            constructions.forEach(function(construction) {
                var ap_remaining = null;

                var tmp = construction.getElementsByClassName('rscItem');
                if (tmp.length < 1) {
                    return;
                }

                ap_remaining = parseInt(tmp[0].textContent);

                var max_ap = (ap_remaining > ap) ? ap : ap_remaining;
                var ap_field = construction.querySelector('div[id^="moreForm_"] form input[type="text"]');

                ap_field.value = max_ap;
            });
        });
    }


    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: false,
            isProtected: false
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.CONSTRUCTION,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc'
            }
        },

        actions: {
            can_run: function() {
                return true;
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                document.addEventListener('d2n_apchange', function() {
                    if (!D2N.is_on_page_in_city('buildings')) {
                        return;
                    }
                    change_ap();
                }, false);
            }
        }

    };
});

Module.register(function() {

    var MODULE_NAME = 'cyanide_protection';

    /******************
     * Module context *
     ******************/

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Enable cyanide protection';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Avoid to eat cyanide by accident by deleting the link to use it.';
        i18n[I18N.LANG.EN][MODULE_NAME + '_cyanide'] = 'Cyanide';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Protéger contre le cyanure';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Supprime le lien permettant l\'utilisation du cyanure pour éviter les accidents.';
        i18n[I18N.LANG.FR][MODULE_NAME + '_cyanide'] = 'Cyanure';

        i18n[I18N.LANG.ES] = {};
        i18n[I18N.LANG.ES][MODULE_NAME + '_cyanide'] = 'Cianuro';

        i18n[I18N.LANG.DE] = {};
        i18n[I18N.LANG.DE][MODULE_NAME + '_cyanide'] = 'Cyanide';

        I18N.set(i18n);
    }


    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: false,
            isProtected: true
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.GENERAL,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc'
            }
        },

        actions: {
            can_run: function() {
                return !D2NE.is_restricted_mode();
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                document.addEventListener('d2n_gamebody_reload', function() {
                    D2N.remove_player_action(I18N.get(MODULE_NAME + '_cyanide'));
                }, false);
            }
        }

    };
});

Module.register(function() {

    var MODULE_NAME = 'davf_old_forum_tags';

    /******************
     * Module context *
     ******************/

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Old forum tags (by Davf)';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'More information here [fr]: http://userstyles.org/styles/97674/hordes-balises-ancienne. Script by Davf, integrated with his permission. Click to open link.';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Hordes Balises Anciennes (par Davf)';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Plus d\'information ici : http://userstyles.org/styles/97674/hordes-balises-ancienne. Script par Davf, intégré avec sa permission. . Cliquez pour ouvrir le lien.';

        I18N.set(i18n);
    }


    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: false,
            isProtected: false
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.FORUM,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc',
                url: "http://userstyles.org/styles/97674/hordes-balises-ancienne"
            }
        },

        actions: {
            can_run: function() {
                return true;
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                JS.injectCSS(
                    // From: http://userstyles.org/styles/97674/hordes-balises-ancienne
                    // Credit: Davf, thanks to him for giving his permission to use his CSS
                    // Integrated on: Mar 18, 2014
                    '.tid_editorContent .tid_spoil {' +
                        'cursor: help !important;' +
                        'background-image: url("http://data.hordes.fr/gfx/design/spoiler.gif") !important;' +
                        'display: block !important;' +
                        'background-repeat: no-repeat !important;' +
                        'background-color: #4c5536 !important;' +
                        'background-position: 4px 1px !important;' +
                        'color: #4d5537 !important;' +
                    '}' +
                    '.tid_editorContent .tid_spoil .tid_wspoil:first-child {' +
                        'padding-left: 60px !important;' +
                    '}' +
                    '.tid_editorContent .tid_spoil:hover {' +
                        'visibility: visible !important;' +
                        'background-image: url("http://data.hordes.fr/gfx/design/spoiler.gif") !important;' +
                        'background-color:transparent !important;' +
                        'color : #98a675 !important;' +
                    '}' +
                    '.tid_editorContent cite {' +
                        'color: #ddab76 !important;' +
                        'border-left: 1px dashed #ddab76 !important;' +
                        'border-bottom: 1px dashed #ddab76 !important;' +
                        'opacity: 0.9 !important;' +
                    '}  ' +
                    '.tid_containerWrapper .tid_editorContent .tid_roleplay .tid_wroleplay a {' +
                        'text-decoration: underline !important;' +
                        'color: #ddab76 !important;' +
                    '}' +
                    '#tid_forum .tid_threadNotice.tid_announce {' +
                        'background-image: url("http://data.hordes.fr/gfx/icons/small_help.gif") !important;' +
                        'background-color: #773939 !important;' +
                        'border: 1px solid #ddab76 !important;' +
                        'outline: 2px solid #5c2b20 !important;' +
                        'color: #f86 !important;' +
                        'border-radius: 0px !important;' +
                        '-moz-border-radius: 0px !important;' +
                        '-webkit-border-radius: 0px !important;' +
                        '-moz-box-shadow: none !important;' +
                        '-webkit-box-shadow: none !important;' +
                        'box-shadow: none !important;' +
                    '}' +
                    '#tid_forum .tid_threadNotice.tid_announce .tid_list {' +
                        'border-top: 1px dashed #f86 !important;' +
                        'padding: 4px 0px !important;' +
                        'margin: 4px 0px !important;' +
                    '}' +
                    '' +
                    '#tid_forum .tid_threadNotice.tid_niceLock {' +
                        'background-image: url("http://data.hordes.fr/gfx/icons/small_nice_lock.gif") !important;' +
                        'background-color: #5C0000 !important;' +
                        'border: 1px solid #ddab76 !important;' +
                        'outline: 2px solid #5c2b20 !important;' +
                        'border-radius: 0px !important;' +
                        '-moz-border-radius: 0px !important;' +
                        '-webkit-border-radius: 0px !important;' +
                        '-moz-box-shadow: none !important;' +
                        '-webkit-box-shadow: none !important;' +
                        'box-shadow: none !important;' +
                    '}' +
                    '#tid_forum .tid_threadNotice.tid_lock {' +
                        'background-color: #5C0000 !important;' +
                        'border: 1px solid #ddab76 !important;' +
                        'outline: 2px solid #5c2b20 !important;' +
                        'border-radius: 0px !important;' +
                        '-moz-border-radius: 0px !important;' +
                        '-webkit-border-radius: 0px !important;' +
                        '-moz-box-shadow: none !important;' +
                        '-webkit-box-shadow: none !important;' +
                        'box-shadow: none !important;' +
                    '}' +
                    '' +
                    '.tid_roleplay {' +
                        'background-color: #79432b !important;' +
                        'color: #ddab76 !important;' +
                        'border: 0px solid transparent !important;' +
                        'background-image: url("http://data.hordes.fr/gfx/icons/small_rp.gif") !important;' +
                        'background-repeat: no-repeat !important;' +
                        'background-position: 4px 3px !important;' +
                        'max-width: none !important;' +
                        'padding-left: 24px !important;' +
                        'margin-bottom: 0px !important;' +
                        '-moz-box-shadow: 0px 0px 2px #79432b !important;' +
                        '-webkit-box-shadow: 0px 0px 2px #79432b !important;' +
                        'box-shadow: 0px 0px 2px #79432b !important;' +
                    '}' +
                    '.tid_wroleplay {' +
                        'background-image: none !important;' +
                        'display: inline-block !important;' +
                    '}' +
                    '.tid_wroleplay .tid_roleplay {' +
                        'background: none !important;' +
                        'padding: 0px !important;' +
                        'margin: 0px !important;' +
                    '}' +
                    '' +
                    '.tid_containerWrapper .tid_editorContent .tid_roleplay .tid_wroleplay em {' +
                        'color: #f0d79e !important;' +
                        'opacity: 1 !important;' +
                    '}' +
                    '.tid_containerWrapper .tid_editorContent .tid_roleplay .tid_wroleplay strong {' +
                        'color: #afb3cf !important;' +
                        'opacity: 1 !important;' +
                    '}' +
                    '.tid_containerWrapper .tid_editorContent .tid_roleplay .tid_wroleplay em em{' +
                        'color: #f0d79e !important;' +
                        'opacity: 0.7 !important;' +
                    '}' +
                    '.tid_containerWrapper .tid_editorContent .tid_roleplay .tid_wroleplay strong strong{' +
                        'color: #afb3cf !important;' +
                        'opacity: 0.8 !important;' +
                    '}' +
                    '.tid_containerWrapper .tid_editorContent .tid_roleplay .tid_wroleplay em em em{' +
                        'color: #f0d79e !important;' +
                        'opacity: 0.7 !important;' +
                    '}' +
                    '.tid_containerWrapper .tid_editorContent .tid_roleplay .tid_wroleplay strong strong strong{' +
                        'color: #afb3cf !important;' +
                        'opacity: 0.8 !important;' +
                    '}'
                );
            }
        }

    };
});

Module.register(function() {

    var MODULE_NAME = 'davf_old_forum';

    /******************
     * Module context *
     ******************/

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Old forum menu design (by Davf)';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'More information here [fr]: http://userstyles.org/styles/97652/hordes-forum-ancien. Script by Davf, integrated with his permission. Click to open link.';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Hordes Forum Ancien (par Davf)';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Plus d\'information ici : http://userstyles.org/styles/97652/hordes-forum-ancien. Script par Davf, intégré avec sa permission. Cliquez pour ouvrir le lien.';

        I18N.set(i18n);
    }


    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: false,
            isProtected: false
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.FORUM,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc',
                url: "http://userstyles.org/styles/97652/hordes-forum-ancien"
            }
        },

        actions: {
            can_run: function() {
                return true;
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                JS.injectCSS(
                    // From: http://userstyles.org/styles/97652/hordes-forum-ancien
                    // Credit: Davf, thanks to him for giving his permission to use his CSS
                    // Integrated on: Mar 18, 2014
                    '#tid_forum .tid_forumThread .tid_actionBar .tid_pages {' +
                        'width: 248px !important;' +
                    '}' +
                    '#tid_forum .tid_actionBar .tid_buttonBar {' +
                        'margin: 0px !important;' +
                        'margin-left: 1px !important;' +
                        'margin-top: -2px !important;' +
                    '}' +
                    '.tid_actionBar.tid_bg4 {' +
                        'background-color: #965c36 !important;' +
                    '}' +
                    '.tid_cur.tid_tip.tid_parsed {' +
                        'background-color: transparent !important;' +
                        'background-image: url("http://data.hordes.fr/gfx/design/button.gif") !important;' +
                        'border: 1px solid #98341c !important;' +
                        'border-bottom: 1px solid black !important;' +
                        'margin-top: auto !important;' +
                        'color: #f0d79e !important;' +
                    '}' +
                    '.tid_cur.tid_tip.tid_parsed:hover {' +
                        'border: 1px solid #98341c !important;' +
                        'border-top: 2px solid black !important;' +
                        'border-bottom: 0px solid #98341c !important; ' +
                        'outline: 1px solid #f0d79e !important;' +
                        'color: white !important;' +
                    '}' +
                    '.tid_buttonBar a {' +
                        'background-image: url("http://data.hordes.fr/gfx/design/button.gif") !important;' +
                        'border: 1px solid #98341c !important;' +
                        'border-bottom: 1px solid black !important;' +
                        'margin-right: 1px !important;' +
                        'color: #f0d79e !important;' +
                    '}' +
                    '' +
                    '.tid_buttonBar a:hover {' +
                        'opacity: 0.99 !important;' +
                        'border: 1px solid #98341c !important;' +
                        'border-top: 2px solid black !important;' +
                        'border-bottom: 0px solid #98341c !important; ' +
                        'outline: 1px solid #f0d79e !important;' +
                        'color: white !important;' +
                    '}' +
                    '.tid_buttonBar .tid_off:hover {' +
                        'border: 1px solid #7c5a50 !important;' +
                        'opacity: 1 !important;' +
                        'outline: 1px solid #421d15 !important;' +
                        'color: #dddbd8 !important;' +
                    '}' +
                    '.tid_buttonBar .tid_off {' +
                        'background-image: url("http://data.hordes.fr/gfx/design/button_off.gif") !important;' +
                        'border: 1px solid #7c5a50 !important;' +
                        'opacity: 1 !important;' +
                        'outline: 1px solid #421d15 !important;' +
                        'color: #dddbd8 !important;' +
                    '}' +
                    '.tid_buttonBar a.tid_off img {' +
                        'opacity: 0.3 !important;      ' +
                    '}' +
                    '.tid_postForm button {' +
                        'background-color: transparent !important;' +
                        'margin-top: 4px !important;' +
                        'background-image: url("http://data.hordes.fr/gfx/design/button.gif") !important;' +
                        'border: 1px solid #98341c !important;' +
                        'outline: 1px solid black !important;' +
                        'color: #f0d79e !important;' +
                        '-moz-box-shadow : none !important;' +
                        '-webkit-box-shadow : none !important;' +
                        'box-shadow : none !important;' +
                    '}' +
                    '.tid_postForm button:hover {' +
                        'opacity: 0.99 !important;' +
                        'border: 1px solid #98341c !important;' +
                        'border-top: 2px solid #98341c !important;' +
                        'border-bottom: 0px solid #98341c !important;  ' +
                        'outline: 1px solid #f0d79e !important;' +
                        'color: white !important;' +
                        'padding: 2px 20px !important;' +
                        '-moz-box-shadow : none !important;' +
                        '-webkit-box-shadow : none !important;' +
                        'box-shadow : none !important;' +
                    '}' +
                    '#tid_forum .tid_mainBar .tid_actionBar {' +
                        'opacity: 1;' +
                    '}'
                );
            }
        }

    };
});

Module.register(function() {

    var MODULE_NAME = 'davf_old_soul';

    /******************
     * Module context *
     ******************/

    var VIDEO_WIDTH = 400;
    var VIDEO_HEIGHT = 300;

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Old soul design for the pictogram (by Davf)';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'More information here [fr]: http://userstyles.org/styles/97547/hordes-me-ancienne. Script by Davf, integrated with his permission. Click to open link.';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Hordes Âme Ancienne (par Davf)';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Plus d\'information ici : http://userstyles.org/styles/97547/hordes-me-ancienne. Script par Davf, intégré avec sa permission. Cliquez pour ouvrir le lien.';

        I18N.set(i18n);
    }


    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: false,
            isProtected: false
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.SOUL,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc',
                url: "http://userstyles.org/styles/97547/hordes-me-ancienne"
            }
        },

        actions: {
            can_run: function() {
                return true;
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                JS.injectCSS(
                    // From: http://userstyles.org/styles/97547/hordes-me-ancienne
                    // Credit: Davf, thanks to him for giving his permission to use his CSS
                    // Integrated on: Mar 18, 2014
                    '.passwidth {' +
                        'margin-top: 200px !important;' +
                    '}' +
                    'div.night {' +
                        'margin-bottom: 165px !important;' +
                    '}' +
                    '.tid_userSheet .tid_userSheetBg {' +
                        'background-color: #5c2b20 !important;' +
                        'background-image: url("http://data.twinoid.com/img/design/ugoals_bgLeft.png") !important;' +
                    '}' +
                    '.tid_userSheet .tid_userSheetBg .tid_bgRight {            ' +
                        'background-image: url("http://data.twinoid.com/img/design/ugoals_bgRight.png") !important;' +
                    '}' +
                    '.tid_userSheet .tid_header {' +
                        'background-color: #452314 !important;' +
                        'background-image: url("http://data.twinoid.com/img/design/ugoals_headerLeft.png") !important;' +
                    '}' +
                    '.tid_userSheet .tid_header .tid_headerRight {' +
                        'padding-top: 0px !important;' +
                        'background-image: url("http://data.twinoid.com/img/design/ugoals_headerRight.png") !important;' +
                    '}' +
                    '.tid_userSheet .tid_footer {' +
                            'background-color: #5c2b20 !important;' +
                            'background-image: url("http://data.twinoid.com/img/design/ugoals_footerLeft.png") !important;' +
                    '}' +
                    '.tid_footerRight {' +
                            'background-image: url("http://data.twinoid.com/img/design/ugoals_footerRight.png") !important;' +
                    '}' +
                    '.tid_userSheet.tid_mine .tid_editable:hover {' +
                        'outline: 1px dotted white !important;' +
                    '}' +
                    //'div.tid_bubble {' +
                        //'background-color: #79432b !important;' +
                        //'color: #ddab76 !important;' +
                        //'box-shadow: 0px 0px 2px #79432b !important;' +
                        //'-webkit-box-shadow: 0px 0px 2px #79432b !important;' +
                        //'border: 0px solid transparent !important;' +
                        //'border-radius: 4px;' +
                    //'}' +
                    '.tid_userSheet.tid_mine .tid_editable .tid_editIcon {' +
                        'margin-top: -6px !important;' +
                        'margin-left: -10px !important;' +
                        'background: #79432b !important;' +
                        '-moz-box-shadow: 0px 0px 3px #ddab76 !important;' +
                        '-webkit-box-shadow: 0px 0px 3px #ddab76 !important;' +
                        'box-shadow: 0px 0px 2px #ddab76 !important;' +
                        'border-radius: 4px !important;' +
                        '-moz-border-radius: 4px !important;' +
                        '-webkit-border-radius: 4px !important;' +
                        'border: 1px solid #ddab76 !important;' +
                    '}' +
                    //'.tid_userStatus .tid_bubble .tid_content .tid_editorContent a {' +
                        //'color: #ddab76 !important;' +
                    //'}' +
                    //'.tid_userStatus .tid_bubble .tid_content .tid_editorContent a:hover {' +
                        //'color: white !important;' +
                    //'}' +
                    //'.tid_bubble  a {' +
                        //'text-decoration: underline !important;' +
                    //'}' +
                    //'.tid_userStatus .tid_bubble  .tid_editorContent strong {' +
                        //'color: #afb3cf !important;' +
                        //'font-weight: bold !important;' +
                    //'}' +
                    '.tid_userSheet.tid_mine .tid_editable:hover {' +
                        'outline: 1px dotted #ddab76 !important;' +
                    '}' +
                    '.tid_userStatus .tid_arrow.tid_left {' +
                        'background-image: none !important;' +
                    '}' +
                    '.tid_userGoals .tid_userGoals_bg {' +
                        'background-image: none  !important;' +
                    '}' +
                    '.tid_userGoals .tid_userGoals_headerLeft {' +
                        'background-image: none  !important;' +
                    '}' +
                    '.tid_userGoals .tid_userGoals_headerLeft .tid_bgRight {' +
                        'background-image: none  !important;' +
                    '}    ' +
                    '.tid_userGoals .tid_userGoals_bg .tid_userGoals_bgRight {' +
                        'background-image: none  !important;' +
                    '}   ' +
                    '.tid_userGoals .tid_userGoals_footerLeft .tid_bgRight {' +
                        'background-image: none  !important;' +
                    '}   ' +
                    '.tid_userGoals .tid_userGoals_footerLeft {' +
                        'background-image: none;' +
                    '}' +
                    '.tid_userGoals .tid_bg2 {' +
                        'background-color: transparent !important;' +
                    '}' +
                    '.tid_userGoals .tid_bg1 {' +
                        'background-color: transparent !important;' +
                    '}' +
                    '.tid_userGoals .tid_bg3 {' +
                        'background-color: transparent !important;' +
                    '}' +
                    '.guser .left {' +
                        'width: 300px;' +
                        'background-image: url("/gfx/design/rewardsBg_bg.gif") !important;' +
                        'background-repeat: repeat-y !important;' +
                        'margin-bottom: auto !important;' +
                        'background-position: 20px 0px !important;' +
                    '}' +
                    '.tid_userGoals {' +
                        'background-image: url("/gfx/loc/' + D2N.get_website_language() + '/rewardsBg_header.gif") !important;' +
                        'background-repeat: no-repeat !important;' +
                        'margin-bottom: 0px !important;' +
                        '-moz-box-shadow: none !important;' +
                        '-webkit-box-shadow: none !important;' +
                        'box-shadow: none !important; ' +
                        'margin-left: auto !important;' +
                        'margin-right: auto !important;' +
                        'width: 260px !important;' +
                    '}' +
                    '.tid_userGoals_footerLeft.tid_bg2 {' +
                        'background-image: url("/gfx/design/rewardsBg_footer.gif") !important;' +
                        'height: 47px !important;' +
                        'margin-top: -22px !important;' +
                    '}' +
                    '.tid_userGoals {' +
                        'width: 260px !important;' +
                    '}' +
                    '.tid_userGoals .tid_userGoals_headerLeft .tid_bgRight {' +
                        'padding-top: 45px !important;' +
                     '}' +
                    '.tid_help.tid_tip.tid_parsed {' +
                    'padding-right: 12px !important;' +
                    '}' +
                    '.tid_userGoals .tid_userGoals_headerBorder {' +
                        'margin: 0px 3px !important;' +
                        'border-top: 0px solid transparent !important;' +
                    '}' +
                    '.tid_userGoals .tid_goals .tid_goalListWrapper {' +
                        'max-height: none !important;' +
                        'border-top: 0px solid white !important;' +
                        'border-bottom: 0px solid white !important;' +
                        '-moz-box-shadow: none !important;' +
                        '-webkit-box-shadow: none !important;' +
                        'box-shadow: none !important;' +
                    '}' +
                    '.tid_userGoals .tid_goals .tid_reachList {' +
                        'border-top: 0px solid white !important;' +
                        'border-bottom: 0px solid white !important;' +
                        '-moz-box-shadow: none !important;' +
                        '-webkit-box-shadow: none !important;' +
                        'box-shadow: none !important;' +
                    '}' +
                    '.tid_userGoals .tid_scrollTrack {' +
                        'display: none !important;' +
                    '}' +
                    '.tid_scrollContentWrapper {' +
                        'max-height: none !important;' +
                        'margin-right:0px !important;  ' +
                    '}' +
                    '.tid_userGoals .tid_goals .tid_goalListWrapper, .tid_userGoals .tid_goals .tid_reachList {' +
                        'max-height: none !important;      ' +
                    '}' +
                    '.tid_userGoals .tid_tabs {' +
                        'padding-right: 0px !important;' +
                        'text-align: center !important;' +
                    '}' +
                     '.tid_userGoals .tid_tabs .tid_over3:hover {' +
                        'background-color: none !important;' +
                        'color: #DDAB76 !important;' +
                     '}' +
                    '.tid_userGoals .tid_tabs a:hover {' +
                        'background-color: none !important;' +
                        'color: #f0d79e !important;' +
                     '}' +
                    '.tid_userGoals .tid_goals .tid_mode_icons .tid_goal.tid_rare {' +
                        'border: 0px solid #feb500;' +
                        'border-top-color: #FFFFB9;' +
                        '-moz-box-shadow: none !important;' +
                        '-webkit-box-shadow: none !important;' +
                        'box-shadow: none !important;' +
                        'border-radius: 0px !important;' +
                        '-moz-border-radius: 0px !important;' +
                        '-webkit-border-radius: 0px !important;' +
                        'background-color: transparent !important;' +
                    '}' +
                    '.tid_goal.tid_bg3.tid_tip.tid_rare.tid_parsed {' +
                        'background-image: url("/gfx/icons/rewardBg_rare.gif") !important;' +
                    '}' +
                    '.tid_userGoals .tid_goals .tid_mode_icons .tid_goal .tid_count {' +
                        'margin-top: 19px !important;   ' +
                    '}' +
                    '.tid_userGoals .tid_goals .tid_top .tid_goal.tid_rare .tid_count {' +
                        'background-color: #3d2016 !important;' +
                    '}' +
                    '.tid_userGoals .tid_goals .tid_mode_icons .tid_goal {' +
                        'border: 0px solid white !important;          ' +
                    '}' +
                    '.tid_userGoals .tid_bg1 .tid_bgRight {' +
                        'color: #f0d79e !important;' +
                        'font-variant: small-caps !important;' +
                    '}' +
                    '.tid_userGoals .tid_goals .tid_reachList table tr {' +
                        'color: white !important;	' +
                    '}' +
                    '.tid_userGoals .tid_goals .tid_reachList table td.tid_name {' +
                        'font-variant: small-caps !important;' +
                        'font-style: normal !important;' +
                    '}' +
                    '.tid_userGoals .tid_goals .tid_reachList table td.tid_value {' +
                        'color: #f0d79e !important;' +
                    '}' +
                    '.tid_note, .tid_blockTitle {' +
                        'color: #f0d79e !important;' +
                    '}' +
                    '#tid_simpleTip > div {' +
                        'background: #5C2b20 !important;' +
                        'border: 1px solid #f0d79e !important;' +
                        'outline: 1px solid #5c2B20 !important;' +
                    '}' +
                    '#tid_simpleTip {' +
                        'background-image: url("http://uppix.net/7vuvNE.png") !important;' +
                        'background-repeat: no-repeat !important;' +
                        'background-position: 50% 100% !important;' +
                        'padding-bottom: 8px !important;' +
                    '}' +
                    '#tid_simpleTip > img {' +
                        'display: none !important;' +
                    '}'
                );
            }
        }

    };
});

Module.register(function() {

    var MODULE_NAME = 'davf_old_twinoid_elements';

    /******************
     * Module context *
     ******************/

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Old Twinoid elements (by Davf)';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'More information here [fr]: http://userstyles.org/styles/99207/hordes-elements-twinoidien. Script by Davf, integrated with his permission. Click to open link.';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Hordes Elements Twinoidien (par Davf)';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Plus d\'information ici : http://userstyles.org/styles/99207/hordes-elements-twinoidien. Script par Davf, intégré avec sa permission. Cliquez pour ouvrir le lien.';

        I18N.set(i18n);
    }


    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: false,
            isProtected: false
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.GENERAL,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc',
                url: "http://userstyles.org/styles/99207/hordes-elements-twinoidien"
            }
        },

        actions: {
            can_run: function() {
                return true;
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                JS.injectCSS(
                    // From: http://userstyles.org/styles/99207/hordes-elements-twinoidien
                    // Credit: Davf, thanks to him for giving his permission to use his CSS
                    // Integrated on: Mar 19, 2014
                    '#tid_userMenu > div.tid_content {' +
                        'background-color: #5c2b20 !important;' +
                        'background-image: none !important;' +
                        'border: 1px solid #ddab76 !important;' +
                    '}' +
                    '#tid_userMenu > div.tid_content div.tid_links div.tid_minorLinks a {' +
                        'color: #ddab76 !important;' +
                    '}' +
                    '#tid_userMenu > div.tid_content div.tid_links div.tid_minorLinks a:hover {' +
                        'background-color: #79432b !important;' +
                    '}' +
                    '#tid_userMenu > div.tid_content.tid_modinit div.tid_links div.tid_majorLinks a {' +
                        'background-color: #79432b !important;' +
                        'color: #f0d79e !important;' +
                    '}' +
                    '#tid_userMenu > div.tid_content.tid_modinit div.tid_links div.tid_majorLinks a:hover {' +
                        'background-color: #996739 !important;' +
                        'color: white !important;' +
                    '}' +
                    '#tid_userMenu .tid_rel.tid_isUnknown {' +
                        'background-color: #5C0000 !important;' +
                        'border: 1px solid #ddab76 !important;' +
                        'border-radius: 0px !important;' +
                    '}' +
                    '#tid_userMenu .tid_links .tid_majorLinks span.tid_placeHolder {' +
                        'border: 1px dashed #ddab76 !important;' +
                    '}' +
                    '#tid_userMenu .tid_sep {' +
                        'border-bottom: 1px dashed #f0d79e !important;' +
                    '}' +
                    '#tid_userMenu .tid_baseInfos .tid_asv div{' +
                        'color: #f0d79e !important;' +
                        'text-shadow: none !important;' +
                        'line-height: 12px;' +
                    '}' +
                    '#tid_userMenu .tid_baseInfos .tid_userStatus .tid_bubble {' +
                        'max-height: 120px !important;' +
                    '}' +
                    '#tid_userMenu .tid_links .tid_tinyLinks a{' +
                        'color: #ddab76 !important;' +
                        'text-decoration: underline !important;' +
                    '}' +
                    '.tid_user.tid_userBg.tid_userFriend {' +
                        'background-image: url("http://data.hordes.fr/gfx/icons/small_online.gif") !important;' +
                        'background-color: #7e4e2a !important;' +
                        'border-top: 1px solid #965c36 !important;' +
                        'border-radius: 4px !important;' +
                        'padding-right: 16px !important;' +
                        'color: #f0d79e !important;' +
                        'font-weight: bold;' +
                    '}' +
                    '.tid_user.tid_userBg.tid_userFriend:hover {' +
                        'border-top: 1px solid #6b3d25 !important;' +
                    '}' +
                    '.tid_user.tid_userBg.tid_userUnknown {' +
                        'background-image: url("http://data.hordes.fr/gfx/icons/small_offline.gif") !important;' +
                        'background-color: #5C0000 !important;' +
                        'border-top: 1px solid #98341c !important;' +
                        'border-radius: 4px !important;' +
                        'padding-right: 16px !important;' +
                        'color: #f0d79e !important;' +
                        'font-weight: bold !important;' +
                    '}' +
                    '.tid_user.tid_userBg.tid_userUnknown:hover {' +
                    'border-top: 1px solid #490000 !important;' +
                    '}' +

                    // Grabbed from: http://userstyles.org/styles/97652/hordes-forum-ancien
                    // (also by Davf)
                    // I find it more appropriate in this module.
                    'div.tid_bubble {' +
                        'background-color: #79432b !important;' +
                        'color: #ddab76 !important;' +
                        'box-shadow: 0px 0px 2px #79432b !important;' +
                        '-webkit-box-shadow: 0px 0px 2px #79432b !important;' +
                        'border: 0px solid transparent !important;' +
                        'border-radius: 4px;' +
                    '}' +
                    '.tid_userStatus .tid_bubble .tid_content .tid_editorContent a {' +
                        'color: #ddab76 !important;' +
                    '}' +
                    '.tid_userStatus .tid_bubble .tid_content .tid_editorContent a:hover {' +
                        'color: white !important;' +
                    '}' +
                    '.tid_bubble  a {' +
                        'text-decoration: underline !important;' +
                    '}' +
                    '.tid_userStatus .tid_bubble  .tid_editorContent strong {' +
                        'color: #afb3cf !important;' +
                        'font-weight: bold !important;' +
                    '}'
                );
            }
        }

    };
});

Module.register(function() {

	var MODULE_NAME = 'despair_death';

	/******************
	 * Module context *
	 ******************/

	 var DESPAIR_DEATH_ID = 'd2ne_despair_death';

	/**
	 * Add the i18n strings for this module.
	 */
	function add_i18n()
	{
		var i18n = {};

		i18n[I18N.LANG.FR] = {};
		i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Afficher les morts par désespoir';
		i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Affiche la quantité de zombies à tuer pour tuer la totalité des zombies présents sur une case.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_to_kill'] = 'Zombies à tuer';

		i18n[I18N.LANG.EN] = {};
		i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Display deaths by despair';
		i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Shows the amount of zombies to kill for the others to die from despair.';
		i18n[I18N.LANG.EN][MODULE_NAME + '_to_kill'] = 'Zombies to kill';

		I18N.set(i18n);
	}

	/************************
	 * Module configuration *
	 ************************/

	return {

		name: MODULE_NAME,
		type: Module.TYPE.INTERFACE_ENHANCEMENT,

		properties: {
			enabled: false,
			isProtected: false
		},

		configurable: {
			enabled: {
				category: Module.PROPERTY_CATEGORY.OUTSIDE,
				type: Module.PROPERTY.BOOLEAN,
				short_desc_I18N: MODULE_NAME + '_short_desc',
				full_desc_I18N: MODULE_NAME + '_full_desc'
			}
		},

		actions: {
			can_run: function() {
				return true;
			},

			init: function() {
				add_i18n();
			},

			load: function() {
				document.addEventListener('d2n_gamebody_reload', function() {
					if(!D2N.is_outside()){
						return;
					}

					var nbZeds = parseInt($("#zombiePts").text());

					var nbKills = 0;

					if(nbZeds > 0) {
						nbKills = Math.round(nbZeds / 1.5 + 1);
					}

					if($("#" + DESPAIR_DEATH_ID).length == 0) {
						$("#sideMap").after("<div id='" + DESPAIR_DEATH_ID+ "' class='block'><div class='header'></div><div class='bg content'>" + I18N.get(MODULE_NAME + "_to_kill") + " : " + nbKills + "</div><div class='footer'></div></div>");
					} else {
						$("#" + DESPAIR_DEATH_ID + " .content").text(I18N.get(MODULE_NAME + "_to_kill") + " : " + nbKills);
					}
				}, false);
			}
		}
	};
});

Module.register(function() {

    var MODULE_NAME = 'auto_escort';

    /******************
     * Module context *
     ******************/

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Auto escort';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Automatically enable the escort mode when outside.';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Attendre une escorte automatiquement';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Active automatiquement le mode escorte lorsque vous êtes hors de la ville.';

        I18N.set(i18n);
    }

    function enable_escort(sk)
    {
      // If the escort link is found
      JS.wait_for_selector('#generic_section div.who a[href^="#user/waitForLeader?sk="]', function() {

        // Grab the session key
        D2N.get_session_key(function(sk) {

          // Enable the escort
          JS.injectJS('js.XmlHttp.get(' + JSON.stringify('user/waitForLeader?sk=' + sk) + ');');
        });
      });
    }

    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: false,
            isProtected: true
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.OUTSIDE,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc'
            }
        },

        actions: {
            can_run: function() {
                return !D2NE.is_restricted_mode();
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                document.addEventListener('d2n_gamebody_reload', function() {
                    // if not outside, abort
                    if (!D2N.is_outside()) {
                        return;
                    }

                    enable_escort();
                }, false);
            }
        }

    };
});

Module.register(function() {

    var MODULE_NAME = 'good_escort_options';

    /******************
     * Module context *
     ******************/

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Directly enable the good escort options';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'When the escort mode is enabled, you can directly be taken further away and your rucksack objects can be manipulated.';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Activer les bonnes options d\'escorte automatiquement';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Quand le mode escorte est activé, vous pouvez automatiquement être éloigné de la ville et les objets de votre sac peuvent être manipulés.';

        I18N.set(i18n);
    }

    function enable_rucksack_manipulation()
    {
        // Try to find the label
        JS.wait_for_selector('#generic_section div.who input[name="fullEscort"]:not(:checked)', function() {

            // Grab the session key
            D2N.get_session_key(function(sk) {

                // If has been found and is not checked, enable the bag access
                JS.injectJS('js.XmlHttp.get(' + JSON.stringify('user/toggleFullEscort?sk=' + sk) + ');');

                // This will trigger a 'd2n_gamebody_reload' event so this
                // function will be called again. But the second time the
                // unchecked label will not be found and nothing will happen
            });
        }, 0);
    }

    function disable_take_further_away_protection(onFinish)
    {
        // Try to find the label
        JS.wait_for_selector('#generic_section div.who input[name="onlyEscortToTown"]:checked', function() {

            // Grab the session key
            D2N.get_session_key(function(sk) {

                // If has been found and is checked, disable the protection
                JS.injectJS('js.XmlHttp.get(' + JSON.stringify('user/toggleEscortToTown?sk=' + sk) + ');');

                // This will trigger a 'd2n_gamebody_reload' event so this
                // function will be called again. But the second time the
                // checked label will not be found so the onFinish callback will
                // be called.
            });
        }, 0, function notFound() {
            onFinish();
        });
    }

    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: false,
            isProtected: false
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.OUTSIDE,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc'
            }
        },

        actions: {
            can_run: function() {
                return true;
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                document.addEventListener('d2n_gamebody_reload', function() {
                    // if not outside, abort
                    if (!D2N.is_outside()) {
                        return;
                    }

                    disable_take_further_away_protection(function onFinish() {
                        enable_rucksack_manipulation();
                    });
                }, false);
            }
        }

    };
});

Module.register(function() {

	var MODULE_NAME = 'external_sites';

	/******************
	 * Module context *
	 ******************/

	/**
	 * The external tools bar ID.
	 */
	var EXTERNAL_SITES_UPDATE_CONTAINER_ID = 'd2ne_external_sites_container';
   
	/**
	 * Cache
	 */
	var button_container_ = null;

	/**
	 * Add the i18n strings for this module.
	 */
	function add_i18n() {
		var i18n = {};

		i18n[I18N.LANG.EN] = {};
		i18n[I18N.LANG.EN][MODULE_NAME + '_disclaimer'] = 'Following links have been added by D2NE';

		i18n[I18N.LANG.FR] = {};
		i18n[I18N.LANG.FR][MODULE_NAME + '_disclaimer'] = 'Les liens suivants ont été ajoutés par D2NE';

		I18N.set(i18n);
	}

	/**
	 * Inject the node composing the external sites into the DOM.
	 */
	function inject_external_sites_node() {
		// If the ul exists, abort
		if (JS.is_defined(document.getElementById(EXTERNAL_SITES_UPDATE_CONTAINER_ID))) {
			return;
		}

		// Define the reference node selector
		var selector = "#sites";

		JS.wait_for_selector(selector, function(node) {
			node.appendChild(JS.jsonToDOM(["p", {}, I18N.get(MODULE_NAME + '_disclaimer')], document));

			var reference_node = node.parentNode;

			// Create the new node
			var ul_container = JS.jsonToDOM(["ul", {"id":EXTERNAL_SITES_UPDATE_CONTAINER_ID}, ""], document);

			// Cache it
			button_container_ = ul_container;

			// Insert it
			node.appendChild(ul_container);
		}, 10);
	}

	/**
	 * Load the module.
	 */
	function load_module() {
		// Inject DOM
		inject_external_sites_node();
	}

	/************************
	 * Module configuration *
	 ************************/

	return {

		name: MODULE_NAME,
		type: Module.TYPE.CONTAINER,

		properties: {
			enabled: true
		},

		container_id: EXTERNAL_SITES_UPDATE_CONTAINER_ID,

		actions: {
			can_run: function() {
				return true;
			},

			init: function(){
				add_i18n();
			},

			load: function() {
				var loaded = false;

				// inject the button if at least one external tool is enabled
				Module.iterate_on_type(Module.TYPE.EXTERNAL_SITE, function(module) {
					// if module is enabled, then the external tools bar has to
					// be enabled
					if (module.is_enabled()) {

						// Load the external tools bar module
						if(!loaded){
							load_module();
							// Ensure it loads only once
							loaded = true;
						}
						var dom = JS.jsonToDOM(["li", {}, 
							["a", { "href": module.properties.link.url, "class": "link", "target": "_blank"},
								["img", { "src": module.properties.link.icon, "width": "16px", "height": "16px" }],
								["div", {"style": "display: inline"}, " "],
								["span", {}, module.properties.link.title]
							]
						], document);

						// Append the module DOM
						JS.wait_for_id(EXTERNAL_SITES_UPDATE_CONTAINER_ID, function(node) {
							node.appendChild(dom);
						});
					}
				});
			},

			refresh: function(){
				on_update_button_click();
			}
		}

	};
});

Module.register(function() {

    var MODULE_NAME = 'external_tools_bar';

    /******************
     * Module context *
     ******************/

    /**
     * The external tools bar ID.
     */
    var EXTERNAL_TOOLS_BAR_UPDATE_CONTAINER_ID = 'd2ne_external_tools_bar_update_container';
    var EXTERNAL_TOOLS_BAR_UPDATE_ID = 'd2ne_external_tools_bar_update';
    var EXTERNAL_TOOLS_TOOLTIP_LIST_ID = 'd2ne_external_tools_tooltip_list';

    /**
     * Update state, an object containing objects of the following form:
     *
     *     var update_state_ = {
     *         "module_name": {
     *             name: "module_friendly_name",
     *             done: true,
     *             error: false
     *         },
     *         "module_name_2": {
     *             name: "module_2_friendly_name",
     *             done: true,
     *             error: true
     *         }
     *     };
     */
    var update_state_ = {};

    /**
     * Store if an update is currently in progress.
     */
    var update_in_progress_ = false;

    /**
     * Cache
     */
    var button_container_ = null;
    var button_hidden_div_ = null;
    var button_link_ = null;

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_update_button'] = 'Update the external tools';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_update_button'] = 'Mettre à jour les outils externes';

        I18N.set(i18n);
    }

    /**
     * Check if an update is in progress.
     */
    function is_update_in_progress()
    {
        return update_in_progress_;
    }

    /**
     * Setter for update_in_progress_.
     * @param boolean value The desired value (true for update, false otherwise)
     */
    function set_update_in_progress(value)
    {
        update_in_progress_ = value;
    }

    /**
     * Enable the button.
     */
    function enable_button()
    {
        button_link_.classList.remove('disabled');
    }

    /**
     * Disable the button.
     */
    function disable_button()
    {
        button_link_.classList.add('disabled');
    }

    /**
     * Get the number of external tools for the current update
     * @return integer The number of external tools
     */
    function get_number_of_external_tools()
    {
        return Object.keys(update_state_).length;
    }

    /**
     * Get the number of external tools update done.
     * @return integer The number of external tools update done
     */
    function get_number_of_external_tool_updates_done()
    {
        var ret = 0;

        for (var key in update_state_) {
            if (update_state_.hasOwnProperty(key)) {
                ret += (update_state_[key].done === true) ? 1 : 0;
            }
        }

        return ret;
    }

    /**
     * Set the size of the hidden div. Also enable/disable the button behind.
     * @param integer updated number of updated tools (with success or not)
     * @param integer total total number of tools
     */
    function update_hidden_div_width(updated, total)
    {
        var percent = (updated / total) * 100;

        // Remove transition if not needed
        if (percent <= 0) {
            button_hidden_div_.classList.remove('smooth_transition');
        // Else add it
        } else {
            button_hidden_div_.classList.add('smooth_transition');
        }

        // change the div width
        button_hidden_div_.style.width = (100 - percent) + '%';
    }

    /**
     * Update the popup content.
     */
    function update_tooltip()
    {
        var element_id = EXTERNAL_TOOLS_BAR_UPDATE_CONTAINER_ID;
        var new_content = '';

        // set the content
        new_content += '<div id="' + EXTERNAL_TOOLS_TOOLTIP_LIST_ID + '" style="margin-top: 0;">';
        for (var tool in update_state_) {
            if (update_state_.hasOwnProperty(tool)) {
                // if done and success
                if (update_state_[tool].done === true) {
                    if (update_state_[tool].error === false) {
                        new_content += '<div style="color: #27F037;">';
                    } else {
                        new_content += '<div style="color: #EE1515;">';
                    }
                } else if(update_state_[tool].updating === true) {
                    new_content += '<div style="color: #ffd500;">';
                } else {
                    new_content += '<div>';
                }

                // Include title
                new_content += update_state_[tool].name + '</div>';
            }
        }
        new_content += '</div>';

        // Update the listeners
        JS.injectJS(
            'var el = document.getElementById(' + JSON.stringify(element_id) + ');' +
            'el.onmouseover = function(event) {' +
                'return js.HordeTip.showSpecialTip(this, \'helpTip\', \'\', ' + JSON.stringify(new_content) + ', event);' +
            '};' +
            'el.onmouseout = function(event) {' +
                'return js.HordeTip.hide(event);' +
            '};'
        );

        // Directly update the tooltip if it is already present in the DOM
        JS.injectJS(
            'if (document.getElementById(' + JSON.stringify(EXTERNAL_TOOLS_TOOLTIP_LIST_ID) + ') !== null) {' +
                'var el = document.getElementById(\'tooltipContent\');' +
                'el.innerHTML = ' + JSON.stringify('<div class="title"></div>' + new_content) + ';' +
            '}'
        );
    }

    /**
     * Reset the update state.
     */
    function reset_update()
    {
        update_state_ = {};
        set_update_in_progress(false);

        Module.iterate_on_type(Module.TYPE.EXTERNAL_TOOL, function(module) {
            if (!module.is_enabled()) {
                return;
            }

            update_state_[module.name] = {
                module: module,
                name: I18N.get(module.name + '_name'),
                done: false,
                error: false,
                updating: false
            };
        });
    }

    /**
     * Called every time an external tool is updated.
     */
    function on_external_tool_update() {
        var number_of_tools = get_number_of_external_tools();
        var number_of_done = get_number_of_external_tool_updates_done();

        // Update the tooltip
        update_tooltip();

        // Update the hidden div
        update_hidden_div_width(number_of_done, number_of_tools);

        // if done
        if (number_of_done >= number_of_tools) {
            // reset the update status
            reset_update();

            // enable the button
            enable_button();
        }
    }

    /**
     * Update all the enabled external tools.
     * @param Function progress_callback Will be called on each state change
     */
    function update_external_tools(progress_callback)
    {
        // Abort if an update is already in progress
        if (is_update_in_progress()) {
            return;
        }

        // Reset update state
        reset_update();
        
        // We are actually doing an update
        set_update_in_progress(true);

        // Disable the button
        disable_button();
        // Update the popup
        update_tooltip();
        // Update the hidden div width
        update_hidden_div_width(get_number_of_external_tool_updates_done(),
                                get_number_of_external_tools());

        JS.each(update_state_, function(module_name, state) {
            var module = state.module;
            state.updating = true;
            
            module.actions.update.call(module, function on_success() {
                state.done = true;
                state.error = false;
                state.updating = false;

                return progress_callback();

            }, function on_failure() {
                state.done = true;
                state.error = true;
                state.updating = false;

                return progress_callback();
            });
        });
    }

    /**
     * Handle the click on the update button.
     */
    function on_update_button_click()
    {
        update_external_tools(function() {
            return on_external_tool_update();
        });
    }

    /**
     * Inject the node composing the external tools bar into the DOM.
     */
    function inject_external_tools_bar_nodes()
    {
        // If the toolbar exists, abort
        if (JS.is_defined(document.getElementById(EXTERNAL_TOOLS_BAR_UPDATE_ID))) {
            return;
        }

        // Define the reference node selector
        var selector = null;
        if (D2N.is_on_page_in_city('bank')) {
            selector = 'a.button > img[src$="/gfx/icons/r_forum.gif"]';
        } else if (D2N.is_outside()) {
            selector = 'a.button > img[src$="/gfx/icons/small_hero.gif"]';
        } else {
            return;
        }

        JS.wait_for_selector(selector, function(node) {
            var reference_node = node.parentNode;

            // Create the new node
            var new_button = JS.jsonToDOM(["div", { "id": EXTERNAL_TOOLS_BAR_UPDATE_CONTAINER_ID },
                ["div", {}],
                ["a", { "href": "javascript:void(0)", "class": "button", "id": EXTERNAL_TOOLS_BAR_UPDATE_ID,
                        "onclick": function() {
                                       if (this.classList.contains('disabled')) {
                                           return;
                                       }
                                       on_update_button_click();
                                   }
                      },
                    ["img", { "src": "/gfx/icons/r_explo2.gif", "width": "16px", "height": "16px" }],
                    ' ' + I18N.get(MODULE_NAME + '_update_button')
                ]
            ], document);

            // Cache it
            button_container_ = new_button;
            button_hidden_div_ = new_button.childNodes[0];
            button_link_ = new_button.childNodes[1];

            // If an update is in progress, disable it
            if (is_update_in_progress()) {
                update_hidden_div_width(get_number_of_external_tool_updates_done(),
                                        get_number_of_external_tools());
                disable_button();
            }

            // Insert it
            JS.insert_after(reference_node, new_button);

            // Update the tooltip
            update_tooltip();
        }, 10);
    }

    /**
     * Inject the external tool bar CSS.
     */
    function inject_external_tools_bar_style()
    {
        JS.injectCSS(
            '#' + EXTERNAL_TOOLS_BAR_UPDATE_CONTAINER_ID + ' {' +
                'position: relative;' +
                'top: 0;' +
                'left: 0;' +
            '}' +

            '#' + EXTERNAL_TOOLS_BAR_UPDATE_CONTAINER_ID + ' div {' +
                'position: absolute;' +
                'top: 0;' +
                'right: 0;' +
                'width: 0%;' +
                'height: 100%;' +
                'background-color: black;' +
                'opacity: 0.5;' +
                'cursor: help;' +
            '}' +

            '#' + EXTERNAL_TOOLS_BAR_UPDATE_CONTAINER_ID + ' div.smooth_transition {' +
                'transition: width 0.8s;' +
                '-moz-transition: width 0.8s;' +
                '-webkit-transition: width 0.8s;' +
            '}' +

            '#' + EXTERNAL_TOOLS_BAR_UPDATE_CONTAINER_ID + ' a.disabled {' +
                'padding-top: 0px;' +
                'padding-bottom: 1px;' +
                'color: #f0d79e;' +
                'border-top-width: 1px;' +
                'border-bottom-width: 2px;' +
                'outline: 1px solid #784323;' +
                'color: #f0d79e;' +
                'cursor: help;' +
            '}'
        );
    }

    /**
     * Load the module.
     */
    function load_module()
    {
        // Reset update state (scan the available external tools and fill the
        // update state)
        reset_update();

        // Inject DOM
        document.addEventListener('d2n_gamebody_reload', function() {
            inject_external_tools_bar_nodes();
        }, false);

        // Inject CSS
        inject_external_tools_bar_style();
    }

    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.CONTAINER,

        properties: {
            enabled: true
        },

        container_id: EXTERNAL_TOOLS_BAR_UPDATE_CONTAINER_ID,

        actions: {
            can_run: function() {
                return true;
            },

            init: function(){
                add_i18n();
            },

            load: function() {
                var loaded = false;

                // inject the button if at least one external tool is enabled
                Module.iterate_on_type(Module.TYPE.EXTERNAL_TOOL, function(module) {
                    // if already loaded, abort
                    if (loaded) {
                        return;
                    }

                    // if module is enabled, then the external tools bar has to
                    // be enabled
                    if (module.is_enabled()) {
                        // Load the external tools bar module
                        load_module();

                        // Ensure it loads only once
                        loaded = true;
                    }
                });
            },

            refresh: function(){
                on_update_button_click();
            }
        }

    };
});

Module.register(function() {

    var MODULE_NAME = 'fetch_api_keys';

    /******************
     * Module context *
     ******************/

    /**
     * Set all the api keys to 'null'.
     */
    function clean_api_keys()
    {
        Module.iterate_on_type(Module.TYPE.EXTERNAL_TOOL, function(module) {
            module.properties.tool.api_key = null;
            module.save_properties();
        });
    }

    /**
     * Register to the 'gamebody_reload' event to clean the api keys when the
     * user go to the settings page.
     */
    function clean_api_keys_if_on_settings_page()
    {
        document.addEventListener('d2n_gamebody_reload', function() {
            if (!D2N.is_on_page('settings')) {
                return;
            }

            clean_api_keys();
        }, false);
    }

    /**
     * Called when the API key is successfully fetched.
     * @param Object module The module
     * @param string key The API key
     */
    function on_api_key_successfully_fetched(module, key)
    {
        module.properties.tool.api_key = key;
        module.save_properties();
    }

    /**
     * Called when the API key can't be fetched.
     * @param Object module The module
     */
    function on_api_key_fetch_error(module)
    {
        module.properties.tool.api_key = null;
        module.save_properties();
    }

    /**
     * Fetch the api keys for each available external tool.
     */
    function fetch_api_keys()
    {
        // Abort if on the settings page
        if (D2N.is_on_page('settings')) {
            return;
        }

        // Get the api key for each module
        Module.iterate_on_type(Module.TYPE.EXTERNAL_TOOL, function(module) {
            // if module is disabled, abort
            if (!module.is_enabled()) {
                return;
            }

            // if the key has already been fetched, abort
            if (module.properties.tool.api_key !== null) {
                return;
            }

            // else try to fetch it
            D2N.get_api_key(module.properties.tool.directory_id, function on_success(key) {
                on_api_key_successfully_fetched(module, key);
            }, function on_failure() {
                on_api_key_fetch_error(module);
            });
        });
    }


    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.BUSINESS_LOGIC,

        properties: {
            enabled: true
        },

        actions: {
            can_run: function() {
                return true;
            },

            load: function() {
                document.addEventListener('d2ne_all_modules_loaded', function() {
                    D2N.is_logged(function(is_logged) { if (is_logged) {
                        fetch_api_keys();
                    }});
                }, false);
                clean_api_keys_if_on_settings_page();
            }
        }

    };
});

Module.register(function() {

	var MODULE_NAME = 'find_item_in_bank';

	/******************
	 * Module context *
	 ******************/

	var FIND_ITEM_ID = 'd2ne_item_finder';

	/**
	 * Add the i18n strings for this module.
	 */
	function add_i18n()
	{
		var i18n = {};

		i18n[I18N.LANG.EN] = {};
		i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Find item in bank';
		i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Help you find an item in the bank';
		i18n[I18N.LANG.EN][MODULE_NAME + '_label'] = 'Select the item you want to find:';
		i18n[I18N.LANG.EN][MODULE_NAME + '_select_item'] = "--- Please select ---";

		i18n[I18N.LANG.FR] = {};
		i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Trouver un objet en banque';
		i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Sur la page de la banque, affiche une liste des objets présent pour les repérer plus facilement.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_label'] = "Sélectionnez l'objet que vous recherchez";
		i18n[I18N.LANG.FR][MODULE_NAME + '_select_item'] = "--- Veuillez choisir ---";

		I18N.set(i18n);
	}

	function get_finder_div()
	{
		var json = [
			"div", { "id": FIND_ITEM_ID, "class": "extractCpt" },
			["img", { "src": "/gfx/forum/smiley/h_arrow.gif" }],
			' ' + I18N.get(MODULE_NAME + '_label') + ' ',
			["select", { "id": "finderSelect" }, ["option", {"value": ""}, I18N.get(MODULE_NAME + "_select_item")]]
		];

		$("ul.stocks li.multi a").each(function(){
			var mouseover = $(this).attr("onmouseover");
			mouseover = mouseover.substring(26).trim();
			mouseover = mouseover.substring(0, mouseover.indexOf("', '"));
			var title = mouseover.substring(0, mouseover.indexOf("<img")).trim();
			var img = mouseover.substring(mouseover.indexOf("<img")).trim();
			json[4].push(["option", {"value": title}, title]);
		});
		return JS.jsonToDOM(json, document);
	}

	function inject_finder()
	{
		if (!D2N.is_on_page_in_city('bank')) {
			return;
		}

		// Add finder
		var selector = "div.right";

		JS.wait_for_selector(selector, function(el) {
			var refNode = el.firstChild;
			if($("#d2ne_abuse_counter")) {
				refNode = refNode.nextSibling;
			}
			el.insertBefore(get_finder_div.call(this), refNode);
			$("#" + FIND_ITEM_ID + " select").change(function(){
				$(".itemLookup").removeClass("itemLookup");
				var text = $(this).val();
				if(text != "") {
					$("ul.stocks li.multi a[onmouseover*='" + text + "']").addClass("itemLookup");
				}
			});
		}.bind(this));
		
		JS.injectCSS(
			"#finderSelect {" +
				"width: 100%;" +
			"}" + 
			".itemLookup {" +
				"background: yellow;" +
				"transition: all 0.5s ease;" +
			"}" +
			"#" + FIND_ITEM_ID + " { " +
				"cursor: auto;" +
				"padding-right: 6px;"  +
			"}"
		);
	}



	/************************
	 * Module configuration *
	 ************************/

	return {

		name: MODULE_NAME,
		type: Module.TYPE.INTERFACE_ENHANCEMENT,

		properties: {
			enabled: false,
            isProtected: false
		},

		configurable: {
			enabled: {
				category: Module.PROPERTY_CATEGORY.BANK,
				type: Module.PROPERTY.BOOLEAN,
				short_desc_I18N: MODULE_NAME + '_short_desc',
				full_desc_I18N: MODULE_NAME + '_full_desc'
			}
		},

		actions: {
			can_run: function() {
				return true;
			},

			init: function() {
				add_i18n();
			},

			load: function() {
				document.addEventListener('d2n_gamebody_reload', function() {
					if (!D2N.is_on_page_in_city('bank')) {
						return;
					}

					if (JS.is_defined(document.getElementById(FIND_ITEM_ID))) {
						return;
					}

					inject_finder.call(this);

				}.bind(this), false);
			}
		}

	};
});

Module.register(function() {

    var MODULE_NAME = 'fold_twinoid_bar';

    /******************
     * Module context *
     ******************/

    /**
     * Stores the Twinoid bar node.
     */
    var tid_cache_ = null;

    /**
     * Stores if the Twinoid bar is hidden.
     */
    var tid_hidden_ = true;

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Fold Twinoid bar';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Fold the Twinoid black bar at the top of the screen. Put your mouse near the top of the screen to show it again.';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Replier la barre Twinoid';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Permet de gagner de la place en repliant la barre Twinoid. Rapprochez votre souris du bord supérieur de l\'écran pour l\'afficher de nouveau.';

        I18N.set(i18n);
    }

    /**
     * Show the twinoid bar.
     */
    function show_tid() {
        tid_cache_.style.display = 'block';
        tid_hidden_ = false;
    }

    /**
     * Hide the twinoid bar.
     */
    function hide_tid() {
        tid_cache_.style.display = 'none';
        tid_hidden_ = true;
    }

    /**
     * Handle the mouse move event.
     * @param integer mouse_y The mouse Y coordinate
     */
    function on_mouse_move(mouse_y)
    {
        // if on the top of the screen, display the bar
        if (tid_hidden_ && mouse_y <= 5) {
            show_tid();
        }

        // if not on the top of the screen, hide the bar if the
        // lateral panels are not visible
        else if (!tid_hidden_ && mouse_y > 32) {
            var tid_side_panels = document.getElementsByClassName('tid_sidePanel');
            var tid_side_panels_length = tid_side_panels.length;

            for (var i = 0; i < tid_side_panels_length; i += 1) {
                var style = getComputedStyle(tid_side_panels[i]);

                if (style.visibility === 'visible') {
                    return;
                }
            }
            hide_tid();
        }
    }

    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: false,
            isProtected: true
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.INTERFACE,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc'
            }
        },

        actions: {
            can_run: function() {
                return !D2NE.is_restricted_mode();
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                JS.injectCSS(
                    '#tid_bar {' +
                        'display: none;' +
                        'position: fixed;' +
                        'z-index: 15;' +
                    '}' +

                    // the bar is 32px height, so as we hide it, we need to pull up
                    // the interface
                    '#gamebody div.infoBar {' +
                        'top: 111px;' + // 143 - 32
                    '}' +
                    'a#backReboot {' +
                        'top: 178px;' + // 200 - 32
                    '}'
                );

                JS.wait_for_id('tid_bar', function(node) {
                    tid_cache_ = node;
                    tid_hidden_ = true;

                    JS.wait_for_class('tid_sidePanel', function() {
                        document.body.addEventListener('mousemove', function(event) {
                            on_mouse_move(event.clientY);
                        }, false);
                    });
                });
            }
        }

    };

});

Module.register(function() {

    var MODULE_NAME = 'forum_include_medias';

    /******************
     * Module context *
     ******************/

    var VIDEO_WIDTH = 400;
    var VIDEO_HEIGHT = 300;

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Replace the medias link by the content';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'In the forum posts, replace the media links by the concerned media (images and YouTube, Vimeo and Dailymotion videos).';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Remplacer les liens de médias par le contenu';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Dans les posts du forum, remplace les liens de média par le média concerné (images et vidéos YouTube, Vimeo et Dailymotion).';

        I18N.set(i18n);
    }

    /**
     * Return the id from a dailymotion url.
     * @link http://stackoverflow.com/a/15942126/1071486
     * @param string video_url
     * @return integer the video id
     * @return null if can not extract it
     */
    function get_dailymotion_video_id(video_url)
    {
        var results = video_url.match(/dailymotion.com\/(?:(?:video|hub)\/([^_]+))?[^#]*(?:#video=([^_&]+))?/);

        if (!results || results.length < 2) {
            return null;
        }
        return results[2] || results[1];
    }

    /**
     * Return the id from a vimeo url.
     * @link http://stackoverflow.com/a/16841070/1071486
     * @param string video_url
     * @return integer the video id
     * @return null if can not extract it
     */
    function get_vimeo_video_id(video_url)
    {
        var results = video_url.match(/(?:https?:\/\/)?(?:www.)?(?:player.)?vimeo.com\/(?:[a-z]*\/)*([0-9]{6,11})/);

        if (!results || results.length < 2) {
            return null;
        }
        return results[1];
    }

    /**
     * Return the id from a youtube video.
     * @link http://linuxpanda.wordpress.com/2013/07/24/ultimate-best-regex-pattern-to-get-grab-parse-youtube-video-id-from-any-youtube-link-url/
     * @param string video_url
     * @return integer the video id
     * @return null if can not extract it
     */
    function get_youtube_video_id(video_url)
    {
        var results = video_url.match(/(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com(?:\/embed\/|\/v\/|\/watch\?v=|\/ytscreeningroom\?v=|\/feeds\/api\/videos\/|\/user\s*[^\w\-\s]|\s*[^\w\-\s]))([\w\-]{11})[a-z0-9;:@?&%=+\/\$_.-]*/i);

        if (!results || results.length < 2) {
            return null;
        }
        return results[1];
    }

    /**
     * Check if the given url is an image.
     * @param string url The URL to check
     * @return boolean true if an image url, else false
     */
    function is_image_link(url)
    {
        return (/.+\.(?:jpe?g|png|gif|bmp)(?:\?.+)?(?:#.\+)?$/).test(url);
    }

    /**
     * Check if the given url is an URL.
     * @param string url The url to check.
     * @return boolean true if an URL, else false
     */
    function is_link(url)
    {
        return (/^https?:\/\//).test(url);
    }

    /**
     * Replace medias in forum posts.
     * @param DOMNode[] posts The posts list
     */
    function replace_medias_in_forum_posts(posts)
    {
        var id;
        var embed_link;

        posts.forEach(function(post) {
            var links = JS.nodelist_to_array(post.querySelectorAll('.tid_editorContent a[href]'));

            // for each link, see if it has to be replaced
            links.forEach(function(link) {
                var new_node = null;

                // images
                if (is_image_link(link.href)) { // if an image
                    // create the node img
                    new_node = JS.jsonToDOM(['img', { "class": 'd2ne_injected', src: link.href }], document);

                // YouTube
                } else if ((id = get_youtube_video_id(link.href)) !== null) {
                    embed_link = '//www.youtube-nocookie.com/embed/' + id + '?rel=0';
                    new_node = JS.jsonToDOM(['iframe', { "class": 'd2ne_injected', width: VIDEO_WIDTH, height: VIDEO_HEIGHT, src: embed_link, frameborder: 0, webkitallowfullscreen: '', mozallowfullscreen: '', allowfullscreen: '' }], document);

                // Vimeo
                } else if ((id = get_vimeo_video_id(link.href)) !== null) {
                    embed_link = '//player.vimeo.com/video/' + id;
                    new_node = JS.jsonToDOM(['iframe', { "class": 'd2ne_injected', width: VIDEO_WIDTH, height: VIDEO_HEIGHT, src: embed_link, frameborder: 0, webkitallowfullscreen: '', mozallowfullscreen: '', allowfullscreen: '' }], document);

                // Dailymotion
                } else if ((id = get_dailymotion_video_id(link.href)) !== null) {
                    embed_link = '//www.dailymotion.com/embed/video/' + id;
                    new_node = JS.jsonToDOM(['iframe', { "class": 'd2ne_injected', width: VIDEO_WIDTH, height: VIDEO_HEIGHT, src: embed_link, frameborder: 0, webkitallowfullscreen: '', mozallowfullscreen: '', allowfullscreen: '', related: 0 }], document);

                // abort for this link
                } else {
                    return;
                }

                // Insert the new node if any
                if (new_node !== null) {
                    JS.insert_after(link, new_node);

                    // And hide the old node if the link is a pure URL (do not
                    // delete it to keep it in the quotes)
                    if (is_link(link.textContent)) {
                        link.style.display = 'none';
                    }
                }
            });
        });
    }


    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: false,
            isProtected: false
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.FORUM,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc'
            }
        },

        actions: {
            can_run: function() {
                return true;
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                JS.injectCSS(
                    'img.d2ne_injected, iframe.d2ne_injected {' +
                        'max-width: 100%;' +
                    '}' +
                    'img.d2ne_injected {' +
                        'float: left;' +
                        'margin: 0 auto;' +
                        'margin-top: 16px;' +
                        'margin-bottom: 16px;' +
                    '}' +
                    '.tid_editorContent cite, .tid_editorContent cite p {' +
                        'overflow: auto;' +
                    '}' +
                    '.tid_editorContent cite img:last-child {' +
                        'margin-bottom: 0;' +
                    '}' +
                    'iframe.d2ne_injected {' +
                        'display: block;' +
                        'margin: 0 auto;' +
                        'margin-top: 16px;' +
                        'margin-bottom: 16px;' +
                        'border: none;' +
                        'box-shadow: 0px 0px 8px black;' +
                        'border-radius: 3px;' +
                    '}' +
                    '.tid_editorContent .tid_spoil {' +
                        'clear: both;' +
                    '}'
                );

                document.addEventListener('d2n_forum_topic', function(event) {
                    var posts = JS.nodelist_to_array(document.getElementsByClassName('tid_post'));
                    replace_medias_in_forum_posts(posts);
                });
            }
        }

    };
});

Module.register(function() {

	var MODULE_NAME = 'forum_protection';
	var CHANGE_TEXT_ID = 'd2ne_converttext';

	/******************
	 * Module context *
	 ******************/

	/**
	 * Add the i18n strings for this module.
	 */
	function add_i18n()
	{
		var i18n = {};

		i18n[I18N.LANG.EN] = {};
		i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Enable forum post protection';
		i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Avoid your post on the forum from being altered by the alcohol or the terror.';
		i18n[I18N.LANG.EN][MODULE_NAME + '_protect'] = 'Protect my text';

		i18n[I18N.LANG.FR] = {};
		i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = "Protéger contre les effets sur les messages du forum";
		i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = "Empêche le message posté sur le forum d'être altéré par les effets de l'alcool ou de la terreur.";
		i18n[I18N.LANG.FR][MODULE_NAME + '_protect'] = 'Protéger mon texte';

		I18N.set(i18n);
	}

	function add_button(){
		JS.wait_for_id("tid_forumMsg", function(node){
			if($("#" + CHANGE_TEXT_ID).length > 0)  {
				return;
			}

			var div = $("<div>");
			div.css("display", "inline-block");
			div.css("margin-left", "5px");

			var link = $("<a>");
			link.click(protect.bind(this));
			link.addClass("d2n_protect_btn");
			link.attr("id", CHANGE_TEXT_ID);
			link.html(I18N.get(MODULE_NAME + "_protect"));

			$(".tid_postForm form button").after(div);
			div.append(link);
		})
	}

	function protect(event){
		event.preventDefault();
		event.stopPropagation();

		var text = $("#tid_forumMsg").val();

		text = text.replace(/\n/gi, "=====");

		// Requête GET sur https://qaz.wtf/u/convert.cgi?text=[Contenu du champ texte]
		JS.network_request("GET", "https://qaz.wtf/u/convert.cgi?text=" + text, null, null, function(response) {
			var doc = document.createElement("html");
			doc.innerHTML = response;
			var line = doc.querySelector("table tr:nth-child(9)");
			var textChanged = line.cells[1].innerText;
			textChanged = textChanged.replace(/=====/gi, "\n").trim();
			$("#tid_forumMsg").val(textChanged);
		}, function(response){
			alert("Impossible de protéger ce texte");
		});
	}


	/************************
	 * Module configuration *
	 ************************/

	return {

		name: MODULE_NAME,
		type: Module.TYPE.INTERFACE_ENHANCEMENT,

		properties: {
			enabled: false,
			isProtected: true
		},

		configurable: {
			enabled: {
				category: Module.PROPERTY_CATEGORY.GENERAL,
				type: Module.PROPERTY.BOOLEAN,
				short_desc_I18N: MODULE_NAME + '_short_desc',
				full_desc_I18N: MODULE_NAME + '_full_desc'
			}
		},

		actions: {
			can_run: function() {
				return !D2NE.is_restricted_mode();
			},

			init: function() {
				add_i18n();
			},

			load: function() {
				if(!D2N.is_on_forum()){
					return;
				}

				JS.injectCSS(
					'.d2n_protect_btn {' +
						'cursor: pointer;' +
						'background-image: url("http://data.hordes.fr/gfx/design/button.gif");' +
						'border: 1px solid #98341c !important;' +
						'outline: 1px solid black !important;' +
						'padding: 1px 10px;' +
						'font-weight: bold;' +
						'font-size: 12pt;' +
						'color: #f0d79e !important;' +
					'}');

				JS.wait_for_selector("#tid_forum_left .tid_actionBar a:nth-child(4)", function(node){
					$(node).click(add_button);
				})

				
				document.addEventListener('d2n_forum_topic', function() {
					JS.wait_for_selector_all("#tid_forum_right .tid_actions .tid_buttonBar a:first-child", function(nodes){
						$(nodes).click(add_button);
					});

					JS.wait_for_selector_all('.tid_tools a[onclick^="_tid.forum.cite"]', function(nodes) {
						$(nodes).click(add_button);
					});
				}, false);
			}
		}

	};
});

Module.register(function() {

	var MODULE_NAME = 'gui_enhancement';
	var OKGIF = "https://d2ne.datw.tf/ok.png";
	var NOKGIF = "https://d2ne.datw.tf/nok.png";
	var CAUTIONGIF = "https://d2ne.datw.tf/caution.png";
	var timer;

	/******************
	 * Module context *
	 ******************/

	/**
	 * Add the i18n strings for this module.
	 */
	function add_i18n()
	{
		var i18n = {};

		i18n[I18N.LANG.FR] = {};
		i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Améliorations de l\'interface';
		i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Modifie l\'interface de Hordes pour améliorer certains aspects du jeu.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_ok_save'] = 'Peut être sauvé.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_nok_save'] = 'Ne peut pas être sauvé.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_ok_escort'] = 'Escorte bien paramétrée.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_nok_escort'] = 'Escorte mal paramétrée.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_distance_text'] = 'Le citoyen est à $[1] km.';

		i18n[I18N.LANG.EN] = {};
		i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'GUI enhancement';
		i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Add some graphical enhancements to the general UI of Die2Nite.';
		i18n[I18N.LANG.EN][MODULE_NAME + '_ok_save'] = 'Can be saved.';
		i18n[I18N.LANG.EN][MODULE_NAME + '_nok_save'] = 'Cannot be saved.';
		i18n[I18N.LANG.EN][MODULE_NAME + '_ok_escort'] = 'Good escort options set.';
		i18n[I18N.LANG.EN][MODULE_NAME + '_nok_escort'] = 'Good escort options not set.';
		i18n[I18N.LANG.EN][MODULE_NAME + '_distance_text'] = 'The citizen is $[1] km away.';

		I18N.set(i18n);
	}

	function getDistance(x1,y1,x2,y2) {
		var a = x1 - x2;
		var b = y1 - y2;

		var c = Math.sqrt(a * a + b * b);
		return Math.round(c);
	}

	/**
	 * Add icon if citizen is outside and beyong reach (for the hero action "Rescue")
	 */
	function enhance_citizen_interface(){
		if($("div.heroMode").length){
			// If we're a hero, shows a green or red sign if the citizen can be rescued
			JS.wait_for_selector('div.citizens table', function(el) {
				var citizensRows = el.querySelectorAll("tr");
				for(var i = 1 ; i < citizensRows.length ; i += 1){
					var row = citizensRows[i];
					// No coordinates columns (dead citizen)
					if(row.children[4] === undefined) {
						continue;
					}

					// Get the citizen's location, and if it's "--", then he is in town
					var location = row.children[4].innerText.trim();
					if(location === "--") {
						continue;
					}

					location = location.split(',');

					// Here, the citizen is outside
					// It's time to see if he is farther than 11km (for the Rescue)
					var coordinates = [];
					coordinates[0] = location[0].substr(1);
					coordinates[1] = location[1].substr(0, location[1].length - 1);

					var distance = getDistance(0, 0, coordinates[0], coordinates[1]);

					var imgSauvetage = null;
					if($("#infosSaving" + i).length === 0){
						imgSauvetage = $("<img>");
						imgSauvetage.attr("id", "infosSaving" + i);
					} else {
						imgSauvetage = $("#infosSaving" + i);
					}

					imgSauvetage.attr("onmouseover", "js.HordeTip.showSpecialTip(this, 'helpTip', '', " + JSON.stringify(I18N.get(MODULE_NAME + '_distance_text').replace("$[1]", distance)) + ", event);");
					imgSauvetage.attr("onomuseout", "js.HordeTip.hide(event);");

					if(distance > 2) {
						imgSauvetage.attr("src", NOKGIF);
						imgSauvetage.attr("alt", I18N.get(MODULE_NAME + '_nok_save'));

					} else {
						imgSauvetage.attr("src", OKGIF);
						imgSauvetage.attr("alt", I18N.get(MODULE_NAME + '_ok_save'));
					}

					if($("#infosSaving" + i).length === 0){
						$(row.children[4].children[0]).append("&nbsp;");
						$(row.children[4].children[0]).append(imgSauvetage);
					}
				}
			});
		}

		JS.injectCSS(
			".citizens table th:nth-child(4) {" +
				"width: 60px;" +
			"}"
		);
	}

	/**
	 * Add icon to quickly see if a citizen escorted has the good escort options
	 */
	function enhance_outside_interface(){

		JS.wait_for_selector('div.who table', function(el) {
			if(D2N.is_outside_at_doors()){
				$(el).css("width", "350px");
			}
			var citizensRows = el.querySelectorAll("tr");
			
			// We go 2 by 2 because between each citizen row, there is a useless hidden row...
			for(var i = 1 ; i < citizensRows.length ; i += 2){
				var citizenRow = citizensRows[i];
				var actions = citizenRow.children[2];

				// If there is no actions possibles, it may be possible that the row is the player himself
				if(actions === undefined || actions === null || actions.innerHTML.trim() === "&nbsp;") {
					continue;
				}

				var escortInfosBtn = actions.querySelector('a img[src^="/gfx/icons/small_more.gif"]');

				var imgEscort = document.createElement("img");
				imgEscort.setAttribute("id", "infoEscort" + i);

				// We cannot know if the good escort options are set
				if(escortInfosBtn === null){
					continue;
				}

				var escortRow = citizensRows[i+1];

				// We move the "Stop escort" button
				var stopEscort = escortRow.querySelector("a[href^='#user/stopEscort']");
				stopEscort.setAttribute("class", "uact");
				$(stopEscort).insertBefore($(escortInfosBtn.parentNode.parentNode));
				$('<span>').html("&nbsp;").insertBefore($(escortInfosBtn.parentNode.parentNode));

				// We move the "Search Ground" button
				var searchGround = escortRow.querySelector("a[href^='#outside/remoteSearchGround']");
				searchGround.setAttribute("class", "uact");
				$(searchGround).insertBefore($(escortInfosBtn.parentNode.parentNode));
				$('<span>').html("&nbsp;").insertBefore($(escortInfosBtn.parentNode.parentNode));
				
				// If the citizen is already searching the ground, we disable the "search" button (as it is useless)
				var searching = citizenRow.children[1].querySelector("img[src$='gfx/icons/small_gather.gif']");
				if(searching !== null && searching !== undefined){
					searchGround.setAttribute("class", "uact uactOff off");
					searchGround.setAttribute("href", "#");
					searchGround.setAttribute("onclick", "return false;");
					searchGround.setAttribute("onmouseover", searching.getAttribute("onmouseover"));
				}


				// We get the escort infos
				var escortInfos = escortRow.querySelectorAll("div.extraInfos p");

				if(escortInfos.length > 0){
					imgEscort.setAttribute("src", NOKGIF);
					imgEscort.setAttribute("alt", I18N.get(MODULE_NAME + '_nok_escort'));
					var infos = "<p style='color: red;'>";
					for(var j = 0 ; j < escortInfos.length ; j += 1){
						infos += escortInfos[j].innerText + "<br />";
					}
					infos += "</p>";

					imgEscort.setAttribute("onmouseover", "js.HordeTip.showSpecialTip(this, 'helpTip', '', " + JSON.stringify(infos) + ", event);");
				} else {
					imgEscort.setAttribute("src", OKGIF);
					imgEscort.setAttribute("alt", I18N.get(MODULE_NAME + '_ok_escort'));
					imgEscort.setAttribute("onmouseover", "js.HordeTip.showSpecialTip(this, 'helpTip', '', " + JSON.stringify(I18N.get(MODULE_NAME + '_ok_escort')) + ", event);");
				}
				imgEscort.setAttribute("onmouseout", "js.HordeTip.hide(event);");

				if(document.getElementById("infoEscort" + i) !== null) {
					$("#infoEscort" + i).remove();
				}

				actions.appendChild(imgEscort);

				i += 1; // to skip the "more" row
			}
		});

		JS.wait_for_selector('#campInfos div.actions', function(el) {
			var actions = el.children;
			for(var i = 0 ; i <= actions.length ; i += 1){
				$(actions[i]).css("width", "auto");
			}
		});
	}

	/**
	 * Make the citizen's table wider
	 */
	function enhance_doors_interface(){
		JS.wait_for_selector("div.who", function(node){
			$(node).css("width", "300px");
		});
	}

	/**
	 * Make the building table better
	 */
	function enhancement_buildings_interface(){
		JS.wait_for_selector("table.table", function(node){
			JS.injectCSS(".bvote table tr td.reco { background-color: #ff0; color: black;} .bvote table tr.reco a.button {outline: 2px solid #ff0;}");
		});

		JS.wait_for_selector(".bvote div.reco", function(node){
			JS.injectCSS(".bvote div.reco { cursor: pointer; }");
			$(node).click(function(){
				var obj = $(".bvote table tr td.reco"); // Objet cible
				var speed = 750; // Durée de l'animation (en ms)
				$('html, body').animate( { scrollTop: $(obj).offset().top - $(window).height() / 2 }, speed ); // Go
				return false;
			});
		});
	}

	/**
	 * Make the bank display compatible with >999 items stacked
	 */
	function enhancement_bank_interface(){
		JS.wait_for_selector('ul.tools.stocks.cityInv', function(node){
			JS.injectCSS(
				"ul.stocks li.multi," +
				"ul.stocks li.multi a {" +
					"width: 42px;" +
				"}" +
				"ul.stocks li.multi img {"+
					"margin-right: -1px;" +
				"}" +
				"ul.stocks li {" +
					"margin-right: 0;" +
				"}"
			);
		});
	}

	/**
	 * Make some general design better
	 */
	function enhance_general_interface(){
		JS.wait_for_selector("#serverTime", function(node){
			var currentHour = +node.innerText.split(":")[0];
			var currentMinutes = +node.innerText.split(":")[1];

			var startNight = 19;
			var endNight = 7;
			if(D2N.is_on_die2nite()) {
				startNight = 18;
				endNight = 6;
			} else if(D2N.is_on_zombinoia()) {
				startNight = 20;
				endNight = 8;
			}

			// From 19:00 to 07:00, it's the night
			if(currentHour >= startNight || currentHour < endNight ){
				// Use the default background (mono-chromatic)
				JS.injectCSS(".bigBg2 { background-image: url(http://data.hordes.fr/gfx/design/bg_big2.jpg); }");
			} else {
				// Use the login background (full of colors)
				JS.injectCSS(".bigBg2 { background-image: url(http://data.hordes.fr/gfx/design/bg_big.jpg); }");
			}
		});

		// Change the classic notification to a less intrusive one
		JS.wait_for_selector("#notificationText", function(node){
			var text = node.innerHTML;

			if(!document.getElementById("discreteNotif")) {
				var dom = JS.jsonToDOM(["div", {"id": "discreteNotif"}, text], document);

				JS.wait_for_selector("#body", function(node){
					node.appendChild(dom);
				});
			} else {
				$("#discreteNotif").html(text);
			}

			if($("#notification").hasClass("showNotif")) {
				$("#discreteNotif").css("height", "");
				$("#discreteNotif").css("opacity", "1");
				$("#discreteNotif").css("padding-top", "10px");
				$("#discreteNotif").css("padding-bottom", "10px");
				clearTimeout(timer);

				var timeout = node.innerText.length * 50;

				if (timeout < 2000)
					timeout = 2000;

				timer = setTimeout(function(){
					$("#discreteNotif").css("height", "0");
					$("#discreteNotif").css("opacity", "0");
					$("#discreteNotif").css("padding-top", "0");
					$("#discreteNotif").css("padding-bottom", "0");
				}, timeout);
			} else {
				$("#discreteNotif").css("height", "0");
				$("#discreteNotif").css("opacity", "0");
				$("#discreteNotif").css("padding-top", "0");
				$("#discreteNotif").css("padding-bottom", "0");
			}

			$('#body').removeClass('hideSwf'); 
			$('#notification').removeClass();

		});
	}

	/************************
	 * Module configuration *
	 ************************/

	return {

		name: MODULE_NAME,
		type: Module.TYPE.INTERFACE_ENHANCEMENT,

		properties: {
			enabled: false,
            isProtected: false
		},

		configurable: {
			enabled: {
				category: Module.PROPERTY_CATEGORY.GENERAL,
				type: Module.PROPERTY.BOOLEAN,
				short_desc_I18N: MODULE_NAME + '_short_desc',
				full_desc_I18N: MODULE_NAME + '_full_desc'
			}
		},

		actions: {
			can_run: function() {
				return true;
			},

			init: function() {
				add_i18n();
			},

			load: function() {
				document.addEventListener('d2n_gamebody_reload', function() {
					if (D2N.is_on_page_in_city('citizens')) {
						enhance_citizen_interface();
					}

					if (D2N.is_outside()){
						enhance_outside_interface();
					}

					if (D2N.is_outside_at_doors()){
						enhance_doors_interface();
					}

					if(D2N.is_on_page_in_city('buildings')) {
						enhancement_buildings_interface();
					}

					if(D2N.is_on_page_in_city('bank')) {
						enhancement_bank_interface();
					}

					enhance_general_interface();
				}, false);

				JS.injectCSS(
					".bigBg2 {" +
						"background-attachment: fixed;" + 
					"}" +
					".newFooter {" +
						"background-position: -1px 0;" + 
					"}" +
					"#discreteNotif {" +
						"position: fixed;" +
						"top: " + $("#tid_bar").height() + "px;" +
						"z-index: 99;" +
						"overflow: hidden;" +
						"background-color: #5c2b20;" +
						"width: 100%;" +
						"opacity: 0;" +
						"padding-left: 10px;" +
						"padding-right: 10px;" +
						"transition: all 0.5s ease;" +
						"outline: 1px solid black;" +
						"border: 1px solid #ad8051;" +
					"}" +
					"#loading_section {" +
						"position: fixed;" +
						"z-index: 9999;" +
					"}"
				);

				if($("#tid_bar_down").parents(".bigBg2").length === 0) {
					$("#tid_bar_down").appendTo(".bigBg2");
				}
			}
		}
	};
});

Module.register(function() {

    var MODULE_NAME = 'hero_bar_stat';

    /******************
     * Module context *
     ******************/

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Enable hero bar stat';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'On soul page, enable days stat on the hero bar.';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Activer le pourcentage sur la barre héros';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Sur la page d\'âme, active le pourcentage sur la barre héros.';

        I18N.set(i18n);
    }


    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: false,
            isProtected: false
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.SOUL,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc'
            }
        },

        actions: {
            can_run: function() {
                return true;
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                JS.injectCSS(
                    'div.heroUpBar div.hfront {' +
                        'padding-left: 6px;' +
                        'text-align: center;' +
                        'font-family: "Century Gothic", "Arial", "Trebuchet MS", Verdana, sans-serif;' +
                        'font-size: 16pt;' +
                        'padding-top: 10px;' +
                        'color: #f0d79e;' +
                        'text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;' +
                    '}');

                document.addEventListener('d2n_gamebody_reload', function() {
                    if (!(D2N.is_on_page('ghost') || D2N.is_on_page('ghost_exp'))) {
                        return;
                    }

                    JS.wait_for_selector('#ghostImg img', function(node) {

                        // abort if not hero
                        if (node.alt !== 'ghost red') {
                            return;
                        }


                        JS.wait_for_selector('#ghost_pages img.hbar', function(node) {
                            var width = parseFloat(node.style.width);
                            //  Notes:
                            //      https://docs.google.com/spreadsheet/ccc?key=0ApXnDqiuh9UWdGVFOUVSTWduemdQMlk2T29RRG1kNlE&usp=sharing
                            //  Deducted relation:
                            //      0% <=> 0px
                            //      100% <=> 590px
                            var min_width = 0;
                            var max_width = 590; //px
                            var percent = parseInt((width - min_width) / (max_width - min_width) * 100);

                            JS.wait_for_selector('div.heroUpBar div.hfront', function(node) {
                                node.textContent = percent + '%';
                            });
                        });
                    });
                }, false);
            }
        }

    };
});

Module.register(function() {

    var MODULE_NAME = 'hide_city_outside_zones_banners';

    /******************
     * Module context *
     ******************/

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Hide the city and outside zones banners';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Hide the banner on the top of the screen when in city or outside.';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Cacher la bannière';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Cache l\'image en haut de l\'écran représentant le lieu où vous êtes (en ville ou dehors sur un bâtiment).';

        I18N.set(i18n);
    }


    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: false,
            isProtected: false
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.INTERFACE,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc'
            }
        },

        actions: {
            can_run: function() {
                return true;
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                JS.injectCSS(
                    'div.sectionArt {' +
                        'display: none;' +
                    '}'
                );
            }
        }

    };
});

Module.register(function() {

    var MODULE_NAME = 'hide_footer';

    /******************
     * Module context *
     ******************/


    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Hide footer';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Hide the page footer with informations about other games, Motion Twin, etc...';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Cacher le pied de page';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Cache le pied de page contenant des informations à propos des autres jeux de Motion Twin, etc...';

        I18N.set(i18n);
    }

    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: false,
            isProtected: false
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.INTERFACE,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc'
            }
        },

        actions: {
            can_run: function() {
                return true;
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                JS.injectCSS(
                    '#tid_bar_down {' +
                        'display: none;' +
                    '}' +
                    '#fbAd {' +
                        'height: 0;' +
                        'overflow: hidden;' +
                    '}'
                );
            }
        }

    };
});

Module.register(function() {

    var MODULE_NAME = 'hide_help';

    /******************
     * Module context *
     ******************/

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Hide help';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Hide all the helps in the interface.';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Cacher les aides';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Cache les aides de jeu dans toute l\'interface.';

        I18N.set(i18n);
    }


    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: false,
            isProtected: false
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.INTERFACE,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc'
            }
        },

        actions: {
            can_run: function() {
                return true;
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                JS.injectCSS(
                    '#mapTips, a.button[href^="#city/exp?editor=1;sk="] + p, .helpLink:not(.d2ne), #generic_section > div > em:last-of-type, .help, .heroHelp, .tid_goalHelp {' +
                        'display: none;' +
                    '}');

                document.addEventListener('d2n_gamebody_reload', function() {
                    if (!(D2N.is_on_page_in_city('guard'))) {
                        return;
                    }

                    // As the watch button is in a help div, if the help is hidden
                    // the button disappeared. So in this case the button is moved
                    // elsewhere in the DOM.
                    var hide_help_module = Module.get('hide_help');
                    if (hide_help_module !== null && hide_help_module.is_enabled()) {
                        JS.wait_for_class('help', function(help) {
                            JS.wait_for_selector('#generic_section a.button', function(button) {
                                button.style.marginTop = '18px';
                                button.style.marginBottom = '15px';
                                help[0].parentNode.insertBefore(button, help[0]);
                            });
                        });
                    }
                }, false);
            }
        }

    };
});

Module.register(function() {

    var MODULE_NAME = 'hide_hero_adds';

    /******************
     * Module context *
     ******************/

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Hide hero adds';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Hide the adds for the hero mode on all the website. Can be useful if you are already hero or don\'t want to be one.';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Cacher les publicités pour le mode héros';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Cache les publicités pour le mode héros. C\'est pratique si vous êtes déjà héros ou si vous ne comptez pas le devenir.';

        I18N.set(i18n);
    }


    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: false,
            isProtected: true
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.INTERFACE,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc'
            }
        },

        actions: {
            can_run: function() {
                return !D2NE.is_restricted_mode();
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                JS.injectCSS(
                    '.heroMode, #ghostHeroAd, #heroContainer, .heroAd, #ghostHeroChoose, .promoBt, .sondageBg {' +
                        'display: none !important;' +
                    '}'
                );
            }
        }

    };
});

Module.register(function() {

    var MODULE_NAME = 'hide_pegi';

    /******************
     * Module context *
     ******************/

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Hide PEGI logo';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Hide the PEGI logo at the bottom of each page.';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Cacher le logo PEGI';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Cache le logo PEGI en bas de page.';

        I18N.set(i18n);
    }


    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: false,
            isProtected: false
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.INTERFACE,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc'
            }
        },

        actions: {
            can_run: function() {
                return true;
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                JS.injectCSS(
                    '.pegi {' +
                        'display: none;' +
                    '}'
                );
            }
        }

    };
});

Module.register(function() {

    var MODULE_NAME = 'hide_rookie_mode';

    /******************
     * Module context *
     ******************/

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Hide rookie mode';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Hide all the links to enable the rookie mode.';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Cacher le mode apprentissage';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Cache tous les liens pour activer le mode apprentissage.';

        I18N.set(i18n);
    }


    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: false,
            isProtected: false
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.INTERFACE,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc'
            }
        },

        actions: {
            can_run: function() {
                return true;
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                JS.injectCSS(
                    'div.block.tutorialBlock, div.expertMode {' +
                        'display: none;' +
                    '}'
                );
            }
        }

    };
});

Module.register(function() {

    var MODULE_NAME = 'hide_rp_content';

    /******************
     * Module context *
     ******************/

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Hide RP content';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Hide all the RP content.';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Cacher le contenu RP';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Cache le contenu RP (Role-Play).';

        I18N.set(i18n);
    }


    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: false,
            isProtected: false
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.INTERFACE,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc'
            }
        },

        actions: {
            can_run: function() {
                return true;
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                JS.injectCSS(
                    '.ambiant, .flavor, .outSpot {' +
                        'display: none;' +
                    '}'
                );
            }
        }

    };
});

Module.register(function() {

    var MODULE_NAME = 'hide_twitter_share_button';

    /******************
     * Module context *
     ******************/

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Hide the Twitter share button';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Hide the Twitter share button on the Gazette.';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Cacher le bouton de partage Twitter';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Cache le bouton de partage Twitter sur la page de la Gazette.';

        I18N.set(i18n);
    }


    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: false,
            isProtected: false
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.INTERFACE,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc'
            }
        },

        actions: {
            can_run: function() {
                return true;
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                JS.injectCSS(
                    '#gameBodyLight ul.linkControl {' +
                        'display: none;' +
                    '}' +
                    '#gameBodyLight div.logControl {' +
                        'margin-top: 351px;' +
                    '}'
                );
            }
        }

    };
});

Module.register(function() {

    var MODULE_NAME = 'highlight_ap';

    /******************
     * Module context *
     ******************/

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Highlight AP';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Add a border with a specific color (from red to green) in function of the remaining number of action point.';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Colorer les PA';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Ajoute une bordure autour du nombre de PA. La couleur (du rouge au vert) varie en fonction du nombre de PA restant.';

        I18N.set(i18n);
    }


    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: false,
            isProtected: false
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.GENERAL,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc'
            }
        },

        actions: {
            can_run: function() {
                return true;
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                var highlight = function() {
                    D2N.get_number_of_ap(function(ap) {
                        var colors = [
                            'ff0000', // 0 AP
                            'ff4700', // 1 AP
                            'ff8e00', // 2 AP
                            'ffd500', // 3 AP
                            'e3ff00', // 4 AP
                            '9cff00', // 5 AP
                            '55ff00', // 6 AP
                            '00ff00'  // 7 AP+
                        ];

                        while (ap >= colors.length) {
                            ap -= 1;
                        }

                        JS.injectCSS(
                            '#movesCounter {' +
                                'border: 1px solid #' + colors[ap] + ' !important;' +
                            '}'
                        );
                    });
                };

                // Highlight on change
                document.addEventListener('d2n_apchange', function() {
                    highlight();
                }, false);
            }
        }

    };
});

Module.register(function() {

	var MODULE_NAME = 'hll_citizen_infos';
	var COLUMN_ID = 'hll_column';

	/******************
	 * Module context *
	 ******************/

	/**
	 * Add the i18n strings for this module.
	 */
	function add_i18n()
	{
		var i18n = {};

		i18n[I18N.LANG.FR] = {};
		i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Affiche les informations Hordes La Loi d\'un citoyen';
		i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Permet d\'afficher les recommandations et plaintes d\'un citoyen sur le site externe Hordes La Loi.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_column_head'] = 'HLL';
		i18n[I18N.LANG.FR][MODULE_NAME + '_unknown'] = 'Inconnue';
		i18n[I18N.LANG.FR][MODULE_NAME + '_council_title'] = 'Conseils';
		i18n[I18N.LANG.FR][MODULE_NAME + '_complain_title'] = 'Plaintes';

		I18N.set(i18n);
	}

	function add_citizens_note(response){
		// Parsing XML answer
		var xmlDoc = JS.parse_xml(response);
		var citizens = xmlDoc.getElementsByTagName("citizen");

		var tableCitizens;
		if (D2N.is_on_page_in_city('citizens')) {
			// Adding header
			tableCitizens = document.querySelectorAll("div.citizens table tr");
			var head = document.createElement("th");
			head.id = COLUMN_ID;
			head.innerHTML = I18N.get(MODULE_NAME + '_column_head');
			head.style.width = "60px";
			tableCitizens[0].appendChild(head);
		} else if (D2N.is_on_page_out_of_city("maps")) {
			tableCitizens = document.querySelectorAll("td.list li");
		}

		// Adding HLL vote
		for(var i = 0 ; i < citizens.length ; i++){
			var nodata  = citizens[i].getAttribute("no-data");
			var percent = citizens[i].getAttribute("percent");
			var id      = citizens[i].getAttribute("id");

			var red   = 255;
			var green = 255;
			var blue  = 0;

			var details = "";
			var info;
			if (D2N.is_on_page_in_city('citizens'))
				info    = document.createElement("td");
			else 
				info    = document.createElement("span");

			if(isNaN(percent) || nodata !== null){
				percent = I18N.get(MODULE_NAME + '_unknown');
				red = 128;
				green = 128;
				blue = 128;
			} else {
				percent = (+percent);
				if(percent > 0){
					red = (100 - percent) / 100 * 255;
					green = 255;
				} else {
					red = 255;
					green = (100 + percent) / 100 * 255;
				}
				var councils   = citizens[i].getElementsByTagName("councils")[0];
				var councilsNb = councils.getAttribute("nb");
				
				var complaints = citizens[i].getElementsByTagName("complaints")[0];
				var complaintsNb = complaints.getAttribute("nb");
				// Display the council number & the details
				details    += "<div style='color: limegreen;'>" + I18N.get(MODULE_NAME + '_council_title') + " : " + councilsNb + "</div>";
				var lstCouncils = councils.childNodes;
				var j = 0;
				if(lstCouncils.length > 0){
					details += "<ul>";
					for(j = 0 ; j < lstCouncils.length ; j++){

						details += "<li>" + lstCouncils[j].getAttribute("lib") + " (" + lstCouncils[j].getAttribute("nb") + ")</li>";
					}
					details += "</ul>";
				}
				// Display the complaints number & the details
				details    += "<div style='color: red;'>" + I18N.get(MODULE_NAME + '_complain_title') + " : " + complaintsNb + "</div>";
				var lstComplaints = complaints.childNodes;
				if(lstComplaints.length > 0){
					details += "<ul>";
					for(j = 0 ; j < lstComplaints.length ; j++){

						details += "<li>" + lstComplaints[j].getAttribute("lib") + " (" +  lstComplaints[j].getAttribute("nb") + ")</li>";
					}
					details += "</ul>";
				}
			}
			
			details = "<div>" + details + "</div>";

			info.setAttribute("data-id", id);
			info.setAttribute("style", "color: rgb(" + red + "," + green + "," + blue + ");");
			if(details !== "<div></div>"){
				info.setAttribute("onmouseover", "js.HordeTip.showSpecialTip(this, 'helpTip', '', " + JSON.stringify(details) + ", event);");
				info.setAttribute("onmouseout", "js.HordeTip.hide(event);");
			}

			info.innerHTML = "<a href='https://hordes-la-loi.fr/users/" + id + "/' target='_blank' style='color: rgb(" + red + "," + green + "," + blue + ");'>" + percent + "</a>";
			if (D2N.is_on_page_in_city('citizens')) {
				tableCitizens[i+1].appendChild(info);
			} else {
				tableCitizens[i].appendChild(info);
			}
		}
		document.getElementById("loading_section").style.display = "none";
	}


	function network_failure(){
		document.getElementById("loading_section").style.display = "none";
	}

	function extract_citizens_id(selector)
	{
		var citizens_link = document.querySelectorAll(selector);
		var ret = [];

		for (var i = 0; i < citizens_link.length; i++) {
			var id = citizens_link[i].getAttribute("tid_id");
			if (id == null) {
				var regex = /uid=(\d+)/;
		        var regex_results;

	            regex_results = regex.exec(citizens_link[i].href);
	            id = regex_results[1];
			}
			ret.push(id);
		}

		return ret;
	}

	/************************
	 * Module configuration *
	 ************************/

	return {

		name: MODULE_NAME,
		type: Module.TYPE.INTERFACE_ENHANCEMENT,

		properties: {
			enabled: false,
			tool: {
				directory_id: 97,
				api_key: null,
				update_method: 'GET',
				update_url: 'https://hordes-la-loi.fr/xml/users/twin-search.xml:'
			},
			isProtected: false
		},

		configurable: {
			enabled: {
				category: Module.PROPERTY_CATEGORY.CITIZENS,
				type: Module.PROPERTY.BOOLEAN,
				short_desc_I18N: MODULE_NAME + '_short_desc',
				full_desc_I18N: MODULE_NAME + '_full_desc'
			}
		},

		actions: {
			can_run: function() {
				return D2N.is_on_hordes();
			},

			init: function() {
				add_i18n();
			},

			load: function() {
				var method = this.properties.tool.update_method;
				var url = this.properties.tool.update_url;
				var key = this.properties.tool.api_key;
				
				document.addEventListener('d2n_gamebody_reload', function() {
					if (D2N.is_on_page_in_city('citizens')) {
						if($("#" + COLUMN_ID).length > 0){
							return;
						}

						document.getElementById("loading_section").style.display = "block";

						JS.wait_for_selector('div.citizens', function(el) {
							var citizens = extract_citizens_id('a[href^="/#ghost/city?go=ghost/user?uid="]');
							JS.network_request(
								method,
								url + citizens.join(','),
								'key=' + key,
								null,
								add_citizens_note,
								network_failure
							);
						});
						return;
					} else if (D2N.is_on_page_out_of_city("maps")) {
						JS.wait_for_selector('td.list', function(el) {
							document.getElementById("loading_section").style.display = "block";
							var citizens = extract_citizens_id('td.list a[href^="#ghost/user?uid="]');
							JS.network_request(
								method,
								"https://hordes-la-loi.fr/xml/users/search.xml:" + citizens.join(','),
								'key=' + key,
								null,
								add_citizens_note,
								network_failure
							);
						});
					}

				}, false);
			}
		}
	};
});

Module.register(function() {

	var MODULE_NAME = 'konami';

	/******************
	 * Module context *
	 ******************/

	var KONAMI_CODE = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65, 13];

	/**
	 * Add the i18n strings for this module.
	 */
	function add_i18n()
	{
		var i18n = {};

		i18n[I18N.LANG.EN] = {};
		i18n[I18N.LANG.EN][MODULE_NAME + '_enable_features'] = 'Congratulation ! You can enjoy the Die2Nite Enhancer advanced features by validating this popup. However, know that some of them might be unstable, source of issues and/or forbidden by the Motion Twin EULA. Under no circumstances this script developer can be held responsable of the consequences of their use. Also note that you will have to do the Konami code again to disable them.\n\nDo you want to continue?';
		i18n[I18N.LANG.EN][MODULE_NAME + '_disable_features'] = 'Do you want to disable the Die2Nite Enhancer advanced features?';
		i18n[I18N.LANG.EN][MODULE_NAME + '_wtf'] = "What's the point?";

		i18n[I18N.LANG.FR] = {};
		i18n[I18N.LANG.FR][MODULE_NAME + '_enable_features'] = 'Félicitations ! Vous pouvez bénéficier des fonctionalités avancées de Die2Nite Enhancer en validant cette popup. Sachez toutefois que certaines d\'entre elles peuvent être instables, sources de problèmes et/ou interdites par les CGU Motion-Twin. En aucun cas le développeur de ce script ne saurait être tenu pour responsable des conséquences de leur utilisation. Notez également qu\'il faudra ressaisir le code Konami si vous souhaitez les désactiver.\n\nVoulez-vous continuer ?';
		i18n[I18N.LANG.FR][MODULE_NAME + '_disable_features'] = 'Souhaitez-vous désactiver les fonctionnalités avancées de Die2Nite Enhancer ?';
		i18n[I18N.LANG.FR][MODULE_NAME + '_wtf'] = "Quel intérêt ?";

		I18N.set(i18n);
	}

	function on_konami_code()
	{
		var old_restricted_mode = D2NE.is_restricted_mode();

		if (D2NE.is_restricted_mode()) {
			D2NE.set_restricted_mode(!confirm(I18N.get(MODULE_NAME + '_enable_features')));
		} else {
			D2NE.set_restricted_mode(confirm(I18N.get(MODULE_NAME + '_disable_features')));
		}

		// if a change occured, reload the page
		if (old_restricted_mode !== D2NE.is_restricted_mode()) {
			JS.reload();
		}
	}

	var actual_index_ = -1;
	function keyboard_listener(code)
	{
		if (actual_index_ < 0 || actual_index_ >= KONAMI_CODE.length) {
			actual_index_ = 0;
		}

		if (KONAMI_CODE[actual_index_] === code) {
			actual_index_ += 1;

			if (actual_index_ >= KONAMI_CODE.length) {
				on_konami_code();
			}
		} else {
			actual_index_ = -1;
		}
	}

	function inject_easter_eggs(){
		if (D2N.is_on_gazette()) {
			JS.wait_for_selector("div.news div.sign", function(el) {
				el.innerText = "- Quelqu'un";
			}.bind(this));
		}

		D2N.is_in_town(function(in_town){
			if(in_town){
				JS.wait_for_id("clock", function(el) {
					$(el).attr("onclick", "");
					$(el).on('click', function(){
						alert(I18N.get(MODULE_NAME + "_wtf"));
					});
				}.bind(this));
			}
		});
	}

	/************************
	 * Module configuration *
	 ************************/

	return {

		name: MODULE_NAME,
		type: Module.TYPE.CONTAINER,

		properties: {
			enabled: true,
			isProtected: false
		},

		actions: {
			can_run: function() {
				return true;
			},

			init: function() {
				add_i18n();
			},

			load: function() {
				document.addEventListener('keydown', function(event) {
					// Cancel event if the cursor is in an input field or textarea
					if (event.target.nodeName === 'INPUT' || event.target.nodeName === 'TEXTAREA') {
						return;
					}

					keyboard_listener(event.keyCode);
				}, false);
				
				document.addEventListener('d2n_gamebody_reload', function() {
					inject_easter_eggs();
				});
			}
		}

	};
});

Module.register(function() {

	var MODULE_NAME = 'link_argordien';

	/******************
	 * Module context *
	 ******************/

	/**
	 * Add the i18n strings for this module.
	 */
	function add_i18n()
	{
		var i18n = {};

		i18n[I18N.LANG.FR] = {};
		i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = "Lien Argordien dans l'annuaire";
		i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = "Rajoute un lien vers l'Argordien dans l'annuaire.";

		I18N.set(i18n);
	}

	/************************
	 * Module configuration *
	 ************************/

	return {

		name: MODULE_NAME,
		type: Module.TYPE.EXTERNAL_SITE,

		properties: {
			enabled: false,
			isProtected: false,
			link: {
                url: 'https://d2ne.datw.tf/argordien/',
                icon: "https://d2ne.datw.tf/argordien/favicon.png",
                title: "Argordien"
            },
		},

		configurable: {
			enabled: {
				category: Module.PROPERTY_CATEGORY.SITES,
				type: Module.PROPERTY.BOOLEAN,
				short_desc_I18N: MODULE_NAME + '_short_desc',
				full_desc_I18N: MODULE_NAME + '_full_desc'
			}
		},

		actions: {
			can_run: function() {
				return D2N.is_on_hordes();
			},

			init: function() {
				add_i18n();
			}
		}

	};
});
Module.register(function() {

	var MODULE_NAME = 'link_gesthordes';

	/******************
	 * Module context *
	 ******************/

	/**
	 * Add the i18n strings for this module.
	 */
	function add_i18n()
	{
		var i18n = {};

		i18n[I18N.LANG.FR] = {};
		i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = "Lien Gest'Hordes dans l'annuaire";
		i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = "Rajoute un lien vers Gest'Hordes dans l'annuaire.";

		I18N.set(i18n);
	}

	/************************
	 * Module configuration *
	 ************************/

	return {

		name: MODULE_NAME,
		type: Module.TYPE.EXTERNAL_SITE,

		properties: {
			enabled: false,
			isProtected: false,
			link: {
                url: 'https://gest-hordes.eragaming.fr',
                icon: "https://gest-hordes.eragaming.fr/images/favicon.gif",
                title: "Gest'Hordes"
            },
		},

		configurable: {
			enabled: {
				category: Module.PROPERTY_CATEGORY.SITES,
				type: Module.PROPERTY.BOOLEAN,
				short_desc_I18N: MODULE_NAME + '_short_desc',
				full_desc_I18N: MODULE_NAME + '_full_desc'
			}
		},

		actions: {
			can_run: function() {
				return D2N.is_on_hordes();
			},

			init: function() {
				add_i18n();
			}
		}

	};
});
Module.register(function() {

	var MODULE_NAME = 'link_twinpedia';

	/******************
	 * Module context *
	 ******************/

	/**
	 * Add the i18n strings for this module.
	 */
	function add_i18n()
	{
		var i18n = {};

		i18n[I18N.LANG.FR] = {};
		i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = "Lien Twinpedia dans l'annuaire";
		i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = "Rajoute un lien vers l'archive Twinpedia dans l'annuaire.";

		I18N.set(i18n);
	}

	/************************
	 * Module configuration *
	 ************************/

	return {

		name: MODULE_NAME,
		type: Module.TYPE.EXTERNAL_SITE,

		properties: {
			enabled: false,
			isProtected: false,
			link: {
                url: 'http://twin.tithom.fr/',
                icon: "/file/19.dat",
                title: "Archive Twinpedia"
            },
		},

		configurable: {
			enabled: {
				category: Module.PROPERTY_CATEGORY.SITES,
				type: Module.PROPERTY.BOOLEAN,
				short_desc_I18N: MODULE_NAME + '_short_desc',
				full_desc_I18N: MODULE_NAME + '_full_desc'
			}
		},

		actions: {
			can_run: function() {
				return D2N.is_on_hordes();
			},

			init: function() {
				add_i18n();
			}
		}

	};
});
Module.register(function() {

    var MODULE_NAME = 'mask_completed_constructions';

    /******************
     * Module context *
     ******************/

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Allow to mask completed constructions';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'While on the construction page, add a link to mask all the completed constructions.';
        i18n[I18N.LANG.EN][MODULE_NAME + '_link_show'] = 'Show completed constructions';
        i18n[I18N.LANG.EN][MODULE_NAME + '_link_hide'] = 'Hide completed constructions';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Permettre de cacher les constructions finies';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Ajoute un lien dans la page constructions permettant de cacher les constructions finies.';
        i18n[I18N.LANG.FR][MODULE_NAME + '_link_show'] = 'Montrer les constructions finies';
        i18n[I18N.LANG.FR][MODULE_NAME + '_link_hide'] = 'Cacher les constructions finies';

        I18N.set(i18n);
    }

    /**
     * Call on a link click.
     */
    function on_link_click()
    {
        this.properties.hide_completed_constructions = !this.properties.hide_completed_constructions;

        $("#btnShowHide").text(this.properties.hide_completed_constructions ? I18N.get(MODULE_NAME + '_link_show') : I18N.get(MODULE_NAME + '_link_hide'));
        if (this.properties.hide_completed_constructions) {
            // Hide the constructions if needed
            $("tr.building.done").css("display", "none");
        } else {
            // Else show the constructions
            $("tr.building.done").css("display", "table-row");
        }

        this.save_properties();
    }

    /**
     * Insert hide/show link into the dom.
     */
    function insert_links_in_dom(node)
    {
        // if the two links are already present, then abort
        // 
        var btn = document.getElementById("btnShowHide");
        if(btn !== null)
            return;

        // Clone node and set the wanted properties (to keep the
        // right link behaviour)
        
        var link = document.createElement("a");
        link.id = "btnShowHide";
        link.className = "button";
        link.textContent = this.properties.hide_completed_constructions ? I18N.get(MODULE_NAME + '_link_show') : I18N.get(MODULE_NAME + '_link_hide');

        var f = on_link_click.bind(this);
        link.addEventListener('click', function() {
            f();
        }, false);

        node.parentNode.insertBefore(link, node.nextSibling);
    }

    /**
     * Hide constructions if needed | Add the link.
     */
    function add_link()
    {
        // Abort if not on the building page
        if (!D2N.is_on_page_in_city('buildings')) {
            return;
        }

        if (this.properties.hide_completed_constructions) {
            // Hide the constructions if needed
            JS.injectCSS(
                'tr.building.done {' +
                    'display: none;' +
                '}'
            );
        } else {
            // Else show the constructions
            JS.injectCSS(
                'tr.building.done {' +
                    'display: table-row;' +
                '}'
            );
        }

        var f = insert_links_in_dom.bind(this);
        JS.wait_for_selector('#generic_section .button', function(node) {
            f(node);
        });
    }


    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: true,
            hide_completed_constructions: false,
            isProtected: false
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.CONSTRUCTION,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc'
            }
        },

        actions: {
            can_run: function() {
                return true;
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                if (!D2N.is_in_city()) {
                    return;
                }

                var f = add_link.bind(this);
                document.addEventListener('d2n_gamebody_reload', function() {
                    f();
                }, false);
            }
        }

    };
});

Module.register(function() {

    var MODULE_NAME = 'outside_empty_bag';

    /******************
     * Module context *
     ******************/

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Empty the backpack in one click';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'You can empty your bagpack only one click while outside.';
        i18n[I18N.LANG.EN][MODULE_NAME + '_tooltip'] = 'Click to drop all your objects on the ground.';
        i18n[I18N.LANG.EN][MODULE_NAME + '_confirm'] = 'Are you sure you want to drop all your objects on the ground? Others players will be able to take them.';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Permettre de vider son sac en une fois';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Lorsque vous êtes à l\'extérieur, il peut être pratique de vider votre sac en une fois. Cette option rend le bouton "Sac à Dos" cliquable et permet de vider son contenu entier dans l\'Outre-Monde.';
        i18n[I18N.LANG.FR][MODULE_NAME + '_tooltip'] = 'Cliquez pour déposer tous vos objets par terre.';
        i18n[I18N.LANG.FR][MODULE_NAME + '_confirm'] = 'Êtes-vous sûr de vouloir déposer vos objets par terre ? D\'autres joueurs seront en mesure d\'en prendre possession.';

        I18N.set(i18n);
    }

    /**
     * Drop the first object in the given list.
     */
    function drop_first_item()
    {
        document.removeEventListener('d2n_gamebody_reload', drop_first_item);

        // Find the first item and try to drop it
        JS.wait_for_selector('ul.tools.shortTools.bagInv > li:nth-child(n + 3):not(.clear) > a', function found(item) {
            // if it a free slot, abort
            if (item.classList.contains('freeSlot')) {
                // all items have been dropped, reload the content
                D2N.get_session_key(function(sk) {
                    JS.injectJS('js.XmlHttp.get(' + JSON.stringify('outside/refresh?sk=' + sk) + ');');
                });
                return;
            }

            var onclick = item.getAttributeNode('onclick').nodeValue;
            var url = onclick.split('\'')[1];

            JS.injectJS('js.XmlHttp.get(' + JSON.stringify(url) + ');');

            // If in camping, try to drop an item to show the MT error and abort
            if (D2N.is_camping()) {
                return;
            }

            // When the item is removed, drop the second
            document.addEventListener('d2n_gamebody_reload', drop_first_item);
        });
    }

    /**
     * Called when the user clicks on the bagpack button.
     */
    function on_bagpack_button_click()
    {
        // if the user confirms
        if (window.confirm(I18N.get(MODULE_NAME + '_confirm'))) {
            // drop his/her objects
            drop_first_item();
        }
    }


    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: false,
            isProtected: false
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.OUTSIDE,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc'
            }
        },

        actions: {
            can_run: function() {
                return true;
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                JS.injectCSS(
                    'ul.tools.shortTools.bagInv > li:nth-child(2) {' +
                        'cursor: pointer;' +
                    '}');

                document.addEventListener('d2n_gamebody_reload', function() {
                    // if not outside, abort
                    if (!D2N.is_outside()) {
                        return;
                    }

                    // get bagpack and register the click event
                    var bagpack = JS.wait_for_selector('ul.tools.shortTools.bagInv > li:nth-child(2)', function(node) {
                        node.addEventListener('click', function() {
                            on_bagpack_button_click();
                        }, false);

                        // add the tooltip
                        JS.injectJS(
                            'var el = document.querySelector(\'ul.tools.shortTools.bagInv > li:nth-child(2)\');' +
                            'el.onmouseover = function(event) {' +
                                'return js.HordeTip.showSpecialTip(this, \'simpleTip\', \'\', ' + JSON.stringify(I18N.get(MODULE_NAME + '_tooltip')) + ', event);' +
                            '};' +
                            'el.onmouseout = function(event) {' +
                                'return js.HordeTip.hide(event);' +
                            '};');

                    });
                }, false);
            }
        }

    };
});


Module.register(function() {

	var MODULE_NAME = 'postit';

	/******************
	 * Module context *
	 ******************/

	var POSTIT_BASE_CLASS = 'd2ne_postit';
	var ADD_POSTIT_BTN_ID = 'add_d2ne_postit';

	/**
	 * Add the i18n strings for this module.
	 */
	function add_i18n()
	{
		var i18n = {};

		i18n[I18N.LANG.EN] = {};
		i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Post-it';
		i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Display some post-its where you need them.';
		i18n[I18N.LANG.EN][MODULE_NAME + '_add_new'] = 'New post-it';

		i18n[I18N.LANG.FR] = {};
		i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Ajouter des post-its';
		i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Ajoute des post-its où vous en avez besoin.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_add_new'] = "Nouveau post-it";

		I18N.set(i18n);
	}

	function add_new_postit(content){
		this.properties.cpt++;
		this.save_properties();

		if($("#postit-" + this.properties.cpt).length > 0) return;

		// Le premier postit est à 40px du haut (a cause de la toolbar Twinoid)
		var toppx = $("#tid_bar").height() + 10;

		// Si on a des postits
		if($("." + POSTIT_BASE_CLASS).length > 0) {
			// On cherche la première place disponible
			for(var i = 1 ; i <= this.properties.cpt ; i++) {
				if($("#postit-" + i).length) {
					// Le postit I existe, on ajoute sa hauteur + la marge entre 2 postit
					toppx += $("." + POSTIT_BASE_CLASS).height() + 10;
				} else {
					// il n'existe pas, on se casse
					break;
				}
			}
		}

		var leftpx = $("#contentBg").offset().left + $("#contentBg").width() + 20;

		var postit = $("#main").append("<div class='" + POSTIT_BASE_CLASS + "' id='postit-" + this.properties.cpt + "' style='top: " + toppx + "px;left:"+leftpx+"px;'>");
		var wrapper = $("#postit-" + this.properties.cpt).append("<div class='d2ne_postit_wrapper'>");
		wrapper.append("<textarea data-target='" + this.properties.cpt + "'>");
		wrapper.append("<a style='position: absolute;right: 5px;top: 5px;color: black;cursor: pointer;' data-target='" + this.properties.cpt + "'>X</a>");

		if(content != null){
			$("#postit-" + this.properties.cpt + " textarea").val(content);
		}

		$("#postit-" + this.properties.cpt + " textarea").keyup(function(ev){
			var cpttarget = $(ev.target).data("target");
			this.properties.postits[cpttarget] = $("#postit-" + cpttarget + " textarea").val();
			this.save_properties();
		}.bind(this));

		$("#postit-" + this.properties.cpt + " a").click(function(ev){
			var cpttarget = $(ev.target).data("target");
			$("#postit-" + cpttarget).remove();
			this.properties.postits[cpttarget] = null;
			this.save_properties();
		}.bind(this));
	}

	function add_button(){
		if($("#" + ADD_POSTIT_BTN_ID).length > 0) 
			return;
		var clss = "";

		if(D2N.is_on_forum()) {
			$("#backReboot").after("<div id='" + ADD_POSTIT_BTN_ID + "'>");
			clss = "postit_add_btn";
		} else if (D2N.is_outside()) {
			$(".left").append("<div id='" + ADD_POSTIT_BTN_ID + "'>");
			clss = "button";
		} else {
			if(D2N.is_on_page_in_city("overview") || D2N.is_on_page_in_city("bank")) {
				$(".right").append("<div id='" + ADD_POSTIT_BTN_ID + "'>");
			} else if (D2N.is_on_page_in_city("home")) {
				$(".home > .left").append("<div id='" + ADD_POSTIT_BTN_ID + "'>");
			} else if (D2N.is_on_page_in_city("well")) {
				$(".wellPane").append("<div id='" + ADD_POSTIT_BTN_ID + "'>");
			} else if (D2N.is_on_page_in_city("buildings")) {
				$("#generic_section .button:first").after("<div id='" + ADD_POSTIT_BTN_ID + "'>");
			} else if (D2N.is_on_page_in_city("doors") || D2N.is_on_page_in_city("refine")) {
				$("#generic_section .button:last").after("<div id='" + ADD_POSTIT_BTN_ID + "'>");
			} else if (D2N.is_on_page_in_city("tower")) {
				$(".tower").append("<div id='" + ADD_POSTIT_BTN_ID + "'>");
			} else if (D2N.is_on_page_in_city("upgrades")) {
				$("#generic_section .button").after("<div id='" + ADD_POSTIT_BTN_ID + "'>");
			}
			clss = "button";
		}
		var link = $("<a class='" + clss + "'>").click(function(){
			add_new_postit.call(this);
		}.bind(this)).html("<img src='//data.hordes.fr/gfx/icons/small_rp.gif'> " + I18N.get(MODULE_NAME + "_add_new"));

		$("#" + ADD_POSTIT_BTN_ID).append(link);
	}

	/************************
	 * Module configuration *
	 ************************/

	return {

		name: MODULE_NAME,
		type: Module.TYPE.INTERFACE_ENHANCEMENT,

		properties: {
			enabled: false,
			cpt: 0,
			postits: []
		},

		configurable: {
			enabled: {
				category: Module.PROPERTY_CATEGORY.GENERAL,
				type: Module.PROPERTY.BOOLEAN,
				short_desc_I18N: MODULE_NAME + '_short_desc',
				full_desc_I18N: MODULE_NAME + '_full_desc'
			}
		},

		actions: {
			can_run: function() {
				return true;
			},

			init: function() {
				add_i18n();
			},

			load: function() {
				JS.injectCSS(
					'.postit_add_btn {' +
						'cursor: pointer;' +
						'background-image: url("http://data.hordes.fr/gfx/design/button.gif");' +
						'border: 1px solid black;' +
					'}' +
					'.' + POSTIT_BASE_CLASS + ' {' +
						'position: fixed;' +
					    "z-index: 999;" +
					'}' +
					'.' + POSTIT_BASE_CLASS + ' textarea {' +
						'width: 200px;' +
						'height: 200px;' +
					'}' +
					'.d2ne_postit_wrapper {' +
						'position: absolute;' +
					    "z-index: 999;" +
					'}'
				);

				$(window).resize(function() {
					$("." + POSTIT_BASE_CLASS).css("left", ($("#contentBg").offset().left + $("#contentBg").width() + 20) + "px");
				});

				if(D2N.is_on_forum()){
					JS.injectCSS(
						'#' + ADD_POSTIT_BTN_ID + ' {' +
							'position: absolute;' +
							'top: 210px;' +
							'margin-left: 110px;' +
							'padding-top: 9px;' +
						'}' +
						'.postit_add_btn {' +
							'color: #f0d79e;' + 
							'padding: 2px 5px;' +
							'font-size: 8pt;' +
							'font-variant: small-caps;' +
						'}'
					);
					add_button.call(this);

					this.properties.cpt = 0;

					for(var i = this.properties.postits.length - 1 ; i > 0 ; i--){
						var value = this.properties.postits[i];
						if(value == undefined || value == null || value == ""){
							this.properties.postits.splice(i, 1);
						}
					}

					for(var post = 1 ; post < this.properties.postits.length ; post++){
						add_new_postit.call(this, this.properties.postits[post]);
					}

					this.save_properties();
				} else {
					document.addEventListener('d2n_gamebody_reload', function() {
						add_button.call(this);

						this.properties.cpt = 0;

						for(var i = this.properties.postits.length - 1 ; i > 0 ; i--){
							var value = this.properties.postits[i];
							if(value == undefined || value == null || value == ""){
								this.properties.postits.splice(i, 1);
							}
						}

						$(this.properties.postits).each(function(index, value){
							if(value != undefined && value != null && value != ""){
								add_new_postit.call(this, value);
							}
						}.bind(this));

						this.save_properties();
					}.bind(this), false);
				}
			}
		}
	};
});

Module.register(function() {

	var MODULE_NAME = 'quote_without_code';

	/******************
	 * Module context *
	 ******************/

	/**
	 * Add the i18n strings for this module.
	 */
	function add_i18n()
	{
		var i18n = {};

		i18n[I18N.LANG.FR] = {};
		i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Citer sans [cite]';
		i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Ajoute un bouton "Citer sans balises" pour citer sans mettre la balise [cite].';

		i18n[I18N.LANG.EN] = {};
		i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Quote without [cite]';
		i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Add a "Quote without code" in order to quote without the [cite] stuff.';

		I18N.set(i18n);
	}

	function add_quote_button(){
		JS.wait_for_selector_all('.tid_tools a[onclick^="_tid.forum.cite"]', function(nodes) {
			for(var i = 0 ; i < nodes.length ; i++){
				var newquote = $("<a>");
				newquote.html(I18N.get(MODULE_NAME + "_short_desc"));
				newquote.attr("onclick", "return false;");
				newquote.attr("href", "#");
				/* jshint ignore:start */
				newquote.click(function(){
					var quoteBtn = $(this).siblings('a[onclick^="_tid.forum.cite"]').click();
					JS.wait_for_id("tid_forumMsg", function(node){
						var content = $(node).val();
						content = content.replace(/\n?\[\/?cite(=.*\n)?]?/g, "");
						$(node).val(content);
					});
				});
				/* jshint ignore:end */
				newquote.insertAfter($(nodes[i]));
			}
		});
	}


	/************************
	 * Module configuration *
	 ************************/

	return {

		name: MODULE_NAME,
		type: Module.TYPE.INTERFACE_ENHANCEMENT,

		properties: {
			enabled: false,
			isProtected: false
		},

		configurable: {
			enabled: {
				category: Module.PROPERTY_CATEGORY.FORUM,
				type: Module.PROPERTY.BOOLEAN,
				short_desc_I18N: MODULE_NAME + '_short_desc',
				full_desc_I18N: MODULE_NAME + '_full_desc'
			}
		},

		actions: {
			can_run: function() {
				return true;
			},

			init: function() {
				add_i18n();
			},

			load: function() {
				if(!D2N.is_on_forum()){
					return;
				}
				
				document.addEventListener('d2n_forum_topic', function() {
					if(!D2N.is_on_forum()){
						return;
					}
					add_quote_button();
				}, false);
			}
		}

	};
});

Module.register(function() {

    var MODULE_NAME = 'remove_soul_distinctions_scrollbar';

    /******************
     * Module context *
     ******************/

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Remove the soul distinctions scrollbar';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'On the soul page, remove the soul distinctions scrollbar';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Enlever la barre de défilement des distinctions';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Sur la page d\'âme, enlève la barre de défilement des distinctions.';

        I18N.set(i18n);
    }


    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: false,
            isProtected: false
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.SOUL,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc'
            }
        },

        actions: {
            can_run: function() {
                return true;
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                JS.injectCSS(
                    '.tid_goalListWrapper, .tid_reachList {' +
                        'max-height: none !important;' +
                    '}' +
                    '.tid_bg2.tid_border2.tid_scrollBar {' +
                        'display: none;' +
                    '}' +
                    '.tid_bg3.tid_border3.tid_scrollTrack {' +
                        'height: auto;' +
                    '}' +
                    '.tid_scrollContentWrapper {' +
                        'overflow: visible;' +
                        'max-height: none;' +
                        'margin-right: 0;' +
                        'padding-top: 4px;' +
                    '}'
                );
            }
        }

    };
});

Module.register(function() {

	var MODULE_NAME = 'savva_calcpa';
	var style = 'background-color: #5c2b20;';
	style += 'outline: 1px solid black;';
	style += 'border: 1px solid #ad8051;';
	style += 'font: normal 10pt Century Gothic, Arial, Trebuchet MS, Verdana, sans-serif;';
	style += 'color: white;';
	style += 'width: auto;';
	style += 'padding-left: 6px;';
	style += 'padding-top: 2px;';
	style += 'padding-bottom: 2px;';
	style += 'margin-left: 1px;';
	style += 'margin-top: 3px;';
	style += 'cursor: help;';

	/******************
	 * Module context *
	 ******************/

	/**
	 * Add the i18n strings for this module.
	 */
	function add_i18n()
	{
		var i18n = {};

		i18n[I18N.LANG.FR] = {};
		i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Affichage des PAs potentiels (par -SAVVA-)';
		i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Calcule les PAs potentiels lors d\'une expédition (oui, c\'est inutile). Script par -SAVVA-, intégré et traduit avec sa permission.';

		i18n[I18N.LANG.FR][MODULE_NAME + '_ap_icon'] = '<img src="http://data.hordes.fr/gfx/loc/fr/small_pa.gif" />';

		i18n[I18N.LANG.FR][MODULE_NAME + '_water_ghoul'] = 'Vous ne pouvez pas vous regénérer avec de l\\\'eau, vous êtes une goule.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_water_already_drunk'] = 'Vous avez déjà bu.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_water_available'] = 'Vous n\\\'avez pas encore bu et vous possédez au moins une ration d\\\'eau.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_water_none'] = 'Vous n\\\'avez pas encore bu mais vous ne possédez pas de ration d\\\'eau.';

		i18n[I18N.LANG.FR][MODULE_NAME + '_meal_already_ate'] = 'Vous avez déjà mangé.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_meal_6_available'] = 'Vous n\\\'avez pas encore mangé et vous possédez au moins une bouffe 6 PA.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_meal_7_available'] = 'Vous n\\\'avez pas encore mangé et vous possédez au moins une bouffe 7 PA.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_meal_none'] = 'Vous n\\\'avez pas encore mangé mais vous ne possédez pas de bouffe.';

		i18n[I18N.LANG.FR][MODULE_NAME + '_alcohol_drunk'] = 'Vous ne pouvez pas boire d\\\'alcool.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_alcohol_available'] = 'Vous pouvez boire votre bouteille d\\\'alcool.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_alcohol_none'] = 'Vous pouvez boire de l\\\'alcool mais vous n\\\'en avez pas.';

		i18n[I18N.LANG.FR][MODULE_NAME + '_coffee_available'] = 'Vous possédez <strong>[NB]</strong> café(s).';
		i18n[I18N.LANG.FR][MODULE_NAME + '_coffee_none'] = 'Vous ne possédez aucun café.';

		i18n[I18N.LANG.FR][MODULE_NAME + '_hurt'] = 'Vous êtes blessé ! (Vos régénérations de PA sont diminués).';
		i18n[I18N.LANG.FR][MODULE_NAME + '_not_hurt'] = 'Vous n\\\'êtes pas blessé.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_not_hurt_sport'] = 'Vous n\\\'êtes pas blessé et vous possédez un Sport-Elec chargé.';

		i18n[I18N.LANG.FR][MODULE_NAME + "_drugs"] = 'Vous possédez <strong>[NBSTERO]</strong> stéroïdes, <strong>[NBTWINO]</strong> Twinoïdes, <strong>[NBRISK]</strong> drogues à risque et <strong>[NBCIDRE]</strong> cidres.';
		i18n[I18N.LANG.FR][MODULE_NAME + "_current_ap"] = 'Vous possédez <strong>[NB]</strong> <img src=&quot;http://data.hordes.fr/gfx/loc/fr/small_pa.gif&quot;> en réserve.';
		i18n[I18N.LANG.FR][MODULE_NAME + "_misc"] = 'Vous possédez <strong>[NB]</strong> objets de régénérations divers.';

		i18n[I18N.LANG.FR][MODULE_NAME + '_text_ap_exact'] = 'Vous possédez <strong>[NB]</strong> <img src="http://data.hordes.fr/gfx/loc/fr/small_pa.gif" />.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_text_ap_approx'] = 'Vous possédez de <strong>[NBMIN]</strong> à <strong>[NBMAX]</strong> <img src="http://data.hordes.fr/gfx/loc/fr/small_pa.gif"> potentiels.';

		i18n[I18N.LANG.FR][MODULE_NAME + '_text_pdc'] = 'Il a <strong>[NB]</strong> PDC.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_text_pdc_vs_zeds'] = 'Les humains sont à <strong>[PDC]</strong> contre <strong>[ZEDS]</strong>.';

		i18n[I18N.LANG.EN] = {};
		i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Display potentials APs (by -SAVVA-)';
		i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Calculate the potentials APs during an expedition (yes, this is useless). Script by -SAVVA-, integrated and translated with his permission.';

		i18n[I18N.LANG.EN][MODULE_NAME + '_ap_icon'] = '<img src="http://data.hordes.fr/gfx/loc/en/small_pa.gif" />';

		i18n[I18N.LANG.EN][MODULE_NAME + '_water_ghoul'] = 'You cannot get more AP with water, you are a ghoul.';
		i18n[I18N.LANG.EN][MODULE_NAME + '_water_already_drunk'] = 'You drank already.';
		i18n[I18N.LANG.EN][MODULE_NAME + '_water_available'] = 'You didn\\\'t drink and you have a water ration.';
		i18n[I18N.LANG.EN][MODULE_NAME + '_water_none'] = 'You didn\\\'t drink but you don\\\'t have a water ration.';

		i18n[I18N.LANG.EN][MODULE_NAME + '_meal_already_ate'] = 'You are fed.';
		i18n[I18N.LANG.EN][MODULE_NAME + '_meal_6_available'] = 'You didn\\\'t eat and you have a 6 AP meal.';
		i18n[I18N.LANG.EN][MODULE_NAME + '_meal_7_available'] = 'You didn\\\'t eat and you have a 7 AP meal.';
		i18n[I18N.LANG.EN][MODULE_NAME + '_meal_none'] = 'You didn\\\'t eat but you don\\\'t have any meal.';

		i18n[I18N.LANG.EN][MODULE_NAME + '_alcohol_drunk'] = 'You can\\\'t drink any alcohol.';
		i18n[I18N.LANG.EN][MODULE_NAME + '_alcohol_available'] = 'You can drink your alcohol.';
		i18n[I18N.LANG.EN][MODULE_NAME + '_alcohol_none'] = 'You can drink alcohol but you don\\\'t have any.';

		i18n[I18N.LANG.EN][MODULE_NAME + '_coffee_available'] = 'You have <strong>[NB]</strong> coffee(s).';
		i18n[I18N.LANG.EN][MODULE_NAME + '_coffee_none'] = 'You don\\\'t have any coffee.';

		i18n[I18N.LANG.EN][MODULE_NAME + '_hurt'] = 'You are hurt ! (Your AP recovery is 1 less).';
		i18n[I18N.LANG.EN][MODULE_NAME + '_not_hurt'] = 'You are not hurt.';
		i18n[I18N.LANG.EN][MODULE_NAME + '_not_hurt_sport'] = 'You are not hurt and you have a charged EMS System.';

		i18n[I18N.LANG.EN][MODULE_NAME + "_drugs"] = 'You have <strong>[NBSTERO]</strong> steroids, <strong>[NBTWINO]</strong> Twinoids, <strong>[NBRISK]</strong> risky drugs and <strong>[NBCIDRE]</strong> Ergot Homebrew.';
		i18n[I18N.LANG.EN][MODULE_NAME + "_current_ap"] = 'You currently have <strong>[NB]</strong> <img src=&quot;http://data.hordes.fr/gfx/loc/fr/small_pa.gif&quot;>.';
		i18n[I18N.LANG.EN][MODULE_NAME + "_misc"] = 'You have <strong>[NB]</strong> miscellaneous regenerating items.';

		i18n[I18N.LANG.EN][MODULE_NAME + '_text_ap_exact'] = 'You have <strong>[NB]</strong> <img src="http://data.hordes.fr/gfx/loc/en/small_pa.gif" />.';		
		i18n[I18N.LANG.EN][MODULE_NAME + '_text_ap_approx'] = 'You can have from <strong>[NBMIN]</strong> to <strong>[NBMAX]</strong> potential <img src="http://data.hordes.fr/gfx/loc/en/small_pa.gif">.';		

		i18n[I18N.LANG.EN][MODULE_NAME + '_text_pdc'] = 'He has <strong>[NB]</strong> CP.';
		i18n[I18N.LANG.EN][MODULE_NAME + '_text_pdc_vs_zeds'] = 'Humans are <strong>[PDC]</strong> vs <strong>[ZEDS]</strong>.';
		
		I18N.set(i18n);
	}

	function refresh() {
		if($('div.sectionArt').length === 1){
			var el = $('div.infoBar ul.inv li img[src*="small_empty_inv.gif"]').length;
			var pa = +$('div.counter').text().substr(1,1);
			var dejaBu = $('div.infoBar ul.status li img[src*="/gfx/icons/status_hasDrunk.gif"]').length;
			var goule = $('div.infoBar ul.status li img[src*="/gfx/icons/small_ghoul.gif"]').length;
			var dejaBouffe = $('div.infoBar ul.status li img[src*="/gfx/icons/status_hasEaten.gif"]').length;
			var pasAlcool = $('div.infoBar ul.status li img[src*="/gfx/icons/status_drunk.gif"]').length + $('ul#myStatus.status img[src*="/gfx/icons/status_hung_over.gif"]').length;
			var blesse = $('div.infoBar ul.status li img[src*="/gfx/icons/status_wound.gif"]').length;
			
			var eau = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_water.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_potion.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_water_can_1.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_water_can_2.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_water_can_3.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_water_cup.gif"]').length;
			var b6 = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_food_chick.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_food_tarte.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_can_open.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_cadaver.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_food_bar3.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_food_sandw.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_vegetable.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_food_bar2.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_food_noodles.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_bone_meat.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_food_bar1.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_food_biscuit.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_food_pims.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_dish.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_fruit.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_hmeat.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_undef.gif"]').length;
			var b7 = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_dish_tasty.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_chama_tasty.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_vegetable_tasty.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_food_noodles_hot.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_egg.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_food_candies.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_apple.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_meat.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_woodsteak.gif"]').length;
			var alcool = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_vodka.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_rhum.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_guiness.gif"]').length;
			var cidre = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_hmbrew.gif"]').length;
			var stero = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_drug.gif"]').length;
			var twino = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_drug_hero.gif"]').length;
			var drogueRisk = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_drug_random.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_beta_drug_bad.gif"]').length;
			var cafe = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_coffee.gif"]').length;
			var sport = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_sport_elec.gif"]').length;
			var autre = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_lsd.gif"]').length;
			var paPotentiels = pa;

			// Gestion de l'eau
			var outEau = "";
			if(goule){
				outEau = I18N.get(MODULE_NAME + "_water_ghoul");
				eau = 0;
			} else {
				if(dejaBu){
					outEau = I18N.get(MODULE_NAME + "_water_already_drunk");
					eau = 0;
				} else {
					if(eau) {
						outEau = I18N.get(MODULE_NAME + "_water_available");
						paPotentiels += 6-blesse;
					} else {
						outEau = I18N.get(MODULE_NAME + "_water_none");
					}
				}
			}

			// Gestion du repas
			var outBouffe = "";
			if(dejaBouffe) {
				outBouffe = I18N.get(MODULE_NAME + "_meal_already_ate");
				b6 = 0;
				b7 = 0;
			} else {
				if(!b6 && !b7){
					outBouffe = I18N.get(MODULE_NAME + "_meal_none");
				} else if (b6){
					outBouffe = I18N.get(MODULE_NAME + "_meal_6_available");
					paPotentiels += 6-blesse;
				} else {
					outBouffe = I18N.get(MODULE_NAME + "_meal_7_available");
					paPotentiels += 7-blesse;
					b6 = 0;
				}
			}

			// Gestion alcool
			var outAlcool = "";
			if(pasAlcool){
				outAlcool = I18N.get(MODULE_NAME + "_alcohol_drunk");
				alcool = 0;
			} else {
				if(alcool){
					outAlcool = I18N.get(MODULE_NAME + "_alcohol_available");
					paPotentiels += 6-blesse;
				} else {
					outAlcool = I18N.get(MODULE_NAME + "_alcohol_none");
				}
			}

			// Gestion café
			var outCafe = "";
			if(cafe === 0) {
				outCafe = I18N.get(MODULE_NAME + "_coffee_none");
			} else {
				outCafe = I18N.get(MODULE_NAME + "_coffee_available").replace('[NB]', cafe);
				paPotentiels += cafe * 4;
			}

			var outSport = "";
			if(blesse) {
				outSport = I18N.get(MODULE_NAME + "_hurt");
				sport = 0;
			} else {
				if(sport){
					outSport = I18N.get(MODULE_NAME + "_not_hurt_sport");
					paPotentiels += 5;
				} else {
					outSport = I18N.get(MODULE_NAME + "_not_hurt");
				}
			}
			var outDrogues = I18N.get(MODULE_NAME + "_drugs").replace("[NBSTERO]", stero).replace("[NBTWINO]", twino).replace("[NBRISK]", drogueRisk).replace("[NBCIDRE]", cidre);
			paPotentiels += stero * (6-blesse) + twino * (8-blesse) + cidre * (6-blesse);
			var paPotentielsMin = paPotentiels;
			paPotentiels += drogueRisk*(7-blesse);
			var outPa = "";
			if(paPotentiels == paPotentielsMin) {
				outPa = I18N.get(MODULE_NAME + "_text_ap_exact").replace("[NB]", paPotentiels);
			}
			if(paPotentiels != paPotentielsMin) {
				outPa = I18N.get(MODULE_NAME + "_text_ap_approx").replace("[NBMIN]", paPotentielsMin).replace("[NBMAX]", paPotentiels);
			}
			var outHoot = I18N.get(MODULE_NAME + "_ap_icon") + ' ('+pa+')';
			var PA6 = 6-blesse;
			var PA7 = 7-blesse;
			var PA8 = 8-blesse;

			if(eau >= 1)
				outHoot += ' + <img src="http://data.hordes.fr/gfx/icons/item_water.gif"> ('+PA6+')';
			if(b6 >= 1)
				outHoot += ' + <img src="/gfx/icons/item_can_open.gif"> ('+PA6+')';
			if(b7 >= 1)
				outHoot += ' + <img src="/gfx/icons/item_meat.gif"> ('+PA7+')';
			if(alcool >= 1)
				outHoot += ' + <img src="/gfx/icons/item_vodka.gif"> ('+PA6+')';
			if(stero >= 1)
				outHoot += ' + '+stero+'<img src="/gfx/icons/item_drug.gif"> ('+stero*PA6+')';
			if(twino >= 1)
				outHoot += ' + '+twino+'<img src="/gfx/icons/item_drug_hero.gif"> ('+twino*PA8+')';
			if(drogueRisk >= 1)
				outHoot += ' + '+drogueRisk+'<img src="/gfx/icons/item_drug_random.gif"> (0-'+drogueRisk*PA7+')';
			if(cafe >= 1)
				outHoot += ' + '+cafe+'<img src="/gfx/icons/item_coffee.gif"> ('+4*cafe+')';
			if(sport >= 1)
				outHoot += ' + <img src="/gfx/icons/item_sport_elec.gif"> (5)';
			if(cidre >= 1)
				outHoot += ' + '+cidre+'<img src="/gfx/icons/item_hmbrew.gif"> ('+PA6*cidre+')';

			var toInject = '<div style="'+style+'" id="CalcPa" ';
			toInject += 'onmouseover="js.HordeTip.showSpecialTip(this, \'simpleTip\', \'\', \'';
			toInject += '<div><img src=&quot;//data.twinoid.com/proxy/www.hordes.fr/gfx/forum/smiley/h_middot.gif&quot;>' + I18N.get(MODULE_NAME + '_current_ap').replace("[NB]", pa) + '<br />';
			toInject += '<img src=&quot;//data.twinoid.com/proxy/www.hordes.fr/gfx/forum/smiley/h_middot.gif&quot;> '+outEau+'<br />';
			toInject += '<img src=&quot;//data.twinoid.com/proxy/www.hordes.fr/gfx/forum/smiley/h_middot.gif&quot;> '+outBouffe+'<br />';
			toInject += '<img src=&quot;//data.twinoid.com/proxy/www.hordes.fr/gfx/forum/smiley/h_middot.gif&quot;> '+outAlcool+'<br />';
			toInject += '<img src=&quot;//data.twinoid.com/proxy/www.hordes.fr/gfx/forum/smiley/h_middot.gif&quot;> '+outDrogues+'<br />';
			toInject += '<img src=&quot;//data.twinoid.com/proxy/www.hordes.fr/gfx/forum/smiley/h_middot.gif&quot;> '+outCafe+'<br />';
			toInject += '<img src=&quot;//data.twinoid.com/proxy/www.hordes.fr/gfx/forum/smiley/h_middot.gif&quot;> '+outSport+'<br />';
			toInject += '<img src=&quot;//data.twinoid.com/proxy/www.hordes.fr/gfx/forum/smiley/h_middot.gif&quot;> '+I18N.get(MODULE_NAME + '_misc').replace('[NB]', autre) +'\', event);" ';
			toInject += 'onmouseout="js.HordeTip.hide(event);">'+outPa+'<br />'+outHoot+'</div><br />';

			if($('div#CalcPa').length == 0){
				$(toInject).insertAfter('div.sectionArt');
			}

			var a;
			var H;
			var Z;
			$('ul.logs li.entry.CL_OutsideMessage').each(function(){
				var messageContent = "";
				var Text = "";
				var regex = /\(h=([0-9]+) z=([0-9]+)(?: me=([0-9]+))?\)/;
				if($(this).children('span').length == 2) {
					// Départ
					messageContent = $(this).children('span').next().text();
					var matches = messageContent.match(regex);
					H = matches[1]*1;
					Z = matches[2]*1;
				} else {
					// Arrivée ou rappat
					messageContent = $(this).children('span').text();
					var matches = messageContent.match(regex);
					H = matches[1]*1;
					Z = matches[2]*1;
					if(matches[3] !== undefined){
						// 4 matches (toute la string, H,Z et ME, c'est une arrivée)
						var ME = matches[3]*1;
						Text = I18N.get(MODULE_NAME + "_text_pdc").replace("[NB]", ME);
						H = H + ME;
					}
				} 

				Text += '<br />' + I18N.get(MODULE_NAME + "_text_pdc_vs_zeds").replace("[PDC]", H).replace("[ZEDS]", Z);
				$(this).append('<span>'+Text+'<span>');
			});
		}
	}

	/************************
	 * Module configuration *
	 ************************/

	return {

		name: MODULE_NAME,
		type: Module.TYPE.INTERFACE_ENHANCEMENT,

		properties: {
			enabled: false,
            isProtected: false
		},

		configurable: {
			enabled: {
				category: Module.PROPERTY_CATEGORY.OUTSIDE,
				type: Module.PROPERTY.BOOLEAN,
				short_desc_I18N: MODULE_NAME + '_short_desc',
				full_desc_I18N: MODULE_NAME + '_full_desc'
			}
		},

		actions: {
			can_run: function() {
				return true;
			},

			init: function() {
				add_i18n();
			},

			load: function() {
				document.addEventListener('d2n_gamebody_reload', function() {
					if(!D2N.is_outside()){
						return;
					}
					refresh();
					$('div.who table.table tbody tr td.name').each(function(){
						var mouseover = $(this).children('a').eq(0).attr('onmouseover');
						$(this).children('a').eq(1).attr('onmouseover', mouseover);

						var mouseout = $(this).children('a').eq(0).attr('onmouseout');
						$(this).children('a').eq(1).attr('onmouseout', mouseout);
					});
				}, false);
			}
		}
	};
});

Module.register(function() {

	var MODULE_NAME = 'savva_expedition';

	/******************
	 * Module context *
	 ******************/

	/**
	 * Add the i18n strings for this module.
	 */
	function add_i18n()
	{
		var i18n = {};

		i18n[I18N.LANG.FR] = {};
		i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Expéditionananère (par -SAVVA-)';
		i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Calcule les PAs à dépenser et dans quel ordre se ravitailler. Script par -SAVVA-, intégré et traduit avec sa permission.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_title'] = 'Expéditionananère';
		
		i18n[I18N.LANG.FR][MODULE_NAME + '_ap_text'] = 'Points d\'action';
		i18n[I18N.LANG.FR][MODULE_NAME + '_ap_icon'] = '<img src="http://data.hordes.fr/gfx/loc/fr/small_pa.gif" />';

		i18n[I18N.LANG.FR][MODULE_NAME + '_advise_normal_text'] = 'Ordre conseillé pour expé à $[NB] PA normal';
		i18n[I18N.LANG.FR][MODULE_NAME + '_advise_thirsty_text'] = 'Ordre conseillé pour expé à $[NB] PA en soif';

		i18n[I18N.LANG.FR][MODULE_NAME + '_dehydration_title'] = 'Déshydratation';
		i18n[I18N.LANG.FR][MODULE_NAME + '_return_normal_text'] = 'Vous rentrerez en ville en état normal.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_return_thirsty_text'] = 'Vous rentrerez en ville en étant assoiffé.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_return_dehydrated_text'] = 'Vous rentrerez en ville en étant déshydraté.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_return_died_text'] = 'Vous mourrez durant cet expédition !<br /><img src="//data.twinoid.com/proxy/www.hordes.fr/gfx/forum/smiley/h_warning.gif"> Le rationnement en eau est insuffisant !';

		i18n[I18N.LANG.FR][MODULE_NAME + '_weapons_title'] = 'Armes';
		i18n[I18N.LANG.FR][MODULE_NAME + '_discharge'] = 'déchargé';
		i18n[I18N.LANG.FR][MODULE_NAME + '_charge'] = 'charge';
		i18n[I18N.LANG.FR][MODULE_NAME + '_total_kills'] = 'Total approximatif : ';


		i18n[I18N.LANG.EN] = {};
		i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Expedition helper (By -SAVVA-)';
		i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Calculate the optimum order to regenerate APs while doing an expedition. Script by -SAVVA-, integrated and translated with his permission.';
		i18n[I18N.LANG.EN][MODULE_NAME + '_title'] = 'Expedition helper';

		i18n[I18N.LANG.EN][MODULE_NAME + '_ap_text'] = 'Action points';
		i18n[I18N.LANG.EN][MODULE_NAME + '_ap_icon'] = '<img src="http://data.hordes.fr/gfx/loc/en/small_pa.gif" />';

		i18n[I18N.LANG.EN][MODULE_NAME + '_advise_normal_text'] = 'Advised order to do an expe of $[NB] AP and coming back normal';
		i18n[I18N.LANG.EN][MODULE_NAME + '_advise_thirsty_text'] = 'Advised order to do an expe of $[NB] AP and coming back thirsty';

		i18n[I18N.LANG.EN][MODULE_NAME + '_dehydration_title'] = 'Dehydration';
		i18n[I18N.LANG.EN][MODULE_NAME + '_return_normal_text'] = 'You\'ll get home being normal.';
		i18n[I18N.LANG.EN][MODULE_NAME + '_return_thirsty_text'] = 'You\'ll get home being thirsty.';
		i18n[I18N.LANG.EN][MODULE_NAME + '_return_dehydrated_text'] = 'You\'ll get home being dehydrated.';
		i18n[I18N.LANG.EN][MODULE_NAME + '_return_died_text'] = 'You\'ll die during this expedition !<br /><img src="//data.twinoid.com/proxy/www.hordes.fr/gfx/forum/smiley/h_warning.gif"> You don\'t have enough water !';

		i18n[I18N.LANG.EN][MODULE_NAME + '_weapons_title'] = 'Weapons';
		i18n[I18N.LANG.EN][MODULE_NAME + '_discharge'] = 'discharged';
		i18n[I18N.LANG.EN][MODULE_NAME + '_chargee'] = 'load';
		i18n[I18N.LANG.EN][MODULE_NAME + '_total_kills'] = 'Rough total: ';


		I18N.set(i18n);
	}

	function refresh() {
		if($('div.sectionArt').length == 1){
			var pa = +$('div.counter').text().substr(1,1);

			var noPASuppBouffe = $('div.infoBar ul.status li img[src*="/gfx/icons/status_hasEaten.gif"]').length;
			var noPASuppEau = $('div.infoBar ul.status li img[src*="/gfx/icons/status_hasDrunk.gif"]').length;

			var pasAlcool = $('div.infoBar ul.status li img[src*="/gfx/icons/status_drunk.gif"]').length + $('ul.status img[src*="/gfx/icons/status_hung_over.gif"]').length;
			var soif = $('div.infoBar ul.status li img[src*="/gfx/icons/status_thirst.gif"]').length;
			var deshy = $('div.infoBar ul.status li img[src*="/gfx/icons/status_dehyd.gif"]').length;

			var goule = $('div.infoBar ul.status li img[src*="/gfx/icons/status_ghoul.gif"]').length;
			var blesse = $('div.infoBar ul.status li img[src*="/gfx/icons/status_wound.gif"]').length;
			var eau = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_water.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_potion.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_water_can_1.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_water_can_2.gif"]').length*2 + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_water_can_3.gif"]').length*3 + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_water_cup.gif"]').length;
			var b6 = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_food_pims.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_food_bar1.gif"]').length + $('div.infoBar ul.inv li img[src*="gfx/icons/item_food_biscuit.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_food_chick.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_food_tarte.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_can_open.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_cadaver.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_food_bar3.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_food_sandw.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_vegetable.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_food_bar2.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_food_noodles.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_dish.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_fruit.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_hmeat.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_undef.gif"]').length;
			var b7 = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_dish_tasty.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_chama_tasty.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_vegetable_tasty.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_food_noodles_hot.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_egg.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_food_candies.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_apple.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_meat.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_woodsteak.gif"]').length;
			var alcool = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_vodka.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_rhum.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_guiness.gif"]').length;
			var cidre = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_hmbrew.gif"]').length;
			var stero = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_drug.gif"]').length;
			var twino = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_drug_hero.gif"]').length;
			var drogueRisk = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_drug_random.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_beta_drug_bad.gif"]').length;
			var cafe = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_coffee.gif"]').length;
			var sport = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_sport_elec.gif"]').length;
			var autre = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_lsd.gif"]').length;
			var expe = pa;
			var outEau = "";
			if(eau >= 1) {
				outEau = ' + <img src="http://data.hordes.fr/gfx/icons/item_water.gif?v=44">';
				if(noPASuppEau == 0) {
					expe += 6-blesse;
				}
			}

			var outBouffe = "";
			if(noPASuppBouffe == 0) {
				if(b6 >= 1 && b7 == 0) {
					outBouffe = ' + <img src="http://data.hordes.fr/gfx/icons/item_can_open.gif?v=44">'; expe += 6-blesse;
				}
				if(b7 >= 1) {
					outBouffe = ' + <img src="http://data.hordes.fr/gfx/icons/item_meat.gif?v=44">'; expe += 7-blesse; b7 = 1; b6 = 0;
				}
			}

			var outAlcool = '';
			if(pasAlcool == 0 && alcool >= 1) {
				outAlcool = ' + <img src="http://data.hordes.fr/gfx/icons/item_vodka.gif?v=44">'; expe += 6-blesse;
			}
			if(pasAlcool == 1) {
				outAlcool = '';
			}

			var outCidre = '';
			if(cidre >= 1) {
				outCidre = ' + '+cidre+' <img src="http://data.hordes.fr/gfx/icons/item_hmbrew.gif?v=44">';
			}

			var outStero = '';
			if(stero >= 1) {
				outStero = ' + '+stero+' <img src="http://data.hordes.fr/gfx/icons/item_drug.gif?v=44">';
			}

			var outTwino = '';
			if(twino >= 1) {
				outTwino = ' + '+twino+' <img src="http://data.hordes.fr/gfx/icons/item_drug_hero.gif?v=44">';
			}

			var outDrogueRisk = '';
			if(drogueRisk >= 1) {
				outDrogueRisk = ' + '+drogueRisk+' <img src="http://data.hordes.fr/gfx/icons/item_drug_random.gif?v=44">';
			}

			expe += stero*(6-blesse) + twino*(8-blesse) + drogueRisk*(6-blesse) + cidre*(6-blesse);
			var outCafe = '';
			if(cafe >= 1) {
				outCafe = ' + '+cafe+'<img src="http://data.hordes.fr/gfx/icons/item_coffee.gif?v=44">'; expe += cafe*4;
			}

			var outBlesse = '';
			if(blesse == 1) {
				outBlesse = '<br />Vous êtes blessé ! (Vos régénérations de PA sont diminués)';
			}

			var outSport = '';
			if(sport == 1 && blesse == 0) {
				outSport = ' + <img src="http://data.hordes.fr/gfx/icons/item_sport_elec.gif?v=44">'; expe += 5;
			}

			var introEau = "";
			if(soif == 0) {
				introEau = I18N.get(MODULE_NAME + "_advise_normal_text").replace("$[NB]", expe);
			}
			if(soif == 1) {
				introEau = I18N.get(MODULE_NAME + "_advise_thirsty_text").replace("$[NB]", expe);
			}

			var ordreEau = I18N.get(MODULE_NAME + "_ap_icon");
			var Eau = '<img src="http://data.hordes.fr/gfx/icons/item_water.gif?v=44">';
			var EauA = '<img src="http://data.hordes.fr/gfx/icons/status_hasDrunk.gif">';
			var Bouffe = '<img src="http://data.hordes.fr/gfx/icons/item_can_open.gif?v=44">';
			var Steak = '<img src="http://data.hordes.fr/gfx/icons/item_meat.gif?v=44">';
			var Stero = '<img src="http://data.hordes.fr/gfx/icons/item_drug.gif?v=44">';
			var Twino = '<img src="http://data.hordes.fr/gfx/icons/item_drug_hero.gif?v=44">';
			var Arisk = '<img src="http://data.hordes.fr/gfx/icons/item_drug_random.gif?v=44">';
			var Alcool = '<img src="http://data.hordes.fr/gfx/icons/item_vodka.gif?v=44">';
			var Cidre = '<img src="http://data.hordes.fr/gfx/icons/item_hmbrew.gif?v=44">';
			var Cafe = '<img src="http://data.hordes.fr/gfx/icons/item_coffee.gif?v=44">';
			var Sport = '<img src="http://data.hordes.fr/gfx/icons/item_sport_elec.gif?v=44">';
			var e = ' <img src="//data.twinoid.com/proxy/www.hordes.fr/gfx/forum/smiley/h_arrow.gif"> ';
			var dejaBu = noPASuppEau == 1;
			var dejaBouffe = 0;
			var assoiffement = 0;

			if(deshy == 1){
				assoiffement = 22 + pa;
			}else if(soif == 1) {
				assoiffement = 11 + pa;
			} else {
				assoiffement = 0 + pa;
			}

			var i = 0 + pa;
			while(i < expe){
				ordreEau += e;
				if(assoiffement > 17 && cafe >= 1) {
					ordreEau += Cafe;
					cafe--;
					assoiffement += 4;
					i += 4;
				} else if(assoiffement > 15 && eau >= 1 && dejaBu == 0) {
					ordreEau += Eau;
					eau--;
					dejaBu = 1;
					assoiffement = 6;
					i += 6;
				} else if(assoiffement > 15 && eau >= 1 && dejaBu == 1) {
					ordreEau += EauA;
					eau--;
					assoiffement = 0;
				} else if(assoiffement <= 14 && b7 >= 1 && dejaBouffe == 0) {
					ordreEau += Steak;
					dejaBouffe = 1;
					assoiffement += 7;
					i += 7;
				} else if(assoiffement <= 15 && b6 >= 1 && dejaBouffe == 0) {
					ordreEau += Bouffe;
					dejaBouffe = 1;
					assoiffement += 6;
					i += 6;
				} else if(assoiffement <= 15 && stero >= 1) {
					ordreEau += Stero;
					stero--;
					assoiffement += 6;
					i += 6;
				} else if(assoiffement <= 13 && twino >= 1) {
					ordreEau += Twino;
					twino--;
					assoiffement += 8;
					i += 8;
				} else if(assoiffement <= 15 && alcool >= 1 && pasAlcool == 0) {
					ordreEau += Alcool;
					pasAlcool = 1;
					assoiffement += 6;
					i += 6;
				} else if(assoiffement <= 15 && cidre >= 1) {
					ordreEau += Cidre;
					cidre--;
					assoiffement += 6;
					i += 6;
				} else if(assoiffement <= 14 && drogueRisk >= 1) {
					ordreEau += Arisk;
					drogueRisk--;
					assoiffement += 6;
					i += 6;
				} else if(assoiffement <= 16 && sport >= 1 && blesse == 0) {
					ordreEau += Sport;
					blesse = 1;
					assoiffement += 5;
					i += 5;
				} else if(eau == 0 && b7 >= 1 && dejaBouffe == 0) {
					ordreEau += Steak;
					dejaBouffe = 1;
					assoiffement += 7;
					i+= 7;
				} else if(eau == 0 && b6 >= 1 && dejaBouffe == 0) {
					ordreEau += Bouffe;
					dejaBouffe = 1;
					assoiffement += 6;
					i += 6;
				} else if(eau == 0 && stero >= 1) {
					ordreEau += Stero;
					stero--;
					assoiffement += 6;
					i += 6;
				} else if(eau == 0 && twino >= 1) {
					ordreEau += Twino;
					twino--;
					assoiffement += 8;
					i += 8;
				} else if(eau == 0 && alcool >= 1 && pasAlcool == 0) {
					ordreEau += Alcool;
					pasAlcool = 1;
					assoiffement += 6;
					i += 6;
				} else if(eau == 0 && cidre >= 1) {
					ordreEau += Cidre;
					cidre--;
					assoiffement += 6;
					i += 6;
				} else if(eau == 0 && drogueRisk >= 1) {
					ordreEau += Arisk;
					drogueRisk--;
					assoiffement += 6;
					i += 6;
				} else if(eau == 0 && sport >= 1 && blesse == 0) {
					ordreEau += Sport;
					blesse = 1;
					assoiffement += 5;
					i += 5;
				} else if(eau == 0 && cafe >= 1) {
					ordreEau += Cafe;
					cafe--;
					assoiffement += 4;
					i += 4;
				} else if(eau >= 1 && dejaBu == 0) {
					ordreEau += Eau;
					eau--;
					assoiffement = 6;
					i +=6;
				}
			}
			if(assoiffement >= 0 && assoiffement < 11) {
				ordreEau += '<br /><img src="http://data.hordes.fr/gfx/icons/status_hasDrunk.gif"> ' + I18N.get(MODULE_NAME + '_return_normal_text');
			} else if(assoiffement >= 11 && assoiffement < 22) {
				ordreEau += '<br /><img src="http://data.hordes.fr/gfx/icons/status_thirst.gif"> ' + I18N.get(MODULE_NAME + '_return_thirsty_text');
			} else if(assoiffement >= 22 && assoiffement < 33) {
				ordreEau += '<br /><img src="http://data.hordes.fr/gfx/icons/status_dehyd.gif"> ' + I18N.get(MODULE_NAME + '_return_dehydrated_text');
			} else if(assoiffement >= 33) {
				ordreEau += '<br /><img src="//data.twinoid.com/proxy/www.hordes.fr/img/icons/r_cwater.gif"> ' + I18N.get(MODULE_NAME + '_return_died_text');
			}
			
			if(goule == 1) {
				ordreEau = '<img src="http://data.hordes.fr/gfx/icons/small_ghoul.gif"> Cet inconvénient n\'affecte pas les goules !';
			}

			var listeArme = '';
			var zombiesTuesMin = 0;
			var zombiesTuesMax = 0;
			var arme = 0;
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_boomfruit.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_boomfruit.gif"> Pamplemousse explosif ('+arme+'), 5 à 9 zombies, 100% détruit<br />';
				zombiesTuesMin += 5*arme;
				zombiesTuesMax += 9*arme;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_grenade.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_grenade.gif"> Bombe à eau ('+arme+'), 1 à 4 zombies, 100% détruit<br />';
				zombiesTuesMin += 1*arme;
				zombiesTuesMax += 4*arme;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_bgrenade.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_bgrenade.gif"> Bombe à eau explosive ('+arme+'), 5 à 12 zombies, 100% détruit<br />';
				zombiesTuesMin += 5*arme;
				zombiesTuesMax += 12*arme;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_iphone.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_iphone.gif"> Téléphone portable ('+arme+'), 1 à 2 zombie, 100% transformé<br />';
				zombiesTuesMin += 1*arme;
				zombiesTuesMax += 2*arme;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_angryc.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_angryc.gif"> Petit chaton furieux ('+arme+'), ???, 100% détruit<br />';
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_pet_dog.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_pet_dog.gif"> Chien hargneux ('+arme+'), 1 zombie, 5% détruit<br />';
				zombiesTuesMin += 1*arme;
				zombiesTuesMax += 20*arme;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_pet_pig.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_pet_pig.gif"> Cochon malodorant (<img src="/gfx/icons/small_heavy.gif">), 1 zombie, 100% détruit<br />';
				zombiesTuesMin += 1;
				zombiesTuesMax += 1;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_pet_cat.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_pet_cat.gif"> Gros chat mignon ('+arme+'), 1 zombie, 12% détruit<br />';
				zombiesTuesMin += 1*arme;
				zombiesTuesMax += 8*arme;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_pet_chick.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_pet_chick.gif"> Poule ('+arme+'), 1 zombie, 100% détruit<br />';
				zombiesTuesMin += 1*arme;
				zombiesTuesMax += 1*arme;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_pet_rat.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_pet_rat.gif"> Rat géant ('+arme+'), 1 zombie, 100% détruit<br />';
				zombiesTuesMin += 1*arme;
				zombiesTuesMax += 1*arme;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_pet_snake.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_pet_snake.gif"> Serpent de 2 mètres (<img src="/gfx/icons/small_heavy.gif">), 1 zombie, 100% détruit<br />';
				zombiesTuesMin += 1;
				zombiesTuesMax += 1;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_small_knife.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_small_knife.gif"> Canif dérisoire ('+arme+'), 1/4 1 zombie, 75% cassé<br />';
				zombiesTuesMin += 0;
				zombiesTuesMax += 1*arme;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_chair_basic.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_chair_basic.gif"> Chaise (<img src="/gfx/icons/small_heavy.gif">), 1/2 1 zombie, 60% cassé<br />';
				zombiesTuesMin += 0;
				zombiesTuesMax += 1;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_wrench.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_wrench.gif"> Clef à Molette ('+arme+'), 1/3 1 zombie, 20% cassé<br />';
				zombiesTuesMin += 0;
				zombiesTuesMax += 1*arme;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_cutcut.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_cutcut.gif"> Coupe-coupe ('+arme+'), 2 zombie, 25% cassé<br />';
				zombiesTuesMin += 2*arme;
				zombiesTuesMax += 8*arme;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_knife.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_knife.gif"> Couteau à dents ('+arme+'), 1 zombie, 20% cassé<br />';
				zombiesTuesMin += 1*arme;
				zombiesTuesMax += 5*arme;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_swiss_knife.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_swiss_knife.gif"> Couteau suisse ('+arme+'), 1/3 1 zombie, 50% détruit<br />';
				zombiesTuesMin += 0;
				zombiesTuesMax += 1*arme;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_cutter.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_cutter.gif"> Cutter ('+arme+'), 1/2 1 zombie, 90% cassé<br />';
				zombiesTuesMin += 0;
				zombiesTuesMax += 1*arme;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_machine_2.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_machine_2.gif"> Four cancérigène (<img src="/gfx/icons/small_heavy.gif">), 1 zombie, 35% cassé<br />';
				zombiesTuesMin += 1;
				zombiesTuesMax += 3;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_staff.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_staff.gif"> Grand Bâton Sec ('+arme+'), 1/2 1 zombie, 70% cassé<br />';
				zombiesTuesMin += 0;
				zombiesTuesMax += 1*arme;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_chain.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_chain.gif"> Grosse chaîne rouillée ('+arme+'), 1/2 1 zombie, 30% cassé<br />';
				zombiesTuesMin += 0;
				zombiesTuesMax += 1*arme;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_bone.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_bone.gif"> Os humain fêlé ('+arme+'), 1 zombie, 75% cassé<br />';
				zombiesTuesMin += 1*arme;
				zombiesTuesMax += 2*arme;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_can_opener.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_can_opener.gif"> Ouvre-boîtes ('+arme+'), 1/3 1 zombie, 100% cassé<br />';
				zombiesTuesMin += 0;
				zombiesTuesMax += 1*arme;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_concrete_wall.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_concrete_wall.gif"> Pavés de béton informe (<img src="/gfx/icons/small_heavy.gif">), 1 à 2 zombies, 70% cassé<br />';
				zombiesTuesMin += 1*arme;
				zombiesTuesMax += 2*arme;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_machine_3.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_machine_3.gif"> Réfrigérateur d\'étudiant (<img src="/gfx/icons/small_heavy.gif">), 1 zombie, 30% cassé<br />';
				zombiesTuesMin += 1;
				zombiesTuesMax += 3;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_lawn.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_lawn.gif"> Tondeuse à gazon (<img src="/gfx/icons/small_heavy.gif">), 2 zombies, 12.5% cassé<br />';
				zombiesTuesMin += 2;
				zombiesTuesMax += 16;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_torch.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_torch.gif"> Torche ('+arme+'), 1/2 1 zombie, 80% transformé<br />';
				zombiesTuesMin += 0;
				zombiesTuesMax += 1*arme;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_torch_off.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_torche_off.gif"> Torche consumée ('+arme+'), 1/4 1 zombie, 90% cassé<br />';
				zombiesTuesMin += 0;
				zombiesTuesMax += 1*arme;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_screw.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_screw.gif"> Tournevis ('+arme+'), 1/4 1 zombie, 50% cassé<br />';
				zombiesTuesMin += 0;
				zombiesTuesMax += 1*arme;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_machine_1.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_machine_1.gif"> Vieille machine à laver (<img src="/gfx/icons/small_heavy.gif">), 1 zombie, 40% cassé<br />';
				zombiesTuesMin += 1;
				zombiesTuesMax += 2;
			}
			
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_pc.gif"]').length;
			if(arme > 0) {
				listeArme += '<img src="/gfx/icons/item_pc.gif"> Unité centrale (<img src="/gfx/icons/small_heavy.gif">), 1 zombie, 55% cassé<br />';
				zombiesTuesMin += 1;
				zombiesTuesMax += 2*arme;
			}
			
			if(eau == $('div.infoBar ul.inv li img[src*="/gfx/icons/item_water.gif"]').length && goule == 0) {
				eau -= 1; 
				if(eau < 0) {
					eau = 0;
				}
			} else {
				eau = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_water.gif"]').length;
			}

			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_watergun_opt_empty.gif"]').length+$('div.infoBar ul.inv li img[src*="/gfx/icons/item_watergun_opt_1.gif"]').length+$('div.infoBar ul.inv li img[src*="/gfx/icons/item_watergun_opt_2.gif"]').length+$('div.infoBar ul.inv li img[src*="/gfx/icons/item_watergun_opt_3.gif"]').length+$('div.infoBar ul.inv li img[src*="/gfx/icons/item_watergun_opt_4.gif"]').length+$('div.infoBar ul.inv li img[src*="/gfx/icons/item_watergun_opt_5.gif"]').length;
			if(arme > 0) {
				arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_watergun_opt_empty.gif"]').length*0+$('div.infoBar ul.inv li img[src*="/gfx/icons/item_watergun_opt_1.gif"]').length*1+$('div.infoBar ul.inv li img[src*="/gfx/icons/item_watergun_opt_2.gif"]').length*2+$('div.infoBar ul.inv li img[src*="/gfx/icons/item_watergun_opt_3.gif"]').length*3+$('div.infoBar ul.inv li img[src*="/gfx/icons/item_watergun_opt_4.gif"]').length*4+$('div.infoBar ul.inv li img[src*="/gfx/icons/item_watergun_opt_5.gif"]').length*5 + eau*5;
				listeArme += '<img src="/gfx/icons/item_watergun_opt_empty.gif"> Aqua-splash ('+arme+'), 1 zombie, 100% -1 ' + I18N.get(MODULE_NAME + '_charge') + '<br />';
				zombiesTuesMin += 1*arme;
				zombiesTuesMax += 1*arme;
			}

			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_watergun_empty.gif"]').length+$('div.infoBar ul.inv li img[src*="/gfx/icons/item_watergun_1.gif"]').length+$('div.infoBar ul.inv li img[src*="/gfx/icons/item_watergun_2.gif"]').length+$('div.infoBar ul.inv li img[src*="/gfx/icons/item_watergun_3.gif"]').length;
			if(arme > 0) {
				arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_watergun_empty.gif"]').length*0+$('div.infoBar ul.inv li img[src*="/gfx/icons/item_watergun_1.gif"]').length*1+$('div.infoBar ul.inv li img[src*="/gfx/icons/item_watergun_2.gif"]').length*2+$('div.infoBar ul.inv li img[src*="/gfx/icons/item_watergun_3.gif"]').length*3+eau*3;
				listeArme += '<img src="/gfx/icons/item_watergun_empty.gif"> Pistolet à eau ('+arme+'), 1 zombie, 100% -1 ' + I18N.get(MODULE_NAME + '_charge') + '<br />';
				zombiesTuesMin += 1*arme;
				zombiesTuesMax += 1*arme;
			} 

			var pile = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_pile.gif"]').length;
			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_mixergun.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_mixergun_empty.gif"]').length;
			if(arme > 0) {
				arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_mixergun.gif"]').length*1 + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_mixergun_empty.gif"]').length*0 + pile;
				listeArme += '<img src="/gfx/icons/item_mixergun_empty.gif"> Batteur électrique ('+arme+'), 1 zombie, 40% ' + I18N.get(MODULE_NAME + '_discharge') + '<br />';
				zombiesTuesMin += 1*arme;
				zombiesTuesMax += 2*arme;
			}

			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_big_pgun.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_big_pgun_empty.gif"]').length;
			if(arme > 0) {
				arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_big_pgun.gif"]').length*1 + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_big_pgun_empty.gif"]').length*0 + pile;
				listeArme += '<img src="/gfx/icons/item_big_pgun_empty.gif"> Devastator ('+arme+'), 2 zombies, 100% ' + I18N.get(MODULE_NAME + '_discharge') + '<br />';
				zombiesTuesMin += 2*arme;
				zombiesTuesMax += 2*arme;
			}

			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_pilegun.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_pilegun_empty.gif"]').length;
			if(arme > 0) {
				arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_pilegun_empty.gif"]').length*0 + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_pilegun.gif"]').length*1 + pile;
				listeArme += '<img src="/gfx/icons/item_pilegun_empty.gif"> Lance-Pile 1-PDTG ('+arme+'), 0 à 1 zombie, 100% ' + I18N.get(MODULE_NAME + '_discharge') + '<br />';
				zombiesTuesMin += 0;
				zombiesTuesMax += 1*arme;
			}

			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_pilegun_up.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_pilegun_up_empty.gif"]').length;
			if(arme > 0) {
				arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_pilegun_up_empty.gif"]').length*0 + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_pilegun_up.gif"]').length*1 + pile;
				listeArme += '<img src="/gfx/icons/item_pilegun_up_empty.gif"> Lance-Pile Mark II ('+arme+'), 1 zombie, 20% ' + I18N.get(MODULE_NAME + '_discharge') + '<br />';
				zombiesTuesMin += 1*arme;
				zombiesTuesMax += 5*arme;
			}

			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_taser.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_taser_empty.gif"]').length;
			if(arme > 0) {
				arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_taser.gif"]').length*1 + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_taser_empty.gif"]').length*0 + pile;
				listeArme += '<img src="/gfx/icons/item_taser_empty.gif"> Taser d\'auto-défense ('+arme+'), 0 à 1 zombie, 100% ' + I18N.get(MODULE_NAME + '_discharge') + '<br />';
				zombiesTuesMin += 0*arme;
				zombiesTuesMax += 1*arme;
			}

			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_chainsaw.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_chainsaw_empty.gif"]').length;
			if(arme > 0) {
				arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_chainsaw_empty.gif"]').length*0 + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_pc.gif"]').length*1 + pile;
				listeArme += '<img src="/gfx/icons/item_chainsaw_empty.gif> Tronçonneuse ('+arme+'), 3 zombies, 30% ' + I18N.get(MODULE_NAME + '_discharge') + '<br />';
				zombiesTuesMin += 3*arme;
				zombiesTuesMax += 9*arme;
			}

			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_lpoint.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_lpoint1.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_lpoint2.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_lpoint3.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_lpoint4.gif"]').length;
			if(arme > 0) {
				arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_lpoint.gif"]').length*0 + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_lpoint1.gif"]').length*1 + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_lpoint2.gif"]').length*2 + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_lpoint3.gif"]').length*3 + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_lpoint4.gif"]').length*4 + pile*4;
				listeArme += '<img src="/gfx/icons/item_lpoint.gif"> Pointeur Laser Brûlant ('+arme+'), 2 zombies, 100% -1 ' + I18N.get(MODULE_NAME + '_charge') + '<br />';
				zombiesTuesMin += 2*arme;
				zombiesTuesMax += 2*arme;
			}

			arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_jerrygun.gif"]').length + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_jerrygun_off.gif"]').length;
			if(arme > 0) {
				arme = $('div.infoBar ul.inv li img[src*="/gfx/icons/item_jerrygun.gif"]').length*1 + $('div.infoBar ul.inv li img[src*="/gfx/icons/item_jerrycan.gif"]').length*1;
				listeArme += '<img src="/gfx/icons/item_jerrygun.gif"> Pompe à Jerrycan ('+arme+'), 1 zombie, 15% ' + I18N.get(MODULE_NAME + '_discharge') + '<br />';
				zombiesTuesMin += 1*arme;
				zombiesTuesMax += 6*arme;
			}

			var cclArmes = "";
			if(zombiesTuesMin == zombiesTuesMax) {
				cclArmes = zombiesTuesMin;
			} else {
				cclArmes = 'de '+zombiesTuesMin+' à '+zombiesTuesMax;
			}

			var toInject = '<h2 style="text-align: center;"><strong>' + I18N.get(MODULE_NAME + "_title") +'</strong></h2>';
			toInject += '<h2>' + I18N.get(MODULE_NAME + "_ap_text") + '</h2>';
			toInject += I18N.get(MODULE_NAME + "_ap_icon") + outEau + outBouffe + outAlcool + outStero + outTwino + outDrogueRisk + outCafe + outCidre + outSport + ' = ' + expe + " " + I18N.get(MODULE_NAME + "_ap_icon") + outBlesse;
			toInject += '<h2>' + I18N.get(MODULE_NAME + "_dehydration_title") + '</h2>' + introEau + ':<br />'+ordreEau;
			toInject += '<h2>' + I18N.get(MODULE_NAME + "_weapons_title") + '</h2>' + listeArme + I18N.get(MODULE_NAME + "_total_kills") + cclArmes +' <img src="/gfx/icons/small_zombie.gif">';

			var inject = $('#Expedition').length == 0;

			var css = "";

			// Aux portes mais dedans
			if(D2N.is_on_page_in_city('doors')) {
				if(inject) {
					$("<div id='Expedition'>" + toInject + "</div>").insertAfter('p.ambiant');
					css = "#Expedition {";
					css += "margin-bottom: 10px;";
					css += "}";
				} else {
					$('#Expedition').html(toInject);
				}
			}

			// À la banque
			if(D2N.is_on_page_in_city('bank')) {
				if(inject) {
					$("<div id='Expedition'>" + toInject + "</div>").appendTo('div.right');
					css = "#Expedition {";
					css += "margin-top: 10px;";
					css += "}";
				} else {
					$('#Expedition').html(toInject);
				}
			}

			// Dans la maison
			if(D2N.is_on_page_in_city('home')) {
				if(inject) {
					$('<ul id="Expedition" class="tools shortTools homeInv">' + toInject + '</ul>').insertAfter('div.right ul.tools.shortTools.homeInv');
					css = "#Expedition h2 {";
					css += "margin-top: 0 !important;";
					css += "background-image: none !important;";
					css += "}";
				} else {
					$('#Expedition').html(toInject);
				}
			}

			// Dehors
			if(D2N.is_outside()) {
				if(inject) {
					$('<div class="block" id="Expedition" style="margin-top: 8px;"><div class="header"></div><div class="bg">' + toInject + '</div><div class="footer"></div></div>').appendTo('.sidePanel');
					css = "#Expedition h2 {";
					css += "margin-bottom: 10px;";
					css += "}";
					css += "#Expedition h2:first-child {";
					css += "margin-top: -7px !important;";
					css += "}";
				} else {
					$('#Expedition').html('<div class="header"></div><div class="bg">' + toInject + '</div><div class="footer"></div>');
				}
			}

			if (css != ""){
				JS.injectCSS(css);
			}
		}
	}

	/************************
	 * Module configuration *
	 ************************/

	return {

		name: MODULE_NAME,
		type: Module.TYPE.INTERFACE_ENHANCEMENT,

		properties: {
			enabled: false,
            isProtected: false
		},

		configurable: {
			enabled: {
				category: Module.PROPERTY_CATEGORY.OUTSIDE,
				type: Module.PROPERTY.BOOLEAN,
				short_desc_I18N: MODULE_NAME + '_short_desc',
				full_desc_I18N: MODULE_NAME + '_full_desc'
			}
		},

		actions: {
			can_run: function() {
				return true;
			},

			init: function() {
				add_i18n();
			},

			load: function() {
				document.addEventListener('d2n_gamebody_reload', function() {
					if(!D2N.is_outside() && !D2N.is_on_page_in_city('bank') && !D2N.is_on_page_in_city('home') && !D2N.is_on_page_in_city('doors')){
						return;
					}
					refresh();
				}, false);
			}
		}
	};
});

Module.register(function() {

    var MODULE_NAME = 'shortcuts';

    /******************
     * Module context *
     ******************/

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Enable shortcuts';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Let you use keyboard shortcuts in town to quickly access important places. The shortcuts are listed below : <br /><dl><dt>G + O</dt><dd>Overview</dd><dt>G + H</dt><dd>Home</dd><dt>G + W</dt><dd>Well</dd><dt>G + B</dt><dd>Bank</dd><dt>G + C</dt><dd>Citizens</dd><dt>G + D</dt><dd>Buildings</dd><dt>G + G</dt><dd>Gates</dd><dt>G + P</dt><dd>Town upgrades</dd><dt>G + T</dt><dd>Watchtower</dd><dt>G + M</dt><dd>Workshop</dd><dt>G + L</dt><dd>Night watch</dd></dl>';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Activer les raccourcis clavier';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Active des raccourcis claviers pour accéder rapidement aux places importantes en ville. Les raccourcis sont listés ci-dessous : <br /><dl><dt>G + O</dt><dd>Vue d\'ensemble</dd><dt>G + H</dt><dd>Maison</dd><dt>G + W</dt><dd>Puits</dd><dt>G + B</dt><dd>Banque</dd><dt>G + C</dt><dd>Citoyens</dd><dt>G + D</dt><dd>Constructions</dd><dt>G + G</dt><dd>Portes</dd><dt>G + P</dt><dd>Évolutions</dd><dt>G + T</dt><dd>Tour de guet</dd><dt>G + M</dt><dd>Atelier</dd><dt>G + L</dt><dd>Veille</dd></dl>';

        I18N.set(i18n);
    }

    /**
     * Handle the keydown event.
     * @param integer keycode The last keycode
     * @param integer previous_keycode The last-but-one keycode
     */
    function on_keydown_event(keycode, previous_keycode) {
        if (previous_keycode !== this.properties.mainBind) {
            return;
        }

        for (var bind in this.properties.binds) {
            if (this.properties.binds[bind] === keycode) {
                return D2N.go_to_city_page(bind);
            }
        }
    }

    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: false,
            // This bind has to be pressed first
            mainBind: 71, // 'G'
            binds: {
                // Page specific bind (have to be preceded by a 'main' bind stroke)
                overview: 79, // 'O'
                home: 72, // 'H'
                well: 87, // 'W'
                bank: 66, // 'B'
                citizens: 67, // 'C'
                buildings: 68, // 'D'
                doors: 71, // 'G'
                upgrades: 80, // 'P'
                tower: 84, // 'T'
                refine: 77, // 'M'
                guard: 76 // 'L'
            },
            isProtected: false
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.GENERAL,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc'
            }
        },

        actions: {
            can_run: function() {
                return true;
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                var f = on_keydown_event.bind(this);

                JS.keydown_event(function(keycode, previous_keycode) {
                    f(keycode, previous_keycode);
                });
            }
        }

    };
});

Module.register(function() {

    var MODULE_NAME = 'show_spoiler';

    /******************
     * Module context *
     ******************/

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Show the spoilers without mouse hover';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Show the spoilers content without hovering with your mouse.';

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Afficher les spoilers sans passer la souris';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Affiche le contenu des spoilers sans avoir besoin de passer la souris.';

        I18N.set(i18n);
    }


    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.INTERFACE_ENHANCEMENT,

        properties: {
            enabled: false,
            isProtected: true
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.FORUM,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc'
            }
        },

        actions: {
            can_run: function() {
                return !D2NE.is_restricted_mode();
            },

            init: function() {
                add_i18n();
            },

            load: function() {
                var css =
                    '.tid_editorContent .tid_spoil {';

                // If davf's module is loaded, we need to adapt the CSS rules
                var old_forum_tags = Module.get('davf_old_forum_tags');
                if (old_forum_tags !== null && old_forum_tags.is_enabled()) {
                    css +=
                        'background-image: url("/gfx/design/spoiler.gif") !important;' +
                        'background-color:transparent !important;' +
                        'color : #98a675 !important;';

                // If not loaded, just do the normal ones
                } else {
                    css +=
                        'background-image: url("http://data.twinoid.com/img/design/spoiler_hover.png");';
                }

                css +=
                    '}' +
                    '.tid_editorContent .tid_spoil *, .tid_editorContent .tid_wspoil {' +
                        'visibility: visible !important;' +
                    '}' +
                '';

                JS.injectCSS(css);
            }
        }

    };
});

Module.register(function() {

    var MODULE_NAME = 'sync_bbh';

    /******************
     * Module context *
     ******************/

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.FR] = {};
        i18n[I18N.LANG.FR][MODULE_NAME + '_name'] = 'BigBroth\'Hordes';
        i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Activer la synchronisation BBH (hors-Pandémonium)';
        i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Ajoute la possibilité de synchroniser avec BigBroth\'Hordes. Ne fonctionne pas en Pandémonium.';

        I18N.set(i18n);
    }


    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.EXTERNAL_TOOL,

        properties: {
            enabled: false,
            tool: {
                directory_id: 51,
                api_key: null,
                update_method: 'POST',
                update_url: 'http://bbh.fred26.fr/update.php'
            },
            isProtected: false
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.EXTERNAL_TOOL,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc'
            }
        },

        actions: {
            can_run: function() {
                return D2N.is_on_hordes();
            },

            init: function() {
                add_i18n();
            },

            update: function(callback_success, callback_failure) {
                JS.network_request(
                    this.properties.tool.update_method,
                    this.properties.tool.update_url,
                    'key=' + this.properties.tool.api_key + '&action=force_maj',
                    {
                        'Content-Type': 'application/x-www-form-urlencoded'
                    },
                    function(response_text) {
                        var parsed_xml = JS.parse_xml(response_text);
                        var hordes_node = parsed_xml.firstChild;
                        var answer = hordes_node.childNodes[1];

                        if (answer && answer.tagName === 'error' && answer.getAttribute('code') === 'ok') {
                            return callback_success();
                        }
                        return callback_failure();
                    },
                    function() {
                        return callback_failure();
                    }
                );
            }
        }

    };
});

Module.register(function() {

    var MODULE_NAME = 'sync_duskdawn';

    /******************
     * Module context *
     ******************/

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_name'] = 'From Dusk Till Dawn';
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Enable Dusk Dawn sync';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Add the possibility to sync with Dusk Dawn';

        I18N.set(i18n);
    }


    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.EXTERNAL_TOOL,

        properties: {
            enabled: false,
            tool: {
                directory_id: 14,
                api_key: null,
                update_method: 'POST',
                update_url: 'http://d2n.duskdawn.net/zone/extended'
            },
            isProtected: false
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.EXTERNAL_TOOL,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc'
            }
        },

        actions: {
            can_run: function() {
                return D2N.is_on_die2nite();
            },

            init: function() {
                add_i18n();
            },

            update: function(callback_success, callback_failure) {
                // Do not update if not outside
                if (!D2N.is_outside()) {
                    callback_failure();
                    return;
                }

                JS.network_request(
                    this.properties.tool.update_method,
                    this.properties.tool.update_url,
                    'key=' + this.properties.tool.api_key,
                    {
                        'Content-Type': 'application/x-www-form-urlencoded'
                    },
                    function(response_text) {
                        callback_success();
                    },
                    function() {
                        callback_failure();
                    }
                );
            }
        }

    };
});

Module.register(function() {

	var MODULE_NAME = 'sync_gh';
	var GHURL = "https://gest-hordes.eragaming.fr";
	/******************
	 * Module context *
	 ******************/

	/**
	 * Add the i18n strings for this module.
	 */
	function add_i18n()
	{
		var i18n = {};

		i18n[I18N.LANG.FR] = {};
		i18n[I18N.LANG.FR][MODULE_NAME + '_name'] = "Gest'Hordes";
		i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = "Activer la synchronisation Gest'Hordes";
		i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = "Ajoute la possibilité de synchroniser avec Gest'Hordes.";

		I18N.set(i18n);
	}

	/************************
	 * Module configuration *
	 ************************/

	return {

		name: MODULE_NAME,
		type: Module.TYPE.EXTERNAL_TOOL,

		properties: {
			enabled: false,
			tool: {
				directory_id: 199,
				api_key: null,
				update_method: 'GET',
				update_url: 'https://gest-hordes.eragaming.fr/'
			},
			isProtected: false
		},

		configurable: {
			enabled: {
				category: Module.PROPERTY_CATEGORY.EXTERNAL_TOOL,
				type: Module.PROPERTY.BOOLEAN,
				short_desc_I18N: MODULE_NAME + '_short_desc',
				full_desc_I18N: MODULE_NAME + '_full_desc'
			}
		},

		actions: {
			can_run: function() {
				return D2N.is_on_hordes();
			},

			init: function() {
				add_i18n();
			},

			update_gest: function(callback_success, callback_failure) {
				JS.network_request(
					"GET",
					GHURL + "/?reset",
					null,
					null,
					function() {
						return callback_success();
					},
					function() {
						return callback_failure();
					}
				);
			},

			update: function(callback_success, callback_failure) {
				var update_gest = this.actions.update_gest;
                JS.network_request(
                    "GET",
                    GHURL + "/?connexion",
                    null,
                    null,
                    function () {
                        update_gest(callback_success, callback_failure);
                    },
                    function () {
                        return callback_failure();
                    }
                );
			}
		}

	};
});

Module.register(function() {

    var MODULE_NAME = 'sync_mapviewer';

    /******************
     * Module context *
     ******************/

    /**
     * Add the i18n strings for this module.
     */
    function add_i18n()
    {
        var i18n = {};

        i18n[I18N.LANG.EN] = {};
        i18n[I18N.LANG.EN][MODULE_NAME + '_name'] = 'Map Viewer';
        i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Enable Map Viewer sync';
        i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Add the possibility to sync with Map Viewer';

        I18N.set(i18n);
    }


    /************************
     * Module configuration *
     ************************/

    return {

        name: MODULE_NAME,
        type: Module.TYPE.EXTERNAL_TOOL,

        properties: {
            enabled: false,
            tool: {
                directory_id: 1,
                api_key: null,
                update_method: 'POST',
                update_url: 'http://die2nite.gamerz.org.uk/plugin'
            },
            isProtected: false
        },

        configurable: {
            enabled: {
                category: Module.PROPERTY_CATEGORY.EXTERNAL_TOOL,
                type: Module.PROPERTY.BOOLEAN,
                short_desc_I18N: MODULE_NAME + '_short_desc',
                full_desc_I18N: MODULE_NAME + '_full_desc'
            }
        },

        actions: {
            can_run: function() {
                return D2N.is_on_die2nite();
            },

            init: function() {
                add_i18n();
            },

            update: function(callback_success, callback_failure) {
                // Do not update if not outside
                if (!D2N.is_outside()) {
                    callback_failure();
                    return;
                }

                JS.network_request(
                    this.properties.tool.update_method,
                    this.properties.tool.update_url,
                    'key=' + this.properties.tool.api_key,
                    {
                        'Content-Type': 'application/x-www-form-urlencoded'
                    },
                    function(response_text) {
                        if (/Zone -?\d+\/-?\d+ was updated successfully/.test(response_text)) {
                            callback_success();
                            return;
                        }
                        callback_failure();
                    },
                    function() {
                        callback_failure();
                    }
                );
            }
        }

    };
});

Module.register(function() {

	var MODULE_NAME = 'camping_predict_injector';
	/******************
	 * Module context *
	 ******************/

	/**
	* Add the i18n strings for this module.
	*/
	function add_i18n() {
		var i18n = {};

		i18n[I18N.LANG.FR] = {};
		i18n[I18N.LANG.FR][MODULE_NAME + '_short_desc'] = 'Camping Predict';
		i18n[I18N.LANG.FR][MODULE_NAME + '_full_desc'] = 'Ajoute les prévisions de Camping Predict. Plus d\'informations ici : http://zenoo.fr/. Script par Zenoo, intégré avec sa permission. Cliquez pour ouvrir le lien.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_city_hard'] = 'Pandémonium';

		i18n[I18N.LANG.FR][MODULE_NAME + '_search_text'] = 'Rechercher';
		i18n[I18N.LANG.FR][MODULE_NAME + '_search_desc'] = 'Cliquez sur ce lien pour effectuer une recherche sur Camping Predict.';
		i18n[I18N.LANG.FR][MODULE_NAME + '_settings_text'] = 'Paramètres';
		i18n[I18N.LANG.FR][MODULE_NAME + '_settings_desc'] = 'Cliquez sur ce lien pour modifier vos paramètres Camping Predict.';

		i18n[I18N.LANG.FR][MODULE_NAME + '_improvements'] = 'Aménagements';
		i18n[I18N.LANG.FR][MODULE_NAME + '_defense_objects'] = 'ODs d\'amélioration';
		i18n[I18N.LANG.FR][MODULE_NAME + '_hiding_citizens'] = 'Campeurs enterrés';
		i18n[I18N.LANG.FR][MODULE_NAME + '_improvements_required'] = 'Aménagements nécessaires';
		i18n[I18N.LANG.FR][MODULE_NAME + '_tomb'] = 'Tombe creusée';
		i18n[I18N.LANG.FR][MODULE_NAME + '_night'] = 'Nuit';
		i18n[I18N.LANG.FR][MODULE_NAME + '_pro_camper'] = 'Campeur pro';
		i18n[I18N.LANG.FR][MODULE_NAME + '_nights_camped'] = 'Nuits campées';
		i18n[I18N.LANG.FR][MODULE_NAME + '_lighthouse'] = 'Phare';
		i18n[I18N.LANG.FR][MODULE_NAME + '_devastated'] = 'Dévasté';
		i18n[I18N.LANG.FR][MODULE_NAME + '_close'] = 'Fermer';

		i18n[I18N.LANG.FR][MODULE_NAME + '_hero_camera'] = 'Appareil photo';

		i18n[I18N.LANG.FR][MODULE_NAME + '_status_title'] = 'Statut';
		i18n[I18N.LANG.FR][MODULE_NAME + '_status_very_high'] = 'Optimal';
		i18n[I18N.LANG.FR][MODULE_NAME + '_status_high'] = 'Élevé';
		i18n[I18N.LANG.FR][MODULE_NAME + '_status_correct'] = 'Correct';
		i18n[I18N.LANG.FR][MODULE_NAME + '_status_medium'] = 'Satisfaisant';
		i18n[I18N.LANG.FR][MODULE_NAME + '_status_decent'] = 'Limité';
		i18n[I18N.LANG.FR][MODULE_NAME + '_status_limited'] = 'Faible';
		i18n[I18N.LANG.FR][MODULE_NAME + '_status_very_low'] = 'Très faible';
		i18n[I18N.LANG.FR][MODULE_NAME + '_status_null'] = 'Quasi nul';
		i18n[I18N.LANG.FR][MODULE_NAME + '_status_highest'] = 'Correct max';

		i18n[I18N.LANG.FR][MODULE_NAME + '_buildings_none'] = 'Aucun';
		i18n[I18N.LANG.FR][MODULE_NAME + '_buildings_unknown'] = 'Bâtiment non-déterré';
		i18n[I18N.LANG.FR][MODULE_NAME + '_buildings_ambulance'] = 'Ambulance';
		i18n[I18N.LANG.FR][MODULE_NAME + '_buildings_cave'] = 'Caverne';
		i18n[I18N.LANG.FR][MODULE_NAME + '_buildings_home_depot'] = 'BricoTout';
		i18n[I18N.LANG.FR][MODULE_NAME + '_buildings_kebab'] = 'Kebab “Chez Coluche”';
		i18n[I18N.LANG.FR][MODULE_NAME + '_buildings_plane'] = 'Carlingue d\'avion';
		i18n[I18N.LANG.FR][MODULE_NAME + '_buildings_pump'] = 'Vieille Pompe Hydraulique';
		i18n[I18N.LANG.FR][MODULE_NAME + '_buildings_small_house'] = 'Petite maison';
		i18n[I18N.LANG.FR][MODULE_NAME + '_buildings_villa'] = 'Villa délabrée';


		i18n[I18N.LANG.EN] = {};
		i18n[I18N.LANG.EN][MODULE_NAME + '_short_desc'] = 'Camping Predict';
		i18n[I18N.LANG.EN][MODULE_NAME + '_full_desc'] = 'Add Camping Predict predictions. More informations here [FR]: http://zenoo.fr/. Script by Zenoo, integrated with his permission. Click to open link.';
		i18n[I18N.LANG.EN][MODULE_NAME + '_city_hard'] = 'Hardcore';

		i18n[I18N.LANG.EN][MODULE_NAME + '_search_text'] = 'Search';
		i18n[I18N.LANG.EN][MODULE_NAME + '_search_desc'] = 'Click here to search on Camping Predict.';
		i18n[I18N.LANG.EN][MODULE_NAME + '_settings_text'] = 'Settings';
		i18n[I18N.LANG.EN][MODULE_NAME + '_settings_desc'] = 'Click here to edit your Camping Predict settings.';

		i18n[I18N.LANG.EN][MODULE_NAME + '_improvements'] = 'Improvements';
		i18n[I18N.LANG.EN][MODULE_NAME + '_defense_objects'] = 'Improving DO\'s';
		i18n[I18N.LANG.EN][MODULE_NAME + '_hiding_citizens'] = 'Hiding citizens';
		i18n[I18N.LANG.EN][MODULE_NAME + '_improvements_required'] = 'Required improvements';
		i18n[I18N.LANG.EN][MODULE_NAME + '_tomb'] = 'Tomb digged';
		i18n[I18N.LANG.EN][MODULE_NAME + '_night'] = 'Night';
		i18n[I18N.LANG.EN][MODULE_NAME + '_pro_camper'] = 'Pro camper';
		i18n[I18N.LANG.EN][MODULE_NAME + '_nights_camped'] = 'Nights camped';
		i18n[I18N.LANG.EN][MODULE_NAME + '_lighthouse'] = 'Lighthouse';
		i18n[I18N.LANG.EN][MODULE_NAME + '_devastated'] = 'Devastated';
		i18n[I18N.LANG.EN][MODULE_NAME + '_close'] = 'Close';

		i18n[I18N.LANG.EN][MODULE_NAME + '_hero_camera'] = 'Pre-war Camera';

		i18n[I18N.LANG.EN][MODULE_NAME + '_status_title'] = 'Status';
		i18n[I18N.LANG.EN][MODULE_NAME + '_status_very_high'] = 'Optimal';
		i18n[I18N.LANG.EN][MODULE_NAME + '_status_high'] = 'High';
		i18n[I18N.LANG.EN][MODULE_NAME + '_status_correct'] = 'Correct';
		i18n[I18N.LANG.EN][MODULE_NAME + '_status_medium'] = 'Largely satisfactory';
		i18n[I18N.LANG.EN][MODULE_NAME + '_status_decent'] = 'Decent';
		i18n[I18N.LANG.EN][MODULE_NAME + '_status_limited'] = 'Limited';
		i18n[I18N.LANG.EN][MODULE_NAME + '_status_very_low'] = 'Really poor';
		i18n[I18N.LANG.EN][MODULE_NAME + '_status_null'] = 'Hee haw';
		i18n[I18N.LANG.EN][MODULE_NAME + '_status_highest'] = 'Highest possible';

		i18n[I18N.LANG.EN][MODULE_NAME + '_buildings_none'] = 'None';
		i18n[I18N.LANG.EN][MODULE_NAME + '_buildings_unknown'] = 'Unsearchable zone';
		i18n[I18N.LANG.EN][MODULE_NAME + '_buildings_ambulance'] = 'Ambulance';
		i18n[I18N.LANG.EN][MODULE_NAME + '_buildings_cave'] = 'Cave';
		i18n[I18N.LANG.EN][MODULE_NAME + '_buildings_home_depot'] = 'Home depot';
		i18n[I18N.LANG.EN][MODULE_NAME + '_buildings_kebab'] = 'Fraser D\'s Kebab-ish';
		i18n[I18N.LANG.EN][MODULE_NAME + '_buildings_plane'] = 'Plane Crash Site';
		i18n[I18N.LANG.EN][MODULE_NAME + '_buildings_pump'] = 'Old Hydraulic Pump';
		i18n[I18N.LANG.EN][MODULE_NAME + '_buildings_small_house'] = 'Small House';
		i18n[I18N.LANG.EN][MODULE_NAME + '_buildings_villa'] = 'Derelict Villa';

		I18N.set(i18n);

		batList[I18N.get(MODULE_NAME + "_buildings_none")] = 0;
		batList[I18N.get(MODULE_NAME + "_buildings_unknown")] = 1;
		batList[I18N.get(MODULE_NAME + "_buildings_plane")] = 2;
		batList[I18N.get(MODULE_NAME + "_buildings_pump")] = 3;
		batList[I18N.get(MODULE_NAME + "_buildings_villa")] = 4;
		batList["Supermarché pillé"] = 5;
		batList["Laboratoire cosmétique"] = 6;
		batList["Carcasses de voitures"] = 7;
		batList["Ancien commissariat"] = 8;
		batList["Bar Miteux"] = 9;
		batList["Abri anti-atomique"] = 10;
		batList["Stand de fête foraine"] = 11;
		batList[I18N.get(MODULE_NAME + "_buildings_small_house")] = 12;
		batList["Petit Bois obscur"] = 13;
		batList["Parking désaffecté"] = 14;
		batList["Chantier à l'abandon"] = 15;
		batList["Ecole maternelle brulée"] = 16;
		batList["Pharmacie Détruite"] = 17;
		batList["Fast-food"] = 18;
		batList[I18N.get(MODULE_NAME + "_buildings_home_depot")] = 19;
		batList["Maison d'un citoyen"] = 20;
		batList["Centrale hydraulique"] = 21;
		batList["Armurerie Guns'N'Zombies"] = 22;
		batList[I18N.get(MODULE_NAME + "_buildings_kebab")] = 23;
		batList["Ancien Velib"] = 24;
		batList["Villa de Duke"] = 25;
		batList[I18N.get(MODULE_NAME + "_buildings_cave")] = 27;
		batList["Caveau familial"] = 28;
		batList["Parc à l'abandon"] = 29;
		batList["Carrière effondrée"] = 30;
		batList["Route barrée"] = 31;
		batList["Tranchée aménagée"] = 32;
		batList["Cache de contrebandiers"] = 33;
		batList["Entrepôt désaffecté"] = 34;
		batList["Camion en panne"] = 35;
		batList[I18N.get(MODULE_NAME + "_buildings_ambulance")] = 36;
		batList["Cimetière indien"] = 37;
		batList["Atomic' Café"] = 38;
		batList["Silos à l'abandon"] = 39;
		batList["Meubles kiela"] = 40;
		batList["Epicerie Fargo"] = 41;
		batList["Mini-market"] = 42;
		batList["Abri de chantier"] = 43;
		batList["Cabane de jardin"] = 44;
		batList["Bibliothèque de quartier"] = 45;
		batList["Bureau de poste"] = 46;
		batList["Hangars de stockage"] = 47;
		batList["Ancien aérodrome"] = 48;
		batList["Immeuble délabré"] = 49;
		batList["Tente d'un citoyen"] = 50;
		batList["Motel “Dusk”"] = 51;
		batList["Mine effondrée"] = 52;
		batList["Gare de triage désertée"] = 53;
		batList["Vieil hôpital de campagne"] = 54;
		batList["Avant-poste militaire"] = 55;
		batList["Camion mairie-mobile"] = 56;
        batList["Camion « mairie-mobile »"] = 56;
		batList["Caverne anciennement habitée"] = 57;
		batList["Char d'assaut en panne"] = 58;
		batList["Un étrange appareil circulaire"] = 59;
		batList["Puits abandonné"] = 60;
		batList["Relais autoroutier"] = 61;
		batList["Le bar des illusions perdues"] = 62;
		batList["Bunker abandonné"] = 100;
		batList["Hôtel abandonné"] = 101;
		batList["Hôpital abandonné"] = 102;
	}

	var finalStatut, correct, customFinalStatut, customCorrect;

	var storedCityType = localStorage.getItem('storedCityType');
	if(storedCityType === null) storedCityType = 0;
	var storedJob = localStorage.getItem('storedJob');
	if(storedJob === null) storedJob = 0;
	var storedKm = localStorage.getItem('storedKm');
	if(storedKm === null) storedKm = 1;
	var storedBat = localStorage.getItem('storedBat');
	if(storedBat === null) storedBat = 0;
	var storedZomb = localStorage.getItem('storedZomb');
	if(storedZomb === null) storedZomb = 0;
	var storedUpgrade = localStorage.getItem('storedUpgrade');
	if(storedUpgrade === null) storedUpgrade = 0;
	var storedOdUpgrade = localStorage.getItem('storedOdUpgrade');
	if(storedOdUpgrade === null) storedOdUpgrade = 0;
	var storedAlreadyCamped = localStorage.getItem('storedAlreadyCamped');
	if(storedAlreadyCamped === null) storedAlreadyCamped = 0;
	var storedAlreadyHidden = localStorage.getItem('storedAlreadyHidden');
	if(storedAlreadyHidden === null) storedAlreadyHidden = 0;
	var storedPelures = localStorage.getItem('storedPelures');
	if(storedPelures === null) storedPelures = 0;
	var storedToiles = localStorage.getItem('storedToiles');
	if(storedToiles === null) storedToiles = 0;
	var storedTomb = localStorage.getItem('storedTomb');
	if(storedTomb === null) storedTomb = 0;
	var storedNight = localStorage.getItem('storedNight');
	if(storedNight === null) storedNight = 1;
	var storedFurtif = localStorage.getItem('storedFurtif');
	if(storedFurtif === null) storedFurtif = 0;
	var storedCP = localStorage.getItem('storedCP');
	if(storedCP === null) storedCP = 1;
	var storedPhare = localStorage.getItem('storedPhare');
	if(storedPhare === null) storedPhare = 0;
	var storedDevastated = localStorage.getItem('storedDevastated');
	if(storedDevastated === null) storedDevastated = 0;

	var permUpgrade = localStorage.getItem('permUpgrade');
	if(permUpgrade === null) permUpgrade = 0;
	var permOdUpgrade = localStorage.getItem('permOdUpgrade');
	if(permOdUpgrade === null) permOdUpgrade = 0;
	var permAlreadyCamped = localStorage.getItem('permAlreadyCamped');
	if(permAlreadyCamped === null) permAlreadyCamped = 0;
	var permTomb = localStorage.getItem('permTomb');
	if(permTomb === null) permTomb = 1;
	var permNight = localStorage.getItem('permNight');
	if(permNight === null) permNight = 1;
	var permCP = localStorage.getItem('permCP');
	if(permCP === null) permCP = 1;
	var permPhare = localStorage.getItem('permPhare');
	if(permPhare === null) permPhare = 0;
	var permDevastated = localStorage.getItem('permDevastated');
	if(permDevastated === null) permDevastated = 0;

	var batList = {};

	function loadCustomChances() {
		var villeVal = +typeVille(storedCityType);
		var kmVal = +distance(storedKm);
		var zoneVal = +zone(storedBat);
		var zombVal = +zombie(storedZomb, storedFurtif);
		var ameVal = +storedUpgrade;
		var ODVal = +calcBonusOD(storedOdUpgrade);
		var cingVal = +camping(storedAlreadyCamped, storedCP, storedCityType);
		var ceurVal = +campeur(storedAlreadyHidden);
		var peauVal = +calcBonusPeau(storedPelures);
		var tenteVal = +calcBonusTente(storedToiles);
		var tombVal = +calcBonusTomb(storedTomb);
		var nuitVal = +calcBonusNuit(storedNight);
		var pharVal = +calcBonusPhare(storedPhare);
		var devastVal = +calcMalusDevast(storedDevastated);

		var result = +villeVal + kmVal + zoneVal + zombVal + ameVal + ODVal + cingVal + ceurVal + peauVal + tenteVal + tombVal + nuitVal + pharVal + devastVal;

		result = round100(result);
		var opti = round10(-result);
		customCorrect = round10(opti - 2);

		var statut = "Error";

		if (result >= 0)
			statut = I18N.get(MODULE_NAME + '_status_very_high');
		else if (result < 0 && result > -2)
			statut = I18N.get(MODULE_NAME + '_status_high');
		else if (result <= -2 && result > -4)
			statut = I18N.get(MODULE_NAME + '_status_correct');
		else if (result <= -4 && result > -7)
			statut = I18N.get(MODULE_NAME + '_status_medium');
		else if (result <= -7 && result > -10)
			statut = I18N.get(MODULE_NAME + '_status_decent');
		else if (result <= -10 && result > -14)
			statut = I18N.get(MODULE_NAME + '_status_limited');
		else if (result <= -14 && result > -18)
			statut = I18N.get(MODULE_NAME + '_status_very_low');
		else if (result <= -18)
			statut = I18N.get(MODULE_NAME + '_status_null');

		var statut2 = statut;

		if (result >= -2)
			statut2 = I18N.get(MODULE_NAME + "_status_highest");

		if(storedJob == 1)
			customFinalStatut = statut;
		else
			customFinalStatut = statut2;

		$('#editor').next().find('.searchResult').show();
		$('#finalStatut').text(customFinalStatut);
		if(storedJob == 1)
			$('#correct').text(opti);
		else
			$('#correct').text(customCorrect);
	}

	function updateParam() {
		$('#permUpgrade input').val(permUpgrade);

		$('#permOdUpgrade input').val(permOdUpgrade);

		$('#permAlreadyCamped input').val(permAlreadyCamped);

		if(permTomb == 1) $('#permTomb input').prop( "checked", true );

		if(permNight == 1) $('#permNight input').prop( "checked", true );

		if(permCP == 1) $('#permCP input').prop( "checked", true );

		if(permPhare == 1) $('#permPhare input').prop( "checked", true );

		if(permDevastated == 1) $('#permDevastated input').prop( "checked", true );

		loadChances();
		$('#finalStatut').text(finalStatut);
		$('#correct').text(correct);
	}

	function updateSearch() {
		$('#storedCityType span[data-value="'+storedCityType+'"]').css({
			'text-decoration':'none',
			'color':'#f0d79e'
		});
		$('#storedCityType span[data-value!="'+storedCityType+'"]').css({
			'text-decoration':'line-through',
			'color':'grey'
		});

		$('#storedJob img[data-value="'+storedJob+'"]').css('opacity','1');
		$('#storedJob img[data-value!="'+storedJob+'"]').css('opacity','0.4');

		$('#storedKm input').val(storedKm);

		displayBat();
		$('#storedBat select option[value="'+storedBat+'"]').prop('selected', true);

		$('#storedZomb input').val(storedZomb);

		$('#storedUpgrade input').val(storedUpgrade);

		$('#storedOdUpgrade input').val(storedOdUpgrade);

		$('#storedAlreadyCamped input').val(storedAlreadyCamped);

		$('#storedAlreadyHidden input').val(storedAlreadyHidden);

		$('#storedPelures input').val(storedPelures);

		$('#storedToiles input').val(storedToiles);

		if(storedTomb == 1)
			$('#storedTomb input').prop( "checked", true );

		if(storedNight == 1)
			$('#storedNight input').prop( "checked", true );

		if(storedFurtif == 1)
			$('#storedFurtif input').prop( "checked", true );
		if(storedJob == 2)
			$('#storedFurtif').show();

		if(storedCP == 1)
			$('#storedCP input').prop( "checked", true );

		if(storedPhare == 1)
			$('#storedPhare input').prop( "checked", true );

		if(storedDevastated == 1)
			$('#storedDevastated input').prop( "checked", true );

		loadCustomChances();
	}

	function displayBat() {
		var km=storedKm;
		if (km >= 0 && km <= 28) {
			var liste = '<option value="0">-------------------' + I18N.get(MODULE_NAME + "_buildings_none") + '-------------------</span></option>';
			if (km >= 1 && km <= 24) {
				liste += '<option value="1">' + I18N.get(MODULE_NAME + "_buildings_unknown") + '</option>';
			}

			if (km >= 10 && km <= 13) {
				liste += '<option value="10">Abri anti-atomique</option>';
			}

			if (km >= 6 && km <= 9) {
				liste += '<option value="43">Abri de chantier</option>';
			}

			if (km >= 2 && km <= 5) {
				liste += '<option value="36">' + I18N.get(MODULE_NAME + "_buildings_ambulance") + '</option>';
			}

			if (km >= 12 && km <= 15) {
				liste += '<option value="48">Ancien Aerodrome</option>';
			}

			if (km >= 6 && km <= 9) {
				liste += '<option value="8">Ancien commissariat</option>';
			}

			if (km >= 4 && km <= 7) {
				liste += '<option value="24">Ancien Velib</option>';
			}

			if (km >= 5 && km <= 8) {
				liste += '<option value="22">Armurerie Guns\'N\'Zombies</option>';
			}

			if (km >= 6 && km <= 9) {
				liste += '<option value="38">Atomic\' Café</option>';
			}

			if (km >= 16 && km <= 19) {
				liste += '<option value="55">Avant-Poste Militaire</option>';
			}

			if (km >= 5 && km <= 8) {
				liste += '<option value="9">Bar Miteux</option>';
			}

			if (km >= 6 && km <= 9) {
				liste += '<option value="45">Bibliothèque de quartier</option>';
			}

			if (km >= 5 && km <= 8) {
				liste += '<option value="19">' + I18N.get(MODULE_NAME + "_buildings_home_depot") + '</option>';
			}

			if (km >= 5 && km <= 28) {
				liste += '<option value="100">Bunker abandonné (ruine)</option>';
			}

			if (km >= 8 && km <= 11) {
				liste += '<option value="46">Bureau de poste</option>';
			}

			if (km >= 6 && km <= 9) {
				liste += '<option value="44">Cabane de jardin</option>';
			}

			if (km >= 2 && km <= 5) {
				liste += '<option value="33">Cache de contrebandiers</option>';
			}

			if (km >= 2 && km <= 5) {
				liste += '<option value="35">Camion en panne</option>';
			}

			if (km >= 16 && km <= 19) {
				liste += '<option value="56">Camion mairie-mobile</option>';
			}

			if (km >= 3 && km <= 6) {
				liste += '<option value="7">Carcasses de voitures</option>';
			}

			if (km >= 4 && km <= 7) {
				liste += '<option value="2">' + I18N.get(MODULE_NAME + "_buildings_plane") + '</option>';
			}

			if (km >= 3 && km <= 6) {
				liste += '<option value="30">Carrière effondrée</option>';
			}

			if (km >= 3 && km <= 6) {
				liste += '<option value="28">Caveau familial</option>';
			}

			if (km >= 3 && km <= 6) {
				liste += '<option value="27">' + I18N.get(MODULE_NAME + "_buildings_cave") + '</option>';
			}

			if (km >= 16 && km <= 19) {
				liste += '<option value="57">Caverne anciennement habitée</option>';
			}

			if (km >= 5 && km <= 8) {
				liste += '<option value="21">Centrale hydraulique</option>';
			}

			if (km >= 4 && km <= 7) {
				liste += '<option value="15">Chantier à l\'abandon</option>';
			}

			if (km >= 21 && km <= 28) {
				liste += '<option value="58">Char d\'assaut en panne</option>';
			}

			if (km >= 3 && km <= 6) {
				liste += '<option value="37">Cimetière indien</option>';
			}

			if (km >= 3 && km <= 6) {
				liste += '<option value="16">Ecole maternelle brulée</option>';
			}

			if (km >= 2 && km <= 5) {
				liste += '<option value="34">Entrepôt désaffecté</option>';
			}

			if (km >= 6 && km <= 9) {
				liste += '<option value="41">Epicerie Fargo</option>';
			}

			if (km >= 6 && km <= 9) {
				liste += '<option value="18">Fast Food</option>';
			}

			if (km >= 10 && km <= 13) {
				liste += '<option value="53">Gare de triage désertée</option>';
			}

			if (km >= 15 && km <= 18) {
				liste += '<option value="47">Hangars de stockage</option>';
			}

			if (km >= 5 && km <= 28) {
				liste += '<option value="102">Hôpital abandonné (ruine)</option>';
			}

			if (km >= 5 && km <= 28) {
				liste += '<option value="101">Hôtel abandonné (ruine)</option>';
			}

			if (km >= 10 && km <= 13) {
				liste += '<option value="49">Immeuble délabré</option>';
			}

			if (km >= 3 && km <= 6) {
				liste += '<option value="23">' + I18N.get(MODULE_NAME + "_buildings_kebab") + '</option>';
			}

			if (km >= 2 && km <= 5) {
				liste += '<option value="6">Laboratoire cosmétique</option>';
			}

			if (km >= 21 && km <= 28) {
				liste += '<option value="62">Le bar des illusions perdues</option>';
			}

			if (km >= 1 && km <= 4) {
				liste += '<option value="20">Maison d\'un citoyen</option>';
			}

			if (km >= 4 && km <= 7) {
				liste += '<option value="40">Meubles kiela</option>';
			}

			if (km >= 12 && km <= 15) {
				liste += '<option value="52">Mine effondrée</option>';
			}

			if (km >= 8 && km <= 11) {
				liste += '<option value="42">Mini-market</option>';
			}

			if (km >= 12 && km <= 15) {
				liste += '<option value="51">Motel “Dusk”</option>';
			}

			if (km >= 4 && km <= 7) {
				liste += '<option value="29">Parc à l\'abandon</option>';
			}

			if (km >= 3 && km <= 6) {
				liste += '<option value="14">Parking désaffecté</option>';
			}

			if (km >= 2 && km <= 5) {
				liste += '<option value="13">Petit Bois obscur</option>';
			}

			if (km >= 2 && km <= 5) {
				liste += '<option value="12">' + I18N.get(MODULE_NAME + "_buildings_small_house") + '</option>';
			}

			if (km >= 4 && km <= 7) {
				liste += '<option value="17">Pharmacie Détruite</option>';
			}

			if (km >= 17 && km <= 20) {
				liste += '<option value="60">Puits abandonné</option>';
			}

			if (km >= 8 && km <= 11) {
				liste += '<option value="61">Relais autoroutier</option>';
			}

			if (km >= 4 && km <= 7) {
				liste += '<option value="31">Route barrée</option>';
			}

			if (km >= 8 && km <= 11) {
				liste += '<option value="39">Silos à l\'abandon</option>';
			}

			if (km >= 5 && km <= 8) {
				liste += '<option value="11">Stand de fête foraine</option>';
			}

			if (km >= 4 && km <= 7) {
				liste += '<option value="5">Supermarché pillé</option>';
			}

			if (km >= 12 && km <= 15) {
				liste += '<option value="50">Tente d\'un citoyen</option>';
			}

			if (km >= 5 && km <= 8) {
				liste += '<option value="32">Tranchée aménagée</option>';
			}

			if (km >= 21 && km <= 28) {
				liste += '<option value="59">Un étrange appareil circulaire</option>';
			}

			if (km >= 16 && km <= 19) {
				liste += '<option value="54">Vieil hôpital de campagne</option>';
			}

			if (km >= 3 && km <= 6) {
				liste += '<option value="3">' + I18N.get(MODULE_NAME + "_buildings_pump") + '</option>';
			}

			if (km >= 12 && km <= 15) {
				liste += '<option value="25">Villa de Duke</option>';
			}

			if (km >= 3 && km <= 6) {
				liste += '<option value="4">' + I18N.get(MODULE_NAME + "_buildings_villa") + '</option>';
			}

			$("#storedBat select").html(liste);
		}
	}

	function round10(val) {
		var valRound = (Math.round(val*10))/10;
		return valRound;
	}

	function round100(val) {
		var valRound = (Math.round(val*100))/100;
		return valRound;
	}

	function inArray(cible, tableau) {
		var length = tableau.length;
		for(var i = 0; i < length; i++) {
			if(tableau[i] == cible)
				return true;
		}
		return false;
	}

	function typeVille(ville) {
		var villeVal;
		if (ville == 0 || ville == 1) {
			// RE / RNE
			villeVal = 0;
		} else if (ville == 2) {
			// Pandé
			villeVal = -14;
		}

		return villeVal;
	}

	function distance(km) {
		if (km == 1)
			return -24;
		else if (km == 2)
			return -19;
		else if (km == 3)
			return -14;
		else if (km == 4)
			return -11;
		else if (km >= 5 && km <= 11)
			return -9;
		else if (km == 12)
			return -8;
		else if (km >= 13 && km <= 14)
			return -7;
		else if (km == 15)
			return -6;
		else if (km >= 16)
			return -5;
	}

	function zone(bat) {
		var suicide0 = [28];
		var suicide1 = [60];
		var suicide2 = [0,100,101,102];
		var suicide3 = [37];
		var rien = [5];
		var bien = [2,3,4,6,7,9,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,27,29,30,31,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,51,52,53,54,56,57,59,61];
		var correctes = [32,55,58,62];
		var bonnes = [8,50];
		var ideal = [10];

		if (inArray(bat, suicide0)) //suicide0
			return 2;
		else if (inArray(bat, suicide1)) //suicide1
			return 1;
		else if (inArray(bat, suicide2)) //suicide2
			return 0;
		else if (inArray(bat, suicide3)) //suicide3
			return -5;
		else if (inArray(bat, rien)) //rien
			return 5;
		else if (inArray(bat, bien)) //bien
			return 7;
		else if (inArray(bat, correctes)) //correctes
			return 9;
		else if (inArray(bat, bonnes)) //bonnes
			return 11;
		else if (inArray(bat, ideal)) //ideal2
			return 15;
		else if (bat == 1 || bat == 33) //cas particulie du batiment non deterre et de la cache du contrebandier
			return 8;
		else
			return 0;
	}

	function zombie(zomb, furtif) {
		var Z;
		if (furtif == 1) {
			Z = -0.6*zomb;
		} else if (furtif == 0) {
			Z = -1.4*zomb;
		}
		return Z;
	}

	/**
	 * Calcule le bonus donné par un nombre d'ODs
	 * @param  {int} OD Nombre d'OD installés
	 * @return {int}    Bonus total
	 */
	function calcBonusOD(OD) {
		var O = 1.8*OD;
		return O;
	}

	function camping(cing, pro, ville) {
		var C = 0;

		if (ville == 0 || ville == 1) {
			//RE ou RNE
			if (pro == 0) {
				// Non campeur pro
				if (cing == 1)
					C = -4; //S8
				else if (cing == 2)
					C = -9; //S8
				else if (cing == 3)
					C = -13; //S8
				else if (cing == 4)
					C = -16; //S8
				else if (cing == 5)
					C = -26; //S8
				else if (cing == 6)
					C = -36; //S8
			} else if (pro == 1) {
				// Campeur pro
				if (cing == 1)
					C = -2; //S8
				else if (cing == 2)
					C = -4; //S8
				else if (cing == 3)
					C = -8;
				else if (cing == 4)
					C = -10;
				else if (cing == 5)
					C = -12;
				else if (cing == 6)
					C = -16;
				else if (cing == 7)
					C = -26;
				else if (cing == 8)
					C = -37;
			}
		}
		else if (ville == 2) {
			// Pande
			if (pro == 0) {
				// Non campeur pro
				if (cing == 1)
					C = -4;
				else if (cing == 2)
					C = -6;
				else if (cing == 3)
					C = -8;
				else if (cing == 4)
					C = -10;
			} else if (pro == 1) {
				// Campeur pro
				if (cing == 1)
					C = -1; //S8
				else if (cing == 2)
					C = -2; //S8
				else if (cing == 3)
					C = -4; //S8
				else if (cing == 4)
					C = -6; //S8
				else if (cing == 5)
					C = -8; //S8
				else if (cing == 6)
					C = -10; //S8
				else if (cing == 7)
					C = -20; //S8
			}
		}

		return C;
	}

	function campeur(ceur) {
		var C;
		if (ceur == 0)
			C = 0;
		else if (ceur == 1)
			C = 0;
		else if (ceur == 2)
			C = -2;
		else if (ceur == 3)
			C = -5;
		else if (ceur == 4)
			C = -10;
		else if (ceur == 5)
			C = -14;
		else if (ceur == 6)
			C = -14;
		else if (ceur == 7)
			C = -14;

		return C;
	}

	/**
	 * Calcule le bonus donné par un nombre de peaux posées
	 * @param  {int} OD Nombre de peaux posées
	 * @return {int}    Bonus total
	 */
	function calcBonusPeau(peau) {
		var P = peau * 1;
		return P;
	}

	/**
	 * Calcule le bonus donné par un nombre de tentes posées
	 * @param  {int} OD Nombre de tentes posées
	 * @return {int}    Bonus total
	 */
	function calcBonusTente(tente) {
		var T = tente * 1;
		return T;
	}

	/**
	 * Renvoi le bonus de la tombe si creusée  
	 * @param  {bool}	tomb	Tombe creusée
	 * @return {int}			Bonus total
	 */
	function calcBonusTomb(tomb) {
		var T = tomb * 1.9;
		return T;
	}

	/**
	 * Retourne le bonus de la nuit (19h - 7h)
	 * @param  {bool}   nuit    Nuit tombée
	 * @return {int}			Bonus total
	 */
	function calcBonusNuit(nuit) {
		var N = nuit * 2;
		return N;
	}

	/**
	 * Retourne le bonus donné par le phare
	 * @param  {bool} phar Phare construit
	 * @return {int}      Bonus total
	 */
	function calcBonusPhare(phar) {
		var P = phar * 5;
		return P;
	}

	/**
	 * Retourne le malus donnée par la ville dévastée
	 * @param  {bool} devast Ville dévastée
	 * @return {int}        Malus donné
	 */
	function calcMalusDevast(devast) {
		var D = devast * -10;
		return D;
	}

	function loadChances() {
		// RE = 0 / RNE = 1 / PANDE = 2
		var cityType;
		if($('div#clock div.day span.hard').text() == '[' + I18N.get(MODULE_NAME + "_city_hard") + ']') cityType = 2;
		else cityType = 0;

		// Mite = 1 / Eclai = 2 / Autre = 0
		var job;

		// Bonus de héros
		if($('div.infoBar ul.inv li:first-child img[src*="small_more2.gif"]').length == 0) {
			if($('div.infoBar ul.inv li:first-child img').attr('onmouseover').indexOf(I18N.get(MODULE_NAME + "_hero_camera")) > 0) {
				// APAG
				if($('div.infoBar ul.inv li:eq(1) img[onmouseover*="item_surv_book.gif"]').length > 0)
					job = 1;
				else if($('div.infoBar ul.inv li:eq(1) img[onmouseover*="item_vest_on.gif"]').length > 0) // Eclaireur camouflé
					job = 2;
				else
					job = 0;
			} else {
				// No APAG
				if($('div.infoBar ul.inv li:eq(0) img[onmouseover*="item_surv_book.gif"]').length > 0)
					job = 1;
				else if($('div.infoBar ul.inv li:eq(0) img[onmouseover*="item_vest_on.gif"]').length > 0) // Eclaireur camouflé
					job = 2;
				else
					job = 0;
			}
		} else {
			if($('div.infoBar ul.inv li:eq(1) img').attr('onmouseover').indexOf(I18N.get(MODULE_NAME + "_hero_camera")) > 0) { 
				// APAG
				if($('div.infoBar ul.inv li:eq(2) img[onmouseover*="item_surv_book.gif"]').length > 0)
					job = 1;
				else if($('div.infoBar ul.inv li:eq(2) img[onmouseover*="item_vest_on.gif"]').length > 0) // Eclaireur camouflé
					job = 2;
				else
					job = 0;
			} else {
				// No APAG
				if($('div.infoBar ul.inv li:eq(1) img[onmouseover*="item_surv_book.gif"]').length > 0)
					job = 1;
				else if($('div.infoBar ul.inv li:eq(1) img[onmouseover*="item_vest_on.gif"]').length > 0) // Eclaireur camouflé
					job = 2;
				else
					job = 0;
			}
		}

		// Distance de la ville
		var kmregex = /([0-9]+) ?km/i;
		var km = +$('div#scoutStatus div.scoutStatus p').text().match(kmregex)[1];
		var bat;
		if($('div.outSpot h2').text() !== null && $('div.outSpot h2').text() != '') 
			bat = batList[$('div.outSpot h2').text()];
		else{
			if($('div.outSpot').text().indexOf('Toute la zone est envahie') >= 0) bat = 1;
			else bat = 0;
		}

		var zomb = +$('div#zombiePts').text().split(" ")[1];
		var upgrade = +permUpgrade;
		var odUpgrade = +permOdUpgrade;
		var alreadyCamped = +permAlreadyCamped;
		// small_camp.gif ? (pour l'internationalisation)
		// var alreadyHidden = $('div.who table.table tbody tr td img[src*="small_camp.gif"]').length;
		var alreadyHidden = $('div.who table.table tbody tr td img[onmouseover*="pour passer la nuit ici"]').length;
		var pelures = $('div.infoBar ul.inv li img[src*="item_smelly_meat.gif"]').length;
		var toiles = $('div.infoBar ul.inv li img[src*="item_sheet.gif"]').length;

		// tomb = 1 / not(tomb) = 0
		var tomb = +permTomb;
		
		// night = 1 / not(night) = 0
		var night = +permNight;
		
		// furtif = 1 / not(furtif) = 0
		var furtif = +(job == 2);
		
		// CP = 1 / not(CP) = 0
		var CP = +permCP;
		
		// phare = 1 / not(phare) = 0
		var phare = +permPhare;
		
		// devastated = 1 / not(devastated) = 0
		var devastated = +permDevastated;

		var villeVal = typeVille(cityType);
		var kmVal = distance(km);
		var zoneVal = zone(bat);
		var zombVal = zombie(zomb, furtif);
		var ameVal = upgrade;
		var ODVal = calcBonusOD(odUpgrade);
		var cingVal = camping(alreadyCamped, CP, cityType);
		var ceurVal = campeur(alreadyHidden);
		var peauVal = calcBonusPeau(pelures);
		var tenteVal = calcBonusTente(toiles);
		var tombVal = calcBonusTomb(tomb);
		var nuitVal = calcBonusNuit(night);
		var pharVal = calcBonusPhare(phare);
		var devastVal = calcMalusDevast(devastated);

		var result = villeVal + kmVal + zoneVal + zombVal + ameVal + ODVal + cingVal + ceurVal + peauVal + tenteVal + tombVal + nuitVal + pharVal + devastVal;

		result = round100(result);
		var opti = round10(-result);
		correct = round10(opti - 2);

		var statut = "Error";

		if (result >= 0)
		{
			statut = I18N.get(MODULE_NAME + '_status_very_high');
		}
		else if (result < 0 && result > -2)
		{
			statut = I18N.get(MODULE_NAME + '_status_high');
		}
		else if (result <= -2 && result > -4)
		{
			statut = I18N.get(MODULE_NAME + '_status_decent');
		}
		else if (result <= -4 && result > -7)
		{
			statut = I18N.get(MODULE_NAME + '_status_medium');
		}
		else if (result <= -7 && result > -10)
		{
			statut = I18N.get(MODULE_NAME + '_status_decent');
		}
		else if (result <= -10 && result > -14)
		{
			statut = I18N.get(MODULE_NAME + '_status_limited');
		}
		else if (result <= -14 && result > -18)
		{
			statut = I18N.get(MODULE_NAME + '_status_very_low');
		}
		else if (result <= -18)
		{
			statut = I18N.get(MODULE_NAME + '_status_null');
		}

		var statut2 = statut;
		if (result >= -2)
		{
			statut2 = I18N.get(MODULE_NAME + "_status_highest");
		}

		if(job == 1) finalStatut = statut;
		else finalStatut = statut2;

		if(job == 1) correct = opti;

		return 'http://www.camping-predict.nadazone.fr/index.php?ville='+cityType+'&metier='+job+'&km='+km+'&bat='+bat+'&z='+zomb+'&ame='+upgrade+'&od='+odUpgrade+'&cg='+alreadyCamped+'&cr='+alreadyHidden+'&pdp='+pelures+'&tdt='+toiles+'&tomb='+tomb+'&nuit='+night+'&furtif='+furtif+'&pro='+CP+'&phar='+phare+'&devast='+devastated;

	}

	var refresh = function() {
		if($('div#sideMap').next().attr('class') == 'block' && $('div.factions div.zombies').text() != '--' && $('#cpedit').length == 0){

			var cpUrl = loadChances();
			var toInject = '' +
			'<div style="float: right;font-size: 8pt;"><a style="cursor:pointer;text-decoration:underline;" id="cpreload" onmouseover="js.HordeTip.showSpecialTip(this, \'simpleTip\', \'\', \'' + I18N.get(MODULE_NAME + "_settings_desc") + '\', event);" onmouseout="js.HordeTip.hide(event)">' + I18N.get(MODULE_NAME + "_settings_text") + '</a>' +
			'&nbsp;<a id="cpedit" style="cursor:pointer;text-decoration:underline;" onmouseover="js.HordeTip.showSpecialTip(this, \'simpleTip\', \'\', \'' + I18N.get(MODULE_NAME + "_search_desc") + '\', event);" onmouseout="js.HordeTip.hide(event)">' + I18N.get(MODULE_NAME + "_search_text") + '</a></div>' +
			'<h2><a href="' + cpUrl + '" target="_blank">Camping Predict</a></h2>' +
			'<div id="cpiparam" style="background-color: #5c2b20;color: #f0d79e;font-weight:bold;font-variant: small-caps;font-family: \'Century Gothic\', \'Arial\', \'Trebuchet MS\', Verdana, sans-serif;margin-top:-13px;margin-bottom:-13px;display:none"><ul style="list-style-type:none;margin-left:-35px;">' +
			'<li style="height:1px;"> </li>' +
			'<li id="permUpgrade" style="margin-top:5px"><input class="field tid_defParsed" style="width:15px;" value=""> ' + I18N.get(MODULE_NAME + "_improvements") + '</li>' +
			'<li id="permOdUpgrade"><input class="field tid_defParsed" style="width:15px;" value=""> ' +  I18N.get(MODULE_NAME + "_defense_objects") + '</li>' +
			'<li id="permAlreadyCamped"><input class="field tid_defParsed" style="width:15px;" value=""> ' + I18N.get(MODULE_NAME + "_nights_camped") + '</li>' +
			'<li id="permTomb" style="cursor:pointer;"><input type="checkbox"> <span class="others"><img src="http://data.hordes.fr/gfx/icons/small_gather.gif"> ' + I18N.get(MODULE_NAME + "_tomb") + '</span></li>' +
			'<li id="permNight" style="cursor:pointer;"><input type="checkbox"> <span class="others"><img src="http://data.hordes.fr/gfx/icons/r_doutsd.gif"> ' + I18N.get(MODULE_NAME + "_night") + '</span></li>' +
			'<li id="permCP" style="cursor:pointer;"><input type="checkbox"> <span class="others"><img src="http://data.hordes.fr/gfx/icons/small_camp.gif"> ' + I18N.get(MODULE_NAME + "_pro_camper") + '</span></li>' +
			'<li id="permPhare" style="cursor:pointer;"><input type="checkbox"> <span class="others"><img src="http://data.hordes.fr/gfx/icons/small_lighthouse.gif"> ' + I18N.get(MODULE_NAME + "_lighthouse") + '</span></li>' +
			'<li id="permDevastated" style="cursor:pointer;"><input type="checkbox"> <span class="others"><img src="http://data.hordes.fr/gfx/icons/item_out_def_broken.gif"> ' + I18N.get(MODULE_NAME + "_devastated") + '</span></li>' +
			'</ul></div>' +
			'<div class="list" id="editor" style="background-color: #5c2b20;color: #f0d79e;font-weight:bold;font-variant: small-caps;font-family: \'Century Gothic\', \'Arial\', \'Trebuchet MS\', Verdana, sans-serif;display:none;"><ul style="list-style-type:none;margin-left:-35px;">' +
			'<li id="storedCityType" style="cursor:pointer;" ><span style="text-decoration:line-through;color:grey;" data-value="0">RE</span> <span style="text-decoration:line-through;color:grey;" data-value="1">RNE</span> <span style="text-decoration:line-through;color:grey;" data-value="2">' + I18N.get(MODULE_NAME + "_city_hard") + '</span> </li>' +
			'<li id="storedJob" style="cursor:pointer;"><img style="opacity:0.4;margin-left:10px;" data-value="0" src="http://data.hordes.fr/gfx/icons/item_basic_suit.gif"><img style="opacity:0.4;margin-left:10px;" data-value="1" src="http://data.hordes.fr/gfx/icons/item_surv_book.gif"><img style="opacity:0.4;margin-left:10px;" data-value="2" src="http://data.hordes.fr/gfx/icons/item_vest_on.gif"></li>' +
			'<li id="storedKm"><input class="field tid_defParsed" style="width:15px;" value=""> km </li>' +
			'<li id="storedBat"><select></select></li>' +
			'<li id="storedZomb"><input class="field tid_defParsed" style="width:15px;" value=""> <img src="http://data.hordes.fr/gfx/icons/small_zombie.gif"></li>' +
			'<li id="storedUpgrade"><input class="field tid_defParsed" style="width:15px;" value=""> ' + I18N.get(MODULE_NAME + "_improvements") + '</li>' +
			'<li id="storedOdUpgrade"><input class="field tid_defParsed" style="width:15px;" value=""> ' +  I18N.get(MODULE_NAME + "_defense_objects") + '</li>' +
			'<li id="storedAlreadyCamped"><input class="field tid_defParsed" style="width:15px;" value=""> ' + I18N.get(MODULE_NAME + "_nights_camped") + '</li>' +
			'<li id="storedAlreadyHidden"><input class="field tid_defParsed" style="width:15px;" value=""> ' + I18N.get(MODULE_NAME + "_hiding_citizens") + '</li>' +
			'<li id="storedPelures"><input class="field tid_defParsed" style="width:15px;" value=""> <img src="http://data.hordes.fr/gfx/icons/item_smelly_meat.gif"></li>' +
			'<li id="storedToiles"><input class="field tid_defParsed" style="width:15px;" value=""> <img src="http://data.hordes.fr/gfx/icons/item_sheet.gif"></li>' +
			'<li id="storedTomb" style="cursor:pointer;"><input type="checkbox"> <span class="others"><img src="http://data.hordes.fr/gfx/icons/small_gather.gif"> ' + I18N.get(MODULE_NAME + "_tomb") + '</span></li>' +
			'<li id="storedNight" style="cursor:pointer;"><input type="checkbox"> <span class="others"><img src="http://data.hordes.fr/gfx/icons/r_doutsd.gif"> ' + I18N.get(MODULE_NAME + "_night") + '</span></li>' +
			'<li id="storedFurtif" style="display:none;cursor:pointer;"><input type="checkbox"> <span class="others"><img src="http://data.hordes.fr/gfx/icons/r_jrangr.gif"> Camouflé</span></li>' +
			'<li id="storedCP" style="cursor:pointer;"><input type="checkbox"> <span class="others"><img src="http://data.hordes.fr/gfx/icons/small_camp.gif"> ' + I18N.get(MODULE_NAME + "_pro_camper") + '</span></li>' +
			'<li id="storedPhare" style="cursor:pointer;"><input type="checkbox"> <span class="others"><img src="http://data.hordes.fr/gfx/icons/small_lighthouse.gif"> ' + I18N.get(MODULE_NAME + "_lighthouse") + '</span></li>' +
			'<li id="storedDevastated" style="cursor:pointer;"><input type="checkbox"> <span class="others"><img src="http://data.hordes.fr/gfx/icons/item_out_def_broken.gif"> ' + I18N.get(MODULE_NAME + "_devastated") + '</span></li>' +
			'</ul></div>' +
			'<ul style="background-image: url(http://data.hordes.fr/gfx/design/inv_ground.gif);color:#DAC69A" class="tools outInv">' +
			'<li class="clear"></li>' +
			'<li class="empty" style="font-weight:bold;width:100%;text-align:left;margin-top:4px;color:inherit;">' + I18N.get(MODULE_NAME + "_status_title") + ' : <span id="finalStatut">'+finalStatut+'</span> </li>' +
			'<li class="clear"></li>' +
			'<li class="empty" style="width:100%;text-align:left;color:inherit;">' + I18N.get(MODULE_NAME + "_improvements_required") + ' : <span id="correct">'+correct+'</span> </li>' +
			'<li class="clear">' +
			'</li><li class="searchResult" style="text-align:right;display:none;">Résultat de la recherche</li></ul>';

			$(toInject).insertAfter('ul.tools.shortTools.nada.outInv');

			$('#cpreload').on('click',function(){
				if(!$('#cpiparam').is(":visible")){

					if($('#editor').is(":visible")){
						$('#editor').slideUp('slow');
						$('#cpedit').text(I18N.get(MODULE_NAME + "_search_text"));
					}

					updateParam();
					$('.searchResult').parent().next().css('margin-top','10px');

					$('#permUpgrade input').unbind('keyup focusout');
					$('#permUpgrade input').keyup(function(e){
						if(e.keyCode == 13)
						{
							if(+$(this).val() >= 0){
								$('#permUpgrade .notvalid').remove();
								localStorage.setItem('permUpgrade', +$(this).val());
								permUpgrade = +$(this).val();
								updateParam();
							}
							else{
								if($('.notvalid', $(this).parent()).length == 0) $(this).parent().append('<img class="notvalid" src="http://zenoo0.fr/hordes/resources/icons/small_warning.gif">');
							}
						}
					});
					$('#permUpgrade input').focusout(function(e){
						if(+$(this).val() >= 0){
							$('#permUpgrade .notvalid').remove();
							localStorage.setItem('permUpgrade', +$(this).val());
							permUpgrade = +$(this).val();
							updateParam();
						}
						else{
							if($('.notvalid', $(this).parent()).length == 0) $(this).parent().append('<img class="notvalid" src="http://zenoo0.fr/hordes/resources/icons/small_warning.gif">');
						}
					});

					$('#permOdUpgrade input').unbind('keyup focusout');
					$('#permOdUpgrade input').keyup(function(e){
						if(e.keyCode == 13)
						{
							if(+$(this).val() >= 0){
								$('#permOdUpgrade .notvalid').remove();
								localStorage.setItem('permOdUpgrade', +$(this).val());
								permOdUpgrade = +$(this).val();
								updateParam();
							}
							else{
								if($('.notvalid', $(this).parent()).length == 0) $(this).parent().append('<img class="notvalid" src="http://zenoo0.fr/hordes/resources/icons/small_warning.gif">');
							}
						}
					});
					$('#permOdUpgrade input').focusout(function(e){
						if(+$(this).val() >= 0){
							$('#permOdUpgrade .notvalid').remove();
							localStorage.setItem('permOdUpgrade', +$(this).val());
							permOdUpgrade = +$(this).val();
							updateParam();
						}
						else{
							if($('.notvalid', $(this).parent()).length == 0) $(this).parent().append('<img class="notvalid" src="http://zenoo0.fr/hordes/resources/icons/small_warning.gif">');
						}
					});

					$('#permAlreadyCamped input').unbind('keyup focusout');
					$('#permAlreadyCamped input').keyup(function(e){
						if(e.keyCode == 13)
						{
							if(+$(this).val() >= 0 && +$(this).val() <= 7){
								$('#permAlreadyCamped .notvalid').remove();
								localStorage.setItem('permAlreadyCamped', +$(this).val());
								permAlreadyCamped = +$(this).val();
								updateParam();
							}
							else{
								if($('.notvalid', $(this).parent()).length == 0) $(this).parent().append('<img class="notvalid" src="http://zenoo0.fr/hordes/resources/icons/small_warning.gif">');
							}
						}
					});
					$('#permAlreadyCamped input').focusout(function(e){
						if(+$(this).val() >= 0 && +$(this).val() <= 7){
							$('#permAlreadyCamped .notvalid').remove();
							localStorage.setItem('permAlreadyCamped', +$(this).val());
							permAlreadyCamped = +$(this).val();
							updateParam();
						}
						else{
							if($('.notvalid', $(this).parent()).length == 0) $(this).parent().append('<img class="notvalid" src="http://zenoo0.fr/hordes/resources/icons/small_warning.gif">');
						}
					});

					$('#permTomb input').unbind('change');
					$('#permTomb input').change(function() {
						if(this.checked) {
							localStorage.setItem('permTomb', 1);
							permTomb = 1;
						}
						else{
							localStorage.setItem('permTomb', 0);
							permTomb = 0;
						}
						updateParam();
					});
					$('#permTomb .others').unbind('click');
					$('#permTomb .others').on('click',function(){$('#permTomb input').prop( "checked", !$('#permTomb input').prop("checked")).trigger('change') ;});

					$('#permNight input').unbind('change');
					$('#permNight input').change(function() {
						if(this.checked) {
							localStorage.setItem('permNight', 1);
							permNight = 1;
						}
						else{
							localStorage.setItem('permNight', 0);
							permNight = 0;
						}
						updateParam();
					});
					$('#permNight .others').unbind('click');
					$('#permNight .others').on('click',function(){$('#permNight input').prop( "checked", !$('#permNight input').prop("checked")).trigger('change') ;});

					$('#permCP input').unbind('change');
					$('#permCP input').change(function() {
						if(this.checked) {
							localStorage.setItem('permCP', 1);
							permCP = 1;
						}
						else{
							localStorage.setItem('permCP', 0);
							permCP = 0;
						}
						updateParam();
					});
					$('#permCP .others').unbind('click');
					$('#permCP .others').on('click',function(){$('#permCP input').prop( "checked", !$('#permCP input').prop("checked")).trigger('change') ;});

					$('#permPhare input').unbind('change');
					$('#permPhare input').change(function() {
						if(this.checked) {
							localStorage.setItem('permPhare', 1);
							permPhare = 1;
						}
						else{
							localStorage.setItem('permPhare', 0);
							permPhare = 0;
						}
						updateParam();
					});
					$('#permPhare .others').unbind('click');
					$('#permPhare .others').on('click',function(){$('#permPhare input').prop( "checked", !$('#permPhare input').prop("checked")).trigger('change') ;});

					$('#permDevastated input').unbind('change');
					$('#permDevastated input').change(function() {
						if(this.checked) {
							localStorage.setItem('permDevastated', 1);
							permDevastated = 1;
						}
						else{
							localStorage.setItem('permDevastated', 0);
							permDevastated = 0;
						}
						updateParam();
					});
					$('#permDevastated .others').unbind('click');
					$('#permDevastated .others').on('click',function(){$('#permDevastated input').prop( "checked", !$('#permDevastated input').prop("checked")).trigger('change') ;});

					$('#cpiparam').slideDown('slow');
				}
				else{
					$('#cpiparam').slideUp('slow');
				}
			});

			$('#cpedit').on('click',function(){
				if(!$('#editor').is(":visible")){

					if($('#cpiparam').is(":visible")) $('#cpiparam').slideUp('slow');

					$('#cpedit').text(I18N.get(MODULE_NAME + "_close"));
					$('.searchResult').parent().next().css('margin-top','10px');

					updateSearch();

					$('#storedCityType span').unbind('click');
					$('#storedCityType span').on('click',function(){
						localStorage.setItem('storedCityType', +$(this).attr('data-value'));
						storedCityType = +$(this).attr('data-value');
						updateSearch();
					});

					$('#storedJob img').unbind('click');
					$('#storedJob img').on('click',function(){
						localStorage.setItem('storedJob', +$(this).attr('data-value'));
						storedJob = +$(this).attr('data-value');
						updateSearch();

						if(+$(this).attr('data-value') != 2) $('#storedFurtif').hide();
					});

					$('#storedKm input').unbind('keyup focusout');
					$('#storedKm input').keyup(function(e){
						if(e.keyCode == 13)
						{
							if(+$(this).val() >= 0 && +$(this).val() <= 28){
								$('#storedKm .notvalid').remove();
								localStorage.setItem('storedKm', +$(this).val());
								storedKm = +$(this).val();
								updateSearch();
							}
							else{
								if($('.notvalid', $(this).parent()).length == 0) $(this).parent().append('<img class="notvalid" src="http://zenoo0.fr/hordes/resources/icons/small_warning.gif">');
							}
						}
					});
					$('#storedKm input').focusout(function(){
						if(+$(this).val() >= 1 && +$(this).val() <= 28){
							$('#storedKm .notvalid').remove();
							localStorage.setItem('storedKm', +$(this).val());
							storedKm = +$(this).val();
							updateSearch();
						}
						else{
							if($('.notvalid', $(this).parent()).length == 0) $(this).parent().append('<img class="notvalid" src="http://zenoo0.fr/hordes/resources/icons/small_warning.gif">');
						}
					});

					$('#storedBat select').unbind('change');
					$('#storedBat select').change(function() {
						localStorage.setItem('storedBat', +$(this).val());
						storedBat = +$(this).val();
						updateSearch();
					});

					$('#storedZomb input').unbind('keyup focusout');
					$('#storedZomb input').keyup(function(e){
						if(e.keyCode == 13)
						{
							if(+$(this).val() >= 0){
								$('#storedZomb .notvalid').remove();
								localStorage.setItem('storedZomb', +$(this).val());
								storedZomb = +$(this).val();
								updateSearch();
							}
							else{
								if($('.notvalid', $(this).parent()).length == 0) $(this).parent().append('<img class="notvalid" src="http://zenoo0.fr/hordes/resources/icons/small_warning.gif">');
							}
						}
					});
					$('#storedZomb input').focusout(function(e){
						if(+$(this).val() >= 0){
							$('#storedZomb .notvalid').remove();
							localStorage.setItem('storedZomb', +$(this).val());
							storedZomb = +$(this).val();
							updateSearch();
						}
						else{
							if($('.notvalid', $(this).parent()).length == 0) $(this).parent().append('<img class="notvalid" src="http://zenoo0.fr/hordes/resources/icons/small_warning.gif">');
						}
					});

					$('#storedUpgrade input').unbind('keyup focusout');
					$('#storedUpgrade input').keyup(function(e){
						if(e.keyCode == 13)
						{
							if(+$(this).val() >= 0){
								$('#storedUpgrade .notvalid').remove();
								localStorage.setItem('storedUpgrade', +$(this).val());
								storedUpgrade = +$(this).val();
								updateSearch();
							}
							else{
								if($('.notvalid', $(this).parent()).length == 0) $(this).parent().append('<img class="notvalid" src="http://zenoo0.fr/hordes/resources/icons/small_warning.gif">');
							}
						}
					});
					$('#storedUpgrade input').focusout(function(e){
						if(+$(this).val() >= 0){
							$('#storedUpgrade .notvalid').remove();
							localStorage.setItem('storedUpgrade', +$(this).val());
							storedUpgrade = +$(this).val();
							updateSearch();
						}
						else{
							if($('.notvalid', $(this).parent()).length == 0) $(this).parent().append('<img class="notvalid" src="http://zenoo0.fr/hordes/resources/icons/small_warning.gif">');
						}
					});

					$('#storedOdUpgrade input').unbind('keyup focusout');
					$('#storedOdUpgrade input').keyup(function(e){
						if(e.keyCode == 13)
						{
							if(+$(this).val() >= 0){
								$('#storedOdUpgrade .notvalid').remove();
								localStorage.setItem('storedOdUpgrade', +$(this).val());
								storedOdUpgrade = +$(this).val();
								updateSearch();
							}
							else{
								if($('.notvalid', $(this).parent()).length == 0) $(this).parent().append('<img class="notvalid" src="http://zenoo0.fr/hordes/resources/icons/small_warning.gif">');
							}
						}
					});
					$('#storedOdUpgrade input').focusout(function(e){
						if(+$(this).val() >= 0){
							$('#storedOdUpgrade .notvalid').remove();
							localStorage.setItem('storedOdUpgrade', +$(this).val());
							storedOdUpgrade = +$(this).val();
							updateSearch();
						}
						else{
							if($('.notvalid', $(this).parent()).length == 0) $(this).parent().append('<img class="notvalid" src="http://zenoo0.fr/hordes/resources/icons/small_warning.gif">');
						}
					});

					$('#storedAlreadyCamped input').unbind('keyup focusout');
					$('#storedAlreadyCamped input').keyup(function(e){
						if(e.keyCode == 13)
						{
							if(+$(this).val() >= 0 && +$(this).val() <= 7){
								$('#storedAlreadyCamped .notvalid').remove();
								localStorage.setItem('storedAlreadyCamped', +$(this).val());
								storedAlreadyCamped = +$(this).val();
								updateSearch();
							}
							else{
								if($('.notvalid', $(this).parent()).length == 0) $(this).parent().append('<img class="notvalid" src="http://zenoo0.fr/hordes/resources/icons/small_warning.gif">');
							}
						}
					});
					$('#storedAlreadyCamped input').focusout(function(e){
						if(+$(this).val() >= 0 && +$(this).val() <= 7){
							$('#storedAlreadyCamped .notvalid').remove();
							localStorage.setItem('storedAlreadyCamped', +$(this).val());
							storedAlreadyCamped = +$(this).val();
							updateSearch();
						}
						else{
							if($('.notvalid', $(this).parent()).length == 0) $(this).parent().append('<img class="notvalid" src="http://zenoo0.fr/hordes/resources/icons/small_warning.gif">');
						}
					});

					$('#storedAlreadyHidden input').unbind('keyup focusout');
					$('#storedAlreadyHidden input').keyup(function(e){
						if(e.keyCode == 13)
						{
							if(+$(this).val() >= 0 && +$(this).val() <= 7){
								$('#storedAlreadyHidden .notvalid').remove();
								localStorage.setItem('storedAlreadyHidden', +$(this).val());
								storedAlreadyHidden = +$(this).val();
								updateSearch();
							}
							else{
								if($('.notvalid', $(this).parent()).length == 0) $(this).parent().append('<img class="notvalid" src="http://zenoo0.fr/hordes/resources/icons/small_warning.gif">');
							}
						}
					});
					$('#storedAlreadyHidden input').focusout(function(e){
						if(+$(this).val() >= 0 && +$(this).val() <= 7){
							$('#storedAlreadyHidden .notvalid').remove();
							localStorage.setItem('storedAlreadyHidden', +$(this).val());
							storedAlreadyHidden = +$(this).val();
							updateSearch();
						}
						else{
							if($('.notvalid', $(this).parent()).length == 0) $(this).parent().append('<img class="notvalid" src="http://zenoo0.fr/hordes/resources/icons/small_warning.gif">');
						}
					});

					$('#storedPelures input').unbind('keyup focusout');
					$('#storedPelures input').keyup(function(e){
						if(e.keyCode == 13)
						{
							if(+$(this).val() >= 0){
								$('#storedPelures .notvalid').remove();
								localStorage.setItem('storedPelures', +$(this).val());
								storedPelures = +$(this).val();
								updateSearch();
							}
							else{
								if($('.notvalid', $(this).parent()).length == 0) $(this).parent().append('<img class="notvalid" src="http://zenoo0.fr/hordes/resources/icons/small_warning.gif">');
							}
						}
					});
					$('#storedPelures input').focusout(function(e){
						if(+$(this).val() >= 0){
							$('#storedPelures .notvalid').remove();
							localStorage.setItem('storedPelures', +$(this).val());
							storedPelures = +$(this).val();
							updateSearch();
						}
						else{
							if($('.notvalid', $(this).parent()).length == 0) $(this).parent().append('<img class="notvalid" src="http://zenoo0.fr/hordes/resources/icons/small_warning.gif">');
						}
					});

					$('#storedToiles input').unbind('keyup focusout');
					$('#storedToiles input').keyup(function(e){
						if(e.keyCode == 13)
						{
							if(+$(this).val() >= 0){
								$('#storedToiles .notvalid').remove();
								localStorage.setItem('storedToiles', +$(this).val());
								storedToiles = +$(this).val();
								updateSearch();
							}
							else{
								if($('.notvalid', $(this).parent()).length == 0) $(this).parent().append('<img class="notvalid" src="http://zenoo0.fr/hordes/resources/icons/small_warning.gif">');
							}
						}
					});
					$('#storedToiles input').focusout(function(e){
						if(+$(this).val() >= 0){
							$('#storedToiles .notvalid').remove();
							localStorage.setItem('storedToiles', +$(this).val());
							storedToiles = +$(this).val();
							updateSearch();
						}
						else{
							if($('.notvalid', $(this).parent()).length == 0) $(this).parent().append('<img class="notvalid" src="http://zenoo0.fr/hordes/resources/icons/small_warning.gif">');
						}
					});

					$('#storedTomb input').unbind('change');
					$('#storedTomb input').change(function() {
						if(this.checked) {
							localStorage.setItem('storedTomb', 1);
							storedTomb = 1;
						}
						else{
							localStorage.setItem('storedTomb', 0);
							storedTomb = 0;
						}
						updateSearch();
					});
					$('#storedTomb .others').unbind('click');
					$('#storedTomb .others').on('click',function(){$('#storedTomb input').prop( "checked", !$('#storedTomb input').prop("checked")).trigger('change') ;});

					$('#storedNight input').unbind('change');
					$('#storedNight input').change(function() {
						if(this.checked) {
							localStorage.setItem('storedNight', 1);
							storedNight = 1;
						}
						else{
							localStorage.setItem('storedNight', 0);
							storedNight = 0;
						}
						updateSearch();
					});
					$('#storedNight .others').unbind('click');
					$('#storedNight .others').on('click',function(){$('#storedNight input').prop( "checked", !$('#storedNight input').prop("checked")).trigger('change') ;});

					$('#storedFurtif input').unbind('change');
					$('#storedFurtif input').change(function() {
						if(this.checked) {
							localStorage.setItem('storedFurtif', 1);
							storedFurtif = 1;
						}
						else{
							localStorage.setItem('storedFurtif', 0);
							storedFurtif = 0;
						}
						updateSearch();
					});
					$('#storedFurtif .others').unbind('click');
					$('#storedFurtif .others').on('click',function(){$('#storedFurtif input').prop( "checked", !$('#storedFurtif input').prop("checked")).trigger('change') ;});

					$('#storedCP input').unbind('change');
					$('#storedCP input').change(function() {
						if(this.checked) {
							localStorage.setItem('storedCP', 1);
							storedCP = 1;
						}
						else{
							localStorage.setItem('storedCP', 0);
							storedCP = 0;
						}
						updateSearch();
					});
					$('#storedCP .others').unbind('click');
					$('#storedCP .others').on('click',function(){$('#storedCP input').prop( "checked", !$('#storedCP input').prop("checked")).trigger('change') ;});

					$('#storedPhare input').unbind('change');
					$('#storedPhare input').change(function() {
						if(this.checked) {
							localStorage.setItem('storedPhare', 1);
							storedPhare = 1;
						}
						else{
							localStorage.setItem('storedPhare', 0);
							storedPhare = 0;
						}
						updateSearch();
					});
					$('#storedPhare .others').unbind('click');
					$('#storedPhare .others').on('click',function(){$('#storedPhare input').prop( "checked", !$('#storedPhare input').prop("checked")).trigger('change') ;});

					$('#storedDevastated input').unbind('change');
					$('#storedDevastated input').change(function() {
						if(this.checked) {
							localStorage.setItem('storedDevastated', 1);
							storedDevastated = 1;
						}
						else{
							localStorage.setItem('storedDevastated', 0);
							storedDevastated = 0;
						}
						updateSearch();
					});
					$('#storedDevastated .others').unbind('click');
					$('#storedDevastated .others').on('click',function(){$('#storedDevastated input').prop( "checked", !$('#storedDevastated input').prop("checked")).trigger('change') ;});

					$('#editor').slideDown('slow');
				}
				else{
					$('#editor').slideUp('slow');
					$('.searchResult').hide();
					$('#finalStatut').text(finalStatut);
					$('#correct').text(correct);
					$('#cpedit').text(I18N.get(MODULE_NAME + "_search_text"));
				}

			});

		}

	};

	/************************
	 * Module configuration *
	 ************************/

	return {

		name: MODULE_NAME,
		type: Module.TYPE.INTERFACE_ENHANCEMENT,

		properties: {
			enabled: false,
			tool: {
				update_method: 'GET',
				update_url: 'https://data.zenoo.fr/cpi.user.js'
			},
            isProtected: false
		},

		configurable: {
			enabled: {
				category: Module.PROPERTY_CATEGORY.OUTSIDE,
				type: Module.PROPERTY.BOOLEAN,
				short_desc_I18N: MODULE_NAME + '_short_desc',
				full_desc_I18N: MODULE_NAME + '_full_desc',
				url: "http://zenoo.fr/"
			}
		},

		actions: {
			can_run: function() {
				return D2N.is_on_hordes() || D2N.is_on_die2nite();
			},

			init: function() {
				add_i18n();
			},

			load: function() {
				document.addEventListener('d2n_gamebody_reload', function() {
					if(!D2N.is_outside()){
						return;
					}

					refresh();
				}, false);
			}
		}
	};
});

// In Safari, it is not possible to exclude domain from the configuration file.
// So the extension will be loaded on external tools domain (because I need to
// ask the user permission to perform Cross-Domain HTTP requests).
// Therefore, if I'm in Safari, but not on a game website, abort.
if (typeof safari !== 'undefined' && !D2N.is_on_game_website()) {
    return;
}

D2NE.init();

})();