some fixes for new vccrun
[k8vacspelynky.git] / mapent / MapTile.vc
bloba27b0329fe05bbfc226593b98262f8c5fa66b83f
1 /**********************************************************************************
2  * Copyright (c) 2008, 2009 Derek Yu and Mossmouth, LLC
3  * Copyright (c) 2010, Moloch
4  * Copyright (c) 2018, Ketmar Dark
5  *
6  * This file is part of Spelunky.
7  *
8  * You can redistribute and/or modify Spelunky, including its source code, under
9  * the terms of the Spelunky User License.
10  *
11  * Spelunky is distributed in the hope that it will be entertaining and useful,
12  * but WITHOUT WARRANTY.  Please see the Spelunky User License for more details.
13  *
14  * The Spelunky User License should be available in "Game Information", which
15  * can be found in the Resource Explorer, or as an external file called COPYING.
16  * If not, please obtain a new copy of Spelunky from <http://spelunkyworld.com/>
17  *
18  **********************************************************************************/
19 class MapTile : MapEntity;
21 enum DefWidth = 16;
22 enum DefHeight = 16;
24 transient SpriteImage sprite;
25 name spriteName;
26 //name spriteLeftDeco, spriteRightDeco;
27 name rubbleSprite1, rubbleSprite2;
29 // object flags
30 bool solid = true;
31 bool invisible;
32 bool invincible;
33 bool water;
34 bool lava;
35 bool ice;
36 bool ladder;
37 bool laddertop;
38 bool tree;
39 bool leaves;
40 bool moveable;
41 bool spikes;
42 bool woodenSpikes;
43 bool enter;
44 bool exit;
45 bool specialExit; // don't generate angry shopkeeper here
46 bool springtrap;
47 bool altar;
48 bool sacrificingAltar;
49 bool shopWall; // may be set to `true` for non-solid tiles too
50 bool cleanDeath;
51 bool smashed;
52 bool bloody;
53 bool platform; // oLadderTop, oLeaves, oTreeBranch
54 bool toSpecialGrid;
55 bool border; // border tile
56 bool hideOre;
57 bool litWholeTile; // lit whole tile with maximum light
58 bool ignoreFrameOffsetX, ignoreFrameOffsetY;
59 bool dontReplaceOthers; // this tile won't replace existing tiles when put on a map
60 bool immuneToReplacement; // this tile immune to replacement by other tiles
61 int ore = 0;
63 transient bool waterMoved; // any move
64 transient bool waterMovedDown;
65 transient int waterSlideCounter;
66 transient int waterSlideOldX, waterSlideOldY;
69 //float myGrav = 0.6;
70 //float grav = 0.6; // the gravity
71 float myGravLimit = 8;
73 // x and y are offsets
74 MapBackTile bgback;
75 MapBackTile bgfront;
77 MapObject gem;
78 bool gemDestroyWithTile;
81 // for default doors
82 void snapToExit (MapEntity e) {
83   if (!e || !e.isInstanceAlive) return;
84   e.fltx = ix+8;
85   e.flty = iy+8;
86   e.updateGrid();
90 // ////////////////////////////////////////////////////////////////////////// //
91 override void Destroy () {
92   delete gem;
94   while (bgback) {
95     auto t = bgback;
96     bgback = t.next;
97     delete t;
98   }
100   while (bgfront) {
101     auto t = bgfront;
102     bgfront = t.next;
103     delete t;
104   }
106   ::Destroy();
110 override void onLoaded () {
111   ::onLoaded();
112   if (spriteName) sprite = level.sprStore[spriteName];
113   for (MapBackTile bt = bgback; bt; bt = bt.next) bt.onLoaded();
114   for (MapBackTile bt = bgfront; bt; bt = bt.next) bt.onLoaded();
115   if (gem) gem.onLoaded();
119 string getExitMessage () {
120   return "";
124 override SpriteImage getSprite (optional out bool doMirror) {
125   doMirror = false;
126   return sprite;
130 override SpriteFrame getSpriteFrame (optional out bool doMirror, optional out int x0, optional out int y0, optional out int x1, optional out int y1) {
131   auto spr = getSprite(doMirror!optional);
132   if (!spr || spr.frames.length == 0) return none;
133   auto spf = spr.frames[trunci(imageFrame)%spr.frames.length];
134   if (!spf) return none;
135   if (specified_x0 || specified_x1) {
136     x0 = (ignoreFrameOffsetX ? 0 : -spf.xofs);
137     x1 = x0+spf.tex.width;
138   }
139   if (specified_y0 || specified_y1) {
140     y0 = (ignoreFrameOffsetY ? 0 : -spf.yofs);
141     y1 = y0+spf.tex.height;
142   }
143   return spf;
147 override void clearSprite () {
148   sprite = none;
149   spriteName = '';
153 override void setSprite (name sprNameL, optional name sprNameR) {
154   if (!sprNameL && sprNameR) sprNameL = sprNameR;
155   if (spriteName != sprNameL) {
156     spriteName = sprNameL;
157     sprite = (sprNameL ? level.sprStore[sprNameL] : none);
158     imageFrame = 0;
159     // fix "active" flag
160     if (!toSpecialGrid) {
161       auto spr = getSprite();
162       active = (spr && spr.frames.length > 1);
163     }
164     /*
165     if (spriteName == 'sBrick' || spriteName == 'sBrick2' ||
166         spriteName == 'sBrickDown' ||
167         spriteName == 'sBrickGold' || spriteName == 'sBrickGoldBig' ||
168         spriteName == 'sCaveSmooth' ||
169         spriteName == 'sCaveUp' || spriteName == 'sCaveUp2')
170     {
171       rubbleSprite1 = 'sRubble';
172       rubbleSprite2 = 'sRubbleSmall';
173     }
174     */
175   }
179 // ////////////////////////////////////////////////////////////////////////// //
180 override int width () { return DefWidth; }
181 override int height () { return DefHeight; }
184 void beautifyTile () {}
185 void scrSetupBlockTop () {}
186 void scrSetupBlockBottom () {}
187 void scrSetupBlockLeft () {}
188 void scrSetupBlockRight () {}
191 // ////////////////////////////////////////////////////////////////////////// //
192 void setGem (name oname, optional bool visibility) {
193   if (gem) {
194     gem.ownerTile = none;
195     gem.instanceRemove();
196     gem = none;
197   }
198   if (!oname) return;
199   gem = level.MakeMapObject(ix+8, iy+8, oname);
200   if (!gem) return;
201   gem.ownerTile = self;
202   gem.hiddenTreasure = !global.hasSpectacles && !global.hasUdjatEye;
203   if (specified_visibility) gem.hiddenTreasure = !visibility;
204   gem.active = false;
205   gem.spectral = true;
206   if (gem.hiddenTreasure) gem.visible = false;
207   gem.saveInterpData();
211 void setGemObject (MapObject obj, optional bool visibility) {
212   if (obj) {
213     if (obj.ownerTile == self) {
214       if (gem != obj) FatalError("MapTile::setGemObject: WTF?! (0)");
215     }
216     if (gem == obj) FatalError("MapTile::setGemObject: WTF?! (1)");
217   }
218   if (gem) {
219     gem.ownerTile = none;
220     gem.instanceRemove();
221     if (!gem.grid) delete gem;
222     gem = none;
223   }
224   if (!obj) return;
225   if (obj.ownerTile) {
226     obj.ownerTile.gem = none;
227     obj.ownerTile = none;
228   }
229   gem = obj;
230   gem.ownerTile = self;
231   gem.hiddenTreasure = !global.hasSpectacles && !global.hasUdjatEye;
232   if (specified_visibility) gem.hiddenTreasure = !visibility;
233   gem.active = false;
234   gem.spectral = true;
235   if (gem.hiddenTreasure) gem.visible = false;
236   gem.fltx = ix+8;
237   gem.flty = iy+8;
238   gem.saveInterpData();
242 void convertGemObjectToDiamond (MapObject ghost) {
243   auto oldgem = gem;
244   if (oldgem && oldgem isa ItemBigGem) {
245     if (ghost) {
246       if (!ghost.isInstanceAlive) return;
247       if (!gem.isRectHitSimple(ghost.x0, ghost.y0, ghost.width, ghost.height)) return;
248     }
249     auto diamond = level.MakeMapObject(oldgem.ix, oldgem.iy, 'oDiamond');
250     setGemObject(diamond);
251   }
255 // ////////////////////////////////////////////////////////////////////////// //
256 void doCreateOre (int probSap, int probEmer, int probRuby, int probItem) {
257   int n = global.randRoom(1, 100);
258        if (n < 20) ore = 1; // sprite_index = sBrickGold;
259   else if (n < 30) ore = 2; // sprite_index = sBrickGoldBig;
260        if (probSap > 0 && global.randRoom(1, probSap) == 1) setGem('oSapphireBig');
261   else if (probEmer > 0 && global.randRoom(1, probEmer) == 1) setGem('oEmeraldBig');
262   else if (probRuby > 0 && global.randRoom(1, probRuby) == 1) setGem('oRubyBig');
263   else if (probItem > 0 && global.randRoom(1, probItem) == 1) setGemObject(level.scrGenerateItem(ix+8, iy+8, GameLevel::GenItemSet.Underground));
267 void copyOreFrom (MapTile t) {
268   if (t == self) return;
269   if (t) {
270     setGemObject(t.gem);
271     ore = t.ore;
272   } else {
273     setGemObject(none);
274     ore = 0;
275   }
279 void removeOre () {
280   ore = 0;
284 void closeExit () {
285   if (exit) setSprite('sEntrance');
289 void openExit () {
290   if (exit) setSprite('sExit');
294 bool isExitActive () {
295   return (visible && exit && spriteName == 'sExit');
299 // ////////////////////////////////////////////////////////////////////////// //
300 void setupTile () {
301   //hangeable = true;
302   switch (objName) {
303     case 'oLadderOrange':
304       //objName = 'oLadder';
305       //goto case 'oLadder';
306     case 'oLadder':
307       objType = 'oLadder';
308       ladder = true;
309       solid = false;
310       invincible = true;
311       spriteName = 'sLadder';
312       break;
313     case 'oLadderTop':
314       objType = 'oLadder';
315       //ladder = true; // no, laddertop is not a ladder
316       laddertop = true;
317       platform = true;
318       solid = false;
319       invincible = true;
320       spriteName = 'sLadderTop';
321       break;
322     case 'oVineTop':
323       objType = 'oVineTop';
324       //ladder = true; // no, laddertop is not a ladder
325       laddertop = true;
326       platform = true;
327       solid = false;
328       invincible = true;
329       spriteName = 'sVineTop';
330       break;
331     case 'oPushBlock':
332       //objName = 'oPushBlock';
333       objType = 'oBlock';
334       moveable = true;
335       spriteName = (global.cityOfGold == 1 ? 'sGoldBlock' : 'sBlock');
336       break;
337     case 'oPushIceBlock':
338       //objName = 'oPushBlock';
339       objType = 'oBlock';
340       ice = true;
341       moveable = true;
342       spriteName = 'sIceBlock';
343       break;
344     case 'oSolidIceBlock':
345       //objName = 'oIceBlock';
346       //goto case 'oIceBlock';
347       objType = 'oBlock';
348       ice = true;
349       solid = true;
350       moveable = false;
351       moveable = true; //k8: why, let it be an easter egg
352       spriteName = 'sIceBlock';
353       break;
354     case 'oIceBlock':
355       objType = 'oBlock';
356       ice = true;
357       solid = true;
358       moveable = true;
359       spriteName = 'sIceBlock';
360       break;
361     case 'oEdgeBrick':
362       // no sense to create ore here
363       objType = 'oBrick';
364       spriteName = (global.randRoom(1, 10) == 1 ? 'sBrick2' : 'sBrick');
365       rubbleSprite1 = 'sRubble';
366       rubbleSprite2 = 'sRubbleSmall';
367       break;
368     case 'oBrickSmooth':
369       objType = 'oBrick';
370       spriteName = 'sCaveSmooth';
371       rubbleSprite1 = 'sRubble';
372       rubbleSprite2 = 'sRubbleSmall';
373       break;
374     case 'oSpikesWood':
375       objType = 'oSpikes';
376       solid = false;
377       spikes = true;
378       woodenSpikes = true;
379       spriteName = 'sSpikesWood';
380       break;
381     case 'oSpikes':
382       objType = 'oSpikes';
383       solid = false;
384       spikes = true;
385       spriteName = 'sSpikes';
386       break;
387     case 'oEntrance':
388       objType = 'oEntrance';
389       enter = true;
390       solid = false;
391       invincible = true;
392       depth = 2000;
393       spriteName = 'sEntrance';
394       break;
395     case 'oExit':
396       objType = 'oExit';
397       solid = false;
398       invincible = true;
399       exit = true;
400       depth = 2000;
401       spriteName = 'sExit';
402       break;
403     case 'oAltarLeft':
404       objType = 'oAltar';
405       altar = true;
406       spriteName = 'sAltarLeft';
407       break;
408     case 'oAltarRight':
409       objType = 'oAltar';
410       altar = true;
411       spriteName = 'sAltarRight';
412       break;
413     case 'oSacAltarLeft':
414       objType = 'oAltar';
415       altar = true;
416       sacrificingAltar = true;
417       spriteName = 'sSacAltarLeft';
418       break;
419     case 'oSacAltarRight':
420       objType = 'oAltar';
421       altar = true;
422       sacrificingAltar = true;
423       spriteName = 'sSacAltarRight';
424       break;
425     case 'oSign':
426       objType = 'oSign';
427       solid = false;
428       spriteName = 'sSign';
429       break;
430     case 'oSignGeneral':
431       objType = 'oSign';
432       solid = false;
433       spriteName = 'sSignGeneral';
434       break;
435     case 'oSignBomb':
436       objType = 'oSign';
437       solid = false;
438       spriteName = 'sSignBomb';
439       break;
440     case 'oSignWeapon':
441       objType = 'oSign';
442       solid = false;
443       spriteName = 'sSignWeapon';
444       break;
445     case 'oSignClothing':
446       objType = 'oSign';
447       solid = false;
448       spriteName = 'sSignClothing';
449       break;
450     case 'oSignRare':
451       objType = 'oSign';
452       solid = false;
453       spriteName = 'sSignRare';
454       break;
455     case 'oSignCraps':
456       objType = 'oSign';
457       solid = false;
458       spriteName = 'sSignCraps';
459       break;
460     case 'oSignKissing':
461       objType = 'oSign';
462       solid = false;
463       spriteName = 'sSignKissing';
464       break;
465     /*
466     case 'oMoai':
467       objType = 'oMoai';
468       solid = true;
469       invincible = true;
470       spriteName = 'sMoai';
471       break;
472     */
473     case 'oMoai2':
474       objType = 'oMoai';
475       solid = true;
476       invincible = true;
477       spriteName = 'sMoai2';
478       break;
479     /*
480     case 'oMoai3':
481       objType = 'oMoai';
482       solid = true;
483       invincible = true;
484       spriteName = 'sMoai3';
485       break;
486     */
487     /*
488     case 'oMoaiInside':
489       objType = 'oMoaiInside';
490       solid = true;
491       invincible = true;
492       spriteName = 'sMoaiInside';
493       depth = 92;
494       break;
495     */
496     default:
497       FatalError(va("unknown map tile type '%n'", objName));
498   }
499   //!active = moveable || toSpecialGrid || lava || water; // will be done in MakeMapTile
500   //if (!solid) hangeable = false; // just in case
504 void setupTileSprite () {
505   if (!spriteName) FatalError("forgot to set sprite for tile type '", objName, "'");
506   sprite = level.sprStore[spriteName];
510 override bool initialize () {
511   if (!::initialize()) return false;
512   setupTile();
513   setupTileSprite();
514   if (moveable && depth == 1001) depth = 1000;
515   if ((exit || enter) && depth == 1001) depth = 2000;
516   if (lava || water) {
517     ++level.liquidTileCount;
518     level.checkWater = true;
519   }
520   // will be done in MakeMapTile
521   /*
522   // animated tiles must be active
523   if (!active) {
524     auto spr = getSprite();
525     if (spr && spr.frames.length > 1) {
526       writeln("activated animated tile '", objName, "'");
527       active = true;
528     }
529   }
530   */
531   return true;
535 // ////////////////////////////////////////////////////////////////////////// //
536 // for now, it works only for spikes
537 final void makeBloody () {
538   if (bloody) return;
539   if (!spikes) return;
540   bloody = true;
541   setSprite(woodenSpikes ? 'sSpikesWoodBlood' : 'sSpikesBlood');
545 // ////////////////////////////////////////////////////////////////////////// //
546 final void appendBackBack (MapBackTile tile, optional int xofs, optional int yofs) {
547   if (!tile) return;
548   if (tile.next) FatalError("MapTile::appendBackBack: wtf?!");
549   tile.fltx = (specified_xofs ? float(xofs) : 0.0);
550   tile.flty = (specified_yofs ? float(yofs) : 16.0);
551   MapBackTile last = bgback;
552   if (last) {
553     while (last.next) last = last.next;
554     last.next = tile;
555   } else {
556     bgback = tile;
557   }
561 final void appendBackFront (MapBackTile tile, optional int xofs, optional int yofs) {
562   if (!tile) return;
563   if (tile.next) FatalError("MapTile::appendBackFront: wtf?!");
564   tile.fltx = (specified_xofs ? float(xofs) : 0.0);
565   tile.flty = (specified_yofs ? float(yofs) : -16.0);
566   MapBackTile last = bgfront;
567   if (last) {
568     while (last.next) last = last.next;
569     last.next = tile;
570   } else {
571     bgfront = tile;
572   }
576 // ////////////////////////////////////////////////////////////////////////// //
578 override void onDestroy () {
583 // ////////////////////////////////////////////////////////////////////////// //
584 override void thinkFrame () {
585   if (!moveable) return;
586   // applies the acceleration
587   //xVel += xAcc;
588   //yVel += yAcc;
590   // approximates the "active" variables
591   if (fabs(xVel) < 0.001) xVel = 0;
592   if (fabs(yVel) < 0.001) yVel = 0;
593   //if (fabs(xAcc) < 0.0001) xAcc = 0;
594   //if (fabs(yAcc) < 0.0001) yAcc = 0;
596   yVel = fclamp(yVel+myGrav, -myGravLimit, myGravLimit);
597   int newY = roundi(flty+yVel); //!!!
598   // check if we need (and can) move down
599   int w = width, hp1 = height+1;
600   auto oldsolid = solid;
601   solid = false;
602   while (newY > iy) {
603     // made non-solid temporarily
604     auto hasAnything = level.checkTilesInRect(x0, y0, w, hp1, delegate bool (MapTile t) {
605       if (t == self) return false;
606       return t.solid;
607     });
608     if (hasAnything) {
609       // hit something, stop right there
610       if (yVel > myGrav*3) playSound('sndThud');
611       yVel = 0;
612       flty = iy;
613       break;
614     }
615     // move us
616     shiftY(1);
617   }
618   solid = oldsolid;
620   if (flty > level.tilesHeight*16+16 && yVel >= 0) {
621     cleanDeath = true;
622     instanceRemove();
623   }
627 // ////////////////////////////////////////////////////////////////////////// //
628 final void getInterpCoordsForTile (float currFrameDelta, int scale, out int drwx, out int drwy) {
629   if (waterSlideCounter) {
630     --waterSlideCounter;
631     drwx = ix*scale;
632     drwy = iy*scale;
633     int sgnx = sign(waterSlideOldX-ix);
634     int sgny = sign(waterSlideOldY-iy);
635     if ((sgnx|sgny) == 0) {
636       waterSlideCounter = 0;
637     } else {
638       drwx += (sgnx*(waterSlideCounter*4))*scale;
639       drwy += (sgny*(waterSlideCounter*4))*scale;
640     }
641   } else {
642     getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
643   }
647 void drawWithOfsBack (int xpos, int ypos, int scale, float currFrameDelta) {
648   //if (backTile) backTile.drawAt(xpos, ypos, scale);
649   if (invisible || !visible || !bgback) return;
651   /*
652   bool doMirror;
653   int fx0, fy0, fx1, fy1;
654   auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
656   // non-moveable and non-special tiles need not to be interpolated
657   int drwx, drwy;
658   getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
659   */
661   for (MapBackTile bt = bgback; bt; bt = bt.next) {
662     bt.fltx += fltx;
663     bt.flty += flty;
664     bt.drawWithOfs(xpos, ypos, scale, currFrameDelta);
665     bt.fltx -= fltx;
666     bt.flty -= flty;
667   }
671 void drawWithOfsFront (int xpos, int ypos, int scale, float currFrameDelta) {
672   //if (backTile) backTile.drawAt(xpos, ypos, scale);
673   if (invisible || !visible /*|| (!bgfront && !spriteLeftDeco && !spriteRightDeco)*/) return;
675   /*
676   bool doMirror;
677   int fx0, fy0, fx1, fy1;
678   auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
680   // non-moveable tiles need not to be interpolated
681   int drwx, drwy;
682   getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
683   */
685   for (MapBackTile bt = bgfront; bt; bt = bt.next) {
686     bt.fltx += fltx;
687     bt.flty += flty;
688     bt.drawWithOfs(xpos, ypos, scale, currFrameDelta);
689     bt.fltx -= fltx;
690     bt.flty -= flty;
691   }
693   /*
694   if (spriteLeftDeco) {
695     auto spr = level.sprStore[spriteLeftDeco];
696     if (spr && spr.frames.length) {
697       auto spf = spr.frames[0];
698       spf.blitAt((ix-16)*scale-xpos, iy*scale-ypos, scale);
699     }
700   }
702   if (spriteRightDeco) {
703     auto spr = level.sprStore[spriteRightDeco];
704     if (spr && spr.frames.length) {
705       auto spf = spr.frames[0];
706       spf.blitAt((ix+16)*scale-xpos, iy*scale-ypos, scale);
707     }
708   }
709   */
713 override void drawWithOfs (int xpos, int ypos, int scale, float currFrameDelta) {
714   if (invisible || !visible) return;
716   bool doMirror;
717   int fx0, fy0, fx1, fy1;
718   auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
720   // non-moveable tiles need not to be interpolated
721   int drwx, drwy;
722   getInterpCoordsForTile(currFrameDelta, scale, out drwx, out drwy);
724   //auto oclr = GLVideo.color;
725   //if (moveable) GLVideo.color = 0xff_7f_00;
727   if (spf) {
728     fx0 = drwx+fx0*scale-xpos;
729     fy0 = drwy+fy0*scale-ypos;
730     fx1 = drwx+fx1*scale-xpos;
731     fy1 = drwy+fy1*scale-ypos;
732     if (!doMirror) {
733       spf.tex.blitExt(fx0, fy0, fx1, fy1, 0, 0, spf.width, spf.height);
734     } else {
735       spf.tex.blitExt(fx0, fy0, fx1, fy1, spf.width, 0, 0, spf.height);
736     }
737   } else if (ore > 0) {
738     fx0 = drwx-xpos;
739     fy0 = drwy-ypos;
740     fx1 = drwx-xpos;
741     fy1 = drwy-ypos;
742   }
744   //GLVideo.color = oclr;
746   if (ore > 0 && !hideOre) {
747     auto ospr = level.sprStore[ore == 1 ? 'sGold' : 'sGoldBig'];
748     ospr.frames[0].blitAt(fx0, fy0, scale);
749   }
753 // ////////////////////////////////////////////////////////////////////////// //
754 // called by Destroy event of blocks that can contain ore (oBrick, oLush, etc)
755 void scrDropOre () {
756   if (ore < 1) return;
758   int x = ix, y = iy;
760   foreach (int i; 0..3) {
761     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');
762     if (gold) {
763       //gold = instance_create(x+8+rand(0,4)-rand(0,4), y+8+rand(0,4)-rand(0,4), oGoldChunk);
764       gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
765       gold.yVel = global.randOther(2, 4);
766     }
767   }
769   if (ore > 1) {
770     // sGoldBig
771     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');
772     if (gold) {
773       gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
774       gold.yVel = global.randOther(2, 4);
775     }
776   }
778   ore = 0;
782 // ////////////////////////////////////////////////////////////////////////// //
783 private final bool cbIsSolidBrick (MapTile t) { return t.solid; }
784 private final bool cbIsBrickOrLushTile (MapTile t) { return (t.objType == 'oBrick' || t.objType == 'oLush'); }
787 // ////////////////////////////////////////////////////////////////////////// //
788 // called by solids during destroy event (oBrick etc) when destroy with force such as a boulder or explosion
789 // these rubble bits shower over the foreground and are not stopped by solids
790 final void scrSprayRubble (int x, int y, int count) {
791   name spr0 = rubbleSprite1;
792   name spr1 = rubbleSprite2; // small
793   while (count-- > 0) {
794     auto rubble = level.MakeMapObject(
795                     x+8+global.randOther(0, 8)-global.randOther(0, 8),
796                     y+8+global.randOther(0, 8)-global.randOther(0, 8), 'oRubblePiece'); //'oRubblePieceNonSolid');
797     rubble.setSprite(global.randOther(1, 3) == 1 ? spr0 : spr1);
798     rubble.xVel = global.randOther(0, 4)-global.randOther(0, 4);
799     rubble.yVel = -global.randOther(4, 8);
800     rubble.yAcc = 0.6;
801     //rubble.image_blend = image_blend;
802   }
806 final void scrDropRubble (int x, int y, int count) {
807   name spr0 = rubbleSprite1;
808   name spr1 = rubbleSprite2; // small
809   while (count-- > 0) {
810     int rx = x+8+global.randOther(0, 8)-global.randOther(0, 8);
811     int ry = y+8+global.randOther(0, 8)-global.randOther(0, 8);
812     if (global.randOther(1, 3) == 1) {
813       auto rubble = level.MakeMapObject(rx, ry, 'oRubble');
814       rubble.setSprite(spr0);
815       //rubble.image_blend = image_blend;
816     } else {
817       auto rubble = level.MakeMapObject(rx, ry, 'oRubbleSmall');
818       rubble.setSprite(spr1);
819       //rubble.image_blend = image_blend;
820     }
821   }
825 void smashedCheckUpTile () {
826   level.checkTilesInRect(ix, iy-16, 16, 16, delegate bool (MapTile t) {
827     //writeln("mtl: '", GetClassName(t.Class), "'; spikes=", t.spikes);
828     if (t.spikes || t isa MapTileGraveBase || t isa MapTileTikiTorch || t isa MapTileSacAltarBase) {
829       t.invincible = false;
830       t.cleanDeath = cleanDeath;
831       t.smashMe();
832       t.instanceRemove();
833     }
834     return false;
835   });
839 void doSprayRubble () {
840   if (objType == 'oBlock' || objType == 'oBrick' || objType == 'oLush' || objType == 'oTemple' || solid) {
841     if (global.cityOfGold != 1) {
842       if (smashed) scrSprayRubble(ix, iy, 3);
843       scrDropRubble(ix, iy, 3);
844     } else {
845       ore = 2;
846       scrDropOre();
847     }
848   }
852 final bool isVineTile (MapTile t) { return (t.objType == 'oVineTop' || t.objType == 'oVine'); }
855 // this should be called from `smashMe()`
856 void checkSmashedVineSupport () {
857   if (!solid || isVineTile(self)) return;
858   // check for support tile ('cause this can be a moving tile, and not an actual support)
859   auto support = level.checkTileAtPoint(ix, iy, delegate bool (MapTile t) {
860     if (t == self) return false;
861     return (t.isInstanceAlive && t.solid);
862   });
863   if (support) return;
864   // no support
865   int x = ix+8;
866   int y = iy+16+8;
867   auto vine = level.checkTileAtPoint(x, y, &isVineTile);
868   if (!vine) return;
869   // yay, we found a vine; now remove it, going all the way down
870   for (;;) {
871     vine = level.checkTileAtPoint(x, y, &isVineTile);
872     if (!vine) return;
873     vine.invincible = false;
874     vine.cleanDeath = cleanDeath;
875     vine.smashMe();
876     y += 16;
877   }
881 bool smashMe () {
882   if (invincible || !isInstanceAlive) return false;
883   smashed = true;
885   checkSmashedVineSupport();
887   if (/*!global.cemetary &&*/ isVineTile(self)) {
888     level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
889     level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
890     cleanDeath = true;
891   }
893   if (altar) level.scrTriggerIdolAltar(stolenIdol:true);
895   instanceRemove();
896   smashedCheckUpTile();
898   if (gem) {
899     if (gemDestroyWithTile) {
900       gem.instanceRemove();
901     } else {
902       if (!gem.grid) FatalError("tile gem has no grid");
903       gem.hiddenTreasure = false;
904       gem.visible = true;
905       gem.active = true;
906       gem.spectral = false;
907     }
908     gem = none; // don't destroy it with tile
909   }
911   scrDropOre();
913   //bool smashed = true;
914   if (!cleanDeath) doSprayRubble();
916   /*
917   if (other.object_index == oBoulder) {
918     if (other.integrity &gt; 0) dosomething = false; //exit;
919   }
920   */
922   //with (oTreasure) state = 1;
923   //with (oSpikes) if (not collision_point(x, y+16, oSolid, 0, 0)) instance_destroy();
925   if (shopWall) {
926     level.scrShopkeeperAnger(GameLevel::SCAnger.TileDestroyed);
927   }
929   // Moloch
930   int x = ix, y = iy;
931   MapTile obj = level.checkTileAtPoint(x, y-16, &cbIsSolidBrick);
932   if (obj) obj.scrSetupBlockBottom();
933   obj = level.checkTileAtPoint(x, y+16, &cbIsSolidBrick);
934   if (obj) obj.scrSetupBlockTop();
935   obj = level.checkTileAtPoint(x-16, y, &cbIsSolidBrick);
936   if (obj) obj.scrSetupBlockLeft();
937   obj = level.checkTileAtPoint(x+16, y, &cbIsSolidBrick);
938   if (obj) obj.scrSetupBlockRight();
940   // Moloch
941   /* was commented in the original
942   instance_activate_object(oLadder);
943   obj = collision_point(x+8, y+24, oLadder, 0, 0);
944   with (obj) {
945     if (sprite_index == sVineTop or sprite_index == sVine or
946         sprite_index == sVineSource or sprite_index == sVineBottom)
947     {
948       instance_destroy();
949     }
950   }
951   */
953   return true;
957 //private transient MapObject mXplo;
960 private final bool cbExplodeTiles (MapTile t) {
961   if (!t.isInstanceAlive) return false;
962   if (t.invincible) return false;
963   switch (t.objType) {
964     case 'oSpikes':
965     case 'oTikiTorch':
966     case 'oGrave':
967     case 'oLampRed':
968     case 'oLamp':
969       t.onExplosionTouch(mXplo);
970       break;
971   }
972   return false;
977 override bool onExplosionTouch (MapObject xplo) {
978   if (invincible) {
979     //writeln("ignore inv block at (", ix/16, ",", iy/16, ")");
980     return false;
981   }
983   //int x = ix, y = iy;
985   if (smashMe()) {
986     //writeln("smashed block at (", ix/16, ",", iy/16, ")");
987     /*
988     auto oxplo = mXplo;
989     mXplo = xplo;
990     level.checkTileAtPoint(x+8, y-1, &cbExplodeTiles); //k8: why i wrote this?!
991     mXplo = oxplo;
992     */
994     /+
995     obj = instance_place(x, y, oGold);
996     if (obj != noone) with (obj) instance_destroy();
998     obj = instance_place(x, y, oGoldBig);
999     if (obj != noone) with (obj) instance_destroy();
1000     +/
1002     return true;
1003   } else {
1004     //writeln("cannot smash block at (", ix/16, ",", iy/16, ")");
1005   }
1007   return false;
1011 // ////////////////////////////////////////////////////////////////////////// //
1012 void onGotSpectacles () {
1013   if (gem) {
1014     gem.hiddenTreasure = false;
1015     gem.visible = true;
1016   }
1020 defaultproperties {
1021   objName = 'oTile';
1022   objType = 'oSolid';
1023   rubbleSprite1 = 'sRubbleTan';
1024   rubbleSprite2 = 'sRubbleTanSmall';
1025   //depth = 9666; //???
1026   depth = 1001;
1030 // ////////////////////////////////////////////////////////////////////////// //
1031 // this tile is returned instead of walkeable MapObject
1032 class MapTileTemp : MapTile;
1034 MapEntity e;
1036 // don't forget to set `fltx` and `flty`!
1037 override int x0 () { return (e ? e.x0 : 0); }
1038 override int y0 () { return (e ? e.y0 : 0); }
1039 override int width () { return (e ? e.width : 0); }
1040 override int height () { return (e ? e.height : 0); }
1042 defaultproperties {
1043   objName = 'oMapObject';
1044   objType = 'oMapObject';
1045   active = false; // just in case
1049 // ////////////////////////////////////////////////////////////////////////// //
1050 class MapTileBrick['oBrick'] : MapTile;
1053 override void setupTile () {
1054   spriteName = (global.randRoom(1, 10) == 1 ? 'sBrick2' : 'sBrick');
1055   int tileX = ix/16, tileY = iy/16;
1056   if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1057     invincible = true;
1058     border = true;
1059     //writeln("BORDER");
1060   } else {
1061     doCreateOre(100, 120, 140, 1200);
1062   }
1066 override void beautifyTile () {
1067   if (level.checkTileAtPoint(ix+8, iy-8, delegate bool (MapTile t) { return (t isa TitleTileSpecialNonSolidInvincible || t isa TitleTileBlack); })) return;
1069   // brick
1070   //if (argument1) if (global.randRoom(1, 10) == 1) sprite_index = sBrick2; // reset sprite for custom border setup
1072   int tileX = ix/16, tileY = iy/16;
1074   auto tt = level.getTileAtGrid(tileX, tileY-1);
1075   if (tt && tt.objName == 'oBlock') tt = none;
1076   //if (y == 0 || isNamedTileAt(x, y-1, 'oBrick') || isNamedTileAt(x, y-1, 'oHardBlock') || isNamedTileAt(x, y-1, 'oEdgeBrick')) up = true;
1077   bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1079   tt = level.getTileAtGrid(tileX, tileY+1);
1080   if (tt && tt.objName == 'oBlock') tt = none;
1081   //if (y == NormalTilesHeight-1 || isNamedTileAt(x, y+1, 'oBrick') || isNamedTileAt(x, y+1, 'oHardBlock') || isNamedTileAt(x, y+1, 'oEdgeBrick')) down = true;
1082   bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1084   /*
1085   tt = level.getTileAtGrid(tileX-1, tileY);
1086   if (tt && tt.objName == 'oBlock') tt = none;
1087   bool left = (tileX >= level.tilesWidth-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1089   tt = level.getTileAtGrid(tileX+1, tileY);
1090   if (tt && tt.objName == 'oBlock') tt = none;
1091   bool right = (tileX <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1092   */
1094   //if (collision_point(x-16, y, oBrick, 0, 0) or collision_point(x-16, y, oHardBlock, 0, 0)) { left = true; } // in the original
1095   //if (collision_point(x+16, y, oBrick, 0, 0) or collision_point(x+16, y, oHardBlock, 0, 0)) { right = true; } // in the original
1097   if (!up) {
1098     setSprite('sCaveUp');
1099     // add rocks and sand
1100     /*
1101     if (global.randRoom(1, 3) < 3) {
1102       tile_add('bgCaveTop', 0, 0, 16, 16, x*16, y*16-16, 3);
1103     } else {
1104       tile_add('bgCaveTop', 16, 0, 16, 16, x*16, y*16-16, 3);
1105     }
1106     */
1107     bool n = (global.randRoom(1, 3) < 3);
1108     appendBackFront(level.CreateBgTile('bgCaveTop', (n ? 0 : 16)));
1109     //instance_create(x, y-16, oCaveTop);
1110   }
1112   if (!down) {
1113     setSprite(!up ? 'sCaveUp2' : 'sBrickDown');
1114     //instance_create(x, y+16, oCaveBottom);
1115   }
1117   // k8
1118   //spriteLeftDeco = (left ? '' : 'sCaveLeft');
1119   //spriteRightDeco = (right ? '' : 'sCaveRight');
1121   /* in the original
1122   if (not left) instance_create(x-16, y, oCaveLeft);
1123   if (not right) instance_create(x+15, y, oCaveRight);
1124   */
1128 override void scrSetupBlockTop () {
1129   //int x = ix, y = iy;
1130   int tileX = ix/16, tileY = iy/16;
1131   auto tt = level.getTileAtGrid(tileX, tileY+1);
1132   //if (y >= (GameLevel::NormalTilesHeight-16)*16 || level.checkTileAtPoint(x, y+16, &cbIsBrickOrLushTile)) down = true;
1133   bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1134   // gfxhigh
1135   auto bt = level.CreateBgTile('bgCaveTop', (global.randRoom(1, 3) < 3 ? 0 : 16));
1136   //bt.flty = -16; // offset
1137   appendBackFront(bt);
1138   // other
1139   setSprite(down ? 'sCaveUp' : 'sCaveUp2');
1143 override void scrSetupBlockBottom () {
1144   //int x = ix, y = iy;
1145   //bool up = false;
1146   //if (y == 0 || level.checkTileAtPoint(x, y-16, &cbIsBrickOrLushTile)) up = true;
1147   int tileX = ix/16, tileY = iy/16;
1148   auto tt = level.getTileAtGrid(tileX, tileY-1);
1149   bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1150   setSprite(up ? 'sBrickDown' : 'sCaveUp2');
1154 defaultproperties {
1155   objType = 'oBrick';
1156   spriteName = 'sBrick';
1157   lava = false;
1158   solid = true;
1159   rubbleSprite1 = 'sRubble';
1160   rubbleSprite2 = 'sRubbleSmall';
1164 // ////////////////////////////////////////////////////////////////////////// //
1165 class MapTileHardBrick['oHardBlock'] : MapTileBrick;
1167 override void setupTile () {
1171 defaultproperties {
1172   objType = 'oHardBlock';
1173   spriteName = 'sBrick';
1174   invincible = true;
1175   solid = true;
1179 // ////////////////////////////////////////////////////////////////////////// //
1180 class MapTileBlock['oBlock'] : MapTile;
1183 override void setupTile () {
1184   int tileX = ix/16, tileY = iy/16;
1185   if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1186     invincible = true;
1187     border = true;
1188     //writeln("BORDER");
1189   }
1190   if (global.cityOfGold == 1) spriteName = 'sGoldBlock';
1194 final bool isBBTTile (MapTile t) { return (t.objType == 'oBrick' || t.objType == 'oTemple' || t.objType == 'oHardBlock'); }
1197 override void beautifyTile () {
1198   bool down = !!level.checkTileAtPoint(ix, iy+16, &isBBTTile);
1200   // don't want push blocks next to lava until we tighten up liquid draining
1201   if (level.isLavaAtPoint(ix-16, iy) || level.isLavaAtPoint(ix+16, iy)) down = false;
1203   if (down && global.randRoom(1, 4) == 1) {
1204     instanceRemove();
1205     level.MakeMapTile(ix/16, iy/16, 'oPushBlock');
1206   }
1210 defaultproperties {
1211   objType = 'oBlock';
1212   spriteName = 'sBlock';
1213   solid = true;
1217 // ////////////////////////////////////////////////////////////////////////// //
1218 class MapTileLush['oLush'] : MapTile;
1221 override void setupTile () {
1222   int tileX = ix/16, tileY = iy/16;
1223   if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1224     invincible = true;
1225     border = true;
1226     //writeln("BORDER");
1227   } else {
1228     doCreateOre(80, 100, 120, 1200);
1229   }
1233 override void beautifyTile () {
1234   if (level.checkTileAtPoint(ix+8, iy-8, delegate bool (MapTile t) { return (t isa TitleTileSpecialNonSolidInvincible || t isa TitleTileBlack); })) return;
1236   int tileX = ix/16, tileY = iy/16;
1238   auto tt = level.getTileAtGrid(tileX, tileY-1);
1239   //if (y == 0 || isNamedTileAt(x, y-1, 'oBrick') || isNamedTileAt(x, y-1, 'oHardBlock') || isNamedTileAt(x, y-1, 'oEdgeBrick')) up = true;
1240   bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral && (tt.objType == 'oLush' || tt.objType == 'oTemple' || tt.objType == 'oBrick')));
1242   tt = level.getTileAtGrid(tileX, tileY+1);
1243   //if (y == NormalTilesHeight-1 || isNamedTileAt(x, y+1, 'oBrick') || isNamedTileAt(x, y+1, 'oHardBlock') || isNamedTileAt(x, y+1, 'oEdgeBrick')) down = true;
1244   bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral && tt.objType == 'oLush'));
1246   if (!up) {
1247     setSprite('sLushUp');
1248     if (!level.isLavaAtPoint(ix+8, iy-8)) {
1249       MapBackTile bt;
1250            if (global.randRoom(1, 8) == 1) bt = level.CreateBgTile('bgCaveTop2', 32);
1251       else if (global.randRoom(1, 3) < 3) bt = level.CreateBgTile('bgCaveTop2', 0);
1252       else bt = level.CreateBgTile('bgCaveTop2', 16);
1253       appendBackFront(bt);
1254     }
1255   }
1257   if (!down) {
1258     setSprite(!up ? 'sLushUp2' : 'sLushDown');
1259     if (!level.isSolidAtPoint(ix, iy+16) && !level.isLavaAtPoint(ix, iy+16)) {
1260       MapBackTile bt;
1261            if (global.randRoom(1, 12) == 1) bt = level.CreateBgTile('bgCaveTop2', 48);
1262       else if (global.randRoom(1, 12) == 1) bt = level.CreateBgTile('bgCaveTop2', 64);
1263       appendBackBack(bt);
1264     }
1265     //instance_create(x, y+16, oLushBottom); // in the original
1266   }
1270 override void scrSetupBlockTop () {
1271   int tileX = ix/16, tileY = iy/16;
1272   auto tt = level.getTileAtGrid(tileX, tileY+1);
1273   bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1274   //setSprite('sLushUpBare');
1275   if (!down) {
1276     setSprite(spriteName == 'sLushUp' || spriteName == 'sLushUp2' || spriteName == 'sLushUp3' ? 'sLushUpBareTop' : 'sLushUpBare2');
1277   } else {
1278     setSprite('sLushUpBare');
1279   }
1283 override void scrSetupBlockBottom () {
1284   int tileX = ix/16, tileY = iy/16;
1285   auto tt = level.getTileAtGrid(tileX, tileY-1);
1286   bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1287   if (!up) {
1288     setSprite(spriteName == 'sLushUp' || spriteName == 'sLushUp2' || spriteName == 'sLushUp3' ? 'sLushUpBareBottom' : 'sLushUpBare2');
1289   } else {
1290     setSprite('sLushDownBare');
1291   }
1295 defaultproperties {
1296   objType = 'oLush';
1297   spriteName = 'sLush';
1298   lava = false;
1299   solid = true;
1300   rubbleSprite1 = 'sRubbleLush';
1301   rubbleSprite2 = 'sRubbleLushSmall';
1305 // ////////////////////////////////////////////////////////////////////////// //
1306 class MapTileDark['oDark'] : MapTile;
1309 override void setupTile () {
1310   int tileX = ix/16, tileY = iy/16;
1311   if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1312     invincible = true;
1313     border = true;
1314     //writeln("BORDER");
1315   } else {
1316     doCreateOre(40, 60, 80, 1200);
1317   }
1321 override void beautifyTile () {
1322   if (level.checkTileAtPoint(ix+8, iy-8, delegate bool (MapTile t) { return (t isa TitleTileSpecialNonSolidInvincible || t isa TitleTileBlack); })) return;
1324   // brick
1325   //if (argument1) if (global.randRoom(1, 10) == 1) sprite_index = sBrick2; // reset sprite for custom border setup
1327   int tileX = ix/16, tileY = iy/16;
1329   auto tt = level.getTileAtGrid(tileX, tileY-1);
1330   //if (y == 0 || isNamedTileAt(x, y-1, 'oBrick') || isNamedTileAt(x, y-1, 'oHardBlock') || isNamedTileAt(x, y-1, 'oEdgeBrick')) up = true;
1331   bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1333   tt = level.getTileAtGrid(tileX, tileY+1);
1334   //if (y == NormalTilesHeight-1 || isNamedTileAt(x, y+1, 'oBrick') || isNamedTileAt(x, y+1, 'oHardBlock') || isNamedTileAt(x, y+1, 'oEdgeBrick')) down = true;
1335   bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1337   //if (collision_point(x-16, y, oBrick, 0, 0) or collision_point(x-16, y, oHardBlock, 0, 0)) { left = true; } // in the original
1338   //if (collision_point(x+16, y, oBrick, 0, 0) or collision_point(x+16, y, oHardBlock, 0, 0)) { right = true; } // in the original
1340   if (!up) {
1341     setSprite('sDarkUp');
1342     bool n = (global.randRoom(1, 3) < 3);
1343     appendBackFront(level.CreateBgTile('bgCaveTop3', (n ? 0 : 16)));
1344   }
1346   if (!down) {
1347     setSprite(!up ? 'sDarkUp2' : 'sDarkDown');
1348   }
1352 override void scrSetupBlockTop () {
1353   int tileX = ix/16, tileY = iy/16;
1354   auto tt = level.getTileAtGrid(tileX, tileY+1);
1355   bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1356   auto bt = level.CreateBgTile('bgCaveTop3', (global.randRoom(1, 3) < 3 ? 0 : 16));
1357   appendBackFront(bt);
1358   //bt.flty = -16; // offset
1359   // other
1360   setSprite(down ? 'sDarkUp' : 'sDarkUp2');
1364 override void scrSetupBlockBottom () {
1365   int tileX = ix/16, tileY = iy/16;
1366   auto tt = level.getTileAtGrid(tileX, tileY-1);
1367   bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1368   setSprite(up ? 'sDarkDown' : 'sDarkUp2');
1372 defaultproperties {
1373   objType = 'oDark';
1374   spriteName = 'sDark';
1375   lava = false;
1376   solid = true;
1377   rubbleSprite1 = 'sRubbleDark';
1378   rubbleSprite2 = 'sRubbleDarkSmall';
1382 // ////////////////////////////////////////////////////////////////////////// //
1383 class MapTileIce['oIce'] : MapTile;
1385 bool hasIceBottom;
1386 int dripTimer;
1388 final bool isIceTile (MapTile t) { return t.ice; }
1389 final MapTile isIceTileAtPoint (int x, int y) { return level.checkTileAtPoint(x, y, &isIceTile); }
1392 override void setupTile () {
1393   if (global.randRoom(1, 80) == 1) {
1394     setGem('oFrozenCaveman', true); // always visible
1395     gem.fltx = ix;
1396     gem.flty = iy;
1397     gem.saveInterpData();
1398     gemDestroyWithTile = true;
1399   }
1400   dripTimer = global.randOther(20, 400);
1401   //hasIceBottom = true;
1402   //dripTimer = 40;
1406 override void thinkFrame () {
1407   if (hasIceBottom) {
1408     if (--dripTimer <= 0) {
1409       //writeln("DRIP!");
1410       level.MakeMapObject(ix+8, iy+16+4, 'oDrip');
1411       dripTimer = global.randOther(20, 400);
1412       //dripTimer = 40;
1413     }
1414   }
1418 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1419   bool up = (specified_noup && noup ? false : !!isIceTileAtPoint(ix, iy-16));
1420   bool down = (specified_nodown && nodown ? false : !!isIceTileAtPoint(ix, iy+16));
1421   bool left = (specified_noleft && noleft ? false : !!isIceTileAtPoint(ix-16, iy));
1422   bool right = (specified_noright && noright ? false : !!isIceTileAtPoint(ix+16, iy));
1424   if (!up) setSprite('sIceUp');
1425   if (!down) {
1426     setSprite(!up ? 'sIceUp2' : 'sIceDown');
1427     if (!level.isSolidAtPoint(ix, iy+16) && global.randOther(1, 20) == 1) hasIceBottom = true;
1428   }
1429   if (!left) {
1430          if (!up && !down) setSprite('sIceUDL');
1431     else if (!up) setSprite('sIceUL');
1432     else if (!down) setSprite('sIceDL');
1433     else setSprite('sIceLeft');
1434   }
1435   if (!right) {
1436          if (!up && !down) setSprite('sIceUDR');
1437     else if (!up) setSprite('sIceUR');
1438     else if (!down) setSprite('sIceDR');
1439     else setSprite('sIceRight');
1440   }
1441   if (!up && !left && !right && down) setSprite('sIceULR');
1442   if (!down && !left && !right && up) setSprite('sIceDLR');
1443   if (up && down && !left && !right) setSprite('sIceLR');
1444   if (!up && !down && !left && !right) setSprite('sIceBlock');
1449 override void beautifyTile () {
1450   setupNiceTileSprite();
1454 override void scrSetupBlockTop () {
1455   setupNiceTileSprite(/*noup:true*/);
1459 override void scrSetupBlockBottom () {
1460   setupNiceTileSprite(/*nodown:true*/);
1464 override void scrSetupBlockLeft () {
1465   setupNiceTileSprite(/*noright:true*/);
1469 override void scrSetupBlockRight () {
1470   setupNiceTileSprite(/*noleft:true*/);
1474 defaultproperties {
1475   objType = 'oIce';
1476   spriteName = 'sIce';
1477   solid = true;
1478   ice = true;
1479   toSpecialGrid = true; // want to think
1483 // ////////////////////////////////////////////////////////////////////////// //
1484 class MapTileTemple['oTemple'] : MapTile;
1486 bool hasIceBottom;
1487 int dripTimer;
1489 final bool isTempleTile (MapTile t) { return (t.objType == 'oTemple' || t.border); } // fake too
1490 final MapTile isTempleTileAtPoint (int x, int y) { return level.checkTileAtPoint(x, y, &isTempleTile); }
1493 override void setupTile () {
1494   setSprite(global.cityOfGold == 1 ? 'sGTemple' : 'sTemple');
1495   int tileX = ix/16, tileY = iy/16;
1496   if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1497     invincible = true;
1498     border = true;
1499     //writeln("BORDER");
1500   } else {
1501     doCreateOre(60, 80, 100, 1200);
1502   }
1506 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1507   bool up = (iy <= 0 ? true : (specified_noup && noup ? false : !!isTempleTileAtPoint(ix, iy-16)));
1508   bool down = (iy >= level.tilesHeight*16-16 ? true : (specified_nodown && nodown ? false : !!isTempleTileAtPoint(ix, iy+16)));
1509   bool left = (ix <= 16 ? true : (specified_noleft && noleft ? false : !!isTempleTileAtPoint(ix-16, iy)));
1510   bool right = (ix >= level.tilesWidth*16-16*2 ? true : (specified_noright && noright ? false : !!isTempleTileAtPoint(ix+16, iy)));
1512   /*
1513   if (argument1) {
1514     if (global.cityOfGold == 1) sprite_index = sGTemple; else sprite_index = sTemple;
1515   }
1516   */
1518   /*!
1519   if (y == 0 or collision_point(x, y-16, oTemple, 0, 0) or collision_point(x, y+16, oTempleFake, 0, 0)) { up = true; }
1520   if (y >= argument0 or collision_point(x, y+16, oTemple, 0, 0) or collision_point(x, y+16, oTempleFake, 0, 0)) { down = true; }
1521   if (collision_point(x-16, y, oTemple, 0, 0) or collision_point(x-16, y, oTempleFake, 0, 0)) { left = true; }
1522   if (collision_point(x+16, y, oTemple, 0, 0) or collision_point(x+16, y, oTempleFake, 0, 0)) { right = true; }
1523   */
1525   if (global.cityOfGold == 1) {
1526     if (!up) {
1527       setSprite('sGTempleUp');
1528            if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 0)); //3
1529       else if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 16)); //3
1530       if (!left && !right) {
1531         if (!down) setSprite('sGTempleUp6'); else setSprite('sGTempleUp5');
1532       } else if (!left) {
1533         if (!down) setSprite('sGTempleUp7'); else setSprite('sGTempleUp3');
1534       } else if (!right) {
1535         if (!down) setSprite('sGTempleUp8'); else setSprite('sGTempleUp4');
1536       } else if (left && right && !down) {
1537         setSprite('sGTempleUp2');
1538       }
1539     } else if (!down) {
1540       setSprite('sGTempleDown');
1541     }
1542   } else {
1543     if (!up) {
1544       setSprite('sTempleUp');
1545       //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);
1546            if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 0)); //3
1547       else if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 16)); //3
1548       if (!left && !right) {
1549         if (!down) setSprite('sTempleUp6'); else setSprite('sTempleUp5');
1550       } else if (!left) {
1551         if (!down) setSprite('sTempleUp7'); else setSprite('sTempleUp3');
1552       } else if (!right) {
1553         if (!down) setSprite('sTempleUp8'); else setSprite('sTempleUp4');
1554       } else if (left && right && !down) {
1555         setSprite('sTempleUp2');
1556       }
1557     } else if (!down) {
1558       setSprite('sTempleDown');
1559     }
1560   }
1564 override void beautifyTile () {
1565   setupNiceTileSprite();
1569 override void scrSetupBlockTop () {
1570   setupNiceTileSprite(/*noup:true*/);
1574 override void scrSetupBlockBottom () {
1575   setupNiceTileSprite(/*nodown:true*/);
1579 override void scrSetupBlockLeft () {
1580   setupNiceTileSprite(/*noright:true*/);
1584 override void scrSetupBlockRight () {
1585   setupNiceTileSprite(/*noleft:true*/);
1589 defaultproperties {
1590   objType = 'oTemple';
1591   spriteName = 'sTemple';
1592   solid = true;
1596 // ////////////////////////////////////////////////////////////////////////// //
1597 class MapTileVine['oVine'] : MapTile;
1600 override void setupTile () {
1604 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1605   //if (argument1) sprite_index = sVine;
1607   bool up = !!level.isLadderAtPoint(ix+8, iy-8);
1608   bool down = !!level.isLadderAtPoint(ix+8, iy+16);
1610   if (!up) {
1611     //tile_add(bgVineRoots, 0, 0, 16, 16, x, y-16, 1000); // use same depth as the vine objects themselves
1612     appendBackFront(level.CreateBgTile('bgVineRoots', 0)); //1000
1613     setSprite('sVineSource');
1614   } else if (!down) {
1615     setSprite('sVineBottom');
1616   }
1620 override void beautifyTile () {
1621   setupNiceTileSprite();
1625 defaultproperties {
1626   objType = 'oVine';
1627   ladder = true;
1628   solid = false;
1629   invincible = true;
1630   spriteName = 'sVine';
1634 // ////////////////////////////////////////////////////////////////////////// //
1635 class MapTileLava['oLava'] : MapTile;
1637 bool spurt;
1638 int spurtTime;
1639 int spurtCounter;
1640 bool spurtSet;
1643 override void setupTile () {
1644   spurtTime = global.randOther(100, 300);
1645   spurtCounter = spurtTime;
1649 override void doSprayRubble () {
1650   foreach (; 0..3) level.MakeMapObject(ix+global.randOther(0, 16), iy+global.randOther(0, 16), 'oLavaDrip');
1651   if (global.randOther(1, 6) == 1) {
1652     auto flame = level.MakeMapObject(ix+8, iy+8, 'oFlame');
1653     if (flame) flame.yVel = 4;
1654   }
1658 override bool onExplosionTouch (MapObject xplo) {
1659   return false;
1663 override void thinkFrame () {
1664   if (!spurtSet && spriteName == 'sLavaTop') {
1665     spurtSet = true;
1666     spurt = (global.randOther(1, 4) == 1);
1667   }
1668   auto dist = pointDistance(ix, iy, level.player.ix, level.player.iy);
1669   if (spurt && dist < 240) {
1670     if (spurtCounter > 0) {
1671       --spurtCounter;
1672     } else {
1673       spurtCounter = spurtTime;
1674       auto flame = level.MakeMapObject(ix+8, iy-4, (global.randOther(1, 8) == 1 ? 'oMagma' : 'oFlame'));
1675       //auto flame = level.MakeMapObject(ix+8, iy-4, 'oMagma');
1676       if (flame) flame.yVel = -global.randOther(1, 4);
1677     }
1678   }
1682 defaultproperties {
1683   objType = 'oLava';
1684   spriteName = 'sLava';
1685   lava = true;
1686   solid = false;
1687   toSpecialGrid = false; // lava tiles are special: they are animated/thinked without a grid
1688   lightRadius = 32+16;
1689   litWholeTile = true;
1690   imageSpeed = 0.5;
1694 // ////////////////////////////////////////////////////////////////////////// //
1695 class MapTileWater['oWater'] : MapTile;
1698 override void setupTile () {
1702 override void doSprayRubble () {
1703   foreach (; 0..3) level.MakeMapObject(ix+global.randOther(0, 16), iy+global.randOther(0, 16), 'oDrip');
1707 override bool onExplosionTouch (MapObject xplo) {
1708   return false;
1712 final bool isWaterTile (MapTile t) { return t.water; }
1713 //final bool isSolidOrWaterTile (MapTile t) { return (t.water || t.solid); }
1716 void setupNiceTileSprite () {
1717   //if (argument1) sprite_index = sWater;
1718   bool up = false;
1719   bool upWater = false;
1720   bool down = false;
1721   //bool left = false;
1722   //bool right = false;
1724   if (level.checkTileAtPoint(ix, iy-16, &isWaterTile)) upWater = true;
1725   if (level.isSolidAtPoint(ix, iy-16)) up = true;
1726   if (level.isSolidAtPoint(ix, iy+16) && !level.checkTileAtPoint(ix, iy+16, &isWaterTile)) down = true;
1728   if (!up && !upWater) setSprite('sWaterTop');
1730   if (upWater && level.checkTileAtPoint(ix, iy-32, &isWaterTile) && down && global.randOther(1, 4) == 1) {
1731     setSprite('sWaterBottomTall2');
1732     auto awater = level.checkTileAtPoint(ix, iy-16, &isWaterTile);
1733     if (awater) awater.setSprite('sWaterBottomTall1');
1734   } else if ((up || upWater) && down) {
1735     switch (global.randOther(1, 4)) {
1736       case 1: setSprite('sWaterBottom'); break;
1737       case 2: setSprite('sWaterBottom2'); break;
1738       case 3: setSprite('sWaterBottom3'); break;
1739       case 4: setSprite('sWaterBottom4'); break;
1740     }
1741   }
1745 override void beautifyTile () {
1746   setupNiceTileSprite();
1750 defaultproperties {
1751   objType = 'oWater';
1752   spriteName = 'sWater';
1753   water = true;
1754   solid = false;
1758 // ////////////////////////////////////////////////////////////////////////// //
1759 class MapTileWaterSwim['oWaterSwim'] : MapTileWater;
1762 // ////////////////////////////////////////////////////////////////////////// //
1763 class MapTileLavaSolid['oLavaSolid'] : MapTile;
1765 override void setupTile () {
1768 override bool onExplosionTouch (MapObject xplo) {
1769   return false;
1772 defaultproperties {
1773   objType = 'oLavaSolid';
1774   spriteName = 'sLava';
1775   lava = true;
1776   solid = true;
1780 // ////////////////////////////////////////////////////////////////////////// //
1781 class MapTileLushTrapBlock['oTrapBlock'] : MapTile;
1783 int deathTimer;
1784 bool dying;
1787 override void doSprayRubble () {
1788   if (smashed) scrSprayRubble(ix, iy, 3);
1789   scrDropRubble(ix, iy, 3);
1790   if (global.cityOfGold == 1) {
1791     ore = 2;
1792     scrDropOre();
1793   }
1794   if (dying) {
1795     playSound('sndThump');
1796     level.scrShake(10);
1797   }
1801 override void setupTile () {
1805 void activate () {
1806   if (dying) return;
1807   if (distanceToEntityCenter(level.player) < 90) {
1808     dying = true;
1809   }
1813 override void thinkFrame () {
1814   if (dying) {
1815     if (deathTimer > 0) --deathTimer; else { invincible = false; smashMe(); instanceRemove(); }
1816   }
1820 defaultproperties {
1821   objType = 'oLavaSolid';
1822   spriteName = 'sSkullBlock';
1823   solid = true;
1824   toSpecialGrid = true;
1825   rubbleSprite1 = 'sRubbleTan';
1826   rubbleSprite2 = 'sRubbleTanSmall';
1827   //depth = ???;
1831 // ////////////////////////////////////////////////////////////////////////// //
1832 class MapTileLeaves['oLeaves'] : MapTile;
1834 bool dead;
1835 bool spriteSet;
1838 final bool isTreeOrLeaves (MapTile t) { return (t != self && (t.tree || t.leaves)); }
1841 override void doSprayRubble () {
1842   if (spriteName != 'sLeavesDead' && spriteName != 'sLeavesDeadR') {
1843     level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1844     level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1845   }
1849 override void setupTile () {
1850   spriteName = (global.cemetary ? 'sLeavesDead' : 'sLeaves');
1854 override void thinkFrame () {
1855   int x = ix, y = iy;
1857   if (!spriteSet) {
1858     spectral = true;
1859     if (level.checkTileAtPoint(x-16, y, &isTreeOrLeaves)) {
1860       setSprite(global.cemetary ? 'sLeavesDeadR' : 'sLeavesRight');
1861     } else if (level.checkTileAtPoint(x+16, y, &isTreeOrLeaves)) {
1862       setSprite(global.cemetary ? 'sLeavesDead' : 'sLeaves');
1863     }
1864     if (level.checkTileAtPoint(x-16, y, &isTreeOrLeaves) &&
1865         level.checkTileAtPoint(x+16, y, &isTreeOrLeaves))
1866     {
1867       setSprite('sLeavesTop');
1868     }
1869     if (dead) {
1870       if (level.checkTileAtPoint(x-16, y, &isTreeOrLeaves)) setSprite('sLeavesDeadR'); else setSprite('sLeavesDead');
1871     }
1872     spectral = false;
1873     spriteSet = true;
1874   }
1876   spectral = true;
1877   if (spriteName == 'sLeavesTop') {
1878     if (!level.checkTileAtPoint(x-16, y, &isTreeOrLeaves) ||
1879         !level.checkTileAtPoint(x+16, y, &isTreeOrLeaves))
1880     {
1881       //doSprayRubble();
1882       invincible = false;
1883       smashMe();
1884       instanceRemove();
1885     }
1886   } else if (spriteName == 'sLeaves' || spriteName == 'sLeavesDead') {
1887     if (!level.checkTileAtPoint(x+16, y, &isTreeOrLeaves)) { invincible = false; smashMe(); instanceRemove(); }
1888   } else if (spriteName == 'sLeavesRight' || spriteName == 'sLeavesDeadR') {
1889     if (!level.checkTileAtPoint(x-16, y, &isTreeOrLeaves)) { invincible = false; smashMe(); instanceRemove(); }
1890   }
1891   spectral = false;
1893   /*
1894   if (sprite_index == sLeavesDeadR) {
1895     desc = "Canopy";
1896     desc2 = "These leaves have died and withered.";
1897   } else {
1898     desc = "Canopy";
1899     desc2 = "The canopy of a proud tree.";
1900   }
1901   */
1905 defaultproperties {
1906   objType = 'oLeaves';
1907   desc = "Canopy";
1908   desc2 = "The top of a proud tree.";
1909   platform = true;
1910   solid = false;
1911   leaves = true;
1912   dead = false;
1913   spriteSet = false;
1914   toSpecialGrid = true;
1915   depth = 1;
1919 // ////////////////////////////////////////////////////////////////////////// //
1920 class MapTileTree['oTree'] : MapTile;
1922 bool burning;
1925 override void doSprayRubble () {
1926   if (smashed) scrSprayRubble(ix, iy, 3);
1927   scrDropRubble(ix, iy, 3);
1931 override void setupTile () {
1935 final bool isTreeTileAtPoint (int x, int y) {
1936   return !!level.checkTileAtPoint(x, y, delegate bool (MapTile t) { return (t.objType == 'oTree'); });
1940 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1941   //if (argument1) sprite_index = sVine;
1943   bool up = isTreeTileAtPoint(ix, iy-16);
1944   //bool down = isTreeTileAtPoint(ix, iy+16);
1945   //bool left = isTreeTileAtPoint(ix-16, iy);
1946   //bool right = isTreeTileAtPoint(ix+16, yi);
1948   if (!up) {
1949     if (global.cemetary || getSprite().Name == 'sTreeTopDead') setSprite('sTreeTopDead'); else setSprite('sTreeTop');
1950     depth = 1;
1951   }
1955 override void beautifyTile () {
1956   setupNiceTileSprite();
1960 override void thinkFrame () {
1961   int x = ix, y = iy;
1963   if (!level.isSolidAtPoint(x, y+16)) { invincible = false; smashMe(); instanceRemove(); return; }
1964   if (level.isLavaAtPoint(x-16, y) || level.isLavaAtPoint(x+16, y)) { invincible = false; smashMe(); instanceRemove(); return; }
1968 defaultproperties {
1969   objType = 'oTree';
1970   desc = "Tree Trunk";
1971   desc2 = "The trunk of a proud tree.";
1972   spriteName = 'sTreeTrunk';
1973   solid = true;
1974   leaves = false;
1975   tree = true;
1976   burning = false;
1977   toSpecialGrid = true;
1978   rubbleSprite1 = 'sRubbleLush';
1979   rubbleSprite2 = 'sRubbleLushSmall';
1980   depth = 2;
1984 // ////////////////////////////////////////////////////////////////////////// //
1985 class MapTileTreeBranch['oTreeBranch'] : MapTile;
1987 bool burning;
1988 bool dead;
1991 final bool isTree (MapTile t) { return (t != self && t.tree); }
1995 override void onDestroy () {
1996   if (!cleanDeath) {
1997     if (spriteName != 'sTreeBranchDeadL' && spriteName != 'sTreeBranchDeadR') {
1998       level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1999     }
2000   }
2001   ::onDestroy();
2005 override void doSprayRubble () {
2006   if (spriteName != 'sTreeBranchDeadL' && spriteName != 'sTreeBranchDeadR') {
2007     level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
2008   }
2012 override void setupTile () {
2013   dead = false;
2015   auto ltree = level.checkTileAtPoint(ix-16, iy, &isTree);
2016   auto rtree = level.checkTileAtPoint(ix+16, iy, &isTree);
2018        if (ltree && !rtree) setSprite(global.cemetary ? 'sTreeBranchDeadR' : 'sTreeBranchRight');
2019   else if (!ltree && rtree) setSprite(global.cemetary ? 'sTreeBranchDeadL' : 'sTreeBranchLeft');
2021   //if (global.cemetary) spriteName = 'sTreeBranchDeadR';
2025 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
2026   //if (argument1) sprite_index = sVine;
2028   bool up = !!level.checkTileAtPoint(ix, iy-16, delegate bool (MapTile t) { return (t.objType == 'oLeaves'); });
2029   //if (collision_point(x, y+16, oTreeBranch, 0, 0)) { down = true; }
2030   //if (collision_point(x-16, y, oTreeBranch, 0, 0)) { left = true; }
2031   bool right = !!level.checkTileAtPoint(ix+16, iy, delegate bool (MapTile t) { return (t.objType == 'oTree'); });
2033   if (up) {
2034     instanceRemove();
2035     return;
2036   }
2038   if (right) {
2039     if (global.cemetary || getSprite().Name == 'sTreeBranchDeadR') setSprite('sTreeBranchDeadL'); else setSprite('sTreeBranchLeft');
2040   }
2044 override void beautifyTile () {
2045   //writeln("!!! ", getSprite().Name);
2046   setupNiceTileSprite();
2050 override void thinkFrame () {
2051   //int x = ix, y = iy;
2053   spectral = true;
2054   auto ltree = level.checkTileAtPoint(ix-16, iy, &isTree);
2055   auto rtree = level.checkTileAtPoint(ix+16, iy, &isTree);
2056   if (!ltree && !rtree) {
2057     //doSprayRubble();
2058     invincible = false;
2059     smashMe();
2060     instanceRemove();
2062   } else {
2063          if (ltree && !rtree) setSprite('sTreeBranchRight');
2064     else if (!ltree && rtree) setSprite('sTreeBranchLeft');
2066   }
2067   spectral = false;
2071 defaultproperties {
2072   objType = 'oTree';
2073   desc = "Tree Branch";
2074   desc2 = "A slight but firm limb of a proud tree.";
2075   spriteName = 'sTreeBranchRight';
2076   platform = true;
2077   solid = false;
2078   leaves = false;
2079   tree = true;
2080   burning = false;
2081   toSpecialGrid = true;
2082   depth = 2;
2086 // ////////////////////////////////////////////////////////////////////////// //
2087 // only for dark levels
2088 class MapTileTikiTorch['oTikiTorch'] : MapTile;
2091 override int height () { return 32; }
2093 override void setupTile () {
2094   writeln("*** TIKI TORCH CREATED");
2097 override void thinkFrame () {
2098   lightRadius = 32+16+global.randOther(-8, 8);
2099   if (level.loserGPU) lightRadius = 32+16;
2103 defaultproperties {
2104   objType = 'oTikiTorch';
2105   desc = "Torch";
2106   desc2 = "A moderately bright torch.";
2107   spriteName = 'sTikiTorch';
2108   platform = false;
2109   solid = false;
2110   leaves = false;
2111   tree = false;
2112   toSpecialGrid = true;
2113   depth = 1000;
2114   imageSpeed = 0.5;
2115   lightRadius = 32+16;
2119 // ////////////////////////////////////////////////////////////////////////// //
2120 class MapTileGiantTikiHead['oGiantTikiHead'] : MapTile;
2122 bool broken;
2125 void breakIt () {
2126   if (!broken) {
2127     broken = true;
2128     setSprite('sGTHHole');
2129   }
2133 override void setupTile () {
2137 override void thinkFrame () {
2141 defaultproperties {
2142   objType = 'oGiantTikiHead';
2143   invincible = true; //???
2144   solid = false;
2145   spriteName = 'sGiantTikiHead';
2146   depth = 1000;
2150 // ////////////////////////////////////////////////////////////////////////// //
2151 class MapTileThinIce['oThinIce'] : MapTile;
2153 int thickness = 60;
2155 override void setupTile () {
2159 override void thinkFrame () {
2160   if (level.player.isRectHitSimple(ix, iy-1, 17, 2)) {
2161     thickness -= 2;
2162     if (global.randOther(1, 100) == 1) level.MakeMapObject(ix+global.randOther(0, 16), iy+9, 'oDrip');
2163   }
2164        if (thickness > 50) setSprite('sThinIce1');
2165   else if (thickness > 40) setSprite('sThinIce2');
2166   else if (thickness > 30) setSprite('sThinIce3');
2167   else if (thickness > 20) setSprite('sThinIce4');
2168   else if (thickness > 10) setSprite('sThinIce5');
2169   else if (thickness > 0) setSprite('sThinIce6');
2170   else instanceRemove();
2174 defaultproperties {
2175   objType = 'oThinIce';
2176   solid = true;
2177   spriteName = 'sThinIce1';
2178   toSpecialGrid = true; // it must think
2182 // ////////////////////////////////////////////////////////////////////////// //
2183 class MapTileDarkFall['oDarkFall'] : MapTile;
2185 //int thickness = 60;
2186 int viscidTop = 1;
2187 int timeFall = 20;
2188 int timeFallMax = 20;
2189 bool falling;
2190 //float grav = 1;
2192 override int height () { return 8; }
2194 override void setupTile () {}
2197 //FIXME: viscidMovement -- ???
2198 override void thinkFrame () {
2199   //isCollisionCharacterTop(1)
2200   if (!falling) {
2201     auto plr = level.player;
2202     if (plr.isRectHitSimple(ix, iy-1, 17, 2)) {
2203       --timeFall;
2204     } else if (plr.status == MapObject::HANGING) {
2205       //writeln("checking for hang...");
2206       int tileX = ix/16;
2207       if (plr.isRectHitSimple((tileX-1)*16, iy, 16, 8) || plr.isRectHitSimple((tileX+1)*16, iy, 16, 8)) {
2208         //writeln("oDarkFall: HANGING! timer=", timeFall);
2209         --timeFall;
2210       }
2211     } else if (timeFall < timeFallMax) {
2212       ++timeFall;
2213     }
2214     if (timeFall <= 0) falling = true;
2215   }
2216   if (!falling) return;
2217   myGrav = grav;
2218   //if (yVel > 10) yVel = 10;
2219   //HACK: so player won't be able to push it
2220   moveable = true;
2221   ::thinkFrame();
2222   moveable = false;
2223   // dropped on solid?
2224   if (level.checkTilesInRect(ix, iy+height, width, 1)) {
2225     int x = ix, y = iy;
2226     instanceRemove();
2227     // not breaked on "Thwomp Trap" (we don't have it yet)
2228     playSound('sndBreak');
2229     level.MakeMapObject(x+8, y+8, 'oSmokePuff');
2230     foreach (; 0..3) {
2231       auto obj = level.MakeMapObject(x+global.randOther(2, 14), y-global.randOther(2, 8), 'oRubbleDark');
2232       if (obj) {
2233         obj.xVel = global.randOther(1, 3)-global.randOther(1, 3);
2234         obj.yVel = -global.randOther(0, 3);
2235       }
2236     }
2237   }
2241 defaultproperties {
2242   objType = 'oDarkFall';
2243   solid = true;
2245   viscidTop = 1;
2246   //setCollisionBounds(0, 0, 16, 8);
2248   grav = 1;
2249   myGravLimit = 10;
2250   //timeFall = 20;
2251   //timeFallMax = 20;
2252   desc = "Falling Platform";
2253   desc2 = "A thin, dark blue platform that will colapse if stood on for any length of time.";
2255   spriteName = 'sDarkFall';
2256   toSpecialGrid = true; // it must think
2257   depth = 1000;
2261 // ////////////////////////////////////////////////////////////////////////// //
2262 class MapTileAlienHull['oAlienShip'] : MapTile;
2265 override void setupTile () {
2266   //writeln("*********** ALIEN SHIP!");
2270 override void doSprayRubble () {
2271   if (smashed) scrSprayRubble(ix, iy, 3);
2272   scrDropRubble(ix, iy, 3);
2276 final bool isAlienShipFloorCB (MapTile t) { return (t.objType == 'oAlienShip' || t.objType == 'oAlienShipFloor'); }
2277 final bool isASFTileAt (int x, int y) { return !!level.checkTileAtPoint(x, y, &isAlienShipFloorCB); }
2280 override void beautifyTile () {
2281   //if (argument1) sprite_index = sAlienTop;
2283   bool up = !!isASFTileAt(ix, iy-16);
2284   bool down = !!isASFTileAt(ix, iy+16);
2285   bool left = !!isASFTileAt(ix-16, iy);
2286   bool right = !!isASFTileAt(ix+16, iy);
2288   if (right && !left) {
2289          if (up && !down) setSprite('sAlienFront2');
2290     else if (down && !up) setSprite('sAlienFront3');
2291   }
2292   if (left && !right) {
2293     if (up) setSprite('sAlienBack2'); else if (down) setSprite('sAlienBack3');
2294   }
2298 defaultproperties {
2299   objType = 'oAlienShip';
2300   solid = true;
2301   spriteName = 'sAlienTop';
2302   rubbleSprite1 = 'sRubbleTan';
2303   rubbleSprite2 = 'sRubbleTanSmall';
2307 // ////////////////////////////////////////////////////////////////////////// //
2308 class MapTileAlienFloor['oAlienShipFloor'] : MapTileAlienHull;
2311 defaultproperties {
2312   objType = 'oAlienShipFloor';
2313   spriteName = 'sAlienFloor';
2317 // ////////////////////////////////////////////////////////////////////////// //
2318 class MapTileXocBlock['oXocBlock'] : MapTile;
2321 override void setupTile () {
2325 override void doSprayRubble () {
2326   foreach (; 0..3) {
2327     auto gold = level.MakeMapObject(ix+8+global.randOther(0, 4)-global.randOther(0, 4), iy+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldChunk');
2328     if (gold) {
2329       gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2330       gold.yVel = global.randOther(2, 4);
2331     }
2332   }
2333   {
2334     auto gold = level.MakeMapObject(ix+8+global.randOther(0, 4)-global.randOther(0, 4), iy+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldNugget');
2335     if (gold) {
2336       gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2337       gold.yVel = global.randOther(2, 4);
2338     }
2339   }
2343 defaultproperties {
2344   objType = 'oXocBlock';
2345   solid = true;
2346   spriteName = 'sGoldBlock';
2347   depth = 1000;
2351 // ////////////////////////////////////////////////////////////////////////// //
2352 class MapTileCeilingTrap['oCeilingTrap'] : MapTile;
2354 //int thickness = 60;
2355 int viscidTop = 1;
2356 //int timeFall = 20;
2357 //int timeFallMax = 20;
2358 //float grav = 1;
2359 int counter;
2360 bool sprung;
2363 enum {
2364   IDLE = 0,
2365   DROP = 1,
2366   WAIT = 2,
2367   RETURN = 3,
2370 int status;
2373 override void onAnimationLooped () {
2374   if (spriteName == 'sCeilingTrapS') setSprite('sCeilingTrap');
2378 override void doSprayRubble () {
2379   if (smashed) scrSprayRubble(ix, iy, 3);
2380   scrDropRubble(ix, iy, 3);
2381   if (global.cityOfGold == 1) {
2382     ore = 2;
2383     scrDropOre();
2384   }
2388 override void setupTile () {
2389   if (global.cityOfGold == 1) spriteName = 'sGoldBlock';
2393 void activate () {
2394   if (status != IDLE) return;
2395   status = DROP;
2396   level.checkTilesInRect(ix-64, iy-64, 129, 129, delegate bool (MapTile t) {
2397     auto door = MapTileDoor(t);
2398     if (door) door.activate();
2399     return false;
2400   });
2404 //FIXME: viscidMovement -- ???
2405 override void thinkFrame () {
2406   if (status == IDLE) {
2407     // nothing
2408   } else if (status == DROP) {
2409     if (counter > 0) {
2410       --counter;
2411     } else {
2412       counter = 3;
2413       flty += 1;
2414     }
2415     yVel = 0;
2416     spectral = true;
2417     if (level.isSolidAtPoint(ix+8, iy+17)) status = WAIT;
2418     spectral = false;
2419     if (spriteName == 'sBlock' || spriteName == 'sGoldBlock') setSprite('sCeilingTrapS');
2420     // out-of-level check
2421     if (isOutsideOfLevel()) {
2422       cleanDeath = true;
2423       instanceRemove();
2424     }
2425   } else if (status == WAIT) {
2426     yVel = 0;
2427     spectral = true;
2428     if (isCollisionBottom(0)) flty -= 1;
2429     spectral = false;
2430   }
2433   if (status != IDLE) {
2434     // player
2435     auto plr = level.player;
2436     if (!plr.dead && !plr.invincible && !plr.isExitingSprite() && /*plr.collidesWith(self)*/ plr.isRectHitSimple(x0, y0, width, height+1)) {
2437       bool doCol = true;
2438       if (!plr.collidesWith(self)) {
2439         doCol = (plr.yVel < -0.01 && plr.y0 > y1);
2440       }
2441       if (doCol) {
2442         if (global.plife > 0) {
2443           global.plife -= global.config.crushDmg;
2444           if (global.plife <= 0 /*and isRealLevel()*/) level.addDeath(objName);
2445         }
2446         plr.spillBlood();
2447         plr.stunned = true;
2448         plr.stunTimer = 20;
2449         playSound('sndHurt');
2450       }
2451     }
2454     level.isObjectInRect(x0-1, y0-1, width+2, height+2, delegate bool (MapObject o) {
2455       if (o == self) return false;
2456       if (o isa EnemyTombLord) return false;
2458       // register hit only if we're moving onto a spikes
2459       if (!o.collidesWith(self)) {
2460         bool onTop = (o.yVel < -0.01 && o.y0 > y1);
2461         if (!onTop) return false;
2462         writeln("*** RUNNING ON TRAP SPIKES");
2463       }
2465       // damsel
2466       auto dms = MonsterDamsel(o);
2467       if (dms) {
2468         if (dms.dead || dms.status == MapObject::STATE_DEAD || dms.invincible) return false;
2469         if (dms.heldBy) dms.heldBy.holdItem = none;
2470         dms.hp -= global.config.crushDmg;
2471         dms.spillBlood();
2472         dms.status = MapObject::THROWN;
2473         dms.counter = dms.stunMax;
2474         dms.calm = false;
2475         //dms.damselDropped = true;
2476         dms.kissCount = min(1, dms.kissCount);
2477         playSound('sndDamsel');
2478         return false;
2479       }
2481       // enemy
2482       auto enemy = MapEnemy(o);
2483       if (enemy) {
2484         if (o.dead || o.status == MapObject::STATE_DEAD || o.invincible) return false;
2485         if (o.heldBy) o.heldBy.holdItem = none;
2486         enemy.hp -= global.config.crushDmg;
2487         enemy.spillBlood();
2488         playSound('sndHit');
2489         return false;
2490       }
2492       return false;
2493     }, castClass:MapEnemy);
2494   }
2498 defaultproperties {
2499   objType = 'oCeilingTrap';
2500   objName = 'Ceiling Trap';
2501   desc = "Ceiling Trap";
2502   desc2 = "This seemingly-innocuous block hides a vicious set of spikes.";
2504   solid = true;
2505   imageSpeed = 0.4;
2507   viscidTop = 1;
2508   //setCollisionBounds(0, 0, 16, 8);
2510   //moveable = true;
2511   grav = 1;
2512   myGravLimit = 10;
2513   counter = 3;
2515   status = IDLE;
2516   sprung = false;
2518   spriteName = 'sBlock';
2519   toSpecialGrid = true; // it must think
2520   rubbleSprite1 = 'sRubbleTan';
2521   rubbleSprite2 = 'sRubbleTanSmall';
2522   depth = 1000;
2526 // ////////////////////////////////////////////////////////////////////////// //
2527 class MapTileTempleFake['oTempleFake'] : MapTile;
2529 int sleepTime = 4; // wait until the grid is filled
2531 override void doSprayRubble () {
2532   if (smashed) scrSprayRubble(ix, iy, 3);
2533   scrDropRubble(ix, iy, 3);
2537 override void setupTile () {
2538   doCreateOre(60, 80, 100, 1200);
2542 override void thinkFrame () {
2543   if (sleepTime > 0) { --sleepTime; return; }
2544   //writeln("CHK!");
2545   auto door = level.checkTilesInRect(ix, iy, 16, 16, delegate bool (MapTile t) {
2546     if (t == self) return false;
2547     //writeln("fake temple: t(", GetClassName(t.Class), "); objName=", t.objName, "; objType=", t.objType, "; door=", t isa MapTileDoor);
2548     return (t isa MapTileDoor);
2549   });
2550   if (!door) {
2551     //writeln("!!!");
2552     auto bt = level.MakeMapTile(ix/16, iy/16, 'oTemple');
2553     if (bt) bt.copyOreFrom(self);
2554     instanceRemove();
2555     return;
2556   }
2557   /*
2558   if (!level.checkTileAtPoint(ix+8, iy+8, delegate bool (MapTile t) { return (t isa MapTileDoor); })) {
2559     writeln("!!!");
2560     level.MakeMapTile(ix/16, iy/16, 'oTemple');
2561     instanceRemove();
2562     return;
2563   }
2564   */
2568 defaultproperties {
2569   objType = 'oTemple';
2570   desc = "Temple Brick";
2571   desc2 = "This is the first brick you've seen that actually qualifies as a brick. It looks rather bland.";
2573   solid = true;
2574   moveable = false;
2575   spriteName = 'sTemple';
2576   toSpecialGrid = true; // it must think
2578   //invisible = true;
2580   rubbleSprite1 = 'sRubbleTan';
2581   rubbleSprite2 = 'sRubbleTanSmall';
2582   depth = 60;
2586 // ////////////////////////////////////////////////////////////////////////// //
2587 class MapTileDoor['oDoor'] : MapTile;
2589 //int thickness = 60;
2590 int viscidTop = 1;
2591 int counter;
2592 bool sprung;
2595 enum {
2596   IDLE = 0,
2597   DROP = 1,
2598   WAIT = 2,
2599   RETURN = 3,
2601 int status;
2604 //setCollisionBounds(1, 0, 15, 32);
2605 override int x0 () { return roundi(fltx)+1; }
2606 override int width () { return 15; }
2607 override int height () { return 32; }
2610 override void doSprayRubble () {
2611   if (smashed) scrSprayRubble(ix, iy, 3);
2612   scrDropRubble(ix, iy, 3);
2613   if (global.cityOfGold == 1) {
2614     ore = 2;
2615     scrDropOre();
2616   }
2620 override void setupTile () {
2624 void activate () {
2625   status = DROP;
2626   myGrav = grav;
2627   yVel = myGrav;
2628   shiftY(2);
2632 //FIXME: viscidMovement -- ???
2633 override void thinkFrame () {
2634   if (status == IDLE) {
2635     // nothing
2636   } else if (status == DROP) {
2637     yVel = fmin(yVel+myGrav, myGravLimit);
2638     //::thinkFrame();
2639     spectral = true;
2640     auto oldsolid = solid;
2641     solid = false;
2642     //writeln("DOOR GOING DOWN; yVel=", yVel, "; ix=", ix, "; iy=", iy, "; x0=", x0, "; y0=", y0, "; w=", width, "; h=", height);
2643     int newY = roundi(flty+yVel); //!!!
2644     // check if we need (and can) move down
2645     int w = width, hp = height;
2646     while (newY > iy) {
2647       // made non-solid temporarily
2648       //auto hasAnything = level.checkTilesInRect(x0, y0+hp, w, 1, &level.cbCollisionAnySolid);
2649       auto hasAnything = level.checkTilesInRect(x0, y0+hp, w, 1, delegate bool (MapTile t) {
2650         if (t == self) return false;
2651         return t.solid;
2652       });
2653       if (hasAnything) {
2654         //writeln("hit '", GetClassName(hasAnything.Class), "' (", hasAnything.objName, ":", hasAnything.objType, "): pos=(", hasAnything.ix, ",", hasAnything.iy, ")");
2655         // hit something, stop right there
2656         if (yVel > myGrav*3) playSound('sndThud');
2657         flty = iy;
2658         status = WAIT;
2659         yVel = 0;
2660         counter = 100;
2661         depth = 180;
2662         //writeln("DOOR STOP");
2663         break;
2664       }
2665       // move us
2666       shiftY(1);
2667     }
2668     spectral = false;
2669     solid = oldsolid;
2671     // out-of-level check
2672     if (flty > level.tilesHeight*16+16) {
2673       cleanDeath = true;
2674       instanceRemove();
2675     }
2676   } else if (status == WAIT) {
2677     yVel = 0;
2678     spectral = true;
2679     if (isCollisionBottom(0)) flty -= 1;
2680     spectral = false;
2681   }
2685 defaultproperties {
2686   objType = 'oDoor';
2687   solid = true;
2689   viscidTop = 1;
2690   //setCollisionBounds(0, 0, 16, 8);
2692   //moveable = true;
2693   grav = 0.6;
2694   myGravLimit = 6;
2695   counter = 0;
2697   status = IDLE;
2698   sprung = false;
2700   desc = "Wall Trap";
2701   desc2 = "The inside of this block carries an extendable wall.";
2703   spriteName = 'sDoor';
2704   toSpecialGrid = true; // it must think
2705   rubbleSprite1 = 'sRubbleTan';
2706   rubbleSprite2 = 'sRubbleTanSmall';
2707   depth = 2000;
2711 // ////////////////////////////////////////////////////////////////////////// //
2712 class MapTileSmashTrap['oSmashTrap'] : MapTile;
2714 //int thickness = 60;
2715 int viscidTop = 1;
2717 int counter;
2718 bool hit = false;
2720 float xv, yv;
2721 float xa, ya;
2724 enum {
2725   IDLE = 0,
2726   ATTACK = 1,
2727   STATE_DEAD = 99,
2730 int status;
2732 enum {
2733   RIGHT = 0,
2734   DOWN = 1,
2735   LEFT = 2,
2736   UP = 3,
2739 int dir;
2742 final string dirName () {
2743   switch (dir) {
2744     case RIGHT: return "right";
2745     case LEFT: return "left";
2746     case UP: return "up";
2747     case DOWN: return "down";
2748   }
2749   return "fucked";
2753 //setCollisionBounds(1, 1, 15, 15);
2754 override int x0 () { return roundi(fltx)+1; }
2755 override int width () { return 15; }
2756 //override int height () { return 16; }
2759 override void doSprayRubble () {
2760   if (global.cityOfGold == 1) {
2761     foreach (; 0..3) {
2762       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');
2763       gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2764       gold.yVel = global.randOther(2, 4);
2765     }
2766     {
2767       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');
2768       gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2769       gold.yVel = global.randOther(2, 4);
2770     }
2771   } else {
2772     if (smashed) scrSprayRubble(ix, iy, 3);
2773     scrDropRubble(ix, iy, 3);
2774   }
2778 override void setupTile () {
2779   // prevent smash trap from spawning in player range when level starts
2780   if (true /+global.config.bizarre /*or isRealLevel()*/ +/) {
2781     if (level.calcNearestEnterDist(ix, iy) < 90) {
2782       cleanDeath = true;
2783       instanceRemove();
2784       return;
2785     }
2786   }
2788   if (global.cityOfGold == 1) setSprite('sSmashTrapGold');
2789   dir = global.randRoom(0, 3);
2793 //FIXME: viscidMovement -- ???
2794 override void thinkFrame () {
2795   spectral = true;
2797   if (status == IDLE) {
2798     auto plr = level.player;
2799     auto dist = pointDistance(ix, iy, plr.ix, plr.iy);
2800     if (counter > 0) --counter;
2801     if (dist < 90 && counter < 1) {
2802       if (abs(plr.iy-(iy+8)) < 8 && plr.ix > ix+8 && !isCollisionRight(2)) {
2803         status = ATTACK;
2804         dir = RIGHT;
2805         xa = 0.5;
2806       } else if (abs(plr.ix-(ix+8)) < 8 && plr.iy > iy+8 && !isCollisionBottom(2)) {
2807         status = ATTACK;
2808         dir = DOWN;
2809         ya = 0.5;
2810       } else if (abs(plr.iy-(iy+8)) < 8 && plr.ix < ix+8 && !isCollisionLeft(2)) {
2811         status = ATTACK;
2812         dir = LEFT;
2813         xa = -0.5;
2814       } else if (abs(plr.ix-(ix+8)) < 8 && plr.iy < iy+8 && !isCollisionTop(2)) {
2815         status = ATTACK;
2816         dir = UP;
2817         ya = -0.5;
2818       }
2819     }
2820   } else if (status == ATTACK) {
2821     bool colLeft = !!isCollisionLeft(1);
2822     bool colRight = !!isCollisionRight(1);
2823     bool colTop = !!isCollisionTop(1);
2824     bool colBot = !!isCollisionBottom(1);
2826     xv = fclamp(xv+xa, -4, 4);
2827     yv = fclamp(yv+ya, -4, 4);
2828     shiftXY(xv, yv);
2829     if (dir == RIGHT) {
2830       if (isCollisionRight(2) && colRight) { shiftX(-2); hit = true; }
2831       if (colRight) { shiftX(-1); hit = true; }
2832     } else if (dir == DOWN) {
2833       if (isCollisionBottom(2) && colBot) { shiftY(-2); hit = true; }
2834       if (colBot) { shiftY(-1); hit = true; }
2835     } else if (dir == LEFT) {
2836       if (isCollisionLeft(2) && colLeft) { shiftX(2); hit = true; }
2837       if (colLeft) { shiftX(1); hit = true; }
2838     } else if (dir == UP) {
2839       if (isCollisionTop(2) && colTop) { shiftY(2); hit = true; }
2840       if (colTop) { shiftY(1); hit = true; }
2841     }
2843     if (level.isObjectInRect(ix-1, iy-1, 18, 18, delegate bool (MapObject o) { return (o isa EnemyTombLord); })) hit = true;
2845     if (hit) {
2846       xv = 0;
2847       yv = 0;
2848       xa = 0;
2849       ya = 0;
2850     }
2852     /*
2853     if (hit) {
2854       auto ct = isCollision();
2855       writeln("dir=", dirName(), "; colLeft=", colLeft, "; colRight=", colRight, "; colTop=", colTop, "; colBot=", colBot, "; col=", (ct ? GetClassName(ct.Class) : '<none>'), " (", (ct ? ct.objName : '<>'), ")");
2856     }
2857     */
2859     if (hit && !isCollision() /*!colRight && !colLeft && !colTop && !colBot*/) {
2860       status = IDLE;
2861       hit = false;
2862       counter = 50;
2863     }
2864   } else if (status == STATE_DEAD) {
2865     xv = 0;
2866     yv = 0;
2867     xa = 0;
2868     ya = 0;
2869     shiftY(0.05);
2870     if (level.isLavaAtPoint(ix, iy-1)) { invincible = false; smashMe(); instanceRemove(); return; }
2871   }
2873   if (level.checkTilesInRect(ix+1, iy+1, 15, 15, &level.cbCollisionLava)) status = STATE_DEAD;
2875   spectral = false;
2877   // player
2878   auto plr = level.player;
2879   if (!plr.dead && !plr.invincible && !plr.isExitingSprite() && /*plr.collidesWith(self)*/ plr.isRectHitSimple(x0-1, y0-1, width+2, height+2)) {
2880     bool doCol = true;
2881     if (!plr.collidesWith(self)) {
2882       bool onLeft = (plr.xVel < -0.01 && plr.x0 > x1);
2883       bool onRight = (plr.xVel > 0.01 && plr.x1 < x0);
2884       bool onTop = (plr.yVel < -0.01 && plr.y0 > y1);
2885       bool onBottom = (plr.yVel > 0.01 && plr.y1 < y0);
2886       if (!onLeft && !onRight && !onTop && !onBottom) {
2887         doCol = false;
2888       } else {
2889         writeln("*** PLAYER RUNNING ON TRAP SPIKES");
2890       }
2891     }
2892     if (doCol) {
2893       if (global.plife > 0) {
2894         global.plife -= global.config.crushDmg;
2895         if (global.plife <= 0 /*and isRealLevel()*/) level.addDeath(objName);
2896         plr.spillBlood();
2897         plr.stunned = true;
2898         plr.stunTimer = 20;
2899         playSound('sndHurt');
2900       }
2901     }
2902   }
2904   level.isObjectInRect(x0-1, y0-1, width+2, height+2, delegate bool (MapObject o) {
2905     if (o == self) return false;
2906     if (o isa EnemyTombLord) return false;
2908     //if (o !isa MapEnemy) return false;
2910     // register hit only if we're moving onto a spikes
2911     if (!o.collidesWith(self)) {
2912       bool onLeft = (o.xVel < -0.01 && o.x0 > x1);
2913       bool onRight = (o.xVel > 0.01 && o.x1 < x0);
2914       bool onTop = (o.yVel < -0.01 && o.y0 > y1);
2915       bool onBottom = (o.yVel > 0.01 && o.y1 < y0);
2916       if (!onLeft && !onRight && !onTop && !onBottom) return false;
2917       writeln("*** RUNNING ON TRAP SPIKES");
2918     }
2920     // damsel
2921     auto dms = MonsterDamsel(o);
2922     if (dms) {
2923       if (!dms.invincible) {
2924         if (dms.heldBy) dms.heldBy.holdItem = none;
2925         dms.hp -= global.config.crushDmg;
2926         dms.spillBlood();
2927         dms.status = MapObject::THROWN;
2928         dms.counter = dms.stunMax;
2929         dms.calm = false;
2930         //dms.damselDropped = true;
2931         dms.kissCount = min(1, dms.kissCount);
2932         playSound('sndDamsel');
2933         return false;
2934       }
2935     }
2937     // enemy
2938     auto enemy = MapEnemy(o);
2939     if (enemy) {
2940       if (o.dead || o.status == STATE_DEAD || o.invincible) return false;
2941       if (o.heldBy) o.heldBy.holdItem = none;
2942       enemy.hp -= global.config.crushDmg;
2943       enemy.spillBlood();
2944       playSound('sndHit');
2945       return false;
2946     }
2948     return false;
2949   }, castClass:MapEnemy);
2953 defaultproperties {
2954   objType = 'oSmashTrap';
2955   objName = 'Smash Trap';
2956   desc = "Smash Trap";
2957   desc2 = "This trap carries an array of extendable blades. It can chase down intruders horizontally and vertically.";
2958   solid = true;
2959   viscidTop = 1;
2960   imageSpeed = 0.4;
2962   xv = 0;
2963   yv = 0;
2964   xa = 0;
2965   ya = 0;
2967   xVel = 0;
2968   yVel = 0;
2969   //xAcc = 0;
2970   //yAcc = 0;
2972   //moveable = true;
2973   grav = 1;
2974   myGravLimit = 10;
2975   counter = 3;
2977   status = IDLE;
2979   spriteName = 'sSmashTrap';
2980   toSpecialGrid = true; // it must think
2981   rubbleSprite1 = 'sRubbleTan';
2982   rubbleSprite2 = 'sRubbleTanSmall';
2983   depth = 60;
2987 // ////////////////////////////////////////////////////////////////////////// //
2988 class MapTileSmashTrapLit['oSmashTrapLit'] : MapTileSmashTrap;
2990 defaultproperties {
2991   spriteName = 'sSmashTrapLit';
2992   lightRadius = 32;
2996 // ////////////////////////////////////////////////////////////////////////// //
2997 class MapTileGoldDoor['oGoldDoor'] : MapTile;
2999 enum {
3000   CLOSED,
3001   SCEPTRE,
3004 int status;
3007 override void doSprayRubble () {
3011 override void setupTile () {
3012   writeln("GENERATED GOLD DOOR");
3016 // it opens if player carrying a sceptre, and has a crown
3017 override void thinkFrame () {
3018   // early exits
3019   if (status == SCEPTRE && !global.hasCrown) return;
3020   auto plr = level.player;
3021   if (plr.holdItem !isa ItemWeaponSceptre) return;
3022   // check for collision
3023   if (plr.collidesWith(self)) {
3024     if (global.hasCrown) {
3025       // take sceptre away
3026       auto it = plr.holdItem;
3027       plr.holdItem = none;
3028       it.instanceRemove();
3029       playSound('sndChestOpen');
3030       level.MakeMapTile(ix/16, iy/16, 'oXGold');
3031       auto obj = level.MakeMapObject(ix-4, iy+6, 'oPoof');
3032       if (obj) obj.xVel = -0.4;
3033       obj = level.MakeMapObject(ix+16+4, iy+6, 'oPoof');
3034       if (obj) obj.xVel = 0.4;
3035       instanceRemove();
3036       //level.osdMessage("THE MYSTERIOUS DOOR IS UNLOCKED!", 3.33);
3037       level.osdMessageTalk("THE MYSTERIOUS DOOR IS UNLOCKED!", timeout:3.33, inShopOnly:false);
3038       return;
3039     }
3040     // no crown
3041     status = SCEPTRE;
3042     level.osdMessage("THE SCEPTRE FITS...\nBUT NOTHING IS HAPPENING!", 3.33);
3043   }
3047 defaultproperties {
3048   objType = 'oGoldDoor';
3049   desc = "Gold Door";
3050   desc2 = "A door with a golden seal on it.";
3052   status = CLOSED;
3054   solid = false;
3055   invincible = true;
3056   moveable = false;
3057   spriteName = 'sGoldDoor';
3058   toSpecialGrid = true; // it must think
3060   depth = 2000;
3064 // ////////////////////////////////////////////////////////////////////////// //
3065 class MapTileUnlockedGoldDoor['oXGold'] : MapTile;
3068 override void doSprayRubble () {
3072 override void setupTile () {
3076 // it opens if player carrying a sceptre, and has a crown
3077 override void thinkFrame () {
3081 defaultproperties {
3082   objType = 'oXGold';
3083   desc = "Gold Door";
3084   desc2 = "A door with an opened golden seal on it.";
3086   solid = false;
3087   invincible = true;
3088   moveable = false;
3089   spriteName = 'sExit';
3090   exit = true;
3091   specialExit = true;
3093   depth = 2000;
3097 // ////////////////////////////////////////////////////////////////////////// //
3098 class MapTileBlackMarketDoor['oXMarket'] : MapTile;
3101 override void doSprayRubble () {
3105 override void setupTile () {
3106   writeln("*** GENERATED BLACK MARKET ENTRANCE ***");
3107   //writeln("*** made black market entrance at (", ix/16, ",", iy/16, ")");
3108   /*
3109   if (global.hasSpectacles || global.hasUdjatEye) {
3110     level.setTileAt(ix/16, iy/16, none);
3111     //depth = 101;
3112   }
3113   */
3117 // it opens if player carrying a sceptre, and has a crown
3118 override void thinkFrame () {
3122 defaultproperties {
3123   objType = 'oXMarket';
3124   desc = "Market Exit";
3125   desc2 = "A door leading to Black Market.";
3127   solid = false;
3128   invincible = true;
3129   moveable = false;
3130   spriteName = 'sExit';
3131   exit = true;
3132   specialExit = true;
3134   dontReplaceOthers = true;
3135   immuneToReplacement = true;
3137   depth = 2000;
3141 // ////////////////////////////////////////////////////////////////////////// //
3142 class MapTileMoai['oMoai'] : MapTile;
3144 override int width () { return 16; }
3145 override int height () { return 64; }
3147 override void doSprayRubble () {}
3148 override void setupTile () {}
3149 override void thinkFrame () {}
3151 defaultproperties {
3152   objType = 'oMoai';
3153   desc = "Moai Head";
3154   desc2 = "";
3156   solid = true;
3157   invincible = true;
3158   moveable = false;
3159   spriteName = 'sMoai';
3160   depth = 1000;
3164 // ////////////////////////////////////////////////////////////////////////// //
3165 class MapTileMoai3['oMoai3'] : MapTileMoai;
3167 defaultproperties {
3168   spriteName = 'sMoai3';
3172 // ////////////////////////////////////////////////////////////////////////// //
3173 class MapTileMoaiInside['oMoaiInside'] : MapTile;
3175 override int width () { return 16; }
3176 override int height () { return 48; }
3178 override void doSprayRubble () {}
3179 override void setupTile () {}
3180 override void thinkFrame () {}
3182 defaultproperties {
3183   objType = 'oMoaiInside';
3184   desc = "Moai Head";
3185   desc2 = "";
3187   solid = true;
3188   invincible = true;
3189   moveable = false;
3190   spriteName = 'sMoaiInside';
3191   depth = 92;
3195 // ////////////////////////////////////////////////////////////////////////// //
3196 class MapTileLamp['oLamp'] : MapTile;
3198 bool redLamp;
3201 override bool onExplosionTouch (MapObject xplo) {
3202   //int x = ix, y = iy;
3203   //doSprayRubble();
3204   invincible = false;
3205   smashMe();
3206   instanceRemove();
3207   //level.MakeMapObject(x+8, y+12, (redLamp ? 'oLampRedItem' : 'oLampItem'));
3208   return true;
3212 override void doSprayRubble () {
3213   if (global.cityOfGold == 1) {
3214     ore = 2;
3215     scrDropOre();
3216   }
3220 override void setupTile () {
3221   if (redLamp) spriteName = 'sLampRed';
3225 override void thinkFrame () {
3226   // 0: dark
3227   // 1: max light
3228   // 2: medium light
3229   float llev = (trunci(imageFrame) < 2 ? 0.0 : 2.0-imageFrame/2.0);
3230   lightRadius = 128+roundi(4*llev);
3231   if (level.loserGPU) lightRadius = 128;
3232   //::thinkFrame();
3234   int x = ix, y = iy;
3235   // drop lamp item if it has no support
3236   if (!level.checkTilesInRect(x+5, y-1, 6, 1)) {
3237     //doSprayRubble();
3238     invincible = false;
3239     smashMe();
3240     instanceRemove();
3241     level.MakeMapObject(x+8, y+12, (redLamp ? 'oLampRedItem' : 'oLampItem'));
3242     return;
3243   }
3247 defaultproperties {
3248   objType = 'oLamp';
3249   desc = "Lamp";
3250   desc2 = "A bright, geothermal-powered lantern, with a stainless steel frame.";
3251   imageSpeed = 0.5;
3252   solid = false;
3253   moveable = false;
3254   spriteName = 'sLamp';
3255   toSpecialGrid = true; // it must think
3256   depth = 201;
3260 // ////////////////////////////////////////////////////////////////////////// //
3261 class MapTileLampRed['oLampRed'] : MapTileLamp;
3263 defaultproperties {
3264   desc2 = "A geothermal-powered lantern that emits red light. Often found in brothels.";
3265   redLamp = true;