fixes for API renaming
[k8vacspelynky.git] / mapent / enemies / monkey.vc
blobb50ec50c28a32c74aff8259545a85c347ff412b6
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 EnemyMonkey['oMonkey'] : MapEnemy;
22 IDLE = 0;
23 BOUNCE = 1;
24 RECOVER = 2;
25 WALK = 3;
26 DROWNED = 4;
27 HANG = 5;
28 CLIMB = 6;
29 GRAB = 7;
32 const int minDist = 80;
34 enum VDir {
35   Up,
36   Down,
39 VDir vdir = VDir.Up;
41 int grabCounter;
42 int grabX, grabY;
43 int throwCounter;
44 int vineCounter;
46 int poopCount = 0;
47 int poopTimer = 0;
48 bool canPoop = true;
49 bool poop = false;
52 override bool initialize () {
53   if (!::initialize()) return false;
54   setSprite('sMonkeyLeft');
55   dir = global.randOther(0, 1);
56   return true;
60 void doPoopTimer () {
61        if (poopCount < 20) canPoop = true;
62   else if (poopCount < 30 && global.randOther(1, 3) == 1) canPoop = true;
63   else if (global.randOther(1, 8) == 1) canPoop = true;
64   else poopTimer = global.randOther(30, 60);
65   //writeln("POOP TIMER: ", poopTimer, "; canPoop=", canPoop);
69 // ////////////////////////////////////////////////////////////////////////// //
70 // for dead players too
71 // first, the code will call `onObjectTouched()` for player
72 // if it returned `false`, the code will call this handler
73 // note that player's handler is called *after* its frame thinker,
74 // but object handler is called *before* frame thinker for the object
75 // return `true` to skip thinker on this frame
76 override bool onTouchedByPlayer (PlayerPawn plr) {
77   if (plr.dead || status == DEAD || dead) return false;
78   if ((stunned || status == STUNNED) && !global.hasSpikeShoes) return false;
80   if (fabs(plr.ix-(ix+8)) > 4 || status == GRAB || !plr.visible) {
81     // do nothing
82   } else if (!plr.dead && (plr.status == JUMPING || plr.status == FALLING) && plr.iy < iy+2 && !plr.swimming) {
83     plr.yVel = -6-0.2*plr.yVel;
84     plr.fallTimer = 0;
85     //if (!invincible) hp -= 1;
86     plr.playSound('sndHit');
87   } else if (!meGoldMonkey && !plr.invincible && grabCounter == 0) {
88     if (iy+8 > plr.iy+2) flty = plr.iy+2-8;
89     if (iy+8 < plr.iy-2) flty = plr.iy-2-8;
90     status = GRAB;
91     xVel = 0;
92     yVel = 0;
93     grabX = ix-plr.ix;
94     grabY = iy-plr.iy;
95     counter = global.randOther(40, 80);
96   }
98   return false;
102 // ////////////////////////////////////////////////////////////////////////// //
103 bool onItemWalkBy (MapItem o) {
104   //if (meGoldMonkey) return true; // do nothing
105   if (throwCounter == 0 && status != GRAB && o.active && !o.heldBy) {
106     auto rope = ItemRopeThrow(o);
107     if (rope) {
108       if (!rope.falling) {
109         rope.xVel = (dir == Dir.Right ? 5 : -5);
110         rope.yVel = -4;
111         if (!level.isSolidAtPoint(rope.ix, rope.iy)) rope.flty -= 2;
112         throwCounter = 60;
113         status = IDLE;
114         counter = global.randOther(20, 60);
115         return true;
116       }
117     } else {
118       writeln("monkey kicked item '", GetClassName(o.Class), "'");
119       o.xVel = (dir == Dir.Right ? 5 : -5);
120       o.yVel = -4;
121       if (!level.isSolidAtPoint(o.ix, o.iy-2)) o.flty -= 2;
122       throwCounter = 60;
123       status = IDLE;
124       counter = global.randOther(20, 60);
125       auto idol = ItemGoldIdol(o);
126       if (idol && idol.trigger) {
127         idol.sacrificeAllowed = true;
128         idol.cursed = false;
129         idol.trigger = false;
130         level.scrTriggerIdolAltar(stolenIdol:false);
131       }
132       return true;
133     }
134   }
135   return false;
139 // ////////////////////////////////////////////////////////////////////////// //
140 override void thinkFrame () {
141   ::thinkFrame();
142   if (!isInstanceAlive) return;
144   if (!heldBy) {
145     moveRel(xVel, yVel);
146     if (status != HANG && status != CLIMB && status != GRAB) yVel += myGrav;
147     yVel = fmin(yVel, yVelLimit);
148   } else {
149     xVel = 0;
150     yVel = 0;
151   }
153   auto plr = level.player;
155   int x = ix, y = iy;
157   if (!heldBy && (plr.dead || status != GRAB) && level.isSolidAtPoint(x+8, y+8)) hp = -999;
159   if (level.isWaterAtPoint(x+8, y+8)) {
160     if (!swimming) {
161       level.MakeMapObject(x+8, y, 'oSplash');
162       swimming = true;
163       playSound('sndSplash');
164     }
165   } else {
166     swimming = false;
167   }
169   if (hp < 1) {
170     spillBlood();
171     if (countsAsKill) level.addKill(objName);
172     instanceRemove();
173     return;
174   }
176   if (poopTimer > 0) {
177     if (--poopTimer == 0) doPoopTimer();
178   }
180   if (meGoldMonkey && canPoop) {
181     // I'm ready to poop!
182     //writeln("poop=", poop);
183     if (poop) {
184       // I'm going to poop right now!
185       poop = false;
186       ++poopCount;
187       MapObject obj;
188            if (global.randOther(1, 50) == 1) obj = level.MakeMapObject(x+8, y+10, 'oRuby');
189       else if (global.randOther(1, 40) == 1) obj = level.MakeMapObject(x+8, y+10, 'oGoldNugget');
190       else if (global.randOther(1, 30) == 1) obj = level.MakeMapObject(x+8, y+10, 'oSapphire');
191       else if (global.randOther(1, 20) == 1) obj = level.MakeMapObject(x+8, y+10, 'oEmerald');
192       else obj = level.MakeMapObject(x+8, y+10, 'oGoldChunk');
193       if (obj) {
194         obj.xVel = global.randOther(3, 5);
195         if (dir == Dir.Right) obj.xVel *= -1;
196         obj.yVel = -global.randOther(2, 4);
197       }
198     }
199     canPoop = false;
200     poopTimer = global.randOther(60, 120); // Perhaps I will poop again soon.
201   }
203   bool colBot = !!isCollisionBottom(1);
205   if (grabCounter > 0) grabCounter -= 1;
206   if (vineCounter > 0) vineCounter -= 1;
207   if (throwCounter > 0) throwCounter -= 1;
208   if (invincible > 0) invincible -= 1;
210   if (heldBy) {
211     //if (status == HANG) status == IDLE;
212     if (/*status == IDLE &&*/ global.randOther(1, 140) == 1) poop = true;
213     //writeln("HOLD; status==IDLE:", status == IDLE, "; status=", status);
214     //setSprite('sMonkeyWalkL');
215   } else {
216     if (isCollisionRight(1)) xVel = -1;
217     if (isCollisionLeft(1)) xVel = 1;
219     auto dist = distanceToEntityCenter(plr);
220     if (!plr.visible) dist = 64;
222     if (status == IDLE) {
223       xVel = 0;
224       if (counter > 0) --counter; else status = WALK;
225            if (dist < 64) status = BOUNCE;
226       else if (meGoldMonkey && global.randOther(1, 140) == 1) poop = true;
227       //if (status == BOUNCE) playSound(global.sndFrog); // in the original
228     } else if (status == WALK) {
229       if (isCollisionLeft(1) || isCollisionRight(1)) {
230         dir ^= 1;
231       }
232       xVel = (dir == Dir.Left ? -2 : 2);
233       if (global.randOther(1, 100) == 1) {
234         status = IDLE;
235         counter = global.randOther(20, 50);
236         if (meGoldMonkey && global.randOther(1, 6) == 1) poop = true;
237         xVel = 0;
238       } else if (global.randOther(1, 140) == 1) {
239         status = BOUNCE;
240         if (meGoldMonkey && global.randOther(1, 10) == 1) poop = true;
241       }
242     } else if (status == RECOVER) {
243       if (colBot) {
244         status = IDLE;
245         xVel = 0;
246         yVel = 0;
247         counter = global.randOther(10, 40);
248       } else if (isCollisionLadder()) {
249         if (vineCounter == 0) {
250           MapTile obj = isCollisionLadder();
251           if (obj) {
252             fltx = obj.ix;
253             flty = obj.iy;
254             status = HANG;
255             xVel = 0;
256             yVel = 0;
257             counter = global.randOther(10, 40);
258           }
259         }
260       }
261     } else if (status == BOUNCE) {
262       if (colBot) {
263         yVel = -global.randOther(4, 5);
264         if (plr.ix < x+8) {
265           dir = Dir.Left;
266           xVel = -2;
267         } else {
268           dir = Dir.Right;
269           xVel = 2;
270         }
271       } else {
272         status = RECOVER;
273         playSound('sndMonkey');
274       }
275     } else if (status == HANG) {
276       if (!isCollisionLadder()) status = IDLE;
277       xVel = 0;
278       yVel = 0;
279       if (counter > 0) {
280         --counter;
281       } else {
282         status = CLIMB;
283         // dir = rand(0, 1);
284       }
285     } else if (status == CLIMB) {
286       xVel = 0;
287       if (vdir == VDir.Up) {
288         yVel = -1;
289         //if (not collision_point(x+8, y, oVine, 0, 0))
290         if (!level.isLadderOrPlatformAtPoint(x+8, y)) {
291           vdir = VDir.Down;
292           status = HANG;
293           counter = global.randOther(10, 40);
294         }
295       } else {
296         yVel = 1;
297         //if (not collision_point(x+8, y+22, oVine, 0, 0))
298         if (!level.isLadderOrPlatformAtPoint(x+8, y+22)) {
299           vdir = VDir.Up;
300           status = HANG;
301           counter = global.randOther(10, 40);
302         }
303       }
304       ///if (dist &lt; 64 and oCharacter.y &gt; y)
305       if (dist < 64 && plr.iy > y+8) {
306         status = BOUNCE;
307         vineCounter = 30;
308         yVel = -global.randOther(2, 4);
309         if (plr.ix < x) {
310           dir = Dir.Left;
311           xVel = -3;
312         } else {
313           dir = Dir.Right;
314           xVel = 3;
315         }
316       }
317     } else if (status == GRAB) {
318       xVel = 0;
319       yVel = 0;
320       depth = 120;
321       fltx = plr.fltx+grabX;
322       flty = plr.flty+grabY;
323       // grid and interpolation fix start
324       prevFltX = plr.prevFltX+grabX;
325       prevFltY = plr.prevFltY+grabY;
326       updateGrid();
327       // grid and interpolation fix end
328       x = ix;
329       y = iy;
330       dir = plr.dir;
331       if (counter > 0) {
332         --counter;
333       } else {
334         int n = 500+trunc(ceil(500.0/4.0)*global.levelType);
335         if (global.randOther(1, 4) == 1) {
336           // trip player
337           plr.xVel = (plr.dir == Dir.Left ? -3 : 3);
338           plr.yVel = -3;
339           plr.stunned = true;
340           plr.stunTimer = 40;
341           plr.status = 16;
342           plr.scrDropItem(LostCause.Monkey, 0, 0);
343           plr.playSound('sndHit');
344         } else if (level.stats.money >= n && global.randOther(1, 10) <= 8) {
345           // throw out money
346           level.stats.takeMoney(n);
347           auto obj = ItemTreasure(level.MakeMapObject(x, y, 'oGoldNugget'));
348           if (obj) {
349             obj.canCollect = false;
350             obj.collectRestoreTimer = 20;
351             obj.xVel = global.randOther(1, 3)-global.randOther(1, 3);
352             obj.yVel = -global.randOther(3, 4);
353           }
354           playSound('sndThrow');
355         } else if (global.randOther(1, 2) == 1 && global.rope > 0) {
356           // throw out rope
357           global.rope -= 1;
358           // from hands?
359           if (global.rope == 0 && plr.isHoldingRope()) {
360             auto it = plr.holdItem;
361             plr.holdItem = none;
362             it.instanceRemove();
363           }
364           auto obj = level.MakeMapObject(x, y, 'oRopeThrow');
365           if (obj) {
366             obj.xVel = global.randOther(1, 3)-global.randOther(1, 3);
367             obj.yVel = -global.randOther(3, 4);
368           }
369           playSound('sndThrow');
370         } else if (global.bombs > 0) {
371           // throw out bomb
372           global.bombs -= 1;
373           // from hands?
374           int throwArmed = -1;
375           if (global.bombs == 0 && plr.isHoldingBomb()) {
376             auto it = plr.holdItem;
377             if (plr.isHoldingArmedBomb()) throwArmed = ItemBomb(it).alarmCount;
378             plr.holdItem = none;
379             it.instanceRemove();
380           }
381           auto obj = ItemBomb(level.MakeMapObject(x, y, 'oBomb'));
382           if (obj) {
383             obj.xVel = global.randOther(1, 3)-global.randOther(1, 3);
384             obj.yVel = -global.randOther(3, 4);
385             int boom = (global.config.bizarre ? 7 : 10);
386             if (throwArmed >= 0 || global.randOther(1, boom) == 1) {
387               /*
388               obj.sprite_index = sBombArmed;
389               obj.armed = true;
390               obj.image_speed = 1;
391               obj.alarm[1] = 40;
392               */
393               if (throwArmed < 0) throwArmed = 40;
394               obj.armIt(throwArmed);
395             }
396           }
397           playSound('sndThrow');
398         }
399         status = BOUNCE;
400         vineCounter = 20;
401         yVel = -global.randOther(2, 4);
402         if (plr.ix > x+8) {
403           dir = Dir.Left;
404           xVel = -3;
405         } else {
406           dir = Dir.Right;
407           xVel = 3;
408         }
409         grabCounter = 60;
410         invincible = 30; // prevent being hit with item thrown
411       }
412     } else if (status != DROWNED) {
413       status = IDLE;
414       xVel = 0;
415     }
417     if (status != GRAB && isCollisionTop(1)) yVel = 1;
418   }
420        if (status == HANG) setSprite('sMonkeyHangL');
421   else if (status == CLIMB || status == GRAB) setSprite('sMonkeyClimbL');
422   else if (!colBot) setSprite('sMonkeyJumpL');
423   else if (status == WALK) setSprite('sMonkeyWalkL');
424   else setSprite('sMonkeyLeft');
426   // kick items
427   if (!meGoldMonkey && throwCounter == 0 && status != GRAB) {
428     level.isObjectInRect(x0, y0, width, height, delegate bool (MapObject o) {
429       auto item = MapItem(o);
430       if (item && !item.heldBy && item.active) return onItemWalkBy(item);
431       return false;
432     });
433   }
437 defaultproperties {
438   objName = 'Monkey';
439   desc = "Monkey";
440   desc2 = "An amazingly irritating primate that robs and stuns passerby.";
442   setCollisionBounds(4, 6, 12, 16);
443   xVel = 0;
444   yVel = 0;
445   yDelta = -0.4;
446   myGrav = 0.2;
447   yVelLimit = 6; // YASM 1.7
448   imageSpeed = 0.4;
450   // stats
451   hp = 1;
452   invincible = 0;
454   // status
455   status = HANG;
457   grabX = 0;
458   grabY = 0;
459   grabCounter = 0;
461   counter = 0;
462   //bounceCounter = 0;
463   vineCounter = 0;
464   throwCounter = 60;
466   doBasicPhysics = false;
467   countsAsKill = true;
468   leavesBody = true; // we will do our own death sequence
469   checkInsideBlock = false;
470   allowWaterProcessing = false;
472   // for gold monkey
473   poopCount = 0;
474   canPoop = false;
475   poop = false;
477   depth = 40;
481 // ////////////////////////////////////////////////////////////////////////// //
482 class EnemyGoldMonkey['oMonkeyGold'] : EnemyMonkey;
485 override void drawWithOfs (int xpos, int ypos, int scale, float currFrameDelta) {
486   //image_blend = make_color_rgb(255, 245, 104);
487   auto oclr = GLVideo.color;
488   GLVideo.color = 0xff_f5_68;
489   ::drawWithOfs(xpos, ypos, scale, currFrameDelta);
490   GLVideo.color = oclr;
494 defaultproperties {
495   meGoldMonkey = true;
496   canPoop = true;
497   desc = "Gold Monkey";
498   desc2 = "This monkey is SPECIAL.";