alot of depth fixes
[k8vacspelynky.git] / mapent / MapTile.vc
blobf323b827203f3e3ef7ebc1490b67894d20dd8534
1 /**********************************************************************************
2  * Copyright (c) 2008, 2009 Derek Yu and Mossmouth, LLC
3  * Copyright (c) 2018, Ketmar Dark
4  *
5  * This file is part of Spelunky.
6  *
7  * You can redistribute and/or modify Spelunky, including its source code, under
8  * the terms of the Spelunky User License.
9  *
10  * Spelunky is distributed in the hope that it will be entertaining and useful,
11  * but WITHOUT WARRANTY.  Please see the Spelunky User License for more details.
12  *
13  * The Spelunky User License should be available in "Game Information", which
14  * can be found in the Resource Explorer, or as an external file called COPYING.
15  * If not, please obtain a new copy of Spelunky from <http://spelunkyworld.com/>
16  *
17  **********************************************************************************/
18 class MapTile : MapEntity;
20 enum Width = 16;
21 enum Height = 16;
23 transient SpriteImage sprite;
24 name spriteName;
26 // object flags
27 bool solid = true;
28 bool invisible;
29 bool invincible;
30 bool water;
31 bool lava;
32 bool wet; // for bricks inside a lake or lava, so they won't stop water flow
33 bool ice;
34 bool ladder;
35 bool laddertop;
36 bool rope;
37 bool tree;
38 bool leaves;
39 //bool hangeable; // don't forget to fix this for solids and trees!
40 bool moveable;
41 bool spikes;
42 bool woodenSpikes;
43 bool enter;
44 bool exit;
45 bool springtrap;
46 bool altar;
47 bool sacrificingAltar;
48 bool shopWall; // may be set to `true` for non-solid tiles too
49 bool cleanDeath;
50 bool smashed;
51 bool bloody;
52 // oLadderTop, oLeaves, oTreeBranch
53 bool platform;
54 bool toSpecialGrid;
55 bool border; // border tile
56 bool hideOre;
57 bool litWholeTile; // lit whole tile with maximum light
58 bool ignoreFrameOffsetX, ignoreFrameOffsetY;
59 int ore = 0;
61 //float myGrav = 0.6;
62 //float grav = 0.6; // the gravity
63 float myGravLimit = 8;
65 // x and y are offsets
66 MapBackTile bgback;
67 MapBackTile bgfront;
69 MapObject gem;
70 bool gemDestroyWithTile;
73 // for default doors
74 void snapToExit (MapEntity e) {
75   if (!e || !e.isInstanceAlive) return;
76   e.fltx = ix+8;
77   e.flty = iy+8;
78   e.updateGrid();
82 // ////////////////////////////////////////////////////////////////////////// //
83 override void Destroy () {
84   delete gem;
86   while (bgback) {
87     auto t = bgback;
88     bgback = t.next;
89     delete t;
90   }
92   while (bgfront) {
93     auto t = bgfront;
94     bgfront = t.next;
95     delete t;
96   }
98   ::Destroy();
102 override void onLoaded () {
103   ::onLoaded();
104   if (spriteName) sprite = level.sprStore[spriteName];
105   for (MapBackTile bt = bgback; bt; bt = bt.next) bt.onLoaded();
106   for (MapBackTile bt = bgfront; bt; bt = bt.next) bt.onLoaded();
107   if (gem) gem.onLoaded();
111 string getExitMessage () {
112   return "";
116 override SpriteImage getSprite (optional out bool doMirror) {
117   doMirror = false;
118   return sprite;
122 override SpriteFrame getSpriteFrame (optional out bool doMirror, optional out int x0, optional out int y0, optional out int x1, optional out int y1) {
123   auto spr = getSprite(doMirror!optional);
124   if (!spr || spr.frames.length == 0) return none;
125   auto spf = spr.frames[trunc(imageFrame)%spr.frames.length];
126   if (!spf) return none;
127   if (specified_x0 || specified_x1) {
128     x0 = (ignoreFrameOffsetX ? 0 : -spf.xofs);
129     x1 = x0+spf.tex.width;
130   }
131   if (specified_y0 || specified_y1) {
132     y0 = (ignoreFrameOffsetY ? 0 : -spf.yofs);
133     y1 = y0+spf.tex.height;
134   }
135   return spf;
139 override void clearSprite () {
140   sprite = none;
141   spriteName = '';
145 override void setSprite (name sprNameL, optional name sprNameR) {
146   if (!sprNameL && sprNameR) sprNameL = sprNameR;
147   if (spriteName != sprNameL) {
148     spriteName = sprNameL;
149     sprite = (sprNameL ? level.sprStore[sprNameL] : none);
150     imageFrame = 0;
151   }
155 // ////////////////////////////////////////////////////////////////////////// //
156 override int width () { return Width; }
157 override int height () { return Height; }
160 void beautifyTile () {}
161 void scrSetupBlockTop () {}
162 void scrSetupBlockBottom () {}
163 void scrSetupBlockLeft () {}
164 void scrSetupBlockRight () {}
167 // ////////////////////////////////////////////////////////////////////////// //
168 void setGem (name oname, optional bool visibility) {
169   if (gem) {
170     gem.ownerTile = none;
171     gem.instanceRemove();
172     gem = none;
173   }
174   if (!oname) return;
175   gem = level.MakeMapObject(ix+8, iy+8, oname);
176   if (!gem) return;
177   gem.ownerTile = self;
178   gem.hiddenTreasure = !global.hasSpectacles && !global.hasUdjatEye;
179   if (specified_visibility) gem.hiddenTreasure = !visibility;
180   gem.active = false;
181   gem.spectral = true;
182   if (gem.hiddenTreasure) gem.visible = false;
186 void setGemObject (MapObject obj, optional bool visibility) {
187   if (obj) {
188     if (obj.ownerTile == self) {
189       if (gem != obj) FatalError("MapTile::setGemObject: WTF?! (0)");
190     }
191     if (gem == obj) FatalError("MapTile::setGemObject: WTF?! (1)");
192   }
193   if (gem) {
194     gem.ownerTile = none;
195     gem.instanceRemove();
196     gem = none;
197   }
198   if (!obj) return;
199   if (obj.ownerTile) {
200     obj.ownerTile.gem = none;
201     obj.ownerTile = none;
202   }
203   gem = obj;
204   gem.ownerTile = self;
205   gem.hiddenTreasure = !global.hasSpectacles && !global.hasUdjatEye;
206   if (specified_visibility) gem.hiddenTreasure = !visibility;
207   gem.active = false;
208   gem.spectral = true;
209   if (gem.hiddenTreasure) gem.visible = false;
210   gem.fltx = ix+8;
211   gem.flty = iy+8;
215 void convertGemObjectToDiamond (MapObject ghost) {
216   auto oldgem = gem;
217   if (oldgem && oldgem isa ItemBigGem) {
218     if (ghost) {
219       if (!ghost.isInstanceAlive) return;
220       if (!gem.isRectHitSimple(ghost.x0, ghost.y0, ghost.width, ghost.height)) return;
221     }
222     auto diamond = level.MakeMapObject(oldgem.ix, oldgem.iy, 'oDiamond');
223     setGemObject(diamond);
224   }
228 // ////////////////////////////////////////////////////////////////////////// //
229 void doCreateOre (int probSap, int probEmer, int probRuby, int probItem) {
230   int n = global.randRoom(1, 100);
231        if (n < 20) ore = 1; // sprite_index = sBrickGold;
232   else if (n < 30) ore = 2; // sprite_index = sBrickGoldBig;
233        if (probSap > 0 && global.randRoom(1, probSap) == 1) setGem('oSapphireBig');
234   else if (probEmer > 0 && global.randRoom(1, probEmer) == 1) setGem('oEmeraldBig');
235   else if (probRuby > 0 && global.randRoom(1, probRuby) == 1) setGem('oRubyBig');
236   else if (probItem > 0 && global.randRoom(1, probItem) == 1) setGemObject(level.scrGenerateItem(ix+8, iy+8, GameLevel::GenItemSet.Underground));
240 void copyOreFrom (MapTile t) {
241   if (t == self) return;
242   if (t) {
243     setGemObject(t.gem);
244     ore = t.ore;
245   } else {
246     setGemObject(none);
247     ore = 0;
248   }
252 void removeOre () {
253   ore = 0;
257 void closeExit () {
258   if (exit) setSprite('sEntrance');
262 void openExit () {
263   if (exit) setSprite('sExit');
267 bool isExitActive () {
268   return (visible && exit && spriteName == 'sExit');
272 // ////////////////////////////////////////////////////////////////////////// //
273 void setupTile () {
274   //hangeable = true;
275   switch (objName) {
276     case 'oLadderOrange':
277       //objName = 'oLadder';
278       //goto case 'oLadder';
279     case 'oLadder':
280       objType = 'oLadder';
281       ladder = true;
282       solid = false;
283       spriteName = 'sLadder';
284       break;
285     case 'oLadderTop':
286       objType = 'oLadder';
287       //ladder = true; // no, laddertop is not a ladder
288       laddertop = true;
289       platform = true;
290       solid = false;
291       spriteName = 'sLadderTop';
292       break;
293     case 'oVineTop':
294       objType = 'oVineTop';
295       //ladder = true; // no, laddertop is not a ladder
296       laddertop = true;
297       platform = true;
298       solid = false;
299       spriteName = 'sVineTop';
300       break;
301     case 'oPushBlock':
302       //objName = 'oPushBlock';
303       objType = 'oBlock';
304       moveable = true;
305       spriteName = (global.cityOfGold ? 'sGoldBlock' : 'sBlock');
306       break;
307     case 'oPushIceBlock':
308       //objName = 'oPushBlock';
309       objType = 'oBlock';
310       ice = true;
311       moveable = true;
312       spriteName = 'sIceBlock';
313       break;
314     case 'oSolidIceBlock':
315       //objName = 'oIceBlock';
316       //goto case 'oIceBlock';
317       objType = 'oBlock';
318       ice = true;
319       solid = true;
320       moveable = false;
321       moveable = true; //k8: why, let it be an easter egg
322       spriteName = 'sIceBlock';
323       break;
324     case 'oIceBlock':
325       objType = 'oBlock';
326       ice = true;
327       solid = true;
328       moveable = true;
329       spriteName = 'sIceBlock';
330       break;
331     case 'oEdgeBrick':
332       // no sense to create ore here
333       objType = 'oBrick';
334       spriteName = (global.randRoom(1, 10) == 1 ? 'sBrick2' : 'sBrick');
335       break;
336     case 'oBrickSmooth':
337       objType = 'oBrick';
338       spriteName = 'sCaveSmooth';
339       break;
340     case 'oSpikesWood':
341       objType = 'oSpikes';
342       solid = false;
343       spikes = true;
344       woodenSpikes = true;
345       spriteName = 'sSpikesWood';
346       break;
347     case 'oSpikes':
348       objType = 'oSpikes';
349       solid = false;
350       spikes = true;
351       spriteName = 'sSpikes';
352       break;
353     case 'oEntrance':
354       objType = 'oEntrance';
355       enter = true;
356       solid = false;
357       invincible = true;
358       depth = 2000;
359       spriteName = 'sEntrance';
360       break;
361     case 'oExit':
362       objType = 'oExit';
363       solid = false;
364       invincible = true;
365       exit = true;
366       depth = 2000;
367       spriteName = 'sExit';
368       break;
369     case 'oAltarLeft':
370       objType = 'oAltar';
371       altar = true;
372       spriteName = 'sAltarLeft';
373       break;
374     case 'oAltarRight':
375       objType = 'oAltar';
376       altar = true;
377       spriteName = 'sAltarRight';
378       break;
379     case 'oSacAltarLeft':
380       objType = 'oAltar';
381       altar = true;
382       sacrificingAltar = true;
383       spriteName = 'sSacAltarLeft';
384       break;
385     case 'oSacAltarRight':
386       objType = 'oAltar';
387       altar = true;
388       sacrificingAltar = true;
389       spriteName = 'sSacAltarRight';
390       break;
391     case 'oSign':
392       objType = 'oSign';
393       solid = false;
394       spriteName = 'sSign';
395       break;
396     case 'oSignGeneral':
397       objType = 'oSign';
398       solid = false;
399       spriteName = 'sSignGeneral';
400       break;
401     case 'oSignBomb':
402       objType = 'oSign';
403       solid = false;
404       spriteName = 'sSignBomb';
405       break;
406     case 'oSignWeapon':
407       objType = 'oSign';
408       solid = false;
409       spriteName = 'sSignWeapon';
410       break;
411     case 'oSignClothing':
412       objType = 'oSign';
413       solid = false;
414       spriteName = 'sSignClothing';
415       break;
416     case 'oSignRare':
417       objType = 'oSign';
418       solid = false;
419       spriteName = 'sSignRare';
420       break;
421     case 'oSignCraps':
422       objType = 'oSign';
423       solid = false;
424       spriteName = 'sSignCraps';
425       break;
426     case 'oSignKissing':
427       objType = 'oSign';
428       solid = false;
429       spriteName = 'sSignKissing';
430       break;
431     /*
432     case 'oMoai':
433       objType = 'oMoai';
434       solid = true;
435       invincible = true;
436       spriteName = 'sMoai';
437       break;
438     */
439     case 'oMoai2':
440       objType = 'oMoai';
441       solid = true;
442       invincible = true;
443       spriteName = 'sMoai2';
444       break;
445     /*
446     case 'oMoai3':
447       objType = 'oMoai';
448       solid = true;
449       invincible = true;
450       spriteName = 'sMoai3';
451       break;
452     */
453     /*
454     case 'oMoaiInside':
455       objType = 'oMoaiInside';
456       solid = true;
457       invincible = true;
458       spriteName = 'sMoaiInside';
459       depth = 92;
460       break;
461     */
462     default:
463       FatalError(va("unknown map tile type '%n'", objName));
464   }
465   //if (!solid) hangeable = false; // just in case
469 void setupTileSprite () {
470   if (!spriteName) FatalError("forgot to set sprite for tile type '", objName, "'");
471   sprite = level.sprStore[spriteName];
475 override bool initialize () {
476   if (!::initialize()) return false;
477   setupTile();
478   setupTileSprite();
479   if (moveable && depth == 1001) depth = 1000;
480   if ((exit || enter) && depth == 1001) depth = 2000;
481   return true;
485 // ////////////////////////////////////////////////////////////////////////// //
486 // for now, it works only for spikes
487 final void makeBloody () {
488   if (bloody) return;
489   if (!spikes) return;
490   bloody = true;
491   setSprite(woodenSpikes ? 'sSpikesWoodBlood' : 'sSpikesBlood');
495 // ////////////////////////////////////////////////////////////////////////// //
496 final void appendBackBack (MapBackTile tile, optional int yofs) {
497   if (!tile) return;
498   if (tile.next) FatalError("MapTile::appendBackBack: wtf?!");
499   tile.flty = (specified_yofs ? float(yofs) : 16.0);
500   MapBackTile last = bgback;
501   if (last) {
502     while (last.next) last = last.next;
503     last.next = tile;
504   } else {
505     bgback = tile;
506   }
510 final void appendBackFront (MapBackTile tile, optional int yofs) {
511   if (!tile) return;
512   if (tile.next) FatalError("MapTile::appendBackFront: wtf?!");
513   tile.flty = (specified_yofs ? float(yofs) : -16.0);
514   MapBackTile last = bgfront;
515   if (last) {
516     while (last.next) last = last.next;
517     last.next = tile;
518   } else {
519     bgfront = tile;
520   }
524 // ////////////////////////////////////////////////////////////////////////// //
526 override void onDestroy () {
531 // ////////////////////////////////////////////////////////////////////////// //
532 override void thinkFrame () {
533   if (!moveable) return;
534   // applies the acceleration
535   //xVel += xAcc;
536   //yVel += yAcc;
538   // approximates the "active" variables
539   if (fabs(xVel) < 0.001) xVel = 0;
540   if (fabs(yVel) < 0.001) yVel = 0;
541   //if (fabs(xAcc) < 0.0001) xAcc = 0;
542   //if (fabs(yAcc) < 0.0001) yAcc = 0;
544   yVel = fclamp(yVel+myGrav, -myGravLimit, myGravLimit);
545   int newY = round(flty+yVel); //!!!
546   // check if we need (and can) move down
547   int w = width, hp1 = height+1;
548   auto oldsolid = solid;
549   solid = false;
550   while (newY > iy) {
551     // made non-solid temporarily
552     auto hasAnything = level.checkTilesInRect(x0, y0, w, hp1, delegate bool (MapTile t) {
553       if (t == self) return false;
554       return t.solid;
555     });
556     if (hasAnything) {
557       // hit something, stop right there
558       if (yVel > myGrav*3) playSound('sndThud');
559       yVel = 0;
560       flty = iy;
561       break;
562     }
563     // move us
564     shiftY(1);
565   }
566   solid = oldsolid;
568   if (flty > level.tilesHeight*16+16 && yVel >= 0) {
569     cleanDeath = true;
570     instanceRemove();
571   }
575 // ////////////////////////////////////////////////////////////////////////// //
576 void drawWithOfsBack (int xpos, int ypos, int scale, float currFrameDelta) {
577   //if (backTile) backTile.drawAt(xpos, ypos, scale);
578   if (invisible || !visible || !bgback) return;
580   bool doMirror;
581   int fx0, fy0, fx1, fy1;
582   auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
584   // non-moveable and non-special tiles need not to be interpolated
585   int drwx, drwy;
586   if (moveable || gridId) {
587     getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
588   } else {
589     drwx = ix*scale;
590     drwy = iy*scale;
591   }
593   for (MapBackTile bt = bgback; bt; bt = bt.next) {
594     bt.fltx += fltx;
595     bt.flty += flty;
596     bt.drawWithOfs(xpos, ypos, scale, currFrameDelta);
597     bt.fltx -= fltx;
598     bt.flty -= flty;
599   }
603 void drawWithOfsFront (int xpos, int ypos, int scale, float currFrameDelta) {
604   //if (backTile) backTile.drawAt(xpos, ypos, scale);
605   if (invisible || !visible || !bgfront) return;
607   bool doMirror;
608   int fx0, fy0, fx1, fy1;
609   auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
611   // non-moveable tiles need not to be interpolated
612   int drwx, drwy;
613   if (moveable) {
614     getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
615   } else {
616     drwx = ix*scale;
617     drwy = iy*scale;
618   }
620   for (MapBackTile bt = bgfront; bt; bt = bt.next) {
621     bt.fltx += fltx;
622     bt.flty += flty;
623     bt.drawWithOfs(xpos, ypos, scale, currFrameDelta);
624     bt.fltx -= fltx;
625     bt.flty -= flty;
626   }
630 override void drawWithOfs (int xpos, int ypos, int scale, float currFrameDelta) {
631   if (invisible || !visible) return;
633   bool doMirror;
634   int fx0, fy0, fx1, fy1;
635   auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
637   // non-moveable tiles need not to be interpolated
638   int drwx, drwy;
639   if (moveable) {
640     getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
641   } else {
642     drwx = ix*scale;
643     drwy = iy*scale;
644   }
646   //auto oclr = Video.color;
647   //if (moveable) Video.color = 0xff_7f_00;
649   if (spf) {
650     fx0 = drwx+fx0*scale-xpos;
651     fy0 = drwy+fy0*scale-ypos;
652     fx1 = drwx+fx1*scale-xpos;
653     fy1 = drwy+fy1*scale-ypos;
654     if (!doMirror) {
655       spf.tex.blitExt(fx0, fy0, fx1, fy1, 0, 0, spf.tex.width, spf.tex.height);
656     } else {
657       spf.tex.blitExt(fx0, fy0, fx1, fy1, spf.tex.width, 0, 0, spf.tex.height);
658     }
659   } else if (ore > 0) {
660     fx0 = drwx-xpos;
661     fy0 = drwy-ypos;
662     fx1 = drwx-xpos;
663     fy1 = drwy-ypos;
664   }
666   //Video.color = oclr;
668   if (ore > 0 && !hideOre) {
669     auto ospr = level.sprStore[ore == 1 ? 'sGold' : 'sGoldBig'];
670     ospr.frames[0].tex.blitAt(fx0, fy0, scale);
671   }
675 // ////////////////////////////////////////////////////////////////////////// //
676 // called by Destroy event of blocks that can contain ore (oBrick, oLush, etc)
677 void scrDropOre () {
678   if (ore < 1) return;
680   int x = ix, y = iy;
682   foreach (int i; 0..3) {
683     auto gold = level.MakeMapObject(x+8+global.randOther(0, 4)-global.randOther(0, 4), y+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldChunk');
684     if (gold) {
685       //gold = instance_create(x+8+rand(0,4)-rand(0,4), y+8+rand(0,4)-rand(0,4), oGoldChunk);
686       gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
687       gold.yVel = global.randOther(2, 4);
688     }
689   }
691   if (ore > 1) {
692     // sGoldBig
693     auto gold = level.MakeMapObject(x+8+global.randOther(0, 4)-global.randOther(0, 4), y+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldNugget');
694     if (gold) {
695       gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
696       gold.yVel = global.randOther(2, 4);
697     }
698   }
700   ore = 0;
704 // ////////////////////////////////////////////////////////////////////////// //
705 private final bool cbIsSolidBrick (MapTile t) { return t.solid; }
706 private final bool cbIsBrickOrLushTile (MapTile t) { return (t.objType == 'oBrick' || t.objType == 'oLush'); }
709 // ////////////////////////////////////////////////////////////////////////// //
710 private transient MapObject mXplo;
712 private final bool cbExplodeTiles (MapTile t) {
713   if (!t.isInstanceAlive) return false;
714   if (t.invincible) return false;
715   switch (t.objType) {
716     case 'oSpikes':
717     case 'oTikiTorch':
718     case 'oGrave':
719     case 'oLampRed':
720     case 'oLamp':
721       t.onExplosionTouch(mXplo);
722       break;
723   }
724   return false;
728 // called by solids during destroy event (oBrick etc) when destroy with force such as a boulder or explosion
729 // these rubble bits shower over the foreground and are not stopped by solids
730 final void scrSprayRubble (int x, int y, int count, name spr0, name spr1) {
731   while (count-- > 0) {
732     auto rubble = level.MakeMapObject(
733                     x+8+global.randOther(0, 8)-global.randOther(0, 8),
734                     y+8+global.randOther(0, 8)-global.randOther(0, 8), 'oRubblePiece'); //'oRubblePieceNonSolid');
735     rubble.setSprite(global.randOther(1, 3) == 1 ? spr0 : spr1);
736     rubble.xVel = global.randOther(0, 4)-global.randOther(0, 4);
737     rubble.yVel = -global.randOther(4, 8);
738     rubble.yAcc = 0.6;
739     //rubble.image_blend = image_blend;
740   }
744 final void scrDropRubble (int x, int y, int count, name spr0, name spr1) {
745   while (count-- > 0) {
746     int rx = x+8+global.randOther(0, 8)-global.randOther(0, 8);
747     int ry = y+8+global.randOther(0, 8)-global.randOther(0, 8);
748     if (global.randOther(1, 3) == 1) {
749       auto rubble = level.MakeMapObject(rx, ry, 'oRubble');
750       rubble.setSprite(spr1);
751       //rubble.image_blend = image_blend;
752     } else {
753       auto rubble = level.MakeMapObject(rx, ry, 'oRubbleSmall');
754       rubble.setSprite(spr0);
755       //rubble.image_blend = image_blend;
756     }
757   }
761 void smashedCheckUpTile () {
762   level.checkTilesInRect(ix, iy-16, 16, 16, delegate bool (MapTile t) {
763     //writeln("mtl: '", GetClassName(t.Class), "'; spikes=", t.spikes);
764     if (t.isInstanceAlive && (t.spikes || t isa MapTileGraveBase)) t.instanceRemove();
765     return false;
766   });
771 void doSprayRubble () {
772   if (objType == 'oBlock') {
773     if (!global.cityOfGold) {
774       if (smashed) scrSprayRubble(ix, iy, 3, 'sRubbleTan', 'sRubbleTanSmall');
775       scrDropRubble(ix, iy, 3, 'sRubbleTan', 'sRubbleTanSmall');
776     } else {
777       // ore = 2;
778       // scrDropOre();
779     }
780   } else if (objType == 'oBrick' || objType == 'oLush') {
781     if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
782     scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
783   }
787 final bool isVineTile (MapTile t) { return (t.objType == 'oVineTop' || t.objType == 'oVine'); }
790 // this should be called from `smashMe()`
791 void checkSmashedVineSupport () {
792   if (!solid || isVineTile(self)) return;
793   // check for support tile ('cause this can be a moving tile, and not an actual support)
794   auto support = level.checkTileAtPoint(ix, iy, delegate bool (MapTile t) {
795     if (t == self) return false;
796     return (t.isInstanceAlive && t.solid);
797   });
798   if (support) return;
799   // no support
800   int x = ix+8;
801   int y = iy+16+8;
802   auto vine = level.checkTileAtPoint(x, y, &isVineTile);
803   if (!vine) return;
804   // yay, we found a vine; now remove it, going all the way down
805   for (;;) {
806     vine = level.checkTileAtPoint(x, y, &isVineTile);
807     if (!vine) return;
808     vine.smashMe();
809     y += 16;
810   }
814 bool smashMe () {
815   if (invincible) return false;
816   smashed = true;
818   checkSmashedVineSupport();
820   if (/*!global.cemetary &&*/ isVineTile(self)) {
821     level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
822     level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
823   }
825   if (altar) level.scrTriggerIdolAltar(stolenIdol:true);
827   instanceRemove();
828   smashedCheckUpTile();
830   if (gem) {
831     if (gemDestroyWithTile) {
832       gem.instanceRemove();
833     } else {
834       gem.hiddenTreasure = false;
835       gem.visible = true;
836       gem.active = true;
837       gem.spectral = false;
838     }
839     gem = none; // don't destroy it with tile
840   }
842   scrDropOre();
844   bool smashed = true;
845   if (!cleanDeath) doSprayRubble();
847   /*
848   if (other.object_index == oBoulder) {
849     if (other.integrity &gt; 0) dosomething = false; //exit;
850   }
851   */
853   //with (oTreasure) state = 1;
854   //with (oSpikes) if (not collision_point(x, y+16, oSolid, 0, 0)) instance_destroy();
856   if (shopWall) {
857     level.scrShopkeeperAnger(GameLevel::SCAnger.TileDestroyed);
858   }
860   // Moloch
861   int x = ix, y = iy;
862   MapTile obj = level.checkTileAtPoint(x, y-16, &cbIsSolidBrick);
863   if (obj) obj.scrSetupBlockBottom();
864   obj = level.checkTileAtPoint(x, y+16, &cbIsSolidBrick);
865   if (obj) obj.scrSetupBlockTop();
866   obj = level.checkTileAtPoint(x-16, y, &cbIsSolidBrick);
867   if (obj) obj.scrSetupBlockLeft();
868   obj = level.checkTileAtPoint(x+16, y, &cbIsSolidBrick);
869   if (obj) obj.scrSetupBlockRight();
871   // Moloch
872   /* was commented in the original
873   instance_activate_object(oLadder);
874   obj = collision_point(x+8, y+24, oLadder, 0, 0);
875   with (obj) {
876     if (sprite_index == sVineTop or sprite_index == sVine or
877         sprite_index == sVineSource or sprite_index == sVineBottom)
878     {
879       instance_destroy();
880     }
881   }
882   */
884   return true;
888 override bool onExplosionTouch (MapObject xplo) {
889   int x = ix, y = iy;
891   if (smashMe()) {
892     mXplo = xplo;
893     level.checkTileAtPoint(x+8, y-1, &cbExplodeTiles);
894     mXplo = none;
896     /+
897     obj = instance_place(x, y, oGold);
898     if (obj != noone) with (obj) instance_destroy();
900     obj = instance_place(x, y, oGoldBig);
901     if (obj != noone) with (obj) instance_destroy();
902     +/
904     return true;
905   }
907   return false;
911 // ////////////////////////////////////////////////////////////////////////// //
912 void onGotSpectacles () {
913   if (gem) {
914     gem.hiddenTreasure = false;
915     gem.visible = true;
916   }
920 defaultproperties {
921   objName = 'oTile';
922   objType = 'oSolid';
923   //depth = 9666; //???
924   depth = 1001;
928 // ////////////////////////////////////////////////////////////////////////// //
929 class MapTileBrick['oBrick'] : MapTile;
932 override void setupTile () {
933   spriteName = (global.randRoom(1, 10) == 1 ? 'sBrick2' : 'sBrick');
934   int tileX = ix/16, tileY = iy/16;
935   if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
936     invincible = true;
937     border = true;
938     //writeln("BORDER");
939   } else {
940     doCreateOre(100, 120, 140, 1200);
941   }
945 override void beautifyTile () {
946   if (level.checkTileAtPoint(ix+8, iy-8, delegate bool (MapTile t) { return (t isa TitleTileSpecialNonSolidInvincible || t isa TitleTileBlack); })) return;
948   // brick
949   //if (argument1) if (global.randRoom(1, 10) == 1) sprite_index = sBrick2; // reset sprite for custom border setup
951   int tileX = ix/16, tileY = iy/16;
953   auto tt = level.getTileAt(tileX, tileY-1);
954   //if (y == 0 || isNamedTileAt(x, y-1, 'oBrick') || isNamedTileAt(x, y-1, 'oHardBlock') || isNamedTileAt(x, y-1, 'oEdgeBrick')) up = true;
955   bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
957   tt = level.getTileAt(tileX, tileY+1);
958   //if (y == NormalTilesHeight-1 || isNamedTileAt(x, y+1, 'oBrick') || isNamedTileAt(x, y+1, 'oHardBlock') || isNamedTileAt(x, y+1, 'oEdgeBrick')) down = true;
959   bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
961   //if (collision_point(x-16, y, oBrick, 0, 0) or collision_point(x-16, y, oHardBlock, 0, 0)) { left = true; } // in the original
962   //if (collision_point(x+16, y, oBrick, 0, 0) or collision_point(x+16, y, oHardBlock, 0, 0)) { right = true; } // in the original
964   if (!up) {
965     setSprite('sCaveUp');
966     // add rocks and sand
967     /*
968     if (global.randRoom(1, 3) < 3) {
969       tile_add('bgCaveTop', 0, 0, 16, 16, x*16, y*16-16, 3);
970     } else {
971       tile_add('bgCaveTop', 16, 0, 16, 16, x*16, y*16-16, 3);
972     }
973     */
974     bool n = (global.randRoom(1, 3) < 3);
975     appendBackFront(level.CreateBgTile('bgCaveTop', (n ? 0 : 16)));
976     //instance_create(x, y-16, oCaveTop);
977   }
979   if (!down) {
980     setSprite(!up ? 'sCaveUp2' : 'sBrickDown');
981     //instance_create(x, y+16, oCaveBottom);
982   }
984   /* in the original
985   if (not left) instance_create(x-16, y, oCaveLeft);
986   if (not right) instance_create(x+15, y, oCaveRight);
987   */
991 override void scrSetupBlockTop () {
992   //int x = ix, y = iy;
993   int tileX = ix/16, tileY = iy/16;
994   auto tt = level.getTileAt(tileX, tileY+1);
995   //if (y >= (GameLevel::NormalTilesHeight-16)*16 || level.checkTileAtPoint(x, y+16, &cbIsBrickOrLushTile)) down = true;
996   bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
997   // gfxhigh
998   auto bt = level.CreateBgTile('bgCaveTop', (global.randRoom(1, 3) < 3 ? 0 : 16));
999   //bt.flty = -16; // offset
1000   appendBackFront(bt);
1001   // other
1002   setSprite(down ? 'sCaveUp' : 'sCaveUp2');
1006 override void scrSetupBlockBottom () {
1007   //int x = ix, y = iy;
1008   //bool up = false;
1009   //if (y == 0 || level.checkTileAtPoint(x, y-16, &cbIsBrickOrLushTile)) up = true;
1010   int tileX = ix/16, tileY = iy/16;
1011   auto tt = level.getTileAt(tileX, tileY-1);
1012   bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1013   setSprite(up ? 'sBrickDown' : 'sCaveUp2');
1017 defaultproperties {
1018   objType = 'oBrick';
1019   spriteName = 'sBrick';
1020   invincible = false;
1021   lava = false;
1022   solid = true;
1023   toSpecialGrid = false;
1027 // ////////////////////////////////////////////////////////////////////////// //
1028 class MapTileHardBrick['oHardBlock'] : MapTileBrick;
1030 override void setupTile () {
1034 defaultproperties {
1035   objType = 'oHardBlock';
1036   spriteName = 'sBrick';
1037   invincible = true;
1038   solid = true;
1042 // ////////////////////////////////////////////////////////////////////////// //
1043 class MapTileBlock['oBlock'] : MapTile;
1046 override void setupTile () {
1047   int tileX = ix/16, tileY = iy/16;
1048   if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1049     invincible = true;
1050     border = true;
1051     //writeln("BORDER");
1052   }
1053   if (global.cityOfGold) spriteName = 'sGoldBlock';
1057 final bool isBBTTile (MapTile t) { return (t.objType == 'oBrick' || t.objType == 'oTemple' || t.objType == 'oHardBlock'); }
1060 override void beautifyTile () {
1061   bool down = !!level.checkTileAtPoint(ix, iy+16, &isBBTTile);
1063   // don't want push blocks next to lava until we tighten up liquid draining
1064   if (level.isLavaAtPoint(ix-16, iy) || level.isLavaAtPoint(ix+16, iy)) down = false;
1066   if (down && global.randRoom(1, 4) == 1) {
1067     instanceRemove();
1068     level.MakeMapTile(ix/16, iy/16, 'oPushBlock');
1069   }
1073 defaultproperties {
1074   objType = 'oBlock';
1075   spriteName = 'sBlock';
1076   invincible = false;
1077   solid = true;
1078   toSpecialGrid = false;
1082 // ////////////////////////////////////////////////////////////////////////// //
1083 class MapTileLush['oLush'] : MapTile;
1086 override void setupTile () {
1087   int tileX = ix/16, tileY = iy/16;
1088   if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1089     invincible = true;
1090     border = true;
1091     //writeln("BORDER");
1092   } else {
1093     doCreateOre(80, 100, 120, 1200);
1094   }
1098 override void beautifyTile () {
1099   if (level.checkTileAtPoint(ix+8, iy-8, delegate bool (MapTile t) { return (t isa TitleTileSpecialNonSolidInvincible || t isa TitleTileBlack); })) return;
1101   int tileX = ix/16, tileY = iy/16;
1103   auto tt = level.getTileAt(tileX, tileY-1);
1104   //if (y == 0 || isNamedTileAt(x, y-1, 'oBrick') || isNamedTileAt(x, y-1, 'oHardBlock') || isNamedTileAt(x, y-1, 'oEdgeBrick')) up = true;
1105   bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral && tt.objType == 'oLush'));
1107   tt = level.getTileAt(tileX, tileY+1);
1108   //if (y == NormalTilesHeight-1 || isNamedTileAt(x, y+1, 'oBrick') || isNamedTileAt(x, y+1, 'oHardBlock') || isNamedTileAt(x, y+1, 'oEdgeBrick')) down = true;
1109   bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral && tt.objType == 'oLush'));
1111   if (!up) {
1112     setSprite('sLushUp');
1113     if (!level.isLavaAtPoint(ix+8, iy-8)) {
1114       MapBackTile bt;
1115            if (global.randRoom(1, 8) == 1) bt = level.CreateBgTile('bgCaveTop2', 32);
1116       else if (global.randRoom(1, 3) < 3) bt = level.CreateBgTile('bgCaveTop2', 0);
1117       else bt = level.CreateBgTile('bgCaveTop2', 16);
1118       appendBackFront(bt);
1119     }
1120   }
1122   if (!down) {
1123     setSprite(!up ? 'sLushUp2' : 'sLushDown');
1124     if (!level.isSolidAtPoint(ix, iy+16) && !level.isLavaAtPoint(ix, iy+16)) {
1125       MapBackTile bt;
1126            if (global.randRoom(1, 12) == 1) bt = level.CreateBgTile('bgCaveTop2', 48);
1127       else if (global.randRoom(1, 12) == 1) bt = level.CreateBgTile('bgCaveTop2', 64);
1128       appendBackBack(bt);
1129     }
1130     //instance_create(x, y+16, oLushBottom); // in the original
1131   }
1135 override void scrSetupBlockTop () {
1136   int tileX = ix/16, tileY = iy/16;
1137   auto tt = level.getTileAt(tileX, tileY+1);
1138   bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1139   //setSprite('sLushUpBare');
1140   if (!down) {
1141     setSprite(spriteName == 'sLushUp' || spriteName == 'sLushUp2' || spriteName == 'sLushUp3' ? 'sLushUpBareTop' : 'sLushUpBare2');
1142   } else {
1143     setSprite('sLushUpBare');
1144   }
1148 override void scrSetupBlockBottom () {
1149   int tileX = ix/16, tileY = iy/16;
1150   auto tt = level.getTileAt(tileX, tileY-1);
1151   bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1152   if (!up) {
1153     setSprite(spriteName == 'sLushUp' || spriteName == 'sLushUp2' || spriteName == 'sLushUp3' ? 'sLushUpBareBottom' : 'sLushUpBare2');
1154   } else {
1155     setSprite('sLushDownBare');
1156   }
1160 defaultproperties {
1161   objType = 'oLush';
1162   spriteName = 'sLush';
1163   invincible = false;
1164   lava = false;
1165   solid = true;
1166   toSpecialGrid = false;
1170 // ////////////////////////////////////////////////////////////////////////// //
1171 class MapTileDark['oDark'] : MapTile;
1174 override void setupTile () {
1175   int tileX = ix/16, tileY = iy/16;
1176   if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1177     invincible = true;
1178     border = true;
1179     //writeln("BORDER");
1180   } else {
1181     doCreateOre(40, 60, 80, 1200);
1182   }
1186 override void beautifyTile () {
1187   if (level.checkTileAtPoint(ix+8, iy-8, delegate bool (MapTile t) { return (t isa TitleTileSpecialNonSolidInvincible || t isa TitleTileBlack); })) return;
1189   // brick
1190   //if (argument1) if (global.randRoom(1, 10) == 1) sprite_index = sBrick2; // reset sprite for custom border setup
1192   int tileX = ix/16, tileY = iy/16;
1194   auto tt = level.getTileAt(tileX, tileY-1);
1195   //if (y == 0 || isNamedTileAt(x, y-1, 'oBrick') || isNamedTileAt(x, y-1, 'oHardBlock') || isNamedTileAt(x, y-1, 'oEdgeBrick')) up = true;
1196   bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1198   tt = level.getTileAt(tileX, tileY+1);
1199   //if (y == NormalTilesHeight-1 || isNamedTileAt(x, y+1, 'oBrick') || isNamedTileAt(x, y+1, 'oHardBlock') || isNamedTileAt(x, y+1, 'oEdgeBrick')) down = true;
1200   bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1202   //if (collision_point(x-16, y, oBrick, 0, 0) or collision_point(x-16, y, oHardBlock, 0, 0)) { left = true; } // in the original
1203   //if (collision_point(x+16, y, oBrick, 0, 0) or collision_point(x+16, y, oHardBlock, 0, 0)) { right = true; } // in the original
1205   if (!up) {
1206     setSprite('sDarkUp');
1207     bool n = (global.randRoom(1, 3) < 3);
1208     appendBackFront(level.CreateBgTile('bgCaveTop3', (n ? 0 : 16)));
1209   }
1211   if (!down) {
1212     setSprite(!up ? 'sDarkUp2' : 'sDarkDown');
1213   }
1217 override void scrSetupBlockTop () {
1218   int tileX = ix/16, tileY = iy/16;
1219   auto tt = level.getTileAt(tileX, tileY+1);
1220   bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1221   auto bt = level.CreateBgTile('bgCaveTop3', (global.randRoom(1, 3) < 3 ? 0 : 16));
1222   appendBackFront(bt);
1223   //bt.flty = -16; // offset
1224   // other
1225   setSprite(down ? 'sDarkUp' : 'sDarkUp2');
1229 override void scrSetupBlockBottom () {
1230   int tileX = ix/16, tileY = iy/16;
1231   auto tt = level.getTileAt(tileX, tileY-1);
1232   bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1233   setSprite(up ? 'sDarkDown' : 'sDarkUp2');
1237 defaultproperties {
1238   objType = 'oDark';
1239   spriteName = 'sDark';
1240   invincible = false;
1241   lava = false;
1242   solid = true;
1243   toSpecialGrid = false;
1247 // ////////////////////////////////////////////////////////////////////////// //
1248 class MapTileIce['oIce'] : MapTile;
1250 bool hasIceBottom;
1251 int dripTimer;
1253 final bool isIceTile (MapTile t) { return t.ice; }
1254 final MapTile isIceTileAtPoint (int x, int y) { return level.checkTileAtPoint(x, y, &isIceTile); }
1257 override void setupTile () {
1258   if (global.randRoom(1, 80) == 1) {
1259     setGem('oFrozenCaveman', true); // always visible
1260     gem.fltx = ix;
1261     gem.flty = iy;
1262     gem.saveInterpData();
1263     gemDestroyWithTile = true;
1264   }
1265   dripTimer = global.randOther(20, 400);
1266   //hasIceBottom = true;
1267   //dripTimer = 40;
1271 override void thinkFrame () {
1272   if (hasIceBottom) {
1273     if (--dripTimer <= 0) {
1274       //writeln("DRIP!");
1275       level.MakeMapObject(ix+8, iy+16+4, 'oDrip');
1276       dripTimer = global.randOther(20, 400);
1277       //dripTimer = 40;
1278     }
1279   }
1283 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1284   bool up = (specified_noup && noup ? false : !!isIceTileAtPoint(ix, iy-16));
1285   bool down = (specified_nodown && nodown ? false : !!isIceTileAtPoint(ix, iy+16));
1286   bool left = (specified_noleft && noleft ? false : !!isIceTileAtPoint(ix-16, iy));
1287   bool right = (specified_noright && noright ? false : !!isIceTileAtPoint(ix+16, iy));
1289   if (!up) setSprite('sIceUp');
1290   if (!down) {
1291     setSprite(!up ? 'sIceUp2' : 'sIceDown');
1292     if (!level.isSolidAtPoint(ix, iy+16) && global.randOther(1, 20) == 1) hasIceBottom = true;
1293   }
1294   if (!left) {
1295          if (!up && !down) setSprite('sIceUDL');
1296     else if (!up) setSprite('sIceUL');
1297     else if (!down) setSprite('sIceDL');
1298     else setSprite('sIceLeft');
1299   }
1300   if (!right) {
1301          if (!up && !down) setSprite('sIceUDR');
1302     else if (!up) setSprite('sIceUR');
1303     else if (!down) setSprite('sIceDR');
1304     else setSprite('sIceRight');
1305   }
1306   if (!up && !left && !right && down) setSprite('sIceULR');
1307   if (!down && !left && !right && up) setSprite('sIceDLR');
1308   if (up && down && !left && !right) setSprite('sIceLR');
1309   if (!up && !down && !left && !right) setSprite('sIceBlock');
1314 override void beautifyTile () {
1315   /+
1316   bool up = !!isIceTileAtPoint(ix, iy-16);
1317   bool down = !!isIceTileAtPoint(ix, iy+16);
1318   bool left = !!isIceTileAtPoint(ix-16, iy);
1319   bool right = !!isIceTileAtPoint(ix+16, iy);
1321   //writeln("beautifying ice; l=", left, "; r=", right, "; u=", up, "; d=", down);
1323   if (!up) setSprite('sIceUp');
1324   if (!down) {
1325     setSprite(!up ? 'sIceUp2' : 'sIceDown');
1326     if (!level.isSolidAtPoint(ix, iy+16) && global.randOther(1, 20) == 1) hasIceBottom = true;
1327   }
1328   if (!left) {
1329          if (!up && !down) setSprite('sIceUDL');
1330     else if (!up) setSprite('sIceUL');
1331     else if (!down) setSprite('sIceDL');
1332     else setSprite('sIceLeft');
1333   }
1334   if (!right) {
1335          if (!up && !down) setSprite('sIceUDR');
1336     else if (!up) setSprite('sIceUR');
1337     else if (!down) setSprite('sIceDR');
1338     else setSprite('sIceRight');
1339   }
1340   if (!up && !left && !right && down) setSprite('sIceULR');
1341   if (!down && !left && !right && up) setSprite('sIceDLR');
1342   if (up && down && !left && !right) setSprite('sIceLR');
1343   if (!up && !down && !left && !right) setSprite('sIceBlock');
1344   +/
1345   setupNiceTileSprite();
1349 override void scrSetupBlockTop () {
1350   setupNiceTileSprite(/*noup:true*/);
1351   /+
1352   bool up = false;
1353   bool down = !!isIceTileAtPoint(ix, iy+16);
1354   bool left = !!isIceTileAtPoint(ix-16, iy);
1355   bool right = !!isIceTileAtPoint(ix+16, iy);
1357   setSprite('sIceUp');
1359   if (!down) setSprite('sIceUp2');
1361   if (!left) setSprite(!up ? 'sIceUDL' : 'sIceUL');
1362   if (!right && !down) setSprite('sIceUDR');
1364   if (!left && !right && down) setSprite('sIceULR');
1365   if (!down && !left && !right) setSprite('sIceBlock');
1366   +/
1370 override void scrSetupBlockBottom () {
1371   setupNiceTileSprite(/*nodown:true*/);
1372   /+
1373   bool up = !!isIceTileAtPoint(ix, iy-16);
1374   bool left = !!isIceTileAtPoint(ix-16, iy);
1375   bool right = !!isIceTileAtPoint(ix+16, iy);
1377   setSprite(!up ? 'sIceUp2' : 'sIceDown');
1379   if (global.randOther(1, 20) == 1) hasIceBottom = true; //instance_create(x, y+16, oIceBottom);
1381   if (!left) setSprite(!up ? 'sIceUDL' : 'sIceDL');
1382   if (!right) setSprite(!up ? 'sIceUDR' : 'sIceDR');
1384   if (!left && !right && up) setSprite('sIceDLR');
1385   if (!up && !left && !right) setSprite('sIceBlock');
1386   +/
1390 override void scrSetupBlockLeft () {
1391   setupNiceTileSprite(/*noright:true*/);
1392   /+
1393   bool up = !!isIceTileAtPoint(ix, iy-16);
1394   bool down = !!isIceTileAtPoint(ix, iy+16);
1395   bool left = !!isIceTileAtPoint(ix-16, iy);
1396   bool right = false;
1398   if (!up) setSprite('sIceUp');
1399   if (!down) { if (!up) setSprite('sIceUp2'); else setSprite('sIceDown'); }
1400   if (!left) {
1401          if (!up && !down) setSprite('sIceUDL');
1402     else if (!up) setSprite('sIceUL');
1403     else if (!down) setSprite('sIceDL');
1404     else setSprite('sIceLeft');
1405   }
1406   if (!right) {
1407          if (!up && !down) setSprite('sIceUDR');
1408     else if (!up) setSprite('sIceUR');
1409     else if (!down) setSprite('sIceDR');
1410     else setSprite('sIceRight');
1411   }
1412   if (!up && !left && !right && down) setSprite('sIceULR');
1413   if (!down && !left && !right && up) setSprite('sIceDLR');
1414   if (up && down && !left && !right) setSprite('sIceLR');
1415   if (!up && !down && !left && !right) setSprite('sIceBlock');
1416   +/
1420 override void scrSetupBlockRight () {
1421   setupNiceTileSprite(/*noleft:true*/);
1422   /+
1423   bool up = !!isIceTileAtPoint(ix, iy-16);
1424   bool down = !!isIceTileAtPoint(ix, iy+16);
1425   bool left = false;
1426   bool right = !!isIceTileAtPoint(ix+16, iy);
1428   if (!up) setSprite('sIceUp');
1429   if (!down) { if (!up) setSprite('sIceUp2'); else setSprite('sIceDown'); }
1430   if (!left) {
1431          if (!up && !down) setSprite('sIceUDL');
1432     else if (!up) setSprite('sIceUL');
1433     else if (!down) setSprite('sIceDL');
1434     else setSprite('sIceLeft');
1435   }
1436   if (!right) {
1437          if (!up && !down) setSprite('sIceUDR');
1438     else if (!up) setSprite('sIceUR');
1439     else if (!down) setSprite('sIceDR');
1440     else setSprite('sIceRight');
1441   }
1442   if (!up && !left && !right && down) setSprite('sIceULR');
1443   if (!down && !left && !right && up) setSprite('sIceDLR');
1444   if (up && down && !left && !right) setSprite('sIceLR');
1445   if (!up && !down && !left && !right) setSprite('sIceBlock');
1446   +/
1450 defaultproperties {
1451   objType = 'oIce';
1452   spriteName = 'sIce';
1453   invincible = false;
1454   solid = true;
1455   ice = true;
1456   toSpecialGrid = true; // want to think
1460 // ////////////////////////////////////////////////////////////////////////// //
1461 class MapTileTemple['oTemple'] : MapTile;
1463 bool hasIceBottom;
1464 int dripTimer;
1466 final bool isTempleTile (MapTile t) { return (t.objType == 'oTemple' || t.border); } // fake too
1467 final MapTile isTempleTileAtPoint (int x, int y) { return level.checkTileAtPoint(x, y, &isTempleTile); }
1470 override void setupTile () {
1471   setSprite(global.cityOfGold ? 'sGTemple' : 'sTemple');
1472   int tileX = ix/16, tileY = iy/16;
1473   if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1474     invincible = true;
1475     border = true;
1476     //writeln("BORDER");
1477   } else {
1478     doCreateOre(60, 80, 100, 1200);
1479   }
1484 override void thinkFrame () {
1485   if (hasIceBottom) {
1486     if (--dripTimer <= 0) {
1487       //writeln("DRIP!");
1488       level.MakeMapObject(ix+8, iy+16+4, 'oDrip');
1489       dripTimer = global.randOther(20, 400);
1490       //dripTimer = 40;
1491     }
1492   }
1497 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1498   bool up = (iy <= 0 ? true : (specified_noup && noup ? false : !!isTempleTileAtPoint(ix, iy-16)));
1499   bool down = (iy >= level.tilesHeight*16-16 ? true : (specified_nodown && nodown ? false : !!isTempleTileAtPoint(ix, iy+16)));
1500   bool left = (ix <= 16 ? true : (specified_noleft && noleft ? false : !!isTempleTileAtPoint(ix-16, iy)));
1501   bool right = (ix >= level.tilesWidth*16-16*2 ? true : (specified_noright && noright ? false : !!isTempleTileAtPoint(ix+16, iy)));
1503   /*
1504   if (argument1) {
1505     if (global.cityOfGold) sprite_index = sGTemple; else sprite_index = sTemple;
1506   }
1507   */
1509   /*!
1510   if (y == 0 or collision_point(x, y-16, oTemple, 0, 0) or collision_point(x, y+16, oTempleFake, 0, 0)) { up = true; }
1511   if (y >= argument0 or collision_point(x, y+16, oTemple, 0, 0) or collision_point(x, y+16, oTempleFake, 0, 0)) { down = true; }
1512   if (collision_point(x-16, y, oTemple, 0, 0) or collision_point(x-16, y, oTempleFake, 0, 0)) { left = true; }
1513   if (collision_point(x+16, y, oTemple, 0, 0) or collision_point(x+16, y, oTempleFake, 0, 0)) { right = true; }
1514   */
1516   if (global.cityOfGold) {
1517     if (!up) {
1518       setSprite('sGTempleUp');
1519            if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 0)); //3
1520       else if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 16)); //3
1521       if (!left && !right) {
1522         if (!down) setSprite('sGTempleUp6'); else setSprite('sGTempleUp5');
1523       } else if (!left) {
1524         if (!down) setSprite('sGTempleUp7'); else setSprite('sGTempleUp3');
1525       } else if (!right) {
1526         if (!down) setSprite('sGTempleUp8'); else setSprite('sGTempleUp4');
1527       } else if (left && right && !down) {
1528         setSprite('sGTempleUp2');
1529       }
1530     } else if (!down) {
1531       setSprite('sGTempleDown');
1532     }
1533   } else {
1534     if (!up) {
1535       setSprite('sTempleUp');
1536       //if (rand(1, 4) == 1) tile_add(bgCaveTop4, 0, 0, 16, 16, x, y-16, 3); else if (rand(1, 4) == 1) tile_add(bgCaveTop4, 16, 0, 16, 16, x, y-16, 3);
1537            if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 0)); //3
1538       else if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 16)); //3
1539       if (!left && !right) {
1540         if (!down) setSprite('sTempleUp6'); else setSprite('sTempleUp5');
1541       } else if (!left) {
1542         if (!down) setSprite('sTempleUp7'); else setSprite('sTempleUp3');
1543       } else if (!right) {
1544         if (!down) setSprite('sTempleUp8'); else setSprite('sTempleUp4');
1545       } else if (left && right && !down) {
1546         setSprite('sTempleUp2');
1547       }
1548     } else if (!down) {
1549       setSprite('sTempleDown');
1550     }
1551   }
1555 override void beautifyTile () {
1556   setupNiceTileSprite();
1560 override void scrSetupBlockTop () {
1561   setupNiceTileSprite(/*noup:true*/);
1565 override void scrSetupBlockBottom () {
1566   setupNiceTileSprite(/*nodown:true*/);
1570 override void scrSetupBlockLeft () {
1571   setupNiceTileSprite(/*noright:true*/);
1575 override void scrSetupBlockRight () {
1576   setupNiceTileSprite(/*noleft:true*/);
1580 defaultproperties {
1581   objType = 'oTemple';
1582   spriteName = 'sTemple';
1583   invincible = false;
1584   solid = true;
1585   //toSpecialGrid = true; // want to think
1589 // ////////////////////////////////////////////////////////////////////////// //
1590 class MapTileVine['oVine'] : MapTile;
1593 override void setupTile () {
1597 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1598   //if (argument1) sprite_index = sVine;
1600   bool up = !!level.isLadderAtPoint(ix+8, iy-8);
1601   bool down = !!level.isLadderAtPoint(ix+8, iy+16);
1603   if (!up) {
1604     //tile_add(bgVineRoots, 0, 0, 16, 16, x, y-16, 1000); // use same depth as the vine objects themselves
1605     appendBackFront(level.CreateBgTile('bgVineRoots', 0)); //1000
1606     setSprite('sVineSource');
1607   } else if (!down) {
1608     setSprite('sVineBottom');
1609   }
1613 override void beautifyTile () {
1614   setupNiceTileSprite();
1618 defaultproperties {
1619   objType = 'oVine';
1620   ladder = true;
1621   solid = false;
1622   spriteName = 'sVine';
1623   //toSpecialGrid = true; // want to think
1627 // ////////////////////////////////////////////////////////////////////////// //
1628 class MapTileLava['oLava'] : MapTile;
1630 bool spurt;
1631 int spurtTime;
1632 int spurtCounter;
1633 bool spurtSet;
1636 override void setupTile () {
1637   spurtTime = global.randOther(100, 300);
1638   spurtCounter = spurtTime;
1642 override void doSprayRubble () {
1643   foreach (; 0..3) level.MakeMapObject(ix+global.randOther(0, 16), iy+global.randOther(0, 16), 'oLavaDrip');
1644   if (global.randOther(1, 6) == 1) {
1645     auto flame = level.MakeMapObject(ix+8, iy+8, 'oFlame');
1646     if (flame) flame.yVel = 4;
1647   }
1651 override bool onExplosionTouch (MapObject xplo) {
1652   return false;
1656 override void thinkFrame () {
1657   if (!spurtSet && spriteName == 'sLavaTop') {
1658     spurtSet = true;
1659     spurt = (global.randOther(1, 4) == 1);
1660   }
1661   auto dist = pointDistance(ix, iy, level.player.ix, level.player.iy);
1662   if (spurt && dist < 240) {
1663     if (spurtCounter > 0) {
1664       --spurtCounter;
1665     } else {
1666       spurtCounter = spurtTime;
1667       auto flame = level.MakeMapObject(ix+8, iy-4, (global.randOther(1, 8) == 1 ? 'oMagma' : 'oFlame'));
1668       //auto flame = level.MakeMapObject(ix+8, iy-4, 'oMagma');
1669       if (flame) flame.yVel = -global.randOther(1, 4);
1670     }
1671   }
1675 defaultproperties {
1676   objType = 'oLava';
1677   spriteName = 'sLava';
1678   invincible = false;
1679   lava = true;
1680   solid = false;
1681   toSpecialGrid = false; // lava tiles are special: they are animated/thinked without a grid
1682   lightRadius = 32+16;
1683   litWholeTile = true;
1684   imageSpeed = 0.5;
1688 // ////////////////////////////////////////////////////////////////////////// //
1689 class MapTileWater['oWater'] : MapTile;
1692 override void setupTile () {
1696 override void doSprayRubble () {
1697   foreach (; 0..3) level.MakeMapObject(ix+global.randOther(0, 16), iy+global.randOther(0, 16), 'oDrip');
1701 override bool onExplosionTouch (MapObject xplo) {
1702   return false;
1706 final bool isWaterTile (MapTile t) { return t.water; }
1707 //final bool isSolidOrWaterTile (MapTile t) { return (t.water || t.solid); }
1710 void setupNiceTileSprite () {
1711   //if (argument1) sprite_index = sWater;
1712   bool up = false;
1713   bool upWater = false;
1714   bool down = false;
1715   bool left = false;
1716   bool right = false;
1718   if (level.checkTileAtPoint(ix, iy-16, &isWaterTile)) upWater = true;
1719   if (level.isSolidAtPoint(ix, iy-16)) up = true;
1720   if (level.isSolidAtPoint(ix, iy+16) && !level.checkTileAtPoint(ix, iy+16, &isWaterTile)) down = true;
1722   if (!up && !upWater) setSprite('sWaterTop');
1724   if (upWater && level.checkTileAtPoint(ix, iy-32, &isWaterTile) && down && global.randOther(1, 4) == 1) {
1725     setSprite('sWaterBottomTall2');
1726     auto water = level.checkTileAtPoint(ix, iy-16, &isWaterTile);
1727     if (water) water.setSprite('sWaterBottomTall1');
1728   } else if ((up || upWater) && down) {
1729     switch (global.randOther(1, 4)) {
1730       case 1: setSprite('sWaterBottom'); break;
1731       case 2: setSprite('sWaterBottom2'); break;
1732       case 3: setSprite('sWaterBottom3'); break;
1733       case 4: setSprite('sWaterBottom4'); break;
1734     }
1735   }
1739 override void beautifyTile () {
1740   setupNiceTileSprite();
1744 defaultproperties {
1745   objType = 'oWater';
1746   spriteName = 'sWater';
1747   invincible = false;
1748   water = true;
1749   solid = false;
1750   toSpecialGrid = false;
1754 // ////////////////////////////////////////////////////////////////////////// //
1755 class MapTileWaterSwim['oWaterSwim'] : MapTileWater;
1758 // ////////////////////////////////////////////////////////////////////////// //
1759 class MapTileLavaSolid['oLavaSolid'] : MapTile;
1761 override void setupTile () {
1764 override bool onExplosionTouch (MapObject xplo) {
1765   return false;
1768 defaultproperties {
1769   objType = 'oLavaSolid';
1770   spriteName = 'sLava';
1771   invincible = false;
1772   lava = true;
1773   solid = true;
1774   toSpecialGrid = false;
1778 // ////////////////////////////////////////////////////////////////////////// //
1779 class MapTileLavaLushTrapBlock['oTrapBlock'] : MapTile;
1781 int deathTimer;
1782 bool dying;
1786 override void onDestroy () {
1787   if (!cleanDeath) {
1788     /*if (not cleanDeath and not global.cleanSolids)*/ {
1789       if (smashed) scrSprayRubble(ix, iy, 3, 'sRubbleTan', 'sRubbleTanSmall');
1790       scrDropRubble(ix, iy, 3, 'sRubbleTan', 'sRubbleTanSmall');
1791       if (dying) {
1792         playSound('sndThump');
1793         level.scrShake(10);
1794       }
1795     }
1796   }
1797   ::onDestroy();
1801 override void doSprayRubble () {
1802   if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
1803   scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
1804   if (dying) {
1805     playSound('sndThump');
1806     level.scrShake(10);
1807   }
1811 override void setupTile () {
1815 void activate () {
1816   if (dying) return;
1817   if (distanceToEntityCenter(level.player) < 90) {
1818     dying = true;
1819   }
1823 override void thinkFrame () {
1824   if (dying) {
1825     if (deathTimer > 0) --deathTimer; else instanceRemove();
1826   }
1830 defaultproperties {
1831   objType = 'oLavaSolid';
1832   spriteName = 'sSkullBlock';
1833   invincible = false;
1834   solid = true;
1835   toSpecialGrid = true;
1836   //depth = ???;
1840 // ////////////////////////////////////////////////////////////////////////// //
1841 class MapTileLeaves['oLeaves'] : MapTile;
1843 bool dead;
1844 bool spriteSet;
1847 final bool isTreeOrLeaves (MapTile t) { return (t != self && t.tree || t.leaves); }
1851 override void onDestroy () {
1852   if (!cleanDeath) {
1853     if (spriteName != 'sLeavesDead' && spriteName != 'sLeavesDeadR') {
1854       level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1855       level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1856     }
1857   }
1858   ::onDestroy();
1862 override void doSprayRubble () {
1863   if (spriteName != 'sLeavesDead' && spriteName != 'sLeavesDeadR') {
1864     level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1865     level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1866   }
1870 override void setupTile () {
1871   spriteName = (global.cemetary ? 'sLeavesDead' : 'sLeaves');
1875 override void thinkFrame () {
1876   int x = ix, y = iy;
1878   if (!spriteSet) {
1879     spectral = true;
1880     if (level.checkTileAtPoint(x-16, y, &isTreeOrLeaves)) {
1881       setSprite(global.cemetary ? 'sLeavesDeadR' : 'sLeavesRight');
1882     } else if (level.checkTileAtPoint(x+16, y, &isTreeOrLeaves)) {
1883       setSprite(global.cemetary ? 'sLeavesDead' : 'sLeaves');
1884     }
1885     if (level.checkTileAtPoint(x-16, y, &isTreeOrLeaves) &&
1886         level.checkTileAtPoint(x+16, y, &isTreeOrLeaves))
1887     {
1888       setSprite('sLeavesTop');
1889     }
1890     if (dead) {
1891       if (level.checkTileAtPoint(x-16, y, &isTreeOrLeaves)) setSprite('sLeavesDeadR'); else setSprite('sLeavesDead');
1892     }
1893     spectral = false;
1894     spriteSet = true;
1895   }
1897   spectral = true;
1898   if (spriteName == 'sLeavesTop') {
1899     if (!level.checkTileAtPoint(x-16, y, &isTreeOrLeaves) ||
1900         !level.checkTileAtPoint(x+16, y, &isTreeOrLeaves))
1901     {
1902       instanceRemove();
1903     }
1904   } else if (spriteName == 'sLeaves' || spriteName == 'sLeavesDead') {
1905     if (!level.checkTileAtPoint(x+16, y, &isTreeOrLeaves)) instanceRemove();
1906   } else if (spriteName == 'sLeavesRight' || spriteName == 'sLeavesDeadR') {
1907     if (!level.checkTileAtPoint(x-16, y, &isTreeOrLeaves)) instanceRemove();
1908   }
1909   spectral = false;
1911   /*
1912   if (sprite_index == sLeavesDeadR) {
1913     desc = "Canopy";
1914     desc2 = "These leaves have died and withered.";
1915   } else {
1916     desc = "Canopy";
1917     desc2 = "The canopy of a proud tree.";
1918   }
1919   */
1923 defaultproperties {
1924   objType = 'oLeaves';
1925   desc = "Canopy";
1926   desc2 = "The top of a proud tree.";
1927   platform = true;
1928   solid = false;
1929   invincible = false;
1930   leaves = true;
1931   dead = false;
1932   spriteSet = false;
1933   toSpecialGrid = true;
1934   depth = 1;
1938 // ////////////////////////////////////////////////////////////////////////// //
1939 class MapTileTree['oTree'] : MapTile;
1941 bool burning;
1945 override void onDestroy () {
1946   if (!cleanDeath) {
1947     if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
1948     scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
1949   }
1950   ::onDestroy();
1954 override void doSprayRubble () {
1955   if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
1956   scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
1960 override void setupTile () {
1964 final bool isTreeTileAtPoint (int x, int y) {
1965   return !!level.checkTileAtPoint(x, y, delegate bool (MapTile t) { return (t.objType == 'oTree'); });
1969 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1970   //if (argument1) sprite_index = sVine;
1972   bool up = isTreeTileAtPoint(ix, iy-16);
1973   //bool down = isTreeTileAtPoint(ix, iy+16);
1974   //bool left = isTreeTileAtPoint(ix-16, iy);
1975   //bool right = isTreeTileAtPoint(ix+16, yi);
1977   if (!up) {
1978     if (global.cemetary || getSprite().Name == 'sTreeTopDead') setSprite('sTreeTopDead'); else setSprite('sTreeTop');
1979     depth = 1;
1980   }
1984 override void beautifyTile () {
1985   setupNiceTileSprite();
1989 override void thinkFrame () {
1990   int x = ix, y = iy;
1992   if (!level.isSolidAtPoint(x, y+16)) { instanceRemove(); return; }
1993   if (level.isLavaAtPoint(x-16, y) || level.isLavaAtPoint(x+16, y)) { instanceRemove(); return; }
1997 defaultproperties {
1998   objType = 'oTree';
1999   desc = "Tree Trunk";
2000   desc2 = "The trunk of a proud tree.";
2001   spriteName = 'sTreeTrunk';
2002   solid = true;
2003   invincible = false;
2004   leaves = false;
2005   tree = true;
2006   burning = false;
2007   toSpecialGrid = true;
2008   depth = 2;
2012 // ////////////////////////////////////////////////////////////////////////// //
2013 class MapTileTreeBranch['oTreeBranch'] : MapTile;
2015 bool burning;
2016 bool dead;
2019 final bool isTree (MapTile t) { return (t != self && t.tree); }
2023 override void onDestroy () {
2024   if (!cleanDeath) {
2025     if (spriteName != 'sTreeBranchDeadL' && spriteName != 'sTreeBranchDeadR') {
2026       level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
2027     }
2028   }
2029   ::onDestroy();
2033 override void doSprayRubble () {
2034   if (spriteName != 'sTreeBranchDeadL' && spriteName != 'sTreeBranchDeadR') {
2035     level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
2036   }
2040 override void setupTile () {
2041   dead = false;
2043   auto ltree = level.checkTileAtPoint(ix-16, iy, &isTree);
2044   auto rtree = level.checkTileAtPoint(ix+16, iy, &isTree);
2046        if (ltree && !rtree) setSprite(global.cemetary ? 'sTreeBranchDeadR' : 'sTreeBranchRight');
2047   else if (!ltree && rtree) setSprite(global.cemetary ? 'sTreeBranchDeadL' : 'sTreeBranchLeft');
2049   //if (global.cemetary) spriteName = 'sTreeBranchDeadR';
2053 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
2054   //if (argument1) sprite_index = sVine;
2056   bool up = !!level.checkTileAtPoint(ix, iy-16, delegate bool (MapTile t) { return (t.objType == 'oLeaves'); });
2057   //if (collision_point(x, y+16, oTreeBranch, 0, 0)) { down = true; }
2058   //if (collision_point(x-16, y, oTreeBranch, 0, 0)) { left = true; }
2059   bool right = !!level.checkTileAtPoint(ix+16, iy, delegate bool (MapTile t) { return (t.objType == 'oTree'); });
2061   if (up) {
2062     instanceRemove();
2063     return;
2064   }
2066   if (right) {
2067     if (global.cemetary || getSprite().Name == 'sTreeBranchDeadR') setSprite('sTreeBranchDeadL'); else setSprite('sTreeBranchLeft');
2068   }
2072 override void beautifyTile () {
2073   //writeln("!!! ", getSprite().Name);
2074   setupNiceTileSprite();
2078 override void thinkFrame () {
2079   int x = ix, y = iy;
2081   spectral = true;
2082   auto ltree = level.checkTileAtPoint(ix-16, iy, &isTree);
2083   auto rtree = level.checkTileAtPoint(ix+16, iy, &isTree);
2084   if (!ltree && !rtree) {
2085     instanceRemove();
2087   } else {
2088          if (ltree && !rtree) setSprite('sTreeBranchRight');
2089     else if (!ltree && rtree) setSprite('sTreeBranchLeft');
2091   }
2092   spectral = false;
2096 defaultproperties {
2097   objType = 'oTree';
2098   desc = "Tree Branch";
2099   desc2 = "A slight but firm limb of a proud tree.";
2100   spriteName = 'sTreeBranchRight';
2101   platform = true;
2102   solid = false;
2103   invincible = false;
2104   leaves = false;
2105   tree = true;
2106   burning = false;
2107   toSpecialGrid = true;
2108   depth = 2;
2112 // ////////////////////////////////////////////////////////////////////////// //
2113 // only for dark levels
2114 class MapTileTikiTorch['oTikiTorch'] : MapTile;
2117 override void setupTile () {
2118   writeln("*** TIKI TORCH CREATED");
2122 override void thinkFrame () {
2123   lightRadius = 32+16+global.randOther(-8, 8);
2127 defaultproperties {
2128   objType = 'oTikiTorch';
2129   desc = "Torch";
2130   desc2 = "A moderately bright torch.";
2131   spriteName = 'sTikiTorch';
2132   platform = false;
2133   solid = false;
2134   invincible = false;
2135   leaves = false;
2136   tree = false;
2137   toSpecialGrid = true;
2138   depth = 1000;
2139   imageSpeed = 0.5;
2140   lightRadius = 32+16;
2144 // ////////////////////////////////////////////////////////////////////////// //
2145 class MapTileGiantTikiHead['oGiantTikiHead'] : MapTile;
2147 bool broken;
2150 void breakIt () {
2151   if (!broken) {
2152     broken = true;
2153     setSprite('sGTHHole');
2154   }
2158 override void setupTile () {
2162 override void thinkFrame () {
2166 defaultproperties {
2167   objType = 'oGiantTikiHead';
2168   invincible = true; //???
2169   solid = false;
2170   spriteName = 'sGiantTikiHead';
2171   toSpecialGrid = true; // it is HUGE
2172   depth = 1000;
2176 // ////////////////////////////////////////////////////////////////////////// //
2177 class MapTileThinIce['oThinIce'] : MapTile;
2179 int thickness = 60;
2181 override void setupTile () {
2185 override void thinkFrame () {
2186   if (level.player.isRectHitSimple(ix, iy-1, 17, 2)) {
2187     thickness -= 2;
2188     if (global.randOther(1, 100) == 1) level.MakeMapObject(ix+global.randOther(0, 16), iy+9, 'oDrip');
2189   }
2190        if (thickness > 50) setSprite('sThinIce1');
2191   else if (thickness > 40) setSprite('sThinIce2');
2192   else if (thickness > 30) setSprite('sThinIce3');
2193   else if (thickness > 20) setSprite('sThinIce4');
2194   else if (thickness > 10) setSprite('sThinIce5');
2195   else if (thickness > 0) setSprite('sThinIce6');
2196   else instanceRemove();
2200 defaultproperties {
2201   objType = 'oThinIce';
2202   solid = true;
2203   spriteName = 'sThinIce1';
2204   toSpecialGrid = true; // it must think
2208 // ////////////////////////////////////////////////////////////////////////// //
2209 class MapTileDarkFall['oDarkFall'] : MapTile;
2211 //int thickness = 60;
2212 int viscidTop = 1;
2213 int timeFall = 20;
2214 int timeFallMax = 20;
2215 //float grav = 1;
2217 override int height () { return 8; }
2219 override void setupTile () {
2223 //FIXME: viscidMovement -- ???
2224 override void thinkFrame () {
2225   //isCollisionCharacterTop(1)
2226   if (level.player.isRectHitSimple(ix, iy-1, 17, 2)) {
2227     --timeFall;
2228     //if (timeFall <= 0) yAcc = grav;
2229   } else if (timeFall < timeFallMax) {
2230     ++timeFall;
2231   }
2232   myGrav = (timeFall <= 0 ? grav : 0.0);
2233   //if (yVel > 10) yVel = 10;
2234   //HACK: so player won't be able to push it
2235   moveable = true;
2236   ::thinkFrame();
2237   moveable = false;
2241 defaultproperties {
2242   objType = 'oDarkFall';
2243   solid = true;
2245   viscidTop = 1;
2246   //setCollisionBounds(0, 0, 16, 8);
2248   invincible = false;
2249   grav = 1;
2250   myGravLimit = 10;
2251   //timeFall = 20;
2252   //timeFallMax = 20;
2253   desc = "Falling Platform";
2254   desc2 = "A thin, dark blue platform that will colapse if stood on for any length of time.";
2256   spriteName = 'sDarkFall';
2257   toSpecialGrid = true; // it must think
2258   depth = 1000;
2262 // ////////////////////////////////////////////////////////////////////////// //
2263 class MapTileAlienHull['oAlienShip'] : MapTile;
2266 override void setupTile () {
2267   //writeln("*********** ALIEN SHIP!");
2271 override void doSprayRubble () {
2272   if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2273   scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2277 final bool isAlienShipFloorCB (MapTile t) { return (t.objType == 'oAlienShip' || t.objType == 'oAlienShipFloor'); }
2278 final bool isASFTileAt (int x, int y) { return !!level.checkTileAtPoint(x, y, &isAlienShipFloorCB); }
2281 override void beautifyTile () {
2282   //if (argument1) sprite_index = sAlienTop;
2284   bool up = !!isASFTileAt(ix, iy-16);
2285   bool down = !!isASFTileAt(ix, iy+16);
2286   bool left = !!isASFTileAt(ix-16, iy);
2287   bool right = !!isASFTileAt(ix+16, iy);
2289   if (right && !left) {
2290          if (up && !down) setSprite('sAlienFront2');
2291     else if (down && !up) setSprite('sAlienFront3');
2292   }
2293   if (left && !right) {
2294     if (up) setSprite('sAlienBack2'); else if (down) setSprite('sAlienBack3');
2295   }
2299 defaultproperties {
2300   objType = 'oAlienShip';
2301   solid = true;
2302   spriteName = 'sAlienTop';
2303   toSpecialGrid = false;
2307 // ////////////////////////////////////////////////////////////////////////// //
2308 class MapTileAlienFloor['oAlienShipFloor'] : MapTileAlienHull;
2311 defaultproperties {
2312   objType = 'oAlienShipFloor';
2313   spriteName = 'sAlienFloor';
2317 // ////////////////////////////////////////////////////////////////////////// //
2318 class MapTileXocBlock['oXocBlock'] : MapTile;
2321 override void setupTile () {
2325 override void doSprayRubble () {
2326   foreach (; 0..3) {
2327     auto gold = level.MakeMapObject(ix+8+global.randOther(0, 4)-global.randOther(0, 4), iy+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldChunk');
2328     if (gold) {
2329       gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2330       gold.yVel = global.randOther(2, 4);
2331     }
2332   }
2333   {
2334     auto gold = level.MakeMapObject(ix+8+global.randOther(0, 4)-global.randOther(0, 4), iy+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldNugget');
2335     if (gold) {
2336       gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2337       gold.yVel = global.randOther(2, 4);
2338     }
2339   }
2343 defaultproperties {
2344   objType = 'oXocBlock';
2345   solid = true;
2346   spriteName = 'sGoldBlock';
2347   toSpecialGrid = false;
2348   depth = 1000;
2352 // ////////////////////////////////////////////////////////////////////////// //
2353 class MapTileCeilingTrap['oCeilingTrap'] : MapTile;
2355 //int thickness = 60;
2356 int viscidTop = 1;
2357 //int timeFall = 20;
2358 //int timeFallMax = 20;
2359 //float grav = 1;
2360 int counter;
2361 bool sprung;
2364 enum {
2365   IDLE = 0,
2366   DROP = 1,
2367   WAIT = 2,
2368   RETURN = 3,
2371 int status;
2374 override void onAnimationLooped () {
2375   if (spriteName == 'sCeilingTrapS') setSprite('sCeilingTrap');
2379 override void doSprayRubble () {
2380   if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2381   scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2385 override void setupTile () {
2386   if (global.cityOfGold) spriteName = 'sGoldBlock';
2390 void activate () {
2391   if (status != IDLE) return;
2392   status = DROP;
2393   level.checkTilesInRect(ix-64, iy-64, 129, 129, delegate bool (MapTile t) {
2394     auto door = MapTileDoor(t);
2395     if (door) door.activate();
2396     return false;
2397   });
2401 //FIXME: viscidMovement -- ???
2402 override void thinkFrame () {
2403   if (status == IDLE) {
2404     // nothing
2405   } else if (status == DROP) {
2406     if (counter > 0) {
2407       --counter;
2408     } else {
2409       counter = 3;
2410       flty += 1;
2411     }
2412     yVel = 0;
2413     spectral = true;
2414     if (level.isSolidAtPoint(ix+8, iy+17)) status = WAIT;
2415     spectral = false;
2416     if (spriteName == 'sBlock' || spriteName == 'sGoldBlock') setSprite('sCeilingTrapS');
2417     // out-of-level check
2418     if (flty > level.tilesHeight*16+16) {
2419       cleanDeath = true;
2420       instanceRemove();
2421     }
2422   } else if (status == WAIT) {
2423     yVel = 0;
2424     spectral = true;
2425     if (isCollisionBottom(0)) flty -= 1;
2426     spectral = false;
2427   }
2430   if (status != IDLE) {
2431     // player
2432     auto plr = level.player;
2433     if (!plr.dead && !plr.invincible && !plr.isExitingSprite() && /*plr.collidesWith(self)*/ plr.isRectHitSimple(x0, y0, width, height+1)) {
2434       bool doCol = true;
2435       if (!plr.collidesWith(self)) {
2436         doCol = (plr.yVel < -0.01 && plr.y0 > y1);
2437       }
2438       if (doCol) {
2439         if (global.plife > 0) {
2440           global.plife -= global.config.crushDmg;
2441           if (global.plife <= 0 /*and isRealLevel()*/) level.addDeath(objName);
2442         }
2443         plr.scrCreateBlood(plr.ix, plr.iy, 1);
2444         plr.stunned = true;
2445         plr.stunTimer = 20;
2446         playSound('sndHurt');
2447       }
2448     }
2451     level.isObjectInRect(x0-1, y0-1, width+2, height+2, delegate bool (MapObject o) {
2452       if (o == self) return false;
2453       if (o isa EnemyTombLord) return false;
2455       if (o !isa MapEnemy && o !isa MonsterDamsel) return false;
2457       // register hit only if we're moving onto a spikes
2458       if (!o.collidesWith(self)) {
2459         bool onTop = (o.yVel < -0.01 && o.y0 > y1);
2460         if (!onTop) return false;
2461         writeln("*** RUNNING ON TRAP SPIKES");
2462       }
2464       // enemy
2465       auto enemy = MapEnemy(o);
2466       if (enemy) {
2467         if (o.dead || o.status == MapObject::DEAD || o.invincible) return false;
2468         if (o.heldBy) o.heldBy.holdItem = none;
2469         enemy.hp -= global.config.crushDmg;
2470         playSound('sndHit');
2471         return false;
2472       }
2474       // damsel
2475       auto dms = MonsterDamsel(o);
2476       if (dms) {
2477         if (!dms.invincible) {
2478           dms.spillBlood();
2479           if (dms.heldBy) dms.heldBy.holdItem = none;
2480           dms.hp -= global.config.crushDmg;
2481           dms.status = MapObject::THROWN;
2482           dms.counter = 120;
2483           //dms.damselDropped = true;
2484           dms.kissCount = min(1, dms.kissCount);
2485           playSound('sndDamsel');
2486           return false;
2487         }
2488       }
2490       return false;
2491     });
2492   }
2496 defaultproperties {
2497   objType = 'oCeilingTrap';
2498   solid = true;
2499   imageSpeed = 0.4;
2501   viscidTop = 1;
2502   //setCollisionBounds(0, 0, 16, 8);
2504   invincible = false;
2505   //moveable = true;
2506   grav = 1;
2507   myGravLimit = 10;
2508   counter = 3;
2510   status = IDLE;
2511   sprung = false;
2513   desc = "Ceiling Trap";
2514   desc2 = "This seemingly-innocuous block hides a vicious set of spikes.";
2516   spriteName = 'sBlock';
2517   toSpecialGrid = true; // it must think
2518   depth = 1000;
2522 // ////////////////////////////////////////////////////////////////////////// //
2523 class MapTileTempleFake['oTempleFake'] : MapTile;
2525 int sleepTime = 4; // wait until the grid is filled
2527 override void doSprayRubble () {
2528   if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2529   scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2533 override void setupTile () {
2534   doCreateOre(60, 80, 100, 1200);
2538 override void thinkFrame () {
2539   if (sleepTime > 0) { --sleepTime; return; }
2540   //writeln("CHK!");
2541   auto door = level.checkTilesInRect(ix, iy, 16, 16, delegate bool (MapTile t) {
2542     if (t == self) return false;
2543     //writeln("fake temple: t(", GetClassName(t.Class), "); objName=", t.objName, "; objType=", t.objType, "; door=", t isa MapTileDoor);
2544     return (t isa MapTileDoor);
2545   });
2546   if (!door) {
2547     //writeln("!!!");
2548     auto bt = level.MakeMapTile(ix/16, iy/16, 'oTemple');
2549     if (bt) bt.copyOreFrom(self);
2550     instanceRemove();
2551     return;
2552   }
2553   /*
2554   if (!level.checkTileAtPoint(ix+8, iy+8, delegate bool (MapTile t) { return (t isa MapTileDoor); })) {
2555     writeln("!!!");
2556     level.MakeMapTile(ix/16, iy/16, 'oTemple');
2557     instanceRemove();
2558     return;
2559   }
2560   */
2564 defaultproperties {
2565   objType = 'oTemple';
2566   desc = "Temple Brick";
2567   desc2 = "This is the first brick you've seen that actually qualifies as a brick. It looks rather bland.";
2569   solid = true;
2570   invincible = false;
2571   moveable = false;
2572   spriteName = 'sTemple';
2573   toSpecialGrid = true; // it must think
2575   //invisible = true;
2577   depth = 60;
2581 // ////////////////////////////////////////////////////////////////////////// //
2582 class MapTileDoor['oDoor'] : MapTile;
2584 //int thickness = 60;
2585 int viscidTop = 1;
2586 int counter;
2587 bool sprung;
2590 enum {
2591   IDLE = 0,
2592   DROP = 1,
2593   WAIT = 2,
2594   RETURN = 3,
2596 int status;
2599 //setCollisionBounds(1, 0, 15, 32);
2600 override int x0 () { return round(fltx)+1; }
2601 override int width () { return 15; }
2602 override int height () { return 32; }
2605 override void doSprayRubble () {
2606   if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2607   scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2611 override void setupTile () {
2615 void activate () {
2616   status = DROP;
2617   myGrav = grav;
2618   yVel = myGrav;
2619   shiftY(2);
2623 //FIXME: viscidMovement -- ???
2624 override void thinkFrame () {
2625   if (status == IDLE) {
2626     // nothing
2627   } else if (status == DROP) {
2628     yVel = fmin(yVel+myGrav, myGravLimit);
2629     //::thinkFrame();
2630     spectral = true;
2631     auto oldsolid = solid;
2632     solid = false;
2633     //writeln("DOOR GOING DOWN; yVel=", yVel, "; ix=", ix, "; iy=", iy, "; x0=", x0, "; y0=", y0, "; w=", width, "; h=", height);
2634     int newY = round(flty+yVel); //!!!
2635     // check if we need (and can) move down
2636     int w = width, hp = height;
2637     while (newY > iy) {
2638       // made non-solid temporarily
2639       //auto hasAnything = level.checkTilesInRect(x0, y0+hp, w, 1, &level.cbCollisionAnySolid);
2640       auto hasAnything = level.checkTilesInRect(x0, y0+hp, w, 1, delegate bool (MapTile t) {
2641         if (t == self) return false;
2642         return t.solid;
2643       });
2644       if (hasAnything) {
2645         //writeln("hit '", GetClassName(hasAnything.Class), "' (", hasAnything.objName, ":", hasAnything.objType, "): pos=(", hasAnything.ix, ",", hasAnything.iy, ")");
2646         // hit something, stop right there
2647         if (yVel > myGrav*3) playSound('sndThud');
2648         flty = iy;
2649         status = WAIT;
2650         yVel = 0;
2651         counter = 100;
2652         depth = 180;
2653         //writeln("DOOR STOP");
2654         break;
2655       }
2656       // move us
2657       shiftY(1);
2658     }
2659     spectral = false;
2660     solid = oldsolid;
2662     // out-of-level check
2663     if (flty > level.tilesHeight*16+16) {
2664       cleanDeath = true;
2665       instanceRemove();
2666     }
2667   } else if (status == WAIT) {
2668     yVel = 0;
2669     spectral = true;
2670     if (isCollisionBottom(0)) flty -= 1;
2671     spectral = false;
2672   }
2676 defaultproperties {
2677   objType = 'oDoor';
2678   solid = true;
2680   viscidTop = 1;
2681   //setCollisionBounds(0, 0, 16, 8);
2683   invincible = false;
2684   //moveable = true;
2685   grav = 0.6;
2686   myGravLimit = 6;
2687   counter = 0;
2689   status = IDLE;
2690   sprung = false;
2692   desc = "Wall Trap";
2693   desc2 = "The inside of this block carries an extendable wall.";
2695   spriteName = 'sDoor';
2696   toSpecialGrid = true; // it must think
2697   depth = 2000;
2701 // ////////////////////////////////////////////////////////////////////////// //
2702 class MapTileSmashTrap['oSmashTrap'] : MapTile;
2704 //int thickness = 60;
2705 int viscidTop = 1;
2707 int counter;
2708 bool hit = false;
2710 float xv, yv;
2711 float xa, ya;
2714 enum {
2715   IDLE = 0,
2716   ATTACK = 1,
2717   DEAD = 99,
2720 int status;
2722 enum {
2723   RIGHT = 0,
2724   DOWN = 1,
2725   LEFT = 2,
2726   UP = 3,
2729 int dir;
2732 final string dirName () {
2733   switch (dir) {
2734     case RIGHT: return "right";
2735     case LEFT: return "left";
2736     case UP: return "up";
2737     case DOWN: return "down";
2738   }
2739   return "fucked";
2743 //setCollisionBounds(1, 1, 15, 15);
2744 override int x0 () { return round(fltx)+1; }
2745 override int width () { return 15; }
2746 //override int height () { return 16; }
2749 override void doSprayRubble () {
2750   if (!global.cityOfGold) {
2751     if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2752     scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2753   } else {
2754     foreach (; 0..3) {
2755       auto gold = level.MakeMapObject(ix+8+global.randOther(0, 4)-global.randOther(0, 4), iy+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldChunk');
2756       gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2757       gold.yVel = global.randOther(2, 4);
2758     }
2759     {
2760       auto gold = level.MakeMapObject(ix+8+global.randOther(0, 4)-global.randOther(0, 4), iy+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldNugget');
2761       gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2762       gold.yVel = global.randOther(2, 4);
2763     }
2764   }
2768 override void setupTile () {
2769   // prevent smash trap from spawning in player range when level starts
2770   if (true /+global.config.bizarre /*or isRealLevel()*/ +/) {
2771     if (level.calcNearestEnterDist(ix, iy) < 90) {
2772       cleanDeath = true;
2773       instanceRemove();
2774       return;
2775     }
2776   }
2778   if (global.cityOfGold) setSprite('sSmashTrapGold');
2779   dir = global.randRoom(0, 3);
2783 //FIXME: viscidMovement -- ???
2784 override void thinkFrame () {
2785   spectral = true;
2787   if (status == IDLE) {
2788     auto plr = level.player;
2789     auto dist = pointDistance(ix, iy, plr.ix, plr.iy);
2790     if (counter > 0) --counter;
2791     if (dist < 90 && counter < 1) {
2792       if (abs(plr.iy-(iy+8)) < 8 && plr.ix > ix+8 && !isCollisionRight(2)) {
2793         status = ATTACK;
2794         dir = RIGHT;
2795         xa = 0.5;
2796       } else if (abs(plr.ix-(ix+8)) < 8 && plr.iy > iy+8 && !isCollisionBottom(2)) {
2797         status = ATTACK;
2798         dir = DOWN;
2799         ya = 0.5;
2800       } else if (abs(plr.iy-(iy+8)) < 8 && plr.ix < ix+8 && !isCollisionLeft(2)) {
2801         status = ATTACK;
2802         dir = LEFT;
2803         xa = -0.5;
2804       } else if (abs(plr.ix-(ix+8)) < 8 && plr.iy < iy+8 && !isCollisionTop(2)) {
2805         status = ATTACK;
2806         dir = UP;
2807         ya = -0.5;
2808       }
2809     }
2810   } else if (status == ATTACK) {
2811     bool colLeft = !!isCollisionLeft(1);
2812     bool colRight = !!isCollisionRight(1);
2813     bool colTop = !!isCollisionTop(1);
2814     bool colBot = !!isCollisionBottom(1);
2816     xv = fclamp(xv+xa, -4, 4);
2817     yv = fclamp(yv+ya, -4, 4);
2818     shiftXY(xv, yv);
2819     if (dir == RIGHT) {
2820       if (isCollisionRight(2) && colRight) { shiftX(-2); hit = true; }
2821       if (colRight) { shiftX(-1); hit = true; }
2822     } else if (dir == DOWN) {
2823       if (isCollisionBottom(2) && colBot) { shiftY(-2); hit = true; }
2824       if (colBot) { shiftY(-1); hit = true; }
2825     } else if (dir == LEFT) {
2826       if (isCollisionLeft(2) && colLeft) { shiftX(2); hit = true; }
2827       if (colLeft) { shiftX(1); hit = true; }
2828     } else if (dir == UP) {
2829       if (isCollisionTop(2) && colTop) { shiftY(2); hit = true; }
2830       if (colTop) { shiftY(1); hit = true; }
2831     }
2833     if (level.isObjectInRect(ix-1, iy-1, 18, 18, delegate bool (MapObject o) { return (o isa EnemyTombLord); })) hit = true;
2835     if (hit) {
2836       xv = 0;
2837       yv = 0;
2838       xa = 0;
2839       ya = 0;
2840     }
2842     /*
2843     if (hit) {
2844       auto ct = isCollision();
2845       writeln("dir=", dirName(), "; colLeft=", colLeft, "; colRight=", colRight, "; colTop=", colTop, "; colBot=", colBot, "; col=", (ct ? GetClassName(ct.Class) : '<none>'), " (", (ct ? ct.objName : '<>'), ")");
2846     }
2847     */
2849     if (hit && !isCollision() /*!colRight && !colLeft && !colTop && !colBot*/) {
2850       status = IDLE;
2851       hit = false;
2852       counter = 50;
2853     }
2854   } else if (status == DEAD) {
2855     xv = 0;
2856     yv = 0;
2857     xa = 0;
2858     ya = 0;
2859     shiftY(0.05);
2860     if (level.isLavaAtPoint(ix, iy-1)) { instanceRemove(); return; }
2861   }
2863   if (level.checkTilesInRect(ix+1, iy+1, 15, 15, &level.cbCollisionLava)) status = DEAD;
2865   spectral = false;
2867   // player
2868   auto plr = level.player;
2869   if (!plr.dead && !plr.invincible && !plr.isExitingSprite() && /*plr.collidesWith(self)*/ plr.isRectHitSimple(x0-1, y0-1, width+2, height+2)) {
2870     bool doCol = true;
2871     if (!plr.collidesWith(self)) {
2872       bool onLeft = (plr.xVel < -0.01 && plr.x0 > x1);
2873       bool onRight = (plr.xVel > 0.01 && plr.x1 < x0);
2874       bool onTop = (plr.yVel < -0.01 && plr.y0 > y1);
2875       bool onBottom = (plr.yVel > 0.01 && plr.y1 < y0);
2876       if (!onLeft && !onRight && !onTop && !onBottom) {
2877         doCol = false;
2878       } else {
2879         writeln("*** PLAYER RUNNING ON TRAP SPIKES");
2880       }
2881     }
2882     if (doCol) {
2883       if (global.plife > 0) {
2884         global.plife -= global.config.crushDmg;
2885         if (global.plife <= 0 /*and isRealLevel()*/) level.addDeath(objName);
2886         plr.scrCreateBlood(plr.ix, plr.iy, 1);
2887         plr.stunned = true;
2888         plr.stunTimer = 20;
2889         playSound('sndHurt');
2890       }
2891     }
2892   }
2894   level.isObjectInRect(x0-1, y0-1, width+2, height+2, delegate bool (MapObject o) {
2895     if (o == self) return false;
2896     if (o isa EnemyTombLord) return false;
2898     if (o !isa MapEnemy && o !isa MonsterDamsel) return false;
2900     // register hit only if we're moving onto a spikes
2901     if (!o.collidesWith(self)) {
2902       bool onLeft = (o.xVel < -0.01 && o.x0 > x1);
2903       bool onRight = (o.xVel > 0.01 && o.x1 < x0);
2904       bool onTop = (o.yVel < -0.01 && o.y0 > y1);
2905       bool onBottom = (o.yVel > 0.01 && o.y1 < y0);
2906       if (!onLeft && !onRight && !onTop && !onBottom) return false;
2907       writeln("*** RUNNING ON TRAP SPIKES");
2908     }
2910     // enemy
2911     auto enemy = MapEnemy(o);
2912     if (enemy) {
2913       if (o.dead || o.status == DEAD || o.invincible) return false;
2914       if (o.heldBy) o.heldBy.holdItem = none;
2915       enemy.hp -= global.config.crushDmg;
2916       playSound('sndHit');
2917       return false;
2918     }
2920     // damsel
2921     auto dms = MonsterDamsel(o);
2922     if (dms) {
2923       if (!dms.invincible) {
2924         dms.spillBlood();
2925         if (dms.heldBy) dms.heldBy.holdItem = none;
2926         dms.hp -= global.config.crushDmg;
2927         dms.status = MapObject::THROWN;
2928         dms.counter = 120;
2929         //dms.damselDropped = true;
2930         dms.kissCount = min(1, dms.kissCount);
2931         playSound('sndDamsel');
2932         return false;
2933       }
2934     }
2936     return false;
2937   });
2941 defaultproperties {
2942   objType = 'oSmashTrap';
2943   objName = 'Smash Trap';
2944   desc = "Smash Trap";
2945   desc2 = "This trap carries an array of extendable blades. It can chase down intruders horizontally and vertically.";
2946   solid = true;
2947   viscidTop = 1;
2948   invincible = false;
2949   imageSpeed = 0.4;
2951   xv = 0;
2952   yv = 0;
2953   xa = 0;
2954   ya = 0;
2956   xVel = 0;
2957   yVel = 0;
2958   //xAcc = 0;
2959   //yAcc = 0;
2961   //moveable = true;
2962   grav = 1;
2963   myGravLimit = 10;
2964   counter = 3;
2966   status = IDLE;
2968   spriteName = 'sSmashTrap';
2969   toSpecialGrid = true; // it must think
2970   depth = 60;
2974 // ////////////////////////////////////////////////////////////////////////// //
2975 class MapTileSmashTrapLit['oSmashTrapLit'] : MapTileSmashTrap;
2977 defaultproperties {
2978   spriteName = 'sSmashTrapLit';
2979   lightRadius = 32;
2983 // ////////////////////////////////////////////////////////////////////////// //
2984 class MapTileGoldDoor['oGoldDoor'] : MapTile;
2986 enum {
2987   CLOSED,
2988   SCEPTRE,
2991 int status;
2994 override void doSprayRubble () {
2998 override void setupTile () {
2999   writeln("GENERATED GOLD DOOR");
3003 // it opens if player carrying a sceptre, and has a crown
3004 override void thinkFrame () {
3005   // early exits
3006   if (status == SCEPTRE && !global.hasCrown) return;
3007   auto plr = level.player;
3008   if (plr.holdItem !isa ItemWeaponSceptre) return;
3009   // check for collision
3010   if (plr.collidesWith(self)) {
3011     if (global.hasCrown) {
3012       // take sceptre away
3013       auto it = plr.holdItem;
3014       plr.holdItem = none;
3015       it.instanceRemove();
3016       playSound('sndChestOpen');
3017       level.MakeMapTile(ix/16, iy/16, 'oXGold');
3018       auto obj = level.MakeMapObject(ix-4, iy+6, 'oPoof');
3019       if (obj) obj.xVel = -0.4;
3020       obj = level.MakeMapObject(ix+16+4, iy+6, 'oPoof');
3021       if (obj) obj.xVel = 0.4;
3022       instanceRemove();
3023       return;
3024     }
3025     // no crown
3026     status = SCEPTRE;
3027     level.osdMessage("THE SCEPTRE FITS...\nBUT NOTHING IS HAPPENING!", 3.33);
3028   }
3032 defaultproperties {
3033   objType = 'oGoldDoor';
3034   desc = "Gold Door";
3035   desc2 = "A door with a golden seal on it.";
3037   status = CLOSED;
3039   solid = false;
3040   invincible = true;
3041   moveable = false;
3042   spriteName = 'sGoldDoor';
3043   toSpecialGrid = true; // it must think
3045   depth = 2000;
3049 // ////////////////////////////////////////////////////////////////////////// //
3050 class MapTileUnlockedGoldDoor['oXGold'] : MapTile;
3053 override void doSprayRubble () {
3057 override void setupTile () {
3061 // it opens if player carrying a sceptre, and has a crown
3062 override void thinkFrame () {
3066 defaultproperties {
3067   objType = 'oXGold';
3068   desc = "Gold Door";
3069   desc2 = "A door with an opened golden seal on it.";
3071   solid = false;
3072   invincible = true;
3073   moveable = false;
3074   spriteName = 'sExit';
3075   toSpecialGrid = false; // it must think
3076   exit = true;
3078   depth = 2000;
3082 // ////////////////////////////////////////////////////////////////////////// //
3083 class MapTileBlackMarketDoor['oXMarket'] : MapTile;
3086 override void doSprayRubble () {
3090 override void setupTile () {
3091   writeln("*** GENERATED BLACK MARKET ENTRANCE ***");
3092   /*
3093   if (global.hasSpectacles || global.hasUdjatEye) {
3094     level.setTileAt(ix/16, iy/16, none);
3095     //depth = 101;
3096   }
3097   */
3101 // it opens if player carrying a sceptre, and has a crown
3102 override void thinkFrame () {
3106 defaultproperties {
3107   objType = 'oXMarket';
3108   desc = "Market Exit";
3109   desc2 = "A door leading to Black Market.";
3111   solid = false;
3112   invincible = true;
3113   moveable = false;
3114   spriteName = 'sExit';
3115   toSpecialGrid = true; // it must be hidden behind the normal tile
3116   exit = true;
3118   depth = 2000;
3122 // ////////////////////////////////////////////////////////////////////////// //
3123 class MapTileMoai['oMoai'] : MapTile;
3125 override int width () { return 16; }
3126 override int height () { return 64; }
3128 override void doSprayRubble () {}
3129 override void setupTile () {}
3130 override void thinkFrame () {}
3132 defaultproperties {
3133   objType = 'oMoai';
3134   desc = "Moai Head";
3135   desc2 = "";
3137   solid = true;
3138   invincible = true;
3139   moveable = false;
3140   spriteName = 'sMoai';
3141   toSpecialGrid = true; // it is big
3142   depth = 1000;
3146 // ////////////////////////////////////////////////////////////////////////// //
3147 class MapTileMoai3['oMoai3'] : MapTileMoai;
3149 defaultproperties {
3150   spriteName = 'sMoai3';
3154 // ////////////////////////////////////////////////////////////////////////// //
3155 class MapTileMoaiInside['oMoaiInside'] : MapTile;
3157 override int width () { return 16; }
3158 override int height () { return 48; }
3160 override void doSprayRubble () {}
3161 override void setupTile () {}
3162 override void thinkFrame () {}
3164 defaultproperties {
3165   objType = 'oMoaiInside';
3166   desc = "Moai Head";
3167   desc2 = "";
3169   solid = true;
3170   invincible = true;
3171   moveable = false;
3172   spriteName = 'sMoaiInside';
3173   toSpecialGrid = true; // it is big
3174   depth = 92;
3178 // ////////////////////////////////////////////////////////////////////////// //
3179 class MapTileLamp['oLamp'] : MapTile;
3181 bool redLamp;
3184 override bool onExplosionTouch (MapObject xplo) {
3185   int x = ix, y = iy;
3186   instanceRemove();
3187   //level.MakeMapObject(x+8, y+12, (redLamp ? 'oLampRedItem' : 'oLampItem'));
3188   return true;
3192 override void doSprayRubble () {
3196 override void setupTile () {
3197   if (redLamp) spriteName = 'sLampRed';
3201 override void thinkFrame () {
3202   // 0: dark
3203   // 1: max light
3204   // 2: medium light
3205   float llev = (trunc(imageFrame) < 2 ? 0.0 : 2.0-imageFrame/2.0);
3206   lightRadius = 128+round(4*llev);
3207   //::thinkFrame();
3209   int x = ix, y = iy;
3210   // drop lamp item if it has no support
3211   if (!level.checkTilesInRect(x+5, y-1, 6, 1)) {
3212     instanceRemove();
3213     level.MakeMapObject(x+8, y+12, (redLamp ? 'oLampRedItem' : 'oLampItem'));
3214     return;
3215   }
3219 defaultproperties {
3220   objType = 'oLamp';
3221   desc = "Lamp";
3222   desc2 = "A bright, geothermal-powered lantern, with a stainless steel frame.";
3223   imageSpeed = 0.5;
3224   solid = false;
3225   invincible = false;
3226   moveable = false;
3227   spriteName = 'sLamp';
3228   toSpecialGrid = true; // it must think
3229   depth = 201;
3233 // ////////////////////////////////////////////////////////////////////////// //
3234 class MapTileLampRed['oLampRed'] : MapTileLamp;
3236 defaultproperties {
3237   desc2 = "A geothermal-powered lantern that emits red light. Often found in brothels.";
3238   redLamp = true;