NXEngine v1.0.0.6
[NXEngine.git] / object.cpp
blob309dc9bd0a1c2e140b7707c0e205d629d7b98972
2 #include "nx.h"
3 #include "common/llist.h"
4 #include "object.fdh"
6 // deletes the specified object, or well, marks it to be deleted.
7 // it's not actually freed till the end of the tick.
8 void Object::Delete()
10 Object * const &o = this;
12 if (o->deleted)
13 return;
15 // make sure no pointers are pointing at us
16 DisconnectGamePointers();
18 // show any damage waiting to be added NOW instead of later
19 if (o->DamageWaiting > 0)
21 DamageText->AddQty(o->DamageWaiting);
22 o->DamageWaiting = 0;
25 // set it's id1 flag, required for some scripts
26 game.flags[o->id1] = true;
28 // mark it for deletion at end of loop
29 // (can't delete now as it may invalidate pointers--we don't know where we were called from)
30 o->deleted = true;
33 void Object::Destroy()
35 Object * const &o = this;
37 // make sure no pointers are pointing at us
38 DisconnectGamePointers();
39 // delete associated floaty text as soon as it's animation is done
40 DamageText->ObjectDestroyed = true;
42 // if any objects are linked to this obj then unlink them
43 Object *link;
44 for(link = firstobject; link; link = link->next)
46 if (link->linkedobject == o)
47 link->linkedobject = NULL;
50 // remove from list and free
51 LL_REMOVE(o, prev, next, firstobject, lastobject);
52 LL_REMOVE(o, lower, higher, lowestobject, highestobject);
53 if (o == player) player = NULL;
55 delete o;
58 // checks all the games pointers that point to an object
59 // record and disconnects them if they are pointing at object o.
60 // used in preparation to delete the object.
61 // protects against dangling pointers.
62 void Object::DisconnectGamePointers()
64 Object * const &o = this;
66 if (o == player->riding) player->riding = NULL;
67 if (o == player->lastriding) player->lastriding = NULL;
68 if (o == player->cannotride) player->cannotride = NULL;
69 if (o == game.bossbar.object) game.bossbar.object = NULL; // any enemy with a boss bar
70 if (o == game.stageboss.object) game.stageboss.object = NULL; // the stage boss
71 if (o == map.focus.target) map.focus.target = NULL;
72 if (o == ID2Lookup[this->id2]) ID2Lookup[this->id2] = NULL;
73 if (o == map.waterlevelobject) map.waterlevelobject = NULL;
77 void c------------------------------() {}
80 void Object::SetType(int type)
82 Object * const &o = this;
84 o->type = type;
85 o->sprite = objprop[type].sprite;
86 o->hp = objprop[type].initial_hp;
87 o->damage = objprop[o->type].damage;
88 o->frame = 0;
90 // apply nxflags to new object type!
91 // (did this so toroko would handle slopes properly in Gard cutscene)
92 o->nxflags = objprop[type].defaultnxflags;
94 // apply defaultflags to new object type, but NOT ALL defaultflags.
95 // otherwise <CNP's _WILL_ get messed up.
96 const static int flags_to_keep = \
97 (FLAG_SCRIPTONTOUCH | FLAG_SCRIPTONDEATH | FLAG_SCRIPTONACTIVATE | \
98 FLAG_APPEAR_ON_FLAGID | FLAG_DISAPPEAR_ON_FLAGID | \
99 FLAG_FACES_RIGHT);
101 uint32_t keep = (o->flags & flags_to_keep);
102 o->flags = (objprop[type].defaultflags & ~flags_to_keep) | keep;
104 //stat("new flags: %04x", o->flags);
106 // setup default clipping extents, in case object turns on clip_enable
107 if (!o->clip_enable)
108 o->ResetClip();
111 void Object::ChangeType(int type)
113 Object * const &o = this;
115 int oldsprite = o->sprite;
117 o->state = 0;
118 o->substate = 0;
119 o->frame = 0;
120 o->timer = 0;
121 o->timer2 = 0;
122 o->animtimer = 0;
124 SetType(type);
126 // adjust position so spawn points of old object and new object line up
127 o->x >>= CSF; o->x <<= CSF;
128 o->y >>= CSF; o->y <<= CSF;
129 o->x += (sprites[oldsprite].spawn_point.x << CSF);
130 o->y += (sprites[oldsprite].spawn_point.y << CSF);
131 o->x -= (sprites[this->sprite].spawn_point.x << CSF);
132 o->y -= (sprites[this->sprite].spawn_point.y << CSF);
134 // added this for when you pick up the puppy in the Deserted House in SZ--
135 // makes objects <CNPed during a <PRI initialize immediately instead of waiting
136 // for <PRI to be released.
137 if (game.frozen)
139 OnTick();
140 OnAftermove();
143 // Sprites appearing out of an OBJ_NULL should generally go to the top of the z-order.
144 // this was originally added so that the Doctor would appear in front of the core
145 // when he teleports in at end of Almond battle (it's since been used in a lot of
146 // other places though).
147 if (oldsprite == SPR_NULL)
149 BringToFront();
152 OnSpawn();
155 // moves an object to the top of the Z-order,
156 // so that it is drawn in front of all other objects.
157 void Object::BringToFront()
159 LL_REMOVE(this, lower, higher, lowestobject, highestobject);
160 LL_ADD_END(this, lower, higher, lowestobject, highestobject);
163 // move an object in the z-order to just below object "behind".
164 void Object::PushBehind(Object *behind)
166 if (behind == this)
167 return;
169 LL_REMOVE(this, lower, higher, lowestobject, highestobject);
170 LL_INSERT_BEFORE(this, behind, lower, higher, lowestobject, highestobject);
173 void Object::PushBehind(int objtype)
175 Object *target = Objects::FindByType(objtype);
176 if (target)
177 PushBehind(target);
178 else
179 staterr("PushBehind: could not find any objects of type %s", DescribeObjectType(objtype));
183 void c------------------------------() {}
186 // for each point in pointlist, treats the point as a CSF'ed offset
187 // within the object's sprite. Then checks the attributes of the tile
188 // under each point. Returns an attribute mask containing the cumulative
189 // attributes of all the tiles under each point in the list.
191 // if tile is non-null, it is set to the tile type of the last tile checked.
192 uint32_t Object::GetAttributes(const Point *pointlist, int npoints, int *tile)
194 int tileno = 0;
195 uint32_t attr = 0;
197 int xoff = (this->x >> CSF);
198 int yoff = (this->y >> CSF);
200 for(int i=0;i<npoints;i++)
202 int x = (xoff + pointlist[i].x) / TILE_W;
203 int y = (yoff + pointlist[i].y) / TILE_H;
205 if (x >= 0 && y >= 0 && x < map.xsize && y < map.ysize)
207 tileno = map.tiles[x][y];
208 attr |= tileattr[tileno];
212 // also go underwater if we go under the variable waterlevel in Almond
213 if (map.waterlevelobject && (this->y + (2<<CSF)) > map.waterlevelobject->y)
215 attr |= TA_WATER;
218 if (tile) *tile = tileno;
219 return attr;
222 // for each point in pointlist, treats the point as a CSF'ed offset
223 // within the object's sprite. The tile under each position is checked
224 // to see if it's attributes contain one or more of the attributes
225 // specified in attrmask.
227 // If any of the points match, returns 1, and optionally returns
228 // the map coordinates of the first matched tile in tile_x/y.
229 bool Object::CheckAttribute(const Point *pointlist, int npoints, uint32_t attrmask,
230 int *tile_x, int *tile_y)
232 int x, y, xoff, yoff;
234 xoff = (this->x >> CSF);
235 yoff = (this->y >> CSF);
237 for(int i=0;i<npoints;i++)
239 x = (xoff + pointlist[i].x) / TILE_W;
240 y = (yoff + pointlist[i].y) / TILE_H;
242 if (x >= 0 && y >= 0 && x < map.xsize && y < map.ysize)
244 if ((tileattr[map.tiles[x][y]] & attrmask) != 0)
246 if (tile_x) *tile_x = x;
247 if (tile_y) *tile_y = y;
248 return true;
253 return false;
256 // treats each point in pointlist as an offset within the object, and returns
257 // true if any of the points intersect with object o2's solidbox.
258 bool Object::CheckSolidIntersect(Object *other, const Point *pointlist, int npoints)
260 int x, y;
261 int ox, oy, o2x, o2y;
262 SIFSprite *s2 = other->Sprite();
264 ox = (this->x >> CSF);
265 oy = (this->y >> CSF);
266 o2x = (other->x >> CSF);
267 o2y = (other->y >> CSF);
269 for(int i=0;i<npoints;i++)
271 x = ox + pointlist[i].x;
272 y = oy + pointlist[i].y;
274 if (x >= (o2x + s2->solidbox.x1) && x <= (o2x + s2->solidbox.x2))
276 if (y >= (o2y + s2->solidbox.y1) && y <= (o2y + s2->solidbox.y2))
278 return true;
283 return false;
287 // update the blocked states of object o.
288 // updatemask specifies which states are in need of updating.
289 void Object::UpdateBlockStates(uint8_t updatemask)
291 Object * const &o = this;
292 SIFSprite *sprite = Sprite();
293 int mask = GetBlockingType();
295 if (updatemask & LEFTMASK)
297 o->blockl = CheckAttribute(&sprite->block_l, mask);
299 // for objects which don't follow slope, have them see the slope as a wall so they
300 // won't just go right through it (looks really weird)
301 if (!(o->nxflags & NXFLAG_FOLLOW_SLOPE))
303 if (!o->blockl)
304 o->blockl = IsSlopeAtPointList(o, &sprite->block_l);
308 if (updatemask & RIGHTMASK)
310 o->blockr = CheckAttribute(&sprite->block_r, mask);
312 // for objects which don't follow slope, have them see the slope as a wall so they
313 // won't just go right through it (looks really weird).
314 if (!(o->nxflags & NXFLAG_FOLLOW_SLOPE))
316 if (!o->blockr)
317 o->blockr = IsSlopeAtPointList(o, &sprite->block_r);
321 if (updatemask & UPMASK)
323 o->blocku = CheckAttribute(&sprite->block_u, mask);
324 if (!o->blocku) o->blocku = CheckBoppedHeadOnSlope(o) ? 1 : 0;
327 if (updatemask & DOWNMASK)
329 o->blockd = CheckAttribute(&sprite->block_d, mask);
330 if (!o->blockd) o->blockd = CheckStandOnSlope(o) ? 1 : 0;
333 // have player be blocked by objects with FLAG_SOLID_BRICK set
334 if (o == player)
335 o->SetBlockForSolidBrick(updatemask);
338 // called from UpdateBlockedStates used w/ player.
339 // sets the object's block/l/r/u/d flags if it is in contact with a SOLID_BRICK object.
340 void Object::SetBlockForSolidBrick(uint8_t updatemask)
342 SIFSprite *thissprite = this->Sprite();
343 Object *o;
345 // no need to check blockpoints that are already set
346 if (this->blockl) updatemask &= ~LEFTMASK;
347 if (this->blockr) updatemask &= ~RIGHTMASK;
348 if (this->blocku) updatemask &= ~UPMASK;
349 if (this->blockd) updatemask &= ~DOWNMASK;
351 FOREACH_OBJECT(o)
353 if (!(o->flags & FLAG_SOLID_BRICK)) continue;
355 if (updatemask & LEFTMASK)
357 if (this->CheckSolidIntersect(o, &thissprite->block_l))
359 this->blockl = BLOCKED_OBJECT; // value of 2 instead of 1
360 updatemask &= ~LEFTMASK; // no need to keep checking
364 if (updatemask & RIGHTMASK)
366 if (this->CheckSolidIntersect(o, &thissprite->block_r))
368 this->blockr = BLOCKED_OBJECT;
369 updatemask &= ~RIGHTMASK;
373 if (updatemask & UPMASK)
375 if (this->CheckSolidIntersect(o, &thissprite->block_u))
377 this->blocku = BLOCKED_OBJECT;
378 updatemask &= ~UPMASK;
380 if (this == player)
381 player->bopped_object = o;
385 if (updatemask & DOWNMASK)
387 if (this->CheckSolidIntersect(o, &thissprite->block_d))
389 this->blockd = BLOCKED_OBJECT;
390 updatemask &= ~DOWNMASK;
392 if (this == player)
393 player->riding = o;
400 void c------------------------------() {}
403 // given an object, returns which tile attribute affects it's blocked state.
404 int Object::GetBlockingType()
406 Object * const &o = this;
408 if (o == player)
409 return TA_SOLID_PLAYER;
411 if (o->type >= OBJ_SHOTS_START && \
412 o->type <= OBJ_SHOTS_END)
414 // Bubbler L1 can't pass tile 44.
415 if (o->type == OBJ_BUBBLER12_SHOT && o->shot.level == 0)
416 return (TA_SOLID_SHOT | TA_SOLID_NPC);
418 return TA_SOLID_SHOT;
421 if (o->flags & FLAG_IGNORETILE44)
422 return TA_SOLID_PLAYER;
424 return TA_SOLID_NPC;
428 void c------------------------------() {}
431 // tries to move the object in the X direction by the given amount.
432 // returns nonzero if the object was blocked.
433 bool Object::apply_xinertia(int inertia)
435 Object * const &o = this;
437 if (inertia == 0)
438 return 0;
440 if (o->flags & FLAG_IGNORE_SOLID)
442 o->x += inertia;
443 return 0;
446 // only apply inertia one pixel at a time so we have
447 // proper hit detection--prevents objects traveling at
448 // high speed from becoming embedded in walls
449 if (inertia > 0)
451 while(inertia > (1<<CSF))
453 if (movehandleslope(o, (1<<CSF))) return 1;
454 inertia -= (1<<CSF);
456 o->UpdateBlockStates(RIGHTMASK);
459 else if (inertia < 0)
461 while(inertia < -(1<<CSF))
463 if (movehandleslope(o, -(1<<CSF))) return 1;
464 inertia += (1<<CSF);
466 o->UpdateBlockStates(LEFTMASK);
470 // apply any remaining inertia
471 if (inertia)
472 movehandleslope(o, inertia);
474 return 0;
477 // tries to move the object in the Y direction by the given amount.
478 // returns nonzero if the object was blocked.
479 bool Object::apply_yinertia(int inertia)
481 Object * const &o = this;
483 if (inertia == 0)
484 return 0;
486 if (o->flags & FLAG_IGNORE_SOLID)
488 o->y += inertia;
489 return 0;
492 // only apply inertia one pixel at a time so we have
493 // proper hit detection--prevents objects traveling at
494 // high speed from becoming embedded in walls
495 if (inertia > 0)
497 if (o->blockd) return 1;
499 while(inertia > (1<<CSF))
501 o->y += (1<<CSF);
502 inertia -= (1<<CSF);
504 o->UpdateBlockStates(DOWNMASK);
505 if (o->blockd) return 1;
508 else if (inertia < 0)
510 if (o->blocku) return 1;
512 while(inertia < -(1<<CSF))
514 o->y -= (1<<CSF);
515 inertia += (1<<CSF);
517 o->UpdateBlockStates(UPMASK);
518 if (o->blocku) return 1;
522 // apply any remaining inertia
523 if (inertia)
524 o->y += inertia;
526 return 0;
530 // handles a moving object with "FLAG_SOLID_BRICK" set
531 // pushing the player as it moves.
532 void Object::PushPlayerOutOfWay(int xinertia, int yinertia)
534 Object * const &o = this;
536 if (xinertia)
538 // give a bit of a gap where they must be--i.e. don't push them if they're right
539 // at the top or the bottom of the brick: needed when he rides it and falls off, then it
540 // turns around and touches him again. in that case what we actually want to do is push him
541 // to the top, not push him side-to-side.
542 if ((player->SolidBottom() - (2<<CSF)) > o->SolidTop() &&\
543 (player->SolidTop() + (2<<CSF)) < o->SolidBottom())
545 if (xinertia > 0 && player->SolidRight() > o->SolidRight() && solidhitdetect(o, player))
546 { // pushing player right
547 if (player->blockr)
548 { // squish!
549 hurtplayer(o->smushdamage);
551 else
553 // align player's blockl grid with our right side
554 player->x = o->SolidRight() - (sprites[player->sprite].block_l[0].x << CSF);
556 // get player a xinertia equal to our own. You can see this
557 // with the moving blocks in Labyrinth H.
558 player->xinertia = xinertia;
559 player->x += -player->xinertia;
562 else if (xinertia < 0 && player->SolidLeft() < o->SolidLeft() && solidhitdetect(o, player))
563 { // pushing player left
564 if (player->blockl)
565 { // squish!
566 hurtplayer(o->smushdamage);
568 else
570 // align player's blockr grid with our left side
571 player->x = o->SolidLeft() - (sprites[player->sprite].block_r[0].x << CSF);
573 // get player a xinertia equal to our own. You can see this
574 // with the moving blocks in Labyrinth H.
575 player->xinertia = xinertia;
576 player->x += -player->xinertia;
582 if (yinertia < 0)
584 if (player->blocku && player->riding == o) // smushed into ceiling!
585 hurtplayer(o->smushdamage);
587 else if (yinertia > 0) // object heading downwards?
589 // player riding object down
590 if (player->riding == o)
592 if (player->yinertia >= 0) // don't do this if he's trying to jump away
594 // align player's blockd grid with our top side so player
595 // doesn't perpetually fall.
596 player->y = o->SolidTop() - (sprites[player->sprite].block_d[0].y << CSF);
599 else if (player->Top() >= o->CenterY() && solidhitdetect(o, player)) // underneath object
601 // push him down if he's underneath us and we're going faster than he is.
602 if (yinertia >= player->yinertia)
604 if (player->blockd) // squished into floor!
605 hurtplayer(o->smushdamage);
607 // align his blocku grid with our bottom side
608 player->y = o->SolidBottom() - (sprites[player->sprite].block_u[0].y << CSF);
614 // snap the object down to the nearest solid tile.
615 // the object must have at least one blockd point for this to work.
616 void Object::SnapToGround()
618 Object * const &o = this;
620 uint32_t flags = o->flags;
621 o->flags &= ~FLAG_IGNORE_SOLID;
623 UpdateBlockStates(DOWNMASK);
624 apply_yinertia(SCREEN_HEIGHT << CSF);
626 o->flags = flags;
627 o->blockd = true;
631 void c------------------------------() {}
634 // deals the specified amount of damage to the object,
635 // and kills it if it's hitpoints reach 0.
637 // It is valid to deal 0 damage. The trails of the Spur do this
638 // to keep the enemy shaking and making noise for as long as
639 // it's in the beam.
641 // shot is an optional parameter specifying a pointer to
642 // the shot that hit the object, and is used to spawn
643 // blood spatter at the correct location.
644 void Object::DealDamage(int dmg, Object *shot)
646 Object * const &o = this;
648 if (o->flags & FLAG_INVULNERABLE)
649 return;
651 o->hp -= dmg;
653 if (o->flags & FLAG_SHOW_FLOATTEXT)
654 o->DamageWaiting += dmg;
656 if (o->hp > 0)
658 if (o->shaketime < objprop[o->type].shaketime - 2)
660 o->shaketime = objprop[o->type].shaketime;
662 if (objprop[o->type].hurt_sound)
663 sound(objprop[o->type].hurt_sound);
665 if (shot)
666 effect(shot->CenterX(), shot->CenterY(), EFFECT_BLOODSPLATTER);
669 else
671 o->Kill();
675 // kills the specified object, performing whatever action is
676 // applicable to that, such as spawning powerups or running scripts.
677 void Object::Kill()
679 Object * const &o = this;
681 o->hp = 0;
682 o->flags &= ~FLAG_SHOOTABLE;
684 // auto disappear the bossbar if we have just killed a boss
685 if (o == game.bossbar.object)
686 game.bossbar.defeated = true;
688 // if a script is set to run on death, run it instead of the usual explosion
689 if (o->flags & FLAG_SCRIPTONDEATH)
691 o->OnDeath();
692 StartScript(o->id2);
694 else
696 // should spawn the smokeclouds first, for z-order reasons
697 SmokeClouds(o, objprop[o->type].death_smoke_amt, 8, 8);
698 effect(o->CenterX(), o->CenterY(), EFFECT_BOOMFLASH);
700 if (objprop[o->type].death_sound)
701 sound(objprop[o->type].death_sound);
703 if (objprop[o->type].ai_routines.ondeath)
705 o->OnDeath();
707 else
709 SpawnPowerups();
710 o->Delete();
716 // spawn the powerups you get when you kill an enemy
717 void Object::SpawnPowerups()
719 Object * const &o = this;
720 int objectType, bonusType;
722 if (!objprop[o->type].xponkill)
723 return;
725 bonusType = random(1, 5);
726 if (bonusType >= 3)
728 SpawnXP(objprop[o->type].xponkill);
729 return;
732 if (bonusType == 2 && \
733 (player->weapons[WPN_MISSILE].hasWeapon || \
734 player->weapons[WPN_SUPER_MISSILE].hasWeapon))
736 objectType = OBJ_MISSILE;
738 else
740 objectType = OBJ_HEART;
743 // upgrade to big 3-cluster versions of powerups
744 // for big enemies.
745 if (objprop[o->type].xponkill > 6)
747 if (objectType == OBJ_HEART)
749 objectType = OBJ_HEART3;
751 else
753 objectType = OBJ_MISSILE3;
757 // create the powerup
758 Object *powerup = CreateObject(o->CenterX(), o->CenterY(), objectType);
759 powerup->x -= (powerup->Width() / 2);
760 powerup->y -= (powerup->Height() / 2);
762 powerup->state = 1; // make it animate
766 // spawn the given quantity of XP at the center of the object.
767 // amt indicates the total number of XP points to spawn.
768 // these will be collated into the appropriate sizes of XP triangles.
769 void Object::SpawnXP(int amt)
771 Object * const &o = this;
773 int x = o->CenterX();
774 int y = o->CenterY();
776 while(amt > 0)
778 Object *xp = CreateObject(x, y, OBJ_XP);
779 xp->xinertia = random(-0x200, 0x200);
781 if (amt >= XP_LARGE_AMT)
783 xp->sprite = SPR_XP_LARGE;
784 amt -= XP_LARGE_AMT;
786 else if (amt >= XP_MED_AMT)
788 xp->sprite = SPR_XP_MED;
789 amt -= XP_MED_AMT;
791 else
793 xp->sprite = SPR_XP_SMALL;
794 amt -= XP_SMALL_AMT;
797 // center the sprite at the center of the object
798 xp->x -= (xp->Width() / 2);
799 xp->y -= (xp->Height() / 2);
801 xp->UpdateBlockStates(ALLDIRMASK);
806 void c------------------------------() {}
809 void Object::RunAI()
811 Object * const &o = this;
813 o->OnTick();
815 // trigger touch-activated scripts.
816 // it actually only triggers once his centerline touches the object.
817 // see the passageway between the Throne Room and Kings Table for a
818 // clear example of the correct coordinates.
819 if (o->flags & FLAG_SCRIPTONTOUCH)
821 if (pdistlx(8<<CSF))
823 int y = player->y + (6 << CSF);
825 // player->riding check is for fans in Final Cave
826 if ((y > o->Top() && y < o->Bottom()) || player->riding == o)
828 if (GetCurrentScript() == -1 && // no override other scripts
829 game.switchstage.mapno == -1) // no repeat exec after <TRA
831 stat("On-touch script %d triggered", o->id2);
832 StartScript(o->id2);
839 // deals contact damage to player of o->damage, if applicable.
840 void Object::DealContactDamage()
842 Object * const &o = this;
844 // no contact damage to player while scripts running
845 if (GetCurrentScript() != -1 || player->inputs_locked)
846 return;
848 if (!(o->flags & FLAG_NOREARTOPATTACK))
850 hurtplayer(o->damage);
851 return;
854 // else, the no rear/top attack flag is set, so only
855 // frontal or bottom contact are harmful to the player
856 switch(o->GetAttackDirection())
858 case -1: // head-on
859 hurtplayer(o->damage);
860 break;
862 case LEFT: // rear attack, p to left
863 if (player->xinertia > -0x100)
864 player->xinertia = -0x100;
865 break;
867 case RIGHT: // rear attack, p to right
868 if (player->xinertia < 0x100)
869 player->xinertia = 0x100;
870 break;
874 // subfunction of HandleContactDamage. On entry, we assume that the player
875 // is in contact with this object, and that the object is trying to deal
876 // damage to him.
877 // returns the type of attack:
878 // - UP a top attack (player hit top of object)
879 // - LEFT rear attack, player to left
880 // - RIGHT rear attack, player to right
881 // - -1 head-on or bottom attack
882 int Object::GetAttackDirection()
884 Object * const &o = this;
885 const int VARIANCE = (5 << CSF);
887 if (player->riding == o)
888 return UP;
890 if (player->Bottom() <= (o->Top() + VARIANCE))
891 return UP;
893 // (added for X treads) if the object is moving, then the "front"
894 // for purposes of this flag is the direction it's moving in.
895 // if it's still, the "front" is the actual direction it's facing.
896 int rtdir = o->dir;
897 if (o->xinertia > 0) rtdir = RIGHT;
898 if (o->xinertia < 0) rtdir = LEFT;
900 if (rtdir == RIGHT)
902 if (player->Right() <= (o->Left() + VARIANCE))
903 return RIGHT;
905 else if (rtdir == LEFT) // the double check makes sense, what if o->dir was UP or DOWN
907 if (player->Left() >= (o->Right() - VARIANCE))
908 return LEFT;
911 return -1;
914 void Object::MoveAtDir(int dir, int speed)
916 this->xinertia = 0;
917 this->yinertia = 0;
919 switch(dir)
921 case LEFT: this->xinertia = -speed; break;
922 case RIGHT: this->xinertia = speed; break;
923 case UP: this->yinertia = -speed; break;
924 case DOWN: this->yinertia = speed; break;
929 void c------------------------------() {}
932 // animate over a list of frames, where the frames need not be consecutive.
933 // every speed ticks we will display a new frame from framelist.
934 // this function requires initilization of animframe and animtimer.
935 void Object::animate_seq(int speed, const int *framelist, int nframes)
937 Object * const &o = this;
939 if (++o->animtimer > speed)
941 o->animtimer = 0;
942 o->animframe++;
945 if (o->animframe >= nframes)
946 o->animframe = 0;
948 o->frame = framelist[o->animframe];
951 // used by objects in Maze M, this hints to curly's AI that the object is attacking.
952 void Object::CurlyTargetHere(int mintime, int maxtime)
954 Object * const &o = this;
956 game.curlytarget.x = o->CenterX();
957 game.curlytarget.y = o->CenterY();
958 game.curlytarget.timeleft = random(mintime, maxtime);
961 // reset the objects clip-extent fields (tp effects etc) to their defaults.
962 // i.e. such that if clip_enable were to be turned on it would have no immediate effect.
963 void Object::ResetClip()
965 Object * const &o = this;
967 o->clipx1 = o->clipy1 = 0;
968 o->clipx2 = sprites[o->sprite].w;
969 o->clipy2 = sprites[o->sprite].h;
972 void c------------------------------() {}
975 void Object::OnTick()
977 if (objprop[this->type].ai_routines.ontick)
978 (*objprop[this->type].ai_routines.ontick)(this);
981 void Object::OnAftermove()
983 if (objprop[this->type].ai_routines.aftermove)
984 (*objprop[this->type].ai_routines.aftermove)(this);
987 void Object::OnSpawn()
989 if (objprop[this->type].ai_routines.onspawn)
990 (*objprop[this->type].ai_routines.onspawn)(this);
993 void Object::OnDeath()
995 if (objprop[this->type].ai_routines.ondeath)
996 (*objprop[this->type].ai_routines.ondeath)(this);