Path: blob/main/projects/cookie-clicker/dungeons.js
1834 views
/*1Orteil's sloppy Cookie Clicker dungeons23Optimizations to do (not mentioning the dozens of missing features) :4-use canvas instead5-only compute AI for mobs with 2 tiles of view6*/7var LaunchDungeons=function()8{9Game.GetWord=function(type)10{11if (type=='secret') return choose(['hidden','secret','mysterious','forgotten','forbidden','lost','sunk','buried','concealed','shrouded','invisible','elder']);12if (type=='ruined') return choose(['ancient','old','ruined','ravaged','destroyed','collapsed','demolished','burnt','torn-down','shattered','dilapidated','abandoned','crumbling','derelict','decaying']);13if (type=='magical') return choose(['arcane','magical','mystical','sacred','honed','banished','unholy','holy','demonic','enchanted','necromantic','bewitched','haunted','occult','astral']);14return '';15}1617/*=====================================================================================18DUNGEONS19=======================================================================================*/20Game.DungeonTypes=[];21Game.DungeonType=function(name)22{23this.name=name;24this.nameGenerator=function(){return 'Mysterious dungeon';};25this.roomTypes=[];26Game.DungeonTypes[this.name]=this;27return this;28};2930/*=====================================================================================31CREATE DUNGEON TYPES32=======================================================================================*/33new Game.DungeonType('Factory').34nameGenerator=function(){35var str='';36str+=Game.GetWord(choose(['secret','ruined','magical']))+' '+choose(['factory','factories','bakery','bakeries','confectionery','laboratory','research center','chocolate forge','chocolate foundry','manufactory','warehouse','machinery','works','bakeworks','workshop','assembly line']);37return str;38};3940new Game.DungeonType('Mine').41nameGenerator=function(){42var str='';43str+=Game.GetWord(choose(['secret','ruined','magical']))+' '+choose(['chocolate','chocolate','chocolate','white chocolate','sugar','cacao'])+' '+choose(['mine','mines','pit','pits','quarry','excavation','tunnel','shaft','lode','trench','mountain','vein','cliff','peak','dome','crater','abyss','chasm','hole','burrow']);44return str;45};4647new Game.DungeonType('Portal').48nameGenerator=function(){49var str='';50str+=Game.GetWord(choose(['secret','ruined','magical']))+' '+choose(['portal','gate','dimension','warpgate','door']);51return str;52};5354new Game.DungeonType('Secret zebra level').55nameGenerator=function(){56var str='';57str+=Game.GetWord(choose(['secret']))+' '+choose(['zebra level']);58return str;59};606162/*=====================================================================================63CREATE TILE TYPES64=======================================================================================*/6566var D=new DungeonGen();67D.loadTiles([68['wall',[1,0],'join'],69['wall corner',[1,0]],70['floor',[1,1],'random3'],71['tiled floor',[1,2],'join'],72['round pillar',[1,4]],73['square pillar',[2,4]],74['potted plant',[3,4]],75['bookshelf',[4,5],'join'],76['door',[1,3],'join'],77['alt wall',[4,0],'join'],78['alt wall corner',[4,0]],79['alt floor',[4,1],'random3'],80['alt tiled floor',[4,2],'join'],81['alt round pillar',[4,4]],82['alt square pillar',[5,4]],83['alt potted plant',[6,4]],84['alt bookshelf',[4,6],'join'],85['alt door',[4,3],'join'],86['water',[1,5]],87['green water',[2,5]],88['dark water',[3,5]],89['wooden wall',[1,7],'join'],90['wooden floor',[1,6],'random3'],91['conveyor belt',[4,7],'join'],92['entrance',[0,1]],93['alt entrance',[0,3]],94['exit',[0,2]],95['alt exit',[0,4]]96]);979899/*=====================================================================================100CREATE MONSTER TYPES101=======================================================================================*/102103/*104An explanation of stats :105-hp : health points106-speed : determines who attacks first in a fight; bypasses dodging; determines how fast heroes auto-run dungeons107-might : determines how much damage is done to opponents108-guard : lowers incoming damage109-dodge : chance of avoiding incoming attacks completely (affected by the opponent's speed)110-luck : heroes only, determines drops and rare encounters111-rarity : monsters only, determines how often a monster is added to the spawn table112-level : monsters only, determines which average room depth the monster is more likely to spawn in (also determines the loot amount)113*/114Game.monsterIconY=10;//offset for dungeonItems.png monsters115Game.Monsters=[];116Game.Monster=function(name,pic,icon,level,stats,loot)117{118this.name=name;119this.pic=pic;120this.icon=icon;121this.level=level;122this.stats={};123for (var i in stats)124{this.stats[i]=stats[i];}125this.stats.hpm=this.stats.hp;126this.stats.rarity=stats.rarity||1;127this.loot=loot||{};128this.boss=0;129this.quotes={};130Game.Monsters[this.name]=this;131}132var basicLoot={cookies:{min:1,max:5,prob:0.5}};133var goodLoot={cookies:{min:3,max:8,prob:1},gear:{prob:0.05}};134var bossLoot={gear:{prob:1}};135var chestLoot={cookies:{min:2,max:20,prob:1},gear:{prob:0.1}};136var bossLoot={cookies:{min:10,max:50,prob:1},gear:{prob:0.2}};137138//general monsters139new Game.Monster('Doughling','doughling',[0,0],1,{hp:5,might:2,guard:2,speed:6,dodge:6,rarity:0.7},basicLoot);140new Game.Monster('Elder doughling','elderDoughling',[1,0],7,{hp:20,might:7,guard:7,speed:4,dodge:4,rarity:0.7},goodLoot);141new Game.Monster('Angry sentient cookie','angrySentientCookie',[5,0],5,{hp:16,might:8,guard:4,speed:5,dodge:5,rarity:1},basicLoot);142new Game.Monster('Baby sentient cookie','babySentientCookie',[4,0],1,{hp:3,might:1,guard:1,speed:7,dodge:7,rarity:1},basicLoot);143new Game.Monster('Burnt sentient cookie','burntSentientCookie',[6,0],5,{hp:16,might:12,guard:2,speed:3,dodge:2,rarity:0.2},basicLoot);144new Game.Monster('Raw sentient cookie','rawSentientCookie',[5,0],5,{hp:16,might:6,guard:4,speed:7,dodge:7,rarity:0.2},basicLoot);145new Game.Monster('Sugar bunny','sugarBunny',[8,0],5,{hp:10,might:3,guard:8,speed:12,dodge:9,rarity:0.001},{cookies:{min:1000,max:10000}});146Game.Monsters['Sugar bunny'].onKill=function(){Game.Win('Follow the white rabbit');};Game.Monsters['Sugar bunny'].AI='flee';147148//factory monsters149new Game.Monster('Crazed kneader','crazedKneader',[0,2],6,{hp:18,might:6,guard:8,speed:3,dodge:2,rarity:0.5},goodLoot);150new Game.Monster('Crazed chip-spurter','crazedDoughSpurter',[0,2],6,{hp:15,might:6,guard:8,speed:5,dodge:3,rarity:0.5},goodLoot);151new Game.Monster('Alarm bot','alarmTurret',[3,2],2,{hp:6,might:3,guard:5,speed:8,dodge:8,rarity:0.5},basicLoot);152new Game.Monster('Chirpy','chirpy',[4,2],3,{hp:7,might:4,guard:6,speed:9,dodge:9,rarity:0.01},{cookies:{min:500,max:5000}});153Game.Monsters['Chirpy'].onKill=function(){Game.Win('Chirped out');};Game.Monsters['Chirpy'].quotes={fight:'oh, hello <3'};154new Game.Monster('Disgruntled worker','disgruntledWorker',[1,2],4,{hp:14,might:5,guard:5,speed:6,dodge:4,rarity:0.6},basicLoot);155new Game.Monster('Disgruntled overseer','disgruntledOverseer',[1,2],7,{hp:22,might:7,guard:5,speed:6,dodge:4,rarity:0.5},basicLoot);156new Game.Monster('Disgruntled cleaning lady','disgruntledCleaningLady',[2,2],4,{hp:13,might:4,guard:5,speed:7,dodge:6,rarity:0.3},basicLoot);157158new Game.Monster('Sentient Furnace','sentientFurnace',[0,3],0,{hp:60,might:14,guard:12,speed:4,dodge:0,rarity:1},bossLoot);//boss159Game.Monsters['Sentient Furnace'].onKill=function(){Game.Win('Getting even with the oven');};Game.Monsters['Sentient Furnace'].AI='static';Game.Monsters['Sentient Furnace'].boss=1;Game.Monsters['Sentient Furnace'].quotes={fight:'YOU ARE NOT READY!',defeat:'OH... BURN.'};160new Game.Monster('Ascended Baking Pod','ascendedBakingPod',[1,3],0,{hp:60,might:12,guard:14,speed:4,dodge:0,rarity:0.7},bossLoot);//boss161Game.Monsters['Ascended Baking Pod'].onKill=function(){Game.Win('Now this is pod-smashing');};Game.Monsters['Ascended Baking Pod'].AI='static';Game.Monsters['Ascended Baking Pod'].boss=1;Game.Monsters['Ascended Baking Pod'].quotes={fight:'rrrrrrrise.',defeat:'blrglblg.'};162163164Game.BossMonsters=[];165for (var i in Game.Monsters)166{167if (Game.Monsters[i].boss) Game.BossMonsters.push(Game.Monsters[i]);168}169170/*=====================================================================================171ENTITY MECHANICS172=======================================================================================*/173174Game.Entity=function(type,subtype,dungeon,pic,stats)//objects you could find on the map : doors, mobs, interactables, items, player, exits...175{176this.type=type;177this.subtype=subtype||'';178this.dungeon=dungeon;179this.pic=pic||[0,0];180this.stats={};181for (var i in stats)182{this.stats[i]=stats[i];}183184this.x=-1;185this.y=-1;186this.obstacle=0;187this.zIndex=1;188if (this.type=='monster')189{190this.obstacle=1;191this.pic=[Game.Monsters[this.subtype].icon[0],Game.Monsters[this.subtype].icon[1]];192this.pic[1]+=Game.monsterIconY;193this.targets=[];194this.stuck=0;195this.zIndex=10;196this.fighting=0;197this.AI=Game.Monsters[this.subtype].AI||'normal';198this.onKill=Game.Monsters[this.subtype].onKill||function(){};199for (var i in Game.Monsters[this.subtype].stats){this.stats[i]=Game.Monsters[this.subtype].stats[i];}200}201else if (this.type=='hero')202{203this.obstacle=1;204this.pic=[Game.Heroes[this.subtype].icon[0],Game.Heroes[this.subtype].icon[1]];205this.targets=[];206this.stuck=0;207this.zIndex=100;208this.fighting=0;209for (var i in Game.Heroes[this.subtype].stats){this.stats[i]=Game.Heroes[this.subtype].stats[i];}210211//increase stats by amount of matching building (change that later to use gear instead)212var mult=Math.max(0,(Game.Objects[this.dungeon.type].amount/20-1));213this.stats.hpm+=Math.ceil(mult*2);214this.stats.hp=this.stats.hpm;215this.stats.might+=mult;216this.stats.guard+=mult;217this.stats.speed+=mult;218this.stats.dodge+=mult;219}220else if (this.type=='item')221{222this.zIndex=5;223this.value=0;224}225else if (this.type=='destructible')//crates, doors226{227this.obstacle=1;228this.life=3;229this.zIndex=15;230if (this.subtype=='door') this.pic=[0,7];231else this.pic=[Math.floor(Math.random()*4+2),7];232233this.onKill=function()234{235if (this.subtype=='random')236{237var value=Math.round(Math.pow(Math.random(),6)*(10+this.dungeon.level));238if (value>0)239{240var entity=this.dungeon.AddEntity('item','cookies',this.x,this.y);241entity.value=value;242}243}244}245}246else if (this.type=='special')247{248this.zIndex=5;249this.value='';250this.obstacle=1;251}252253this.Say=function(what)254{255if (this.type=='monster')256{257if (Game.Monsters[this.subtype].quotes[what]) this.dungeon.Log(this.subtype+' : "<span style="color:#f96;">'+choose(Game.Monsters[this.subtype].quotes[what].split('|'))+'</span>"');258}259}260this.Draw=function()//return the string to draw this261{262var name='?';263if (this.subtype=='random') name='clutter'; else name=this.subtype;264if (this.type=='item' && this.subtype=='cookies' && this.value>0)265{266if (this.value<2) this.pic=[0,5];267else if (this.value<3) this.pic=[1,5];268else if (this.value<4) this.pic=[2,5];269else if (this.value<6) this.pic=[3,5];270else if (this.value<10) this.pic=[4,5];271else if (this.value<20) this.pic=[5,5];272else if (this.value<30) this.pic=[7,5];273else if (this.value<70) this.pic=[6,5];274else if (this.value<200) this.pic=[8,5];275else this.pic=[6,6];// if (this.value<1000) this.pic=[1,5];276}277else if (this.type=='special' && this.subtype=='upgrade')278{279if (this.value!='') this.pic=[7,6]; else this.pic=[8,6];280}281return '<div class="thing" title="'+name+'" style="z-index:'+(200+this.zIndex)+';left:'+(this.x*16)+'px;top:'+(this.y*16)+'px;background-position:'+(-this.pic[0]*16)+'px '+(-this.pic[1]*16)+'px;"></div>';282}283this.Wander=function()//AI to move around aimlessly284{285this.targets=[];286this.targets.push([-1,0],[1,0],[0,-1],[0,1]);287this.Move();288}289this.GoTo=function(x,y)//AI to move to a specific point290{291this.targets=[];292if (this.x<x) this.targets.push([1,0]);293if (this.x>x) this.targets.push([-1,0]);294if (this.y<y) this.targets.push([0,1]);295if (this.y>y) this.targets.push([0,-1]);296if (!this.Move())//really stuck? try to maneuver laterally!297{298this.targets=[];299if (this.x==x) this.targets.push([1,0],[-1,0]);//somehow this feels inverted... but it doesn't work the other way300if (this.y==y) this.targets.push([0,1],[0,-1]);//hypothesis : *MAGIC*301this.Move();302}303}304this.Flee=function(x,y)//AI to run away from a specific point305{306this.targets=[];307if (this.x>x) this.targets.push([1,0]);308if (this.x<x) this.targets.push([-1,0]);309if (this.y>y) this.targets.push([0,1]);310if (this.y<y) this.targets.push([0,-1]);311if (!this.Move())//really stuck? try to maneuver laterally!312{313this.targets=[];314if (this.x==x) this.targets.push([1,0],[-1,0]);//somehow this feels inverted... but it doesn't work the other way315if (this.y==y) this.targets.push([0,1],[0,-1]);//hypothesis : *MAGIC*316this.Move();317}318}319this.Move=function()//AI to move to the target320{321if (this.targets.length>0)322{323var goodTargets=[];324if (this.type=='hero') goodTargets=this.targets;325else326{327for (var i in this.targets)328{329var thisTarget=this.targets[i];330if (this.dungeon.CheckObstacle(this.x+thisTarget[0],this.y+thisTarget[1])!=-1) goodTargets.push([thisTarget[0],thisTarget[1]]);331}332}333if (goodTargets.length>0)334{335var target=choose(goodTargets);336var obstacle=this.dungeon.CheckObstacle(this.x+target[0],this.y+target[1]);337if (obstacle==this) obstacle=0;338if (obstacle==0 && this.AI!='static')339{340this.x+=target[0];341this.y+=target[1];342}343else this.stuck+=2;344if (obstacle!=0 && obstacle!=-1)345{346obstacle.HitBy(this);347}348if (obstacle==-1) return 0;349}350else {this.stuck+=2;return 0;}351if (this.AI=='static') this.stuck=0;352return 1;353}354return 0;355}356this.HitBy=function(by)//attacked by another entity357{358if (this.type=='destructible' && by.type=='hero')//break destructibles359{360by.stuck=0;361this.life--;362if (this.life<=0)363{364if (this.onKill) this.onKill();365this.Destroy();366}367else this.pic=[this.pic[0],this.pic[1]+1];368}369else if (this.type=='special' && this.subtype=='upgrade')//upgrade relic370{371this.obstacle=0;372if (Game.Upgrades[this.value]) Game.Upgrades[this.value].earn();373this.value='';374}375else if ((this.type=='monster' && by.type=='hero') || (this.type=='hero' && by.type=='monster') && this.stats.hp>0)//it's a fight!376{377by.stuck=0;378379var monster=(this.type=='hero'?by:this);380var hero=(this.type=='hero'?this:by);381this.dungeon.currentOpponent=monster;382383if (monster.fighting==0)//first meeting384{385Game.Heroes[hero.subtype].Say('meet '+Game.Monsters[monster.subtype].name);386this.Say('fight');387}388if (this.fighting==0)389{390this.fighting=1;391by.fighting=1;392}393394var attackStr='';395var attackerName='';396var defenderName='';397if (by.type=='hero') attackerName=Game.Heroes[by.subtype].name;398else if (by.type=='monster') attackerName=Game.Monsters[by.subtype].name;399if (this.type=='hero') defenderName=Game.Heroes[this.subtype].name;400else if (this.type=='monster') defenderName=Game.Monsters[this.subtype].name;401402//battle formulas (have fun with these)403attackStr+=attackerName+' swings at '+defenderName+'!';404var damage=Math.round(Math.max(1,Math.min(by.stats.might,Math.pow(((by.stats.might+2.5)/Math.max(1,this.stats.guard)),2)))*(0.8+Math.random()*0.4+Math.pow(Math.random()*0.8,6)));405var dodge=Math.random()>(by.stats.speed/Math.max(1,this.stats.dodge+2.5));406if (dodge)407{408attackStr+=' '+defenderName+' dodged the attack.';409}410else411{412if (by.stats.luck && by.type=='hero' && Math.random()<by.stats.luck*0.01) {damage*=2;attackStr+=' <b>It\'s a critical!</b>';}//very rare critical based on luck413attackStr+=' <b>'+damage+'</b> damage!';414415this.stats.hp-=damage;416this.stats.hp=Math.max(this.stats.hp,0);417if (this.stats.luck && this.type=='hero')418{419if (this.stats.hp==0 && Math.random()<this.stats.luck*0.01) {this.stats.hp=1;attackStr+=' '+defenderName+' was saved from certain death!';}//very rare life-saving based on luck420}421}422423if (this.type=='hero') attackStr='<span style="color:#f99;">'+attackStr+'</span>';424if (attackStr!='') this.dungeon.Log(attackStr);425426if (this.stats.hp<=0)//die427{428this.dungeon.Log(attackerName+' crushed '+defenderName+'!');429if (this.type=='hero')430{431Game.Heroes[this.subtype].Say('defeat');432this.dungeon.Log('<span style="color:#f66;">'+Game.Heroes[this.subtype].name+' has been defeated.</span>');433this.dungeon.FailLevel();434}435if (this.type=='monster' && by.type=='hero')436{437l('monsterSlot'+this.dungeon.id).style.visibility='hidden';438this.dungeon.monstersKilledThisRun+=1;439if (Math.random()<0.05) Game.Heroes[by.subtype].Say('win');440Game.Heroes[by.subtype].Say('win against '+Game.Monsters[this.subtype].name);441this.Say('defeat');442if (Game.Monsters[this.subtype].loot)443{444var loot=Game.Monsters[this.subtype].loot;445if (loot.gear && (!loot.gear.prob || Math.random()<loot.gear.prob)) {}//drop gear446if (loot.cookies && (!loot.cookies.prob || Math.random()<loot.cookies.prob))447{448var entity=this.dungeon.AddEntity('item','cookies',this.x,this.y);//drop cookies449entity.value=Math.round(loot.cookies.min+Math.random()*(loot.cookies.max-loot.cookies.min));450}451}452if (this.onKill) this.onKill();453this.Destroy();454}455}456}457}458this.Turn=function()//do this every turn (walk around, heal up...)459{460if (this.type=='monster')461{462var howManyTurns=this.GetInitiative();463for (var i=0;i<howManyTurns;i++)464{465if (1==1)//this.AI!='static')466{467if (this.AI=='flee') this.Flee(this.dungeon.heroEntity.x,this.dungeon.heroEntity.y);//flee from the player468else469{470this.GoTo(this.dungeon.heroEntity.x,this.dungeon.heroEntity.y);//track the player471if (this.stuck || this.targets.length==[]) this.Wander();//can't reach the player? walk around randomly472}473}474}475}476if (this.type=='monster' || this.type=='hero')477{478if (this.stuck>0) this.stuck--;479this.stuck=Math.min(10,this.stuck);480this.targets=[];481}482if ((this.type=='hero' || this.type=='monster') && this.fighting==0 && this.stats.hp<this.stats.hpm) this.stats.hp++;//heal up483if (this.type=='hero')//collect items and cookies484{485var entities=this.dungeon.GetEntities(this.x,this.y);486for (var i in entities)487{488if (entities[i].type=='item' && entities[i].subtype=='cookies')489{490var entity=entities[i];491var value=Math.ceil(entity.value*Game.Objects[this.dungeon.type].amount*50*(1+Math.random()*((this.stats.luck)/20)));//temporary; scale with matching building CpS later492if (value>0)493{494this.dungeon.Log('<span style="color:#9f9;">Found <b>'+Beautify(value)+'</b> cookie'+(value==1?'':'s')+'!</span>');495this.dungeon.cookiesMadeThisRun+=value;496Game.Earn(value);497}498entity.Destroy();499}500}501}502if (this.type=='hero') this.fighting=0;503}504this.Destroy=function()505{506this.dungeon.entities.splice(this.dungeon.entities.indexOf(this),1);507}508this.GetInitiative=function()509{510return randomFloor((this.stats.speed/5)*(1/Math.max(1,(this.dungeon.heroEntity.stats.speed/5))));511}512}513514/*=====================================================================================515DUNGEON MECHANICS516=======================================================================================*/517518Game.Dungeons=[];519Game.Dungeon=function(type,id)520{521this.type=type;522this.id=id;523Game.Dungeons[this.id]=this;524this.log=[];525this.logNew=0;526this.name=Game.DungeonTypes[this.type].nameGenerator();527this.hero=null;528this.currentOpponent=0;529this.level=0;530this.auto=1;531this.portalPic='';532533this.cookiesMadeThisRun=0;534this.monstersKilledThisRun=0;535536this.Log=function(what,nested)537{538if (typeof what==='string')539{540this.log.unshift(what);541this.logNew++;542}543else {for (var i in what) {this.Log(what[i],1);}}544//if (!nested) this.UpdateLog();545}546547this.UpdateLog=function()548{549this.log=this.log.slice(0,30);550var str='';551for (var i in this.log)552{553if (i<this.logNew) str+='<div class="new">'+this.log[i]+'</div>';554else str+='<div>'+this.log[i]+'</div>';555}556this.logNew=0;557l('dungeonLog'+this.id).innerHTML=str;558}559560this.entities=[];561this.GetEntities=function(x,y)//returns the first entity found on tile x,y562{563var entities=[];564for (var i in this.entities) {if (this.entities[i].x==x && this.entities[i].y==y) entities.push(this.entities[i]);}565return entities;566}567this.AddEntity=function(type,subtype,x,y)568{569//this.RemoveEntities(x,y);570var entity=new Game.Entity(type,subtype,this);571entity.x=x;572entity.y=y;573entity.dungeon=this;574this.entities.push(entity);575return entity;576}577this.RemoveEntities=function(x,y)578{579var entities=this.GetEntities(x,y);580for (var i in entities)581{582entities[i].Destroy();583}584}585this.DrawEntities=function()586{587var str='';588for (var i in this.entities) {str+=this.entities[i].Draw();}589return str;590}591592this.CheckObstacle=function(x,y)//returns 0 for no obstacle; -1 for a wall; an entity if there's at least one entity on this tile593{594if (x<0 || x>=this.map.w || y<0 || y>=this.map.h) return -1;595var entities=this.GetEntities(x,y);596for (var i in entities)597{598if (entities[i].obstacle) return entities[i];599}600return this.map.isObstacle(x,y)?-1:0;601}602603604this.map={};605this.Generate=function()606{607if (this.level==0) this.name=Game.DungeonTypes[this.type].nameGenerator();608this.entities=[];609var M=new D.Map(40,40,Math.random(),{610roomSize:10,611corridorSize:5,612fillRatio:1/2,613corridorRatio:0.3,614pillarRatio:Math.random()*0.8+0.2,615waterRatio:Math.random(),616branching:Math.ceil(Math.random()*6),617sizeVariance:0.4618});619r=0;620while (r!=1)621{622r=M.dig();623}624//all done! decorate and render.625M.finish();626//spawn treasure627/*628for (var i in M.rooms)629{630if (M.rooms[i].freeTiles>1)631{632for (var ii=0;ii<Math.ceil(Math.sqrt(M.rooms[i].freeTiles*(M.rooms[i].gen*0.25+0.1))/2);ii++)633{634if (Math.random()<0.95 && M.rooms[i].freeTiles>1)635{636var spot=M.getBestSpotInRoom(M.rooms[i]);637M.data[spot.x][spot.y][0]=0;638spot.score=0;639M.rooms[i].freeTiles--;640}641}642}643}*/644645for (var i in M.doors)//place door entities on door positions646{647//M.data[M.doors[i][0]][M.doors[i][1]][0]=TILE_FLOOR_EDGE;648this.AddEntity('destructible','door',M.doors[i][0],M.doors[i][1]);649}650//set tile graphics651for (var i in M.rooms)652{653var altStr=choose(['alt ','','']);654var tiles={655'void':altStr+'void',656'wall':altStr+'wall',657'wall corner':altStr+'wall corner',658'floor':altStr+'tiled floor',659'floor edges':altStr+'floor',//choose([altStr+'floor',altStr+'floor edges']),660'door':altStr+'door',661'water':choose(['water','green water','dark water']),662'pillar':choose([altStr+'wall',altStr+'round pillar',altStr+'square pillar',altStr+'potted plant','conveyor belt']),663'entrance':altStr+'entrance',664'exit':altStr+'exit',665};666if (Math.random()<0.1) {tiles['wall corner']='wooden wall';tiles['wall']='wooden wall';tiles['floor edges']='wooden floor';tiles['pillar']='wooden wall';}667if (Math.random()<0.1) {tiles['wall corner']=altStr+'bookshelf';tiles['wall']=altStr+'bookshelf';tiles['pillar']=altStr+'bookshelf';}668M.assignTiles(M.rooms[i],tiles);669}670this.map=M;671this.map.str=this.map.getStr();672673//place a boss674var tile=this.map.exit;675var monsters=[];676for (var ii in Game.BossMonsters)677{678var me=Game.BossMonsters[ii];679if (me.level<=(depth+this.level) && Math.random()<(me.stats.rarity||1)) monsters.push(me.name);680}681if (monsters.length==0) monsters=[choose(Game.BossMonsters).name];682if (monsters.length>0)683{684this.AddEntity('monster',choose(monsters),tile[0],tile[1]);685this.map.removeFreeTile(tile[0],tile[1]);686}687688//place relics689/*690var tile=this.map.getBestSpotInRoom(this.map.getRoom(this.map.exit[0],this.map.exit[1]));691var entity=this.AddEntity('special','upgrade',tile.x,tile.y);692entity.value='Dungeon cookie upgrade';693this.map.removeFreeTile(tile.x,tile.y);694for (var i=0;i<Math.floor(Math.pow(Math.random(),2)*3);i++)695{696var room=choose(this.map.rooms);697if (room.freeTiles.length>10)698{699var tile=this.map.getBestSpotInRoom(room);700var entity=this.AddEntity('special','upgrade',tile.x,tile.y);701entity.value='Dungeon cookie upgrade';702this.map.removeFreeTile(tile.x,tile.y);703}704}*/705706//sprinkle monsters and treasure707for (var i=0;i<Math.ceil(this.map.freeTiles.length*0.7);i++)//let's fill this up with A LOT of stuff708{709var tile=choose(this.map.freeTiles);710if (tile!=-1)711{712var room=this.map.getRoom(tile[0],tile[1]);713var depth=room.gen+1;714if (Math.random()<0.2)//2 in 10 spawns are monsters715{716var monsters=[];717for (var ii in Game.Monsters)718{719var me=Game.Monsters[ii];720if (me.level!=0 && me.level<=(depth+this.level) && Math.random()<(me.stats.rarity||1)) monsters.push(me.name);//spawn type depending on monster level and rarity721}722if (monsters.length>0)723{724this.AddEntity('monster',choose(monsters),tile[0],tile[1]);725this.map.removeFreeTile(tile[0],tile[1]);726}727}728else//the rest of the spawns are destructibles or loot729{730if (Math.random()<0.6)731{732var value=Math.round(Math.pow(Math.random(),6)*(10+this.level));733if (value>0)734{735var entity=this.AddEntity('item','cookies',tile[0],tile[1]);//random cookies736entity.value=value;737}738}739else this.AddEntity('destructible','random',tile[0],tile[1]);//random crates etc740this.map.removeFreeTile(tile[0],tile[1]);741}742}743}744}745746this.onTile=-1;747748this.Draw=function()749{750var str='';751var x=-this.hero.x;752var y=-this.hero.y;753str+='<div id="map'+this.id+'" class="map" style="width:'+(9*16)+'px;height:'+(9*16)+'px;"><div class="mapContainer" id="mapcontainer'+this.id+'" style="position:absolute;left:'+(x*16)+'px;top:'+(y*16)+'px;"><div id="mapitems'+this.id+'"></div>'+this.map.str+'</div></div>';754str+='<div style="position:absolute;left:'+(9*16+16)+'px;">'+755'<a class="control west" onclick="Game.HeroesById['+this.hero.id+'].Move(-1,0);"></a><br>'+756'<a class="control east" onclick="Game.HeroesById['+this.hero.id+'].Move(1,0);"></a><br>'+757'<a class="control north" onclick="Game.HeroesById['+this.hero.id+'].Move(0,-1);"></a><br>'+758'<a class="control south" onclick="Game.HeroesById['+this.hero.id+'].Move(0,1);"></a><br>'+759'<a class="control middle" onclick="Game.HeroesById['+this.hero.id+'].Move(0,0);"></a><br>'+760'</div>';761str+='<div style="position:absolute;left:'+(9*16+16+48*3)+'px;bottom:16px;height:100%;">'+762'<div class="dungeonName"><a onclick="Game.ObjectsById['+this.id+'].setSpecial(0);">Exit</a> - <span class="title" style="font-size:12px;">'+this.name+'</span> lvl.'+(this.level+1)+'</div>'+763'<div id="heroSlot'+this.id+'" class="mobSlot"><div id="picHero'+this.id+'" class="mobPic"></div><div id="nameHero'+this.id+'" class="title mobName"></div><div class="hpmBar"><div id="hpHero'+this.id+'" class="hpBar"></div></div></div>'+764'<div id="monsterSlot'+this.id+'" class="mobSlot" style="left:128px;"><div id="picMonster'+this.id+'" class="mobPic"></div><div id="nameMonster'+this.id+'" class="title mobName"></div><div class="hpmBar"><div id="hpMonster'+this.id+'" class="hpBar"></div></div></div>'+765'</div>'+766'<div id="dungeonLog'+this.id+'" class="dungeonLog"></div>';767l('rowSpecial'+this.id).innerHTML='<div style="width:100%;height:100%;z-index:10000;position:absolute;left:0px;top:0px;">'+str+'</div>';768769l('picHero'+this.id).style.backgroundImage='url(img/'+this.hero.portrait+'.png)';770l('nameHero'+this.id).innerHTML=this.hero.name;771}772this.Refresh=function()773{774if (!l('mapcontainer'+this.id)) this.Draw();775var x=4-this.hero.x;776var y=4-this.hero.y;777l('mapcontainer'+this.id).style.left=(x*16)+'px';778l('mapcontainer'+this.id).style.top=(y*16)+'px';779l('mapitems'+this.id).innerHTML=this.DrawEntities();780}781this.RedrawMap=function()782{783this.map.str=this.map.getStr();784this.Draw();785}786this.Turn=function()787{788for (var i in this.entities)789{790if (this.entities[i] && this.entities[i].type) this.entities[i].Turn();791}792if (this.currentOpponent)793{794l('monsterSlot'+this.id).style.visibility='visible';795l('hpMonster'+this.id).style.width=Math.round((this.currentOpponent.stats.hp/this.currentOpponent.stats.hpm)*100)+'%';796l('picMonster'+this.id).style.backgroundImage='url(img/'+Game.Monsters[this.currentOpponent.subtype].pic+'.png)';797l('nameMonster'+this.id).innerHTML=Game.Monsters[this.currentOpponent.subtype].name;798l('picHero'+this.id).style.backgroundImage='url(img/'+this.hero.pic+'.png)';799}800else801{802l('monsterSlot'+this.id).style.visibility='hidden';803l('hpMonster'+this.id).style.width='100%';804l('picHero'+this.id).style.backgroundImage='url(img/'+this.hero.portrait+'.png)';805}806this.currentOpponent=0;807l('hpHero'+this.id).style.width=Math.round((this.heroEntity.stats.hp/this.heroEntity.stats.hpm)*100)+'%';808809this.Refresh();810this.UpdateLog();811812if (this.hero.x==this.map.exit[0] && this.hero.y==this.map.exit[1])813{814this.CompleteLevel();815}816}817818this.DrawButton=function()819{820var str='';821//str+='<div style="text-align:center;margin:48px auto;color:#999;"><a onclick="Game.ObjectsById['+this.id+'].setSpecial(1);">Enter</a></div>';822str+='<div style="width:144px;height:144px;position:absolute;left:0px;bottom:0px;"><a class="specialButtonPic" style="background-image:url(img/'+this.portalPic+'.png);" onclick="Game.ObjectsById['+this.id+'].setSpecial(1);"><div class="specialButtonText">Enter dungeons</div></a></div>';823return str;824}825826this.CompleteLevel=function()827{828this.hero.Say('completion');829this.level++;830this.Generate();831Game.HeroesById[0].EnterDungeon(this,this.map.entrance[0],this.map.entrance[1]);832this.Draw();833}834this.FailLevel=function()835{836this.Log('Cookies made this run : '+Beautify(this.cookiesMadeThisRun)+' | Monsters defeated this run : '+Beautify(this.monstersKilledThisRun));837this.cookiesMadeThisRun=0;838this.monstersKilledThisRun=0;839this.level=0;840this.Generate();841Game.HeroesById[0].EnterDungeon(this,this.map.entrance[0],this.map.entrance[1]);842this.Draw();843}844}845846Game.DungeonLocationChain=function(map,x,y)//return an array of the rooms between the root room and this tile's room, inclusive847{//we shouldn't need all this if we used A*...848var room=map.getRoom(x,y);849var chain=[];850if (room!=-1)851{852while (room.parent)853{854chain.push(room);855room=room.parent;856}857}858chain.reverse();859return chain;860}861Game.DungeonLinkLocationChains=function(start,end)//return the room in which the first location chain should go to to get closer to the second location chain862{863/*8644 cases865-we're already in the same room866-the target is in a different branch867-the target is above in the same branch868-the target is below in the same branch869*/870start.reverse();871end.reverse();872if (start[0].id==end[0].id) return start[start.length-1];//same room873for (var i in end)874{875if (start[0]==end[i].parent) return end[i];//inferior branch, go to the inferior room876}877if (start.length>1) return start[1];//different or superior branch, go to the superior room878return start[0];//eeeh, let's just stay in the same room879}880881/*=====================================================================================882CREATE DUNGEONS883=======================================================================================*/884Game.Objects['Factory'].special=function()885{886this.dungeon=new Game.Dungeon('Factory',this.id);887this.dungeon.Generate();888this.specialDrawFunction=function(){this.dungeon.Refresh();};889this.drawSpecialButton=function(){return this.dungeon.DrawButton();};890this.dungeon.timer=0;891this.dungeon.timerWarmup=5;892this.dungeon.portalPic='dungeonFactory';893894this.EachFrame=function()895{896if (this.dungeon.auto)897{898if (this.dungeon.timer>0) this.dungeon.timer--;899if (this.dungeon.timer==0)900{901this.dungeon.timer=Game.fps*(Math.max(0.1,2-(this.dungeon.hero.stats.speed*0.2))+Math.max(this.dungeon.timerWarmup,0));902if (this.dungeon.timerWarmup>0) this.dungeon.timerWarmup--;903904var dungeon=this.dungeon;905var hero=dungeon.heroEntity;906907var targetRoom=Game.DungeonLinkLocationChains(Game.DungeonLocationChain(dungeon.map,hero.x,hero.y),Game.DungeonLocationChain(dungeon.map,dungeon.map.exit[0],dungeon.map.exit[1]));908var targetTile=(targetRoom.gen==0 || targetRoom.id==dungeon.map.getRoom(hero.x,hero.y).id)?[dungeon.map.exit[0],dungeon.map.exit[1]]:targetRoom.door;909hero.GoTo(targetTile[0],targetTile[1]);910if (hero.stuck) hero.Wander();911dungeon.hero.x=hero.x;912dungeon.hero.y=hero.y;913dungeon.Turn();914}915}916}917918if (document.addEventListener)//clean this up later919{920l('rowSpecial'+this.dungeon.id).removeEventListener('keydown',arguments.callee,false);921l('rowSpecial'+this.dungeon.id).addEventListener('keydown',function(event)922{923var dungeon=Game.Objects['Factory'].dungeon;924var control=0;925if (event.keyCode==37) {dungeon.hero.Move(-1,0);control=1;}926else if (event.keyCode==38) {dungeon.hero.Move(0,-1);control=1;}927else if (event.keyCode==39) {dungeon.hero.Move(1,0);control=1;}928else if (event.keyCode==40) {dungeon.hero.Move(0,1);control=1;}929else if (event.keyCode==32) {dungeon.hero.Move(0,0);control=1;}//space930else if (event.keyCode==65)//A (auto)931{932if (dungeon.auto)933{934dungeon.auto=0;935dungeon.timerWarmup=-1;936}937else938{939dungeon.auto=1;940dungeon.timer=0;941dungeon.timerWarmup=0;942}943event.preventDefault();944}945946if (control)947{948event.preventDefault();949dungeon.timer=Game.fps*10;950dungeon.timerWarmup=5;951}952}953);954}955956var hero=choose(Game.HeroesById);957hero.EnterDungeon(this.dungeon,this.dungeon.map.entrance[0],this.dungeon.map.entrance[1]);958}959960/*=====================================================================================961HEROES962=======================================================================================*/963Game.Heroes=[];964Game.HeroesById=[];965Game.Hero=function(name,pic,portrait,icon)966{967this.name=name;968this.pic=pic;969this.portrait=portrait;970this.icon=icon;971this.stats={972hp:25,973hpm:25,974might:5,975guard:5,976speed:5,977dodge:5,978luck:5979};980this.dialogue={981'greeting':'Oh hey.|Sup.',982'entrance':'Here we go.|So exciting.',983'completion':'That was easy.|All done here.',984'defeat':'Welp.|Better luck next time.'985};986this.gear={987'armor':-1,988'weapon':-1989};990this.inDungeon=-1;991this.completedDungeons=0;992993this.x=0;994this.y=0;995996this.EnterDungeon=function(dungeon,x,y)997{998this.inDungeon=dungeon.id;999dungeon.hero=this;1000this.x=x;1001this.y=y;1002dungeon.heroEntity=dungeon.AddEntity('hero',dungeon.hero.name,x,y);1003var room=dungeon.map.getRoom(this.x,this.y);1004if (room!=-1 && room.hidden) {room.hidden=0;dungeon.RedrawMap();}1005Game.Dungeons[this.inDungeon].Refresh();1006dungeon.Log('--------------------');1007if (dungeon.level==0) this.Say('greeting');1008this.Say('entrance');1009l('monsterSlot'+dungeon.id).style.visibility='hidden';1010}1011this.Move=function(x,y)1012{1013var dungeon=Game.Dungeons[this.inDungeon];1014dungeon.heroEntity.targets=[[x,y]];1015if (dungeon.heroEntity.Move())1016{1017this.x=dungeon.heroEntity.x;1018this.y=dungeon.heroEntity.y;1019dungeon.Turn();1020}1021}10221023this.Say=function(what)1024{1025if (this.dialogue[what]) Game.Dungeons[this.inDungeon].Log(this.name+' : "<span style="color:#99f;">'+choose(this.dialogue[what].split('|'))+'</span>"');1026}10271028this.save=function()1029{1030var str='';1031str+=1032this.inDungeon+','+1033this.completedDungeons+','+1034this.gear.armor+','+1035this.gear.weapon1036;1037return str;1038}1039this.load=function(data)1040{1041var str=data.split(',');1042this.inDungeon=parseInt(str[0]);1043this.completedDungeons=parseInt(str[1]);1044this.gear.armor=parseInt(str[2]);1045this.gear.weapon=parseInt(str[3]);1046}1047this.id=Game.HeroesById.length;1048Game.HeroesById.push(this);1049Game.Heroes[this.name]=this;1050}10511052/*=====================================================================================1053CREATE HEROES1054=======================================================================================*/1055var hero=new Game.Hero('Chip','girlscoutChip','portraitChip',[1,0]);1056hero.dialogue={1057'intro':'I\'m Chip! I just really like exploring stuff. Let\'s go have an adventure!',1058'greeting':'Hello there!|I\'m ready!|Where are we going today?|Adventure!',1059'win':'Take that!|Hah!|That\'s right.',1060'entrance':'Chipping in!|Welp, here goes nothing!|I wonder what I\'ll find!|Hey, this place is new!|This place seems familiar.|Let\'s make it happen.',1061'completion':'I\'m one smart cookie.|Oh yeah!|Let\'s explore some more!|That was easy!|That sure was fun!|I\'m not lost, am I?|More exploring? Sure, why not!',1062'defeat':'B-better luck next time.|That really hurt!|I yield! I yield!|That went badly.|No half-baked excuses next time.|I think I scraped my knee!|Owie.|Woopsie!',1063'win against Sentient Furnace':'The irony, it burns! (...it\'s funny because it was burning. And made of iron. ...Moving on.)',1064'win against Ascended Baking Pod':'Where is your pod now?|That was disturbing.'1065};1066hero.stats={1067hp:30,1068hpm:30,1069might:5,1070guard:5,1071speed:5,1072dodge:5,1073luck:51074};1075var hero=new Game.Hero('Crumb','girlscoutCrumb','portraitCrumb',[2,0]);1076hero.dialogue={1077'intro':'I\'m Crumb. I look like this because of a baking accident when I was little. Big deal. At least now I don\'t get hurt as easily as others, I guess.',1078'greeting':'Hi there.|Ready for adventure, I guess.|Reporting for duty.',1079'win':'Oh sorry, did that hurt?|Should have moved out of the way.|Oops. My bad.',1080'entrance':'Let\'s do this, I guess.|Well, let\'s go...|I gotta go in there?|Are we really doing this?|I hope I won\'t get lost like last time.|Let\'s get this over with.',1081'completion':'I... I did it...|I\'m glad that\'s over.|What, there\'s more?|In I go, I guess.|It doesn\'t end, does it?|But it\'s dark in there.',1082'defeat':'I, uh, ouch.|Why does that always happen to me?|I\'m just no good, am I?|Oh no.|I\'m... I\'m not crying.|Well that wasn\'t fun at all.|I\'m sorry I failed you.|Please... make them go away...',1083'meet Ascended Baking Pod':'That thing shouldn\'t even be alive.|Is that where they all came from?',1084'win against Ascended Baking Pod':'Hm. Fascinating.'1085};1086hero.stats={1087hp:25,1088hpm:25,1089might:5,1090guard:7,1091speed:4,1092dodge:4,1093luck:51094};1095var hero=new Game.Hero('Doe','girlscoutDoe','portraitDoe',[3,0]);1096hero.dialogue={1097'intro':'H-hey. Name\'s Doe. I\'m pretty fast. I uh, I promise I\'ll do my best.',1098'greeting':'H-hey.|Oh, uh, h-hi there.|C-can I join?',1099'win':'Th-that looks like it hurt... awesome...|D-did I do that?|N-neat... there\'s pieces everywhere.',1100'entrance':'Alright, let\'s do this!|I-if I really have to.|I-in there? By myself?|...won\'t you come with me this time?|H-here I go!',1101'completion':'Oh... oh my.|That\'s... I uh, I\'m glad.|Y-yeah that was real easy. Piece of pie!|T-too easy, right?|S-so many cookies...|Ooh? F-fascinating.',1102'defeat':'I-if you can\'t beat them... join them.|I-it\'s because I stutter, isn\'t it?|W-well that\'s just no good at all.|I, uh, I meant for that to happen.|H-how embarrassing.',1103'meet Ascended Baking Pod':'W-whoah... it\'s... magnificent...',1104'win against Ascended Baking Pod':'I\'m sorry, buddy.|I... I think I hurt it...|Oh no... I-I think I broke it...'1105};1106hero.stats={1107hp:25,1108hpm:25,1109might:4,1110guard:4,1111speed:7,1112dodge:5,1113luck:51114};1115var hero=new Game.Hero('Lucky','girlscoutLucky','portraitLucky',[4,0]);1116hero.dialogue={1117'intro':'Oh joy! My name\'s Lucky. Guess what I\'m good at?',1118'greeting':'I\'m feeling lucky!|It\'s a bright day today!|Let\'s do great things together.',1119'win':'Ooh lucky shot!|Pow! One more.|Damn straight!',1120'entrance':'Glad to be of service!|Oooh this one\'ll be interesting.|This will be a good one, I can feel it!|Here I come!',1121'completion':'Over already?|Let\'s explore some more!|That was lucky!|That was no luck, I\'m just that good.|Alright, let\'s move on!|I\'m just getting warmed up!',1122'defeat':'I can\'t believe it!|...This is a joke, right?|Hey! No fair!|B-but...|I\'m gonna need a bandaid. And some hot chocolate.|I\'ll, uh, try again later.|Bad luck! Bad luck!',1123'win against Ascended Baking Pod':'Golly, that was peculiar.'1124};1125hero.stats={1126hp:25,1127hpm:25,1128might:5,1129guard:4,1130speed:4,1131dodge:5,1132luck:71133};11341135};11361137