fixed black market generator: if "generate lake" flag is also set, black market will...
[k8vacspelynky.git] / mapent / tiles / boulder.vc
blobb02bbc1879542ad3fb4a3f7c18422ce15ba9d483
1 /**********************************************************************************
2  * Copyright (c) 2008, 2009 Derek Yu and Mossmouth, LLC
3  * Copyright (c) 2010, Moloch
4  * Copyright (c) 2018, Ketmar Dark
5  *
6  * This file is part of Spelunky.
7  *
8  * You can redistribute and/or modify Spelunky, including its source code, under
9  * the terms of the Spelunky User License.
10  *
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.
13  *
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/>
17  *
18  **********************************************************************************/
19 class ObjBoulder['oBoulder'] : MapTile;
21 bool monkey; // if trap was triggered by a monkey
22 bool bounced;
23 bool rerolled;
24 int integrity;
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;
41   bool doMirror;
42   int fx0, fy0, fx1, fy1;
43   auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
44   fx0 = 0;
45   fy0 = 0;
46   fx1 = 32;
47   fy1 = 32;
49   // non-moveable tiles need not to be interpolated
50   int drwx, drwy;
51   getInterpCoords(currFrameDelta, scale, out drwx, out drwy);
53   for (MapBackTile bt = bgback; bt; bt = bt.next) {
54     bt.fltx += fltx;
55     bt.flty += flty;
56     bt.drawWithOfs(xpos, ypos, scale, currFrameDelta);
57     bt.fltx -= fltx;
58     bt.flty -= flty;
59   }
61   if (spf) {
62     fx0 = drwx+fx0*scale-xpos;
63     fy0 = drwy+fy0*scale-ypos;
64     fx1 = drwx+fx1*scale-xpos;
65     fy1 = drwy+fy1*scale-ypos;
66     if (!doMirror) {
67       spf.tex.blitExt(fx0, fy0, fx1, fy1, 0, 0, spf.width, spf.height);
68     } else {
69       spf.tex.blitExt(fx0, fy0, fx1, fy1, spf.width, 0, 0, spf.height);
70     }
71   } else if (ore > 0) {
72     fx0 = drwx-xpos;
73     fy0 = drwy-ypos;
74     fx1 = drwx-xpos;
75     fy1 = drwy-ypos;
76   }
78   if (ore > 0) {
79     auto ospr = level.sprStore[ore == 1 ? 'sGold' : 'sGoldBig'];
80     ospr.frames[0].blitAt(fx0, fy0, scale);
81   }
83   for (MapBackTile bt = bgfront; bt; bt = bt.next) {
84     bt.fltx += fltx;
85     bt.flty += flty;
86     bt.drawWithOfs(xpos, ypos, scale, currFrameDelta);
87     bt.fltx -= fltx;
88     bt.flty -= flty;
89   }
94 override bool onExplosionTouch (MapObject xplo) {
95   /+
96   /*if (smashed)*/ scrSprayRubble(ix, iy, 4);
97   scrDropRubble(ix, iy, 3);
98   +/
99   crashBoulder();
100   instanceRemove();
101   return true;
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);
114     rubble.yAcc = 0.6;
115     //rubble.image_blend = image_blend;
116   }
121 void crashBoulder () {
122   foreach (; 0..3) {
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);
129     rubble.yAcc = 0.6;
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);
137     rubble.yAcc = 0.6;
139     if (global.randOther(1, 3) == 1) {
140       level.MakeMapObject(
141         xCenter+global.randOther(0, 15)-global.randOther(0, 15),
142         yCenter+global.randOther(0, 15)-global.randOther(0, 15), 'oRock');
143     }
144     /*
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);
150     */
151   }
152   foreach (; 0..6) {
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);
162       rubble.yAcc = 0.6;
163     }
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);
172     rubble.yAcc = 0.6;
173   }
174   playSound('sndBreak');
175   instanceRemove();
179 override void doSprayRubble () {
180   crashBoulder();
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;
203   spectral = true;
205   // check if we'll run into solids
206   solidsList.clear();
207   exitWasHit = false;
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;
212     // exit?
213     if (t.exit /*&& fabs(xVel) < 0.5 && fabs(yVel) < 0.5*/) {
214       //crashBoulder();
215       exitWasHit = true;
216       return false;
217     }
218     // ignore non-solid invincible tiles
219     if (!t.solid && !t.spikes) return false;
220     bool found = false;
221     foreach (MapTile st; solidsList) if (st == t) { found = true; break; }
222     if (!found) solidsList[$] = t;
223     return false;
224   }, precise:true);
226   //writeln("boulder: processing ", solidsList.length, " tiles");
227   bool inviHit = false;
228   bool goUp = false;
229   bool smashedSomething = false;
230   foreach (MapTile t; solidsList) {
231     if (!t || !t.isInstanceAlive) continue;
232     // destroy spikes
233     if (t.spikes) {
234       if (!t.invincible) t.smashMe();
235       continue;
236     }
237     if (t.invincible || fabs(xVel) < 1 || t isa ObjBoulder) {
238       inviHit = true;
239       continue;
240     }
241     if (!t.solid) continue;
242     solidWasHit = true;
243     if (removeBottomTiles) {
244       t.smashMe();
245       smashedSomething = true;
246     } else {
247       if (fabs(xVel) < 1 || t.iy > iy+24/*22*/) {
248         writeln("boulder: go up");
249         goUp = true;
250       } else {
251         //writeln("boulder hits '", GetClassName(t.Class), "' with speed (", xVel, ",", yVel, ")");
252         if (fabs(xVel) >= 1) {
253           t.smashMe();
254           smashedSomething = true;
255         }
256       }
257     }
258   }
259   solidsList.clear();
261   // effects
262   if (smashedSomething) playSound('sndCrunch');
263   if (inviHit) xVel = -xVel*(global.config.boulderChaos ? 0.8 : 0.5);
265   /*
266   foreach (auto idx; 0..14) {
267     if (!isCollisionBottom(0)) break;
268     writeln("  UP: ", idx);
269     flty = iy-1;
270   }
271   */
273   if (goUp && fabs(xVel) > 1.2) {
274     foreach (auto idx; 0..14) {
275       if (!isCollisionBottom(0)) break;
276       //writeln("  UP: ", idx);
277       flty = iy-1;
278     }
279   }
281   if (solidWasHit) {
282     if (xVel > 0) xVel -= 0.1; else if (xVel < 0) xVel += 0.1;
283     if (fabs(xVel) < 1) xVel = 0;
284   }
286   if (exitWasHit && fabs(xVel) < 0.5 && fabs(yVel) < 0.5) {
287     crashBoulder();
288     return;
289   }
290   exitWasHit = false;
291   removeBottomTiles = false;
293   spectral = ospec;
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
306   spectral = true;
308   //auto oxVel = xVel, oyVel = yVel;
309   /*
310   moveRel(xVel, yVel);
311   xVel = oxVel;
312   yVel = oyVel;
313   //x += xVel;
314   //y += yVel;
315   */
317   shiftXY(xVel, yVel);
319   // check for player crush
320   if (!plr.dead && fabs(xVel) > 0.5 || fabs(yVel > 0.5)) {
321     if (plr.collidesWith(self)) {
322       global.plife = -99;
323     }
324   }
326   //if (yVel < 8) yVel += myGrav;
327   yVel = fmin(yVel+myGrav, 8);
329   // level boundaries
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; }
333   // tear webs
334   for (;;) {
335     auto web = level.isObjectInRect(ix+8, iy+8, 16, 16, delegate bool (MapObject o) { return (o isa ItemWeb); });
336     if (!web) break;
337     web.instanceRemove();
338   }
340   destructTerrain();
343   if (isCollisionTop(1) && yVel < 0) yVel = -yVel*0.8;
344   if (isCollisionBottom(1)) {
345     // bounce
346     if (yVel > 3) {
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);
354           flty = iy-1;
355         }
356       } else {
357         while (!isCollisionBottom(-2)) flty = iy+1;
358       }
359       /+
360       if (global.config.boulderChaos) {
361         yVel = -yVel*(bounced ? 0.3 : 0.5);
362         /*
363         if (!bounced) yVel = -yVel*0.5; //0.3
364         else yVel = -yVel*0.3; // else yVel = 0;
365         */
366       } else {
367         yVel = -yVel*(bounced ? 0.0 : 0.3);
368         /*
369         if (!bounced) yVel = -yVel*0.3; //0.3
370         else yVel = 0; // else yVel = 0;
371         */
372       }
373       +/
374     } else {
375       yVel = 0;
376     }
377     // friction
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);
381     }
382     if (!bounced && xVel == 0) {
383       int dx;
384       // bowl the monkey if monkey triggered trap
385       if (monkey) {
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));
388       } else {
389         // otherwise bowl at player
390         dx = plr.ix;
391       }
392       xVel = (global.config.boulderChaos ? 5.0 : 4.5)*(dx < ix+15 ? -1 : 1);
393       bounced = true;
394     }
395     if (fabs(xVel) < 0.5) xVel = 0;
396   }
398   /+
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;
403   }
404   +/
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;
411   }
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;
424   // horizontal snap
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;
430   }
432   if (xVel == 0) {
433     while (isCollisionBottom(0)) flty = iy-1;
434   }
436   spectral = false;
438   /*
439   if (xVel == 0 and yVel == 0) {
440     instance_create(x, y, oBoulderStatic);
441     instance_destroy();
442   }
443   */
447 defaultproperties {
448   objType = 'oBoulder';
449   desc = "Boulder";
450   desc2 = "A large, heavy boulder.";
451   spriteName = 'sBoulder';
452   ignoreFrameOffsetX = true;
453   ignoreFrameOffsetY = true;
454   myGrav = 0.6;
455   toSpecialGrid = true; // it need to think, so...
456   solid = true;
457   depth = 200;
458   //moveable = true;