mattock fixes: web blocks brick crushing; don't make hit sound if mattock hits no...
[k8vacspelynky.git] / mapent / MapTile.vc
blobbcae134461add76588d0b0f7cc0f4975bbabd247
1 /**********************************************************************************
2  * Copyright (c) 2008, 2009 Derek Yu and Mossmouth, LLC
3  * Copyright (c) 2010, Moloch
4  * Copyright (c) 2018, Ketmar Dark
5  *
6  * This file is part of Spelunky.
7  *
8  * You can redistribute and/or modify Spelunky, including its source code, under
9  * the terms of the Spelunky User License.
10  *
11  * Spelunky is distributed in the hope that it will be entertaining and useful,
12  * but WITHOUT WARRANTY.  Please see the Spelunky User License for more details.
13  *
14  * The Spelunky User License should be available in "Game Information", which
15  * can be found in the Resource Explorer, or as an external file called COPYING.
16  * If not, please obtain a new copy of Spelunky from <http://spelunkyworld.com/>
17  *
18  **********************************************************************************/
19 class MapTile : MapEntity;
21 enum Width = 16;
22 enum Height = 16;
24 transient SpriteImage sprite;
25 name spriteName;
27 // object flags
28 bool solid = true;
29 bool invisible;
30 bool invincible;
31 bool water;
32 bool lava;
33 bool ice;
34 bool ladder;
35 bool laddertop;
36 bool tree;
37 bool leaves;
38 bool moveable;
39 bool spikes;
40 bool woodenSpikes;
41 bool enter;
42 bool exit;
43 bool springtrap;
44 bool altar;
45 bool sacrificingAltar;
46 bool shopWall; // may be set to `true` for non-solid tiles too
47 bool cleanDeath;
48 bool smashed;
49 bool bloody;
50 bool platform; // oLadderTop, oLeaves, oTreeBranch
51 bool toSpecialGrid;
52 bool border; // border tile
53 bool hideOre;
54 bool litWholeTile; // lit whole tile with maximum light
55 bool ignoreFrameOffsetX, ignoreFrameOffsetY;
56 bool dontReplaceOthers; // this tile won't replace existing tiles when put on a map
57 bool immuneToReplacement; // this tile immune to replacement by other tiles
58 int ore = 0;
60 transient bool waterMoved; // any move
61 transient bool waterMovedDown;
62 transient int waterSlideCounter;
63 transient int waterSlideOldX, waterSlideOldY;
66 //float myGrav = 0.6;
67 //float grav = 0.6; // the gravity
68 float myGravLimit = 8;
70 // x and y are offsets
71 MapBackTile bgback;
72 MapBackTile bgfront;
74 MapObject gem;
75 bool gemDestroyWithTile;
78 // for default doors
79 void snapToExit (MapEntity e) {
80   if (!e || !e.isInstanceAlive) return;
81   e.fltx = ix+8;
82   e.flty = iy+8;
83   e.updateGrid();
87 // ////////////////////////////////////////////////////////////////////////// //
88 override void Destroy () {
89   delete gem;
91   while (bgback) {
92     auto t = bgback;
93     bgback = t.next;
94     delete t;
95   }
97   while (bgfront) {
98     auto t = bgfront;
99     bgfront = t.next;
100     delete t;
101   }
103   ::Destroy();
107 override void onLoaded () {
108   ::onLoaded();
109   if (spriteName) sprite = level.sprStore[spriteName];
110   for (MapBackTile bt = bgback; bt; bt = bt.next) bt.onLoaded();
111   for (MapBackTile bt = bgfront; bt; bt = bt.next) bt.onLoaded();
112   if (gem) gem.onLoaded();
116 string getExitMessage () {
117   return "";
121 override SpriteImage getSprite (optional out bool doMirror) {
122   doMirror = false;
123   return sprite;
127 override SpriteFrame getSpriteFrame (optional out bool doMirror, optional out int x0, optional out int y0, optional out int x1, optional out int y1) {
128   auto spr = getSprite(doMirror!optional);
129   if (!spr || spr.frames.length == 0) return none;
130   auto spf = spr.frames[trunc(imageFrame)%spr.frames.length];
131   if (!spf) return none;
132   if (specified_x0 || specified_x1) {
133     x0 = (ignoreFrameOffsetX ? 0 : -spf.xofs);
134     x1 = x0+spf.tex.width;
135   }
136   if (specified_y0 || specified_y1) {
137     y0 = (ignoreFrameOffsetY ? 0 : -spf.yofs);
138     y1 = y0+spf.tex.height;
139   }
140   return spf;
144 override void clearSprite () {
145   sprite = none;
146   spriteName = '';
150 override void setSprite (name sprNameL, optional name sprNameR) {
151   if (!sprNameL && sprNameR) sprNameL = sprNameR;
152   if (spriteName != sprNameL) {
153     spriteName = sprNameL;
154     sprite = (sprNameL ? level.sprStore[sprNameL] : none);
155     imageFrame = 0;
156   }
160 // ////////////////////////////////////////////////////////////////////////// //
161 override int width () { return Width; }
162 override int height () { return Height; }
165 void beautifyTile () {}
166 void scrSetupBlockTop () {}
167 void scrSetupBlockBottom () {}
168 void scrSetupBlockLeft () {}
169 void scrSetupBlockRight () {}
172 // ////////////////////////////////////////////////////////////////////////// //
173 void setGem (name oname, optional bool visibility) {
174   if (gem) {
175     gem.ownerTile = none;
176     gem.instanceRemove();
177     gem = none;
178   }
179   if (!oname) return;
180   gem = level.MakeMapObject(ix+8, iy+8, oname);
181   if (!gem) return;
182   gem.ownerTile = self;
183   gem.hiddenTreasure = !global.hasSpectacles && !global.hasUdjatEye;
184   if (specified_visibility) gem.hiddenTreasure = !visibility;
185   gem.active = false;
186   gem.spectral = true;
187   if (gem.hiddenTreasure) gem.visible = false;
188   gem.saveInterpData();
192 void setGemObject (MapObject obj, optional bool visibility) {
193   if (obj) {
194     if (obj.ownerTile == self) {
195       if (gem != obj) FatalError("MapTile::setGemObject: WTF?! (0)");
196     }
197     if (gem == obj) FatalError("MapTile::setGemObject: WTF?! (1)");
198   }
199   if (gem) {
200     gem.ownerTile = none;
201     gem.instanceRemove();
202     if (!gem.grid) delete gem;
203     gem = none;
204   }
205   if (!obj) return;
206   if (obj.ownerTile) {
207     obj.ownerTile.gem = none;
208     obj.ownerTile = none;
209   }
210   gem = obj;
211   gem.ownerTile = self;
212   gem.hiddenTreasure = !global.hasSpectacles && !global.hasUdjatEye;
213   if (specified_visibility) gem.hiddenTreasure = !visibility;
214   gem.active = false;
215   gem.spectral = true;
216   if (gem.hiddenTreasure) gem.visible = false;
217   gem.fltx = ix+8;
218   gem.flty = iy+8;
219   gem.saveInterpData();
223 void convertGemObjectToDiamond (MapObject ghost) {
224   auto oldgem = gem;
225   if (oldgem && oldgem isa ItemBigGem) {
226     if (ghost) {
227       if (!ghost.isInstanceAlive) return;
228       if (!gem.isRectHitSimple(ghost.x0, ghost.y0, ghost.width, ghost.height)) return;
229     }
230     auto diamond = level.MakeMapObject(oldgem.ix, oldgem.iy, 'oDiamond');
231     setGemObject(diamond);
232   }
236 // ////////////////////////////////////////////////////////////////////////// //
237 void doCreateOre (int probSap, int probEmer, int probRuby, int probItem) {
238   int n = global.randRoom(1, 100);
239        if (n < 20) ore = 1; // sprite_index = sBrickGold;
240   else if (n < 30) ore = 2; // sprite_index = sBrickGoldBig;
241        if (probSap > 0 && global.randRoom(1, probSap) == 1) setGem('oSapphireBig');
242   else if (probEmer > 0 && global.randRoom(1, probEmer) == 1) setGem('oEmeraldBig');
243   else if (probRuby > 0 && global.randRoom(1, probRuby) == 1) setGem('oRubyBig');
244   else if (probItem > 0 && global.randRoom(1, probItem) == 1) setGemObject(level.scrGenerateItem(ix+8, iy+8, GameLevel::GenItemSet.Underground));
248 void copyOreFrom (MapTile t) {
249   if (t == self) return;
250   if (t) {
251     setGemObject(t.gem);
252     ore = t.ore;
253   } else {
254     setGemObject(none);
255     ore = 0;
256   }
260 void removeOre () {
261   ore = 0;
265 void closeExit () {
266   if (exit) setSprite('sEntrance');
270 void openExit () {
271   if (exit) setSprite('sExit');
275 bool isExitActive () {
276   return (visible && exit && spriteName == 'sExit');
280 // ////////////////////////////////////////////////////////////////////////// //
281 void setupTile () {
282   //hangeable = true;
283   switch (objName) {
284     case 'oLadderOrange':
285       //objName = 'oLadder';
286       //goto case 'oLadder';
287     case 'oLadder':
288       objType = 'oLadder';
289       ladder = true;
290       solid = false;
291       spriteName = 'sLadder';
292       break;
293     case 'oLadderTop':
294       objType = 'oLadder';
295       //ladder = true; // no, laddertop is not a ladder
296       laddertop = true;
297       platform = true;
298       solid = false;
299       spriteName = 'sLadderTop';
300       break;
301     case 'oVineTop':
302       objType = 'oVineTop';
303       //ladder = true; // no, laddertop is not a ladder
304       laddertop = true;
305       platform = true;
306       solid = false;
307       spriteName = 'sVineTop';
308       break;
309     case 'oPushBlock':
310       //objName = 'oPushBlock';
311       objType = 'oBlock';
312       moveable = true;
313       spriteName = (global.cityOfGold == 1 ? 'sGoldBlock' : 'sBlock');
314       break;
315     case 'oPushIceBlock':
316       //objName = 'oPushBlock';
317       objType = 'oBlock';
318       ice = true;
319       moveable = true;
320       spriteName = 'sIceBlock';
321       break;
322     case 'oSolidIceBlock':
323       //objName = 'oIceBlock';
324       //goto case 'oIceBlock';
325       objType = 'oBlock';
326       ice = true;
327       solid = true;
328       moveable = false;
329       moveable = true; //k8: why, let it be an easter egg
330       spriteName = 'sIceBlock';
331       break;
332     case 'oIceBlock':
333       objType = 'oBlock';
334       ice = true;
335       solid = true;
336       moveable = true;
337       spriteName = 'sIceBlock';
338       break;
339     case 'oEdgeBrick':
340       // no sense to create ore here
341       objType = 'oBrick';
342       spriteName = (global.randRoom(1, 10) == 1 ? 'sBrick2' : 'sBrick');
343       break;
344     case 'oBrickSmooth':
345       objType = 'oBrick';
346       spriteName = 'sCaveSmooth';
347       break;
348     case 'oSpikesWood':
349       objType = 'oSpikes';
350       solid = false;
351       spikes = true;
352       woodenSpikes = true;
353       spriteName = 'sSpikesWood';
354       break;
355     case 'oSpikes':
356       objType = 'oSpikes';
357       solid = false;
358       spikes = true;
359       spriteName = 'sSpikes';
360       break;
361     case 'oEntrance':
362       objType = 'oEntrance';
363       enter = true;
364       solid = false;
365       invincible = true;
366       depth = 2000;
367       spriteName = 'sEntrance';
368       break;
369     case 'oExit':
370       objType = 'oExit';
371       solid = false;
372       invincible = true;
373       exit = true;
374       depth = 2000;
375       spriteName = 'sExit';
376       break;
377     case 'oAltarLeft':
378       objType = 'oAltar';
379       altar = true;
380       spriteName = 'sAltarLeft';
381       break;
382     case 'oAltarRight':
383       objType = 'oAltar';
384       altar = true;
385       spriteName = 'sAltarRight';
386       break;
387     case 'oSacAltarLeft':
388       objType = 'oAltar';
389       altar = true;
390       sacrificingAltar = true;
391       spriteName = 'sSacAltarLeft';
392       break;
393     case 'oSacAltarRight':
394       objType = 'oAltar';
395       altar = true;
396       sacrificingAltar = true;
397       spriteName = 'sSacAltarRight';
398       break;
399     case 'oSign':
400       objType = 'oSign';
401       solid = false;
402       spriteName = 'sSign';
403       break;
404     case 'oSignGeneral':
405       objType = 'oSign';
406       solid = false;
407       spriteName = 'sSignGeneral';
408       break;
409     case 'oSignBomb':
410       objType = 'oSign';
411       solid = false;
412       spriteName = 'sSignBomb';
413       break;
414     case 'oSignWeapon':
415       objType = 'oSign';
416       solid = false;
417       spriteName = 'sSignWeapon';
418       break;
419     case 'oSignClothing':
420       objType = 'oSign';
421       solid = false;
422       spriteName = 'sSignClothing';
423       break;
424     case 'oSignRare':
425       objType = 'oSign';
426       solid = false;
427       spriteName = 'sSignRare';
428       break;
429     case 'oSignCraps':
430       objType = 'oSign';
431       solid = false;
432       spriteName = 'sSignCraps';
433       break;
434     case 'oSignKissing':
435       objType = 'oSign';
436       solid = false;
437       spriteName = 'sSignKissing';
438       break;
439     /*
440     case 'oMoai':
441       objType = 'oMoai';
442       solid = true;
443       invincible = true;
444       spriteName = 'sMoai';
445       break;
446     */
447     case 'oMoai2':
448       objType = 'oMoai';
449       solid = true;
450       invincible = true;
451       spriteName = 'sMoai2';
452       break;
453     /*
454     case 'oMoai3':
455       objType = 'oMoai';
456       solid = true;
457       invincible = true;
458       spriteName = 'sMoai3';
459       break;
460     */
461     /*
462     case 'oMoaiInside':
463       objType = 'oMoaiInside';
464       solid = true;
465       invincible = true;
466       spriteName = 'sMoaiInside';
467       depth = 92;
468       break;
469     */
470     default:
471       FatalError(va("unknown map tile type '%n'", objName));
472   }
473   //!active = moveable || toSpecialGrid || lava || water; // will be done in MakeMapTile
474   //if (!solid) hangeable = false; // just in case
478 void setupTileSprite () {
479   if (!spriteName) FatalError("forgot to set sprite for tile type '", objName, "'");
480   sprite = level.sprStore[spriteName];
484 override bool initialize () {
485   if (!::initialize()) return false;
486   setupTile();
487   setupTileSprite();
488   if (moveable && depth == 1001) depth = 1000;
489   if ((exit || enter) && depth == 1001) depth = 2000;
490   if (lava || water) {
491     ++level.liquidTileCount;
492     level.checkWater = true;
493   }
494   // will be done in MakeMapTile
495   /*
496   // animated tiles must be active
497   if (!active) {
498     auto spr = getSprite();
499     if (spr && spr.frames.length > 1) {
500       writeln("activated animated tile '", objName, "'");
501       active = true;
502     }
503   }
504   */
505   return true;
509 // ////////////////////////////////////////////////////////////////////////// //
510 // for now, it works only for spikes
511 final void makeBloody () {
512   if (bloody) return;
513   if (!spikes) return;
514   bloody = true;
515   setSprite(woodenSpikes ? 'sSpikesWoodBlood' : 'sSpikesBlood');
519 // ////////////////////////////////////////////////////////////////////////// //
520 final void appendBackBack (MapBackTile tile, optional int yofs) {
521   if (!tile) return;
522   if (tile.next) FatalError("MapTile::appendBackBack: wtf?!");
523   tile.flty = (specified_yofs ? float(yofs) : 16.0);
524   MapBackTile last = bgback;
525   if (last) {
526     while (last.next) last = last.next;
527     last.next = tile;
528   } else {
529     bgback = tile;
530   }
534 final void appendBackFront (MapBackTile tile, optional int yofs) {
535   if (!tile) return;
536   if (tile.next) FatalError("MapTile::appendBackFront: wtf?!");
537   tile.flty = (specified_yofs ? float(yofs) : -16.0);
538   MapBackTile last = bgfront;
539   if (last) {
540     while (last.next) last = last.next;
541     last.next = tile;
542   } else {
543     bgfront = tile;
544   }
548 // ////////////////////////////////////////////////////////////////////////// //
550 override void onDestroy () {
555 // ////////////////////////////////////////////////////////////////////////// //
556 override void thinkFrame () {
557   if (!moveable) return;
558   // applies the acceleration
559   //xVel += xAcc;
560   //yVel += yAcc;
562   // approximates the "active" variables
563   if (fabs(xVel) < 0.001) xVel = 0;
564   if (fabs(yVel) < 0.001) yVel = 0;
565   //if (fabs(xAcc) < 0.0001) xAcc = 0;
566   //if (fabs(yAcc) < 0.0001) yAcc = 0;
568   yVel = fclamp(yVel+myGrav, -myGravLimit, myGravLimit);
569   int newY = round(flty+yVel); //!!!
570   // check if we need (and can) move down
571   int w = width, hp1 = height+1;
572   auto oldsolid = solid;
573   solid = false;
574   while (newY > iy) {
575     // made non-solid temporarily
576     auto hasAnything = level.checkTilesInRect(x0, y0, w, hp1, delegate bool (MapTile t) {
577       if (t == self) return false;
578       return t.solid;
579     });
580     if (hasAnything) {
581       // hit something, stop right there
582       if (yVel > myGrav*3) playSound('sndThud');
583       yVel = 0;
584       flty = iy;
585       break;
586     }
587     // move us
588     shiftY(1);
589   }
590   solid = oldsolid;
592   if (flty > level.tilesHeight*16+16 && yVel >= 0) {
593     cleanDeath = true;
594     instanceRemove();
595   }
599 // ////////////////////////////////////////////////////////////////////////// //
600 final void getInterpCoordsForTile (float currFrameDelta, int scale, out int drwx, out int drwy) {
601   if (waterSlideCounter) {
602     --waterSlideCounter;
603     drwx = ix*scale;
604     drwy = iy*scale;
605     int sgnx = sign(waterSlideOldX-ix);
606     int sgny = sign(waterSlideOldY-iy);
607     if ((sgnx|sgny) == 0) {
608       waterSlideCounter = 0;
609     } else {
610       drwx += (sgnx*(waterSlideCounter*4))*scale;
611       drwy += (sgny*(waterSlideCounter*4))*scale;
612     }
613   } else {
614     getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
615   }
619 void drawWithOfsBack (int xpos, int ypos, int scale, float currFrameDelta) {
620   //if (backTile) backTile.drawAt(xpos, ypos, scale);
621   if (invisible || !visible || !bgback) return;
623   /*
624   bool doMirror;
625   int fx0, fy0, fx1, fy1;
626   auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
628   // non-moveable and non-special tiles need not to be interpolated
629   int drwx, drwy;
630   getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
631   */
633   for (MapBackTile bt = bgback; bt; bt = bt.next) {
634     bt.fltx += fltx;
635     bt.flty += flty;
636     bt.drawWithOfs(xpos, ypos, scale, currFrameDelta);
637     bt.fltx -= fltx;
638     bt.flty -= flty;
639   }
643 void drawWithOfsFront (int xpos, int ypos, int scale, float currFrameDelta) {
644   //if (backTile) backTile.drawAt(xpos, ypos, scale);
645   if (invisible || !visible || !bgfront) return;
647   /*
648   bool doMirror;
649   int fx0, fy0, fx1, fy1;
650   auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
652   // non-moveable tiles need not to be interpolated
653   int drwx, drwy;
654   getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
655   */
657   for (MapBackTile bt = bgfront; bt; bt = bt.next) {
658     bt.fltx += fltx;
659     bt.flty += flty;
660     bt.drawWithOfs(xpos, ypos, scale, currFrameDelta);
661     bt.fltx -= fltx;
662     bt.flty -= flty;
663   }
667 override void drawWithOfs (int xpos, int ypos, int scale, float currFrameDelta) {
668   if (invisible || !visible) return;
670   bool doMirror;
671   int fx0, fy0, fx1, fy1;
672   auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
674   // non-moveable tiles need not to be interpolated
675   int drwx, drwy;
676   getInterpCoordsForTile(currFrameDelta, scale, out drwx, out drwy);
678   //auto oclr = Video.color;
679   //if (moveable) Video.color = 0xff_7f_00;
681   if (spf) {
682     fx0 = drwx+fx0*scale-xpos;
683     fy0 = drwy+fy0*scale-ypos;
684     fx1 = drwx+fx1*scale-xpos;
685     fy1 = drwy+fy1*scale-ypos;
686     if (!doMirror) {
687       spf.tex.blitExt(fx0, fy0, fx1, fy1, 0, 0, spf.tex.width, spf.tex.height);
688     } else {
689       spf.tex.blitExt(fx0, fy0, fx1, fy1, spf.tex.width, 0, 0, spf.tex.height);
690     }
691   } else if (ore > 0) {
692     fx0 = drwx-xpos;
693     fy0 = drwy-ypos;
694     fx1 = drwx-xpos;
695     fy1 = drwy-ypos;
696   }
698   //Video.color = oclr;
700   if (ore > 0 && !hideOre) {
701     auto ospr = level.sprStore[ore == 1 ? 'sGold' : 'sGoldBig'];
702     ospr.frames[0].tex.blitAt(fx0, fy0, scale);
703   }
707 // ////////////////////////////////////////////////////////////////////////// //
708 // called by Destroy event of blocks that can contain ore (oBrick, oLush, etc)
709 void scrDropOre () {
710   if (ore < 1) return;
712   int x = ix, y = iy;
714   foreach (int i; 0..3) {
715     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');
716     if (gold) {
717       //gold = instance_create(x+8+rand(0,4)-rand(0,4), y+8+rand(0,4)-rand(0,4), oGoldChunk);
718       gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
719       gold.yVel = global.randOther(2, 4);
720     }
721   }
723   if (ore > 1) {
724     // sGoldBig
725     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');
726     if (gold) {
727       gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
728       gold.yVel = global.randOther(2, 4);
729     }
730   }
732   ore = 0;
736 // ////////////////////////////////////////////////////////////////////////// //
737 private final bool cbIsSolidBrick (MapTile t) { return t.solid; }
738 private final bool cbIsBrickOrLushTile (MapTile t) { return (t.objType == 'oBrick' || t.objType == 'oLush'); }
741 // ////////////////////////////////////////////////////////////////////////// //
742 // called by solids during destroy event (oBrick etc) when destroy with force such as a boulder or explosion
743 // these rubble bits shower over the foreground and are not stopped by solids
744 final void scrSprayRubble (int x, int y, int count, name spr0, name spr1) {
745   while (count-- > 0) {
746     auto rubble = level.MakeMapObject(
747                     x+8+global.randOther(0, 8)-global.randOther(0, 8),
748                     y+8+global.randOther(0, 8)-global.randOther(0, 8), 'oRubblePiece'); //'oRubblePieceNonSolid');
749     rubble.setSprite(global.randOther(1, 3) == 1 ? spr0 : spr1);
750     rubble.xVel = global.randOther(0, 4)-global.randOther(0, 4);
751     rubble.yVel = -global.randOther(4, 8);
752     rubble.yAcc = 0.6;
753     //rubble.image_blend = image_blend;
754   }
758 final void scrDropRubble (int x, int y, int count, name spr0, name spr1) {
759   while (count-- > 0) {
760     int rx = x+8+global.randOther(0, 8)-global.randOther(0, 8);
761     int ry = y+8+global.randOther(0, 8)-global.randOther(0, 8);
762     if (global.randOther(1, 3) == 1) {
763       auto rubble = level.MakeMapObject(rx, ry, 'oRubble');
764       rubble.setSprite(spr1);
765       //rubble.image_blend = image_blend;
766     } else {
767       auto rubble = level.MakeMapObject(rx, ry, 'oRubbleSmall');
768       rubble.setSprite(spr0);
769       //rubble.image_blend = image_blend;
770     }
771   }
775 void smashedCheckUpTile () {
776   level.checkTilesInRect(ix, iy-16, 16, 16, delegate bool (MapTile t) {
777     //writeln("mtl: '", GetClassName(t.Class), "'; spikes=", t.spikes);
778     if (t.spikes || t isa MapTileGraveBase || t isa MapTileTikiTorch) t.instanceRemove();
779     return false;
780   });
784 void doSprayRubble () {
785   if (objType == 'oBlock' || objType == 'oBrick' || objType == 'oLush' || objType == 'oTemple') {
786     if (global.cityOfGold != 1) {
787       if (smashed) scrSprayRubble(ix, iy, 3, 'sRubbleTan', 'sRubbleTanSmall');
788       scrDropRubble(ix, iy, 3, 'sRubbleTan', 'sRubbleTanSmall');
789     } else {
790       ore = 2;
791       scrDropOre();
792     }
793   }
797 final bool isVineTile (MapTile t) { return (t.objType == 'oVineTop' || t.objType == 'oVine'); }
800 // this should be called from `smashMe()`
801 void checkSmashedVineSupport () {
802   if (!solid || isVineTile(self)) return;
803   // check for support tile ('cause this can be a moving tile, and not an actual support)
804   auto support = level.checkTileAtPoint(ix, iy, delegate bool (MapTile t) {
805     if (t == self) return false;
806     return (t.isInstanceAlive && t.solid);
807   });
808   if (support) return;
809   // no support
810   int x = ix+8;
811   int y = iy+16+8;
812   auto vine = level.checkTileAtPoint(x, y, &isVineTile);
813   if (!vine) return;
814   // yay, we found a vine; now remove it, going all the way down
815   for (;;) {
816     vine = level.checkTileAtPoint(x, y, &isVineTile);
817     if (!vine) return;
818     vine.smashMe();
819     y += 16;
820   }
824 void resetupArrowTraps () {
825   foreach (MapTileArrowTrap o; level.objGrid.allObjects(MapTileArrowTrap)) o.resetupChecker();
829 bool smashMe () {
830   if (invincible) return false;
831   smashed = true;
832   resetupArrowTraps();
834   checkSmashedVineSupport();
836   if (/*!global.cemetary &&*/ isVineTile(self)) {
837     level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
838     level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
839   }
841   if (altar) level.scrTriggerIdolAltar(stolenIdol:true);
843   instanceRemove();
844   smashedCheckUpTile();
846   if (gem) {
847     if (gemDestroyWithTile) {
848       gem.instanceRemove();
849     } else {
850       if (!gem.grid) FatalError("tile gem has no grid");
851       gem.hiddenTreasure = false;
852       gem.visible = true;
853       gem.active = true;
854       gem.spectral = false;
855     }
856     gem = none; // don't destroy it with tile
857   }
859   scrDropOre();
861   bool smashed = true;
862   if (!cleanDeath) doSprayRubble();
864   /*
865   if (other.object_index == oBoulder) {
866     if (other.integrity &gt; 0) dosomething = false; //exit;
867   }
868   */
870   //with (oTreasure) state = 1;
871   //with (oSpikes) if (not collision_point(x, y+16, oSolid, 0, 0)) instance_destroy();
873   if (shopWall) {
874     level.scrShopkeeperAnger(GameLevel::SCAnger.TileDestroyed);
875   }
877   // Moloch
878   int x = ix, y = iy;
879   MapTile obj = level.checkTileAtPoint(x, y-16, &cbIsSolidBrick);
880   if (obj) obj.scrSetupBlockBottom();
881   obj = level.checkTileAtPoint(x, y+16, &cbIsSolidBrick);
882   if (obj) obj.scrSetupBlockTop();
883   obj = level.checkTileAtPoint(x-16, y, &cbIsSolidBrick);
884   if (obj) obj.scrSetupBlockLeft();
885   obj = level.checkTileAtPoint(x+16, y, &cbIsSolidBrick);
886   if (obj) obj.scrSetupBlockRight();
888   // Moloch
889   /* was commented in the original
890   instance_activate_object(oLadder);
891   obj = collision_point(x+8, y+24, oLadder, 0, 0);
892   with (obj) {
893     if (sprite_index == sVineTop or sprite_index == sVine or
894         sprite_index == sVineSource or sprite_index == sVineBottom)
895     {
896       instance_destroy();
897     }
898   }
899   */
901   return true;
905 //private transient MapObject mXplo;
908 private final bool cbExplodeTiles (MapTile t) {
909   if (!t.isInstanceAlive) return false;
910   if (t.invincible) return false;
911   switch (t.objType) {
912     case 'oSpikes':
913     case 'oTikiTorch':
914     case 'oGrave':
915     case 'oLampRed':
916     case 'oLamp':
917       t.onExplosionTouch(mXplo);
918       break;
919   }
920   return false;
925 override bool onExplosionTouch (MapObject xplo) {
926   if (invincible) {
927     //writeln("ignore inv block at (", ix/16, ",", iy/16, ")");
928     return false;
929   }
931   int x = ix, y = iy;
933   if (smashMe()) {
934     //writeln("smashed block at (", ix/16, ",", iy/16, ")");
935     /*
936     auto oxplo = mXplo;
937     mXplo = xplo;
938     level.checkTileAtPoint(x+8, y-1, &cbExplodeTiles); //k8: why i wrote this?!
939     mXplo = oxplo;
940     */
942     /+
943     obj = instance_place(x, y, oGold);
944     if (obj != noone) with (obj) instance_destroy();
946     obj = instance_place(x, y, oGoldBig);
947     if (obj != noone) with (obj) instance_destroy();
948     +/
950     return true;
951   } else {
952     //writeln("cannot smash block at (", ix/16, ",", iy/16, ")");
953   }
955   return false;
959 // ////////////////////////////////////////////////////////////////////////// //
960 void onGotSpectacles () {
961   if (gem) {
962     gem.hiddenTreasure = false;
963     gem.visible = true;
964   }
968 defaultproperties {
969   objName = 'oTile';
970   objType = 'oSolid';
971   //depth = 9666; //???
972   depth = 1001;
976 // ////////////////////////////////////////////////////////////////////////// //
977 // this tile is returned instead of walkeable MapObject
978 class MapTileTemp : MapTile;
980 MapEntity e;
982 // don't forget to set `fltx` and `flty`!
983 override int x0 () { return (e ? e.x0 : 0); }
984 override int y0 () { return (e ? e.y0 : 0); }
985 override int width () { return (e ? e.width : 0); }
986 override int height () { return (e ? e.height : 0); }
988 defaultproperties {
989   objName = 'oMapObject';
990   objType = 'oMapObject';
991   active = false; // just in case
995 // ////////////////////////////////////////////////////////////////////////// //
996 class MapTileBrick['oBrick'] : MapTile;
999 override void setupTile () {
1000   spriteName = (global.randRoom(1, 10) == 1 ? 'sBrick2' : 'sBrick');
1001   int tileX = ix/16, tileY = iy/16;
1002   if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1003     invincible = true;
1004     border = true;
1005     //writeln("BORDER");
1006   } else {
1007     doCreateOre(100, 120, 140, 1200);
1008   }
1012 override void beautifyTile () {
1013   if (level.checkTileAtPoint(ix+8, iy-8, delegate bool (MapTile t) { return (t isa TitleTileSpecialNonSolidInvincible || t isa TitleTileBlack); })) return;
1015   // brick
1016   //if (argument1) if (global.randRoom(1, 10) == 1) sprite_index = sBrick2; // reset sprite for custom border setup
1018   int tileX = ix/16, tileY = iy/16;
1020   auto tt = level.getTileAtGrid(tileX, tileY-1);
1021   if (tt && tt.objName == 'oBlock') tt = none;
1022   //if (y == 0 || isNamedTileAt(x, y-1, 'oBrick') || isNamedTileAt(x, y-1, 'oHardBlock') || isNamedTileAt(x, y-1, 'oEdgeBrick')) up = true;
1023   bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1025   tt = level.getTileAtGrid(tileX, tileY+1);
1026   //if (y == NormalTilesHeight-1 || isNamedTileAt(x, y+1, 'oBrick') || isNamedTileAt(x, y+1, 'oHardBlock') || isNamedTileAt(x, y+1, 'oEdgeBrick')) down = true;
1027   bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1029   //if (collision_point(x-16, y, oBrick, 0, 0) or collision_point(x-16, y, oHardBlock, 0, 0)) { left = true; } // in the original
1030   //if (collision_point(x+16, y, oBrick, 0, 0) or collision_point(x+16, y, oHardBlock, 0, 0)) { right = true; } // in the original
1032   if (!up) {
1033     setSprite('sCaveUp');
1034     // add rocks and sand
1035     /*
1036     if (global.randRoom(1, 3) < 3) {
1037       tile_add('bgCaveTop', 0, 0, 16, 16, x*16, y*16-16, 3);
1038     } else {
1039       tile_add('bgCaveTop', 16, 0, 16, 16, x*16, y*16-16, 3);
1040     }
1041     */
1042     bool n = (global.randRoom(1, 3) < 3);
1043     appendBackFront(level.CreateBgTile('bgCaveTop', (n ? 0 : 16)));
1044     //instance_create(x, y-16, oCaveTop);
1045   }
1047   if (!down) {
1048     setSprite(!up ? 'sCaveUp2' : 'sBrickDown');
1049     //instance_create(x, y+16, oCaveBottom);
1050   }
1052   /* in the original
1053   if (not left) instance_create(x-16, y, oCaveLeft);
1054   if (not right) instance_create(x+15, y, oCaveRight);
1055   */
1059 override void scrSetupBlockTop () {
1060   //int x = ix, y = iy;
1061   int tileX = ix/16, tileY = iy/16;
1062   auto tt = level.getTileAtGrid(tileX, tileY+1);
1063   //if (y >= (GameLevel::NormalTilesHeight-16)*16 || level.checkTileAtPoint(x, y+16, &cbIsBrickOrLushTile)) down = true;
1064   bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1065   // gfxhigh
1066   auto bt = level.CreateBgTile('bgCaveTop', (global.randRoom(1, 3) < 3 ? 0 : 16));
1067   //bt.flty = -16; // offset
1068   appendBackFront(bt);
1069   // other
1070   setSprite(down ? 'sCaveUp' : 'sCaveUp2');
1074 override void scrSetupBlockBottom () {
1075   //int x = ix, y = iy;
1076   //bool up = false;
1077   //if (y == 0 || level.checkTileAtPoint(x, y-16, &cbIsBrickOrLushTile)) up = true;
1078   int tileX = ix/16, tileY = iy/16;
1079   auto tt = level.getTileAtGrid(tileX, tileY-1);
1080   bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1081   setSprite(up ? 'sBrickDown' : 'sCaveUp2');
1085 defaultproperties {
1086   objType = 'oBrick';
1087   spriteName = 'sBrick';
1088   lava = false;
1089   solid = true;
1090   toSpecialGrid = false;
1094 // ////////////////////////////////////////////////////////////////////////// //
1095 class MapTileHardBrick['oHardBlock'] : MapTileBrick;
1097 override void setupTile () {
1101 defaultproperties {
1102   objType = 'oHardBlock';
1103   spriteName = 'sBrick';
1104   invincible = true;
1105   solid = true;
1109 // ////////////////////////////////////////////////////////////////////////// //
1110 class MapTileBlock['oBlock'] : MapTile;
1113 override void setupTile () {
1114   int tileX = ix/16, tileY = iy/16;
1115   if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1116     invincible = true;
1117     border = true;
1118     //writeln("BORDER");
1119   }
1120   if (global.cityOfGold == 1) spriteName = 'sGoldBlock';
1124 final bool isBBTTile (MapTile t) { return (t.objType == 'oBrick' || t.objType == 'oTemple' || t.objType == 'oHardBlock'); }
1127 override void beautifyTile () {
1128   bool down = !!level.checkTileAtPoint(ix, iy+16, &isBBTTile);
1130   // don't want push blocks next to lava until we tighten up liquid draining
1131   if (level.isLavaAtPoint(ix-16, iy) || level.isLavaAtPoint(ix+16, iy)) down = false;
1133   if (down && global.randRoom(1, 4) == 1) {
1134     instanceRemove();
1135     level.MakeMapTile(ix/16, iy/16, 'oPushBlock');
1136   }
1140 defaultproperties {
1141   objType = 'oBlock';
1142   spriteName = 'sBlock';
1143   solid = true;
1144   toSpecialGrid = false;
1148 // ////////////////////////////////////////////////////////////////////////// //
1149 class MapTileLush['oLush'] : MapTile;
1152 override void setupTile () {
1153   int tileX = ix/16, tileY = iy/16;
1154   if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1155     invincible = true;
1156     border = true;
1157     //writeln("BORDER");
1158   } else {
1159     doCreateOre(80, 100, 120, 1200);
1160   }
1164 override void beautifyTile () {
1165   if (level.checkTileAtPoint(ix+8, iy-8, delegate bool (MapTile t) { return (t isa TitleTileSpecialNonSolidInvincible || t isa TitleTileBlack); })) return;
1167   int tileX = ix/16, tileY = iy/16;
1169   auto tt = level.getTileAtGrid(tileX, tileY-1);
1170   //if (y == 0 || isNamedTileAt(x, y-1, 'oBrick') || isNamedTileAt(x, y-1, 'oHardBlock') || isNamedTileAt(x, y-1, 'oEdgeBrick')) up = true;
1171   bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral && (tt.objType == 'oLush' || tt.objType == 'oTemple')));
1173   tt = level.getTileAtGrid(tileX, tileY+1);
1174   //if (y == NormalTilesHeight-1 || isNamedTileAt(x, y+1, 'oBrick') || isNamedTileAt(x, y+1, 'oHardBlock') || isNamedTileAt(x, y+1, 'oEdgeBrick')) down = true;
1175   bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral && tt.objType == 'oLush'));
1177   if (!up) {
1178     setSprite('sLushUp');
1179     if (!level.isLavaAtPoint(ix+8, iy-8)) {
1180       MapBackTile bt;
1181            if (global.randRoom(1, 8) == 1) bt = level.CreateBgTile('bgCaveTop2', 32);
1182       else if (global.randRoom(1, 3) < 3) bt = level.CreateBgTile('bgCaveTop2', 0);
1183       else bt = level.CreateBgTile('bgCaveTop2', 16);
1184       appendBackFront(bt);
1185     }
1186   }
1188   if (!down) {
1189     setSprite(!up ? 'sLushUp2' : 'sLushDown');
1190     if (!level.isSolidAtPoint(ix, iy+16) && !level.isLavaAtPoint(ix, iy+16)) {
1191       MapBackTile bt;
1192            if (global.randRoom(1, 12) == 1) bt = level.CreateBgTile('bgCaveTop2', 48);
1193       else if (global.randRoom(1, 12) == 1) bt = level.CreateBgTile('bgCaveTop2', 64);
1194       appendBackBack(bt);
1195     }
1196     //instance_create(x, y+16, oLushBottom); // in the original
1197   }
1201 override void scrSetupBlockTop () {
1202   int tileX = ix/16, tileY = iy/16;
1203   auto tt = level.getTileAtGrid(tileX, tileY+1);
1204   bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1205   //setSprite('sLushUpBare');
1206   if (!down) {
1207     setSprite(spriteName == 'sLushUp' || spriteName == 'sLushUp2' || spriteName == 'sLushUp3' ? 'sLushUpBareTop' : 'sLushUpBare2');
1208   } else {
1209     setSprite('sLushUpBare');
1210   }
1214 override void scrSetupBlockBottom () {
1215   int tileX = ix/16, tileY = iy/16;
1216   auto tt = level.getTileAtGrid(tileX, tileY-1);
1217   bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1218   if (!up) {
1219     setSprite(spriteName == 'sLushUp' || spriteName == 'sLushUp2' || spriteName == 'sLushUp3' ? 'sLushUpBareBottom' : 'sLushUpBare2');
1220   } else {
1221     setSprite('sLushDownBare');
1222   }
1226 defaultproperties {
1227   objType = 'oLush';
1228   spriteName = 'sLush';
1229   lava = false;
1230   solid = true;
1231   toSpecialGrid = false;
1235 // ////////////////////////////////////////////////////////////////////////// //
1236 class MapTileDark['oDark'] : MapTile;
1239 override void setupTile () {
1240   int tileX = ix/16, tileY = iy/16;
1241   if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1242     invincible = true;
1243     border = true;
1244     //writeln("BORDER");
1245   } else {
1246     doCreateOre(40, 60, 80, 1200);
1247   }
1251 override void beautifyTile () {
1252   if (level.checkTileAtPoint(ix+8, iy-8, delegate bool (MapTile t) { return (t isa TitleTileSpecialNonSolidInvincible || t isa TitleTileBlack); })) return;
1254   // brick
1255   //if (argument1) if (global.randRoom(1, 10) == 1) sprite_index = sBrick2; // reset sprite for custom border setup
1257   int tileX = ix/16, tileY = iy/16;
1259   auto tt = level.getTileAtGrid(tileX, tileY-1);
1260   //if (y == 0 || isNamedTileAt(x, y-1, 'oBrick') || isNamedTileAt(x, y-1, 'oHardBlock') || isNamedTileAt(x, y-1, 'oEdgeBrick')) up = true;
1261   bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1263   tt = level.getTileAtGrid(tileX, tileY+1);
1264   //if (y == NormalTilesHeight-1 || isNamedTileAt(x, y+1, 'oBrick') || isNamedTileAt(x, y+1, 'oHardBlock') || isNamedTileAt(x, y+1, 'oEdgeBrick')) down = true;
1265   bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1267   //if (collision_point(x-16, y, oBrick, 0, 0) or collision_point(x-16, y, oHardBlock, 0, 0)) { left = true; } // in the original
1268   //if (collision_point(x+16, y, oBrick, 0, 0) or collision_point(x+16, y, oHardBlock, 0, 0)) { right = true; } // in the original
1270   if (!up) {
1271     setSprite('sDarkUp');
1272     bool n = (global.randRoom(1, 3) < 3);
1273     appendBackFront(level.CreateBgTile('bgCaveTop3', (n ? 0 : 16)));
1274   }
1276   if (!down) {
1277     setSprite(!up ? 'sDarkUp2' : 'sDarkDown');
1278   }
1282 override void scrSetupBlockTop () {
1283   int tileX = ix/16, tileY = iy/16;
1284   auto tt = level.getTileAtGrid(tileX, tileY+1);
1285   bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1286   auto bt = level.CreateBgTile('bgCaveTop3', (global.randRoom(1, 3) < 3 ? 0 : 16));
1287   appendBackFront(bt);
1288   //bt.flty = -16; // offset
1289   // other
1290   setSprite(down ? 'sDarkUp' : 'sDarkUp2');
1294 override void scrSetupBlockBottom () {
1295   int tileX = ix/16, tileY = iy/16;
1296   auto tt = level.getTileAtGrid(tileX, tileY-1);
1297   bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1298   setSprite(up ? 'sDarkDown' : 'sDarkUp2');
1302 defaultproperties {
1303   objType = 'oDark';
1304   spriteName = 'sDark';
1305   lava = false;
1306   solid = true;
1307   toSpecialGrid = false;
1311 // ////////////////////////////////////////////////////////////////////////// //
1312 class MapTileIce['oIce'] : MapTile;
1314 bool hasIceBottom;
1315 int dripTimer;
1317 final bool isIceTile (MapTile t) { return t.ice; }
1318 final MapTile isIceTileAtPoint (int x, int y) { return level.checkTileAtPoint(x, y, &isIceTile); }
1321 override void setupTile () {
1322   if (global.randRoom(1, 80) == 1) {
1323     setGem('oFrozenCaveman', true); // always visible
1324     gem.fltx = ix;
1325     gem.flty = iy;
1326     gem.saveInterpData();
1327     gemDestroyWithTile = true;
1328   }
1329   dripTimer = global.randOther(20, 400);
1330   //hasIceBottom = true;
1331   //dripTimer = 40;
1335 override void thinkFrame () {
1336   if (hasIceBottom) {
1337     if (--dripTimer <= 0) {
1338       //writeln("DRIP!");
1339       level.MakeMapObject(ix+8, iy+16+4, 'oDrip');
1340       dripTimer = global.randOther(20, 400);
1341       //dripTimer = 40;
1342     }
1343   }
1347 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1348   bool up = (specified_noup && noup ? false : !!isIceTileAtPoint(ix, iy-16));
1349   bool down = (specified_nodown && nodown ? false : !!isIceTileAtPoint(ix, iy+16));
1350   bool left = (specified_noleft && noleft ? false : !!isIceTileAtPoint(ix-16, iy));
1351   bool right = (specified_noright && noright ? false : !!isIceTileAtPoint(ix+16, iy));
1353   if (!up) setSprite('sIceUp');
1354   if (!down) {
1355     setSprite(!up ? 'sIceUp2' : 'sIceDown');
1356     if (!level.isSolidAtPoint(ix, iy+16) && global.randOther(1, 20) == 1) hasIceBottom = true;
1357   }
1358   if (!left) {
1359          if (!up && !down) setSprite('sIceUDL');
1360     else if (!up) setSprite('sIceUL');
1361     else if (!down) setSprite('sIceDL');
1362     else setSprite('sIceLeft');
1363   }
1364   if (!right) {
1365          if (!up && !down) setSprite('sIceUDR');
1366     else if (!up) setSprite('sIceUR');
1367     else if (!down) setSprite('sIceDR');
1368     else setSprite('sIceRight');
1369   }
1370   if (!up && !left && !right && down) setSprite('sIceULR');
1371   if (!down && !left && !right && up) setSprite('sIceDLR');
1372   if (up && down && !left && !right) setSprite('sIceLR');
1373   if (!up && !down && !left && !right) setSprite('sIceBlock');
1378 override void beautifyTile () {
1379   /+
1380   bool up = !!isIceTileAtPoint(ix, iy-16);
1381   bool down = !!isIceTileAtPoint(ix, iy+16);
1382   bool left = !!isIceTileAtPoint(ix-16, iy);
1383   bool right = !!isIceTileAtPoint(ix+16, iy);
1385   //writeln("beautifying ice; l=", left, "; r=", right, "; u=", up, "; d=", down);
1387   if (!up) setSprite('sIceUp');
1388   if (!down) {
1389     setSprite(!up ? 'sIceUp2' : 'sIceDown');
1390     if (!level.isSolidAtPoint(ix, iy+16) && global.randOther(1, 20) == 1) hasIceBottom = true;
1391   }
1392   if (!left) {
1393          if (!up && !down) setSprite('sIceUDL');
1394     else if (!up) setSprite('sIceUL');
1395     else if (!down) setSprite('sIceDL');
1396     else setSprite('sIceLeft');
1397   }
1398   if (!right) {
1399          if (!up && !down) setSprite('sIceUDR');
1400     else if (!up) setSprite('sIceUR');
1401     else if (!down) setSprite('sIceDR');
1402     else setSprite('sIceRight');
1403   }
1404   if (!up && !left && !right && down) setSprite('sIceULR');
1405   if (!down && !left && !right && up) setSprite('sIceDLR');
1406   if (up && down && !left && !right) setSprite('sIceLR');
1407   if (!up && !down && !left && !right) setSprite('sIceBlock');
1408   +/
1409   setupNiceTileSprite();
1413 override void scrSetupBlockTop () {
1414   setupNiceTileSprite(/*noup:true*/);
1415   /+
1416   bool up = false;
1417   bool down = !!isIceTileAtPoint(ix, iy+16);
1418   bool left = !!isIceTileAtPoint(ix-16, iy);
1419   bool right = !!isIceTileAtPoint(ix+16, iy);
1421   setSprite('sIceUp');
1423   if (!down) setSprite('sIceUp2');
1425   if (!left) setSprite(!up ? 'sIceUDL' : 'sIceUL');
1426   if (!right && !down) setSprite('sIceUDR');
1428   if (!left && !right && down) setSprite('sIceULR');
1429   if (!down && !left && !right) setSprite('sIceBlock');
1430   +/
1434 override void scrSetupBlockBottom () {
1435   setupNiceTileSprite(/*nodown:true*/);
1436   /+
1437   bool up = !!isIceTileAtPoint(ix, iy-16);
1438   bool left = !!isIceTileAtPoint(ix-16, iy);
1439   bool right = !!isIceTileAtPoint(ix+16, iy);
1441   setSprite(!up ? 'sIceUp2' : 'sIceDown');
1443   if (global.randOther(1, 20) == 1) hasIceBottom = true; //instance_create(x, y+16, oIceBottom);
1445   if (!left) setSprite(!up ? 'sIceUDL' : 'sIceDL');
1446   if (!right) setSprite(!up ? 'sIceUDR' : 'sIceDR');
1448   if (!left && !right && up) setSprite('sIceDLR');
1449   if (!up && !left && !right) setSprite('sIceBlock');
1450   +/
1454 override void scrSetupBlockLeft () {
1455   setupNiceTileSprite(/*noright:true*/);
1456   /+
1457   bool up = !!isIceTileAtPoint(ix, iy-16);
1458   bool down = !!isIceTileAtPoint(ix, iy+16);
1459   bool left = !!isIceTileAtPoint(ix-16, iy);
1460   bool right = false;
1462   if (!up) setSprite('sIceUp');
1463   if (!down) { if (!up) setSprite('sIceUp2'); else setSprite('sIceDown'); }
1464   if (!left) {
1465          if (!up && !down) setSprite('sIceUDL');
1466     else if (!up) setSprite('sIceUL');
1467     else if (!down) setSprite('sIceDL');
1468     else setSprite('sIceLeft');
1469   }
1470   if (!right) {
1471          if (!up && !down) setSprite('sIceUDR');
1472     else if (!up) setSprite('sIceUR');
1473     else if (!down) setSprite('sIceDR');
1474     else setSprite('sIceRight');
1475   }
1476   if (!up && !left && !right && down) setSprite('sIceULR');
1477   if (!down && !left && !right && up) setSprite('sIceDLR');
1478   if (up && down && !left && !right) setSprite('sIceLR');
1479   if (!up && !down && !left && !right) setSprite('sIceBlock');
1480   +/
1484 override void scrSetupBlockRight () {
1485   setupNiceTileSprite(/*noleft:true*/);
1486   /+
1487   bool up = !!isIceTileAtPoint(ix, iy-16);
1488   bool down = !!isIceTileAtPoint(ix, iy+16);
1489   bool left = false;
1490   bool right = !!isIceTileAtPoint(ix+16, iy);
1492   if (!up) setSprite('sIceUp');
1493   if (!down) { if (!up) setSprite('sIceUp2'); else setSprite('sIceDown'); }
1494   if (!left) {
1495          if (!up && !down) setSprite('sIceUDL');
1496     else if (!up) setSprite('sIceUL');
1497     else if (!down) setSprite('sIceDL');
1498     else setSprite('sIceLeft');
1499   }
1500   if (!right) {
1501          if (!up && !down) setSprite('sIceUDR');
1502     else if (!up) setSprite('sIceUR');
1503     else if (!down) setSprite('sIceDR');
1504     else setSprite('sIceRight');
1505   }
1506   if (!up && !left && !right && down) setSprite('sIceULR');
1507   if (!down && !left && !right && up) setSprite('sIceDLR');
1508   if (up && down && !left && !right) setSprite('sIceLR');
1509   if (!up && !down && !left && !right) setSprite('sIceBlock');
1510   +/
1514 defaultproperties {
1515   objType = 'oIce';
1516   spriteName = 'sIce';
1517   solid = true;
1518   ice = true;
1519   toSpecialGrid = true; // want to think
1523 // ////////////////////////////////////////////////////////////////////////// //
1524 class MapTileTemple['oTemple'] : MapTile;
1526 bool hasIceBottom;
1527 int dripTimer;
1529 final bool isTempleTile (MapTile t) { return (t.objType == 'oTemple' || t.border); } // fake too
1530 final MapTile isTempleTileAtPoint (int x, int y) { return level.checkTileAtPoint(x, y, &isTempleTile); }
1533 override void setupTile () {
1534   setSprite(global.cityOfGold == 1 ? 'sGTemple' : 'sTemple');
1535   int tileX = ix/16, tileY = iy/16;
1536   if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1537     invincible = true;
1538     border = true;
1539     //writeln("BORDER");
1540   } else {
1541     doCreateOre(60, 80, 100, 1200);
1542   }
1547 override void thinkFrame () {
1548   if (hasIceBottom) {
1549     if (--dripTimer <= 0) {
1550       //writeln("DRIP!");
1551       level.MakeMapObject(ix+8, iy+16+4, 'oDrip');
1552       dripTimer = global.randOther(20, 400);
1553       //dripTimer = 40;
1554     }
1555   }
1560 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1561   bool up = (iy <= 0 ? true : (specified_noup && noup ? false : !!isTempleTileAtPoint(ix, iy-16)));
1562   bool down = (iy >= level.tilesHeight*16-16 ? true : (specified_nodown && nodown ? false : !!isTempleTileAtPoint(ix, iy+16)));
1563   bool left = (ix <= 16 ? true : (specified_noleft && noleft ? false : !!isTempleTileAtPoint(ix-16, iy)));
1564   bool right = (ix >= level.tilesWidth*16-16*2 ? true : (specified_noright && noright ? false : !!isTempleTileAtPoint(ix+16, iy)));
1566   /*
1567   if (argument1) {
1568     if (global.cityOfGold == 1) sprite_index = sGTemple; else sprite_index = sTemple;
1569   }
1570   */
1572   /*!
1573   if (y == 0 or collision_point(x, y-16, oTemple, 0, 0) or collision_point(x, y+16, oTempleFake, 0, 0)) { up = true; }
1574   if (y >= argument0 or collision_point(x, y+16, oTemple, 0, 0) or collision_point(x, y+16, oTempleFake, 0, 0)) { down = true; }
1575   if (collision_point(x-16, y, oTemple, 0, 0) or collision_point(x-16, y, oTempleFake, 0, 0)) { left = true; }
1576   if (collision_point(x+16, y, oTemple, 0, 0) or collision_point(x+16, y, oTempleFake, 0, 0)) { right = true; }
1577   */
1579   if (global.cityOfGold == 1) {
1580     if (!up) {
1581       setSprite('sGTempleUp');
1582            if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 0)); //3
1583       else if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 16)); //3
1584       if (!left && !right) {
1585         if (!down) setSprite('sGTempleUp6'); else setSprite('sGTempleUp5');
1586       } else if (!left) {
1587         if (!down) setSprite('sGTempleUp7'); else setSprite('sGTempleUp3');
1588       } else if (!right) {
1589         if (!down) setSprite('sGTempleUp8'); else setSprite('sGTempleUp4');
1590       } else if (left && right && !down) {
1591         setSprite('sGTempleUp2');
1592       }
1593     } else if (!down) {
1594       setSprite('sGTempleDown');
1595     }
1596   } else {
1597     if (!up) {
1598       setSprite('sTempleUp');
1599       //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);
1600            if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 0)); //3
1601       else if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 16)); //3
1602       if (!left && !right) {
1603         if (!down) setSprite('sTempleUp6'); else setSprite('sTempleUp5');
1604       } else if (!left) {
1605         if (!down) setSprite('sTempleUp7'); else setSprite('sTempleUp3');
1606       } else if (!right) {
1607         if (!down) setSprite('sTempleUp8'); else setSprite('sTempleUp4');
1608       } else if (left && right && !down) {
1609         setSprite('sTempleUp2');
1610       }
1611     } else if (!down) {
1612       setSprite('sTempleDown');
1613     }
1614   }
1618 override void beautifyTile () {
1619   setupNiceTileSprite();
1623 override void scrSetupBlockTop () {
1624   setupNiceTileSprite(/*noup:true*/);
1628 override void scrSetupBlockBottom () {
1629   setupNiceTileSprite(/*nodown:true*/);
1633 override void scrSetupBlockLeft () {
1634   setupNiceTileSprite(/*noright:true*/);
1638 override void scrSetupBlockRight () {
1639   setupNiceTileSprite(/*noleft:true*/);
1643 defaultproperties {
1644   objType = 'oTemple';
1645   spriteName = 'sTemple';
1646   solid = true;
1647   //toSpecialGrid = true; // want to think
1651 // ////////////////////////////////////////////////////////////////////////// //
1652 class MapTileVine['oVine'] : MapTile;
1655 override void setupTile () {
1659 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1660   //if (argument1) sprite_index = sVine;
1662   bool up = !!level.isLadderAtPoint(ix+8, iy-8);
1663   bool down = !!level.isLadderAtPoint(ix+8, iy+16);
1665   if (!up) {
1666     //tile_add(bgVineRoots, 0, 0, 16, 16, x, y-16, 1000); // use same depth as the vine objects themselves
1667     appendBackFront(level.CreateBgTile('bgVineRoots', 0)); //1000
1668     setSprite('sVineSource');
1669   } else if (!down) {
1670     setSprite('sVineBottom');
1671   }
1675 override void beautifyTile () {
1676   setupNiceTileSprite();
1680 defaultproperties {
1681   objType = 'oVine';
1682   ladder = true;
1683   solid = false;
1684   spriteName = 'sVine';
1685   //toSpecialGrid = true; // want to think
1689 // ////////////////////////////////////////////////////////////////////////// //
1690 class MapTileLava['oLava'] : MapTile;
1692 bool spurt;
1693 int spurtTime;
1694 int spurtCounter;
1695 bool spurtSet;
1698 override void setupTile () {
1699   spurtTime = global.randOther(100, 300);
1700   spurtCounter = spurtTime;
1704 override void doSprayRubble () {
1705   foreach (; 0..3) level.MakeMapObject(ix+global.randOther(0, 16), iy+global.randOther(0, 16), 'oLavaDrip');
1706   if (global.randOther(1, 6) == 1) {
1707     auto flame = level.MakeMapObject(ix+8, iy+8, 'oFlame');
1708     if (flame) flame.yVel = 4;
1709   }
1713 override bool onExplosionTouch (MapObject xplo) {
1714   return false;
1718 override void thinkFrame () {
1719   if (!spurtSet && spriteName == 'sLavaTop') {
1720     spurtSet = true;
1721     spurt = (global.randOther(1, 4) == 1);
1722   }
1723   auto dist = pointDistance(ix, iy, level.player.ix, level.player.iy);
1724   if (spurt && dist < 240) {
1725     if (spurtCounter > 0) {
1726       --spurtCounter;
1727     } else {
1728       spurtCounter = spurtTime;
1729       auto flame = level.MakeMapObject(ix+8, iy-4, (global.randOther(1, 8) == 1 ? 'oMagma' : 'oFlame'));
1730       //auto flame = level.MakeMapObject(ix+8, iy-4, 'oMagma');
1731       if (flame) flame.yVel = -global.randOther(1, 4);
1732     }
1733   }
1737 defaultproperties {
1738   objType = 'oLava';
1739   spriteName = 'sLava';
1740   lava = true;
1741   solid = false;
1742   toSpecialGrid = false; // lava tiles are special: they are animated/thinked without a grid
1743   lightRadius = 32+16;
1744   litWholeTile = true;
1745   imageSpeed = 0.5;
1749 // ////////////////////////////////////////////////////////////////////////// //
1750 class MapTileWater['oWater'] : MapTile;
1753 override void setupTile () {
1757 override void doSprayRubble () {
1758   foreach (; 0..3) level.MakeMapObject(ix+global.randOther(0, 16), iy+global.randOther(0, 16), 'oDrip');
1762 override bool onExplosionTouch (MapObject xplo) {
1763   return false;
1767 final bool isWaterTile (MapTile t) { return t.water; }
1768 //final bool isSolidOrWaterTile (MapTile t) { return (t.water || t.solid); }
1771 void setupNiceTileSprite () {
1772   //if (argument1) sprite_index = sWater;
1773   bool up = false;
1774   bool upWater = false;
1775   bool down = false;
1776   bool left = false;
1777   bool right = false;
1779   if (level.checkTileAtPoint(ix, iy-16, &isWaterTile)) upWater = true;
1780   if (level.isSolidAtPoint(ix, iy-16)) up = true;
1781   if (level.isSolidAtPoint(ix, iy+16) && !level.checkTileAtPoint(ix, iy+16, &isWaterTile)) down = true;
1783   if (!up && !upWater) setSprite('sWaterTop');
1785   if (upWater && level.checkTileAtPoint(ix, iy-32, &isWaterTile) && down && global.randOther(1, 4) == 1) {
1786     setSprite('sWaterBottomTall2');
1787     auto water = level.checkTileAtPoint(ix, iy-16, &isWaterTile);
1788     if (water) water.setSprite('sWaterBottomTall1');
1789   } else if ((up || upWater) && down) {
1790     switch (global.randOther(1, 4)) {
1791       case 1: setSprite('sWaterBottom'); break;
1792       case 2: setSprite('sWaterBottom2'); break;
1793       case 3: setSprite('sWaterBottom3'); break;
1794       case 4: setSprite('sWaterBottom4'); break;
1795     }
1796   }
1800 override void beautifyTile () {
1801   setupNiceTileSprite();
1805 defaultproperties {
1806   objType = 'oWater';
1807   spriteName = 'sWater';
1808   water = true;
1809   solid = false;
1810   toSpecialGrid = false;
1814 // ////////////////////////////////////////////////////////////////////////// //
1815 class MapTileWaterSwim['oWaterSwim'] : MapTileWater;
1818 // ////////////////////////////////////////////////////////////////////////// //
1819 class MapTileLavaSolid['oLavaSolid'] : MapTile;
1821 override void setupTile () {
1824 override bool onExplosionTouch (MapObject xplo) {
1825   return false;
1828 defaultproperties {
1829   objType = 'oLavaSolid';
1830   spriteName = 'sLava';
1831   lava = true;
1832   solid = true;
1833   toSpecialGrid = false;
1837 // ////////////////////////////////////////////////////////////////////////// //
1838 class MapTileLushTrapBlock['oTrapBlock'] : MapTile;
1840 int deathTimer;
1841 bool dying;
1845 override void onDestroy () {
1846   if (!cleanDeath) {
1847     /*if (not cleanDeath and not global.cleanSolids)*/ {
1848       if (smashed) scrSprayRubble(ix, iy, 3, 'sRubbleTan', 'sRubbleTanSmall');
1849       scrDropRubble(ix, iy, 3, 'sRubbleTan', 'sRubbleTanSmall');
1850       if (dying) {
1851         playSound('sndThump');
1852         level.scrShake(10);
1853       }
1854     }
1855   }
1856   ::onDestroy();
1860 override void doSprayRubble () {
1861   if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
1862   scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
1863   if (global.cityOfGold == 1) {
1864     ore = 2;
1865     scrDropOre();
1866   }
1867   if (dying) {
1868     playSound('sndThump');
1869     level.scrShake(10);
1870   }
1874 override void setupTile () {
1878 void activate () {
1879   if (dying) return;
1880   if (distanceToEntityCenter(level.player) < 90) {
1881     dying = true;
1882   }
1886 override void thinkFrame () {
1887   if (dying) {
1888     if (deathTimer > 0) --deathTimer; else instanceRemove();
1889   }
1893 defaultproperties {
1894   objType = 'oLavaSolid';
1895   spriteName = 'sSkullBlock';
1896   solid = true;
1897   toSpecialGrid = true;
1898   //depth = ???;
1902 // ////////////////////////////////////////////////////////////////////////// //
1903 class MapTileLeaves['oLeaves'] : MapTile;
1905 bool dead;
1906 bool spriteSet;
1909 final bool isTreeOrLeaves (MapTile t) { return (t != self && t.tree || t.leaves); }
1913 override void onDestroy () {
1914   if (!cleanDeath) {
1915     if (spriteName != 'sLeavesDead' && spriteName != 'sLeavesDeadR') {
1916       level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1917       level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1918     }
1919   }
1920   ::onDestroy();
1924 override void doSprayRubble () {
1925   if (spriteName != 'sLeavesDead' && spriteName != 'sLeavesDeadR') {
1926     level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1927     level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1928   }
1932 override void setupTile () {
1933   spriteName = (global.cemetary ? 'sLeavesDead' : 'sLeaves');
1937 override void thinkFrame () {
1938   int x = ix, y = iy;
1940   if (!spriteSet) {
1941     spectral = true;
1942     if (level.checkTileAtPoint(x-16, y, &isTreeOrLeaves)) {
1943       setSprite(global.cemetary ? 'sLeavesDeadR' : 'sLeavesRight');
1944     } else if (level.checkTileAtPoint(x+16, y, &isTreeOrLeaves)) {
1945       setSprite(global.cemetary ? 'sLeavesDead' : 'sLeaves');
1946     }
1947     if (level.checkTileAtPoint(x-16, y, &isTreeOrLeaves) &&
1948         level.checkTileAtPoint(x+16, y, &isTreeOrLeaves))
1949     {
1950       setSprite('sLeavesTop');
1951     }
1952     if (dead) {
1953       if (level.checkTileAtPoint(x-16, y, &isTreeOrLeaves)) setSprite('sLeavesDeadR'); else setSprite('sLeavesDead');
1954     }
1955     spectral = false;
1956     spriteSet = true;
1957   }
1959   spectral = true;
1960   if (spriteName == 'sLeavesTop') {
1961     if (!level.checkTileAtPoint(x-16, y, &isTreeOrLeaves) ||
1962         !level.checkTileAtPoint(x+16, y, &isTreeOrLeaves))
1963     {
1964       instanceRemove();
1965     }
1966   } else if (spriteName == 'sLeaves' || spriteName == 'sLeavesDead') {
1967     if (!level.checkTileAtPoint(x+16, y, &isTreeOrLeaves)) instanceRemove();
1968   } else if (spriteName == 'sLeavesRight' || spriteName == 'sLeavesDeadR') {
1969     if (!level.checkTileAtPoint(x-16, y, &isTreeOrLeaves)) instanceRemove();
1970   }
1971   spectral = false;
1973   /*
1974   if (sprite_index == sLeavesDeadR) {
1975     desc = "Canopy";
1976     desc2 = "These leaves have died and withered.";
1977   } else {
1978     desc = "Canopy";
1979     desc2 = "The canopy of a proud tree.";
1980   }
1981   */
1985 defaultproperties {
1986   objType = 'oLeaves';
1987   desc = "Canopy";
1988   desc2 = "The top of a proud tree.";
1989   platform = true;
1990   solid = false;
1991   leaves = true;
1992   dead = false;
1993   spriteSet = false;
1994   toSpecialGrid = true;
1995   depth = 1;
1999 // ////////////////////////////////////////////////////////////////////////// //
2000 class MapTileTree['oTree'] : MapTile;
2002 bool burning;
2006 override void onDestroy () {
2007   if (!cleanDeath) {
2008     if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2009     scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2010   }
2011   ::onDestroy();
2015 override void doSprayRubble () {
2016   if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2017   scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2021 override void setupTile () {
2025 final bool isTreeTileAtPoint (int x, int y) {
2026   return !!level.checkTileAtPoint(x, y, delegate bool (MapTile t) { return (t.objType == 'oTree'); });
2030 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
2031   //if (argument1) sprite_index = sVine;
2033   bool up = isTreeTileAtPoint(ix, iy-16);
2034   //bool down = isTreeTileAtPoint(ix, iy+16);
2035   //bool left = isTreeTileAtPoint(ix-16, iy);
2036   //bool right = isTreeTileAtPoint(ix+16, yi);
2038   if (!up) {
2039     if (global.cemetary || getSprite().Name == 'sTreeTopDead') setSprite('sTreeTopDead'); else setSprite('sTreeTop');
2040     depth = 1;
2041   }
2045 override void beautifyTile () {
2046   setupNiceTileSprite();
2050 override void thinkFrame () {
2051   int x = ix, y = iy;
2053   if (!level.isSolidAtPoint(x, y+16)) { instanceRemove(); return; }
2054   if (level.isLavaAtPoint(x-16, y) || level.isLavaAtPoint(x+16, y)) { instanceRemove(); return; }
2058 defaultproperties {
2059   objType = 'oTree';
2060   desc = "Tree Trunk";
2061   desc2 = "The trunk of a proud tree.";
2062   spriteName = 'sTreeTrunk';
2063   solid = true;
2064   leaves = false;
2065   tree = true;
2066   burning = false;
2067   toSpecialGrid = true;
2068   depth = 2;
2072 // ////////////////////////////////////////////////////////////////////////// //
2073 class MapTileTreeBranch['oTreeBranch'] : MapTile;
2075 bool burning;
2076 bool dead;
2079 final bool isTree (MapTile t) { return (t != self && t.tree); }
2083 override void onDestroy () {
2084   if (!cleanDeath) {
2085     if (spriteName != 'sTreeBranchDeadL' && spriteName != 'sTreeBranchDeadR') {
2086       level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
2087     }
2088   }
2089   ::onDestroy();
2093 override void doSprayRubble () {
2094   if (spriteName != 'sTreeBranchDeadL' && spriteName != 'sTreeBranchDeadR') {
2095     level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
2096   }
2100 override void setupTile () {
2101   dead = false;
2103   auto ltree = level.checkTileAtPoint(ix-16, iy, &isTree);
2104   auto rtree = level.checkTileAtPoint(ix+16, iy, &isTree);
2106        if (ltree && !rtree) setSprite(global.cemetary ? 'sTreeBranchDeadR' : 'sTreeBranchRight');
2107   else if (!ltree && rtree) setSprite(global.cemetary ? 'sTreeBranchDeadL' : 'sTreeBranchLeft');
2109   //if (global.cemetary) spriteName = 'sTreeBranchDeadR';
2113 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
2114   //if (argument1) sprite_index = sVine;
2116   bool up = !!level.checkTileAtPoint(ix, iy-16, delegate bool (MapTile t) { return (t.objType == 'oLeaves'); });
2117   //if (collision_point(x, y+16, oTreeBranch, 0, 0)) { down = true; }
2118   //if (collision_point(x-16, y, oTreeBranch, 0, 0)) { left = true; }
2119   bool right = !!level.checkTileAtPoint(ix+16, iy, delegate bool (MapTile t) { return (t.objType == 'oTree'); });
2121   if (up) {
2122     instanceRemove();
2123     return;
2124   }
2126   if (right) {
2127     if (global.cemetary || getSprite().Name == 'sTreeBranchDeadR') setSprite('sTreeBranchDeadL'); else setSprite('sTreeBranchLeft');
2128   }
2132 override void beautifyTile () {
2133   //writeln("!!! ", getSprite().Name);
2134   setupNiceTileSprite();
2138 override void thinkFrame () {
2139   int x = ix, y = iy;
2141   spectral = true;
2142   auto ltree = level.checkTileAtPoint(ix-16, iy, &isTree);
2143   auto rtree = level.checkTileAtPoint(ix+16, iy, &isTree);
2144   if (!ltree && !rtree) {
2145     instanceRemove();
2147   } else {
2148          if (ltree && !rtree) setSprite('sTreeBranchRight');
2149     else if (!ltree && rtree) setSprite('sTreeBranchLeft');
2151   }
2152   spectral = false;
2156 defaultproperties {
2157   objType = 'oTree';
2158   desc = "Tree Branch";
2159   desc2 = "A slight but firm limb of a proud tree.";
2160   spriteName = 'sTreeBranchRight';
2161   platform = true;
2162   solid = false;
2163   leaves = false;
2164   tree = true;
2165   burning = false;
2166   toSpecialGrid = true;
2167   depth = 2;
2171 // ////////////////////////////////////////////////////////////////////////// //
2172 // only for dark levels
2173 class MapTileTikiTorch['oTikiTorch'] : MapTile;
2176 override int height () { return 32; }
2178 override void setupTile () {
2179   writeln("*** TIKI TORCH CREATED");
2182 override void thinkFrame () {
2183   lightRadius = 32+16+global.randOther(-8, 8);
2187 defaultproperties {
2188   objType = 'oTikiTorch';
2189   desc = "Torch";
2190   desc2 = "A moderately bright torch.";
2191   spriteName = 'sTikiTorch';
2192   platform = false;
2193   solid = false;
2194   leaves = false;
2195   tree = false;
2196   toSpecialGrid = true;
2197   depth = 1000;
2198   imageSpeed = 0.5;
2199   lightRadius = 32+16;
2203 // ////////////////////////////////////////////////////////////////////////// //
2204 class MapTileGiantTikiHead['oGiantTikiHead'] : MapTile;
2206 bool broken;
2209 void breakIt () {
2210   if (!broken) {
2211     broken = true;
2212     setSprite('sGTHHole');
2213   }
2217 override void setupTile () {
2221 override void thinkFrame () {
2225 defaultproperties {
2226   objType = 'oGiantTikiHead';
2227   invincible = true; //???
2228   solid = false;
2229   spriteName = 'sGiantTikiHead';
2230   toSpecialGrid = true; // it is HUGE
2231   depth = 1000;
2235 // ////////////////////////////////////////////////////////////////////////// //
2236 class MapTileThinIce['oThinIce'] : MapTile;
2238 int thickness = 60;
2240 override void setupTile () {
2244 override void thinkFrame () {
2245   if (level.player.isRectHitSimple(ix, iy-1, 17, 2)) {
2246     thickness -= 2;
2247     if (global.randOther(1, 100) == 1) level.MakeMapObject(ix+global.randOther(0, 16), iy+9, 'oDrip');
2248   }
2249        if (thickness > 50) setSprite('sThinIce1');
2250   else if (thickness > 40) setSprite('sThinIce2');
2251   else if (thickness > 30) setSprite('sThinIce3');
2252   else if (thickness > 20) setSprite('sThinIce4');
2253   else if (thickness > 10) setSprite('sThinIce5');
2254   else if (thickness > 0) setSprite('sThinIce6');
2255   else instanceRemove();
2259 defaultproperties {
2260   objType = 'oThinIce';
2261   solid = true;
2262   spriteName = 'sThinIce1';
2263   toSpecialGrid = true; // it must think
2267 // ////////////////////////////////////////////////////////////////////////// //
2268 class MapTileDarkFall['oDarkFall'] : MapTile;
2270 //int thickness = 60;
2271 int viscidTop = 1;
2272 int timeFall = 20;
2273 int timeFallMax = 20;
2274 bool falling;
2275 //float grav = 1;
2277 override int height () { return 8; }
2279 override void setupTile () {}
2282 //FIXME: viscidMovement -- ???
2283 override void thinkFrame () {
2284   //isCollisionCharacterTop(1)
2285   if (!falling) {
2286     auto plr = level.player;
2287     if (plr.isRectHitSimple(ix, iy-1, 17, 2)) {
2288       --timeFall;
2289     } else if (plr.status == MapObject::HANGING) {
2290       //writeln("checking for hang...");
2291       int tileX = ix/16;
2292       if (plr.isRectHitSimple((tileX-1)*16, iy, 16, 8) || plr.isRectHitSimple((tileX+1)*16, iy, 16, 8)) {
2293         //writeln("oDarkFall: HANGING! timer=", timeFall);
2294         --timeFall;
2295       }
2296     } else if (timeFall < timeFallMax) {
2297       ++timeFall;
2298     }
2299     if (timeFall <= 0) falling = true;
2300   }
2301   if (!falling) return;
2302   myGrav = grav;
2303   //if (yVel > 10) yVel = 10;
2304   //HACK: so player won't be able to push it
2305   moveable = true;
2306   ::thinkFrame();
2307   moveable = false;
2308   // dropped on solid?
2309   if (level.checkTilesInRect(ix, iy+height, width, 1)) {
2310     int x = ix, y = iy;
2311     instanceRemove();
2312     // not breaked on "Thwomp Trap" (we don't have it yet)
2313     playSound('sndBreak');
2314     level.MakeMapObject(x+8, y+8, 'oSmokePuff');
2315     foreach (; 0..3) {
2316       auto obj = level.MakeMapObject(x+global.randOther(2, 14), y-global.randOther(2, 8), 'oRubbleDark');
2317       if (obj) {
2318         obj.xVel = global.randOther(1, 3)-global.randOther(1, 3);
2319         obj.yVel = -global.randOther(0, 3);
2320       }
2321     }
2322   }
2326 defaultproperties {
2327   objType = 'oDarkFall';
2328   solid = true;
2330   viscidTop = 1;
2331   //setCollisionBounds(0, 0, 16, 8);
2333   grav = 1;
2334   myGravLimit = 10;
2335   //timeFall = 20;
2336   //timeFallMax = 20;
2337   desc = "Falling Platform";
2338   desc2 = "A thin, dark blue platform that will colapse if stood on for any length of time.";
2340   spriteName = 'sDarkFall';
2341   toSpecialGrid = true; // it must think
2342   depth = 1000;
2346 // ////////////////////////////////////////////////////////////////////////// //
2347 class MapTileAlienHull['oAlienShip'] : MapTile;
2350 override void setupTile () {
2351   //writeln("*********** ALIEN SHIP!");
2355 override void doSprayRubble () {
2356   if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2357   scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2361 final bool isAlienShipFloorCB (MapTile t) { return (t.objType == 'oAlienShip' || t.objType == 'oAlienShipFloor'); }
2362 final bool isASFTileAt (int x, int y) { return !!level.checkTileAtPoint(x, y, &isAlienShipFloorCB); }
2365 override void beautifyTile () {
2366   //if (argument1) sprite_index = sAlienTop;
2368   bool up = !!isASFTileAt(ix, iy-16);
2369   bool down = !!isASFTileAt(ix, iy+16);
2370   bool left = !!isASFTileAt(ix-16, iy);
2371   bool right = !!isASFTileAt(ix+16, iy);
2373   if (right && !left) {
2374          if (up && !down) setSprite('sAlienFront2');
2375     else if (down && !up) setSprite('sAlienFront3');
2376   }
2377   if (left && !right) {
2378     if (up) setSprite('sAlienBack2'); else if (down) setSprite('sAlienBack3');
2379   }
2383 defaultproperties {
2384   objType = 'oAlienShip';
2385   solid = true;
2386   spriteName = 'sAlienTop';
2387   toSpecialGrid = false;
2391 // ////////////////////////////////////////////////////////////////////////// //
2392 class MapTileAlienFloor['oAlienShipFloor'] : MapTileAlienHull;
2395 defaultproperties {
2396   objType = 'oAlienShipFloor';
2397   spriteName = 'sAlienFloor';
2401 // ////////////////////////////////////////////////////////////////////////// //
2402 class MapTileXocBlock['oXocBlock'] : MapTile;
2405 override void setupTile () {
2409 override void doSprayRubble () {
2410   foreach (; 0..3) {
2411     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');
2412     if (gold) {
2413       gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2414       gold.yVel = global.randOther(2, 4);
2415     }
2416   }
2417   {
2418     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');
2419     if (gold) {
2420       gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2421       gold.yVel = global.randOther(2, 4);
2422     }
2423   }
2427 defaultproperties {
2428   objType = 'oXocBlock';
2429   solid = true;
2430   spriteName = 'sGoldBlock';
2431   toSpecialGrid = false;
2432   depth = 1000;
2436 // ////////////////////////////////////////////////////////////////////////// //
2437 class MapTileCeilingTrap['oCeilingTrap'] : MapTile;
2439 //int thickness = 60;
2440 int viscidTop = 1;
2441 //int timeFall = 20;
2442 //int timeFallMax = 20;
2443 //float grav = 1;
2444 int counter;
2445 bool sprung;
2448 enum {
2449   IDLE = 0,
2450   DROP = 1,
2451   WAIT = 2,
2452   RETURN = 3,
2455 int status;
2458 override void onAnimationLooped () {
2459   if (spriteName == 'sCeilingTrapS') setSprite('sCeilingTrap');
2463 override void doSprayRubble () {
2464   if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2465   scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2466   if (global.cityOfGold == 1) {
2467     ore = 2;
2468     scrDropOre();
2469   }
2473 override void setupTile () {
2474   if (global.cityOfGold == 1) spriteName = 'sGoldBlock';
2478 void activate () {
2479   if (status != IDLE) return;
2480   status = DROP;
2481   level.checkTilesInRect(ix-64, iy-64, 129, 129, delegate bool (MapTile t) {
2482     auto door = MapTileDoor(t);
2483     if (door) door.activate();
2484     return false;
2485   });
2489 //FIXME: viscidMovement -- ???
2490 override void thinkFrame () {
2491   if (status == IDLE) {
2492     // nothing
2493   } else if (status == DROP) {
2494     if (counter > 0) {
2495       --counter;
2496     } else {
2497       counter = 3;
2498       flty += 1;
2499     }
2500     yVel = 0;
2501     spectral = true;
2502     if (level.isSolidAtPoint(ix+8, iy+17)) status = WAIT;
2503     spectral = false;
2504     if (spriteName == 'sBlock' || spriteName == 'sGoldBlock') setSprite('sCeilingTrapS');
2505     // out-of-level check
2506     if (isOutsideOfLevel()) {
2507       cleanDeath = true;
2508       instanceRemove();
2509     }
2510   } else if (status == WAIT) {
2511     yVel = 0;
2512     spectral = true;
2513     if (isCollisionBottom(0)) flty -= 1;
2514     spectral = false;
2515   }
2518   if (status != IDLE) {
2519     // player
2520     auto plr = level.player;
2521     if (!plr.dead && !plr.invincible && !plr.isExitingSprite() && /*plr.collidesWith(self)*/ plr.isRectHitSimple(x0, y0, width, height+1)) {
2522       bool doCol = true;
2523       if (!plr.collidesWith(self)) {
2524         doCol = (plr.yVel < -0.01 && plr.y0 > y1);
2525       }
2526       if (doCol) {
2527         if (global.plife > 0) {
2528           global.plife -= global.config.crushDmg;
2529           if (global.plife <= 0 /*and isRealLevel()*/) level.addDeath(objName);
2530         }
2531         plr.spillBlood();
2532         plr.stunned = true;
2533         plr.stunTimer = 20;
2534         playSound('sndHurt');
2535       }
2536     }
2539     level.isObjectInRect(x0-1, y0-1, width+2, height+2, delegate bool (MapObject o) {
2540       if (o == self) return false;
2541       if (o isa EnemyTombLord) return false;
2543       // register hit only if we're moving onto a spikes
2544       if (!o.collidesWith(self)) {
2545         bool onTop = (o.yVel < -0.01 && o.y0 > y1);
2546         if (!onTop) return false;
2547         writeln("*** RUNNING ON TRAP SPIKES");
2548       }
2550       // damsel
2551       auto dms = MonsterDamsel(o);
2552       if (dms) {
2553         if (dms.dead || dms.status == MapObject::DEAD || dms.invincible) return false;
2554         if (dms.heldBy) dms.heldBy.holdItem = none;
2555         dms.hp -= global.config.crushDmg;
2556         dms.spillBlood();
2557         dms.status = MapObject::THROWN;
2558         dms.counter = dms.stunMax;
2559         dms.calm = false;
2560         //dms.damselDropped = true;
2561         dms.kissCount = min(1, dms.kissCount);
2562         playSound('sndDamsel');
2563         return false;
2564       }
2566       // enemy
2567       auto enemy = MapEnemy(o);
2568       if (enemy) {
2569         if (o.dead || o.status == MapObject::DEAD || o.invincible) return false;
2570         if (o.heldBy) o.heldBy.holdItem = none;
2571         enemy.hp -= global.config.crushDmg;
2572         enemy.spillBlood();
2573         playSound('sndHit');
2574         return false;
2575       }
2577       return false;
2578     }, castClass:MapEnemy);
2579   }
2583 defaultproperties {
2584   objType = 'oCeilingTrap';
2585   objName = 'Ceiling Trap';
2586   desc = "Ceiling Trap";
2587   desc2 = "This seemingly-innocuous block hides a vicious set of spikes.";
2589   solid = true;
2590   imageSpeed = 0.4;
2592   viscidTop = 1;
2593   //setCollisionBounds(0, 0, 16, 8);
2595   //moveable = true;
2596   grav = 1;
2597   myGravLimit = 10;
2598   counter = 3;
2600   status = IDLE;
2601   sprung = false;
2603   spriteName = 'sBlock';
2604   toSpecialGrid = true; // it must think
2605   depth = 1000;
2609 // ////////////////////////////////////////////////////////////////////////// //
2610 class MapTileTempleFake['oTempleFake'] : MapTile;
2612 int sleepTime = 4; // wait until the grid is filled
2614 override void doSprayRubble () {
2615   if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2616   scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2620 override void setupTile () {
2621   doCreateOre(60, 80, 100, 1200);
2625 override void thinkFrame () {
2626   if (sleepTime > 0) { --sleepTime; return; }
2627   //writeln("CHK!");
2628   auto door = level.checkTilesInRect(ix, iy, 16, 16, delegate bool (MapTile t) {
2629     if (t == self) return false;
2630     //writeln("fake temple: t(", GetClassName(t.Class), "); objName=", t.objName, "; objType=", t.objType, "; door=", t isa MapTileDoor);
2631     return (t isa MapTileDoor);
2632   });
2633   if (!door) {
2634     //writeln("!!!");
2635     auto bt = level.MakeMapTile(ix/16, iy/16, 'oTemple');
2636     if (bt) bt.copyOreFrom(self);
2637     instanceRemove();
2638     return;
2639   }
2640   /*
2641   if (!level.checkTileAtPoint(ix+8, iy+8, delegate bool (MapTile t) { return (t isa MapTileDoor); })) {
2642     writeln("!!!");
2643     level.MakeMapTile(ix/16, iy/16, 'oTemple');
2644     instanceRemove();
2645     return;
2646   }
2647   */
2651 defaultproperties {
2652   objType = 'oTemple';
2653   desc = "Temple Brick";
2654   desc2 = "This is the first brick you've seen that actually qualifies as a brick. It looks rather bland.";
2656   solid = true;
2657   moveable = false;
2658   spriteName = 'sTemple';
2659   toSpecialGrid = true; // it must think
2661   //invisible = true;
2663   depth = 60;
2667 // ////////////////////////////////////////////////////////////////////////// //
2668 class MapTileDoor['oDoor'] : MapTile;
2670 //int thickness = 60;
2671 int viscidTop = 1;
2672 int counter;
2673 bool sprung;
2676 enum {
2677   IDLE = 0,
2678   DROP = 1,
2679   WAIT = 2,
2680   RETURN = 3,
2682 int status;
2685 //setCollisionBounds(1, 0, 15, 32);
2686 override int x0 () { return round(fltx)+1; }
2687 override int width () { return 15; }
2688 override int height () { return 32; }
2691 override void doSprayRubble () {
2692   if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2693   scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2694   if (global.cityOfGold == 1) {
2695     ore = 2;
2696     scrDropOre();
2697   }
2701 override void setupTile () {
2705 void activate () {
2706   status = DROP;
2707   myGrav = grav;
2708   yVel = myGrav;
2709   shiftY(2);
2713 //FIXME: viscidMovement -- ???
2714 override void thinkFrame () {
2715   if (status == IDLE) {
2716     // nothing
2717   } else if (status == DROP) {
2718     yVel = fmin(yVel+myGrav, myGravLimit);
2719     //::thinkFrame();
2720     spectral = true;
2721     auto oldsolid = solid;
2722     solid = false;
2723     //writeln("DOOR GOING DOWN; yVel=", yVel, "; ix=", ix, "; iy=", iy, "; x0=", x0, "; y0=", y0, "; w=", width, "; h=", height);
2724     int newY = round(flty+yVel); //!!!
2725     // check if we need (and can) move down
2726     int w = width, hp = height;
2727     while (newY > iy) {
2728       // made non-solid temporarily
2729       //auto hasAnything = level.checkTilesInRect(x0, y0+hp, w, 1, &level.cbCollisionAnySolid);
2730       auto hasAnything = level.checkTilesInRect(x0, y0+hp, w, 1, delegate bool (MapTile t) {
2731         if (t == self) return false;
2732         return t.solid;
2733       });
2734       if (hasAnything) {
2735         //writeln("hit '", GetClassName(hasAnything.Class), "' (", hasAnything.objName, ":", hasAnything.objType, "): pos=(", hasAnything.ix, ",", hasAnything.iy, ")");
2736         // hit something, stop right there
2737         if (yVel > myGrav*3) playSound('sndThud');
2738         flty = iy;
2739         status = WAIT;
2740         yVel = 0;
2741         counter = 100;
2742         depth = 180;
2743         //writeln("DOOR STOP");
2744         break;
2745       }
2746       // move us
2747       shiftY(1);
2748     }
2749     spectral = false;
2750     solid = oldsolid;
2752     // out-of-level check
2753     if (flty > level.tilesHeight*16+16) {
2754       cleanDeath = true;
2755       instanceRemove();
2756     }
2757   } else if (status == WAIT) {
2758     yVel = 0;
2759     spectral = true;
2760     if (isCollisionBottom(0)) flty -= 1;
2761     spectral = false;
2762   }
2766 defaultproperties {
2767   objType = 'oDoor';
2768   solid = true;
2770   viscidTop = 1;
2771   //setCollisionBounds(0, 0, 16, 8);
2773   //moveable = true;
2774   grav = 0.6;
2775   myGravLimit = 6;
2776   counter = 0;
2778   status = IDLE;
2779   sprung = false;
2781   desc = "Wall Trap";
2782   desc2 = "The inside of this block carries an extendable wall.";
2784   spriteName = 'sDoor';
2785   toSpecialGrid = true; // it must think
2786   depth = 2000;
2790 // ////////////////////////////////////////////////////////////////////////// //
2791 class MapTileSmashTrap['oSmashTrap'] : MapTile;
2793 //int thickness = 60;
2794 int viscidTop = 1;
2796 int counter;
2797 bool hit = false;
2799 float xv, yv;
2800 float xa, ya;
2803 enum {
2804   IDLE = 0,
2805   ATTACK = 1,
2806   DEAD = 99,
2809 int status;
2811 enum {
2812   RIGHT = 0,
2813   DOWN = 1,
2814   LEFT = 2,
2815   UP = 3,
2818 int dir;
2821 final string dirName () {
2822   switch (dir) {
2823     case RIGHT: return "right";
2824     case LEFT: return "left";
2825     case UP: return "up";
2826     case DOWN: return "down";
2827   }
2828   return "fucked";
2832 //setCollisionBounds(1, 1, 15, 15);
2833 override int x0 () { return round(fltx)+1; }
2834 override int width () { return 15; }
2835 //override int height () { return 16; }
2838 override void doSprayRubble () {
2839   if (global.cityOfGold == 1) {
2840     foreach (; 0..3) {
2841       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');
2842       gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2843       gold.yVel = global.randOther(2, 4);
2844     }
2845     {
2846       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');
2847       gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2848       gold.yVel = global.randOther(2, 4);
2849     }
2850   } else {
2851     if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2852     scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2853   }
2857 override void setupTile () {
2858   // prevent smash trap from spawning in player range when level starts
2859   if (true /+global.config.bizarre /*or isRealLevel()*/ +/) {
2860     if (level.calcNearestEnterDist(ix, iy) < 90) {
2861       cleanDeath = true;
2862       instanceRemove();
2863       return;
2864     }
2865   }
2867   if (global.cityOfGold == 1) setSprite('sSmashTrapGold');
2868   dir = global.randRoom(0, 3);
2872 //FIXME: viscidMovement -- ???
2873 override void thinkFrame () {
2874   spectral = true;
2876   if (status == IDLE) {
2877     auto plr = level.player;
2878     auto dist = pointDistance(ix, iy, plr.ix, plr.iy);
2879     if (counter > 0) --counter;
2880     if (dist < 90 && counter < 1) {
2881       if (abs(plr.iy-(iy+8)) < 8 && plr.ix > ix+8 && !isCollisionRight(2)) {
2882         status = ATTACK;
2883         dir = RIGHT;
2884         xa = 0.5;
2885       } else if (abs(plr.ix-(ix+8)) < 8 && plr.iy > iy+8 && !isCollisionBottom(2)) {
2886         status = ATTACK;
2887         dir = DOWN;
2888         ya = 0.5;
2889       } else if (abs(plr.iy-(iy+8)) < 8 && plr.ix < ix+8 && !isCollisionLeft(2)) {
2890         status = ATTACK;
2891         dir = LEFT;
2892         xa = -0.5;
2893       } else if (abs(plr.ix-(ix+8)) < 8 && plr.iy < iy+8 && !isCollisionTop(2)) {
2894         status = ATTACK;
2895         dir = UP;
2896         ya = -0.5;
2897       }
2898     }
2899   } else if (status == ATTACK) {
2900     bool colLeft = !!isCollisionLeft(1);
2901     bool colRight = !!isCollisionRight(1);
2902     bool colTop = !!isCollisionTop(1);
2903     bool colBot = !!isCollisionBottom(1);
2905     xv = fclamp(xv+xa, -4, 4);
2906     yv = fclamp(yv+ya, -4, 4);
2907     shiftXY(xv, yv);
2908     if (dir == RIGHT) {
2909       if (isCollisionRight(2) && colRight) { shiftX(-2); hit = true; }
2910       if (colRight) { shiftX(-1); hit = true; }
2911     } else if (dir == DOWN) {
2912       if (isCollisionBottom(2) && colBot) { shiftY(-2); hit = true; }
2913       if (colBot) { shiftY(-1); hit = true; }
2914     } else if (dir == LEFT) {
2915       if (isCollisionLeft(2) && colLeft) { shiftX(2); hit = true; }
2916       if (colLeft) { shiftX(1); hit = true; }
2917     } else if (dir == UP) {
2918       if (isCollisionTop(2) && colTop) { shiftY(2); hit = true; }
2919       if (colTop) { shiftY(1); hit = true; }
2920     }
2922     if (level.isObjectInRect(ix-1, iy-1, 18, 18, delegate bool (MapObject o) { return (o isa EnemyTombLord); })) hit = true;
2924     if (hit) {
2925       xv = 0;
2926       yv = 0;
2927       xa = 0;
2928       ya = 0;
2929     }
2931     /*
2932     if (hit) {
2933       auto ct = isCollision();
2934       writeln("dir=", dirName(), "; colLeft=", colLeft, "; colRight=", colRight, "; colTop=", colTop, "; colBot=", colBot, "; col=", (ct ? GetClassName(ct.Class) : '<none>'), " (", (ct ? ct.objName : '<>'), ")");
2935     }
2936     */
2938     if (hit && !isCollision() /*!colRight && !colLeft && !colTop && !colBot*/) {
2939       status = IDLE;
2940       hit = false;
2941       counter = 50;
2942     }
2943   } else if (status == DEAD) {
2944     xv = 0;
2945     yv = 0;
2946     xa = 0;
2947     ya = 0;
2948     shiftY(0.05);
2949     if (level.isLavaAtPoint(ix, iy-1)) { instanceRemove(); return; }
2950   }
2952   if (level.checkTilesInRect(ix+1, iy+1, 15, 15, &level.cbCollisionLava)) status = DEAD;
2954   spectral = false;
2956   // player
2957   auto plr = level.player;
2958   if (!plr.dead && !plr.invincible && !plr.isExitingSprite() && /*plr.collidesWith(self)*/ plr.isRectHitSimple(x0-1, y0-1, width+2, height+2)) {
2959     bool doCol = true;
2960     if (!plr.collidesWith(self)) {
2961       bool onLeft = (plr.xVel < -0.01 && plr.x0 > x1);
2962       bool onRight = (plr.xVel > 0.01 && plr.x1 < x0);
2963       bool onTop = (plr.yVel < -0.01 && plr.y0 > y1);
2964       bool onBottom = (plr.yVel > 0.01 && plr.y1 < y0);
2965       if (!onLeft && !onRight && !onTop && !onBottom) {
2966         doCol = false;
2967       } else {
2968         writeln("*** PLAYER RUNNING ON TRAP SPIKES");
2969       }
2970     }
2971     if (doCol) {
2972       if (global.plife > 0) {
2973         global.plife -= global.config.crushDmg;
2974         if (global.plife <= 0 /*and isRealLevel()*/) level.addDeath(objName);
2975         plr.spillBlood();
2976         plr.stunned = true;
2977         plr.stunTimer = 20;
2978         playSound('sndHurt');
2979       }
2980     }
2981   }
2983   level.isObjectInRect(x0-1, y0-1, width+2, height+2, delegate bool (MapObject o) {
2984     if (o == self) return false;
2985     if (o isa EnemyTombLord) return false;
2987     //if (o !isa MapEnemy) return false;
2989     // register hit only if we're moving onto a spikes
2990     if (!o.collidesWith(self)) {
2991       bool onLeft = (o.xVel < -0.01 && o.x0 > x1);
2992       bool onRight = (o.xVel > 0.01 && o.x1 < x0);
2993       bool onTop = (o.yVel < -0.01 && o.y0 > y1);
2994       bool onBottom = (o.yVel > 0.01 && o.y1 < y0);
2995       if (!onLeft && !onRight && !onTop && !onBottom) return false;
2996       writeln("*** RUNNING ON TRAP SPIKES");
2997     }
2999     // damsel
3000     auto dms = MonsterDamsel(o);
3001     if (dms) {
3002       if (!dms.invincible) {
3003         if (dms.heldBy) dms.heldBy.holdItem = none;
3004         dms.hp -= global.config.crushDmg;
3005         dms.spillBlood();
3006         dms.status = MapObject::THROWN;
3007         dms.counter = dms.stunMax;
3008         dms.calm = false;
3009         //dms.damselDropped = true;
3010         dms.kissCount = min(1, dms.kissCount);
3011         playSound('sndDamsel');
3012         return false;
3013       }
3014     }
3016     // enemy
3017     auto enemy = MapEnemy(o);
3018     if (enemy) {
3019       if (o.dead || o.status == DEAD || o.invincible) return false;
3020       if (o.heldBy) o.heldBy.holdItem = none;
3021       enemy.hp -= global.config.crushDmg;
3022       enemy.spillBlood();
3023       playSound('sndHit');
3024       return false;
3025     }
3027     return false;
3028   }, castClass:MapEnemy);
3032 defaultproperties {
3033   objType = 'oSmashTrap';
3034   objName = 'Smash Trap';
3035   desc = "Smash Trap";
3036   desc2 = "This trap carries an array of extendable blades. It can chase down intruders horizontally and vertically.";
3037   solid = true;
3038   viscidTop = 1;
3039   imageSpeed = 0.4;
3041   xv = 0;
3042   yv = 0;
3043   xa = 0;
3044   ya = 0;
3046   xVel = 0;
3047   yVel = 0;
3048   //xAcc = 0;
3049   //yAcc = 0;
3051   //moveable = true;
3052   grav = 1;
3053   myGravLimit = 10;
3054   counter = 3;
3056   status = IDLE;
3058   spriteName = 'sSmashTrap';
3059   toSpecialGrid = true; // it must think
3060   depth = 60;
3064 // ////////////////////////////////////////////////////////////////////////// //
3065 class MapTileSmashTrapLit['oSmashTrapLit'] : MapTileSmashTrap;
3067 defaultproperties {
3068   spriteName = 'sSmashTrapLit';
3069   lightRadius = 32;
3073 // ////////////////////////////////////////////////////////////////////////// //
3074 class MapTileGoldDoor['oGoldDoor'] : MapTile;
3076 enum {
3077   CLOSED,
3078   SCEPTRE,
3081 int status;
3084 override void doSprayRubble () {
3088 override void setupTile () {
3089   writeln("GENERATED GOLD DOOR");
3093 // it opens if player carrying a sceptre, and has a crown
3094 override void thinkFrame () {
3095   // early exits
3096   if (status == SCEPTRE && !global.hasCrown) return;
3097   auto plr = level.player;
3098   if (plr.holdItem !isa ItemWeaponSceptre) return;
3099   // check for collision
3100   if (plr.collidesWith(self)) {
3101     if (global.hasCrown) {
3102       // take sceptre away
3103       auto it = plr.holdItem;
3104       plr.holdItem = none;
3105       it.instanceRemove();
3106       playSound('sndChestOpen');
3107       level.MakeMapTile(ix/16, iy/16, 'oXGold');
3108       auto obj = level.MakeMapObject(ix-4, iy+6, 'oPoof');
3109       if (obj) obj.xVel = -0.4;
3110       obj = level.MakeMapObject(ix+16+4, iy+6, 'oPoof');
3111       if (obj) obj.xVel = 0.4;
3112       instanceRemove();
3113       //level.osdMessage("THE MYSTERIOUS DOOR IS UNLOCKED!", 3.33);
3114       level.osdMessageTalk("THE MYSTERIOUS DOOR IS UNLOCKED!", timeout:3.33, inShopOnly:false);
3115       return;
3116     }
3117     // no crown
3118     status = SCEPTRE;
3119     level.osdMessage("THE SCEPTRE FITS...\nBUT NOTHING IS HAPPENING!", 3.33);
3120   }
3124 defaultproperties {
3125   objType = 'oGoldDoor';
3126   desc = "Gold Door";
3127   desc2 = "A door with a golden seal on it.";
3129   status = CLOSED;
3131   solid = false;
3132   invincible = true;
3133   moveable = false;
3134   spriteName = 'sGoldDoor';
3135   toSpecialGrid = true; // it must think
3137   depth = 2000;
3141 // ////////////////////////////////////////////////////////////////////////// //
3142 class MapTileUnlockedGoldDoor['oXGold'] : MapTile;
3145 override void doSprayRubble () {
3149 override void setupTile () {
3153 // it opens if player carrying a sceptre, and has a crown
3154 override void thinkFrame () {
3158 defaultproperties {
3159   objType = 'oXGold';
3160   desc = "Gold Door";
3161   desc2 = "A door with an opened golden seal on it.";
3163   solid = false;
3164   invincible = true;
3165   moveable = false;
3166   spriteName = 'sExit';
3167   toSpecialGrid = false; // it must think
3168   exit = true;
3170   depth = 2000;
3174 // ////////////////////////////////////////////////////////////////////////// //
3175 class MapTileBlackMarketDoor['oXMarket'] : MapTile;
3178 override void doSprayRubble () {
3182 override void setupTile () {
3183   writeln("*** GENERATED BLACK MARKET ENTRANCE ***");
3184   /*
3185   if (global.hasSpectacles || global.hasUdjatEye) {
3186     level.setTileAt(ix/16, iy/16, none);
3187     //depth = 101;
3188   }
3189   */
3193 // it opens if player carrying a sceptre, and has a crown
3194 override void thinkFrame () {
3198 defaultproperties {
3199   objType = 'oXMarket';
3200   desc = "Market Exit";
3201   desc2 = "A door leading to Black Market.";
3203   solid = false;
3204   invincible = true;
3205   moveable = false;
3206   spriteName = 'sExit';
3207   toSpecialGrid = true; // it must be hidden behind the normal tile
3208   exit = true;
3210   depth = 2000;
3214 // ////////////////////////////////////////////////////////////////////////// //
3215 class MapTileMoai['oMoai'] : MapTile;
3217 override int width () { return 16; }
3218 override int height () { return 64; }
3220 override void doSprayRubble () {}
3221 override void setupTile () {}
3222 override void thinkFrame () {}
3224 defaultproperties {
3225   objType = 'oMoai';
3226   desc = "Moai Head";
3227   desc2 = "";
3229   solid = true;
3230   invincible = true;
3231   moveable = false;
3232   spriteName = 'sMoai';
3233   toSpecialGrid = true; // it is big
3234   depth = 1000;
3238 // ////////////////////////////////////////////////////////////////////////// //
3239 class MapTileMoai3['oMoai3'] : MapTileMoai;
3241 defaultproperties {
3242   spriteName = 'sMoai3';
3246 // ////////////////////////////////////////////////////////////////////////// //
3247 class MapTileMoaiInside['oMoaiInside'] : MapTile;
3249 override int width () { return 16; }
3250 override int height () { return 48; }
3252 override void doSprayRubble () {}
3253 override void setupTile () {}
3254 override void thinkFrame () {}
3256 defaultproperties {
3257   objType = 'oMoaiInside';
3258   desc = "Moai Head";
3259   desc2 = "";
3261   solid = true;
3262   invincible = true;
3263   moveable = false;
3264   spriteName = 'sMoaiInside';
3265   toSpecialGrid = true; // it is big
3266   depth = 92;
3270 // ////////////////////////////////////////////////////////////////////////// //
3271 class MapTileLamp['oLamp'] : MapTile;
3273 bool redLamp;
3276 override bool onExplosionTouch (MapObject xplo) {
3277   int x = ix, y = iy;
3278   instanceRemove();
3279   //level.MakeMapObject(x+8, y+12, (redLamp ? 'oLampRedItem' : 'oLampItem'));
3280   return true;
3284 override void doSprayRubble () {
3285   if (global.cityOfGold == 1) {
3286     ore = 2;
3287     scrDropOre();
3288   }
3292 override void setupTile () {
3293   if (redLamp) spriteName = 'sLampRed';
3297 override void thinkFrame () {
3298   // 0: dark
3299   // 1: max light
3300   // 2: medium light
3301   float llev = (trunc(imageFrame) < 2 ? 0.0 : 2.0-imageFrame/2.0);
3302   lightRadius = 128+round(4*llev);
3303   //::thinkFrame();
3305   int x = ix, y = iy;
3306   // drop lamp item if it has no support
3307   if (!level.checkTilesInRect(x+5, y-1, 6, 1)) {
3308     instanceRemove();
3309     level.MakeMapObject(x+8, y+12, (redLamp ? 'oLampRedItem' : 'oLampItem'));
3310     return;
3311   }
3315 defaultproperties {
3316   objType = 'oLamp';
3317   desc = "Lamp";
3318   desc2 = "A bright, geothermal-powered lantern, with a stainless steel frame.";
3319   imageSpeed = 0.5;
3320   solid = false;
3321   moveable = false;
3322   spriteName = 'sLamp';
3323   toSpecialGrid = true; // it must think
3324   depth = 201;
3328 // ////////////////////////////////////////////////////////////////////////// //
3329 class MapTileLampRed['oLampRed'] : MapTileLamp;
3331 defaultproperties {
3332   desc2 = "A geothermal-powered lantern that emits red light. Often found in brothels.";
3333   redLamp = true;