diff --git a/DungeonGen.js b/DungeonGen.js new file mode 100644 index 0000000..72234de --- /dev/null +++ b/DungeonGen.js @@ -0,0 +1,871 @@ +/* +Orteil's crappy dungeon generation library, 2013 +Unfinished and buggy, use at your own risk (please credit) +http://orteil.dashnet.org + +Rough process (might or might not be what actually happens) : +1 make a room in the middle +2 pick one of its walls (not corners) +3 select a free tile on the other side of that wall +4 iteratively expand the selection in one (corridors) or two (rooms) directions, stopping when we meet a wall or when we're above the size threshold +5 compute that selection into a room +6 add decorations to the room (pillars, water) but only on the center tiles, as to leave free passages (sprinkle destructible decorations anywhere) +7 take a random floor tile in the room and repeat step 4, but don't stop at the walls of this room (this creates branching) - repeat about 5 times for interesting shapes +8 add those branches to the room +9 carve the room into the map, and set the initially selected wall as a door - set the new room's parent to the previous room, and add it to its parent's children +10 repeat step 2 with any free wall on the map until the amount of tiles dug is above the desired fill ratio + +Note : I should probably switch the rendering to canvas to allow stuff like occlusion shadows and lights +*/ + +if (1==1 || undefined==Math.seedrandom) +{ + //seeded random function, courtesy of http://davidbau.com/archives/2010/01/30/random_seeds_coded_hints_and_quintillions.html + (function(a,b,c,d,e,f){function k(a){var b,c=a.length,e=this,f=0,g=e.i=e.j=0,h=e.S=[];for(c||(a=[c++]);d>f;)h[f]=f++;for(f=0;d>f;f++)h[f]=h[g=j&g+a[f%c]+(b=h[f])],h[g]=b;(e.g=function(a){for(var b,c=0,f=e.i,g=e.j,h=e.S;a--;)b=h[f=j&f+1],c=c*d+h[j&(h[f]=h[g=j&g+b])+(h[g]=b)];return e.i=f,e.j=g,c})(d)}function l(a,b){var e,c=[],d=(typeof a)[0];if(b&&"o"==d)for(e in a)try{c.push(l(a[e],b-1))}catch(f){}return c.length?c:"s"==d?a:a+"\0"}function m(a,b){for(var d,c=a+"",e=0;c.length>e;)b[j&e]=j&(d^=19*b[j&e])+c.charCodeAt(e++);return o(b)}function n(c){try{return a.crypto.getRandomValues(c=new Uint8Array(d)),o(c)}catch(e){return[+new Date,a,a.navigator.plugins,a.screen,o(b)]}}function o(a){return String.fromCharCode.apply(0,a)}var g=c.pow(d,e),h=c.pow(2,f),i=2*h,j=d-1;c.seedrandom=function(a,f){var j=[],p=m(l(f?[a,o(b)]:0 in arguments?a:n(),3),j),q=new k(j);return m(o(q.S),b),c.random=function(){for(var a=q.g(e),b=g,c=0;h>a;)a=(a+c)*d,b*=d,c=q.g(1);for(;a>=i;)a/=2,b/=2,c>>>=1;return(a+c)/b},p},m(c.random(),b)})(this,[],Math,256,6,52); +} + +if (1==1 || undefined==choose) {function choose(arr) {if (arr.length==0) return 0; else return arr[Math.floor(Math.random()*arr.length)];}} + + +var DungeonGen=function() +{ + var TILE_EMPTY=0;//solid + var TILE_LIMIT=-100;//can't build anything here; edges of map + var TILE_FLOOR_EDGE=100; + var TILE_FLOOR_CENTER=110; + var TILE_DOOR=200; + var TILE_PILLAR=300;//not just pillars, could be any type of repetitive decoration + var TILE_WATER=400; + var TILE_WALL=500; + var TILE_WALL_CORNER=510; + var TILE_ENTRANCE=250; + var TILE_EXIT=260; + + var colors=[]; + colors[TILE_EMPTY]='000'; + colors[TILE_LIMIT]='900'; + colors[TILE_FLOOR_EDGE]='ffc'; + colors[TILE_FLOOR_CENTER]='ff9'; + colors[TILE_DOOR]='f9f'; + colors[TILE_PILLAR]='990'; + colors[TILE_WATER]='99f'; + colors[TILE_WALL]='960'; + colors[TILE_WALL_CORNER]='630'; + colors[TILE_ENTRANCE]='f9f'; + colors[TILE_EXIT]='f9f'; + + var rand=function(a,b){return Math.floor(Math.random()*(b-a+1)+a);}//return random value between a and b + + var Patterns=[]; + this.Pattern=function(name,func) + { + this.name=name; + this.func=func; + Patterns.push(this); + } + new this.Pattern('Pillars',function(x,y,room) + { + if ((x+room.x)%2==0 && (y+room.y)%2==0 && Math.random()<0.8) return TILE_PILLAR; + return 0; + }); + new this.Pattern('Large pillars',function(x,y,room) + { + if ((x+room.x)%3<2 && (y+room.y)%3<2 && Math.random()<0.8) return TILE_PILLAR; + return 0; + }); + new this.Pattern('Sparse pillars',function(x,y,room) + { + if ((x+room.x)%3==0 && (y+room.y)%3==0 && Math.random()<0.8) return TILE_PILLAR; + return 0; + }); + new this.Pattern('Lines',function(x,y,room) + { + if (room.x%2==0) if ((x+room.x)%2==0 && Math.random()<0.98) return TILE_PILLAR; + if (room.x%2==1) if ((y+room.y)%2==0 && Math.random()<0.98) return TILE_PILLAR; + return 0; + }); + + + var getRandomPattern=function() + {return choose(Patterns);} + + var defaultGenerator=function(me) + { + me.roomSize=10; + me.corridorSize=5; + me.fillRatio=1/3; + me.corridorRatio=0.2; + me.pillarRatio=0.2; + me.waterRatio=0; + me.branching=4; + me.sizeVariance=0.2; + + me.fillRatio=0.1+Math.random()*0.4; + me.roomSize=Math.ceil(rand(5,15)*me.fillRatio*2); + me.corridorSize=Math.ceil(rand(1,7)*me.fillRatio*2); + me.corridorRatio=Math.random()*0.8+0.1; + me.pillarRatio=Math.random()*0.5+0.5; + me.waterRatio=Math.pow(Math.random(),2); + me.branching=Math.floor(Math.random()*6); + me.sizeVariance=Math.random(); + } + + + this.Map=function(w,h,seed,params) + { + //create a new map + //leave the seed out for a random seed + //params is an object that contains custom parameters as defined in defaultGenerator + //example : MyMap=new DungeonGen.Map(30,30,MySeed,{waterRatio:0.8}); (80 percent of the rooms will contain water) + if (undefined!=seed) this.seed=seed; else {Math.seedrandom();this.seed=Math.random();} + Math.seedrandom(this.seed); + this.seedState=Math.random; + this.w=w||20; + this.h=h||20; + + this.roomsAreHidden=0; + + this.rooms=[]; + this.freeWalls=[];//all walls that would be a good spot for a door + this.freeTiles=[];//all passable floor tiles + this.doors=[]; + this.tiles=this.w*this.h; + this.tilesDug=0; + this.digs=0;//amount of digging steps + this.stuck=0;//how many times we ran into a problem; stop digging if we get too many of these + + this.data=[];//fill the map with 0 + for (var x=0;x=this.w-1 || ry+rh>=this.h-1) return false; + for (var x=rx;x0 && h>0) + { + if (w>0) {forcedExpansions.push(1,3);w--;} + if (h>0) {forcedExpansions.push(2,4);h--;} + } + + for (var i=0;i0) side=forcedExpansions[0]; + if (side==1) {xd=-1;wd=1;} + else if (side==2) {yd=-1;hd=1;} + else if (side==3) {wd=1;} + else if (side==4) {hd=1;} + if (this.canPlaceRoom(rx+xd,ry+yd,rw+wd,rh+hd)) {rx+=xd;ry+=yd;rw+=wd;rh+=hd;} else expansions.splice(expansions.indexOf(side),1); + if (forcedExpansions.length>0) forcedExpansions.splice(0,1); + } + if (rw>1 || rh>1) + { + this.expandRoom(room,rx,ry,rw,rh); + } + } + } + + + this.Map.prototype.carve=function(room) + { + //carve a room into the map + for (var i in room.tiles) + { + var thisTile=room.tiles[i]; + var x=thisTile.x;var y=thisTile.y; + var myType=this.data[x][y][0]; + var type=thisTile.type; + + if ((type==TILE_WALL || type==TILE_WALL_CORNER) && this.isWall(x,y)!=-1) {this.freeWalls.splice(this.isWall(x,y),1);} + + if (this.data[x][y][1]!=-1 && (type==TILE_WALL || type==TILE_WALL_CORNER)) {} + else + { + if (this.data[x][y][1]==-1) this.tilesDug++; + this.data[x][y]=[thisTile.type,room.id,0]; + if (x>1 && y>1 && x0 && room.freeTiles>0)//we got a decent room + { + this.carve(room); + this.data[door[0]][door[1]][0]=TILE_DOOR;//place door + room.door=[door[0],door[1]]; + this.data[door[0]][door[1]][1]=room.id;//set ID + this.freeWalls.splice(this.isWall(door[0],door[1]),1);//the door isn't a wall anymore + this.doors.push([door[0],door[1],room]); + //remove free tiles on either side of the door + if (this.isFloor(door[0]+side[0],door[1]+side[1])!=-1) this.removeFreeTile(door[0]+side[0],door[1]+side[1]); + if (this.isFloor(door[0]-side[0],door[1]-side[1])!=-1) this.removeFreeTile(door[0]-side[0],door[1]-side[1]); + room.parent=parentRoom; + parentRoom.children.push(room); + room.gen=parentRoom.gen+1; + } + else//not a good spot; remove this tile from the list of walls + { + this.freeWalls.splice(this.isWall(door[0],door[1]),1); + success=0; + } + } + } + if (success) return room; + else return 0; + } + + this.Map.prototype.getRandomSpotInRoom=function(room) + { + var listOfTiles=[]; + for (var i in room.tiles) + { + if ((room.tiles[i].type==TILE_FLOOR_EDGE || room.tiles[i].type==TILE_FLOOR_CENTER) && this.isFloor(room.tiles[i].x,room.tiles[i].y)!=-1) + { + listOfTiles.push(room.tiles[i]); + } + } + if (listOfTiles.length==0) return -1; + return choose(listOfTiles); + } + this.Map.prototype.getBestSpotInRoom=function(room) + { + var highest=-1; + var listOfHighest=[]; + for (var i in room.tiles) + { + if ((room.tiles[i].type==TILE_FLOOR_EDGE || room.tiles[i].type==TILE_FLOOR_CENTER) && this.isFloor(room.tiles[i].x,room.tiles[i].y)!=-1) + { + if (room.tiles[i].score>highest) + { + listOfHighest=[]; + highest=room.tiles[i].score; + listOfHighest.push(room.tiles[i]); + } + else if (room.tiles[i].score==highest) + { + listOfHighest.push(room.tiles[i]); + } + } + } + if (listOfHighest.length==0) return -1; + return choose(listOfHighest); + } + this.Map.prototype.getEarliestRoom=function() + { + return this.rooms[0]; + } + this.Map.prototype.getDeepestRoom=function() + { + var deepest=0; + var deepestRoom=this.rooms[0]; + for (var i in this.rooms) + { + if ((this.rooms[i].gen+Math.sqrt(this.rooms[i].freeTiles)*0.05)>=deepest && this.rooms[i].corridor==0 && this.rooms[i].freeTiles>4) {deepest=(this.rooms[i].gen+Math.sqrt(this.rooms[i].freeTiles)*0.05);deepestRoom=this.rooms[i];} + } + return deepestRoom; + } + + this.Map.prototype.dig=function() + { + //one step in which we try to carve new stuff + //returns 0 when we couldn't dig this step, 1 when we could, and 2 when the digging is complete + Math.random=this.seedState; + + var badDig=0; + + if (this.digs==0)//first dig : build a starting room in the middle of the map + { + var w=rand(3,7); + var h=rand(3,7); + var room=this.newRoom(Math.floor(this.w/2-w/2),Math.floor(this.h/2-h/2),w,h); + room.corridor=0; + this.planRoom(room); + this.carve(room); + } + else + { + if (this.newRandomRoom()==0) badDig++; + } + if (badDig>0) this.stuck++; + + this.digs++; + + var finished=0; + if (this.tilesDug>=this.tiles*this.fillRatio) finished=1; + if (this.stuck>100) finished=1; + + if (finished==1)//last touch : try to add a whole room at the end + { + for (var i=0;i<10;i++) + { + var newRoom=this.newRandomRoom({corridor:0,w:rand(3,7),h:rand(3,7)}); + if (newRoom!=0 && newRoom.freeTiles>15) break; + } + } + + Math.seedrandom(); + if (finished==1) return 1; else if (badDig>0) return -1; else return 0; + } + + this.Map.prototype.finish=function() + { + //touch up the map : add pillars in corners etc + /* + //set paths + for (var i in this.rooms) + { + var me=this.rooms[i]; + if (me.door!=0) + { + var doors=[]; + doors.push(me.door); + for (var ii in me.children) + { + if (me.children[ii].door!=0) doors.push(me.children[ii].door); + } + for (var ii in doors) + { + this.data[doors[ii][0]][doors[ii][1]][0]=TILE_LIMIT; + //ideally we should run agents that step from each door to the next + } + } + } + */ + for (var i in this.rooms) + { + var pillars=Math.random()4) + { + if ((angle==1 || (complete && walls==7)) && me==TILE_FLOOR_EDGE && x1!=TILE_DOOR && x2!=TILE_DOOR && y1!=TILE_DOOR && y2!=TILE_DOOR) + { + this.data[x][y][0]=TILE_PILLAR; + me=TILE_PILLAR; + this.removeFreeTile(x,y); + this.rooms[i].freeTiles--; + } + } + + //calculate score (for placing items and exits) + if (top==1 || bottom==1 || left==1 || right==1) + { + this.rooms[i].tiles[ii].score+=2; + } + if (walls>5 || floors>5) + { + this.rooms[i].tiles[ii].score+=1; + } + if (walls==7 || floors==8) + { + this.rooms[i].tiles[ii].score+=5; + } + if ((me!=TILE_FLOOR_CENTER && me!=TILE_FLOOR_EDGE) || x1==TILE_DOOR || x2==TILE_DOOR || y1==TILE_DOOR || y2==TILE_DOOR) this.rooms[i].tiles[ii].score=-1; + + } + } + + + + //carve entrance and exit + var entrance=this.getBestSpotInRoom(this.getEarliestRoom()); + this.data[entrance.x][entrance.y][0]=TILE_ENTRANCE; + this.entrance=[entrance.x,entrance.y]; + entrance.score=0; + this.removeFreeTile(entrance.x,entrance.y); + var exit=this.getBestSpotInRoom(this.getDeepestRoom()); + this.data[exit.x][exit.y][0]=TILE_EXIT; + this.exit=[exit.x,exit.y]; + this.removeFreeTile(exit.x,exit.y); + exit.score=0; + + /* + for (var i in this.doors)//remove door tiles (to add later; replace the tiles by entities that delete themselves when opened) + { + this.data[this.doors[i][0]][this.doors[i][1]][0]=TILE_FLOOR_EDGE; + } + */ + } + + this.Map.prototype.isObstacle=function(x,y) + { + var free=[TILE_FLOOR_EDGE,TILE_FLOOR_CENTER,TILE_DOOR,TILE_ENTRANCE,TILE_EXIT]; + for (var i in free) + { + if (this.data[x][y][0]==free[i]) return 0; + } + return 1; + } + + var joinTile=function(map,x,y,joinWith) + { + //for the tile at x,y, return 2 if it joins with its horizontal neighbors, 3 if it joins with its vertical neighbors, 1 if it joins with either both or neither. + //joinWith contains the tile types that count as joinable, in addition to this tile. (don't add the tested tile to joinWith!) + var p=1; + var me=map.data[x][y][0]; + var x1=map.data[x-1][y][0]; + var x2=map.data[x+1][y][0]; + var y1=map.data[x][y-1][0]; + var y2=map.data[x][y+1][0]; + joinWith.push(me); + var joinsX=0; + for (var i in joinWith) + { + if (x1==joinWith[i]) joinsX++; + if (x2==joinWith[i]) joinsX++; + } + var joinsY=0; + for (var i in joinWith) + { + if (y1==joinWith[i]) joinsY++; + if (y2==joinWith[i]) joinsY++; + } + if (joinsX==2 && joinsY==2) p=1; + else if (joinsX==2) p=2; + else if (joinsY==2) p=3; + return p; + } + this.Map.prototype.getPic=function(x,y) + { + //return a position [x,y] in the tiles (as 0, 1, 2...) for the tile on the map at position x,y + if (Tiles[this.data[x][y][2]]) + { + if (Tiles[this.data[x][y][2]].joinType=='join') + { + var thisPic=Tiles[this.data[x][y][2]].pic; + thisPic=[thisPic[0],thisPic[1]];//why is this even necessary? + var joinWith=[]; + if (this.data[x][y][0]==TILE_WALL) joinWith.push(TILE_WALL_CORNER); + else if (this.data[x][y][0]==TILE_DOOR) joinWith.push(TILE_WALL,TILE_WALL_CORNER); + thisPic[0]+=joinTile(this,x,y,joinWith)-1; + return thisPic; + } + else if (Tiles[this.data[x][y][2]].joinType=='random3') + { + var thisPic=Tiles[this.data[x][y][2]].pic; + thisPic=[thisPic[0],thisPic[1]]; + thisPic[0]+=Math.floor(Math.random()*3); + return thisPic; + } + return Tiles[this.data[x][y][2]].pic; + } + return [0,0]; + } + + var Tiles=[]; + var TilesByName=[]; + this.Tile=function(name,pic,joinType) + { + this.name=name; + this.pic=pic; + this.joinType=joinType||'none'; + this.id=Tiles.length; + Tiles[this.id]=this; + TilesByName[this.name]=this; + } + new this.Tile('void',[0,0]); + this.loadTiles=function(tiles) + { + for (var i in tiles) + { + var name=tiles[i][0]; + var pic=tiles[i][1]; + var joinType=tiles[i][2]; + new this.Tile(name,pic,joinType); + } + } + + var computeTile=function(tile,tiles,value,name) + { + if (tile==value && tiles[name]) return TilesByName[tiles[name]]; + return 0; + } + this.Map.prototype.assignTiles=function(room,tiles) + { + //set the displayed tiles for this room + for (var i in room.tiles) + { + var type=Tiles[0]; + var me=room.tiles[i]; + var tile=this.data[me.x][me.y][0]; + type=computeTile(tile,tiles,TILE_WALL_CORNER,'wall corner')||type; + type=computeTile(tile,tiles,TILE_WALL,'wall')||type; + type=computeTile(tile,tiles,TILE_FLOOR_EDGE,'floor edges')||type; + type=computeTile(tile,tiles,TILE_FLOOR_CENTER,'floor')||type; + type=computeTile(tile,tiles,TILE_PILLAR,'pillar')||type; + type=computeTile(tile,tiles,TILE_DOOR,'door')||type; + type=computeTile(tile,tiles,TILE_WATER,'water')||type; + type=computeTile(tile,tiles,TILE_ENTRANCE,'entrance')||type; + type=computeTile(tile,tiles,TILE_EXIT,'exit')||type; + + this.data[me.x][me.y][2]=type.id; + } + } + + + this.Map.prototype.draw=function(size) + { + //return a string containing a rough visual representation of the map + var str=''; + var size=size||10; + for (var y=0;y'+text+''; + } + str+='
'; + } + str='
'+str+'
'; + return str; + } + + this.Map.prototype.drawDetailed=function() + { + //return a string containing a rough visual representation of the map (with graphics) + var str=''; + var size=16; + for (var y=0;y'; + } + str+='
'; + } + str='
'+str+'
'; + return str; + } + + this.Map.prototype.getStr=function() + { + //return a string containing the map with tile graphics, ready to be pasted in a wrapper + var str=''; + var size=16; + for (var y=0;y'; + } + str+='
'; + } + return str; + } + +} \ No newline at end of file diff --git a/ajax.js b/ajax.js new file mode 100644 index 0000000..9065c26 --- /dev/null +++ b/ajax.js @@ -0,0 +1,6 @@ +function ajax(url,callback){ + var ajaxRequest; + try{ajaxRequest = new XMLHttpRequest();} catch (e){try{ajaxRequest=new ActiveXObject('Msxml2.XMLHTTP');} catch (e) {try{ajaxRequest=new ActiveXObject('Microsoft.XMLHTTP');} catch (e){alert("Something broke!");return false;}}} + if (callback){ajaxRequest.onreadystatechange=function(){if(ajaxRequest.readyState==4){callback(ajaxRequest.responseText);}}} + ajaxRequest.open('GET',url+'&nocache='+(new Date().getTime()),true);ajaxRequest.send(null); +} \ No newline at end of file diff --git a/dungeons.js b/dungeons.js new file mode 100644 index 0000000..7271fb3 --- /dev/null +++ b/dungeons.js @@ -0,0 +1,1136 @@ +/* +Orteil's sloppy Cookie Clicker dungeons + +Optimizations to do (not mentioning the dozens of missing features) : +-use canvas instead +-only compute AI for mobs with 2 tiles of view +*/ +var LaunchDungeons=function() +{ + Game.GetWord=function(type) + { + if (type=='secret') return choose(['hidden','secret','mysterious','forgotten','forbidden','lost','sunk','buried','concealed','shrouded','invisible','elder']); + if (type=='ruined') return choose(['ancient','old','ruined','ravaged','destroyed','collapsed','demolished','burnt','torn-down','shattered','dilapidated','abandoned','crumbling','derelict','decaying']); + if (type=='magical') return choose(['arcane','magical','mystical','sacred','honed','banished','unholy','holy','demonic','enchanted','necromantic','bewitched','haunted','occult','astral']); + return ''; + } + + /*===================================================================================== + DUNGEONS + =======================================================================================*/ + Game.DungeonTypes=[]; + Game.DungeonType=function(name) + { + this.name=name; + this.nameGenerator=function(){return 'Mysterious dungeon';}; + this.roomTypes=[]; + Game.DungeonTypes[this.name]=this; + return this; + }; + + /*===================================================================================== + CREATE DUNGEON TYPES + =======================================================================================*/ + new Game.DungeonType('Factory'). + nameGenerator=function(){ + var str=''; + str+=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']); + return str; + }; + + new Game.DungeonType('Mine'). + nameGenerator=function(){ + var str=''; + str+=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']); + return str; + }; + + new Game.DungeonType('Portal'). + nameGenerator=function(){ + var str=''; + str+=Game.GetWord(choose(['secret','ruined','magical']))+' '+choose(['portal','gate','dimension','warpgate','door']); + return str; + }; + + new Game.DungeonType('Secret zebra level'). + nameGenerator=function(){ + var str=''; + str+=Game.GetWord(choose(['secret']))+' '+choose(['zebra level']); + return str; + }; + + + /*===================================================================================== + CREATE TILE TYPES + =======================================================================================*/ + + var D=new DungeonGen(); + D.loadTiles([ + ['wall',[1,0],'join'], + ['wall corner',[1,0]], + ['floor',[1,1],'random3'], + ['tiled floor',[1,2],'join'], + ['round pillar',[1,4]], + ['square pillar',[2,4]], + ['potted plant',[3,4]], + ['bookshelf',[4,5],'join'], + ['door',[1,3],'join'], + ['alt wall',[4,0],'join'], + ['alt wall corner',[4,0]], + ['alt floor',[4,1],'random3'], + ['alt tiled floor',[4,2],'join'], + ['alt round pillar',[4,4]], + ['alt square pillar',[5,4]], + ['alt potted plant',[6,4]], + ['alt bookshelf',[4,6],'join'], + ['alt door',[4,3],'join'], + ['water',[1,5]], + ['green water',[2,5]], + ['dark water',[3,5]], + ['wooden wall',[1,7],'join'], + ['wooden floor',[1,6],'random3'], + ['conveyor belt',[4,7],'join'], + ['entrance',[0,1]], + ['alt entrance',[0,3]], + ['exit',[0,2]], + ['alt exit',[0,4]] + ]); + + + /*===================================================================================== + CREATE MONSTER TYPES + =======================================================================================*/ + + /* + An explanation of stats : + -hp : health points + -speed : determines who attacks first in a fight; bypasses dodging; determines how fast heroes auto-run dungeons + -might : determines how much damage is done to opponents + -guard : lowers incoming damage + -dodge : chance of avoiding incoming attacks completely (affected by the opponent's speed) + -luck : heroes only, determines drops and rare encounters + -rarity : monsters only, determines how often a monster is added to the spawn table + -level : monsters only, determines which average room depth the monster is more likely to spawn in (also determines the loot amount) + */ + Game.monsterIconY=10;//offset for dungeonItems.png monsters + Game.Monsters=[]; + Game.Monster=function(name,pic,icon,level,stats,loot) + { + this.name=name; + this.pic=pic; + this.icon=icon; + this.level=level; + this.stats={}; + for (var i in stats) + {this.stats[i]=stats[i];} + this.stats.hpm=this.stats.hp; + this.stats.rarity=stats.rarity||1; + this.loot=loot||{}; + this.boss=0; + this.quotes={}; + Game.Monsters[this.name]=this; + } + var basicLoot={cookies:{min:1,max:5,prob:0.5}}; + var goodLoot={cookies:{min:3,max:8,prob:1},gear:{prob:0.05}}; + var bossLoot={gear:{prob:1}}; + var chestLoot={cookies:{min:2,max:20,prob:1},gear:{prob:0.1}}; + var bossLoot={cookies:{min:10,max:50,prob:1},gear:{prob:0.2}}; + + //general monsters + new Game.Monster('Doughling','doughling',[0,0],1,{hp:5,might:2,guard:2,speed:6,dodge:6,rarity:0.7},basicLoot); + new Game.Monster('Elder doughling','elderDoughling',[1,0],7,{hp:20,might:7,guard:7,speed:4,dodge:4,rarity:0.7},goodLoot); + new Game.Monster('Angry sentient cookie','angrySentientCookie',[5,0],5,{hp:16,might:8,guard:4,speed:5,dodge:5,rarity:1},basicLoot); + new Game.Monster('Baby sentient cookie','babySentientCookie',[4,0],1,{hp:3,might:1,guard:1,speed:7,dodge:7,rarity:1},basicLoot); + new Game.Monster('Burnt sentient cookie','burntSentientCookie',[6,0],5,{hp:16,might:12,guard:2,speed:3,dodge:2,rarity:0.2},basicLoot); + new Game.Monster('Raw sentient cookie','rawSentientCookie',[5,0],5,{hp:16,might:6,guard:4,speed:7,dodge:7,rarity:0.2},basicLoot); + new 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}}); + Game.Monsters['Sugar bunny'].onKill=function(){Game.Win('Follow the white rabbit');};Game.Monsters['Sugar bunny'].AI='flee'; + + //factory monsters + new Game.Monster('Crazed kneader','crazedKneader',[0,2],6,{hp:18,might:6,guard:8,speed:3,dodge:2,rarity:0.5},goodLoot); + new Game.Monster('Crazed chip-spurter','crazedDoughSpurter',[0,2],6,{hp:15,might:6,guard:8,speed:5,dodge:3,rarity:0.5},goodLoot); + new Game.Monster('Alarm bot','alarmTurret',[3,2],2,{hp:6,might:3,guard:5,speed:8,dodge:8,rarity:0.5},basicLoot); + new 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}}); + Game.Monsters['Chirpy'].onKill=function(){Game.Win('Chirped out');};Game.Monsters['Chirpy'].quotes={fight:'oh, hello <3'}; + new Game.Monster('Disgruntled worker','disgruntledWorker',[1,2],4,{hp:14,might:5,guard:5,speed:6,dodge:4,rarity:0.6},basicLoot); + new Game.Monster('Disgruntled overseer','disgruntledOverseer',[1,2],7,{hp:22,might:7,guard:5,speed:6,dodge:4,rarity:0.5},basicLoot); + new Game.Monster('Disgruntled cleaning lady','disgruntledCleaningLady',[2,2],4,{hp:13,might:4,guard:5,speed:7,dodge:6,rarity:0.3},basicLoot); + + new Game.Monster('Sentient Furnace','sentientFurnace',[0,3],0,{hp:60,might:14,guard:12,speed:4,dodge:0,rarity:1},bossLoot);//boss + Game.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.'}; + new Game.Monster('Ascended Baking Pod','ascendedBakingPod',[1,3],0,{hp:60,might:12,guard:14,speed:4,dodge:0,rarity:0.7},bossLoot);//boss + Game.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.'}; + + + Game.BossMonsters=[]; + for (var i in Game.Monsters) + { + if (Game.Monsters[i].boss) Game.BossMonsters.push(Game.Monsters[i]); + } + + /*===================================================================================== + ENTITY MECHANICS + =======================================================================================*/ + + Game.Entity=function(type,subtype,dungeon,pic,stats)//objects you could find on the map : doors, mobs, interactables, items, player, exits... + { + this.type=type; + this.subtype=subtype||''; + this.dungeon=dungeon; + this.pic=pic||[0,0]; + this.stats={}; + for (var i in stats) + {this.stats[i]=stats[i];} + + this.x=-1; + this.y=-1; + this.obstacle=0; + this.zIndex=1; + if (this.type=='monster') + { + this.obstacle=1; + this.pic=[Game.Monsters[this.subtype].icon[0],Game.Monsters[this.subtype].icon[1]]; + this.pic[1]+=Game.monsterIconY; + this.targets=[]; + this.stuck=0; + this.zIndex=10; + this.fighting=0; + this.AI=Game.Monsters[this.subtype].AI||'normal'; + this.onKill=Game.Monsters[this.subtype].onKill||function(){}; + for (var i in Game.Monsters[this.subtype].stats){this.stats[i]=Game.Monsters[this.subtype].stats[i];} + } + else if (this.type=='hero') + { + this.obstacle=1; + this.pic=[Game.Heroes[this.subtype].icon[0],Game.Heroes[this.subtype].icon[1]]; + this.targets=[]; + this.stuck=0; + this.zIndex=100; + this.fighting=0; + for (var i in Game.Heroes[this.subtype].stats){this.stats[i]=Game.Heroes[this.subtype].stats[i];} + + //increase stats by amount of matching building (change that later to use gear instead) + var mult=Math.max(0,(Game.Objects[this.dungeon.type].amount/20-1)); + this.stats.hpm+=Math.ceil(mult*2); + this.stats.hp=this.stats.hpm; + this.stats.might+=mult; + this.stats.guard+=mult; + this.stats.speed+=mult; + this.stats.dodge+=mult; + } + else if (this.type=='item') + { + this.zIndex=5; + this.value=0; + } + else if (this.type=='destructible')//crates, doors + { + this.obstacle=1; + this.life=3; + this.zIndex=15; + if (this.subtype=='door') this.pic=[0,7]; + else this.pic=[Math.floor(Math.random()*4+2),7]; + + this.onKill=function() + { + if (this.subtype=='random') + { + var value=Math.round(Math.pow(Math.random(),6)*(10+this.dungeon.level)); + if (value>0) + { + var entity=this.dungeon.AddEntity('item','cookies',this.x,this.y); + entity.value=value; + } + } + } + } + else if (this.type=='special') + { + this.zIndex=5; + this.value=''; + this.obstacle=1; + } + + this.Say=function(what) + { + if (this.type=='monster') + { + if (Game.Monsters[this.subtype].quotes[what]) this.dungeon.Log(this.subtype+' : "'+choose(Game.Monsters[this.subtype].quotes[what].split('|'))+'"'); + } + } + this.Draw=function()//return the string to draw this + { + var name='?'; + if (this.subtype=='random') name='clutter'; else name=this.subtype; + if (this.type=='item' && this.subtype=='cookies' && this.value>0) + { + if (this.value<2) this.pic=[0,5]; + else if (this.value<3) this.pic=[1,5]; + else if (this.value<4) this.pic=[2,5]; + else if (this.value<6) this.pic=[3,5]; + else if (this.value<10) this.pic=[4,5]; + else if (this.value<20) this.pic=[5,5]; + else if (this.value<30) this.pic=[7,5]; + else if (this.value<70) this.pic=[6,5]; + else if (this.value<200) this.pic=[8,5]; + else this.pic=[6,6];// if (this.value<1000) this.pic=[1,5]; + } + else if (this.type=='special' && this.subtype=='upgrade') + { + if (this.value!='') this.pic=[7,6]; else this.pic=[8,6]; + } + return '
'; + } + this.Wander=function()//AI to move around aimlessly + { + this.targets=[]; + this.targets.push([-1,0],[1,0],[0,-1],[0,1]); + this.Move(); + } + this.GoTo=function(x,y)//AI to move to a specific point + { + this.targets=[]; + if (this.xx) this.targets.push([-1,0]); + if (this.yy) this.targets.push([0,-1]); + if (!this.Move())//really stuck? try to maneuver laterally! + { + this.targets=[]; + if (this.x==x) this.targets.push([1,0],[-1,0]);//somehow this feels inverted... but it doesn't work the other way + if (this.y==y) this.targets.push([0,1],[0,-1]);//hypothesis : *MAGIC* + this.Move(); + } + } + this.Flee=function(x,y)//AI to run away from a specific point + { + this.targets=[]; + if (this.x>x) this.targets.push([1,0]); + if (this.xy) this.targets.push([0,1]); + if (this.y0) + { + var goodTargets=[]; + if (this.type=='hero') goodTargets=this.targets; + else + { + for (var i in this.targets) + { + var thisTarget=this.targets[i]; + if (this.dungeon.CheckObstacle(this.x+thisTarget[0],this.y+thisTarget[1])!=-1) goodTargets.push([thisTarget[0],thisTarget[1]]); + } + } + if (goodTargets.length>0) + { + var target=choose(goodTargets); + var obstacle=this.dungeon.CheckObstacle(this.x+target[0],this.y+target[1]); + if (obstacle==this) obstacle=0; + if (obstacle==0 && this.AI!='static') + { + this.x+=target[0]; + this.y+=target[1]; + } + else this.stuck+=2; + if (obstacle!=0 && obstacle!=-1) + { + obstacle.HitBy(this); + } + if (obstacle==-1) return 0; + } + else {this.stuck+=2;return 0;} + if (this.AI=='static') this.stuck=0; + return 1; + } + return 0; + } + this.HitBy=function(by)//attacked by another entity + { + if (this.type=='destructible' && by.type=='hero')//break destructibles + { + by.stuck=0; + this.life--; + if (this.life<=0) + { + if (this.onKill) this.onKill(); + this.Destroy(); + } + else this.pic=[this.pic[0],this.pic[1]+1]; + } + else if (this.type=='special' && this.subtype=='upgrade')//upgrade relic + { + this.obstacle=0; + if (Game.Upgrades[this.value]) Game.Upgrades[this.value].earn(); + this.value=''; + } + else if ((this.type=='monster' && by.type=='hero') || (this.type=='hero' && by.type=='monster') && this.stats.hp>0)//it's a fight! + { + by.stuck=0; + + var monster=(this.type=='hero'?by:this); + var hero=(this.type=='hero'?this:by); + this.dungeon.currentOpponent=monster; + + if (monster.fighting==0)//first meeting + { + Game.Heroes[hero.subtype].Say('meet '+Game.Monsters[monster.subtype].name); + this.Say('fight'); + } + if (this.fighting==0) + { + this.fighting=1; + by.fighting=1; + } + + var attackStr=''; + var attackerName=''; + var defenderName=''; + if (by.type=='hero') attackerName=Game.Heroes[by.subtype].name; + else if (by.type=='monster') attackerName=Game.Monsters[by.subtype].name; + if (this.type=='hero') defenderName=Game.Heroes[this.subtype].name; + else if (this.type=='monster') defenderName=Game.Monsters[this.subtype].name; + + //battle formulas (have fun with these) + attackStr+=attackerName+' swings at '+defenderName+'!'; + var 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))); + var dodge=Math.random()>(by.stats.speed/Math.max(1,this.stats.dodge+2.5)); + if (dodge) + { + attackStr+=' '+defenderName+' dodged the attack.'; + } + else + { + if (by.stats.luck && by.type=='hero' && Math.random()It\'s a critical!';}//very rare critical based on luck + attackStr+=' '+damage+' damage!'; + + this.stats.hp-=damage; + this.stats.hp=Math.max(this.stats.hp,0); + if (this.stats.luck && this.type=='hero') + { + if (this.stats.hp==0 && Math.random()'; + if (attackStr!='') this.dungeon.Log(attackStr); + + if (this.stats.hp<=0)//die + { + this.dungeon.Log(attackerName+' crushed '+defenderName+'!'); + if (this.type=='hero') + { + Game.Heroes[this.subtype].Say('defeat'); + this.dungeon.Log(''+Game.Heroes[this.subtype].name+' has been defeated.'); + this.dungeon.FailLevel(); + } + if (this.type=='monster' && by.type=='hero') + { + l('monsterSlot'+this.dungeon.id).style.visibility='hidden'; + this.dungeon.monstersKilledThisRun+=1; + if (Math.random()<0.05) Game.Heroes[by.subtype].Say('win'); + Game.Heroes[by.subtype].Say('win against '+Game.Monsters[this.subtype].name); + this.Say('defeat'); + if (Game.Monsters[this.subtype].loot) + { + var loot=Game.Monsters[this.subtype].loot; + if (loot.gear && (!loot.gear.prob || Math.random()0) this.stuck--; + this.stuck=Math.min(10,this.stuck); + this.targets=[]; + } + if ((this.type=='hero' || this.type=='monster') && this.fighting==0 && this.stats.hp0) + { + this.dungeon.Log('Found '+Beautify(value)+' cookie'+(value==1?'':'s')+'!'); + this.dungeon.cookiesMadeThisRun+=value; + Game.Earn(value); + } + entity.Destroy(); + } + } + } + if (this.type=='hero') this.fighting=0; + } + this.Destroy=function() + { + this.dungeon.entities.splice(this.dungeon.entities.indexOf(this),1); + } + this.GetInitiative=function() + { + return randomFloor((this.stats.speed/5)*(1/Math.max(1,(this.dungeon.heroEntity.stats.speed/5)))); + } + } + + /*===================================================================================== + DUNGEON MECHANICS + =======================================================================================*/ + + Game.Dungeons=[]; + Game.Dungeon=function(type,id) + { + this.type=type; + this.id=id; + Game.Dungeons[this.id]=this; + this.log=[]; + this.logNew=0; + this.name=Game.DungeonTypes[this.type].nameGenerator(); + this.hero=null; + this.currentOpponent=0; + this.level=0; + this.auto=1; + this.portalPic=''; + + this.cookiesMadeThisRun=0; + this.monstersKilledThisRun=0; + + this.Log=function(what,nested) + { + if (typeof what==='string') + { + this.log.unshift(what); + this.logNew++; + } + else {for (var i in what) {this.Log(what[i],1);}} + //if (!nested) this.UpdateLog(); + } + + this.UpdateLog=function() + { + this.log=this.log.slice(0,30); + var str=''; + for (var i in this.log) + { + if (i'; + else str+='
'+this.log[i]+'
'; + } + this.logNew=0; + l('dungeonLog'+this.id).innerHTML=str; + } + + this.entities=[]; + this.GetEntities=function(x,y)//returns the first entity found on tile x,y + { + var entities=[]; + for (var i in this.entities) {if (this.entities[i].x==x && this.entities[i].y==y) entities.push(this.entities[i]);} + return entities; + } + this.AddEntity=function(type,subtype,x,y) + { + //this.RemoveEntities(x,y); + var entity=new Game.Entity(type,subtype,this); + entity.x=x; + entity.y=y; + entity.dungeon=this; + this.entities.push(entity); + return entity; + } + this.RemoveEntities=function(x,y) + { + var entities=this.GetEntities(x,y); + for (var i in entities) + { + entities[i].Destroy(); + } + } + this.DrawEntities=function() + { + var str=''; + for (var i in this.entities) {str+=this.entities[i].Draw();} + return str; + } + + this.CheckObstacle=function(x,y)//returns 0 for no obstacle; -1 for a wall; an entity if there's at least one entity on this tile + { + if (x<0 || x>=this.map.w || y<0 || y>=this.map.h) return -1; + var entities=this.GetEntities(x,y); + for (var i in entities) + { + if (entities[i].obstacle) return entities[i]; + } + return this.map.isObstacle(x,y)?-1:0; + } + + + this.map={}; + this.Generate=function() + { + if (this.level==0) this.name=Game.DungeonTypes[this.type].nameGenerator(); + this.entities=[]; + var M=new D.Map(40,40,Math.random(),{ + roomSize:10, + corridorSize:5, + fillRatio:1/2, + corridorRatio:0.3, + pillarRatio:Math.random()*0.8+0.2, + waterRatio:Math.random(), + branching:Math.ceil(Math.random()*6), + sizeVariance:0.4 + }); + r=0; + while (r!=1) + { + r=M.dig(); + } + //all done! decorate and render. + M.finish(); + //spawn treasure + /* + for (var i in M.rooms) + { + if (M.rooms[i].freeTiles>1) + { + for (var ii=0;ii1) + { + var spot=M.getBestSpotInRoom(M.rooms[i]); + M.data[spot.x][spot.y][0]=0; + spot.score=0; + M.rooms[i].freeTiles--; + } + } + } + }*/ + + for (var i in M.doors)//place door entities on door positions + { + //M.data[M.doors[i][0]][M.doors[i][1]][0]=TILE_FLOOR_EDGE; + this.AddEntity('destructible','door',M.doors[i][0],M.doors[i][1]); + } + //set tile graphics + for (var i in M.rooms) + { + var altStr=choose(['alt ','','']); + var tiles={ + 'void':altStr+'void', + 'wall':altStr+'wall', + 'wall corner':altStr+'wall corner', + 'floor':altStr+'tiled floor', + 'floor edges':altStr+'floor',//choose([altStr+'floor',altStr+'floor edges']), + 'door':altStr+'door', + 'water':choose(['water','green water','dark water']), + 'pillar':choose([altStr+'wall',altStr+'round pillar',altStr+'square pillar',altStr+'potted plant','conveyor belt']), + 'entrance':altStr+'entrance', + 'exit':altStr+'exit', + }; + if (Math.random()<0.1) {tiles['wall corner']='wooden wall';tiles['wall']='wooden wall';tiles['floor edges']='wooden floor';tiles['pillar']='wooden wall';} + if (Math.random()<0.1) {tiles['wall corner']=altStr+'bookshelf';tiles['wall']=altStr+'bookshelf';tiles['pillar']=altStr+'bookshelf';} + M.assignTiles(M.rooms[i],tiles); + } + this.map=M; + this.map.str=this.map.getStr(); + + //place a boss + var tile=this.map.exit; + var monsters=[]; + for (var ii in Game.BossMonsters) + { + var me=Game.BossMonsters[ii]; + if (me.level<=(depth+this.level) && Math.random()<(me.stats.rarity||1)) monsters.push(me.name); + } + if (monsters.length==0) monsters=[choose(Game.BossMonsters).name]; + if (monsters.length>0) + { + this.AddEntity('monster',choose(monsters),tile[0],tile[1]); + this.map.removeFreeTile(tile[0],tile[1]); + } + + //place relics + /* + var tile=this.map.getBestSpotInRoom(this.map.getRoom(this.map.exit[0],this.map.exit[1])); + var entity=this.AddEntity('special','upgrade',tile.x,tile.y); + entity.value='Dungeon cookie upgrade'; + this.map.removeFreeTile(tile.x,tile.y); + for (var i=0;i10) + { + var tile=this.map.getBestSpotInRoom(room); + var entity=this.AddEntity('special','upgrade',tile.x,tile.y); + entity.value='Dungeon cookie upgrade'; + this.map.removeFreeTile(tile.x,tile.y); + } + }*/ + + //sprinkle monsters and treasure + for (var i=0;i0) + { + this.AddEntity('monster',choose(monsters),tile[0],tile[1]); + this.map.removeFreeTile(tile[0],tile[1]); + } + } + else//the rest of the spawns are destructibles or loot + { + if (Math.random()<0.6) + { + var value=Math.round(Math.pow(Math.random(),6)*(10+this.level)); + if (value>0) + { + var entity=this.AddEntity('item','cookies',tile[0],tile[1]);//random cookies + entity.value=value; + } + } + else this.AddEntity('destructible','random',tile[0],tile[1]);//random crates etc + this.map.removeFreeTile(tile[0],tile[1]); + } + } + } + } + + this.onTile=-1; + + this.Draw=function() + { + var str=''; + var x=-this.hero.x; + var y=-this.hero.y; + str+='
'+this.map.str+'
'; + str+='
'+ + '
'+ + '
'+ + '
'+ + '
'+ + '
'+ + '
'; + str+='
'+ + '
Exit - '+this.name+' lvl.'+(this.level+1)+'
'+ + '
'+ + '
'+ + '
'+ + '
'; + l('rowSpecial'+this.id).innerHTML='
'+str+'
'; + + l('picHero'+this.id).style.backgroundImage='url(img/'+this.hero.portrait+'.png)'; + l('nameHero'+this.id).innerHTML=this.hero.name; + } + this.Refresh=function() + { + if (!l('mapcontainer'+this.id)) this.Draw(); + var x=4-this.hero.x; + var y=4-this.hero.y; + l('mapcontainer'+this.id).style.left=(x*16)+'px'; + l('mapcontainer'+this.id).style.top=(y*16)+'px'; + l('mapitems'+this.id).innerHTML=this.DrawEntities(); + } + this.RedrawMap=function() + { + this.map.str=this.map.getStr(); + this.Draw(); + } + this.Turn=function() + { + for (var i in this.entities) + { + if (this.entities[i] && this.entities[i].type) this.entities[i].Turn(); + } + if (this.currentOpponent) + { + l('monsterSlot'+this.id).style.visibility='visible'; + l('hpMonster'+this.id).style.width=Math.round((this.currentOpponent.stats.hp/this.currentOpponent.stats.hpm)*100)+'%'; + l('picMonster'+this.id).style.backgroundImage='url(img/'+Game.Monsters[this.currentOpponent.subtype].pic+'.png)'; + l('nameMonster'+this.id).innerHTML=Game.Monsters[this.currentOpponent.subtype].name; + l('picHero'+this.id).style.backgroundImage='url(img/'+this.hero.pic+'.png)'; + } + else + { + l('monsterSlot'+this.id).style.visibility='hidden'; + l('hpMonster'+this.id).style.width='100%'; + l('picHero'+this.id).style.backgroundImage='url(img/'+this.hero.portrait+'.png)'; + } + this.currentOpponent=0; + l('hpHero'+this.id).style.width=Math.round((this.heroEntity.stats.hp/this.heroEntity.stats.hpm)*100)+'%'; + + this.Refresh(); + this.UpdateLog(); + + if (this.hero.x==this.map.exit[0] && this.hero.y==this.map.exit[1]) + { + this.CompleteLevel(); + } + } + + this.DrawButton=function() + { + var str=''; + //str+=''; + str+=''; + return str; + } + + this.CompleteLevel=function() + { + this.hero.Say('completion'); + this.level++; + this.Generate(); + Game.HeroesById[0].EnterDungeon(this,this.map.entrance[0],this.map.entrance[1]); + this.Draw(); + } + this.FailLevel=function() + { + this.Log('Cookies made this run : '+Beautify(this.cookiesMadeThisRun)+' | Monsters defeated this run : '+Beautify(this.monstersKilledThisRun)); + this.cookiesMadeThisRun=0; + this.monstersKilledThisRun=0; + this.level=0; + this.Generate(); + Game.HeroesById[0].EnterDungeon(this,this.map.entrance[0],this.map.entrance[1]); + this.Draw(); + } + } + + Game.DungeonLocationChain=function(map,x,y)//return an array of the rooms between the root room and this tile's room, inclusive + {//we shouldn't need all this if we used A*... + var room=map.getRoom(x,y); + var chain=[]; + if (room!=-1) + { + while (room.parent) + { + chain.push(room); + room=room.parent; + } + } + chain.reverse(); + return chain; + } + Game.DungeonLinkLocationChains=function(start,end)//return the room in which the first location chain should go to to get closer to the second location chain + { + /* + 4 cases + -we're already in the same room + -the target is in a different branch + -the target is above in the same branch + -the target is below in the same branch + */ + start.reverse(); + end.reverse(); + if (start[0].id==end[0].id) return start[start.length-1];//same room + for (var i in end) + { + if (start[0]==end[i].parent) return end[i];//inferior branch, go to the inferior room + } + if (start.length>1) return start[1];//different or superior branch, go to the superior room + return start[0];//eeeh, let's just stay in the same room + } + + /*===================================================================================== + CREATE DUNGEONS + =======================================================================================*/ + Game.Objects['Factory'].special=function() + { + this.dungeon=new Game.Dungeon('Factory',this.id); + this.dungeon.Generate(); + this.specialDrawFunction=function(){this.dungeon.Refresh();}; + this.drawSpecialButton=function(){return this.dungeon.DrawButton();}; + this.dungeon.timer=0; + this.dungeon.timerWarmup=5; + this.dungeon.portalPic='dungeonFactory'; + + this.EachFrame=function() + { + if (this.dungeon.auto) + { + if (this.dungeon.timer>0) this.dungeon.timer--; + if (this.dungeon.timer==0) + { + this.dungeon.timer=Game.fps*(Math.max(0.1,2-(this.dungeon.hero.stats.speed*0.2))+Math.max(this.dungeon.timerWarmup,0)); + if (this.dungeon.timerWarmup>0) this.dungeon.timerWarmup--; + + var dungeon=this.dungeon; + var hero=dungeon.heroEntity; + + var targetRoom=Game.DungeonLinkLocationChains(Game.DungeonLocationChain(dungeon.map,hero.x,hero.y),Game.DungeonLocationChain(dungeon.map,dungeon.map.exit[0],dungeon.map.exit[1])); + var targetTile=(targetRoom.gen==0 || targetRoom.id==dungeon.map.getRoom(hero.x,hero.y).id)?[dungeon.map.exit[0],dungeon.map.exit[1]]:targetRoom.door; + hero.GoTo(targetTile[0],targetTile[1]); + if (hero.stuck) hero.Wander(); + dungeon.hero.x=hero.x; + dungeon.hero.y=hero.y; + dungeon.Turn(); + } + } + } + + if (document.addEventListener)//clean this up later + { + l('rowSpecial'+this.dungeon.id).removeEventListener('keydown',arguments.callee,false); + l('rowSpecial'+this.dungeon.id).addEventListener('keydown',function(event) + { + var dungeon=Game.Objects['Factory'].dungeon; + var control=0; + if (event.keyCode==37) {dungeon.hero.Move(-1,0);control=1;} + else if (event.keyCode==38) {dungeon.hero.Move(0,-1);control=1;} + else if (event.keyCode==39) {dungeon.hero.Move(1,0);control=1;} + else if (event.keyCode==40) {dungeon.hero.Move(0,1);control=1;} + else if (event.keyCode==32) {dungeon.hero.Move(0,0);control=1;}//space + else if (event.keyCode==65)//A (auto) + { + if (dungeon.auto) + { + dungeon.auto=0; + dungeon.timerWarmup=-1; + } + else + { + dungeon.auto=1; + dungeon.timer=0; + dungeon.timerWarmup=0; + } + event.preventDefault(); + } + + if (control) + { + event.preventDefault(); + dungeon.timer=Game.fps*10; + dungeon.timerWarmup=5; + } + } + ); + } + + var hero=choose(Game.HeroesById); + hero.EnterDungeon(this.dungeon,this.dungeon.map.entrance[0],this.dungeon.map.entrance[1]); + } + + /*===================================================================================== + HEROES + =======================================================================================*/ + Game.Heroes=[]; + Game.HeroesById=[]; + Game.Hero=function(name,pic,portrait,icon) + { + this.name=name; + this.pic=pic; + this.portrait=portrait; + this.icon=icon; + this.stats={ + hp:25, + hpm:25, + might:5, + guard:5, + speed:5, + dodge:5, + luck:5 + }; + this.dialogue={ + 'greeting':'Oh hey.|Sup.', + 'entrance':'Here we go.|So exciting.', + 'completion':'That was easy.|All done here.', + 'defeat':'Welp.|Better luck next time.' + }; + this.gear={ + 'armor':-1, + 'weapon':-1 + }; + this.inDungeon=-1; + this.completedDungeons=0; + + this.x=0; + this.y=0; + + this.EnterDungeon=function(dungeon,x,y) + { + this.inDungeon=dungeon.id; + dungeon.hero=this; + this.x=x; + this.y=y; + dungeon.heroEntity=dungeon.AddEntity('hero',dungeon.hero.name,x,y); + var room=dungeon.map.getRoom(this.x,this.y); + if (room!=-1 && room.hidden) {room.hidden=0;dungeon.RedrawMap();} + Game.Dungeons[this.inDungeon].Refresh(); + dungeon.Log('--------------------'); + if (dungeon.level==0) this.Say('greeting'); + this.Say('entrance'); + l('monsterSlot'+dungeon.id).style.visibility='hidden'; + } + this.Move=function(x,y) + { + var dungeon=Game.Dungeons[this.inDungeon]; + dungeon.heroEntity.targets=[[x,y]]; + if (dungeon.heroEntity.Move()) + { + this.x=dungeon.heroEntity.x; + this.y=dungeon.heroEntity.y; + dungeon.Turn(); + } + } + + this.Say=function(what) + { + if (this.dialogue[what]) Game.Dungeons[this.inDungeon].Log(this.name+' : "'+choose(this.dialogue[what].split('|'))+'"'); + } + + this.save=function() + { + var str=''; + str+= + this.inDungeon+','+ + this.completedDungeons+','+ + this.gear.armor+','+ + this.gear.weapon + ; + return str; + } + this.load=function(data) + { + var str=data.split(','); + this.inDungeon=parseInt(str[0]); + this.completedDungeons=parseInt(str[1]); + this.gear.armor=parseInt(str[2]); + this.gear.weapon=parseInt(str[3]); + } + this.id=Game.HeroesById.length; + Game.HeroesById.push(this); + Game.Heroes[this.name]=this; + } + + /*===================================================================================== + CREATE HEROES + =======================================================================================*/ + var hero=new Game.Hero('Chip','girlscoutChip','portraitChip',[1,0]); + hero.dialogue={ + 'intro':'I\'m Chip! I just really like exploring stuff. Let\'s go have an adventure!', + 'greeting':'Hello there!|I\'m ready!|Where are we going today?|Adventure!', + 'win':'Take that!|Hah!|That\'s right.', + '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.', + '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!', + '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!', + 'win against Sentient Furnace':'The irony, it burns! (...it\'s funny because it was burning. And made of iron. ...Moving on.)', + 'win against Ascended Baking Pod':'Where is your pod now?|That was disturbing.' + }; + hero.stats={ + hp:30, + hpm:30, + might:5, + guard:5, + speed:5, + dodge:5, + luck:5 + }; + var hero=new Game.Hero('Crumb','girlscoutCrumb','portraitCrumb',[2,0]); + hero.dialogue={ + '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.', + 'greeting':'Hi there.|Ready for adventure, I guess.|Reporting for duty.', + 'win':'Oh sorry, did that hurt?|Should have moved out of the way.|Oops. My bad.', + '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.', + '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.', + '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...', + 'meet Ascended Baking Pod':'That thing shouldn\'t even be alive.|Is that where they all came from?', + 'win against Ascended Baking Pod':'Hm. Fascinating.' + }; + hero.stats={ + hp:25, + hpm:25, + might:5, + guard:7, + speed:4, + dodge:4, + luck:5 + }; + var hero=new Game.Hero('Doe','girlscoutDoe','portraitDoe',[3,0]); + hero.dialogue={ + 'intro':'H-hey. Name\'s Doe. I\'m pretty fast. I uh, I promise I\'ll do my best.', + 'greeting':'H-hey.|Oh, uh, h-hi there.|C-can I join?', + 'win':'Th-that looks like it hurt... awesome...|D-did I do that?|N-neat... there\'s pieces everywhere.', + '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!', + '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.', + '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.', + 'meet Ascended Baking Pod':'W-whoah... it\'s... magnificent...', + 'win against Ascended Baking Pod':'I\'m sorry, buddy.|I... I think I hurt it...|Oh no... I-I think I broke it...' + }; + hero.stats={ + hp:25, + hpm:25, + might:4, + guard:4, + speed:7, + dodge:5, + luck:5 + }; + var hero=new Game.Hero('Lucky','girlscoutLucky','portraitLucky',[4,0]); + hero.dialogue={ + 'intro':'Oh joy! My name\'s Lucky. Guess what I\'m good at?', + 'greeting':'I\'m feeling lucky!|It\'s a bright day today!|Let\'s do great things together.', + 'win':'Ooh lucky shot!|Pow! One more.|Damn straight!', + '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!', + '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!', + '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!', + 'win against Ascended Baking Pod':'Golly, that was peculiar.' + }; + hero.stats={ + hp:25, + hpm:25, + might:5, + guard:4, + speed:4, + dodge:5, + luck:7 + }; + +}; \ No newline at end of file