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;
26 //name spriteLeftDeco, spriteRightDeco;
27 name rubbleSprite1, rubbleSprite2;
45 bool specialExit; // don't generate angry shopkeeper here
48 bool sacrificingAltar;
49 bool shopWall; // may be set to `true` for non-solid tiles too
53 bool platform; // oLadderTop, oLeaves, oTreeBranch
55 bool border; // border tile
57 bool litWholeTile; // lit whole tile with maximum light
58 bool ignoreFrameOffsetX, ignoreFrameOffsetY;
59 bool dontReplaceOthers; // this tile won't replace existing tiles when put on a map
60 bool immuneToReplacement; // this tile immune to replacement by other tiles
63 transient bool waterMoved; // any move
64 transient bool waterMovedDown;
65 transient int waterSlideCounter;
66 transient int waterSlideOldX, waterSlideOldY;
70 //float grav = 0.6; // the gravity
71 float myGravLimit = 8;
73 // x and y are offsets
78 bool gemDestroyWithTile;
82 void snapToExit (MapEntity e) {
83 if (!e || !e.isInstanceAlive) return;
90 // ////////////////////////////////////////////////////////////////////////// //
91 override void Destroy () {
110 override void onLoaded () {
112 if (spriteName) sprite = level.sprStore[spriteName];
113 for (MapBackTile bt = bgback; bt; bt = bt.next) bt.onLoaded();
114 for (MapBackTile bt = bgfront; bt; bt = bt.next) bt.onLoaded();
115 if (gem) gem.onLoaded();
119 string getExitMessage () {
124 override SpriteImage getSprite (optional out bool doMirror) {
130 override SpriteFrame getSpriteFrame (optional out bool doMirror, optional out int x0, optional out int y0, optional out int x1, optional out int y1) {
131 auto spr = getSprite(doMirror!optional);
132 if (!spr || spr.frames.length == 0) return none;
133 auto spf = spr.frames[trunci(imageFrame)%spr.frames.length];
134 if (!spf) return none;
135 if (specified_x0 || specified_x1) {
136 x0 = (ignoreFrameOffsetX ? 0 : -spf.xofs);
137 x1 = x0+spf.tex.width;
139 if (specified_y0 || specified_y1) {
140 y0 = (ignoreFrameOffsetY ? 0 : -spf.yofs);
141 y1 = y0+spf.tex.height;
147 override void clearSprite () {
153 override void setSprite (name sprNameL, optional name sprNameR) {
154 if (!sprNameL && sprNameR) sprNameL = sprNameR;
155 if (spriteName != sprNameL) {
156 spriteName = sprNameL;
157 sprite = (sprNameL ? level.sprStore[sprNameL] : none);
160 if (!toSpecialGrid) {
161 auto spr = getSprite();
162 active = (spr && spr.frames.length > 1);
165 if (spriteName == 'sBrick' || spriteName == 'sBrick2' ||
166 spriteName == 'sBrickDown' ||
167 spriteName == 'sBrickGold' || spriteName == 'sBrickGoldBig' ||
168 spriteName == 'sCaveSmooth' ||
169 spriteName == 'sCaveUp' || spriteName == 'sCaveUp2')
171 rubbleSprite1 = 'sRubble';
172 rubbleSprite2 = 'sRubbleSmall';
179 // ////////////////////////////////////////////////////////////////////////// //
180 override int width () { return DefWidth; }
181 override int height () { return DefHeight; }
184 void beautifyTile () {}
185 void scrSetupBlockTop () {}
186 void scrSetupBlockBottom () {}
187 void scrSetupBlockLeft () {}
188 void scrSetupBlockRight () {}
191 // ////////////////////////////////////////////////////////////////////////// //
192 void setGem (name oname, optional bool visibility) {
194 gem.ownerTile = none;
195 gem.instanceRemove();
199 gem = level.MakeMapObject(ix+8, iy+8, oname);
201 gem.ownerTile = self;
202 gem.hiddenTreasure = !global.hasSpectacles && !global.hasUdjatEye;
203 if (specified_visibility) gem.hiddenTreasure = !visibility;
206 if (gem.hiddenTreasure) gem.visible = false;
207 gem.saveInterpData();
211 void setGemObject (MapObject obj, optional bool visibility) {
213 if (obj.ownerTile == self) {
214 if (gem != obj) FatalError("MapTile::setGemObject: WTF?! (0)");
216 if (gem == obj) FatalError("MapTile::setGemObject: WTF?! (1)");
219 gem.ownerTile = none;
220 gem.instanceRemove();
221 if (!gem.grid) delete gem;
226 obj.ownerTile.gem = none;
227 obj.ownerTile = none;
230 gem.ownerTile = self;
231 gem.hiddenTreasure = !global.hasSpectacles && !global.hasUdjatEye;
232 if (specified_visibility) gem.hiddenTreasure = !visibility;
235 if (gem.hiddenTreasure) gem.visible = false;
238 gem.saveInterpData();
242 void convertGemObjectToDiamond (MapObject ghost) {
244 if (oldgem && oldgem isa ItemBigGem) {
246 if (!ghost.isInstanceAlive) return;
247 if (!gem.isRectHitSimple(ghost.x0, ghost.y0, ghost.width, ghost.height)) return;
249 auto diamond = level.MakeMapObject(oldgem.ix, oldgem.iy, 'oDiamond');
250 setGemObject(diamond);
255 // ////////////////////////////////////////////////////////////////////////// //
256 void doCreateOre (int probSap, int probEmer, int probRuby, int probItem) {
257 int n = global.randRoom(1, 100);
258 if (n < 20) ore = 1; // sprite_index = sBrickGold;
259 else if (n < 30) ore = 2; // sprite_index = sBrickGoldBig;
260 if (probSap > 0 && global.randRoom(1, probSap) == 1) setGem('oSapphireBig');
261 else if (probEmer > 0 && global.randRoom(1, probEmer) == 1) setGem('oEmeraldBig');
262 else if (probRuby > 0 && global.randRoom(1, probRuby) == 1) setGem('oRubyBig');
263 else if (probItem > 0 && global.randRoom(1, probItem) == 1) setGemObject(level.scrGenerateItem(ix+8, iy+8, GameLevel::GenItemSet.Underground));
267 void copyOreFrom (MapTile t) {
268 if (t == self) return;
285 if (exit) setSprite('sEntrance');
290 if (exit) setSprite('sExit');
294 bool isExitActive () {
295 return (visible && exit && spriteName == 'sExit');
299 // ////////////////////////////////////////////////////////////////////////// //
303 case 'oLadderOrange':
304 //objName = 'oLadder';
305 //goto case 'oLadder';
311 spriteName = 'sLadder';
315 //ladder = true; // no, laddertop is not a ladder
320 spriteName = 'sLadderTop';
323 objType = 'oVineTop';
324 //ladder = true; // no, laddertop is not a ladder
329 spriteName = 'sVineTop';
332 //objName = 'oPushBlock';
335 spriteName = (global.cityOfGold == 1 ? 'sGoldBlock' : 'sBlock');
337 case 'oPushIceBlock':
338 //objName = 'oPushBlock';
342 spriteName = 'sIceBlock';
344 case 'oSolidIceBlock':
345 //objName = 'oIceBlock';
346 //goto case 'oIceBlock';
351 moveable = true; //k8: why, let it be an easter egg
352 spriteName = 'sIceBlock';
359 spriteName = 'sIceBlock';
362 // no sense to create ore here
364 spriteName = (global.randRoom(1, 10) == 1 ? 'sBrick2' : 'sBrick');
365 rubbleSprite1 = 'sRubble';
366 rubbleSprite2 = 'sRubbleSmall';
370 spriteName = 'sCaveSmooth';
371 rubbleSprite1 = 'sRubble';
372 rubbleSprite2 = 'sRubbleSmall';
379 spriteName = 'sSpikesWood';
385 spriteName = 'sSpikes';
388 objType = 'oEntrance';
393 spriteName = 'sEntrance';
401 spriteName = 'sExit';
406 spriteName = 'sAltarLeft';
411 spriteName = 'sAltarRight';
413 case 'oSacAltarLeft':
416 sacrificingAltar = true;
417 spriteName = 'sSacAltarLeft';
419 case 'oSacAltarRight':
422 sacrificingAltar = true;
423 spriteName = 'sSacAltarRight';
428 spriteName = 'sSign';
433 spriteName = 'sSignGeneral';
438 spriteName = 'sSignBomb';
443 spriteName = 'sSignWeapon';
445 case 'oSignClothing':
448 spriteName = 'sSignClothing';
453 spriteName = 'sSignRare';
458 spriteName = 'sSignCraps';
463 spriteName = 'sSignKissing';
470 spriteName = 'sMoai';
477 spriteName = 'sMoai2';
484 spriteName = 'sMoai3';
489 objType = 'oMoaiInside';
492 spriteName = 'sMoaiInside';
497 FatalError(va("unknown map tile type '%n'", objName));
499 //!active = moveable || toSpecialGrid || lava || water; // will be done in MakeMapTile
500 //if (!solid) hangeable = false; // just in case
504 void setupTileSprite () {
505 if (!spriteName) FatalError("forgot to set sprite for tile type '", objName, "'");
506 sprite = level.sprStore[spriteName];
510 override bool initialize () {
511 if (!::initialize()) return false;
514 if (moveable && depth == 1001) depth = 1000;
515 if ((exit || enter) && depth == 1001) depth = 2000;
517 ++level.liquidTileCount;
518 level.checkWater = true;
520 // will be done in MakeMapTile
522 // animated tiles must be active
524 auto spr = getSprite();
525 if (spr && spr.frames.length > 1) {
526 writeln("activated animated tile '", objName, "'");
535 // ////////////////////////////////////////////////////////////////////////// //
536 // for now, it works only for spikes
537 final void makeBloody () {
541 setSprite(woodenSpikes ? 'sSpikesWoodBlood' : 'sSpikesBlood');
545 // ////////////////////////////////////////////////////////////////////////// //
546 final void appendBackBack (MapBackTile tile, optional int xofs, optional int yofs) {
548 if (tile.next) FatalError("MapTile::appendBackBack: wtf?!");
549 tile.fltx = (specified_xofs ? float(xofs) : 0.0);
550 tile.flty = (specified_yofs ? float(yofs) : 16.0);
551 MapBackTile last = bgback;
553 while (last.next) last = last.next;
561 final void appendBackFront (MapBackTile tile, optional int xofs, optional int yofs) {
563 if (tile.next) FatalError("MapTile::appendBackFront: wtf?!");
564 tile.fltx = (specified_xofs ? float(xofs) : 0.0);
565 tile.flty = (specified_yofs ? float(yofs) : -16.0);
566 MapBackTile last = bgfront;
568 while (last.next) last = last.next;
576 // ////////////////////////////////////////////////////////////////////////// //
578 override void onDestroy () {
583 // ////////////////////////////////////////////////////////////////////////// //
584 override void thinkFrame () {
585 if (!moveable) return;
586 // applies the acceleration
590 // approximates the "active" variables
591 if (fabs(xVel) < 0.001) xVel = 0;
592 if (fabs(yVel) < 0.001) yVel = 0;
593 //if (fabs(xAcc) < 0.0001) xAcc = 0;
594 //if (fabs(yAcc) < 0.0001) yAcc = 0;
596 yVel = fclamp(yVel+myGrav, -myGravLimit, myGravLimit);
597 int newY = roundi(flty+yVel); //!!!
598 // check if we need (and can) move down
599 int w = width, hp1 = height+1;
600 auto oldsolid = solid;
603 // made non-solid temporarily
604 auto hasAnything = level.checkTilesInRect(x0, y0, w, hp1, delegate bool (MapTile t) {
605 if (t == self) return false;
609 // hit something, stop right there
610 if (yVel > myGrav*3) playSound('sndThud');
620 if (flty > level.tilesHeight*16+16 && yVel >= 0) {
627 // ////////////////////////////////////////////////////////////////////////// //
628 final void getInterpCoordsForTile (float currFrameDelta, int scale, out int drwx, out int drwy) {
629 if (waterSlideCounter) {
633 int sgnx = sign(waterSlideOldX-ix);
634 int sgny = sign(waterSlideOldY-iy);
635 if ((sgnx|sgny) == 0) {
636 waterSlideCounter = 0;
638 drwx += (sgnx*(waterSlideCounter*4))*scale;
639 drwy += (sgny*(waterSlideCounter*4))*scale;
642 getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
647 void drawWithOfsBack (int xpos, int ypos, int scale, float currFrameDelta) {
648 //if (backTile) backTile.drawAt(xpos, ypos, scale);
649 if (invisible || !visible || !bgback) return;
653 int fx0, fy0, fx1, fy1;
654 auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
656 // non-moveable and non-special tiles need not to be interpolated
658 getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
661 for (MapBackTile bt = bgback; bt; bt = bt.next) {
664 bt.drawWithOfs(xpos, ypos, scale, currFrameDelta);
671 void drawWithOfsFront (int xpos, int ypos, int scale, float currFrameDelta) {
672 //if (backTile) backTile.drawAt(xpos, ypos, scale);
673 if (invisible || !visible /*|| (!bgfront && !spriteLeftDeco && !spriteRightDeco)*/) return;
677 int fx0, fy0, fx1, fy1;
678 auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
680 // non-moveable tiles need not to be interpolated
682 getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
685 for (MapBackTile bt = bgfront; bt; bt = bt.next) {
688 bt.drawWithOfs(xpos, ypos, scale, currFrameDelta);
694 if (spriteLeftDeco) {
695 auto spr = level.sprStore[spriteLeftDeco];
696 if (spr && spr.frames.length) {
697 auto spf = spr.frames[0];
698 spf.blitAt((ix-16)*scale-xpos, iy*scale-ypos, scale);
702 if (spriteRightDeco) {
703 auto spr = level.sprStore[spriteRightDeco];
704 if (spr && spr.frames.length) {
705 auto spf = spr.frames[0];
706 spf.blitAt((ix+16)*scale-xpos, iy*scale-ypos, scale);
713 override void drawWithOfs (int xpos, int ypos, int scale, float currFrameDelta) {
714 if (invisible || !visible) return;
717 int fx0, fy0, fx1, fy1;
718 auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
720 // non-moveable tiles need not to be interpolated
722 getInterpCoordsForTile(currFrameDelta, scale, out drwx, out drwy);
724 //auto oclr = GLVideo.color;
725 //if (moveable) GLVideo.color = 0xff_7f_00;
728 fx0 = drwx+fx0*scale-xpos;
729 fy0 = drwy+fy0*scale-ypos;
730 fx1 = drwx+fx1*scale-xpos;
731 fy1 = drwy+fy1*scale-ypos;
733 spf.tex.blitExt(fx0, fy0, fx1, fy1, 0, 0, spf.width, spf.height);
735 spf.tex.blitExt(fx0, fy0, fx1, fy1, spf.width, 0, 0, spf.height);
737 } else if (ore > 0) {
744 //GLVideo.color = oclr;
746 if (ore > 0 && !hideOre) {
747 auto ospr = level.sprStore[ore == 1 ? 'sGold' : 'sGoldBig'];
748 ospr.frames[0].blitAt(fx0, fy0, scale);
753 // ////////////////////////////////////////////////////////////////////////// //
754 // called by Destroy event of blocks that can contain ore (oBrick, oLush, etc)
760 foreach (int i; 0..3) {
761 auto gold = level.MakeMapObject(x+8+global.randOther(0, 4)-global.randOther(0, 4), y+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldChunk');
763 //gold = instance_create(x+8+rand(0,4)-rand(0,4), y+8+rand(0,4)-rand(0,4), oGoldChunk);
764 gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
765 gold.yVel = global.randOther(2, 4);
771 auto gold = level.MakeMapObject(x+8+global.randOther(0, 4)-global.randOther(0, 4), y+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldNugget');
773 gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
774 gold.yVel = global.randOther(2, 4);
782 // ////////////////////////////////////////////////////////////////////////// //
783 private final bool cbIsSolidBrick (MapTile t) { return t.solid; }
784 private final bool cbIsBrickOrLushTile (MapTile t) { return (t.objType == 'oBrick' || t.objType == 'oLush'); }
787 // ////////////////////////////////////////////////////////////////////////// //
788 // called by solids during destroy event (oBrick etc) when destroy with force such as a boulder or explosion
789 // these rubble bits shower over the foreground and are not stopped by solids
790 final void scrSprayRubble (int x, int y, int count) {
791 name spr0 = rubbleSprite1;
792 name spr1 = rubbleSprite2; // small
793 while (count-- > 0) {
794 auto rubble = level.MakeMapObject(
795 x+8+global.randOther(0, 8)-global.randOther(0, 8),
796 y+8+global.randOther(0, 8)-global.randOther(0, 8), 'oRubblePiece'); //'oRubblePieceNonSolid');
797 rubble.setSprite(global.randOther(1, 3) == 1 ? spr0 : spr1);
798 rubble.xVel = global.randOther(0, 4)-global.randOther(0, 4);
799 rubble.yVel = -global.randOther(4, 8);
801 //rubble.image_blend = image_blend;
806 final void scrDropRubble (int x, int y, int count) {
807 name spr0 = rubbleSprite1;
808 name spr1 = rubbleSprite2; // small
809 while (count-- > 0) {
810 int rx = x+8+global.randOther(0, 8)-global.randOther(0, 8);
811 int ry = y+8+global.randOther(0, 8)-global.randOther(0, 8);
812 if (global.randOther(1, 3) == 1) {
813 auto rubble = level.MakeMapObject(rx, ry, 'oRubble');
814 rubble.setSprite(spr0);
815 //rubble.image_blend = image_blend;
817 auto rubble = level.MakeMapObject(rx, ry, 'oRubbleSmall');
818 rubble.setSprite(spr1);
819 //rubble.image_blend = image_blend;
825 void smashedCheckUpTile () {
826 level.checkTilesInRect(ix, iy-16, 16, 16, delegate bool (MapTile t) {
827 //writeln("mtl: '", GetClassName(t.Class), "'; spikes=", t.spikes);
828 if (t.spikes || t isa MapTileGraveBase || t isa MapTileTikiTorch || t isa MapTileSacAltarBase) {
829 t.invincible = false;
830 t.cleanDeath = cleanDeath;
839 void doSprayRubble () {
840 if (objType == 'oBlock' || objType == 'oBrick' || objType == 'oLush' || objType == 'oTemple' || solid) {
841 if (global.cityOfGold != 1) {
842 if (smashed) scrSprayRubble(ix, iy, 3);
843 scrDropRubble(ix, iy, 3);
852 final bool isVineTile (MapTile t) { return (t.objType == 'oVineTop' || t.objType == 'oVine'); }
855 // this should be called from `smashMe()`
856 void checkSmashedVineSupport () {
857 if (!solid || isVineTile(self)) return;
858 // check for support tile ('cause this can be a moving tile, and not an actual support)
859 auto support = level.checkTileAtPoint(ix, iy, delegate bool (MapTile t) {
860 if (t == self) return false;
861 return (t.isInstanceAlive && t.solid);
867 auto vine = level.checkTileAtPoint(x, y, &isVineTile);
869 // yay, we found a vine; now remove it, going all the way down
871 vine = level.checkTileAtPoint(x, y, &isVineTile);
873 vine.invincible = false;
874 vine.cleanDeath = cleanDeath;
882 if (invincible || !isInstanceAlive) return false;
885 checkSmashedVineSupport();
887 if (/*!global.cemetary &&*/ isVineTile(self)) {
888 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
889 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
893 if (altar) level.scrTriggerIdolAltar(stolenIdol:true);
896 smashedCheckUpTile();
899 if (gemDestroyWithTile) {
900 gem.instanceRemove();
902 if (!gem.grid) FatalError("tile gem has no grid");
903 gem.hiddenTreasure = false;
906 gem.spectral = false;
908 gem = none; // don't destroy it with tile
913 //bool smashed = true;
914 if (!cleanDeath) doSprayRubble();
917 if (other.object_index == oBoulder) {
918 if (other.integrity > 0) dosomething = false; //exit;
922 //with (oTreasure) state = 1;
923 //with (oSpikes) if (not collision_point(x, y+16, oSolid, 0, 0)) instance_destroy();
926 level.scrShopkeeperAnger(GameLevel::SCAnger.TileDestroyed);
931 MapTile obj = level.checkTileAtPoint(x, y-16, &cbIsSolidBrick);
932 if (obj) obj.scrSetupBlockBottom();
933 obj = level.checkTileAtPoint(x, y+16, &cbIsSolidBrick);
934 if (obj) obj.scrSetupBlockTop();
935 obj = level.checkTileAtPoint(x-16, y, &cbIsSolidBrick);
936 if (obj) obj.scrSetupBlockLeft();
937 obj = level.checkTileAtPoint(x+16, y, &cbIsSolidBrick);
938 if (obj) obj.scrSetupBlockRight();
941 /* was commented in the original
942 instance_activate_object(oLadder);
943 obj = collision_point(x+8, y+24, oLadder, 0, 0);
945 if (sprite_index == sVineTop or sprite_index == sVine or
946 sprite_index == sVineSource or sprite_index == sVineBottom)
957 //private transient MapObject mXplo;
960 private final bool cbExplodeTiles (MapTile t) {
961 if (!t.isInstanceAlive) return false;
962 if (t.invincible) return false;
969 t.onExplosionTouch(mXplo);
977 override bool onExplosionTouch (MapObject xplo) {
979 //writeln("ignore inv block at (", ix/16, ",", iy/16, ")");
983 //int x = ix, y = iy;
986 //writeln("smashed block at (", ix/16, ",", iy/16, ")");
990 level.checkTileAtPoint(x+8, y-1, &cbExplodeTiles); //k8: why i wrote this?!
995 obj = instance_place(x, y, oGold);
996 if (obj != noone) with (obj) instance_destroy();
998 obj = instance_place(x, y, oGoldBig);
999 if (obj != noone) with (obj) instance_destroy();
1004 //writeln("cannot smash block at (", ix/16, ",", iy/16, ")");
1011 // ////////////////////////////////////////////////////////////////////////// //
1012 void onGotSpectacles () {
1014 gem.hiddenTreasure = false;
1023 rubbleSprite1 = 'sRubbleTan';
1024 rubbleSprite2 = 'sRubbleTanSmall';
1025 //depth = 9666; //???
1030 // ////////////////////////////////////////////////////////////////////////// //
1031 // this tile is returned instead of walkeable MapObject
1032 class MapTileTemp : MapTile;
1036 // don't forget to set `fltx` and `flty`!
1037 override int x0 () { return (e ? e.x0 : 0); }
1038 override int y0 () { return (e ? e.y0 : 0); }
1039 override int width () { return (e ? e.width : 0); }
1040 override int height () { return (e ? e.height : 0); }
1043 objName = 'oMapObject';
1044 objType = 'oMapObject';
1045 active = false; // just in case
1049 // ////////////////////////////////////////////////////////////////////////// //
1050 class MapTileBrick['oBrick'] : MapTile;
1053 override void setupTile () {
1054 spriteName = (global.randRoom(1, 10) == 1 ? 'sBrick2' : 'sBrick');
1055 int tileX = ix/16, tileY = iy/16;
1056 if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1059 //writeln("BORDER");
1061 doCreateOre(100, 120, 140, 1200);
1066 override void beautifyTile () {
1067 if (level.checkTileAtPoint(ix+8, iy-8, delegate bool (MapTile t) { return (t isa TitleTileSpecialNonSolidInvincible || t isa TitleTileBlack); })) return;
1070 //if (argument1) if (global.randRoom(1, 10) == 1) sprite_index = sBrick2; // reset sprite for custom border setup
1072 int tileX = ix/16, tileY = iy/16;
1074 auto tt = level.getTileAtGrid(tileX, tileY-1);
1075 if (tt && tt.objName == 'oBlock') tt = none;
1076 //if (y == 0 || isNamedTileAt(x, y-1, 'oBrick') || isNamedTileAt(x, y-1, 'oHardBlock') || isNamedTileAt(x, y-1, 'oEdgeBrick')) up = true;
1077 bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1079 tt = level.getTileAtGrid(tileX, tileY+1);
1080 if (tt && tt.objName == 'oBlock') tt = none;
1081 //if (y == NormalTilesHeight-1 || isNamedTileAt(x, y+1, 'oBrick') || isNamedTileAt(x, y+1, 'oHardBlock') || isNamedTileAt(x, y+1, 'oEdgeBrick')) down = true;
1082 bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1085 tt = level.getTileAtGrid(tileX-1, tileY);
1086 if (tt && tt.objName == 'oBlock') tt = none;
1087 bool left = (tileX >= level.tilesWidth-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1089 tt = level.getTileAtGrid(tileX+1, tileY);
1090 if (tt && tt.objName == 'oBlock') tt = none;
1091 bool right = (tileX <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1094 //if (collision_point(x-16, y, oBrick, 0, 0) or collision_point(x-16, y, oHardBlock, 0, 0)) { left = true; } // in the original
1095 //if (collision_point(x+16, y, oBrick, 0, 0) or collision_point(x+16, y, oHardBlock, 0, 0)) { right = true; } // in the original
1098 setSprite('sCaveUp');
1099 // add rocks and sand
1101 if (global.randRoom(1, 3) < 3) {
1102 tile_add('bgCaveTop', 0, 0, 16, 16, x*16, y*16-16, 3);
1104 tile_add('bgCaveTop', 16, 0, 16, 16, x*16, y*16-16, 3);
1107 bool n = (global.randRoom(1, 3) < 3);
1108 appendBackFront(level.CreateBgTile('bgCaveTop', (n ? 0 : 16)));
1109 //instance_create(x, y-16, oCaveTop);
1113 setSprite(!up ? 'sCaveUp2' : 'sBrickDown');
1114 //instance_create(x, y+16, oCaveBottom);
1118 //spriteLeftDeco = (left ? '' : 'sCaveLeft');
1119 //spriteRightDeco = (right ? '' : 'sCaveRight');
1122 if (not left) instance_create(x-16, y, oCaveLeft);
1123 if (not right) instance_create(x+15, y, oCaveRight);
1128 override void scrSetupBlockTop () {
1129 //int x = ix, y = iy;
1130 int tileX = ix/16, tileY = iy/16;
1131 auto tt = level.getTileAtGrid(tileX, tileY+1);
1132 //if (y >= (GameLevel::NormalTilesHeight-16)*16 || level.checkTileAtPoint(x, y+16, &cbIsBrickOrLushTile)) down = true;
1133 bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1135 auto bt = level.CreateBgTile('bgCaveTop', (global.randRoom(1, 3) < 3 ? 0 : 16));
1136 //bt.flty = -16; // offset
1137 appendBackFront(bt);
1139 setSprite(down ? 'sCaveUp' : 'sCaveUp2');
1143 override void scrSetupBlockBottom () {
1144 //int x = ix, y = iy;
1146 //if (y == 0 || level.checkTileAtPoint(x, y-16, &cbIsBrickOrLushTile)) up = true;
1147 int tileX = ix/16, tileY = iy/16;
1148 auto tt = level.getTileAtGrid(tileX, tileY-1);
1149 bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1150 setSprite(up ? 'sBrickDown' : 'sCaveUp2');
1156 spriteName = 'sBrick';
1159 rubbleSprite1 = 'sRubble';
1160 rubbleSprite2 = 'sRubbleSmall';
1164 // ////////////////////////////////////////////////////////////////////////// //
1165 class MapTileHardBrick['oHardBlock'] : MapTileBrick;
1167 override void setupTile () {
1172 objType = 'oHardBlock';
1173 spriteName = 'sBrick';
1179 // ////////////////////////////////////////////////////////////////////////// //
1180 class MapTileBlock['oBlock'] : MapTile;
1183 override void setupTile () {
1184 int tileX = ix/16, tileY = iy/16;
1185 if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1188 //writeln("BORDER");
1190 if (global.cityOfGold == 1) spriteName = 'sGoldBlock';
1194 final bool isBBTTile (MapTile t) { return (t.objType == 'oBrick' || t.objType == 'oTemple' || t.objType == 'oHardBlock'); }
1197 override void beautifyTile () {
1198 bool down = !!level.checkTileAtPoint(ix, iy+16, &isBBTTile);
1200 // don't want push blocks next to lava until we tighten up liquid draining
1201 if (level.isLavaAtPoint(ix-16, iy) || level.isLavaAtPoint(ix+16, iy)) down = false;
1203 if (down && global.randRoom(1, 4) == 1) {
1205 level.MakeMapTile(ix/16, iy/16, 'oPushBlock');
1212 spriteName = 'sBlock';
1217 // ////////////////////////////////////////////////////////////////////////// //
1218 class MapTileLush['oLush'] : MapTile;
1221 override void setupTile () {
1222 int tileX = ix/16, tileY = iy/16;
1223 if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1226 //writeln("BORDER");
1228 doCreateOre(80, 100, 120, 1200);
1233 override void beautifyTile () {
1234 if (level.checkTileAtPoint(ix+8, iy-8, delegate bool (MapTile t) { return (t isa TitleTileSpecialNonSolidInvincible || t isa TitleTileBlack); })) return;
1236 int tileX = ix/16, tileY = iy/16;
1238 auto tt = level.getTileAtGrid(tileX, tileY-1);
1239 //if (y == 0 || isNamedTileAt(x, y-1, 'oBrick') || isNamedTileAt(x, y-1, 'oHardBlock') || isNamedTileAt(x, y-1, 'oEdgeBrick')) up = true;
1240 bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral && (tt.objType == 'oLush' || tt.objType == 'oTemple' || tt.objType == 'oBrick')));
1242 tt = level.getTileAtGrid(tileX, tileY+1);
1243 //if (y == NormalTilesHeight-1 || isNamedTileAt(x, y+1, 'oBrick') || isNamedTileAt(x, y+1, 'oHardBlock') || isNamedTileAt(x, y+1, 'oEdgeBrick')) down = true;
1244 bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral && tt.objType == 'oLush'));
1247 setSprite('sLushUp');
1248 if (!level.isLavaAtPoint(ix+8, iy-8)) {
1250 if (global.randRoom(1, 8) == 1) bt = level.CreateBgTile('bgCaveTop2', 32);
1251 else if (global.randRoom(1, 3) < 3) bt = level.CreateBgTile('bgCaveTop2', 0);
1252 else bt = level.CreateBgTile('bgCaveTop2', 16);
1253 appendBackFront(bt);
1258 setSprite(!up ? 'sLushUp2' : 'sLushDown');
1259 if (!level.isSolidAtPoint(ix, iy+16) && !level.isLavaAtPoint(ix, iy+16)) {
1261 if (global.randRoom(1, 12) == 1) bt = level.CreateBgTile('bgCaveTop2', 48);
1262 else if (global.randRoom(1, 12) == 1) bt = level.CreateBgTile('bgCaveTop2', 64);
1265 //instance_create(x, y+16, oLushBottom); // in the original
1270 override void scrSetupBlockTop () {
1271 int tileX = ix/16, tileY = iy/16;
1272 auto tt = level.getTileAtGrid(tileX, tileY+1);
1273 bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1274 //setSprite('sLushUpBare');
1276 setSprite(spriteName == 'sLushUp' || spriteName == 'sLushUp2' || spriteName == 'sLushUp3' ? 'sLushUpBareTop' : 'sLushUpBare2');
1278 setSprite('sLushUpBare');
1283 override void scrSetupBlockBottom () {
1284 int tileX = ix/16, tileY = iy/16;
1285 auto tt = level.getTileAtGrid(tileX, tileY-1);
1286 bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1288 setSprite(spriteName == 'sLushUp' || spriteName == 'sLushUp2' || spriteName == 'sLushUp3' ? 'sLushUpBareBottom' : 'sLushUpBare2');
1290 setSprite('sLushDownBare');
1297 spriteName = 'sLush';
1300 rubbleSprite1 = 'sRubbleLush';
1301 rubbleSprite2 = 'sRubbleLushSmall';
1305 // ////////////////////////////////////////////////////////////////////////// //
1306 class MapTileDark['oDark'] : MapTile;
1309 override void setupTile () {
1310 int tileX = ix/16, tileY = iy/16;
1311 if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1314 //writeln("BORDER");
1316 doCreateOre(40, 60, 80, 1200);
1321 override void beautifyTile () {
1322 if (level.checkTileAtPoint(ix+8, iy-8, delegate bool (MapTile t) { return (t isa TitleTileSpecialNonSolidInvincible || t isa TitleTileBlack); })) return;
1325 //if (argument1) if (global.randRoom(1, 10) == 1) sprite_index = sBrick2; // reset sprite for custom border setup
1327 int tileX = ix/16, tileY = iy/16;
1329 auto tt = level.getTileAtGrid(tileX, tileY-1);
1330 //if (y == 0 || isNamedTileAt(x, y-1, 'oBrick') || isNamedTileAt(x, y-1, 'oHardBlock') || isNamedTileAt(x, y-1, 'oEdgeBrick')) up = true;
1331 bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1333 tt = level.getTileAtGrid(tileX, tileY+1);
1334 //if (y == NormalTilesHeight-1 || isNamedTileAt(x, y+1, 'oBrick') || isNamedTileAt(x, y+1, 'oHardBlock') || isNamedTileAt(x, y+1, 'oEdgeBrick')) down = true;
1335 bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1337 //if (collision_point(x-16, y, oBrick, 0, 0) or collision_point(x-16, y, oHardBlock, 0, 0)) { left = true; } // in the original
1338 //if (collision_point(x+16, y, oBrick, 0, 0) or collision_point(x+16, y, oHardBlock, 0, 0)) { right = true; } // in the original
1341 setSprite('sDarkUp');
1342 bool n = (global.randRoom(1, 3) < 3);
1343 appendBackFront(level.CreateBgTile('bgCaveTop3', (n ? 0 : 16)));
1347 setSprite(!up ? 'sDarkUp2' : 'sDarkDown');
1352 override void scrSetupBlockTop () {
1353 int tileX = ix/16, tileY = iy/16;
1354 auto tt = level.getTileAtGrid(tileX, tileY+1);
1355 bool down = (tileY >= level.tilesHeight-2 || (tt && tt.solid && tt.visible && !tt.spectral));
1356 auto bt = level.CreateBgTile('bgCaveTop3', (global.randRoom(1, 3) < 3 ? 0 : 16));
1357 appendBackFront(bt);
1358 //bt.flty = -16; // offset
1360 setSprite(down ? 'sDarkUp' : 'sDarkUp2');
1364 override void scrSetupBlockBottom () {
1365 int tileX = ix/16, tileY = iy/16;
1366 auto tt = level.getTileAtGrid(tileX, tileY-1);
1367 bool up = (tileY <= 1 || (tt && tt.solid && tt.visible && !tt.spectral));
1368 setSprite(up ? 'sDarkDown' : 'sDarkUp2');
1374 spriteName = 'sDark';
1377 rubbleSprite1 = 'sRubbleDark';
1378 rubbleSprite2 = 'sRubbleDarkSmall';
1382 // ////////////////////////////////////////////////////////////////////////// //
1383 class MapTileIce['oIce'] : MapTile;
1388 final bool isIceTile (MapTile t) { return t.ice; }
1389 final MapTile isIceTileAtPoint (int x, int y) { return level.checkTileAtPoint(x, y, &isIceTile); }
1392 override void setupTile () {
1393 if (global.randRoom(1, 80) == 1) {
1394 setGem('oFrozenCaveman', true); // always visible
1397 gem.saveInterpData();
1398 gemDestroyWithTile = true;
1400 dripTimer = global.randOther(20, 400);
1401 //hasIceBottom = true;
1406 override void thinkFrame () {
1408 if (--dripTimer <= 0) {
1410 level.MakeMapObject(ix+8, iy+16+4, 'oDrip');
1411 dripTimer = global.randOther(20, 400);
1418 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1419 bool up = (specified_noup && noup ? false : !!isIceTileAtPoint(ix, iy-16));
1420 bool down = (specified_nodown && nodown ? false : !!isIceTileAtPoint(ix, iy+16));
1421 bool left = (specified_noleft && noleft ? false : !!isIceTileAtPoint(ix-16, iy));
1422 bool right = (specified_noright && noright ? false : !!isIceTileAtPoint(ix+16, iy));
1424 if (!up) setSprite('sIceUp');
1426 setSprite(!up ? 'sIceUp2' : 'sIceDown');
1427 if (!level.isSolidAtPoint(ix, iy+16) && global.randOther(1, 20) == 1) hasIceBottom = true;
1430 if (!up && !down) setSprite('sIceUDL');
1431 else if (!up) setSprite('sIceUL');
1432 else if (!down) setSprite('sIceDL');
1433 else setSprite('sIceLeft');
1436 if (!up && !down) setSprite('sIceUDR');
1437 else if (!up) setSprite('sIceUR');
1438 else if (!down) setSprite('sIceDR');
1439 else setSprite('sIceRight');
1441 if (!up && !left && !right && down) setSprite('sIceULR');
1442 if (!down && !left && !right && up) setSprite('sIceDLR');
1443 if (up && down && !left && !right) setSprite('sIceLR');
1444 if (!up && !down && !left && !right) setSprite('sIceBlock');
1449 override void beautifyTile () {
1450 setupNiceTileSprite();
1454 override void scrSetupBlockTop () {
1455 setupNiceTileSprite(/*noup:true*/);
1459 override void scrSetupBlockBottom () {
1460 setupNiceTileSprite(/*nodown:true*/);
1464 override void scrSetupBlockLeft () {
1465 setupNiceTileSprite(/*noright:true*/);
1469 override void scrSetupBlockRight () {
1470 setupNiceTileSprite(/*noleft:true*/);
1476 spriteName = 'sIce';
1479 toSpecialGrid = true; // want to think
1483 // ////////////////////////////////////////////////////////////////////////// //
1484 class MapTileTemple['oTemple'] : MapTile;
1489 final bool isTempleTile (MapTile t) { return (t.objType == 'oTemple' || t.border); } // fake too
1490 final MapTile isTempleTileAtPoint (int x, int y) { return level.checkTileAtPoint(x, y, &isTempleTile); }
1493 override void setupTile () {
1494 setSprite(global.cityOfGold == 1 ? 'sGTemple' : 'sTemple');
1495 int tileX = ix/16, tileY = iy/16;
1496 if (tileX == 0 || tileY == 0 || tileX == level.tilesWidth-1 || tileY == level.tilesHeight-1) {
1499 //writeln("BORDER");
1501 doCreateOre(60, 80, 100, 1200);
1506 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1507 bool up = (iy <= 0 ? true : (specified_noup && noup ? false : !!isTempleTileAtPoint(ix, iy-16)));
1508 bool down = (iy >= level.tilesHeight*16-16 ? true : (specified_nodown && nodown ? false : !!isTempleTileAtPoint(ix, iy+16)));
1509 bool left = (ix <= 16 ? true : (specified_noleft && noleft ? false : !!isTempleTileAtPoint(ix-16, iy)));
1510 bool right = (ix >= level.tilesWidth*16-16*2 ? true : (specified_noright && noright ? false : !!isTempleTileAtPoint(ix+16, iy)));
1514 if (global.cityOfGold == 1) sprite_index = sGTemple; else sprite_index = sTemple;
1519 if (y == 0 or collision_point(x, y-16, oTemple, 0, 0) or collision_point(x, y+16, oTempleFake, 0, 0)) { up = true; }
1520 if (y >= argument0 or collision_point(x, y+16, oTemple, 0, 0) or collision_point(x, y+16, oTempleFake, 0, 0)) { down = true; }
1521 if (collision_point(x-16, y, oTemple, 0, 0) or collision_point(x-16, y, oTempleFake, 0, 0)) { left = true; }
1522 if (collision_point(x+16, y, oTemple, 0, 0) or collision_point(x+16, y, oTempleFake, 0, 0)) { right = true; }
1525 if (global.cityOfGold == 1) {
1527 setSprite('sGTempleUp');
1528 if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 0)); //3
1529 else if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 16)); //3
1530 if (!left && !right) {
1531 if (!down) setSprite('sGTempleUp6'); else setSprite('sGTempleUp5');
1533 if (!down) setSprite('sGTempleUp7'); else setSprite('sGTempleUp3');
1534 } else if (!right) {
1535 if (!down) setSprite('sGTempleUp8'); else setSprite('sGTempleUp4');
1536 } else if (left && right && !down) {
1537 setSprite('sGTempleUp2');
1540 setSprite('sGTempleDown');
1544 setSprite('sTempleUp');
1545 //if (rand(1, 4) == 1) tile_add(bgCaveTop4, 0, 0, 16, 16, x, y-16, 3); else if (rand(1, 4) == 1) tile_add(bgCaveTop4, 16, 0, 16, 16, x, y-16, 3);
1546 if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 0)); //3
1547 else if (global.randOther(1, 4) == 1) appendBackFront(level.CreateBgTile('bgCaveTop4', 16)); //3
1548 if (!left && !right) {
1549 if (!down) setSprite('sTempleUp6'); else setSprite('sTempleUp5');
1551 if (!down) setSprite('sTempleUp7'); else setSprite('sTempleUp3');
1552 } else if (!right) {
1553 if (!down) setSprite('sTempleUp8'); else setSprite('sTempleUp4');
1554 } else if (left && right && !down) {
1555 setSprite('sTempleUp2');
1558 setSprite('sTempleDown');
1564 override void beautifyTile () {
1565 setupNiceTileSprite();
1569 override void scrSetupBlockTop () {
1570 setupNiceTileSprite(/*noup:true*/);
1574 override void scrSetupBlockBottom () {
1575 setupNiceTileSprite(/*nodown:true*/);
1579 override void scrSetupBlockLeft () {
1580 setupNiceTileSprite(/*noright:true*/);
1584 override void scrSetupBlockRight () {
1585 setupNiceTileSprite(/*noleft:true*/);
1590 objType = 'oTemple';
1591 spriteName = 'sTemple';
1596 // ////////////////////////////////////////////////////////////////////////// //
1597 class MapTileVine['oVine'] : MapTile;
1600 override void setupTile () {
1604 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1605 //if (argument1) sprite_index = sVine;
1607 bool up = !!level.isLadderAtPoint(ix+8, iy-8);
1608 bool down = !!level.isLadderAtPoint(ix+8, iy+16);
1611 //tile_add(bgVineRoots, 0, 0, 16, 16, x, y-16, 1000); // use same depth as the vine objects themselves
1612 appendBackFront(level.CreateBgTile('bgVineRoots', 0)); //1000
1613 setSprite('sVineSource');
1615 setSprite('sVineBottom');
1620 override void beautifyTile () {
1621 setupNiceTileSprite();
1630 spriteName = 'sVine';
1634 // ////////////////////////////////////////////////////////////////////////// //
1635 class MapTileLava['oLava'] : MapTile;
1643 override void setupTile () {
1644 spurtTime = global.randOther(100, 300);
1645 spurtCounter = spurtTime;
1649 override void doSprayRubble () {
1650 foreach (; 0..3) level.MakeMapObject(ix+global.randOther(0, 16), iy+global.randOther(0, 16), 'oLavaDrip');
1651 if (global.randOther(1, 6) == 1) {
1652 auto flame = level.MakeMapObject(ix+8, iy+8, 'oFlame');
1653 if (flame) flame.yVel = 4;
1658 override bool onExplosionTouch (MapObject xplo) {
1663 override void thinkFrame () {
1664 if (!spurtSet && spriteName == 'sLavaTop') {
1666 spurt = (global.randOther(1, 4) == 1);
1668 auto dist = pointDistance(ix, iy, level.player.ix, level.player.iy);
1669 if (spurt && dist < 240) {
1670 if (spurtCounter > 0) {
1673 spurtCounter = spurtTime;
1674 auto flame = level.MakeMapObject(ix+8, iy-4, (global.randOther(1, 8) == 1 ? 'oMagma' : 'oFlame'));
1675 //auto flame = level.MakeMapObject(ix+8, iy-4, 'oMagma');
1676 if (flame) flame.yVel = -global.randOther(1, 4);
1684 spriteName = 'sLava';
1687 toSpecialGrid = false; // lava tiles are special: they are animated/thinked without a grid
1688 lightRadius = 32+16;
1689 litWholeTile = true;
1694 // ////////////////////////////////////////////////////////////////////////// //
1695 class MapTileWater['oWater'] : MapTile;
1698 override void setupTile () {
1702 override void doSprayRubble () {
1703 foreach (; 0..3) level.MakeMapObject(ix+global.randOther(0, 16), iy+global.randOther(0, 16), 'oDrip');
1707 override bool onExplosionTouch (MapObject xplo) {
1712 final bool isWaterTile (MapTile t) { return t.water; }
1713 //final bool isSolidOrWaterTile (MapTile t) { return (t.water || t.solid); }
1716 void setupNiceTileSprite () {
1717 //if (argument1) sprite_index = sWater;
1719 bool upWater = false;
1721 //bool left = false;
1722 //bool right = false;
1724 if (level.checkTileAtPoint(ix, iy-16, &isWaterTile)) upWater = true;
1725 if (level.isSolidAtPoint(ix, iy-16)) up = true;
1726 if (level.isSolidAtPoint(ix, iy+16) && !level.checkTileAtPoint(ix, iy+16, &isWaterTile)) down = true;
1728 if (!up && !upWater) setSprite('sWaterTop');
1730 if (upWater && level.checkTileAtPoint(ix, iy-32, &isWaterTile) && down && global.randOther(1, 4) == 1) {
1731 setSprite('sWaterBottomTall2');
1732 auto awater = level.checkTileAtPoint(ix, iy-16, &isWaterTile);
1733 if (awater) awater.setSprite('sWaterBottomTall1');
1734 } else if ((up || upWater) && down) {
1735 switch (global.randOther(1, 4)) {
1736 case 1: setSprite('sWaterBottom'); break;
1737 case 2: setSprite('sWaterBottom2'); break;
1738 case 3: setSprite('sWaterBottom3'); break;
1739 case 4: setSprite('sWaterBottom4'); break;
1745 override void beautifyTile () {
1746 setupNiceTileSprite();
1752 spriteName = 'sWater';
1758 // ////////////////////////////////////////////////////////////////////////// //
1759 class MapTileWaterSwim['oWaterSwim'] : MapTileWater;
1762 // ////////////////////////////////////////////////////////////////////////// //
1763 class MapTileLavaSolid['oLavaSolid'] : MapTile;
1765 override void setupTile () {
1768 override bool onExplosionTouch (MapObject xplo) {
1773 objType = 'oLavaSolid';
1774 spriteName = 'sLava';
1780 // ////////////////////////////////////////////////////////////////////////// //
1781 class MapTileLushTrapBlock['oTrapBlock'] : MapTile;
1787 override void doSprayRubble () {
1788 if (smashed) scrSprayRubble(ix, iy, 3);
1789 scrDropRubble(ix, iy, 3);
1790 if (global.cityOfGold == 1) {
1795 playSound('sndThump');
1801 override void setupTile () {
1807 if (distanceToEntityCenter(level.player) < 90) {
1813 override void thinkFrame () {
1815 if (deathTimer > 0) --deathTimer; else { invincible = false; smashMe(); instanceRemove(); }
1821 objType = 'oLavaSolid';
1822 spriteName = 'sSkullBlock';
1824 toSpecialGrid = true;
1825 rubbleSprite1 = 'sRubbleTan';
1826 rubbleSprite2 = 'sRubbleTanSmall';
1831 // ////////////////////////////////////////////////////////////////////////// //
1832 class MapTileLeaves['oLeaves'] : MapTile;
1838 final bool isTreeOrLeaves (MapTile t) { return (t != self && (t.tree || t.leaves)); }
1841 override void doSprayRubble () {
1842 if (spriteName != 'sLeavesDead' && spriteName != 'sLeavesDeadR') {
1843 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1844 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
1849 override void setupTile () {
1850 spriteName = (global.cemetary ? 'sLeavesDead' : 'sLeaves');
1854 override void thinkFrame () {
1859 if (level.checkTileAtPoint(x-16, y, &isTreeOrLeaves)) {
1860 setSprite(global.cemetary ? 'sLeavesDeadR' : 'sLeavesRight');
1861 } else if (level.checkTileAtPoint(x+16, y, &isTreeOrLeaves)) {
1862 setSprite(global.cemetary ? 'sLeavesDead' : 'sLeaves');
1864 if (level.checkTileAtPoint(x-16, y, &isTreeOrLeaves) &&
1865 level.checkTileAtPoint(x+16, y, &isTreeOrLeaves))
1867 setSprite('sLeavesTop');
1870 if (level.checkTileAtPoint(x-16, y, &isTreeOrLeaves)) setSprite('sLeavesDeadR'); else setSprite('sLeavesDead');
1877 if (spriteName == 'sLeavesTop') {
1878 if (!level.checkTileAtPoint(x-16, y, &isTreeOrLeaves) ||
1879 !level.checkTileAtPoint(x+16, y, &isTreeOrLeaves))
1886 } else if (spriteName == 'sLeaves' || spriteName == 'sLeavesDead') {
1887 if (!level.checkTileAtPoint(x+16, y, &isTreeOrLeaves)) { invincible = false; smashMe(); instanceRemove(); }
1888 } else if (spriteName == 'sLeavesRight' || spriteName == 'sLeavesDeadR') {
1889 if (!level.checkTileAtPoint(x-16, y, &isTreeOrLeaves)) { invincible = false; smashMe(); instanceRemove(); }
1894 if (sprite_index == sLeavesDeadR) {
1896 desc2 = "These leaves have died and withered.";
1899 desc2 = "The canopy of a proud tree.";
1906 objType = 'oLeaves';
1908 desc2 = "The top of a proud tree.";
1914 toSpecialGrid = true;
1919 // ////////////////////////////////////////////////////////////////////////// //
1920 class MapTileTree['oTree'] : MapTile;
1925 override void doSprayRubble () {
1926 if (smashed) scrSprayRubble(ix, iy, 3);
1927 scrDropRubble(ix, iy, 3);
1931 override void setupTile () {
1935 final bool isTreeTileAtPoint (int x, int y) {
1936 return !!level.checkTileAtPoint(x, y, delegate bool (MapTile t) { return (t.objType == 'oTree'); });
1940 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
1941 //if (argument1) sprite_index = sVine;
1943 bool up = isTreeTileAtPoint(ix, iy-16);
1944 //bool down = isTreeTileAtPoint(ix, iy+16);
1945 //bool left = isTreeTileAtPoint(ix-16, iy);
1946 //bool right = isTreeTileAtPoint(ix+16, yi);
1949 if (global.cemetary || getSprite().Name == 'sTreeTopDead') setSprite('sTreeTopDead'); else setSprite('sTreeTop');
1955 override void beautifyTile () {
1956 setupNiceTileSprite();
1960 override void thinkFrame () {
1963 if (!level.isSolidAtPoint(x, y+16)) { invincible = false; smashMe(); instanceRemove(); return; }
1964 if (level.isLavaAtPoint(x-16, y) || level.isLavaAtPoint(x+16, y)) { invincible = false; smashMe(); instanceRemove(); return; }
1970 desc = "Tree Trunk";
1971 desc2 = "The trunk of a proud tree.";
1972 spriteName = 'sTreeTrunk';
1977 toSpecialGrid = true;
1978 rubbleSprite1 = 'sRubbleLush';
1979 rubbleSprite2 = 'sRubbleLushSmall';
1984 // ////////////////////////////////////////////////////////////////////////// //
1985 class MapTileTreeBranch['oTreeBranch'] : MapTile;
1991 final bool isTree (MapTile t) { return (t != self && t.tree); }
1995 override void onDestroy () {
1997 if (spriteName != 'sTreeBranchDeadL' && spriteName != 'sTreeBranchDeadR') {
1998 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
2005 override void doSprayRubble () {
2006 if (spriteName != 'sTreeBranchDeadL' && spriteName != 'sTreeBranchDeadR') {
2007 level.MakeMapObject(ix+8+global.randOther(0, 8)-global.randOther(0, 8), iy+8+global.randOther(0, 8)-global.randOther(0, 8), 'oLeaf');
2012 override void setupTile () {
2015 auto ltree = level.checkTileAtPoint(ix-16, iy, &isTree);
2016 auto rtree = level.checkTileAtPoint(ix+16, iy, &isTree);
2018 if (ltree && !rtree) setSprite(global.cemetary ? 'sTreeBranchDeadR' : 'sTreeBranchRight');
2019 else if (!ltree && rtree) setSprite(global.cemetary ? 'sTreeBranchDeadL' : 'sTreeBranchLeft');
2021 //if (global.cemetary) spriteName = 'sTreeBranchDeadR';
2025 void setupNiceTileSprite (optional bool noleft, optional bool noright, optional bool noup, optional bool nodown) {
2026 //if (argument1) sprite_index = sVine;
2028 bool up = !!level.checkTileAtPoint(ix, iy-16, delegate bool (MapTile t) { return (t.objType == 'oLeaves'); });
2029 //if (collision_point(x, y+16, oTreeBranch, 0, 0)) { down = true; }
2030 //if (collision_point(x-16, y, oTreeBranch, 0, 0)) { left = true; }
2031 bool right = !!level.checkTileAtPoint(ix+16, iy, delegate bool (MapTile t) { return (t.objType == 'oTree'); });
2039 if (global.cemetary || getSprite().Name == 'sTreeBranchDeadR') setSprite('sTreeBranchDeadL'); else setSprite('sTreeBranchLeft');
2044 override void beautifyTile () {
2045 //writeln("!!! ", getSprite().Name);
2046 setupNiceTileSprite();
2050 override void thinkFrame () {
2051 //int x = ix, y = iy;
2054 auto ltree = level.checkTileAtPoint(ix-16, iy, &isTree);
2055 auto rtree = level.checkTileAtPoint(ix+16, iy, &isTree);
2056 if (!ltree && !rtree) {
2063 if (ltree && !rtree) setSprite('sTreeBranchRight');
2064 else if (!ltree && rtree) setSprite('sTreeBranchLeft');
2073 desc = "Tree Branch";
2074 desc2 = "A slight but firm limb of a proud tree.";
2075 spriteName = 'sTreeBranchRight';
2081 toSpecialGrid = true;
2086 // ////////////////////////////////////////////////////////////////////////// //
2087 // only for dark levels
2088 class MapTileTikiTorch['oTikiTorch'] : MapTile;
2091 override int height () { return 32; }
2093 override void setupTile () {
2094 writeln("*** TIKI TORCH CREATED");
2097 override void thinkFrame () {
2098 lightRadius = 32+16+global.randOther(-8, 8);
2099 if (level.loserGPU) lightRadius = 32+16;
2104 objType = 'oTikiTorch';
2106 desc2 = "A moderately bright torch.";
2107 spriteName = 'sTikiTorch';
2112 toSpecialGrid = true;
2115 lightRadius = 32+16;
2119 // ////////////////////////////////////////////////////////////////////////// //
2120 class MapTileGiantTikiHead['oGiantTikiHead'] : MapTile;
2128 setSprite('sGTHHole');
2133 override void setupTile () {
2137 override void thinkFrame () {
2142 objType = 'oGiantTikiHead';
2143 invincible = true; //???
2145 spriteName = 'sGiantTikiHead';
2150 // ////////////////////////////////////////////////////////////////////////// //
2151 class MapTileThinIce['oThinIce'] : MapTile;
2155 override void setupTile () {
2159 override void thinkFrame () {
2160 if (level.player.isRectHitSimple(ix, iy-1, 17, 2)) {
2162 if (global.randOther(1, 100) == 1) level.MakeMapObject(ix+global.randOther(0, 16), iy+9, 'oDrip');
2164 if (thickness > 50) setSprite('sThinIce1');
2165 else if (thickness > 40) setSprite('sThinIce2');
2166 else if (thickness > 30) setSprite('sThinIce3');
2167 else if (thickness > 20) setSprite('sThinIce4');
2168 else if (thickness > 10) setSprite('sThinIce5');
2169 else if (thickness > 0) setSprite('sThinIce6');
2170 else instanceRemove();
2175 objType = 'oThinIce';
2177 spriteName = 'sThinIce1';
2178 toSpecialGrid = true; // it must think
2182 // ////////////////////////////////////////////////////////////////////////// //
2183 class MapTileDarkFall['oDarkFall'] : MapTile;
2185 //int thickness = 60;
2188 int timeFallMax = 20;
2192 override int height () { return 8; }
2194 override void setupTile () {}
2197 //FIXME: viscidMovement -- ???
2198 override void thinkFrame () {
2199 //isCollisionCharacterTop(1)
2201 auto plr = level.player;
2202 if (plr.isRectHitSimple(ix, iy-1, 17, 2)) {
2204 } else if (plr.status == MapObject::HANGING) {
2205 //writeln("checking for hang...");
2207 if (plr.isRectHitSimple((tileX-1)*16, iy, 16, 8) || plr.isRectHitSimple((tileX+1)*16, iy, 16, 8)) {
2208 //writeln("oDarkFall: HANGING! timer=", timeFall);
2211 } else if (timeFall < timeFallMax) {
2214 if (timeFall <= 0) falling = true;
2216 if (!falling) return;
2218 //if (yVel > 10) yVel = 10;
2219 //HACK: so player won't be able to push it
2223 // dropped on solid?
2224 if (level.checkTilesInRect(ix, iy+height, width, 1)) {
2227 // not breaked on "Thwomp Trap" (we don't have it yet)
2228 playSound('sndBreak');
2229 level.MakeMapObject(x+8, y+8, 'oSmokePuff');
2231 auto obj = level.MakeMapObject(x+global.randOther(2, 14), y-global.randOther(2, 8), 'oRubbleDark');
2233 obj.xVel = global.randOther(1, 3)-global.randOther(1, 3);
2234 obj.yVel = -global.randOther(0, 3);
2242 objType = 'oDarkFall';
2246 //setCollisionBounds(0, 0, 16, 8);
2252 desc = "Falling Platform";
2253 desc2 = "A thin, dark blue platform that will colapse if stood on for any length of time.";
2255 spriteName = 'sDarkFall';
2256 toSpecialGrid = true; // it must think
2261 // ////////////////////////////////////////////////////////////////////////// //
2262 class MapTileAlienHull['oAlienShip'] : MapTile;
2265 override void setupTile () {
2266 //writeln("*********** ALIEN SHIP!");
2270 override void doSprayRubble () {
2271 if (smashed) scrSprayRubble(ix, iy, 3);
2272 scrDropRubble(ix, iy, 3);
2276 final bool isAlienShipFloorCB (MapTile t) { return (t.objType == 'oAlienShip' || t.objType == 'oAlienShipFloor'); }
2277 final bool isASFTileAt (int x, int y) { return !!level.checkTileAtPoint(x, y, &isAlienShipFloorCB); }
2280 override void beautifyTile () {
2281 //if (argument1) sprite_index = sAlienTop;
2283 bool up = !!isASFTileAt(ix, iy-16);
2284 bool down = !!isASFTileAt(ix, iy+16);
2285 bool left = !!isASFTileAt(ix-16, iy);
2286 bool right = !!isASFTileAt(ix+16, iy);
2288 if (right && !left) {
2289 if (up && !down) setSprite('sAlienFront2');
2290 else if (down && !up) setSprite('sAlienFront3');
2292 if (left && !right) {
2293 if (up) setSprite('sAlienBack2'); else if (down) setSprite('sAlienBack3');
2299 objType = 'oAlienShip';
2301 spriteName = 'sAlienTop';
2302 rubbleSprite1 = 'sRubbleTan';
2303 rubbleSprite2 = 'sRubbleTanSmall';
2307 // ////////////////////////////////////////////////////////////////////////// //
2308 class MapTileAlienFloor['oAlienShipFloor'] : MapTileAlienHull;
2312 objType = 'oAlienShipFloor';
2313 spriteName = 'sAlienFloor';
2317 // ////////////////////////////////////////////////////////////////////////// //
2318 class MapTileXocBlock['oXocBlock'] : MapTile;
2321 override void setupTile () {
2325 override void doSprayRubble () {
2327 auto gold = level.MakeMapObject(ix+8+global.randOther(0, 4)-global.randOther(0, 4), iy+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldChunk');
2329 gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2330 gold.yVel = global.randOther(2, 4);
2334 auto gold = level.MakeMapObject(ix+8+global.randOther(0, 4)-global.randOther(0, 4), iy+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldNugget');
2336 gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2337 gold.yVel = global.randOther(2, 4);
2344 objType = 'oXocBlock';
2346 spriteName = 'sGoldBlock';
2351 // ////////////////////////////////////////////////////////////////////////// //
2352 class MapTileCeilingTrap['oCeilingTrap'] : MapTile;
2354 //int thickness = 60;
2356 //int timeFall = 20;
2357 //int timeFallMax = 20;
2373 override void onAnimationLooped () {
2374 if (spriteName == 'sCeilingTrapS') setSprite('sCeilingTrap');
2378 override void doSprayRubble () {
2379 if (smashed) scrSprayRubble(ix, iy, 3);
2380 scrDropRubble(ix, iy, 3);
2381 if (global.cityOfGold == 1) {
2388 override void setupTile () {
2389 if (global.cityOfGold == 1) spriteName = 'sGoldBlock';
2394 if (status != IDLE) return;
2396 level.checkTilesInRect(ix-64, iy-64, 129, 129, delegate bool (MapTile t) {
2397 auto door = MapTileDoor(t);
2398 if (door) door.activate();
2404 //FIXME: viscidMovement -- ???
2405 override void thinkFrame () {
2406 if (status == IDLE) {
2408 } else if (status == DROP) {
2417 if (level.isSolidAtPoint(ix+8, iy+17)) status = WAIT;
2419 if (spriteName == 'sBlock' || spriteName == 'sGoldBlock') setSprite('sCeilingTrapS');
2420 // out-of-level check
2421 if (isOutsideOfLevel()) {
2425 } else if (status == WAIT) {
2428 if (isCollisionBottom(0)) flty -= 1;
2433 if (status != IDLE) {
2435 auto plr = level.player;
2436 if (!plr.dead && !plr.invincible && !plr.isExitingSprite() && /*plr.collidesWith(self)*/ plr.isRectHitSimple(x0, y0, width, height+1)) {
2438 if (!plr.collidesWith(self)) {
2439 doCol = (plr.yVel < -0.01 && plr.y0 > y1);
2442 if (global.plife > 0) {
2443 global.plife -= global.config.crushDmg;
2444 if (global.plife <= 0 /*and isRealLevel()*/) level.addDeath(objName);
2449 playSound('sndHurt');
2454 level.isObjectInRect(x0-1, y0-1, width+2, height+2, delegate bool (MapObject o) {
2455 if (o == self) return false;
2456 if (o isa EnemyTombLord) return false;
2458 // register hit only if we're moving onto a spikes
2459 if (!o.collidesWith(self)) {
2460 bool onTop = (o.yVel < -0.01 && o.y0 > y1);
2461 if (!onTop) return false;
2462 writeln("*** RUNNING ON TRAP SPIKES");
2466 auto dms = MonsterDamsel(o);
2468 if (dms.dead || dms.status == MapObject::STATE_DEAD || dms.invincible) return false;
2469 if (dms.heldBy) dms.heldBy.holdItem = none;
2470 dms.hp -= global.config.crushDmg;
2472 dms.status = MapObject::THROWN;
2473 dms.counter = dms.stunMax;
2475 //dms.damselDropped = true;
2476 dms.kissCount = min(1, dms.kissCount);
2477 playSound('sndDamsel');
2482 auto enemy = MapEnemy(o);
2484 if (o.dead || o.status == MapObject::STATE_DEAD || o.invincible) return false;
2485 if (o.heldBy) o.heldBy.holdItem = none;
2486 enemy.hp -= global.config.crushDmg;
2488 playSound('sndHit');
2493 }, castClass:MapEnemy);
2499 objType = 'oCeilingTrap';
2500 objName = 'Ceiling Trap';
2501 desc = "Ceiling Trap";
2502 desc2 = "This seemingly-innocuous block hides a vicious set of spikes.";
2508 //setCollisionBounds(0, 0, 16, 8);
2518 spriteName = 'sBlock';
2519 toSpecialGrid = true; // it must think
2520 rubbleSprite1 = 'sRubbleTan';
2521 rubbleSprite2 = 'sRubbleTanSmall';
2526 // ////////////////////////////////////////////////////////////////////////// //
2527 class MapTileTempleFake['oTempleFake'] : MapTile;
2529 int sleepTime = 4; // wait until the grid is filled
2531 override void doSprayRubble () {
2532 if (smashed) scrSprayRubble(ix, iy, 3);
2533 scrDropRubble(ix, iy, 3);
2537 override void setupTile () {
2538 doCreateOre(60, 80, 100, 1200);
2542 override void thinkFrame () {
2543 if (sleepTime > 0) { --sleepTime; return; }
2545 auto door = level.checkTilesInRect(ix, iy, 16, 16, delegate bool (MapTile t) {
2546 if (t == self) return false;
2547 //writeln("fake temple: t(", GetClassName(t.Class), "); objName=", t.objName, "; objType=", t.objType, "; door=", t isa MapTileDoor);
2548 return (t isa MapTileDoor);
2552 auto bt = level.MakeMapTile(ix/16, iy/16, 'oTemple');
2553 if (bt) bt.copyOreFrom(self);
2558 if (!level.checkTileAtPoint(ix+8, iy+8, delegate bool (MapTile t) { return (t isa MapTileDoor); })) {
2560 level.MakeMapTile(ix/16, iy/16, 'oTemple');
2569 objType = 'oTemple';
2570 desc = "Temple Brick";
2571 desc2 = "This is the first brick you've seen that actually qualifies as a brick. It looks rather bland.";
2575 spriteName = 'sTemple';
2576 toSpecialGrid = true; // it must think
2580 rubbleSprite1 = 'sRubbleTan';
2581 rubbleSprite2 = 'sRubbleTanSmall';
2586 // ////////////////////////////////////////////////////////////////////////// //
2587 class MapTileDoor['oDoor'] : MapTile;
2589 //int thickness = 60;
2604 //setCollisionBounds(1, 0, 15, 32);
2605 override int x0 () { return roundi(fltx)+1; }
2606 override int width () { return 15; }
2607 override int height () { return 32; }
2610 override void doSprayRubble () {
2611 if (smashed) scrSprayRubble(ix, iy, 3);
2612 scrDropRubble(ix, iy, 3);
2613 if (global.cityOfGold == 1) {
2620 override void setupTile () {
2632 //FIXME: viscidMovement -- ???
2633 override void thinkFrame () {
2634 if (status == IDLE) {
2636 } else if (status == DROP) {
2637 yVel = fmin(yVel+myGrav, myGravLimit);
2640 auto oldsolid = solid;
2642 //writeln("DOOR GOING DOWN; yVel=", yVel, "; ix=", ix, "; iy=", iy, "; x0=", x0, "; y0=", y0, "; w=", width, "; h=", height);
2643 int newY = roundi(flty+yVel); //!!!
2644 // check if we need (and can) move down
2645 int w = width, hp = height;
2647 // made non-solid temporarily
2648 //auto hasAnything = level.checkTilesInRect(x0, y0+hp, w, 1, &level.cbCollisionAnySolid);
2649 auto hasAnything = level.checkTilesInRect(x0, y0+hp, w, 1, delegate bool (MapTile t) {
2650 if (t == self) return false;
2654 //writeln("hit '", GetClassName(hasAnything.Class), "' (", hasAnything.objName, ":", hasAnything.objType, "): pos=(", hasAnything.ix, ",", hasAnything.iy, ")");
2655 // hit something, stop right there
2656 if (yVel > myGrav*3) playSound('sndThud');
2662 //writeln("DOOR STOP");
2671 // out-of-level check
2672 if (flty > level.tilesHeight*16+16) {
2676 } else if (status == WAIT) {
2679 if (isCollisionBottom(0)) flty -= 1;
2690 //setCollisionBounds(0, 0, 16, 8);
2701 desc2 = "The inside of this block carries an extendable wall.";
2703 spriteName = 'sDoor';
2704 toSpecialGrid = true; // it must think
2705 rubbleSprite1 = 'sRubbleTan';
2706 rubbleSprite2 = 'sRubbleTanSmall';
2711 // ////////////////////////////////////////////////////////////////////////// //
2712 class MapTileSmashTrap['oSmashTrap'] : MapTile;
2714 //int thickness = 60;
2742 final string dirName () {
2744 case RIGHT: return "right";
2745 case LEFT: return "left";
2746 case UP: return "up";
2747 case DOWN: return "down";
2753 //setCollisionBounds(1, 1, 15, 15);
2754 override int x0 () { return roundi(fltx)+1; }
2755 override int width () { return 15; }
2756 //override int height () { return 16; }
2759 override void doSprayRubble () {
2760 if (global.cityOfGold == 1) {
2762 auto gold = level.MakeMapObject(ix+8+global.randOther(0, 4)-global.randOther(0, 4), iy+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldChunk');
2763 gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2764 gold.yVel = global.randOther(2, 4);
2767 auto gold = level.MakeMapObject(ix+8+global.randOther(0, 4)-global.randOther(0, 4), iy+8+global.randOther(0, 4)-global.randOther(0, 4), 'oGoldNugget');
2768 gold.xVel = global.randOther(0, 3)-global.randOther(0, 3);
2769 gold.yVel = global.randOther(2, 4);
2772 if (smashed) scrSprayRubble(ix, iy, 3);
2773 scrDropRubble(ix, iy, 3);
2778 override void setupTile () {
2779 // prevent smash trap from spawning in player range when level starts
2780 if (true /+global.config.bizarre /*or isRealLevel()*/ +/) {
2781 if (level.calcNearestEnterDist(ix, iy) < 90) {
2788 if (global.cityOfGold == 1) setSprite('sSmashTrapGold');
2789 dir = global.randRoom(0, 3);
2793 //FIXME: viscidMovement -- ???
2794 override void thinkFrame () {
2797 if (status == IDLE) {
2798 auto plr = level.player;
2799 auto dist = pointDistance(ix, iy, plr.ix, plr.iy);
2800 if (counter > 0) --counter;
2801 if (dist < 90 && counter < 1) {
2802 if (abs(plr.iy-(iy+8)) < 8 && plr.ix > ix+8 && !isCollisionRight(2)) {
2806 } else if (abs(plr.ix-(ix+8)) < 8 && plr.iy > iy+8 && !isCollisionBottom(2)) {
2810 } else if (abs(plr.iy-(iy+8)) < 8 && plr.ix < ix+8 && !isCollisionLeft(2)) {
2814 } else if (abs(plr.ix-(ix+8)) < 8 && plr.iy < iy+8 && !isCollisionTop(2)) {
2820 } else if (status == ATTACK) {
2821 bool colLeft = !!isCollisionLeft(1);
2822 bool colRight = !!isCollisionRight(1);
2823 bool colTop = !!isCollisionTop(1);
2824 bool colBot = !!isCollisionBottom(1);
2826 xv = fclamp(xv+xa, -4, 4);
2827 yv = fclamp(yv+ya, -4, 4);
2830 if (isCollisionRight(2) && colRight) { shiftX(-2); hit = true; }
2831 if (colRight) { shiftX(-1); hit = true; }
2832 } else if (dir == DOWN) {
2833 if (isCollisionBottom(2) && colBot) { shiftY(-2); hit = true; }
2834 if (colBot) { shiftY(-1); hit = true; }
2835 } else if (dir == LEFT) {
2836 if (isCollisionLeft(2) && colLeft) { shiftX(2); hit = true; }
2837 if (colLeft) { shiftX(1); hit = true; }
2838 } else if (dir == UP) {
2839 if (isCollisionTop(2) && colTop) { shiftY(2); hit = true; }
2840 if (colTop) { shiftY(1); hit = true; }
2843 if (level.isObjectInRect(ix-1, iy-1, 18, 18, delegate bool (MapObject o) { return (o isa EnemyTombLord); })) hit = true;
2854 auto ct = isCollision();
2855 writeln("dir=", dirName(), "; colLeft=", colLeft, "; colRight=", colRight, "; colTop=", colTop, "; colBot=", colBot, "; col=", (ct ? GetClassName(ct.Class) : '<none>'), " (", (ct ? ct.objName : '<>'), ")");
2859 if (hit && !isCollision() /*!colRight && !colLeft && !colTop && !colBot*/) {
2864 } else if (status == STATE_DEAD) {
2870 if (level.isLavaAtPoint(ix, iy-1)) { invincible = false; smashMe(); instanceRemove(); return; }
2873 if (level.checkTilesInRect(ix+1, iy+1, 15, 15, &level.cbCollisionLava)) status = STATE_DEAD;
2878 auto plr = level.player;
2879 if (!plr.dead && !plr.invincible && !plr.isExitingSprite() && /*plr.collidesWith(self)*/ plr.isRectHitSimple(x0-1, y0-1, width+2, height+2)) {
2881 if (!plr.collidesWith(self)) {
2882 bool onLeft = (plr.xVel < -0.01 && plr.x0 > x1);
2883 bool onRight = (plr.xVel > 0.01 && plr.x1 < x0);
2884 bool onTop = (plr.yVel < -0.01 && plr.y0 > y1);
2885 bool onBottom = (plr.yVel > 0.01 && plr.y1 < y0);
2886 if (!onLeft && !onRight && !onTop && !onBottom) {
2889 writeln("*** PLAYER RUNNING ON TRAP SPIKES");
2893 if (global.plife > 0) {
2894 global.plife -= global.config.crushDmg;
2895 if (global.plife <= 0 /*and isRealLevel()*/) level.addDeath(objName);
2899 playSound('sndHurt');
2904 level.isObjectInRect(x0-1, y0-1, width+2, height+2, delegate bool (MapObject o) {
2905 if (o == self) return false;
2906 if (o isa EnemyTombLord) return false;
2908 //if (o !isa MapEnemy) return false;
2910 // register hit only if we're moving onto a spikes
2911 if (!o.collidesWith(self)) {
2912 bool onLeft = (o.xVel < -0.01 && o.x0 > x1);
2913 bool onRight = (o.xVel > 0.01 && o.x1 < x0);
2914 bool onTop = (o.yVel < -0.01 && o.y0 > y1);
2915 bool onBottom = (o.yVel > 0.01 && o.y1 < y0);
2916 if (!onLeft && !onRight && !onTop && !onBottom) return false;
2917 writeln("*** RUNNING ON TRAP SPIKES");
2921 auto dms = MonsterDamsel(o);
2923 if (!dms.invincible) {
2924 if (dms.heldBy) dms.heldBy.holdItem = none;
2925 dms.hp -= global.config.crushDmg;
2927 dms.status = MapObject::THROWN;
2928 dms.counter = dms.stunMax;
2930 //dms.damselDropped = true;
2931 dms.kissCount = min(1, dms.kissCount);
2932 playSound('sndDamsel');
2938 auto enemy = MapEnemy(o);
2940 if (o.dead || o.status == STATE_DEAD || o.invincible) return false;
2941 if (o.heldBy) o.heldBy.holdItem = none;
2942 enemy.hp -= global.config.crushDmg;
2944 playSound('sndHit');
2949 }, castClass:MapEnemy);
2954 objType = 'oSmashTrap';
2955 objName = 'Smash Trap';
2956 desc = "Smash Trap";
2957 desc2 = "This trap carries an array of extendable blades. It can chase down intruders horizontally and vertically.";
2979 spriteName = 'sSmashTrap';
2980 toSpecialGrid = true; // it must think
2981 rubbleSprite1 = 'sRubbleTan';
2982 rubbleSprite2 = 'sRubbleTanSmall';
2987 // ////////////////////////////////////////////////////////////////////////// //
2988 class MapTileSmashTrapLit['oSmashTrapLit'] : MapTileSmashTrap;
2991 spriteName = 'sSmashTrapLit';
2996 // ////////////////////////////////////////////////////////////////////////// //
2997 class MapTileGoldDoor['oGoldDoor'] : MapTile;
3007 override void doSprayRubble () {
3011 override void setupTile () {
3012 writeln("GENERATED GOLD DOOR");
3016 // it opens if player carrying a sceptre, and has a crown
3017 override void thinkFrame () {
3019 if (status == SCEPTRE && !global.hasCrown) return;
3020 auto plr = level.player;
3021 if (plr.holdItem !isa ItemWeaponSceptre) return;
3022 // check for collision
3023 if (plr.collidesWith(self)) {
3024 if (global.hasCrown) {
3025 // take sceptre away
3026 auto it = plr.holdItem;
3027 plr.holdItem = none;
3028 it.instanceRemove();
3029 playSound('sndChestOpen');
3030 level.MakeMapTile(ix/16, iy/16, 'oXGold');
3031 auto obj = level.MakeMapObject(ix-4, iy+6, 'oPoof');
3032 if (obj) obj.xVel = -0.4;
3033 obj = level.MakeMapObject(ix+16+4, iy+6, 'oPoof');
3034 if (obj) obj.xVel = 0.4;
3036 //level.osdMessage("THE MYSTERIOUS DOOR IS UNLOCKED!", 3.33);
3037 level.osdMessageTalk("THE MYSTERIOUS DOOR IS UNLOCKED!", timeout:3.33, inShopOnly:false);
3042 level.osdMessage("THE SCEPTRE FITS...\nBUT NOTHING IS HAPPENING!", 3.33);
3048 objType = 'oGoldDoor';
3050 desc2 = "A door with a golden seal on it.";
3057 spriteName = 'sGoldDoor';
3058 toSpecialGrid = true; // it must think
3064 // ////////////////////////////////////////////////////////////////////////// //
3065 class MapTileUnlockedGoldDoor['oXGold'] : MapTile;
3068 override void doSprayRubble () {
3072 override void setupTile () {
3076 // it opens if player carrying a sceptre, and has a crown
3077 override void thinkFrame () {
3084 desc2 = "A door with an opened golden seal on it.";
3089 spriteName = 'sExit';
3097 // ////////////////////////////////////////////////////////////////////////// //
3098 class MapTileBlackMarketDoor['oXMarket'] : MapTile;
3101 override void doSprayRubble () {
3105 override void setupTile () {
3106 writeln("*** GENERATED BLACK MARKET ENTRANCE ***");
3107 //writeln("*** made black market entrance at (", ix/16, ",", iy/16, ")");
3109 if (global.hasSpectacles || global.hasUdjatEye) {
3110 level.setTileAt(ix/16, iy/16, none);
3117 // it opens if player carrying a sceptre, and has a crown
3118 override void thinkFrame () {
3123 objType = 'oXMarket';
3124 desc = "Market Exit";
3125 desc2 = "A door leading to Black Market.";
3130 spriteName = 'sExit';
3134 dontReplaceOthers = true;
3135 immuneToReplacement = true;
3141 // ////////////////////////////////////////////////////////////////////////// //
3142 class MapTileMoai['oMoai'] : MapTile;
3144 override int width () { return 16; }
3145 override int height () { return 64; }
3147 override void doSprayRubble () {}
3148 override void setupTile () {}
3149 override void thinkFrame () {}
3159 spriteName = 'sMoai';
3164 // ////////////////////////////////////////////////////////////////////////// //
3165 class MapTileMoai3['oMoai3'] : MapTileMoai;
3168 spriteName = 'sMoai3';
3172 // ////////////////////////////////////////////////////////////////////////// //
3173 class MapTileMoaiInside['oMoaiInside'] : MapTile;
3175 override int width () { return 16; }
3176 override int height () { return 48; }
3178 override void doSprayRubble () {}
3179 override void setupTile () {}
3180 override void thinkFrame () {}
3183 objType = 'oMoaiInside';
3190 spriteName = 'sMoaiInside';
3195 // ////////////////////////////////////////////////////////////////////////// //
3196 class MapTileLamp['oLamp'] : MapTile;
3201 override bool onExplosionTouch (MapObject xplo) {
3202 //int x = ix, y = iy;
3207 //level.MakeMapObject(x+8, y+12, (redLamp ? 'oLampRedItem' : 'oLampItem'));
3212 override void doSprayRubble () {
3213 if (global.cityOfGold == 1) {
3220 override void setupTile () {
3221 if (redLamp) spriteName = 'sLampRed';
3225 override void thinkFrame () {
3229 float llev = (trunci(imageFrame) < 2 ? 0.0 : 2.0-imageFrame/2.0);
3230 lightRadius = 128+roundi(4*llev);
3231 if (level.loserGPU) lightRadius = 128;
3235 // drop lamp item if it has no support
3236 if (!level.checkTilesInRect(x+5, y-1, 6, 1)) {
3241 level.MakeMapObject(x+8, y+12, (redLamp ? 'oLampRedItem' : 'oLampItem'));
3250 desc2 = "A bright, geothermal-powered lantern, with a stainless steel frame.";
3254 spriteName = 'sLamp';
3255 toSpecialGrid = true; // it must think
3260 // ////////////////////////////////////////////////////////////////////////// //
3261 class MapTileLampRed['oLampRed'] : MapTileLamp;
3264 desc2 = "A geothermal-powered lantern that emits red light. Often found in brothels.";