diff --git a/src/info/actions.h b/src/info/actions.h index 676e0caac..d52e430d6 100644 --- a/src/info/actions.h +++ b/src/info/actions.h @@ -98,7 +98,6 @@ _(A_SetReactionTime, SETREACTIONTIME) _(A_Boss1Spikeballs, BOSS1SPIKEBALLS) _(A_Boss3TakeDamage, BOSS3TAKEDAMAGE) _(A_Boss3Path, BOSS3PATH) -_(A_Boss3ShockThink, BOSS3SHOCKTHINK) _(A_LinedefExecute, LINEDEFEXECUTE) _(A_LinedefExecuteFromArg, LINEDEFEXECUTEFROMARG) _(A_PlaySeeSound, PLAYSEESOUND) diff --git a/src/info/mobjs.h b/src/info/mobjs.h index 6d1939ea0..834f25b3a 100644 --- a/src/info/mobjs.h +++ b/src/info/mobjs.h @@ -46,7 +46,6 @@ _(BOSSFLYPOINT) _(EGGTRAP) _(BOSS3WAYPOINT) _(BOSS9GATHERPOINT) -_(BOSSJUNK) // Boss 1 _(EGGMOBILE) @@ -59,19 +58,16 @@ _(EGGMOBILE_FIRE) _(EGGMOBILE2) _(EGGMOBILE2_POGO) _(GOOP) -_(GOOPTRAIL) // Boss 3 _(EGGMOBILE3) +_(PROPELLER) _(FAKEMOBILE) -_(SHOCKWAVE) // Boss 4 _(EGGMOBILE4) _(EGGMOBILE4_MACE) _(JETFLAME) -_(EGGROBO1) -_(EGGROBO1JET) // Black Eggman (Boss 7) _(BLACKEGGMAN) diff --git a/src/info/sprites.h b/src/info/sprites.h index b8328f6ab..2f467e73b 100644 --- a/src/info/sprites.h +++ b/src/info/sprites.h @@ -58,18 +58,9 @@ _(SHCK) // Boss 3 Shockwave // Boss 4 (Castle Eggman) _(EGGP) _(EFIR) // Boss 4 jet flame -_(EGR1) // Boss 4 Spectator Eggrobo // Boss 5 (Arid Canyon) -_(FANG) // replaces EGGQ -_(BRKN) -_(WHAT) -_(VWRE) -_(PROJ) // projector light -_(FBOM) -_(FSGN) -_(BARX) // bomb explosion (also used by barrel) -_(BARD) // bomb dust (also used by barrel) +_(EGGQ) // Boss 6 (Red Volcano) _(EGGR) diff --git a/src/info/states.h b/src/info/states.h index b7cd4477e..0c28afde4 100644 --- a/src/info/states.h +++ b/src/info/states.h @@ -323,10 +323,10 @@ _(SONIC3KBOSSEXPLOSION5) _(SONIC3KBOSSEXPLOSION6) _(JETFUME1) +_(JETFUME2) // Boss 1 _(EGGMOBILE_STND) -_(EGGMOBILE_ROFL) _(EGGMOBILE_LATK1) _(EGGMOBILE_LATK2) _(EGGMOBILE_LATK3) @@ -336,6 +336,7 @@ _(EGGMOBILE_LATK6) _(EGGMOBILE_LATK7) _(EGGMOBILE_LATK8) _(EGGMOBILE_LATK9) +_(EGGMOBILE_LATK10) _(EGGMOBILE_RATK1) _(EGGMOBILE_RATK2) _(EGGMOBILE_RATK3) @@ -345,6 +346,7 @@ _(EGGMOBILE_RATK6) _(EGGMOBILE_RATK7) _(EGGMOBILE_RATK8) _(EGGMOBILE_RATK9) +_(EGGMOBILE_RATK10) _(EGGMOBILE_PANIC1) _(EGGMOBILE_PANIC2) _(EGGMOBILE_PANIC3) @@ -355,25 +357,27 @@ _(EGGMOBILE_PANIC7) _(EGGMOBILE_PANIC8) _(EGGMOBILE_PANIC9) _(EGGMOBILE_PANIC10) -_(EGGMOBILE_PANIC11) -_(EGGMOBILE_PANIC12) -_(EGGMOBILE_PANIC13) -_(EGGMOBILE_PANIC14) -_(EGGMOBILE_PANIC15) _(EGGMOBILE_PAIN) _(EGGMOBILE_PAIN2) _(EGGMOBILE_DIE1) _(EGGMOBILE_DIE2) _(EGGMOBILE_DIE3) _(EGGMOBILE_DIE4) +_(EGGMOBILE_DIE5) +_(EGGMOBILE_DIE6) +_(EGGMOBILE_DIE7) +_(EGGMOBILE_DIE8) +_(EGGMOBILE_DIE9) +_(EGGMOBILE_DIE10) +_(EGGMOBILE_DIE11) +_(EGGMOBILE_DIE12) +_(EGGMOBILE_DIE13) +_(EGGMOBILE_DIE14) _(EGGMOBILE_FLEE1) _(EGGMOBILE_FLEE2) _(EGGMOBILE_BALL) _(EGGMOBILE_TARGET) -_(BOSSEGLZ1) -_(BOSSEGLZ2) - // Boss 2 _(EGGMOBILE2_STND) _(EGGMOBILE2_POGO1) @@ -389,6 +393,16 @@ _(EGGMOBILE2_DIE1) _(EGGMOBILE2_DIE2) _(EGGMOBILE2_DIE3) _(EGGMOBILE2_DIE4) +_(EGGMOBILE2_DIE5) +_(EGGMOBILE2_DIE6) +_(EGGMOBILE2_DIE7) +_(EGGMOBILE2_DIE8) +_(EGGMOBILE2_DIE9) +_(EGGMOBILE2_DIE10) +_(EGGMOBILE2_DIE11) +_(EGGMOBILE2_DIE12) +_(EGGMOBILE2_DIE13) +_(EGGMOBILE2_DIE14) _(EGGMOBILE2_FLEE1) _(EGGMOBILE2_FLEE2) @@ -400,11 +414,9 @@ _(BOSSSPIGOT) _(GOOP1) _(GOOP2) _(GOOP3) -_(GOOPTRAIL) // Boss 3 _(EGGMOBILE3_STND) -_(EGGMOBILE3_SHOCK) _(EGGMOBILE3_ATK1) _(EGGMOBILE3_ATK2) _(EGGMOBILE3_ATK3A) @@ -413,16 +425,54 @@ _(EGGMOBILE3_ATK3C) _(EGGMOBILE3_ATK3D) _(EGGMOBILE3_ATK4) _(EGGMOBILE3_ATK5) -_(EGGMOBILE3_ROFL) +_(EGGMOBILE3_LAUGH1) +_(EGGMOBILE3_LAUGH2) +_(EGGMOBILE3_LAUGH3) +_(EGGMOBILE3_LAUGH4) +_(EGGMOBILE3_LAUGH5) +_(EGGMOBILE3_LAUGH6) +_(EGGMOBILE3_LAUGH7) +_(EGGMOBILE3_LAUGH8) +_(EGGMOBILE3_LAUGH9) +_(EGGMOBILE3_LAUGH10) +_(EGGMOBILE3_LAUGH11) +_(EGGMOBILE3_LAUGH12) +_(EGGMOBILE3_LAUGH13) +_(EGGMOBILE3_LAUGH14) +_(EGGMOBILE3_LAUGH15) +_(EGGMOBILE3_LAUGH16) +_(EGGMOBILE3_LAUGH17) +_(EGGMOBILE3_LAUGH18) +_(EGGMOBILE3_LAUGH19) +_(EGGMOBILE3_LAUGH20) _(EGGMOBILE3_PAIN) _(EGGMOBILE3_PAIN2) _(EGGMOBILE3_DIE1) _(EGGMOBILE3_DIE2) _(EGGMOBILE3_DIE3) _(EGGMOBILE3_DIE4) +_(EGGMOBILE3_DIE5) +_(EGGMOBILE3_DIE6) +_(EGGMOBILE3_DIE7) +_(EGGMOBILE3_DIE8) +_(EGGMOBILE3_DIE9) +_(EGGMOBILE3_DIE10) +_(EGGMOBILE3_DIE11) +_(EGGMOBILE3_DIE12) +_(EGGMOBILE3_DIE13) +_(EGGMOBILE3_DIE14) _(EGGMOBILE3_FLEE1) _(EGGMOBILE3_FLEE2) +// Boss 3 Propeller +_(PROPELLER1) +_(PROPELLER2) +_(PROPELLER3) +_(PROPELLER4) +_(PROPELLER5) +_(PROPELLER6) +_(PROPELLER7) + // Boss 3 Pinch _(FAKEMOBILE_INIT) _(FAKEMOBILE) @@ -432,15 +482,8 @@ _(FAKEMOBILE_ATK3A) _(FAKEMOBILE_ATK3B) _(FAKEMOBILE_ATK3C) _(FAKEMOBILE_ATK3D) -_(FAKEMOBILE_DIE1) -_(FAKEMOBILE_DIE2) - -_(BOSSSEBH1) -_(BOSSSEBH2) - -// Boss 3 Shockwave -_(SHOCKWAVE1) -_(SHOCKWAVE2) +_(FAKEMOBILE_ATK4) +_(FAKEMOBILE_ATK5) // Boss 4 _(EGGMOBILE4_STND) @@ -458,30 +501,36 @@ _(EGGMOBILE4_RATK5) _(EGGMOBILE4_RATK6) _(EGGMOBILE4_RAISE1) _(EGGMOBILE4_RAISE2) -_(EGGMOBILE4_PAIN1) -_(EGGMOBILE4_PAIN2) +_(EGGMOBILE4_RAISE3) +_(EGGMOBILE4_RAISE4) +_(EGGMOBILE4_RAISE5) +_(EGGMOBILE4_RAISE6) +_(EGGMOBILE4_RAISE7) +_(EGGMOBILE4_RAISE8) +_(EGGMOBILE4_RAISE9) +_(EGGMOBILE4_RAISE10) +_(EGGMOBILE4_PAIN) _(EGGMOBILE4_DIE1) _(EGGMOBILE4_DIE2) _(EGGMOBILE4_DIE3) _(EGGMOBILE4_DIE4) +_(EGGMOBILE4_DIE5) +_(EGGMOBILE4_DIE6) +_(EGGMOBILE4_DIE7) +_(EGGMOBILE4_DIE8) +_(EGGMOBILE4_DIE9) +_(EGGMOBILE4_DIE10) +_(EGGMOBILE4_DIE11) +_(EGGMOBILE4_DIE12) +_(EGGMOBILE4_DIE13) +_(EGGMOBILE4_DIE14) _(EGGMOBILE4_FLEE1) _(EGGMOBILE4_FLEE2) _(EGGMOBILE4_MACE) -_(EGGMOBILE4_MACE_DIE1) -_(EGGMOBILE4_MACE_DIE2) -_(EGGMOBILE4_MACE_DIE3) // Boss 4 jet flame -_(JETFLAME) - -// Boss 4 Spectator Eggrobo -_(EGGROBO1_STND) -_(EGGROBO1_BSLAP1) -_(EGGROBO1_BSLAP2) -_(EGGROBO1_PISSED) - -// Boss 4 Spectator Eggrobo jet flame -_(EGGROBOJET) +_(JETFLAME1) +_(JETFLAME2) // Black Eggman (Boss 7) _(BLACKEGG_STND) @@ -714,27 +763,54 @@ _(CYBRAKDEMONVILEEXPLOSION2) _(CYBRAKDEMONVILEEXPLOSION3) // Metal Sonic (Race) -_(METALSONIC_RACE) +// S_PLAY_STND +_(METALSONIC_STAND) +// S_PLAY_TAP1 +_(METALSONIC_WAIT1) +_(METALSONIC_WAIT2) +// S_PLAY_RUN1 +_(METALSONIC_WALK1) +_(METALSONIC_WALK2) +_(METALSONIC_WALK3) +_(METALSONIC_WALK4) +_(METALSONIC_WALK5) +_(METALSONIC_WALK6) +_(METALSONIC_WALK7) +_(METALSONIC_WALK8) +// S_PLAY_SPD1 +_(METALSONIC_RUN1) +_(METALSONIC_RUN2) +_(METALSONIC_RUN3) +_(METALSONIC_RUN4) // Metal Sonic (Battle) _(METALSONIC_FLOAT) _(METALSONIC_VECTOR) _(METALSONIC_STUN) +_(METALSONIC_BLOCK) _(METALSONIC_RAISE) _(METALSONIC_GATHER) _(METALSONIC_DASH) _(METALSONIC_BOUNCE) -_(METALSONIC_BADBOUNCE) _(METALSONIC_SHOOT) _(METALSONIC_PAIN) -_(METALSONIC_DEATH1) -_(METALSONIC_DEATH2) -_(METALSONIC_DEATH3) -_(METALSONIC_DEATH4) +_(METALSONIC_DEATH) _(METALSONIC_FLEE1) _(METALSONIC_FLEE2) +_(METALSONIC_FLEE3) +_(METALSONIC_FLEE4) _(MSSHIELD_F1) _(MSSHIELD_F2) +_(MSSHIELD_F3) +_(MSSHIELD_F4) +_(MSSHIELD_F5) +_(MSSHIELD_F6) +_(MSSHIELD_F7) +_(MSSHIELD_F8) +_(MSSHIELD_F9) +_(MSSHIELD_F10) +_(MSSHIELD_F11) +_(MSSHIELD_F12) // Ring _(RING) @@ -1128,14 +1204,6 @@ _(BLUEMONITOREXPLOSION2) _(ROCKET) _(LASER) -_(LASER2) -_(LASERFLASH) - -_(LASERFLAME1) -_(LASERFLAME2) -_(LASERFLAME3) -_(LASERFLAME4) -_(LASERFLAME5) _(TORPEDO) diff --git a/src/p_enemy.c b/src/p_enemy.c index 5179c90ce..ffc0819f8 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -2094,29 +2094,23 @@ void A_Boss7FireMissiles(mobj_t *actor) // 3 - Boss 1 Middle // >=3 - Generic middle // +// Function: A_Boss1Laser +// +// Description: Shoot an object at your target ala Bosses: +// +// var1 = object # to shoot +// var2: +// 0 - Boss 1 Left side +// 1 - Boss 1 Right side +// void A_Boss1Laser(mobj_t *actor) { fixed_t x, y, z, floorz, speed; INT32 locvar1 = var1; - INT32 locvar2 = (var2 & 65535); - INT32 upperend = (var2>>16); + INT32 locvar2 = var2; INT32 i; angle_t angle; mobj_t *point; - tic_t dur; - static const UINT8 LASERCOLORS[] = - { - SKINCOLOR_KSUPER3, - SKINCOLOR_KSUPER4, - SKINCOLOR_KSUPER5, - SKINCOLOR_KETCHUP, - SKINCOLOR_RED, - SKINCOLOR_RED, - SKINCOLOR_KETCHUP, - SKINCOLOR_KSUPER5, - SKINCOLOR_KSUPER4, - SKINCOLOR_KSUPER3, - }; if (LUA_CallAction(A_BOSS1LASER, actor)) return; @@ -2124,48 +2118,24 @@ void A_Boss1Laser(mobj_t *actor) if (!actor->target) return; - if (actor->state->tics > 1) - dur = actor->tics; - else - { - if ((upperend & 1) && (actor->extravalue2 > 1)) - actor->extravalue2--; - - dur = actor->extravalue2; - } - switch (locvar2) { case 0: - x = actor->x + P_ReturnThrustX(actor, actor->angle+ANGLE_90, FixedMul(44*FRACUNIT, actor->scale)); - y = actor->y + P_ReturnThrustY(actor, actor->angle+ANGLE_90, FixedMul(44*FRACUNIT, actor->scale)); + x = actor->x + P_ReturnThrustX(actor, actor->angle+ANGLE_90, FixedMul(43*FRACUNIT, actor->scale)); + y = actor->y + P_ReturnThrustY(actor, actor->angle+ANGLE_90, FixedMul(43*FRACUNIT, actor->scale)); if (actor->eflags & MFE_VERTICALFLIP) z = actor->z + actor->height - FixedMul(56*FRACUNIT, actor->scale) - mobjinfo[locvar1].height; else z = actor->z + FixedMul(56*FRACUNIT, actor->scale); break; case 1: - x = actor->x + P_ReturnThrustX(actor, actor->angle-ANGLE_90, FixedMul(44*FRACUNIT, actor->scale)); - y = actor->y + P_ReturnThrustY(actor, actor->angle-ANGLE_90, FixedMul(44*FRACUNIT, actor->scale)); + x = actor->x + P_ReturnThrustX(actor, actor->angle-ANGLE_90, FixedMul(43*FRACUNIT, actor->scale)); + y = actor->y + P_ReturnThrustY(actor, actor->angle-ANGLE_90, FixedMul(43*FRACUNIT, actor->scale)); if (actor->eflags & MFE_VERTICALFLIP) z = actor->z + actor->height - FixedMul(56*FRACUNIT, actor->scale) - mobjinfo[locvar1].height; else z = actor->z + FixedMul(56*FRACUNIT, actor->scale); break; - case 2: - var1 = locvar1; var2 = 3; // Fire middle laser - A_Boss1Laser(actor); - var1 = locvar1; var2 = 0; // Fire left laser - A_Boss1Laser(actor); - var1 = locvar1; var2 = 1; // Fire right laser - A_Boss1Laser(actor); - return; - break; - case 3: - x = actor->x + P_ReturnThrustX(actor, actor->angle, FixedMul(42*FRACUNIT, actor->scale)); - y = actor->y + P_ReturnThrustY(actor, actor->angle, FixedMul(42*FRACUNIT, actor->scale)); - z = actor->z + actor->height/2; - break; default: x = actor->x; y = actor->y; @@ -2173,25 +2143,30 @@ void A_Boss1Laser(mobj_t *actor) break; } - if (!(actor->flags2 & MF2_FIRING) && dur > 1) + if (!(actor->flags2 & MF2_FIRING)) { actor->angle = R_PointToAngle2(x, y, actor->target->x, actor->target->y); if (mobjinfo[locvar1].seesound) S_StartSound(actor, mobjinfo[locvar1].seesound); - - point = P_SpawnMobj(x + P_ReturnThrustX(actor, actor->angle, actor->radius), y + P_ReturnThrustY(actor, actor->angle, actor->radius), actor->z - actor->height / 2, MT_EGGMOBILE_TARGET); - point->angle = actor->angle; - point->fuse = dur+1; - P_SetTarget(&point->target, actor->target); - P_SetTarget(&actor->target, point); + if (!(actor->spawnpoint && actor->spawnpoint->options & MTF_AMBUSH)) + { + point = P_SpawnMobj(x + P_ReturnThrustX(actor, actor->angle, actor->radius), y + P_ReturnThrustY(actor, actor->angle, actor->radius), actor->z - actor->height / 2, MT_EGGMOBILE_TARGET); + point->fuse = actor->tics+1; + P_SetTarget(&point->target, actor->target); + P_SetTarget(&actor->target, point); + } } + else if (actor->target && !(actor->spawnpoint && actor->spawnpoint->options & MTF_AMBUSH)) + actor->angle = R_PointToAngle2(x, y, actor->target->x, actor->target->y); - angle = R_PointToAngle2(z + (mobjinfo[locvar1].height>>1), 0, actor->target->z, R_PointToDist2(x, y, actor->target->x, actor->target->y)); - + if (actor->spawnpoint && actor->spawnpoint->options & MTF_AMBUSH) + angle = FixedAngle(FixedDiv(actor->tics*160*FRACUNIT, actor->state->tics*FRACUNIT) + 10*FRACUNIT); + else + angle = R_PointToAngle2(z + (mobjinfo[locvar1].height>>1), 0, actor->target->z, R_PointToDist2(x, y, actor->target->x, actor->target->y)); point = P_SpawnMobj(x, y, z, locvar1); P_SetTarget(&point->target, actor); point->angle = actor->angle; - speed = point->radius; + speed = point->radius*2; point->momz = FixedMul(FINECOSINE(angle>>ANGLETOFINESHIFT), speed); point->momx = FixedMul(FINESINE(angle>>ANGLETOFINESHIFT), FixedMul(FINECOSINE(point->angle>>ANGLETOFINESHIFT), speed)); point->momy = FixedMul(FINESINE(angle>>ANGLETOFINESHIFT), FixedMul(FINESINE(point->angle>>ANGLETOFINESHIFT), speed)); @@ -2200,72 +2175,26 @@ void A_Boss1Laser(mobj_t *actor) { mobj_t *mo = P_SpawnMobj(point->x, point->y, point->z, point->type); mo->angle = point->angle; - mo->color = LASERCOLORS[((UINT8)(i + 3*dur) >> 2) % sizeof(LASERCOLORS)]; // codeing P_UnsetThingPosition(mo); - mo->flags = MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY; + mo->flags = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY; P_SetThingPosition(mo); - if (dur & 1 && mo->info->missilestate) - { - P_SetMobjState(mo, mo->info->missilestate); - if (mo->info->meleestate) - { - mobj_t *mo2 = P_SpawnMobjFromMobj(mo, 0, 0, 0, MT_PARTICLE); - mo2->flags2 |= MF2_LINKDRAW; - P_SetTarget(&mo2->tracer, actor); - P_SetMobjState(mo2, mo->info->meleestate); - } - } - - if (dur == 1) - P_SpawnGhostMobj(mo); - x = point->x, y = point->y, z = point->z; if (P_RailThinker(point)) break; } - x += point->momx; - y += point->momy; floorz = P_FloorzAtPos(x, y, z, mobjinfo[MT_EGGMOBILE_FIRE].height); - if (z - floorz < mobjinfo[MT_EGGMOBILE_FIRE].height>>1 && dur & 1) + if (z - floorz < mobjinfo[MT_EGGMOBILE_FIRE].height>>1) { - point = P_SpawnMobj(x, y, floorz, MT_EGGMOBILE_FIRE); - point->angle = actor->angle; - point->destscale = actor->scale; - P_SetScale(point, point->destscale); + point = P_SpawnMobj(x, y, floorz+1, MT_EGGMOBILE_FIRE); P_SetTarget(&point->target, actor); - P_MobjCheckWater(point); - if (point->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER)) - { - for (i = 0; i < 2; i++) - { - UINT8 size = 3; - mobj_t *steam = P_SpawnMobj(x, y, point->watertop - size*mobjinfo[MT_DUST].height, MT_DUST); - P_SetScale(steam, size*actor->scale); - P_SetObjectMomZ(steam, FRACUNIT + 2*P_RandomFixed(), true); - P_InstaThrust(steam, FixedAngle(P_RandomKey(360)*FRACUNIT), 2*P_RandomFixed()); - if (point->info->painsound) - S_StartSound(steam, point->info->painsound); - } - } - else - { - fixed_t distx = P_ReturnThrustX(point, point->angle, point->radius); - fixed_t disty = P_ReturnThrustY(point, point->angle, point->radius); - if (P_TryMove(point, point->x + distx, point->y + disty, false, NULL) // prevents the sprite from clipping into the wall or dangling off ledges - && P_TryMove(point, point->x - 2*distx, point->y - 2*disty, false, NULL) - && P_TryMove(point, point->x + distx, point->y + disty, false, NULL)) - { - if (point->info->seesound) - S_StartSound(point, point->info->seesound); - } - else - P_RemoveMobj(point); - } + point->destscale = 3*FRACUNIT; + point->scalespeed = FRACUNIT>>2; + point->fuse = TICRATE; } - if (dur > 1) + if (actor->tics > 1) actor->flags2 |= MF2_FIRING; else actor->flags2 &= ~MF2_FIRING; @@ -2278,16 +2207,11 @@ void A_Boss1Laser(mobj_t *actor) // var1: // 0 - accelerative focus with friction // 1 - steady focus with fixed movement speed -// anything else - don't move -// var2: -// 0 - don't trace target, just move forwards -// & 1 - change horizontal angle -// & 2 - change vertical angle +// var2 = unused // void A_FocusTarget(mobj_t *actor) { INT32 locvar1 = var1; - INT32 locvar2 = var2; if (LUA_CallAction(A_FOCUSTARGET, actor)) return; @@ -2295,9 +2219,9 @@ void A_FocusTarget(mobj_t *actor) if (actor->target) { fixed_t speed = FixedMul(actor->info->speed, actor->scale); - fixed_t dist = (locvar2 ? R_PointToDist2(actor->x, actor->y, actor->target->x, actor->target->y) : speed+1); - angle_t hangle = ((locvar2 & 1) ? R_PointToAngle2(actor->x, actor->y, actor->target->x, actor->target->y) : actor->angle); - angle_t vangle = ((locvar2 & 2) ? R_PointToAngle2(actor->z , 0, actor->target->z + (actor->target->height>>1), dist) : ANGLE_90); + fixed_t dist = R_PointToDist2(actor->x, actor->y, actor->target->x, actor->target->y); + angle_t vangle = R_PointToAngle2(actor->z , 0, actor->target->z + (actor->target->height>>1), dist); + angle_t hangle = R_PointToAngle2(actor->x, actor->y, actor->target->x, actor->target->y); switch(locvar1) { case 0: @@ -6143,7 +6067,9 @@ void A_Boss3TakeDamage(mobj_t *actor) return; actor->movecount = var1; - actor->movefactor = -512*FRACUNIT; + + if (actor->target && actor->target->spawnpoint) + actor->threshold = actor->target->spawnpoint->extrainfo; } // Function: A_Boss3Path @@ -6178,28 +6104,24 @@ void A_Boss3Path(mobj_t *actor) } else if (actor->threshold >= 0) // Traveling mode { - fixed_t dist = 0; + thinker_t *th; + mobj_t *mo2; + fixed_t dist, dist2; fixed_t speed; - if (!(actor->flags2 & MF2_STRONGBOX)) + P_SetTarget(&actor->target, NULL); + + // scan the thinkers + // to find a point that matches + // the number + for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) { - mobj_t *mo2; - INT32 i; + if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) + continue; - P_SetTarget(&actor->target, NULL); - - // Find waypoint - TAG_ITER_THINGS(actor->cusval, i) + mo2 = (mobj_t *)th; + if (mo2->type == MT_BOSS3WAYPOINT && mo2->spawnpoint && mo2->spawnpoint->angle == actor->threshold) { - mo2 = mapthings[i].mobj; - - if (!mo2) - continue; - if (mo2->type != MT_BOSS3WAYPOINT) - continue; - if (mapthings[i].args[0] != actor->threshold) - continue; - P_SetTarget(&actor->target, mo2); break; } @@ -6207,117 +6129,67 @@ void A_Boss3Path(mobj_t *actor) if (!actor->target) // Should NEVER happen { - CONS_Debug(DBG_GAMELOGIC, "Error: Boss 3 Dummy was unable to find specified waypoint: %d, %d\n", actor->threshold, actor->cusval); + CONS_Debug(DBG_GAMELOGIC, "Error: Boss 3 Dummy was unable to find specified waypoint: %d\n", actor->threshold); return; } + dist = P_AproxDistance(P_AproxDistance(actor->target->x - actor->x, actor->target->y - actor->y), actor->target->z - actor->z); + + if (dist < 1) + dist = 1; + if (actor->tracer && ((actor->tracer->movedir) || (actor->tracer->health <= actor->tracer->info->damage))) speed = actor->info->speed * 2; else speed = actor->info->speed; - if (actor->target->x == actor->x && actor->target->y == actor->y) - { - dist = P_AproxDistance(P_AproxDistance(actor->target->x - actor->x, actor->target->y - actor->y), actor->target->z + actor->movefactor - actor->z); + actor->momx = FixedMul(FixedDiv(actor->target->x - actor->x, dist), speed); + actor->momy = FixedMul(FixedDiv(actor->target->y - actor->y, dist), speed); + actor->momz = FixedMul(FixedDiv(actor->target->z - actor->z, dist), speed); - if (dist < 1) - dist = 1; + if (actor->momx != 0 || actor->momy != 0) + actor->angle = R_PointToAngle2(0, 0, actor->momx, actor->momy); - actor->momx = FixedMul(FixedDiv(actor->target->x - actor->x, dist), speed); - actor->momy = FixedMul(FixedDiv(actor->target->y - actor->y, dist), speed); - actor->momz = FixedMul(FixedDiv(actor->target->z + actor->movefactor - actor->z, dist), speed); + dist2 = P_AproxDistance(P_AproxDistance(actor->target->x - (actor->x + actor->momx), actor->target->y - (actor->y + actor->momy)), actor->target->z - (actor->z + actor->momz)); - if (actor->momx != 0 || actor->momy != 0) - actor->angle = R_PointToAngle2(0, 0, actor->momx, actor->momy); - } + if (dist2 < 1) + dist2 = 1; - if (dist <= speed) + if ((dist >> FRACBITS) <= (dist2 >> FRACBITS)) { // If further away, set XYZ of mobj to waypoint location P_UnsetThingPosition(actor); actor->x = actor->target->x; actor->y = actor->target->y; - actor->z = actor->target->z + actor->movefactor; + actor->z = actor->target->z; actor->momx = actor->momy = actor->momz = 0; P_SetThingPosition(actor); - if (!actor->movefactor) // firing mode - { - actor->movecount |= 2; - actor->movefactor = -512*FRACUNIT; - actor->flags2 &= ~MF2_STRONGBOX; - } - else if (!(actor->flags2 & MF2_STRONGBOX)) // just spawned or going down - { - actor->flags2 |= MF2_STRONGBOX; - actor->movefactor = -512*FRACUNIT; - } - else if (!(actor->flags2 & MF2_AMBUSH)) // just shifted tube - { - actor->flags2 |= MF2_AMBUSH; - actor->movefactor = 0; - } - else // just hit the bottom of your tube + if (actor->threshold == 0) { P_RemoveMobj(actor); // Cycle completed. Dummy removed. return; } - } - } -} -// Function: A_Boss3ShockThink -// -// Description: Inserts new interstitial shockwave objects when the space between others spreads too much. -// -// var1 = unused -// var2 = unused -// -void A_Boss3ShockThink(mobj_t *actor) -{ - if (LUA_CallAction(A_BOSS3SHOCKTHINK, actor)) - return; + // Set to next waypoint in sequence + if (actor->target->spawnpoint) + { + // From the center point, choose one of the five paths + if (actor->target->spawnpoint->angle == 0) + { + P_RemoveMobj(actor); // Cycle completed. Dummy removed. + return; + } + else + actor->threshold = actor->target->spawnpoint->extrainfo; - if (actor->momx || actor->momy) - actor->angle = R_PointToAngle2(0, 0, actor->momx, actor->momy) + ANGLE_90; - - if (actor->hnext && !P_MobjWasRemoved(actor->hnext)) - { - mobj_t *snext = actor->hnext; - mobj_t *snew; - fixed_t x0, y0, x1, y1; - - // Break the link if movements are too different - if (R_PointToDist2(0, 0, snext->momx - actor->momx, snext->momy - actor->momy) > 12*actor->scale) - { - P_SetTarget(&actor->hnext, NULL); - return; - } - - // Check distance between shockwave objects to determine whether interstitial ones should be spawned - x0 = actor->x; - y0 = actor->y; - x1 = snext->x; - y1 = snext->y; - if (R_PointToDist2(0, 0, x1 - x0, y1 - y0) > 2*actor->radius) - { - snew = P_SpawnMobj((x0 >> 1) + (x1 >> 1), - (y0 >> 1) + (y1 >> 1), - (actor->z >> 1) + (snext->z >> 1), actor->type); - snew->momx = (actor->momx + snext->momx) >> 1; - snew->momy = (actor->momy + snext->momy) >> 1; - snew->momz = (actor->momz + snext->momz) >> 1; // is this really needed? - snew->angle = (actor->angle + snext->angle) >> 1; - P_SetTarget(&snew->target, actor->target); - snew->fuse = actor->fuse; - - P_SetScale(snew, actor->scale); - snew->destscale = actor->destscale; - snew->scalespeed = actor->scalespeed; - - P_SetTarget(&actor->hnext, snew); - P_SetTarget(&snew->hnext, snext); + // If the deaf flag is set, go into firing mode + if (actor->target->spawnpoint->options & MTF_AMBUSH) + actor->movecount |= 2; + } + else // This should never happen, as well + CONS_Debug(DBG_GAMELOGIC, "Error: Boss 3 Dummy waypoint has no spawnpoint associated with it.\n"); } } } @@ -7167,7 +7039,7 @@ void A_SetObjectFlags2(mobj_t *actor) // // var1: // 0 - Triple jet fume pattern -// 1 - Unused (formerly Boss 3's propeller) +// 1 - Boss 3's propeller // 2 - Metal Sonic jet fume // 3 - Boss 4 jet flame // var2 = unused @@ -7226,12 +7098,12 @@ void A_BossJetFume(mobj_t *actor) P_SetTarget(&actor->tracer, filler); } - /*else if (locvar1 == 1) // Boss 3 propeller + else if (locvar1 == 1) // Boss 3 propeller { fixed_t jetx, jety, jetz; - jetx = actor->x + P_ReturnThrustX(actor, actor->angle, -60*actor->scale); - jety = actor->y + P_ReturnThrustY(actor, actor->angle, -60*actor->scale); + jetx = actor->x + P_ReturnThrustX(actor, actor->angle, -FixedMul(60*FRACUNIT, actor->scale)); + jety = actor->y + P_ReturnThrustY(actor, actor->angle, -FixedMul(60*FRACUNIT, actor->scale)); if (actor->eflags & MFE_VERTICALFLIP) jetz = actor->z + actor->height - FixedMul(17*FRACUNIT + mobjinfo[MT_PROPELLER].height, actor->scale); else @@ -7246,18 +7118,17 @@ void A_BossJetFume(mobj_t *actor) filler->angle = actor->angle - ANGLE_180; P_SetTarget(&actor->tracer, filler); - }*/ + } else if (locvar1 == 2) // Metal Sonic jet fumes { filler = P_SpawnMobj(actor->x, actor->y, actor->z, MT_JETFUME1); P_SetTarget(&filler->target, actor); filler->fuse = 59; P_SetTarget(&actor->tracer, filler); - P_SetScale(filler, (filler->destscale = actor->scale/3)); + filler->destscale = actor->scale/2; + P_SetScale(filler, filler->destscale); if (actor->eflags & MFE_VERTICALFLIP) filler->flags2 |= MF2_OBJECTFLIP; - filler->color = SKINCOLOR_CYAN; - filler->colorized = true; } else if (locvar1 == 3) // Boss 4 jet flame { @@ -7265,7 +7136,7 @@ void A_BossJetFume(mobj_t *actor) if (actor->eflags & MFE_VERTICALFLIP) jetz = actor->z + actor->height + FixedMul(50*FRACUNIT - mobjinfo[MT_JETFLAME].height, actor->scale); else - jetz = actor->z - 50*actor->scale; + jetz = actor->z - FixedMul(50*FRACUNIT, actor->scale); filler = P_SpawnMobj(actor->x, actor->y, jetz, MT_JETFLAME); P_SetTarget(&filler->target, actor); // Boss 4 already uses its tracer for other things @@ -7274,30 +7145,6 @@ void A_BossJetFume(mobj_t *actor) if (actor->eflags & MFE_VERTICALFLIP) filler->flags2 |= MF2_OBJECTFLIP; } - else if (locvar1 == 4) // Boss 4 Spectator Eggrobo jet flame - { - fixed_t jetx, jety, jetz, movefactor = 12; - - jetz = actor->z; - if (actor->eflags & MFE_VERTICALFLIP) - jetz += (actor->height - FixedMul(mobjinfo[MT_EGGROBO1JET].height, actor->scale)); - - while (true) - { - jetx = actor->x + P_ReturnThrustX(actor, actor->angle+ANGLE_90, movefactor*actor->scale) - P_ReturnThrustX(actor, actor->angle, 19*actor->scale); - jety = actor->y + P_ReturnThrustY(actor, actor->angle+ANGLE_90, movefactor*actor->scale) - P_ReturnThrustY(actor, actor->angle, 19*actor->scale); - filler = P_SpawnMobj(jetx, jety, jetz, MT_EGGROBO1JET); - filler->movefactor = movefactor; - P_SetTarget(&filler->target, actor); - filler->destscale = actor->scale; - P_SetScale(filler, filler->destscale); - if (actor->eflags & MFE_VERTICALFLIP) - filler->flags2 |= MF2_OBJECTFLIP; - if (movefactor <= 0) - break; - movefactor = -movefactor; - } - } } // Function: A_RandomState diff --git a/src/p_inter.c b/src/p_inter.c index a3c102509..d79a6cc5c 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1498,7 +1498,6 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget case MT_EGGMOBILE3: { - mobj_t *mo2; thinker_t *th; UINT32 i = 0; // to check how many clones we've removed @@ -1514,20 +1513,10 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget if (mo->tracer != target) continue; - P_KillMobj(mo, inflictor, source, damagetype); - mo->destscale = mo->scale/8; - mo->scalespeed = (mo->scale - mo->destscale)/(2*TICRATE); - mo->momz = mo->info->speed; - mo->angle = FixedAngle((P_RandomKey(36)*10)<angle = mo->angle; - P_SetMobjState(mo2, S_BOSSSEBH2); + P_RemoveMobj(mo); if (++i == 2) // we've already removed 2 of these, let's stop now break; - else - S_StartSound(mo, mo->info->deathsound); // done once to prevent sound stacking } } break; diff --git a/src/p_mobj.c b/src/p_mobj.c index 4af901fb6..5a5c12730 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -4353,6 +4353,1559 @@ static void P_GenericBossThinker(mobj_t *mobj) } } +// AI for the first boss. +static void P_Boss1Thinker(mobj_t *mobj) +{ + if (mobj->flags2 & MF2_FRET && (statenum_t)(mobj->state-states) == mobj->info->spawnstate) { + mobj->flags2 &= ~(MF2_FRET|MF2_SKULLFLY); + mobj->momx = mobj->momy = mobj->momz = 0; + } + + if (!mobj->tracer) + { + var1 = 0; + A_BossJetFume(mobj); + } + + if (!mobj->target || !(mobj->target->flags & MF_SHOOTABLE)) + { + if (mobj->target && mobj->target->health + && mobj->target->type == MT_EGGMOBILE_TARGET) // Oh, we're just firing our laser. + return; // It's okay, then. + + if (mobj->health <= 0) + { + if (P_BossTargetPlayer(mobj, false) && mobj->info->mass) // Bid farewell! + S_StartSound(mobj, mobj->info->mass); + return; + } + + // look for a new target + if (P_BossTargetPlayer(mobj, false) && mobj->info->seesound) + S_StartSound(mobj, mobj->info->seesound); + + return; + } + + if (mobj->state != &states[mobj->info->spawnstate] && mobj->health > 0 && mobj->flags & MF_FLOAT && !(mobj->flags2 & MF2_SKULLFLY)) + mobj->momz = FixedMul(mobj->momz,7*FRACUNIT/8); + + if (mobj->state == &states[mobj->info->meleestate] + || (mobj->state == &states[mobj->info->missilestate] + && mobj->health > mobj->info->damage)) + { + mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y); + } +} + +// AI for the second boss. +// No, it does NOT convert "Boss" to a "Thinker". =P +static void P_Boss2Thinker(mobj_t *mobj) +{ + if (mobj->movecount) + mobj->movecount--; + + if (!mobj->movecount) + mobj->flags2 &= ~MF2_FRET; + + if (!mobj->tracer) + { + var1 = 0; + A_BossJetFume(mobj); + } + + if (mobj->health <= mobj->info->damage && (!mobj->target || !(mobj->target->flags & MF_SHOOTABLE))) + { + if (mobj->health <= 0) + { + // look for a new target + if (P_BossTargetPlayer(mobj, false) && mobj->info->mass) // Bid farewell! + S_StartSound(mobj, mobj->info->mass); + return; + } + + // look for a new target + if (P_BossTargetPlayer(mobj, false) && mobj->info->seesound) + S_StartSound(mobj, mobj->info->seesound); + + return; + } + + if (mobj->state == &states[mobj->info->spawnstate] && mobj->health > mobj->info->damage) + A_Boss2Chase(mobj); + else if (mobj->health > 0 && mobj->state != &states[mobj->info->painstate] && mobj->state != &states[mobjinfo[mobj->info->missilestate].raisestate]) + { + mobj->flags &= ~MF_NOGRAVITY; + A_Boss2Pogo(mobj); + P_LinedefExecute(LE_PINCHPHASE, mobj, NULL); + } +} + +// AI for the third boss. +// +// Notes for reminders: +// movedir = move 2x fast? +// movecount = fire missiles? +// reactiontime = shock the water? +// threshold = next waypoint # +// extravalue1 = previous time shock sound was used +// +static void P_Boss3Thinker(mobj_t *mobj) +{ + if (mobj->state == &states[mobj->info->spawnstate]) + mobj->flags2 &= ~MF2_FRET; + + if (mobj->flags2 & MF2_FRET) + mobj->movedir = 1; + + if (!mobj->tracer) + { + var1 = 1; + A_BossJetFume(mobj); + } + + if (mobj->health <= 0) + { + mobj->movecount = 0; + mobj->reactiontime = 0; + + if (mobj->state < &states[mobj->info->xdeathstate]) + return; + + if (mobj->threshold == -1) + { + mobj->momz = mobj->info->speed; + return; + } + } + + if (mobj->reactiontime) // Shock mode + { + UINT32 i; + + if (mobj->state != &states[mobj->info->spawnstate]) + P_SetMobjState(mobj, mobj->info->spawnstate); + + mobj->reactiontime--; + if (!mobj->reactiontime) + { + ffloor_t *rover; + + // Shock the water + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + + if (!players[i].mo) + continue; + + if (players[i].mo->health <= 0) + continue; + + if (players[i].mo->eflags & MFE_UNDERWATER) + P_DamageMobj(players[i].mo, mobj, mobj, 1, DMG_VOLTAGE); + } + + // Make the water flash + for (i = 0; i < numsectors; i++) + { + if (!sectors[i].ffloors) + continue; + + for (rover = sectors[i].ffloors; rover; rover = rover->next) + { + if (!(rover->fofflags & FOF_EXISTS)) + continue; + + if (!(rover->fofflags & FOF_SWIMMABLE)) + continue; + + P_SpawnLightningFlash(rover->master->frontsector); + break; + } + } + + if ((UINT32)mobj->extravalue1 + TICRATE*2 < leveltime) + { + mobj->extravalue1 = (INT32)leveltime; + S_StartSound(0, sfx_buzz1); + } + + // If in the center, check to make sure + // none of the players are in the water + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + + if (!players[i].mo || players[i].bot) + continue; + + if (players[i].mo->health <= 0) + continue; + + if (players[i].mo->eflags & MFE_UNDERWATER) + { // Stay put + mobj->reactiontime = 2*TICRATE; + return; + } + } + } + + if (!mobj->reactiontime && mobj->health <= mobj->info->damage) + { // Spawn pinch dummies from the center when we're leaving it. + thinker_t *th; + mobj_t *mo2; + mobj_t *dummy; + SINT8 way = mobj->threshold - 1; // 0 through 4. + SINT8 way2; + + i = 0; // reset i to 0 so we can check how many clones we've removed + + // scan the thinkers to make sure all the old pinch dummies are gone before making new ones + // this can happen if the boss was hurt earlier than expected + for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) + { + if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) + continue; + + mo2 = (mobj_t *)th; + if (mo2->type == (mobjtype_t)mobj->info->mass && mo2->tracer == mobj) + { + P_RemoveMobj(mo2); + i++; + } + if (i == 2) // we've already removed 2 of these, let's stop now + break; + } + + way = (way + P_RandomRange(1,3)) % 5; // dummy 1 at one of the first three options after eggmobile + dummy = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->info->mass); + dummy->angle = mobj->angle; + dummy->threshold = way + 1; + P_SetTarget(&dummy->tracer, mobj); + + do + way2 = (way + P_RandomRange(1,3)) % 5; // dummy 2 has to be careful, + while (way2 == mobj->threshold - 1); // to make sure it doesn't try to go the Eggman Way if dummy 1 rolled high. + dummy = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->info->mass); + dummy->angle = mobj->angle; + dummy->threshold = way2 + 1; + P_SetTarget(&dummy->tracer, mobj); + + CONS_Debug(DBG_GAMELOGIC, "Eggman path %d - Dummy selected paths %d and %d\n", mobj->threshold, way + 1, dummy->threshold); + P_LinedefExecute(LE_PINCHPHASE, mobj, NULL); + } + } + else if (mobj->movecount) // Firing mode + { + UINT32 i; + + // look for a new target + P_BossTargetPlayer(mobj, false); + + if (!mobj->target || !mobj->target->player) + return; + + // Are there any players underwater? If so, shock them! + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + + if (!players[i].mo || players[i].bot) + continue; + + if (players[i].mo->health <= 0) + continue; + + if (players[i].mo->eflags & MFE_UNDERWATER) + { + mobj->movecount = 0; + P_SetMobjState(mobj, mobj->info->spawnstate); + return; + } + } + + // Always face your target. + A_FaceTarget(mobj); + + // Check if the attack animation is running. If not, play it. + if (mobj->state < &states[mobj->info->missilestate] || mobj->state > &states[mobj->info->raisestate]) + { + if (mobj->health <= mobj->info->damage) // pinch phase + mobj->movecount--; // limited number of shots before diving again + if (mobj->movecount) + P_SetMobjState(mobj, mobj->info->missilestate); + } + } + else if (mobj->threshold >= 0) // Traveling mode + { + thinker_t *th; + mobj_t *mo2; + fixed_t dist, dist2; + fixed_t speed; + + P_SetTarget(&mobj->target, NULL); + + if (mobj->state != &states[mobj->info->spawnstate] && mobj->health > 0 + && !(mobj->flags2 & MF2_FRET)) + P_SetMobjState(mobj, mobj->info->spawnstate); + + // scan the thinkers + // to find a point that matches + // the number + for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) + { + if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) + continue; + + mo2 = (mobj_t *)th; + + if (!mo2) + continue; + + if (mo2->type != MT_BOSS3WAYPOINT) + continue; + + if (!mo2->spawnpoint) + continue; + + if (mo2->spawnpoint->angle != mobj->threshold) + continue; + + P_SetTarget(&mobj->target, mo2); + break; + } + + if (!mobj->target) // Should NEVER happen + { + CONS_Debug(DBG_GAMELOGIC, "Error: Boss 3 was unable to find specified waypoint: %d\n", mobj->threshold); + return; + } + + dist = P_AproxDistance(P_AproxDistance(mobj->target->x - mobj->x, mobj->target->y - mobj->y), mobj->target->z - mobj->z); + + if (dist < 1) + dist = 1; + + if ((mobj->movedir) || (mobj->health <= mobj->info->damage)) + speed = mobj->info->speed * 2; + else + speed = mobj->info->speed; + + mobj->momx = FixedMul(FixedDiv(mobj->target->x - mobj->x, dist), speed); + mobj->momy = FixedMul(FixedDiv(mobj->target->y - mobj->y, dist), speed); + mobj->momz = FixedMul(FixedDiv(mobj->target->z - mobj->z, dist), speed); + + if (mobj->momx != 0 || mobj->momy != 0) + mobj->angle = R_PointToAngle2(0, 0, mobj->momx, mobj->momy); + + dist2 = P_AproxDistance(P_AproxDistance(mobj->target->x - (mobj->x + mobj->momx), mobj->target->y - (mobj->y + mobj->momy)), mobj->target->z - (mobj->z + mobj->momz)); + + if (dist2 < 1) + dist2 = 1; + + if ((dist >> FRACBITS) <= (dist2 >> FRACBITS)) + { + // If further away, set XYZ of mobj to waypoint location + P_UnsetThingPosition(mobj); + mobj->x = mobj->target->x; + mobj->y = mobj->target->y; + mobj->z = mobj->target->z; + mobj->momx = mobj->momy = mobj->momz = 0; + P_SetThingPosition(mobj); + + if (mobj->threshold == 0) + { + mobj->reactiontime = 1; // Bzzt! Shock the water! + mobj->movedir = 0; + + if (mobj->health <= 0) + { + mobj->flags |= MF_NOGRAVITY|MF_NOCLIP; + mobj->flags |= MF_NOCLIPHEIGHT; + mobj->threshold = -1; + return; + } + } + + // Set to next waypoint in sequence + if (mobj->target->spawnpoint) + { + // From the center point, choose one of the five paths + if (mobj->target->spawnpoint->angle == 0) + mobj->threshold = P_RandomRange(1,5); + else + mobj->threshold = mobj->target->spawnpoint->extrainfo; + + // If the deaf flag is set, go into firing mode + if (mobj->target->spawnpoint->options & MTF_AMBUSH) + mobj->movecount = mobj->health+1; + } + else // This should never happen, as well + CONS_Debug(DBG_GAMELOGIC, "Error: Boss 3 waypoint has no spawnpoint associated with it.\n"); + } + } +} + +// Move Boss4's sectors by delta. +static boolean P_Boss4MoveCage(mobj_t *mobj, fixed_t delta) +{ + INT32 snum; + sector_t *sector; + boolean gotcage = false; + + if (!mobj->spawnpoint) + return false; + + TAG_ITER_SECTORS(mobj->spawnpoint->args[4], snum) + { + sector = §ors[snum]; + sector->floorheight += delta; + sector->ceilingheight += delta; + P_CheckSector(sector, true); + gotcage = true; + } + return gotcage; +} + +// Move Boss4's arms to angle +static void P_Boss4MoveSpikeballs(mobj_t *mobj, angle_t angle, fixed_t fz) +{ + INT32 s; + mobj_t *base = mobj, *seg; + fixed_t dist, bz = mobj->watertop+(16<tracer)) + { + for (seg = base, dist = 172*FRACUNIT, s = 9; seg; seg = seg->hnext, dist += 124*FRACUNIT, --s) + P_MoveOrigin(seg, mobj->x + P_ReturnThrustX(mobj, angle, dist), mobj->y + P_ReturnThrustY(mobj, angle, dist), bz + FixedMul(fz, FixedDiv(s<watertop+(16<tracer)) + { + for (seg = base, dist = 112*FRACUNIT, s = 9; seg; seg = seg->hnext, dist += 132*FRACUNIT, --s) + { + seg->z = bz + FixedMul(fz, FixedDiv(s<x + P_ReturnThrustX(mobj, angle, dist), mobj->y + P_ReturnThrustY(mobj, angle, dist), true, NULL); + } + angle += ANGLE_MAX/3; + } +} + +// Destroy cage FOFs. +static void P_Boss4DestroyCage(mobj_t *mobj) +{ + INT32 snum; + size_t a; + sector_t *sector, *rsec; + ffloor_t *rover; + + if (!mobj->spawnpoint) + return; + + TAG_ITER_SECTORS(mobj->spawnpoint->args[4], snum) + { + sector = §ors[snum]; + + // Destroy the FOFs. + for (a = 0; a < sector->numattached; a++) + { + rsec = §ors[sector->attached[a]]; + for (rover = rsec->ffloors; rover; rover = rover->next) + if (rover->fofflags & FOF_EXISTS && rover->secnum == (size_t)snum) + { + if (rover->fofflags & FOF_RENDERALL) // checking for FF_RENDERANY. + EV_CrumbleChain(rsec, rover); // This FOF is visible to some extent? Crumble it. + else // Completely invisible FOF + { + // no longer exists (can't collide with again) + rover->fofflags &= ~FOF_EXISTS; + sector->moved = true; + rsec->moved = true; + } + } + } + } +} + +// Destroy Boss4's arms +static void P_Boss4PopSpikeballs(mobj_t *mobj) +{ + mobj_t *base = mobj->tracer, *seg, *next; + P_SetTarget(&mobj->tracer, NULL); + while(base) + { + next = base->tracer; + P_SetTarget(&base->tracer, NULL); + for (seg = base; seg; seg = seg->hnext) + { + if (seg->health) + P_KillMobj(seg, NULL, NULL, DMG_INSTAKILL); + } + base = next; + } +} + +// +// AI for the fourth boss. +// +static void P_Boss4Thinker(mobj_t *mobj) +{ + if ((statenum_t)(mobj->state-states) == mobj->info->spawnstate) + { + if (mobj->health > mobj->info->damage || mobj->movedir == 4) + mobj->flags2 &= ~MF2_FRET; + mobj->reactiontime = 0; // Drop the cage immediately. + } + + // Oh no, we dead? D: + if (!mobj->health) + { + if (mobj->tracer) // need to clean up! + { + P_Boss4DestroyCage(mobj); // Just in case pinch phase was skipped. + P_Boss4PopSpikeballs(mobj); + } + return; + } + + // movedir == battle stage: + // 0: initialization + // 1: phase 1 forward + // 2: phase 1 reverse + // 3: pinch rise + // 4: pinch phase + switch(mobj->movedir) + { + // WELCOME to your DOOM! + case 0: + { + // For this stage only: + // movecount == cage height + // threshold == cage momz + if (mobj->movecount == 0) // Initialize stage! + { + fixed_t z; + INT32 i, arm; + mobj_t *seg, *base = mobj; + // First frame init, spawn all the things. + mobj->watertop = mobj->z; + z = mobj->z + mobj->height/2 - mobjinfo[MT_EGGMOBILE4_MACE].height/2; + for (arm = 0; arm <3 ; arm++) + { + seg = P_SpawnMobj(mobj->x, mobj->y, z, MT_EGGMOBILE4_MACE); + P_SetTarget(&base->tracer, seg); + base = seg; + P_SetTarget(&seg->target, mobj); + for (i = 0; i < 9; i++) + { + P_SetTarget(&seg->hnext, P_SpawnMobj(mobj->x, mobj->y, z, MT_EGGMOBILE4_MACE)); + P_SetTarget(&seg->hnext->hprev, seg); + seg = seg->hnext; + } + } + // Move the cage up to the sky. + mobj->movecount = 800*FRACUNIT; + if (!P_Boss4MoveCage(mobj, mobj->movecount)) + { + mobj->movecount = 0; + mobj->threshold = 3*TICRATE; + mobj->extravalue1 = 1; + mobj->movedir++; // We don't have a cage, just continue. + } + else + P_Boss4MoveSpikeballs(mobj, 0, mobj->movecount); + } + else // Cage slams down over Eggman's head! + { + fixed_t oldz = mobj->movecount; + mobj->threshold -= 5*FRACUNIT; + mobj->movecount += mobj->threshold; + if (mobj->movecount < 0) + mobj->movecount = 0; + P_Boss4MoveCage(mobj, mobj->movecount - oldz); + P_Boss4MoveSpikeballs(mobj, 0, mobj->movecount); + if (mobj->movecount == 0) + { + mobj->threshold = 3*TICRATE; + mobj->extravalue1 = 1; + P_LinedefExecute(LE_BOSS4DROP, mobj, NULL); + mobj->movedir++; // Initialization complete, next phase! + } + } + return; + } + + // Normal operation + case 1: + case 2: + break; + + // Pinch phase init! + case 3: + { + fixed_t z; + if (mobj->z < mobj->watertop+(512<momz = 8*FRACUNIT; + else + { + mobj->momz = 0; + mobj->movedir++; + } + mobj->movecount += 400<<(FRACBITS>>1); + mobj->movecount %= 360*FRACUNIT; + z = mobj->z - mobj->watertop - mobjinfo[MT_EGGMOBILE4_MACE].height - mobj->height/2; + if (z < 0) // We haven't risen high enough to pull the spikeballs along yet + P_Boss4MoveSpikeballs(mobj, FixedAngle(mobj->movecount), 0); // So don't pull the spikeballs along yet. + else + P_Boss4PinchSpikeballs(mobj, FixedAngle(mobj->movecount), z); + return; + } + // Pinch phase! + case 4: + { + if (mobj->z < (mobj->watertop + ((512+128*(mobj->info->damage-mobj->health))<momz = 8*FRACUNIT; + else + mobj->momz = 0; + mobj->movecount += (800+800*(mobj->info->damage-mobj->health))<<(FRACBITS>>1); + mobj->movecount %= 360*FRACUNIT; + P_Boss4PinchSpikeballs(mobj, FixedAngle(mobj->movecount), mobj->z - mobj->watertop - mobjinfo[MT_EGGMOBILE4_MACE].height - mobj->height/2); + + if (!mobj->target || !mobj->target->health) + P_SupermanLook4Players(mobj); + A_FaceTarget(mobj); + return; + } + + default: // ????? + return; + } + + // Haahahahahaaa, and let the FUN.. BEGIN! + // movedir == arms direction + // movecount == arms angle + // threshold == countdown to next attack + // reactiontime == cage raise, speed burst + // movefactor == cage z + // friction == turns until helm lift + + // Raise the cage! + if (mobj->reactiontime == 1) + { + fixed_t oldz = mobj->movefactor; + mobj->movefactor += 8*FRACUNIT; + if (mobj->movefactor > 128*FRACUNIT) + mobj->movefactor = 128*FRACUNIT; + P_Boss4MoveCage(mobj, mobj->movefactor - oldz); + } + // Drop the cage! + else if (mobj->movefactor) + { + fixed_t oldz = mobj->movefactor; + mobj->movefactor -= 4*FRACUNIT; + if (mobj->movefactor < 0) + mobj->movefactor = 0; + P_Boss4MoveCage(mobj, mobj->movefactor - oldz); + if (!mobj->movefactor) + { + if (mobj->health <= mobj->info->damage) + { // Proceed to pinch phase! + P_Boss4DestroyCage(mobj); + mobj->movedir = 3; + P_LinedefExecute(LE_PINCHPHASE, mobj, NULL); + return; + } + P_LinedefExecute(LE_BOSS4DROP, mobj, NULL); + } + } + + { + fixed_t movespeed = 170<<(FRACBITS>>1); + if (mobj->reactiontime == 2) + movespeed *= 3; + if (mobj->movedir == 2) + mobj->movecount -= movespeed; + else + mobj->movecount += movespeed; + } + mobj->movecount %= 360*FRACUNIT; + P_Boss4MoveSpikeballs(mobj, FixedAngle(mobj->movecount), mobj->movefactor); + + // Check for attacks, always tick the timer even while animating!! + if (!(mobj->flags2 & MF2_FRET) // but pause for pain so we don't interrupt pinch phase, eep! + && mobj->threshold-- == 0) + { + // 5 -> 2.5 second timer + mobj->threshold = 5*TICRATE-(TICRATE/2)*(mobj->info->spawnhealth-mobj->health); + if (mobj->threshold < 1) + mobj->threshold = 1; + + if (mobj->extravalue1-- == 0) + { + P_SetMobjState(mobj, mobj->info->raisestate); + mobj->extravalue1 = 3; + } + else + { + if (mobj->reactiontime == 1) // Cage is raised? + mobj->reactiontime = 0; // Drop it! + switch(P_RandomKey(10)) + { + // Telegraph Right (Speed Up!!) + case 1: + case 3: + case 4: + case 5: + case 6: + P_SetMobjState(mobj, mobj->info->missilestate); + break; + // Telegraph Left (Reverse Direction) + default: + P_SetMobjState(mobj, mobj->info->meleestate); + break; + } + } + } + + // Leave if animating. + if ((statenum_t)(mobj->state-states) != mobj->info->spawnstate) + return; + + // Map allows us to get killed despite cage being down? + if (mobj->health <= mobj->info->damage) + { // Proceed to pinch phase! + P_Boss4DestroyCage(mobj); + // spawn jet's flame now you're flying upwards + // tracer is already used, so if this ever gets reached again we've got problems + var1 = 3; + A_BossJetFume(mobj); + mobj->movedir = 3; + P_LinedefExecute(LE_PINCHPHASE, mobj, NULL); + return; + } + + mobj->reactiontime = 0; // Drop the cage if it hasn't been dropped already. + if (!mobj->target || !mobj->target->health) + P_SupermanLook4Players(mobj); + A_FaceTarget(mobj); +} + +// +// AI for Black Eggman +// Note: You CANNOT have more than ONE Black Eggman +// in a level! Just don't try it! +// +static void P_Boss7Thinker(mobj_t *mobj) +{ + if (!mobj->target || !(mobj->target->flags & MF_SHOOTABLE)) + { + // look for a new target + if (P_BossTargetPlayer(mobj, false)) + return; // got a new target + + P_SetMobjStateNF(mobj, mobj->info->spawnstate); + return; + } + + if (mobj->health >= mobj->info->spawnhealth && (leveltime & 14) == 0) + { + mobj_t *smoke = P_SpawnMobj(mobj->x, mobj->y, mobj->z + mobj->height, MT_SMOKE); + smoke->destscale = mobj->destscale; + P_SetScale(smoke, smoke->destscale); + smoke->momz = FixedMul(FRACUNIT, smoke->scale); + } + + if (mobj->state == &states[S_BLACKEGG_STND] && mobj->tics == mobj->state->tics) + { + mobj->reactiontime += P_RandomByte(); + + if (mobj->health <= mobj->info->damage) + mobj->reactiontime /= 4; + } + else if (mobj->state == &states[S_BLACKEGG_DIE4] && mobj->tics == mobj->state->tics) + A_BossDeath(mobj); + else if (mobj->state >= &states[S_BLACKEGG_WALK1] + && mobj->state <= &states[S_BLACKEGG_WALK6]) + A_Boss7Chase(mobj); + else if (mobj->state == &states[S_BLACKEGG_PAIN1] && mobj->tics == mobj->state->tics) + { + if (mobj->health > 0) + mobj->health--; + + S_StartSound(0, (mobj->health) ? sfx_behurt : sfx_bedie2); + + mobj->reactiontime /= 3; + + if (mobj->health <= 0) + { + INT32 i; + + P_KillMobj(mobj, NULL, NULL, DMG_INSTAKILL); + + // It was a team effort + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + + P_AddPlayerScore(&players[i], 1000); + } + } + } + else if (mobj->state == &states[S_BLACKEGG_PAIN35] && mobj->tics == 1) + { + if (mobj->health == mobj->info->damage) + { + // Begin platform destruction + mobj->flags2 |= MF2_FRET; + P_SetMobjState(mobj, mobj->info->raisestate); + P_LinedefExecute(LE_PINCHPHASE, mobj, NULL); + } + } + else if (mobj->state == &states[S_BLACKEGG_HITFACE4] && mobj->tics == mobj->state->tics) + { + // This is where Black Eggman hits his face. + // If a player is on top of him, the player gets hurt. + // But, if the player has managed to escape, + // Black Eggman gets hurt! + INT32 i; + mobj->state->nextstate = mobj->info->painstate; // Reset + + S_StartSound(0, sfx_bedeen); + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + + if (!players[i].mo) + continue; + + if (players[i].mo->health <= 0) + continue; + + if (P_AproxDistance(players[i].mo->x - mobj->x, players[i].mo->y - mobj->y) > (mobj->radius + players[i].mo->radius)) + continue; + + if (players[i].mo->z > mobj->z + mobj->height - FRACUNIT + && players[i].mo->z < mobj->z + mobj->height + 128*FRACUNIT) // You can't be in the vicinity, either... + { + // Punch him! + P_DamageMobj(players[i].mo, mobj, mobj, 1, DMG_NORMAL); + mobj->state->nextstate = mobj->info->spawnstate; + + // Laugh + S_StartSound(0, sfx_bewar1 + P_RandomKey(4)); + } + } + } + else if (mobj->state == &states[S_BLACKEGG_GOOP]) + { + // Lob cannon balls + if (mobj->movecount-- <= 0 || !mobj->target) + { + P_SetMobjState(mobj, mobj->info->spawnstate); + return; + } + + if ((leveltime & 15) == 0) + { + var1 = MT_CANNONBALL; + + var2 = 2*TICRATE + (80<<16); + + A_LobShot(mobj); + S_StartSound(0, sfx_begoop); + } + } + else if (mobj->state == &states[S_BLACKEGG_SHOOT2]) + { + // Chaingun goop + mobj_t *missile; + + if (mobj->movecount-- <= 0 || !mobj->target) + { + P_SetMobjState(mobj, mobj->info->spawnstate); + return; + } + + A_FaceTarget(mobj); + + missile = P_SpawnXYZMissile(mobj, mobj->target, MT_BLACKEGGMAN_GOOPFIRE, + mobj->x + P_ReturnThrustX(mobj, mobj->angle-ANGLE_90, FixedDiv(mobj->radius, 3*FRACUNIT/2)+(4*FRACUNIT)), + mobj->y + P_ReturnThrustY(mobj, mobj->angle-ANGLE_90, FixedDiv(mobj->radius, 3*FRACUNIT/2)+(4*FRACUNIT)), + mobj->z + FixedDiv(mobj->height, 3*FRACUNIT/2)); + + S_StopSound(missile); + + if (leveltime & 1) + S_StartSound(0, sfx_beshot); + } + else if (mobj->state == &states[S_BLACKEGG_JUMP1] && mobj->tics == 1) + { + mobj_t *hitspot = NULL, *mo2; + angle_t an; + fixed_t dist, closestdist; + fixed_t vertical, horizontal; + fixed_t airtime = 5*TICRATE; + INT32 waypointNum = 0; + thinker_t *th; + INT32 i; + boolean foundgoop = false; + INT32 closestNum; + + // Looks for players in goop. If you find one, try to jump on him. + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + + if (!players[i].mo) + continue; + + if (players[i].mo->health <= 0) + continue; + + if (players[i].powers[pw_ingoop]) + { + closestNum = -1; + closestdist = 16384*FRACUNIT; // Just in case... + + // Find waypoint he is closest to + for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) + { + if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) + continue; + + mo2 = (mobj_t *)th; + if (mo2->type == MT_BOSS3WAYPOINT && mo2->spawnpoint) + { + dist = P_AproxDistance(players[i].mo->x - mo2->x, players[i].mo->y - mo2->y); + + if (closestNum == -1 || dist < closestdist) + { + closestNum = (mo2->spawnpoint->options & 7); + closestdist = dist; + foundgoop = true; + } + } + } + waypointNum = closestNum; + break; + } + } + + if (!foundgoop) + { + if (mobj->z > 1056*FRACUNIT) + waypointNum = 0; + else + waypointNum = 1 + P_RandomKey(4); + } + + // Don't jump to the center when health is low. + // Force the player to beat you with missiles. + if (mobj->health <= mobj->info->damage && waypointNum == 0) + waypointNum = 1 + P_RandomKey(4); + + if (mobj->tracer && mobj->tracer->type == MT_BOSS3WAYPOINT + && mobj->tracer->spawnpoint && (mobj->tracer->spawnpoint->options & 7) == waypointNum) + { + if (P_RandomChance(FRACUNIT/2)) + waypointNum++; + else + waypointNum--; + + waypointNum %= 5; + + if (waypointNum < 0) + waypointNum = 0; + } + + if (waypointNum == 0 && mobj->health <= mobj->info->damage) + waypointNum = 1 + (P_RandomFixed() & 1); + + // scan the thinkers to find + // the waypoint to use + for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) + { + if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) + continue; + + mo2 = (mobj_t *)th; + if (mo2->type == MT_BOSS3WAYPOINT && mo2->spawnpoint && (mo2->spawnpoint->options & 7) == waypointNum) + { + hitspot = mo2; + break; + } + } + + if (hitspot == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "BlackEggman unable to find waypoint #%d!\n", waypointNum); + P_SetMobjState(mobj, mobj->info->spawnstate); + return; + } + + P_SetTarget(&mobj->tracer, hitspot); + + mobj->angle = R_PointToAngle2(mobj->x, mobj->y, hitspot->x, hitspot->y); + + an = mobj->angle; + an >>= ANGLETOFINESHIFT; + + dist = P_AproxDistance(hitspot->x - mobj->x, hitspot->y - mobj->y); + + horizontal = dist / airtime; + vertical = (gravity*airtime)/2; + + mobj->momx = FixedMul(horizontal, FINECOSINE(an)); + mobj->momy = FixedMul(horizontal, FINESINE(an)); + mobj->momz = vertical; + +// mobj->momz = 10*FRACUNIT; + } + else if (mobj->state == &states[S_BLACKEGG_JUMP2] && mobj->z <= mobj->floorz) + { + // BANG! onto the ground + INT32 i,j; + fixed_t ns; + fixed_t x,y,z; + mobj_t *mo2; + + S_StartSound(0, sfx_befall); + + z = mobj->floorz; + for (j = 0; j < 2; j++) + { + for (i = 0; i < 32; i++) + { + const angle_t fa = (i*FINEANGLES/16) & FINEMASK; + ns = 64 * FRACUNIT; + x = mobj->x + FixedMul(FINESINE(fa),ns); + y = mobj->y + FixedMul(FINECOSINE(fa),ns); + + mo2 = P_SpawnMobj(x, y, z, MT_EXPLODE); + ns = 16 * FRACUNIT; + mo2->momx = FixedMul(FINESINE(fa),ns); + mo2->momy = FixedMul(FINECOSINE(fa),ns); + } + z -= 32*FRACUNIT; + } + + // Hurt player?? + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + + if (!players[i].mo) + continue; + + if (players[i].mo->health <= 0) + continue; + + if (P_AproxDistance(players[i].mo->x - mobj->x, players[i].mo->y - mobj->y) > mobj->radius*4) + continue; + + if (players[i].mo->z > mobj->z + 128*FRACUNIT) + continue; + + if (players[i].mo->z < mobj->z - 64*FRACUNIT) + continue; + + P_DamageMobj(players[i].mo, mobj, mobj, 1, DMG_NORMAL); + + // Laugh + S_StartSound(0, sfx_bewar1 + P_RandomKey(4)); + } + + P_SetMobjState(mobj, mobj->info->spawnstate); + } + else if (mobj->state == &states[mobj->info->deathstate] && mobj->tics == mobj->state->tics) + S_StartSound(0, sfx_bedie1 + (P_RandomFixed() & 1)); + +} + +// Metal Sonic battle boss +// You CAN put multiple Metal Sonics in a single map +// because I am a totally competent programmer who can do shit right. +static void P_Boss9Thinker(mobj_t *mobj) +{ + TryMoveResult_t result = {0}; + + if ((statenum_t)(mobj->state-states) == mobj->info->spawnstate) + mobj->flags2 &= ~MF2_FRET; + + if (!mobj->tracer) + { + thinker_t *th; + mobj_t *mo2; + mobj_t *last=NULL; + + // Initialize the boss, spawn jet fumes, etc. + mobj->threshold = 0; + mobj->reactiontime = 0; + mobj->watertop = mobj->floorz + 32*FRACUNIT; + var1 = 2; + A_BossJetFume(mobj); + + // Run through the thinkers ONCE and find all of the MT_BOSS9GATHERPOINT in the map. + // Build a hoop linked list of 'em! + for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) + { + if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) + continue; + + mo2 = (mobj_t *)th; + if (mo2->type == MT_BOSS9GATHERPOINT) + { + if (last) + P_SetTarget(&last->hnext, mo2); + else + P_SetTarget(&mobj->hnext, mo2); + P_SetTarget(&mo2->hprev, last); + last = mo2; + } + } + } + + if (mobj->health <= 0) + return; + + if ((!mobj->target || !(mobj->target->flags & MF_SHOOTABLE))) + { + P_BossTargetPlayer(mobj, false); + if (mobj->target && !P_IsObjectOnGround(mobj->target)) + P_SetTarget(&mobj->target, NULL); // Wait for them to hit the ground first + if (!mobj->target) // Still no target, aww. + { + // Reset the boss. + P_SetMobjState(mobj, mobj->info->spawnstate); + mobj->fuse = 0; + mobj->momx = FixedDiv(mobj->momx, FRACUNIT + (FRACUNIT>>2)); + mobj->momy = FixedDiv(mobj->momy, FRACUNIT + (FRACUNIT>>2)); + mobj->momz = FixedDiv(mobj->momz, FRACUNIT + (FRACUNIT>>2)); + return; + } + else if (!mobj->fuse) + mobj->fuse = 10*TICRATE; + } + + // AI goes here. + { + boolean danger = true; + angle_t angle; + if (mobj->threshold) + mobj->momz = (mobj->watertop-mobj->z)/16; // Float to your desired position FASTER + else + mobj->momz = (mobj->watertop-mobj->z)/40; // Float to your desired position + + if (mobj->movecount == 2) + { + mobj_t *spawner; + fixed_t dist = 0; + angle = 0x06000000*leveltime; + + // Alter your energy bubble's size/position + if (mobj->health > 3) + { + mobj->tracer->destscale = FRACUNIT + (4*TICRATE - mobj->fuse)*(FRACUNIT/2)/TICRATE + FixedMul(FINECOSINE(angle>>ANGLETOFINESHIFT),FRACUNIT/2); + P_SetScale(mobj->tracer, mobj->tracer->destscale); + P_SetOrigin(mobj->tracer, mobj->x, mobj->y, mobj->z + mobj->height/2 - mobj->tracer->height/2); + mobj->tracer->momx = mobj->momx; + mobj->tracer->momy = mobj->momy; + mobj->tracer->momz = mobj->momz; + } + + // Face your target + P_BossTargetPlayer(mobj, true); + angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y); // absolute angle + angle = (angle-mobj->angle); // relative angle + if (angle < ANGLE_180) + mobj->angle += angle/8; + else + mobj->angle -= InvAngle(angle)/8; + + // Spawn energy particles + for (spawner = mobj->hnext; spawner; spawner = spawner->hnext) + { + dist = P_AproxDistance(spawner->x - mobj->x, spawner->y - mobj->y); + if (P_RandomRange(1,(dist>>FRACBITS)/16) == 1) + break; + } + if (spawner && dist) + { + mobj_t *missile = P_SpawnMissile(spawner, mobj, MT_MSGATHER); + missile->momz = FixedDiv(missile->momz, 7*FRACUNIT/4); + missile->fuse = (dist/P_AproxDistance(missile->momx, missile->momy)); + if (missile->fuse > mobj->fuse) + P_RemoveMobj(missile); + } + } + + // Pre-threshold reactiontime stuff for attack phases + if (mobj->reactiontime && mobj->movecount == 3) + { + if (mobj->movedir == 0 || mobj->movedir == 2) // Pausing between bounces in the pinball phase + { + if (mobj->target->player->powers[pw_tailsfly]) // Trying to escape, eh? + mobj->watertop = mobj->target->z + mobj->target->momz*6; // Readjust your aim. >:3 + else + mobj->watertop = mobj->target->floorz + 16*FRACUNIT; + if (!(mobj->threshold%4)) + mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x + mobj->target->momx*4, mobj->target->y + mobj->target->momy*4); + } + // Pausing between energy ball shots + mobj->reactiontime--; + return; + } + + // threshold is used for attacks/maneuvers. + if (mobj->threshold) + { + fixed_t speed = 20*FRACUNIT + FixedMul(40*FRACUNIT, FixedDiv((mobj->info->spawnhealth - mobj->health)<info->spawnhealth<movecount == 3 && mobj->movedir == 1) + { + if (!(mobj->threshold&1)) + { + mobj_t *missile; + if (mobj->info->seesound) + S_StartSound(mobj, mobj->info->seesound); + P_SetMobjState(mobj, mobj->info->missilestate); + if (mobj->extravalue1 == 3) + mobj->reactiontime = TICRATE/16; + else + mobj->reactiontime = TICRATE/8; + + A_FaceTarget(mobj); + missile = P_SpawnMissile(mobj, mobj->target, mobj->info->speed); + if (mobj->extravalue1 == 2 || mobj->extravalue1 == 3) + { + missile->destscale = FRACUNIT>>1; + P_SetScale(missile, missile->destscale); + } + missile->fuse = 3*TICRATE; + missile->z -= missile->height/2; + + if (mobj->extravalue1 == 2) + { + int i; + mobj_t *spread; + missile->flags |= MF_MISSILE; + for (i = 0; i < 5; i++) + { + if (i == 2) + continue; + spread = P_SpawnMobj(missile->x, missile->y, missile->z, missile->type); + spread->angle = missile->angle+(ANGLE_11hh/2)*(i-2); + P_InstaThrust(spread,spread->angle,spread->info->speed); + spread->momz = missile->momz; + spread->destscale = FRACUNIT>>1; + P_SetScale(spread, spread->destscale); + spread->fuse = 3*TICRATE; + } + missile->flags &= ~MF_MISSILE; + } + } + else + { + P_SetMobjState(mobj, mobj->state->nextstate); + if (mobj->extravalue1 == 3) + mobj->reactiontime = TICRATE/8; + else + mobj->reactiontime = TICRATE/4; + } + mobj->threshold--; + return; + } + + P_SpawnGhostMobj(mobj); + + // Pinball attack! + if (mobj->movecount == 3 && (mobj->movedir == 0 || mobj->movedir == 2)) + { + if ((statenum_t)(mobj->state-states) != mobj->info->seestate) + P_SetMobjState(mobj, mobj->info->seestate); + if (mobj->movedir == 0) // mobj health == 1 + P_InstaThrust(mobj, mobj->angle, 38*FRACUNIT); + else if (mobj->health == 3) + P_InstaThrust(mobj, mobj->angle, 22*FRACUNIT); + else // mobj health == 2 + P_InstaThrust(mobj, mobj->angle, 30*FRACUNIT); + + if (!P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true, &result)) // Hit a wall? Find a direction to bounce + { + mobj->threshold--; + if (mobj->threshold) + { + P_SetMobjState(mobj, mobj->state->nextstate); + if (mobj->info->mass) + S_StartSound(mobj, mobj->info->mass); + if (!(mobj->threshold%4)) // We've decided to lock onto the player this bounce. + { + mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x + mobj->target->momx*4, mobj->target->y + mobj->target->momy*4); + mobj->reactiontime = TICRATE; // targetting time + } + else // No homing, just use P_BounceMove + { + P_BounceMove(mobj, &result); + mobj->angle = R_PointToAngle2(0,0,mobj->momx,mobj->momy); + mobj->reactiontime = TICRATE/4; // just a pause before you bounce away + } + mobj->momx = mobj->momy = 0; + } + } + return; + } + + // Vector form dodge! + mobj->angle += mobj->movedir; + P_InstaThrust(mobj, mobj->angle, -speed); + while (!P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true, &result) && tries++ < 16) + { + mobj->angle += mobj->movedir; + P_InstaThrust(mobj, mobj->angle, -speed); + } + mobj->momx = mobj->momy = 0; + mobj->threshold--; + if (!mobj->threshold) + { // Go into stun after dodge. + // from 3*TICRATE down to 1.25*TICRATE + //mobj->reactiontime = 5*TICRATE/4 + (FixedMul((7*TICRATE/4)<health-1)<info->spawnhealth-1)<>FRACBITS); + // from 3*TICRATE down to 2*TICRATE + mobj->reactiontime = 2*TICRATE + (FixedMul((1*TICRATE)<health-1)<info->spawnhealth-1)<>FRACBITS); + mobj->flags |= MF_SPECIAL|MF_SHOOTABLE; + P_SetMobjState(mobj, mobj->state->nextstate); + } + return; + } + + angle = 0x06000000*leveltime; + mobj->momz += FixedMul(FINECOSINE(angle>>ANGLETOFINESHIFT),2*FRACUNIT); // Use that "angle" to bob gently in the air + // This is below threshold because we don't want to bob while zipping around + + // Ohh you're in for it now.. + if (mobj->flags2 & MF2_FRET && mobj->health <= mobj->info->damage) + mobj->fuse = 0; + + // reactiontime is used for delays. + if (mobj->reactiontime) + { + // Stunned after vector form + if (mobj->movedir > ANGLE_180) + mobj->angle -= FixedAngle(FixedMul(AngleFixed(InvAngle(mobj->movedir)),FixedDiv(mobj->reactiontime<angle += FixedAngle(FixedMul(AngleFixed(mobj->movedir),FixedDiv(mobj->reactiontime<reactiontime--; + if (!mobj->reactiontime) + // Out of stun. + P_SetMobjState(mobj, mobj->state->nextstate); + return; + } + + // Not stunned? Can hit. + // Here because stun won't always get the chance to complete due to pinch phase activating, being hit, etc. + mobj->flags &= ~(MF_SPECIAL|MF_SHOOTABLE); + + if (mobj->health <= mobj->info->damage && mobj->fuse && !(mobj->fuse%TICRATE)) + { + var1 = 1; + var2 = 0; + A_BossScream(mobj); + } + + // Don't move if we're still in pain! + if (mobj->flags2 & MF2_FRET) + return; + + if (mobj->state == &states[mobj->info->raisestate]) // Charging energy + { + if (mobj->momx != 0 || mobj->momy != 0) // Apply the air breaks + { + if (abs(mobj->momx)+abs(mobj->momy) < FRACUNIT) + mobj->momx = mobj->momy = 0; + else + P_Thrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy), -6*FRACUNIT/8); + } + return; + } + + if (mobj->fuse == 0) + { + // It's time to attack! What are we gonna do?! + switch(mobj->movecount) + { + case 0: + default: + // Fly up and prepare for an attack! + // We have to charge up first, so let's go up into the air + P_SetMobjState(mobj, mobj->info->raisestate); + if (mobj->floorz >= mobj->target->floorz) + mobj->watertop = mobj->floorz + 256*FRACUNIT; + else + mobj->watertop = mobj->target->floorz + 256*FRACUNIT; + break; + + case 1: + { + // Okay, we're up? Good, time to gather energy... + if (mobj->health > mobj->info->damage) + { // No more bubble if we're broken (pinch phase) + mobj_t *shield = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_MSSHIELD_FRONT); + P_SetTarget(&mobj->tracer, shield); + P_SetTarget(&shield->target, mobj); + } + else + P_LinedefExecute(LE_PINCHPHASE, mobj, NULL); + mobj->fuse = 4*TICRATE; + mobj->flags |= MF_PAIN; + if (mobj->info->attacksound) + S_StartSound(mobj, mobj->info->attacksound); + A_FaceTarget(mobj); + break; + } + + case 2: + // We're all charged and ready now! Unleash the fury!! + if (mobj->health > mobj->info->damage) + { + mobj_t *removemobj = mobj->tracer; + P_SetTarget(&mobj->tracer, mobj->hnext); + P_RemoveMobj(removemobj); + } + if (mobj->health <= mobj->info->damage) + { + // Attack 1: Pinball dash! + if (mobj->health == 1) + mobj->movedir = 0; + else + mobj->movedir = 2; + if (mobj->info->seesound) + S_StartSound(mobj, mobj->info->seesound); + P_SetMobjState(mobj, mobj->info->seestate); + if (mobj->movedir == 2) + mobj->threshold = 16; // bounce 16 times + else + mobj->threshold = 32; // bounce 32 times + mobj->watertop = mobj->target->floorz + 16*FRACUNIT; + P_LinedefExecute(LE_PINCHPHASE, mobj, NULL); + } + else + { + // Attack 2: Energy shot! + mobj->movedir = 1; + + if (mobj->health >= 8) + mobj->extravalue1 = 0; + else if (mobj->health >= 5) + mobj->extravalue1 = 2; + else if (mobj->health >= 4) + mobj->extravalue1 = 1; + else + mobj->extravalue1 = 3; + + switch(mobj->extravalue1) + { + case 0: // shoot once + case 2: // spread-shot + default: + mobj->threshold = 2; + break; + case 1: // shoot 3 times + mobj->threshold = 3*2; + break; + case 3: // shoot like a goddamn machinegun + mobj->threshold = 8*2; + break; + } + } + break; + + case 3: + // Return to idle. + mobj->watertop = mobj->target->floorz + 32*FRACUNIT; + P_SetMobjState(mobj, mobj->info->spawnstate); + mobj->flags &= ~MF_PAIN; + mobj->fuse = 10*TICRATE; + break; + } + mobj->movecount++; + mobj->movecount %= 4; + return; + } + + // Idle AI + if (mobj->state == &states[mobj->info->spawnstate]) + { + fixed_t dist; + + // Target the closest player + P_BossTargetPlayer(mobj, true); + + // Face your target + angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y); // absolute angle + angle = (angle-mobj->angle); // relative angle + if (angle < ANGLE_180) + mobj->angle += angle/8; + else + mobj->angle -= InvAngle(angle)/8; + //A_FaceTarget(mobj); + + // Check if we're being attacked + if (mobj->target->x+mobj->target->radius+abs(mobj->target->momx*2) < mobj->x-mobj->radius) + danger = false; + if (mobj->target->x-mobj->target->radius-abs(mobj->target->momx*2) > mobj->x+mobj->radius) + danger = false; + if (mobj->target->y+mobj->target->radius+abs(mobj->target->momy*2) < mobj->y-mobj->radius) + danger = false; + if (mobj->target->y-mobj->target->radius-abs(mobj->target->momy*2) > mobj->y+mobj->radius) + danger = false; + if (mobj->target->z+mobj->target->height+mobj->target->momz*2 < mobj->z) + danger = false; + if (mobj->target->z+mobj->target->momz*2 > mobj->z+mobj->height) + danger = false; + if (danger) + { + // An incoming attack is detected! What should we do?! + // Go into vector form! + mobj->movedir = ANGLE_11hh - FixedAngle(FixedMul(AngleFixed(ANGLE_11hh), FixedDiv((mobj->info->spawnhealth - mobj->health)<info->spawnhealth-1)<movedir = InvAngle(mobj->movedir); + mobj->threshold = 6 + (FixedMul(24<info->spawnhealth - mobj->health)<info->spawnhealth-1)<>FRACBITS); + if (mobj->info->activesound) + S_StartSound(mobj, mobj->info->activesound); + if (mobj->info->painchance) + P_SetMobjState(mobj, mobj->info->painchance); + return; + } + + // Move normally: Approach the player using normal thrust and simulated friction. + dist = P_AproxDistance(mobj->x-mobj->target->x, mobj->y-mobj->target->y); + P_Thrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy), -3*FRACUNIT/8); + if (dist < 64*FRACUNIT) + P_Thrust(mobj, mobj->angle, -4*FRACUNIT); + else if (dist > 180*FRACUNIT) + P_Thrust(mobj, mobj->angle, FRACUNIT); + mobj->momz += P_AproxDistance(mobj->momx, mobj->momy)/12; // Move up higher the faster you're going. + } + } +} + // // P_GetClosestAxis // @@ -5457,9 +7010,6 @@ static void P_MobjSceneryThink(mobj_t *mobj) return; } break; - case MT_BOSSJUNK: - mobj->renderflags ^= RF_DONTDRAW; - break; case MT_MACEPOINT: case MT_CHAINMACEPOINT: case MT_SPRINGBALLPOINT: @@ -6150,6 +7700,28 @@ static boolean P_MobjBossThink(mobj_t *mobj) else switch (mobj->type) { + case MT_EGGMOBILE: + if (mobj->health < mobj->info->damage+1 && leveltime & 1 && mobj->health > 0) + P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_SMOKE); + if (mobj->flags2 & MF2_SKULLFLY) + P_SpawnGhostMobj(mobj); + P_Boss1Thinker(mobj); + break; + case MT_EGGMOBILE2: + P_Boss2Thinker(mobj); + break; + case MT_EGGMOBILE3: + P_Boss3Thinker(mobj); + break; + case MT_EGGMOBILE4: + P_Boss4Thinker(mobj); + break; + case MT_BLACKEGGMAN: + P_Boss7Thinker(mobj); + break; + case MT_METALSONIC_BATTLE: + P_Boss9Thinker(mobj); + break; // No SRB2Kart bosses... yet :) default: // Generic SOC-made boss if (mobj->flags2 & MF2_SKULLFLY) @@ -6476,6 +8048,122 @@ static boolean P_MobjRegularThink(mobj_t *mobj) mobj->tracer->z += mobj->height; } break; + case MT_NIGHTSCORE: + mobj->color = (UINT16)(leveltime % SKINCOLOR_WHITE); + break; + case MT_JETFUME1: + { + fixed_t jetx, jety; + + if (!mobj->target // if you have no target + || (!(mobj->target->flags & MF_BOSS) && mobj->target->health <= 0)) // or your target isn't a boss and it's popped now + { // then remove yourself as well! + P_RemoveMobj(mobj); + return false; + } + + jetx = mobj->target->x + P_ReturnThrustX(mobj->target, mobj->target->angle, FixedMul(-64*FRACUNIT, mobj->target->scale)); + jety = mobj->target->y + P_ReturnThrustY(mobj->target, mobj->target->angle, FixedMul(-64*FRACUNIT, mobj->target->scale)); + + if (mobj->fuse == 56) // First one + { + P_UnsetThingPosition(mobj); + mobj->x = jetx; + mobj->y = jety; + if (mobj->target->eflags & MFE_VERTICALFLIP) + mobj->z = mobj->target->z + mobj->target->height - mobj->height - FixedMul(38*FRACUNIT, mobj->target->scale); + else + mobj->z = mobj->target->z + FixedMul(38*FRACUNIT, mobj->target->scale); + mobj->floorz = mobj->z; + mobj->ceilingz = mobj->z+mobj->height; + P_SetThingPosition(mobj); + } + else if (mobj->fuse == 57) + { + P_UnsetThingPosition(mobj); + mobj->x = jetx + P_ReturnThrustX(mobj->target, mobj->target->angle-ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale)); + mobj->y = jety + P_ReturnThrustY(mobj->target, mobj->target->angle-ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale)); + if (mobj->target->eflags & MFE_VERTICALFLIP) + mobj->z = mobj->target->z + mobj->target->height - mobj->height - FixedMul(12*FRACUNIT, mobj->target->scale); + else + mobj->z = mobj->target->z + FixedMul(12*FRACUNIT, mobj->target->scale); + mobj->floorz = mobj->z; + mobj->ceilingz = mobj->z+mobj->height; + P_SetThingPosition(mobj); + } + else if (mobj->fuse == 58) + { + P_UnsetThingPosition(mobj); + mobj->x = jetx + P_ReturnThrustX(mobj->target, mobj->target->angle+ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale)); + mobj->y = jety + P_ReturnThrustY(mobj->target, mobj->target->angle+ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale)); + if (mobj->target->eflags & MFE_VERTICALFLIP) + mobj->z = mobj->target->z + mobj->target->height - mobj->height - FixedMul(12*FRACUNIT, mobj->target->scale); + else + mobj->z = mobj->target->z + FixedMul(12*FRACUNIT, mobj->target->scale); + mobj->floorz = mobj->z; + mobj->ceilingz = mobj->z+mobj->height; + P_SetThingPosition(mobj); + } + else if (mobj->fuse == 59) + { + jetx = mobj->target->x + P_ReturnThrustX(mobj->target, mobj->target->angle, -mobj->target->radius); + jety = mobj->target->y + P_ReturnThrustY(mobj->target, mobj->target->angle, -mobj->target->radius); + P_UnsetThingPosition(mobj); + mobj->x = jetx; + mobj->y = jety; + if (mobj->target->eflags & MFE_VERTICALFLIP) + mobj->z = mobj->target->z + mobj->target->height/2 + mobj->height/2; + else + mobj->z = mobj->target->z + mobj->target->height/2 - mobj->height/2; + mobj->floorz = mobj->z; + mobj->ceilingz = mobj->z+mobj->height; + P_SetThingPosition(mobj); + } + mobj->fuse++; + } + break; + case MT_PROPELLER: + { + fixed_t jetx, jety; + + if (!mobj->target // if you have no target + || (!(mobj->target->flags & MF_BOSS) && mobj->target->health <= 0)) // or your target isn't a boss and it's popped now + { // then remove yourself as well! + P_RemoveMobj(mobj); + return false; + } + + jetx = mobj->target->x + P_ReturnThrustX(mobj->target, mobj->target->angle, FixedMul(-60*FRACUNIT, mobj->target->scale)); + jety = mobj->target->y + P_ReturnThrustY(mobj->target, mobj->target->angle, FixedMul(-60*FRACUNIT, mobj->target->scale)); + + P_UnsetThingPosition(mobj); + mobj->x = jetx; + mobj->y = jety; + mobj->z = mobj->target->z + FixedMul(17*FRACUNIT, mobj->target->scale); + mobj->angle = mobj->target->angle - ANGLE_180; + mobj->floorz = mobj->z; + mobj->ceilingz = mobj->z+mobj->height; + P_SetThingPosition(mobj); + } + break; + case MT_JETFLAME: + { + if (!mobj->target // if you have no target + || (!(mobj->target->flags & MF_BOSS) && mobj->target->health <= 0)) // or your target isn't a boss and it's popped now + { // then remove yourself as well! + P_RemoveMobj(mobj); + return false; + } + + P_UnsetThingPosition(mobj); + mobj->x = mobj->target->x; + mobj->y = mobj->target->y; + mobj->z = mobj->target->z - FixedMul(50*FRACUNIT, mobj->target->scale); + mobj->floorz = mobj->z; + mobj->ceilingz = mobj->z+mobj->height; + P_SetThingPosition(mobj); + } + break; case MT_PLAYER: if (mobj->player) P_PlayerMobjThinker(mobj); @@ -9250,10 +10938,6 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) case MT_KART_LEFTOVER: mobj->color = SKINCOLOR_RED; break; - case MT_EGGROBO1: - mobj->movecount = P_RandomKey(13); - mobj->color = FIRSTRAINBOWCOLOR + P_RandomKey(FIRSTSUPERCOLOR - FIRSTRAINBOWCOLOR); - break; case MT_REDRING: // Make MT_REDRING red by default mobj->color = skincolor_redring; break; @@ -9281,13 +10965,6 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) if (nummaprings >= 0) nummaprings++; break; - case MT_METALSONIC_RACE: - mobj->skin = &skins[5]; - /* FALLTHRU */ - case MT_METALSONIC_BATTLE: - mobj->color = skins[5].prefcolor; - sc = 5; - break; case MT_CORK: mobj->flags2 |= MF2_SUPERFIRE; break; diff --git a/src/p_setup.c b/src/p_setup.c index 9d772e5f7..9c2cbdd58 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7355,25 +7355,12 @@ static void P_ConvertBinaryThingTypes(void) } break; } - case 1101: //Torch - case 1119: //Candle - case 1120: //Candle pricket - mapthings[i].args[0] = !!(mapthings[i].options & MTF_EXTRA); - break; case 1121: //Flame holder if (mapthings[i].options & MTF_OBJECTSPECIAL) mapthings[i].args[0] |= TMFH_NOFLAME; if (mapthings[i].options & MTF_EXTRA) mapthings[i].args[0] |= TMFH_CORONA; break; - case 1127: //Spectator EggRobo - if (mapthings[i].options & MTF_AMBUSH) - mapthings[i].args[0] = TMED_LEFT; - else if (mapthings[i].options & MTF_OBJECTSPECIAL) - mapthings[i].args[0] = TMED_RIGHT; - else - mapthings[i].args[0] = TMED_NONE; - break; case 1200: //Tumbleweed (Big) case 1201: //Tumbleweed (Small) mapthings[i].args[0] = !!(mapthings[i].options & MTF_AMBUSH); diff --git a/src/p_spec.h b/src/p_spec.h index d86765a2b..7bfab7f13 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -111,13 +111,6 @@ typedef enum TMP_CLASSIC = 3, } textmappushabletype_t; -typedef enum -{ - TMED_NONE = 0, - TMED_RIGHT = 1, - TMED_LEFT = 2, -} textmapeggrobodirection_t; - typedef enum { TMMR_SAME = 0,