1 /**********************************************************************************
2 * Copyright (c) 2008, 2009 Derek Yu and Mossmouth, LLC
3 * Copyright (c) 2010, Moloch
4 * Copyright (c) 2018, Ketmar Dark
6 * This file is part of Spelunky.
8 * You can redistribute and/or modify Spelunky, including its source code, under
9 * the terms of the Spelunky User License.
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.
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/>
18 **********************************************************************************/
19 class MapTile : MapEntity;
24 transient SpriteImage sprite;
45 bool sacrificingAltar;
46 bool shopWall; // may be set to `true` for non-solid tiles too
50 bool platform; // oLadderTop, oLeaves, oTreeBranch
52 bool border; // border tile
54 bool litWholeTile; // lit whole tile with maximum light
55 bool ignoreFrameOffsetX, ignoreFrameOffsetY;
56 bool dontReplaceOthers; // this tile won't replace existing tiles when put on a map
57 bool immuneToReplacement; // this tile immune to replacement by other tiles
60 transient bool waterMoved; // any move
61 transient bool waterMovedDown;
62 transient int waterSlideCounter;
63 transient int waterSlideOldX, waterSlideOldY;
67 //float grav = 0.6; // the gravity
68 float myGravLimit = 8;
70 // x and y are offsets
75 bool gemDestroyWithTile;
79 void snapToExit (MapEntity e) {
80 if (!e || !e.isInstanceAlive) return;
87 // ////////////////////////////////////////////////////////////////////////// //
88 override void Destroy () {
107 override void onLoaded () {
109 if (spriteName) sprite = level.sprStore[spriteName];
110 for (MapBackTile bt = bgback; bt; bt = bt.next) bt.onLoaded();
111 for (MapBackTile bt = bgfront; bt; bt = bt.next) bt.onLoaded();
112 if (gem) gem.onLoaded();
116 string getExitMessage () {
121 override SpriteImage getSprite (optional out bool doMirror) {
127 override SpriteFrame getSpriteFrame (optional out bool doMirror, optional out int x0, optional out int y0, optional out int x1, optional out int y1) {
128 auto spr = getSprite(doMirror!optional);
129 if (!spr || spr.frames.length == 0) return none;
130 auto spf = spr.frames[trunc(imageFrame)%spr.frames.length];
131 if (!spf) return none;
132 if (specified_x0 || specified_x1) {
133 x0 = (ignoreFrameOffsetX ? 0 : -spf.xofs);
134 x1 = x0+spf.tex.width;
136 if (specified_y0 || specified_y1) {
137 y0 = (ignoreFrameOffsetY ? 0 : -spf.yofs);
138 y1 = y0+spf.tex.height;
144 override void clearSprite () {
150 override void setSprite (name sprNameL, optional name sprNameR) {
151 if (!sprNameL && sprNameR) sprNameL = sprNameR;
152 if (spriteName != sprNameL) {
153 spriteName = sprNameL;
154 sprite = (sprNameL ? level.sprStore[sprNameL] : none);
160 // ////////////////////////////////////////////////////////////////////////// //
161 override int width () { return Width; }
162 override int height () { return Height; }
165 void beautifyTile () {}
166 void scrSetupBlockTop () {}
167 void scrSetupBlockBottom () {}
168 void scrSetupBlockLeft () {}
169 void scrSetupBlockRight () {}
172 // ////////////////////////////////////////////////////////////////////////// //
173 void setGem (name oname, optional bool visibility) {
175 gem.ownerTile = none;
176 gem.instanceRemove();
180 gem = level.MakeMapObject(ix+8, iy+8, oname);
182 gem.ownerTile = self;
183 gem.hiddenTreasure = !global.hasSpectacles && !global.hasUdjatEye;
184 if (specified_visibility) gem.hiddenTreasure = !visibility;
187 if (gem.hiddenTreasure) gem.visible = false;
188 gem.saveInterpData();
192 void setGemObject (MapObject obj, optional bool visibility) {
194 if (obj.ownerTile == self) {
195 if (gem != obj) FatalError("MapTile::setGemObject: WTF?! (0)");
197 if (gem == obj) FatalError("MapTile::setGemObject: WTF?! (1)");
200 gem.ownerTile = none;
201 gem.instanceRemove();
202 if (!gem.grid) delete gem;
207 obj.ownerTile.gem = none;
208 obj.ownerTile = none;
211 gem.ownerTile = self;
212 gem.hiddenTreasure = !global.hasSpectacles && !global.hasUdjatEye;
213 if (specified_visibility) gem.hiddenTreasure = !visibility;
216 if (gem.hiddenTreasure) gem.visible = false;
219 gem.saveInterpData();
223 void convertGemObjectToDiamond (MapObject ghost) {
225 if (oldgem && oldgem isa ItemBigGem) {
227 if (!ghost.isInstanceAlive) return;
228 if (!gem.isRectHitSimple(ghost.x0, ghost.y0, ghost.width, ghost.height)) return;
230 auto diamond = level.MakeMapObject(oldgem.ix, oldgem.iy, 'oDiamond');
231 setGemObject(diamond);
236 // ////////////////////////////////////////////////////////////////////////// //
237 void doCreateOre (int probSap, int probEmer, int probRuby, int probItem) {
238 int n = global.randRoom(1, 100);
239 if (n < 20) ore = 1; // sprite_index = sBrickGold;
240 else if (n < 30) ore = 2; // sprite_index = sBrickGoldBig;
241 if (probSap > 0 && global.randRoom(1, probSap) == 1) setGem('oSapphireBig');
242 else if (probEmer > 0 && global.randRoom(1, probEmer) == 1) setGem('oEmeraldBig');
243 else if (probRuby > 0 && global.randRoom(1, probRuby) == 1) setGem('oRubyBig');
244 else if (probItem > 0 && global.randRoom(1, probItem) == 1) setGemObject(level.scrGenerateItem(ix+8, iy+8, GameLevel::GenItemSet.Underground));
248 void copyOreFrom (MapTile t) {
249 if (t == self) return;
266 if (exit) setSprite('sEntrance');
271 if (exit) setSprite('sExit');
275 bool isExitActive () {
276 return (visible && exit && spriteName == 'sExit');
280 // ////////////////////////////////////////////////////////////////////////// //
284 case 'oLadderOrange':
285 //objName = 'oLadder';
286 //goto case 'oLadder';
291 spriteName = 'sLadder';
295 //ladder = true; // no, laddertop is not a ladder
299 spriteName = 'sLadderTop';
302 objType = 'oVineTop';
303 //ladder = true; // no, laddertop is not a ladder
307 spriteName = 'sVineTop';
310 //objName = 'oPushBlock';
313 spriteName = (global.cityOfGold == 1 ? 'sGoldBlock' : 'sBlock');
315 case 'oPushIceBlock':
316 //objName = 'oPushBlock';
320 spriteName = 'sIceBlock';
322 case 'oSolidIceBlock':
323 //objName = 'oIceBlock';
324 //goto case 'oIceBlock';
329 moveable = true; //k8: why, let it be an easter egg
330 spriteName = 'sIceBlock';
337 spriteName = 'sIceBlock';
340 // no sense to create ore here
342 spriteName = (global.randRoom(1, 10) == 1 ? 'sBrick2' : 'sBrick');
346 spriteName = 'sCaveSmooth';
353 spriteName = 'sSpikesWood';
359 spriteName = 'sSpikes';
362 objType = 'oEntrance';
367 spriteName = 'sEntrance';
375 spriteName = 'sExit';
380 spriteName = 'sAltarLeft';
385 spriteName = 'sAltarRight';
387 case 'oSacAltarLeft':
390 sacrificingAltar = true;
391 spriteName = 'sSacAltarLeft';
393 case 'oSacAltarRight':
396 sacrificingAltar = true;
397 spriteName = 'sSacAltarRight';
402 spriteName = 'sSign';
407 spriteName = 'sSignGeneral';
412 spriteName = 'sSignBomb';
417 spriteName = 'sSignWeapon';
419 case 'oSignClothing':
422 spriteName = 'sSignClothing';
427 spriteName = 'sSignRare';
432 spriteName = 'sSignCraps';
437 spriteName = 'sSignKissing';
444 spriteName = 'sMoai';
451 spriteName = 'sMoai2';
458 spriteName = 'sMoai3';
463 objType = 'oMoaiInside';
466 spriteName = 'sMoaiInside';
471 FatalError(va("unknown map tile type '%n'", objName));
473 //!active = moveable || toSpecialGrid || lava || water; // will be done in MakeMapTile
474 //if (!solid) hangeable = false; // just in case
478 void setupTileSprite () {
479 if (!spriteName) FatalError("forgot to set sprite for tile type '", objName, "'");
480 sprite = level.sprStore[spriteName];
484 override bool initialize () {
485 if (!::initialize()) return false;
488 if (moveable && depth == 1001) depth = 1000;
489 if ((exit || enter) && depth == 1001) depth = 2000;
491 ++level.liquidTileCount;
492 level.checkWater = true;
494 // will be done in MakeMapTile
496 // animated tiles must be active
498 auto spr = getSprite();
499 if (spr && spr.frames.length > 1) {
500 writeln("activated animated tile '", objName, "'");
509 // ////////////////////////////////////////////////////////////////////////// //
510 // for now, it works only for spikes
511 final void makeBloody () {
515 setSprite(woodenSpikes ? 'sSpikesWoodBlood' : 'sSpikesBlood');
519 // ////////////////////////////////////////////////////////////////////////// //
520 final void appendBackBack (MapBackTile tile, optional int yofs) {
522 if (tile.next) FatalError("MapTile::appendBackBack: wtf?!");
523 tile.flty = (specified_yofs ? float(yofs) : 16.0);
524 MapBackTile last = bgback;
526 while (last.next) last = last.next;
534 final void appendBackFront (MapBackTile tile, optional int yofs) {
536 if (tile.next) FatalError("MapTile::appendBackFront: wtf?!");
537 tile.flty = (specified_yofs ? float(yofs) : -16.0);
538 MapBackTile last = bgfront;
540 while (last.next) last = last.next;
548 // ////////////////////////////////////////////////////////////////////////// //
550 override void onDestroy () {
555 // ////////////////////////////////////////////////////////////////////////// //
556 override void thinkFrame () {
557 if (!moveable) return;
558 // applies the acceleration
562 // approximates the "active" variables
563 if (fabs(xVel) < 0.001) xVel = 0;
564 if (fabs(yVel) < 0.001) yVel = 0;
565 //if (fabs(xAcc) < 0.0001) xAcc = 0;
566 //if (fabs(yAcc) < 0.0001) yAcc = 0;
568 yVel = fclamp(yVel+myGrav, -myGravLimit, myGravLimit);
569 int newY = round(flty+yVel); //!!!
570 // check if we need (and can) move down
571 int w = width, hp1 = height+1;
572 auto oldsolid = solid;
575 // made non-solid temporarily
576 auto hasAnything = level.checkTilesInRect(x0, y0, w, hp1, delegate bool (MapTile t) {
577 if (t == self) return false;
581 // hit something, stop right there
582 if (yVel > myGrav*3) playSound('sndThud');
592 if (flty > level.tilesHeight*16+16 && yVel >= 0) {
599 // ////////////////////////////////////////////////////////////////////////// //
600 final void getInterpCoordsForTile (float currFrameDelta, int scale, out int drwx, out int drwy) {
601 if (waterSlideCounter) {
605 int sgnx = sign(waterSlideOldX-ix);
606 int sgny = sign(waterSlideOldY-iy);
607 if ((sgnx|sgny) == 0) {
608 waterSlideCounter = 0;
610 drwx += (sgnx*(waterSlideCounter*4))*scale;
611 drwy += (sgny*(waterSlideCounter*4))*scale;
614 getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
619 void drawWithOfsBack (int xpos, int ypos, int scale, float currFrameDelta) {
620 //if (backTile) backTile.drawAt(xpos, ypos, scale);
621 if (invisible || !visible || !bgback) return;
625 int fx0, fy0, fx1, fy1;
626 auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
628 // non-moveable and non-special tiles need not to be interpolated
630 getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
633 for (MapBackTile bt = bgback; bt; bt = bt.next) {
636 bt.drawWithOfs(xpos, ypos, scale, currFrameDelta);
643 void drawWithOfsFront (int xpos, int ypos, int scale, float currFrameDelta) {
644 //if (backTile) backTile.drawAt(xpos, ypos, scale);
645 if (invisible || !visible || !bgfront) return;
649 int fx0, fy0, fx1, fy1;
650 auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
652 // non-moveable tiles need not to be interpolated
654 getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
657 for (MapBackTile bt = bgfront; bt; bt = bt.next) {
660 bt.drawWithOfs(xpos, ypos, scale, currFrameDelta);
667 override void drawWithOfs (int xpos, int ypos, int scale, float currFrameDelta) {
668 if (invisible || !visible) return;
671 int fx0, fy0, fx1, fy1;
672 auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
674 // non-moveable tiles need not to be interpolated
676 getInterpCoordsForTile(currFrameDelta, scale, out drwx, out drwy);
678 //auto oclr = Video.color;
679 //if (moveable) Video.color = 0xff_7f_00;
682 fx0 = drwx+fx0*scale-xpos;
683 fy0 = drwy+fy0*scale-ypos;
684 fx1 = drwx+fx1*scale-xpos;
685 fy1 = drwy+fy1*scale-ypos;
687 spf.tex.blitExt(fx0, fy0, fx1, fy1, 0, 0, spf.tex.width, spf.tex.height);
689 spf.tex.blitExt(fx0, fy0, fx1, fy1, spf.tex.width, 0, 0, spf.tex.height);
691 } else if (ore > 0) {
698 //Video.color = oclr;
700 if (ore > 0 && !hideOre) {
701 auto ospr = level.sprStore[ore == 1 ? 'sGold' : 'sGoldBig'];
702 ospr.frames[0].tex.blitAt(fx0, fy0, scale);
707 // ////////////////////////////////////////////////////////////////////////// //
708 // called by Destroy event of blocks that can contain ore (oBrick, oLush, etc)
714 foreach (int i; 0..3) {
715 auto gold = level.MakeMapObject(x+8+global.randOther(0, 4)-global.randOther(0, 4), y+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldChunk');
717 //gold = instance_create(x+8+rand(0,4)-rand(0,4), y+8+rand(0,4)-rand(0,4), oGoldChunk);
718 gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
719 gold.yVel = global.randOther(2, 4);
725 auto gold = level.MakeMapObject(x+8+global.randOther(0, 4)-global.randOther(0, 4), y+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldNugget');
727 gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
728 gold.yVel = global.randOther(2, 4);
736 // ////////////////////////////////////////////////////////////////////////// //
737 private final bool cbIsSolidBrick (MapTile t) { return t.solid; }
738 private final bool cbIsBrickOrLushTile (MapTile t) { return (t.objType == 'oBrick' || t.objType == 'oLush'); }
741 // ////////////////////////////////////////////////////////////////////////// //
742 // called by solids during destroy event (oBrick etc) when destroy with force such as a boulder or explosion
743 // these rubble bits shower over the foreground and are not stopped by solids
744 final void scrSprayRubble (int x, int y, int count, name spr0, name spr1) {
745 while (count-- > 0) {
746 auto rubble = level.MakeMapObject(
747 x+8+global.randOther(0, 8)-global.randOther(0, 8),
748 y+8+global.randOther(0, 8)-global.randOther(0, 8), 'oRubblePiece'); //'oRubblePieceNonSolid');
749 rubble.setSprite(global.randOther(1, 3) == 1 ? spr0 : spr1);
750 rubble.xVel = global.randOther(0, 4)-global.randOther(0, 4);
751 rubble.yVel = -global.randOther(4, 8);
753 //rubble.image_blend = image_blend;
758 final void scrDropRubble (int x, int y, int count, name spr0, name spr1) {
759 while (count-- > 0) {
760 int rx = x+8+global.randOther(0, 8)-global.randOther(0, 8);
761 int ry = y+8+global.randOther(0, 8)-global.randOther(0, 8);
762 if (global.randOther(1, 3) == 1) {
763 auto rubble = level.MakeMapObject(rx, ry, 'oRubble');
764 rubble.setSprite(spr1);
765 //rubble.image_blend = image_blend;
767 auto rubble = level.MakeMapObject(rx, ry, 'oRubbleSmall');
768 rubble.setSprite(spr0);
769 //rubble.image_blend = image_blend;
775 void smashedCheckUpTile () {
776 level.checkTilesInRect(ix, iy-16, 16, 16, delegate bool (MapTile t) {
777 //writeln("mtl: '", GetClassName(t.Class), "'; spikes=", t.spikes);
778 if (t.spikes || t isa MapTileGraveBase || t isa MapTileTikiTorch) t.instanceRemove();
784 void doSprayRubble () {
785 if (objType == 'oBlock' || objType == 'oBrick' || objType == 'oLush' || objType == 'oTemple') {
786 if (global.cityOfGold != 1) {
787 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubbleTan', 'sRubbleTanSmall');
788 scrDropRubble(ix, iy, 3, 'sRubbleTan', 'sRubbleTanSmall');
797 final bool isVineTile (MapTile t) { return (t.objType == 'oVineTop' || t.objType == 'oVine'); }
800 // this should be called from `smashMe()`
801 void checkSmashedVineSupport () {
802 if (!solid || isVineTile(self)) return;
803 // check for support tile ('cause this can be a moving tile, and not an actual support)
804 auto support = level.checkTileAtPoint(ix, iy, delegate bool (MapTile t) {
805 if (t == self) return false;
806 return (t.isInstanceAlive && t.solid);
812 auto vine = level.checkTileAtPoint(x, y, &isVineTile);
814 // yay, we found a vine; now remove it, going all the way down
816 vine = level.checkTileAtPoint(x, y, &isVineTile);
824 void resetupArrowTraps () {
825 foreach (MapTileArrowTrap o; level.objGrid.allObjects(MapTileArrowTrap)) o.resetupChecker();
830 if (invincible) return false;
834 checkSmashedVineSupport();
836 if (/*!global.cemetary &&*/ isVineTile(self)) {
837 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
838 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
841 if (altar) level.scrTriggerIdolAltar(stolenIdol:true);
844 smashedCheckUpTile();
847 if (gemDestroyWithTile) {
848 gem.instanceRemove();
850 if (!gem.grid) FatalError("tile gem has no grid");
851 gem.hiddenTreasure = false;
854 gem.spectral = false;
856 gem = none; // don't destroy it with tile
862 if (!cleanDeath) doSprayRubble();
865 if (other.object_index == oBoulder) {
866 if (other.integrity > 0) dosomething = false; //exit;
870 //with (oTreasure) state = 1;
871 //with (oSpikes) if (not collision_point(x, y+16, oSolid, 0, 0)) instance_destroy();
874 level.scrShopkeeperAnger(GameLevel::SCAnger.TileDestroyed);
879 MapTile obj = level.checkTileAtPoint(x, y-16, &cbIsSolidBrick);
880 if (obj) obj.scrSetupBlockBottom();
881 obj = level.checkTileAtPoint(x, y+16, &cbIsSolidBrick);
882 if (obj) obj.scrSetupBlockTop();
883 obj = level.checkTileAtPoint(x-16, y, &cbIsSolidBrick);
884 if (obj) obj.scrSetupBlockLeft();
885 obj = level.checkTileAtPoint(x+16, y, &cbIsSolidBrick);
886 if (obj) obj.scrSetupBlockRight();
889 /* was commented in the original
890 instance_activate_object(oLadder);
891 obj = collision_point(x+8, y+24, oLadder, 0, 0);
893 if (sprite_index == sVineTop or sprite_index == sVine or
894 sprite_index == sVineSource or sprite_index == sVineBottom)
905 //private transient MapObject mXplo;
908 private final bool cbExplodeTiles (MapTile t) {
909 if (!t.isInstanceAlive) return false;
910 if (t.invincible) return false;
917 t.onExplosionTouch(mXplo);
925 override bool onExplosionTouch (MapObject xplo) {
927 //writeln("ignore inv block at (", ix/16, ",", iy/16, ")");
934 //writeln("smashed block at (", ix/16, ",", iy/16, ")");
938 level.checkTileAtPoint(x+8, y-1, &cbExplodeTiles); //k8: why i wrote this?!
943 obj = instance_place(x, y, oGold);
944 if (obj != noone) with (obj) instance_destroy();
946 obj = instance_place(x, y, oGoldBig);
947 if (obj != noone) with (obj) instance_destroy();
952 //writeln("cannot smash block at (", ix/16, ",", iy/16, ")");
959 // ////////////////////////////////////////////////////////////////////////// //
960 void onGotSpectacles () {
962 gem.hiddenTreasure = false;
971 //depth = 9666; //???
976 // ////////////////////////////////////////////////////////////////////////// //
977 // this tile is returned instead of walkeable MapObject
978 class MapTileTemp : MapTile;
982 // don't forget to set `fltx` and `flty`!
983 override int x0 () { return (e ? e.x0 : 0); }
984 override int y0 () { return (e ? e.y0 : 0); }
985 override int width () { return (e ? e.width : 0); }
986 override int height () { return (e ? e.height : 0); }
989 objName = 'oMapObject';
990 objType = 'oMapObject';
991 active = false; // just in case
995 // ////////////////////////////////////////////////////////////////////////// //
996 class MapTileBrick['oBrick'] : MapTile;
999 override void setupTile () {
1000 spriteName = (global.randRoom(1, 10) == 1 ? 'sBrick2' : 'sBrick');
1001 int tileX = ix/16, tileY = iy/16;
1002 if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1005 //writeln("BORDER");
1007 doCreateOre(100, 120, 140, 1200);
1012 override void beautifyTile () {
1013 if (level.checkTileAtPoint(ix+8, iy-8, delegate bool (MapTile t) { return (t isa TitleTileSpecialNonSolidInvincible || t isa TitleTileBlack); })) return;
1016 //if (argument1) if (global.randRoom(1, 10) == 1) sprite_index = sBrick2; // reset sprite for custom border setup
1018 int tileX = ix/16, tileY = iy/16;
1020 auto tt = level.getTileAtGrid(tileX, tileY-1);
1021 if (tt && tt.objName == 'oBlock') tt = none;
1022 //if (y == 0 || isNamedTileAt(x, y-1, 'oBrick') || isNamedTileAt(x, y-1, 'oHardBlock') || isNamedTileAt(x, y-1, 'oEdgeBrick')) up = true;
1023 bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1025 tt = level.getTileAtGrid(tileX, tileY+1);
1026 //if (y == NormalTilesHeight-1 || isNamedTileAt(x, y+1, 'oBrick') || isNamedTileAt(x, y+1, 'oHardBlock') || isNamedTileAt(x, y+1, 'oEdgeBrick')) down = true;
1027 bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1029 //if (collision_point(x-16, y, oBrick, 0, 0) or collision_point(x-16, y, oHardBlock, 0, 0)) { left = true; } // in the original
1030 //if (collision_point(x+16, y, oBrick, 0, 0) or collision_point(x+16, y, oHardBlock, 0, 0)) { right = true; } // in the original
1033 setSprite('sCaveUp');
1034 // add rocks and sand
1036 if (global.randRoom(1, 3) < 3) {
1037 tile_add('bgCaveTop', 0, 0, 16, 16, x*16, y*16-16, 3);
1039 tile_add('bgCaveTop', 16, 0, 16, 16, x*16, y*16-16, 3);
1042 bool n = (global.randRoom(1, 3) < 3);
1043 appendBackFront(level.CreateBgTile('bgCaveTop', (n ? 0 : 16)));
1044 //instance_create(x, y-16, oCaveTop);
1048 setSprite(!up ? 'sCaveUp2' : 'sBrickDown');
1049 //instance_create(x, y+16, oCaveBottom);
1053 if (not left) instance_create(x-16, y, oCaveLeft);
1054 if (not right) instance_create(x+15, y, oCaveRight);
1059 override void scrSetupBlockTop () {
1060 //int x = ix, y = iy;
1061 int tileX = ix/16, tileY = iy/16;
1062 auto tt = level.getTileAtGrid(tileX, tileY+1);
1063 //if (y >= (GameLevel::NormalTilesHeight-16)*16 || level.checkTileAtPoint(x, y+16, &cbIsBrickOrLushTile)) down = true;
1064 bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1066 auto bt = level.CreateBgTile('bgCaveTop', (global.randRoom(1, 3) < 3 ? 0 : 16));
1067 //bt.flty = -16; // offset
1068 appendBackFront(bt);
1070 setSprite(down ? 'sCaveUp' : 'sCaveUp2');
1074 override void scrSetupBlockBottom () {
1075 //int x = ix, y = iy;
1077 //if (y == 0 || level.checkTileAtPoint(x, y-16, &cbIsBrickOrLushTile)) up = true;
1078 int tileX = ix/16, tileY = iy/16;
1079 auto tt = level.getTileAtGrid(tileX, tileY-1);
1080 bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1081 setSprite(up ? 'sBrickDown' : 'sCaveUp2');
1087 spriteName = 'sBrick';
1090 toSpecialGrid = false;
1094 // ////////////////////////////////////////////////////////////////////////// //
1095 class MapTileHardBrick['oHardBlock'] : MapTileBrick;
1097 override void setupTile () {
1102 objType = 'oHardBlock';
1103 spriteName = 'sBrick';
1109 // ////////////////////////////////////////////////////////////////////////// //
1110 class MapTileBlock['oBlock'] : MapTile;
1113 override void setupTile () {
1114 int tileX = ix/16, tileY = iy/16;
1115 if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1118 //writeln("BORDER");
1120 if (global.cityOfGold == 1) spriteName = 'sGoldBlock';
1124 final bool isBBTTile (MapTile t) { return (t.objType == 'oBrick' || t.objType == 'oTemple' || t.objType == 'oHardBlock'); }
1127 override void beautifyTile () {
1128 bool down = !!level.checkTileAtPoint(ix, iy+16, &isBBTTile);
1130 // don't want push blocks next to lava until we tighten up liquid draining
1131 if (level.isLavaAtPoint(ix-16, iy) || level.isLavaAtPoint(ix+16, iy)) down = false;
1133 if (down && global.randRoom(1, 4) == 1) {
1135 level.MakeMapTile(ix/16, iy/16, 'oPushBlock');
1142 spriteName = 'sBlock';
1144 toSpecialGrid = false;
1148 // ////////////////////////////////////////////////////////////////////////// //
1149 class MapTileLush['oLush'] : MapTile;
1152 override void setupTile () {
1153 int tileX = ix/16, tileY = iy/16;
1154 if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1157 //writeln("BORDER");
1159 doCreateOre(80, 100, 120, 1200);
1164 override void beautifyTile () {
1165 if (level.checkTileAtPoint(ix+8, iy-8, delegate bool (MapTile t) { return (t isa TitleTileSpecialNonSolidInvincible || t isa TitleTileBlack); })) return;
1167 int tileX = ix/16, tileY = iy/16;
1169 auto tt = level.getTileAtGrid(tileX, tileY-1);
1170 //if (y == 0 || isNamedTileAt(x, y-1, 'oBrick') || isNamedTileAt(x, y-1, 'oHardBlock') || isNamedTileAt(x, y-1, 'oEdgeBrick')) up = true;
1171 bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral && (tt.objType == 'oLush' || tt.objType == 'oTemple')));
1173 tt = level.getTileAtGrid(tileX, tileY+1);
1174 //if (y == NormalTilesHeight-1 || isNamedTileAt(x, y+1, 'oBrick') || isNamedTileAt(x, y+1, 'oHardBlock') || isNamedTileAt(x, y+1, 'oEdgeBrick')) down = true;
1175 bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral && tt.objType == 'oLush'));
1178 setSprite('sLushUp');
1179 if (!level.isLavaAtPoint(ix+8, iy-8)) {
1181 if (global.randRoom(1, 8) == 1) bt = level.CreateBgTile('bgCaveTop2', 32);
1182 else if (global.randRoom(1, 3) < 3) bt = level.CreateBgTile('bgCaveTop2', 0);
1183 else bt = level.CreateBgTile('bgCaveTop2', 16);
1184 appendBackFront(bt);
1189 setSprite(!up ? 'sLushUp2' : 'sLushDown');
1190 if (!level.isSolidAtPoint(ix, iy+16) && !level.isLavaAtPoint(ix, iy+16)) {
1192 if (global.randRoom(1, 12) == 1) bt = level.CreateBgTile('bgCaveTop2', 48);
1193 else if (global.randRoom(1, 12) == 1) bt = level.CreateBgTile('bgCaveTop2', 64);
1196 //instance_create(x, y+16, oLushBottom); // in the original
1201 override void scrSetupBlockTop () {
1202 int tileX = ix/16, tileY = iy/16;
1203 auto tt = level.getTileAtGrid(tileX, tileY+1);
1204 bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1205 //setSprite('sLushUpBare');
1207 setSprite(spriteName == 'sLushUp' || spriteName == 'sLushUp2' || spriteName == 'sLushUp3' ? 'sLushUpBareTop' : 'sLushUpBare2');
1209 setSprite('sLushUpBare');
1214 override void scrSetupBlockBottom () {
1215 int tileX = ix/16, tileY = iy/16;
1216 auto tt = level.getTileAtGrid(tileX, tileY-1);
1217 bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1219 setSprite(spriteName == 'sLushUp' || spriteName == 'sLushUp2' || spriteName == 'sLushUp3' ? 'sLushUpBareBottom' : 'sLushUpBare2');
1221 setSprite('sLushDownBare');
1228 spriteName = 'sLush';
1231 toSpecialGrid = false;
1235 // ////////////////////////////////////////////////////////////////////////// //
1236 class MapTileDark['oDark'] : MapTile;
1239 override void setupTile () {
1240 int tileX = ix/16, tileY = iy/16;
1241 if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1244 //writeln("BORDER");
1246 doCreateOre(40, 60, 80, 1200);
1251 override void beautifyTile () {
1252 if (level.checkTileAtPoint(ix+8, iy-8, delegate bool (MapTile t) { return (t isa TitleTileSpecialNonSolidInvincible || t isa TitleTileBlack); })) return;
1255 //if (argument1) if (global.randRoom(1, 10) == 1) sprite_index = sBrick2; // reset sprite for custom border setup
1257 int tileX = ix/16, tileY = iy/16;
1259 auto tt = level.getTileAtGrid(tileX, tileY-1);
1260 //if (y == 0 || isNamedTileAt(x, y-1, 'oBrick') || isNamedTileAt(x, y-1, 'oHardBlock') || isNamedTileAt(x, y-1, 'oEdgeBrick')) up = true;
1261 bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1263 tt = level.getTileAtGrid(tileX, tileY+1);
1264 //if (y == NormalTilesHeight-1 || isNamedTileAt(x, y+1, 'oBrick') || isNamedTileAt(x, y+1, 'oHardBlock') || isNamedTileAt(x, y+1, 'oEdgeBrick')) down = true;
1265 bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1267 //if (collision_point(x-16, y, oBrick, 0, 0) or collision_point(x-16, y, oHardBlock, 0, 0)) { left = true; } // in the original
1268 //if (collision_point(x+16, y, oBrick, 0, 0) or collision_point(x+16, y, oHardBlock, 0, 0)) { right = true; } // in the original
1271 setSprite('sDarkUp');
1272 bool n = (global.randRoom(1, 3) < 3);
1273 appendBackFront(level.CreateBgTile('bgCaveTop3', (n ? 0 : 16)));
1277 setSprite(!up ? 'sDarkUp2' : 'sDarkDown');
1282 override void scrSetupBlockTop () {
1283 int tileX = ix/16, tileY = iy/16;
1284 auto tt = level.getTileAtGrid(tileX, tileY+1);
1285 bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1286 auto bt = level.CreateBgTile('bgCaveTop3', (global.randRoom(1, 3) < 3 ? 0 : 16));
1287 appendBackFront(bt);
1288 //bt.flty = -16; // offset
1290 setSprite(down ? 'sDarkUp' : 'sDarkUp2');
1294 override void scrSetupBlockBottom () {
1295 int tileX = ix/16, tileY = iy/16;
1296 auto tt = level.getTileAtGrid(tileX, tileY-1);
1297 bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1298 setSprite(up ? 'sDarkDown' : 'sDarkUp2');
1304 spriteName = 'sDark';
1307 toSpecialGrid = false;
1311 // ////////////////////////////////////////////////////////////////////////// //
1312 class MapTileIce['oIce'] : MapTile;
1317 final bool isIceTile (MapTile t) { return t.ice; }
1318 final MapTile isIceTileAtPoint (int x, int y) { return level.checkTileAtPoint(x, y, &isIceTile); }
1321 override void setupTile () {
1322 if (global.randRoom(1, 80) == 1) {
1323 setGem('oFrozenCaveman', true); // always visible
1326 gem.saveInterpData();
1327 gemDestroyWithTile = true;
1329 dripTimer = global.randOther(20, 400);
1330 //hasIceBottom = true;
1335 override void thinkFrame () {
1337 if (--dripTimer <= 0) {
1339 level.MakeMapObject(ix+8, iy+16+4, 'oDrip');
1340 dripTimer = global.randOther(20, 400);
1347 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1348 bool up = (specified_noup && noup ? false : !!isIceTileAtPoint(ix, iy-16));
1349 bool down = (specified_nodown && nodown ? false : !!isIceTileAtPoint(ix, iy+16));
1350 bool left = (specified_noleft && noleft ? false : !!isIceTileAtPoint(ix-16, iy));
1351 bool right = (specified_noright && noright ? false : !!isIceTileAtPoint(ix+16, iy));
1353 if (!up) setSprite('sIceUp');
1355 setSprite(!up ? 'sIceUp2' : 'sIceDown');
1356 if (!level.isSolidAtPoint(ix, iy+16) && global.randOther(1, 20) == 1) hasIceBottom = true;
1359 if (!up && !down) setSprite('sIceUDL');
1360 else if (!up) setSprite('sIceUL');
1361 else if (!down) setSprite('sIceDL');
1362 else setSprite('sIceLeft');
1365 if (!up && !down) setSprite('sIceUDR');
1366 else if (!up) setSprite('sIceUR');
1367 else if (!down) setSprite('sIceDR');
1368 else setSprite('sIceRight');
1370 if (!up && !left && !right && down) setSprite('sIceULR');
1371 if (!down && !left && !right && up) setSprite('sIceDLR');
1372 if (up && down && !left && !right) setSprite('sIceLR');
1373 if (!up && !down && !left && !right) setSprite('sIceBlock');
1378 override void beautifyTile () {
1380 bool up = !!isIceTileAtPoint(ix, iy-16);
1381 bool down = !!isIceTileAtPoint(ix, iy+16);
1382 bool left = !!isIceTileAtPoint(ix-16, iy);
1383 bool right = !!isIceTileAtPoint(ix+16, iy);
1385 //writeln("beautifying ice; l=", left, "; r=", right, "; u=", up, "; d=", down);
1387 if (!up) setSprite('sIceUp');
1389 setSprite(!up ? 'sIceUp2' : 'sIceDown');
1390 if (!level.isSolidAtPoint(ix, iy+16) && global.randOther(1, 20) == 1) hasIceBottom = true;
1393 if (!up && !down) setSprite('sIceUDL');
1394 else if (!up) setSprite('sIceUL');
1395 else if (!down) setSprite('sIceDL');
1396 else setSprite('sIceLeft');
1399 if (!up && !down) setSprite('sIceUDR');
1400 else if (!up) setSprite('sIceUR');
1401 else if (!down) setSprite('sIceDR');
1402 else setSprite('sIceRight');
1404 if (!up && !left && !right && down) setSprite('sIceULR');
1405 if (!down && !left && !right && up) setSprite('sIceDLR');
1406 if (up && down && !left && !right) setSprite('sIceLR');
1407 if (!up && !down && !left && !right) setSprite('sIceBlock');
1409 setupNiceTileSprite();
1413 override void scrSetupBlockTop () {
1414 setupNiceTileSprite(/*noup:true*/);
1417 bool down = !!isIceTileAtPoint(ix, iy+16);
1418 bool left = !!isIceTileAtPoint(ix-16, iy);
1419 bool right = !!isIceTileAtPoint(ix+16, iy);
1421 setSprite('sIceUp');
1423 if (!down) setSprite('sIceUp2');
1425 if (!left) setSprite(!up ? 'sIceUDL' : 'sIceUL');
1426 if (!right && !down) setSprite('sIceUDR');
1428 if (!left && !right && down) setSprite('sIceULR');
1429 if (!down && !left && !right) setSprite('sIceBlock');
1434 override void scrSetupBlockBottom () {
1435 setupNiceTileSprite(/*nodown:true*/);
1437 bool up = !!isIceTileAtPoint(ix, iy-16);
1438 bool left = !!isIceTileAtPoint(ix-16, iy);
1439 bool right = !!isIceTileAtPoint(ix+16, iy);
1441 setSprite(!up ? 'sIceUp2' : 'sIceDown');
1443 if (global.randOther(1, 20) == 1) hasIceBottom = true; //instance_create(x, y+16, oIceBottom);
1445 if (!left) setSprite(!up ? 'sIceUDL' : 'sIceDL');
1446 if (!right) setSprite(!up ? 'sIceUDR' : 'sIceDR');
1448 if (!left && !right && up) setSprite('sIceDLR');
1449 if (!up && !left && !right) setSprite('sIceBlock');
1454 override void scrSetupBlockLeft () {
1455 setupNiceTileSprite(/*noright:true*/);
1457 bool up = !!isIceTileAtPoint(ix, iy-16);
1458 bool down = !!isIceTileAtPoint(ix, iy+16);
1459 bool left = !!isIceTileAtPoint(ix-16, iy);
1462 if (!up) setSprite('sIceUp');
1463 if (!down) { if (!up) setSprite('sIceUp2'); else setSprite('sIceDown'); }
1465 if (!up && !down) setSprite('sIceUDL');
1466 else if (!up) setSprite('sIceUL');
1467 else if (!down) setSprite('sIceDL');
1468 else setSprite('sIceLeft');
1471 if (!up && !down) setSprite('sIceUDR');
1472 else if (!up) setSprite('sIceUR');
1473 else if (!down) setSprite('sIceDR');
1474 else setSprite('sIceRight');
1476 if (!up && !left && !right && down) setSprite('sIceULR');
1477 if (!down && !left && !right && up) setSprite('sIceDLR');
1478 if (up && down && !left && !right) setSprite('sIceLR');
1479 if (!up && !down && !left && !right) setSprite('sIceBlock');
1484 override void scrSetupBlockRight () {
1485 setupNiceTileSprite(/*noleft:true*/);
1487 bool up = !!isIceTileAtPoint(ix, iy-16);
1488 bool down = !!isIceTileAtPoint(ix, iy+16);
1490 bool right = !!isIceTileAtPoint(ix+16, iy);
1492 if (!up) setSprite('sIceUp');
1493 if (!down) { if (!up) setSprite('sIceUp2'); else setSprite('sIceDown'); }
1495 if (!up && !down) setSprite('sIceUDL');
1496 else if (!up) setSprite('sIceUL');
1497 else if (!down) setSprite('sIceDL');
1498 else setSprite('sIceLeft');
1501 if (!up && !down) setSprite('sIceUDR');
1502 else if (!up) setSprite('sIceUR');
1503 else if (!down) setSprite('sIceDR');
1504 else setSprite('sIceRight');
1506 if (!up && !left && !right && down) setSprite('sIceULR');
1507 if (!down && !left && !right && up) setSprite('sIceDLR');
1508 if (up && down && !left && !right) setSprite('sIceLR');
1509 if (!up && !down && !left && !right) setSprite('sIceBlock');
1516 spriteName = 'sIce';
1519 toSpecialGrid = true; // want to think
1523 // ////////////////////////////////////////////////////////////////////////// //
1524 class MapTileTemple['oTemple'] : MapTile;
1529 final bool isTempleTile (MapTile t) { return (t.objType == 'oTemple' || t.border); } // fake too
1530 final MapTile isTempleTileAtPoint (int x, int y) { return level.checkTileAtPoint(x, y, &isTempleTile); }
1533 override void setupTile () {
1534 setSprite(global.cityOfGold == 1 ? 'sGTemple' : 'sTemple');
1535 int tileX = ix/16, tileY = iy/16;
1536 if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1539 //writeln("BORDER");
1541 doCreateOre(60, 80, 100, 1200);
1547 override void thinkFrame () {
1549 if (--dripTimer <= 0) {
1551 level.MakeMapObject(ix+8, iy+16+4, 'oDrip');
1552 dripTimer = global.randOther(20, 400);
1560 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1561 bool up = (iy <= 0 ? true : (specified_noup && noup ? false : !!isTempleTileAtPoint(ix, iy-16)));
1562 bool down = (iy >= level.tilesHeight*16-16 ? true : (specified_nodown && nodown ? false : !!isTempleTileAtPoint(ix, iy+16)));
1563 bool left = (ix <= 16 ? true : (specified_noleft && noleft ? false : !!isTempleTileAtPoint(ix-16, iy)));
1564 bool right = (ix >= level.tilesWidth*16-16*2 ? true : (specified_noright && noright ? false : !!isTempleTileAtPoint(ix+16, iy)));
1568 if (global.cityOfGold == 1) sprite_index = sGTemple; else sprite_index = sTemple;
1573 if (y == 0 or collision_point(x, y-16, oTemple, 0, 0) or collision_point(x, y+16, oTempleFake, 0, 0)) { up = true; }
1574 if (y >= argument0 or collision_point(x, y+16, oTemple, 0, 0) or collision_point(x, y+16, oTempleFake, 0, 0)) { down = true; }
1575 if (collision_point(x-16, y, oTemple, 0, 0) or collision_point(x-16, y, oTempleFake, 0, 0)) { left = true; }
1576 if (collision_point(x+16, y, oTemple, 0, 0) or collision_point(x+16, y, oTempleFake, 0, 0)) { right = true; }
1579 if (global.cityOfGold == 1) {
1581 setSprite('sGTempleUp');
1582 if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 0)); //3
1583 else if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 16)); //3
1584 if (!left && !right) {
1585 if (!down) setSprite('sGTempleUp6'); else setSprite('sGTempleUp5');
1587 if (!down) setSprite('sGTempleUp7'); else setSprite('sGTempleUp3');
1588 } else if (!right) {
1589 if (!down) setSprite('sGTempleUp8'); else setSprite('sGTempleUp4');
1590 } else if (left && right && !down) {
1591 setSprite('sGTempleUp2');
1594 setSprite('sGTempleDown');
1598 setSprite('sTempleUp');
1599 //if (rand(1, 4) == 1) tile_add(bgCaveTop4, 0, 0, 16, 16, x, y-16, 3); else if (rand(1, 4) == 1) tile_add(bgCaveTop4, 16, 0, 16, 16, x, y-16, 3);
1600 if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 0)); //3
1601 else if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 16)); //3
1602 if (!left && !right) {
1603 if (!down) setSprite('sTempleUp6'); else setSprite('sTempleUp5');
1605 if (!down) setSprite('sTempleUp7'); else setSprite('sTempleUp3');
1606 } else if (!right) {
1607 if (!down) setSprite('sTempleUp8'); else setSprite('sTempleUp4');
1608 } else if (left && right && !down) {
1609 setSprite('sTempleUp2');
1612 setSprite('sTempleDown');
1618 override void beautifyTile () {
1619 setupNiceTileSprite();
1623 override void scrSetupBlockTop () {
1624 setupNiceTileSprite(/*noup:true*/);
1628 override void scrSetupBlockBottom () {
1629 setupNiceTileSprite(/*nodown:true*/);
1633 override void scrSetupBlockLeft () {
1634 setupNiceTileSprite(/*noright:true*/);
1638 override void scrSetupBlockRight () {
1639 setupNiceTileSprite(/*noleft:true*/);
1644 objType = 'oTemple';
1645 spriteName = 'sTemple';
1647 //toSpecialGrid = true; // want to think
1651 // ////////////////////////////////////////////////////////////////////////// //
1652 class MapTileVine['oVine'] : MapTile;
1655 override void setupTile () {
1659 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1660 //if (argument1) sprite_index = sVine;
1662 bool up = !!level.isLadderAtPoint(ix+8, iy-8);
1663 bool down = !!level.isLadderAtPoint(ix+8, iy+16);
1666 //tile_add(bgVineRoots, 0, 0, 16, 16, x, y-16, 1000); // use same depth as the vine objects themselves
1667 appendBackFront(level.CreateBgTile('bgVineRoots', 0)); //1000
1668 setSprite('sVineSource');
1670 setSprite('sVineBottom');
1675 override void beautifyTile () {
1676 setupNiceTileSprite();
1684 spriteName = 'sVine';
1685 //toSpecialGrid = true; // want to think
1689 // ////////////////////////////////////////////////////////////////////////// //
1690 class MapTileLava['oLava'] : MapTile;
1698 override void setupTile () {
1699 spurtTime = global.randOther(100, 300);
1700 spurtCounter = spurtTime;
1704 override void doSprayRubble () {
1705 foreach (; 0..3) level.MakeMapObject(ix+global.randOther(0, 16), iy+global.randOther(0, 16), 'oLavaDrip');
1706 if (global.randOther(1, 6) == 1) {
1707 auto flame = level.MakeMapObject(ix+8, iy+8, 'oFlame');
1708 if (flame) flame.yVel = 4;
1713 override bool onExplosionTouch (MapObject xplo) {
1718 override void thinkFrame () {
1719 if (!spurtSet && spriteName == 'sLavaTop') {
1721 spurt = (global.randOther(1, 4) == 1);
1723 auto dist = pointDistance(ix, iy, level.player.ix, level.player.iy);
1724 if (spurt && dist < 240) {
1725 if (spurtCounter > 0) {
1728 spurtCounter = spurtTime;
1729 auto flame = level.MakeMapObject(ix+8, iy-4, (global.randOther(1, 8) == 1 ? 'oMagma' : 'oFlame'));
1730 //auto flame = level.MakeMapObject(ix+8, iy-4, 'oMagma');
1731 if (flame) flame.yVel = -global.randOther(1, 4);
1739 spriteName = 'sLava';
1742 toSpecialGrid = false; // lava tiles are special: they are animated/thinked without a grid
1743 lightRadius = 32+16;
1744 litWholeTile = true;
1749 // ////////////////////////////////////////////////////////////////////////// //
1750 class MapTileWater['oWater'] : MapTile;
1753 override void setupTile () {
1757 override void doSprayRubble () {
1758 foreach (; 0..3) level.MakeMapObject(ix+global.randOther(0, 16), iy+global.randOther(0, 16), 'oDrip');
1762 override bool onExplosionTouch (MapObject xplo) {
1767 final bool isWaterTile (MapTile t) { return t.water; }
1768 //final bool isSolidOrWaterTile (MapTile t) { return (t.water || t.solid); }
1771 void setupNiceTileSprite () {
1772 //if (argument1) sprite_index = sWater;
1774 bool upWater = false;
1779 if (level.checkTileAtPoint(ix, iy-16, &isWaterTile)) upWater = true;
1780 if (level.isSolidAtPoint(ix, iy-16)) up = true;
1781 if (level.isSolidAtPoint(ix, iy+16) && !level.checkTileAtPoint(ix, iy+16, &isWaterTile)) down = true;
1783 if (!up && !upWater) setSprite('sWaterTop');
1785 if (upWater && level.checkTileAtPoint(ix, iy-32, &isWaterTile) && down && global.randOther(1, 4) == 1) {
1786 setSprite('sWaterBottomTall2');
1787 auto water = level.checkTileAtPoint(ix, iy-16, &isWaterTile);
1788 if (water) water.setSprite('sWaterBottomTall1');
1789 } else if ((up || upWater) && down) {
1790 switch (global.randOther(1, 4)) {
1791 case 1: setSprite('sWaterBottom'); break;
1792 case 2: setSprite('sWaterBottom2'); break;
1793 case 3: setSprite('sWaterBottom3'); break;
1794 case 4: setSprite('sWaterBottom4'); break;
1800 override void beautifyTile () {
1801 setupNiceTileSprite();
1807 spriteName = 'sWater';
1810 toSpecialGrid = false;
1814 // ////////////////////////////////////////////////////////////////////////// //
1815 class MapTileWaterSwim['oWaterSwim'] : MapTileWater;
1818 // ////////////////////////////////////////////////////////////////////////// //
1819 class MapTileLavaSolid['oLavaSolid'] : MapTile;
1821 override void setupTile () {
1824 override bool onExplosionTouch (MapObject xplo) {
1829 objType = 'oLavaSolid';
1830 spriteName = 'sLava';
1833 toSpecialGrid = false;
1837 // ////////////////////////////////////////////////////////////////////////// //
1838 class MapTileLushTrapBlock['oTrapBlock'] : MapTile;
1845 override void onDestroy () {
1847 /*if (not cleanDeath and not global.cleanSolids)*/ {
1848 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubbleTan', 'sRubbleTanSmall');
1849 scrDropRubble(ix, iy, 3, 'sRubbleTan', 'sRubbleTanSmall');
1851 playSound('sndThump');
1860 override void doSprayRubble () {
1861 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
1862 scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
1863 if (global.cityOfGold == 1) {
1868 playSound('sndThump');
1874 override void setupTile () {
1880 if (distanceToEntityCenter(level.player) < 90) {
1886 override void thinkFrame () {
1888 if (deathTimer > 0) --deathTimer; else instanceRemove();
1894 objType = 'oLavaSolid';
1895 spriteName = 'sSkullBlock';
1897 toSpecialGrid = true;
1902 // ////////////////////////////////////////////////////////////////////////// //
1903 class MapTileLeaves['oLeaves'] : MapTile;
1909 final bool isTreeOrLeaves (MapTile t) { return (t != self && t.tree || t.leaves); }
1913 override void onDestroy () {
1915 if (spriteName != 'sLeavesDead' && spriteName != 'sLeavesDeadR') {
1916 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1917 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1924 override void doSprayRubble () {
1925 if (spriteName != 'sLeavesDead' && spriteName != 'sLeavesDeadR') {
1926 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1927 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1932 override void setupTile () {
1933 spriteName = (global.cemetary ? 'sLeavesDead' : 'sLeaves');
1937 override void thinkFrame () {
1942 if (level.checkTileAtPoint(x-16, y, &isTreeOrLeaves)) {
1943 setSprite(global.cemetary ? 'sLeavesDeadR' : 'sLeavesRight');
1944 } else if (level.checkTileAtPoint(x+16, y, &isTreeOrLeaves)) {
1945 setSprite(global.cemetary ? 'sLeavesDead' : 'sLeaves');
1947 if (level.checkTileAtPoint(x-16, y, &isTreeOrLeaves) &&
1948 level.checkTileAtPoint(x+16, y, &isTreeOrLeaves))
1950 setSprite('sLeavesTop');
1953 if (level.checkTileAtPoint(x-16, y, &isTreeOrLeaves)) setSprite('sLeavesDeadR'); else setSprite('sLeavesDead');
1960 if (spriteName == 'sLeavesTop') {
1961 if (!level.checkTileAtPoint(x-16, y, &isTreeOrLeaves) ||
1962 !level.checkTileAtPoint(x+16, y, &isTreeOrLeaves))
1966 } else if (spriteName == 'sLeaves' || spriteName == 'sLeavesDead') {
1967 if (!level.checkTileAtPoint(x+16, y, &isTreeOrLeaves)) instanceRemove();
1968 } else if (spriteName == 'sLeavesRight' || spriteName == 'sLeavesDeadR') {
1969 if (!level.checkTileAtPoint(x-16, y, &isTreeOrLeaves)) instanceRemove();
1974 if (sprite_index == sLeavesDeadR) {
1976 desc2 = "These leaves have died and withered.";
1979 desc2 = "The canopy of a proud tree.";
1986 objType = 'oLeaves';
1988 desc2 = "The top of a proud tree.";
1994 toSpecialGrid = true;
1999 // ////////////////////////////////////////////////////////////////////////// //
2000 class MapTileTree['oTree'] : MapTile;
2006 override void onDestroy () {
2008 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2009 scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2015 override void doSprayRubble () {
2016 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2017 scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2021 override void setupTile () {
2025 final bool isTreeTileAtPoint (int x, int y) {
2026 return !!level.checkTileAtPoint(x, y, delegate bool (MapTile t) { return (t.objType == 'oTree'); });
2030 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
2031 //if (argument1) sprite_index = sVine;
2033 bool up = isTreeTileAtPoint(ix, iy-16);
2034 //bool down = isTreeTileAtPoint(ix, iy+16);
2035 //bool left = isTreeTileAtPoint(ix-16, iy);
2036 //bool right = isTreeTileAtPoint(ix+16, yi);
2039 if (global.cemetary || getSprite().Name == 'sTreeTopDead') setSprite('sTreeTopDead'); else setSprite('sTreeTop');
2045 override void beautifyTile () {
2046 setupNiceTileSprite();
2050 override void thinkFrame () {
2053 if (!level.isSolidAtPoint(x, y+16)) { instanceRemove(); return; }
2054 if (level.isLavaAtPoint(x-16, y) || level.isLavaAtPoint(x+16, y)) { instanceRemove(); return; }
2060 desc = "Tree Trunk";
2061 desc2 = "The trunk of a proud tree.";
2062 spriteName = 'sTreeTrunk';
2067 toSpecialGrid = true;
2072 // ////////////////////////////////////////////////////////////////////////// //
2073 class MapTileTreeBranch['oTreeBranch'] : MapTile;
2079 final bool isTree (MapTile t) { return (t != self && t.tree); }
2083 override void onDestroy () {
2085 if (spriteName != 'sTreeBranchDeadL' && spriteName != 'sTreeBranchDeadR') {
2086 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
2093 override void doSprayRubble () {
2094 if (spriteName != 'sTreeBranchDeadL' && spriteName != 'sTreeBranchDeadR') {
2095 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
2100 override void setupTile () {
2103 auto ltree = level.checkTileAtPoint(ix-16, iy, &isTree);
2104 auto rtree = level.checkTileAtPoint(ix+16, iy, &isTree);
2106 if (ltree && !rtree) setSprite(global.cemetary ? 'sTreeBranchDeadR' : 'sTreeBranchRight');
2107 else if (!ltree && rtree) setSprite(global.cemetary ? 'sTreeBranchDeadL' : 'sTreeBranchLeft');
2109 //if (global.cemetary) spriteName = 'sTreeBranchDeadR';
2113 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
2114 //if (argument1) sprite_index = sVine;
2116 bool up = !!level.checkTileAtPoint(ix, iy-16, delegate bool (MapTile t) { return (t.objType == 'oLeaves'); });
2117 //if (collision_point(x, y+16, oTreeBranch, 0, 0)) { down = true; }
2118 //if (collision_point(x-16, y, oTreeBranch, 0, 0)) { left = true; }
2119 bool right = !!level.checkTileAtPoint(ix+16, iy, delegate bool (MapTile t) { return (t.objType == 'oTree'); });
2127 if (global.cemetary || getSprite().Name == 'sTreeBranchDeadR') setSprite('sTreeBranchDeadL'); else setSprite('sTreeBranchLeft');
2132 override void beautifyTile () {
2133 //writeln("!!! ", getSprite().Name);
2134 setupNiceTileSprite();
2138 override void thinkFrame () {
2142 auto ltree = level.checkTileAtPoint(ix-16, iy, &isTree);
2143 auto rtree = level.checkTileAtPoint(ix+16, iy, &isTree);
2144 if (!ltree && !rtree) {
2148 if (ltree && !rtree) setSprite('sTreeBranchRight');
2149 else if (!ltree && rtree) setSprite('sTreeBranchLeft');
2158 desc = "Tree Branch";
2159 desc2 = "A slight but firm limb of a proud tree.";
2160 spriteName = 'sTreeBranchRight';
2166 toSpecialGrid = true;
2171 // ////////////////////////////////////////////////////////////////////////// //
2172 // only for dark levels
2173 class MapTileTikiTorch['oTikiTorch'] : MapTile;
2176 override int height () { return 32; }
2178 override void setupTile () {
2179 writeln("*** TIKI TORCH CREATED");
2182 override void thinkFrame () {
2183 lightRadius = 32+16+global.randOther(-8, 8);
2188 objType = 'oTikiTorch';
2190 desc2 = "A moderately bright torch.";
2191 spriteName = 'sTikiTorch';
2196 toSpecialGrid = true;
2199 lightRadius = 32+16;
2203 // ////////////////////////////////////////////////////////////////////////// //
2204 class MapTileGiantTikiHead['oGiantTikiHead'] : MapTile;
2212 setSprite('sGTHHole');
2217 override void setupTile () {
2221 override void thinkFrame () {
2226 objType = 'oGiantTikiHead';
2227 invincible = true; //???
2229 spriteName = 'sGiantTikiHead';
2230 toSpecialGrid = true; // it is HUGE
2235 // ////////////////////////////////////////////////////////////////////////// //
2236 class MapTileThinIce['oThinIce'] : MapTile;
2240 override void setupTile () {
2244 override void thinkFrame () {
2245 if (level.player.isRectHitSimple(ix, iy-1, 17, 2)) {
2247 if (global.randOther(1, 100) == 1) level.MakeMapObject(ix+global.randOther(0, 16), iy+9, 'oDrip');
2249 if (thickness > 50) setSprite('sThinIce1');
2250 else if (thickness > 40) setSprite('sThinIce2');
2251 else if (thickness > 30) setSprite('sThinIce3');
2252 else if (thickness > 20) setSprite('sThinIce4');
2253 else if (thickness > 10) setSprite('sThinIce5');
2254 else if (thickness > 0) setSprite('sThinIce6');
2255 else instanceRemove();
2260 objType = 'oThinIce';
2262 spriteName = 'sThinIce1';
2263 toSpecialGrid = true; // it must think
2267 // ////////////////////////////////////////////////////////////////////////// //
2268 class MapTileDarkFall['oDarkFall'] : MapTile;
2270 //int thickness = 60;
2273 int timeFallMax = 20;
2277 override int height () { return 8; }
2279 override void setupTile () {}
2282 //FIXME: viscidMovement -- ???
2283 override void thinkFrame () {
2284 //isCollisionCharacterTop(1)
2286 auto plr = level.player;
2287 if (plr.isRectHitSimple(ix, iy-1, 17, 2)) {
2289 } else if (plr.status == MapObject::HANGING) {
2290 //writeln("checking for hang...");
2292 if (plr.isRectHitSimple((tileX-1)*16, iy, 16, 8) || plr.isRectHitSimple((tileX+1)*16, iy, 16, 8)) {
2293 //writeln("oDarkFall: HANGING! timer=", timeFall);
2296 } else if (timeFall < timeFallMax) {
2299 if (timeFall <= 0) falling = true;
2301 if (!falling) return;
2303 //if (yVel > 10) yVel = 10;
2304 //HACK: so player won't be able to push it
2308 // dropped on solid?
2309 if (level.checkTilesInRect(ix, iy+height, width, 1)) {
2312 // not breaked on "Thwomp Trap" (we don't have it yet)
2313 playSound('sndBreak');
2314 level.MakeMapObject(x+8, y+8, 'oSmokePuff');
2316 auto obj = level.MakeMapObject(x+global.randOther(2, 14), y-global.randOther(2, 8), 'oRubbleDark');
2318 obj.xVel = global.randOther(1, 3)-global.randOther(1, 3);
2319 obj.yVel = -global.randOther(0, 3);
2327 objType = 'oDarkFall';
2331 //setCollisionBounds(0, 0, 16, 8);
2337 desc = "Falling Platform";
2338 desc2 = "A thin, dark blue platform that will colapse if stood on for any length of time.";
2340 spriteName = 'sDarkFall';
2341 toSpecialGrid = true; // it must think
2346 // ////////////////////////////////////////////////////////////////////////// //
2347 class MapTileAlienHull['oAlienShip'] : MapTile;
2350 override void setupTile () {
2351 //writeln("*********** ALIEN SHIP!");
2355 override void doSprayRubble () {
2356 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2357 scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2361 final bool isAlienShipFloorCB (MapTile t) { return (t.objType == 'oAlienShip' || t.objType == 'oAlienShipFloor'); }
2362 final bool isASFTileAt (int x, int y) { return !!level.checkTileAtPoint(x, y, &isAlienShipFloorCB); }
2365 override void beautifyTile () {
2366 //if (argument1) sprite_index = sAlienTop;
2368 bool up = !!isASFTileAt(ix, iy-16);
2369 bool down = !!isASFTileAt(ix, iy+16);
2370 bool left = !!isASFTileAt(ix-16, iy);
2371 bool right = !!isASFTileAt(ix+16, iy);
2373 if (right && !left) {
2374 if (up && !down) setSprite('sAlienFront2');
2375 else if (down && !up) setSprite('sAlienFront3');
2377 if (left && !right) {
2378 if (up) setSprite('sAlienBack2'); else if (down) setSprite('sAlienBack3');
2384 objType = 'oAlienShip';
2386 spriteName = 'sAlienTop';
2387 toSpecialGrid = false;
2391 // ////////////////////////////////////////////////////////////////////////// //
2392 class MapTileAlienFloor['oAlienShipFloor'] : MapTileAlienHull;
2396 objType = 'oAlienShipFloor';
2397 spriteName = 'sAlienFloor';
2401 // ////////////////////////////////////////////////////////////////////////// //
2402 class MapTileXocBlock['oXocBlock'] : MapTile;
2405 override void setupTile () {
2409 override void doSprayRubble () {
2411 auto gold = level.MakeMapObject(ix+8+global.randOther(0, 4)-global.randOther(0, 4), iy+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldChunk');
2413 gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2414 gold.yVel = global.randOther(2, 4);
2418 auto gold = level.MakeMapObject(ix+8+global.randOther(0, 4)-global.randOther(0, 4), iy+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldNugget');
2420 gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2421 gold.yVel = global.randOther(2, 4);
2428 objType = 'oXocBlock';
2430 spriteName = 'sGoldBlock';
2431 toSpecialGrid = false;
2436 // ////////////////////////////////////////////////////////////////////////// //
2437 class MapTileCeilingTrap['oCeilingTrap'] : MapTile;
2439 //int thickness = 60;
2441 //int timeFall = 20;
2442 //int timeFallMax = 20;
2458 override void onAnimationLooped () {
2459 if (spriteName == 'sCeilingTrapS') setSprite('sCeilingTrap');
2463 override void doSprayRubble () {
2464 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2465 scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2466 if (global.cityOfGold == 1) {
2473 override void setupTile () {
2474 if (global.cityOfGold == 1) spriteName = 'sGoldBlock';
2479 if (status != IDLE) return;
2481 level.checkTilesInRect(ix-64, iy-64, 129, 129, delegate bool (MapTile t) {
2482 auto door = MapTileDoor(t);
2483 if (door) door.activate();
2489 //FIXME: viscidMovement -- ???
2490 override void thinkFrame () {
2491 if (status == IDLE) {
2493 } else if (status == DROP) {
2502 if (level.isSolidAtPoint(ix+8, iy+17)) status = WAIT;
2504 if (spriteName == 'sBlock' || spriteName == 'sGoldBlock') setSprite('sCeilingTrapS');
2505 // out-of-level check
2506 if (isOutsideOfLevel()) {
2510 } else if (status == WAIT) {
2513 if (isCollisionBottom(0)) flty -= 1;
2518 if (status != IDLE) {
2520 auto plr = level.player;
2521 if (!plr.dead && !plr.invincible && !plr.isExitingSprite() && /*plr.collidesWith(self)*/ plr.isRectHitSimple(x0, y0, width, height+1)) {
2523 if (!plr.collidesWith(self)) {
2524 doCol = (plr.yVel < -0.01 && plr.y0 > y1);
2527 if (global.plife > 0) {
2528 global.plife -= global.config.crushDmg;
2529 if (global.plife <= 0 /*and isRealLevel()*/) level.addDeath(objName);
2534 playSound('sndHurt');
2539 level.isObjectInRect(x0-1, y0-1, width+2, height+2, delegate bool (MapObject o) {
2540 if (o == self) return false;
2541 if (o isa EnemyTombLord) return false;
2543 // register hit only if we're moving onto a spikes
2544 if (!o.collidesWith(self)) {
2545 bool onTop = (o.yVel < -0.01 && o.y0 > y1);
2546 if (!onTop) return false;
2547 writeln("*** RUNNING ON TRAP SPIKES");
2551 auto dms = MonsterDamsel(o);
2553 if (dms.dead || dms.status == MapObject::DEAD || dms.invincible) return false;
2554 if (dms.heldBy) dms.heldBy.holdItem = none;
2555 dms.hp -= global.config.crushDmg;
2557 dms.status = MapObject::THROWN;
2558 dms.counter = dms.stunMax;
2560 //dms.damselDropped = true;
2561 dms.kissCount = min(1, dms.kissCount);
2562 playSound('sndDamsel');
2567 auto enemy = MapEnemy(o);
2569 if (o.dead || o.status == MapObject::DEAD || o.invincible) return false;
2570 if (o.heldBy) o.heldBy.holdItem = none;
2571 enemy.hp -= global.config.crushDmg;
2573 playSound('sndHit');
2578 }, castClass:MapEnemy);
2584 objType = 'oCeilingTrap';
2585 objName = 'Ceiling Trap';
2586 desc = "Ceiling Trap";
2587 desc2 = "This seemingly-innocuous block hides a vicious set of spikes.";
2593 //setCollisionBounds(0, 0, 16, 8);
2603 spriteName = 'sBlock';
2604 toSpecialGrid = true; // it must think
2609 // ////////////////////////////////////////////////////////////////////////// //
2610 class MapTileTempleFake['oTempleFake'] : MapTile;
2612 int sleepTime = 4; // wait until the grid is filled
2614 override void doSprayRubble () {
2615 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2616 scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2620 override void setupTile () {
2621 doCreateOre(60, 80, 100, 1200);
2625 override void thinkFrame () {
2626 if (sleepTime > 0) { --sleepTime; return; }
2628 auto door = level.checkTilesInRect(ix, iy, 16, 16, delegate bool (MapTile t) {
2629 if (t == self) return false;
2630 //writeln("fake temple: t(", GetClassName(t.Class), "); objName=", t.objName, "; objType=", t.objType, "; door=", t isa MapTileDoor);
2631 return (t isa MapTileDoor);
2635 auto bt = level.MakeMapTile(ix/16, iy/16, 'oTemple');
2636 if (bt) bt.copyOreFrom(self);
2641 if (!level.checkTileAtPoint(ix+8, iy+8, delegate bool (MapTile t) { return (t isa MapTileDoor); })) {
2643 level.MakeMapTile(ix/16, iy/16, 'oTemple');
2652 objType = 'oTemple';
2653 desc = "Temple Brick";
2654 desc2 = "This is the first brick you've seen that actually qualifies as a brick. It looks rather bland.";
2658 spriteName = 'sTemple';
2659 toSpecialGrid = true; // it must think
2667 // ////////////////////////////////////////////////////////////////////////// //
2668 class MapTileDoor['oDoor'] : MapTile;
2670 //int thickness = 60;
2685 //setCollisionBounds(1, 0, 15, 32);
2686 override int x0 () { return round(fltx)+1; }
2687 override int width () { return 15; }
2688 override int height () { return 32; }
2691 override void doSprayRubble () {
2692 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2693 scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2694 if (global.cityOfGold == 1) {
2701 override void setupTile () {
2713 //FIXME: viscidMovement -- ???
2714 override void thinkFrame () {
2715 if (status == IDLE) {
2717 } else if (status == DROP) {
2718 yVel = fmin(yVel+myGrav, myGravLimit);
2721 auto oldsolid = solid;
2723 //writeln("DOOR GOING DOWN; yVel=", yVel, "; ix=", ix, "; iy=", iy, "; x0=", x0, "; y0=", y0, "; w=", width, "; h=", height);
2724 int newY = round(flty+yVel); //!!!
2725 // check if we need (and can) move down
2726 int w = width, hp = height;
2728 // made non-solid temporarily
2729 //auto hasAnything = level.checkTilesInRect(x0, y0+hp, w, 1, &level.cbCollisionAnySolid);
2730 auto hasAnything = level.checkTilesInRect(x0, y0+hp, w, 1, delegate bool (MapTile t) {
2731 if (t == self) return false;
2735 //writeln("hit '", GetClassName(hasAnything.Class), "' (", hasAnything.objName, ":", hasAnything.objType, "): pos=(", hasAnything.ix, ",", hasAnything.iy, ")");
2736 // hit something, stop right there
2737 if (yVel > myGrav*3) playSound('sndThud');
2743 //writeln("DOOR STOP");
2752 // out-of-level check
2753 if (flty > level.tilesHeight*16+16) {
2757 } else if (status == WAIT) {
2760 if (isCollisionBottom(0)) flty -= 1;
2771 //setCollisionBounds(0, 0, 16, 8);
2782 desc2 = "The inside of this block carries an extendable wall.";
2784 spriteName = 'sDoor';
2785 toSpecialGrid = true; // it must think
2790 // ////////////////////////////////////////////////////////////////////////// //
2791 class MapTileSmashTrap['oSmashTrap'] : MapTile;
2793 //int thickness = 60;
2821 final string dirName () {
2823 case RIGHT: return "right";
2824 case LEFT: return "left";
2825 case UP: return "up";
2826 case DOWN: return "down";
2832 //setCollisionBounds(1, 1, 15, 15);
2833 override int x0 () { return round(fltx)+1; }
2834 override int width () { return 15; }
2835 //override int height () { return 16; }
2838 override void doSprayRubble () {
2839 if (global.cityOfGold == 1) {
2841 auto gold = level.MakeMapObject(ix+8+global.randOther(0, 4)-global.randOther(0, 4), iy+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldChunk');
2842 gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2843 gold.yVel = global.randOther(2, 4);
2846 auto gold = level.MakeMapObject(ix+8+global.randOther(0, 4)-global.randOther(0, 4), iy+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldNugget');
2847 gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2848 gold.yVel = global.randOther(2, 4);
2851 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2852 scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2857 override void setupTile () {
2858 // prevent smash trap from spawning in player range when level starts
2859 if (true /+global.config.bizarre /*or isRealLevel()*/ +/) {
2860 if (level.calcNearestEnterDist(ix, iy) < 90) {
2867 if (global.cityOfGold == 1) setSprite('sSmashTrapGold');
2868 dir = global.randRoom(0, 3);
2872 //FIXME: viscidMovement -- ???
2873 override void thinkFrame () {
2876 if (status == IDLE) {
2877 auto plr = level.player;
2878 auto dist = pointDistance(ix, iy, plr.ix, plr.iy);
2879 if (counter > 0) --counter;
2880 if (dist < 90 && counter < 1) {
2881 if (abs(plr.iy-(iy+8)) < 8 && plr.ix > ix+8 && !isCollisionRight(2)) {
2885 } else if (abs(plr.ix-(ix+8)) < 8 && plr.iy > iy+8 && !isCollisionBottom(2)) {
2889 } else if (abs(plr.iy-(iy+8)) < 8 && plr.ix < ix+8 && !isCollisionLeft(2)) {
2893 } else if (abs(plr.ix-(ix+8)) < 8 && plr.iy < iy+8 && !isCollisionTop(2)) {
2899 } else if (status == ATTACK) {
2900 bool colLeft = !!isCollisionLeft(1);
2901 bool colRight = !!isCollisionRight(1);
2902 bool colTop = !!isCollisionTop(1);
2903 bool colBot = !!isCollisionBottom(1);
2905 xv = fclamp(xv+xa, -4, 4);
2906 yv = fclamp(yv+ya, -4, 4);
2909 if (isCollisionRight(2) && colRight) { shiftX(-2); hit = true; }
2910 if (colRight) { shiftX(-1); hit = true; }
2911 } else if (dir == DOWN) {
2912 if (isCollisionBottom(2) && colBot) { shiftY(-2); hit = true; }
2913 if (colBot) { shiftY(-1); hit = true; }
2914 } else if (dir == LEFT) {
2915 if (isCollisionLeft(2) && colLeft) { shiftX(2); hit = true; }
2916 if (colLeft) { shiftX(1); hit = true; }
2917 } else if (dir == UP) {
2918 if (isCollisionTop(2) && colTop) { shiftY(2); hit = true; }
2919 if (colTop) { shiftY(1); hit = true; }
2922 if (level.isObjectInRect(ix-1, iy-1, 18, 18, delegate bool (MapObject o) { return (o isa EnemyTombLord); })) hit = true;
2933 auto ct = isCollision();
2934 writeln("dir=", dirName(), "; colLeft=", colLeft, "; colRight=", colRight, "; colTop=", colTop, "; colBot=", colBot, "; col=", (ct ? GetClassName(ct.Class) : '<none>'), " (", (ct ? ct.objName : '<>'), ")");
2938 if (hit && !isCollision() /*!colRight && !colLeft && !colTop && !colBot*/) {
2943 } else if (status == DEAD) {
2949 if (level.isLavaAtPoint(ix, iy-1)) { instanceRemove(); return; }
2952 if (level.checkTilesInRect(ix+1, iy+1, 15, 15, &level.cbCollisionLava)) status = DEAD;
2957 auto plr = level.player;
2958 if (!plr.dead && !plr.invincible && !plr.isExitingSprite() && /*plr.collidesWith(self)*/ plr.isRectHitSimple(x0-1, y0-1, width+2, height+2)) {
2960 if (!plr.collidesWith(self)) {
2961 bool onLeft = (plr.xVel < -0.01 && plr.x0 > x1);
2962 bool onRight = (plr.xVel > 0.01 && plr.x1 < x0);
2963 bool onTop = (plr.yVel < -0.01 && plr.y0 > y1);
2964 bool onBottom = (plr.yVel > 0.01 && plr.y1 < y0);
2965 if (!onLeft && !onRight && !onTop && !onBottom) {
2968 writeln("*** PLAYER RUNNING ON TRAP SPIKES");
2972 if (global.plife > 0) {
2973 global.plife -= global.config.crushDmg;
2974 if (global.plife <= 0 /*and isRealLevel()*/) level.addDeath(objName);
2978 playSound('sndHurt');
2983 level.isObjectInRect(x0-1, y0-1, width+2, height+2, delegate bool (MapObject o) {
2984 if (o == self) return false;
2985 if (o isa EnemyTombLord) return false;
2987 //if (o !isa MapEnemy) return false;
2989 // register hit only if we're moving onto a spikes
2990 if (!o.collidesWith(self)) {
2991 bool onLeft = (o.xVel < -0.01 && o.x0 > x1);
2992 bool onRight = (o.xVel > 0.01 && o.x1 < x0);
2993 bool onTop = (o.yVel < -0.01 && o.y0 > y1);
2994 bool onBottom = (o.yVel > 0.01 && o.y1 < y0);
2995 if (!onLeft && !onRight && !onTop && !onBottom) return false;
2996 writeln("*** RUNNING ON TRAP SPIKES");
3000 auto dms = MonsterDamsel(o);
3002 if (!dms.invincible) {
3003 if (dms.heldBy) dms.heldBy.holdItem = none;
3004 dms.hp -= global.config.crushDmg;
3006 dms.status = MapObject::THROWN;
3007 dms.counter = dms.stunMax;
3009 //dms.damselDropped = true;
3010 dms.kissCount = min(1, dms.kissCount);
3011 playSound('sndDamsel');
3017 auto enemy = MapEnemy(o);
3019 if (o.dead || o.status == DEAD || o.invincible) return false;
3020 if (o.heldBy) o.heldBy.holdItem = none;
3021 enemy.hp -= global.config.crushDmg;
3023 playSound('sndHit');
3028 }, castClass:MapEnemy);
3033 objType = 'oSmashTrap';
3034 objName = 'Smash Trap';
3035 desc = "Smash Trap";
3036 desc2 = "This trap carries an array of extendable blades. It can chase down intruders horizontally and vertically.";
3058 spriteName = 'sSmashTrap';
3059 toSpecialGrid = true; // it must think
3064 // ////////////////////////////////////////////////////////////////////////// //
3065 class MapTileSmashTrapLit['oSmashTrapLit'] : MapTileSmashTrap;
3068 spriteName = 'sSmashTrapLit';
3073 // ////////////////////////////////////////////////////////////////////////// //
3074 class MapTileGoldDoor['oGoldDoor'] : MapTile;
3084 override void doSprayRubble () {
3088 override void setupTile () {
3089 writeln("GENERATED GOLD DOOR");
3093 // it opens if player carrying a sceptre, and has a crown
3094 override void thinkFrame () {
3096 if (status == SCEPTRE && !global.hasCrown) return;
3097 auto plr = level.player;
3098 if (plr.holdItem !isa ItemWeaponSceptre) return;
3099 // check for collision
3100 if (plr.collidesWith(self)) {
3101 if (global.hasCrown) {
3102 // take sceptre away
3103 auto it = plr.holdItem;
3104 plr.holdItem = none;
3105 it.instanceRemove();
3106 playSound('sndChestOpen');
3107 level.MakeMapTile(ix/16, iy/16, 'oXGold');
3108 auto obj = level.MakeMapObject(ix-4, iy+6, 'oPoof');
3109 if (obj) obj.xVel = -0.4;
3110 obj = level.MakeMapObject(ix+16+4, iy+6, 'oPoof');
3111 if (obj) obj.xVel = 0.4;
3113 //level.osdMessage("THE MYSTERIOUS DOOR IS UNLOCKED!", 3.33);
3114 level.osdMessageTalk("THE MYSTERIOUS DOOR IS UNLOCKED!", timeout:3.33, inShopOnly:false);
3119 level.osdMessage("THE SCEPTRE FITS...\nBUT NOTHING IS HAPPENING!", 3.33);
3125 objType = 'oGoldDoor';
3127 desc2 = "A door with a golden seal on it.";
3134 spriteName = 'sGoldDoor';
3135 toSpecialGrid = true; // it must think
3141 // ////////////////////////////////////////////////////////////////////////// //
3142 class MapTileUnlockedGoldDoor['oXGold'] : MapTile;
3145 override void doSprayRubble () {
3149 override void setupTile () {
3153 // it opens if player carrying a sceptre, and has a crown
3154 override void thinkFrame () {
3161 desc2 = "A door with an opened golden seal on it.";
3166 spriteName = 'sExit';
3167 toSpecialGrid = false; // it must think
3174 // ////////////////////////////////////////////////////////////////////////// //
3175 class MapTileBlackMarketDoor['oXMarket'] : MapTile;
3178 override void doSprayRubble () {
3182 override void setupTile () {
3183 writeln("*** GENERATED BLACK MARKET ENTRANCE ***");
3185 if (global.hasSpectacles || global.hasUdjatEye) {
3186 level.setTileAt(ix/16, iy/16, none);
3193 // it opens if player carrying a sceptre, and has a crown
3194 override void thinkFrame () {
3199 objType = 'oXMarket';
3200 desc = "Market Exit";
3201 desc2 = "A door leading to Black Market.";
3206 spriteName = 'sExit';
3207 toSpecialGrid = true; // it must be hidden behind the normal tile
3214 // ////////////////////////////////////////////////////////////////////////// //
3215 class MapTileMoai['oMoai'] : MapTile;
3217 override int width () { return 16; }
3218 override int height () { return 64; }
3220 override void doSprayRubble () {}
3221 override void setupTile () {}
3222 override void thinkFrame () {}
3232 spriteName = 'sMoai';
3233 toSpecialGrid = true; // it is big
3238 // ////////////////////////////////////////////////////////////////////////// //
3239 class MapTileMoai3['oMoai3'] : MapTileMoai;
3242 spriteName = 'sMoai3';
3246 // ////////////////////////////////////////////////////////////////////////// //
3247 class MapTileMoaiInside['oMoaiInside'] : MapTile;
3249 override int width () { return 16; }
3250 override int height () { return 48; }
3252 override void doSprayRubble () {}
3253 override void setupTile () {}
3254 override void thinkFrame () {}
3257 objType = 'oMoaiInside';
3264 spriteName = 'sMoaiInside';
3265 toSpecialGrid = true; // it is big
3270 // ////////////////////////////////////////////////////////////////////////// //
3271 class MapTileLamp['oLamp'] : MapTile;
3276 override bool onExplosionTouch (MapObject xplo) {
3279 //level.MakeMapObject(x+8, y+12, (redLamp ? 'oLampRedItem' : 'oLampItem'));
3284 override void doSprayRubble () {
3285 if (global.cityOfGold == 1) {
3292 override void setupTile () {
3293 if (redLamp) spriteName = 'sLampRed';
3297 override void thinkFrame () {
3301 float llev = (trunc(imageFrame) < 2 ? 0.0 : 2.0-imageFrame/2.0);
3302 lightRadius = 128+round(4*llev);
3306 // drop lamp item if it has no support
3307 if (!level.checkTilesInRect(x+5, y-1, 6, 1)) {
3309 level.MakeMapObject(x+8, y+12, (redLamp ? 'oLampRedItem' : 'oLampItem'));
3318 desc2 = "A bright, geothermal-powered lantern, with a stainless steel frame.";
3322 spriteName = 'sLamp';
3323 toSpecialGrid = true; // it must think
3328 // ////////////////////////////////////////////////////////////////////////// //
3329 class MapTileLampRed['oLampRed'] : MapTileLamp;
3332 desc2 = "A geothermal-powered lantern that emits red light. Often found in brothels.";