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 EnemyOlmec['oOlmec'] : MapEnemy;
34 bool firstThink = true;
38 int alarmEnablePlayer;
41 int alarmAlienEjector;
44 override bool initialize () {
45 if (!::initialize()) return false;
46 setSprite('sOlmecStart1');
48 //dir = global.randRoom(0, 1);
53 override void onBulletHit (ObjBullet bullet) {
57 override bool onTouchedByPlayer (PlayerPawn plr) {
58 return false; // don't skip thinker
62 // return `false` to do standard weapon processing
63 override bool onTouchedByPlayerWeapon (PlayerPawn plr, PlayerWeapon wpn) {
68 override void thinkFrame () {
69 auto plr = level.player;
73 plr.movementBlocked = true;
76 if (alarmEnablePlayer > 0) {
77 if (--alarmEnablePlayer == 0) {
79 view_hborder[0] = 128;
82 view_object[0] = oPlayer1;
84 level.cameraReturnToPlayer();
85 global.forceAllSoundsAtPlayer = false;
86 plr.movementBlocked = false;
89 global.playMusic('musBoss');
90 if (global.config.bizarre && global.config.bizarrePlus) alarmAlienEjector = global.randOther(150, 300);
91 //playSound(global.sndBoss);
95 if (alarmBossAwake > 0) {
96 if (--alarmBossAwake == 0) {
97 switch (bossAwakePhase++) {
99 setSprite(global.config.bizarre ? 'sOlmecStart2B' : 'sOlmecStart2');
101 auto debris = level.MakeMapObject(ix+global.randOther(0, 32), iy+global.randOther(0, 32), 'oOlmecDebris');
103 debris.xVel = -global.randOther(1, 4);
104 debris.yVel = -global.randOther(1, 3);
107 /*plr.*/playSound('sndThump'); // make it local to player, 'cause camera is panned
108 level.forEachObject(delegate bool (MapObject o) {
109 if (o isa ObjHawkmanWorship) {
110 auto hm = level.MakeMapObject(o.ix, o.iy, 'oHawkman');
119 if (o isa ObjCavemanWorship) {
124 }, allowSpectrals:true);
128 setSprite(global.config.bizarre ? 'sOlmecStart3B' : 'sOlmecStart3');
130 auto debris = level.MakeMapObject(ix+32+global.randOther(0, 32), iy+global.randOther(0, 32), 'oOlmecDebris');
132 debris.xVel = global.randOther(1, 4);
133 debris.yVel = -global.randOther(1, 3);
136 /*plr.*/playSound('sndThump'); // make it local to player, 'cause camera is panned
140 setSprite(global.config.bizarre ? 'sOlmec2' : 'sOlmec');
142 auto debris = level.MakeMapObject(ix+32+global.randOther(0, 32), iy+global.randOther(0, 32), 'oOlmecDebris');
145 debris.xVel = global.randOther(-4, 4);
146 } while (debris.xVel == 0);
147 debris.yVel = -global.randOther(1, 3);
150 /*plr.*/playSound('sndThump'); // make it local to player, 'cause camera is panned
156 /*plr.*/playSound('sndBigJump'); // make it local to player, 'cause camera is panned
157 /*plr.*/playSound('sndAlert'); // make it local to player, 'cause camera is panned
161 level.forEachObject(delegate bool (MapObject o) {
162 if (o isa ObjCavemanWorship) {
163 auto obj = level.MakeMapObject(o.ix, o.iy, 'oCaveman');
169 }, allowSpectrals:true);
175 if (alarmAlienEjector > 0) {
176 if (--alarmAlienEjector == 0) {
177 if (global.config.bizarre && global.config.bizarrePlus) {
179 if (view_yview[0] > 288 and view_yview[0] < 416) {
180 instance_activate_object(oAlien);
181 instance_activate_object(oAlienEject);
182 if (instance_number(oAlien)+instance_number(oAlienEject) < 48) {
183 instance_create(24+rand(1, 39)*8, view_yview[0]-8, oAlienEject);
187 alarmAlienEjector = global.randOther(30, 300);
194 auto oldFX = fltx, oldFY = flty;
195 bool fixCarry = (carryPlayer || plr.isRectHitSimple(ix-1, iy, 68, 63));
198 yVel = fmin(yVel+myGrav, 6);
200 if (isCollisionTop(1)) {
201 level.MakeMapObject(ix, iy-16, 'oOlmecSlam');
203 if (yVel < 0) yVel = -yVel*0.8;
206 if (isCollisionLeft(1)) {
209 //if (xVel < 0) xVel = -xVel * 0.8;
211 if (isCollisionRight(1)) {
214 //if (xVel > 0) xVel = -xVel * 0.8;
217 if (level.isLavaAtPoint(ix, iy+64)) status = DROWNING;
219 if (level.isLavaAtPoint(ix, iy-2)) {
221 if (!global.customLevel) {
222 global.enemyKills[21] += 1;
223 oFinalBoss.olmecDead = true;
226 if (isRoom("rLoadLevel")) global.shake = 0;
228 level.onOlmecDead(self);
233 //auto dist = distanceToEntityCenter(plr)+32;
235 carryPlayer = plr.isRectHitSimple(ix, iy-2, 65, 67);
236 //if (collision_rectangle(x, y-2, x+64, y+64, oPlayer1, 0, 0)) carryPlayer = true; else carryPlayer = false;
238 if (status == START1) {
240 if (global.scumWholeLevel) {
244 if (view_xview[0] < 176) {
252 level.cameraSlideToPoint(176, 0, 2, 0);
253 global.forceAllSoundsAtPlayer = true;
254 alarmBossAwake = 100;
255 status = START2; //!!!
256 if (isCollisionBottom(1)) yVel = 0;
257 } else if (status == START2) {
258 if (isCollisionBottom(1)) yVel = 0;
259 } else if (status == IDLE) {
260 if (counter > 0) --counter;
261 if (counter == 0) status = BOUNCE;
262 if (isCollisionBottom(1)) yVel = 0;
264 } else if (status == CREATE) {
266 level.MakeMapObject(ix+32+global.randOther(0, 32)-global.randOther(0, 32), iy+14+global.randOther(0, 32)-global.randOther(0, 32), 'oPsychicCreate2');
268 level.MakeMapObject(ix+32, iy+16, 'oYellowBall');
269 level.MakeMapObject(ix+32, iy+16, 'oYellowBall');
270 level.MakeMapObject(ix+32, iy+16, 'oYellowBall');
271 playSound('sndPsychic');
273 } else if (status == RECOVER) {
274 if (isCollisionBottom(1)) {
275 playSound('sndThump');
279 counter = global.randOther(40, (global.config.gameMode == GameConfig::GameMode.Vanilla ? 100 : 60));
283 } else if (counter == 1) {
284 if (plr.ix < ix) xVel = -0.25;
285 else if (plr.ix > ix+64) xVel = 0.25;
289 if (xVel < 0 && toggle) xVel -= 0.25; else if (xVel < 0 && !toggle) xVel += 0.25;
290 if (xVel > 0 && toggle) xVel += 0.25; else if (xVel > 0 && !toggle) xVel -= 0.25;
291 if (xVel <= -2 || xVel >= 2) toggle = !toggle;
294 //if ((not oPlayer1.active and yVel >= 0) or
295 // (oPlayer1.y > y and abs(oPlayer1.x - (x+32)) < 32 and xVel > -1))
296 if ((plr.movementBlocked && yVel >= 0) ||
297 (plr.iy > iy && abs(plr.ix-(ix+32)) < 32 && xVel > -1 && !plr.dead))
306 } else if (status == BOUNCE) {
307 if (isCollisionBottom(1)) {
312 playSound('sndBigJump');
314 } else if (status == PREPARE) {
323 } else if (status == SLAM) {
326 if (isCollisionBottom(1)) {
328 level.MakeMapObject(ix, iy+64, 'oOlmecSlam');
332 if (plr.movementBlocked || global.randOther(1, 2) == 1) status = IDLE; else status = CREATE;
335 counter = (global.config.gameMode == GameConfig::GameMode.Vanilla ? 60 : 40);
336 if (plr.movementBlocked) alarmEnablePlayer = 50;
339 } else if (status == DROWNING) {
344 if (!sndIsPlaying('sndFlame')) playSound('sndFlame');
345 depth = 1100; // hide behind lava
348 if (isCollisionTop(1)) yVel = 1;
349 if (isCollisionLeft(1) || isCollisionRight(1)) xVel = -xVel;
350 if (isCollision()) flty = iy-2;
353 float dx = fltx-oldFX;
354 float dy = flty-oldFY;
369 desc2 = "A construct built by an ancient civilization, equipped with tremendous crushing force and powerful summoning magic.";
373 setCollisionBounds(2, 0, 62, 64);
381 walkableSolid = true;
386 if (global.customLevel) {
388 sprite_index = sOlmec;
400 if (!global.customLevel) {
402 if (global.bizarre) view_vborder[0] = 48;/*0*/ else view_vborder[0] = 0;
403 if (global.bizarre) view_yview[0] = 272; else view_yview[0] = 400;
404 view_object[0] = oOlmec;
410 // ////////////////////////////////////////////////////////////////////////// //
411 class ObjOlmecDebris['oOlmecDebris'] : MapObject;
414 override bool initialize () {
415 if (!::initialize()) return false;
416 switch (global.randOther(1, 3)) {
417 case 1: setSprite('sOlmecDebris'); break;
418 case 2: setSprite('sOlmecDebris2'); break;
419 case 3: setSprite('sOlmecDebris3'); break;
421 xVel = global.randOtherFloat(4)-global.randOtherFloat(4);
422 yVel = -1-global.randOtherFloat(2);
427 override void thinkFrame () {
433 yVel = fmin(yVel+grav, 6);
434 if (level.checkTileAtPoint(ix, iy+4, delegate bool (MapTile t) { return (t.objType == 'oTemple'); })) {
439 level.MakeMapObject(ix, iy, 'oSmokePuff');
444 if (fabs(xVel) < 0.1) xVel = 0; else if (fabs(xVel) != 0) xVel *= 0.3;
451 objName = 'Olmec Debris';
457 setCollisionBounds(-4, -4, 4, 4);
464 // ////////////////////////////////////////////////////////////////////////// //
465 class ObjOlmecSlam['oOlmecSlam'] : MapObject;
467 //int alarmDeath = 1;
470 override bool initialize () {
471 if (!::initialize()) return false;
472 setSprite('sOlmecSlam');
473 playSound('sndSlam');
478 override void drawWithOfs (int xpos, int ypos, int scale, float currFrameDelta) {
482 override void thinkFrame () {
483 level.checkTilesInRect(ix, iy, 64, 8, delegate bool (MapTile t) {
484 if (!t.isInstanceAlive) return false;
485 //writeln("tile: <", GetClassName(t.Class), ">; objType='", t.objType, "'");
486 //if (t.invincible) return false;
494 //if (--alarmDeath == 0) { instanceRemove(); return; }
500 objName = 'Olmec Slam';
506 // ////////////////////////////////////////////////////////////////////////// //
507 class ObjCavemanWorship['oCavemanWorship'] : MapObject;
510 override bool initialize () {
511 if (!::initialize()) return false;
512 setSprite('sCavemanWorshipR');
517 override void thinkFrame () {
524 desc2 = "A caveman, bent over in worship of the statue.";
531 // ////////////////////////////////////////////////////////////////////////// //
532 class ObjHawkmanWorship['oHawkmanWorship'] : MapObject;
535 override bool initialize () {
536 if (!::initialize()) return false;
537 setSprite('sHawkLeft');
542 override void thinkFrame () {
549 desc2 = "One of Kali's followers, this hawkman believes the statue in front of him is a sign from his goddess. He is sadly mistaken.";
556 // ////////////////////////////////////////////////////////////////////////// //
557 class ItemSfxPsychicCreate2['oPsychicCreate2'] : MapObject;
561 override bool initialize () {
562 if (!::initialize()) return false;
563 setSprite('sPsychicCreate');
564 level.forEachObject(delegate bool (MapObject o) {
565 if (o isa EnemyOlmec) {
566 dirAngle = pointDirection(ix, iy, o.ix+32, o.iy+16);
575 override void onAnimationLooped () {
580 override void thinkFrame () {
581 shiftXY(2*cos(dirAngle), -2*sin(dirAngle));
586 objName = 'Psychic Wave';
594 // ////////////////////////////////////////////////////////////////////////// //
595 class ItemSfxYellowBall['oYellowBall'] : MapObject;
599 override bool initialize () {
600 if (!::initialize()) return false;
601 setSprite('sYellowBall');
602 yVel = -1*(global.randOtherFloat(3)+4);
603 xVel = global.randOther(2, 5);
604 if (global.randOther(1, 2) == 1) xVel *= -1;
610 override void onAnimationLooped () {
616 override void thinkFrame () {
617 if (isOutsideOfLevel()) { instanceRemove(); return; }
619 if (--trailTimer == 0) {
620 level.MakeMapObject(ix, iy, 'oYellowTrail');
627 if (level.checkTilesInRect(ix-8, iy-8, 17, 17) &&
628 !level.isObjectInRect(ix-8, iy-8, 17, 17, delegate bool (MapObject o) { return (o isa EnemyOlmec); }))
630 shiftXY(-xVel, -yVel);
632 if (global.config.bizarre) {
633 switch (global.randOther(1, 13)) {
634 case 1: level.MakeMapObject(ix-8, iy-8, 'oBat'); break;
635 case 2: level.MakeMapObject(ix-8, iy-8, 'oSpider'); break;
636 case 3: level.MakeMapObject(ix-8, iy-8, 'oSnake'); break;
637 case 4: level.MakeMapObject(ix-8, iy-8, 'oFrog'); break;
638 case 5: level.MakeMapObject(ix-8, iy-8, 'oZombie'); break;
639 case 6: level.MakeMapObject(ix-8, iy-8, 'oFireFrog'); break; // oSkeleton
640 case 7: obj = level.MakeMapObject(ix-8, iy-8, 'oMonkey'); if (obj) obj.status = IDLE; break;
642 obj = level.MakeMapObject(ix, iy-16, 'oMagma');
648 case 9: level.MakeMapObject(ix-8, iy-8, 'oFireFrog'); break;
649 case 10: level.MakeMapObject(ix-8, iy-8, 'oCobra'); break;
650 case 11: level.MakeMapObject(ix-8, iy-8, 'oGreenFrog'); break;
651 case 12: level.MakeMapObject(ix-8, iy-10, 'oGreenSpider'); break;
654 auto di = distanceToEntityCenter(level.player)+16;
656 obj = level.MakeMapObject(ix-8, iy-8, 'oFloater');
658 obj.explosive = true;
659 obj.setSprite('sFloaterEvil');
666 if (rand(1,4) == 1) obj.carries = "Bomb Bag";
667 else obj.carries = choose("Big Sapphire", "Big Emerald", "Big Ruby");
671 level.MakeMapObject(ix-8, iy-8, 'oFireFrog');
678 switch (global.randOther(1, 4)) {
679 case 1: level.MakeMapObject(ix-8, iy-8, 'oBat'); break;
680 case 2: level.MakeMapObject(ix-8, iy-8, 'oSpider'); break;
681 case 3: level.MakeMapObject(ix-8, iy-8, 'oSnake'); break;
682 case 4: level.MakeMapObject(ix-8, iy-8, 'oFrog'); break;
690 yVel = fmin(yVel+0.15, 6);
695 objName = 'Yellow Ball';