text: // Screeps Visual: https://github.com/screepers/screeps-visual
  let lzstring = function(){function o(o,r){if(!t[o]){t[o]={};for(var n=0;n<o.length;n++)t[o][o.charAt(n)]=n}return t[o][r]}var r=String.fromCharCode,n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$",t={},i={compressToBase64:function(o){if(null==o)return"";var r=i._compress(o,6,function(o){return n.charAt(o)});switch(r.length%4){default:case 0:return r;case 1:return r+"===";case 2:return r+"==";case 3:return r+"="}},decompressFromBase64:function(r){return null==r?"":""==r?null:i._decompress(r.length,32,function(e){return o(n,r.charAt(e))})},compressToUTF16:function(o){return null==o?"":i._compress(o,15,function(o){return r(o+32)})+" "},decompressFromUTF16:function(o){return null==o?"":""==o?null:i._decompress(o.length,16384,function(r){return o.charCodeAt(r)-32})},compressToUint8Array:function(o){for(var r=i.compress(o),n=new Uint8Array(2*r.length),e=0,t=r.length;t>e;e++){var s=r.charCodeAt(e);n[2*e]=s>>>8,n[2*e+1]=s%256}return n},decompressFromUint8Array:function(o){if(null===o||void 0===o)return i.decompress(o);for(var n=new Array(o.length/2),e=0,t=n.length;t>e;e++)n[e]=256*o[2*e]+o[2*e+1];var s=[];return n.forEach(function(o){s.push(r(o))}),i.decompress(s.join(""))},compressToEncodedURIComponent:function(o){return null==o?"":i._compress(o,6,function(o){return e.charAt(o)})},decompressFromEncodedURIComponent:function(r){return null==r?"":""==r?null:(r=r.replace(/ /g,"+"),i._decompress(r.length,32,function(n){return o(e,r.charAt(n))}))},compress:function(o){return i._compress(o,16,function(o){return r(o)})},_compress:function(o,r,n){if(null==o)return"";var e,t,i,s={},p={},u="",c="",a="",l=2,f=3,h=2,d=[],m=0,v=0;for(i=0;i<o.length;i+=1)if(u=o.charAt(i),Object.prototype.hasOwnProperty.call(s,u)||(s[u]=f++,p[u]=!0),c=a+u,Object.prototype.hasOwnProperty.call(s,c))a=c;else{if(Object.prototype.hasOwnProperty.call(p,a)){if(a.charCodeAt(0)<256){for(e=0;h>e;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++),s[c]=f++,a=String(u)}if(""!==a){if(Object.prototype.hasOwnProperty.call(p,a)){if(a.charCodeAt(0)<256){for(e=0;h>e;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++)}for(t=2,e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;for(;;){if(m<<=1,v==r-1){d.push(n(m));break}v++}return d.join("")},decompress:function(o){return null==o?"":""==o?null:i._decompress(o.length,32768,function(r){return o.charCodeAt(r)})},_decompress:function(o,n,e){var t,i,s,p,u,c,a,l,f=[],h=4,d=4,m=3,v="",w=[],A={val:e(0),position:n,index:1};for(i=0;3>i;i+=1)f[i]=i;for(p=0,c=Math.pow(2,2),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(t=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 2:return""}for(f[3]=l,s=l,w.push(l);;){if(A.index>o)return"";for(p=0,c=Math.pow(2,m),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(l=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 2:return w.join("")}if(0==h&&(h=Math.pow(2,m),m++),f[l])v=f[l];else{if(l!==d)return null;v=s+s.charAt(0)}w.push(v),f[d++]=s+v.charAt(0),h--,s=v,0==h&&(h=Math.pow(2,m),m++)}}};return i}();
  
  global.__VISUAL_MEMORY_PATH = global.__VISUAL_MEMORY_PATH || '__visual'
  global.__VISUAL_DEFAULT_COMPRESSION = global.__VISUAL_DEFAULT_COMPRESSION || 'none'
  
  global.RawVisual = {
    compression: __VISUAL_DEFAULT_COMPRESSION,
    dirty: false,
    stats: {
      save:{
        cpu:0,
        time:0
      },
      load:{
        cpu:0,
        time:0
      }
    },
    get raw(){
      return _.get(Memory,__VISUAL_MEMORY_PATH,'{}')
    },
    set raw(value){
      _.set(Memory,__VISUAL_MEMORY_PATH,value)    
    },
    get data(){
      if(!this._data) this.load()
      return new Proxy(this._data,{ set: (t,k,v)=>(t[k]=v,this.dirty=true) })
    },
    commit(){
      if(this.dirty)
        this.save()
    },
    load(){
      let startCPU = Game.cpu.getUsed()
      let startTime = Date.now()
      let data = this.raw
      try{
        if(data.slice(0,3) == 'lz:')
          data = lzstring.decompress(data.slice(3)) || '{}'
        if(data.slice(0,5) == 'lz64:')
          data = lzstring.decompressFromBase64(data.slice(5)) || '{}'
        this._data = JSON.parse(data) || {}
      }catch(e){
        this._data = {}
      }
      let endCPU = Game.cpu.getUsed()
      let endTime = Date.now()
      this.stats.load.cpu = endCPU - startCPU
      this.stats.load.time = endTime - startTime
      return this.data
    },
    save(){
      let startCPU = Game.cpu.getUsed()
      let startTime = Date.now()
      let data = JSON.stringify(this.data)
      if(this.compression == 'lz')
        data = 'lz:'+lzstring.compress(data)
      if(this.compression == 'lz64')
        data = 'lz64:'+lzstring.compressToBase64(data)
      this.raw = data
      this.dirty = false
      let endCPU = Game.cpu.getUsed()
      let endTime = Date.now()
      this.stats.save.cpu = endCPU - startCPU
      this.stats.save.time = endTime - startTime
    }
  }
  
  class Visual {
    constructor(room,scale=1){
      this.room = room  
      this.queue = []
      this.setCanvasScale(scale)
    }
    commit(){
      RawVisual.data[this.room] = this.queue
    }
    setCanvasScale(scale=1){
      this.queue.push(['setCanvasScale',50*scale])
    }
  }
  
  let functions = [
    'clearRect','fillRect','strokeRect','fillText','strokeText','setLineDash',
    'createLinearGradient','createRadialGradient','createPattern',
    'beginPath','closePath','moveTo','lineTo','bezierCurveTo','quadraticCurveTo','arc','arcTo','ellipse','rect',
    'fill','stroke','clip',
    'rotate','scale','translate','transform','setTransform','resetTransform',
    'save','restore',
    'defineColors',
    'drawMatrix',
    'drawLine',
    'drawCell',
    'writeText',
  ]
  
  let props = [
    'lineWidth','lineCap','lineJoin','miterLimit','lineDashOffset',
    'font','textAlign','textBaseline','direction',
    'fillStyle','strokeStyle',
    'shadowBlur','shadowColor','shadowOffsetX','shadowOffsetY',
    'globalAlpha','globalCompositeOperation'
  ]
  
  functions.forEach(f=>{
    Visual.prototype[f] = function(...a){ 
      this.queue.push([f,...a])
    }  
  })
  
  props.forEach(p=>{
    Object.defineProperty(Visual.prototype,p,{
      get: function(){
        return this[`_${p}`]
      },
      set: function(v){
        this[`_${p}`] = v
        this.queue.push([p,v])
      }
    })
  })  
  
  
  module.exports = Visual
  