PageRenderTime 61ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/src/com/robotacid/level/Map.as

http://github.com/st33d/red-rogue
ActionScript | 1531 lines | 1141 code | 209 blank | 181 comment | 770 complexity | 50395cf73eaf47634c7a3667aac502a9 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. package com.robotacid.level {
  2. import com.robotacid.engine.Altar;
  3. import com.robotacid.engine.ChaosWall;
  4. import com.robotacid.engine.Character;
  5. import com.robotacid.engine.ColliderEntity;
  6. import com.robotacid.engine.ColliderEntitySensor;
  7. import com.robotacid.engine.Effect;
  8. import com.robotacid.engine.FadeLight;
  9. import com.robotacid.engine.Gate;
  10. import com.robotacid.engine.Item;
  11. import com.robotacid.engine.MapTileConverter;
  12. import com.robotacid.engine.MapTileManager;
  13. import com.robotacid.engine.Player;
  14. import com.robotacid.engine.Portal;
  15. import com.robotacid.engine.Stone;
  16. import com.robotacid.engine.Torch;
  17. import com.robotacid.engine.Trap;
  18. import com.robotacid.engine.Writing;
  19. import com.robotacid.geom.Pixel;
  20. import com.robotacid.gfx.BlitRect;
  21. import com.robotacid.gfx.BlitSprite;
  22. import com.robotacid.gfx.Renderer;
  23. import com.robotacid.phys.Collider;
  24. import com.robotacid.util.array.randomiseArray;
  25. import com.robotacid.util.Rng;
  26. import flash.display.Bitmap;
  27. import flash.display.BitmapData;
  28. import flash.display.Sprite;
  29. import flash.geom.Point;
  30. import flash.geom.Rectangle;
  31. /**
  32. * This is the random map generator
  33. *
  34. * The layout for every level is calculated in here.
  35. *
  36. * MapBitmap creates the passage ways and creates a connectivity graph to place ladders and ledges
  37. * the convertMapBitmap method converts that data into references to graphics and entities
  38. * within that method Content.populateLevel distributes monsters and treasure
  39. *
  40. * @author Aaron Steed, robotacid.com
  41. */
  42. public class Map {
  43. public static var game:Game;
  44. public static var renderer:Renderer;
  45. public static var random:Rng;
  46. public static var seed:uint = 0;
  47. public var level:int;
  48. public var type:int;
  49. public var width:int;
  50. public var height:int;
  51. public var start:Pixel;
  52. public var stairsUp:Pixel;
  53. public var stairsDown:Pixel;
  54. public var portals:Vector.<Pixel>;
  55. public var zone:int;
  56. public var completionCount:int;
  57. public var completionTotal:int;
  58. public var cleared:Boolean;
  59. public var bitmap:MapBitmap;
  60. private var i:int, j:int;
  61. public var layers:Array;
  62. // types
  63. public static const MAIN_DUNGEON:int = 0;
  64. public static const ITEM_DUNGEON:int = 1;
  65. public static const AREA:int = 2;
  66. // zones
  67. public static const DUNGEONS:int = 0;
  68. public static const SEWERS:int = 1;
  69. public static const CAVES:int = 2;
  70. public static const CHAOS:int = 3;
  71. // layers
  72. public static const BACKGROUND:int = 0;
  73. public static const BLOCKS:int = 1;
  74. public static const ENTITIES:int = 2;
  75. // outside area levels
  76. public static const OVERWORLD:int = 0;
  77. public static const UNDERWORLD:int = 1;
  78. public static const LAYER_NUM:int = 3;
  79. public static const BACKGROUND_WIDTH:int = 8;
  80. public static const BACKGROUND_HEIGHT:int = 8;
  81. public static const UNDERWORLD_BOAT_MIN:int = 8;
  82. public static const UNDERWORLD_BOAT_MAX:int = 17;
  83. public static const UNDERWORLD_PORTAL_X:int = 13;
  84. public static const OVERWORLD_STAIRS_X:int = 12;
  85. public static const OVERWORLD_PORTAL_X:int = 17;
  86. public static const LEVELS_PER_ZONE:int = 3;
  87. public static const ZONE_TOTAL:int = 4;
  88. public static const ZONE_NAMES:Vector.<String> = Vector.<String>(["dungeons", "sewers", "caves", "chaos"]);
  89. public function Map(level:int, type:int = MAIN_DUNGEON) {
  90. this.level = level;
  91. this.type = type;
  92. completionCount = completionTotal = 0;
  93. layers = [];
  94. portals = new Vector.<Pixel>();
  95. if(type == MAIN_DUNGEON || type == ITEM_DUNGEON){
  96. zone = game.content.getLevelZone(level);
  97. } else {
  98. zone = 0;
  99. }
  100. if(type == MAIN_DUNGEON){
  101. if(level > 0){
  102. random.r = game.content.getSeed(level, type);
  103. bitmap = new MapBitmap(level, type, zone);
  104. width = bitmap.width;
  105. height = bitmap.height;
  106. convertMapBitmap(bitmap.bitmapData);
  107. } else {
  108. createTestBed();
  109. }
  110. } else if(type == ITEM_DUNGEON){
  111. random.r = game.content.getSeed(level, type);
  112. var sideDungeonSize:int = 1 + (Number(level) / 10);
  113. bitmap = new MapBitmap(sideDungeonSize, type, zone);
  114. width = bitmap.width;
  115. height = bitmap.height;
  116. convertMapBitmap(bitmap.bitmapData);
  117. } else if(type == AREA){
  118. if(level == OVERWORLD){
  119. createOverworld();
  120. } else if(level == UNDERWORLD){
  121. createUnderworld();
  122. }
  123. }
  124. createBackground();
  125. // remove gate pixels over exits for ai graph
  126. if(stairsDown && bitmap.bitmapData.getPixel32(stairsDown.x, stairsDown.y) == MapBitmap.GATE) bitmap.bitmapData.setPixel32(stairsDown.x, stairsDown.y, MapBitmap.EMPTY);
  127. if(stairsUp && bitmap.bitmapData.getPixel32(stairsUp.x, stairsUp.y) == MapBitmap.GATE) bitmap.bitmapData.setPixel32(stairsUp.x, stairsUp.y, MapBitmap.EMPTY);
  128. for(i = 0; i < portals.length; i++){
  129. if(bitmap.bitmapData.getPixel32(portals[i].x, portals[i].y) == MapBitmap.GATE) bitmap.bitmapData.setPixel32(portals[i].x, portals[i].y, MapBitmap.EMPTY);
  130. }
  131. //bitmap.scaleX = bitmap.scaleY = 2;
  132. //game.addChild(bitmap);
  133. }
  134. /* Create the test bed
  135. *
  136. * This is a debugging playground for testing new content and trying to lure consistent
  137. * bugs out into the open (which is nigh on fucking impossible in a procedural world)
  138. */
  139. public function createTestBed():void{
  140. bitmap = new MapBitmap(0, AREA);
  141. width = bitmap.width;
  142. height = bitmap.height;
  143. // background
  144. layers.push(createGrid(null, width, height));
  145. // blocks - start with a full grid
  146. layers.push(createGrid(MapTileConverter.WALL, width, height));
  147. // game objects
  148. layers.push(createGrid(null, width, height));
  149. fill(0, 1, 1, width-2, height-2, layers[BLOCKS]);
  150. // access point
  151. setPortal((width * 0.5) >> 0, height - 2, <portal type={Portal.PORTAL} targetLevel={-1} targetType={Map.MAIN_DUNGEON} />);
  152. start = portals[0];
  153. // set zone for background debugging
  154. zone = (game.gameMenu.editorList.dungeonLevelList.selection) / LEVELS_PER_ZONE;
  155. if(zone >= 4) zone = CHAOS;
  156. }
  157. /* This is where we convert our map template into a level proper made of tileIds and other
  158. * information
  159. */
  160. public function convertMapBitmap(bitmapData:BitmapData):void{
  161. width = bitmapData.width;
  162. height = bitmapData.height;
  163. // background
  164. layers.push(createGrid(null, bitmapData.width, bitmapData.height));
  165. // blocks - start with a full grid
  166. layers.push(createGrid(MapTileConverter.WALL, bitmapData.width, bitmapData.height));
  167. // game objects
  168. layers.push(createGrid(null, bitmapData.width, bitmapData.height));
  169. var pixels:Vector.<uint> = bitmapData.getVector(bitmapData.rect);
  170. // create ladders, ledges and features
  171. // do a first pass to set up pit-traps and secrets and remove them from pixels[]
  172. // it makes the convoluted ledge/ladder checks simpler
  173. var r:int, c:int;
  174. for(i = width; i < pixels.length - width; i++){
  175. c = i % width;
  176. r = i / width;
  177. if(pixels[i] == MapBitmap.PIT){
  178. createPitTrap(c, r);
  179. pixels[i] = MapBitmap.WALL;
  180. } else if(pixels[i] == MapBitmap.SECRET){
  181. createSecretWall(c, r);
  182. pixels[i] = MapBitmap.WALL;
  183. } else if(pixels[i] == MapBitmap.GATE){
  184. // gates are created later
  185. pixels[i] = MapBitmap.EMPTY;
  186. }
  187. }
  188. // now for ladders, ledges and empty spaces
  189. for(i = width; i < pixels.length - width; i++){
  190. c = i % width;
  191. r = i / width;
  192. if(pixels[i] == MapBitmap.EMPTY && pixels[i + width] == MapBitmap.LADDER_LEDGE){
  193. layers[BLOCKS][r][c] = MapTileConverter.LADDER_TOP;
  194. } else if(pixels[i] == MapBitmap.LEDGE && pixels[i + width] == MapBitmap.LADDER_LEDGE){
  195. if((pixels[i - 1] == MapBitmap.EMPTY || pixels[i - 1] == MapBitmap.LADDER) && (pixels[i + 1] == MapBitmap.EMPTY || pixels[i + 1] == MapBitmap.LADDER)){
  196. layers[BLOCKS][r][c] = MapTileConverter.LADDER_TOP_LEDGE;
  197. } else if((pixels[i - 1] == MapBitmap.EMPTY || pixels[i - 1] == MapBitmap.LADDER) && (pixels[i + 1] == MapBitmap.LEDGE || pixels[i + 1] == MapBitmap.LADDER_LEDGE)){
  198. layers[BLOCKS][r][c] = MapTileConverter.LADDER_TOP_LEDGE_END_LEFT;
  199. } else if((pixels[i - 1] == MapBitmap.EMPTY || pixels[i - 1] == MapBitmap.LADDER) && pixels[i + 1] == MapBitmap.WALL){
  200. layers[BLOCKS][r][c] = MapTileConverter.LADDER_TOP_LEDGE_START_RIGHT_END;
  201. } else if(pixels[i - 1] == MapBitmap.WALL && (pixels[i + 1] == MapBitmap.LEDGE || pixels[i + 1] == MapBitmap.LADDER_LEDGE)){
  202. layers[BLOCKS][r][c] = MapTileConverter.LADDER_TOP_LEDGE_START_LEFT;
  203. } else if(pixels[i - 1] == MapBitmap.WALL && pixels[i + 1] == MapBitmap.WALL){
  204. layers[BLOCKS][r][c] = MapTileConverter.LADDER_TOP_LEDGE_SINGLE;
  205. } else if((pixels[i - 1] == MapBitmap.LEDGE || pixels[i - 1] == MapBitmap.LADDER_LEDGE) && pixels[i + 1] == MapBitmap.WALL){
  206. layers[BLOCKS][r][c] = MapTileConverter.LADDER_TOP_LEDGE_START_RIGHT;
  207. } else if((pixels[i - 1] == MapBitmap.LEDGE || pixels[i - 1] == MapBitmap.LADDER_LEDGE) && (pixels[i + 1] == MapBitmap.EMPTY || pixels[i + 1] == MapBitmap.LADDER)){
  208. layers[BLOCKS][r][c] = MapTileConverter.LADDER_TOP_LEDGE_END_RIGHT;
  209. } else if(pixels[i - 1] == MapBitmap.WALL && (pixels[i + 1] == MapBitmap.EMPTY || pixels[i + 1] == MapBitmap.LADDER)){
  210. layers[BLOCKS][r][c] = MapTileConverter.LADDER_TOP_LEDGE_START_LEFT_END;
  211. } else if((pixels[i - 1] == MapBitmap.LEDGE || pixels[i - 1] == MapBitmap.LADDER_LEDGE) && (pixels[i + 1] == MapBitmap.LEDGE || pixels[i + 1] == MapBitmap.LADDER_LEDGE)){
  212. layers[BLOCKS][r][c] = MapTileConverter.LADDER_TOP_LEDGE_MIDDLE;
  213. }
  214. } else if(pixels[i] == MapBitmap.LADDER_LEDGE){
  215. if((pixels[i - 1] == MapBitmap.EMPTY || pixels[i - 1] == MapBitmap.LADDER) && (pixels[i + 1] == MapBitmap.EMPTY || pixels[i + 1] == MapBitmap.LADDER)){
  216. layers[BLOCKS][r][c] = MapTileConverter.LADDER_LEDGE;
  217. } else if((pixels[i - 1] == MapBitmap.EMPTY || pixels[i - 1] == MapBitmap.LADDER) && (pixels[i + 1] == MapBitmap.LEDGE || pixels[i + 1] == MapBitmap.LADDER_LEDGE)){
  218. layers[BLOCKS][r][c] = MapTileConverter.LADDER_LEDGE_END_LEFT;
  219. } else if((pixels[i - 1] == MapBitmap.EMPTY || pixels[i - 1] == MapBitmap.LADDER) && pixels[i + 1] == MapBitmap.WALL){
  220. layers[BLOCKS][r][c] = MapTileConverter.LADDER_LEDGE_START_RIGHT_END;
  221. } else if(pixels[i - 1] == MapBitmap.WALL && (pixels[i + 1] == MapBitmap.LEDGE || pixels[i + 1] == MapBitmap.LADDER_LEDGE)){
  222. layers[BLOCKS][r][c] = MapTileConverter.LADDER_LEDGE_START_LEFT;
  223. } else if(pixels[i - 1] == MapBitmap.WALL && pixels[i + 1] == MapBitmap.WALL){
  224. layers[BLOCKS][r][c] = MapTileConverter.LADDER_LEDGE_SINGLE;
  225. } else if((pixels[i - 1] == MapBitmap.LEDGE || pixels[i - 1] == MapBitmap.LADDER_LEDGE) && pixels[i + 1] == MapBitmap.WALL){
  226. layers[BLOCKS][r][c] = MapTileConverter.LADDER_LEDGE_START_RIGHT;
  227. } else if((pixels[i - 1] == MapBitmap.LEDGE || pixels[i - 1] == MapBitmap.LADDER_LEDGE) && (pixels[i + 1] == MapBitmap.EMPTY || pixels[i + 1] == MapBitmap.LADDER)){
  228. layers[BLOCKS][r][c] = MapTileConverter.LADDER_LEDGE_END_RIGHT;
  229. } else if(pixels[i - 1] == MapBitmap.WALL && (pixels[i + 1] == MapBitmap.EMPTY || pixels[i + 1] == MapBitmap.LADDER)){
  230. layers[BLOCKS][r][c] = MapTileConverter.LADDER_LEDGE_START_LEFT_END;
  231. } else if((pixels[i - 1] == MapBitmap.LEDGE || pixels[i - 1] == MapBitmap.LADDER_LEDGE) && (pixels[i + 1] == MapBitmap.LEDGE || pixels[i + 1] == MapBitmap.LADDER_LEDGE)){
  232. layers[BLOCKS][r][c] = MapTileConverter.LADDER_LEDGE_MIDDLE;
  233. }
  234. } else if(pixels[i] == MapBitmap.LADDER){
  235. layers[BLOCKS][r][c] = MapTileConverter.LADDER;
  236. } else if(pixels[i] == MapBitmap.LEDGE){
  237. if((pixels[i - 1] == MapBitmap.EMPTY || pixels[i - 1] == MapBitmap.LADDER) && (pixels[i + 1] == MapBitmap.EMPTY || pixels[i + 1] == MapBitmap.LADDER)){
  238. layers[BLOCKS][r][c] = MapTileConverter.LEDGE;
  239. } else if((pixels[i - 1] == MapBitmap.EMPTY || pixels[i - 1] == MapBitmap.LADDER) && (pixels[i + 1] == MapBitmap.LEDGE || pixels[i + 1] == MapBitmap.LADDER_LEDGE)){
  240. layers[BLOCKS][r][c] = MapTileConverter.LEDGE_END_LEFT;
  241. } else if((pixels[i - 1] == MapBitmap.EMPTY || pixels[i - 1] == MapBitmap.LADDER) && pixels[i + 1] == MapBitmap.WALL){
  242. layers[BLOCKS][r][c] = MapTileConverter.LEDGE_START_RIGHT_END;
  243. } else if(pixels[i - 1] == MapBitmap.WALL && (pixels[i + 1] == MapBitmap.LEDGE || pixels[i + 1] == MapBitmap.LADDER_LEDGE)){
  244. layers[BLOCKS][r][c] = MapTileConverter.LEDGE_START_LEFT;
  245. } else if(pixels[i - 1] == MapBitmap.WALL && pixels[i + 1] == MapBitmap.WALL){
  246. layers[BLOCKS][r][c] = MapTileConverter.LEDGE_SINGLE;
  247. } else if((pixels[i - 1] == MapBitmap.LEDGE || pixels[i - 1] == MapBitmap.LADDER_LEDGE) && pixels[i + 1] == MapBitmap.WALL){
  248. layers[BLOCKS][r][c] = MapTileConverter.LEDGE_START_RIGHT;
  249. } else if((pixels[i - 1] == MapBitmap.LEDGE || pixels[i - 1] == MapBitmap.LADDER_LEDGE) && (pixels[i + 1] == MapBitmap.EMPTY || pixels[i + 1] == MapBitmap.LADDER)){
  250. layers[BLOCKS][r][c] = MapTileConverter.LEDGE_END_RIGHT;
  251. } else if(pixels[i - 1] == MapBitmap.WALL && (pixels[i + 1] == MapBitmap.EMPTY || pixels[i + 1] == MapBitmap.LADDER)){
  252. layers[BLOCKS][r][c] = MapTileConverter.LEDGE_START_LEFT_END;
  253. } else if((pixels[i - 1] == MapBitmap.LEDGE || pixels[i - 1] == MapBitmap.LADDER_LEDGE) && (pixels[i + 1] == MapBitmap.LEDGE || pixels[i + 1] == MapBitmap.LADDER_LEDGE)){
  254. layers[BLOCKS][r][c] = MapTileConverter.LEDGE_MIDDLE;
  255. }
  256. } else if(pixels[i] == MapBitmap.EMPTY){
  257. layers[BLOCKS][r][c] = 0;
  258. }
  259. }
  260. // create access points
  261. var portalXMLs:Array = game.content.getPortals(level, type);
  262. var portalType:int;
  263. if(type == MAIN_DUNGEON){
  264. createAccessPoint(Portal.STAIRS, sortRoomsTopWards);
  265. for(i = 0; i < portalXMLs.length; i++){
  266. portalType = portalXMLs[i].@type;
  267. createAccessPoint(portalType, null, portalXMLs[i]);
  268. }
  269. createAccessPoint(Portal.STAIRS, sortRoomsBottomWards);
  270. } else if(type == ITEM_DUNGEON){
  271. for(i = 0; i < portalXMLs.length; i++){
  272. portalType = portalXMLs[i].@type;
  273. createAccessPoint(portalType, i == 0 ? sortRoomsTopWards : null, portalXMLs[i]);
  274. }
  275. }
  276. // reload pixels - access point creation altered the bitmap
  277. pixels = bitmapData.getVector(bitmapData.rect);
  278. // gates may fragment the level, so we need as many options for placing a key as possible
  279. if(bitmap.gates.length) createGates();
  280. // a good dungeon needs to be full of loot and monsters
  281. // in comes the content manager to mete out a decent amount of action and reward per level
  282. // content manager stocks are limited to avoid scumming
  283. completionCount += game.content.populateLevel(type, level, bitmap, layers, random);
  284. // now add some extra flavour
  285. createOtherTraps(pixels);
  286. // beyond the starting position of the underworld portal, the minion cannot have written anything
  287. if(level <= Writing.story.length && type == MAIN_DUNGEON) createWritings(pixels);
  288. createAltars(pixels);
  289. if(zone == DUNGEONS) createTorches(pixels);
  290. createDecor(pixels);
  291. createChaosWalls(pixels);
  292. createCritters();
  293. completionTotal = completionCount;
  294. cleared = game.content.getCleared(level, type);
  295. if(cleared) completionTotal = completionCount = 0;
  296. }
  297. /* Create the overworld
  298. *
  299. * The overworld is present to create a contrast with the dungeon. It is in colour and so
  300. * are you. There is a health stone for restoring health and a grindstone -
  301. * an allegory of improving yourself in the real world as opposed to a fantasy where
  302. * you kill people to better yourself
  303. */
  304. public function createOverworld():void{
  305. bitmap = new MapBitmap(OVERWORLD, AREA);
  306. width = bitmap.width;
  307. height = bitmap.height;
  308. layers.push(createGrid(null, width, height));
  309. // blocks - start with a full grid
  310. layers.push(createGrid(1, width, height));
  311. // game objects
  312. layers.push(createGrid(null, width, height));
  313. fill(0, 1, 0, width-2, height-1, layers[BLOCKS]);
  314. // create the grindstone and healstone
  315. layers[BLOCKS][height - 2][1] = MapTileConverter.WALL;
  316. layers[ENTITIES][height - 2][1] = MapTileConverter.HEAL_STONE;
  317. layers[BLOCKS][height - 2][width - 2] = MapTileConverter.WALL;
  318. layers[ENTITIES][height - 2][width - 2] = MapTileConverter.GRIND_STONE;
  319. var portalXMLs:Array = game.content.getPortals(level, type);
  320. if(portalXMLs.length){
  321. // given that there can only be one type of portal on the overworld - the rogue's portal
  322. // we create the rogue's portal here
  323. setPortal(OVERWORLD_PORTAL_X, height - 2, portalXMLs[0]);
  324. }
  325. setStairsDown(OVERWORLD_STAIRS_X, height - 2);
  326. // the player may have left content on the overworld as a sort of bank
  327. game.content.populateLevel(type, OVERWORLD, bitmap, layers, random);
  328. }
  329. /* Create the underworld
  330. *
  331. * The underworld is where the minion came from. It consists of a boat on the river Styx under a black
  332. * sky where stars are exploding. Death (an NPC) is there.
  333. */
  334. public function createUnderworld():void{
  335. bitmap = new MapBitmap(UNDERWORLD, AREA);
  336. width = bitmap.width;
  337. height = bitmap.height;
  338. layers.push(createGrid(null, width, height));
  339. // blocks - start with a full grid
  340. layers.push(createGrid(1, width, height));
  341. // game objects
  342. layers.push(createGrid(null, width, height));
  343. fill(0, 1, 0, width-2, height-1, layers[BLOCKS]);
  344. // create the boat
  345. fill(MapTileConverter.WALL, UNDERWORLD_BOAT_MIN, height - 2, UNDERWORLD_BOAT_MAX - UNDERWORLD_BOAT_MIN, 1, layers[BLOCKS]);
  346. setValue(UNDERWORLD_BOAT_MIN, height - 3, BLOCKS, MapTileConverter.WALL);
  347. setValue(UNDERWORLD_BOAT_MAX - 1, height - 3, BLOCKS, MapTileConverter.WALL);
  348. var portalXMLs:Array = game.content.getPortals(level, type);
  349. if(portalXMLs.length){
  350. setPortal(UNDERWORLD_PORTAL_X, height - 3, portalXMLs[0]);
  351. }
  352. // the player may have left content on the underworld as a sort of bank
  353. game.content.populateLevel(type, UNDERWORLD, bitmap, layers, random);
  354. // create sensors to resolve any contact with the waters
  355. var waterSensor:ColliderEntitySensor = new ColliderEntitySensor(
  356. new Rectangle(Game.SCALE, -3 + (height - 1) * Game.SCALE, (width - 2) * Game.SCALE, 3),
  357. underworldWaterCallback
  358. )
  359. // create death
  360. var deathCharacter:Stone = new Stone((UNDERWORLD_PORTAL_X - 3) * Game.SCALE, (height - 2) * Game.SCALE, Stone.DEATH);
  361. layers[ENTITIES][height - 2][UNDERWORLD_PORTAL_X - 3] = deathCharacter;
  362. }
  363. /* Resolves what happens to entities that fall in the water in the Underworld */
  364. public static function underworldWaterCallback(colliderEntity:ColliderEntity):void{
  365. if(colliderEntity is Item){
  366. renderer.createSparkRect(colliderEntity.collider, 20, 0, -1);
  367. colliderEntity.active = false;
  368. } else if(colliderEntity is Character){
  369. Effect.teleportCharacter(colliderEntity as Character, new Pixel(UNDERWORLD_PORTAL_X + 1, MapBitmap.UNDERWORLD_HEIGHT - 3));
  370. }
  371. }
  372. /* Creates an entry/exit point for the level - used to set stairs and portals generated by the portal rune */
  373. public function createAccessPoint(type:int, sort:Function = null, xml:XML = null):void{
  374. var index:int = 0;
  375. var rooms:Vector.<Room> = bitmap.rooms;
  376. if(Boolean(sort)) rooms.sort(sort);
  377. var portalRoom:Room = rooms[index];
  378. var r:int, c:int, pos:uint;
  379. var candidates:Vector.<Surface> = new Vector.<Surface>();
  380. var choice:Surface;
  381. var surface:Surface;
  382. var i:int;
  383. do{
  384. candidates.length = 0;
  385. for(i = 0; i < portalRoom.surfaces.length; i++){
  386. surface = portalRoom.surfaces[i];
  387. if(goodPortalPosition(surface.x, surface.y, random.value() < 0.2)){
  388. candidates.push(surface);
  389. }
  390. }
  391. if(candidates.length){
  392. choice = candidates[random.rangeInt(candidates.length)];
  393. } else {
  394. if(index == rooms.length - 1) throw new Error("failed to create portal");
  395. else {
  396. index++;
  397. portalRoom = rooms[index];
  398. }
  399. }
  400. } while(candidates.length == 0);
  401. if(type == Portal.STAIRS){
  402. if(sort == sortRoomsTopWards) setStairsUp(choice.x, choice.y);
  403. else if(sort == sortRoomsBottomWards) setStairsDown(choice.x, choice.y);
  404. } else {
  405. setPortal(choice.x, choice.y, xml);
  406. }
  407. Surface.removeSurface(choice.x, choice.y);
  408. }
  409. // room sorting callbacks
  410. public static function sortRoomsTopWards(a:Room, b:Room):Number{
  411. if(a.y < b.y) return -1;
  412. else if(a.y > b.y) return 1;
  413. return 0;
  414. }
  415. public static function sortRoomsBottomWards(a:Room, b:Room):Number{
  416. if(a.y > b.y) return -1;
  417. else if(a.y < b.y) return 1;
  418. return 0;
  419. }
  420. /* Creates a stairway up */
  421. public function setStairsUp(x:int, y:int):void{
  422. layers[ENTITIES][y][x] = MapTileConverter.STAIRS_UP;
  423. // prevent background decorator from creating graphics covering portals
  424. if(bitmap.bitmapData.getPixel32(x, y) == MapBitmap.EMPTY) bitmap.bitmapData.setPixel32(x, y, MapBitmap.GATE);
  425. stairsUp = new Pixel(x, y);
  426. if(isPortalToPreviousLevel(x, y, Portal.STAIRS, level - 1, level > 1 ? MAIN_DUNGEON : AREA)){
  427. start = stairsUp;
  428. if(Surface.map[y][x] && Surface.map[y][x].room) Surface.map[y][x].room.start = true;
  429. }
  430. }
  431. /* Creates a stairway down */
  432. public function setStairsDown(x:int, y:int):void{
  433. layers[ENTITIES][y][x] = MapTileConverter.STAIRS_DOWN;
  434. // prevent background decorator from creating graphics covering portals
  435. if(bitmap.bitmapData.getPixel32(x, y) == MapBitmap.EMPTY) bitmap.bitmapData.setPixel32(x, y, MapBitmap.GATE);
  436. stairsDown = new Pixel(x, y);
  437. if(isPortalToPreviousLevel(x, y, Portal.STAIRS, level + 1, MAIN_DUNGEON)){
  438. start = stairsDown;
  439. if(Surface.map[y][x] && Surface.map[y][x].room) Surface.map[y][x].room.start = true;
  440. }
  441. }
  442. /* Creates a portal */
  443. public function setPortal(x:int, y:int, xml:XML):void{
  444. var p:Pixel = new Pixel(x, y);
  445. var portal:Portal = Content.XMLToEntity(x, y, xml, level, type);
  446. game.portalHash[portal.hashKey] = portal;
  447. if(type == AREA){
  448. portal.maskPortalBase(level);
  449. }
  450. layers[ENTITIES][y][x] = portal;
  451. // prevent background decorator from creating graphics covering portals
  452. if(bitmap.bitmapData.getPixel32(x, y) == MapBitmap.EMPTY) bitmap.bitmapData.setPixel32(x, y, MapBitmap.GATE);
  453. if(isPortalToPreviousLevel(x, y, int(xml.@type), int(xml.@targetLevel), int(xml.@targetType))){
  454. start = p;
  455. if(Surface.map[y][x] && Surface.map[y][x].room) Surface.map[y][x].room.start = true;
  456. }
  457. portals.push(p);
  458. }
  459. /* Is this portal going to be the entry point for the level?
  460. *
  461. * Map.start and Game.entrance are used by the game engine to set the camera and entry point for the level
  462. * which is why this query belongs in a function to be called by various elements
  463. *
  464. * The logic follows: does this portal lead to the last level you were on
  465. *
  466. * This used to be a lot more complicated till I refactored portals */
  467. public static function isPortalToPreviousLevel(x:int, y:int, type:int, targetLevel:int, targetType:int):Boolean{
  468. return Player.previousPortalType == type && Player.previousLevel == targetLevel && Player.previousMapType == targetType;
  469. }
  470. /* Getting a good position for the portals is complex.
  471. * We want clear spaces to each side and solid floor below - preferably walls below on stairs down
  472. * we also don't want to appear next to another portal - portals have frilly edges
  473. * - hence the mess in this method */
  474. public function goodPortalPosition(x:int, y:int, ledgeAllowed:Boolean = true):Boolean{
  475. var p:Pixel;
  476. for(var i:int = 0; i < portals.length; i++){
  477. p = portals[i];
  478. if(
  479. (p.x == x && (p.y == y || p.y == y - 1 || p.y == y + 1)) ||
  480. (p.y == y && (p.x == x || p.x == x - 1 || p.x == x + 1))
  481. ){
  482. return false;
  483. }
  484. }
  485. if(stairsUp){
  486. p = stairsUp;
  487. if(
  488. (p.x == x && (p.y == y || p.y == y - 1 || p.y == y + 1)) ||
  489. (p.y == y && (p.x == x || p.x == x - 1 || p.x == x + 1))
  490. ){
  491. return false;
  492. }
  493. }
  494. if(stairsDown){
  495. p = stairsDown;
  496. if(
  497. (p.x == x && (p.y == y || p.y == y - 1 || p.y == y + 1)) ||
  498. (p.y == y && (p.x == x || p.x == x - 1 || p.x == x + 1))
  499. ){
  500. return false;
  501. }
  502. }
  503. var pos:uint = bitmap.bitmapData.getPixel32(x, y);
  504. var posAbove:uint = bitmap.bitmapData.getPixel32(x, y - 1);
  505. var posBelow:uint = bitmap.bitmapData.getPixel32(x, y + 1);
  506. var posLeft:uint = bitmap.bitmapData.getPixel32(x - 1, y);
  507. var posRight:uint = bitmap.bitmapData.getPixel32(x + 1, y);
  508. if(bitmap.leftSecretRoom && bitmap.leftSecretRoom.contains(x, y)) return false;
  509. if(bitmap.rightSecretRoom && bitmap.rightSecretRoom.contains(x, y)) return false;
  510. return (
  511. (posAbove != MapBitmap.WALL && posAbove != MapBitmap.GATE) &&
  512. (posLeft != MapBitmap.WALL && posRight != MapBitmap.WALL) &&
  513. (posLeft != MapBitmap.GATE && posRight != MapBitmap.GATE) &&
  514. (posLeft != MapBitmap.SECRET && posRight != MapBitmap.SECRET) &&
  515. (posBelow == MapBitmap.WALL || (posBelow == MapBitmap.LEDGE && ledgeAllowed)) &&
  516. (pos == MapBitmap.EMPTY || pos == MapBitmap.LEDGE)
  517. );
  518. }
  519. /* Adds critters to the level - decorative entites that squish on contact */
  520. public function createCritters():void{
  521. // the compiler won't let me create this as a constant so I have to drop it in here
  522. // better than resorting to magic numbers I suppose
  523. var ZONE_CRITTERS:Array = [
  524. [MapTileConverter.SPIDER, MapTileConverter.SPIDER, MapTileConverter.BAT, MapTileConverter.RAT],
  525. [MapTileConverter.RAT, MapTileConverter.RAT, MapTileConverter.RAT, MapTileConverter.RAT, MapTileConverter.SPIDER],
  526. [MapTileConverter.BAT, MapTileConverter.BAT, MapTileConverter.BAT, MapTileConverter.BAT, MapTileConverter.RAT, MapTileConverter.SPIDER],
  527. [MapTileConverter.COG, MapTileConverter.COG_RAT, MapTileConverter.COG_SPIDER, MapTileConverter.COG_BAT]
  528. ];
  529. var critterPalette:Array = ZONE_CRITTERS[zone];
  530. var r:int, c:int, critterId:int;
  531. var critterNum:int = Math.sqrt(width * height) * 1.25;
  532. var breaker:int = 0;
  533. while(critterNum){
  534. r = 1 + random.range(bitmap.height - 1);
  535. c = 1 + random.range(bitmap.width - 1);
  536. critterId = critterPalette[random.rangeInt(critterPalette.length)];
  537. // may god forgive me for this if statement:
  538. if(
  539. !layers[ENTITIES][r][c] &&
  540. layers[BLOCKS][r][c] != 1 &&
  541. (
  542. (
  543. (
  544. critterId == MapTileConverter.RAT ||
  545. critterId == MapTileConverter.COG_RAT
  546. )&&
  547. (
  548. bitmap.bitmapData.getPixel32(c, r + 1) == MapBitmap.LEDGE ||
  549. bitmap.bitmapData.getPixel32(c, r + 1) == MapBitmap.LADDER_LEDGE ||
  550. layers[BLOCKS][r + 1][c] == MapTileConverter.WALL
  551. )
  552. ) ||
  553. (
  554. (
  555. critterId == MapTileConverter.SPIDER ||
  556. critterId == MapTileConverter.BAT ||
  557. critterId == MapTileConverter.COG_SPIDER ||
  558. critterId == MapTileConverter.COG_BAT
  559. ) &&
  560. (
  561. layers[BLOCKS][r - 1][c] == MapTileConverter.WALL &&
  562. bitmap.bitmapData.getPixel32(c, r - 1) != MapBitmap.PIT
  563. )
  564. ) ||
  565. (
  566. critterId == MapTileConverter.COG &&
  567. (
  568. (
  569. layers[BLOCKS][r - 1][c] == MapTileConverter.WALL &&
  570. bitmap.bitmapData.getPixel32(c, r - 1) != MapBitmap.PIT
  571. ) ||
  572. layers[BLOCKS][r + 1][c] == MapTileConverter.WALL ||
  573. layers[BLOCKS][r][c - 1] == MapTileConverter.WALL ||
  574. layers[BLOCKS][r][c + 1] == MapTileConverter.WALL
  575. )
  576. )
  577. )
  578. ){
  579. layers[ENTITIES][r][c] = critterId;
  580. critterNum--;
  581. }
  582. if((breaker++) > 1000) break;
  583. }
  584. }
  585. /* Create barriers in the dungeon */
  586. public function createGates():void{
  587. // debug for repeating levels
  588. //trace(random.seed);
  589. var i:int, j:int, site:Pixel, n:int, entranceCol:uint, surface:Surface, gate:Gate, item:Item;
  590. var connections:BitmapData = bitmap.bitmapData.clone();
  591. connections.threshold(connections, connections.rect, new Point(), "!=", MapBitmap.WALL, 0xFF000000);
  592. var connectionsBuffer:BitmapData = connections.clone();
  593. var fragmented:Boolean = false;
  594. var connectionPixels:Vector.<uint>;
  595. var gateType:int;
  596. var checkCol:uint;
  597. var fragmentationSites:Vector.<Pixel> = new Vector.<Pixel>();
  598. // scrape for fragmentation sites
  599. for(i = bitmap.gates.length - 1; i > -1; i--){
  600. site = bitmap.gates[i];
  601. // nanny-condition, in a perfect world I won't have put an entity here
  602. if(!layers[ENTITIES][site.y][site.x]){
  603. // do a connectivity test
  604. connections.setPixel32(site.x, site.y, 0xFFFFFFFF);
  605. connections.floodFill(site.x + 1, site.y, 0xFF0000FF);
  606. connections.floodFill(site.x - 1, site.y, 0xFF00FF00);
  607. // fragmentation means a section of the dungeon will be hermetically
  608. // sealed from all manner of access (no dropping in, no teleport)
  609. if(connections.getColorBoundsRect(0xFFFFFFFF, 0xFF0000FF).width){
  610. fragmentationSites.push(site);
  611. bitmap.gates.splice(i, 1);
  612. //trace("fragmentation", site);
  613. }
  614. // revert to buffer
  615. connections = connectionsBuffer.clone();
  616. } else {
  617. bitmap.gates.splice(i, 1);
  618. }
  619. }
  620. if(fragmentationSites.length){
  621. site = fragmentationSites[random.rangeInt(fragmentationSites.length)];
  622. connections.setPixel32(site.x, site.y, 0xFFFFFFFF);
  623. connections.floodFill(site.x + 1, site.y, 0xFF0000FF);
  624. connections.floodFill(site.x - 1, site.y, 0xFF00FF00);
  625. // fragmentation debugging
  626. //var temp:Bitmap = new Bitmap(connections);
  627. //game.addChild(temp);
  628. // iterate through surfaces and mark them as connected to the entrance or not
  629. entranceCol = connections.getPixel32(start.x, start.y);
  630. connectionPixels = connections.getVector(connections.rect);
  631. for(j = 0; j < Surface.surfaces.length; j++){
  632. surface = Surface.surfaces[j];
  633. n = surface.x + surface.y * width;
  634. checkCol = connectionPixels[n];
  635. if(checkCol == entranceCol){
  636. surface.nearEntrance = true;
  637. }
  638. }
  639. // create a location for the key to the gate,
  640. // try not to put it next to the entrance or the gate
  641. var minDist:int = 10;
  642. var collapseMinDist:int = 100;
  643. do{
  644. surface = Surface.surfaces[random.rangeInt(Surface.surfaces.length)];
  645. if(collapseMinDist-- <= 0){
  646. collapseMinDist = 100;
  647. minDist--;
  648. }
  649. } while(
  650. !surface.nearEntrance ||
  651. layers[ENTITIES][surface.y][surface.x] ||
  652. (
  653. Math.abs(start.x - surface.x) < minDist &&
  654. Math.abs(start.y - surface.y) < minDist
  655. ) ||
  656. (
  657. Math.abs(site.x - surface.x) < minDist &&
  658. Math.abs(site.y - surface.y) < minDist
  659. ) ||
  660. !bitmap.adjustedMapRect.contains((surface.x + 0.5) * Game.SCALE, (surface.y + 0.5) * Game.SCALE)
  661. );
  662. // key
  663. var xml:XML =<item name={0} type={Item.KEY} level={0} />;
  664. item = Content.XMLToEntity(surface.x, surface.y, xml);
  665. item.dropToMap(surface.x, surface.y, false);
  666. layers[ENTITIES][surface.y][surface.x] = item;
  667. gateType = Gate.LOCK;
  668. Surface.removeSurface(surface.x, surface.y);
  669. // pressure pad
  670. // to do
  671. // an illustration of fragmentation needs to be saved for Effect.teleportCharacter
  672. // and for wall walkers that change race inside a locked area
  673. Surface.fragmentationMap = connections;
  674. Surface.entranceCol = entranceCol;
  675. gateType = Gate.LOCK;
  676. gate = new Gate(site.x * Game.SCALE, site.y * Game.SCALE, gateType);
  677. gate.mapX = site.x;
  678. gate.mapY = site.y;
  679. gate.mapZ = MapTileManager.ENTITY_LAYER;
  680. layers[ENTITIES][site.y][site.x] = gate;
  681. Surface.removeSurface(site.x, site.y);
  682. }
  683. // pick a random normal site for a raise gate or chaos gate
  684. if(bitmap.gates.length){
  685. i = -1 + random.rangeInt(4);
  686. while(i-- > 0){
  687. site = bitmap.gates[random.rangeInt(bitmap.gates.length)];
  688. if(layers[ENTITIES][site.y][site.x]) continue;
  689. if(zone == CHAOS) gateType = random.coinFlip() ? Gate.CHAOS : Gate.RAISE;
  690. else gateType = Gate.RAISE;
  691. gate = new Gate(site.x * Game.SCALE, site.y * Game.SCALE, gateType);
  692. gate.mapX = site.x;
  693. gate.mapY = site.y;
  694. gate.mapZ = MapTileManager.ENTITY_LAYER;
  695. layers[ENTITIES][site.y][site.x] = gate;
  696. Surface.removeSurface(site.x, site.y);
  697. }
  698. }
  699. }
  700. /* Generate patches of readable text */
  701. public function createWritings(pixels:Vector.<uint>):void{
  702. Writing.writings = new Vector.<Writing>();
  703. var i:int, n:int;
  704. var surface:Surface, writing:Writing;
  705. var range:int = (height - 1) * width - 1;
  706. var candidates:Vector.<Surface> = new Vector.<Surface>();
  707. for(i = 0; i < Surface.surfaces.length; i++){
  708. surface = Surface.surfaces[i];
  709. if(surface.properties == (Collider.SOLID | Collider.WALL)){
  710. n = surface.x + surface.y * width;
  711. if(
  712. n > width + 1 && n < range &&
  713. (pixels[n + (width + 1)] == MapBitmap.WALL || pixels[n + (width + 1)] == MapBitmap.LEDGE) &&
  714. (pixels[n + width] == MapBitmap.WALL || pixels[n + width] == MapBitmap.LEDGE) &&
  715. (pixels[n + (width - 1)] == MapBitmap.WALL || pixels[n + (width - 1)] == MapBitmap.LEDGE) &&
  716. pixels[n] == MapBitmap.EMPTY &&
  717. pixels[n - 1] == MapBitmap.EMPTY &&
  718. pixels[n + 1] == MapBitmap.EMPTY &&
  719. Surface.map[surface.y][surface.x - 1] &&
  720. Surface.map[surface.y][surface.x + 1]
  721. ){
  722. candidates.push(surface);
  723. }
  724. }
  725. }
  726. //trace("candidates", candidates.length);
  727. if(candidates.length){
  728. var index:int;
  729. var writings:int = 1 + random.rangeInt(3);
  730. //trace("writings", writings);
  731. var level:int = this.level - 1;
  732. // randomise selections
  733. var selection:Array = [];
  734. var levelArray:Array = Writing.story[level];
  735. for(i = 0; i < levelArray.length; i++){
  736. selection.push(i);
  737. }
  738. randomiseArray(selection, random);
  739. while((writings--) && selection.length && candidates.length){
  740. n = random.rangeInt(candidates.length);
  741. surface = candidates[n];
  742. candidates.splice(n, 1);
  743. i = surface.x + surface.y * width;
  744. if(
  745. pixels[i] == MapBitmap.EMPTY &&
  746. pixels[i + 1] == MapBitmap.EMPTY &&
  747. pixels[i - 1] == MapBitmap.EMPTY
  748. ){
  749. index = selection.pop();
  750. writing = new Writing(surface.x, surface.y, levelArray[index], level, index);
  751. writing.mapZ = MapTileManager.ENTITY_LAYER;
  752. layers[ENTITIES][surface.y][surface.x] = writing;
  753. Surface.removeSurface(surface.x, surface.y);
  754. // hack to avoid over writing
  755. pixels[i] = MapBitmap.GATE;
  756. pixels[i + 1] = MapBitmap.GATE;
  757. pixels[i - 1] = MapBitmap.GATE;
  758. }
  759. }
  760. }
  761. }
  762. /* Generate slot machines */
  763. public function createAltars(pixels:Vector.<uint>):void{
  764. var totalAltars:int = game.content.getAltars(level, type);
  765. if(totalAltars <= 0) return;
  766. var i:int, n:int;
  767. var surface:Surface, altar:Altar;
  768. var range:int = (height - 1) * width - 1;
  769. var candidates:Vector.<Surface> = new Vector.<Surface>();
  770. for(i = 0; i < Surface.surfaces.length; i++){
  771. surface = Surface.surfaces[i];
  772. if(surface.properties == (Collider.SOLID | Collider.WALL)){
  773. n = surface.x + surface.y * width;
  774. if(
  775. n > width + 1 && n < range &&
  776. (pixels[n + (width + 1)] == MapBitmap.WALL || pixels[n + (width + 1)] == MapBitmap.LEDGE) &&
  777. (pixels[n + width] == MapBitmap.WALL || pixels[n + width] == MapBitmap.LEDGE) &&
  778. (pixels[n + (width - 1)] == MapBitmap.WALL || pixels[n + (width - 1)] == MapBitmap.LEDGE) &&
  779. pixels[n] == MapBitmap.EMPTY &&
  780. pixels[n - 1] == MapBitmap.EMPTY &&
  781. pixels[n + 1] == MapBitmap.EMPTY &&
  782. Surface.map[surface.y][surface.x - 1] &&
  783. Surface.map[surface.y][surface.x + 1]
  784. ){
  785. candidates.push(surface);
  786. }
  787. }
  788. }
  789. //trace("candidates", candidates.length);
  790. if(candidates.length){
  791. while((totalAltars--) && candidates.length){
  792. n = random.rangeInt(candidates.length);
  793. surface = candidates[n];
  794. candidates.splice(n, 1);
  795. i = surface.x + surface.y * width;
  796. if(
  797. pixels[i] == MapBitmap.EMPTY &&
  798. pixels[i + 1] == MapBitmap.EMPTY &&
  799. pixels[i - 1] == MapBitmap.EMPTY &&
  800. !layers[ENTITIES][surface.y][surface.x + 1] &&
  801. !layers[ENTITIES][surface.y][surface.x - 1]
  802. ){
  803. altar = new Altar(new AltarMC, surface.x, surface.y);
  804. layers[ENTITIES][surface.y][surface.x] = altar;
  805. Surface.removeSurface(surface.x, surface.y);
  806. }
  807. }
  808. }
  809. }
  810. /* Create lights in the dungeon */
  811. public function createTorches(pixels:Vector.<uint>):void{
  812. var i:int, n:int, room:Room, surface:Surface, torch:Torch;
  813. for(i = 0; i < bitmap.rooms.length; i++){
  814. room = bitmap.rooms[i];
  815. if(room.surfaces.length){
  816. surface = room.surfaces[random.rangeInt(room.surfaces.length)];
  817. n = surface.x + surface.y * width;
  818. if(
  819. pixels[n] == MapBitmap.EMPTY &&
  820. !layers[ENTITIES][surface.y][surface.x]
  821. ){
  822. torch = new Torch(new TorchMC, surface.x, surface.y);
  823. layers[ENTITIES][surface.y][surface.x] = torch;
  824. Surface.removeSurface(surface.x, surface.y);
  825. }
  826. }
  827. }
  828. }
  829. /* Generate decorative background graphics */
  830. public function createDecor(pixels:Vector.<uint>):void{
  831. var pixel:uint, i:int, n:int, r:int, c:int, good:Boolean, type:String;
  832. for(i = width; i < pixels.length - width; i++){
  833. pixel = pixels[i];
  834. if(pixel == MapBitmap.EMPTY){
  835. r = i / width;
  836. c = i % width;
  837. // pillar, chain, recess
  838. if(zone == DUNGEONS){
  839. // pillar / chain
  840. if(pixels[i - width] == MapBitmap.WALL && random.value() < 0.4){
  841. // single pillars spawn very easily, so they need to be reduced in popularity
  842. if(pixels[i + width] == MapBitmap.WALL && random.value() < 0.4){
  843. layers[BACKGROUND][r][c] = random.coinFlip() ? MapTileConverter.PILLAR_SINGLE1 : MapTileConverter.PILLAR_SINGLE2;
  844. } else if(pixels[i + width] == MapBitmap.EMPTY){
  845. // test for pillar or chain
  846. good = true;
  847. for(n = i + width; n < pixels.length; n += width){
  848. if(pixels[n] != MapBitmap.EMPTY){
  849. if(pixels[n] == MapBitmap.WALL || pixels[n] == MapBitmap.LEDGE){
  850. if(pixels[n] == MapBitmap.WALL) type = "pillar";
  851. else if(pixels[n] == MapBitmap.LEDGE) type = "chain";
  852. n -= width;
  853. } else {
  854. good = false;
  855. }
  856. break;
  857. }
  858. }
  859. if(good){
  860. // choose pillar or chain
  861. if(type == "pillar"){
  862. layers[BACKGROUND][r][c] = MapTileConverter.PILLAR_TOP;
  863. r = n / width;
  864. c = n % width;
  865. layers[BACKGROUND][r][c] = MapTileConverter.PILLAR_BOTTOM;
  866. for(n -= width; n > i; n -= width){
  867. r = n / width;
  868. c = n % width;
  869. layers[BACKGROUND][r][c] = random.coinFlip() ? MapTileConverter.PILLAR_MID1 : MapTileConverter.PILLAR_MID2;
  870. }
  871. } else if(type == "chain"){
  872. layers[BACKGROUND][r][c] = MapTileConverter.CHAIN_TOP;
  873. r = n / width;
  874. c = n % width;
  875. layers[BACKGROUND][r][c] = MapTileConverter.CHAIN_BOTTOM;
  876. for(n -= width; n > i; n -= width){
  877. r = n / width;
  878. c = n % width;
  879. layers[BACKGROUND][r][c] = MapTileConverter.CHAIN_MID;
  880. }
  881. }
  882. }
  883. }
  884. // skull
  885. } else if((pixels[i + width] == MapBitmap.WALL || pixels[i + width] == MapBitmap.LEDGE) && random.value() < 0.01 && !layers[BACKGROUND][r][c]){
  886. layers[BACKGROUND][r][c] = MapTileConverter.SKULL;
  887. // recess (avoid putting a recess above a portal)
  888. } else if(random.value() < 0.05 && !layers[BACKGROUND][r][c] && pixels[i + width] != MapBitmap.GATE){
  889. layers[BACKGROUND][r][c] = MapTileConverter.RECESS;
  890. }
  891. // outlet, drain
  892. } else if(zone == SEWERS){
  893. // skull
  894. if((pixels[i + width] == MapBitmap.WALL || pixels[i + width] == MapBitmap.LEDGE) && random.value() < 0.01){
  895. layers[BACKGROUND][r][c] = MapTileConverter.SKULL;
  896. // drain
  897. } else if(pixels[i + width] == MapBitmap.WALL && random.value() < 0.05){
  898. layers[BACKGROUND][r][c] = MapTileConverter.DRAIN;
  899. // outlet
  900. } else if(pixels[i - width] == MapBitmap.WALL && random.value() < 0.05){
  901. layers[BACKGROUND][r][c] = MapTileConverter.OUTLET;
  902. }
  903. // stalagmite, crack
  904. } else if(zone == CAVES){
  905. // crack
  906. if(random.value() < 0.01 && !layers[BACKGROUND][r][c]){
  907. layers[BACKGROUND][r][c] = [MapTileConverter.CRACK1, MapTileConverter.CRACK2, MapTileConverter.CRACK3][game.random.rangeInt(3)];
  908. // skull
  909. } else if((pixels[i + width] == MapBitmap.WALL || pixels[i + width] == MapBitmap.LEDGE) && random.value() < 0.01){
  910. layers[BACKGROUND][r][c] = MapTileConverter.SKULL;
  911. // stalagmite
  912. } else if(pixels[i + width] == MapBitmap.WALL && random.value() < 0.4){
  913. layers[BACKGROUND][r][c] = [MapTileConverter.STALAGMITE1, MapTileConverter.STALAGMITE2, MapTileConverter.STALAGMITE3, MapTileConverter.STALAGMITE4][game.random.rangeInt(4)];
  914. // stalagtite
  915. } else if(pixels[i - width] == MapBitmap.WALL && random.value() < 0.4){
  916. layers[BACKGROUND][r][c] = [MapTileConverter.STALAGTITE1, MapTileConverter.STALAGTITE2, MapTileConverter.STALAGTITE3, MapTileConverter.STALAGTITE4][game.random.rangeInt(4)];
  917. }
  918. // growth
  919. } else if(zone == CHAOS){
  920. // skull
  921. if((pixels[i + width] == MapBitmap.WALL || pixels[i + width] == MapBitmap.LEDGE) && random.value() < 0.01){
  922. layers[BACKGROUND][r][c] = MapTileConverter.SKULL;
  923. // growth up
  924. } else if(pixels[i + width] == MapBitmap.WALL && random.value() < 0.5){
  925. layers[BACKGROUND][r][c] = [MapTileConverter.GROWTH1, MapTileConverter.GROWTH2, MapTileConverter.GROWTH3, MapTileConverter.GROWTH4, MapTileConverter.GROWTH5, MapTileConverter.GROWTH6][game.random.rangeInt(6)];
  926. // growth down
  927. } else if(pixels[i - width] == MapBitmap.WALL && random.value() < 0.5){
  928. layers[BACKGROUND][r][c] = [MapTileConverter.GROWTH7, MapTileConverter.GROWTH8, MapTileConverter.GROWTH9, MapTileConverter.GROWTH10, MapTileConverter.GROWTH11, MapTileConverter.GROWTH12][game.random.rangeInt(6)];
  929. }
  930. }
  931. }
  932. }
  933. // put stairs in background to over print decor
  934. if(stairsUp) layers[BACKGROUND][stairsUp.y][stairsUp.x] = MapTileConverter.STAIRS_UP_GFX;
  935. if(stairsDown) layers[BACKGROUND][stairsDown.y][stairsDown.x] = MapTileConverter.STAIRS_DOWN_GFX;
  936. }
  937. /* Sprinkle on some chaos walls to make exploring weirder */
  938. public function createChaosWalls(pixels:Vector.<uint>):void{
  939. ChaosWall.init(width, height);
  940. // we only put chaos walls in corridors, so we need a stretch of three wall blocks to
  941. // either side of the chaos wall
  942. var i:int, r:int, c:int, n:int;
  943. for(i = width; i < pixels.length - width; i++){
  944. c = i % width;
  945. r = i / width;
  946. if(c > 0 && c < width - 1 && pixels[i] == MapBitmap.EMPTY){
  947. if(zone == CHAOS && !layers[ENTITIES][r][c] && !layers[BLOCKS][r][c] && random.value() < 0.4){
  948. if(pixels[i + width] == MapBitmap.WALL){
  949. n = i;
  950. while(n > width && pixels[n] == MapBitmap.EMPTY && !layers[ENTITIES][r][c] && !layers[BLOCKS][r][c]){
  951. layers[ENTITIES][r][c] = new ChaosWall(c, r);
  952. layers[BLOCKS][r][c] = MapTileConverter.WALL;
  953. n -= width;
  954. r = n / width;
  955. }
  956. }
  957. if(pixels[i - width] == MapBitmap.WALL){
  958. n = i;
  959. while(n < pixels.length - width && pixels[n] == MapBitmap.EMPTY && !layers[ENTITIES][r][c] && !layers[BLOCKS][r][c]){
  960. layers[ENTITIES][r][c] = new ChaosWall(c, r);
  961. layers[BLOCKS][r][c] = MapTileConverter.WALL;
  962. n += width;
  963. r = n / width;
  964. }
  965. }
  966. if(pixels[i - 1] == MapBitmap.WALL){
  967. n = i;
  968. while(n < pixels.length - width && pixels[n] == MapBitmap.EMPTY && !layers[ENTITIES][r][c] && !layers[BLOCKS][r][c]){
  969. layers[ENTITIES][r][c] = new ChaosWall(c, r);
  970. layers[BLOCKS][r][c] = MapTileConverter.WALL;
  971. n++;
  972. c = n % width;
  973. }
  974. }
  975. if(pixels[i + 1] == MapBitmap.WALL){
  976. n = i;
  977. while(n > width && pixels[n] == MapBitmap.EMPTY && !layers[ENTITIES][r][c] && !layers[BLOCKS][r][c]){
  978. layers[ENTITIES][r][c] = new ChaosWall(c, r);
  979. layers[BLOCKS][r][c] = MapTileConverter.WALL;
  980. n--;
  981. c = n % width;
  982. }
  983. }
  984. } else {
  985. if(
  986. // horizontal corridor
  987. (
  988. pixels[(i - width) - 1] == MapBitmap.WALL &&
  989. pixels[i - width] == MapBitmap.WALL &&
  990. pixels[(i - width) + 1] == MapBitmap.WALL &&
  991. pixels[(i + width) - 1] == MapBitmap.WALL &&
  992. pixels[i + width] == MapBitmap.WALL &&
  993. pixels[(i + width) + 1] == MapBitmap.WALL
  994. ) ||
  995. // vertical corridor
  996. (
  997. pixels[(i - width) - 1] == MapBitmap.WALL &&
  998. pixels[i - 1] == MapBitmap.WALL &&
  999. pixels[(i + width) - 1] == MapBitmap.WALL &&
  1000. pixels[(i - width) + 1] == MapBitmap.WALL &&
  1001. pixels[i + 1] == MapBitmap.WALL &&
  1002. pixels[(i + width) + 1] == MapBitmap.WALL
  1003. )
  1004. ){
  1005. if(random.value() < 0.4 && !layers[ENTITIES][r][c] && !layers[BLOCKS][r][c]){
  1006. //if(!layers[ENTITIES][r][c]){
  1007. layers[ENTITIES][r][c] = new ChaosWall(c, r);
  1008. layers[BLOCKS][r][c] = MapTileConverter.WALL;
  1009. }
  1010. }
  1011. }
  1012. }
  1013. }
  1014. }
  1015. /* This adds traps other than pit traps to the level */
  1016. public function createOtherTraps(pixels:Vector.<uint>):void{
  1017. // TRAPS BY ZONE
  1018. // All zones have pit traps
  1019. // the compiler won't let me create this as a constant so I have to drop it in here
  1020. // better than resorting to magic numbers I suppose
  1021. var ZONE_TRAPS:Array = [
  1022. [Trap.TELEPORT_DART],
  1023. [Trap.STUN_MUSHROOM, Trap.TELEPORT_DART, Trap.CONFUSION_MUSHROOM],
  1024. [Trap.STUN_MUSHROOM, Trap.BLEED_DART, Trap.CONFUSION_MUSHROOM, Trap.FEAR_MUSHROOM],
  1025. [Trap.MONSTER_PORTAL, Trap.STUN_MUSHROOM, Trap.BLEED_DART, Trap.TELEPORT_DART, Trap.CONFUSION_MUSHROOM, Trap.FEAR_MUSHROOM]
  1026. ];
  1027. var totalTraps:int = game.content.getTraps(level, type) - bitmap.pitTraps;
  1028. completionCount += totalTraps;
  1029. if(totalTraps == 0) return;
  1030. var dartPos:Pixel;
  1031. var trapPositions:Vector.<Surface> = new Vector.<Surface>();
  1032. var surface:Surface;
  1033. var mapWidth:int = bitmap.bitmapData.width;
  1034. var n:int;
  1035. for(i = 0; i < Surface.surfaces.length; i++){
  1036. surface = Surface.surfaces[i];
  1037. if(surface.properties == (Collider.SOLID | Collider.WALL)){
  1038. if(layers[ENTITIES][surface.y][surface.x]) continue;
  1039. for(j = surface.x + surface.y * mapWidth; j > mapWidth; j -= mapWidth){
  1040. // no combining ladders or pit traps with dart traps
  1041. // it confuses the trap and it's unfair to have to climb a ladder into a dart
  1042. if(pixels[j] == MapBitmap.LADDER || pixels[j] == MapBitmap.LADDER_LEDGE || pixels[j] == MapBitmap.PIT){
  1043. break;
  1044. } else if(pixels[j] == MapBitmap.WALL){
  1045. trapPositions.push(surface);
  1046. break;
  1047. }
  1048. }
  1049. }
  1050. }
  1051. var trapIndex:int, trapPos:Surface, trapType:int, sprite:Sprite, trap:Trap;
  1052. while(totalTraps > 0 && trapPositions.length > 0){
  1053. trapIndex = random.range(trapPositions.length);
  1054. trapPos = trapPositions[trapIndex];
  1055. trapType = ZONE_TRAPS[zone][random.rangeInt(ZONE_TRAPS[zone].length)];
  1056. sprite = new Sprite();
  1057. sprite.x = trapPos.x * Game.SCALE;
  1058. sprite.y = (trapPos.y + 1) * Game.SCALE;
  1059. if(trapType != Trap.MONSTER_PORTAL){
  1060. // get dart gun position
  1061. dartPos = trapPos.copy();
  1062. while(pixels[dartPos.x + dartPos.y * width] != MapBitmap.WALL){
  1063. dartPos.y--;
  1064. }
  1065. } else {
  1066. dartPos = null;
  1067. }
  1068. trap = new Trap(sprite, trapPos.x, trapPos.y + 1, trapType, dartPos);
  1069. trap.mapX = trapPos.x;
  1070. trap.mapY = trapPos.y + 1;
  1071. trap.mapZ = MapTileManager.ENTITY_LAYER;
  1072. layers[ENTITIES][trapPos.y + 1][trapPos.x] = trap;
  1073. trapPositions.splice(trapIndex, 1);
  1074. Surface.removeSurface(trapPos.x, trapPos.y);
  1075. totalTraps--;
  1076. }
  1077. }
  1078. /* Debugging or set-piece method */
  1079. public function createCharacter(x:int, y:int, name:int, level:int):void{
  1080. var characterXML:XML = <character />;
  1081. characterXML.@name = name;
  1082. characterXML.@type = Character.MONSTER;
  1083. characterXML.@level = level;
  1084. layers[ENTITIES][y][x] = Content.XMLToEntity(x, y, characterXML);
  1085. }
  1086. public function setValue(x:int, y:int, z:int, value:*):void{
  1087. layers[z][y][x] = value;
  1088. }
  1089. public function getValue(x:int, y:int, z:int):*{
  1090. return layers[z][y][x];
  1091. }
  1092. /* Creates a secret wall that can be broken through */
  1093. public function createSecretWall(x:int, y:int):void{
  1094. var side:int;
  1095. if(x > width * 0.5) side = Collider.RIGHT;
  1096. else side = Collider.LEFT;
  1097. var wall:Stone = new Stone(x * Game.SCALE, y * Game.SCALE, Stone.SECRET_WALL, side);
  1098. wall.mapX = x;
  1099. wall.mapY = y;
  1100. wall.mapZ = MapTileManager.ENTITY_LAYER;
  1101. layers[ENTITIES][y][x] = wall;
  1102. completionCount++;
  1103. }
  1104. /* Creates a pit trap */
  1105. public function createPitTrap(x:int, y:int):void{
  1106. var sprite:Spri

Large files files are truncated, but you can click here to view the full file