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 ObjBoulder['oBoulder'] : MapTile;
21 bool monkey; // if trap was triggered by a monkey
27 override int width () { return 32; }
28 override int height () { return 32; }
31 override void setupTile () {
32 integrity = global.randOther(1, 3);
37 override void drawWithOfs (int xpos, int ypos, int scale, float currFrameDelta) {
38 //if (backTile) backTile.drawAt(xpos, ypos, scale);
39 if (invisible || !visible) return;
42 int fx0, fy0, fx1, fy1;
43 auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
49 // non-moveable tiles need not to be interpolated
51 getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
53 for (MapBackTile bt = bgback; bt; bt = bt.next) {
56 bt.drawWithOfs(xpos, ypos, scale, currFrameDelta);
62 fx0 = drwx+fx0*scale-xpos;
63 fy0 = drwy+fy0*scale-ypos;
64 fx1 = drwx+fx1*scale-xpos;
65 fy1 = drwy+fy1*scale-ypos;
67 spf.tex.blitExt(fx0, fy0, fx1, fy1, 0, 0, spf.width, spf.height);
69 spf.tex.blitExt(fx0, fy0, fx1, fy1, spf.width, 0, 0, spf.height);
79 auto ospr = level.sprStore[ore == 1 ? 'sGold' : 'sGoldBig'];
80 ospr.frames[0].blitAt(fx0, fy0, scale);
83 for (MapBackTile bt = bgfront; bt; bt = bt.next) {
86 bt.drawWithOfs(xpos, ypos, scale, currFrameDelta);
94 override bool onExplosionTouch (MapObject xplo) {
96 /*if (smashed)*/ scrSprayRubble(ix, iy, 4);
97 scrDropRubble(ix, iy, 3);
106 final void scrSprayBigRubble (int x, int y, int count, name sprName) {
107 while (count-- > 0) {
108 auto rubble = level.MakeMapObject(
109 x+global.randOther(0, 15)-global.randOther(0, 15),
110 y+global.randOther(0, 15)-global.randOther(0, 15), 'oRubblePiece'); //'oRubblePieceNonSolid');
111 rubble.setSprite(sprName);
112 rubble.xVel = global.randOther(0, 4)-global.randOther(0, 4);
113 rubble.yVel = -global.randOther(4, 8);
115 //rubble.image_blend = image_blend;
121 void crashBoulder () {
123 auto rubble = level.MakeMapObject(
124 xCenter+global.randOther(0, 15)-global.randOther(0, 15),
125 yCenter+global.randOther(0, 15)-global.randOther(0, 15), 'oRubblePiece'); //'oRubblePieceNonSolid');
126 rubble.setSprite('sRubbleTanSmall');
127 rubble.xVel = global.randOther(0, 4)-global.randOther(0, 4);
128 rubble.yVel = -global.randOther(4, 8);
131 rubble = level.MakeMapObject(
132 xCenter+global.randOther(0, 15)-global.randOther(0, 15),
133 yCenter+global.randOther(0, 15)-global.randOther(0, 15), 'oRubble'); //'oRubblePieceNonSolid');
134 rubble.setSprite('sRubbleTan');
135 rubble.xVel = global.randOther(0, 4)-global.randOther(0, 4);
136 rubble.yVel = -global.randOther(4, 8);
139 if (global.randOther(1, 3) == 1) {
141 xCenter+global.randOther(0, 15)-global.randOther(0, 15),
142 yCenter+global.randOther(0, 15)-global.randOther(0, 15), 'oRock');
145 rubble = instance_create(other.x+rand(0, 15)-rand(0, 15), other.y+rand(0, 15)-rand(0, 15), oRubblePieceNonSolid);
146 rubble.sprite_index = sRubbleTan;
147 rubble = instance_create(other.x+rand(0, 15)-rand(0, 15), other.y+rand(0, 15)-rand(0, 15), oRubble);
148 rubble.sprite_index = sRubbleTan;
149 if (rand(1, 3) == 1) instance_create(other.x+rand(0, 15)-rand(0, 15), other.y+rand(0, 15)-rand(0, 15), oRock);
153 if (global.randOther(1, 2) == 1) {
154 //rubble = instance_create(other.x+rand(0, 15)-rand(0, 15), other.y+rand(0, 15)-rand(0, 15), oRubblePieceNonSolid);
155 //rubble.sprite_index = sRubbleTanSmall;
156 auto rubble = level.MakeMapObject(
157 xCenter+global.randOther(0, 15)-global.randOther(0, 15),
158 yCenter+global.randOther(0, 15)-global.randOther(0, 15), 'oRubblePiece'); //'oRubblePieceNonSolid');
159 rubble.setSprite('sRubbleTanSmall');
160 rubble.xVel = global.randOther(0, 4)-global.randOther(0, 4);
161 rubble.yVel = -global.randOther(4, 8);
164 //rubble = instance_create(other.x+rand(0, 15)-rand(0, 15), other.y+rand(0, 15)-rand(0, 15), oRubbleSmall);
165 //rubble.sprite_index = sRubbleTanSmall;
166 auto rubble = level.MakeMapObject(
167 xCenter+global.randOther(0, 15)-global.randOther(0, 15),
168 yCenter+global.randOther(0, 15)-global.randOther(0, 15), 'oRubble'); //'oRubblePieceNonSolid');
169 rubble.setSprite('sRubbleTan');
170 rubble.xVel = global.randOther(0, 4)-global.randOther(0, 4);
171 rubble.yVel = -global.randOther(4, 8);
174 playSound('sndBreak');
179 override void doSprayRubble () {
184 final bool isCollidedWithExit () {
185 return !!level.checkTilesInRect(ix, iy, width, height, &level.cbCollisionExitTile);
189 final bool collidesWithPlayer () {
190 if (level.player.dead) return false;
191 //return level.player.isRectHitSimple(x0, y0, width, height);
192 return collidesWith(level.player);
196 transient array!MapTile solidsList;
197 transient bool exitWasHit;
198 bool removeBottomTiles;
201 void destructTerrain () {
202 auto ospec = spectral;
205 // check if we'll run into solids
208 bool solidWasHit = false;
209 //invinceHit = false;
210 level.checkTilesInRect(ix, iy, width, height, delegate bool (MapTile t) {
211 if (t == self || t.spectral || !t.visible) return false;
213 if (t.exit /*&& fabs(xVel) < 0.5 && fabs(yVel) < 0.5*/) {
218 // ignore non-solid invincible tiles
219 if (!t.solid && !t.spikes) return false;
221 foreach (MapTile st; solidsList) if (st == t) { found = true; break; }
222 if (!found) solidsList[$] = t;
226 //writeln("boulder: processing ", solidsList.length, " tiles");
227 bool inviHit = false;
229 bool smashedSomething = false;
230 foreach (MapTile t; solidsList) {
231 if (!t || !t.isInstanceAlive) continue;
234 if (!t.invincible) t.smashMe();
237 if (t.invincible || fabs(xVel) < 1 || t isa ObjBoulder) {
241 if (!t.solid) continue;
243 if (removeBottomTiles) {
245 smashedSomething = true;
247 if (fabs(xVel) < 1 || t.iy > iy+24/*22*/) {
248 writeln("boulder: go up");
251 //writeln("boulder hits '", GetClassName(t.Class), "' with speed (", xVel, ",", yVel, ")");
252 if (fabs(xVel) >= 1) {
254 smashedSomething = true;
262 if (smashedSomething) playSound('sndCrunch');
263 if (inviHit) xVel = -xVel*(global.config.boulderChaos ? 0.8 : 0.5);
266 foreach (auto idx; 0..14) {
267 if (!isCollisionBottom(0)) break;
268 writeln(" UP: ", idx);
273 if (goUp && fabs(xVel) > 1.2) {
274 foreach (auto idx; 0..14) {
275 if (!isCollisionBottom(0)) break;
276 //writeln(" UP: ", idx);
282 if (xVel > 0) xVel -= 0.1; else if (xVel < 0) xVel += 0.1;
283 if (fabs(xVel) < 1) xVel = 0;
286 if (exitWasHit && fabs(xVel) < 0.5 && fabs(yVel) < 0.5) {
291 removeBottomTiles = false;
297 override void thinkFrame () {
298 //writeln("!!! yVel=", yVel, "; myGrav=", myGrav);
300 //writeln("ixy=(", ix, ",", iy, "); xy0=(", x0, ",", y0, "); size=(", width, "x", height, "); cwp=", collidesWith(level.player));
302 auto plr = level.player;
303 // no need to check for collision with a player, it is done in PlayerPawn
305 // so we won't collide with ourselves
308 //auto oxVel = xVel, oyVel = yVel;
319 // check for player crush
320 if (!plr.dead && fabs(xVel) > 0.5 || fabs(yVel > 0.5)) {
321 if (plr.collidesWith(self)) {
326 //if (yVel < 8) yVel += myGrav;
327 yVel = fmin(yVel+myGrav, 8);
330 if (ix-1 <= 16 && xVel < 0) { fltx = ix+1; xVel = -xVel; }
331 if (ix+33 >= level.tilesWidth*16-16 && xVel > 0) { fltx = ix-1; xVel = -xVel; }
335 auto web = level.isObjectInRect(ix+8, iy+8, 16, 16, delegate bool (MapObject o) { return (o isa ItemWeb); });
337 web.instanceRemove();
343 if (isCollisionTop(1) && yVel < 0) yVel = -yVel*0.8;
344 if (isCollisionBottom(1)) {
347 removeBottomTiles = (bounced && yVel > 6);
348 writeln("boulder bounce: bounced=", bounced, "; yVel=", yVel, "; removeBottomTiles=", removeBottomTiles);
349 yVel = -yVel*(global.config.boulderChaos ? (bounced ? 0.3 : 0.5) : (bounced ? 0.0 : 0.3));
350 if (!removeBottomTiles) {
351 foreach (auto idx; 0..9) {
352 if (!isCollisionBottom(0)) break;
353 //writeln(" UPX: ", idx);
357 while (!isCollisionBottom(-2)) flty = iy+1;
360 if (global.config.boulderChaos) {
361 yVel = -yVel*(bounced ? 0.3 : 0.5);
363 if (!bounced) yVel = -yVel*0.5; //0.3
364 else yVel = -yVel*0.3; // else yVel = 0;
367 yVel = -yVel*(bounced ? 0.0 : 0.3);
369 if (!bounced) yVel = -yVel*0.3; //0.3
370 else yVel = 0; // else yVel = 0;
378 if (fabs(xVel) != 0) {
379 //if (global.config.boulderChaos) xVel *= 0.999; else xVel *= 0.99;
380 xVel *= (global.config.boulderChaos ? 0.999 : 0.99);
382 if (!bounced && xVel == 0) {
384 // bowl the monkey if monkey triggered trap
386 auto thief = level.findNearestEnemy(ix, iy, delegate bool (MapEnemy o) { return (o isa EnemyMonkey); });
387 dx = (thief ? thief.ix+8 : (global.randOther(0, 1) ? -1 : 1));
389 // otherwise bowl at player
392 xVel = (global.config.boulderChaos ? 5.0 : 4.5)*(dx < ix+15 ? -1 : 1);
395 if (fabs(xVel) < 0.5) xVel = 0;
399 if (!level.isSolidAtPoint(ix, iy+16)) {
400 bool colLeft = !!level.isSolidInRect(ix-16, iy-16, 9, 33/*, false, true*/);
401 bool colRight = !!level.isSolidInRect(ix+8, iy-16, 9, 33/*, false, true*/);
402 if (colLeft && !colRight) fltx += 1; else if (colRight && !colLeft) fltx -= 1;
406 if (!level.isSolidAtPoint(ix+15, iy+32)) {
407 bool colLeft = !!level.isSolidInRect(ix, iy, 8, 32);
408 bool colRight = !!level.isSolidInRect(ix+24, iy, 8, 32);
409 if (colLeft && !colRight) fltx = ix+1;
410 else if (colRight && !colLeft) fltx = ix-1;
413 imageSpeed = fabs(xVel)/5.0;
415 auto imgFrame = imageFrame;
416 if (xVel < 0) setSprite('sBoulderRotateL');
417 else if (xVel > 0) setSprite('sBoulderRotateR');
418 else setSprite('sBoulder');
419 imageFrame = imgFrame;
421 if (xVel == 0) fltx = ix;
422 if (yVel == 0) flty = iy;
425 if (xVel == 0 && fabs(yVel) < 1) {
426 bool colLeft = !!isCollisionLeft(0);
427 bool colRight = !!isCollisionRight(0);
428 if (colLeft && !colRight) fltx = ix+1;
429 else if (colRight && !colLeft) fltx = ix-1;
433 while (isCollisionBottom(0)) flty = iy-1;
439 if (xVel == 0 and yVel == 0) {
440 instance_create(x, y, oBoulderStatic);
448 objType = 'oBoulder';
450 desc2 = "A large, heavy boulder.";
451 spriteName = 'sBoulder';
452 ignoreFrameOffsetX = true;
453 ignoreFrameOffsetY = true;
455 toSpecialGrid = true; // it need to think, so...