1 /**********************************************************************************
2 * Copyright (c) 2008, 2009 Derek Yu and Mossmouth, LLC
3 * Copyright (c) 2018, Ketmar Dark
5 * This file is part of Spelunky.
7 * You can redistribute and/or modify Spelunky, including its source code, under
8 * the terms of the Spelunky User License.
10 * Spelunky is distributed in the hope that it will be entertaining and useful,
11 * but WITHOUT WARRANTY. Please see the Spelunky User License for more details.
13 * The Spelunky User License should be available in "Game Information", which
14 * can be found in the Resource Explorer, or as an external file called COPYING.
15 * If not, please obtain a new copy of Spelunky from <http://spelunkyworld.com/>
17 **********************************************************************************/
18 class MapTile : MapEntity;
23 transient SpriteImage sprite;
32 bool wet; // for bricks inside a lake or lava, so they won't stop water flow
39 //bool hangeable; // don't forget to fix this for solids and trees!
47 bool sacrificingAltar;
48 bool shopWall; // may be set to `true` for non-solid tiles too
52 // oLadderTop, oLeaves, oTreeBranch
55 bool border; // border tile
57 bool litWholeTile; // lit whole tile with maximum light
58 bool ignoreFrameOffsetX, ignoreFrameOffsetY;
62 //float grav = 0.6; // the gravity
63 float myGravLimit = 8;
65 // x and y are offsets
70 bool gemDestroyWithTile;
74 void snapToExit (MapEntity e) {
75 if (!e || !e.isInstanceAlive) return;
82 // ////////////////////////////////////////////////////////////////////////// //
83 override void Destroy () {
102 override void onLoaded () {
104 if (spriteName) sprite = level.sprStore[spriteName];
105 for (MapBackTile bt = bgback; bt; bt = bt.next) bt.onLoaded();
106 for (MapBackTile bt = bgfront; bt; bt = bt.next) bt.onLoaded();
107 if (gem) gem.onLoaded();
111 string getExitMessage () {
116 override SpriteImage getSprite (optional out bool doMirror) {
122 override SpriteFrame getSpriteFrame (optional out bool doMirror, optional out int x0, optional out int y0, optional out int x1, optional out int y1) {
123 auto spr = getSprite(doMirror!optional);
124 if (!spr || spr.frames.length == 0) return none;
125 auto spf = spr.frames[trunc(imageFrame)%spr.frames.length];
126 if (!spf) return none;
127 if (specified_x0 || specified_x1) {
128 x0 = (ignoreFrameOffsetX ? 0 : -spf.xofs);
129 x1 = x0+spf.tex.width;
131 if (specified_y0 || specified_y1) {
132 y0 = (ignoreFrameOffsetY ? 0 : -spf.yofs);
133 y1 = y0+spf.tex.height;
139 override void clearSprite () {
145 override void setSprite (name sprNameL, optional name sprNameR) {
146 if (!sprNameL && sprNameR) sprNameL = sprNameR;
147 if (spriteName != sprNameL) {
148 spriteName = sprNameL;
149 sprite = (sprNameL ? level.sprStore[sprNameL] : none);
155 // ////////////////////////////////////////////////////////////////////////// //
156 override int width () { return Width; }
157 override int height () { return Height; }
160 void beautifyTile () {}
161 void scrSetupBlockTop () {}
162 void scrSetupBlockBottom () {}
163 void scrSetupBlockLeft () {}
164 void scrSetupBlockRight () {}
167 // ////////////////////////////////////////////////////////////////////////// //
168 void setGem (name oname, optional bool visibility) {
170 gem.ownerTile = none;
171 gem.instanceRemove();
175 gem = level.MakeMapObject(ix+8, iy+8, oname);
177 gem.ownerTile = self;
178 gem.hiddenTreasure = !global.hasSpectacles && !global.hasUdjatEye;
179 if (specified_visibility) gem.hiddenTreasure = !visibility;
182 if (gem.hiddenTreasure) gem.visible = false;
186 void setGemObject (MapObject obj, optional bool visibility) {
188 if (obj.ownerTile == self) {
189 if (gem != obj) FatalError("MapTile::setGemObject: WTF?! (0)");
191 if (gem == obj) FatalError("MapTile::setGemObject: WTF?! (1)");
194 gem.ownerTile = none;
195 gem.instanceRemove();
200 obj.ownerTile.gem = none;
201 obj.ownerTile = none;
204 gem.ownerTile = self;
205 gem.hiddenTreasure = !global.hasSpectacles && !global.hasUdjatEye;
206 if (specified_visibility) gem.hiddenTreasure = !visibility;
209 if (gem.hiddenTreasure) gem.visible = false;
215 void convertGemObjectToDiamond (MapObject ghost) {
217 if (oldgem && oldgem isa ItemBigGem) {
219 if (!ghost.isInstanceAlive) return;
220 if (!gem.isRectHitSimple(ghost.x0, ghost.y0, ghost.width, ghost.height)) return;
222 auto diamond = level.MakeMapObject(oldgem.ix, oldgem.iy, 'oDiamond');
223 setGemObject(diamond);
228 // ////////////////////////////////////////////////////////////////////////// //
229 void doCreateOre (int probSap, int probEmer, int probRuby, int probItem) {
230 int n = global.randRoom(1, 100);
231 if (n < 20) ore = 1; // sprite_index = sBrickGold;
232 else if (n < 30) ore = 2; // sprite_index = sBrickGoldBig;
233 if (probSap > 0 && global.randRoom(1, probSap) == 1) setGem('oSapphireBig');
234 else if (probEmer > 0 && global.randRoom(1, probEmer) == 1) setGem('oEmeraldBig');
235 else if (probRuby > 0 && global.randRoom(1, probRuby) == 1) setGem('oRubyBig');
236 else if (probItem > 0 && global.randRoom(1, probItem) == 1) setGemObject(level.scrGenerateItem(ix+8, iy+8, GameLevel::GenItemSet.Underground));
240 void copyOreFrom (MapTile t) {
241 if (t == self) return;
258 if (exit) setSprite('sEntrance');
263 if (exit) setSprite('sExit');
267 bool isExitActive () {
268 return (visible && exit && spriteName == 'sExit');
272 // ////////////////////////////////////////////////////////////////////////// //
276 case 'oLadderOrange':
277 //objName = 'oLadder';
278 //goto case 'oLadder';
283 spriteName = 'sLadder';
287 //ladder = true; // no, laddertop is not a ladder
291 spriteName = 'sLadderTop';
294 objType = 'oVineTop';
295 //ladder = true; // no, laddertop is not a ladder
299 spriteName = 'sVineTop';
302 //objName = 'oPushBlock';
305 spriteName = (global.cityOfGold ? 'sGoldBlock' : 'sBlock');
307 case 'oPushIceBlock':
308 //objName = 'oPushBlock';
312 spriteName = 'sIceBlock';
314 case 'oSolidIceBlock':
315 //objName = 'oIceBlock';
316 //goto case 'oIceBlock';
321 moveable = true; //k8: why, let it be an easter egg
322 spriteName = 'sIceBlock';
329 spriteName = 'sIceBlock';
332 // no sense to create ore here
334 spriteName = (global.randRoom(1, 10) == 1 ? 'sBrick2' : 'sBrick');
338 spriteName = 'sCaveSmooth';
345 spriteName = 'sSpikesWood';
351 spriteName = 'sSpikes';
354 objType = 'oEntrance';
359 spriteName = 'sEntrance';
367 spriteName = 'sExit';
372 spriteName = 'sAltarLeft';
377 spriteName = 'sAltarRight';
379 case 'oSacAltarLeft':
382 sacrificingAltar = true;
383 spriteName = 'sSacAltarLeft';
385 case 'oSacAltarRight':
388 sacrificingAltar = true;
389 spriteName = 'sSacAltarRight';
394 spriteName = 'sSign';
399 spriteName = 'sSignGeneral';
404 spriteName = 'sSignBomb';
409 spriteName = 'sSignWeapon';
411 case 'oSignClothing':
414 spriteName = 'sSignClothing';
419 spriteName = 'sSignRare';
424 spriteName = 'sSignCraps';
429 spriteName = 'sSignKissing';
436 spriteName = 'sMoai';
443 spriteName = 'sMoai2';
450 spriteName = 'sMoai3';
455 objType = 'oMoaiInside';
458 spriteName = 'sMoaiInside';
463 FatalError(va("unknown map tile type '%n'", objName));
465 //if (!solid) hangeable = false; // just in case
469 void setupTileSprite () {
470 if (!spriteName) FatalError("forgot to set sprite for tile type '", objName, "'");
471 sprite = level.sprStore[spriteName];
475 override bool initialize () {
476 if (!::initialize()) return false;
479 if (moveable && depth == 1001) depth = 1000;
480 if ((exit || enter) && depth == 1001) depth = 2000;
485 // ////////////////////////////////////////////////////////////////////////// //
486 // for now, it works only for spikes
487 final void makeBloody () {
491 setSprite(woodenSpikes ? 'sSpikesWoodBlood' : 'sSpikesBlood');
495 // ////////////////////////////////////////////////////////////////////////// //
496 final void appendBackBack (MapBackTile tile, optional int yofs) {
498 if (tile.next) FatalError("MapTile::appendBackBack: wtf?!");
499 tile.flty = (specified_yofs ? float(yofs) : 16.0);
500 MapBackTile last = bgback;
502 while (last.next) last = last.next;
510 final void appendBackFront (MapBackTile tile, optional int yofs) {
512 if (tile.next) FatalError("MapTile::appendBackFront: wtf?!");
513 tile.flty = (specified_yofs ? float(yofs) : -16.0);
514 MapBackTile last = bgfront;
516 while (last.next) last = last.next;
524 // ////////////////////////////////////////////////////////////////////////// //
526 override void onDestroy () {
531 // ////////////////////////////////////////////////////////////////////////// //
532 override void thinkFrame () {
533 if (!moveable) return;
534 // applies the acceleration
538 // approximates the "active" variables
539 if (fabs(xVel) < 0.001) xVel = 0;
540 if (fabs(yVel) < 0.001) yVel = 0;
541 //if (fabs(xAcc) < 0.0001) xAcc = 0;
542 //if (fabs(yAcc) < 0.0001) yAcc = 0;
544 yVel = fclamp(yVel+myGrav, -myGravLimit, myGravLimit);
545 int newY = round(flty+yVel); //!!!
546 // check if we need (and can) move down
547 int w = width, hp1 = height+1;
548 auto oldsolid = solid;
551 // made non-solid temporarily
552 auto hasAnything = level.checkTilesInRect(x0, y0, w, hp1, delegate bool (MapTile t) {
553 if (t == self) return false;
557 // hit something, stop right there
558 if (yVel > myGrav*3) playSound('sndThud');
568 if (flty > level.tilesHeight*16+16 && yVel >= 0) {
575 // ////////////////////////////////////////////////////////////////////////// //
576 void drawWithOfsBack (int xpos, int ypos, int scale, float currFrameDelta) {
577 //if (backTile) backTile.drawAt(xpos, ypos, scale);
578 if (invisible || !visible || !bgback) return;
581 int fx0, fy0, fx1, fy1;
582 auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
584 // non-moveable and non-special tiles need not to be interpolated
586 if (moveable || gridId) {
587 getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
593 for (MapBackTile bt = bgback; bt; bt = bt.next) {
596 bt.drawWithOfs(xpos, ypos, scale, currFrameDelta);
603 void drawWithOfsFront (int xpos, int ypos, int scale, float currFrameDelta) {
604 //if (backTile) backTile.drawAt(xpos, ypos, scale);
605 if (invisible || !visible || !bgfront) return;
608 int fx0, fy0, fx1, fy1;
609 auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
611 // non-moveable tiles need not to be interpolated
614 getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
620 for (MapBackTile bt = bgfront; bt; bt = bt.next) {
623 bt.drawWithOfs(xpos, ypos, scale, currFrameDelta);
630 override void drawWithOfs (int xpos, int ypos, int scale, float currFrameDelta) {
631 if (invisible || !visible) return;
634 int fx0, fy0, fx1, fy1;
635 auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
637 // non-moveable tiles need not to be interpolated
640 getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
646 //auto oclr = Video.color;
647 //if (moveable) Video.color = 0xff_7f_00;
650 fx0 = drwx+fx0*scale-xpos;
651 fy0 = drwy+fy0*scale-ypos;
652 fx1 = drwx+fx1*scale-xpos;
653 fy1 = drwy+fy1*scale-ypos;
655 spf.tex.blitExt(fx0, fy0, fx1, fy1, 0, 0, spf.tex.width, spf.tex.height);
657 spf.tex.blitExt(fx0, fy0, fx1, fy1, spf.tex.width, 0, 0, spf.tex.height);
659 } else if (ore > 0) {
666 //Video.color = oclr;
668 if (ore > 0 && !hideOre) {
669 auto ospr = level.sprStore[ore == 1 ? 'sGold' : 'sGoldBig'];
670 ospr.frames[0].tex.blitAt(fx0, fy0, scale);
675 // ////////////////////////////////////////////////////////////////////////// //
676 // called by Destroy event of blocks that can contain ore (oBrick, oLush, etc)
682 foreach (int i; 0..3) {
683 auto gold = level.MakeMapObject(x+8+global.randOther(0, 4)-global.randOther(0, 4), y+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldChunk');
685 //gold = instance_create(x+8+rand(0,4)-rand(0,4), y+8+rand(0,4)-rand(0,4), oGoldChunk);
686 gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
687 gold.yVel = global.randOther(2, 4);
693 auto gold = level.MakeMapObject(x+8+global.randOther(0, 4)-global.randOther(0, 4), y+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldNugget');
695 gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
696 gold.yVel = global.randOther(2, 4);
704 // ////////////////////////////////////////////////////////////////////////// //
705 private final bool cbIsSolidBrick (MapTile t) { return t.solid; }
706 private final bool cbIsBrickOrLushTile (MapTile t) { return (t.objType == 'oBrick' || t.objType == 'oLush'); }
709 // ////////////////////////////////////////////////////////////////////////// //
710 private transient MapObject mXplo;
712 private final bool cbExplodeTiles (MapTile t) {
713 if (!t.isInstanceAlive) return false;
714 if (t.invincible) return false;
721 t.onExplosionTouch(mXplo);
728 // called by solids during destroy event (oBrick etc) when destroy with force such as a boulder or explosion
729 // these rubble bits shower over the foreground and are not stopped by solids
730 final void scrSprayRubble (int x, int y, int count, name spr0, name spr1) {
731 while (count-- > 0) {
732 auto rubble = level.MakeMapObject(
733 x+8+global.randOther(0, 8)-global.randOther(0, 8),
734 y+8+global.randOther(0, 8)-global.randOther(0, 8), 'oRubblePiece'); //'oRubblePieceNonSolid');
735 rubble.setSprite(global.randOther(1, 3) == 1 ? spr0 : spr1);
736 rubble.xVel = global.randOther(0, 4)-global.randOther(0, 4);
737 rubble.yVel = -global.randOther(4, 8);
739 //rubble.image_blend = image_blend;
744 final void scrDropRubble (int x, int y, int count, name spr0, name spr1) {
745 while (count-- > 0) {
746 int rx = x+8+global.randOther(0, 8)-global.randOther(0, 8);
747 int ry = y+8+global.randOther(0, 8)-global.randOther(0, 8);
748 if (global.randOther(1, 3) == 1) {
749 auto rubble = level.MakeMapObject(rx, ry, 'oRubble');
750 rubble.setSprite(spr1);
751 //rubble.image_blend = image_blend;
753 auto rubble = level.MakeMapObject(rx, ry, 'oRubbleSmall');
754 rubble.setSprite(spr0);
755 //rubble.image_blend = image_blend;
761 void smashedCheckUpTile () {
762 level.checkTilesInRect(ix, iy-16, 16, 16, delegate bool (MapTile t) {
763 //writeln("mtl: '", GetClassName(t.Class), "'; spikes=", t.spikes);
764 if (t.isInstanceAlive && (t.spikes || t isa MapTileGraveBase)) t.instanceRemove();
771 void doSprayRubble () {
772 if (objType == 'oBlock') {
773 if (!global.cityOfGold) {
774 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubbleTan', 'sRubbleTanSmall');
775 scrDropRubble(ix, iy, 3, 'sRubbleTan', 'sRubbleTanSmall');
780 } else if (objType == 'oBrick' || objType == 'oLush') {
781 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
782 scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
787 final bool isVineTile (MapTile t) { return (t.objType == 'oVineTop' || t.objType == 'oVine'); }
790 // this should be called from `smashMe()`
791 void checkSmashedVineSupport () {
792 if (!solid || isVineTile(self)) return;
793 // check for support tile ('cause this can be a moving tile, and not an actual support)
794 auto support = level.checkTileAtPoint(ix, iy, delegate bool (MapTile t) {
795 if (t == self) return false;
796 return (t.isInstanceAlive && t.solid);
802 auto vine = level.checkTileAtPoint(x, y, &isVineTile);
804 // yay, we found a vine; now remove it, going all the way down
806 vine = level.checkTileAtPoint(x, y, &isVineTile);
815 if (invincible) return false;
818 checkSmashedVineSupport();
820 if (/*!global.cemetary &&*/ isVineTile(self)) {
821 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
822 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
825 if (altar) level.scrTriggerIdolAltar(stolenIdol:true);
828 smashedCheckUpTile();
831 if (gemDestroyWithTile) {
832 gem.instanceRemove();
834 gem.hiddenTreasure = false;
837 gem.spectral = false;
839 gem = none; // don't destroy it with tile
845 if (!cleanDeath) doSprayRubble();
848 if (other.object_index == oBoulder) {
849 if (other.integrity > 0) dosomething = false; //exit;
853 //with (oTreasure) state = 1;
854 //with (oSpikes) if (not collision_point(x, y+16, oSolid, 0, 0)) instance_destroy();
857 level.scrShopkeeperAnger(GameLevel::SCAnger.TileDestroyed);
862 MapTile obj = level.checkTileAtPoint(x, y-16, &cbIsSolidBrick);
863 if (obj) obj.scrSetupBlockBottom();
864 obj = level.checkTileAtPoint(x, y+16, &cbIsSolidBrick);
865 if (obj) obj.scrSetupBlockTop();
866 obj = level.checkTileAtPoint(x-16, y, &cbIsSolidBrick);
867 if (obj) obj.scrSetupBlockLeft();
868 obj = level.checkTileAtPoint(x+16, y, &cbIsSolidBrick);
869 if (obj) obj.scrSetupBlockRight();
872 /* was commented in the original
873 instance_activate_object(oLadder);
874 obj = collision_point(x+8, y+24, oLadder, 0, 0);
876 if (sprite_index == sVineTop or sprite_index == sVine or
877 sprite_index == sVineSource or sprite_index == sVineBottom)
888 override bool onExplosionTouch (MapObject xplo) {
893 level.checkTileAtPoint(x+8, y-1, &cbExplodeTiles);
897 obj = instance_place(x, y, oGold);
898 if (obj != noone) with (obj) instance_destroy();
900 obj = instance_place(x, y, oGoldBig);
901 if (obj != noone) with (obj) instance_destroy();
911 // ////////////////////////////////////////////////////////////////////////// //
912 void onGotSpectacles () {
914 gem.hiddenTreasure = false;
923 //depth = 9666; //???
928 // ////////////////////////////////////////////////////////////////////////// //
929 class MapTileBrick['oBrick'] : MapTile;
932 override void setupTile () {
933 spriteName = (global.randRoom(1, 10) == 1 ? 'sBrick2' : 'sBrick');
934 int tileX = ix/16, tileY = iy/16;
935 if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
940 doCreateOre(100, 120, 140, 1200);
945 override void beautifyTile () {
946 if (level.checkTileAtPoint(ix+8, iy-8, delegate bool (MapTile t) { return (t isa TitleTileSpecialNonSolidInvincible || t isa TitleTileBlack); })) return;
949 //if (argument1) if (global.randRoom(1, 10) == 1) sprite_index = sBrick2; // reset sprite for custom border setup
951 int tileX = ix/16, tileY = iy/16;
953 auto tt = level.getTileAt(tileX, tileY-1);
954 //if (y == 0 || isNamedTileAt(x, y-1, 'oBrick') || isNamedTileAt(x, y-1, 'oHardBlock') || isNamedTileAt(x, y-1, 'oEdgeBrick')) up = true;
955 bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
957 tt = level.getTileAt(tileX, tileY+1);
958 //if (y == NormalTilesHeight-1 || isNamedTileAt(x, y+1, 'oBrick') || isNamedTileAt(x, y+1, 'oHardBlock') || isNamedTileAt(x, y+1, 'oEdgeBrick')) down = true;
959 bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
961 //if (collision_point(x-16, y, oBrick, 0, 0) or collision_point(x-16, y, oHardBlock, 0, 0)) { left = true; } // in the original
962 //if (collision_point(x+16, y, oBrick, 0, 0) or collision_point(x+16, y, oHardBlock, 0, 0)) { right = true; } // in the original
965 setSprite('sCaveUp');
966 // add rocks and sand
968 if (global.randRoom(1, 3) < 3) {
969 tile_add('bgCaveTop', 0, 0, 16, 16, x*16, y*16-16, 3);
971 tile_add('bgCaveTop', 16, 0, 16, 16, x*16, y*16-16, 3);
974 bool n = (global.randRoom(1, 3) < 3);
975 appendBackFront(level.CreateBgTile('bgCaveTop', (n ? 0 : 16)));
976 //instance_create(x, y-16, oCaveTop);
980 setSprite(!up ? 'sCaveUp2' : 'sBrickDown');
981 //instance_create(x, y+16, oCaveBottom);
985 if (not left) instance_create(x-16, y, oCaveLeft);
986 if (not right) instance_create(x+15, y, oCaveRight);
991 override void scrSetupBlockTop () {
992 //int x = ix, y = iy;
993 int tileX = ix/16, tileY = iy/16;
994 auto tt = level.getTileAt(tileX, tileY+1);
995 //if (y >= (GameLevel::NormalTilesHeight-16)*16 || level.checkTileAtPoint(x, y+16, &cbIsBrickOrLushTile)) down = true;
996 bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
998 auto bt = level.CreateBgTile('bgCaveTop', (global.randRoom(1, 3) < 3 ? 0 : 16));
999 //bt.flty = -16; // offset
1000 appendBackFront(bt);
1002 setSprite(down ? 'sCaveUp' : 'sCaveUp2');
1006 override void scrSetupBlockBottom () {
1007 //int x = ix, y = iy;
1009 //if (y == 0 || level.checkTileAtPoint(x, y-16, &cbIsBrickOrLushTile)) up = true;
1010 int tileX = ix/16, tileY = iy/16;
1011 auto tt = level.getTileAt(tileX, tileY-1);
1012 bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1013 setSprite(up ? 'sBrickDown' : 'sCaveUp2');
1019 spriteName = 'sBrick';
1023 toSpecialGrid = false;
1027 // ////////////////////////////////////////////////////////////////////////// //
1028 class MapTileHardBrick['oHardBlock'] : MapTileBrick;
1030 override void setupTile () {
1035 objType = 'oHardBlock';
1036 spriteName = 'sBrick';
1042 // ////////////////////////////////////////////////////////////////////////// //
1043 class MapTileBlock['oBlock'] : MapTile;
1046 override void setupTile () {
1047 int tileX = ix/16, tileY = iy/16;
1048 if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1051 //writeln("BORDER");
1053 if (global.cityOfGold) spriteName = 'sGoldBlock';
1057 final bool isBBTTile (MapTile t) { return (t.objType == 'oBrick' || t.objType == 'oTemple' || t.objType == 'oHardBlock'); }
1060 override void beautifyTile () {
1061 bool down = !!level.checkTileAtPoint(ix, iy+16, &isBBTTile);
1063 // don't want push blocks next to lava until we tighten up liquid draining
1064 if (level.isLavaAtPoint(ix-16, iy) || level.isLavaAtPoint(ix+16, iy)) down = false;
1066 if (down && global.randRoom(1, 4) == 1) {
1068 level.MakeMapTile(ix/16, iy/16, 'oPushBlock');
1075 spriteName = 'sBlock';
1078 toSpecialGrid = false;
1082 // ////////////////////////////////////////////////////////////////////////// //
1083 class MapTileLush['oLush'] : MapTile;
1086 override void setupTile () {
1087 int tileX = ix/16, tileY = iy/16;
1088 if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1091 //writeln("BORDER");
1093 doCreateOre(80, 100, 120, 1200);
1098 override void beautifyTile () {
1099 if (level.checkTileAtPoint(ix+8, iy-8, delegate bool (MapTile t) { return (t isa TitleTileSpecialNonSolidInvincible || t isa TitleTileBlack); })) return;
1101 int tileX = ix/16, tileY = iy/16;
1103 auto tt = level.getTileAt(tileX, tileY-1);
1104 //if (y == 0 || isNamedTileAt(x, y-1, 'oBrick') || isNamedTileAt(x, y-1, 'oHardBlock') || isNamedTileAt(x, y-1, 'oEdgeBrick')) up = true;
1105 bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral && tt.objType == 'oLush'));
1107 tt = level.getTileAt(tileX, tileY+1);
1108 //if (y == NormalTilesHeight-1 || isNamedTileAt(x, y+1, 'oBrick') || isNamedTileAt(x, y+1, 'oHardBlock') || isNamedTileAt(x, y+1, 'oEdgeBrick')) down = true;
1109 bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral && tt.objType == 'oLush'));
1112 setSprite('sLushUp');
1113 if (!level.isLavaAtPoint(ix+8, iy-8)) {
1115 if (global.randRoom(1, 8) == 1) bt = level.CreateBgTile('bgCaveTop2', 32);
1116 else if (global.randRoom(1, 3) < 3) bt = level.CreateBgTile('bgCaveTop2', 0);
1117 else bt = level.CreateBgTile('bgCaveTop2', 16);
1118 appendBackFront(bt);
1123 setSprite(!up ? 'sLushUp2' : 'sLushDown');
1124 if (!level.isSolidAtPoint(ix, iy+16) && !level.isLavaAtPoint(ix, iy+16)) {
1126 if (global.randRoom(1, 12) == 1) bt = level.CreateBgTile('bgCaveTop2', 48);
1127 else if (global.randRoom(1, 12) == 1) bt = level.CreateBgTile('bgCaveTop2', 64);
1130 //instance_create(x, y+16, oLushBottom); // in the original
1135 override void scrSetupBlockTop () {
1136 int tileX = ix/16, tileY = iy/16;
1137 auto tt = level.getTileAt(tileX, tileY+1);
1138 bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1139 //setSprite('sLushUpBare');
1141 setSprite(spriteName == 'sLushUp' || spriteName == 'sLushUp2' || spriteName == 'sLushUp3' ? 'sLushUpBareTop' : 'sLushUpBare2');
1143 setSprite('sLushUpBare');
1148 override void scrSetupBlockBottom () {
1149 int tileX = ix/16, tileY = iy/16;
1150 auto tt = level.getTileAt(tileX, tileY-1);
1151 bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1153 setSprite(spriteName == 'sLushUp' || spriteName == 'sLushUp2' || spriteName == 'sLushUp3' ? 'sLushUpBareBottom' : 'sLushUpBare2');
1155 setSprite('sLushDownBare');
1162 spriteName = 'sLush';
1166 toSpecialGrid = false;
1170 // ////////////////////////////////////////////////////////////////////////// //
1171 class MapTileDark['oDark'] : MapTile;
1174 override void setupTile () {
1175 int tileX = ix/16, tileY = iy/16;
1176 if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1179 //writeln("BORDER");
1181 doCreateOre(40, 60, 80, 1200);
1186 override void beautifyTile () {
1187 if (level.checkTileAtPoint(ix+8, iy-8, delegate bool (MapTile t) { return (t isa TitleTileSpecialNonSolidInvincible || t isa TitleTileBlack); })) return;
1190 //if (argument1) if (global.randRoom(1, 10) == 1) sprite_index = sBrick2; // reset sprite for custom border setup
1192 int tileX = ix/16, tileY = iy/16;
1194 auto tt = level.getTileAt(tileX, tileY-1);
1195 //if (y == 0 || isNamedTileAt(x, y-1, 'oBrick') || isNamedTileAt(x, y-1, 'oHardBlock') || isNamedTileAt(x, y-1, 'oEdgeBrick')) up = true;
1196 bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1198 tt = level.getTileAt(tileX, tileY+1);
1199 //if (y == NormalTilesHeight-1 || isNamedTileAt(x, y+1, 'oBrick') || isNamedTileAt(x, y+1, 'oHardBlock') || isNamedTileAt(x, y+1, 'oEdgeBrick')) down = true;
1200 bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1202 //if (collision_point(x-16, y, oBrick, 0, 0) or collision_point(x-16, y, oHardBlock, 0, 0)) { left = true; } // in the original
1203 //if (collision_point(x+16, y, oBrick, 0, 0) or collision_point(x+16, y, oHardBlock, 0, 0)) { right = true; } // in the original
1206 setSprite('sDarkUp');
1207 bool n = (global.randRoom(1, 3) < 3);
1208 appendBackFront(level.CreateBgTile('bgCaveTop3', (n ? 0 : 16)));
1212 setSprite(!up ? 'sDarkUp2' : 'sDarkDown');
1217 override void scrSetupBlockTop () {
1218 int tileX = ix/16, tileY = iy/16;
1219 auto tt = level.getTileAt(tileX, tileY+1);
1220 bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1221 auto bt = level.CreateBgTile('bgCaveTop3', (global.randRoom(1, 3) < 3 ? 0 : 16));
1222 appendBackFront(bt);
1223 //bt.flty = -16; // offset
1225 setSprite(down ? 'sDarkUp' : 'sDarkUp2');
1229 override void scrSetupBlockBottom () {
1230 int tileX = ix/16, tileY = iy/16;
1231 auto tt = level.getTileAt(tileX, tileY-1);
1232 bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1233 setSprite(up ? 'sDarkDown' : 'sDarkUp2');
1239 spriteName = 'sDark';
1243 toSpecialGrid = false;
1247 // ////////////////////////////////////////////////////////////////////////// //
1248 class MapTileIce['oIce'] : MapTile;
1253 final bool isIceTile (MapTile t) { return t.ice; }
1254 final MapTile isIceTileAtPoint (int x, int y) { return level.checkTileAtPoint(x, y, &isIceTile); }
1257 override void setupTile () {
1258 if (global.randRoom(1, 80) == 1) {
1259 setGem('oFrozenCaveman', true); // always visible
1262 gem.saveInterpData();
1263 gemDestroyWithTile = true;
1265 dripTimer = global.randOther(20, 400);
1266 //hasIceBottom = true;
1271 override void thinkFrame () {
1273 if (--dripTimer <= 0) {
1275 level.MakeMapObject(ix+8, iy+16+4, 'oDrip');
1276 dripTimer = global.randOther(20, 400);
1283 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1284 bool up = (specified_noup && noup ? false : !!isIceTileAtPoint(ix, iy-16));
1285 bool down = (specified_nodown && nodown ? false : !!isIceTileAtPoint(ix, iy+16));
1286 bool left = (specified_noleft && noleft ? false : !!isIceTileAtPoint(ix-16, iy));
1287 bool right = (specified_noright && noright ? false : !!isIceTileAtPoint(ix+16, iy));
1289 if (!up) setSprite('sIceUp');
1291 setSprite(!up ? 'sIceUp2' : 'sIceDown');
1292 if (!level.isSolidAtPoint(ix, iy+16) && global.randOther(1, 20) == 1) hasIceBottom = true;
1295 if (!up && !down) setSprite('sIceUDL');
1296 else if (!up) setSprite('sIceUL');
1297 else if (!down) setSprite('sIceDL');
1298 else setSprite('sIceLeft');
1301 if (!up && !down) setSprite('sIceUDR');
1302 else if (!up) setSprite('sIceUR');
1303 else if (!down) setSprite('sIceDR');
1304 else setSprite('sIceRight');
1306 if (!up && !left && !right && down) setSprite('sIceULR');
1307 if (!down && !left && !right && up) setSprite('sIceDLR');
1308 if (up && down && !left && !right) setSprite('sIceLR');
1309 if (!up && !down && !left && !right) setSprite('sIceBlock');
1314 override void beautifyTile () {
1316 bool up = !!isIceTileAtPoint(ix, iy-16);
1317 bool down = !!isIceTileAtPoint(ix, iy+16);
1318 bool left = !!isIceTileAtPoint(ix-16, iy);
1319 bool right = !!isIceTileAtPoint(ix+16, iy);
1321 //writeln("beautifying ice; l=", left, "; r=", right, "; u=", up, "; d=", down);
1323 if (!up) setSprite('sIceUp');
1325 setSprite(!up ? 'sIceUp2' : 'sIceDown');
1326 if (!level.isSolidAtPoint(ix, iy+16) && global.randOther(1, 20) == 1) hasIceBottom = true;
1329 if (!up && !down) setSprite('sIceUDL');
1330 else if (!up) setSprite('sIceUL');
1331 else if (!down) setSprite('sIceDL');
1332 else setSprite('sIceLeft');
1335 if (!up && !down) setSprite('sIceUDR');
1336 else if (!up) setSprite('sIceUR');
1337 else if (!down) setSprite('sIceDR');
1338 else setSprite('sIceRight');
1340 if (!up && !left && !right && down) setSprite('sIceULR');
1341 if (!down && !left && !right && up) setSprite('sIceDLR');
1342 if (up && down && !left && !right) setSprite('sIceLR');
1343 if (!up && !down && !left && !right) setSprite('sIceBlock');
1345 setupNiceTileSprite();
1349 override void scrSetupBlockTop () {
1350 setupNiceTileSprite(/*noup:true*/);
1353 bool down = !!isIceTileAtPoint(ix, iy+16);
1354 bool left = !!isIceTileAtPoint(ix-16, iy);
1355 bool right = !!isIceTileAtPoint(ix+16, iy);
1357 setSprite('sIceUp');
1359 if (!down) setSprite('sIceUp2');
1361 if (!left) setSprite(!up ? 'sIceUDL' : 'sIceUL');
1362 if (!right && !down) setSprite('sIceUDR');
1364 if (!left && !right && down) setSprite('sIceULR');
1365 if (!down && !left && !right) setSprite('sIceBlock');
1370 override void scrSetupBlockBottom () {
1371 setupNiceTileSprite(/*nodown:true*/);
1373 bool up = !!isIceTileAtPoint(ix, iy-16);
1374 bool left = !!isIceTileAtPoint(ix-16, iy);
1375 bool right = !!isIceTileAtPoint(ix+16, iy);
1377 setSprite(!up ? 'sIceUp2' : 'sIceDown');
1379 if (global.randOther(1, 20) == 1) hasIceBottom = true; //instance_create(x, y+16, oIceBottom);
1381 if (!left) setSprite(!up ? 'sIceUDL' : 'sIceDL');
1382 if (!right) setSprite(!up ? 'sIceUDR' : 'sIceDR');
1384 if (!left && !right && up) setSprite('sIceDLR');
1385 if (!up && !left && !right) setSprite('sIceBlock');
1390 override void scrSetupBlockLeft () {
1391 setupNiceTileSprite(/*noright:true*/);
1393 bool up = !!isIceTileAtPoint(ix, iy-16);
1394 bool down = !!isIceTileAtPoint(ix, iy+16);
1395 bool left = !!isIceTileAtPoint(ix-16, iy);
1398 if (!up) setSprite('sIceUp');
1399 if (!down) { if (!up) setSprite('sIceUp2'); else setSprite('sIceDown'); }
1401 if (!up && !down) setSprite('sIceUDL');
1402 else if (!up) setSprite('sIceUL');
1403 else if (!down) setSprite('sIceDL');
1404 else setSprite('sIceLeft');
1407 if (!up && !down) setSprite('sIceUDR');
1408 else if (!up) setSprite('sIceUR');
1409 else if (!down) setSprite('sIceDR');
1410 else setSprite('sIceRight');
1412 if (!up && !left && !right && down) setSprite('sIceULR');
1413 if (!down && !left && !right && up) setSprite('sIceDLR');
1414 if (up && down && !left && !right) setSprite('sIceLR');
1415 if (!up && !down && !left && !right) setSprite('sIceBlock');
1420 override void scrSetupBlockRight () {
1421 setupNiceTileSprite(/*noleft:true*/);
1423 bool up = !!isIceTileAtPoint(ix, iy-16);
1424 bool down = !!isIceTileAtPoint(ix, iy+16);
1426 bool right = !!isIceTileAtPoint(ix+16, iy);
1428 if (!up) setSprite('sIceUp');
1429 if (!down) { if (!up) setSprite('sIceUp2'); else setSprite('sIceDown'); }
1431 if (!up && !down) setSprite('sIceUDL');
1432 else if (!up) setSprite('sIceUL');
1433 else if (!down) setSprite('sIceDL');
1434 else setSprite('sIceLeft');
1437 if (!up && !down) setSprite('sIceUDR');
1438 else if (!up) setSprite('sIceUR');
1439 else if (!down) setSprite('sIceDR');
1440 else setSprite('sIceRight');
1442 if (!up && !left && !right && down) setSprite('sIceULR');
1443 if (!down && !left && !right && up) setSprite('sIceDLR');
1444 if (up && down && !left && !right) setSprite('sIceLR');
1445 if (!up && !down && !left && !right) setSprite('sIceBlock');
1452 spriteName = 'sIce';
1456 toSpecialGrid = true; // want to think
1460 // ////////////////////////////////////////////////////////////////////////// //
1461 class MapTileTemple['oTemple'] : MapTile;
1466 final bool isTempleTile (MapTile t) { return (t.objType == 'oTemple' || t.border); } // fake too
1467 final MapTile isTempleTileAtPoint (int x, int y) { return level.checkTileAtPoint(x, y, &isTempleTile); }
1470 override void setupTile () {
1471 setSprite(global.cityOfGold ? 'sGTemple' : 'sTemple');
1472 int tileX = ix/16, tileY = iy/16;
1473 if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1476 //writeln("BORDER");
1478 doCreateOre(60, 80, 100, 1200);
1484 override void thinkFrame () {
1486 if (--dripTimer <= 0) {
1488 level.MakeMapObject(ix+8, iy+16+4, 'oDrip');
1489 dripTimer = global.randOther(20, 400);
1497 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1498 bool up = (iy <= 0 ? true : (specified_noup && noup ? false : !!isTempleTileAtPoint(ix, iy-16)));
1499 bool down = (iy >= level.tilesHeight*16-16 ? true : (specified_nodown && nodown ? false : !!isTempleTileAtPoint(ix, iy+16)));
1500 bool left = (ix <= 16 ? true : (specified_noleft && noleft ? false : !!isTempleTileAtPoint(ix-16, iy)));
1501 bool right = (ix >= level.tilesWidth*16-16*2 ? true : (specified_noright && noright ? false : !!isTempleTileAtPoint(ix+16, iy)));
1505 if (global.cityOfGold) sprite_index = sGTemple; else sprite_index = sTemple;
1510 if (y == 0 or collision_point(x, y-16, oTemple, 0, 0) or collision_point(x, y+16, oTempleFake, 0, 0)) { up = true; }
1511 if (y >= argument0 or collision_point(x, y+16, oTemple, 0, 0) or collision_point(x, y+16, oTempleFake, 0, 0)) { down = true; }
1512 if (collision_point(x-16, y, oTemple, 0, 0) or collision_point(x-16, y, oTempleFake, 0, 0)) { left = true; }
1513 if (collision_point(x+16, y, oTemple, 0, 0) or collision_point(x+16, y, oTempleFake, 0, 0)) { right = true; }
1516 if (global.cityOfGold) {
1518 setSprite('sGTempleUp');
1519 if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 0)); //3
1520 else if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 16)); //3
1521 if (!left && !right) {
1522 if (!down) setSprite('sGTempleUp6'); else setSprite('sGTempleUp5');
1524 if (!down) setSprite('sGTempleUp7'); else setSprite('sGTempleUp3');
1525 } else if (!right) {
1526 if (!down) setSprite('sGTempleUp8'); else setSprite('sGTempleUp4');
1527 } else if (left && right && !down) {
1528 setSprite('sGTempleUp2');
1531 setSprite('sGTempleDown');
1535 setSprite('sTempleUp');
1536 //if (rand(1, 4) == 1) tile_add(bgCaveTop4, 0, 0, 16, 16, x, y-16, 3); else if (rand(1, 4) == 1) tile_add(bgCaveTop4, 16, 0, 16, 16, x, y-16, 3);
1537 if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 0)); //3
1538 else if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 16)); //3
1539 if (!left && !right) {
1540 if (!down) setSprite('sTempleUp6'); else setSprite('sTempleUp5');
1542 if (!down) setSprite('sTempleUp7'); else setSprite('sTempleUp3');
1543 } else if (!right) {
1544 if (!down) setSprite('sTempleUp8'); else setSprite('sTempleUp4');
1545 } else if (left && right && !down) {
1546 setSprite('sTempleUp2');
1549 setSprite('sTempleDown');
1555 override void beautifyTile () {
1556 setupNiceTileSprite();
1560 override void scrSetupBlockTop () {
1561 setupNiceTileSprite(/*noup:true*/);
1565 override void scrSetupBlockBottom () {
1566 setupNiceTileSprite(/*nodown:true*/);
1570 override void scrSetupBlockLeft () {
1571 setupNiceTileSprite(/*noright:true*/);
1575 override void scrSetupBlockRight () {
1576 setupNiceTileSprite(/*noleft:true*/);
1581 objType = 'oTemple';
1582 spriteName = 'sTemple';
1585 //toSpecialGrid = true; // want to think
1589 // ////////////////////////////////////////////////////////////////////////// //
1590 class MapTileVine['oVine'] : MapTile;
1593 override void setupTile () {
1597 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1598 //if (argument1) sprite_index = sVine;
1600 bool up = !!level.isLadderAtPoint(ix+8, iy-8);
1601 bool down = !!level.isLadderAtPoint(ix+8, iy+16);
1604 //tile_add(bgVineRoots, 0, 0, 16, 16, x, y-16, 1000); // use same depth as the vine objects themselves
1605 appendBackFront(level.CreateBgTile('bgVineRoots', 0)); //1000
1606 setSprite('sVineSource');
1608 setSprite('sVineBottom');
1613 override void beautifyTile () {
1614 setupNiceTileSprite();
1622 spriteName = 'sVine';
1623 //toSpecialGrid = true; // want to think
1627 // ////////////////////////////////////////////////////////////////////////// //
1628 class MapTileLava['oLava'] : MapTile;
1636 override void setupTile () {
1637 spurtTime = global.randOther(100, 300);
1638 spurtCounter = spurtTime;
1642 override void doSprayRubble () {
1643 foreach (; 0..3) level.MakeMapObject(ix+global.randOther(0, 16), iy+global.randOther(0, 16), 'oLavaDrip');
1644 if (global.randOther(1, 6) == 1) {
1645 auto flame = level.MakeMapObject(ix+8, iy+8, 'oFlame');
1646 if (flame) flame.yVel = 4;
1651 override bool onExplosionTouch (MapObject xplo) {
1656 override void thinkFrame () {
1657 if (!spurtSet && spriteName == 'sLavaTop') {
1659 spurt = (global.randOther(1, 4) == 1);
1661 auto dist = pointDistance(ix, iy, level.player.ix, level.player.iy);
1662 if (spurt && dist < 240) {
1663 if (spurtCounter > 0) {
1666 spurtCounter = spurtTime;
1667 auto flame = level.MakeMapObject(ix+8, iy-4, (global.randOther(1, 8) == 1 ? 'oMagma' : 'oFlame'));
1668 //auto flame = level.MakeMapObject(ix+8, iy-4, 'oMagma');
1669 if (flame) flame.yVel = -global.randOther(1, 4);
1677 spriteName = 'sLava';
1681 toSpecialGrid = false; // lava tiles are special: they are animated/thinked without a grid
1682 lightRadius = 32+16;
1683 litWholeTile = true;
1688 // ////////////////////////////////////////////////////////////////////////// //
1689 class MapTileWater['oWater'] : MapTile;
1692 override void setupTile () {
1696 override void doSprayRubble () {
1697 foreach (; 0..3) level.MakeMapObject(ix+global.randOther(0, 16), iy+global.randOther(0, 16), 'oDrip');
1701 override bool onExplosionTouch (MapObject xplo) {
1706 final bool isWaterTile (MapTile t) { return t.water; }
1707 //final bool isSolidOrWaterTile (MapTile t) { return (t.water || t.solid); }
1710 void setupNiceTileSprite () {
1711 //if (argument1) sprite_index = sWater;
1713 bool upWater = false;
1718 if (level.checkTileAtPoint(ix, iy-16, &isWaterTile)) upWater = true;
1719 if (level.isSolidAtPoint(ix, iy-16)) up = true;
1720 if (level.isSolidAtPoint(ix, iy+16) && !level.checkTileAtPoint(ix, iy+16, &isWaterTile)) down = true;
1722 if (!up && !upWater) setSprite('sWaterTop');
1724 if (upWater && level.checkTileAtPoint(ix, iy-32, &isWaterTile) && down && global.randOther(1, 4) == 1) {
1725 setSprite('sWaterBottomTall2');
1726 auto water = level.checkTileAtPoint(ix, iy-16, &isWaterTile);
1727 if (water) water.setSprite('sWaterBottomTall1');
1728 } else if ((up || upWater) && down) {
1729 switch (global.randOther(1, 4)) {
1730 case 1: setSprite('sWaterBottom'); break;
1731 case 2: setSprite('sWaterBottom2'); break;
1732 case 3: setSprite('sWaterBottom3'); break;
1733 case 4: setSprite('sWaterBottom4'); break;
1739 override void beautifyTile () {
1740 setupNiceTileSprite();
1746 spriteName = 'sWater';
1750 toSpecialGrid = false;
1754 // ////////////////////////////////////////////////////////////////////////// //
1755 class MapTileWaterSwim['oWaterSwim'] : MapTileWater;
1758 // ////////////////////////////////////////////////////////////////////////// //
1759 class MapTileLavaSolid['oLavaSolid'] : MapTile;
1761 override void setupTile () {
1764 override bool onExplosionTouch (MapObject xplo) {
1769 objType = 'oLavaSolid';
1770 spriteName = 'sLava';
1774 toSpecialGrid = false;
1778 // ////////////////////////////////////////////////////////////////////////// //
1779 class MapTileLavaLushTrapBlock['oTrapBlock'] : MapTile;
1786 override void onDestroy () {
1788 /*if (not cleanDeath and not global.cleanSolids)*/ {
1789 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubbleTan', 'sRubbleTanSmall');
1790 scrDropRubble(ix, iy, 3, 'sRubbleTan', 'sRubbleTanSmall');
1792 playSound('sndThump');
1801 override void doSprayRubble () {
1802 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
1803 scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
1805 playSound('sndThump');
1811 override void setupTile () {
1817 if (distanceToEntityCenter(level.player) < 90) {
1823 override void thinkFrame () {
1825 if (deathTimer > 0) --deathTimer; else instanceRemove();
1831 objType = 'oLavaSolid';
1832 spriteName = 'sSkullBlock';
1835 toSpecialGrid = true;
1840 // ////////////////////////////////////////////////////////////////////////// //
1841 class MapTileLeaves['oLeaves'] : MapTile;
1847 final bool isTreeOrLeaves (MapTile t) { return (t != self && t.tree || t.leaves); }
1851 override void onDestroy () {
1853 if (spriteName != 'sLeavesDead' && spriteName != 'sLeavesDeadR') {
1854 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1855 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1862 override void doSprayRubble () {
1863 if (spriteName != 'sLeavesDead' && spriteName != 'sLeavesDeadR') {
1864 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1865 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1870 override void setupTile () {
1871 spriteName = (global.cemetary ? 'sLeavesDead' : 'sLeaves');
1875 override void thinkFrame () {
1880 if (level.checkTileAtPoint(x-16, y, &isTreeOrLeaves)) {
1881 setSprite(global.cemetary ? 'sLeavesDeadR' : 'sLeavesRight');
1882 } else if (level.checkTileAtPoint(x+16, y, &isTreeOrLeaves)) {
1883 setSprite(global.cemetary ? 'sLeavesDead' : 'sLeaves');
1885 if (level.checkTileAtPoint(x-16, y, &isTreeOrLeaves) &&
1886 level.checkTileAtPoint(x+16, y, &isTreeOrLeaves))
1888 setSprite('sLeavesTop');
1891 if (level.checkTileAtPoint(x-16, y, &isTreeOrLeaves)) setSprite('sLeavesDeadR'); else setSprite('sLeavesDead');
1898 if (spriteName == 'sLeavesTop') {
1899 if (!level.checkTileAtPoint(x-16, y, &isTreeOrLeaves) ||
1900 !level.checkTileAtPoint(x+16, y, &isTreeOrLeaves))
1904 } else if (spriteName == 'sLeaves' || spriteName == 'sLeavesDead') {
1905 if (!level.checkTileAtPoint(x+16, y, &isTreeOrLeaves)) instanceRemove();
1906 } else if (spriteName == 'sLeavesRight' || spriteName == 'sLeavesDeadR') {
1907 if (!level.checkTileAtPoint(x-16, y, &isTreeOrLeaves)) instanceRemove();
1912 if (sprite_index == sLeavesDeadR) {
1914 desc2 = "These leaves have died and withered.";
1917 desc2 = "The canopy of a proud tree.";
1924 objType = 'oLeaves';
1926 desc2 = "The top of a proud tree.";
1933 toSpecialGrid = true;
1938 // ////////////////////////////////////////////////////////////////////////// //
1939 class MapTileTree['oTree'] : MapTile;
1945 override void onDestroy () {
1947 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
1948 scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
1954 override void doSprayRubble () {
1955 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
1956 scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
1960 override void setupTile () {
1964 final bool isTreeTileAtPoint (int x, int y) {
1965 return !!level.checkTileAtPoint(x, y, delegate bool (MapTile t) { return (t.objType == 'oTree'); });
1969 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1970 //if (argument1) sprite_index = sVine;
1972 bool up = isTreeTileAtPoint(ix, iy-16);
1973 //bool down = isTreeTileAtPoint(ix, iy+16);
1974 //bool left = isTreeTileAtPoint(ix-16, iy);
1975 //bool right = isTreeTileAtPoint(ix+16, yi);
1978 if (global.cemetary || getSprite().Name == 'sTreeTopDead') setSprite('sTreeTopDead'); else setSprite('sTreeTop');
1984 override void beautifyTile () {
1985 setupNiceTileSprite();
1989 override void thinkFrame () {
1992 if (!level.isSolidAtPoint(x, y+16)) { instanceRemove(); return; }
1993 if (level.isLavaAtPoint(x-16, y) || level.isLavaAtPoint(x+16, y)) { instanceRemove(); return; }
1999 desc = "Tree Trunk";
2000 desc2 = "The trunk of a proud tree.";
2001 spriteName = 'sTreeTrunk';
2007 toSpecialGrid = true;
2012 // ////////////////////////////////////////////////////////////////////////// //
2013 class MapTileTreeBranch['oTreeBranch'] : MapTile;
2019 final bool isTree (MapTile t) { return (t != self && t.tree); }
2023 override void onDestroy () {
2025 if (spriteName != 'sTreeBranchDeadL' && spriteName != 'sTreeBranchDeadR') {
2026 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
2033 override void doSprayRubble () {
2034 if (spriteName != 'sTreeBranchDeadL' && spriteName != 'sTreeBranchDeadR') {
2035 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
2040 override void setupTile () {
2043 auto ltree = level.checkTileAtPoint(ix-16, iy, &isTree);
2044 auto rtree = level.checkTileAtPoint(ix+16, iy, &isTree);
2046 if (ltree && !rtree) setSprite(global.cemetary ? 'sTreeBranchDeadR' : 'sTreeBranchRight');
2047 else if (!ltree && rtree) setSprite(global.cemetary ? 'sTreeBranchDeadL' : 'sTreeBranchLeft');
2049 //if (global.cemetary) spriteName = 'sTreeBranchDeadR';
2053 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
2054 //if (argument1) sprite_index = sVine;
2056 bool up = !!level.checkTileAtPoint(ix, iy-16, delegate bool (MapTile t) { return (t.objType == 'oLeaves'); });
2057 //if (collision_point(x, y+16, oTreeBranch, 0, 0)) { down = true; }
2058 //if (collision_point(x-16, y, oTreeBranch, 0, 0)) { left = true; }
2059 bool right = !!level.checkTileAtPoint(ix+16, iy, delegate bool (MapTile t) { return (t.objType == 'oTree'); });
2067 if (global.cemetary || getSprite().Name == 'sTreeBranchDeadR') setSprite('sTreeBranchDeadL'); else setSprite('sTreeBranchLeft');
2072 override void beautifyTile () {
2073 //writeln("!!! ", getSprite().Name);
2074 setupNiceTileSprite();
2078 override void thinkFrame () {
2082 auto ltree = level.checkTileAtPoint(ix-16, iy, &isTree);
2083 auto rtree = level.checkTileAtPoint(ix+16, iy, &isTree);
2084 if (!ltree && !rtree) {
2088 if (ltree && !rtree) setSprite('sTreeBranchRight');
2089 else if (!ltree && rtree) setSprite('sTreeBranchLeft');
2098 desc = "Tree Branch";
2099 desc2 = "A slight but firm limb of a proud tree.";
2100 spriteName = 'sTreeBranchRight';
2107 toSpecialGrid = true;
2112 // ////////////////////////////////////////////////////////////////////////// //
2113 // only for dark levels
2114 class MapTileTikiTorch['oTikiTorch'] : MapTile;
2117 override void setupTile () {
2118 writeln("*** TIKI TORCH CREATED");
2122 override void thinkFrame () {
2123 lightRadius = 32+16+global.randOther(-8, 8);
2128 objType = 'oTikiTorch';
2130 desc2 = "A moderately bright torch.";
2131 spriteName = 'sTikiTorch';
2137 toSpecialGrid = true;
2140 lightRadius = 32+16;
2144 // ////////////////////////////////////////////////////////////////////////// //
2145 class MapTileGiantTikiHead['oGiantTikiHead'] : MapTile;
2153 setSprite('sGTHHole');
2158 override void setupTile () {
2162 override void thinkFrame () {
2167 objType = 'oGiantTikiHead';
2168 invincible = true; //???
2170 spriteName = 'sGiantTikiHead';
2171 toSpecialGrid = true; // it is HUGE
2176 // ////////////////////////////////////////////////////////////////////////// //
2177 class MapTileThinIce['oThinIce'] : MapTile;
2181 override void setupTile () {
2185 override void thinkFrame () {
2186 if (level.player.isRectHitSimple(ix, iy-1, 17, 2)) {
2188 if (global.randOther(1, 100) == 1) level.MakeMapObject(ix+global.randOther(0, 16), iy+9, 'oDrip');
2190 if (thickness > 50) setSprite('sThinIce1');
2191 else if (thickness > 40) setSprite('sThinIce2');
2192 else if (thickness > 30) setSprite('sThinIce3');
2193 else if (thickness > 20) setSprite('sThinIce4');
2194 else if (thickness > 10) setSprite('sThinIce5');
2195 else if (thickness > 0) setSprite('sThinIce6');
2196 else instanceRemove();
2201 objType = 'oThinIce';
2203 spriteName = 'sThinIce1';
2204 toSpecialGrid = true; // it must think
2208 // ////////////////////////////////////////////////////////////////////////// //
2209 class MapTileDarkFall['oDarkFall'] : MapTile;
2211 //int thickness = 60;
2214 int timeFallMax = 20;
2217 override int height () { return 8; }
2219 override void setupTile () {
2223 //FIXME: viscidMovement -- ???
2224 override void thinkFrame () {
2225 //isCollisionCharacterTop(1)
2226 if (level.player.isRectHitSimple(ix, iy-1, 17, 2)) {
2228 //if (timeFall <= 0) yAcc = grav;
2229 } else if (timeFall < timeFallMax) {
2232 myGrav = (timeFall <= 0 ? grav : 0.0);
2233 //if (yVel > 10) yVel = 10;
2234 //HACK: so player won't be able to push it
2242 objType = 'oDarkFall';
2246 //setCollisionBounds(0, 0, 16, 8);
2253 desc = "Falling Platform";
2254 desc2 = "A thin, dark blue platform that will colapse if stood on for any length of time.";
2256 spriteName = 'sDarkFall';
2257 toSpecialGrid = true; // it must think
2262 // ////////////////////////////////////////////////////////////////////////// //
2263 class MapTileAlienHull['oAlienShip'] : MapTile;
2266 override void setupTile () {
2267 //writeln("*********** ALIEN SHIP!");
2271 override void doSprayRubble () {
2272 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2273 scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2277 final bool isAlienShipFloorCB (MapTile t) { return (t.objType == 'oAlienShip' || t.objType == 'oAlienShipFloor'); }
2278 final bool isASFTileAt (int x, int y) { return !!level.checkTileAtPoint(x, y, &isAlienShipFloorCB); }
2281 override void beautifyTile () {
2282 //if (argument1) sprite_index = sAlienTop;
2284 bool up = !!isASFTileAt(ix, iy-16);
2285 bool down = !!isASFTileAt(ix, iy+16);
2286 bool left = !!isASFTileAt(ix-16, iy);
2287 bool right = !!isASFTileAt(ix+16, iy);
2289 if (right && !left) {
2290 if (up && !down) setSprite('sAlienFront2');
2291 else if (down && !up) setSprite('sAlienFront3');
2293 if (left && !right) {
2294 if (up) setSprite('sAlienBack2'); else if (down) setSprite('sAlienBack3');
2300 objType = 'oAlienShip';
2302 spriteName = 'sAlienTop';
2303 toSpecialGrid = false;
2307 // ////////////////////////////////////////////////////////////////////////// //
2308 class MapTileAlienFloor['oAlienShipFloor'] : MapTileAlienHull;
2312 objType = 'oAlienShipFloor';
2313 spriteName = 'sAlienFloor';
2317 // ////////////////////////////////////////////////////////////////////////// //
2318 class MapTileXocBlock['oXocBlock'] : MapTile;
2321 override void setupTile () {
2325 override void doSprayRubble () {
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');
2329 gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2330 gold.yVel = global.randOther(2, 4);
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');
2336 gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2337 gold.yVel = global.randOther(2, 4);
2344 objType = 'oXocBlock';
2346 spriteName = 'sGoldBlock';
2347 toSpecialGrid = false;
2352 // ////////////////////////////////////////////////////////////////////////// //
2353 class MapTileCeilingTrap['oCeilingTrap'] : MapTile;
2355 //int thickness = 60;
2357 //int timeFall = 20;
2358 //int timeFallMax = 20;
2374 override void onAnimationLooped () {
2375 if (spriteName == 'sCeilingTrapS') setSprite('sCeilingTrap');
2379 override void doSprayRubble () {
2380 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2381 scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2385 override void setupTile () {
2386 if (global.cityOfGold) spriteName = 'sGoldBlock';
2391 if (status != IDLE) return;
2393 level.checkTilesInRect(ix-64, iy-64, 129, 129, delegate bool (MapTile t) {
2394 auto door = MapTileDoor(t);
2395 if (door) door.activate();
2401 //FIXME: viscidMovement -- ???
2402 override void thinkFrame () {
2403 if (status == IDLE) {
2405 } else if (status == DROP) {
2414 if (level.isSolidAtPoint(ix+8, iy+17)) status = WAIT;
2416 if (spriteName == 'sBlock' || spriteName == 'sGoldBlock') setSprite('sCeilingTrapS');
2417 // out-of-level check
2418 if (flty > level.tilesHeight*16+16) {
2422 } else if (status == WAIT) {
2425 if (isCollisionBottom(0)) flty -= 1;
2430 if (status != IDLE) {
2432 auto plr = level.player;
2433 if (!plr.dead && !plr.invincible && !plr.isExitingSprite() && /*plr.collidesWith(self)*/ plr.isRectHitSimple(x0, y0, width, height+1)) {
2435 if (!plr.collidesWith(self)) {
2436 doCol = (plr.yVel < -0.01 && plr.y0 > y1);
2439 if (global.plife > 0) {
2440 global.plife -= global.config.crushDmg;
2441 if (global.plife <= 0 /*and isRealLevel()*/) level.addDeath(objName);
2443 plr.scrCreateBlood(plr.ix, plr.iy, 1);
2446 playSound('sndHurt');
2451 level.isObjectInRect(x0-1, y0-1, width+2, height+2, delegate bool (MapObject o) {
2452 if (o == self) return false;
2453 if (o isa EnemyTombLord) return false;
2455 if (o !isa MapEnemy && o !isa MonsterDamsel) return false;
2457 // register hit only if we're moving onto a spikes
2458 if (!o.collidesWith(self)) {
2459 bool onTop = (o.yVel < -0.01 && o.y0 > y1);
2460 if (!onTop) return false;
2461 writeln("*** RUNNING ON TRAP SPIKES");
2465 auto enemy = MapEnemy(o);
2467 if (o.dead || o.status == MapObject::DEAD || o.invincible) return false;
2468 if (o.heldBy) o.heldBy.holdItem = none;
2469 enemy.hp -= global.config.crushDmg;
2470 playSound('sndHit');
2475 auto dms = MonsterDamsel(o);
2477 if (!dms.invincible) {
2479 if (dms.heldBy) dms.heldBy.holdItem = none;
2480 dms.hp -= global.config.crushDmg;
2481 dms.status = MapObject::THROWN;
2483 //dms.damselDropped = true;
2484 dms.kissCount = min(1, dms.kissCount);
2485 playSound('sndDamsel');
2497 objType = 'oCeilingTrap';
2502 //setCollisionBounds(0, 0, 16, 8);
2513 desc = "Ceiling Trap";
2514 desc2 = "This seemingly-innocuous block hides a vicious set of spikes.";
2516 spriteName = 'sBlock';
2517 toSpecialGrid = true; // it must think
2522 // ////////////////////////////////////////////////////////////////////////// //
2523 class MapTileTempleFake['oTempleFake'] : MapTile;
2525 int sleepTime = 4; // wait until the grid is filled
2527 override void doSprayRubble () {
2528 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2529 scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2533 override void setupTile () {
2534 doCreateOre(60, 80, 100, 1200);
2538 override void thinkFrame () {
2539 if (sleepTime > 0) { --sleepTime; return; }
2541 auto door = level.checkTilesInRect(ix, iy, 16, 16, delegate bool (MapTile t) {
2542 if (t == self) return false;
2543 //writeln("fake temple: t(", GetClassName(t.Class), "); objName=", t.objName, "; objType=", t.objType, "; door=", t isa MapTileDoor);
2544 return (t isa MapTileDoor);
2548 auto bt = level.MakeMapTile(ix/16, iy/16, 'oTemple');
2549 if (bt) bt.copyOreFrom(self);
2554 if (!level.checkTileAtPoint(ix+8, iy+8, delegate bool (MapTile t) { return (t isa MapTileDoor); })) {
2556 level.MakeMapTile(ix/16, iy/16, 'oTemple');
2565 objType = 'oTemple';
2566 desc = "Temple Brick";
2567 desc2 = "This is the first brick you've seen that actually qualifies as a brick. It looks rather bland.";
2572 spriteName = 'sTemple';
2573 toSpecialGrid = true; // it must think
2581 // ////////////////////////////////////////////////////////////////////////// //
2582 class MapTileDoor['oDoor'] : MapTile;
2584 //int thickness = 60;
2599 //setCollisionBounds(1, 0, 15, 32);
2600 override int x0 () { return round(fltx)+1; }
2601 override int width () { return 15; }
2602 override int height () { return 32; }
2605 override void doSprayRubble () {
2606 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2607 scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2611 override void setupTile () {
2623 //FIXME: viscidMovement -- ???
2624 override void thinkFrame () {
2625 if (status == IDLE) {
2627 } else if (status == DROP) {
2628 yVel = fmin(yVel+myGrav, myGravLimit);
2631 auto oldsolid = solid;
2633 //writeln("DOOR GOING DOWN; yVel=", yVel, "; ix=", ix, "; iy=", iy, "; x0=", x0, "; y0=", y0, "; w=", width, "; h=", height);
2634 int newY = round(flty+yVel); //!!!
2635 // check if we need (and can) move down
2636 int w = width, hp = height;
2638 // made non-solid temporarily
2639 //auto hasAnything = level.checkTilesInRect(x0, y0+hp, w, 1, &level.cbCollisionAnySolid);
2640 auto hasAnything = level.checkTilesInRect(x0, y0+hp, w, 1, delegate bool (MapTile t) {
2641 if (t == self) return false;
2645 //writeln("hit '", GetClassName(hasAnything.Class), "' (", hasAnything.objName, ":", hasAnything.objType, "): pos=(", hasAnything.ix, ",", hasAnything.iy, ")");
2646 // hit something, stop right there
2647 if (yVel > myGrav*3) playSound('sndThud');
2653 //writeln("DOOR STOP");
2662 // out-of-level check
2663 if (flty > level.tilesHeight*16+16) {
2667 } else if (status == WAIT) {
2670 if (isCollisionBottom(0)) flty -= 1;
2681 //setCollisionBounds(0, 0, 16, 8);
2693 desc2 = "The inside of this block carries an extendable wall.";
2695 spriteName = 'sDoor';
2696 toSpecialGrid = true; // it must think
2701 // ////////////////////////////////////////////////////////////////////////// //
2702 class MapTileSmashTrap['oSmashTrap'] : MapTile;
2704 //int thickness = 60;
2732 final string dirName () {
2734 case RIGHT: return "right";
2735 case LEFT: return "left";
2736 case UP: return "up";
2737 case DOWN: return "down";
2743 //setCollisionBounds(1, 1, 15, 15);
2744 override int x0 () { return round(fltx)+1; }
2745 override int width () { return 15; }
2746 //override int height () { return 16; }
2749 override void doSprayRubble () {
2750 if (!global.cityOfGold) {
2751 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2752 scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2755 auto gold = level.MakeMapObject(ix+8+global.randOther(0, 4)-global.randOther(0, 4), iy+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldChunk');
2756 gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2757 gold.yVel = global.randOther(2, 4);
2760 auto gold = level.MakeMapObject(ix+8+global.randOther(0, 4)-global.randOther(0, 4), iy+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldNugget');
2761 gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2762 gold.yVel = global.randOther(2, 4);
2768 override void setupTile () {
2769 // prevent smash trap from spawning in player range when level starts
2770 if (true /+global.config.bizarre /*or isRealLevel()*/ +/) {
2771 if (level.calcNearestEnterDist(ix, iy) < 90) {
2778 if (global.cityOfGold) setSprite('sSmashTrapGold');
2779 dir = global.randRoom(0, 3);
2783 //FIXME: viscidMovement -- ???
2784 override void thinkFrame () {
2787 if (status == IDLE) {
2788 auto plr = level.player;
2789 auto dist = pointDistance(ix, iy, plr.ix, plr.iy);
2790 if (counter > 0) --counter;
2791 if (dist < 90 && counter < 1) {
2792 if (abs(plr.iy-(iy+8)) < 8 && plr.ix > ix+8 && !isCollisionRight(2)) {
2796 } else if (abs(plr.ix-(ix+8)) < 8 && plr.iy > iy+8 && !isCollisionBottom(2)) {
2800 } else if (abs(plr.iy-(iy+8)) < 8 && plr.ix < ix+8 && !isCollisionLeft(2)) {
2804 } else if (abs(plr.ix-(ix+8)) < 8 && plr.iy < iy+8 && !isCollisionTop(2)) {
2810 } else if (status == ATTACK) {
2811 bool colLeft = !!isCollisionLeft(1);
2812 bool colRight = !!isCollisionRight(1);
2813 bool colTop = !!isCollisionTop(1);
2814 bool colBot = !!isCollisionBottom(1);
2816 xv = fclamp(xv+xa, -4, 4);
2817 yv = fclamp(yv+ya, -4, 4);
2820 if (isCollisionRight(2) && colRight) { shiftX(-2); hit = true; }
2821 if (colRight) { shiftX(-1); hit = true; }
2822 } else if (dir == DOWN) {
2823 if (isCollisionBottom(2) && colBot) { shiftY(-2); hit = true; }
2824 if (colBot) { shiftY(-1); hit = true; }
2825 } else if (dir == LEFT) {
2826 if (isCollisionLeft(2) && colLeft) { shiftX(2); hit = true; }
2827 if (colLeft) { shiftX(1); hit = true; }
2828 } else if (dir == UP) {
2829 if (isCollisionTop(2) && colTop) { shiftY(2); hit = true; }
2830 if (colTop) { shiftY(1); hit = true; }
2833 if (level.isObjectInRect(ix-1, iy-1, 18, 18, delegate bool (MapObject o) { return (o isa EnemyTombLord); })) hit = true;
2844 auto ct = isCollision();
2845 writeln("dir=", dirName(), "; colLeft=", colLeft, "; colRight=", colRight, "; colTop=", colTop, "; colBot=", colBot, "; col=", (ct ? GetClassName(ct.Class) : '<none>'), " (", (ct ? ct.objName : '<>'), ")");
2849 if (hit && !isCollision() /*!colRight && !colLeft && !colTop && !colBot*/) {
2854 } else if (status == DEAD) {
2860 if (level.isLavaAtPoint(ix, iy-1)) { instanceRemove(); return; }
2863 if (level.checkTilesInRect(ix+1, iy+1, 15, 15, &level.cbCollisionLava)) status = DEAD;
2868 auto plr = level.player;
2869 if (!plr.dead && !plr.invincible && !plr.isExitingSprite() && /*plr.collidesWith(self)*/ plr.isRectHitSimple(x0-1, y0-1, width+2, height+2)) {
2871 if (!plr.collidesWith(self)) {
2872 bool onLeft = (plr.xVel < -0.01 && plr.x0 > x1);
2873 bool onRight = (plr.xVel > 0.01 && plr.x1 < x0);
2874 bool onTop = (plr.yVel < -0.01 && plr.y0 > y1);
2875 bool onBottom = (plr.yVel > 0.01 && plr.y1 < y0);
2876 if (!onLeft && !onRight && !onTop && !onBottom) {
2879 writeln("*** PLAYER RUNNING ON TRAP SPIKES");
2883 if (global.plife > 0) {
2884 global.plife -= global.config.crushDmg;
2885 if (global.plife <= 0 /*and isRealLevel()*/) level.addDeath(objName);
2886 plr.scrCreateBlood(plr.ix, plr.iy, 1);
2889 playSound('sndHurt');
2894 level.isObjectInRect(x0-1, y0-1, width+2, height+2, delegate bool (MapObject o) {
2895 if (o == self) return false;
2896 if (o isa EnemyTombLord) return false;
2898 if (o !isa MapEnemy && o !isa MonsterDamsel) return false;
2900 // register hit only if we're moving onto a spikes
2901 if (!o.collidesWith(self)) {
2902 bool onLeft = (o.xVel < -0.01 && o.x0 > x1);
2903 bool onRight = (o.xVel > 0.01 && o.x1 < x0);
2904 bool onTop = (o.yVel < -0.01 && o.y0 > y1);
2905 bool onBottom = (o.yVel > 0.01 && o.y1 < y0);
2906 if (!onLeft && !onRight && !onTop && !onBottom) return false;
2907 writeln("*** RUNNING ON TRAP SPIKES");
2911 auto enemy = MapEnemy(o);
2913 if (o.dead || o.status == DEAD || o.invincible) return false;
2914 if (o.heldBy) o.heldBy.holdItem = none;
2915 enemy.hp -= global.config.crushDmg;
2916 playSound('sndHit');
2921 auto dms = MonsterDamsel(o);
2923 if (!dms.invincible) {
2925 if (dms.heldBy) dms.heldBy.holdItem = none;
2926 dms.hp -= global.config.crushDmg;
2927 dms.status = MapObject::THROWN;
2929 //dms.damselDropped = true;
2930 dms.kissCount = min(1, dms.kissCount);
2931 playSound('sndDamsel');
2942 objType = 'oSmashTrap';
2943 objName = 'Smash Trap';
2944 desc = "Smash Trap";
2945 desc2 = "This trap carries an array of extendable blades. It can chase down intruders horizontally and vertically.";
2968 spriteName = 'sSmashTrap';
2969 toSpecialGrid = true; // it must think
2974 // ////////////////////////////////////////////////////////////////////////// //
2975 class MapTileSmashTrapLit['oSmashTrapLit'] : MapTileSmashTrap;
2978 spriteName = 'sSmashTrapLit';
2983 // ////////////////////////////////////////////////////////////////////////// //
2984 class MapTileGoldDoor['oGoldDoor'] : MapTile;
2994 override void doSprayRubble () {
2998 override void setupTile () {
2999 writeln("GENERATED GOLD DOOR");
3003 // it opens if player carrying a sceptre, and has a crown
3004 override void thinkFrame () {
3006 if (status == SCEPTRE && !global.hasCrown) return;
3007 auto plr = level.player;
3008 if (plr.holdItem !isa ItemWeaponSceptre) return;
3009 // check for collision
3010 if (plr.collidesWith(self)) {
3011 if (global.hasCrown) {
3012 // take sceptre away
3013 auto it = plr.holdItem;
3014 plr.holdItem = none;
3015 it.instanceRemove();
3016 playSound('sndChestOpen');
3017 level.MakeMapTile(ix/16, iy/16, 'oXGold');
3018 auto obj = level.MakeMapObject(ix-4, iy+6, 'oPoof');
3019 if (obj) obj.xVel = -0.4;
3020 obj = level.MakeMapObject(ix+16+4, iy+6, 'oPoof');
3021 if (obj) obj.xVel = 0.4;
3027 level.osdMessage("THE SCEPTRE FITS...\nBUT NOTHING IS HAPPENING!", 3.33);
3033 objType = 'oGoldDoor';
3035 desc2 = "A door with a golden seal on it.";
3042 spriteName = 'sGoldDoor';
3043 toSpecialGrid = true; // it must think
3049 // ////////////////////////////////////////////////////////////////////////// //
3050 class MapTileUnlockedGoldDoor['oXGold'] : MapTile;
3053 override void doSprayRubble () {
3057 override void setupTile () {
3061 // it opens if player carrying a sceptre, and has a crown
3062 override void thinkFrame () {
3069 desc2 = "A door with an opened golden seal on it.";
3074 spriteName = 'sExit';
3075 toSpecialGrid = false; // it must think
3082 // ////////////////////////////////////////////////////////////////////////// //
3083 class MapTileBlackMarketDoor['oXMarket'] : MapTile;
3086 override void doSprayRubble () {
3090 override void setupTile () {
3091 writeln("*** GENERATED BLACK MARKET ENTRANCE ***");
3093 if (global.hasSpectacles || global.hasUdjatEye) {
3094 level.setTileAt(ix/16, iy/16, none);
3101 // it opens if player carrying a sceptre, and has a crown
3102 override void thinkFrame () {
3107 objType = 'oXMarket';
3108 desc = "Market Exit";
3109 desc2 = "A door leading to Black Market.";
3114 spriteName = 'sExit';
3115 toSpecialGrid = true; // it must be hidden behind the normal tile
3122 // ////////////////////////////////////////////////////////////////////////// //
3123 class MapTileMoai['oMoai'] : MapTile;
3125 override int width () { return 16; }
3126 override int height () { return 64; }
3128 override void doSprayRubble () {}
3129 override void setupTile () {}
3130 override void thinkFrame () {}
3140 spriteName = 'sMoai';
3141 toSpecialGrid = true; // it is big
3146 // ////////////////////////////////////////////////////////////////////////// //
3147 class MapTileMoai3['oMoai3'] : MapTileMoai;
3150 spriteName = 'sMoai3';
3154 // ////////////////////////////////////////////////////////////////////////// //
3155 class MapTileMoaiInside['oMoaiInside'] : MapTile;
3157 override int width () { return 16; }
3158 override int height () { return 48; }
3160 override void doSprayRubble () {}
3161 override void setupTile () {}
3162 override void thinkFrame () {}
3165 objType = 'oMoaiInside';
3172 spriteName = 'sMoaiInside';
3173 toSpecialGrid = true; // it is big
3178 // ////////////////////////////////////////////////////////////////////////// //
3179 class MapTileLamp['oLamp'] : MapTile;
3184 override bool onExplosionTouch (MapObject xplo) {
3187 //level.MakeMapObject(x+8, y+12, (redLamp ? 'oLampRedItem' : 'oLampItem'));
3192 override void doSprayRubble () {
3196 override void setupTile () {
3197 if (redLamp) spriteName = 'sLampRed';
3201 override void thinkFrame () {
3205 float llev = (trunc(imageFrame) < 2 ? 0.0 : 2.0-imageFrame/2.0);
3206 lightRadius = 128+round(4*llev);
3210 // drop lamp item if it has no support
3211 if (!level.checkTilesInRect(x+5, y-1, 6, 1)) {
3213 level.MakeMapObject(x+8, y+12, (redLamp ? 'oLampRedItem' : 'oLampItem'));
3222 desc2 = "A bright, geothermal-powered lantern, with a stainless steel frame.";
3227 spriteName = 'sLamp';
3228 toSpecialGrid = true; // it must think
3233 // ////////////////////////////////////////////////////////////////////////// //
3234 class MapTileLampRed['oLampRed'] : MapTileLamp;
3237 desc2 = "A geothermal-powered lantern that emits red light. Often found in brothels.";