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;
44 bool sacrificingAltar;
45 bool shopWall; // may be set to `true` for non-solid tiles too
49 bool platform; // oLadderTop, oLeaves, oTreeBranch
51 bool border; // border tile
53 bool litWholeTile; // lit whole tile with maximum light
54 bool ignoreFrameOffsetX, ignoreFrameOffsetY;
55 bool dontReplaceOthers; // this tile won't replace existing tiles when put on a map
56 bool immuneToReplacement; // this tile immune to replacement by other tiles
59 transient bool waterMoved; // any move
60 transient bool waterMovedDown;
61 transient int waterSlideCounter;
62 transient int waterSlideOldX, waterSlideOldY;
66 //float grav = 0.6; // the gravity
67 float myGravLimit = 8;
69 // x and y are offsets
74 bool gemDestroyWithTile;
78 void snapToExit (MapEntity e) {
79 if (!e || !e.isInstanceAlive) return;
86 // ////////////////////////////////////////////////////////////////////////// //
87 override void Destroy () {
106 override void onLoaded () {
108 if (spriteName) sprite = level.sprStore[spriteName];
109 for (MapBackTile bt = bgback; bt; bt = bt.next) bt.onLoaded();
110 for (MapBackTile bt = bgfront; bt; bt = bt.next) bt.onLoaded();
111 if (gem) gem.onLoaded();
115 string getExitMessage () {
120 override SpriteImage getSprite (optional out bool doMirror) {
126 override SpriteFrame getSpriteFrame (optional out bool doMirror, optional out int x0, optional out int y0, optional out int x1, optional out int y1) {
127 auto spr = getSprite(doMirror!optional);
128 if (!spr || spr.frames.length == 0) return none;
129 auto spf = spr.frames[trunc(imageFrame)%spr.frames.length];
130 if (!spf) return none;
131 if (specified_x0 || specified_x1) {
132 x0 = (ignoreFrameOffsetX ? 0 : -spf.xofs);
133 x1 = x0+spf.tex.width;
135 if (specified_y0 || specified_y1) {
136 y0 = (ignoreFrameOffsetY ? 0 : -spf.yofs);
137 y1 = y0+spf.tex.height;
143 override void clearSprite () {
149 override void setSprite (name sprNameL, optional name sprNameR) {
150 if (!sprNameL && sprNameR) sprNameL = sprNameR;
151 if (spriteName != sprNameL) {
152 spriteName = sprNameL;
153 sprite = (sprNameL ? level.sprStore[sprNameL] : none);
159 // ////////////////////////////////////////////////////////////////////////// //
160 override int width () { return Width; }
161 override int height () { return Height; }
164 void beautifyTile () {}
165 void scrSetupBlockTop () {}
166 void scrSetupBlockBottom () {}
167 void scrSetupBlockLeft () {}
168 void scrSetupBlockRight () {}
171 // ////////////////////////////////////////////////////////////////////////// //
172 void setGem (name oname, optional bool visibility) {
174 gem.ownerTile = none;
175 gem.instanceRemove();
179 gem = level.MakeMapObject(ix+8, iy+8, oname);
181 gem.ownerTile = self;
182 gem.hiddenTreasure = !global.hasSpectacles && !global.hasUdjatEye;
183 if (specified_visibility) gem.hiddenTreasure = !visibility;
186 if (gem.hiddenTreasure) gem.visible = false;
187 gem.saveInterpData();
191 void setGemObject (MapObject obj, optional bool visibility) {
193 if (obj.ownerTile == self) {
194 if (gem != obj) FatalError("MapTile::setGemObject: WTF?! (0)");
196 if (gem == obj) FatalError("MapTile::setGemObject: WTF?! (1)");
199 gem.ownerTile = none;
200 gem.instanceRemove();
201 if (!gem.grid) delete gem;
206 obj.ownerTile.gem = none;
207 obj.ownerTile = none;
210 gem.ownerTile = self;
211 gem.hiddenTreasure = !global.hasSpectacles && !global.hasUdjatEye;
212 if (specified_visibility) gem.hiddenTreasure = !visibility;
215 if (gem.hiddenTreasure) gem.visible = false;
218 gem.saveInterpData();
222 void convertGemObjectToDiamond (MapObject ghost) {
224 if (oldgem && oldgem isa ItemBigGem) {
226 if (!ghost.isInstanceAlive) return;
227 if (!gem.isRectHitSimple(ghost.x0, ghost.y0, ghost.width, ghost.height)) return;
229 auto diamond = level.MakeMapObject(oldgem.ix, oldgem.iy, 'oDiamond');
230 setGemObject(diamond);
235 // ////////////////////////////////////////////////////////////////////////// //
236 void doCreateOre (int probSap, int probEmer, int probRuby, int probItem) {
237 int n = global.randRoom(1, 100);
238 if (n < 20) ore = 1; // sprite_index = sBrickGold;
239 else if (n < 30) ore = 2; // sprite_index = sBrickGoldBig;
240 if (probSap > 0 && global.randRoom(1, probSap) == 1) setGem('oSapphireBig');
241 else if (probEmer > 0 && global.randRoom(1, probEmer) == 1) setGem('oEmeraldBig');
242 else if (probRuby > 0 && global.randRoom(1, probRuby) == 1) setGem('oRubyBig');
243 else if (probItem > 0 && global.randRoom(1, probItem) == 1) setGemObject(level.scrGenerateItem(ix+8, iy+8, GameLevel::GenItemSet.Underground));
247 void copyOreFrom (MapTile t) {
248 if (t == self) return;
265 if (exit) setSprite('sEntrance');
270 if (exit) setSprite('sExit');
274 bool isExitActive () {
275 return (visible && exit && spriteName == 'sExit');
279 // ////////////////////////////////////////////////////////////////////////// //
283 case 'oLadderOrange':
284 //objName = 'oLadder';
285 //goto case 'oLadder';
290 spriteName = 'sLadder';
294 //ladder = true; // no, laddertop is not a ladder
298 spriteName = 'sLadderTop';
301 objType = 'oVineTop';
302 //ladder = true; // no, laddertop is not a ladder
306 spriteName = 'sVineTop';
309 //objName = 'oPushBlock';
312 spriteName = (global.cityOfGold == 1 ? 'sGoldBlock' : 'sBlock');
314 case 'oPushIceBlock':
315 //objName = 'oPushBlock';
319 spriteName = 'sIceBlock';
321 case 'oSolidIceBlock':
322 //objName = 'oIceBlock';
323 //goto case 'oIceBlock';
328 moveable = true; //k8: why, let it be an easter egg
329 spriteName = 'sIceBlock';
336 spriteName = 'sIceBlock';
339 // no sense to create ore here
341 spriteName = (global.randRoom(1, 10) == 1 ? 'sBrick2' : 'sBrick');
345 spriteName = 'sCaveSmooth';
352 spriteName = 'sSpikesWood';
358 spriteName = 'sSpikes';
361 objType = 'oEntrance';
366 spriteName = 'sEntrance';
374 spriteName = 'sExit';
379 spriteName = 'sAltarLeft';
384 spriteName = 'sAltarRight';
386 case 'oSacAltarLeft':
389 sacrificingAltar = true;
390 spriteName = 'sSacAltarLeft';
392 case 'oSacAltarRight':
395 sacrificingAltar = true;
396 spriteName = 'sSacAltarRight';
401 spriteName = 'sSign';
406 spriteName = 'sSignGeneral';
411 spriteName = 'sSignBomb';
416 spriteName = 'sSignWeapon';
418 case 'oSignClothing':
421 spriteName = 'sSignClothing';
426 spriteName = 'sSignRare';
431 spriteName = 'sSignCraps';
436 spriteName = 'sSignKissing';
443 spriteName = 'sMoai';
450 spriteName = 'sMoai2';
457 spriteName = 'sMoai3';
462 objType = 'oMoaiInside';
465 spriteName = 'sMoaiInside';
470 FatalError(va("unknown map tile type '%n'", objName));
472 //!active = moveable || toSpecialGrid || lava || water; // will be done in MakeMapTile
473 //if (!solid) hangeable = false; // just in case
477 void setupTileSprite () {
478 if (!spriteName) FatalError("forgot to set sprite for tile type '", objName, "'");
479 sprite = level.sprStore[spriteName];
483 override bool initialize () {
484 if (!::initialize()) return false;
487 if (moveable && depth == 1001) depth = 1000;
488 if ((exit || enter) && depth == 1001) depth = 2000;
490 ++level.liquidTileCount;
491 level.checkWater = true;
493 // will be done in MakeMapTile
495 // animated tiles must be active
497 auto spr = getSprite();
498 if (spr && spr.frames.length > 1) {
499 writeln("activated animated tile '", objName, "'");
508 // ////////////////////////////////////////////////////////////////////////// //
509 // for now, it works only for spikes
510 final void makeBloody () {
514 setSprite(woodenSpikes ? 'sSpikesWoodBlood' : 'sSpikesBlood');
518 // ////////////////////////////////////////////////////////////////////////// //
519 final void appendBackBack (MapBackTile tile, optional int yofs) {
521 if (tile.next) FatalError("MapTile::appendBackBack: wtf?!");
522 tile.flty = (specified_yofs ? float(yofs) : 16.0);
523 MapBackTile last = bgback;
525 while (last.next) last = last.next;
533 final void appendBackFront (MapBackTile tile, optional int yofs) {
535 if (tile.next) FatalError("MapTile::appendBackFront: wtf?!");
536 tile.flty = (specified_yofs ? float(yofs) : -16.0);
537 MapBackTile last = bgfront;
539 while (last.next) last = last.next;
547 // ////////////////////////////////////////////////////////////////////////// //
549 override void onDestroy () {
554 // ////////////////////////////////////////////////////////////////////////// //
555 override void thinkFrame () {
556 if (!moveable) return;
557 // applies the acceleration
561 // approximates the "active" variables
562 if (fabs(xVel) < 0.001) xVel = 0;
563 if (fabs(yVel) < 0.001) yVel = 0;
564 //if (fabs(xAcc) < 0.0001) xAcc = 0;
565 //if (fabs(yAcc) < 0.0001) yAcc = 0;
567 yVel = fclamp(yVel+myGrav, -myGravLimit, myGravLimit);
568 int newY = round(flty+yVel); //!!!
569 // check if we need (and can) move down
570 int w = width, hp1 = height+1;
571 auto oldsolid = solid;
574 // made non-solid temporarily
575 auto hasAnything = level.checkTilesInRect(x0, y0, w, hp1, delegate bool (MapTile t) {
576 if (t == self) return false;
580 // hit something, stop right there
581 if (yVel > myGrav*3) playSound('sndThud');
591 if (flty > level.tilesHeight*16+16 && yVel >= 0) {
598 // ////////////////////////////////////////////////////////////////////////// //
599 final void getInterpCoordsForTile (float currFrameDelta, int scale, out int drwx, out int drwy) {
600 if (waterSlideCounter) {
604 int sgnx = sign(waterSlideOldX-ix);
605 int sgny = sign(waterSlideOldY-iy);
606 if ((sgnx|sgny) == 0) {
607 waterSlideCounter = 0;
609 drwx += (sgnx*(waterSlideCounter*4))*scale;
610 drwy += (sgny*(waterSlideCounter*4))*scale;
613 getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
618 void drawWithOfsBack (int xpos, int ypos, int scale, float currFrameDelta) {
619 //if (backTile) backTile.drawAt(xpos, ypos, scale);
620 if (invisible || !visible || !bgback) return;
624 int fx0, fy0, fx1, fy1;
625 auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
627 // non-moveable and non-special tiles need not to be interpolated
629 getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
632 for (MapBackTile bt = bgback; bt; bt = bt.next) {
635 bt.drawWithOfs(xpos, ypos, scale, currFrameDelta);
642 void drawWithOfsFront (int xpos, int ypos, int scale, float currFrameDelta) {
643 //if (backTile) backTile.drawAt(xpos, ypos, scale);
644 if (invisible || !visible || !bgfront) return;
648 int fx0, fy0, fx1, fy1;
649 auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
651 // non-moveable tiles need not to be interpolated
653 getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
656 for (MapBackTile bt = bgfront; bt; bt = bt.next) {
659 bt.drawWithOfs(xpos, ypos, scale, currFrameDelta);
666 override void drawWithOfs (int xpos, int ypos, int scale, float currFrameDelta) {
667 if (invisible || !visible) return;
670 int fx0, fy0, fx1, fy1;
671 auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
673 // non-moveable tiles need not to be interpolated
675 getInterpCoordsForTile(currFrameDelta, scale, out drwx, out drwy);
677 //auto oclr = Video.color;
678 //if (moveable) Video.color = 0xff_7f_00;
681 fx0 = drwx+fx0*scale-xpos;
682 fy0 = drwy+fy0*scale-ypos;
683 fx1 = drwx+fx1*scale-xpos;
684 fy1 = drwy+fy1*scale-ypos;
686 spf.tex.blitExt(fx0, fy0, fx1, fy1, 0, 0, spf.tex.width, spf.tex.height);
688 spf.tex.blitExt(fx0, fy0, fx1, fy1, spf.tex.width, 0, 0, spf.tex.height);
690 } else if (ore > 0) {
697 //Video.color = oclr;
699 if (ore > 0 && !hideOre) {
700 auto ospr = level.sprStore[ore == 1 ? 'sGold' : 'sGoldBig'];
701 ospr.frames[0].tex.blitAt(fx0, fy0, scale);
706 // ////////////////////////////////////////////////////////////////////////// //
707 // called by Destroy event of blocks that can contain ore (oBrick, oLush, etc)
713 foreach (int i; 0..3) {
714 auto gold = level.MakeMapObject(x+8+global.randOther(0, 4)-global.randOther(0, 4), y+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldChunk');
716 //gold = instance_create(x+8+rand(0,4)-rand(0,4), y+8+rand(0,4)-rand(0,4), oGoldChunk);
717 gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
718 gold.yVel = global.randOther(2, 4);
724 auto gold = level.MakeMapObject(x+8+global.randOther(0, 4)-global.randOther(0, 4), y+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldNugget');
726 gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
727 gold.yVel = global.randOther(2, 4);
735 // ////////////////////////////////////////////////////////////////////////// //
736 private final bool cbIsSolidBrick (MapTile t) { return t.solid; }
737 private final bool cbIsBrickOrLushTile (MapTile t) { return (t.objType == 'oBrick' || t.objType == 'oLush'); }
740 // ////////////////////////////////////////////////////////////////////////// //
741 // called by solids during destroy event (oBrick etc) when destroy with force such as a boulder or explosion
742 // these rubble bits shower over the foreground and are not stopped by solids
743 final void scrSprayRubble (int x, int y, int count, name spr0, name spr1) {
744 while (count-- > 0) {
745 auto rubble = level.MakeMapObject(
746 x+8+global.randOther(0, 8)-global.randOther(0, 8),
747 y+8+global.randOther(0, 8)-global.randOther(0, 8), 'oRubblePiece'); //'oRubblePieceNonSolid');
748 rubble.setSprite(global.randOther(1, 3) == 1 ? spr0 : spr1);
749 rubble.xVel = global.randOther(0, 4)-global.randOther(0, 4);
750 rubble.yVel = -global.randOther(4, 8);
752 //rubble.image_blend = image_blend;
757 final void scrDropRubble (int x, int y, int count, name spr0, name spr1) {
758 while (count-- > 0) {
759 int rx = x+8+global.randOther(0, 8)-global.randOther(0, 8);
760 int ry = y+8+global.randOther(0, 8)-global.randOther(0, 8);
761 if (global.randOther(1, 3) == 1) {
762 auto rubble = level.MakeMapObject(rx, ry, 'oRubble');
763 rubble.setSprite(spr1);
764 //rubble.image_blend = image_blend;
766 auto rubble = level.MakeMapObject(rx, ry, 'oRubbleSmall');
767 rubble.setSprite(spr0);
768 //rubble.image_blend = image_blend;
774 void smashedCheckUpTile () {
775 level.checkTilesInRect(ix, iy-16, 16, 16, delegate bool (MapTile t) {
776 //writeln("mtl: '", GetClassName(t.Class), "'; spikes=", t.spikes);
777 if (t.isInstanceAlive && (t.spikes || t isa MapTileGraveBase)) t.instanceRemove();
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;
907 private final bool cbExplodeTiles (MapTile t) {
908 if (!t.isInstanceAlive) return false;
909 if (t.invincible) return false;
916 t.onExplosionTouch(mXplo);
923 override bool onExplosionTouch (MapObject xplo) {
925 //writeln("ignore inv block at (", ix/16, ",", iy/16, ")");
932 //writeln("smashed block at (", ix/16, ",", iy/16, ")");
935 level.checkTileAtPoint(x+8, y-1, &cbExplodeTiles); //k8: why i wrote this?!
939 obj = instance_place(x, y, oGold);
940 if (obj != noone) with (obj) instance_destroy();
942 obj = instance_place(x, y, oGoldBig);
943 if (obj != noone) with (obj) instance_destroy();
948 //writeln("cannot smash block at (", ix/16, ",", iy/16, ")");
955 // ////////////////////////////////////////////////////////////////////////// //
956 void onGotSpectacles () {
958 gem.hiddenTreasure = false;
967 //depth = 9666; //???
972 // ////////////////////////////////////////////////////////////////////////// //
973 // this tile is returned instead of walkeable MapObject
974 class MapTileTemp : MapTile;
978 // don't forget to set `fltx` and `flty`!
979 override int x0 () { return (e ? e.x0 : 0); }
980 override int y0 () { return (e ? e.y0 : 0); }
981 override int width () { return (e ? e.width : 0); }
982 override int height () { return (e ? e.height : 0); }
985 objName = 'oMapObject';
986 objType = 'oMapObject';
987 active = false; // just in case
991 // ////////////////////////////////////////////////////////////////////////// //
992 class MapTileBrick['oBrick'] : MapTile;
995 override void setupTile () {
996 spriteName = (global.randRoom(1, 10) == 1 ? 'sBrick2' : 'sBrick');
997 int tileX = ix/16, tileY = iy/16;
998 if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1001 //writeln("BORDER");
1003 doCreateOre(100, 120, 140, 1200);
1008 override void beautifyTile () {
1009 if (level.checkTileAtPoint(ix+8, iy-8, delegate bool (MapTile t) { return (t isa TitleTileSpecialNonSolidInvincible || t isa TitleTileBlack); })) return;
1012 //if (argument1) if (global.randRoom(1, 10) == 1) sprite_index = sBrick2; // reset sprite for custom border setup
1014 int tileX = ix/16, tileY = iy/16;
1016 auto tt = level.getTileAtGrid(tileX, tileY-1);
1017 if (tt && tt.objName == 'oBlock') tt = none;
1018 //if (y == 0 || isNamedTileAt(x, y-1, 'oBrick') || isNamedTileAt(x, y-1, 'oHardBlock') || isNamedTileAt(x, y-1, 'oEdgeBrick')) up = true;
1019 bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1021 tt = level.getTileAtGrid(tileX, tileY+1);
1022 //if (y == NormalTilesHeight-1 || isNamedTileAt(x, y+1, 'oBrick') || isNamedTileAt(x, y+1, 'oHardBlock') || isNamedTileAt(x, y+1, 'oEdgeBrick')) down = true;
1023 bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1025 //if (collision_point(x-16, y, oBrick, 0, 0) or collision_point(x-16, y, oHardBlock, 0, 0)) { left = true; } // in the original
1026 //if (collision_point(x+16, y, oBrick, 0, 0) or collision_point(x+16, y, oHardBlock, 0, 0)) { right = true; } // in the original
1029 setSprite('sCaveUp');
1030 // add rocks and sand
1032 if (global.randRoom(1, 3) < 3) {
1033 tile_add('bgCaveTop', 0, 0, 16, 16, x*16, y*16-16, 3);
1035 tile_add('bgCaveTop', 16, 0, 16, 16, x*16, y*16-16, 3);
1038 bool n = (global.randRoom(1, 3) < 3);
1039 appendBackFront(level.CreateBgTile('bgCaveTop', (n ? 0 : 16)));
1040 //instance_create(x, y-16, oCaveTop);
1044 setSprite(!up ? 'sCaveUp2' : 'sBrickDown');
1045 //instance_create(x, y+16, oCaveBottom);
1049 if (not left) instance_create(x-16, y, oCaveLeft);
1050 if (not right) instance_create(x+15, y, oCaveRight);
1055 override void scrSetupBlockTop () {
1056 //int x = ix, y = iy;
1057 int tileX = ix/16, tileY = iy/16;
1058 auto tt = level.getTileAtGrid(tileX, tileY+1);
1059 //if (y >= (GameLevel::NormalTilesHeight-16)*16 || level.checkTileAtPoint(x, y+16, &cbIsBrickOrLushTile)) down = true;
1060 bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1062 auto bt = level.CreateBgTile('bgCaveTop', (global.randRoom(1, 3) < 3 ? 0 : 16));
1063 //bt.flty = -16; // offset
1064 appendBackFront(bt);
1066 setSprite(down ? 'sCaveUp' : 'sCaveUp2');
1070 override void scrSetupBlockBottom () {
1071 //int x = ix, y = iy;
1073 //if (y == 0 || level.checkTileAtPoint(x, y-16, &cbIsBrickOrLushTile)) up = true;
1074 int tileX = ix/16, tileY = iy/16;
1075 auto tt = level.getTileAtGrid(tileX, tileY-1);
1076 bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1077 setSprite(up ? 'sBrickDown' : 'sCaveUp2');
1083 spriteName = 'sBrick';
1086 toSpecialGrid = false;
1090 // ////////////////////////////////////////////////////////////////////////// //
1091 class MapTileHardBrick['oHardBlock'] : MapTileBrick;
1093 override void setupTile () {
1098 objType = 'oHardBlock';
1099 spriteName = 'sBrick';
1105 // ////////////////////////////////////////////////////////////////////////// //
1106 class MapTileBlock['oBlock'] : MapTile;
1109 override void setupTile () {
1110 int tileX = ix/16, tileY = iy/16;
1111 if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1114 //writeln("BORDER");
1116 if (global.cityOfGold == 1) spriteName = 'sGoldBlock';
1120 final bool isBBTTile (MapTile t) { return (t.objType == 'oBrick' || t.objType == 'oTemple' || t.objType == 'oHardBlock'); }
1123 override void beautifyTile () {
1124 bool down = !!level.checkTileAtPoint(ix, iy+16, &isBBTTile);
1126 // don't want push blocks next to lava until we tighten up liquid draining
1127 if (level.isLavaAtPoint(ix-16, iy) || level.isLavaAtPoint(ix+16, iy)) down = false;
1129 if (down && global.randRoom(1, 4) == 1) {
1131 level.MakeMapTile(ix/16, iy/16, 'oPushBlock');
1138 spriteName = 'sBlock';
1140 toSpecialGrid = false;
1144 // ////////////////////////////////////////////////////////////////////////// //
1145 class MapTileLush['oLush'] : MapTile;
1148 override void setupTile () {
1149 int tileX = ix/16, tileY = iy/16;
1150 if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1153 //writeln("BORDER");
1155 doCreateOre(80, 100, 120, 1200);
1160 override void beautifyTile () {
1161 if (level.checkTileAtPoint(ix+8, iy-8, delegate bool (MapTile t) { return (t isa TitleTileSpecialNonSolidInvincible || t isa TitleTileBlack); })) return;
1163 int tileX = ix/16, tileY = iy/16;
1165 auto tt = level.getTileAtGrid(tileX, tileY-1);
1166 //if (y == 0 || isNamedTileAt(x, y-1, 'oBrick') || isNamedTileAt(x, y-1, 'oHardBlock') || isNamedTileAt(x, y-1, 'oEdgeBrick')) up = true;
1167 bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral && (tt.objType == 'oLush' || tt.objType == 'oTemple')));
1169 tt = level.getTileAtGrid(tileX, tileY+1);
1170 //if (y == NormalTilesHeight-1 || isNamedTileAt(x, y+1, 'oBrick') || isNamedTileAt(x, y+1, 'oHardBlock') || isNamedTileAt(x, y+1, 'oEdgeBrick')) down = true;
1171 bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral && tt.objType == 'oLush'));
1174 setSprite('sLushUp');
1175 if (!level.isLavaAtPoint(ix+8, iy-8)) {
1177 if (global.randRoom(1, 8) == 1) bt = level.CreateBgTile('bgCaveTop2', 32);
1178 else if (global.randRoom(1, 3) < 3) bt = level.CreateBgTile('bgCaveTop2', 0);
1179 else bt = level.CreateBgTile('bgCaveTop2', 16);
1180 appendBackFront(bt);
1185 setSprite(!up ? 'sLushUp2' : 'sLushDown');
1186 if (!level.isSolidAtPoint(ix, iy+16) && !level.isLavaAtPoint(ix, iy+16)) {
1188 if (global.randRoom(1, 12) == 1) bt = level.CreateBgTile('bgCaveTop2', 48);
1189 else if (global.randRoom(1, 12) == 1) bt = level.CreateBgTile('bgCaveTop2', 64);
1192 //instance_create(x, y+16, oLushBottom); // in the original
1197 override void scrSetupBlockTop () {
1198 int tileX = ix/16, tileY = iy/16;
1199 auto tt = level.getTileAtGrid(tileX, tileY+1);
1200 bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1201 //setSprite('sLushUpBare');
1203 setSprite(spriteName == 'sLushUp' || spriteName == 'sLushUp2' || spriteName == 'sLushUp3' ? 'sLushUpBareTop' : 'sLushUpBare2');
1205 setSprite('sLushUpBare');
1210 override void scrSetupBlockBottom () {
1211 int tileX = ix/16, tileY = iy/16;
1212 auto tt = level.getTileAtGrid(tileX, tileY-1);
1213 bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1215 setSprite(spriteName == 'sLushUp' || spriteName == 'sLushUp2' || spriteName == 'sLushUp3' ? 'sLushUpBareBottom' : 'sLushUpBare2');
1217 setSprite('sLushDownBare');
1224 spriteName = 'sLush';
1227 toSpecialGrid = false;
1231 // ////////////////////////////////////////////////////////////////////////// //
1232 class MapTileDark['oDark'] : MapTile;
1235 override void setupTile () {
1236 int tileX = ix/16, tileY = iy/16;
1237 if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1240 //writeln("BORDER");
1242 doCreateOre(40, 60, 80, 1200);
1247 override void beautifyTile () {
1248 if (level.checkTileAtPoint(ix+8, iy-8, delegate bool (MapTile t) { return (t isa TitleTileSpecialNonSolidInvincible || t isa TitleTileBlack); })) return;
1251 //if (argument1) if (global.randRoom(1, 10) == 1) sprite_index = sBrick2; // reset sprite for custom border setup
1253 int tileX = ix/16, tileY = iy/16;
1255 auto tt = level.getTileAtGrid(tileX, tileY-1);
1256 //if (y == 0 || isNamedTileAt(x, y-1, 'oBrick') || isNamedTileAt(x, y-1, 'oHardBlock') || isNamedTileAt(x, y-1, 'oEdgeBrick')) up = true;
1257 bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1259 tt = level.getTileAtGrid(tileX, tileY+1);
1260 //if (y == NormalTilesHeight-1 || isNamedTileAt(x, y+1, 'oBrick') || isNamedTileAt(x, y+1, 'oHardBlock') || isNamedTileAt(x, y+1, 'oEdgeBrick')) down = true;
1261 bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1263 //if (collision_point(x-16, y, oBrick, 0, 0) or collision_point(x-16, y, oHardBlock, 0, 0)) { left = true; } // in the original
1264 //if (collision_point(x+16, y, oBrick, 0, 0) or collision_point(x+16, y, oHardBlock, 0, 0)) { right = true; } // in the original
1267 setSprite('sDarkUp');
1268 bool n = (global.randRoom(1, 3) < 3);
1269 appendBackFront(level.CreateBgTile('bgCaveTop3', (n ? 0 : 16)));
1273 setSprite(!up ? 'sDarkUp2' : 'sDarkDown');
1278 override void scrSetupBlockTop () {
1279 int tileX = ix/16, tileY = iy/16;
1280 auto tt = level.getTileAtGrid(tileX, tileY+1);
1281 bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1282 auto bt = level.CreateBgTile('bgCaveTop3', (global.randRoom(1, 3) < 3 ? 0 : 16));
1283 appendBackFront(bt);
1284 //bt.flty = -16; // offset
1286 setSprite(down ? 'sDarkUp' : 'sDarkUp2');
1290 override void scrSetupBlockBottom () {
1291 int tileX = ix/16, tileY = iy/16;
1292 auto tt = level.getTileAtGrid(tileX, tileY-1);
1293 bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1294 setSprite(up ? 'sDarkDown' : 'sDarkUp2');
1300 spriteName = 'sDark';
1303 toSpecialGrid = false;
1307 // ////////////////////////////////////////////////////////////////////////// //
1308 class MapTileIce['oIce'] : MapTile;
1313 final bool isIceTile (MapTile t) { return t.ice; }
1314 final MapTile isIceTileAtPoint (int x, int y) { return level.checkTileAtPoint(x, y, &isIceTile); }
1317 override void setupTile () {
1318 if (global.randRoom(1, 80) == 1) {
1319 setGem('oFrozenCaveman', true); // always visible
1322 gem.saveInterpData();
1323 gemDestroyWithTile = true;
1325 dripTimer = global.randOther(20, 400);
1326 //hasIceBottom = true;
1331 override void thinkFrame () {
1333 if (--dripTimer <= 0) {
1335 level.MakeMapObject(ix+8, iy+16+4, 'oDrip');
1336 dripTimer = global.randOther(20, 400);
1343 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1344 bool up = (specified_noup && noup ? false : !!isIceTileAtPoint(ix, iy-16));
1345 bool down = (specified_nodown && nodown ? false : !!isIceTileAtPoint(ix, iy+16));
1346 bool left = (specified_noleft && noleft ? false : !!isIceTileAtPoint(ix-16, iy));
1347 bool right = (specified_noright && noright ? false : !!isIceTileAtPoint(ix+16, iy));
1349 if (!up) setSprite('sIceUp');
1351 setSprite(!up ? 'sIceUp2' : 'sIceDown');
1352 if (!level.isSolidAtPoint(ix, iy+16) && global.randOther(1, 20) == 1) hasIceBottom = true;
1355 if (!up && !down) setSprite('sIceUDL');
1356 else if (!up) setSprite('sIceUL');
1357 else if (!down) setSprite('sIceDL');
1358 else setSprite('sIceLeft');
1361 if (!up && !down) setSprite('sIceUDR');
1362 else if (!up) setSprite('sIceUR');
1363 else if (!down) setSprite('sIceDR');
1364 else setSprite('sIceRight');
1366 if (!up && !left && !right && down) setSprite('sIceULR');
1367 if (!down && !left && !right && up) setSprite('sIceDLR');
1368 if (up && down && !left && !right) setSprite('sIceLR');
1369 if (!up && !down && !left && !right) setSprite('sIceBlock');
1374 override void beautifyTile () {
1376 bool up = !!isIceTileAtPoint(ix, iy-16);
1377 bool down = !!isIceTileAtPoint(ix, iy+16);
1378 bool left = !!isIceTileAtPoint(ix-16, iy);
1379 bool right = !!isIceTileAtPoint(ix+16, iy);
1381 //writeln("beautifying ice; l=", left, "; r=", right, "; u=", up, "; d=", down);
1383 if (!up) setSprite('sIceUp');
1385 setSprite(!up ? 'sIceUp2' : 'sIceDown');
1386 if (!level.isSolidAtPoint(ix, iy+16) && global.randOther(1, 20) == 1) hasIceBottom = true;
1389 if (!up && !down) setSprite('sIceUDL');
1390 else if (!up) setSprite('sIceUL');
1391 else if (!down) setSprite('sIceDL');
1392 else setSprite('sIceLeft');
1395 if (!up && !down) setSprite('sIceUDR');
1396 else if (!up) setSprite('sIceUR');
1397 else if (!down) setSprite('sIceDR');
1398 else setSprite('sIceRight');
1400 if (!up && !left && !right && down) setSprite('sIceULR');
1401 if (!down && !left && !right && up) setSprite('sIceDLR');
1402 if (up && down && !left && !right) setSprite('sIceLR');
1403 if (!up && !down && !left && !right) setSprite('sIceBlock');
1405 setupNiceTileSprite();
1409 override void scrSetupBlockTop () {
1410 setupNiceTileSprite(/*noup:true*/);
1413 bool down = !!isIceTileAtPoint(ix, iy+16);
1414 bool left = !!isIceTileAtPoint(ix-16, iy);
1415 bool right = !!isIceTileAtPoint(ix+16, iy);
1417 setSprite('sIceUp');
1419 if (!down) setSprite('sIceUp2');
1421 if (!left) setSprite(!up ? 'sIceUDL' : 'sIceUL');
1422 if (!right && !down) setSprite('sIceUDR');
1424 if (!left && !right && down) setSprite('sIceULR');
1425 if (!down && !left && !right) setSprite('sIceBlock');
1430 override void scrSetupBlockBottom () {
1431 setupNiceTileSprite(/*nodown:true*/);
1433 bool up = !!isIceTileAtPoint(ix, iy-16);
1434 bool left = !!isIceTileAtPoint(ix-16, iy);
1435 bool right = !!isIceTileAtPoint(ix+16, iy);
1437 setSprite(!up ? 'sIceUp2' : 'sIceDown');
1439 if (global.randOther(1, 20) == 1) hasIceBottom = true; //instance_create(x, y+16, oIceBottom);
1441 if (!left) setSprite(!up ? 'sIceUDL' : 'sIceDL');
1442 if (!right) setSprite(!up ? 'sIceUDR' : 'sIceDR');
1444 if (!left && !right && up) setSprite('sIceDLR');
1445 if (!up && !left && !right) setSprite('sIceBlock');
1450 override void scrSetupBlockLeft () {
1451 setupNiceTileSprite(/*noright:true*/);
1453 bool up = !!isIceTileAtPoint(ix, iy-16);
1454 bool down = !!isIceTileAtPoint(ix, iy+16);
1455 bool left = !!isIceTileAtPoint(ix-16, iy);
1458 if (!up) setSprite('sIceUp');
1459 if (!down) { if (!up) setSprite('sIceUp2'); else setSprite('sIceDown'); }
1461 if (!up && !down) setSprite('sIceUDL');
1462 else if (!up) setSprite('sIceUL');
1463 else if (!down) setSprite('sIceDL');
1464 else setSprite('sIceLeft');
1467 if (!up && !down) setSprite('sIceUDR');
1468 else if (!up) setSprite('sIceUR');
1469 else if (!down) setSprite('sIceDR');
1470 else setSprite('sIceRight');
1472 if (!up && !left && !right && down) setSprite('sIceULR');
1473 if (!down && !left && !right && up) setSprite('sIceDLR');
1474 if (up && down && !left && !right) setSprite('sIceLR');
1475 if (!up && !down && !left && !right) setSprite('sIceBlock');
1480 override void scrSetupBlockRight () {
1481 setupNiceTileSprite(/*noleft:true*/);
1483 bool up = !!isIceTileAtPoint(ix, iy-16);
1484 bool down = !!isIceTileAtPoint(ix, iy+16);
1486 bool right = !!isIceTileAtPoint(ix+16, iy);
1488 if (!up) setSprite('sIceUp');
1489 if (!down) { if (!up) setSprite('sIceUp2'); else setSprite('sIceDown'); }
1491 if (!up && !down) setSprite('sIceUDL');
1492 else if (!up) setSprite('sIceUL');
1493 else if (!down) setSprite('sIceDL');
1494 else setSprite('sIceLeft');
1497 if (!up && !down) setSprite('sIceUDR');
1498 else if (!up) setSprite('sIceUR');
1499 else if (!down) setSprite('sIceDR');
1500 else setSprite('sIceRight');
1502 if (!up && !left && !right && down) setSprite('sIceULR');
1503 if (!down && !left && !right && up) setSprite('sIceDLR');
1504 if (up && down && !left && !right) setSprite('sIceLR');
1505 if (!up && !down && !left && !right) setSprite('sIceBlock');
1512 spriteName = 'sIce';
1515 toSpecialGrid = true; // want to think
1519 // ////////////////////////////////////////////////////////////////////////// //
1520 class MapTileTemple['oTemple'] : MapTile;
1525 final bool isTempleTile (MapTile t) { return (t.objType == 'oTemple' || t.border); } // fake too
1526 final MapTile isTempleTileAtPoint (int x, int y) { return level.checkTileAtPoint(x, y, &isTempleTile); }
1529 override void setupTile () {
1530 setSprite(global.cityOfGold == 1 ? 'sGTemple' : 'sTemple');
1531 int tileX = ix/16, tileY = iy/16;
1532 if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1535 //writeln("BORDER");
1537 doCreateOre(60, 80, 100, 1200);
1543 override void thinkFrame () {
1545 if (--dripTimer <= 0) {
1547 level.MakeMapObject(ix+8, iy+16+4, 'oDrip');
1548 dripTimer = global.randOther(20, 400);
1556 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1557 bool up = (iy <= 0 ? true : (specified_noup && noup ? false : !!isTempleTileAtPoint(ix, iy-16)));
1558 bool down = (iy >= level.tilesHeight*16-16 ? true : (specified_nodown && nodown ? false : !!isTempleTileAtPoint(ix, iy+16)));
1559 bool left = (ix <= 16 ? true : (specified_noleft && noleft ? false : !!isTempleTileAtPoint(ix-16, iy)));
1560 bool right = (ix >= level.tilesWidth*16-16*2 ? true : (specified_noright && noright ? false : !!isTempleTileAtPoint(ix+16, iy)));
1564 if (global.cityOfGold == 1) sprite_index = sGTemple; else sprite_index = sTemple;
1569 if (y == 0 or collision_point(x, y-16, oTemple, 0, 0) or collision_point(x, y+16, oTempleFake, 0, 0)) { up = true; }
1570 if (y >= argument0 or collision_point(x, y+16, oTemple, 0, 0) or collision_point(x, y+16, oTempleFake, 0, 0)) { down = true; }
1571 if (collision_point(x-16, y, oTemple, 0, 0) or collision_point(x-16, y, oTempleFake, 0, 0)) { left = true; }
1572 if (collision_point(x+16, y, oTemple, 0, 0) or collision_point(x+16, y, oTempleFake, 0, 0)) { right = true; }
1575 if (global.cityOfGold == 1) {
1577 setSprite('sGTempleUp');
1578 if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 0)); //3
1579 else if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 16)); //3
1580 if (!left && !right) {
1581 if (!down) setSprite('sGTempleUp6'); else setSprite('sGTempleUp5');
1583 if (!down) setSprite('sGTempleUp7'); else setSprite('sGTempleUp3');
1584 } else if (!right) {
1585 if (!down) setSprite('sGTempleUp8'); else setSprite('sGTempleUp4');
1586 } else if (left && right && !down) {
1587 setSprite('sGTempleUp2');
1590 setSprite('sGTempleDown');
1594 setSprite('sTempleUp');
1595 //if (rand(1, 4) == 1) tile_add(bgCaveTop4, 0, 0, 16, 16, x, y-16, 3); else if (rand(1, 4) == 1) tile_add(bgCaveTop4, 16, 0, 16, 16, x, y-16, 3);
1596 if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 0)); //3
1597 else if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 16)); //3
1598 if (!left && !right) {
1599 if (!down) setSprite('sTempleUp6'); else setSprite('sTempleUp5');
1601 if (!down) setSprite('sTempleUp7'); else setSprite('sTempleUp3');
1602 } else if (!right) {
1603 if (!down) setSprite('sTempleUp8'); else setSprite('sTempleUp4');
1604 } else if (left && right && !down) {
1605 setSprite('sTempleUp2');
1608 setSprite('sTempleDown');
1614 override void beautifyTile () {
1615 setupNiceTileSprite();
1619 override void scrSetupBlockTop () {
1620 setupNiceTileSprite(/*noup:true*/);
1624 override void scrSetupBlockBottom () {
1625 setupNiceTileSprite(/*nodown:true*/);
1629 override void scrSetupBlockLeft () {
1630 setupNiceTileSprite(/*noright:true*/);
1634 override void scrSetupBlockRight () {
1635 setupNiceTileSprite(/*noleft:true*/);
1640 objType = 'oTemple';
1641 spriteName = 'sTemple';
1643 //toSpecialGrid = true; // want to think
1647 // ////////////////////////////////////////////////////////////////////////// //
1648 class MapTileVine['oVine'] : MapTile;
1651 override void setupTile () {
1655 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1656 //if (argument1) sprite_index = sVine;
1658 bool up = !!level.isLadderAtPoint(ix+8, iy-8);
1659 bool down = !!level.isLadderAtPoint(ix+8, iy+16);
1662 //tile_add(bgVineRoots, 0, 0, 16, 16, x, y-16, 1000); // use same depth as the vine objects themselves
1663 appendBackFront(level.CreateBgTile('bgVineRoots', 0)); //1000
1664 setSprite('sVineSource');
1666 setSprite('sVineBottom');
1671 override void beautifyTile () {
1672 setupNiceTileSprite();
1680 spriteName = 'sVine';
1681 //toSpecialGrid = true; // want to think
1685 // ////////////////////////////////////////////////////////////////////////// //
1686 class MapTileLava['oLava'] : MapTile;
1694 override void setupTile () {
1695 spurtTime = global.randOther(100, 300);
1696 spurtCounter = spurtTime;
1700 override void doSprayRubble () {
1701 foreach (; 0..3) level.MakeMapObject(ix+global.randOther(0, 16), iy+global.randOther(0, 16), 'oLavaDrip');
1702 if (global.randOther(1, 6) == 1) {
1703 auto flame = level.MakeMapObject(ix+8, iy+8, 'oFlame');
1704 if (flame) flame.yVel = 4;
1709 override bool onExplosionTouch (MapObject xplo) {
1714 override void thinkFrame () {
1715 if (!spurtSet && spriteName == 'sLavaTop') {
1717 spurt = (global.randOther(1, 4) == 1);
1719 auto dist = pointDistance(ix, iy, level.player.ix, level.player.iy);
1720 if (spurt && dist < 240) {
1721 if (spurtCounter > 0) {
1724 spurtCounter = spurtTime;
1725 auto flame = level.MakeMapObject(ix+8, iy-4, (global.randOther(1, 8) == 1 ? 'oMagma' : 'oFlame'));
1726 //auto flame = level.MakeMapObject(ix+8, iy-4, 'oMagma');
1727 if (flame) flame.yVel = -global.randOther(1, 4);
1735 spriteName = 'sLava';
1738 toSpecialGrid = false; // lava tiles are special: they are animated/thinked without a grid
1739 lightRadius = 32+16;
1740 litWholeTile = true;
1745 // ////////////////////////////////////////////////////////////////////////// //
1746 class MapTileWater['oWater'] : MapTile;
1749 override void setupTile () {
1753 override void doSprayRubble () {
1754 foreach (; 0..3) level.MakeMapObject(ix+global.randOther(0, 16), iy+global.randOther(0, 16), 'oDrip');
1758 override bool onExplosionTouch (MapObject xplo) {
1763 final bool isWaterTile (MapTile t) { return t.water; }
1764 //final bool isSolidOrWaterTile (MapTile t) { return (t.water || t.solid); }
1767 void setupNiceTileSprite () {
1768 //if (argument1) sprite_index = sWater;
1770 bool upWater = false;
1775 if (level.checkTileAtPoint(ix, iy-16, &isWaterTile)) upWater = true;
1776 if (level.isSolidAtPoint(ix, iy-16)) up = true;
1777 if (level.isSolidAtPoint(ix, iy+16) && !level.checkTileAtPoint(ix, iy+16, &isWaterTile)) down = true;
1779 if (!up && !upWater) setSprite('sWaterTop');
1781 if (upWater && level.checkTileAtPoint(ix, iy-32, &isWaterTile) && down && global.randOther(1, 4) == 1) {
1782 setSprite('sWaterBottomTall2');
1783 auto water = level.checkTileAtPoint(ix, iy-16, &isWaterTile);
1784 if (water) water.setSprite('sWaterBottomTall1');
1785 } else if ((up || upWater) && down) {
1786 switch (global.randOther(1, 4)) {
1787 case 1: setSprite('sWaterBottom'); break;
1788 case 2: setSprite('sWaterBottom2'); break;
1789 case 3: setSprite('sWaterBottom3'); break;
1790 case 4: setSprite('sWaterBottom4'); break;
1796 override void beautifyTile () {
1797 setupNiceTileSprite();
1803 spriteName = 'sWater';
1806 toSpecialGrid = false;
1810 // ////////////////////////////////////////////////////////////////////////// //
1811 class MapTileWaterSwim['oWaterSwim'] : MapTileWater;
1814 // ////////////////////////////////////////////////////////////////////////// //
1815 class MapTileLavaSolid['oLavaSolid'] : MapTile;
1817 override void setupTile () {
1820 override bool onExplosionTouch (MapObject xplo) {
1825 objType = 'oLavaSolid';
1826 spriteName = 'sLava';
1829 toSpecialGrid = false;
1833 // ////////////////////////////////////////////////////////////////////////// //
1834 class MapTileLushTrapBlock['oTrapBlock'] : MapTile;
1841 override void onDestroy () {
1843 /*if (not cleanDeath and not global.cleanSolids)*/ {
1844 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubbleTan', 'sRubbleTanSmall');
1845 scrDropRubble(ix, iy, 3, 'sRubbleTan', 'sRubbleTanSmall');
1847 playSound('sndThump');
1856 override void doSprayRubble () {
1857 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
1858 scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
1859 if (global.cityOfGold == 1) {
1864 playSound('sndThump');
1870 override void setupTile () {
1876 if (distanceToEntityCenter(level.player) < 90) {
1882 override void thinkFrame () {
1884 if (deathTimer > 0) --deathTimer; else instanceRemove();
1890 objType = 'oLavaSolid';
1891 spriteName = 'sSkullBlock';
1893 toSpecialGrid = true;
1898 // ////////////////////////////////////////////////////////////////////////// //
1899 class MapTileLeaves['oLeaves'] : MapTile;
1905 final bool isTreeOrLeaves (MapTile t) { return (t != self && t.tree || t.leaves); }
1909 override void onDestroy () {
1911 if (spriteName != 'sLeavesDead' && spriteName != 'sLeavesDeadR') {
1912 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1913 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1920 override void doSprayRubble () {
1921 if (spriteName != 'sLeavesDead' && spriteName != 'sLeavesDeadR') {
1922 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1923 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1928 override void setupTile () {
1929 spriteName = (global.cemetary ? 'sLeavesDead' : 'sLeaves');
1933 override void thinkFrame () {
1938 if (level.checkTileAtPoint(x-16, y, &isTreeOrLeaves)) {
1939 setSprite(global.cemetary ? 'sLeavesDeadR' : 'sLeavesRight');
1940 } else if (level.checkTileAtPoint(x+16, y, &isTreeOrLeaves)) {
1941 setSprite(global.cemetary ? 'sLeavesDead' : 'sLeaves');
1943 if (level.checkTileAtPoint(x-16, y, &isTreeOrLeaves) &&
1944 level.checkTileAtPoint(x+16, y, &isTreeOrLeaves))
1946 setSprite('sLeavesTop');
1949 if (level.checkTileAtPoint(x-16, y, &isTreeOrLeaves)) setSprite('sLeavesDeadR'); else setSprite('sLeavesDead');
1956 if (spriteName == 'sLeavesTop') {
1957 if (!level.checkTileAtPoint(x-16, y, &isTreeOrLeaves) ||
1958 !level.checkTileAtPoint(x+16, y, &isTreeOrLeaves))
1962 } else if (spriteName == 'sLeaves' || spriteName == 'sLeavesDead') {
1963 if (!level.checkTileAtPoint(x+16, y, &isTreeOrLeaves)) instanceRemove();
1964 } else if (spriteName == 'sLeavesRight' || spriteName == 'sLeavesDeadR') {
1965 if (!level.checkTileAtPoint(x-16, y, &isTreeOrLeaves)) instanceRemove();
1970 if (sprite_index == sLeavesDeadR) {
1972 desc2 = "These leaves have died and withered.";
1975 desc2 = "The canopy of a proud tree.";
1982 objType = 'oLeaves';
1984 desc2 = "The top of a proud tree.";
1990 toSpecialGrid = true;
1995 // ////////////////////////////////////////////////////////////////////////// //
1996 class MapTileTree['oTree'] : MapTile;
2002 override void onDestroy () {
2004 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2005 scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2011 override void doSprayRubble () {
2012 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2013 scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2017 override void setupTile () {
2021 final bool isTreeTileAtPoint (int x, int y) {
2022 return !!level.checkTileAtPoint(x, y, delegate bool (MapTile t) { return (t.objType == 'oTree'); });
2026 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
2027 //if (argument1) sprite_index = sVine;
2029 bool up = isTreeTileAtPoint(ix, iy-16);
2030 //bool down = isTreeTileAtPoint(ix, iy+16);
2031 //bool left = isTreeTileAtPoint(ix-16, iy);
2032 //bool right = isTreeTileAtPoint(ix+16, yi);
2035 if (global.cemetary || getSprite().Name == 'sTreeTopDead') setSprite('sTreeTopDead'); else setSprite('sTreeTop');
2041 override void beautifyTile () {
2042 setupNiceTileSprite();
2046 override void thinkFrame () {
2049 if (!level.isSolidAtPoint(x, y+16)) { instanceRemove(); return; }
2050 if (level.isLavaAtPoint(x-16, y) || level.isLavaAtPoint(x+16, y)) { instanceRemove(); return; }
2056 desc = "Tree Trunk";
2057 desc2 = "The trunk of a proud tree.";
2058 spriteName = 'sTreeTrunk';
2063 toSpecialGrid = true;
2068 // ////////////////////////////////////////////////////////////////////////// //
2069 class MapTileTreeBranch['oTreeBranch'] : MapTile;
2075 final bool isTree (MapTile t) { return (t != self && t.tree); }
2079 override void onDestroy () {
2081 if (spriteName != 'sTreeBranchDeadL' && spriteName != 'sTreeBranchDeadR') {
2082 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
2089 override void doSprayRubble () {
2090 if (spriteName != 'sTreeBranchDeadL' && spriteName != 'sTreeBranchDeadR') {
2091 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
2096 override void setupTile () {
2099 auto ltree = level.checkTileAtPoint(ix-16, iy, &isTree);
2100 auto rtree = level.checkTileAtPoint(ix+16, iy, &isTree);
2102 if (ltree && !rtree) setSprite(global.cemetary ? 'sTreeBranchDeadR' : 'sTreeBranchRight');
2103 else if (!ltree && rtree) setSprite(global.cemetary ? 'sTreeBranchDeadL' : 'sTreeBranchLeft');
2105 //if (global.cemetary) spriteName = 'sTreeBranchDeadR';
2109 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
2110 //if (argument1) sprite_index = sVine;
2112 bool up = !!level.checkTileAtPoint(ix, iy-16, delegate bool (MapTile t) { return (t.objType == 'oLeaves'); });
2113 //if (collision_point(x, y+16, oTreeBranch, 0, 0)) { down = true; }
2114 //if (collision_point(x-16, y, oTreeBranch, 0, 0)) { left = true; }
2115 bool right = !!level.checkTileAtPoint(ix+16, iy, delegate bool (MapTile t) { return (t.objType == 'oTree'); });
2123 if (global.cemetary || getSprite().Name == 'sTreeBranchDeadR') setSprite('sTreeBranchDeadL'); else setSprite('sTreeBranchLeft');
2128 override void beautifyTile () {
2129 //writeln("!!! ", getSprite().Name);
2130 setupNiceTileSprite();
2134 override void thinkFrame () {
2138 auto ltree = level.checkTileAtPoint(ix-16, iy, &isTree);
2139 auto rtree = level.checkTileAtPoint(ix+16, iy, &isTree);
2140 if (!ltree && !rtree) {
2144 if (ltree && !rtree) setSprite('sTreeBranchRight');
2145 else if (!ltree && rtree) setSprite('sTreeBranchLeft');
2154 desc = "Tree Branch";
2155 desc2 = "A slight but firm limb of a proud tree.";
2156 spriteName = 'sTreeBranchRight';
2162 toSpecialGrid = true;
2167 // ////////////////////////////////////////////////////////////////////////// //
2168 // only for dark levels
2169 class MapTileTikiTorch['oTikiTorch'] : MapTile;
2172 override void setupTile () {
2173 writeln("*** TIKI TORCH CREATED");
2177 override void thinkFrame () {
2178 lightRadius = 32+16+global.randOther(-8, 8);
2183 objType = 'oTikiTorch';
2185 desc2 = "A moderately bright torch.";
2186 spriteName = 'sTikiTorch';
2191 toSpecialGrid = true;
2194 lightRadius = 32+16;
2198 // ////////////////////////////////////////////////////////////////////////// //
2199 class MapTileGiantTikiHead['oGiantTikiHead'] : MapTile;
2207 setSprite('sGTHHole');
2212 override void setupTile () {
2216 override void thinkFrame () {
2221 objType = 'oGiantTikiHead';
2222 invincible = true; //???
2224 spriteName = 'sGiantTikiHead';
2225 toSpecialGrid = true; // it is HUGE
2230 // ////////////////////////////////////////////////////////////////////////// //
2231 class MapTileThinIce['oThinIce'] : MapTile;
2235 override void setupTile () {
2239 override void thinkFrame () {
2240 if (level.player.isRectHitSimple(ix, iy-1, 17, 2)) {
2242 if (global.randOther(1, 100) == 1) level.MakeMapObject(ix+global.randOther(0, 16), iy+9, 'oDrip');
2244 if (thickness > 50) setSprite('sThinIce1');
2245 else if (thickness > 40) setSprite('sThinIce2');
2246 else if (thickness > 30) setSprite('sThinIce3');
2247 else if (thickness > 20) setSprite('sThinIce4');
2248 else if (thickness > 10) setSprite('sThinIce5');
2249 else if (thickness > 0) setSprite('sThinIce6');
2250 else instanceRemove();
2255 objType = 'oThinIce';
2257 spriteName = 'sThinIce1';
2258 toSpecialGrid = true; // it must think
2262 // ////////////////////////////////////////////////////////////////////////// //
2263 class MapTileDarkFall['oDarkFall'] : MapTile;
2265 //int thickness = 60;
2268 int timeFallMax = 20;
2272 override int height () { return 8; }
2274 override void setupTile () {}
2277 //FIXME: viscidMovement -- ???
2278 override void thinkFrame () {
2279 //isCollisionCharacterTop(1)
2281 auto plr = level.player;
2282 if (plr.isRectHitSimple(ix, iy-1, 17, 2)) {
2284 } else if (plr.status == MapObject::HANGING) {
2285 //writeln("checking for hang...");
2287 if (plr.isRectHitSimple((tileX-1)*16, iy, 16, 8) || plr.isRectHitSimple((tileX+1)*16, iy, 16, 8)) {
2288 //writeln("oDarkFall: HANGING! timer=", timeFall);
2291 } else if (timeFall < timeFallMax) {
2294 if (timeFall <= 0) falling = true;
2296 if (!falling) return;
2298 //if (yVel > 10) yVel = 10;
2299 //HACK: so player won't be able to push it
2303 // dropped on solid?
2304 if (level.checkTilesInRect(ix, iy+height, width, 1)) {
2307 // not breaked on "Thwomp Trap" (we don't have it yet)
2308 playSound('sndBreak');
2309 level.MakeMapObject(x+8, y+8, 'oSmokePuff');
2311 auto obj = level.MakeMapObject(x+global.randOther(2, 14), y-global.randOther(2, 8), 'oRubbleDark');
2313 obj.xVel = global.randOther(1, 3)-global.randOther(1, 3);
2314 obj.yVel = -global.randOther(0, 3);
2322 objType = 'oDarkFall';
2326 //setCollisionBounds(0, 0, 16, 8);
2332 desc = "Falling Platform";
2333 desc2 = "A thin, dark blue platform that will colapse if stood on for any length of time.";
2335 spriteName = 'sDarkFall';
2336 toSpecialGrid = true; // it must think
2341 // ////////////////////////////////////////////////////////////////////////// //
2342 class MapTileAlienHull['oAlienShip'] : MapTile;
2345 override void setupTile () {
2346 //writeln("*********** ALIEN SHIP!");
2350 override void doSprayRubble () {
2351 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2352 scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2356 final bool isAlienShipFloorCB (MapTile t) { return (t.objType == 'oAlienShip' || t.objType == 'oAlienShipFloor'); }
2357 final bool isASFTileAt (int x, int y) { return !!level.checkTileAtPoint(x, y, &isAlienShipFloorCB); }
2360 override void beautifyTile () {
2361 //if (argument1) sprite_index = sAlienTop;
2363 bool up = !!isASFTileAt(ix, iy-16);
2364 bool down = !!isASFTileAt(ix, iy+16);
2365 bool left = !!isASFTileAt(ix-16, iy);
2366 bool right = !!isASFTileAt(ix+16, iy);
2368 if (right && !left) {
2369 if (up && !down) setSprite('sAlienFront2');
2370 else if (down && !up) setSprite('sAlienFront3');
2372 if (left && !right) {
2373 if (up) setSprite('sAlienBack2'); else if (down) setSprite('sAlienBack3');
2379 objType = 'oAlienShip';
2381 spriteName = 'sAlienTop';
2382 toSpecialGrid = false;
2386 // ////////////////////////////////////////////////////////////////////////// //
2387 class MapTileAlienFloor['oAlienShipFloor'] : MapTileAlienHull;
2391 objType = 'oAlienShipFloor';
2392 spriteName = 'sAlienFloor';
2396 // ////////////////////////////////////////////////////////////////////////// //
2397 class MapTileXocBlock['oXocBlock'] : MapTile;
2400 override void setupTile () {
2404 override void doSprayRubble () {
2406 auto gold = level.MakeMapObject(ix+8+global.randOther(0, 4)-global.randOther(0, 4), iy+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldChunk');
2408 gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2409 gold.yVel = global.randOther(2, 4);
2413 auto gold = level.MakeMapObject(ix+8+global.randOther(0, 4)-global.randOther(0, 4), iy+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldNugget');
2415 gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2416 gold.yVel = global.randOther(2, 4);
2423 objType = 'oXocBlock';
2425 spriteName = 'sGoldBlock';
2426 toSpecialGrid = false;
2431 // ////////////////////////////////////////////////////////////////////////// //
2432 class MapTileCeilingTrap['oCeilingTrap'] : MapTile;
2434 //int thickness = 60;
2436 //int timeFall = 20;
2437 //int timeFallMax = 20;
2453 override void onAnimationLooped () {
2454 if (spriteName == 'sCeilingTrapS') setSprite('sCeilingTrap');
2458 override void doSprayRubble () {
2459 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2460 scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2461 if (global.cityOfGold == 1) {
2468 override void setupTile () {
2469 if (global.cityOfGold == 1) spriteName = 'sGoldBlock';
2474 if (status != IDLE) return;
2476 level.checkTilesInRect(ix-64, iy-64, 129, 129, delegate bool (MapTile t) {
2477 auto door = MapTileDoor(t);
2478 if (door) door.activate();
2484 //FIXME: viscidMovement -- ???
2485 override void thinkFrame () {
2486 if (status == IDLE) {
2488 } else if (status == DROP) {
2497 if (level.isSolidAtPoint(ix+8, iy+17)) status = WAIT;
2499 if (spriteName == 'sBlock' || spriteName == 'sGoldBlock') setSprite('sCeilingTrapS');
2500 // out-of-level check
2501 if (isOutsideOfLevel()) {
2505 } else if (status == WAIT) {
2508 if (isCollisionBottom(0)) flty -= 1;
2513 if (status != IDLE) {
2515 auto plr = level.player;
2516 if (!plr.dead && !plr.invincible && !plr.isExitingSprite() && /*plr.collidesWith(self)*/ plr.isRectHitSimple(x0, y0, width, height+1)) {
2518 if (!plr.collidesWith(self)) {
2519 doCol = (plr.yVel < -0.01 && plr.y0 > y1);
2522 if (global.plife > 0) {
2523 global.plife -= global.config.crushDmg;
2524 if (global.plife <= 0 /*and isRealLevel()*/) level.addDeath(objName);
2529 playSound('sndHurt');
2534 level.isObjectInRect(x0-1, y0-1, width+2, height+2, delegate bool (MapObject o) {
2535 if (o == self) return false;
2536 if (o isa EnemyTombLord) return false;
2538 // register hit only if we're moving onto a spikes
2539 if (!o.collidesWith(self)) {
2540 bool onTop = (o.yVel < -0.01 && o.y0 > y1);
2541 if (!onTop) return false;
2542 writeln("*** RUNNING ON TRAP SPIKES");
2546 auto dms = MonsterDamsel(o);
2548 if (dms.dead || dms.status == MapObject::DEAD || dms.invincible) return false;
2549 if (dms.heldBy) dms.heldBy.holdItem = none;
2550 dms.hp -= global.config.crushDmg;
2552 dms.status = MapObject::THROWN;
2553 dms.counter = dms.stunMax;
2555 //dms.damselDropped = true;
2556 dms.kissCount = min(1, dms.kissCount);
2557 playSound('sndDamsel');
2562 auto enemy = MapEnemy(o);
2564 if (o.dead || o.status == MapObject::DEAD || o.invincible) return false;
2565 if (o.heldBy) o.heldBy.holdItem = none;
2566 enemy.hp -= global.config.crushDmg;
2568 playSound('sndHit');
2573 }, castClass:MapEnemy);
2579 objType = 'oCeilingTrap';
2580 objName = 'Ceiling Trap';
2581 desc = "Ceiling Trap";
2582 desc2 = "This seemingly-innocuous block hides a vicious set of spikes.";
2588 //setCollisionBounds(0, 0, 16, 8);
2598 spriteName = 'sBlock';
2599 toSpecialGrid = true; // it must think
2604 // ////////////////////////////////////////////////////////////////////////// //
2605 class MapTileTempleFake['oTempleFake'] : MapTile;
2607 int sleepTime = 4; // wait until the grid is filled
2609 override void doSprayRubble () {
2610 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2611 scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2615 override void setupTile () {
2616 doCreateOre(60, 80, 100, 1200);
2620 override void thinkFrame () {
2621 if (sleepTime > 0) { --sleepTime; return; }
2623 auto door = level.checkTilesInRect(ix, iy, 16, 16, delegate bool (MapTile t) {
2624 if (t == self) return false;
2625 //writeln("fake temple: t(", GetClassName(t.Class), "); objName=", t.objName, "; objType=", t.objType, "; door=", t isa MapTileDoor);
2626 return (t isa MapTileDoor);
2630 auto bt = level.MakeMapTile(ix/16, iy/16, 'oTemple');
2631 if (bt) bt.copyOreFrom(self);
2636 if (!level.checkTileAtPoint(ix+8, iy+8, delegate bool (MapTile t) { return (t isa MapTileDoor); })) {
2638 level.MakeMapTile(ix/16, iy/16, 'oTemple');
2647 objType = 'oTemple';
2648 desc = "Temple Brick";
2649 desc2 = "This is the first brick you've seen that actually qualifies as a brick. It looks rather bland.";
2653 spriteName = 'sTemple';
2654 toSpecialGrid = true; // it must think
2662 // ////////////////////////////////////////////////////////////////////////// //
2663 class MapTileDoor['oDoor'] : MapTile;
2665 //int thickness = 60;
2680 //setCollisionBounds(1, 0, 15, 32);
2681 override int x0 () { return round(fltx)+1; }
2682 override int width () { return 15; }
2683 override int height () { return 32; }
2686 override void doSprayRubble () {
2687 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2688 scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2689 if (global.cityOfGold == 1) {
2696 override void setupTile () {
2708 //FIXME: viscidMovement -- ???
2709 override void thinkFrame () {
2710 if (status == IDLE) {
2712 } else if (status == DROP) {
2713 yVel = fmin(yVel+myGrav, myGravLimit);
2716 auto oldsolid = solid;
2718 //writeln("DOOR GOING DOWN; yVel=", yVel, "; ix=", ix, "; iy=", iy, "; x0=", x0, "; y0=", y0, "; w=", width, "; h=", height);
2719 int newY = round(flty+yVel); //!!!
2720 // check if we need (and can) move down
2721 int w = width, hp = height;
2723 // made non-solid temporarily
2724 //auto hasAnything = level.checkTilesInRect(x0, y0+hp, w, 1, &level.cbCollisionAnySolid);
2725 auto hasAnything = level.checkTilesInRect(x0, y0+hp, w, 1, delegate bool (MapTile t) {
2726 if (t == self) return false;
2730 //writeln("hit '", GetClassName(hasAnything.Class), "' (", hasAnything.objName, ":", hasAnything.objType, "): pos=(", hasAnything.ix, ",", hasAnything.iy, ")");
2731 // hit something, stop right there
2732 if (yVel > myGrav*3) playSound('sndThud');
2738 //writeln("DOOR STOP");
2747 // out-of-level check
2748 if (flty > level.tilesHeight*16+16) {
2752 } else if (status == WAIT) {
2755 if (isCollisionBottom(0)) flty -= 1;
2766 //setCollisionBounds(0, 0, 16, 8);
2777 desc2 = "The inside of this block carries an extendable wall.";
2779 spriteName = 'sDoor';
2780 toSpecialGrid = true; // it must think
2785 // ////////////////////////////////////////////////////////////////////////// //
2786 class MapTileSmashTrap['oSmashTrap'] : MapTile;
2788 //int thickness = 60;
2816 final string dirName () {
2818 case RIGHT: return "right";
2819 case LEFT: return "left";
2820 case UP: return "up";
2821 case DOWN: return "down";
2827 //setCollisionBounds(1, 1, 15, 15);
2828 override int x0 () { return round(fltx)+1; }
2829 override int width () { return 15; }
2830 //override int height () { return 16; }
2833 override void doSprayRubble () {
2834 if (global.cityOfGold == 1) {
2836 auto gold = level.MakeMapObject(ix+8+global.randOther(0, 4)-global.randOther(0, 4), iy+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldChunk');
2837 gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2838 gold.yVel = global.randOther(2, 4);
2841 auto gold = level.MakeMapObject(ix+8+global.randOther(0, 4)-global.randOther(0, 4), iy+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldNugget');
2842 gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2843 gold.yVel = global.randOther(2, 4);
2846 if (smashed) scrSprayRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2847 scrDropRubble(ix, iy, 3, 'sRubble', 'sRubbleSmall');
2852 override void setupTile () {
2853 // prevent smash trap from spawning in player range when level starts
2854 if (true /+global.config.bizarre /*or isRealLevel()*/ +/) {
2855 if (level.calcNearestEnterDist(ix, iy) < 90) {
2862 if (global.cityOfGold == 1) setSprite('sSmashTrapGold');
2863 dir = global.randRoom(0, 3);
2867 //FIXME: viscidMovement -- ???
2868 override void thinkFrame () {
2871 if (status == IDLE) {
2872 auto plr = level.player;
2873 auto dist = pointDistance(ix, iy, plr.ix, plr.iy);
2874 if (counter > 0) --counter;
2875 if (dist < 90 && counter < 1) {
2876 if (abs(plr.iy-(iy+8)) < 8 && plr.ix > ix+8 && !isCollisionRight(2)) {
2880 } else if (abs(plr.ix-(ix+8)) < 8 && plr.iy > iy+8 && !isCollisionBottom(2)) {
2884 } else if (abs(plr.iy-(iy+8)) < 8 && plr.ix < ix+8 && !isCollisionLeft(2)) {
2888 } else if (abs(plr.ix-(ix+8)) < 8 && plr.iy < iy+8 && !isCollisionTop(2)) {
2894 } else if (status == ATTACK) {
2895 bool colLeft = !!isCollisionLeft(1);
2896 bool colRight = !!isCollisionRight(1);
2897 bool colTop = !!isCollisionTop(1);
2898 bool colBot = !!isCollisionBottom(1);
2900 xv = fclamp(xv+xa, -4, 4);
2901 yv = fclamp(yv+ya, -4, 4);
2904 if (isCollisionRight(2) && colRight) { shiftX(-2); hit = true; }
2905 if (colRight) { shiftX(-1); hit = true; }
2906 } else if (dir == DOWN) {
2907 if (isCollisionBottom(2) && colBot) { shiftY(-2); hit = true; }
2908 if (colBot) { shiftY(-1); hit = true; }
2909 } else if (dir == LEFT) {
2910 if (isCollisionLeft(2) && colLeft) { shiftX(2); hit = true; }
2911 if (colLeft) { shiftX(1); hit = true; }
2912 } else if (dir == UP) {
2913 if (isCollisionTop(2) && colTop) { shiftY(2); hit = true; }
2914 if (colTop) { shiftY(1); hit = true; }
2917 if (level.isObjectInRect(ix-1, iy-1, 18, 18, delegate bool (MapObject o) { return (o isa EnemyTombLord); })) hit = true;
2928 auto ct = isCollision();
2929 writeln("dir=", dirName(), "; colLeft=", colLeft, "; colRight=", colRight, "; colTop=", colTop, "; colBot=", colBot, "; col=", (ct ? GetClassName(ct.Class) : '<none>'), " (", (ct ? ct.objName : '<>'), ")");
2933 if (hit && !isCollision() /*!colRight && !colLeft && !colTop && !colBot*/) {
2938 } else if (status == DEAD) {
2944 if (level.isLavaAtPoint(ix, iy-1)) { instanceRemove(); return; }
2947 if (level.checkTilesInRect(ix+1, iy+1, 15, 15, &level.cbCollisionLava)) status = DEAD;
2952 auto plr = level.player;
2953 if (!plr.dead && !plr.invincible && !plr.isExitingSprite() && /*plr.collidesWith(self)*/ plr.isRectHitSimple(x0-1, y0-1, width+2, height+2)) {
2955 if (!plr.collidesWith(self)) {
2956 bool onLeft = (plr.xVel < -0.01 && plr.x0 > x1);
2957 bool onRight = (plr.xVel > 0.01 && plr.x1 < x0);
2958 bool onTop = (plr.yVel < -0.01 && plr.y0 > y1);
2959 bool onBottom = (plr.yVel > 0.01 && plr.y1 < y0);
2960 if (!onLeft && !onRight && !onTop && !onBottom) {
2963 writeln("*** PLAYER RUNNING ON TRAP SPIKES");
2967 if (global.plife > 0) {
2968 global.plife -= global.config.crushDmg;
2969 if (global.plife <= 0 /*and isRealLevel()*/) level.addDeath(objName);
2973 playSound('sndHurt');
2978 level.isObjectInRect(x0-1, y0-1, width+2, height+2, delegate bool (MapObject o) {
2979 if (o == self) return false;
2980 if (o isa EnemyTombLord) return false;
2982 //if (o !isa MapEnemy) return false;
2984 // register hit only if we're moving onto a spikes
2985 if (!o.collidesWith(self)) {
2986 bool onLeft = (o.xVel < -0.01 && o.x0 > x1);
2987 bool onRight = (o.xVel > 0.01 && o.x1 < x0);
2988 bool onTop = (o.yVel < -0.01 && o.y0 > y1);
2989 bool onBottom = (o.yVel > 0.01 && o.y1 < y0);
2990 if (!onLeft && !onRight && !onTop && !onBottom) return false;
2991 writeln("*** RUNNING ON TRAP SPIKES");
2995 auto dms = MonsterDamsel(o);
2997 if (!dms.invincible) {
2998 if (dms.heldBy) dms.heldBy.holdItem = none;
2999 dms.hp -= global.config.crushDmg;
3001 dms.status = MapObject::THROWN;
3002 dms.counter = dms.stunMax;
3004 //dms.damselDropped = true;
3005 dms.kissCount = min(1, dms.kissCount);
3006 playSound('sndDamsel');
3012 auto enemy = MapEnemy(o);
3014 if (o.dead || o.status == DEAD || o.invincible) return false;
3015 if (o.heldBy) o.heldBy.holdItem = none;
3016 enemy.hp -= global.config.crushDmg;
3018 playSound('sndHit');
3023 }, castClass:MapEnemy);
3028 objType = 'oSmashTrap';
3029 objName = 'Smash Trap';
3030 desc = "Smash Trap";
3031 desc2 = "This trap carries an array of extendable blades. It can chase down intruders horizontally and vertically.";
3053 spriteName = 'sSmashTrap';
3054 toSpecialGrid = true; // it must think
3059 // ////////////////////////////////////////////////////////////////////////// //
3060 class MapTileSmashTrapLit['oSmashTrapLit'] : MapTileSmashTrap;
3063 spriteName = 'sSmashTrapLit';
3068 // ////////////////////////////////////////////////////////////////////////// //
3069 class MapTileGoldDoor['oGoldDoor'] : MapTile;
3079 override void doSprayRubble () {
3083 override void setupTile () {
3084 writeln("GENERATED GOLD DOOR");
3088 // it opens if player carrying a sceptre, and has a crown
3089 override void thinkFrame () {
3091 if (status == SCEPTRE && !global.hasCrown) return;
3092 auto plr = level.player;
3093 if (plr.holdItem !isa ItemWeaponSceptre) return;
3094 // check for collision
3095 if (plr.collidesWith(self)) {
3096 if (global.hasCrown) {
3097 // take sceptre away
3098 auto it = plr.holdItem;
3099 plr.holdItem = none;
3100 it.instanceRemove();
3101 playSound('sndChestOpen');
3102 level.MakeMapTile(ix/16, iy/16, 'oXGold');
3103 auto obj = level.MakeMapObject(ix-4, iy+6, 'oPoof');
3104 if (obj) obj.xVel = -0.4;
3105 obj = level.MakeMapObject(ix+16+4, iy+6, 'oPoof');
3106 if (obj) obj.xVel = 0.4;
3108 //level.osdMessage("THE MYSTERIOUS DOOR IS UNLOCKED!", 3.33);
3109 level.osdMessageTalk("THE MYSTERIOUS DOOR IS UNLOCKED!", timeout:3.33, inShopOnly:false);
3114 level.osdMessage("THE SCEPTRE FITS...\nBUT NOTHING IS HAPPENING!", 3.33);
3120 objType = 'oGoldDoor';
3122 desc2 = "A door with a golden seal on it.";
3129 spriteName = 'sGoldDoor';
3130 toSpecialGrid = true; // it must think
3136 // ////////////////////////////////////////////////////////////////////////// //
3137 class MapTileUnlockedGoldDoor['oXGold'] : MapTile;
3140 override void doSprayRubble () {
3144 override void setupTile () {
3148 // it opens if player carrying a sceptre, and has a crown
3149 override void thinkFrame () {
3156 desc2 = "A door with an opened golden seal on it.";
3161 spriteName = 'sExit';
3162 toSpecialGrid = false; // it must think
3169 // ////////////////////////////////////////////////////////////////////////// //
3170 class MapTileBlackMarketDoor['oXMarket'] : MapTile;
3173 override void doSprayRubble () {
3177 override void setupTile () {
3178 writeln("*** GENERATED BLACK MARKET ENTRANCE ***");
3180 if (global.hasSpectacles || global.hasUdjatEye) {
3181 level.setTileAt(ix/16, iy/16, none);
3188 // it opens if player carrying a sceptre, and has a crown
3189 override void thinkFrame () {
3194 objType = 'oXMarket';
3195 desc = "Market Exit";
3196 desc2 = "A door leading to Black Market.";
3201 spriteName = 'sExit';
3202 toSpecialGrid = true; // it must be hidden behind the normal tile
3209 // ////////////////////////////////////////////////////////////////////////// //
3210 class MapTileMoai['oMoai'] : MapTile;
3212 override int width () { return 16; }
3213 override int height () { return 64; }
3215 override void doSprayRubble () {}
3216 override void setupTile () {}
3217 override void thinkFrame () {}
3227 spriteName = 'sMoai';
3228 toSpecialGrid = true; // it is big
3233 // ////////////////////////////////////////////////////////////////////////// //
3234 class MapTileMoai3['oMoai3'] : MapTileMoai;
3237 spriteName = 'sMoai3';
3241 // ////////////////////////////////////////////////////////////////////////// //
3242 class MapTileMoaiInside['oMoaiInside'] : MapTile;
3244 override int width () { return 16; }
3245 override int height () { return 48; }
3247 override void doSprayRubble () {}
3248 override void setupTile () {}
3249 override void thinkFrame () {}
3252 objType = 'oMoaiInside';
3259 spriteName = 'sMoaiInside';
3260 toSpecialGrid = true; // it is big
3265 // ////////////////////////////////////////////////////////////////////////// //
3266 class MapTileLamp['oLamp'] : MapTile;
3271 override bool onExplosionTouch (MapObject xplo) {
3274 //level.MakeMapObject(x+8, y+12, (redLamp ? 'oLampRedItem' : 'oLampItem'));
3279 override void doSprayRubble () {
3280 if (global.cityOfGold == 1) {
3287 override void setupTile () {
3288 if (redLamp) spriteName = 'sLampRed';
3292 override void thinkFrame () {
3296 float llev = (trunc(imageFrame) < 2 ? 0.0 : 2.0-imageFrame/2.0);
3297 lightRadius = 128+round(4*llev);
3301 // drop lamp item if it has no support
3302 if (!level.checkTilesInRect(x+5, y-1, 6, 1)) {
3304 level.MakeMapObject(x+8, y+12, (redLamp ? 'oLampRedItem' : 'oLampItem'));
3313 desc2 = "A bright, geothermal-powered lantern, with a stainless steel frame.";
3317 spriteName = 'sLamp';
3318 toSpecialGrid = true; // it must think
3323 // ////////////////////////////////////////////////////////////////////////// //
3324 class MapTileLampRed['oLampRed'] : MapTileLamp;
3327 desc2 = "A geothermal-powered lantern that emits red light. Often found in brothels.";