"oDarkFall" fixes
[k8vacspelynky.git] / mapent / MapTile.vc
blob7c8fa18855911e1c9ec9a6b1051a067a5c8844ca
1 /**********************************************************************************
2  * Copyright (c) 2008, 2009 Derek Yu and Mossmouth, LLC
3  * Copyright (c) 2018, Ketmar Dark
4  *
5  * This file is part of Spelunky.
6  *
7  * You can redistribute and/or modify Spelunky, including its source code, under
8  * the terms of the Spelunky User License.
9  *
10  * Spelunky is distributed in the hope that it will be entertaining and useful,
11  * but WITHOUT WARRANTY.  Please see the Spelunky User License for more details.
12  *
13  * The Spelunky User License should be available in "Game Information", which
14  * can be found in the Resource Explorer, or as an external file called COPYING.
15  * If not, please obtain a new copy of Spelunky from <http://spelunkyworld.com/>
16  *
17  **********************************************************************************/
18 class MapTile : MapEntity;
20 enum Width = 16;
21 enum Height = 16;
23 transient SpriteImage sprite;
24 name spriteName;
26 // object flags
27 bool solid = true;
28 bool invisible;
29 bool invincible;
30 bool water;
31 bool lava;
32 bool ice;
33 bool ladder;
34 bool laddertop;
35 bool tree;
36 bool leaves;
37 bool moveable;
38 bool spikes;
39 bool woodenSpikes;
40 bool enter;
41 bool exit;
42 bool springtrap;
43 bool altar;
44 bool sacrificingAltar;
45 bool shopWall; // may be set to `true` for non-solid tiles too
46 bool cleanDeath;
47 bool smashed;
48 bool bloody;
49 bool platform; // oLadderTop, oLeaves, oTreeBranch
50 bool toSpecialGrid;
51 bool border; // border tile
52 bool hideOre;
53 bool litWholeTile; // lit whole tile with maximum light
54 bool ignoreFrameOffsetX, ignoreFrameOffsetY;
55 bool dontReplaceOthers; // this tile won't replace existing tiles when put on a map
56 bool immuneToReplacement; // this tile immune to replacement by other tiles
57 int ore = 0;
59 transient bool waterMoved; // any move
60 transient bool waterMovedDown;
61 transient int waterSlideCounter;
62 transient int waterSlideOldX, waterSlideOldY;
65 //float myGrav = 0.6;
66 //float grav = 0.6; // the gravity
67 float myGravLimit = 8;
69 // x and y are offsets
70 MapBackTile bgback;
71 MapBackTile bgfront;
73 MapObject gem;
74 bool gemDestroyWithTile;
77 // for default doors
78 void snapToExit (MapEntity e) {
79   if (!e || !e.isInstanceAlive) return;
80   e.fltx = ix+8;
81   e.flty = iy+8;
82   e.updateGrid();
86 // ////////////////////////////////////////////////////////////////////////// //
87 override void Destroy () {
88   delete gem;
90   while (bgback) {
91     auto t = bgback;
92     bgback = t.next;
93     delete t;
94   }
96   while (bgfront) {
97     auto t = bgfront;
98     bgfront = t.next;
99     delete t;
100   }
102   ::Destroy();
106 override void onLoaded () {
107   ::onLoaded();
108   if (spriteName) sprite = level.sprStore[spriteName];
109   for (MapBackTile bt = bgback; bt; bt = bt.next) bt.onLoaded();
110   for (MapBackTile bt = bgfront; bt; bt = bt.next) bt.onLoaded();
111   if (gem) gem.onLoaded();
115 string getExitMessage () {
116   return "";
120 override SpriteImage getSprite (optional out bool doMirror) {
121   doMirror = false;
122   return sprite;
126 override SpriteFrame getSpriteFrame (optional out bool doMirror, optional out int x0, optional out int y0, optional out int x1, optional out int y1) {
127   auto spr = getSprite(doMirror!optional);
128   if (!spr || spr.frames.length == 0) return none;
129   auto spf = spr.frames[trunc(imageFrame)%spr.frames.length];
130   if (!spf) return none;
131   if (specified_x0 || specified_x1) {
132     x0 = (ignoreFrameOffsetX ? 0 : -spf.xofs);
133     x1 = x0+spf.tex.width;
134   }
135   if (specified_y0 || specified_y1) {
136     y0 = (ignoreFrameOffsetY ? 0 : -spf.yofs);
137     y1 = y0+spf.tex.height;
138   }
139   return spf;
143 override void clearSprite () {
144   sprite = none;
145   spriteName = '';
149 override void setSprite (name sprNameL, optional name sprNameR) {
150   if (!sprNameL && sprNameR) sprNameL = sprNameR;
151   if (spriteName != sprNameL) {
152     spriteName = sprNameL;
153     sprite = (sprNameL ? level.sprStore[sprNameL] : none);
154     imageFrame = 0;
155   }
159 // ////////////////////////////////////////////////////////////////////////// //
160 override int width () { return Width; }
161 override int height () { return Height; }
164 void beautifyTile () {}
165 void scrSetupBlockTop () {}
166 void scrSetupBlockBottom () {}
167 void scrSetupBlockLeft () {}
168 void scrSetupBlockRight () {}
171 // ////////////////////////////////////////////////////////////////////////// //
172 void setGem (name oname, optional bool visibility) {
173   if (gem) {
174     gem.ownerTile = none;
175     gem.instanceRemove();
176     gem = none;
177   }
178   if (!oname) return;
179   gem = level.MakeMapObject(ix+8, iy+8, oname);
180   if (!gem) return;
181   gem.ownerTile = self;
182   gem.hiddenTreasure = !global.hasSpectacles && !global.hasUdjatEye;
183   if (specified_visibility) gem.hiddenTreasure = !visibility;
184   gem.active = false;
185   gem.spectral = true;
186   if (gem.hiddenTreasure) gem.visible = false;
187   gem.saveInterpData();
191 void setGemObject (MapObject obj, optional bool visibility) {
192   if (obj) {
193     if (obj.ownerTile == self) {
194       if (gem != obj) FatalError("MapTile::setGemObject: WTF?! (0)");
195     }
196     if (gem == obj) FatalError("MapTile::setGemObject: WTF?! (1)");
197   }
198   if (gem) {
199     gem.ownerTile = none;
200     gem.instanceRemove();
201     if (!gem.grid) delete gem;
202     gem = none;
203   }
204   if (!obj) return;
205   if (obj.ownerTile) {
206     obj.ownerTile.gem = none;
207     obj.ownerTile = none;
208   }
209   gem = obj;
210   gem.ownerTile = self;
211   gem.hiddenTreasure = !global.hasSpectacles && !global.hasUdjatEye;
212   if (specified_visibility) gem.hiddenTreasure = !visibility;
213   gem.active = false;
214   gem.spectral = true;
215   if (gem.hiddenTreasure) gem.visible = false;
216   gem.fltx = ix+8;
217   gem.flty = iy+8;
218   gem.saveInterpData();
222 void convertGemObjectToDiamond (MapObject ghost) {
223   auto oldgem = gem;
224   if (oldgem && oldgem isa ItemBigGem) {
225     if (ghost) {
226       if (!ghost.isInstanceAlive) return;
227       if (!gem.isRectHitSimple(ghost.x0, ghost.y0, ghost.width, ghost.height)) return;
228     }
229     auto diamond = level.MakeMapObject(oldgem.ix, oldgem.iy, 'oDiamond');
230     setGemObject(diamond);
231   }
235 // ////////////////////////////////////////////////////////////////////////// //
236 void doCreateOre (int probSap, int probEmer, int probRuby, int probItem) {
237   int n = global.randRoom(1, 100);
238        if (n < 20) ore = 1; // sprite_index = sBrickGold;
239   else if (n < 30) ore = 2; // sprite_index = sBrickGoldBig;
240        if (probSap > 0 && global.randRoom(1, probSap) == 1) setGem('oSapphireBig');
241   else if (probEmer > 0 && global.randRoom(1, probEmer) == 1) setGem('oEmeraldBig');
242   else if (probRuby > 0 && global.randRoom(1, probRuby) == 1) setGem('oRubyBig');
243   else if (probItem > 0 && global.randRoom(1, probItem) == 1) setGemObject(level.scrGenerateItem(ix+8, iy+8, GameLevel::GenItemSet.Underground));
247 void copyOreFrom (MapTile t) {
248   if (t == self) return;
249   if (t) {
250     setGemObject(t.gem);
251     ore = t.ore;
252   } else {
253     setGemObject(none);
254     ore = 0;
255   }
259 void removeOre () {
260   ore = 0;
264 void closeExit () {
265   if (exit) setSprite('sEntrance');
269 void openExit () {
270   if (exit) setSprite('sExit');
274 bool isExitActive () {
275   return (visible && exit && spriteName == 'sExit');
279 // ////////////////////////////////////////////////////////////////////////// //
280 void setupTile () {
281   //hangeable = true;
282   switch (objName) {
283     case 'oLadderOrange':
284       //objName = 'oLadder';
285       //goto case 'oLadder';
286     case 'oLadder':
287       objType = 'oLadder';
288       ladder = true;
289       solid = false;
290       spriteName = 'sLadder';
291       break;
292     case 'oLadderTop':
293       objType = 'oLadder';
294       //ladder = true; // no, laddertop is not a ladder
295       laddertop = true;
296       platform = true;
297       solid = false;
298       spriteName = 'sLadderTop';
299       break;
300     case 'oVineTop':
301       objType = 'oVineTop';
302       //ladder = true; // no, laddertop is not a ladder
303       laddertop = true;
304       platform = true;
305       solid = false;
306       spriteName = 'sVineTop';
307       break;
308     case 'oPushBlock':
309       //objName = 'oPushBlock';
310       objType = 'oBlock';
311       moveable = true;
312       spriteName = (global.cityOfGold == 1 ? 'sGoldBlock' : 'sBlock');
313       break;
314     case 'oPushIceBlock':
315       //objName = 'oPushBlock';
316       objType = 'oBlock';
317       ice = true;
318       moveable = true;
319       spriteName = 'sIceBlock';
320       break;
321     case 'oSolidIceBlock':
322       //objName = 'oIceBlock';
323       //goto case 'oIceBlock';
324       objType = 'oBlock';
325       ice = true;
326       solid = true;
327       moveable = false;
328       moveable = true; //k8: why, let it be an easter egg
329       spriteName = 'sIceBlock';
330       break;
331     case 'oIceBlock':
332       objType = 'oBlock';
333       ice = true;
334       solid = true;
335       moveable = true;
336       spriteName = 'sIceBlock';
337       break;
338     case 'oEdgeBrick':
339       // no sense to create ore here
340       objType = 'oBrick';
341       spriteName = (global.randRoom(1, 10) == 1 ? 'sBrick2' : 'sBrick');
342       break;
343     case 'oBrickSmooth':
344       objType = 'oBrick';
345       spriteName = 'sCaveSmooth';
346       break;
347     case 'oSpikesWood':
348       objType = 'oSpikes';
349       solid = false;
350       spikes = true;
351       woodenSpikes = true;
352       spriteName = 'sSpikesWood';
353       break;
354     case 'oSpikes':
355       objType = 'oSpikes';
356       solid = false;
357       spikes = true;
358       spriteName = 'sSpikes';
359       break;
360     case 'oEntrance':
361       objType = 'oEntrance';
362       enter = true;
363       solid = false;
364       invincible = true;
365       depth = 2000;
366       spriteName = 'sEntrance';
367       break;
368     case 'oExit':
369       objType = 'oExit';
370       solid = false;
371       invincible = true;
372       exit = true;
373       depth = 2000;
374       spriteName = 'sExit';
375       break;
376     case 'oAltarLeft':
377       objType = 'oAltar';
378       altar = true;
379       spriteName = 'sAltarLeft';
380       break;
381     case 'oAltarRight':
382       objType = 'oAltar';
383       altar = true;
384       spriteName = 'sAltarRight';
385       break;
386     case 'oSacAltarLeft':
387       objType = 'oAltar';
388       altar = true;
389       sacrificingAltar = true;
390       spriteName = 'sSacAltarLeft';
391       break;
392     case 'oSacAltarRight':
393       objType = 'oAltar';
394       altar = true;
395       sacrificingAltar = true;
396       spriteName = 'sSacAltarRight';
397       break;
398     case 'oSign':
399       objType = 'oSign';
400       solid = false;
401       spriteName = 'sSign';
402       break;
403     case 'oSignGeneral':
404       objType = 'oSign';
405       solid = false;
406       spriteName = 'sSignGeneral';
407       break;
408     case 'oSignBomb':
409       objType = 'oSign';
410       solid = false;
411       spriteName = 'sSignBomb';
412       break;
413     case 'oSignWeapon':
414       objType = 'oSign';
415       solid = false;
416       spriteName = 'sSignWeapon';
417       break;
418     case 'oSignClothing':
419       objType = 'oSign';
420       solid = false;
421       spriteName = 'sSignClothing';
422       break;
423     case 'oSignRare':
424       objType = 'oSign';
425       solid = false;
426       spriteName = 'sSignRare';
427       break;
428     case 'oSignCraps':
429       objType = 'oSign';
430       solid = false;
431       spriteName = 'sSignCraps';
432       break;
433     case 'oSignKissing':
434       objType = 'oSign';
435       solid = false;
436       spriteName = 'sSignKissing';
437       break;
438     /*
439     case 'oMoai':
440       objType = 'oMoai';
441       solid = true;
442       invincible = true;
443       spriteName = 'sMoai';
444       break;
445     */
446     case 'oMoai2':
447       objType = 'oMoai';
448       solid = true;
449       invincible = true;
450       spriteName = 'sMoai2';
451       break;
452     /*
453     case 'oMoai3':
454       objType = 'oMoai';
455       solid = true;
456       invincible = true;
457       spriteName = 'sMoai3';
458       break;
459     */
460     /*
461     case 'oMoaiInside':
462       objType = 'oMoaiInside';
463       solid = true;
464       invincible = true;
465       spriteName = 'sMoaiInside';
466       depth = 92;
467       break;
468     */
469     default:
470       FatalError(va("unknown map tile type '%n'", objName));
471   }
472   //!active = moveable || toSpecialGrid || lava || water; // will be done in MakeMapTile
473   //if (!solid) hangeable = false; // just in case
477 void setupTileSprite () {
478   if (!spriteName) FatalError("forgot to set sprite for tile type '", objName, "'");
479   sprite = level.sprStore[spriteName];
483 override bool initialize () {
484   if (!::initialize()) return false;
485   setupTile();
486   setupTileSprite();
487   if (moveable && depth == 1001) depth = 1000;
488   if ((exit || enter) && depth == 1001) depth = 2000;
489   if (lava || water) {
490     ++level.liquidTileCount;
491     level.checkWater = true;
492   }
493   // will be done in MakeMapTile
494   /*
495   // animated tiles must be active
496   if (!active) {
497     auto spr = getSprite();
498     if (spr && spr.frames.length > 1) {
499       writeln("activated animated tile '", objName, "'");
500       active = true;
501     }
502   }
503   */
504   return true;
508 // ////////////////////////////////////////////////////////////////////////// //
509 // for now, it works only for spikes
510 final void makeBloody () {
511   if (bloody) return;
512   if (!spikes) return;
513   bloody = true;
514   setSprite(woodenSpikes ? 'sSpikesWoodBlood' : 'sSpikesBlood');
518 // ////////////////////////////////////////////////////////////////////////// //
519 final void appendBackBack (MapBackTile tile, optional int yofs) {
520   if (!tile) return;
521   if (tile.next) FatalError("MapTile::appendBackBack: wtf?!");
522   tile.flty = (specified_yofs ? float(yofs) : 16.0);
523   MapBackTile last = bgback;
524   if (last) {
525     while (last.next) last = last.next;
526     last.next = tile;
527   } else {
528     bgback = tile;
529   }
533 final void appendBackFront (MapBackTile tile, optional int yofs) {
534   if (!tile) return;
535   if (tile.next) FatalError("MapTile::appendBackFront: wtf?!");
536   tile.flty = (specified_yofs ? float(yofs) : -16.0);
537   MapBackTile last = bgfront;
538   if (last) {
539     while (last.next) last = last.next;
540     last.next = tile;
541   } else {
542     bgfront = tile;
543   }
547 // ////////////////////////////////////////////////////////////////////////// //
549 override void onDestroy () {
554 // ////////////////////////////////////////////////////////////////////////// //
555 override void thinkFrame () {
556   if (!moveable) return;
557   // applies the acceleration
558   //xVel += xAcc;
559   //yVel += yAcc;
561   // approximates the "active" variables
562   if (fabs(xVel) < 0.001) xVel = 0;
563   if (fabs(yVel) < 0.001) yVel = 0;
564   //if (fabs(xAcc) < 0.0001) xAcc = 0;
565   //if (fabs(yAcc) < 0.0001) yAcc = 0;
567   yVel = fclamp(yVel+myGrav, -myGravLimit, myGravLimit);
568   int newY = round(flty+yVel); //!!!
569   // check if we need (and can) move down
570   int w = width, hp1 = height+1;
571   auto oldsolid = solid;
572   solid = false;
573   while (newY > iy) {
574     // made non-solid temporarily
575     auto hasAnything = level.checkTilesInRect(x0, y0, w, hp1, delegate bool (MapTile t) {
576       if (t == self) return false;
577       return t.solid;
578     });
579     if (hasAnything) {
580       // hit something, stop right there
581       if (yVel > myGrav*3) playSound('sndThud');
582       yVel = 0;
583       flty = iy;
584       break;
585     }
586     // move us
587     shiftY(1);
588   }
589   solid = oldsolid;
591   if (flty > level.tilesHeight*16+16 && yVel >= 0) {
592     cleanDeath = true;
593     instanceRemove();
594   }
598 // ////////////////////////////////////////////////////////////////////////// //
599 final void getInterpCoordsForTile (float currFrameDelta, int scale, out int drwx, out int drwy) {
600   if (waterSlideCounter) {
601     --waterSlideCounter;
602     drwx = ix*scale;
603     drwy = iy*scale;
604     int sgnx = sign(waterSlideOldX-ix);
605     int sgny = sign(waterSlideOldY-iy);
606     if ((sgnx|sgny) == 0) {
607       waterSlideCounter = 0;
608     } else {
609       drwx += (sgnx*(waterSlideCounter*4))*scale;
610       drwy += (sgny*(waterSlideCounter*4))*scale;
611     }
612   } else {
613     getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
614   }
618 void drawWithOfsBack (int xpos, int ypos, int scale, float currFrameDelta) {
619   //if (backTile) backTile.drawAt(xpos, ypos, scale);
620   if (invisible || !visible || !bgback) return;
622   /*
623   bool doMirror;
624   int fx0, fy0, fx1, fy1;
625   auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
627   // non-moveable and non-special tiles need not to be interpolated
628   int drwx, drwy;
629   getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
630   */
632   for (MapBackTile bt = bgback; bt; bt = bt.next) {
633     bt.fltx += fltx;
634     bt.flty += flty;
635     bt.drawWithOfs(xpos, ypos, scale, currFrameDelta);
636     bt.fltx -= fltx;
637     bt.flty -= flty;
638   }
642 void drawWithOfsFront (int xpos, int ypos, int scale, float currFrameDelta) {
643   //if (backTile) backTile.drawAt(xpos, ypos, scale);
644   if (invisible || !visible || !bgfront) return;
646   /*
647   bool doMirror;
648   int fx0, fy0, fx1, fy1;
649   auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
651   // non-moveable tiles need not to be interpolated
652   int drwx, drwy;
653   getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
654   */
656   for (MapBackTile bt = bgfront; bt; bt = bt.next) {
657     bt.fltx += fltx;
658     bt.flty += flty;
659     bt.drawWithOfs(xpos, ypos, scale, currFrameDelta);
660     bt.fltx -= fltx;
661     bt.flty -= flty;
662   }
666 override void drawWithOfs (int xpos, int ypos, int scale, float currFrameDelta) {
667   if (invisible || !visible) return;
669   bool doMirror;
670   int fx0, fy0, fx1, fy1;
671   auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
673   // non-moveable tiles need not to be interpolated
674   int drwx, drwy;
675   getInterpCoordsForTile(currFrameDelta, scale, out drwx, out drwy);
677   //auto oclr = Video.color;
678   //if (moveable) Video.color = 0xff_7f_00;
680   if (spf) {
681     fx0 = drwx+fx0*scale-xpos;
682     fy0 = drwy+fy0*scale-ypos;
683     fx1 = drwx+fx1*scale-xpos;
684     fy1 = drwy+fy1*scale-ypos;
685     if (!doMirror) {
686       spf.tex.blitExt(fx0, fy0, fx1, fy1, 0, 0, spf.tex.width, spf.tex.height);
687     } else {
688       spf.tex.blitExt(fx0, fy0, fx1, fy1, spf.tex.width, 0, 0, spf.tex.height);
689     }
690   } else if (ore > 0) {
691     fx0 = drwx-xpos;
692     fy0 = drwy-ypos;
693     fx1 = drwx-xpos;
694     fy1 = drwy-ypos;
695   }
697   //Video.color = oclr;
699   if (ore > 0 && !hideOre) {
700     auto ospr = level.sprStore[ore == 1 ? 'sGold' : 'sGoldBig'];
701     ospr.frames[0].tex.blitAt(fx0, fy0, scale);
702   }
706 // ////////////////////////////////////////////////////////////////////////// //
707 // called by Destroy event of blocks that can contain ore (oBrick, oLush, etc)
708 void scrDropOre () {
709   if (ore < 1) return;
711   int x = ix, y = iy;
713   foreach (int i; 0..3) {
714     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');
715     if (gold) {
716       //gold = instance_create(x+8+rand(0,4)-rand(0,4), y+8+rand(0,4)-rand(0,4), oGoldChunk);
717       gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
718       gold.yVel = global.randOther(2, 4);
719     }
720   }
722   if (ore > 1) {
723     // sGoldBig
724     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');
725     if (gold) {
726       gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
727       gold.yVel = global.randOther(2, 4);
728     }
729   }
731   ore = 0;
735 // ////////////////////////////////////////////////////////////////////////// //
736 private final bool cbIsSolidBrick (MapTile t) { return t.solid; }
737 private final bool cbIsBrickOrLushTile (MapTile t) { return (t.objType == 'oBrick' || t.objType == 'oLush'); }
740 // ////////////////////////////////////////////////////////////////////////// //
741 // called by solids during destroy event (oBrick etc) when destroy with force such as a boulder or explosion
742 // these rubble bits shower over the foreground and are not stopped by solids
743 final void scrSprayRubble (int x, int y, int count, name spr0, name spr1) {
744   while (count-- > 0) {
745     auto rubble = level.MakeMapObject(
746                     x+8+global.randOther(0, 8)-global.randOther(0, 8),
747                     y+8+global.randOther(0, 8)-global.randOther(0, 8), 'oRubblePiece'); //'oRubblePieceNonSolid');
748     rubble.setSprite(global.randOther(1, 3) == 1 ? spr0 : spr1);
749     rubble.xVel = global.randOther(0, 4)-global.randOther(0, 4);
750     rubble.yVel = -global.randOther(4, 8);
751     rubble.yAcc = 0.6;
752     //rubble.image_blend = image_blend;
753   }
757 final void scrDropRubble (int x, int y, int count, name spr0, name spr1) {
758   while (count-- > 0) {
759     int rx = x+8+global.randOther(0, 8)-global.randOther(0, 8);
760     int ry = y+8+global.randOther(0, 8)-global.randOther(0, 8);
761     if (global.randOther(1, 3) == 1) {
762       auto rubble = level.MakeMapObject(rx, ry, 'oRubble');
763       rubble.setSprite(spr1);
764       //rubble.image_blend = image_blend;
765     } else {
766       auto rubble = level.MakeMapObject(rx, ry, 'oRubbleSmall');
767       rubble.setSprite(spr0);
768       //rubble.image_blend = image_blend;
769     }
770   }
774 void smashedCheckUpTile () {
775   level.checkTilesInRect(ix, iy-16, 16, 16, delegate bool (MapTile t) {
776     //writeln("mtl: '", GetClassName(t.Class), "'; spikes=", t.spikes);
777     if (t.isInstanceAlive && (t.spikes || t isa MapTileGraveBase)) t.instanceRemove();
778     return false;
779   });
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;
907 private final bool cbExplodeTiles (MapTile t) {
908   if (!t.isInstanceAlive) return false;
909   if (t.invincible) return false;
910   switch (t.objType) {
911     case 'oSpikes':
912     case 'oTikiTorch':
913     case 'oGrave':
914     case 'oLampRed':
915     case 'oLamp':
916       t.onExplosionTouch(mXplo);
917       break;
918   }
919   return false;
923 override bool onExplosionTouch (MapObject xplo) {
924   if (invincible) {
925     //writeln("ignore inv block at (", ix/16, ",", iy/16, ")");
926     return false;
927   }
929   int x = ix, y = iy;
931   if (smashMe()) {
932     //writeln("smashed block at (", ix/16, ",", iy/16, ")");
933     auto oxplo = mXplo;
934     mXplo = xplo;
935     level.checkTileAtPoint(x+8, y-1, &cbExplodeTiles); //k8: why i wrote this?!
936     mXplo = oxplo;
938     /+
939     obj = instance_place(x, y, oGold);
940     if (obj != noone) with (obj) instance_destroy();
942     obj = instance_place(x, y, oGoldBig);
943     if (obj != noone) with (obj) instance_destroy();
944     +/
946     return true;
947   } else {
948     //writeln("cannot smash block at (", ix/16, ",", iy/16, ")");
949   }
951   return false;
955 // ////////////////////////////////////////////////////////////////////////// //
956 void onGotSpectacles () {
957   if (gem) {
958     gem.hiddenTreasure = false;
959     gem.visible = true;
960   }
964 defaultproperties {
965   objName = 'oTile';
966   objType = 'oSolid';
967   //depth = 9666; //???
968   depth = 1001;
972 // ////////////////////////////////////////////////////////////////////////// //
973 // this tile is returned instead of walkeable MapObject
974 class MapTileTemp : MapTile;
976 MapEntity e;
978 // don't forget to set `fltx` and `flty`!
979 override int x0 () { return (e ? e.x0 : 0); }
980 override int y0 () { return (e ? e.y0 : 0); }
981 override int width () { return (e ? e.width : 0); }
982 override int height () { return (e ? e.height : 0); }
984 defaultproperties {
985   objName = 'oMapObject';
986   objType = 'oMapObject';
987   active = false; // just in case
991 // ////////////////////////////////////////////////////////////////////////// //
992 class MapTileBrick['oBrick'] : MapTile;
995 override void setupTile () {
996   spriteName = (global.randRoom(1, 10) == 1 ? 'sBrick2' : 'sBrick');
997   int tileX = ix/16, tileY = iy/16;
998   if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
999     invincible = true;
1000     border = true;
1001     //writeln("BORDER");
1002   } else {
1003     doCreateOre(100, 120, 140, 1200);
1004   }
1008 override void beautifyTile () {
1009   if (level.checkTileAtPoint(ix+8, iy-8, delegate bool (MapTile t) { return (t isa TitleTileSpecialNonSolidInvincible || t isa TitleTileBlack); })) return;
1011   // brick
1012   //if (argument1) if (global.randRoom(1, 10) == 1) sprite_index = sBrick2; // reset sprite for custom border setup
1014   int tileX = ix/16, tileY = iy/16;
1016   auto tt = level.getTileAtGrid(tileX, tileY-1);
1017   if (tt && tt.objName == 'oBlock') tt = none;
1018   //if (y == 0 || isNamedTileAt(x, y-1, 'oBrick') || isNamedTileAt(x, y-1, 'oHardBlock') || isNamedTileAt(x, y-1, 'oEdgeBrick')) up = true;
1019   bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1021   tt = level.getTileAtGrid(tileX, tileY+1);
1022   //if (y == NormalTilesHeight-1 || isNamedTileAt(x, y+1, 'oBrick') || isNamedTileAt(x, y+1, 'oHardBlock') || isNamedTileAt(x, y+1, 'oEdgeBrick')) down = true;
1023   bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1025   //if (collision_point(x-16, y, oBrick, 0, 0) or collision_point(x-16, y, oHardBlock, 0, 0)) { left = true; } // in the original
1026   //if (collision_point(x+16, y, oBrick, 0, 0) or collision_point(x+16, y, oHardBlock, 0, 0)) { right = true; } // in the original
1028   if (!up) {
1029     setSprite('sCaveUp');
1030     // add rocks and sand
1031     /*
1032     if (global.randRoom(1, 3) < 3) {
1033       tile_add('bgCaveTop', 0, 0, 16, 16, x*16, y*16-16, 3);
1034     } else {
1035       tile_add('bgCaveTop', 16, 0, 16, 16, x*16, y*16-16, 3);
1036     }
1037     */
1038     bool n = (global.randRoom(1, 3) < 3);
1039     appendBackFront(level.CreateBgTile('bgCaveTop', (n ? 0 : 16)));
1040     //instance_create(x, y-16, oCaveTop);
1041   }
1043   if (!down) {
1044     setSprite(!up ? 'sCaveUp2' : 'sBrickDown');
1045     //instance_create(x, y+16, oCaveBottom);
1046   }
1048   /* in the original
1049   if (not left) instance_create(x-16, y, oCaveLeft);
1050   if (not right) instance_create(x+15, y, oCaveRight);
1051   */
1055 override void scrSetupBlockTop () {
1056   //int x = ix, y = iy;
1057   int tileX = ix/16, tileY = iy/16;
1058   auto tt = level.getTileAtGrid(tileX, tileY+1);
1059   //if (y >= (GameLevel::NormalTilesHeight-16)*16 || level.checkTileAtPoint(x, y+16, &cbIsBrickOrLushTile)) down = true;
1060   bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1061   // gfxhigh
1062   auto bt = level.CreateBgTile('bgCaveTop', (global.randRoom(1, 3) < 3 ? 0 : 16));
1063   //bt.flty = -16; // offset
1064   appendBackFront(bt);
1065   // other
1066   setSprite(down ? 'sCaveUp' : 'sCaveUp2');
1070 override void scrSetupBlockBottom () {
1071   //int x = ix, y = iy;
1072   //bool up = false;
1073   //if (y == 0 || level.checkTileAtPoint(x, y-16, &cbIsBrickOrLushTile)) up = true;
1074   int tileX = ix/16, tileY = iy/16;
1075   auto tt = level.getTileAtGrid(tileX, tileY-1);
1076   bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1077   setSprite(up ? 'sBrickDown' : 'sCaveUp2');
1081 defaultproperties {
1082   objType = 'oBrick';
1083   spriteName = 'sBrick';
1084   lava = false;
1085   solid = true;
1086   toSpecialGrid = false;
1090 // ////////////////////////////////////////////////////////////////////////// //
1091 class MapTileHardBrick['oHardBlock'] : MapTileBrick;
1093 override void setupTile () {
1097 defaultproperties {
1098   objType = 'oHardBlock';
1099   spriteName = 'sBrick';
1100   invincible = true;
1101   solid = true;
1105 // ////////////////////////////////////////////////////////////////////////// //
1106 class MapTileBlock['oBlock'] : MapTile;
1109 override void setupTile () {
1110   int tileX = ix/16, tileY = iy/16;
1111   if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1112     invincible = true;
1113     border = true;
1114     //writeln("BORDER");
1115   }
1116   if (global.cityOfGold == 1) spriteName = 'sGoldBlock';
1120 final bool isBBTTile (MapTile t) { return (t.objType == 'oBrick' || t.objType == 'oTemple' || t.objType == 'oHardBlock'); }
1123 override void beautifyTile () {
1124   bool down = !!level.checkTileAtPoint(ix, iy+16, &isBBTTile);
1126   // don't want push blocks next to lava until we tighten up liquid draining
1127   if (level.isLavaAtPoint(ix-16, iy) || level.isLavaAtPoint(ix+16, iy)) down = false;
1129   if (down && global.randRoom(1, 4) == 1) {
1130     instanceRemove();
1131     level.MakeMapTile(ix/16, iy/16, 'oPushBlock');
1132   }
1136 defaultproperties {
1137   objType = 'oBlock';
1138   spriteName = 'sBlock';
1139   solid = true;
1140   toSpecialGrid = false;
1144 // ////////////////////////////////////////////////////////////////////////// //
1145 class MapTileLush['oLush'] : MapTile;
1148 override void setupTile () {
1149   int tileX = ix/16, tileY = iy/16;
1150   if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1151     invincible = true;
1152     border = true;
1153     //writeln("BORDER");
1154   } else {
1155     doCreateOre(80, 100, 120, 1200);
1156   }
1160 override void beautifyTile () {
1161   if (level.checkTileAtPoint(ix+8, iy-8, delegate bool (MapTile t) { return (t isa TitleTileSpecialNonSolidInvincible || t isa TitleTileBlack); })) return;
1163   int tileX = ix/16, tileY = iy/16;
1165   auto tt = level.getTileAtGrid(tileX, tileY-1);
1166   //if (y == 0 || isNamedTileAt(x, y-1, 'oBrick') || isNamedTileAt(x, y-1, 'oHardBlock') || isNamedTileAt(x, y-1, 'oEdgeBrick')) up = true;
1167   bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral && (tt.objType == 'oLush' || tt.objType == 'oTemple')));
1169   tt = level.getTileAtGrid(tileX, tileY+1);
1170   //if (y == NormalTilesHeight-1 || isNamedTileAt(x, y+1, 'oBrick') || isNamedTileAt(x, y+1, 'oHardBlock') || isNamedTileAt(x, y+1, 'oEdgeBrick')) down = true;
1171   bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral && tt.objType == 'oLush'));
1173   if (!up) {
1174     setSprite('sLushUp');
1175     if (!level.isLavaAtPoint(ix+8, iy-8)) {
1176       MapBackTile bt;
1177            if (global.randRoom(1, 8) == 1) bt = level.CreateBgTile('bgCaveTop2', 32);
1178       else if (global.randRoom(1, 3) < 3) bt = level.CreateBgTile('bgCaveTop2', 0);
1179       else bt = level.CreateBgTile('bgCaveTop2', 16);
1180       appendBackFront(bt);
1181     }
1182   }
1184   if (!down) {
1185     setSprite(!up ? 'sLushUp2' : 'sLushDown');
1186     if (!level.isSolidAtPoint(ix, iy+16) && !level.isLavaAtPoint(ix, iy+16)) {
1187       MapBackTile bt;
1188            if (global.randRoom(1, 12) == 1) bt = level.CreateBgTile('bgCaveTop2', 48);
1189       else if (global.randRoom(1, 12) == 1) bt = level.CreateBgTile('bgCaveTop2', 64);
1190       appendBackBack(bt);
1191     }
1192     //instance_create(x, y+16, oLushBottom); // in the original
1193   }
1197 override void scrSetupBlockTop () {
1198   int tileX = ix/16, tileY = iy/16;
1199   auto tt = level.getTileAtGrid(tileX, tileY+1);
1200   bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1201   //setSprite('sLushUpBare');
1202   if (!down) {
1203     setSprite(spriteName == 'sLushUp' || spriteName == 'sLushUp2' || spriteName == 'sLushUp3' ? 'sLushUpBareTop' : 'sLushUpBare2');
1204   } else {
1205     setSprite('sLushUpBare');
1206   }
1210 override void scrSetupBlockBottom () {
1211   int tileX = ix/16, tileY = iy/16;
1212   auto tt = level.getTileAtGrid(tileX, tileY-1);
1213   bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1214   if (!up) {
1215     setSprite(spriteName == 'sLushUp' || spriteName == 'sLushUp2' || spriteName == 'sLushUp3' ? 'sLushUpBareBottom' : 'sLushUpBare2');
1216   } else {
1217     setSprite('sLushDownBare');
1218   }
1222 defaultproperties {
1223   objType = 'oLush';
1224   spriteName = 'sLush';
1225   lava = false;
1226   solid = true;
1227   toSpecialGrid = false;
1231 // ////////////////////////////////////////////////////////////////////////// //
1232 class MapTileDark['oDark'] : MapTile;
1235 override void setupTile () {
1236   int tileX = ix/16, tileY = iy/16;
1237   if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1238     invincible = true;
1239     border = true;
1240     //writeln("BORDER");
1241   } else {
1242     doCreateOre(40, 60, 80, 1200);
1243   }
1247 override void beautifyTile () {
1248   if (level.checkTileAtPoint(ix+8, iy-8, delegate bool (MapTile t) { return (t isa TitleTileSpecialNonSolidInvincible || t isa TitleTileBlack); })) return;
1250   // brick
1251   //if (argument1) if (global.randRoom(1, 10) == 1) sprite_index = sBrick2; // reset sprite for custom border setup
1253   int tileX = ix/16, tileY = iy/16;
1255   auto tt = level.getTileAtGrid(tileX, tileY-1);
1256   //if (y == 0 || isNamedTileAt(x, y-1, 'oBrick') || isNamedTileAt(x, y-1, 'oHardBlock') || isNamedTileAt(x, y-1, 'oEdgeBrick')) up = true;
1257   bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1259   tt = level.getTileAtGrid(tileX, tileY+1);
1260   //if (y == NormalTilesHeight-1 || isNamedTileAt(x, y+1, 'oBrick') || isNamedTileAt(x, y+1, 'oHardBlock') || isNamedTileAt(x, y+1, 'oEdgeBrick')) down = true;
1261   bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1263   //if (collision_point(x-16, y, oBrick, 0, 0) or collision_point(x-16, y, oHardBlock, 0, 0)) { left = true; } // in the original
1264   //if (collision_point(x+16, y, oBrick, 0, 0) or collision_point(x+16, y, oHardBlock, 0, 0)) { right = true; } // in the original
1266   if (!up) {
1267     setSprite('sDarkUp');
1268     bool n = (global.randRoom(1, 3) < 3);
1269     appendBackFront(level.CreateBgTile('bgCaveTop3', (n ? 0 : 16)));
1270   }
1272   if (!down) {
1273     setSprite(!up ? 'sDarkUp2' : 'sDarkDown');
1274   }
1278 override void scrSetupBlockTop () {
1279   int tileX = ix/16, tileY = iy/16;
1280   auto tt = level.getTileAtGrid(tileX, tileY+1);
1281   bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1282   auto bt = level.CreateBgTile('bgCaveTop3', (global.randRoom(1, 3) < 3 ? 0 : 16));
1283   appendBackFront(bt);
1284   //bt.flty = -16; // offset
1285   // other
1286   setSprite(down ? 'sDarkUp' : 'sDarkUp2');
1290 override void scrSetupBlockBottom () {
1291   int tileX = ix/16, tileY = iy/16;
1292   auto tt = level.getTileAtGrid(tileX, tileY-1);
1293   bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1294   setSprite(up ? 'sDarkDown' : 'sDarkUp2');
1298 defaultproperties {
1299   objType = 'oDark';
1300   spriteName = 'sDark';
1301   lava = false;
1302   solid = true;
1303   toSpecialGrid = false;
1307 // ////////////////////////////////////////////////////////////////////////// //
1308 class MapTileIce['oIce'] : MapTile;
1310 bool hasIceBottom;
1311 int dripTimer;
1313 final bool isIceTile (MapTile t) { return t.ice; }
1314 final MapTile isIceTileAtPoint (int x, int y) { return level.checkTileAtPoint(x, y, &isIceTile); }
1317 override void setupTile () {
1318   if (global.randRoom(1, 80) == 1) {
1319     setGem('oFrozenCaveman', true); // always visible
1320     gem.fltx = ix;
1321     gem.flty = iy;
1322     gem.saveInterpData();
1323     gemDestroyWithTile = true;
1324   }
1325   dripTimer = global.randOther(20, 400);
1326   //hasIceBottom = true;
1327   //dripTimer = 40;
1331 override void thinkFrame () {
1332   if (hasIceBottom) {
1333     if (--dripTimer <= 0) {
1334       //writeln("DRIP!");
1335       level.MakeMapObject(ix+8, iy+16+4, 'oDrip');
1336       dripTimer = global.randOther(20, 400);
1337       //dripTimer = 40;
1338     }
1339   }
1343 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1344   bool up = (specified_noup && noup ? false : !!isIceTileAtPoint(ix, iy-16));
1345   bool down = (specified_nodown && nodown ? false : !!isIceTileAtPoint(ix, iy+16));
1346   bool left = (specified_noleft && noleft ? false : !!isIceTileAtPoint(ix-16, iy));
1347   bool right = (specified_noright && noright ? false : !!isIceTileAtPoint(ix+16, iy));
1349   if (!up) setSprite('sIceUp');
1350   if (!down) {
1351     setSprite(!up ? 'sIceUp2' : 'sIceDown');
1352     if (!level.isSolidAtPoint(ix, iy+16) && global.randOther(1, 20) == 1) hasIceBottom = true;
1353   }
1354   if (!left) {
1355          if (!up && !down) setSprite('sIceUDL');
1356     else if (!up) setSprite('sIceUL');
1357     else if (!down) setSprite('sIceDL');
1358     else setSprite('sIceLeft');
1359   }
1360   if (!right) {
1361          if (!up && !down) setSprite('sIceUDR');
1362     else if (!up) setSprite('sIceUR');
1363     else if (!down) setSprite('sIceDR');
1364     else setSprite('sIceRight');
1365   }
1366   if (!up && !left && !right && down) setSprite('sIceULR');
1367   if (!down && !left && !right && up) setSprite('sIceDLR');
1368   if (up && down && !left && !right) setSprite('sIceLR');
1369   if (!up && !down && !left && !right) setSprite('sIceBlock');
1374 override void beautifyTile () {
1375   /+
1376   bool up = !!isIceTileAtPoint(ix, iy-16);
1377   bool down = !!isIceTileAtPoint(ix, iy+16);
1378   bool left = !!isIceTileAtPoint(ix-16, iy);
1379   bool right = !!isIceTileAtPoint(ix+16, iy);
1381   //writeln("beautifying ice; l=", left, "; r=", right, "; u=", up, "; d=", down);
1383   if (!up) setSprite('sIceUp');
1384   if (!down) {
1385     setSprite(!up ? 'sIceUp2' : 'sIceDown');
1386     if (!level.isSolidAtPoint(ix, iy+16) && global.randOther(1, 20) == 1) hasIceBottom = true;
1387   }
1388   if (!left) {
1389          if (!up && !down) setSprite('sIceUDL');
1390     else if (!up) setSprite('sIceUL');
1391     else if (!down) setSprite('sIceDL');
1392     else setSprite('sIceLeft');
1393   }
1394   if (!right) {
1395          if (!up && !down) setSprite('sIceUDR');
1396     else if (!up) setSprite('sIceUR');
1397     else if (!down) setSprite('sIceDR');
1398     else setSprite('sIceRight');
1399   }
1400   if (!up && !left && !right && down) setSprite('sIceULR');
1401   if (!down && !left && !right && up) setSprite('sIceDLR');
1402   if (up && down && !left && !right) setSprite('sIceLR');
1403   if (!up && !down && !left && !right) setSprite('sIceBlock');
1404   +/
1405   setupNiceTileSprite();
1409 override void scrSetupBlockTop () {
1410   setupNiceTileSprite(/*noup:true*/);
1411   /+
1412   bool up = false;
1413   bool down = !!isIceTileAtPoint(ix, iy+16);
1414   bool left = !!isIceTileAtPoint(ix-16, iy);
1415   bool right = !!isIceTileAtPoint(ix+16, iy);
1417   setSprite('sIceUp');
1419   if (!down) setSprite('sIceUp2');
1421   if (!left) setSprite(!up ? 'sIceUDL' : 'sIceUL');
1422   if (!right && !down) setSprite('sIceUDR');
1424   if (!left && !right && down) setSprite('sIceULR');
1425   if (!down && !left && !right) setSprite('sIceBlock');
1426   +/
1430 override void scrSetupBlockBottom () {
1431   setupNiceTileSprite(/*nodown:true*/);
1432   /+
1433   bool up = !!isIceTileAtPoint(ix, iy-16);
1434   bool left = !!isIceTileAtPoint(ix-16, iy);
1435   bool right = !!isIceTileAtPoint(ix+16, iy);
1437   setSprite(!up ? 'sIceUp2' : 'sIceDown');
1439   if (global.randOther(1, 20) == 1) hasIceBottom = true; //instance_create(x, y+16, oIceBottom);
1441   if (!left) setSprite(!up ? 'sIceUDL' : 'sIceDL');
1442   if (!right) setSprite(!up ? 'sIceUDR' : 'sIceDR');
1444   if (!left && !right && up) setSprite('sIceDLR');
1445   if (!up && !left && !right) setSprite('sIceBlock');
1446   +/
1450 override void scrSetupBlockLeft () {
1451   setupNiceTileSprite(/*noright:true*/);
1452   /+
1453   bool up = !!isIceTileAtPoint(ix, iy-16);
1454   bool down = !!isIceTileAtPoint(ix, iy+16);
1455   bool left = !!isIceTileAtPoint(ix-16, iy);
1456   bool right = false;
1458   if (!up) setSprite('sIceUp');
1459   if (!down) { if (!up) setSprite('sIceUp2'); else setSprite('sIceDown'); }
1460   if (!left) {
1461          if (!up && !down) setSprite('sIceUDL');
1462     else if (!up) setSprite('sIceUL');
1463     else if (!down) setSprite('sIceDL');
1464     else setSprite('sIceLeft');
1465   }
1466   if (!right) {
1467          if (!up && !down) setSprite('sIceUDR');
1468     else if (!up) setSprite('sIceUR');
1469     else if (!down) setSprite('sIceDR');
1470     else setSprite('sIceRight');
1471   }
1472   if (!up && !left && !right && down) setSprite('sIceULR');
1473   if (!down && !left && !right && up) setSprite('sIceDLR');
1474   if (up && down && !left && !right) setSprite('sIceLR');
1475   if (!up && !down && !left && !right) setSprite('sIceBlock');
1476   +/
1480 override void scrSetupBlockRight () {
1481   setupNiceTileSprite(/*noleft:true*/);
1482   /+
1483   bool up = !!isIceTileAtPoint(ix, iy-16);
1484   bool down = !!isIceTileAtPoint(ix, iy+16);
1485   bool left = false;
1486   bool right = !!isIceTileAtPoint(ix+16, iy);
1488   if (!up) setSprite('sIceUp');
1489   if (!down) { if (!up) setSprite('sIceUp2'); else setSprite('sIceDown'); }
1490   if (!left) {
1491          if (!up && !down) setSprite('sIceUDL');
1492     else if (!up) setSprite('sIceUL');
1493     else if (!down) setSprite('sIceDL');
1494     else setSprite('sIceLeft');
1495   }
1496   if (!right) {
1497          if (!up && !down) setSprite('sIceUDR');
1498     else if (!up) setSprite('sIceUR');
1499     else if (!down) setSprite('sIceDR');
1500     else setSprite('sIceRight');
1501   }
1502   if (!up && !left && !right && down) setSprite('sIceULR');
1503   if (!down && !left && !right && up) setSprite('sIceDLR');
1504   if (up && down && !left && !right) setSprite('sIceLR');
1505   if (!up && !down && !left && !right) setSprite('sIceBlock');
1506   +/
1510 defaultproperties {
1511   objType = 'oIce';
1512   spriteName = 'sIce';
1513   solid = true;
1514   ice = true;
1515   toSpecialGrid = true; // want to think
1519 // ////////////////////////////////////////////////////////////////////////// //
1520 class MapTileTemple['oTemple'] : MapTile;
1522 bool hasIceBottom;
1523 int dripTimer;
1525 final bool isTempleTile (MapTile t) { return (t.objType == 'oTemple' || t.border); } // fake too
1526 final MapTile isTempleTileAtPoint (int x, int y) { return level.checkTileAtPoint(x, y, &isTempleTile); }
1529 override void setupTile () {
1530   setSprite(global.cityOfGold == 1 ? 'sGTemple' : 'sTemple');
1531   int tileX = ix/16, tileY = iy/16;
1532   if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1533     invincible = true;
1534     border = true;
1535     //writeln("BORDER");
1536   } else {
1537     doCreateOre(60, 80, 100, 1200);
1538   }
1543 override void thinkFrame () {
1544   if (hasIceBottom) {
1545     if (--dripTimer <= 0) {
1546       //writeln("DRIP!");
1547       level.MakeMapObject(ix+8, iy+16+4, 'oDrip');
1548       dripTimer = global.randOther(20, 400);
1549       //dripTimer = 40;
1550     }
1551   }
1556 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1557   bool up = (iy <= 0 ? true : (specified_noup && noup ? false : !!isTempleTileAtPoint(ix, iy-16)));
1558   bool down = (iy >= level.tilesHeight*16-16 ? true : (specified_nodown && nodown ? false : !!isTempleTileAtPoint(ix, iy+16)));
1559   bool left = (ix <= 16 ? true : (specified_noleft && noleft ? false : !!isTempleTileAtPoint(ix-16, iy)));
1560   bool right = (ix >= level.tilesWidth*16-16*2 ? true : (specified_noright && noright ? false : !!isTempleTileAtPoint(ix+16, iy)));
1562   /*
1563   if (argument1) {
1564     if (global.cityOfGold == 1) sprite_index = sGTemple; else sprite_index = sTemple;
1565   }
1566   */
1568   /*!
1569   if (y == 0 or collision_point(x, y-16, oTemple, 0, 0) or collision_point(x, y+16, oTempleFake, 0, 0)) { up = true; }
1570   if (y >= argument0 or collision_point(x, y+16, oTemple, 0, 0) or collision_point(x, y+16, oTempleFake, 0, 0)) { down = true; }
1571   if (collision_point(x-16, y, oTemple, 0, 0) or collision_point(x-16, y, oTempleFake, 0, 0)) { left = true; }
1572   if (collision_point(x+16, y, oTemple, 0, 0) or collision_point(x+16, y, oTempleFake, 0, 0)) { right = true; }
1573   */
1575   if (global.cityOfGold == 1) {
1576     if (!up) {
1577       setSprite('sGTempleUp');
1578            if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 0)); //3
1579       else if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 16)); //3
1580       if (!left && !right) {
1581         if (!down) setSprite('sGTempleUp6'); else setSprite('sGTempleUp5');
1582       } else if (!left) {
1583         if (!down) setSprite('sGTempleUp7'); else setSprite('sGTempleUp3');
1584       } else if (!right) {
1585         if (!down) setSprite('sGTempleUp8'); else setSprite('sGTempleUp4');
1586       } else if (left && right && !down) {
1587         setSprite('sGTempleUp2');
1588       }
1589     } else if (!down) {
1590       setSprite('sGTempleDown');
1591     }
1592   } else {
1593     if (!up) {
1594       setSprite('sTempleUp');
1595       //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);
1596            if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 0)); //3
1597       else if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 16)); //3
1598       if (!left && !right) {
1599         if (!down) setSprite('sTempleUp6'); else setSprite('sTempleUp5');
1600       } else if (!left) {
1601         if (!down) setSprite('sTempleUp7'); else setSprite('sTempleUp3');
1602       } else if (!right) {
1603         if (!down) setSprite('sTempleUp8'); else setSprite('sTempleUp4');
1604       } else if (left && right && !down) {
1605         setSprite('sTempleUp2');
1606       }
1607     } else if (!down) {
1608       setSprite('sTempleDown');
1609     }
1610   }
1614 override void beautifyTile () {
1615   setupNiceTileSprite();
1619 override void scrSetupBlockTop () {
1620   setupNiceTileSprite(/*noup:true*/);
1624 override void scrSetupBlockBottom () {
1625   setupNiceTileSprite(/*nodown:true*/);
1629 override void scrSetupBlockLeft () {
1630   setupNiceTileSprite(/*noright:true*/);
1634 override void scrSetupBlockRight () {
1635   setupNiceTileSprite(/*noleft:true*/);
1639 defaultproperties {
1640   objType = 'oTemple';
1641   spriteName = 'sTemple';
1642   solid = true;
1643   //toSpecialGrid = true; // want to think
1647 // ////////////////////////////////////////////////////////////////////////// //
1648 class MapTileVine['oVine'] : MapTile;
1651 override void setupTile () {
1655 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1656   //if (argument1) sprite_index = sVine;
1658   bool up = !!level.isLadderAtPoint(ix+8, iy-8);
1659   bool down = !!level.isLadderAtPoint(ix+8, iy+16);
1661   if (!up) {
1662     //tile_add(bgVineRoots, 0, 0, 16, 16, x, y-16, 1000); // use same depth as the vine objects themselves
1663     appendBackFront(level.CreateBgTile('bgVineRoots', 0)); //1000
1664     setSprite('sVineSource');
1665   } else if (!down) {
1666     setSprite('sVineBottom');
1667   }
1671 override void beautifyTile () {
1672   setupNiceTileSprite();
1676 defaultproperties {
1677   objType = 'oVine';
1678   ladder = true;
1679   solid = false;
1680   spriteName = 'sVine';
1681   //toSpecialGrid = true; // want to think
1685 // ////////////////////////////////////////////////////////////////////////// //
1686 class MapTileLava['oLava'] : MapTile;
1688 bool spurt;
1689 int spurtTime;
1690 int spurtCounter;
1691 bool spurtSet;
1694 override void setupTile () {
1695   spurtTime = global.randOther(100, 300);
1696   spurtCounter = spurtTime;
1700 override void doSprayRubble () {
1701   foreach (; 0..3) level.MakeMapObject(ix+global.randOther(0, 16), iy+global.randOther(0, 16), 'oLavaDrip');
1702   if (global.randOther(1, 6) == 1) {
1703     auto flame = level.MakeMapObject(ix+8, iy+8, 'oFlame');
1704     if (flame) flame.yVel = 4;
1705   }
1709 override bool onExplosionTouch (MapObject xplo) {
1710   return false;
1714 override void thinkFrame () {
1715   if (!spurtSet && spriteName == 'sLavaTop') {
1716     spurtSet = true;
1717     spurt = (global.randOther(1, 4) == 1);
1718   }
1719   auto dist = pointDistance(ix, iy, level.player.ix, level.player.iy);
1720   if (spurt && dist < 240) {
1721     if (spurtCounter > 0) {
1722       --spurtCounter;
1723     } else {
1724       spurtCounter = spurtTime;
1725       auto flame = level.MakeMapObject(ix+8, iy-4, (global.randOther(1, 8) == 1 ? 'oMagma' : 'oFlame'));
1726       //auto flame = level.MakeMapObject(ix+8, iy-4, 'oMagma');
1727       if (flame) flame.yVel = -global.randOther(1, 4);
1728     }
1729   }
1733 defaultproperties {
1734   objType = 'oLava';
1735   spriteName = 'sLava';
1736   lava = true;
1737   solid = false;
1738   toSpecialGrid = false; // lava tiles are special: they are animated/thinked without a grid
1739   lightRadius = 32+16;
1740   litWholeTile = true;
1741   imageSpeed = 0.5;
1745 // ////////////////////////////////////////////////////////////////////////// //
1746 class MapTileWater['oWater'] : MapTile;
1749 override void setupTile () {
1753 override void doSprayRubble () {
1754   foreach (; 0..3) level.MakeMapObject(ix+global.randOther(0, 16), iy+global.randOther(0, 16), 'oDrip');
1758 override bool onExplosionTouch (MapObject xplo) {
1759   return false;
1763 final bool isWaterTile (MapTile t) { return t.water; }
1764 //final bool isSolidOrWaterTile (MapTile t) { return (t.water || t.solid); }
1767 void setupNiceTileSprite () {
1768   //if (argument1) sprite_index = sWater;
1769   bool up = false;
1770   bool upWater = false;
1771   bool down = false;
1772   bool left = false;
1773   bool right = false;
1775   if (level.checkTileAtPoint(ix, iy-16, &isWaterTile)) upWater = true;
1776   if (level.isSolidAtPoint(ix, iy-16)) up = true;
1777   if (level.isSolidAtPoint(ix, iy+16) && !level.checkTileAtPoint(ix, iy+16, &isWaterTile)) down = true;
1779   if (!up && !upWater) setSprite('sWaterTop');
1781   if (upWater && level.checkTileAtPoint(ix, iy-32, &isWaterTile) && down && global.randOther(1, 4) == 1) {
1782     setSprite('sWaterBottomTall2');
1783     auto water = level.checkTileAtPoint(ix, iy-16, &isWaterTile);
1784     if (water) water.setSprite('sWaterBottomTall1');
1785   } else if ((up || upWater) && down) {
1786     switch (global.randOther(1, 4)) {
1787       case 1: setSprite('sWaterBottom'); break;
1788       case 2: setSprite('sWaterBottom2'); break;
1789       case 3: setSprite('sWaterBottom3'); break;
1790       case 4: setSprite('sWaterBottom4'); break;
1791     }
1792   }
1796 override void beautifyTile () {
1797   setupNiceTileSprite();
1801 defaultproperties {
1802   objType = 'oWater';
1803   spriteName = 'sWater';
1804   water = true;
1805   solid = false;
1806   toSpecialGrid = false;
1810 // ////////////////////////////////////////////////////////////////////////// //
1811 class MapTileWaterSwim['oWaterSwim'] : MapTileWater;
1814 // ////////////////////////////////////////////////////////////////////////// //
1815 class MapTileLavaSolid['oLavaSolid'] : MapTile;
1817 override void setupTile () {
1820 override bool onExplosionTouch (MapObject xplo) {
1821   return false;
1824 defaultproperties {
1825   objType = 'oLavaSolid';
1826   spriteName = 'sLava';
1827   lava = true;
1828   solid = true;
1829   toSpecialGrid = false;
1833 // ////////////////////////////////////////////////////////////////////////// //
1834 class MapTileLushTrapBlock['oTrapBlock'] : MapTile;
1836 int deathTimer;
1837 bool dying;
1841 override void onDestroy () {
1842   if (!cleanDeath) {
1843     /*if (not cleanDeath and not global.cleanSolids)*/ {
1844       if (smashed) scrSprayRubble(ix, iy, 3, 'sRubbleTan', 'sRubbleTanSmall');
1845       scrDropRubble(ix, iy, 3, 'sRubbleTan', 'sRubbleTanSmall');
1846       if (dying) {
1847         playSound('sndThump');
1848         level.scrShake(10);
1849       }
1850     }
1851   }
1852   ::onDestroy();
1856 override void doSprayRubble () {
1857   if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
1858   scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
1859   if (global.cityOfGold == 1) {
1860     ore = 2;
1861     scrDropOre();
1862   }
1863   if (dying) {
1864     playSound('sndThump');
1865     level.scrShake(10);
1866   }
1870 override void setupTile () {
1874 void activate () {
1875   if (dying) return;
1876   if (distanceToEntityCenter(level.player) < 90) {
1877     dying = true;
1878   }
1882 override void thinkFrame () {
1883   if (dying) {
1884     if (deathTimer > 0) --deathTimer; else instanceRemove();
1885   }
1889 defaultproperties {
1890   objType = 'oLavaSolid';
1891   spriteName = 'sSkullBlock';
1892   solid = true;
1893   toSpecialGrid = true;
1894   //depth = ???;
1898 // ////////////////////////////////////////////////////////////////////////// //
1899 class MapTileLeaves['oLeaves'] : MapTile;
1901 bool dead;
1902 bool spriteSet;
1905 final bool isTreeOrLeaves (MapTile t) { return (t != self && t.tree || t.leaves); }
1909 override void onDestroy () {
1910   if (!cleanDeath) {
1911     if (spriteName != 'sLeavesDead' && spriteName != 'sLeavesDeadR') {
1912       level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1913       level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1914     }
1915   }
1916   ::onDestroy();
1920 override void doSprayRubble () {
1921   if (spriteName != 'sLeavesDead' && spriteName != 'sLeavesDeadR') {
1922     level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1923     level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1924   }
1928 override void setupTile () {
1929   spriteName = (global.cemetary ? 'sLeavesDead' : 'sLeaves');
1933 override void thinkFrame () {
1934   int x = ix, y = iy;
1936   if (!spriteSet) {
1937     spectral = true;
1938     if (level.checkTileAtPoint(x-16, y, &isTreeOrLeaves)) {
1939       setSprite(global.cemetary ? 'sLeavesDeadR' : 'sLeavesRight');
1940     } else if (level.checkTileAtPoint(x+16, y, &isTreeOrLeaves)) {
1941       setSprite(global.cemetary ? 'sLeavesDead' : 'sLeaves');
1942     }
1943     if (level.checkTileAtPoint(x-16, y, &isTreeOrLeaves) &&
1944         level.checkTileAtPoint(x+16, y, &isTreeOrLeaves))
1945     {
1946       setSprite('sLeavesTop');
1947     }
1948     if (dead) {
1949       if (level.checkTileAtPoint(x-16, y, &isTreeOrLeaves)) setSprite('sLeavesDeadR'); else setSprite('sLeavesDead');
1950     }
1951     spectral = false;
1952     spriteSet = true;
1953   }
1955   spectral = true;
1956   if (spriteName == 'sLeavesTop') {
1957     if (!level.checkTileAtPoint(x-16, y, &isTreeOrLeaves) ||
1958         !level.checkTileAtPoint(x+16, y, &isTreeOrLeaves))
1959     {
1960       instanceRemove();
1961     }
1962   } else if (spriteName == 'sLeaves' || spriteName == 'sLeavesDead') {
1963     if (!level.checkTileAtPoint(x+16, y, &isTreeOrLeaves)) instanceRemove();
1964   } else if (spriteName == 'sLeavesRight' || spriteName == 'sLeavesDeadR') {
1965     if (!level.checkTileAtPoint(x-16, y, &isTreeOrLeaves)) instanceRemove();
1966   }
1967   spectral = false;
1969   /*
1970   if (sprite_index == sLeavesDeadR) {
1971     desc = "Canopy";
1972     desc2 = "These leaves have died and withered.";
1973   } else {
1974     desc = "Canopy";
1975     desc2 = "The canopy of a proud tree.";
1976   }
1977   */
1981 defaultproperties {
1982   objType = 'oLeaves';
1983   desc = "Canopy";
1984   desc2 = "The top of a proud tree.";
1985   platform = true;
1986   solid = false;
1987   leaves = true;
1988   dead = false;
1989   spriteSet = false;
1990   toSpecialGrid = true;
1991   depth = 1;
1995 // ////////////////////////////////////////////////////////////////////////// //
1996 class MapTileTree['oTree'] : MapTile;
1998 bool burning;
2002 override void onDestroy () {
2003   if (!cleanDeath) {
2004     if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2005     scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2006   }
2007   ::onDestroy();
2011 override void doSprayRubble () {
2012   if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2013   scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2017 override void setupTile () {
2021 final bool isTreeTileAtPoint (int x, int y) {
2022   return !!level.checkTileAtPoint(x, y, delegate bool (MapTile t) { return (t.objType == 'oTree'); });
2026 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
2027   //if (argument1) sprite_index = sVine;
2029   bool up = isTreeTileAtPoint(ix, iy-16);
2030   //bool down = isTreeTileAtPoint(ix, iy+16);
2031   //bool left = isTreeTileAtPoint(ix-16, iy);
2032   //bool right = isTreeTileAtPoint(ix+16, yi);
2034   if (!up) {
2035     if (global.cemetary || getSprite().Name == 'sTreeTopDead') setSprite('sTreeTopDead'); else setSprite('sTreeTop');
2036     depth = 1;
2037   }
2041 override void beautifyTile () {
2042   setupNiceTileSprite();
2046 override void thinkFrame () {
2047   int x = ix, y = iy;
2049   if (!level.isSolidAtPoint(x, y+16)) { instanceRemove(); return; }
2050   if (level.isLavaAtPoint(x-16, y) || level.isLavaAtPoint(x+16, y)) { instanceRemove(); return; }
2054 defaultproperties {
2055   objType = 'oTree';
2056   desc = "Tree Trunk";
2057   desc2 = "The trunk of a proud tree.";
2058   spriteName = 'sTreeTrunk';
2059   solid = true;
2060   leaves = false;
2061   tree = true;
2062   burning = false;
2063   toSpecialGrid = true;
2064   depth = 2;
2068 // ////////////////////////////////////////////////////////////////////////// //
2069 class MapTileTreeBranch['oTreeBranch'] : MapTile;
2071 bool burning;
2072 bool dead;
2075 final bool isTree (MapTile t) { return (t != self && t.tree); }
2079 override void onDestroy () {
2080   if (!cleanDeath) {
2081     if (spriteName != 'sTreeBranchDeadL' && spriteName != 'sTreeBranchDeadR') {
2082       level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
2083     }
2084   }
2085   ::onDestroy();
2089 override void doSprayRubble () {
2090   if (spriteName != 'sTreeBranchDeadL' && spriteName != 'sTreeBranchDeadR') {
2091     level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
2092   }
2096 override void setupTile () {
2097   dead = false;
2099   auto ltree = level.checkTileAtPoint(ix-16, iy, &isTree);
2100   auto rtree = level.checkTileAtPoint(ix+16, iy, &isTree);
2102        if (ltree && !rtree) setSprite(global.cemetary ? 'sTreeBranchDeadR' : 'sTreeBranchRight');
2103   else if (!ltree && rtree) setSprite(global.cemetary ? 'sTreeBranchDeadL' : 'sTreeBranchLeft');
2105   //if (global.cemetary) spriteName = 'sTreeBranchDeadR';
2109 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
2110   //if (argument1) sprite_index = sVine;
2112   bool up = !!level.checkTileAtPoint(ix, iy-16, delegate bool (MapTile t) { return (t.objType == 'oLeaves'); });
2113   //if (collision_point(x, y+16, oTreeBranch, 0, 0)) { down = true; }
2114   //if (collision_point(x-16, y, oTreeBranch, 0, 0)) { left = true; }
2115   bool right = !!level.checkTileAtPoint(ix+16, iy, delegate bool (MapTile t) { return (t.objType == 'oTree'); });
2117   if (up) {
2118     instanceRemove();
2119     return;
2120   }
2122   if (right) {
2123     if (global.cemetary || getSprite().Name == 'sTreeBranchDeadR') setSprite('sTreeBranchDeadL'); else setSprite('sTreeBranchLeft');
2124   }
2128 override void beautifyTile () {
2129   //writeln("!!! ", getSprite().Name);
2130   setupNiceTileSprite();
2134 override void thinkFrame () {
2135   int x = ix, y = iy;
2137   spectral = true;
2138   auto ltree = level.checkTileAtPoint(ix-16, iy, &isTree);
2139   auto rtree = level.checkTileAtPoint(ix+16, iy, &isTree);
2140   if (!ltree && !rtree) {
2141     instanceRemove();
2143   } else {
2144          if (ltree && !rtree) setSprite('sTreeBranchRight');
2145     else if (!ltree && rtree) setSprite('sTreeBranchLeft');
2147   }
2148   spectral = false;
2152 defaultproperties {
2153   objType = 'oTree';
2154   desc = "Tree Branch";
2155   desc2 = "A slight but firm limb of a proud tree.";
2156   spriteName = 'sTreeBranchRight';
2157   platform = true;
2158   solid = false;
2159   leaves = false;
2160   tree = true;
2161   burning = false;
2162   toSpecialGrid = true;
2163   depth = 2;
2167 // ////////////////////////////////////////////////////////////////////////// //
2168 // only for dark levels
2169 class MapTileTikiTorch['oTikiTorch'] : MapTile;
2172 override void setupTile () {
2173   writeln("*** TIKI TORCH CREATED");
2177 override void thinkFrame () {
2178   lightRadius = 32+16+global.randOther(-8, 8);
2182 defaultproperties {
2183   objType = 'oTikiTorch';
2184   desc = "Torch";
2185   desc2 = "A moderately bright torch.";
2186   spriteName = 'sTikiTorch';
2187   platform = false;
2188   solid = false;
2189   leaves = false;
2190   tree = false;
2191   toSpecialGrid = true;
2192   depth = 1000;
2193   imageSpeed = 0.5;
2194   lightRadius = 32+16;
2198 // ////////////////////////////////////////////////////////////////////////// //
2199 class MapTileGiantTikiHead['oGiantTikiHead'] : MapTile;
2201 bool broken;
2204 void breakIt () {
2205   if (!broken) {
2206     broken = true;
2207     setSprite('sGTHHole');
2208   }
2212 override void setupTile () {
2216 override void thinkFrame () {
2220 defaultproperties {
2221   objType = 'oGiantTikiHead';
2222   invincible = true; //???
2223   solid = false;
2224   spriteName = 'sGiantTikiHead';
2225   toSpecialGrid = true; // it is HUGE
2226   depth = 1000;
2230 // ////////////////////////////////////////////////////////////////////////// //
2231 class MapTileThinIce['oThinIce'] : MapTile;
2233 int thickness = 60;
2235 override void setupTile () {
2239 override void thinkFrame () {
2240   if (level.player.isRectHitSimple(ix, iy-1, 17, 2)) {
2241     thickness -= 2;
2242     if (global.randOther(1, 100) == 1) level.MakeMapObject(ix+global.randOther(0, 16), iy+9, 'oDrip');
2243   }
2244        if (thickness > 50) setSprite('sThinIce1');
2245   else if (thickness > 40) setSprite('sThinIce2');
2246   else if (thickness > 30) setSprite('sThinIce3');
2247   else if (thickness > 20) setSprite('sThinIce4');
2248   else if (thickness > 10) setSprite('sThinIce5');
2249   else if (thickness > 0) setSprite('sThinIce6');
2250   else instanceRemove();
2254 defaultproperties {
2255   objType = 'oThinIce';
2256   solid = true;
2257   spriteName = 'sThinIce1';
2258   toSpecialGrid = true; // it must think
2262 // ////////////////////////////////////////////////////////////////////////// //
2263 class MapTileDarkFall['oDarkFall'] : MapTile;
2265 //int thickness = 60;
2266 int viscidTop = 1;
2267 int timeFall = 20;
2268 int timeFallMax = 20;
2269 bool falling;
2270 //float grav = 1;
2272 override int height () { return 8; }
2274 override void setupTile () {}
2277 //FIXME: viscidMovement -- ???
2278 override void thinkFrame () {
2279   //isCollisionCharacterTop(1)
2280   if (!falling) {
2281     auto plr = level.player;
2282     if (plr.isRectHitSimple(ix, iy-1, 17, 2)) {
2283       --timeFall;
2284     } else if (plr.status == MapObject::HANGING) {
2285       //writeln("checking for hang...");
2286       int tileX = ix/16;
2287       if (plr.isRectHitSimple((tileX-1)*16, iy, 16, 8) || plr.isRectHitSimple((tileX+1)*16, iy, 16, 8)) {
2288         //writeln("oDarkFall: HANGING! timer=", timeFall);
2289         --timeFall;
2290       }
2291     } else if (timeFall < timeFallMax) {
2292       ++timeFall;
2293     }
2294     if (timeFall <= 0) falling = true;
2295   }
2296   if (!falling) return;
2297   myGrav = grav;
2298   //if (yVel > 10) yVel = 10;
2299   //HACK: so player won't be able to push it
2300   moveable = true;
2301   ::thinkFrame();
2302   moveable = false;
2303   // dropped on solid?
2304   if (level.checkTilesInRect(ix, iy+height, width, 1)) {
2305     int x = ix, y = iy;
2306     instanceRemove();
2307     // not breaked on "Thwomp Trap" (we don't have it yet)
2308     playSound('sndBreak');
2309     level.MakeMapObject(x+8, y+8, 'oSmokePuff');
2310     foreach (; 0..3) {
2311       auto obj = level.MakeMapObject(x+global.randOther(2, 14), y-global.randOther(2, 8), 'oRubbleDark');
2312       if (obj) {
2313         obj.xVel = global.randOther(1, 3)-global.randOther(1, 3);
2314         obj.yVel = -global.randOther(0, 3);
2315       }
2316     }
2317   }
2321 defaultproperties {
2322   objType = 'oDarkFall';
2323   solid = true;
2325   viscidTop = 1;
2326   //setCollisionBounds(0, 0, 16, 8);
2328   grav = 1;
2329   myGravLimit = 10;
2330   //timeFall = 20;
2331   //timeFallMax = 20;
2332   desc = "Falling Platform";
2333   desc2 = "A thin, dark blue platform that will colapse if stood on for any length of time.";
2335   spriteName = 'sDarkFall';
2336   toSpecialGrid = true; // it must think
2337   depth = 1000;
2341 // ////////////////////////////////////////////////////////////////////////// //
2342 class MapTileAlienHull['oAlienShip'] : MapTile;
2345 override void setupTile () {
2346   //writeln("*********** ALIEN SHIP!");
2350 override void doSprayRubble () {
2351   if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2352   scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2356 final bool isAlienShipFloorCB (MapTile t) { return (t.objType == 'oAlienShip' || t.objType == 'oAlienShipFloor'); }
2357 final bool isASFTileAt (int x, int y) { return !!level.checkTileAtPoint(x, y, &isAlienShipFloorCB); }
2360 override void beautifyTile () {
2361   //if (argument1) sprite_index = sAlienTop;
2363   bool up = !!isASFTileAt(ix, iy-16);
2364   bool down = !!isASFTileAt(ix, iy+16);
2365   bool left = !!isASFTileAt(ix-16, iy);
2366   bool right = !!isASFTileAt(ix+16, iy);
2368   if (right && !left) {
2369          if (up && !down) setSprite('sAlienFront2');
2370     else if (down && !up) setSprite('sAlienFront3');
2371   }
2372   if (left && !right) {
2373     if (up) setSprite('sAlienBack2'); else if (down) setSprite('sAlienBack3');
2374   }
2378 defaultproperties {
2379   objType = 'oAlienShip';
2380   solid = true;
2381   spriteName = 'sAlienTop';
2382   toSpecialGrid = false;
2386 // ////////////////////////////////////////////////////////////////////////// //
2387 class MapTileAlienFloor['oAlienShipFloor'] : MapTileAlienHull;
2390 defaultproperties {
2391   objType = 'oAlienShipFloor';
2392   spriteName = 'sAlienFloor';
2396 // ////////////////////////////////////////////////////////////////////////// //
2397 class MapTileXocBlock['oXocBlock'] : MapTile;
2400 override void setupTile () {
2404 override void doSprayRubble () {
2405   foreach (; 0..3) {
2406     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');
2407     if (gold) {
2408       gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2409       gold.yVel = global.randOther(2, 4);
2410     }
2411   }
2412   {
2413     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');
2414     if (gold) {
2415       gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2416       gold.yVel = global.randOther(2, 4);
2417     }
2418   }
2422 defaultproperties {
2423   objType = 'oXocBlock';
2424   solid = true;
2425   spriteName = 'sGoldBlock';
2426   toSpecialGrid = false;
2427   depth = 1000;
2431 // ////////////////////////////////////////////////////////////////////////// //
2432 class MapTileCeilingTrap['oCeilingTrap'] : MapTile;
2434 //int thickness = 60;
2435 int viscidTop = 1;
2436 //int timeFall = 20;
2437 //int timeFallMax = 20;
2438 //float grav = 1;
2439 int counter;
2440 bool sprung;
2443 enum {
2444   IDLE = 0,
2445   DROP = 1,
2446   WAIT = 2,
2447   RETURN = 3,
2450 int status;
2453 override void onAnimationLooped () {
2454   if (spriteName == 'sCeilingTrapS') setSprite('sCeilingTrap');
2458 override void doSprayRubble () {
2459   if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2460   scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2461   if (global.cityOfGold == 1) {
2462     ore = 2;
2463     scrDropOre();
2464   }
2468 override void setupTile () {
2469   if (global.cityOfGold == 1) spriteName = 'sGoldBlock';
2473 void activate () {
2474   if (status != IDLE) return;
2475   status = DROP;
2476   level.checkTilesInRect(ix-64, iy-64, 129, 129, delegate bool (MapTile t) {
2477     auto door = MapTileDoor(t);
2478     if (door) door.activate();
2479     return false;
2480   });
2484 //FIXME: viscidMovement -- ???
2485 override void thinkFrame () {
2486   if (status == IDLE) {
2487     // nothing
2488   } else if (status == DROP) {
2489     if (counter > 0) {
2490       --counter;
2491     } else {
2492       counter = 3;
2493       flty += 1;
2494     }
2495     yVel = 0;
2496     spectral = true;
2497     if (level.isSolidAtPoint(ix+8, iy+17)) status = WAIT;
2498     spectral = false;
2499     if (spriteName == 'sBlock' || spriteName == 'sGoldBlock') setSprite('sCeilingTrapS');
2500     // out-of-level check
2501     if (isOutsideOfLevel()) {
2502       cleanDeath = true;
2503       instanceRemove();
2504     }
2505   } else if (status == WAIT) {
2506     yVel = 0;
2507     spectral = true;
2508     if (isCollisionBottom(0)) flty -= 1;
2509     spectral = false;
2510   }
2513   if (status != IDLE) {
2514     // player
2515     auto plr = level.player;
2516     if (!plr.dead && !plr.invincible && !plr.isExitingSprite() && /*plr.collidesWith(self)*/ plr.isRectHitSimple(x0, y0, width, height+1)) {
2517       bool doCol = true;
2518       if (!plr.collidesWith(self)) {
2519         doCol = (plr.yVel < -0.01 && plr.y0 > y1);
2520       }
2521       if (doCol) {
2522         if (global.plife > 0) {
2523           global.plife -= global.config.crushDmg;
2524           if (global.plife <= 0 /*and isRealLevel()*/) level.addDeath(objName);
2525         }
2526         plr.spillBlood();
2527         plr.stunned = true;
2528         plr.stunTimer = 20;
2529         playSound('sndHurt');
2530       }
2531     }
2534     level.isObjectInRect(x0-1, y0-1, width+2, height+2, delegate bool (MapObject o) {
2535       if (o == self) return false;
2536       if (o isa EnemyTombLord) return false;
2538       // register hit only if we're moving onto a spikes
2539       if (!o.collidesWith(self)) {
2540         bool onTop = (o.yVel < -0.01 && o.y0 > y1);
2541         if (!onTop) return false;
2542         writeln("*** RUNNING ON TRAP SPIKES");
2543       }
2545       // damsel
2546       auto dms = MonsterDamsel(o);
2547       if (dms) {
2548         if (dms.dead || dms.status == MapObject::DEAD || dms.invincible) return false;
2549         if (dms.heldBy) dms.heldBy.holdItem = none;
2550         dms.hp -= global.config.crushDmg;
2551         dms.spillBlood();
2552         dms.status = MapObject::THROWN;
2553         dms.counter = dms.stunMax;
2554         dms.calm = false;
2555         //dms.damselDropped = true;
2556         dms.kissCount = min(1, dms.kissCount);
2557         playSound('sndDamsel');
2558         return false;
2559       }
2561       // enemy
2562       auto enemy = MapEnemy(o);
2563       if (enemy) {
2564         if (o.dead || o.status == MapObject::DEAD || o.invincible) return false;
2565         if (o.heldBy) o.heldBy.holdItem = none;
2566         enemy.hp -= global.config.crushDmg;
2567         enemy.spillBlood();
2568         playSound('sndHit');
2569         return false;
2570       }
2572       return false;
2573     }, castClass:MapEnemy);
2574   }
2578 defaultproperties {
2579   objType = 'oCeilingTrap';
2580   objName = 'Ceiling Trap';
2581   desc = "Ceiling Trap";
2582   desc2 = "This seemingly-innocuous block hides a vicious set of spikes.";
2584   solid = true;
2585   imageSpeed = 0.4;
2587   viscidTop = 1;
2588   //setCollisionBounds(0, 0, 16, 8);
2590   //moveable = true;
2591   grav = 1;
2592   myGravLimit = 10;
2593   counter = 3;
2595   status = IDLE;
2596   sprung = false;
2598   spriteName = 'sBlock';
2599   toSpecialGrid = true; // it must think
2600   depth = 1000;
2604 // ////////////////////////////////////////////////////////////////////////// //
2605 class MapTileTempleFake['oTempleFake'] : MapTile;
2607 int sleepTime = 4; // wait until the grid is filled
2609 override void doSprayRubble () {
2610   if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2611   scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2615 override void setupTile () {
2616   doCreateOre(60, 80, 100, 1200);
2620 override void thinkFrame () {
2621   if (sleepTime > 0) { --sleepTime; return; }
2622   //writeln("CHK!");
2623   auto door = level.checkTilesInRect(ix, iy, 16, 16, delegate bool (MapTile t) {
2624     if (t == self) return false;
2625     //writeln("fake temple: t(", GetClassName(t.Class), "); objName=", t.objName, "; objType=", t.objType, "; door=", t isa MapTileDoor);
2626     return (t isa MapTileDoor);
2627   });
2628   if (!door) {
2629     //writeln("!!!");
2630     auto bt = level.MakeMapTile(ix/16, iy/16, 'oTemple');
2631     if (bt) bt.copyOreFrom(self);
2632     instanceRemove();
2633     return;
2634   }
2635   /*
2636   if (!level.checkTileAtPoint(ix+8, iy+8, delegate bool (MapTile t) { return (t isa MapTileDoor); })) {
2637     writeln("!!!");
2638     level.MakeMapTile(ix/16, iy/16, 'oTemple');
2639     instanceRemove();
2640     return;
2641   }
2642   */
2646 defaultproperties {
2647   objType = 'oTemple';
2648   desc = "Temple Brick";
2649   desc2 = "This is the first brick you've seen that actually qualifies as a brick. It looks rather bland.";
2651   solid = true;
2652   moveable = false;
2653   spriteName = 'sTemple';
2654   toSpecialGrid = true; // it must think
2656   //invisible = true;
2658   depth = 60;
2662 // ////////////////////////////////////////////////////////////////////////// //
2663 class MapTileDoor['oDoor'] : MapTile;
2665 //int thickness = 60;
2666 int viscidTop = 1;
2667 int counter;
2668 bool sprung;
2671 enum {
2672   IDLE = 0,
2673   DROP = 1,
2674   WAIT = 2,
2675   RETURN = 3,
2677 int status;
2680 //setCollisionBounds(1, 0, 15, 32);
2681 override int x0 () { return round(fltx)+1; }
2682 override int width () { return 15; }
2683 override int height () { return 32; }
2686 override void doSprayRubble () {
2687   if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2688   scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2689   if (global.cityOfGold == 1) {
2690     ore = 2;
2691     scrDropOre();
2692   }
2696 override void setupTile () {
2700 void activate () {
2701   status = DROP;
2702   myGrav = grav;
2703   yVel = myGrav;
2704   shiftY(2);
2708 //FIXME: viscidMovement -- ???
2709 override void thinkFrame () {
2710   if (status == IDLE) {
2711     // nothing
2712   } else if (status == DROP) {
2713     yVel = fmin(yVel+myGrav, myGravLimit);
2714     //::thinkFrame();
2715     spectral = true;
2716     auto oldsolid = solid;
2717     solid = false;
2718     //writeln("DOOR GOING DOWN; yVel=", yVel, "; ix=", ix, "; iy=", iy, "; x0=", x0, "; y0=", y0, "; w=", width, "; h=", height);
2719     int newY = round(flty+yVel); //!!!
2720     // check if we need (and can) move down
2721     int w = width, hp = height;
2722     while (newY > iy) {
2723       // made non-solid temporarily
2724       //auto hasAnything = level.checkTilesInRect(x0, y0+hp, w, 1, &level.cbCollisionAnySolid);
2725       auto hasAnything = level.checkTilesInRect(x0, y0+hp, w, 1, delegate bool (MapTile t) {
2726         if (t == self) return false;
2727         return t.solid;
2728       });
2729       if (hasAnything) {
2730         //writeln("hit '", GetClassName(hasAnything.Class), "' (", hasAnything.objName, ":", hasAnything.objType, "): pos=(", hasAnything.ix, ",", hasAnything.iy, ")");
2731         // hit something, stop right there
2732         if (yVel > myGrav*3) playSound('sndThud');
2733         flty = iy;
2734         status = WAIT;
2735         yVel = 0;
2736         counter = 100;
2737         depth = 180;
2738         //writeln("DOOR STOP");
2739         break;
2740       }
2741       // move us
2742       shiftY(1);
2743     }
2744     spectral = false;
2745     solid = oldsolid;
2747     // out-of-level check
2748     if (flty > level.tilesHeight*16+16) {
2749       cleanDeath = true;
2750       instanceRemove();
2751     }
2752   } else if (status == WAIT) {
2753     yVel = 0;
2754     spectral = true;
2755     if (isCollisionBottom(0)) flty -= 1;
2756     spectral = false;
2757   }
2761 defaultproperties {
2762   objType = 'oDoor';
2763   solid = true;
2765   viscidTop = 1;
2766   //setCollisionBounds(0, 0, 16, 8);
2768   //moveable = true;
2769   grav = 0.6;
2770   myGravLimit = 6;
2771   counter = 0;
2773   status = IDLE;
2774   sprung = false;
2776   desc = "Wall Trap";
2777   desc2 = "The inside of this block carries an extendable wall.";
2779   spriteName = 'sDoor';
2780   toSpecialGrid = true; // it must think
2781   depth = 2000;
2785 // ////////////////////////////////////////////////////////////////////////// //
2786 class MapTileSmashTrap['oSmashTrap'] : MapTile;
2788 //int thickness = 60;
2789 int viscidTop = 1;
2791 int counter;
2792 bool hit = false;
2794 float xv, yv;
2795 float xa, ya;
2798 enum {
2799   IDLE = 0,
2800   ATTACK = 1,
2801   DEAD = 99,
2804 int status;
2806 enum {
2807   RIGHT = 0,
2808   DOWN = 1,
2809   LEFT = 2,
2810   UP = 3,
2813 int dir;
2816 final string dirName () {
2817   switch (dir) {
2818     case RIGHT: return "right";
2819     case LEFT: return "left";
2820     case UP: return "up";
2821     case DOWN: return "down";
2822   }
2823   return "fucked";
2827 //setCollisionBounds(1, 1, 15, 15);
2828 override int x0 () { return round(fltx)+1; }
2829 override int width () { return 15; }
2830 //override int height () { return 16; }
2833 override void doSprayRubble () {
2834   if (global.cityOfGold == 1) {
2835     foreach (; 0..3) {
2836       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');
2837       gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2838       gold.yVel = global.randOther(2, 4);
2839     }
2840     {
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), 'oGoldNugget');
2842       gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2843       gold.yVel = global.randOther(2, 4);
2844     }
2845   } else {
2846     if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2847     scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2848   }
2852 override void setupTile () {
2853   // prevent smash trap from spawning in player range when level starts
2854   if (true /+global.config.bizarre /*or isRealLevel()*/ +/) {
2855     if (level.calcNearestEnterDist(ix, iy) < 90) {
2856       cleanDeath = true;
2857       instanceRemove();
2858       return;
2859     }
2860   }
2862   if (global.cityOfGold == 1) setSprite('sSmashTrapGold');
2863   dir = global.randRoom(0, 3);
2867 //FIXME: viscidMovement -- ???
2868 override void thinkFrame () {
2869   spectral = true;
2871   if (status == IDLE) {
2872     auto plr = level.player;
2873     auto dist = pointDistance(ix, iy, plr.ix, plr.iy);
2874     if (counter > 0) --counter;
2875     if (dist < 90 && counter < 1) {
2876       if (abs(plr.iy-(iy+8)) < 8 && plr.ix > ix+8 && !isCollisionRight(2)) {
2877         status = ATTACK;
2878         dir = RIGHT;
2879         xa = 0.5;
2880       } else if (abs(plr.ix-(ix+8)) < 8 && plr.iy > iy+8 && !isCollisionBottom(2)) {
2881         status = ATTACK;
2882         dir = DOWN;
2883         ya = 0.5;
2884       } else if (abs(plr.iy-(iy+8)) < 8 && plr.ix < ix+8 && !isCollisionLeft(2)) {
2885         status = ATTACK;
2886         dir = LEFT;
2887         xa = -0.5;
2888       } else if (abs(plr.ix-(ix+8)) < 8 && plr.iy < iy+8 && !isCollisionTop(2)) {
2889         status = ATTACK;
2890         dir = UP;
2891         ya = -0.5;
2892       }
2893     }
2894   } else if (status == ATTACK) {
2895     bool colLeft = !!isCollisionLeft(1);
2896     bool colRight = !!isCollisionRight(1);
2897     bool colTop = !!isCollisionTop(1);
2898     bool colBot = !!isCollisionBottom(1);
2900     xv = fclamp(xv+xa, -4, 4);
2901     yv = fclamp(yv+ya, -4, 4);
2902     shiftXY(xv, yv);
2903     if (dir == RIGHT) {
2904       if (isCollisionRight(2) && colRight) { shiftX(-2); hit = true; }
2905       if (colRight) { shiftX(-1); hit = true; }
2906     } else if (dir == DOWN) {
2907       if (isCollisionBottom(2) && colBot) { shiftY(-2); hit = true; }
2908       if (colBot) { shiftY(-1); hit = true; }
2909     } else if (dir == LEFT) {
2910       if (isCollisionLeft(2) && colLeft) { shiftX(2); hit = true; }
2911       if (colLeft) { shiftX(1); hit = true; }
2912     } else if (dir == UP) {
2913       if (isCollisionTop(2) && colTop) { shiftY(2); hit = true; }
2914       if (colTop) { shiftY(1); hit = true; }
2915     }
2917     if (level.isObjectInRect(ix-1, iy-1, 18, 18, delegate bool (MapObject o) { return (o isa EnemyTombLord); })) hit = true;
2919     if (hit) {
2920       xv = 0;
2921       yv = 0;
2922       xa = 0;
2923       ya = 0;
2924     }
2926     /*
2927     if (hit) {
2928       auto ct = isCollision();
2929       writeln("dir=", dirName(), "; colLeft=", colLeft, "; colRight=", colRight, "; colTop=", colTop, "; colBot=", colBot, "; col=", (ct ? GetClassName(ct.Class) : '<none>'), " (", (ct ? ct.objName : '<>'), ")");
2930     }
2931     */
2933     if (hit && !isCollision() /*!colRight && !colLeft && !colTop && !colBot*/) {
2934       status = IDLE;
2935       hit = false;
2936       counter = 50;
2937     }
2938   } else if (status == DEAD) {
2939     xv = 0;
2940     yv = 0;
2941     xa = 0;
2942     ya = 0;
2943     shiftY(0.05);
2944     if (level.isLavaAtPoint(ix, iy-1)) { instanceRemove(); return; }
2945   }
2947   if (level.checkTilesInRect(ix+1, iy+1, 15, 15, &level.cbCollisionLava)) status = DEAD;
2949   spectral = false;
2951   // player
2952   auto plr = level.player;
2953   if (!plr.dead && !plr.invincible && !plr.isExitingSprite() && /*plr.collidesWith(self)*/ plr.isRectHitSimple(x0-1, y0-1, width+2, height+2)) {
2954     bool doCol = true;
2955     if (!plr.collidesWith(self)) {
2956       bool onLeft = (plr.xVel < -0.01 && plr.x0 > x1);
2957       bool onRight = (plr.xVel > 0.01 && plr.x1 < x0);
2958       bool onTop = (plr.yVel < -0.01 && plr.y0 > y1);
2959       bool onBottom = (plr.yVel > 0.01 && plr.y1 < y0);
2960       if (!onLeft && !onRight && !onTop && !onBottom) {
2961         doCol = false;
2962       } else {
2963         writeln("*** PLAYER RUNNING ON TRAP SPIKES");
2964       }
2965     }
2966     if (doCol) {
2967       if (global.plife > 0) {
2968         global.plife -= global.config.crushDmg;
2969         if (global.plife <= 0 /*and isRealLevel()*/) level.addDeath(objName);
2970         plr.spillBlood();
2971         plr.stunned = true;
2972         plr.stunTimer = 20;
2973         playSound('sndHurt');
2974       }
2975     }
2976   }
2978   level.isObjectInRect(x0-1, y0-1, width+2, height+2, delegate bool (MapObject o) {
2979     if (o == self) return false;
2980     if (o isa EnemyTombLord) return false;
2982     //if (o !isa MapEnemy) return false;
2984     // register hit only if we're moving onto a spikes
2985     if (!o.collidesWith(self)) {
2986       bool onLeft = (o.xVel < -0.01 && o.x0 > x1);
2987       bool onRight = (o.xVel > 0.01 && o.x1 < x0);
2988       bool onTop = (o.yVel < -0.01 && o.y0 > y1);
2989       bool onBottom = (o.yVel > 0.01 && o.y1 < y0);
2990       if (!onLeft && !onRight && !onTop && !onBottom) return false;
2991       writeln("*** RUNNING ON TRAP SPIKES");
2992     }
2994     // damsel
2995     auto dms = MonsterDamsel(o);
2996     if (dms) {
2997       if (!dms.invincible) {
2998         if (dms.heldBy) dms.heldBy.holdItem = none;
2999         dms.hp -= global.config.crushDmg;
3000         dms.spillBlood();
3001         dms.status = MapObject::THROWN;
3002         dms.counter = dms.stunMax;
3003         dms.calm = false;
3004         //dms.damselDropped = true;
3005         dms.kissCount = min(1, dms.kissCount);
3006         playSound('sndDamsel');
3007         return false;
3008       }
3009     }
3011     // enemy
3012     auto enemy = MapEnemy(o);
3013     if (enemy) {
3014       if (o.dead || o.status == DEAD || o.invincible) return false;
3015       if (o.heldBy) o.heldBy.holdItem = none;
3016       enemy.hp -= global.config.crushDmg;
3017       enemy.spillBlood();
3018       playSound('sndHit');
3019       return false;
3020     }
3022     return false;
3023   }, castClass:MapEnemy);
3027 defaultproperties {
3028   objType = 'oSmashTrap';
3029   objName = 'Smash Trap';
3030   desc = "Smash Trap";
3031   desc2 = "This trap carries an array of extendable blades. It can chase down intruders horizontally and vertically.";
3032   solid = true;
3033   viscidTop = 1;
3034   imageSpeed = 0.4;
3036   xv = 0;
3037   yv = 0;
3038   xa = 0;
3039   ya = 0;
3041   xVel = 0;
3042   yVel = 0;
3043   //xAcc = 0;
3044   //yAcc = 0;
3046   //moveable = true;
3047   grav = 1;
3048   myGravLimit = 10;
3049   counter = 3;
3051   status = IDLE;
3053   spriteName = 'sSmashTrap';
3054   toSpecialGrid = true; // it must think
3055   depth = 60;
3059 // ////////////////////////////////////////////////////////////////////////// //
3060 class MapTileSmashTrapLit['oSmashTrapLit'] : MapTileSmashTrap;
3062 defaultproperties {
3063   spriteName = 'sSmashTrapLit';
3064   lightRadius = 32;
3068 // ////////////////////////////////////////////////////////////////////////// //
3069 class MapTileGoldDoor['oGoldDoor'] : MapTile;
3071 enum {
3072   CLOSED,
3073   SCEPTRE,
3076 int status;
3079 override void doSprayRubble () {
3083 override void setupTile () {
3084   writeln("GENERATED GOLD DOOR");
3088 // it opens if player carrying a sceptre, and has a crown
3089 override void thinkFrame () {
3090   // early exits
3091   if (status == SCEPTRE && !global.hasCrown) return;
3092   auto plr = level.player;
3093   if (plr.holdItem !isa ItemWeaponSceptre) return;
3094   // check for collision
3095   if (plr.collidesWith(self)) {
3096     if (global.hasCrown) {
3097       // take sceptre away
3098       auto it = plr.holdItem;
3099       plr.holdItem = none;
3100       it.instanceRemove();
3101       playSound('sndChestOpen');
3102       level.MakeMapTile(ix/16, iy/16, 'oXGold');
3103       auto obj = level.MakeMapObject(ix-4, iy+6, 'oPoof');
3104       if (obj) obj.xVel = -0.4;
3105       obj = level.MakeMapObject(ix+16+4, iy+6, 'oPoof');
3106       if (obj) obj.xVel = 0.4;
3107       instanceRemove();
3108       //level.osdMessage("THE MYSTERIOUS DOOR IS UNLOCKED!", 3.33);
3109       level.osdMessageTalk("THE MYSTERIOUS DOOR IS UNLOCKED!", timeout:3.33, inShopOnly:false);
3110       return;
3111     }
3112     // no crown
3113     status = SCEPTRE;
3114     level.osdMessage("THE SCEPTRE FITS...\nBUT NOTHING IS HAPPENING!", 3.33);
3115   }
3119 defaultproperties {
3120   objType = 'oGoldDoor';
3121   desc = "Gold Door";
3122   desc2 = "A door with a golden seal on it.";
3124   status = CLOSED;
3126   solid = false;
3127   invincible = true;
3128   moveable = false;
3129   spriteName = 'sGoldDoor';
3130   toSpecialGrid = true; // it must think
3132   depth = 2000;
3136 // ////////////////////////////////////////////////////////////////////////// //
3137 class MapTileUnlockedGoldDoor['oXGold'] : MapTile;
3140 override void doSprayRubble () {
3144 override void setupTile () {
3148 // it opens if player carrying a sceptre, and has a crown
3149 override void thinkFrame () {
3153 defaultproperties {
3154   objType = 'oXGold';
3155   desc = "Gold Door";
3156   desc2 = "A door with an opened golden seal on it.";
3158   solid = false;
3159   invincible = true;
3160   moveable = false;
3161   spriteName = 'sExit';
3162   toSpecialGrid = false; // it must think
3163   exit = true;
3165   depth = 2000;
3169 // ////////////////////////////////////////////////////////////////////////// //
3170 class MapTileBlackMarketDoor['oXMarket'] : MapTile;
3173 override void doSprayRubble () {
3177 override void setupTile () {
3178   writeln("*** GENERATED BLACK MARKET ENTRANCE ***");
3179   /*
3180   if (global.hasSpectacles || global.hasUdjatEye) {
3181     level.setTileAt(ix/16, iy/16, none);
3182     //depth = 101;
3183   }
3184   */
3188 // it opens if player carrying a sceptre, and has a crown
3189 override void thinkFrame () {
3193 defaultproperties {
3194   objType = 'oXMarket';
3195   desc = "Market Exit";
3196   desc2 = "A door leading to Black Market.";
3198   solid = false;
3199   invincible = true;
3200   moveable = false;
3201   spriteName = 'sExit';
3202   toSpecialGrid = true; // it must be hidden behind the normal tile
3203   exit = true;
3205   depth = 2000;
3209 // ////////////////////////////////////////////////////////////////////////// //
3210 class MapTileMoai['oMoai'] : MapTile;
3212 override int width () { return 16; }
3213 override int height () { return 64; }
3215 override void doSprayRubble () {}
3216 override void setupTile () {}
3217 override void thinkFrame () {}
3219 defaultproperties {
3220   objType = 'oMoai';
3221   desc = "Moai Head";
3222   desc2 = "";
3224   solid = true;
3225   invincible = true;
3226   moveable = false;
3227   spriteName = 'sMoai';
3228   toSpecialGrid = true; // it is big
3229   depth = 1000;
3233 // ////////////////////////////////////////////////////////////////////////// //
3234 class MapTileMoai3['oMoai3'] : MapTileMoai;
3236 defaultproperties {
3237   spriteName = 'sMoai3';
3241 // ////////////////////////////////////////////////////////////////////////// //
3242 class MapTileMoaiInside['oMoaiInside'] : MapTile;
3244 override int width () { return 16; }
3245 override int height () { return 48; }
3247 override void doSprayRubble () {}
3248 override void setupTile () {}
3249 override void thinkFrame () {}
3251 defaultproperties {
3252   objType = 'oMoaiInside';
3253   desc = "Moai Head";
3254   desc2 = "";
3256   solid = true;
3257   invincible = true;
3258   moveable = false;
3259   spriteName = 'sMoaiInside';
3260   toSpecialGrid = true; // it is big
3261   depth = 92;
3265 // ////////////////////////////////////////////////////////////////////////// //
3266 class MapTileLamp['oLamp'] : MapTile;
3268 bool redLamp;
3271 override bool onExplosionTouch (MapObject xplo) {
3272   int x = ix, y = iy;
3273   instanceRemove();
3274   //level.MakeMapObject(x+8, y+12, (redLamp ? 'oLampRedItem' : 'oLampItem'));
3275   return true;
3279 override void doSprayRubble () {
3280   if (global.cityOfGold == 1) {
3281     ore = 2;
3282     scrDropOre();
3283   }
3287 override void setupTile () {
3288   if (redLamp) spriteName = 'sLampRed';
3292 override void thinkFrame () {
3293   // 0: dark
3294   // 1: max light
3295   // 2: medium light
3296   float llev = (trunc(imageFrame) < 2 ? 0.0 : 2.0-imageFrame/2.0);
3297   lightRadius = 128+round(4*llev);
3298   //::thinkFrame();
3300   int x = ix, y = iy;
3301   // drop lamp item if it has no support
3302   if (!level.checkTilesInRect(x+5, y-1, 6, 1)) {
3303     instanceRemove();
3304     level.MakeMapObject(x+8, y+12, (redLamp ? 'oLampRedItem' : 'oLampItem'));
3305     return;
3306   }
3310 defaultproperties {
3311   objType = 'oLamp';
3312   desc = "Lamp";
3313   desc2 = "A bright, geothermal-powered lantern, with a stainless steel frame.";
3314   imageSpeed = 0.5;
3315   solid = false;
3316   moveable = false;
3317   spriteName = 'sLamp';
3318   toSpecialGrid = true; // it must think
3319   depth = 201;
3323 // ////////////////////////////////////////////////////////////////////////// //
3324 class MapTileLampRed['oLampRed'] : MapTileLamp;
3326 defaultproperties {
3327   desc2 = "A geothermal-powered lantern that emits red light. Often found in brothels.";
3328   redLamp = true;