ReinMakesGames2

Operation: Somebody probably already did this.

Oct 13th, 2022
101
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 148.51 KB | Gaming | 0 0
  1. /*=====================================================================================
  2. HELPER FUNCTIONS
  3. =======================================================================================*/
  4.  
  5. function triggerAnim(element,anim)
  6. {
  7.     if (!element) return;
  8.     element.classList.remove(anim);
  9.     void element.offsetWidth;
  10.     element.classList.add(anim);
  11. }
  12.  
  13. function isNumber(n)
  14. {return (!isNaN(parseFloat(n)) && isFinite(n));}
  15.  
  16. function parseNum(n)
  17. {
  18.     if (n===true) return 1;
  19.     else if (n===false) return 0;
  20.     else if (isNaN(n)) return 0;
  21.     else return n;
  22. }
  23.  
  24. var luck=function(x)
  25. {
  26.     if (Math.random()<x/100) return true; else return false;
  27. }
  28. var rand=function(x,y)
  29. {
  30.     if (!y) y=0;
  31.     if (y<x) {var tmp=x;x=y;y=tmp;}
  32.     y++;
  33.     return Math.floor(Math.random()*(y-x)+x);
  34. }
  35. var frand=function(x,y)
  36. {
  37.     if (!y) y=0;
  38.     if (y<x) {var tmp=x;x=y;y=tmp;}
  39.     return (Math.random()*(y-x)+x);
  40. }
  41.  
  42. //String2 is a pseudo-string type with some added functions that are as ambiguous in purpose as they are buggy
  43. var String2=function(str)
  44. {
  45.     this.val=str;
  46. }
  47. String2.prototype.toString=function(){return this.val;}
  48.  
  49. /*
  50.     String2.gulp(str) : if the string begins with str, remove str from the string and return what we gulped; else return false
  51.     If str is unspecified, gulp a whole word until the next space and return it
  52.     If a quote " is encountered, keep gulping until the next quote
  53.     Example :
  54.         var str=STR2('set value3 to 100');
  55.         if (str.gulp('set'))
  56.         {
  57.             var val=str.gulpUntil('to');
  58.             var amount=str.gulpUntil();
  59.             console.log(val,amount);
  60.         }
  61. */
  62. String2.prototype.gulp=function(str,isSymbol)
  63. {
  64.     if (str)
  65.     {
  66.         if (!isSymbol) str=str+=' ';
  67.         if (this.val.indexOf(str)==0)
  68.         {
  69.             this.val=this.val.substring(str.length);
  70.             if (isSymbol && this.val.charAt(0)==' ') this.val=this.val.substring(1);//remove first space
  71.             return str;
  72.         }
  73.         else return false;
  74.     }
  75.     else
  76.     {
  77.         if ((this.val.split('"').length-1)>=2 && (this.val.indexOf(' ')>this.val.indexOf('"')))
  78.         {
  79.             var str=this.val.substring(0,this.val.indexOf('"',this.val.indexOf('"')+1)+1);
  80.             this.val=this.val.substring(str.length+1);
  81.             return str;
  82.         }
  83.         else if (this.val.indexOf(' ')>0)
  84.         {
  85.             var str=this.val.substring(0,this.val.indexOf(' '));
  86.             this.val=this.val.substring(str.length+1);
  87.             return str;
  88.         }
  89.         else
  90.         {
  91.             var str=this.val;this.val='';return str;
  92.         }
  93.     }
  94. }
  95. String2.prototype.gulpSymbol=function(str){return this.gulp(str,true);}//like gulp, but ignores spaces
  96. String2.prototype.gulpUntil=function(str,isSymbol,toEnd)
  97. {
  98.     //gulp all words until str (str is not included in the result, and removed from the String2)
  99.     //if str is unspecified (or toEnd is true and str wasn't found), just return the rest of the string
  100.     var bumpStr=str;
  101.     var paddedBumpStr=bumpStr;
  102.     if (!isSymbol) paddedBumpStr=' '+paddedBumpStr;
  103.     if (!str || (toEnd && this.val.indexOf(paddedBumpStr)==-1))
  104.     {
  105.         var str=this.val;
  106.         this.val='';
  107.         return str;
  108.     }
  109.     else if (this.val.indexOf(paddedBumpStr)>=0)
  110.     {
  111.         var str=this.val.substring(0,this.val.indexOf(paddedBumpStr));
  112.         if (isSymbol) this.val=this.val.substring(str.length+bumpStr.length);
  113.         else this.val=this.val.substring(str.length+1+bumpStr.length);
  114.         if (this.val.charAt(0)==' ') this.val=this.val.substring(1);//remove first space
  115.         if (this.val.charAt(this.val.length-1)==' ') this.val=this.val.slice(0,-1);//remove last space
  116.         return str;
  117.     }
  118.     else return false;
  119. }
  120. String2.prototype.gulpUntilSymbol=function(str,toEnd){return this.gulpUntil(str,true,toEnd);}//like gulpUntil, but ignores spaces
  121. var STR2=function(str)//shortcut
  122. {return new String2(str);}
  123.  
  124.  
  125. //the old Beautify function from Cookie Clicker, shortened to B(value)
  126. //initially adapted from http://cookieclicker.wikia.com/wiki/Frozen_Cookies_%28JavaScript_Add-on%29
  127. function formatEveryThirdPower(notations)
  128. {
  129.     return function (value)
  130.     {
  131.         var base = 0,
  132.         notationValue = '';
  133.         if (!isFinite(value)) return 'Inf.';
  134.         if (value >= 1000)
  135.         {
  136.             value /= 1000;
  137.             while(Math.round(value) >= 1000)
  138.             {
  139.                 value /= 1000;
  140.                 base++;
  141.             }
  142.             if (base >= notations.length) {return 'Inf.';} else {notationValue = notations[base];}
  143.         }
  144.         return (value<1000000000000?( Math.round(value * 10) / 10 ):Math.floor(value)) + notationValue;
  145.     };
  146. }
  147.  
  148. function rawFormatter(value) {return Math.round(value * 1000) / 1000;}
  149.  
  150. var formatLong=[' thousand',' million',' billion',' trillion',' quadrillion',' quintillion',' sextillion',' septillion',' octillion',' nonillion'];
  151. var prefixes=['','un','duo','tre','quattuor','quin','sex','septen','octo','novem'];
  152. var suffixes=['decillion','vigintillion','trigintillion','quadragintillion','quinquagintillion','sexagintillion','septuagintillion','octogintillion','nonagintillion'];
  153. for (var i in suffixes)
  154. {
  155.     for (var ii in prefixes)
  156.     {
  157.         formatLong.push(' '+prefixes[ii]+suffixes[i]);
  158.     }
  159. }
  160.  
  161. var formatShort=['k','M','B','T','Qa','Qi','Sx','Sp','Oc','No'];
  162. var prefixes=['','Un','Do','Tr','Qa','Qi','Sx','Sp','Oc','No'];
  163. var suffixes=['D','V','T','Qa','Qi','Sx','Sp','O','N'];
  164. for (var i in suffixes)
  165. {
  166.     for (var ii in prefixes)
  167.     {
  168.         formatShort.push(' '+prefixes[ii]+suffixes[i]);
  169.     }
  170. }
  171. formatShort[10]='Dc';
  172.  
  173.  
  174. var numberFormatters =
  175. [
  176.     formatEveryThirdPower(formatShort),
  177.     formatEveryThirdPower(formatLong),
  178.     rawFormatter
  179. ];
  180. var numberFormatter=numberFormatters[2];
  181. function Beautify(value,floats)
  182. {
  183.     var negative=(value<0);
  184.     var decimal='';
  185.     var fixed=value.toFixed(floats);
  186.     if (Math.abs(value)<1000 && floats>0 && Math.floor(fixed)!=fixed) decimal='.'+(fixed.toString()).split('.')[1];
  187.     value=Math.floor(Math.abs(value));
  188.     if (floats>0 && fixed==value+1) value++;
  189.     var formatter=numberFormatter;
  190.     var output=formatter(value).toString().replace(/\B(?=(\d{3})+(?!\d))/g,',');
  191.     if (output=='0') negative=false;
  192.     return negative?'-'+output:output+decimal;
  193. }
  194. var B=Beautify;
  195.  
  196.  
  197. function BeautifyTime(value)
  198. {
  199.     //value should be in seconds
  200.     value=Math.max(Math.ceil(value,0));
  201.     var years=Math.floor(value/31536000);
  202.     value-=years*31536000;
  203.     var days=Math.floor(value/86400);
  204.     value-=days*86400;
  205.     var hours=Math.floor(value/3600)%24;
  206.     value-=hours*3600;
  207.     var minutes=Math.floor(value/60)%60;
  208.     value-=minutes*60;
  209.     var seconds=Math.floor(value)%60;
  210.     var str='';
  211.     if (years) str+=B(years)+'Y';
  212.     if (days || str!='') str+=B(days)+'d';
  213.     if (hours || str!='') str+=hours+'h';
  214.     if (minutes || str!='') str+=minutes+'m';
  215.     if (seconds || str!='') str+=seconds+'s';
  216.     if (str=='') str+='0s';
  217.     return str;
  218. }
  219. var BT=BeautifyTime;
  220.  
  221.  
  222. var sayTime=function(time,detail)
  223. {
  224.     //time is a value where one second is equal to 1000.
  225.     //detail skips days when >1, hours when >2, minutes when >3 and seconds when >4.
  226.     //if detail is -1, output something like "3 hours, 9 minutes, 48 seconds"
  227.     if (time<=0) return '';
  228.     var str='';
  229.     var detail=detail||0;
  230.     time=Math.floor(time);
  231.     var second=1000;
  232.     if (detail==-1)
  233.     {
  234.         var days=0;
  235.         var hours=0;
  236.         var minutes=0;
  237.         var seconds=0;
  238.         if (time>=second*60*60*24) days=(Math.floor(time/(second*60*60*24)));
  239.         if (time>=second*60*60) hours=(Math.floor(time/(second*60*60)));
  240.         if (time>=second*60) minutes=(Math.floor(time/(second*60)));
  241.         if (time>=second) seconds=(Math.floor(time/(second)));
  242.         hours-=days*24;
  243.         minutes-=hours*60+days*24*60;
  244.         seconds-=minutes*60+hours*60*60+days*24*60*60;
  245.         if (days>10) {hours=0;}
  246.         if (days) {minutes=0;seconds=0;}
  247.         if (hours) {seconds=0;}
  248.         var bits=[];
  249.         if (days>0) bits.push(B(days)+' day'+(days==1?'':'s'));
  250.         if (hours>0) bits.push(B(hours)+' hour'+(hours==1?'':'s'));
  251.         if (minutes>0) bits.push(B(minutes)+' minute'+(minutes==1?'':'s'));
  252.         if (seconds>0) bits.push(B(seconds)+' second'+(seconds==1?'':'s'));
  253.         if (bits.length==0) str='less than 1 second';
  254.         else str=bits.join(', ');
  255.     }
  256.     else
  257.     {
  258.         if (time>=second*60*60*24*2 && detail<2) str=B(Math.floor(time/(second*60*60*24)))+' days';
  259.         else if (time>=second*60*60*24 && detail<2) str='1 day';
  260.         else if (time>=second*60*60*2 && detail<3) str=B(Math.floor(time/(second*60*60)))+' hours';
  261.         else if (time>=second*60*60 && detail<3) str='1 hour';
  262.         else if (time>=second*60*2 && detail<4) str=B(Math.floor(time/(second*60)))+' minutes';
  263.         else if (time>=second*60 && detail<4) str='1 minute';
  264.         else if (time>=second*2 && detail<5) str=B(Math.floor(time/(second)))+' seconds';
  265.         else if (time>=second && detail<5) str='1 second';
  266.         else str='less than 1 second';
  267.     }
  268.     return str;
  269. }
  270.  
  271. function cap(str)
  272. {return str.charAt(0).toUpperCase()+str.slice(1);}
  273.  
  274. //file save function from https://github.com/eligrey/FileSaver.js
  275. var saveAs=saveAs||function(view){"use strict";if(typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var doc=view.document,get_URL=function(){return view.URL||view.webkitURL||view},save_link=doc.createElementNS("http://www.w3.org/1999/xhtml","a"),can_use_save_link="download"in save_link,click=function(node){var event=new MouseEvent("click");node.dispatchEvent(event)},is_safari=/Version\/[\d\.]+.*Safari/.test(navigator.userAgent),webkit_req_fs=view.webkitRequestFileSystem,req_fs=view.requestFileSystem||webkit_req_fs||view.mozRequestFileSystem,throw_outside=function(ex){(view.setImmediate||view.setTimeout)(function(){throw ex},0)},force_saveable_type="application/octet-stream",fs_min_size=0,arbitrary_revoke_timeout=500,revoke=function(file){var revoker=function(){if(typeof file==="string"){get_URL().revokeObjectURL(file)}else{file.remove()}};if(view.chrome){revoker()}else{setTimeout(revoker,arbitrary_revoke_timeout)}},dispatch=function(filesaver,event_types,event){event_types=[].concat(event_types);var i=event_types.length;while(i--){var listener=filesaver["on"+event_types[i]];if(typeof listener==="function"){try{listener.call(filesaver,event||filesaver)}catch(ex){throw_outside(ex)}}}},auto_bom=function(blob){if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)){return new Blob(["\ufeff",blob],{type:blob.type})}return blob},FileSaver=function(blob,name,no_auto_bom){if(!no_auto_bom){blob=auto_bom(blob)}var filesaver=this,type=blob.type,blob_changed=false,object_url,target_view,dispatch_all=function(){dispatch(filesaver,"writestart progress write writeend".split(" "))},fs_error=function(){if(target_view&&is_safari&&typeof FileReader!=="undefined"){var reader=new FileReader;reader.onloadend=function(){var base64Data=reader.result;target_view.location.href="data:attachment/file"+base64Data.slice(base64Data.search(/[,;]/));filesaver.readyState=filesaver.DONE;dispatch_all()};reader.readAsDataURL(blob);filesaver.readyState=filesaver.INIT;return}if(blob_changed||!object_url){object_url=get_URL().createObjectURL(blob)}if(target_view){target_view.location.href=object_url}else{var new_tab=view.open(object_url,"_blank");if(new_tab==undefined&&is_safari){view.location.href=object_url}}filesaver.readyState=filesaver.DONE;dispatch_all();revoke(object_url)},abortable=function(func){return function(){if(filesaver.readyState!==filesaver.DONE){return func.apply(this,arguments)}}},create_if_not_found={create:true,exclusive:false},slice;filesaver.readyState=filesaver.INIT;if(!name){name="download"}if(can_use_save_link){object_url=get_URL().createObjectURL(blob);setTimeout(function(){save_link.href=object_url;save_link.download=name;click(save_link);dispatch_all();revoke(object_url);filesaver.readyState=filesaver.DONE});return}if(view.chrome&&type&&type!==force_saveable_type){slice=blob.slice||blob.webkitSlice;blob=slice.call(blob,0,blob.size,force_saveable_type);blob_changed=true}if(webkit_req_fs&&name!=="download"){name+=".download"}if(type===force_saveable_type||webkit_req_fs){target_view=view}if(!req_fs){fs_error();return}fs_min_size+=blob.size;req_fs(view.TEMPORARY,fs_min_size,abortable(function(fs){fs.root.getDirectory("saved",create_if_not_found,abortable(function(dir){var save=function(){dir.getFile(name,create_if_not_found,abortable(function(file){file.createWriter(abortable(function(writer){writer.onwriteend=function(event){target_view.location.href=file.toURL();filesaver.readyState=filesaver.DONE;dispatch(filesaver,"writeend",event);revoke(file)};writer.onerror=function(){var error=writer.error;if(error.code!==error.ABORT_ERR){fs_error()}};"writestart progress write abort".split(" ").forEach(function(event){writer["on"+event]=filesaver["on"+event]});writer.write(blob);filesaver.abort=function(){writer.abort();filesaver.readyState=filesaver.DONE};filesaver.readyState=filesaver.WRITING}),fs_error)}),fs_error)};dir.getFile(name,{create:false},abortable(function(file){file.remove();save()}),abortable(function(ex){if(ex.code===ex.NOT_FOUND_ERR){save()}else{fs_error()}}))}),fs_error)}),fs_error)},FS_proto=FileSaver.prototype,saveAs=function(blob,name,no_auto_bom){return new FileSaver(blob,name,no_auto_bom)};if(typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob){return function(blob,name,no_auto_bom){if(!no_auto_bom){blob=auto_bom(blob)}return navigator.msSaveOrOpenBlob(blob,name||"download")}}FS_proto.abort=function(){var filesaver=this;filesaver.readyState=filesaver.DONE;dispatch(filesaver,"abort")};FS_proto.readyState=FS_proto.INIT=0;FS_proto.WRITING=1;FS_proto.DONE=2;FS_proto.error=FS_proto.onwritestart=FS_proto.onprogress=FS_proto.onwrite=FS_proto.onabort=FS_proto.onerror=FS_proto.onwriteend=null;return saveAs}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined"&&module.exports){module.exports.saveAs=saveAs}else if(typeof define!=="undefined"&&define!==null&&define.amd!=null){define([],function(){return saveAs})}
  276.  
  277.  
  278.    
  279. /*=====================================================================================
  280. ENGINE
  281. =======================================================================================*/
  282.  
  283. var TOPARSE=TOPARSE||false;
  284.  
  285. G.Init=function()
  286. {
  287.    
  288.     G.stabilizeResize=function()
  289.     {
  290.     }
  291.    
  292.     /*=====================================================================================
  293.     SETTINGS
  294.     =======================================================================================*/
  295.     G.settings={
  296.         'vol':{val:100},
  297.         'autosave':{val:1},
  298.         'numdisp':{val:0,onchange:function(val){
  299.             val=parseInt(val);
  300.             if (val>=0 && val<=2) numberFormatter=numberFormatters[val];
  301.         }},
  302.         'cssfilts':{val:1,onchange:function(val){
  303.             if (val)
  304.             {
  305.                 G.l.classList.remove('filtersOff');
  306.                 G.l.classList.add('filtersOn');
  307.             }
  308.             else
  309.             {
  310.                 G.l.classList.remove('filtersOn');
  311.                 G.l.classList.add('filtersOff');
  312.             }
  313.         }},
  314.         'particles':{val:2},
  315.         'showFPS':{val:G.local?1:0,onchange:function(val){
  316.             if (val)
  317.             {
  318.                 G.fpsGraph.style.display='block';
  319.                 G.fpsCounter.style.display='block';
  320.             }
  321.             else
  322.             {
  323.                 G.fpsGraph.style.display='none';
  324.                 G.fpsCounter.style.display='none';
  325.             }
  326.         }},
  327.     };
  328.     for (var i in G.settings){G.settings[i].key=i;}
  329.     G.setSetting=function(what,val)
  330.     {
  331.         G.settings[what].val=val;
  332.         if (G.settings[what].onchange) G.settings[what].onchange(G.settings[what].val);
  333.     }
  334.     G.getSetting=function(what)
  335.     {
  336.         return G.settings[what].val;
  337.     }
  338.     G.loadSettings=function()
  339.     {
  340.         for (var i in G.settings)
  341.         {
  342.             if (G.settings[i].onchange) G.settings[i].onchange(G.settings[i].val);
  343.         }
  344.     }
  345.    
  346.     G.makeChoices=function(o)
  347.     {
  348.         var str='';
  349.         var buttonIds=[];
  350.         for (var i in o.list)
  351.         {
  352.             buttonIds.push('button-'+(G.buttonsN+parseInt(i)));
  353.         }
  354.         for (var i in o.list)
  355.         {
  356.             var id=parseInt(i);
  357.             str+=G.button({
  358.                 text:o.list[i].text,
  359.                 classes:'tickbox '+(o.val()==id?'on':'off'),
  360.                 tooltip:o.list[i].tooltip,
  361.                 onclick:function(e){
  362.                     for (var i in buttonIds)
  363.                     {
  364.                         l(buttonIds[i]).classList.remove('on');
  365.                         l(buttonIds[i]).classList.add('off');
  366.                         if (e.target.id==buttonIds[i])
  367.                         {
  368.                             var id=parseInt(i);
  369.                             o.func(id);
  370.                         }
  371.                     }
  372.                     e.target.classList.remove('off');
  373.                     e.target.classList.add('on');
  374.                     triggerAnim(e.target,'glow');
  375.                 },
  376.             });
  377.         }
  378.         return str;
  379.     }
  380.     G.makeTick=function(o)
  381.     {
  382.         if (!o.off) o.off=o.on;
  383.         return G.button({
  384.             text:(o.val()?o.on:o.off),
  385.             classes:'tickbox '+(o.val()?'on':'off'),
  386.             tooltip:o.tooltip,
  387.             onclick:function(e){
  388.                 if (o.val()) o.func(0); else o.func(1);
  389.                 if (o.val())
  390.                 {
  391.                     e.target.classList.remove('off');
  392.                     e.target.classList.add('on');
  393.                     e.target.innerHTML=o.on;
  394.                 }
  395.                 else
  396.                 {
  397.                     e.target.classList.remove('on');
  398.                     e.target.classList.add('off');
  399.                     e.target.innerHTML=o.off;
  400.                 }
  401.                 triggerAnim(e.target,'glow');
  402.             },
  403.         });
  404.     }
  405.    
  406.    
  407.     /*=====================================================================================
  408.     SAVE & LOAD
  409.     =======================================================================================*/
  410.    
  411.     G.saveTo=0;//we save the game to the key "IGM-"+game's url
  412.     G.save=function(returnOnly)
  413.     {
  414.         if (!G.saveTo) return false;
  415.         G.doEffectsForAll('save');
  416.         G.doEffectsForAll('undo grants');
  417.         var str='';
  418.         str+='BEGIN|';
  419.         var list=[];
  420.             list.push('1');//engine version
  421.             list.push(G.startDate);
  422.             list.push(parseInt(Date.now()));//time last played
  423.         str+=list.join(';');
  424.         str+='|SET|';
  425.         var list=[];
  426.         for (var i in G.settings)
  427.         {
  428.             var me=G.settings[i];
  429.             list.push(me.key+','+parseInt(me.val));
  430.         }
  431.         str+=list.join(';');
  432.         str+='|RES|';
  433.         var list=[];
  434.         for (var i in G.res)
  435.         {
  436.             var me=G.res[i];
  437.             list.push(me.key+','+((me.show<<1)|(me.lit))+','+me.amount+','+me.maxAmount+','+me.earned);
  438.         }
  439.         str+=list.join(';');
  440.         str+='|BTN|';
  441.         var list=[];
  442.         for (var i in G.buttons)
  443.         {
  444.             var me=G.buttons[i];
  445.             list.push(me.key+','+((me.show<<1)|(me.lit))+','+me.clicks);
  446.         }
  447.         str+=list.join(';');
  448.         str+='|BLD|';
  449.         var list=[];
  450.         for (var i in G.buildings)
  451.         {
  452.             var me=G.buildings[i];
  453.             list.push(me.key+','+((me.show<<1)|(me.lit))+','+me.amount+','+me.maxAmount);
  454.         }
  455.         str+=list.join(';');
  456.         str+='|UPG|';
  457.         var list=[];
  458.         for (var i in G.upgrades)
  459.         {
  460.             var me=G.upgrades[i];
  461.             list.push(me.key+','+((me.show<<1)|(me.lit))+','+me.owned);
  462.         }
  463.         str+=list.join(';');
  464.         str+='|ACH|';
  465.         var list=[];
  466.         for (var i in G.achievs)
  467.         {
  468.             var me=G.achievs[i];
  469.             list.push(me.key+','+((me.show<<1)|(me.lit))+','+me.owned);
  470.         }
  471.         str+=list.join(';');
  472.         str+='|ITM|';
  473.         //group up items by base key
  474.         var itemsByBase=[];
  475.         for (var i in G.items)
  476.         {
  477.             if (!itemsByBase[G.items[i].base.key]) itemsByBase[G.items[i].base.key]=[];
  478.             itemsByBase[G.items[i].base.key].push(G.items[i]);
  479.         }
  480.         var list=[];
  481.         for (var i in itemsByBase)
  482.         {
  483.             var list2=[];
  484.             for (var ii in itemsByBase[i])
  485.             {
  486.                 var me=itemsByBase[i][ii];
  487.                 list2.push(((me.show<<1)|(me.lit)));
  488.             }
  489.             list.push(i+'/'+list2.join('/'));
  490.         }
  491.         str+=list.join(';');
  492.         str+='|SHN|';
  493.         var list=[];
  494.         for (var i in G.shinies)
  495.         {
  496.             var me=G.shinies[i];
  497.             list.push(me.key+','+me.clicks);
  498.         }
  499.         str+=list.join(';');
  500.         str+='|END';
  501.         G.doEffectsForAll('do grants');
  502.         str=window.btoa(str);
  503.         if (returnOnly) return str;
  504.         localStorage[G.saveTo]=str;
  505.         if (localStorage[G.saveTo]!=str) return false;
  506.         G.toast({text:'Game saved',classes:'center',dur:3});
  507.         return true;
  508.     }
  509.     G.saveData=0;
  510.     G.applyLoad=function(data)
  511.     {
  512.         if (!G.saveData) return false;
  513.         var str=data||G.saveData;
  514.         try{str=window.atob(str);}catch(err){return false;}
  515.         G.saveData=0;
  516.         str=str.split('|');
  517.         if (str[0]!='BEGIN' || str[str.length-1]!='END') return false;
  518.         var blocks=[];
  519.         var blockNames=['BEGIN','SET','RES','BTN','BLD','UPG','ACH','ITM'];
  520.         for (var i in str)
  521.         {
  522.             if (i>0)
  523.             {
  524.                 if (blockNames.indexOf(str[i])==-1 && blockNames.indexOf(str[i-1])!=-1) blocks[str[i-1]]=str[i].split(';');
  525.             }
  526.         }
  527.         for (var block in blocks)
  528.         {
  529.             if (block=='ITM')
  530.             {
  531.                 for (var subblock in blocks[block])
  532.                 {
  533.                     var things=blocks[block][subblock].split('/');
  534.                     var key=things[0];
  535.                     if (G.thingsByName[key])
  536.                     {
  537.                         var base=G.thingsByName[key];
  538.                         things.shift();
  539.                         for (var thing in things)
  540.                         {
  541.                             var bits=things[thing].split(',');
  542.                             var i=0;
  543.                             var me={};
  544.                             var vis=parseInt(bits[i++]);me.show=vis>>1;me.lit=vis&1;
  545.                             G.gainItem(base,me);
  546.                         }
  547.                     }
  548.                 }
  549.             }
  550.             else if (block=='BEGIN')
  551.             {
  552.                 var i=0;
  553.                 var bits=blocks[block];
  554.                 G.saveUsedEngineVersion=parseInt(bits[i++]);
  555.                 G.startDate=parseInt(bits[i++]);
  556.                 G.lastDate=parseInt(bits[i++]);
  557.             }
  558.             else
  559.             {
  560.                 for (var thing in blocks[block])
  561.                 {
  562.                     var bits=blocks[block][thing].split(',');
  563.                     var i=0;
  564.                     var key=bits[i++];
  565.                     if (block=='SET' && G.settings[key])
  566.                     {
  567.                         var me=G.settings[key];
  568.                         G.settings[key].val=parseInt(bits[i++]);
  569.                     }
  570.                     else if (G.thingsByName[key])
  571.                     {
  572.                         var me=G.thingsByName[key];
  573.                         if (block=='RES')
  574.                         {
  575.                             var vis=parseInt(bits[i++]);me.show=vis>>1;me.lit=vis&1;
  576.                             me.amount=parseFloat(bits[i++]);
  577.                             me.maxAmount=parseFloat(bits[i++]);
  578.                             me.earned=parseFloat(bits[i++]);
  579.                         }
  580.                         else if (block=='BTN')
  581.                         {
  582.                             var vis=parseInt(bits[i++]);me.show=vis>>1;me.lit=vis&1;
  583.                             me.clicks=parseInt(bits[i++]);
  584.                         }
  585.                         else if (block=='BLD')
  586.                         {
  587.                             var vis=parseInt(bits[i++]);me.show=vis>>1;me.lit=vis&1;
  588.                             me.amount=parseFloat(bits[i++]);
  589.                             me.maxAmount=parseFloat(bits[i++]);
  590.                         }
  591.                         else if (block=='UPG')
  592.                         {
  593.                             var vis=parseInt(bits[i++]);me.show=vis>>1;me.lit=vis&1;
  594.                             me.owned=parseInt(bits[i++]);
  595.                         }
  596.                         else if (block=='ACH')
  597.                         {
  598.                             var vis=parseInt(bits[i++]);me.show=vis>>1;me.lit=vis&1;
  599.                             me.owned=parseInt(bits[i++]);
  600.                         }
  601.                         else if (block=='SHN')
  602.                         {
  603.                             me.clicks=parseInt(bits[i++]);
  604.                         }
  605.                     }
  606.                 }
  607.             }
  608.         }
  609.         G.toast({text:'Game loaded',classes:'center',dur:3});
  610.         return true;
  611.     }
  612.     G.load=function(data)
  613.     {
  614.         //reset the game and get the save data
  615.         //(the save data is parsed inside G.parse using G.applyLoad)
  616.         //the data parameter lets us load arbitrary save data; if none is specified, use localStorage instead
  617.         G.turnOff();
  618.         G.saveData=0;
  619.         if (!data && G.saveTo) var data=localStorage[G.saveTo];
  620.         if (!data) data=0;
  621.         G.saveData=data;
  622.         try {
  623.             G.Reset(-1);
  624.             var done=G.parse(G.data);
  625.         }
  626.         catch(e)
  627.         {
  628.             G.context='(javascript)';
  629.             console.log(e);
  630.             G.parseError(e.message);
  631.             return false;
  632.         }
  633.         if (done && !G.parsedOnce)
  634.         {
  635.             //TODO : remove stylesheets first
  636.             //load stylesheets
  637.             G.stylesheetUrls=G.stylesheets.slice(0);
  638.             G.stylesheets=[];
  639.             G.stylesheetsToLoad=0;
  640.             var onCompletion=function()
  641.             {
  642.                 G.stylesheetsToLoad--;
  643.                 if (G.stylesheetsToLoad<=0)
  644.                 {
  645.                     G.turnOn();
  646.                 }
  647.             }
  648.             var onBadCompletion=function()
  649.             {
  650.                 G.stylesheetsToLoad--;
  651.                 if (G.stylesheetsToLoad<=0)
  652.                 {
  653.                     //TODO : display a warning message; perhaps a confirm prompt
  654.                     setTimeout(G.turnOn,1000);
  655.                 }
  656.             }
  657.             if (G.stylesheetUrls.length==0) G.stylesheetUrls.push('stuff/basic.css');
  658.             for (var i in G.stylesheetUrls)
  659.             {
  660.                 var me={url:G.stylesheetUrls[i],loaded:false};
  661.                 G.stylesheets.push(me);
  662.                 G.stylesheetsToLoad++;
  663.             }
  664.             for (var i in G.stylesheets)
  665.             {
  666.                 var me=G.stylesheets[i];
  667.                 if (me.url.endsWith('.css'))
  668.                 {
  669.                     var link=document.createElement('link');
  670.                     link.type='text/css';
  671.                     link.rel='stylesheet';
  672.                     link.href=me.url;
  673.                     link.onload=function(me){return function(){me.loaded=true;onCompletion();}}(me);
  674.                     link.onerror=function(me){return function(){console.log('WARNING : Failed to load the stylesheet at '+me.url+'.');onBadCompletion();}}(me);
  675.                     document.head.appendChild(link);
  676.                 }
  677.                 else if (!G.local)
  678.                 {
  679.                     ajax('server.php?q=fetch|'+me.url,function(me){return function(out){
  680.                         if (out=='[NONE]')
  681.                         {
  682.                             console.log('WARNING : Failed to load the stylesheet at '+me.url+'.');
  683.                             onBadCompletion();
  684.                             return false;
  685.                         }
  686.                         var css=document.createElement('style');
  687.                         css.type='text/css';
  688.                         css.rel='stylesheet';
  689.                         css.innerHTML=out;
  690.                         document.head.appendChild(css);
  691.                         me.loaded=true;
  692.                         onCompletion();
  693.                     }}(me));
  694.                 }
  695.                 else
  696.                 {
  697.                     console.log('WARNING : Failed to load the stylesheet at '+me.url+'.');
  698.                     onBadCompletion();
  699.                 }
  700.             }
  701.            
  702.             if (G.css!='')
  703.             {
  704.                 var css=document.createElement('style');
  705.                 css.type='text/css';
  706.                 css.innerHTML=G.css;
  707.                 document.getElementsByTagName('head')[0].appendChild(css);
  708.             }
  709.            
  710.             if (G.bloomFilter>0)
  711.             {
  712.                 //disabled (problems in Firefox, slowdowns on complex games, all-around just kinda gaudy)
  713.                 /*var bloom=document.createElementNS('http://www.w3.org/2000/svg','svg');
  714.                 bloom.innerHTML=`
  715.                 <svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  716.                     <defs>
  717.                         <filter id="bloom" x="0" y="0" width="100%" height="100%">
  718.                             <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur" />
  719.                             <feColorMatrix in="blur" type="matrix" values="
  720.                                 1 0 0 0 0
  721.                                 0 1 0 0 0
  722.                                 0 0 1 0 0
  723.                                 0 0 0 `+G.bloomFilter+` 0
  724.                             " result="blur2" />
  725.                             <feComposite result="mix" operator="atop" in2="SourceGraphic" in="blur2" />
  726.                             <feBlend in2="blur2" in="mix" result="out" mode="overlay" />
  727.                         </filter>
  728.                     </defs>
  729.                 </svg>
  730.                 `;
  731.                 document.head.appendChild(bloom);*/
  732.             }
  733.             G.parsedOnce=true;
  734.         }
  735.         else if (done)
  736.         {
  737.             setTimeout(G.turnOn,10);
  738.         }
  739.         G.loadSettings();
  740.     }
  741.     G.clear=function(data)
  742.     {
  743.         //completely get rid of the save data
  744.         if (G.saveTo) localStorage.removeItem(G.saveTo);
  745.         G.load();
  746.         G.loadSettings();
  747.     }
  748.    
  749.     G.getSaveData=function(){return G.save(true);}
  750.     G.loadFromData=function(data){return G.load(data);}
  751.    
  752.    
  753.     G.fileSave=function()
  754.     {
  755.         var filename='IGM-'+(G.name.replace(/[^a-zA-Z0-9]+/g,'')||'game');
  756.         var text=G.getSaveData();
  757.         var blob=new Blob([text],{type:'text/plain;charset=utf-8'});
  758.         saveAs(blob,filename+'.txt');
  759.         G.toast({text:'File saved to '+filename+'.txt',classes:'center',dur:3});
  760.     }
  761.     G.fileLoad=function(e)
  762.     {
  763.         if (e.target.files.length==0) return false;
  764.         var file=e.target.files[0];
  765.         var reader=new FileReader();
  766.         reader.onload=function(e)
  767.         {
  768.             G.loadFromData(e.target.result);
  769.         }
  770.         reader.readAsText(file);
  771.     }
  772.    
  773.    
  774.     /*=====================================================================================
  775.     GET GAME SOURCE
  776.     =======================================================================================*/
  777.    
  778.     G.parsedOnce=false;//we've parsed the data at least once; this helps prevent declaring stylesheets more than once etc
  779.     G.playing=false;
  780.    
  781.     G.dataLoaded=function(data)
  782.     {
  783.         if (data=='[NONE]') {G.noData();return false;}
  784.         if (G.local) G.data=TOPARSE;
  785.         else if (data) G.data=data;
  786.         else {G.noData();return false;}
  787.         console.log('Got '+B(G.data.length)+' characters of data.');
  788.         parent.postMessage({code:G.data},'*');
  789.         G.load();
  790.     }
  791.    
  792.     G.noData=function()
  793.     {
  794.         var str=G.url||'';
  795.             str=str.replaceAll('&','&amp;');
  796.             str=str.replaceAll('<','&lt;');
  797.             str=str.replaceAll('>','&gt;');
  798.             str=str.replaceAll('"','&quot;');
  799.             str=str.replaceAll('\'','&apos;');
  800.        
  801.         G.l.innerHTML=`
  802.             <div id="errorWrap">
  803.                 <div id="noGameData">
  804.                     <b>Woops!</b><br>
  805.                     The specified game data<br>
  806.                     <small>(`+str+`)</small><br>
  807.                     could not be found.<br>
  808.                     <a href="./index.html" target="_top">Go back to homepage</a>
  809.                 </div>
  810.             </div>
  811.         `;
  812.         G.l.classList.add('on');
  813.     }
  814.    
  815.     if (G.urlVars && G.urlVars.g)
  816.     {
  817.         //load data file
  818.         G.url=decodeURIComponent(G.urlVars.g);
  819.        
  820.         if (G.url.indexOf('/')==0) G.url='./games'+G.url+'.txt';
  821.         else if (!G.local && G.url.indexOf('www.')!=0 && G.url.indexOf('http://')!=0 && G.url.indexOf('https://')!=0) G.url='http://pastebin.com/raw.php?i='+G.url;
  822.        
  823.         console.log('Fetching game at '+G.url+'...');
  824.         G.saveTo=G.url;
  825.         if (TOPARSE) setTimeout(G.dataLoaded,100);
  826.         else if (G.local) LoadScript(G.url,G.dataLoaded,function(){G.noData();});
  827.         else ajax('server.php?q=fetch|'+G.url,G.dataLoaded);
  828.     }
  829.     else G.noData();
  830.    
  831.    
  832.     /*=====================================================================================
  833.     RESET
  834.     =======================================================================================*/
  835.     G.Reset=function()
  836.     {
  837.         //G.Reset is called on save load.
  838.         //Most of the engine's code (perhaps more than necessary) is in here as we want to start as fresh as possible on each data load.
  839.        
  840.         G.T=0;
  841.         G.domReady=false;
  842.         G.playing=false;
  843.        
  844.         G.l.innerHTML=`
  845.             <div id="content"></div>
  846.             <div id="meta-buttons">
  847.                 <div class="meta-button" id="meta-button-settings"></div>
  848.                 <div class="meta-button" id="meta-button-info"></div>
  849.             </div>
  850.             <div id="shinies" class="shadowed"></div>
  851.             <div id="particles" class="shadowed"></div>
  852.             <div id="toasts" class="shadowed"></div>
  853.             <div id="popups"><div id="darken" class="off"></div></div>
  854.             <div id="tooltip">
  855.                 <div id="tooltipPU" class="tooltipPoint"></div>
  856.                 <div id="tooltipPD" class="tooltipPoint"></div>
  857.                 <div id="tooltipPL" class="tooltipPoint"></div>
  858.                 <div id="tooltipPR" class="tooltipPoint"></div>
  859.                 <div id="tooltipContent"></div>
  860.             </div>
  861.         `;
  862.        
  863.        
  864.         /*=====================================================================================
  865.         LOG
  866.         =======================================================================================*/
  867.         G.initLog=function()
  868.         {
  869.             G.logL=l('log')||0;
  870.             G.logLin=0;
  871.             G.logs=[];
  872.             G.maxLogs=50;
  873.             if (G.logL)
  874.             {
  875.                 G.logLin=l('logInner');
  876.             }
  877.         }
  878.         G.log=function(text,classes)
  879.         {
  880.             if (!G.logL) return false;
  881.             var scrolled=!(Math.abs(G.logL.scrollTop-(G.logL.scrollHeight-G.logL.offsetHeight))<3);
  882.             var me={};
  883.             var str='<div class="messageContent"><div>'+text+'</div></div>';
  884.             me.text=str;
  885.             var div=document.createElement('div');
  886.             div.innerHTML=str;
  887.             div.className='message popInVertical'+(classes?(' '+classes):'');
  888.             me.l=div;
  889.             G.logLin.appendChild(div);
  890.             G.logs.push(me);
  891.             if (G.logs.length>G.maxLogs)
  892.             {
  893.                 var el=G.logLin.firstChild;
  894.                 for (var i in G.logs)
  895.                 {
  896.                     if (G.logs[i].l==el)
  897.                     {
  898.                         G.logs.splice(i,1);
  899.                         break;
  900.                     }
  901.                 }
  902.                 G.logLin.removeChild(el);
  903.             }
  904.             if (!scrolled) G.logL.scrollTop=G.logL.scrollHeight-G.logL.offsetHeight;
  905.             G.addCallbacks();
  906.         }
  907.         G.clearLog=function()
  908.         {
  909.             G.logs=[];
  910.             G.logLin.innerHTML='';
  911.         }
  912.         G.initLog();
  913.        
  914.        
  915.         /*=====================================================================================
  916.         PARTICLES
  917.         =======================================================================================*/
  918.         G.particlesL=l('particles');
  919.         G.particlesN=50;
  920.         G.particlesI=0;
  921.         G.particles=[];
  922.         G.particlesReset=function()
  923.         {
  924.             var str='';
  925.             for (var i=0;i<G.particlesN;i++)
  926.             {
  927.                 str+='<div id="particle-'+i+'" class="particle"><div id="particleText-'+i+'" class="particleText"></div></div>';
  928.             }
  929.             G.particlesL.innerHTML=str;
  930.            
  931.             for (var i=0;i<G.particlesN;i++)
  932.             {
  933.                 G.particles[i]={id:i,low:false,t:-1,x:0,y:0,xd:0,yd:0,l:l('particle-'+i),lt:l('particleText-'+i)};
  934.             }
  935.         }
  936.         G.particlesReset();
  937.        
  938.         G.particleAt=function(el,icon,text)
  939.         {
  940.             if (G.getSetting('particles')==0) return false;
  941.             var me=G.particles[G.particlesI];
  942.             /*var box=el.getBoundingClientRect();
  943.             var x=box.left;
  944.             var y=box.top;
  945.             var w=box.right-x;
  946.             var h=box.bottom-y;
  947.             me.x=x+w*0.2+Math.random()*w*0.6-24;
  948.             me.y=y+h*0.2+Math.random()*h*0.6-24-48;*/
  949.             me.x=G.mouseX-24+(Math.random()*20-10);
  950.             me.y=G.mouseY-24-48+(Math.random()*20-10);
  951.             me.xd=Math.random()*8-4;
  952.             me.yd=-Math.random()*8-4;
  953.             me.r=Math.random()*90-45;
  954.             me.t=0;
  955.             if (text) me.lt.innerHTML=text;
  956.             me.baseCSS='display:block;'+G.resolveIcon(icon,true);
  957.             if (G.getSetting('particles')<3 && (G.currentFps<20 || G.getSetting('particles')==1))
  958.             {
  959.                 //if low fps, trigger simple CSS animation instead
  960.                 me.low=true;
  961.                 me.l.style.cssText=me.baseCSS+'opacity:0;left:'+Math.floor(me.x)+'px;top:'+Math.floor(me.y)+'px;';
  962.                 triggerAnim(me.l,'particlePop');
  963.             }
  964.             else {me.low=false;me.l.classList.remove('particlePop');}
  965.             G.particlesI++;
  966.             if (G.particlesI>=G.particlesN) G.particlesI=0;
  967.         }
  968.         G.particlesLogic=function()
  969.         {
  970.             for (var i=0;i<G.particlesN;i++)
  971.             {
  972.                 var me=G.particles[i];
  973.                 if (!me.low && me.t>=0)
  974.                 {
  975.                     var r=Math.pow(me.t/20,0.15);
  976.                     var r2=Math.pow(me.t/20,5);
  977.                     me.l.style.cssText=me.baseCSS+'opacity:'+(1-r2)+';transform:translate('+me.x+'px,'+me.y+'px) rotate('+(me.r*(1-r))+'deg) scale('+(0.5+0.5*r)+','+(1.5-0.5*r)+');transform-origin:50% 100%;';
  978.                     me.x+=me.xd;
  979.                     me.y+=me.yd;
  980.                     me.xd*=0.95;
  981.                     me.yd+=1;
  982.                     me.yd=Math.min(me.yd,6);
  983.                     me.t++;
  984.                     if (me.t>20)
  985.                     {
  986.                         me.t=-1;
  987.                         me.l.style.cssText='display:none;';
  988.                         me.lt.innerHTML='';
  989.                     }
  990.                 }
  991.             }
  992.         }
  993.        
  994.         /*=====================================================================================
  995.         POPUPS
  996.         =======================================================================================*/
  997.         G.popupsL=l('popups');
  998.         G.popups=[];
  999.         G.popup=function(el,o)
  1000.         {
  1001.             //TODO : handle el
  1002.             var me={};
  1003.             for (var i in o){me[i]=o[i];}
  1004.             me.l=document.createElement('div');
  1005.             var classes='popup';
  1006.             if (me.classes) classes+=' '+me.classes;
  1007.             me.l.className=classes;
  1008.             me.l.innerHTML=(me.text||'');
  1009.             if (me.init) me.init(me);
  1010.             var buttonl=document.createElement('div');
  1011.             buttonl.innerHTML='x';
  1012.             buttonl.className='closeButton closesThePopup';
  1013.             me.l.insertBefore(buttonl,me.l.firstChild);
  1014.             var closers=me.l.getElementsByClassName('closesThePopup');
  1015.             for (var i in closers)
  1016.             {AddEvent(closers[i],'click',function(me){return function(){G.closePopup(me);}}(me));}
  1017.             G.popupsL.appendChild(me.l);
  1018.             G.popups.push(me);
  1019.             G.addCallbacks();
  1020.             return me;
  1021.         }
  1022.         G.closePopup=function(me)
  1023.         {
  1024.             if (!me) var me=G.popups[G.popups.length-1];
  1025.             if (me.onClose) me.onClose(me);
  1026.             G.popups.splice(G.popups.indexOf(me),1);
  1027.             me.l.parentNode.removeChild(me.l);
  1028.         }
  1029.         G.popupDraw=function()
  1030.         {
  1031.             var topb=0;
  1032.             var bottomb=G.h;
  1033.             var leftb=0;
  1034.             var rightb=G.w;
  1035.             for (var i in G.popups)
  1036.             {
  1037.                 var me=G.popups[i];
  1038.                 if (me.func) me.func(me);
  1039.                 me.l.style.left=Math.floor((rightb-leftb)/2-me.l.clientWidth/2)+'px';
  1040.                 me.l.style.top=Math.floor((bottomb-topb)/2-me.l.clientHeight/2)+'px';
  1041.                 me.l.style.opacity=1;
  1042.             }
  1043.         }
  1044.        
  1045.         /*=====================================================================================
  1046.         TOASTS
  1047.         =======================================================================================*/
  1048.         G.toastsL=l('toasts');
  1049.         G.toasts=[];
  1050.         G.toast=function(o)
  1051.         {
  1052.             var me={};
  1053.             for (var i in o){me[i]=o[i];}
  1054.             me.l=document.createElement('div');
  1055.             var classes='toast popInVertical';
  1056.             if (me.classes) classes+=' '+me.classes;
  1057.             me.l.className=classes;
  1058.             me.l.innerHTML=(me.text||'');
  1059.             me.t=0;
  1060.             me.toDie=0;
  1061.             if (me.init) me.init(me);
  1062.             var buttonl=document.createElement('div');
  1063.             buttonl.innerHTML='x';
  1064.             buttonl.className='closeButton closesThePopup';
  1065.             me.l.insertBefore(buttonl,me.l.firstChild);
  1066.             var closers=me.l.getElementsByClassName('closesThePopup');
  1067.             for (var i in closers)
  1068.             {AddEvent(closers[i],'click',function(me){return function(){G.closeToast(me);}}(me));}
  1069.             G.toastsL.appendChild(me.l);
  1070.             G.toasts.push(me);
  1071.             G.addCallbacks();
  1072.             return me;
  1073.         }
  1074.         G.closeToast=function(me)
  1075.         {
  1076.             if (!me) var me=G.toasts[G.toasts.length-1];
  1077.             if (me.toDie) return false;
  1078.             me.toDie=1;
  1079.             me.l.classList.remove('popInVertical');
  1080.             me.l.classList.add('popOutVertical');
  1081.             if (me.onClose) me.onClose(me);
  1082.         }
  1083.         G.killToast=function(me)
  1084.         {
  1085.             if (!me) var me=G.toasts[G.toasts.length-1];
  1086.             G.toasts.splice(G.toasts.indexOf(me),1);
  1087.             me.l.parentNode.removeChild(me.l);
  1088.         }
  1089.         G.toastLogic=function()
  1090.         {
  1091.             for (var i in G.toasts)
  1092.             {
  1093.                 var me=G.toasts[i];
  1094.                 if (me.toDie)
  1095.                 {
  1096.                     me.toDie++;
  1097.                     if (me.toDie>=G.fps*0.3) G.killToast(me);
  1098.                 }
  1099.                 else
  1100.                 {
  1101.                     me.t++;
  1102.                     if (me.dur>0 && me.t>=me.dur*G.fps) G.closeToast(me);
  1103.                 }
  1104.             }
  1105.         }
  1106.        
  1107.         /*=====================================================================================
  1108.         TOOLTIP
  1109.         =======================================================================================*/
  1110.         G.tooltipL=l('tooltip');
  1111.         G.tooltipContentL=l('tooltipContent');
  1112.         G.tooltipPU=l('tooltipPU');
  1113.         G.tooltipPD=l('tooltipPD');
  1114.         G.tooltipPL=l('tooltipPL');
  1115.         G.tooltipPR=l('tooltipPR');
  1116.         G.pseudoHover=new Event('pseudoHover');
  1117.         G.tooltipReset=function()
  1118.         {
  1119.             G.tooltip={
  1120.                 parent:0,origin:0,classes:'',text:'',on:false,settled:false,t:0,
  1121.             };
  1122.         }
  1123.         G.tooltipReset();
  1124.         G.addTooltip=function(el,o)
  1125.         {
  1126.             AddEvent(el,'mouseover',function(el,o){return function(){
  1127.                 var settled=(el==G.tooltip.parent);
  1128.                 G.showTooltip(el,o);
  1129.                 if (settled) G.tooltip.settled=true;
  1130.             }}(el,o));
  1131.             AddEvent(el,'pseudoHover',function(el,o){return function(){
  1132.                 G.showTooltip(el,o);
  1133.             }}(el,o));
  1134.             AddEvent(el,'mouseout',function(el,o){return function(){
  1135.                 G.hideTooltip(el);
  1136.             }}(el,o));
  1137.         }
  1138.         G.showTooltip=function(el,o)
  1139.         {
  1140.             G.tooltipReset();
  1141.             var me=G.tooltip;
  1142.             me.on=true;
  1143.             for (var i in o){me[i]=o[i];}
  1144.             me.parent=el;
  1145.             if (!me.origin) me.origin='top';
  1146.         }
  1147.         G.hideTooltip=function(el)
  1148.         {
  1149.             var prev=G.tooltip.parent;
  1150.             if (el==-1) G.tooltipReset();
  1151.             else if (!el || el==prev)
  1152.             {
  1153.                 G.tooltipReset();
  1154.                 if (!prev) G.tooltip.settled=true;
  1155.                 var underneath=document.elementFromPoint(G.mouseX,G.mouseY);
  1156.                 if (underneath && prev && underneath!=prev)
  1157.                 {
  1158.                     underneath.dispatchEvent(G.pseudoHover);
  1159.                     G.tooltip.settled=true;
  1160.                 }
  1161.             }
  1162.         }
  1163.         G.tooltipDraw=function()
  1164.         {
  1165.             var me=G.tooltip;
  1166.             if (me.on)
  1167.             {
  1168.                 if (!me.parent || !document.body.contains(me.parent)) {G.hideTooltip();}
  1169.                 else
  1170.                 {
  1171.                     if (!me.settled)
  1172.                     {
  1173.                         if (me.classes) G.tooltipL.className=me.classes;
  1174.                         if (me.text) G.tooltipContentL.innerHTML=me.text;
  1175.                         G.tooltipL.style.opacity='0';
  1176.                         G.tooltipL.style.display='block';
  1177.                         G.tooltipL.classList.remove('stretchIn');
  1178.                         G.tooltipL.classList.remove('stretchInV');
  1179.                     }
  1180.                     if (me.func && me.t%10==0) G.tooltipContentL.innerHTML=me.func();
  1181.                    
  1182.                     var div=me.parent;
  1183.                     var box=div.getBoundingClientRect();
  1184.                    
  1185.                     var topb=0;
  1186.                     var bottomb=G.h;
  1187.                     var leftb=0;
  1188.                     var rightb=G.w;
  1189.                     var margin=8;
  1190.                     var tx=G.tooltipL.offsetLeft;
  1191.                     var ty=G.tooltipL.offsetTop;
  1192.                     var tw=G.tooltipL.clientWidth;
  1193.                     var th=G.tooltipL.clientHeight;
  1194.                     var x=0;
  1195.                     var y=0;
  1196.                     var i=0;
  1197.                     var origin=me.origin;
  1198.                    
  1199.                     //try to fit within the screen
  1200.                     var spins=[];
  1201.                     if (origin=='top') spins=['top','bottom','right','left'];
  1202.                     else if (origin=='bottom') spins=['bottom','top','right','left'];
  1203.                     else if (origin=='left') spins=['left','right','top','bottom'];
  1204.                     else if (origin=='right') spins=['right','left','top','bottom'];
  1205.                    
  1206.                     for (var i=0;i<4;i++)
  1207.                     {
  1208.                         var spin=spins[i];
  1209.                         origin=spin;
  1210.                         if (spin=='top')
  1211.                         {
  1212.                             x=(box.left+box.right)/2;
  1213.                             y=box.top;
  1214.                             x=x-tw/2;
  1215.                             y=y-th-margin;
  1216.                             x=Math.max(0,Math.min(G.w-tw,x));
  1217.                         }
  1218.                         else if (spin=='bottom')
  1219.                         {
  1220.                             x=(box.left+box.right)/2;
  1221.                             y=box.bottom;
  1222.                             x=x-tw/2;
  1223.                             y=y+margin;
  1224.                             x=Math.max(0,Math.min(G.w-tw,x));
  1225.                         }
  1226.                         else if (spin=='left')
  1227.                         {
  1228.                             x=box.left;
  1229.                             y=(box.top+box.bottom)/2;
  1230.                             x=x-tw-margin;
  1231.                             y=y-th/2;
  1232.                             y=Math.max(0,Math.min(G.h-th,y));
  1233.                         }
  1234.                         else if (spin=='right')
  1235.                         {
  1236.                             x=box.right;
  1237.                             y=(box.top+box.bottom)/2;
  1238.                             x=x+margin;
  1239.                             y=y-th/2;
  1240.                             y=Math.max(0,Math.min(G.h-th,y));
  1241.                         }
  1242.                         if (y>=topb && y+th<=bottomb && x>=leftb && x+tw<=rightb) break;
  1243.                     }
  1244.                    
  1245.                     G.tooltipPU.style.display='none';
  1246.                     G.tooltipPD.style.display='none';
  1247.                     G.tooltipPL.style.display='none';
  1248.                     G.tooltipPR.style.display='none';
  1249.                     if (origin=='top')
  1250.                     {
  1251.                         G.tooltipPD.style.display='block';
  1252.                         G.tooltipPD.style.left=Math.floor((box.left+box.right)/2-x-6)+'px';
  1253.                         G.tooltipPD.style.bottom=Math.floor(-6)+'px';
  1254.                     }
  1255.                     else if (origin=='bottom')
  1256.                     {
  1257.                         G.tooltipPU.style.display='block';
  1258.                         G.tooltipPU.style.left=Math.floor((box.left+box.right)/2-x-6)+'px';
  1259.                         G.tooltipPU.style.top=Math.floor(-6)+'px';
  1260.                     }
  1261.                     else if (origin=='left')
  1262.                     {
  1263.                         G.tooltipPR.style.display='block';
  1264.                         G.tooltipPR.style.right=Math.floor(-6)+'px';
  1265.                         G.tooltipPR.style.top=Math.floor((box.top+box.bottom)/2-y-6)+'px';
  1266.                     }
  1267.                     else if (origin=='right')
  1268.                     {
  1269.                         G.tooltipPL.style.display='block';
  1270.                         G.tooltipPL.style.left=Math.floor(-6)+'px';
  1271.                         G.tooltipPL.style.top=Math.floor((box.top+box.bottom)/2-y-6)+'px';
  1272.                     }
  1273.                    
  1274.                     if (!me.settled) triggerAnim(G.tooltipL,(origin=='top' || origin=='bottom')?'stretchIn':'stretchInV');
  1275.                     me.settled=true;
  1276.                    
  1277.                     G.tooltipL.style.left=Math.floor(x)+'px';
  1278.                     G.tooltipL.style.top=Math.floor(y)+'px';
  1279.                     G.tooltipL.style.opacity='1';
  1280.                     me.t++;
  1281.                 }
  1282.             }
  1283.             else
  1284.             {
  1285.                 if (!me.settled)
  1286.                 {
  1287.                     me.settled=true;
  1288.                     G.tooltipL.classList.remove('stretchIn');
  1289.                     G.tooltipL.classList.remove('stretchInV');
  1290.                     G.tooltipL.style.opacity='0';
  1291.                     triggerAnim(G.tooltipL,'fadeOutQuick');
  1292.                     //G.tooltipL.style.display='none';
  1293.                     //G.tooltipL.className='';
  1294.                 }
  1295.             }
  1296.         }
  1297.        
  1298.         /*=====================================================================================
  1299.         THINGS
  1300.         =======================================================================================*/
  1301.         G.thingsN=0;
  1302.         G.things=[];//by id
  1303.         G.thingsByName=[];//by name id
  1304.         G.thingsByTag={};
  1305.         G.res=[];//by id
  1306.         G.buttons=[];//by id
  1307.         G.buildings=[];//by id
  1308.         G.upgrades=[];//by id
  1309.         G.itemTypes=[];//by id
  1310.         G.items=[];//by id
  1311.             G.itemsN=0;
  1312.         G.achievs=[];//by id
  1313.         G.shinies=[];//by id
  1314.         G.boxes=[];//by id
  1315.        
  1316.         G.Thing=function(o)
  1317.         {
  1318.             if (o.ids) var ids=o.ids.substring(1,o.ids.length).split('|');
  1319.             for (var i in ids)
  1320.             {
  1321.                 ids[i]=G.makeSafe(ids[i]);
  1322.             }
  1323.             this.key='-';
  1324.             if (ids) this.key=ids[0];
  1325.             if (ids) this.name=ids[0]; else this.name='???';
  1326.            
  1327.             this.tags=0;
  1328.             this.classes='';
  1329.             this.show=1;
  1330.             this.lit=0;
  1331.             this.alwaysHidden=0;//overrides .show
  1332.            
  1333.             this.refreshText=true;//if true, change the text during Draw
  1334.             this.toRefresh=1;//if 1, update visibility
  1335.            
  1336.             if (G.baseThing)
  1337.             {
  1338.                 for (var i in G.baseThing)
  1339.                 {
  1340.                     if (i=='effects')
  1341.                     {
  1342.                         if (!this[i]) this[i]={};
  1343.                         for (var ii in G.baseThing[i])
  1344.                         {
  1345.                             if (!this[i][ii]) this[i][ii]=[];
  1346.                             this[i][ii]=G.baseThing[i][ii];
  1347.                         }
  1348.                     }
  1349.                     else this[i]=G.baseThing[i];
  1350.                 }
  1351.             }
  1352.             for (var i in o)
  1353.             {
  1354.                 if (i=='effects')
  1355.                 {
  1356.                     if (!this[i]) this[i]={};
  1357.                     for (var ii in o[i])
  1358.                     {
  1359.                         if (!this[i][ii]) this[i][ii]=[];
  1360.                         this[i][ii]=this[i][ii].concat(o[i][ii]);
  1361.                     }
  1362.                 }
  1363.                 else if (Array.isArray(o[i]) && this[i]) this[i]=this[i].concat(o[i]);
  1364.                 else this[i]=o[i];
  1365.             }
  1366.             if (!this.plural) this.plural=this.name;
  1367.             if (this.type=='item') this.id='ITEM'+G.itemsN;
  1368.             else this.id=G.thingsN;
  1369.             this.ids=[];
  1370.             if (ids) this.ids=ids;
  1371.             if (this.tags) this.tags=this.tags.split(' '); else this.tags=[];
  1372.             for (var i in this.tags)
  1373.             {
  1374.                 this.tags[i]=G.makeSafe(this.tags[i]);
  1375.                 if (!G.thingsByTag[this.tags[i]]) G.thingsByTag[this.tags[i]]=[];
  1376.                 G.thingsByTag[this.tags[i]].push(this);
  1377.             }
  1378.            
  1379.             if (this.type=='res') G.res.push(this);
  1380.             else if (this.type=='button') G.buttons.push(this);
  1381.             else if (this.type=='building') G.buildings.push(this);
  1382.             else if (this.type=='upgrade') G.upgrades.push(this);
  1383.             else if (this.type=='itemType') G.itemTypes.push(this);
  1384.             else if (this.type=='item') G.items.push(this);
  1385.             else if (this.type=='achiev') G.achievs.push(this);
  1386.             else if (this.type=='shiny') G.shinies.push(this);
  1387.             else if (this.type=='box') G.boxes.push(this);
  1388.             else {G.parseError('The type "'+this.type+'" is not recognized.');return {type:0};}
  1389.            
  1390.             if (this.type!='item' && this.type!='box') G.things.push(this);
  1391.             if (ids) {for (var i in ids){G.thingsByName[ids[i]]=this;}}
  1392.             //console.log(this);
  1393.             if (this.type=='item') G.itemsN++; else if (this.type!='box') G.thingsN++;
  1394.             return this;
  1395.         }
  1396.         G.createThing=function(thing)
  1397.         {
  1398.             if (thing)
  1399.             {
  1400.                 if (thing.ids=='*TEMPLATE') {G.baseThing=thing;}
  1401.                 else new G.Thing(thing);
  1402.             }
  1403.         }
  1404.         G.copyEffects=function(effects)
  1405.         {
  1406.             //returns new effects by type cloned from the original; this lets us do have a Thing using the same effects as another
  1407.             var out={};
  1408.             for (var effectType in effects)
  1409.             {
  1410.                 out[effectType]=[];
  1411.                 for (var i in effects[effectType])
  1412.                 {
  1413.                     var eff={};
  1414.                     for (var ii in effects[effectType][i])
  1415.                     {
  1416.                         eff[ii]=effects[effectType][i][ii];
  1417.                     }
  1418.                     if (eff.type=='if')
  1419.                     {
  1420.                         eff.effs=G.copyEffects(eff.effs);
  1421.                     }
  1422.                     out[effectType].push(eff);
  1423.                 }
  1424.             }
  1425.             return out;
  1426.         }
  1427.        
  1428.         G.shiniesE=[];
  1429.         G.shiniesL=l('shinies');
  1430.         G.shiniesN=0;
  1431.         G.spawnShiny=function(type)
  1432.         {
  1433.             var fail=false;
  1434.             var me={
  1435.                 type:type,
  1436.                 x:0,
  1437.                 y:0,
  1438.                 t:0,
  1439.                 tm:Math.max(0,G.fps*type.dur*type.durMult),
  1440.             };
  1441.             me.l=document.createElement('div');
  1442.             var moves=me.type.moves;
  1443.             if ('anywhere' in moves)
  1444.             {
  1445.                 me.x=Math.random()*G.w;
  1446.                 me.y=Math.random()*(G.h);
  1447.             }
  1448.             else if ('onRight' in moves)
  1449.             {
  1450.                 me.x=G.w;
  1451.                 me.y=Math.random()*(G.h);
  1452.             }
  1453.             else if ('onLeft' in moves)
  1454.             {
  1455.                 me.x=0;
  1456.                 me.y=Math.random()*(G.h);
  1457.             }
  1458.             else if ('onBottom' in moves)
  1459.             {
  1460.                 me.x=Math.random()*G.w;
  1461.                 me.y=(G.h);
  1462.             }
  1463.             else if ('onTop' in moves)
  1464.             {
  1465.                 me.x=Math.random()*G.w;
  1466.                 me.y=0;
  1467.             }
  1468.             else if ('onMouse' in moves)
  1469.             {
  1470.                 me.x=G.mouseX;
  1471.                 me.y=G.mouseY;
  1472.             }
  1473.             else if ('onThing' in moves || 'onBox' in moves)
  1474.             {
  1475.                 if ('onThing' in moves) var it=moves['onThing'];
  1476.                 else var it=moves['onBox'];
  1477.                 if (it && it.l)
  1478.                 {
  1479.                     var box=it.l.getBoundingClientRect();
  1480.                     me.x=box.left+Math.random()*(box.right-box.left);
  1481.                     me.y=box.top+Math.random()*(box.bottom-box.top);
  1482.                 }
  1483.                 else fail=true;
  1484.             }
  1485.            
  1486.             me.a=0;
  1487.             if ('randomAngle' in moves)
  1488.             {
  1489.                 me.a=Math.random()*360;
  1490.             }
  1491.             me.d=0;
  1492.             if ('moveRandom' in moves)
  1493.             {
  1494.                 me.d=Math.random();
  1495.             }
  1496.            
  1497.             if (fail) return false;
  1498.            
  1499.             var classes='thing shiny';
  1500.             if (me.type.classes) classes+=' '+me.type.classes;
  1501.             if (!me.type.noClick)
  1502.             {
  1503.                 AddEvent(me.l,'click',function(me){return function(){
  1504.                     if (me.t<me.tm) {me.type.click();me.t=me.tm;}
  1505.                 }}(me));
  1506.             }
  1507.             var str='';
  1508.             if (!me.type.icon) classes+=' noIcon';
  1509.             else
  1510.             {
  1511.                 var icon=G.resolveIcon(me.type.icon);
  1512.                 str+='<div class="thing-icon shiny-icon" style="'+icon+'"></div>';
  1513.             }
  1514.             if (me.type.noText || !me.type.customName) classes+=' noText';
  1515.             else str+='<div class="thing-text shiny-text">'+me.type.name+'</div>';
  1516.             me.l.innerHTML=str;
  1517.             me.l.className=classes;
  1518.             G.shiniesL.appendChild(me.l);
  1519.             me.offx=-me.l.clientWidth/2;
  1520.             me.offy=-me.l.clientHeight/2;
  1521.             me.id=G.shiniesN;
  1522.             G.shiniesN++;
  1523.             G.shiniesE.push(me);
  1524.         }
  1525.         G.shiniesLogic=function()
  1526.         {
  1527.             var shinies=[];
  1528.             for (var i in G.shiniesE)
  1529.             {
  1530.                 var me=G.shiniesE[i];
  1531.                 me.t++;
  1532.                 if (me.t>=me.tm)
  1533.                 {
  1534.                     me.l.parentNode.removeChild(me.l);
  1535.                 }
  1536.                 else shinies.push(me);
  1537.             }
  1538.             G.shiniesE=shinies;
  1539.         }
  1540.         G.shiniesDraw=function()
  1541.         {
  1542.             for (var i in G.shiniesE)
  1543.             {
  1544.                 var me=G.shiniesE[i];
  1545.                 var moves=me.type.moves;
  1546.                 var r=me.t/me.tm;
  1547.                 var x=me.x;
  1548.                 var y=me.y;
  1549.                 var o=1;
  1550.                 var a=me.a;
  1551.                 var s=1;
  1552.                 if ('fade' in moves)
  1553.                 {
  1554.                     if (r<0.15) o=r/0.15;
  1555.                     else if (r<0.85) o=1;
  1556.                     else o=1-(r-0.85)/0.15;
  1557.                 }
  1558.                 if ('grow' in moves) s*=r;
  1559.                 else if ('shrink' in moves) s*=1-r;
  1560.                 else if ('growShrink' in moves) s*=Math.sqrt(1-(1-r*2)*(1-r*2));
  1561.                 if ('wiggle' in moves)
  1562.                 {
  1563.                     a+=Math.sin(me.t*(moves['wiggle']||0.25)+me.id)*18;
  1564.                 }
  1565.                 if ('spinCW' in moves) a+=me.t*(moves['spinCW']||1);
  1566.                 else if ('spinCCW' in moves) a-=me.t*(moves['spinCCW']||1);
  1567.                 else if ('spinRandom' in moves) a+=me.t*(me.id%2==0?1:-1)*(moves['spinRandom']||1);
  1568.                 if ('pulse' in moves)
  1569.                 {
  1570.                     s*=1+0.05*Math.sin(me.t*(moves['pulse']||0.35)+me.id);
  1571.                 }
  1572.                 if ('followMouse' in moves)
  1573.                 {
  1574.                     x=G.mouseX;
  1575.                     y=G.mouseY;
  1576.                 }
  1577.                 else if ('followMouseSlow' in moves)
  1578.                 {
  1579.                     x+=(G.mouseX-x)*(moves['followMouseSlow']||0.1);
  1580.                     y+=((G.mouseY)-y)*(moves['followMouseSlow']||0.1);
  1581.                     me.x=x;
  1582.                     me.y=y;
  1583.                 }
  1584.                 else if ('moveRandom' in moves)
  1585.                 {
  1586.                     x+=Math.sin(me.d*Math.PI*2)*(moves['moveRandom']||3);
  1587.                     y+=Math.cos(me.d*Math.PI*2)*(moves['moveRandom']||3);
  1588.                     me.x=x;
  1589.                     me.y=y;
  1590.                 }
  1591.                 else if ('moveLeft' in moves) x=moves['moveLeft']||(me.x*(1-r));
  1592.                 else if ('moveRight' in moves) x=moves['moveRight']||(me.x+(G.w-me.x)*(r));
  1593.                 else if ('moveTop' in moves) y=moves['moveTop']||(me.y*(1-r));
  1594.                 else if ('moveBottom' in moves) y=moves['moveBottom']||(me.y+(G.h-me.y)*(r));
  1595.                 if ('bobVertical' in moves)
  1596.                 {
  1597.                     y+=Math.sin(me.t*(moves['bobVertical']||0.2)+me.id)*8;
  1598.                 }
  1599.                 if ('bobHorizontal' in moves)
  1600.                 {
  1601.                     x+=Math.cos(me.t*(moves['bobHorizontal']||0.2)+me.id)*8;
  1602.                 }
  1603.                 var sx=s;
  1604.                 var sy=s;
  1605.                 if ('bounce' in moves)
  1606.                 {
  1607.                     var bounce=me.t*(moves['bounce']||0.05)+me.id;
  1608.                     y-=Math.abs(Math.cos(bounce)*128)-64;
  1609.                     /*sx=1+Math.sin(bounce*2+0.3*(Math.PI*2))*0.2;
  1610.                     sy=1+Math.sin(bounce*2-0.2*(Math.PI*2))*0.2;
  1611.                     a+=Math.sin(bounce*2-0.15*(Math.PI*2))*18+12;*/
  1612.                 }
  1613.                 x+=me.offx;
  1614.                 y+=me.offy;
  1615.                 me.l.style.transform='translate('+(x)+'px,'+(y)+'px) rotate('+(a)+'deg) scale('+(sx)+','+(sy)+')';
  1616.                 me.l.style.opacity=o;
  1617.             }
  1618.         }
  1619.        
  1620.         G.A=0;
  1621.        
  1622.         G.itemsMax=100;//how many items can be owned maximum
  1623.         G.itemsTotal=0;//how many items we currently have
  1624.         G.gainItem=function(thing,o)
  1625.         {
  1626.             if (G.itemsTotal>=G.itemsMax) return false;
  1627.             if (thing && thing.type=='itemType')
  1628.             {
  1629.                 var inherit=['name','classes','costs','effects'];
  1630.                 var data={type:'item',base:thing};
  1631.                 for (var i in inherit)
  1632.                 {
  1633.                     data[inherit[i]]=thing[inherit[i]];
  1634.                 }
  1635.                 data.effects=G.copyEffects(data.effects);
  1636.                 for (var i in o)
  1637.                 {
  1638.                     data[i]=o[i];
  1639.                 }
  1640.                 var item=new G.Thing(data);
  1641.                 item.tags=thing.tags;
  1642.                 if (G.domReady) item.createDom();
  1643.                 G.itemsTotal++;
  1644.                 return item;
  1645.             }
  1646.             else console.log('Couldn\'t create the item : ',thing,o||'no extra parameters');
  1647.             return false;
  1648.         }
  1649.         G.loseItem=function(thing)
  1650.         {
  1651.             if (thing && thing.type=='itemType')
  1652.             {
  1653.                 var ret=false;
  1654.                 for (var i in G.items)
  1655.                 {
  1656.                     var item=G.items[i];
  1657.                     if (item.base==thing && !item.removed)
  1658.                     {
  1659.                         item.remove();
  1660.                         ret=item;
  1661.                         break;
  1662.                     }
  1663.                 }
  1664.                 return ret;
  1665.             }
  1666.             else if (thing && thing.type=='item')
  1667.             {
  1668.                 thing.remove();
  1669.                 return thing;
  1670.             }
  1671.             else console.log('Couldn\'t lose the item : ',thing);
  1672.             return false;
  1673.         }
  1674.         G.Thing.prototype.remove=function()
  1675.         {
  1676.             if (this.type=='item' && !this.removed)
  1677.             {
  1678.                 this.removed=true;
  1679.                 this.doEffects('undo grants');
  1680.                 if (G.domReady) this.removeDom();
  1681.                 G.itemsTotal--;
  1682.                 G.itemsToRefresh=true;
  1683.             }
  1684.         }
  1685.         G.itemsToRefresh=true;
  1686.         G.refreshItems=function()
  1687.         {
  1688.             G.itemsToRefresh=false;
  1689.             var arr=[];
  1690.             for (var i in G.items)
  1691.             {
  1692.                 var me=G.items[i];
  1693.                 if (!me.removed)
  1694.                 {
  1695.                     arr.push(me);
  1696.                 }
  1697.                 else
  1698.                 {
  1699.                     if (G.things.indexOf(me)!=-1) G.things.splice(G.things.indexOf(me),1);
  1700.                 }
  1701.             }
  1702.             G.items=arr;
  1703.         }
  1704.        
  1705.         G.Thing.prototype.tooltip=function()
  1706.         {
  1707.             var me=this;
  1708.             var str='';
  1709.             if (!me.noBuy && me.costs.length>0)//me.type=='res' || me.type=='building')
  1710.             {
  1711.                 str+='<div class="costs">'+G.getCostsStr(me.getCosts())+'</div>';
  1712.             }
  1713.             if (me.icon) str+='<div class="thing-icon" style="'+G.resolveIcon(me.icon,true)+'"></div>';
  1714.             if (me.name) str+='<div class="title">'+me.name+'</div>';
  1715.             if ((me.type=='upgrade' || me.type=='achiev') && me.owned) str+='<div class="subtitle">(owned)</div>';
  1716.             if (me.type=='res' || me.type=='building') str+='<div class="subtitle">(amount : '+B(me.amount)+')</div>';
  1717.             if (me.showEarned) str+='<div class="subtitle">(total earned : '+B(me.earned)+')</div>';
  1718.             if (me.showMax) str+='<div class="subtitle">(max : '+B(me.maxAmount)+')</div>';
  1719.             if (me.showClicks) str+='<div class="subtitle">(clicks : '+B(me.clicks)+')</div>';
  1720.             if (me.desc) str+='<div class="desc"><div>'+G.getTextValue(me.desc,me)+'</div></div>';
  1721.             return str;
  1722.         }
  1723.        
  1724.         G.Thing.prototype.updateDisplayed=function()
  1725.         {
  1726.             var me=this;
  1727.             var hide=false;
  1728.             if (me.alwaysHidden || !me.show) hide=true;
  1729.             else
  1730.             {
  1731.                 if (me.hiddenWhen0)
  1732.                 {
  1733.                     if ((me.type=='building' || me.type=='res') && me.amount<=0) hide=true;
  1734.                     else if ((me.type=='upgrade' || me.type=='achiev') && me.owned<=0) hide=true;
  1735.                 }
  1736.                 if (!hide && me.reqFunc)
  1737.                 {
  1738.                     if ((me.type=='building' || me.type=='res') && !me.checkReqs()) hide=true;
  1739.                     else if ((me.type=='upgrade' || me.type=='achiev') && me.owned<=0 && !me.checkReqs()) hide=true;
  1740.                 }
  1741.             }
  1742.             me.displayed=hide?0:1;
  1743.         }
  1744.        
  1745.         G.Thing.prototype.createDom=function()
  1746.         {
  1747.             var me=this;
  1748.             if (!me.alwaysHidden && me.type!='itemType')
  1749.             {
  1750.                 //select which box we should put this in; first by tag, then by type
  1751.                 var box=0;
  1752.                 var category=0;
  1753.                 for (var i in me.tags)
  1754.                 {
  1755.                     if (G.thingPlacement[me.tags[i]]) {category=me.tags[i];box=G.thingPlacement[category];break;}
  1756.                 }
  1757.                 if (!box)
  1758.                 {
  1759.                     if (me.type=='res') category='Resources';
  1760.                     else if (me.type=='button') category='Buttons';
  1761.                     else if (me.type=='building') category='Buildings';
  1762.                     else if (me.type=='upgrade') category='Upgrades';
  1763.                     else if (me.type=='achiev') category='Achievements';
  1764.                     else if (me.type=='item') category='Items';
  1765.                     if (category && G.thingPlacement[category])
  1766.                     {
  1767.                         box=G.thingPlacement[category];
  1768.                     }
  1769.                 }
  1770.                 if (box && box.type && box.type=='box')
  1771.                 {
  1772.                     me.box=box;
  1773.                     var classes='thing '+me.type;
  1774.                     if (me.classes) classes+=' '+me.classes;
  1775.                     if (false && me.show) classes+=' visible'; else classes+=' hidden';
  1776.                     if (me.lit) classes+=' lit'; else classes+=' dim';
  1777.                     if (!me.icon) classes+=' noIcon';
  1778.                     if (me.noText) classes+=' noText';
  1779.                     if (me.tags) classes+=' tag-'+me.tags.join(' tag-');
  1780.                     var iconClasses='thing-icon';
  1781.                     if (me.iconClasses) iconClasses+=' '+me.iconClasses;
  1782.                     var icon=G.resolveIcon(me.icon);
  1783.                     var div=me.box.childrenl[category];
  1784.                     var str='';
  1785.                     str+='<div id="thing-'+me.id+'" class="'+classes+'">';
  1786.                     if (me.box.showIcons && me.icon) str+='<div id="thing-icon-'+me.id+'" class="'+iconClasses+'" style="'+icon+'"></div>';
  1787.                     if (me.text && !me.noText) str+='<div id="thing-text-'+me.id+'" class="thing-text">'+G.getTextValue(me.text,me)+'</div>';
  1788.                     else if ((me.box.showNames && !me.noText) || !me.icon) str+='<div id="thing-text-'+me.id+'" class="thing-text">'+me.name+'</div>';
  1789.                     if (me.box.showCosts && !me.noBuy && me.costs.length>0 && !me.noText) str+='<div id="thing-costs-'+me.id+'" class="thing-costs"></div>';
  1790.                     str+='</div>';
  1791.                     div.appendChild(document.createRange().createContextualFragment(str));
  1792.                    
  1793.                     me.l=l('thing-'+me.id)||0;
  1794.                     me.iconl=l('thing-icon-'+me.id)||0;
  1795.                     me.textl=l('thing-text-'+me.id)||0;
  1796.                     me.costsl=l('thing-costs-'+me.id)||0;
  1797.                     if (me.l)
  1798.                     {
  1799.                         AddEvent(me.l,'click',function(me){return function(){me.click();}}(me));
  1800.                         if (me.tooltip && !me.noTooltip && !me.box.noTooltip)
  1801.                         {
  1802.                             var obj={func:function(me){return function(){return me.tooltip();}}(me)};
  1803.                             if (me.tooltipOrigin) obj.origin=me.tooltipOrigin;
  1804.                             else if (me.box.tooltipOrigin) obj.origin=me.box.tooltipOrigin;
  1805.                             if (me.tooltipClasses) obj.classes=me.tooltipClasses;
  1806.                             else if (me.box.tooltipClasses) obj.classes=me.box.tooltipClasses;
  1807.                             G.addTooltip(me.l,obj);
  1808.                         }
  1809.                     }
  1810.                 }
  1811.             }
  1812.         }
  1813.         G.Thing.prototype.getQuickDom=function(id)
  1814.         {
  1815.             //returns simplified non-gameplay DOM with no bindings save for tooltip, such as something you'd see in the stats page
  1816.             var me=this;
  1817.             var classes='thing '+me.type;
  1818.             if (me.classes) classes+=' '+me.classes;
  1819.             if (!me.icon) classes+=' noIcon';
  1820.             if (me.noText) classes+=' noText';
  1821.             if (me.tags) classes+=' tag-'+me.tags.join(' tag-');
  1822.             var iconClasses='thing-icon';
  1823.             if (me.iconClasses) iconClasses+=' '+me.iconClasses;
  1824.             var icon=G.resolveIcon(me.icon);
  1825.             var str='';
  1826.             str+='<div '+(id?'id="'+id+'" ':'')+'class="'+classes+'">';
  1827.             str+='<div class="'+iconClasses+'" style="'+icon+'"></div>';
  1828.             if (!me.icon) str+='<div class="thing-text">'+me.name+'</div>';
  1829.             str+='</div>';
  1830.             if (me.tooltip && !me.noTooltip)
  1831.             {
  1832.                 var obj={func:function(me){return function(){return me.tooltip();}}(me)};
  1833.                 if (me.tooltipClasses) obj.classes=me.tooltipClasses;
  1834.                 str=G.tooltipped(str,obj,'display:inline-block;');
  1835.             }
  1836.            
  1837.             return str;
  1838.         }
  1839.         G.Thing.prototype.removeDom=function()
  1840.         {
  1841.             if (G.tooltip.parent==this.l) G.hideTooltip();
  1842.             this.l.parentNode.removeChild(this.l);
  1843.             delete this.l;
  1844.             delete this.iconl;
  1845.             delete this.textl;
  1846.             delete this.costsl;
  1847.         }
  1848.        
  1849.         G.Thing.prototype.getCosts=function(amount)
  1850.         {
  1851.             //also see : G.getCostsStr, G.spendCosts
  1852.             var costs=this.costs;
  1853.             var costsByThing={};
  1854.             var amount=typeof amount==='undefined'?1:amount;
  1855.             var mult=1;
  1856.             var refund=false;
  1857.             if (amount<0) refund=true;
  1858.             if (refund) amount=Math.min(this.amount,Math.abs(amount));
  1859.             if (this.type=='building' && refund) mult*=this.refundRate;
  1860.             var add=this.costAdd;
  1861.             mult*=this.costMult;
  1862.             if (refund) mult*=this.refundMult;
  1863.             for (var n=0;n<amount;n++)
  1864.             {
  1865.                 for (var i in costs)
  1866.                 {
  1867.                     for (var ii in costs[i])
  1868.                     {
  1869.                         var me=costs[i][ii];
  1870.                         var w=me.w;
  1871.                         if (!costsByThing[w.id]) costsByThing[w.id]=0;
  1872.                         if (refund && this.type=='building' && this.amount==0){}
  1873.                         else
  1874.                         {
  1875.                             var v=(me.v);
  1876.                             if (this.type=='building') v=(me.v*Math.pow(this.costRate,Math.max(0,this.amount+(refund?-n:n))));
  1877.                             v+=add;
  1878.                             v+=w.costAdd;
  1879.                             if (this.costAddFor[w.id]) v+=this.costAddFor[w.id];
  1880.                             v*=mult;
  1881.                             v*=w.costMult;
  1882.                             if (refund) v*=w.refundMult;
  1883.                             if (this.costMultFor[w.id]) v*=this.costMultFor[w.id];
  1884.                             if (refund && this.refundMultFor[w.id]) v*=this.refundMultFor[w.id];
  1885.                             costsByThing[w.id]+=v;
  1886.                         }
  1887.                     }
  1888.                 }
  1889.             }
  1890.             return costsByThing;
  1891.         }
  1892.        
  1893.         G.Thing.prototype.checkReqs=function()
  1894.         {
  1895.             if (this.reqFunc && this.reqFunc()) return true;
  1896.             else return false;
  1897.         }
  1898.        
  1899.         G.Thing.prototype.logic=function()
  1900.         {
  1901.             //every logic tick
  1902.             if (true)
  1903.             {
  1904.                 var me=this;
  1905.                 if (me.type=='res')
  1906.                 {
  1907.                     if (me.amountD!=me.amount) me.refreshText=true;
  1908.                     if (Math.abs(me.amount-me.amountD)<0.01) {me.amountD=me.amount;}
  1909.                     else {me.amountD+=(me.amount-me.amountD)*(Math.abs(me.amount-me.amountD)<10?0.5:0.1);}
  1910.                 }
  1911.                 else if (me.type=='shiny')
  1912.                 {
  1913.                     if (me.freq>0)
  1914.                     {
  1915.                         if (me.timeLeft>0)
  1916.                         {
  1917.                             me.timeLeft--;
  1918.                             if (me.timeLeft==0)
  1919.                             {
  1920.                                 G.spawnShiny(me);
  1921.                                 me.timeLeft=-1;
  1922.                             }
  1923.                         }
  1924.                         if (me.timeLeft==-1)//init time
  1925.                         {
  1926.                             me.timeLeft=Math.ceil(Math.max(0,(me.freq+Math.random()*me.freqV)*me.freqMult)*G.fps);
  1927.                         }
  1928.                     }
  1929.                 }
  1930.             }
  1931.         }
  1932.        
  1933.         G.Thing.prototype.draw=function()
  1934.         {
  1935.             //every draw tick
  1936.             var me=this;
  1937.             if (me.l && !me.alwaysHidden)
  1938.             {
  1939.                 if (me.toRefresh)
  1940.                 {
  1941.                     if (me.toRefresh!=2) me.updateDisplayed();
  1942.                     if (!me.displayed)
  1943.                     {
  1944.                         if (G.tooltip.parent==me.l) G.hideTooltip(-1);
  1945.                         me.l.classList.add('hidden');
  1946.                         me.l.classList.remove('visible');
  1947.                     }
  1948.                     else
  1949.                     {
  1950.                         me.l.classList.add('visible');
  1951.                         me.l.classList.remove('hidden');
  1952.                        
  1953.                         var lit=me.lit;
  1954.                         if (lit)
  1955.                         {
  1956.                             this.l.classList.add('lit');
  1957.                             this.l.classList.remove('dim');
  1958.                         }
  1959.                         else
  1960.                         {
  1961.                             this.l.classList.add('dim');
  1962.                             this.l.classList.remove('lit');
  1963.                         }
  1964.                     }
  1965.                 }
  1966.                 if (me.text && (me.toRefresh || G.drawT%10==0))
  1967.                 {
  1968.                     me.textl.innerHTML=G.getTextValue(me.text,me);
  1969.                 }
  1970.                 me.toRefresh=0;
  1971.                 if (me.displayed)
  1972.                 {
  1973.                     if (me.text)
  1974.                     {
  1975.                     }
  1976.                     else if (me.refreshText)
  1977.                     {
  1978.                         me.refreshText=false;
  1979.                         if (me.type=='res')
  1980.                         {
  1981.                             var str=me.plural+' : ';
  1982.                             str+=B(Math.floor(me.amountD));
  1983.                             if (me.box.showPs)
  1984.                             {
  1985.                                 var ps=me.ps;
  1986.                                 if (ps>0) str+=' (+'+B(ps,1)+'/s)';
  1987.                                 else if (ps<0) str+=' ('+B(ps,1)+'/s)';
  1988.                                 //else str+=' (0/s)';
  1989.                             }
  1990.                             me.textl.innerHTML=str;
  1991.                         }
  1992.                         else if (me.type=='building')
  1993.                         {
  1994.                             var str=me.plural+' : ';
  1995.                             str+=B(me.amount);
  1996.                             me.textl.innerHTML=str;
  1997.                         }
  1998.                     }
  1999.                     if (me.type=='res')
  2000.                     {
  2001.                         if (me.ps>0) me.l.classList.add('earning');
  2002.                         else me.l.classList.remove('earning');
  2003.                         if (me.ps<0) me.l.classList.add('losing');
  2004.                         else me.l.classList.remove('losing');
  2005.                     }
  2006.                     if (G.drawT%10==0)
  2007.                     {
  2008.                         var owned=0;
  2009.                         if (me.owned && (me.type=='upgrade' || me.type=='achiev')) owned=1;
  2010.                         else if (me.amount>0 && (me.type=='res' || me.type=='building')) owned=1;
  2011.                         if (me.costs.length>0)
  2012.                         {
  2013.                             var amount=1;
  2014.                             if (me.type=='building') {amount=G.bulk;}
  2015.                             var costs=G.getCostsStr(me.getCosts(amount),(me.type=='upgrade'?(owned>0):0),true);
  2016.                             if (!me.noText && me.costsl) me.costsl.innerHTML=costs.str;
  2017.                             if (costs.lacking>0 && (!owned || me.type=='building')) me.l.classList.add('cantAfford');
  2018.                             else me.l.classList.remove('cantAfford');
  2019.                         }
  2020.                         me.updateOwned(owned>0);
  2021.                     }
  2022.                 }
  2023.             }
  2024.         }
  2025.        
  2026.         G.bulk=1;//how much of buildings we buy at once (or sell, if negative)
  2027.        
  2028.         G.Thing.prototype.click=function()
  2029.         {
  2030.             if (true)
  2031.             {
  2032.                 var win=false;
  2033.                 var out=0;
  2034.                 if (this.type=='button' || this.type=='shiny')
  2035.                 {
  2036.                     this.clicks++;
  2037.                     win=true;
  2038.                 }
  2039.                 if (this.type=='building' && !this.noBuy)
  2040.                 {
  2041.                     var amount=G.bulk;
  2042.                     if (amount<0)//selling
  2043.                     {
  2044.                         amount=Math.max(-this.amount,amount);
  2045.                         if (this.amount+amount>=0) out=G.refundCosts(this.getCosts(amount));
  2046.                     }
  2047.                     else if (!this.limit || this.limit()>=this.amount+amount)//buying
  2048.                     {
  2049.                         out=G.spendCosts(this.getCosts(amount));
  2050.                     }
  2051.                     if (out)
  2052.                     {
  2053.                         this.doEffects('undo grants');
  2054.                         this.earn(amount);
  2055.                         win=true;
  2056.                     }
  2057.                 }
  2058.                 else if (this.type=='upgrade' && !this.owned && !this.noBuy)
  2059.                 {
  2060.                     out=G.spendCosts(this.getCosts());
  2061.                     if (out)
  2062.                     {
  2063.                         this.doEffects('undo grants');
  2064.                         this.earn(1);
  2065.                         win=true;
  2066.                     }
  2067.                 }
  2068.                
  2069.                 var out2=this.doEffects('click',true);
  2070.                 if (out2.produced && out.produced)
  2071.                 {
  2072.                     for (var i in out2.produced)
  2073.                     {
  2074.                         if (!out.produced[i]) out.produced[i]=0;
  2075.                         out.produced[i]+=out2.produced[i];
  2076.                     }
  2077.                 }
  2078.                 else out=out2;
  2079.                
  2080.                 if (win && this.l)
  2081.                 {
  2082.                     if (this.type=='button') G.hideTooltip();
  2083.                     if (out && !G.noParticles)
  2084.                     {
  2085.                         for (var i in out.produced)
  2086.                         {G.particleAt(this.l,G.things[i].icon,(G.things[i].icon?'':(G.things[i].name))+(out.produced[i]>0?'+':'')+B(out.produced[i]));}
  2087.                     }
  2088.                 }
  2089.             }
  2090.         }
  2091.        
  2092.         G.Thing.prototype.updateOwned=function(val)
  2093.         {
  2094.             if (val) {this.l.classList.add('owned');this.l.classList.remove('notOwned');}
  2095.             else {this.l.classList.add('notOwned');this.l.classList.remove('owned');}
  2096.         }
  2097.         G.Thing.prototype.set=function(v)
  2098.         {
  2099.             var w=this;
  2100.             if (w.type=='res' || w.type=='building')
  2101.             {
  2102.                 if (w.type=='building') v=Math.round(v);
  2103.                 w.amount=v;
  2104.                 if (w.type=='res' && v>0) w.earned=Math.max(v,w.earned);
  2105.                 if (!w.canBeNegative) w.amount=Math.max(0,w.amount);
  2106.                 w.maxAmount=Math.max(w.amount,w.maxAmount);
  2107.                 this.updateOwned(this.amount>0);
  2108.                 this.refreshText=true;
  2109.             }
  2110.             else if (w.type=='upgrade' || w.type=='achiev')
  2111.             {
  2112.                 var v2=v?1:0;
  2113.                 if (w.owned!=v2 && w.l)
  2114.                 {
  2115.                     w.updateOwned(v2>0);
  2116.                 }
  2117.                 w.owned=v2;
  2118.             }
  2119.         }
  2120.         G.Thing.prototype.earn=function(v)
  2121.         {
  2122.             var w=this;
  2123.             if (w.type=='res' || w.type=='building')
  2124.             {
  2125.                 if (w.type=='building') v=Math.round(v);
  2126.                 var old=w.amount;
  2127.                 w.amount+=v;
  2128.                 if (w.type=='res' && v>0) w.earned+=v;
  2129.                 if (!w.canBeNegative) w.amount=Math.max(0,w.amount);
  2130.                 if (w.amount-old>0) w.doEffects('earn');
  2131.                 else if (w.amount-old<0) w.doEffects('lose');
  2132.                 w.maxAmount=Math.max(w.amount,w.maxAmount);
  2133.                 if (w.l)
  2134.                 {
  2135.                     w.updateOwned(w.amount>0);
  2136.                 }
  2137.                 w.refreshText=true;
  2138.             }
  2139.             else if (w.type=='upgrade' || w.type=='achiev')
  2140.             {
  2141.                 var v2=v?1:0;
  2142.                 if (w.owned!=v2 && w.l)
  2143.                 {
  2144.                     w.updateOwned(v2>0);
  2145.                 }
  2146.                 w.owned=v2;
  2147.                 if (w.owned) w.doEffects('earn');
  2148.                 else w.doEffects('lose');
  2149.                
  2150.                 if (w.owned && w.type=='achiev')
  2151.                 {
  2152.                     var str='';
  2153.                     if (w.icon) str+='<div class="thing-icon" style="'+G.resolveIcon(w.icon,true)+'"></div>';
  2154.                     if (w.name) str+='Got achievement :<div class="title">'+w.name+'</div>'; else str+='Got achievement!';
  2155.                     G.toast({text:str,dur:10});
  2156.                 }
  2157.             }
  2158.         }
  2159.         G.Thing.prototype.grant=function(v)
  2160.         {
  2161.             var w=this;
  2162.             if (w.type=='res' || w.type=='building')
  2163.             {
  2164.                 if (w.type=='building') v=Math.round(v);
  2165.                 w.amount+=v;
  2166.                 if (w.type=='res' && v>0) w.earned=Math.max(w.earned,w.amount);
  2167.                 if (!w.canBeNegative) w.amount=Math.max(0,w.amount);
  2168.                 w.maxAmount=Math.max(w.amount,w.maxAmount);
  2169.                 if (w.l)
  2170.                 {
  2171.                     w.updateOwned(w.amount>0);
  2172.                 }
  2173.                 w.refreshText=true;
  2174.             }
  2175.         }
  2176.         G.Thing.prototype.light=function()
  2177.         {
  2178.             if (this.lit || this.alwaysHidden) return false;
  2179.             this.lit=1;
  2180.             this.toRefresh=1;
  2181.         }
  2182.         G.Thing.prototype.dim=function()
  2183.         {
  2184.             if (!this.lit || this.alwaysHidden) return false;
  2185.             this.lit=0;
  2186.             this.toRefresh=1;
  2187.         }
  2188.         G.Thing.prototype.display=function()
  2189.         {
  2190.             if (this.show || this.alwaysHidden) return false;
  2191.             this.show=1;
  2192.             this.toRefresh=1;
  2193.         }
  2194.         G.Thing.prototype.hide=function()
  2195.         {
  2196.             if (!this.show || this.alwaysHidden) return false;
  2197.             this.show=0;
  2198.             this.toRefresh=1;
  2199.         }
  2200.        
  2201.         G.doEffectsForAll=function(type)
  2202.         {
  2203.             var things=[];
  2204.             for (var i in G.things)
  2205.             {
  2206.                 var me=G.things[i];
  2207.                 if (me.type=='button' || me.type=='res' || (me.type=='building' && me.amount>0) || (me.type=='upgrade' && me.owned) || (me.type=='achiev' && me.owned)) things.push(me);
  2208.             }
  2209.             for (var i in G.items)
  2210.             {things.push(G.items[i]);}
  2211.        
  2212.             for (var i in things)
  2213.             {
  2214.                 var me=things[i];
  2215.                 me.doEffects(type);
  2216.             }
  2217.         }
  2218.        
  2219.         /*=====================================================================================
  2220.         TICK
  2221.         =======================================================================================*/
  2222.         G.tick=function()
  2223.         {
  2224.             //every second :
  2225.            
  2226.             var things=[];
  2227.             for (var i in G.things)
  2228.             {
  2229.                 var me=G.things[i];
  2230.                 if (me.type=='button' || me.type=='res' || (me.type=='building' && me.amount>0) || (me.type=='upgrade' && me.owned) || (me.type=='achiev' && me.owned)) things.push(me);
  2231.             }
  2232.             for (var i in G.items)
  2233.             {things.push(G.items[i]);}
  2234.            
  2235.             //reset boosts and production
  2236.             for (var i in G.things)
  2237.             {
  2238.                 var me=G.things[i];
  2239.                 me.ps=0;
  2240.                 me.boostAdd=0;
  2241.                 me.boostMult=1;
  2242.                 me.boostAddFor=[];
  2243.                 me.boostMultFor=[];
  2244.                 me.costAdd=0;
  2245.                 me.costMult=1;
  2246.                 me.refundMult=1;
  2247.                 me.costAddFor=[];
  2248.                 me.costMultFor=[];
  2249.                 me.refundMultFor=[];
  2250.                 if (me.type=='shiny')
  2251.                 {
  2252.                     me.durMult=1;
  2253.                     me.freqMult=1;
  2254.                 }
  2255.             }
  2256.            
  2257.             //cache boosts
  2258.             for (var i in things)
  2259.             {
  2260.                 var me=things[i];
  2261.                 me.doEffects('cache boosts');
  2262.             }
  2263.            
  2264.             //apply grants
  2265.             for (var i in things)
  2266.             {
  2267.                 var me=things[i];
  2268.                 me.doEffects('do grants');
  2269.             }
  2270.            
  2271.             //cache production
  2272.             for (var i in things)
  2273.             {
  2274.                 var me=things[i];
  2275.                 me.doEffects('cache ps');
  2276.             }
  2277.            
  2278.             //tick
  2279.             for (var i in things)
  2280.             {
  2281.                 var me=things[i];
  2282.                 me.doEffects('tick');
  2283.             }
  2284.            
  2285.             //apply production
  2286.             for (var i in things)
  2287.             {
  2288.                 var me=things[i];
  2289.                 if (me.type=='res' || me.type=='building')
  2290.                 {
  2291.                     if (me.ps!=0)
  2292.                     {
  2293.                         me.earn(me.ps);
  2294.                     }
  2295.                 }
  2296.             }
  2297.            
  2298.             for (var i in G.things)
  2299.             {
  2300.                 var me=G.things[i];
  2301.                 if (me.isAlways) me.amount=me.isAlways();
  2302.                 if (me.limit) me.amount=Math.min(me.limit(),me.amount);
  2303.                 if (me.type=='achiev' && !me.owned)
  2304.                 {
  2305.                     if (me.checkReqs())
  2306.                     {
  2307.                         me.earn(1);
  2308.                     }
  2309.                 }
  2310.                 var old=me.displayed||0;
  2311.                 me.updateDisplayed();
  2312.                 if (me.displayed!=old) me.toRefresh=2;//triggers a refresh without re-running updateDisplayed()
  2313.             }
  2314.         }
  2315.        
  2316.         /*=====================================================================================
  2317.         EFFECTS
  2318.         =======================================================================================*/
  2319.         G.effectNestLevel=0;
  2320.         G.localVars=[];
  2321.         G.doEffect=function(effect,owner,context,amount,out)
  2322.         {
  2323.             G.effectNestLevel++;
  2324.             if (G.effectNestLevel>10) {G.effectNestLevel--;return false;}//possible endless loop
  2325.             var me=effect;
  2326.             var type=me.type;
  2327.             if (me.w)
  2328.             {
  2329.                 var w=me.w;
  2330.                 if (w==='this') w=owner;
  2331.                 else if (w[0]==='this') w=[owner];
  2332.             }
  2333.             if (type=='if' || type=='else')
  2334.             {
  2335.                 var pass=false;
  2336.                 if (me.cond && context!='undo grants')//when undoing grants, disregard conditions
  2337.                 {
  2338.                     var amount2=0;
  2339.                     if (!owner) amount2=0;
  2340.                     else
  2341.                     {
  2342.                         if (owner.type=='building' || owner.type=='res') amount2=owner.amount;
  2343.                         else if (owner.type=='upgrade' || owner.type=='achiev') amount2=(owner.owned?1:0);
  2344.                         else if (owner.type=='button' || owner.type=='shiny') amount2=owner.clicks;
  2345.                     }
  2346.                     if (me.cond(owner,amount2)) pass=true;
  2347.                 }
  2348.                 else pass=true;
  2349.                
  2350.                 if (pass)
  2351.                 {
  2352.                     for (var i in me.effs)
  2353.                     {
  2354.                         G.doEffect(me.effs[i],owner,context,amount,out);
  2355.                     }
  2356.                 }
  2357.                 else if (me.or)
  2358.                 {
  2359.                     for (var i in me.or)
  2360.                     {
  2361.                         G.doEffect(me.or[i],owner,context,amount,out);
  2362.                     }
  2363.                 }
  2364.             }
  2365.             else if (type=='grant' && context=='do grants' && !me.done)
  2366.             {
  2367.                 me.done=true;
  2368.                 w.grant(amount*G.getVarValue(me.v,owner));
  2369.             }
  2370.             else if (type=='grant' && context=='undo grants' && me.done)
  2371.             {
  2372.                 me.done=false;
  2373.                 w.grant(-amount*G.getVarValue(me.v,owner));
  2374.             }
  2375.             else if (context=='do grants' || context=='undo grants') {}
  2376.             else if (context=='cache boosts')
  2377.             {
  2378.                 if (type=='increase yield' || type=='lower yield')
  2379.                 {
  2380.                     var w=G.getThings(me.w,owner);
  2381.                     var gain=amount*G.getVarValue(me.v,owner);
  2382.                     if (type=='lower yield') gain*=-1;
  2383.                     for (var i=0;i<w.length;i++)
  2384.                     {
  2385.                         w[i].boostAdd+=gain;
  2386.                     }
  2387.                 }
  2388.                 else if (type=='multiply yield')
  2389.                 {
  2390.                     var w=G.getThings(me.w,owner);
  2391.                     var gain=amount*G.getVarValue(me.v,owner);
  2392.                     for (var i=0;i<w.length;i++)
  2393.                     {
  2394.                         w[i].boostMult*=gain;
  2395.                     }
  2396.                 }
  2397.                 else if (type=='increase yield for' || type=='lower yield for')
  2398.                 {
  2399.                     var w=G.getThings(me.w,owner);
  2400.                     var z=G.getThings(me.z,owner);
  2401.                     var gain=amount*G.getVarValue(me.v,owner);
  2402.                     if (type=='lower yield for') gain*=-1;
  2403.                     for (var i=0;i<w.length;i++)
  2404.                     {
  2405.                         for (var ii=0;ii<z.length;ii++)
  2406.                         {
  2407.                             if (!w[i].boostAddFor[z[ii].id]) w[i].boostAddFor[z[ii].id]=0;
  2408.                             w[i].boostAddFor[z[ii].id]+=gain;
  2409.                         }
  2410.                     }
  2411.                 }
  2412.                 else if (type=='multiply yield for')
  2413.                 {
  2414.                     var w=G.getThings(me.w,owner);
  2415.                     var z=G.getThings(me.z,owner);
  2416.                     var gain=amount*G.getVarValue(me.v,owner);
  2417.                     for (var i=0;i<w.length;i++)
  2418.                     {
  2419.                         for (var ii=0;ii<z.length;ii++)
  2420.                         {
  2421.                             if (!w[i].boostMultFor[z[ii].id]) w[i].boostMultFor[z[ii].id]=1;
  2422.                             w[i].boostMultFor[z[ii].id]*=gain;
  2423.                         }
  2424.                     }
  2425.                 }
  2426.                 else if (type=='increase cost' || type=='lower cost')
  2427.                 {
  2428.                     var w=G.getThings(me.w,owner);
  2429.                     var gain=amount*G.getVarValue(me.v,owner);
  2430.                     if (type=='lower cost') gain*=-1;
  2431.                     for (var i=0;i<w.length;i++)
  2432.                     {
  2433.                         w[i].costAdd+=gain;
  2434.                     }
  2435.                 }
  2436.                 else if (type=='multiply cost')
  2437.                 {
  2438.                     var w=G.getThings(me.w,owner);
  2439.                     var gain=amount*G.getVarValue(me.v,owner);
  2440.                     for (var i=0;i<w.length;i++)
  2441.                     {
  2442.                         w[i].costMult*=gain;
  2443.                     }
  2444.                 }
  2445.                 else if (type=='multiply refund')
  2446.                 {
  2447.                     var w=G.getThings(me.w,owner);
  2448.                     var gain=amount*G.getVarValue(me.v,owner);
  2449.                     for (var i=0;i<w.length;i++)
  2450.                     {
  2451.                         w[i].refundMult*=gain;
  2452.                     }
  2453.                 }
  2454.                 else if (type=='multiply duration')
  2455.                 {
  2456.                     var w=G.getThings(me.w,owner);
  2457.                     var gain=amount*G.getVarValue(me.v,owner);
  2458.                     for (var i=0;i<w.length;i++)
  2459.                     {
  2460.                         if (w[i].type=='shiny') w[i].durMult*=gain;
  2461.                     }
  2462.                 }
  2463.                 else if (type=='multiply frequency')
  2464.                 {
  2465.                     var w=G.getThings(me.w,owner);
  2466.                     var gain=amount*G.getVarValue(me.v,owner);
  2467.                     for (var i=0;i<w.length;i++)
  2468.                     {
  2469.                         if (w[i].type=='shiny') w[i].freqMult*=gain;
  2470.                     }
  2471.                 }
  2472.             }
  2473.             else if (context=='cache ps')
  2474.             {
  2475.                 if (type=='yield' || type=='lose')
  2476.                 {
  2477.                     var w=G.getThings(me.w,owner);
  2478.                     var v=amount;
  2479.                     if (me.v) v*=G.getVarValue(me.v,owner);
  2480.                     if (owner.type=='item') owner=owner.base;
  2481.                     for (var i=0;i<w.length;i++)
  2482.                     {
  2483.                         var w2=w[i];
  2484.                         if (w2.type=='res' || w2.type=='building')
  2485.                         {
  2486.                             if (type=='lose') v=-v;
  2487.                             else if (w.type!='building')
  2488.                             {
  2489.                                 v+=w2.boostAdd;
  2490.                                 v*=w2.boostMult;
  2491.                                
  2492.                                 v+=owner.boostAdd;
  2493.                                 v*=owner.boostMult;
  2494.                                
  2495.                                 if (owner.boostAddFor[w2.id]) v+=owner.boostAddFor[w2.id];
  2496.                                 if (owner.boostMultFor[w2.id]) v*=owner.boostMultFor[w2.id];
  2497.                             }
  2498.                             w2.ps+=v;
  2499.                         }
  2500.                     }
  2501.                 }
  2502.             }
  2503.             else//on tick, on click, others
  2504.             {
  2505.                 if (type=='set')
  2506.                 {
  2507.                     var w=G.getThings(me.w,owner);
  2508.                     var v=G.getVarValue(me.v,owner);
  2509.                     for (var i=0;i<w.length;i++)
  2510.                     {
  2511.                         if (w[i].type=='res' || w[i].type=='building') w[i].set(v);
  2512.                     }
  2513.                 }
  2514.                 else if (type=='set var')
  2515.                 {
  2516.                     var w=me.w;
  2517.                     var v=G.getVarValue(me.v,owner);
  2518.                     G.localVars[w]=v;
  2519.                 }
  2520.                 else if ((type=='yield' || type=='lose') && context!='tick')
  2521.                 {
  2522.                     var w2=G.getThings(me.w,owner);
  2523.                     var v=amount;
  2524.                     if (me.v) v*=G.getVarValue(me.v,owner);
  2525.                     if (owner.type=='item') owner=owner.base;
  2526.                     for (var i=0;i<w2.length;i++)
  2527.                     {
  2528.                         var w=w2[i];
  2529.                         if ((w.type=='upgrade' || w.type=='achiev'))
  2530.                         {
  2531.                             if (type=='yield' && !w.owned) {w.owned=1;w.doEffects('earn');}
  2532.                             else if (type=='lose' && w.owned) {w.owned=0;w.doEffects('lose');}
  2533.                         }
  2534.                         else if (w.type=='itemType' || w.type=='item')
  2535.                         {
  2536.                             if (type=='yield')//gain V items of type W, or of the same type as the existing item W
  2537.                             {
  2538.                                 if (w.type=='item') w=w.base;
  2539.                                 for (var ii=0;ii<v;ii++)
  2540.                                 {
  2541.                                     var newItem=G.gainItem(w);
  2542.                                 }
  2543.                             }
  2544.                             else//lose V items of type W, or lose the specified existing item
  2545.                             {
  2546.                                 if (w.type=='itemType')
  2547.                                 {
  2548.                                     var v2=('v' in me?v:100);
  2549.                                     if (v2<0) v2=0;
  2550.                                     for (var ii=0;ii<v2;ii++)
  2551.                                     {
  2552.                                         G.loseItem(w);
  2553.                                     }
  2554.                                 }
  2555.                                 else
  2556.                                 {
  2557.                                     G.loseItem(w);
  2558.                                 }
  2559.                             }
  2560.                         }
  2561.                         else
  2562.                         {
  2563.                             if (w.type=='res' || w.type=='building')
  2564.                             {
  2565.                                 var v2=(('v' in me || type=='yield')?v:w.amount);
  2566.                                 if (type=='lose') v2=-v2;
  2567.                                 else if (w.type=='res')
  2568.                                 {
  2569.                                     v2+=w.boostAdd;
  2570.                                     v2*=w.boostMult;
  2571.                                    
  2572.                                     v2+=owner.boostAdd;
  2573.                                     v2*=owner.boostMult;
  2574.                                    
  2575.                                     if (owner.boostAddFor[w.id]) v2+=owner.boostAddFor[w.id];
  2576.                                     if (owner.boostMultFor[w.id]) v2*=owner.boostMultFor[w.id];
  2577.                                 }
  2578.                                
  2579.                                 if (out && v2!=0)
  2580.                                 {
  2581.                                     if (!out.produced[w.id]) out.produced[w.id]=0;
  2582.                                     out.produced[w.id]+=v2;
  2583.                                 }
  2584.                                 w.earn(v2);
  2585.                             }
  2586.                         }
  2587.                     }
  2588.                 }
  2589.                 else if (type=='spawn')
  2590.                 {
  2591.                     if (me.w.type=='shiny') G.spawnShiny(me.w);
  2592.                 }
  2593.                 else if (type=='log')
  2594.                 {
  2595.                     if (me.classes) G.log(G.getTextValue(me.w,owner,1),me.classes);
  2596.                     else G.log(G.getTextValue(me.w,owner,1));
  2597.                 }
  2598.                 else if (type=='clear log')
  2599.                 {
  2600.                     G.clearLog();
  2601.                 }
  2602.                 else if (type=='toast')
  2603.                 {
  2604.                     G.toast({text:'<div>'+G.getTextValue(me.w,owner,1)+'</div>',classes:'center',dur:10});
  2605.                 }
  2606.                 else if (type=='echo')
  2607.                 {
  2608.                     console.log(owner.name+' : '+G.makeUnsafe(G.getTextValue(me.w,owner,1)));
  2609.                 }
  2610.                 else if (type=='do')
  2611.                 {
  2612.                     for (var i in w)
  2613.                     {
  2614.                         var it=w[i];
  2615.                         if (it.effects[me.v]) it.doEffects(me.v);
  2616.                     }
  2617.                 }
  2618.                 else if (type=='light')
  2619.                 {
  2620.                     var w=G.getThings(me.w,owner);
  2621.                     for (var i in w){w[i].light();}
  2622.                 }
  2623.                 else if (type=='dim')
  2624.                 {
  2625.                     var w=G.getThings(me.w,owner);
  2626.                     for (var i in w){w[i].dim();}
  2627.                 }
  2628.                 else if (type=='show')
  2629.                 {
  2630.                     var w=G.getThings(me.w,owner);
  2631.                     for (var i in w){w[i].display();}
  2632.                 }
  2633.                 else if (type=='hide')
  2634.                 {
  2635.                     var w=G.getThings(me.w,owner);
  2636.                     for (var i in w){w[i].hide();}
  2637.                 }
  2638.                 else if (type=='anim')
  2639.                 {
  2640.                     if (owner.l) triggerAnim(owner.l,me.w);
  2641.                 }
  2642.                 else if (type=='animicon')
  2643.                 {
  2644.                     if (owner.iconl) triggerAnim(owner.iconl,me.w);
  2645.                 }
  2646.                 else if (type=='eval' && G.local)
  2647.                 {
  2648.                     eval(me.w);
  2649.                 }
  2650.             }
  2651.             G.effectNestLevel--;
  2652.         }
  2653.        
  2654.         G.effectsByContext={
  2655.             'start':'start',
  2656.             'save':'save',
  2657.             'load':'load',
  2658.             'earn':'earn',
  2659.             'lose':'lose',
  2660.             'tick':'tick',
  2661.             'cache ps':'tick',
  2662.             'cache boosts':'tick',
  2663.             'do grants':'tick',
  2664.             'undo grants':'tick',
  2665.             'click':'click',
  2666.         };
  2667.         G.Thing.prototype.doEffects=function(context,returnOut)
  2668.         {
  2669.             if (returnOut)
  2670.             {
  2671.                 var out={};
  2672.                 out.produced={};
  2673.             }
  2674.             var amount=1;
  2675.             if (this.type=='building') amount=this.amount;
  2676.            
  2677.             var custom=false;
  2678.             if (!G.effectsByContext[context]) custom=true;
  2679.             var resetVars=true;
  2680.             if (custom || context=='earn' || context=='lose') resetVars=false;
  2681.            
  2682.             if (resetVars) G.localVars=[];
  2683.             var effs=this.effects[G.effectsByContext[context]]||this.effects[context]||[];
  2684.             for (var i in effs)
  2685.             {
  2686.                 G.doEffect(effs[i],this,context,amount,out);
  2687.             }
  2688.             if (resetVars) G.localVars=[];
  2689.            
  2690.             if (returnOut) return out;
  2691.         }
  2692.        
  2693.        
  2694.         G.spendCosts=function(costs,mult)
  2695.         {
  2696.             var out={produced:{}};
  2697.             var mult=mult||1;
  2698.             for (var i in costs)
  2699.             {
  2700.                 var w=G.things[i];
  2701.                 var v=costs[i];
  2702.                 if (!w.canBeNegative && w.amount<v*mult) return false;
  2703.             }
  2704.             for (var i in costs)
  2705.             {
  2706.                 var w=G.things[i];
  2707.                 var v=costs[i]*mult;
  2708.                 w.earn(-v);
  2709.                 if (v!=0)
  2710.                 {
  2711.                     if (!out.produced[w.id]) out.produced[w.id]=0;
  2712.                     out.produced[w.id]-=v;
  2713.                 }
  2714.             }
  2715.             return out;
  2716.         }
  2717.         G.refundCosts=function(costs)
  2718.         {
  2719.             var out={produced:{}};
  2720.             for (var i in costs)
  2721.             {
  2722.                 var w=G.things[i];
  2723.                 var v=-costs[i];
  2724.                 if (!w.canBeNegative && w.amount<v) return false;
  2725.             }
  2726.             for (var i in costs)
  2727.             {
  2728.                 var w=G.things[i];
  2729.                 var v=-costs[i];
  2730.                 w.earn(-v);
  2731.                 if (v!=0)
  2732.                 {
  2733.                     if (!out.produced[w.id]) out.produced[w.id]=0;
  2734.                     out.produced[w.id]-=v;
  2735.                 }
  2736.             }
  2737.             return out;
  2738.         }
  2739.        
  2740.        
  2741.         /*=====================================================================================
  2742.         HELPERS
  2743.         =======================================================================================*/
  2744.        
  2745.         G.getCostsStr=function(costs,neutral,specialOutput)
  2746.         {
  2747.             var str='';
  2748.             var notEnough=0;
  2749.             var t=0;
  2750.             for (var i in costs)
  2751.             {
  2752.                 var w=G.things[i];
  2753.                 var v=costs[i];
  2754.                 if (v>w.amount && w.ps<=0) t=-1;
  2755.                 else if (w.ps>0 && t!=-1) t=Math.max(t,(v-w.amount)/w.ps);
  2756.                 var classes='cost';
  2757.                 if (!neutral && !w.canBeNegative && v>w.amount) {classes+=' notEnough';notEnough++;}
  2758.                 else if (!neutral) classes+=' hasEnough';
  2759.                 str+='<div class="'+classes+'">'+B(v)+' '+(v==1?w.name:w.plural)+'</div>';
  2760.             }
  2761.             if (t>0 && !neutral) str+='<div class="costTimeRemaining">(in '+sayTime(t*1000+750)+')</div>';
  2762.             if (!specialOutput) return str;
  2763.             else return {str:str,lacking:notEnough};
  2764.         }
  2765.        
  2766.         G.resolveIcon=function(icon,small)
  2767.         {
  2768.             //returns a bit of CSS
  2769.             var str='';
  2770.             if (icon)
  2771.             {
  2772.                 str='background-image:';
  2773.                 for (var ii in icon)
  2774.                 {
  2775.                     str+='url('+icon[ii].url+'),';
  2776.                 }
  2777.                 str=str.slice(0,-1);
  2778.                 str+=';background-position:';
  2779.                 for (var ii in icon)
  2780.                 {
  2781.                     str+=icon[ii].x+'px '+icon[ii].y+'px,';
  2782.                 }
  2783.                 str=str.slice(0,-1);
  2784.                 if (small)
  2785.                 {
  2786.                     str+=';background-size:';
  2787.                     for (var ii in icon)
  2788.                     {
  2789.                         if (icon[ii].tile) str+='auto,';
  2790.                         else str+='100%,';
  2791.                     }
  2792.                     str=str.slice(0,-1);
  2793.                 }
  2794.                 str+=';';
  2795.             }
  2796.             return str;
  2797.         }
  2798.        
  2799.         G.strToList=function(str)
  2800.         {
  2801.             //convert a string such as "3 gold, 1 banana, 11 monkeys" into an array of things [{w:Thing,v:3}...]
  2802.             var list={};
  2803.             var str=str.replaceAll(', and ',',');
  2804.             var str=str.replaceAll(' and ',',');
  2805.             var str=str.replaceAll(', ',',');
  2806.             str=str.split(',');
  2807.             for (var i in str)
  2808.             {
  2809.                 var bit=str[i].split(' ');
  2810.                 if (bit[1] && G.thingsByName[bit[1]])
  2811.                 {
  2812.                     var val=parseFloat(bit[0]);
  2813.                     var thing=bit[1];//G.thingsByName[bit[1]];
  2814.                     if (list[thing]) list[thing]+=val;
  2815.                     else list[thing]=val;
  2816.                 }
  2817.             }
  2818.             var list2=[];
  2819.             for (var i in list)
  2820.             {
  2821.                 list2.push({w:G.thingsByName[i],v:list[i]});
  2822.             }
  2823.             return list2;
  2824.         }
  2825.        
  2826.         G.strToThings=function(str)
  2827.         {
  2828.             //convert a string such as "monkeys, tagged:fruit" into an array of things [Thing...]
  2829.             var list=[];
  2830.             var str=str.replaceAll(', and ',',');
  2831.             var str=str.replaceAll(' and ',',');
  2832.             var str=str.replaceAll(', ',',');
  2833.             str=str.split(',');
  2834.             for (var i in str)
  2835.             {
  2836.                 var bit=str[i].split(':');
  2837.                 if (bit[0] && G.thingsByName[bit[0]] && list.indexOf(bit[0])==-1) list.push(bit[0]);
  2838.                 //TODO : tags
  2839.                 /*if (bit[1] && G.thingsByName[bit[0]])
  2840.                 {
  2841.                     var val=parseFloat(bit[0]);
  2842.                     var thing=bit[1];//G.thingsByName[bit[1]];
  2843.                     if (list[thing]) list[thing]+=val;
  2844.                     else list[thing]=val;
  2845.                 }*/
  2846.             }
  2847.             var list2=[];
  2848.             for (var i in list)
  2849.             {
  2850.                 list2.push(G.thingsByName[list[i]]);
  2851.             }
  2852.             return list2;
  2853.         }
  2854.        
  2855.         /*=====================================================================================
  2856.         PARSE EXPRESSION STRING TO FUNCTION
  2857.         =======================================================================================*/
  2858.         G.parseToFunc=function(cmd,dummyTest)
  2859.         {
  2860.             /*  this function takes a string and returns a function based on the string that can be safely executed
  2861.                 if dummyTest is true, test the syntax safely; if no syntax error, return 'ok'; otherwise, return the error text
  2862.                 examples :
  2863.                     G.parseToFunc('floor(gold/10)+3');
  2864.                     G.parseToFunc('max(random(2,luck),2)*2');
  2865.                 available operations :
  2866.                     a+b
  2867.                     a-b
  2868.                     a*b
  2869.                     a/b
  2870.                     floor(a)
  2871.                     round(a)
  2872.                     ceil(a)
  2873.                     roundr(a) (returns a rounded up or down randomly depending on its value; 0.1 is much more likely to be rounded down than up)
  2874.                     min(a,b)
  2875.                     max(a,b)
  2876.                     pow(a,b)
  2877.                     chance(a) chance(a%) (a is between 0 and 100; returns true if pass, false otherwise)
  2878.                     random(a) random(a,b)
  2879.                     frandom(a) frandom(a,b)
  2880.                     a=b a==b a is b
  2881.                     a!=b !(a=b) a isn't b
  2882.                     && and
  2883.                     || or
  2884.                     this (if the function is called with func(Thing,Thing.amount) this will return the Thing's amount)
  2885.                     thingKey (returns the Thing's amount if res or building; if upgrade or achiev, return 1 if owned, 0 if not)
  2886.                     $localVar (returns the value of the local variable with that name)
  2887.                     have thingKey (returns whether we have the upgrade or achiev, or if the res or building is above 0)
  2888.             */
  2889.            
  2890.             //put a space before and after every word and number
  2891.             var cmd2='';
  2892.             var inWord=0;
  2893.             cmd='('+cmd+')';
  2894.             for (var i=0;i<cmd.length;i++)
  2895.             {
  2896.                 var it=cmd.charAt(i);
  2897.                 if (G.Kalphanum.indexOf(it)!=-1 || it==':')
  2898.                 {
  2899.                     if (!inWord) cmd2+=' ';
  2900.                     inWord=1;
  2901.                 }
  2902.                 else
  2903.                 {
  2904.                     if (inWord) cmd2+=' ';
  2905.                     inWord=0;
  2906.                 }
  2907.                 cmd2+=it;
  2908.             }
  2909.             cmd2=cmd2.replace(/ +(?= )/g,'');//remove all multiple spaces
  2910.            
  2911.            
  2912.             var tokens=[];
  2913.             var str=STR2(cmd2);
  2914.             var ok=1;
  2915.             var prevKeyword='';
  2916.             var setKeyword=0;
  2917.            
  2918.             while(ok && str.val.length>0)
  2919.             {
  2920.                 var setKeyword=0;
  2921.                 if (str.gulpSymbol(',')) tokens.push(',');
  2922.                 else if (str.gulpSymbol('('))
  2923.                 {
  2924.                     if (prevKeyword=='floor') tokens.push('Math.floor(');
  2925.                     else if (prevKeyword=='round') tokens.push('Math.round(');
  2926.                     else if (prevKeyword=='ceil') tokens.push('Math.ceil(');
  2927.                     else if (prevKeyword=='roundr') tokens.push('randomFloor(');
  2928.                     else if (prevKeyword=='min') tokens.push('Math.min(');
  2929.                     else if (prevKeyword=='max') tokens.push('Math.max(');
  2930.                     else if (prevKeyword=='pow') tokens.push('Math.pow(');
  2931.                     else if (prevKeyword=='chance') tokens.push('luck(');
  2932.                     else if (prevKeyword=='random') tokens.push('rand(');
  2933.                     else if (prevKeyword=='frandom') tokens.push('frand(');
  2934.                     else tokens.push('(');
  2935.                 }
  2936.                 else if (str.gulpSymbol(')')) tokens.push(')');
  2937.                 else if (str.gulpSymbol('+')) tokens.push('+');
  2938.                 else if (str.gulpSymbol('-')) tokens.push('-');
  2939.                 else if (str.gulpSymbol('*')) tokens.push('*');
  2940.                 else if (str.gulpSymbol('/')) tokens.push('/');
  2941.                 else if (str.gulpSymbol('<=')) tokens.push('<=');
  2942.                 else if (str.gulpSymbol('>=')) tokens.push('>=');
  2943.                 else if (str.gulpSymbol('<')) tokens.push('<');
  2944.                 else if (str.gulpSymbol('>')) tokens.push('>');
  2945.                 else if (str.gulpSymbol('%)') || str.gulpSymbol('% )')) tokens.push(')');
  2946.                 else if (str.gulpSymbol('%')) tokens.push('%');
  2947.                 else if (str.gulpSymbol('isn \' t ') || str.gulpSymbol('!=')) tokens.push('!=');
  2948.                 else if (str.gulp('is') || str.gulpSymbol('==') || str.gulpSymbol('=')) tokens.push('==');
  2949.                 else if (str.gulpSymbol('!')) tokens.push('!');
  2950.                 else if (str.gulp('and')) tokens.push('&&');
  2951.                 else if (str.gulp('or')) tokens.push('||');
  2952.                 else if (str.gulp('this')) tokens.push(dummyTest?'V':'v');
  2953.                 else if (str.gulp('ItemsLeft')) tokens.push(' (G.itemsMax-G.itemsTotal) ');
  2954.                 else if (str.gulp('lit')) tokens.push(dummyTest?'V':'w?w.lit:0');
  2955.                 else if (str.gulp('dim')) tokens.push(dummyTest?'V':'w?(!w.lit):0');
  2956.                 else if (str.val.charAt(0)=='$')
  2957.                 {
  2958.                     str.gulpSymbol('$');
  2959.                     var localVar='$'+str.gulp();
  2960.                     localVar=G.validateVar(localVar);
  2961.                     if (dummyTest) tokens.push('V'); else tokens.push('(G.localVars[\''+localVar+'\']||0)');
  2962.                 }
  2963.                 else
  2964.                 {
  2965.                     var tkn=str.gulp();
  2966.                     //console.log(' doing ['+tkn+']');
  2967.                     if (    tkn=='have'
  2968.                         ||  tkn=='no'
  2969.                         ||  tkn=='floor'
  2970.                         ||  tkn=='round'
  2971.                         ||  tkn=='roundr'
  2972.                         ||  tkn=='ceil'
  2973.                         ||  tkn=='min'
  2974.                         ||  tkn=='max'
  2975.                         ||  tkn=='pow'
  2976.                         ||  tkn=='chance'
  2977.                         ||  tkn=='random'
  2978.                         ||  tkn=='frandom'
  2979.                     ){setKeyword=tkn;}
  2980.                     else if (G.thingsByName[tkn.split(':')[0]])
  2981.                     {
  2982.                         var tkn2=tkn.split(':')[1];
  2983.                         if (tkn2)
  2984.                         {
  2985.                             if (    tkn2=='clicks'
  2986.                                 ||  tkn2=='ps'
  2987.                                 ||  tkn2=='max'
  2988.                                 ||  tkn2=='earned'
  2989.                             ){}
  2990.                             else {ok=0;tokens.push(tkn);}
  2991.                         }
  2992.                         if (ok)
  2993.                         {
  2994.                             if (dummyTest) tokens.push('V');
  2995.                             else
  2996.                             {
  2997.                                 var thing=G.thingsByName[tkn.split(':')[0]];
  2998.                                 if (tkn2=='ps' && thing.type=='res') tokens.push('(G.things['+thing.id+'].ps)');
  2999.                                     else if (tkn2=='ps') tokens.push('(false)');
  3000.                                 else if (tkn2=='clicks' && (thing.type=='button' || thing.type=='shiny')) tokens.push('(G.things['+thing.id+'].clicks)');
  3001.                                     else if (tkn2=='clicks') tokens.push('(false)');
  3002.                                 else if (tkn2=='max' && (thing.type=='res' || thing.type=='building')) tokens.push('(G.things['+thing.id+'].maxAmount)');
  3003.                                     else if (tkn2=='max') tokens.push('(false)');
  3004.                                 else if (tkn2=='earned' && (thing.type=='res')) tokens.push('(G.things['+thing.id+'].earned)');
  3005.                                     else if (tkn2=='earned') tokens.push('(false)');
  3006.                                 else if (prevKeyword=='have')
  3007.                                 {
  3008.                                     if (thing.type=='res' || thing.type=='building') tokens.push('(G.things['+thing.id+'].amount>0)');
  3009.                                     else if (thing.type=='upgrade' || thing.type=='achiev') tokens.push('(G.things['+thing.id+'].owned==1)');
  3010.                                 }
  3011.                                 else if (prevKeyword=='no')
  3012.                                 {
  3013.                                     if (thing.type=='res' || thing.type=='building') tokens.push('(G.things['+thing.id+'].amount==0)');
  3014.                                     else if (thing.type=='upgrade' || thing.type=='achiev') tokens.push('(G.things['+thing.id+'].owned==0)');
  3015.                                 }
  3016.                                 else if (thing.type=='res' || thing.type=='building') tokens.push('G.things['+thing.id+'].amount');
  3017.                                 else if (thing.type=='upgrade' || thing.type=='achiev') tokens.push('(G.things['+thing.id+'].owned==1)');
  3018.                                 else {ok=0;tokens.push(tkn);}
  3019.                             }
  3020.                         }
  3021.                     }
  3022.                     else if (isNumber(tkn)) {tokens.push(parseFloat(tkn)+'');}
  3023.                     else {ok=0;tokens.push(tkn);}
  3024.                 }
  3025.                 if (setKeyword) prevKeyword=setKeyword; else prevKeyword='';
  3026.             }
  3027.             //console.log(' [ '+tokens.join(' ][ ')+' ]');
  3028.             var funcBody=tokens.join(' ');
  3029.             funcBody='return parseNum( '+funcBody+' );';
  3030.             if (!ok && tokens[tokens.length-1])
  3031.             {
  3032.                 //console.log(' ERROR : '+tokens[tokens.length-1]);
  3033.                 if (dummyTest) return 'Failed to parse "'+tokens[tokens.length-1]+'"';
  3034.                 else return false;
  3035.             }
  3036.             else
  3037.             {
  3038.                 if (dummyTest) funcBody='var V=1; '+funcBody;
  3039.                 else funcBody='var w=w||0;var v=v||0; '+funcBody;
  3040.                 //console.log(' '+funcBody);
  3041.                 var func=0;
  3042.                 var result=0;
  3043.                 try
  3044.                 {
  3045.                     func=new Function('w','v',funcBody);
  3046.                     if (dummyTest) result=func();
  3047.                     if (dummyTest) return 'ok'; else return func;
  3048.                     //console.log(' Result is '+result);
  3049.                 } catch (e)
  3050.                 {
  3051.                     if (true)//e instanceof SyntaxError)
  3052.                     {
  3053.                         if (dummyTest)
  3054.                         {
  3055.                             //console.log(funcBody);
  3056.                             //console.log(e);
  3057.                             return 'Error when executing function : "'+e.message+'".';
  3058.                         }
  3059.                         else return false;
  3060.                     }
  3061.                 }
  3062.             }
  3063.         }
  3064.        
  3065.         /*=====================================================================================
  3066.         PARSER
  3067.         =======================================================================================*/
  3068.         G.line=0;
  3069.         G.lineNums=[];
  3070.         G.foundError=false;
  3071.         G.context='';
  3072.         G.parseError=function(str)
  3073.         {
  3074.             var rawStr=str;
  3075.            
  3076.             if (!G.foundError)
  3077.             {
  3078.                 G.l.innerHTML=`
  3079.                     <div id="errorWrap">
  3080.                         <div id="error">
  3081.                             The game couldn't function due to the following problems :
  3082.                             <div id="errors"></div>
  3083.                             <small>You may want to contact the author of this game.</small>
  3084.                         </div>
  3085.                     </div>
  3086.                 `;
  3087.                 G.l.classList.add('on');
  3088.             }
  3089.            
  3090.             var str=str||'';
  3091.                 str=str.replaceAll('&','&amp;');
  3092.                 str=str.replaceAll('<','&lt;');
  3093.                 str=str.replaceAll('>','&gt;');
  3094.                 str=str.replaceAll('"','&quot;');
  3095.                 str=str.replaceAll('\'','&apos;');
  3096.            
  3097.             var div=document.createElement('div');
  3098.             div.className='error';
  3099.             div.innerHTML='&bull; ERROR '+(G.context||'(unidentified)')+' :<br>['+str+']';
  3100.             l('errors').appendChild(div);
  3101.             console.log('ERROR '+(G.context||'(unidentified)')+' : '+rawStr);
  3102.            
  3103.             G.foundError=true;
  3104.             return false;
  3105.         }
  3106.         G.shorten=function(str,len)
  3107.         {
  3108.             if (str.length<len) return str; else return str.substring(0,len)+'…';
  3109.         }
  3110.        
  3111.         //sanitizing functions
  3112.         //regexps are used scarcely because I don't trust myself with those
  3113.         G.Kalpha='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXZ';
  3114.         G.Kalphanum='.1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXZ';
  3115.         G.Knum='.1234567890';
  3116.         G.Kvar='1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXZ';
  3117.         G.reservedKeywords=['this','that','all','except','but','is','isn\'t','no','not','have','floor','round','ceil','max','min','include','by','and','or','for','per','in','if','end','else','name','desc','tag','tags','Box','Boxes','Building','Buildings','Resource','Resources','Button','Buttons','Shiny','Shinies','Upgrade','Upgrades','Item','Items','Achievement','Achievements','Setting','Settings','slot1','slot2','slot3','slot4','prop1','prop2','prop3','prop4'];
  3118.         G.checkId=function(str)
  3119.         {
  3120.             //raise an error if str is not alphanumeric or collides with a reserved word
  3121.             if (G.reservedKeywords.indexOf(str)!=-1) return G.parseError('"'+str+'" is a reserved keyword, pick another one!');
  3122.             else if (str.length>20) return G.parseError('"'+str+'" is longer than 20 characters, the maximum allowed size for keys.');
  3123.             else
  3124.             {
  3125.                 var fail=false;
  3126.                 var letterFound=false;
  3127.                 for (var i=0;i<str.length;i++)
  3128.                 {
  3129.                     var it=str.charAt(i);
  3130.                     if (G.Kalphanum.indexOf(it)==-1) {fail=true;break;}
  3131.                     if (G.Kalpha.indexOf(it)!=-1) {letterFound=true;}
  3132.                 }
  3133.                 if (fail || !letterFound) return G.parseError('"'+str+'" is not a valid key. A key can only contain letters and digits.');
  3134.             }
  3135.             return true;
  3136.         }
  3137.         G.validateId=function(ids)
  3138.         {
  3139.             //perform G.checkId on the declaring token of a new Thing (in the form *key1|key2|key3)
  3140.             var ids=ids.substring(1,ids.length).split('|');
  3141.             for (var i in ids)
  3142.             {
  3143.                 if (G.thingsByName[ids[i]]) return G.parseError('There is already something with the id "'+ids[i]+'" ('+G.thingsByName[ids[i]].name+').');
  3144.                 if (!G.checkId(ids[i])) return false;
  3145.             }
  3146.             return true;
  3147.         }
  3148.         G.Kclass=/^([a-z_]|-[a-z_-])[a-z\d_-]*$/i;
  3149.         G.validateClasses=function(str)
  3150.         {
  3151.             //raise an error if str isn't composed of alphanumeric words separated by spaces
  3152.             var bits=str.split(' ');
  3153.             for (var i in bits)
  3154.             {
  3155.                 if (!G.Kclass.test(bits[i])) return G.parseError('"'+bits[i]+'" is not a valid CSS class name.');
  3156.             }
  3157.             return str;
  3158.         }
  3159.         G.validateVar=function(str)
  3160.         {
  3161.             if (str.charAt(0)!='$') return G.parseError('"'+str+'" is not a valid variable name.');
  3162.             str=str.substring(1);
  3163.             if (str.length<=0) return G.parseError('"'+str+'" is too short for a variable name.');
  3164.             var fail=false;
  3165.             for (var i=0;i<str.length;i++)
  3166.             {
  3167.                 var it=str.charAt(i);
  3168.                 if (G.Kvar.indexOf(it)==-1) {fail=true;break;}
  3169.             }
  3170.             str='$'+str;
  3171.             if (fail) return G.parseError('"'+str+'" is not a valid variable name as it is not alphanumeric.');
  3172.             return str;
  3173.         }
  3174.        
  3175.         G.makeSafe=function(str)
  3176.         {
  3177.             //makes the string okay to use in html without triggering any tags, hopefully
  3178.             str=str||'';
  3179.             str=str.replaceAll('&','&amp;');
  3180.             str=str.replaceAll('<','&lt;');
  3181.             str=str.replaceAll('>','&gt;');
  3182.             str=str.replaceAll('"','&quot;');
  3183.             str=str.replaceAll('\'','&apos;');
  3184.             return str;
  3185.         }
  3186.         G.makeUnsafe=function(str)
  3187.         {
  3188.             str=str||'';
  3189.             str=str.replaceAll('&lt;','<');
  3190.             str=str.replaceAll('&gt;','>');
  3191.             str=str.replaceAll('&quot;','"');
  3192.             str=str.replaceAll('&apos;','\'');
  3193.             str=str.replaceAll('&amp;','&');
  3194.             return str;
  3195.         }
  3196.         G.makeHTML=function(str)
  3197.         {
  3198.             //G.makeSafe, plus additional stuff
  3199.             //replace pseudotags
  3200.             str=G.makeSafe(str);
  3201.             str=str.replace(/&lt;\/&gt;/gi,'<br>');
  3202.             str=str.replace(/&lt;\/\/&gt;/gi,'</div><div>');
  3203.             str=str.replace(/&lt;\.&gt;/gi,'</div><div>&bull; ');
  3204.             str=str.replace(/&lt;t&gt;/gi,'<div class="title">').replace(/&lt;\/t&gt;/gi,'</div>');
  3205.             str=str.replace(/&lt;b&gt;/gi,'<b>').replace(/&lt;\/b&gt;/gi,'</b>');
  3206.             str=str.replace(/&lt;i&gt;/gi,'<i>').replace(/&lt;\/i&gt;/gi,'</i>');
  3207.             str=str.replace(/&lt;u&gt;/gi,'<u>').replace(/&lt;\/u&gt;/gi,'</u>');
  3208.             str=str.replace(/&lt;q&gt;/gi,'<q>').replace(/&lt;\/q&gt;/gi,'</q>');
  3209.             str=str.replace(/&lt;#([0-9A-F]{3,6})&gt;/gi,'<span style="color:#$1;">').replace(/&lt;\/#&gt;/gi,'</span>');
  3210.             return str;
  3211.         }
  3212.        
  3213.         //grabbers
  3214.         G.grabThing=function(str)
  3215.         {
  3216.             //"bunny" will return the Thing with the key "bunny"
  3217.             if (str=='this') return 'this';
  3218.             else if (str.indexOf(':')!=-1)
  3219.             {
  3220.                 //return G.thingsByTag[???]
  3221.                 return G.parseError('Selectors ("'+str+'") do not work for this command.');
  3222.             }
  3223.             else if (!G.thingsByName[str]) return G.parseError('Nothing found with the key "'+str+'".');
  3224.             return G.thingsByName[str];
  3225.         }
  3226.         G.grabThings=function(str)
  3227.         {
  3228.             //output is always an array
  3229.             //"bunny" will return the Thing with the key "bunny"
  3230.             //"Upgrades" will return all Things with type "upgrade"
  3231.             //"tag:animal" will return all Things with the tag "animal"
  3232.             if (str=='this') return ['this'];
  3233.             else if (str.indexOf(':')!=-1)//selector
  3234.             {
  3235.                 var bits=str.split(':');
  3236.                 var type=0;
  3237.                 var tag=0;
  3238.                 var notTag=0;
  3239.                 var specials=[];
  3240.                 for (var i=0;i<bits.length;i++)
  3241.                 {
  3242.                     var me=bits[i];
  3243.                     if (me=='tag' && bits[i+1]) {i++;tag=G.makeSafe(bits[i]);}
  3244.                     else if (me=='notTag' && bits[i+1]) {i++;notTag=G.makeSafe(bits[i]);}
  3245.                     else if (me=='Buttons') type='button';
  3246.                     else if (me=='Resources') type='res';
  3247.                     else if (me=='Buildings') type='building';
  3248.                     else if (me=='Upgrades') type='upgrade';
  3249.                     else if (me=='Achievements') type='achiev';
  3250.                     else if (me=='Items') type='itemType';
  3251.                     else if (me=='Shinies') type='shiny';
  3252.                     else if (me=='All') {}
  3253.                     else if (   me=='owned'
  3254.                             ||  me=='notOwned'
  3255.                                 ) specials.push(me);
  3256.                     else if (me=='') {}
  3257.                     else return G.parseError('In the selector "'+str+'", failed to understand "'+me+'".');
  3258.                 }
  3259.                 var arr=[];
  3260.                 for (var i in G.things)
  3261.                 {
  3262.                     var me=G.things[i];
  3263.                     if (
  3264.                         (!type || me.type==type)
  3265.                     &&  (!tag || me.tags.includes(tag))
  3266.                     &&  (!notTag || !me.tags.includes(notTag))
  3267.                     ) arr.push(me);
  3268.                 }
  3269.                 if (specials.length>0)
  3270.                 {
  3271.                     var obj={w:arr};
  3272.                     for (var i in specials){obj[specials[i]]=1;}
  3273.                     return obj;
  3274.                 }
  3275.                 else return arr;
  3276.             }
  3277.             else if (!G.thingsByName[str]) return G.parseError('Nothing found with the key "'+str+'".');
  3278.             return [G.thingsByName[str]];
  3279.         }
  3280.         G.getThings=function(w,owner)
  3281.         {
  3282.             if (w.w)
  3283.             {
  3284.                 var arr=[];
  3285.                 for (var i in w.w)
  3286.                 {
  3287.                     var me=w.w[i];
  3288.                     if ((!w.owned || (me.amount>0 || me.owned))
  3289.                     &&  (!w.notOwned || (me.amount<=0 || !me.owned))
  3290.                     ) arr.push(me);
  3291.                 }
  3292.                 return arr;
  3293.             }
  3294.             else if (w[0]=='this') return [owner];
  3295.             //TODO : items
  3296.             else return w;
  3297.         }
  3298.        
  3299.         G.KisFunc=('+-*/=<>()%!,:').split('');
  3300.         G.isFunc=function(str)
  3301.         {
  3302.             //check if the string contains one of the above characters
  3303.             for (var i=0;i<G.KisFunc.length;i++){if (str.indexOf(G.KisFunc[i])!=-1) return true;}
  3304.             return false;
  3305.         }
  3306.         G.grabVar=function(str)
  3307.         {
  3308.             //"4" will return 4
  3309.             //"bunny" will return the Thing with the key "bunny"
  3310.             //"(4+bunnies*3)" will return a function that executes that operation
  3311.             //"$local" will return the local variable named "local" (or 0 if not found)
  3312.             if (str=='this') return 'this';
  3313.             if (str.charAt(0)!='(' && G.isFunc(str)) str='('+str+')';
  3314.             if (str.charAt(0)=='(' && str.charAt(str.length-1)==')')
  3315.             {
  3316.                 var success=G.parseToFunc(str,1);
  3317.                 if (success=='ok') return {type:'f',f:G.parseToFunc(str)};
  3318.                 return G.parseError('Syntax error in "'+str+'" : '+success);
  3319.             }
  3320.             if (str.charAt(0)=='$') return {type:'l',l:str};
  3321.             if (isNumber(str)) return parseFloat(str);
  3322.             if (!G.thingsByName[str]) return G.parseError('"'+str+'" is not a valid thing, local variable, number, or expression.');
  3323.             return G.thingsByName[str];
  3324.         }
  3325.         G.getVarValue=function(w,thing)
  3326.         {
  3327.             if (w=='this' && thing) return thing.amount;
  3328.             else if (w.type)
  3329.             {
  3330.                 var amount=0;
  3331.                 if (w.type=='building' || w.type=='res') amount=w.amount;
  3332.                 else if (w.type=='upgrade' || w.type=='achiev') amount=(w.owned?1:0);
  3333.                 if (w.type=='f')
  3334.                 {
  3335.                     if (!thing) amount=0;
  3336.                     else
  3337.                     {
  3338.                         if (thing.type=='building' || thing.type=='res') amount=thing.amount;
  3339.                         else if (thing.type=='upgrade' || thing.type=='achiev') amount=(thing.owned?1:0);
  3340.                         else if (thing.type=='button' || thing.type=='shiny') amount=thing.clicks;
  3341.                     }
  3342.                     return w.f(thing,amount);
  3343.                 }
  3344.                 else if (w.type=='l')
  3345.                 {
  3346.                     if (!G.localVars[w.l]) return 0;
  3347.                     else return G.localVars[w.l];
  3348.                 }
  3349.                 else return amount;
  3350.                 return 0;
  3351.             }
  3352.             else return w;
  3353.         }
  3354.        
  3355.         G.grabText=function(str)
  3356.         {
  3357.             var bits=[];
  3358.             var bit='';
  3359.             //chop into bits
  3360.             var inTkn=false;
  3361.             for (var i=0;i<str.length;i++)
  3362.             {
  3363.                 var c=str.charAt(i);
  3364.                 var add=false;
  3365.                 if (c=='[')
  3366.                 {
  3367.                     if (inTkn>0) add=true;
  3368.                     else
  3369.                     {
  3370.                         bits.push({t:G.makeHTML(bit)});
  3371.                         bit='';
  3372.                     }
  3373.                     inTkn++;
  3374.                 }
  3375.                 else if (c==']' && inTkn>0)
  3376.                 {
  3377.                     inTkn--;
  3378.                     if (inTkn>0) add=true;
  3379.                     else
  3380.                     {
  3381.                         //bits.push({c:bit});
  3382.                         if (bit.indexOf('?')==0)//[?(test)|(exp1)|(exp2)]=>if (test) is >0, return (exp1), else return (exp2)
  3383.                         {
  3384.                             bit=bit.substring(1).split('|');
  3385.                             var X=bit[0]||0;
  3386.                             var Y=bit[1]||'';
  3387.                             var Z=bit[2]||'';
  3388.                             bits.push({i:G.grabVar(X),w1:G.grabText(Y),w2:G.grabText(Z)});
  3389.                         }
  3390.                         else if (bit.indexOf('s?')==0)//[s?(exp)]=>if (exp) is !=1, return "s"
  3391.                         {
  3392.                             bit=bit.substring(2);
  3393.                             var X=bit||0;
  3394.                             bits.push({s:G.grabVar(X)});
  3395.                         }
  3396.                         else if (bit.indexOf('n:')==0)//[n:thingId]=>returns the name of the given thing
  3397.                         {
  3398.                             bit=bit.substring(2);
  3399.                             var X=bit||0;
  3400.                             if (X=='this') bits.push({ns:true});
  3401.                             else
  3402.                             {
  3403.                                 if (!G.thingsByName[X]) return G.parseError('"'+X+'" is not a valid thing.');
  3404.                                 bits.push({n:G.thingsByName[X]});
  3405.                             }
  3406.                         }
  3407.                         else if (bit.indexOf('p:')==0)//[p:thingId]=>returns the plural of the given thing
  3408.                         {
  3409.                             bit=bit.substring(2);
  3410.                             var X=bit||0;
  3411.                             if (X=='this') bits.push({ps:true});
  3412.                             else
  3413.                             {
  3414.                                 if (!G.thingsByName[X]) return G.parseError('"'+X+'" is not a valid thing.');
  3415.                                 bits.push({p:G.thingsByName[X]});
  3416.                             }
  3417.                         }
  3418.                         else bits.push({w:G.grabVar(bit)});//[(exp)]=>value of exp
  3419.                         bit='';
  3420.                     }
  3421.                 }
  3422.                 else add=true;
  3423.                 if (add) bit+=c;
  3424.             }
  3425.             if (bit!='') bits.push({t:G.makeHTML(bit)});
  3426.             //console.log(bits);
  3427.             return bits;
  3428.         }
  3429.         G.getTextValue=function(w,thing,wrapMode)
  3430.         {
  3431.             var str='';
  3432.             for (var i in w)
  3433.             {
  3434.                 var bit=w[i];
  3435.                 if (bit.t) str+=bit.t;//raw text
  3436.                 else if (bit.i) str+=G.getVarValue(bit.i,thing)?G.getTextValue(bit.w1,thing,1):G.getTextValue(bit.w2,thing,1);//ternary
  3437.                 else if (bit.s) str+=(G.getVarValue(bit.s,thing)!=1)?'s':'';//s if !=1
  3438.                 else if (bit.w) str+=B(G.getVarValue(bit.w,thing));//var
  3439.                 else if (bit.n) str+=bit.n.name;//name
  3440.                 else if (bit.p) str+=bit.n.plural;//plural
  3441.                 else if (bit.ns) str+=thing.name;//name self
  3442.                 else if (bit.ps) str+=thing.plural;//plural self
  3443.             }
  3444.             if (wrapMode==1) return str;
  3445.             else if (wrapMode==2) return '<div style="display:inline;">'+str+'</div>';
  3446.             return '<div>'+str+'</div>';
  3447.         }
  3448.        
  3449.         G.applyIncludes=function(str)
  3450.         {
  3451.             var out='';
  3452.             var lines=[];
  3453.             str=str.replaceAll('[i:','[include ');
  3454.             str=STR2(str);
  3455.             while (str.val.length>0)
  3456.             {
  3457.                 out+=str.gulpUntilSymbol('[include ',true);
  3458.                 if (str.val.length>0)
  3459.                 {
  3460.                     var bit=str.val;
  3461.                     var incStr='';
  3462.                     var inInclude=true;
  3463.                     var inStr=false;
  3464.                     var trailSpace=false;
  3465.                     for (var i=0;i<bit.length;i++)
  3466.                     {
  3467.                         var it=bit.charAt(i);
  3468.                         if (it==']' && !inStr) {inInclude=false;if (bit.charAt(i+1)==' '){trailSpace=true;};break;}
  3469.                         else if (it=='"' && inInclude) inStr=!inStr;
  3470.                         incStr+=it;
  3471.                     }
  3472.                    
  3473.                     var bits=STR2(incStr);
  3474.                     var inc=G.includes[bits.gulp()];
  3475.                    
  3476.                     var incContents=inc.mono?inc.text:inc.lines.join('///NEWLINE///');
  3477.                     var args={};
  3478.                     for (var i in inc.args){args[i]=inc.args[i];}
  3479.                     while(bits.val.length>0)
  3480.                     {
  3481.                         var arg=STR2(bits.gulp());
  3482.                         if (arg.gulpSymbol('%'))
  3483.                         {
  3484.                             var name=arg.gulpUntilSymbol('="',true);
  3485.                             var def=arg.gulpUntilSymbol('"',true);
  3486.                             if ((name in args) && def) args[name]=def;
  3487.                         }
  3488.                     }
  3489.                     for (var i in args)
  3490.                     {
  3491.                         incContents=incContents.replaceAll('[%'+i+']',args[i]||'???');
  3492.                     }
  3493.                     if (!inc.mono) {lines=incContents.split('///NEWLINE///');break;}
  3494.                     else out+=incContents;
  3495.                    
  3496.                     if (trailSpace) out+=' ';
  3497.                     str.gulpSymbol(incStr+']');
  3498.                 }
  3499.             }
  3500.             if (lines.length>0) return lines; else return [out];
  3501.         }
  3502.        
  3503.         G.parse=function(toParse)
  3504.         {
  3505.            
  3506.             var game={
  3507.                 name:'Untitled',
  3508.                 author:'Anonymous',
  3509.                 desc:'',
  3510.                 created:0,
  3511.                 updated:0,
  3512.                 version:0,
  3513.                 forumPost:0,
  3514.                 costRate:1.15,
  3515.                 refundRate:0.5,
  3516.                 css:'',
  3517.                 stylesheets:[],
  3518.                 spritesheets:{},
  3519.                 noParticles:false,
  3520.                 noBulkParticles:false,
  3521.             };
  3522.            
  3523.             G.foundError=false;
  3524.             G.context='when initializing';
  3525.            
  3526.             G.line=0;
  3527.            
  3528.             G.includes=[];
  3529.            
  3530.             for (var STEP=0;STEP<2;STEP++)//pre-parse on the first step, then turn into proper lines on the second
  3531.             {
  3532.                 if (STEP==0) G.context='when pre-parsing';
  3533.                 else G.context='when parsing';
  3534.                 var lines=toParse.split(String.fromCharCode(10));
  3535.                 var lines2=[];
  3536.                 var lineNum=0;
  3537.                 var lineNums=[];
  3538.                
  3539.                 var inComment=false;//inside a multiline comment
  3540.                
  3541.                 for (var i in lines)
  3542.                 {
  3543.                     var line=lines[i].trim();
  3544.                     if (line.length>0)
  3545.                     {
  3546.                         if (line.substring(0,2)=='/*') {inComment=true;}//multiline comment start
  3547.                         if (line.slice(-2)=='*/') {inComment=false;line='';/*line.slice(0,-2);*/}//multiline comment end
  3548.                         if (inComment || line.substring(0,2)=='//') {}//comment
  3549.                         else lines2.push(line);lineNums.push(lineNum);
  3550.                     }
  3551.                     lineNum++;
  3552.                 }
  3553.                 lines=lines2;
  3554.                 G.lineNums=lineNums;
  3555.                
  3556.                 if (STEP==1)
  3557.                 {
  3558.                     var i=0;
  3559.                     while(i<lines.length)
  3560.                     {
  3561.                         if (i<lines.length-1)
  3562.                         {
  3563.                             var next=lines[i+1];
  3564.                             var out=G.applyIncludes(next);
  3565.                             lines.splice(i+1,1);
  3566.                             for (var ii=out.length-1;ii>=0;ii--)
  3567.                             {
  3568.                                 lines.splice(i+1,0,out[ii]);
  3569.                             }
  3570.                         }
  3571.                         i++;
  3572.                     }
  3573.                 }
  3574.                
  3575.                 //chop into blocks
  3576.                 var blockNames={
  3577.                     'Let\'s make a game!':  'main',
  3578.                     'Settings':             'settings',
  3579.                     'CSS':                  'css',
  3580.                     'Includes':             'includes',
  3581.                     'Layout':               'layout',
  3582.                     'Resources':            'resources',
  3583.                     'Buttons':              'buttons',
  3584.                     'Buildings':            'buildings',
  3585.                     'Upgrades':             'upgrades',
  3586.                     'Items':                'items',
  3587.                     'Achievements':         'achievs',
  3588.                     'Shinies':              'shinies',
  3589.                 };
  3590.                 var contentBlocks=['Resources','Buttons','Buildings','Upgrades','Items','Achievements','Shinies'];
  3591.                 var blocksNamesById={};for (var i in blockNames){blocksNamesById[blockNames[i]]=i;}
  3592.                 var blocks={};
  3593.                 var block=0;
  3594.                 var prevBlock=0;
  3595.                 for (var i in lines)
  3596.                 {
  3597.                     var me=lines[i];
  3598.                     if (blockNames[me])
  3599.                     {
  3600.                         prevBlock=block;
  3601.                         block=blockNames[me];
  3602.                         if (!blocks[block]) blocks[block]=[];
  3603.                         if (contentBlocks.includes(me)) blocks[block].push('new block');
  3604.                     }
  3605.                     else if (block) {blocks[block].push(me);}
  3606.                 }
  3607.                
  3608.                 if (STEP==0 && blocks['includes'])
  3609.                 {
  3610.                     var thing=0;
  3611.                     var i=0;
  3612.                     //for (var i in blocks['includes'])
  3613.                     while(i<blocks['includes'].length)
  3614.                     {
  3615.                         var line=STR2(blocks['includes'][i]);
  3616.                         if (line.gulpSymbol('*include '))
  3617.                         {
  3618.                             if (thing!=0) G.includes[thing.name]=thing;
  3619.                             thing={};
  3620.                             thing.name=line.gulp();
  3621.                             thing.args={};
  3622.                             while(line.val.length>0)
  3623.                             {
  3624.                                 if (line.gulpSymbol(':'))//mono (only one line)
  3625.                                 {
  3626.                                     thing.mono=true;
  3627.                                     thing.text=line.val;
  3628.                                     G.includes[thing.name]=thing;
  3629.                                     thing=0;
  3630.                                 }
  3631.                                 else
  3632.                                 {
  3633.                                     var arg=STR2(line.gulp());
  3634.                                     if (arg.gulpSymbol('%'))
  3635.                                     {
  3636.                                         var name=arg.gulpUntilSymbol('="',true);
  3637.                                         var def=arg.gulpUntilSymbol('"',true);
  3638.                                         if (def) thing.args[name]=def;
  3639.                                         else thing.args[name]=0;
  3640.                                     }
  3641.                                 }
  3642.                             }
  3643.                             if (thing)//still in thing therefore not mono
  3644.                             {
  3645.                                 thing.lines=[];
  3646.                             }
  3647.                         }
  3648.                         else if (thing)
  3649.                         {
  3650.                             thing.lines.push(line.val);
  3651.                         }
  3652.                        
  3653.                         if (i<blocks['includes'].length-1)
  3654.                         {
  3655.                             var next=blocks['includes'][i+1];
  3656.                             var out=G.applyIncludes(next);
  3657.                             blocks['includes'].splice(i+1,1);
  3658.                             for (var ii=out.length-1;ii>=0;ii--)
  3659.                             {
  3660.                                 blocks['includes'].splice(i+1,0,out[ii]);
  3661.                             }
  3662.                         }
  3663.                         i++;
  3664.                         if (i>1000) {console.log('TOO MUCH INCLUDING HAPPENING!!! THERE IS A BUG SOMEWHERE PROBABLY AAAAAHHHH');i+=10000;}
  3665.                     }
  3666.                     if (thing!=0) G.includes[thing.name]=thing;
  3667.                 }
  3668.                 delete blocks['includes'];
  3669.                
  3670.                 if (STEP==0)
  3671.                 {
  3672.                     //default layout
  3673.                     if (!blocks['layout'] || blocks['layout'][0]=='use default' || blocks['layout'].length==0)
  3674.                     {
  3675.                         if (blocks['layout'] && blocks['layout'][0]=='use default') blocks['layout'].splice(0,1);
  3676.                         blocks['layout']=((
  3677. `*main
  3678.     contains:res, buttons
  3679.     *res
  3680.         contains:Resources
  3681.         class:fullWidth
  3682.     *buttons
  3683.         contains:Buttons
  3684. *store
  3685.     contains:buildings, upgrades
  3686.     *buildings
  3687.         contains:BulkDisplay, Buildings
  3688.         header:<t>Buildings</t>
  3689.         tooltip origin:left
  3690.     *upgrades
  3691.         contains:Upgrades
  3692.         header:<t>Upgrades</t>
  3693.         costs:hide
  3694.         names:hide`
  3695.                         ).split(String.fromCharCode(10))).concat(blocks['layout']);
  3696.                     }
  3697.                     //paste back into 1 string
  3698.                     toParse='';
  3699.                     for (var i in blocks)
  3700.                     {
  3701.                         toParse+=blocksNamesById[i]+String.fromCharCode(10);
  3702.                         toParse+=blocks[i].join(String.fromCharCode(10))+String.fromCharCode(10);
  3703.                     }
  3704.                 }
  3705.             }
  3706.            
  3707.             if (lines[0]!='Let\'s make a game!') return G.parseError('A valid source file must begin with "Let\'s make a game!"');
  3708.            
  3709.             var block='main';
  3710.             var thing=0;//not an actual Thing at this stage; this is an anonymous object that gets filled with properties as we define them; when we change blocks or start declaring another Thing, this gets turned into an actual Thing
  3711.             var virtualLineNum=0;
  3712.            
  3713.             var inEffects=false;//inside a thing's effects: / end effects
  3714.             var effectsType=0;//the type of the current effect block; can be "click", "tick", "sell", "c:customEffectName" etc
  3715.             var ends=0;//+1 everytime we start a new effect or condition block, -1 if we find "end"
  3716.            
  3717.             G.baseThing=0;//this is a pseudo-thing which can be defined in a section, making all subsequent Things use it as a base
  3718.            
  3719.            
  3720.             for (var block in blocks)
  3721.             {
  3722.                 thing=0;ok=1;G.baseThing=0;
  3723.                 for (var i in blocks[block])
  3724.                 {
  3725.                     G.line=virtualLineNum;
  3726.                     var me=blocks[block][i];//lines[i];
  3727.                     //G.context='on line '+(G.lineNums[G.line]||'?')+', "'+G.shorten(me,30)+'"';
  3728.                     G.context='on line "'+G.shorten(me,30)+'"';//line numbers are unreliable in the current system
  3729.                    
  3730.                     var ok=0;
  3731.                    
  3732.                     //if (me.substring(0,2)=='/*') {ok=1;inComment=true;}//multiline comment start
  3733.                     //if (me.slice(-2)=='*/') {ok=1;inComment=false;}//multiline comment end
  3734.                     //if (ok || inComment || me.substring(0,2)=='//') {ok=1;}//comment
  3735.                     if (true)
  3736.                     {
  3737.                         var prop=me.substring(0,me.indexOf(':')).toLowerCase();
  3738.                         var val=me.substring(me.indexOf(':')+1);
  3739.                         //we don't run G.makeSafe on the initial val because some commands, such as expressions, may use different handlers
  3740.                         if (me=='new block') {G.baseThing=0;ok=1;}
  3741.                         else if (block=='main')
  3742.                         {
  3743.                             ok=1;
  3744.                             if (prop=='name') game.name=G.makeSafe(val);
  3745.                             else if (prop=='by' || prop=='author') game.author=G.makeSafe(val);
  3746.                             else if (prop=='desc') game.desc=val;
  3747.                             else if (prop=='created') game.created=G.makeSafe(val);
  3748.                             else if (prop=='updated') game.updated=G.makeSafe(val);
  3749.                             else if (prop=='version') game.version=parseFloat(val);
  3750.                             else if (prop=='forum post') game.forumPost=parseInt(val);
  3751.                             else ok=0;
  3752.                         }
  3753.                         else if (block=='settings')
  3754.                         {
  3755.                             ok=1;
  3756.                             if (prop=='background')
  3757.                             {
  3758.                                 game.background=G.makeSafe(val);
  3759.                             }
  3760.                             else if (prop=='tiling background')
  3761.                             {
  3762.                                 game.background=G.makeSafe(val);
  3763.                                 game.backgroundTile=true;
  3764.                             }
  3765.                             else if (prop=='spritesheet')
  3766.                             {
  3767.                                 var sprites=G.makeSafe(val).split(', ');
  3768.                                 game.spritesheets[sprites[0]]={name:sprites[0],w:parseInt(sprites[1].split(' by ')[0]),h:parseInt(sprites[1].split(' by ')[1]),url:sprites[2]};
  3769.                             }
  3770.                             else if (prop=='stylesheet')
  3771.                             {
  3772.                                 game.stylesheets.push(val);
  3773.                             }
  3774.                             else if (prop=='bloom')
  3775.                             {
  3776.                                 G.bloomFilter=Math.min(100,Math.max(0,parseFloat(val)))/200;
  3777.                             }
  3778.                             else if (prop=='building cost increase') game.costRate=parseFloat(val.replace('%',''))/100;
  3779.                             else if (prop=='building cost refund') game.refundRate=parseFloat(val.replace('%',''))/100;
  3780.                             else if (me=='no particles') game.noParticles=true;
  3781.                             else if (me=='no bulk particles') game.noBulkParticles=true;
  3782.                         }
  3783.                         else if (block=='css')
  3784.                         {
  3785.                             ok=1;
  3786.                             if (me) game.css+=''+/*G.makeSafe*/(me)+String.fromCharCode(10);
  3787.                         }
  3788.                         else if (block=='layout' || block=='resources' || block=='buttons' || block=='shinies' || block=='buildings' || block=='upgrades' || block=='items' || block=='achievs')
  3789.                         {
  3790.                             //create and edit things
  3791.                             ok=1;
  3792.                             var effects=thing.effects;
  3793.                             if (me.charAt(0)=='*')
  3794.                             {
  3795.                                 G.createThing(thing);
  3796.                                
  3797.                                 if (me=='*TEMPLATE'){}
  3798.                                 else if (!G.validateId(me)) return false;
  3799.                                
  3800.                                 var type='';
  3801.                                 if (block=='layout') type='box';
  3802.                                 if (block=='resources') type='res';
  3803.                                 else if (block=='buttons') type='button';
  3804.                                 else if (block=='buildings') type='building';
  3805.                                 else if (block=='upgrades') type='upgrade';
  3806.                                 else if (block=='items') type='itemType';
  3807.                                 else if (block=='achievs') type='achiev';
  3808.                                 else if (block=='shinies') type='shiny';
  3809.                                 thing={type:type};
  3810.                                 thing.ids=me;
  3811.                                
  3812.                                 if (thing.type=='box')
  3813.                                 {
  3814.                                     thing.showCosts=true;
  3815.                                     thing.showNames=true;
  3816.                                     thing.showIcons=true;
  3817.                                     thing.showPs=true;
  3818.                                 }
  3819.                                 else
  3820.                                 {
  3821.                                     thing.effects={};
  3822.                                     /*
  3823.                                         .effects is groups of effects by effectType
  3824.                                         ie.:
  3825.                                         .effects={
  3826.                                             'click':['effect text 1','effect text 2'],
  3827.                                             'tick':['effect text 1','effect text 2'],
  3828.                                         };
  3829.                                         the effect texts are parsed into actual effects later on
  3830.                                     */
  3831.                                     thing.costs=[];
  3832.                                     thing.reqs=[];
  3833.                                     thing.costRate=game.costRate;
  3834.                                     thing.refundRate=game.refundRate;
  3835.                                    
  3836.                                     if (thing.type=='shiny')
  3837.                                     {
  3838.                                         thing.freq=0;
  3839.                                         thing.freqV=0;
  3840.                                         thing.dur=10;//default to 10 secs duration
  3841.                                         thing.moves=[];
  3842.                                     }
  3843.                                 }
  3844.                             }
  3845.                             //boxes only
  3846.                             else if (!thing) G.parseError('Trying to edit something, but there is nothing to edit.');
  3847.                             else if (type=='box')
  3848.                             {
  3849.                                 if (prop=='contains')
  3850.                                 {
  3851.                                     if (!thing.children){thing.children=[];}
  3852.                                     val=G.makeSafe(val).split(', ');
  3853.                                     for (var ii in val){thing.children.push(val[ii]);}
  3854.                                 }
  3855.                                 else if (prop=='in')
  3856.                                 {
  3857.                                     thing.isIn=G.makeSafe(val);
  3858.                                 }
  3859.                                 else if (prop=='costs') thing.showCosts=(val=='show'?true:false);
  3860.                                 else if (prop=='names') thing.showNames=(val=='show'?true:false);
  3861.                                 else if (prop=='icons') thing.showIcons=(val=='show'?true:false);
  3862.                                 else if (prop=='ps') thing.showPs=(val=='show'?true:false);
  3863.                                 else if (prop=='header') thing.header=val;
  3864.                                 else if (prop=='footer') thing.footer=val;
  3865.                                 else if (prop=='class') thing.classes=G.validateClasses(val);
  3866.                                 else if (prop=='tooltip origin' && (val=='top' || val=='bottom' || val=='left' || val=='right')) thing.tooltipOrigin=G.makeSafe(val);
  3867.                                 else if (prop=='tooltip class') thing.tooltipClasses=G.validateClasses(val);
  3868.                                 else if (me=='no tooltip') thing.noTooltip=true;
  3869.                                 else ok=0;
  3870.                             }
  3871.                             //general
  3872.                             else if (prop=='name') {val=val.split('|');thing.name=G.makeSafe(val[0]);thing.plural=(G.makeSafe(val[1])||G.makeSafe(val[0]));thing.customName=true;}
  3873.                             else if (prop=='desc') thing.desc=val;
  3874.                             else if (prop=='text') thing.text=val;
  3875.                             else if (prop=='tag' || prop=='tags') thing.tags=G.makeSafe(val);
  3876.                             else if (prop=='class') thing.classes=G.validateClasses(val);
  3877.                             else if (prop=='icon class') thing.iconClasses=G.validateClasses(val);
  3878.                             else if (prop=='icon') thing.icon=G.makeSafe(val);
  3879.                             else if (prop=='start with' && (thing.type=='res' || thing.type=='building')) thing.startWith=parseFloat(val);
  3880.                             else if ((me=='start with' || me=='owned') && (thing.type=='upgrade' || thing.type=='achiev')) thing.startWith=true;
  3881.                             else if (prop=='is always' && (thing.type=='res' || thing.type=='building')) {thing.isAlways=val;}
  3882.                             else if (me=='can be negative' && (thing.type=='res')) {thing.canBeNegative=true;}
  3883.                             else if (me=='show max' && (thing.type=='res' || thing.type=='building')) thing.showMax=true;
  3884.                             else if (me=='show earned' && (thing.type=='res')) thing.showEarned=true;
  3885.                             else if (me=='show clicks' && thing.type=='button') thing.showClicks=true;
  3886.                             else if (prop=='cost') thing.costs.push((val));
  3887.                             else if (prop=='cost increase') thing.costRate=parseFloat(val.replace('%',''))/100;
  3888.                             else if (prop=='cost refund') thing.refundRate=parseFloat(val.replace('%',''))/100;
  3889.                             else if (prop=='req') thing.reqs.push((val));
  3890.                             else if (me=='unlocked') thing.unlocked=true;//(val=='true' || val=='yes');
  3891.                             else if (me=='no text') thing.noText=true;
  3892.                             else if (me=='no click') thing.noClick=true;
  3893.                             else if (me=='shown') thing.show=1;
  3894.                             else if (me=='hidden') thing.show=0;
  3895.                             else if (me=='lit') thing.lit=1;
  3896.                             else if (me=='dim') thing.lit=0;
  3897.                             else if (me=='always hidden') thing.alwaysHidden=1;
  3898.                             else if (me=='hidden when 0') thing.hiddenWhen0=true;
  3899.                             else if (me=='no buy') thing.noBuy=true;
  3900.                             else if (prop=='limit' && thing.type=='building') thing.limit=val;
  3901.                             else if (prop=='movement' && thing.type=='shiny') thing.moves=G.makeSafe(val).split(' ');
  3902.                             else if (prop=='frequency' && thing.type=='shiny') thing.freq=parseFloat(val);
  3903.                             else if (prop=='frequency variation' && thing.type=='shiny') thing.freqV=parseFloat(val);
  3904.                             else if (prop=='duration' && thing.type=='shiny') thing.dur=parseFloat(val);
  3905.                             //effects
  3906.                             else if (prop=='passive' || prop.indexOf('on ')==0)
  3907.                             {
  3908.                                 if (inEffects || ends>0) G.parseError('Attempted to start an effect inside another effect.');
  3909.                                 else
  3910.                                 {
  3911.                                     ends++;
  3912.                                     inEffects=true;
  3913.                                     var type='tick';//"passive" just redirects to "on tick"
  3914.                                     if (prop!='passive') type=prop.substring(3);
  3915.                                     /*if (  type=='passive'
  3916.                                     ||      type=='click'
  3917.                                     ||      type=='tick'
  3918.                                     ||      type=='start'
  3919.                                     ||      type=='save'
  3920.                                     ||      type=='load'
  3921.                                     ||      type=='earn'
  3922.                                     ||      type=='lose'
  3923.                                     ) effectsType=type;
  3924.                                     else G.parseError('The effect type "'+type+'" was not recognized.');*/
  3925.                                     if (G.validateId(type)) effectsType=type; else G.parseError('The effect type "'+type+'" is invalid.');
  3926.                                     if (val && val.length>0)//1-line effect
  3927.                                     {
  3928.                                         if (!thing.effects[effectsType]) thing.effects[effectsType]=[];
  3929.                                         thing.effects[effectsType].push(val);
  3930.                                         ends--;inEffects=false;effectsType=0;
  3931.                                     }
  3932.                                     else
  3933.                                     {
  3934.                                         if (!thing.effects[effectsType]) thing.effects[effectsType]=[];
  3935.                                     }
  3936.                                 }
  3937.                             }
  3938.                             else if (me=='end')
  3939.                             {
  3940.                                 ends--;
  3941.                                 if (ends>0 && effectsType)
  3942.                                 {
  3943.                                     //end to a condition inside an effect block
  3944.                                     thing.effects[effectsType].push(me);
  3945.                                 }
  3946.                                 else if (ends==0 && effectsType)
  3947.                                 {
  3948.                                     //end to an effect block
  3949.                                     //if (!thing.effects[effectsType]) thing.effects[effectsType]=[];
  3950.                                     //thing.effects[effectsType].push(val);
  3951.                                     inEffects=false;effectsType=0;
  3952.                                 }
  3953.                                 else if (ends<0) G.parseError('Tried to end conditions or effects when there were none started.');
  3954.                             }
  3955.                             else if (inEffects && effectsType)
  3956.                             {
  3957.                                 if (me.indexOf('if (')==0)
  3958.                                 {
  3959.                                     if (me.charAt(me.length-1)==')') ends++;//only ends++ for multi-line condition blocks
  3960.                                 }
  3961.                                 thing.effects[effectsType].push(me);
  3962.                             }
  3963.                             else if (me.indexOf('if (')==0 || me.indexOf('else if (')==0) G.parseError('Tried to start a condition outside of an effect block.');
  3964.                             //visual
  3965.                             else if (prop=='tooltip origin' && (val=='top' || val=='bottom' || val=='left' || val=='right')) thing.tooltipOrigin=G.makeSafe(val);
  3966.                             else if (prop=='tooltip class') thing.tooltipClasses=G.validateClasses(val);
  3967.                             else if (me=='no tooltip') thing.noTooltip=true;
  3968.                             else ok=0;
  3969.                         }
  3970.                     }
  3971.                     if (G.foundError || !ok) return G.parseError('Could not parse "'+G.shorten(me,30)+'".');
  3972.                     virtualLineNum++;
  3973.                 }
  3974.                 G.createThing(thing);
  3975.                 if (inEffects || ends>0) return G.parseError('An effects block was not closed properly.');
  3976.                 G.baseThing=0;
  3977.             }
  3978.            
  3979.             G.spritesheets=game.spritesheets||{};
  3980.            
  3981.            
  3982.             //turn text into valid text objects
  3983.             if (game.desc) game.desc=G.grabText(game.desc);
  3984.             var props=['header','footer'];
  3985.             for (var i in G.boxes)
  3986.             {
  3987.                 var me=G.boxes[i];
  3988.                 G.context='when parsing text for the box "'+G.shorten(me.key,30)+'"';
  3989.                 for (var ii in props)
  3990.                 {
  3991.                     if (me[props[ii]]) me[props[ii]]=G.grabText(me[props[ii]]);
  3992.                 }
  3993.             }
  3994.             var props=['desc','text'];
  3995.             for (var i in G.things)
  3996.             {
  3997.                 var me=G.things[i];
  3998.                 G.context='when parsing text for the thing "'+G.shorten(me.name,30)+'"';
  3999.                 for (var ii in props)
  4000.                 {
  4001.                     if (me[props[ii]]) me[props[ii]]=G.grabText(me[props[ii]]);
  4002.                 }
  4003.             }
  4004.            
  4005.            
  4006.             //clean icons, costs, reqs and effects
  4007.            
  4008.             var gulpCond=function(str)
  4009.             {
  4010.                 //get whatever is within the next "if ()", parse it into a function and return that function
  4011.                 if (str.gulp('if') && str.gulpSymbol('('))
  4012.                 {
  4013.                     var parens=1;
  4014.                     var cond='';
  4015.                     //find matching parens
  4016.                     for (var i=0;i<str.val.length;i++)
  4017.                     {
  4018.                         var it=str.val.charAt(i);
  4019.                         if (it=='(') parens++;
  4020.                         else if (it==')') parens--;
  4021.                         if (parens==0) {break;}
  4022.                         cond+=it+'';
  4023.                     }
  4024.                     if (cond!='')
  4025.                     {
  4026.                         str.gulpSymbol(cond+')');
  4027.                         return G.grabVar('('+cond+')');
  4028.                     }
  4029.                 }
  4030.                 return false;
  4031.             }
  4032.            
  4033.             for (var i in G.things)
  4034.             {
  4035.                 var me=G.things[i];
  4036.                
  4037.                 if (me.icon)
  4038.                 {
  4039.                     G.context='when getting the icon for the thing "'+G.shorten(me.name,30)+'"';
  4040.                     var icons=[];
  4041.                     var icon=me.icon.split(' ');
  4042.                     for (var ii in icon)
  4043.                     {
  4044.                         if (icon[ii].charAt(icon[ii].length-1)==']')//tile
  4045.                         {
  4046.                             var sheet=G.spritesheets[icon[ii].split('[')[0]];
  4047.                             var coords=icon[ii].substring(icon[ii].indexOf('[')+1,icon[ii].indexOf(']')).split(',');
  4048.                             icons.push({tile:true,url:sheet.url,x:-parseInt(coords[0])*sheet.w,y:-parseInt(coords[1])*sheet.h});
  4049.                         }
  4050.                         else icons.push({url:icon[ii],x:0,y:0});//plain image
  4051.                     }
  4052.                     me.icon=icons;
  4053.                 }
  4054.                
  4055.                 if (me.type=='shiny')
  4056.                 {
  4057.                     G.context='when preparing the shiny "'+G.shorten(me.name,30)+'"';
  4058.                     var moves={};
  4059.                     var recMoves=('anywhere,onRight,onLeft,onTop,onBottom,onMouse,onThing,onBox,followMouse,followMouseSlow,wiggle,pulse,randomAngle,spinCW,spinCCW,spinRandom,fade,grow,shrink,growShrink,moveRandom,moveLeft,moveRight,moveTop,moveBottom,bobVertical,bobHorizontal,bounce').split(',');
  4060.                     var wMoves=('onThing,onBox').split(',');//moves that accept a thing instead of a number
  4061.                     for (var ii in me.moves)
  4062.                     {
  4063.                         var it=me.moves[ii].split(':');
  4064.                         if (recMoves.indexOf(it[0])==-1) G.parseError('"'+it[0]+'" is not a recognized movement type.');
  4065.                         else if(wMoves.indexOf(it[0])!=-1 && !G.thingsByName[it[1]]) G.parseError('The movement type "'+me.moves[ii]+'" has an invalid parameter.');
  4066.                         else if(wMoves.indexOf(it[0])!=-1) moves[it[0]]=G.thingsByName[it[1]];
  4067.                         else moves[it[0]]=parseFloat(it[1]||0);
  4068.                     }
  4069.                     me.moves=moves;
  4070.                 }
  4071.                
  4072.                 if (me.noClick)
  4073.                 {
  4074.                     if (!me.classes) me.classes='noClick';
  4075.                     else me.classes+=' noClick';
  4076.                 }
  4077.                
  4078.                 G.context='when preparing the thing "'+G.shorten(me.name,30)+'"';
  4079.                 if (me.isAlways) me.isAlways=G.grabVar('('+me.isAlways+')').f;
  4080.                 if (me.limit) me.limit=G.grabVar('('+me.limit+')').f;
  4081.                
  4082.                 var condStack=[];//the first one is the condition effect currently being filled
  4083.                
  4084.                 //breaking up one-line effects and pre-parsing
  4085.                 for (var effectType in me.effects)
  4086.                 {
  4087.                     G.context='when parsing the effect block "'+G.shorten(effectType,30)+'" for the thing "'+G.shorten(me.name,30)+'"';
  4088.                     var list=[];
  4089.                     var effs=me.effects[effectType];
  4090.                     var elses=0;
  4091.                     for (var ii in effs)
  4092.                     {
  4093.                         var cond=0;
  4094.                         var str=STR2(effs[ii]);
  4095.                         if (str.val=='end' && elses>0)
  4096.                         {
  4097.                             //double ends if we're closing an else
  4098.                             for (var iii=0;iii<elses;iii++) list.push({type:'?',eff:'end'});
  4099.                             elses--;
  4100.                         }
  4101.                         if (str.val=='else')
  4102.                         {
  4103.                             list.push({type:'else',effs:[]});
  4104.                             str.val='';
  4105.                             elses++;
  4106.                         }
  4107.                         else
  4108.                         {
  4109.                             var isElse=str.gulp('else');
  4110.                             if (isElse) elses++;
  4111.                             cond=gulpCond(str);
  4112.                             if (cond.type && cond.type=='f')
  4113.                             {
  4114.                                 list.push({type:(isElse?'else':'if'),cond:cond.f,effs:[],or:[]});
  4115.                             }
  4116.                         }
  4117.                         if (str.val.length>0)
  4118.                         {
  4119.                             list.push({type:'?',eff:str.val});
  4120.                             if (cond) list.push({type:'?',eff:'end'});
  4121.                         }
  4122.                         if (G.foundError) return false;
  4123.                     }
  4124.                     me.effects[effectType]=list;
  4125.                 }
  4126.                
  4127.                 for (var effectType in me.effects)
  4128.                 {
  4129.                     G.context='when parsing the effect block "'+G.shorten(effectType,30)+'" for the thing "'+G.shorten(me.name,30)+'"';
  4130.                     var list=[];
  4131.                     var effs=me.effects[effectType];
  4132.                     for (var ii in effs)
  4133.                     {
  4134.                         var eff=effs[ii];
  4135.                         if (eff.type=='if' || eff.type=='else')
  4136.                         {
  4137.                             if (eff.type=='else' && condStack.length>0) condStack[0].or.push(eff);
  4138.                             else if (condStack.length>0) condStack[0].effs.push(eff);
  4139.                             else list.push(eff);
  4140.                             condStack.unshift(eff);
  4141.                         }
  4142.                         if (eff.type=='?')
  4143.                         {
  4144.                             var str=STR2(eff.eff);
  4145.                             delete eff.eff;
  4146.                            
  4147.                             var add=true;
  4148.                            
  4149.                             var cmd=str.val;
  4150.                            
  4151.                             if (str.val=='end')
  4152.                             {
  4153.                                 add=false;
  4154.                                 if (condStack.length>0) condStack.shift();
  4155.                                 eff.type='end';
  4156.                             }
  4157.                             else
  4158.                             {
  4159.                                 var whatDo=0;
  4160.                                 if (str.gulpSymbol('log('))
  4161.                                 {
  4162.                                     var classes=G.validateClasses(str.gulpUntilSymbol(')'));
  4163.                                     eff={type:'log',w:G.grabText(str.gulpUntil()),classes:classes};
  4164.                                 }
  4165.                                 else if (str.gulp('log')) eff={type:'log',w:G.grabText(str.gulpUntil())};
  4166.                                 else if (str.val==('clear log') || str.val==('clean log')) eff={type:'clear log'};
  4167.                                 else if (str.gulp('toast')) eff={type:'toast',w:G.grabText(str.gulpUntil())};
  4168.                                 else if (str.gulp('echo')) eff={type:'echo',w:G.grabText(str.gulpUntil())};
  4169.                                 else if (str.gulp('eval')) eff={type:'eval',w:str.gulpUntil()};
  4170.                                
  4171.                                 else if (str.gulp('do'))
  4172.                                 {eff={type:'do'};whatDo='do X with Ys';}
  4173.                            
  4174.                                 else if (str.gulp('anim icon')) eff={type:'animicon',w:G.validateClasses(str.gulpUntil())};
  4175.                                 else if (str.gulp('anim')) eff={type:'anim',w:G.validateClasses(str.gulpUntil())};
  4176.                                
  4177.                                 else if (str.gulp('light')) {eff={type:'light'};whatDo='do Xs';}
  4178.                                 else if (str.gulp('dim')) {eff={type:'dim'};whatDo='do Xs';}
  4179.                                 else if (str.gulp('show')) {eff={type:'show'};whatDo='do Xs';}
  4180.                                 else if (str.gulp('hide')) {eff={type:'hide'};whatDo='do Xs';}
  4181.                            
  4182.                                 else if (str.gulp('spawn'))
  4183.                                 {eff={type:'spawn'};whatDo='do X';}
  4184.                                
  4185.                                 else if (str.gulp('yield') || str.gulp('gain') || str.gulp('win') || str.gulp('get'))
  4186.                                 {eff={type:'yield'};whatDo='do X Ys';}
  4187.                                
  4188.                                 else if (str.gulp('lose'))
  4189.                                 {eff={type:'lose'};whatDo='do X Ys';}
  4190.                            
  4191.                                 else if (str.gulp('grant'))
  4192.                                 {eff={type:'grant'};whatDo='do X Y';}
  4193.                            
  4194.                                 else if (str.gulp('increase cost of'))
  4195.                                 {eff={type:'increase cost'};whatDo='change Xs by Y';}
  4196.                                 else if (str.gulp('lower cost of'))
  4197.                                 {eff={type:'lower cost'};whatDo='change Xs by Y';}
  4198.                                 else if (str.gulp('multiply cost of'))
  4199.                                 {eff={type:'multiply cost'};whatDo='change Xs by Y';}
  4200.                                 else if (str.gulp('multiply refund of'))
  4201.                                 {eff={type:'multiply refund'};whatDo='change Xs by Y';}
  4202.                            
  4203.                                 else if (str.gulp('increase yield of') || str.gulp('increase gain of'))
  4204.                                 {eff={type:'increase yield'};whatDo='change Xs by Y';}
  4205.                                 else if (str.gulp('lower yield of') || str.gulp('lower gain of'))
  4206.                                 {eff={type:'lower yield'};whatDo='change Xs by Y';}
  4207.                                 else if (str.gulp('multiply yield of') || str.gulp('multiply gain of'))
  4208.                                 {eff={type:'multiply yield'};whatDo='change Xs by Y';}
  4209.                            
  4210.                                 else if (str.gulp('multiply duration of'))
  4211.                                 {eff={type:'multiply duration'};whatDo='change Xs by Y';}
  4212.                                 else if (str.gulp('multiply frequency of'))
  4213.                                 {eff={type:'multiply frequency'};whatDo='change Xs by Y';}
  4214.                            
  4215.                                 if (eff.type=='?')
  4216.                                 {
  4217.                                     //this one is a bit annoying
  4218.                                     var tmp=STR2(str.val);
  4219.                                     var type=tmp.gulp();
  4220.                                     if (type=='increase' || type=='lower' || type=='multiply')
  4221.                                     {
  4222.                                         var Z=tmp.gulp();
  4223.                                         if (tmp.gulp('yield of') || tmp.gulp('gain of'))
  4224.                                         {
  4225.                                             eff={type:type+' yield for'};whatDo='change Zs for Xs by Y';
  4226.                                             str.val=Z+' for '+tmp.val;
  4227.                                         }
  4228.                                     }
  4229.                                 }
  4230.                                
  4231.                                 if (eff.type=='?')//still nothing?
  4232.                                 {
  4233.                                     //test for "X is Y" or "X=Y"
  4234.                                     str.val=str.val.replace(' = ','=');
  4235.                                     str.val=str.val.replace('=',' = ');
  4236.                                     var bits=str.val.split(' ');
  4237.                                     if (bits[1] && (bits[1]=='is' || bits[1]=='are' || bits[1]=='=') && bits[0].length>0 && bits[2])
  4238.                                     {
  4239.                                         bits[1]='=';
  4240.                                         str.val=bits.join(' ');
  4241.                                         if (str.val.charAt(0)=='$') {eff={type:'set var'};whatDo='$X=Y';}
  4242.                                         else {eff={type:'set'};whatDo='X=Y';}
  4243.                                     }
  4244.                                 }
  4245.                                
  4246.                                 if (whatDo=='X=Y')
  4247.                                 {
  4248.                                     //X is a selector, Y is a number or variable
  4249.                                     var bits=str.val.split(' = ');
  4250.                                     var X=bits[0]||0;
  4251.                                     var Y=bits[1]||0;
  4252.                                     if (X) eff.w=G.grabThings(X);
  4253.                                     if (Y) eff.v=G.grabVar(Y);
  4254.                                 }
  4255.                                 else if (whatDo=='$X=Y')
  4256.                                 {
  4257.                                     //X is a local var, Y is a number or variable
  4258.                                     var bits=str.val.split(' = ');
  4259.                                     var X=bits[0]||0;
  4260.                                     var Y=bits[1]||0;
  4261.                                     if (X) eff.w=G.validateVar(X);
  4262.                                     if (Y) eff.v=G.grabVar(Y);
  4263.                                 }
  4264.                                 else if (whatDo=='do X')
  4265.                                 {
  4266.                                     //X is a thing
  4267.                                     var X=str.gulp();
  4268.                                     if (X) eff.w=G.grabThing(X);
  4269.                                 }
  4270.                                 else if (whatDo=='do Xs')
  4271.                                 {
  4272.                                     //X is a selector
  4273.                                     var X=str.gulp();
  4274.                                     if (X) eff.w=G.grabThings(X);
  4275.                                 }
  4276.                                 else if (whatDo=='do X Y')
  4277.                                 {
  4278.                                     //X can be a number or a variable, but Y is always a thing
  4279.                                     //X may be absent
  4280.                                     var Y=str.val.split(' ');Y=Y[Y.length-1];
  4281.                                     var X=str.gulpUntil(Y);
  4282.                                     if (X) eff.v=G.grabVar(X);
  4283.                                     if (Y) eff.w=G.grabThing(Y);
  4284.                                 }
  4285.                                 else if (whatDo=='do X Ys')
  4286.                                 {
  4287.                                     //X can be a number or a variable, but Y is always a selector
  4288.                                     //X may be absent
  4289.                                     var Y=str.val.split(' ');Y=Y[Y.length-1];
  4290.                                     var X=str.gulpUntil(Y);
  4291.                                     if (X) eff.v=G.grabVar(X);
  4292.                                     if (Y) eff.w=G.grabThings(Y);
  4293.                                 }
  4294.                                 else if (whatDo=='change X by Y')
  4295.                                 {
  4296.                                     //X is a thing, Y is a number or variable
  4297.                                     eff.w=G.grabThing(str.gulpUntil('by'));
  4298.                                     eff.v=G.grabVar(str.gulpUntil());
  4299.                                 }
  4300.                                 else if (whatDo=='change Xs by Y')
  4301.                                 {
  4302.                                     //X is a selector, Y is a number or variable
  4303.                                     eff.w=G.grabThings(str.gulpUntil('by'));
  4304.                                     eff.v=G.grabVar(str.gulpUntil());
  4305.                                 }
  4306.                                 else if (whatDo=='change Zs for Xs by Y')
  4307.                                 {
  4308.                                     //X is a selector, Y is a number or variable, Z is a selector
  4309.                                     eff.z=G.grabThings(str.gulpUntil('for'));
  4310.                                     eff.w=G.grabThings(str.gulpUntil('by'));
  4311.                                     eff.v=G.grabVar(str.gulpUntil());
  4312.                                 }
  4313.                                 else if (whatDo=='do X with Ys')
  4314.                                 {
  4315.                                     //X is an effect name, Y is a selector
  4316.                                     if (str.val.indexOf(' with ')==-1)
  4317.                                     {
  4318.                                         eff.v=str.val;
  4319.                                         eff.w=G.grabThings('this');
  4320.                                     }
  4321.                                     else
  4322.                                     {
  4323.                                         eff.v=str.gulpUntil('with');
  4324.                                         eff.w=G.grabThings(str.gulpUntil());
  4325.                                     }
  4326.                                     if (!G.validateId(eff.v)) G.parseError('The effect type "'+eff.v+'" is invalid.');
  4327.                                     //else if (eff.w && !eff.w.effects[eff.v]) G.parseError(eff.w.name+' does not have any effects named "'+eff.v+'".');
  4328.                                 }
  4329.                             }
  4330.                             if (eff.type=='?') {add=false;G.parseError('Could not parse the effect "'+G.shorten(cmd,30)+'".');}
  4331.                            
  4332.                             if (add)
  4333.                             {
  4334.                                 if (condStack.length>0) condStack[0].effs.push(eff);
  4335.                                 else list.push(eff);
  4336.                             }
  4337.                         }
  4338.                         if (G.foundError) return false;
  4339.                     }
  4340.                     me.effects[effectType]=list;
  4341.                 }
  4342.                
  4343.                 G.context='when parsing costs for the thing "'+G.shorten(me.name,30)+'"';
  4344.                 var list=[];
  4345.                 for (var ii in me.costs)
  4346.                 {
  4347.                     list.push(G.strToList(me.costs[ii]));
  4348.                 }
  4349.                 me.costs=list;
  4350.                
  4351.                 //construct a func that checks reqs
  4352.                 G.context='when parsing requirements for the thing "'+G.shorten(me.name,30)+'"';
  4353.                 if (me.reqs.length>0)
  4354.                 {
  4355.                     var str=me.reqs.join(', ');
  4356.                     str=str.replaceAll(', and ',',');
  4357.                     str=str.replaceAll(' and ',',');
  4358.                     str=str.replaceAll(', ',',');
  4359.                     var list=str.split(',');
  4360.                     var str='';
  4361.                     for (var ii in list)
  4362.                     {
  4363.                         var bits=list[ii].split(' ');
  4364.                         if (bits[2] && (bits[2]=='clicks' || bits[2]=='click')) {bits[2]=0;bits[1]+=':clicks';}
  4365.                         if (bits[2] && (bits[2]=='per') && bits[3] && (bits[3]=='second')) {bits[2]=0;bits[3]=0;bits[1]+=':ps';}
  4366.                         if (bits[0] && bits[1] && !bits[2] && isNumber(bits[0])) str+=bits[1]+' >= '+bits[0];
  4367.                         else str+=list[ii];
  4368.                         str+=' and ';
  4369.                     }
  4370.                     str='( '+str+' 1 )';
  4371.                     me.reqFunc=G.grabVar(str).f;
  4372.                     if (G.foundError) return false;
  4373.                 }
  4374.             }
  4375.            
  4376.             //setup game
  4377.             G.context='when initializing data';
  4378.             for (var i in G.things)
  4379.             {
  4380.                 var me=G.things[i];
  4381.                 if (me.type!='box')
  4382.                 {
  4383.                     me.boostAdd=0;
  4384.                     me.boostMult=1;
  4385.                     me.boostAddFor=[];
  4386.                     me.boostMultFor=[];
  4387.                     me.costAdd=0;
  4388.                     me.costMult=1;
  4389.                     me.refundMult=1;
  4390.                     me.costAddFor=[];
  4391.                     me.costMultFor=[];
  4392.                     me.refundMultFor=[];
  4393.                 }
  4394.                 if (me.type=='button' || me.type=='shiny')
  4395.                 {
  4396.                     me.clicks=0;
  4397.                 }
  4398.                 if (me.type=='shiny')
  4399.                 {
  4400.                     me.timeLeft=-1;
  4401.                     me.durMult=1;
  4402.                     me.freqMult=1;
  4403.                 }
  4404.                 if (me.type=='res' || me.type=='building')
  4405.                 {
  4406.                     me.amount=0;
  4407.                     if (me.startWith) me.amount=me.startWith;
  4408.                     me.maxAmount=me.amount;
  4409.                 }
  4410.                 if (me.type=='res')
  4411.                 {
  4412.                     me.ps=0;
  4413.                     me.amountD=me.amount;
  4414.                     me.earned=me.amount;
  4415.                 }
  4416.                 if (me.type=='upgrade' || me.type=='achiev')
  4417.                 {
  4418.                     me.owned=0;
  4419.                     if (me.startWith) me.owned=1;
  4420.                 }
  4421.             }
  4422.            
  4423.             G.startDate=parseInt(Date.now());
  4424.            
  4425.             //parse the load data if any
  4426.             var loadedData=G.applyLoad();
  4427.            
  4428.             //build layout
  4429.            
  4430.             if (game.background) G.l.style.backgroundImage='url('+game.background+')';
  4431.             if (game.backgroundTile) G.l.classList.add('tilingBackground'); else G.l.classList.remove('tilingBackground');
  4432.            
  4433.        
  4434.             G.thingPlacement={
  4435.                 'Resources':'none',
  4436.                 'Buttons':'none',
  4437.                 'Buildings':'none',
  4438.                 'Upgrades':'none',
  4439.                 'Achievements':'none',
  4440.                 'Items':'none',
  4441.             };
  4442.            
  4443.             G.specialBoxes={
  4444.                     'BulkDisplay':`<div id="bulkDisplay" class="box-bit"><div class="box-bit-content"></div></div>`,
  4445.                     'Log':`<div id="log" class="box-bit"><div id="logOuter" class="box-bit-content"><div id="logInner"></div></div></div>`,
  4446.             };
  4447.            
  4448.             //put boxes inside each other
  4449.             for (var i in G.boxes)
  4450.             {
  4451.                 var me=G.boxes[i];
  4452.                 G.context='when placing the box "'+G.shorten(me.key,30)+'"';
  4453.                 me.name=me.key;
  4454.                 if (!me.children) me.children=[];
  4455.                 if (me.isIn)
  4456.                 {
  4457.                     var it=me.isIn;
  4458.                     if (G.thingsByName[it] && G.thingsByName[it].type=='box')
  4459.                     {
  4460.                         if (!G.thingsByName[it].children) G.thingsByName[it].children=[];
  4461.                         G.thingsByName[it].children.push(me.name);
  4462.                     }
  4463.                     else return G.parseError('Tried to put the box "'+me.name+'" in the box "'+it+'", but "'+it+'" is not an existing box.');
  4464.                 }
  4465.             }
  4466.             for (var i in G.boxes)
  4467.             {
  4468.                 var me=G.boxes[i];
  4469.                 G.context='when placing the box "'+G.shorten(me.key,30)+'"';
  4470.                 for (var ii in me.children)
  4471.                 {
  4472.                     var it=me.children[ii];
  4473.                    
  4474.                     //generic Thing keyword
  4475.                     if (G.thingPlacement[it]) G.thingPlacement[it]=me;
  4476.                     //special predefined elements
  4477.                     else if (G.specialBoxes[it]) {me.children[ii]={type:'special',str:G.specialBoxes[it]};}
  4478.                     //tag
  4479.                     else if (it.indexOf('tag:')==0){
  4480.                         var tag=it.slice(4);
  4481.                         me.children[ii]=tag;
  4482.                         G.thingPlacement[tag]=me;
  4483.                     }
  4484.                     //other box
  4485.                     else if (G.thingsByName[it] && G.thingsByName[it].type=='box') {G.thingsByName[it].parent=me;me.children[ii]=G.thingsByName[it];}
  4486.                     else return G.parseError('Tried to put "'+it+'" in the box "'+me.name+'", but "'+it+'" is not an existing box or a valid Thing keyword.');
  4487.                 }
  4488.             }
  4489.            
  4490.             //create dom
  4491.             G.context='when placing boxes';
  4492.             var rootBoxes=[];
  4493.             for (var i in G.boxes)
  4494.             {
  4495.                 var me=G.boxes[i];
  4496.                 if (!me.parent) rootBoxes.push(me);
  4497.             }
  4498.             var getBoxStr=function(box)
  4499.             {
  4500.                 var str='';
  4501.                 if (box.type && box.type=='special')
  4502.                 {
  4503.                     return box.str;
  4504.                 }
  4505.                 else if (box.type && box.type=='box')
  4506.                 {
  4507.                     for (var i in box.children){str+=getBoxStr(box.children[i]);}
  4508.                     var content=str;
  4509.                     var classes='box';
  4510.                     if (box.classes) classes+=' '+box.classes;
  4511.                     str='<div class="'+classes+'" id="box-'+box.name+'">';
  4512.                     if (box.header) str+='<div class="box-header">'+G.getTextValue(box.header)+'</div>';
  4513.                     if (box.footer) str+='<div class="box-footer">'+G.getTextValue(box.footer)+'</div>';
  4514.                     str+=content;
  4515.                     str+='</div>';
  4516.                     return str;
  4517.                 }
  4518.                 else {return '<div class="box-things" id="box-things-'+box+'"></div>';}
  4519.             }
  4520.             var str='';
  4521.             for (var i in rootBoxes)
  4522.             {
  4523.                 str+=getBoxStr(rootBoxes[i]);
  4524.             }
  4525.             l('content').innerHTML=str;
  4526.            
  4527.             for (var i in G.boxes)
  4528.             {
  4529.                 var me=G.boxes[i];
  4530.                 me.l=l('box-'+me.name)||0;
  4531.                 me.childrenl={};
  4532.                 for (var ii in me.children)
  4533.                 {
  4534.                     var child=me.children[ii];
  4535.                     if (child.type && child.type=='box')
  4536.                     {}
  4537.                     else {me.childrenl[child]=l('box-things-'+child)||0;}
  4538.                 }
  4539.             }
  4540.            
  4541.             //place things
  4542.             G.context='when placing things';
  4543.             for (var i in G.things)
  4544.             {
  4545.                 G.things[i].createDom();
  4546.             }
  4547.             for (var i in G.items)
  4548.             {
  4549.                 G.items[i].createDom();
  4550.             }
  4551.            
  4552.             G.context='in the final stages of initialization';
  4553.            
  4554.             //bulk display
  4555.             G.bulkDisplay=l('bulkDisplay')||0;
  4556.             if (G.bulkDisplay)
  4557.             {
  4558.                 G.addTooltip(G.bulkDisplay,{func:function(){return '<div class="title">Bulk-buying and selling</div><div class="desc"><div>Buy 50 at once by pressing Shift.</div><div>Sell 1 by pressing Ctrl.</div><div>Sell 50 at once by pressing Shift+Ctrl.</div></div>';}});
  4559.                 G.bulkDisplay=G.bulkDisplay.getElementsByClassName('box-bit-content')[0];
  4560.             }
  4561.            
  4562.             //log
  4563.             G.initLog();
  4564.            
  4565.             //info and settings
  4566.             var me=l('meta-button-info');
  4567.             G.addTooltip(me,{func:function(){return '<div class="title">Info & Stats</div><div class="desc"><div>View information about this game, and statistics about your playthrough.</div></div>';}});
  4568.             AddEvent(me,'click',function(){G.setMainPopup('info');});
  4569.            
  4570.             var me=l('meta-button-settings');
  4571.             G.addTooltip(me,{func:function(){return '<div class="title">Settings</div><div class="desc"><div>Import and export your game data, and edit settings for video, audio and gameplay.</div></div>';}});
  4572.             AddEvent(me,'click',function(){G.setMainPopup('settings');});
  4573.            
  4574.             G.darkenL=l('darken');
  4575.             AddEvent(G.darkenL,'click',function(){G.setMainPopup();});
  4576.            
  4577.             //setTimeout(function(){G.setMainPopup('settings');},100);
  4578.            
  4579.             G.mainPopup=0;
  4580.             G.mainPopupEl=0;
  4581.             G.setMainPopup=function(what)
  4582.             {
  4583.                 if (what && what!=G.mainPopup)
  4584.                 {
  4585.                     if (G.mainPopupEl) G.closePopup(G.mainPopupEl);
  4586.                     G.darkenL.className='on';
  4587.                     if (what=='settings')
  4588.                     {
  4589.                         var text=`
  4590.                         <div class="headerTitle">Settings</div>
  4591.                         <div style="padding:4px;overflow-y:auto;">
  4592.                        
  4593.                             <div style="display:flex;justify-content:space-evenly;align-items:center;padding:8px;text-align:center;">
  4594.                                 <div>`+
  4595.                                 G.button({
  4596.                                     text:'Save',
  4597.                                     tooltip:'Save your game.<br>If Autosave is enabled, the game saves by itself every 30 seconds.<br>You may also save with Ctrl+S.',
  4598.                                     onclick:function(e){
  4599.                                         triggerAnim(e.target,'glow');
  4600.                                         G.save();
  4601.                                     },
  4602.                                 })
  4603.                                 +`<br>`+
  4604.                                 G.button({
  4605.                                     text:'Load',
  4606.                                     tooltip:'Reload your game.',
  4607.                                     onclick:function(e){
  4608.                                         triggerAnim(e.target,'glow');
  4609.                                         G.load();
  4610.                                     },
  4611.                                 })
  4612.                                 +`</div>
  4613.                                 <div>`+
  4614.                                 G.button({
  4615.                                     text:'Export',
  4616.                                     tooltip:'Export your save data to a file.<br>Use this to backup your save or to share it with other players.',
  4617.                                     onclick:function(e){
  4618.                                         triggerAnim(e.target,'glow');
  4619.                                         /*G.popup(0,{text:`
  4620.                                             <div class="headerTitle">Export save</div>
  4621.                                             <div style="padding:4px;overflow-y:auto;overflow-x:hidden;text-align:center;">
  4622.                                             <div>This is your save code.<br>Copy it and keep it somewhere safe!</div>
  4623.                                                 <textarea id="textareaPrompt" style="margin-top:8px;width:100%;height:128px;overflow-x:hidden;
  4624.                                                 overflow-y:scroll;" readonly>`+(0)+`</textarea>
  4625.                                                 <div class="footerTitle hoverShine closesThePopup">Done</div>
  4626.                                             </div>
  4627.                                         `});*/
  4628.                                         G.fileSave();
  4629.                                     },
  4630.                                 })
  4631.                                 +`<br>`+
  4632.                                 G.button({
  4633.                                     text:'Import<input id="FileLoadInput" type="file" style="cursor:pointer;opacity:0;position:absolute;left:0px;top:0px;width:100%;height:100%;" onchange="G.fileLoad(event);"/>',
  4634.                                     tooltip:'Import save data from a file that was previously exported.',
  4635.                                     onclick:function(e){
  4636.                                         triggerAnim(e.target,'glow');
  4637.                                         /*G.popup(0,{text:`
  4638.                                             <div class="headerTitle">Import save</div>
  4639.                                             <div style="padding:4px;overflow-y:auto;overflow-x:hidden;text-align:center;">
  4640.                                             <div>Please paste in the code that was given to you on save export.</div>
  4641.                                                 <textarea id="textareaPrompt" style="margin-top:8px;width:100%;height:128px;overflow-x:hidden;
  4642.                                                 overflow-y:scroll;">`+(0)+`</textarea>
  4643.                                                 <div class="footerTitle hoverShine closesThePopup">Load</div>
  4644.                                             </div>
  4645.                                         `});*/
  4646.                                     },
  4647.                                 })
  4648.                                 +`</div>
  4649.                                 <div>`+
  4650.                                 G.button({
  4651.                                     text:'Wipe',
  4652.                                     classes:'red',
  4653.                                     tooltip:'Wipe your data for this game.<br>You will lose all your progress.<br>This cannot be undone!',
  4654.                                     onclick:function(e){
  4655.                                         triggerAnim(e.target,'glow');
  4656.                                         G.clear();
  4657.                                     },
  4658.                                 })
  4659.                                 +`</div>
  4660.                             </div>
  4661.                            
  4662.                             <div class="listing b">Autosave : `+G.makeTick({
  4663.                                 val:function(){return G.getSetting('autosave');},
  4664.                                 on:'On',off:'Off',
  4665.                                 func:function(val){G.setSetting('autosave',val);},
  4666.                                 tooltip:'If this is enabled, the game will auto-save every 30 seconds.',
  4667.                             })+`</div>
  4668.                             <div class="listing b">Number display : `+G.makeChoices({
  4669.                                 val:function(){return G.getSetting('numdisp');},
  4670.                                 func:function(val){G.setSetting('numdisp',val);},
  4671.                                 list:
  4672.                                 [
  4673.                                     {text:'Shortest',tooltip:'Numbers will be displayed in the form<br><b>1k, 1T, 1UnD</b>.'},
  4674.                                     {text:'Short',tooltip:'Numbers will be displayed in the form<br><b>1 thousand, 1 trillion, 1 undecillion</b>.'},
  4675.                                     {text:'Full',tooltip:'Numbers will be displayed in the form<br><b>1,000, 1,000,000,000,000, 1e+36</b>.'},
  4676.                                 ],
  4677.                             })+`</div>
  4678.                             <div class="listing b">Particles : `+G.makeChoices({
  4679.                                 val:function(){return G.getSetting('particles');},
  4680.                                 func:function(val){G.setSetting('particles',val);},
  4681.                                 list:
  4682.                                 [
  4683.                                     {text:'None',tooltip:'No particles will be displayed.'},
  4684.                                     {text:'Low',tooltip:'Particles are displayed in low-performance mode.'},
  4685.                                     {text:'Auto',tooltip:'Particles are displayed in high-performance mode, but switch to low-performance mode in low fps.'},
  4686.                                     {text:'Full',tooltip:'Particles are displayed in high-performance mode.'},
  4687.                                 ],
  4688.                             })+`</div>
  4689.                             <div class="listing b">CSS filters : `+G.makeTick({
  4690.                                 val:function(){return G.getSetting('cssfilts');},
  4691.                                 on:'On',off:'Off',
  4692.                                 func:function(val){G.setSetting('cssfilts',val);},
  4693.                                 tooltip:'CSS filters are visual effects such as blur and shadows which may lower performance in some browsers.',
  4694.                             })+`</div>
  4695.                             <div class="listing b">Show fps : `+G.makeTick({
  4696.                                 val:function(){return G.getSetting('showFPS');},
  4697.                                 on:'On',off:'Off',
  4698.                                 func:function(val){G.setSetting('showFPS',val);},
  4699.                                 tooltip:'Display the framerate graph in the bottom-left.',
  4700.                             })+`</div>
  4701.                         </div>
  4702.                         `;
  4703.                     }
  4704.                     else if (what=='info')
  4705.                     {
  4706.                         var text=`
  4707.                         <div class="headerTitle">Info</div>
  4708.                         <div style="padding:4px;overflow-y:auto;">
  4709.                             <div class="sectionTitle">About</div>
  4710.                             <div class="listing">You are playing <b>`+G.name+`</b>`+(G.version?' v.'+B(G.version):'')+`, by <b>`+(G.author||'Anonymous')+`</b>.
  4711.                             `+(G.forumPost?'<div><a href="http://forum.dashnet.org/discussion/'+G.forumPost+'" target="_blank">[View this game\'s forum post]</a></div>':'')+`
  4712.                             </div>
  4713.                             `+(G.desc?'<div class="listing desc"><div>'+G.getTextValue(G.desc)+'</div></div>':'')+`
  4714.                             <div class="listing">Game started <b>`+G.selfUpdatingText(function(){return sayTime(parseInt(Date.now())-G.startDate);})+` ago</b>.</div>
  4715.                             <div class="sectionTitle">Achievements</div>
  4716.                             <div class="listing b" style="max-width:640px;margin:auto;">
  4717.                                 `+G.selfUpdatingText(function(){
  4718.                                     var str='';
  4719.                                     var owned=0;
  4720.                                     var total=0;
  4721.                                     for (var i in G.achievs)
  4722.                                     {
  4723.                                         var me=G.achievs[i];
  4724.                                         if (me.owned) {str+=me.getQuickDom();owned++;}
  4725.                                         total++;
  4726.                                     }
  4727.                                     str='<div>Owned : '+B(owned)+'/'+B(total)+'</div><div style="padding:8px;">'+str+'</div>';
  4728.                                     return str;
  4729.                                 },5)+`
  4730.                             </div>
  4731.                         </div>
  4732.                         `;
  4733.                         //TODO : custom stats
  4734.                     }
  4735.                     G.mainPopupEl=G.popup(0,{
  4736.                         text:text+'<div class="footerTitle hoverShine closesThePopup">Close</div>',
  4737.                         classes:'mainPopup',
  4738.                         onClose:function(me){G.mainPopup=0;G.mainPopupEl=0;G.darkenL.className='off';},
  4739.                     });
  4740.                 }
  4741.                 else if (G.mainPopupEl) {G.closePopup(G.mainPopupEl);what=0;}
  4742.                 G.mainPopup=what;
  4743.                 G.hideTooltip();
  4744.             }
  4745.            
  4746.            
  4747.             if (loadedData) G.doEffectsForAll('load');
  4748.             else G.doEffectsForAll('start');
  4749.            
  4750.             //all done!
  4751.            
  4752.             for (var i in game){G[i]=game[i];}
  4753.             return true;
  4754.         }
  4755.     }
  4756.    
  4757.     G.Reset();
  4758. }
  4759.  
  4760. G.turnOn=function()
  4761. {
  4762.     G.l.classList.add('on');
  4763.     G.domReady=true;
  4764.     G.playing=true;
  4765. }
  4766. G.turnOff=function()
  4767. {
  4768.     G.l.classList.remove('on');
  4769.     G.domReady=false;
  4770.     G.playing=false;
  4771. }
  4772.  
  4773. /*=====================================================================================
  4774. BASIC FLOW
  4775. =======================================================================================*/
  4776. G.Logic=function()
  4777. {
  4778.     if (G.playing)
  4779.     {
  4780.         if (G.T%G.fps==1) G.tick();
  4781.         for (var i in G.things)
  4782.         {
  4783.             G.things[i].logic();
  4784.         }
  4785.         if (G.itemsToRefresh) G.refreshItems();
  4786.        
  4787.         //keys
  4788.         if (G.keysD[27])//esc
  4789.         {
  4790.             if (G.popups.length>0) G.closePopup();
  4791.         }
  4792.         if (G.keys[17] && G.keysD[83])//ctrl-s
  4793.         {
  4794.             G.save();
  4795.         }
  4796.        
  4797.         var oldBulk=G.bulk;
  4798.         G.bulk=1;
  4799.         if (G.keys[16]) G.bulk=50;//shift
  4800.         if (G.keys[17]) G.bulk=-G.bulk;//ctrl
  4801.         if (G.bulk!=oldBulk && !G.noBulkParticles) G.particleAt(G.l,0,(G.bulk<0?'Selling '+B(-G.bulk):'Buying '+B(G.bulk)));
  4802.        
  4803.         if (G.getSetting('autosave') && (G.T+1)%(G.fps*30)==0) G.save();
  4804.     }
  4805.     G.shiniesLogic();
  4806.     G.particlesLogic();
  4807.     G.toastLogic();
  4808. }
  4809.  
  4810. G.Draw=function()
  4811. {
  4812.     if (G.playing)
  4813.     {
  4814.         for (var i in G.things)
  4815.         {
  4816.             G.things[i].draw();
  4817.         }
  4818.        
  4819.         if (G.bulkDisplay) G.bulkDisplay.innerHTML=(G.bulk>0?'Buying':'Selling')+' <b>'+B(Math.abs(G.bulk))+'</b>';
  4820.     }
  4821.     G.shiniesDraw();
  4822.     G.popupDraw();
  4823.     G.tooltipDraw();
  4824. }
Add Comment
Please, Sign In to add comment