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 EnemyJaws['oJaws'] : MapEnemy;
21 const int bubbleTimerMax = 40;
27 override bool initialize () {
28 if (!::initialize()) return false;
29 setSprite('sJawsLeft');
30 foreach (ref auto n; crates) {
31 auto obj = level.MakeMapObject(ix, iy, 'oJawsCrate');
40 writeln("*** JAWS STUCK!");
41 while (isCollision() && !isCollisionTop(1)) shiftY(-1);
47 override void onAnimationLooped () {
48 if (spriteLName == 'sJawsTurnL') {
50 setSprite('sJawsLeft');
53 setCollisionBounds(0, 0, 64, 32);
54 } else if (spriteLName == 'sJawsTurnR') {
56 setSprite('sJawsRight');
61 setCollisionBounds(-48, 0, 16, 32);
66 // ////////////////////////////////////////////////////////////////////////// //
67 // for bullets and whip: always use bounding box
68 //TODO: pixel-perfect with head and body
69 override bool collidesWith (MapEntity e, optional bool ignoreDims) {
70 if (!e || width < 1 || height < 1) return false;
71 if (e == self) return false; // never
72 //if (e isa WeaponWhipBase) writeln("collides with whip: ", isRectHitSimple(e.x0, e.y0, e.width, e.height));
73 //if (e isa PlayerPawn) writeln("collides with player: ", isRectHitSimple(e.x0, e.y0, e.width, e.height));
74 return isRectHitSimple(e.x0, e.y0, e.width, e.height);
78 override bool onTouchedByPlayer (PlayerPawn plr) {
79 //writeln("*** MEGAMOUTH TOUCHED THE PLAYER!");
80 if (dead || status >= STUNNED || plr.dead) return false;
87 if (plr.invincible == 0) {
90 if (plr.status != CLIMBING) {
91 plr.xVel = (plrx < x ? -6 : 6);
93 if (global.plife > 0) {
94 global.plife -= damage;
95 if (global.plife < 0) level.addDeath(objName);
97 plr.playSound('sndHurt');
99 return false; // don't skip thinker
103 // ////////////////////////////////////////////////////////////////////////// //
104 override void drawWithOfs (int xpos, int ypos, int scale, float currFrameDelta) {
106 getInterpCoords(currFrameDelta, scale, out xi, out yi);
109 int fx0, fy0, fx1, fy1;
110 auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
113 auto oclr = GLVideo.color;
114 GLVideo.color = oclr|(trunci(fclamp(255.0-255*imageAlpha, 0.0, 255.0))<<24);
116 fx0 = xi+fx0*scale-xpos;
117 fy0 = yi+fy0*scale-ypos;
118 fx1 = xi+fx1*scale-xpos;
119 fy1 = yi+fy1*scale-ypos;
121 spf.tex.blitExt(fx0, fy0, fx1, fy1, 0, 0, spf.width, spf.height, angle:imageAngle);
123 spf.tex.blitExt(fx0, fy0, fx1, fy1, spf.width, 0, 0, spf.height, angle:imageAngle);
129 if (spriteLName == 'sJawsLeft') {
131 if (hp < 10) jbody = 'sJawsBody3L';
132 else if (hp < 20) jbody = 'sJawsBody2L';
133 else jbody = 'sJawsBody1L';
134 } else if (spriteLName == 'sJawsRight') {
136 if (hp < 10) jbody = 'sJawsBody3R';
137 else if (hp < 20) jbody = 'sJawsBody2R';
138 else jbody = 'sJawsBody1R';
142 auto spr = level.sprStore[jbody];
144 // it has only one frame, nobody cares
146 spf.blitAt(fx0+xsign*scale, fy0, scale:scale, angle:imageAngle);
150 GLVideo.color = oclr;
153 oclr = GLVideo.color;
154 GLVideo.color = 0xff_ff_00;
155 GLVideo.drawRect(x0*scale-xpos, y0*scale-ypos, width*scale, height*scale);
156 GLVideo.color = 0x00_ff_00;
157 GLVideo.drawRect(ix*scale-xpos, iy*scale-ypos, 2, 2);
160 GLVideo.color = 0x3f_ff_00_00;
161 GLVideo.fillRect(x0*scale-xpos, y0*scale-ypos, width*scale, height*scale);
164 GLVideo.color = oclr;
169 // ////////////////////////////////////////////////////////////////////////// //
170 // WARNING: no checks!
176 setCollisionBounds(0, 0, 64, 32);
177 setSprite('sJawsTurnL');
182 // WARNING: no checks!
186 setSprite('sJawsTurnR');
191 // ////////////////////////////////////////////////////////////////////////// //
192 override void thinkFrame () {
194 //if (!isInstanceAlive) return;
196 if (!level.isWaterAtPoint(ix+8, iy+16)) hp -= 1;
199 if (countsAsKill) level.addKill(objName);
200 foreach (; 0..4) scrCreateBlood(ix+22+global.randOther(0, 4), iy+14+global.randOther(0, 4), 1);
201 foreach (; 0..4) level.MakeMapObject(ix+22+global.randOther(0, 4), iy+14+global.randOther(0, 6), 'oBone');
203 foreach (ref auto cobj; crates) {
209 obj.xVel = global.randOther(0, 3)-global.randOther(0, 3);
210 obj.yVel = -global.randOther(1, 2);
212 obj.spectral = false;
217 if (holds != "") scrMakeItem(carries, x+16, y+16, 1); else scrMakeItem(carries, x+16, y+16);
220 //!!with (oBossBlock) dying = true;
225 //auto dist = pointDistance(ix, iy, level.player.ix, level.player.iy);
227 if (status == IDLE) {
230 if (level.isWaterAtPoint(ix+18, iy+16) && !level.isSolidAtPoint(ix+18, iy+16)) {
232 } else if (!level.checkTilesInRect(ix-32, iy, 33, 33)) {
236 if (level.isWaterAtPoint(ix-2, iy+16) && !level.isSolidAtPoint(ix-2, iy+16)) {
238 } else if (!level.checkTilesInRect(ix+16, iy, 33, 33)) {
242 if (!isCollisionBottom(2)) flty += 1;
243 if (level.player.swimming && !level.player.dead) status = ATTACK;
244 } else if (status == PAUSE) {
249 dirAngle = (dirAngle > 90 && dirAngle < 270 ? 180 : 0);
251 } else if (status == ATTACK) {
252 if (level.player.swimming && !level.player.dead) {
253 if (spriteLName == 'sJawsLeft' || spriteLName == 'sJawsRight') {
254 dirAngle = pointDirection(ix+8, iy+16, level.player.ix, level.player.iy-8);
257 if (level.player.ix < ix+8) {
258 if (spriteLName == 'sJawsRight' && !level.checkTilesInRect(ix-32, iy, 33, 33)) {
263 if (spriteLName == 'sJawsLeft' && !level.isSolidAtPoint(ix-2, iy+16)) {
269 if (level.isWaterAtPoint(roundi(fltx+cos(dirAngle)), roundi(flty-sin(dirAngle))) &&
270 !level.isSolidAtPoint(roundi(fltx+cos(dirAngle)), roundi(flty-sin(dirAngle))))
272 moveRel(3*cos(dirAngle), -3*sin(dirAngle));
277 dirAngle = (dirAngle > 90 && dirAngle < 270 ? 180 : 0);
281 if (bubbleTimer > 0) {
284 if (level.isWaterAtPoint(ix, (iy&~0x0f)-8)) level.MakeMapObject(ix, iy+16, 'oBubble');
285 bubbleTimer = bubbleTimerMax;
288 if (spriteLName == 'sJawsLeft') setCollisionBounds(0, 0, 64, 32);
289 else if (spriteLName == 'sJawsRight') setCollisionBounds(-48, 0, 16, 32);
294 objName = 'Megamouth';
296 desc2 = "A rare, enormous variety of piranha with fangs as large as human arm bones. It can eat entire boats, the contents of which are sometimes found in its stomach.";
297 setCollisionBounds(0, 0, 48, 32);
314 doBasicPhysics = false;
315 flying = true; // actually, we are swimming, but meh
319 leavesBody = true; // we will do our own death sequence