diff --git a/src/deh_tables.c b/src/deh_tables.c index 5bbe64f9f..3023b70eb 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -4732,6 +4732,7 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_BALLSWITCH_PAD_ACTIVE", "S_SPIKEDTARGET", + "S_SPIKEDLENS", "S_BLENDEYE_MAIN", "S_BLENDEYE_MAIN_LAUNCHED", diff --git a/src/info.c b/src/info.c index f3c13bd3b..1f4d31045 100644 --- a/src/info.c +++ b/src/info.c @@ -5540,6 +5540,7 @@ state_t states[NUMSTATES] = {SPR_SA2S, FF_FLOORSPRITE|FF_ANIMATE|FF_GLOBALANIM|1, -1, {NULL}, 1, 1, S_NULL}, // S_BALLSWITCH_PAD_ACTIVE {SPR_STRG, FF_ADD|FF_FLOORSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_SPIKEDTARGET, + {SPR_STRG, FF_ADD, -1, {NULL}, 0, 0, S_NULL}, // S_SPIKEDLENS, {SPR_BLEA, 0, -1, {NULL}, 0, 0, S_NULL}, // S_BLENDEYE_MAIN, {SPR_BLEA, 1, -1, {NULL}, 0, 0, S_NULL}, // S_BLENDEYE_MAIN_LAUNCHED, @@ -31239,7 +31240,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 42*FRACUNIT, // radius 56*FRACUNIT, // height 0, // display offset - 0, // mass + DMG_NORMAL, // mass 0, // damage sfx_None, // activesound MF_BOSS|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPTHING|MF_SOLID, // flags diff --git a/src/info.h b/src/info.h index c754a86cb..b57082a5f 100644 --- a/src/info.h +++ b/src/info.h @@ -5966,6 +5966,7 @@ typedef enum state S_BALLSWITCH_PAD_ACTIVE, S_SPIKEDTARGET, + S_SPIKEDLENS, S_BLENDEYE_MAIN, S_BLENDEYE_MAIN_LAUNCHED, diff --git a/src/k_boss.h b/src/k_boss.h index 698e86c44..0fbd2857c 100644 --- a/src/k_boss.h +++ b/src/k_boss.h @@ -138,7 +138,19 @@ fixed_t *VS_RandomPointOnArena(mobj_t *arena, fixed_t radiussubtract); // Blend Eye +void VS_BlendEye_Init(mobj_t *mobj); +void VS_BlendEye_Thinker(mobj_t *mobj); +boolean VS_BlendEye_Touched(mobj_t *special, mobj_t *toucher); +void VS_BlendEye_Damage(mobj_t *mobj, mobj_t *inflictor, mobj_t *source, INT32 damage); +void VS_BlendEye_Death(mobj_t *mobj); + +boolean VS_BlendEye_Eye_Thinker(mobj_t *mobj); +void VS_BlendEye_Glass_Death(mobj_t *mobj); +void VS_BlendEye_Eggbeater_Touched(mobj_t *t1, mobj_t *t2); +void VS_BlendEye_Generator_DeadThinker(mobj_t *mobj); + boolean VS_PuyoTouched(mobj_t *special, mobj_t *toucher); +void VS_PuyoThinker(mobj_t *mobj); void VS_PuyoDeath(mobj_t *mobj); #ifdef __cplusplus diff --git a/src/objects/versus/arena.c b/src/objects/versus/arena.c index fdc65161c..bc0fbc4df 100644 --- a/src/objects/versus/arena.c +++ b/src/objects/versus/arena.c @@ -65,8 +65,17 @@ fixed_t *VS_PredictAroundArena(mobj_t *arena, mobj_t *movingobject, fixed_t magn { static fixed_t dest[2] = {0, 0}; - if (P_MobjWasRemoved(arena) || P_MobjWasRemoved(movingobject)) + if (P_MobjWasRemoved(arena)) + { + CONS_Alert(CONS_ERROR, "VS_PredictAroundArena: No Versus arena provided."); return dest; + } + + if (P_MobjWasRemoved(movingobject)) + { + CONS_Alert(CONS_ERROR, "VS_PredictAroundArena: No moving object provided."); + return dest; + } fixed_t radius = FixedHypot(movingobject->x - arena->x, movingobject->y - arena->y); angle_t basedir = R_PointToAngle2(arena->x, arena->y, movingobject->x, movingobject->y); diff --git a/src/objects/versus/blendeye.c b/src/objects/versus/blendeye.c index f5ea72aae..e56f516c4 100644 --- a/src/objects/versus/blendeye.c +++ b/src/objects/versus/blendeye.c @@ -12,10 +12,1159 @@ #include "../../p_local.h" #include "../../m_random.h" +#include "../../k_kart.h" +#include "../../k_hitlag.h" +#include "../../k_battle.h" #include "../../k_boss.h" +#include "../../k_respawn.h" #include "../../s_sound.h" +#include "../../g_game.h" #include "../../r_main.h" // R_PointToAngle2, R_PointToDist2 +#define PUYOARCMULTIPLIER (3) + +typedef enum +{ + BLENDEYE_RESTART = 0, + BLENDEYE_LOADAMMO, + BLENDEYE_WAITAMMO, + BLENDEYE_BLENDING, + BLENDEYE_EXPLODING, + BLENDEYE_PINCH_THROWN, + BLENDEYE_PINCH_BOBBING, + BLENDEYE_PINCH_WHISKING, + BLENDEYE_PINCH_DRILLING, + BLENDEYE_PINCH_EXPLODING, + BLENDEYE_PINCH_BLOWNUP, +} blendeye_phase_e; + +#define EYECOLOR \ + (encoremode \ + ? SKINCOLOR_JAWZ \ + : SKINCOLOR_KETCHUP \ + ) + +#define K_SpawnBlendEyeExplosion(mo) \ + K_SpawnMineExplosion( \ + mo, \ + EYECOLOR, \ + 0 \ + ) + +/// - MAIN BODY - /// + +void VS_BlendEye_Init(mobj_t *mobj) +{ + UINT8 i; + mobj_t *prev, *newmo; + angle_t ang = 0; + + // necessary preamble + P_SetScale(mobj, (mobj->destscale = mobj->scale*2)); + + // spawn the glass + prev = mobj; + for (i = 0; i < 8; i++) + { + ang = mobj->angle + FixedAngle(i*360*FRACUNIT/8); + newmo = P_SpawnMobjFromMobj(mobj, + P_ReturnThrustX(mobj, ang, 39*FRACUNIT), + P_ReturnThrustY(mobj, ang, 39*FRACUNIT), + 44*FRACUNIT, + MT_BLENDEYE_GLASS); + newmo->angle = ang - ANGLE_90; + P_SetTarget(&prev->hnext, newmo); + prev = newmo; + } + + // spawn the generators + prev = mobj; + ang = mobj->angle + ANGLE_45; + UINT8 numgenerators = (encoremode ? 4 : 3); + for (i = 0; i < numgenerators; i++) + { + newmo = P_SpawnMobjFromMobj(mobj, + P_ReturnThrustX(mobj, ang, 62*FRACUNIT), + P_ReturnThrustY(mobj, ang, 62*FRACUNIT), + 0, + MT_BLENDEYE_GENERATOR); + P_SetScale(newmo, newmo->destscale = (3*newmo->destscale)/4); + newmo->momz = -512*FRACUNIT; // cba to hardcode a diff. z coord + newmo->angle = ang - ANGLE_90; + P_SetTarget(&newmo->tracer, mobj); + P_SetTarget(&prev->hprev, newmo); + prev = newmo; + if (i == 1 && numgenerators == 3) + ang -= ANGLE_135; + else + ang -= ANGLE_90; + } + + // spawn the eye... + { + P_SetTarget(&mobj->tracer, P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_BLENDEYE_EYE)); + P_SetTarget(&mobj->tracer->target, mobj); + mobj->tracer->angle = mobj->angle - ANGLE_90; + // ...and shield! + prev = mobj->tracer; + for (i = 0; i < 8; i++) + { + ang = FixedAngle(i*360*FRACUNIT/8); + newmo = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_BLENDEYE_SHIELD); + newmo->angle = mobj->angle + ang-ANGLE_90; + newmo->movedir = ang; + if (i == 0) + P_SetMobjState(newmo, S_INVISIBLE); + else if (i == 1) + P_SetMobjState(newmo, S_BLENDEYE_SHIELD_R); + else if (i == 7) + P_SetMobjState(newmo, S_BLENDEYE_SHIELD_L); + newmo->cvmem = i; + P_SetTarget(&prev->hnext, newmo); + prev = newmo; + } + } + + // initialise other important stuff + if (encoremode) + mobj->health++; + mobj->tracer->health = mobj->health; + mobj->health += numgenerators; + mobj->tracer->cvmem = mobj->health; + + mobj->movedir = BLENDEYE_RESTART; + mobj->movecount = TICRATE; + + mobj->reactiontime = mobj->z + (3*mobj->height)/2; + + { + const char *enemyname, *subtitle; + if (!encoremode) + { + enemyname = "Blend Eye"; + subtitle = "Here to serve"; + } + else + { + enemyname = "Mean Blend Eye"; + subtitle = "Promoted to assistant manager"; + } + + K_InitBossHealthBar(enemyname, subtitle, 0, mobj->tracer->health*(FRACUNIT/mobj->health), mobj->tracer->cvmem); + K_DeclareWeakspot(mobj, SPOT_NONE, EYECOLOR, true); + } +} + +static mobj_t *sourceofmurder; + +static inline BlockItReturn_t PIT_MurderPuyos(mobj_t *thing) +{ + if (thing->type != MT_BLENDEYE_PUYO) + return BMIT_CONTINUE; // not a puyo + + if (thing->z < sourceofmurder->z) + return BMIT_CONTINUE; // too low + + P_InstaThrust(thing, R_PointToAngle2(sourceofmurder->x, sourceofmurder->y, thing->x, thing->y), (thing->z - sourceofmurder->z)/4); + thing->momz = (thing->z - sourceofmurder->z)/16; + P_KillMobj(thing, sourceofmurder, sourceofmurder, DMG_NORMAL); + + return BMIT_CONTINUE; // Indiscriminate +} + +static void VS_BlendEye_MurderPuyos(mobj_t *mobj) +{ + INT32 bx, by, xl, xh, yl, yh; + + sourceofmurder = mobj; + + yh = (unsigned)(mobj->y + (mobj->radius + MAXRADIUS) - bmaporgy)>>MAPBLOCKSHIFT; + yl = (unsigned)(mobj->y - (mobj->radius + MAXRADIUS) - bmaporgy)>>MAPBLOCKSHIFT; + xh = (unsigned)(mobj->x + (mobj->radius + MAXRADIUS) - bmaporgx)>>MAPBLOCKSHIFT; + xl = (unsigned)(mobj->x - (mobj->radius + MAXRADIUS) - bmaporgx)>>MAPBLOCKSHIFT; + + BMBOUNDFIX (xl, xh, yl, yh); + + for (by = yl; by <= yh; by++) + for (bx = xl; bx <= xh; bx++) + P_BlockThingsIterator(bx, by, PIT_MurderPuyos); +} + +void VS_BlendEye_Thinker(mobj_t *mobj) +{ + boolean deathwatch = false; // may be useful for later + + // Init. + { + if (!mobj->tracer) + { + CONS_Alert(CONS_ERROR, "VS_BlendEye_Thinker: Reached thinker but init failed"); + P_RemoveMobj(mobj); + return; + } + + if (!mobj->tracer->tracer) + { + P_SetTarget(&mobj->tracer->tracer, VS_GetArena(mobj->thing_args[0])); + if (!mobj->tracer->tracer) + { + P_RemoveMobj(mobj); + return; + } + } + } + + mobj->flags2 &= ~MF2_FRET; + + // Targeting. + if (P_MobjWasRemoved(mobj->target) + || !mobj->target->player + || mobj->target->player->respawn.state != RESPAWNST_NONE + || mobj->target->health <= 0) + { + P_SupermanLook4Players(mobj); + deathwatch = (P_MobjWasRemoved(mobj->target) + || !mobj->target->player + || mobj->target->player->respawn.state != RESPAWNST_NONE + || mobj->target->health <= 0); + } + + INT32 i; + fixed_t tiltstrength; + + // phases and attacks + switch (mobj->movedir) + { + case BLENDEYE_LOADAMMO: + { + // SPAWN YOUR AMMO + if ((mobj->movecount % 5) == 0) + { + i = (mobj->movecount/5); + angle_t ang = mobj->tracer->cusval + FixedAngle(i*360*FRACUNIT/3); + fixed_t h = mobj->z + mobj->height - 4*mapobjectscale; + fixed_t dist = mobj->cvmem - 2*FRACUNIT; + if (i <= 3) + ang += ANGLE_180; + + mobj_t *ammo = P_SpawnMobjFromMobj(mobj, + P_ReturnThrustX(mobj, ang, dist), + P_ReturnThrustY(mobj, ang, dist), + 256*FRACUNIT, + MT_BLENDEYE_PUYO); + P_SetScale(ammo, (ammo->destscale *= 2)); + + if (i <= 3) + h += ammo->height; + else + ammo->cusval = (i-3)*TICRATE; + + P_SetObjectMomZ(ammo, -10*FRACUNIT, false); + ammo->angle = ang; + ammo->cvmem = dist; + ammo->movefactor = (TICRATE/2) + P_RandomKey(PR_MOVINGTARGET, TICRATE/8); + P_SetTarget(&ammo->tracer, mobj); + ammo->extravalue1 = h; + ammo->flags2 |= MF2_STRONGBOX; + } + + if ((--mobj->movecount) == 0) + { + mobj->movedir = BLENDEYE_WAITAMMO; + mobj->movecount = TICRATE; + } + + break; + } + case BLENDEYE_WAITAMMO: + { + // WAIT #1 + if (deathwatch) + ; + else if ((--mobj->movecount) == 0) + { + mobj->movedir = BLENDEYE_BLENDING; + mobj->movecount = 4*TICRATE; + mobj->extravalue1 = mobj->reactiontime; + mobj->flags2 |= MF2_AMBUSH; + S_StartSound(NULL, sfx_befan1); + if (mobj->tracer) + P_SetMobjState(mobj->tracer, mobj->tracer->info->raisestate); + } + + break; + } + case BLENDEYE_BLENDING: + { + // BLENDING IN ACTION + if (!S_SoundPlaying(NULL, sfx_befan1) && !S_SoundPlaying(NULL, sfx_befan2)) + S_StartSound(NULL, sfx_befan2); + + tiltstrength = 5; + if (leveltime & 1) + tiltstrength = -tiltstrength; + + mobj->rollangle = FixedAngle(tiltstrength*FRACUNIT); + + if ((--mobj->movecount) == 0) + { + mobj->rollangle = 0; + mobj->movedir = 0; + mobj->movecount = 3*TICRATE/2; + mobj->flags2 &= ~MF2_AMBUSH; + //S_StopSoundByID(NULL, sfx_befan2); + //S_StartSound(NULL, sfx_s3k85); + if (mobj->tracer) + P_SetMobjState(mobj->tracer, mobj->tracer->info->spawnstate); + } + + break; + } + case BLENDEYE_EXPLODING: + { + // BLOWS UP!? + if ((--mobj->movecount) == 0) + { + // PINCH TRANSITION BEGIN ! + mobj->rollangle = 0; + mobj->movedir = BLENDEYE_PINCH_THROWN; + mobj_t *ref = mobj->hnext; + mobj_t *refnext = NULL; + while (ref) + { + refnext = ref->hnext; + P_KillMobj(ref, mobj, mobj, DMG_NORMAL); + ref = refnext; + } + S_StartSound(NULL, sfx_shattr); + if (mobj->tracer) + { + boolean lastturned = false; + ref = mobj->tracer->hnext; + P_SetTarget(&mobj->tracer->hnext, NULL); + while (ref) + { + if (ref->cvmem == 0) + ref->flags |= MF_NOCLIPTHING; + else if (ref->cvmem == 1) + P_SetMobjState(ref, S_BLENDEYE_SHIELD_BUSTED_R); + else if (ref->cvmem == 7) + P_SetMobjState(ref, S_BLENDEYE_SHIELD_BUSTED_L); + else if (lastturned == true) + lastturned = false; + else if (P_RandomChance(PR_DECORATION, FRACUNIT/2)) + { + P_SetMobjState(ref, S_BLENDEYE_SHIELD_BUSTED); + if (P_RandomChance(PR_DECORATION, FRACUNIT/2)) + { + ref->frame++; + } + lastturned = true; + } + ref = ref->hnext; + } + mobj->tracer->renderflags |= RF_DONTDRAW; + mobj->flags &= ~MF_NOGRAVITY; + mobj->flags2 |= MF2_FRET; + + mobj_t *boomo = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_FZEROBOOM); + boomo->extravalue1 = 2; + P_SetScale(boomo, boomo->scale/3); + boomo->destscale = mapobjectscale; + K_SpawnBlendEyeExplosion(boomo); + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + P_FlashPal(&players[i], PAL_WHITE, 1); + } + S_StartSound(NULL, sfx_s3k4e); + } + + VS_BlendEye_MurderPuyos(mobj); + + mobj->momz = 12*mobj->scale; + P_SetMobjState(mobj, mobj->info->raisestate); + mobj->extravalue1 = mobj->reactiontime; + if (!deathwatch) + { + mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y); + } + mobj->movecount = P_RandomChance(PR_MOVINGTARGET, FRACUNIT/2) ? 1 : -1; + P_InstaThrust(mobj, mobj->angle + mobj->movecount*ANGLE_90, 4*mobj->scale); + } + else if ((mobj->movecount % 3) == 0) + { + // SPAWN A MINI-POP + angle_t rot = FixedAngle(P_RandomKey(PR_EXPLOSION, 360)*FRACUNIT); + fixed_t offs = P_RandomKey(PR_EXPLOSION, (mobj->info->height - mobjinfo[MT_SONIC3KBOSSEXPLODE].height)/FRACUNIT)*FRACUNIT; + P_SpawnMobjFromMobj(mobj, + P_ReturnThrustX(mobj, rot, 56*FRACUNIT), + P_ReturnThrustY(mobj, rot, 56*FRACUNIT), + offs, + MT_SONIC3KBOSSEXPLODE); + S_StartSound(NULL, sfx_s3kb4); + mobj_t *ref = mobj->hnext; + while (ref) + { + P_SetMobjState(ref, ref->info->painstate); + ref = ref->hnext; + } + + tiltstrength = 5; + if (((mobj->movecount/2) % 3) == 0) + tiltstrength = -tiltstrength; + + mobj->rollangle = FixedAngle(tiltstrength*FRACUNIT); + } + else + { + mobj_t *ref = mobj->hnext; + while (ref) + { + P_SetMobjState(ref, ref->info->spawnstate); + ref = ref->hnext; + } + mobj->rollangle = 0; + } + + return; + } + case BLENDEYE_PINCH_THROWN: + { + // WOOOAAAAH, MY HEAD'S SPINNING + mobj->rollangle += (mobj->movecount*ANG20); + if (mobj->momz < 0 && mobj->z < mobj->extravalue1) + { + mobj->movedir = BLENDEYE_PINCH_BOBBING; + mobj->flags |= MF_NOGRAVITY|MF_SHOOTABLE; + mobj->flags &= ~MF_NOCLIPTHING; + mobj->flags2 &= ~MF2_FRET; + mobj->momx = mobj->momy = mobj->momz = 0; + mobj->rollangle = 0; + mobj->movecount = -TICRATE/2; + P_SetMobjState(mobj, mobj->info->spawnstate); + if (mobj->tracer) + mobj->tracer->renderflags &= ~RF_DONTDRAW; + + if (!deathwatch) + { + mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y); + } + + P_SetTarget(&mobj->hprev, P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_OVERLAY)); + P_SetTarget(&mobj->hprev->target, mobj); + mobj->hprev->flags |= MF_DONTENCOREMAP; + mobj->hprev->threshold |= OV_DONTROLL; + P_SetMobjState(mobj->hprev, S_BLENDEYE_FLAME); + S_StartSound(NULL, sfx_s3k4f); + mobj->cvmem = mobj->info->radius + 4*FRACUNIT; + } + + //return; + break; + } + case BLENDEYE_PINCH_BOBBING: + case BLENDEYE_PINCH_WHISKING: + { + // BOBBING UP AND DOWN + fixed_t bobsize = 6*mobj->scale; + fixed_t bob = mobj->movecount; + bob = -P_ReturnThrustY(mobj, FixedAngle(bob*10*FRACUNIT), bobsize); + mobj->movecount++; + if (mobj->movedir == BLENDEYE_PINCH_WHISKING) + { + if (encoremode) // RUN FOR LONGER + mobj->extravalue1 -= (mobj->scale/4); + else + mobj->extravalue1 -= (2*mobj->scale/7); + + if (!S_SoundPlaying(NULL, sfx_befan1) && !S_SoundPlaying(NULL, sfx_befan2)) + S_StartSound(NULL, sfx_befan2); + + tiltstrength = 5; + if (leveltime & 1) + tiltstrength = -tiltstrength; + mobj->rollangle = FixedAngle(tiltstrength*FRACUNIT); + + if (deathwatch) + { + mobj->movedir = BLENDEYE_PINCH_BOBBING; + mobj->movecount = 0; + mobj->rollangle = 0; + mobj->extravalue1 = mobj->reactiontime; + mobj->flags2 &= ~MF2_AMBUSH; + if (mobj->tracer && mobj->tracer->hprev) + { + P_SetMobjState(mobj->tracer->hprev, S_BLENDEYE_EGGBEATER); + P_SetMobjState(mobj->tracer, mobj->tracer->info->spawnstate); + } + } + else if (mobj->tracer && mobj->tracer->tracer && mobj->tracer->hprev) + { + // MOVE TOWARDS TARGET + angle_t mompoint = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y); // replace first two with mobj->tracer->hnext->x and ->y for alt behaviour + fixed_t mommag = FixedHypot(mobj->target->x - mobj->x, mobj->target->y - mobj->y); + fixed_t bestspeed = 5*mobj->scale; + fixed_t accel = (mobj->scale/2); + boolean forcegoaround = false; + angle_t test = R_PointToAngle2(mobj->tracer->tracer->x, mobj->tracer->tracer->y, mobj->x, mobj->y) + - R_PointToAngle2(mobj->tracer->tracer->x, mobj->tracer->tracer->y, mobj->target->x, mobj->target->y); + if (test >= ANGLE_180) + test = InvAngle(test); + + if (test > ANGLE_90) + { + bestspeed += (AngleFixed(test-ANGLE_90)/(encoremode ? 20 : 30)); + forcegoaround = true; + } + + if (mobj->tracer->hprev->extravalue1 + accel < bestspeed) + mobj->tracer->hprev->extravalue1 += accel; + else + mobj->tracer->hprev->extravalue1 = bestspeed; + + if (mommag > mobj->tracer->hprev->extravalue1) + mommag = mobj->tracer->hprev->extravalue1; + + if (mommag) + { + fixed_t *predict = VS_PredictAroundArena(mobj->tracer->tracer, mobj, mommag, mompoint, mobj->radius, forcegoaround, FRACUNIT); + P_MoveOrigin(mobj, predict[0], predict[1], mobj->z); + } + + // Slight bonus lifting hazard for those who get too close! + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + + if (players[i].spectator) + continue; + + if (P_MobjWasRemoved(players[i].mo)) + continue; + + if (players[i].mo->health == 0 || players[i].mo->hitlag) + continue; + + if (players[i].mo->z > mobj->z) + continue; + + if (FixedHypot(players[i].mo->x - mobj->x, players[i].mo->y - mobj->y) > (mobj->radius + 4*mapobjectscale)) + continue; + + players[i].mo->momz += 4*mapobjectscale; + } + } + } + else if (!mobj->tracer->hprev && mobj->movecount == TICRATE/3) + { + // SPAWN THE EGGBEATER + P_SetTarget(&mobj->tracer->hprev, + P_SpawnMobjFromMobj(mobj, 0, 0, + -mobjinfo[MT_BLENDEYE_EGGBEATER].height, + MT_BLENDEYE_EGGBEATER + ) + ); + P_SetTarget(&mobj->tracer->hprev->tracer, mobj); + mobj->tracer->hprev->extravalue1 = 0; + S_StartSound(NULL, sfx_cdfm39); + + // Gainax sharpness + mobj_t *shwing = P_SpawnMobjFromMobj(mobj->tracer->hprev, 0, 0, 0, MT_OVERLAY); + shwing->threshold = OV_DONTSCREENOFFSET; + P_SetTarget(&shwing->target, mobj->tracer->hprev); + P_SetMobjState(shwing, S_EMERALDFLARE1); + shwing->fuse = (TICRATE - mobj->movecount); + shwing->spritexoffset = -24*FRACUNIT; + shwing->spriteyoffset = 14*FRACUNIT; + + // Dr. Robontik's Rage Stagers ?! + mobj_t *angery = P_SpawnMobjFromMobj(mobj->tracer, + P_ReturnThrustX(mobj, mobj->angle, FRACUNIT), + P_ReturnThrustY(mobj, mobj->angle, FRACUNIT), + 15*FRACUNIT, + MT_GHOST + ); + + P_SetMobjState(angery, S_SPIKEDLENS); + P_SetScale(angery, angery->scale/2); + angery->height = 1; // prevent z correction in P_MobjScaleThink + angery->destscale *= 4; + angery->fuse = TICRATE/7; // 5 + angery->scalespeed = ((angery->destscale - angery->scale)/angery->fuse); + angery->colorized = true; + angery->color = EYECOLOR; + angery->flags2 |= MF2_BOSSNOTRAP; // Quicker fadeout + } + else if (mobj->movecount >= TICRATE && !deathwatch) + { + mobj->movedir = BLENDEYE_PINCH_WHISKING; + if (mobj->tracer) + { + P_SetMobjState(mobj->tracer, mobj->tracer->info->raisestate); + if (mobj->tracer->hprev) + P_SetMobjState(mobj->tracer->hprev, S_BLENDEYE_EGGBEATER_SPIN); + + mobj->tracer->cusval = (encoremode ? 6 : 5); + } + S_StartSound(NULL, sfx_befan1); + mobj->flags2 |= MF2_AMBUSH; + } + if (mobj->z < mobj->extravalue1 - bobsize) + mobj->z = (2*mobj->z + mobj->extravalue1 + bob)/3; + else + mobj->z = mobj->extravalue1 + bob; + + if (P_MobjWasRemoved(mobj->hprev)) + ; + else if (mobj->z <= mobj->extravalue1) + { + if ((mobj->hprev->state-states) != S_BLENDEYE_FLAME) + P_SetMobjState(mobj->hprev, S_BLENDEYE_FLAME); + + if (!S_SoundPlaying(mobj->hprev, sfx_s3k7f)) + S_StartSound(mobj->hprev, sfx_s3k7f); + } + else + { + if ((mobj->hprev->state-states) != S_INVISIBLE) + P_SetMobjState(mobj->hprev, S_INVISIBLE); + } + + if (mobj->tracer && mobj->tracer->hprev) + { + if ((mobj->extravalue1 + bob) <= (mobj->floorz + mobj->tracer->hprev->height)) + { + mobj->movedir = BLENDEYE_PINCH_DRILLING; + mobj->movecount = 0; + S_StopSoundByID(NULL, sfx_befan2); + S_StartSound(NULL, sfx_s3k85); + mobj->flags2 &= ~MF2_AMBUSH; + if (mobj->hprev) + P_SetMobjState(mobj->hprev, S_INVISIBLE); + mobj->tracer->hprev->flags &= ~MF_PAIN; + } + else if (deathwatch) + ; + else if (mobj->tracer->cusval && !(mobj->movecount % (TICRATE))) + { + angle_t ang = FixedAngle(P_RandomKey(PR_MOVINGTARGET, 360)*FRACUNIT); + fixed_t dist = 2*FRACUNIT; + mobj_t *ammo = P_SpawnMobjFromMobj(mobj, + P_ReturnThrustX(mobj, ang, dist), + P_ReturnThrustY(mobj, ang, dist), + 0, + MT_BLENDEYE_PUYO); + P_SetOrigin(ammo, ammo->x, ammo->y, ammo->floorz+1); + P_SetScale(ammo, (ammo->destscale *= 2)); + ammo->angle = ang; + ammo->cvmem = dist; + ammo->movefactor = (TICRATE/2) + P_RandomKey(PR_MOVINGTARGET, TICRATE/8); + P_SetTarget(&ammo->tracer, mobj); + ammo->extravalue1 = mobj->floorz; + if (mobj->tracer->cusval & 1) + ammo->flags2 |= MF2_BOSSNOTRAP; + mobj->tracer->cusval--; + } + } + + break; + } + case BLENDEYE_PINCH_DRILLING: + { + // ACCIDENTIALLY DRILLING INTO THE GROUND + if (mobj->z > mobj->floorz+FRACUNIT) + { + tiltstrength = 10; + if (leveltime & 1) + tiltstrength = -tiltstrength; + mobj->rollangle = FixedAngle(tiltstrength*FRACUNIT); + mobj->angle -= (ANG10+ANG20); + mobj->z = (2*mobj->z + mobj->floorz)/3; + mobj->tracer->cusval = FixedAngle(P_RandomKey(PR_MOVINGTARGET, 360)*FRACUNIT); + if (!mobj->tracer->hprev) + ; + else for (i = 0; i < 6; i++) + { + angle_t angle = mobj->tracer->cusval + FixedAngle(i*360*FRACUNIT/12); + fixed_t momx = P_ReturnThrustX(mobj, angle, mobj->tracer->hprev->radius); + fixed_t momy = P_ReturnThrustY(mobj, angle, mobj->tracer->hprev->radius); + mobj_t *dustmo = P_SpawnMobjFromMobj( + mobj->tracer->hprev, + 0, 0, 0, + (encoremode + ? MT_BLENDEYE_PUYO_DUST + : MT_BLENDEYE_PUYO_DUST_COFFEE + ) + ); + P_SetScale(dustmo, (dustmo->destscale *= 2)); + dustmo->momx = momx/(3-(i & 1)); + dustmo->momy = momy/(3-(i & 1)); + dustmo->momz = (mobj->z - mobj->floorz)/(2+(i & 1)); + } + } + else + { + mobj->z = mobj->floorz; + if ((mobj->tracer->hprev->state-states) != S_BLENDEYE_EGGBEATER) + { + mobj->rollangle = 0; + P_SetMobjState(mobj->tracer->hprev, S_BLENDEYE_EGGBEATER); + P_SetMobjState(mobj->tracer, mobj->tracer->info->spawnstate); + } + + if ((mobj->movecount == TICRATE/2) && !deathwatch) + K_DeclareWeakspot(mobj, SPOT_WEAK, SKINCOLOR_EMERALD, false); + + if ((mobj->movecount > 2*TICRATE) && (leveltime & 1)) + { + if (mobj->hprev && (mobj->hprev->state-states) != S_BLENDEYE_FLAME) + { + P_SetMobjState(mobj->hprev, S_BLENDEYE_FLAME); + S_StartSound(NULL, sfx_s3k4f); + } + mobj->z += FRACUNIT; + if (!S_SoundPlaying(NULL, sfx_s3k69)) + S_StartSound(NULL, sfx_s3k69); + } + + if ((++mobj->movecount) == 3*TICRATE) + { + mobj->movedir = BLENDEYE_PINCH_BOBBING; + mobj->movecount = 0; + mobj->rollangle = 0; + mobj->extravalue1 = mobj->reactiontime; + S_StopSoundByID(NULL, sfx_s3k69); // it's like poetry + S_StartSound(NULL, sfx_mbs53); + if (mobj->tracer) + { + P_SetMobjState(mobj->tracer, mobj->tracer->info->spawnstate); + if (mobj->tracer->hprev && !(mobj->tracer->hprev->flags & MF_PAIN)) + { + mobj->tracer->hprev->flags |= MF_PAIN; + mobj->tracer->hprev->extravalue1 = 0; + if (mobj->tracer->hprev->z != mobj->floorz) + ; + else for (i = 0; i < 6; i++) + { + angle_t angle = mobj->tracer->cusval + FixedAngle(i*360*FRACUNIT/12); + fixed_t momx = P_ReturnThrustX(mobj, angle, mobj->tracer->hprev->radius); + fixed_t momy = P_ReturnThrustY(mobj, angle, mobj->tracer->hprev->radius); + mobj_t *dustmo = P_SpawnMobjFromMobj( + mobj->tracer->hprev, + 0, 0, 0, + (encoremode + ? MT_BLENDEYE_PUYO_DUST + : MT_BLENDEYE_PUYO_DUST_COFFEE + ) + ); + P_SetScale(dustmo, (dustmo->destscale *= 2)); + dustmo->momx = momx/(3-(i & 1)); + dustmo->momy = momy/(3-(i & 1)); + dustmo->momz = (mobj->tracer->hprev->radius)/(2+(i & 1)); + } + } + } + } + } + + break; + } + case BLENDEYE_PINCH_EXPLODING: + { + // SHE'S DYING... + if ((--mobj->movecount) == 0) + { + // ALL DONE + { + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + if (players[i].spectator) + continue; + if (players[i].pflags & PF_NOCONTEST) + continue; + + P_DoAllPlayersExit(0, true); + break; + } + } + + mobj->movedir = BLENDEYE_PINCH_BLOWNUP; + + fixed_t offs = P_RandomChance(PR_EXPLOSION, FRACUNIT/2) + ? ANGLE_135 + : InvAngle(ANGLE_135); + P_InstaThrust(mobj, mobj->angle + offs, 8*mapobjectscale); + mobj->momz = 16*mapobjectscale; + mobj->flags |= MF_NOCLIPHEIGHT; + mobj->flags &= ~MF_NOGRAVITY; + mobj->z++; + + P_InstaThrust(mobj->tracer, mobj->angle, 8*mapobjectscale); + mobj->tracer->momz = 12*mapobjectscale; + mobj->tracer->flags |= MF_NOCLIPHEIGHT; + mobj->tracer->flags &= ~MF_NOGRAVITY; + mobj->tracer->z++; + + if (mobj->tracer->hprev) + { + P_InstaThrust(mobj->tracer->hprev, mobj->angle - offs, 8*mapobjectscale); + mobj->tracer->hprev->momz = 6*mapobjectscale; + mobj->tracer->hprev->flags |= MF_NOCLIPHEIGHT; + mobj->tracer->hprev->flags &= ~MF_NOGRAVITY; + mobj->tracer->hprev->z++; + P_SetScale(mobj->hprev, mapobjectscale/4); + K_SpawnBlendEyeExplosion(mobj->hprev); + S_StartSound(NULL, sfx_s3k4e); + } + + P_RemoveMobj(mobj->hprev); + P_SetTarget(&mobj->hprev, NULL); + } + else if ((mobj->movecount % 2) == 0) + { + // SPAWN A MINI-POP (2) + angle_t rot = FixedAngle(P_RandomKey(PR_EXPLOSION, 360)*FRACUNIT); + fixed_t offs = P_RandomKey(PR_EXPLOSION, (mobj->info->height - mobjinfo[MT_SONIC3KBOSSEXPLODE].height)/FRACUNIT)*FRACUNIT; + P_SpawnMobjFromMobj(mobj, + P_ReturnThrustX(mobj, rot, mobj->info->radius), + P_ReturnThrustY(mobj, rot, mobj->info->radius), + offs, + MT_SONIC3KBOSSEXPLODE); + S_StartSound(NULL, sfx_s3kb4); + tiltstrength = 5; + if (((mobj->movecount/2) % 2) == 0) + tiltstrength = -tiltstrength; + + mobj->rollangle = FixedAngle(tiltstrength*FRACUNIT); + } + else + mobj->rollangle = 0; + + return; + } + case BLENDEYE_PINCH_BLOWNUP: + { + // CURTAINS FOR YOU + if (mobj->z != mobj->floorz) + mobj->rollangle += ANG20; + else + mobj->rollangle = ANGLE_180; + + if (mobj->tracer->hprev->z != mobj->floorz) + mobj->tracer->hprev->rollangle += ANG20; + else + mobj->tracer->hprev->rollangle = (mobj->tracer->hprev->rollangle >= ANGLE_180) + ? ANGLE_135 + : InvAngle(ANGLE_135); + + mobj->renderflags ^= RF_DONTDRAW; + mobj->tracer->renderflags ^= RF_DONTDRAW; + if (mobj->tracer->hprev) + mobj->tracer->hprev->renderflags ^= RF_DONTDRAW; + + return; + } + default: + { + // RESTART CYCLE + if (deathwatch) + ; + else if ((--mobj->movecount) == 0) + { + mobj->movedir = BLENDEYE_LOADAMMO; + mobj->movecount = 6*5; + mobj->tracer->cusval = FixedAngle(P_RandomKey(PR_MOVINGTARGET, 360)*FRACUNIT); + mobj->cvmem = 24*FRACUNIT; + } + else if (mobj->movecount == TICRATE) + { + mobj_t *ref = mobj->hprev; + while (ref) + { + if (ref->health > 0) + K_DeclareWeakspot(ref, SPOT_WEAK, SKINCOLOR_EMERALD, false); + ref = ref->hprev; + } + } + + break; + } + } + + // Look around. + if (mobj->target) // !deathwatch + { + fixed_t x, y; + INT32 angledelta = AngleDeltaSigned(R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y), mobj->angle)/4; + const INT32 maxdelta = (ANGLE_45/2); + + if (angledelta > maxdelta) + angledelta = maxdelta; + else if (angledelta < -maxdelta) + angledelta = -maxdelta; + + // Before angle update, move the shield. + if (mobj->tracer && mobj->tracer->hnext && mobj->movedir < BLENDEYE_EXPLODING) + { + mobj_t *ref = mobj->tracer->hnext; + while (ref) + { + x = mobj->x + P_ReturnThrustX(mobj, mobj->tracer->movedir + ref->movedir, 40*mobj->scale); + y = mobj->y + P_ReturnThrustY(mobj, mobj->tracer->movedir + ref->movedir, 40*mobj->scale); + P_MoveOrigin(ref, x, y, mobj->z); + ref->angle = mobj->tracer->movedir + ref->movedir - ANGLE_90; + ref = ref->hnext; + } + mobj->tracer->movedir = mobj->angle; + } + + if (mobj->movedir != BLENDEYE_PINCH_DRILLING || mobj->movecount > TICRATE/2) + mobj->angle += angledelta; + + // And after, move the eye. + if (mobj->tracer) + { + x = mobj->x + P_ReturnThrustX(mobj, mobj->angle, 38*mobj->scale); + y = mobj->y + P_ReturnThrustY(mobj, mobj->angle, 38*mobj->scale); + P_MoveOrigin(mobj->tracer, x, y, mobj->z + (mobj->height - mobj->tracer->height)/2); + mobj->tracer->angle = mobj->angle - ANGLE_90; + if (mobj->tracer->hprev) + { + P_MoveOrigin(mobj->tracer->hprev, mobj->x, mobj->y, max(mobj->floorz, mobj->z - (mobj->tracer->hprev->height))); + } + } + } +} + +boolean VS_BlendEye_Touched(mobj_t *special, mobj_t *toucher) +{ + if (toucher->hitlag > 0) + return false; + + fixed_t thrust = FixedHypot(toucher->momx, toucher->momy); + angle_t ang = R_PointToAngle2(special->x, special->y, toucher->x - toucher->momx, toucher->y - toucher->momy); + P_InstaThrust(toucher, ang + ANGLE_90, P_ReturnThrustX(toucher, ang + ANGLE_90, thrust)); + thrust = P_ReturnThrustX(toucher, ang, thrust); + if (thrust < 4*mapobjectscale) + thrust = (4*mapobjectscale); + P_Thrust(toucher, ang, thrust); + return true; +} + +void VS_BlendEye_Damage(mobj_t *mobj, mobj_t *inflictor, mobj_t *source, INT32 damage) +{ + (void)inflictor; + + S_StartSound(NULL, mobj->info->painsound); + K_UpdateBossHealthBar((mobj->health - damage)*(FRACUNIT/mobj->tracer->cvmem), 8); + if (mobj->tracer) + { + if (mobj->movedir == BLENDEYE_PINCH_BOBBING) + { + if (mobj->hprev) + P_SetMobjState(mobj->hprev, S_INVISIBLE); + } + else if (mobj->movedir == BLENDEYE_PINCH_WHISKING) + { + mobj->movedir = BLENDEYE_PINCH_BOBBING; + mobj->movecount = 0; + mobj->rollangle = 0; + mobj->flags2 &= ~MF2_AMBUSH; + mobj->extravalue1 = mobj->reactiontime; + if (mobj->hprev && (mobj->hprev->state-states) != S_BLENDEYE_FLAME) + { + P_SetMobjState(mobj->hprev, S_BLENDEYE_FLAME); + S_StartSound(NULL, sfx_s3k4f); + } + if (mobj->tracer->hprev) + { + P_SetMobjState(mobj->tracer->hprev, S_BLENDEYE_EGGBEATER); + mobj->tracer->hprev->flags |= MF_PAIN; + P_SetMobjState(mobj->tracer, mobj->tracer->info->spawnstate); + } + } + else if (mobj->movedir == BLENDEYE_PINCH_DRILLING) + { + if (mobj->tracer->hprev) + mobj->tracer->hprev->flags &= ~MF_PAIN; + + mobj->movecount = (2*TICRATE)+(TICRATE/2); + P_SetMobjState(mobj->tracer, mobj->tracer->info->spawnstate); + } + else if ((mobj->health - damage) == mobj->tracer->health) //pre-pinch + { + mobj->flags2 &= ~MF2_AMBUSH; + mobj->rollangle = 0; + mobj->movedir = BLENDEYE_EXPLODING; + mobj->movecount = 2*TICRATE; + P_SetMobjState(mobj->tracer, mobj->tracer->info->spawnstate); + } + } + + // make the glass stress + mobj_t *ref = mobj->hnext; + while (ref) + { + P_SetMobjState(ref, ref->info->painstate); + ref = ref->hnext; + } + + if (source && source->player) + { + P_AddPlayerScore(source->player, 1); + K_SpawnBattlePoints(source->player, NULL, 1); + } +} + +void VS_BlendEye_Death(mobj_t *mobj) +{ + mobj->flags2 &= ~MF2_AMBUSH; + mobj->rollangle = 0; + mobj->movedir = BLENDEYE_PINCH_EXPLODING; + mobj->movecount = 3*TICRATE/2; + + if (mobj->tracer) + { + P_SetMobjState(mobj->tracer, mobj->tracer->info->spawnstate); + mobj->tracer->flags |= MF_NOCLIP|MF_NOCLIPTHING; + + if (mobj->hprev) + mobj->tracer->hprev->flags |= MF_NOCLIP|MF_NOCLIPTHING; + + if (mobj->tracer->hprev) + { + P_SetMobjState(mobj->tracer->hprev, S_BLENDEYE_EGGBEATER); + } + } + + if (mobj->hprev) + P_SetMobjState(mobj->hprev, S_INVISIBLE); + + mobj->flags |= MF_NOCLIP|MF_NOCLIPTHING; + + K_AddHitLag(mobj, 6, true); +} + +/// - AUXILLIARY OBJECTS - /// + +boolean VS_BlendEye_Eye_Thinker(mobj_t *mobj) +{ + if (P_MobjWasRemoved(mobj->target)) + { + P_RemoveMobj(mobj); + return false; + } + + if (mobj->target->hitlag) + { + P_InstaThrust(mobj, mobj->angle, 25*mobj->scale); + K_AddHitLag(mobj, mobj->target->hitlag, (mobj->target->eflags & MFE_DAMAGEHITLAG) == MFE_DAMAGEHITLAG); + return false; + } + + if (!(mobj->flags & MF_NOCLIPHEIGHT)) + { + mobj->momx = mobj->momy = 0; + } + + return true; +} + +void VS_BlendEye_Glass_Death(mobj_t *mobj) +{ + UINT8 i = 0; + fixed_t thrust = 30*mapobjectscale; + for (i = 0; i <= 8; i++) + { + fixed_t along = P_RandomRange(PR_DECORATION, -mobj->radius/FRACUNIT, mobj->radius/FRACUNIT)*FRACUNIT; + fixed_t up = (mobj->height - FixedMul(mobjinfo[MT_BATTLEBUMPER_DEBRIS].height, mobj->scale)); + if (up > 0) + up = P_RandomKey(PR_DECORATION, mobj->height/FRACUNIT)*FRACUNIT; + mobj_t *glassmo = P_SpawnMobj( + mobj->x + P_ReturnThrustX(mobj, mobj->angle, along), + mobj->y + P_ReturnThrustY(mobj, mobj->angle, along), + mobj->z + up, + MT_BATTLEBUMPER_DEBRIS); + glassmo->color = SKINCOLOR_WHITE; + glassmo->renderflags |= RF_ADD; + angle_t vang = R_PointToAngle2(0, mobj->z, 39*FRACUNIT, glassmo->z); + P_InstaThrust(glassmo, mobj->angle + ANGLE_90, P_ReturnThrustX(glassmo, vang, thrust)); + glassmo->momz = P_ReturnThrustX(glassmo, vang, thrust); + } +} + +void VS_BlendEye_Eggbeater_Touched(mobj_t *t1, mobj_t *t2) +{ + if (t2->hitlag || (t1->health <= 0 || t2->health <= 0)) + return; + + if (t1->z == t1->floorz) + return; + + INT32 minextravalue1 = -(P_IsObjectOnGround(t2) ? 5 : 15)*t1->scale; + if (t1->extravalue1 > minextravalue1) + t1->extravalue1 = minextravalue1; + + if ((t2->player->tumbleBounces > 0) && (t2->momz > 0)) + return; + + fixed_t thrust = FixedHypot(t2->momx, t2->momy); + if (thrust < 20*mapobjectscale) + thrust = 20*mapobjectscale; + + P_Thrust( + t2, + R_PointToAngle2(t1->x, t1->y, t2->x - t2->momx, t2->y - t2->momy), + thrust + ); +} + +void VS_BlendEye_Generator_DeadThinker(mobj_t *mobj) +{ + if (leveltime & 1) + return; + + if (P_RandomChance(PR_DECORATION, FRACUNIT/4)) + return; + + angle_t twist = mobj->angle; + fixed_t dist = 18*FRACUNIT; + fixed_t z = 26*FRACUNIT; + fixed_t fb = 6*FRACUNIT; + + if (P_RandomChance(PR_DECORATION, FRACUNIT/2)) + { + z = 42*FRACUNIT; + dist = 28*FRACUNIT; + twist += ANGLE_180; + fb = -6*FRACUNIT; + } + + if ((mobj->state-states) != S_BLENDEYE_GENERATOR_BUSTED_R) + twist += ANGLE_180; + + mobj_t *dust = P_SpawnMobjFromMobj( + mobj, + P_ReturnThrustX(mobj, twist, dist) + P_ReturnThrustY(mobj, twist, fb), + P_ReturnThrustY(mobj, twist, dist) + P_ReturnThrustX(mobj, twist, fb), + z, + encoremode + ? MT_BLENDEYE_PUYO_DUST + : MT_BLENDEYE_PUYO_DUST_COFFEE + ); + dust->angle = FixedAngle(P_RandomKey(PR_DECORATION, 360)*FRACUNIT); + P_InstaThrust(dust, dust->angle, 4*mapobjectscale); + dust->momz = 8*mapobjectscale; +} + +/// - PUYO HAZARDS - /// + boolean VS_PuyoTouched(mobj_t *special, mobj_t *toucher) { if (!special->health || !toucher->health) @@ -24,7 +1173,7 @@ boolean VS_PuyoTouched(mobj_t *special, mobj_t *toucher) if (special->state-states < S_BLENDEYE_PUYO) return false; // too small - P_DamageMobj(toucher, special, special->tracer, 1, DMG_NORMAL); + P_DamageMobj(toucher, special, special, 1, DMG_NORMAL); special->momx = 0; special->momy = 0; @@ -33,6 +1182,410 @@ boolean VS_PuyoTouched(mobj_t *special, mobj_t *toucher) return true; } +// Basically a duplication of A_Boss5Jump. I'm not adding A_ action calls to header files. + +static void VS_PuyoJump(mobj_t *shot, mobj_t *target) +{ + fixed_t v; // Velocity to jump at + fixed_t a1, a2, aToUse; // Velocity squared + fixed_t g; // Gravity + fixed_t x; // Horizontal difference + INT32 x_int; // x! But in integer form! + fixed_t y; // Vertical difference (yes that's normally z in SRB2 shut up) + INT32 y_int; // y! But in integer form! + INT32 intHypotenuse; // x^2 + y^2. Frequently overflows fixed point, hence why we need integers proper. + fixed_t fixedHypotenuse; // However, we can work around that and still get a fixed-point number. + angle_t theta; // Angle of attack + // INT32 locvar1 = var1; + // INT32 locvar2 = var2; + + if (!shot || !target) + return; // Don't even bother if we've got nothing to aim at. + + // Scale with map + g = FixedMul(gravity, mapobjectscale); + + // Look up distance between shot and its target + x = FixedHypot(target->x - shot->x, target->y - shot->y); + // Look up height difference between shot and its target + y = target->z - shot->z; + + // Get x^2 + y^2. Have to do it in a roundabout manner, because this overflows fixed_t way too easily otherwise. + x_int = x>>FRACBITS; + y_int = y>>FRACBITS; + intHypotenuse = (x_int*x_int) + (y_int*y_int); + fixedHypotenuse = FixedSqrt(intHypotenuse) *256; + + // a = g(y+/-sqrt(x^2+y^2)). a1 can be +, a2 can be -. + a1 = FixedMul(g,y+fixedHypotenuse); + a2 = FixedMul(g,y-fixedHypotenuse); + + // Determine which one isn't actually an imaginary number (or the smaller of the two, if both are real), and use that for v. + if (a1 < 0 || a2 < 0) + { + if (a1 < 0 && a2 < 0) + { + //Somehow, v^2 is negative in both cases. v is therefore imaginary and something is horribly wrong. Abort! + return; + } + // Just find which one's NOT negative, and use that + aToUse = max(a1,a2); + } + else + { + // Both are positive; use whichever's smaller so it can decay faster + aToUse = min(a1,a2); + } + v = FixedSqrt(aToUse); + // Okay, so we know the velocity. Let's actually find theta. + // We can cut the "+/- sqrt" part out entirely, since v was calculated specifically for it to equal zero. So: + //theta = tantoangle[FixedDiv(aToUse,FixedMul(g,x)) >> DBITS]; + theta = tantoangle[SlopeDiv(aToUse,FixedMul(g,x))]; + + // Okay, complicated math done. Let's make this object jump already. + + shot->angle = R_PointToAngle2(shot->x, shot->y, target->x, target->y); + + if (shot->eflags & MFE_VERTICALFLIP) + shot->z--; + else + shot->z++; + + // Horizontal axes first. First parameter is initial horizontal impulse, second is to correct its angle. + fixedHypotenuse = FixedMul(v, FINECOSINE(theta >> ANGLETOFINESHIFT)); // variable reuse + shot->momx = FixedMul(fixedHypotenuse, FINECOSINE(shot->angle >> ANGLETOFINESHIFT)); + shot->momy = FixedMul(fixedHypotenuse, FINESINE(shot->angle >> ANGLETOFINESHIFT)); + // Then the vertical axis. No angle-correction needed here. + shot->momz = FixedMul(v, FINESINE(theta >> ANGLETOFINESHIFT)); + // I hope that's all that's needed, ugh +} + +static mobj_t *VS_PredictedPuyoShot(mobj_t *arena, mobj_t *source, mobj_t *shot, mobj_t *target, mobj_t *reticule) +{ + fixed_t x = 0, y = 0, z = INT32_MIN; + fixed_t momx = 0, momy = 0, mommag = 0; + angle_t mompoint = 0; + + if (P_MobjWasRemoved(arena)) + { + CONS_Alert(CONS_ERROR, "VS_PredictedPuyoShot: No Versus arena provided."); + return NULL; + } + + // handle coords + if (P_MobjWasRemoved(target) == false) // aimed leading shot + { + x = target->x; + y = target->y; + z = target->floorz; + + if (P_MobjWasRemoved(shot) == false + && (target->player == NULL || P_PlayerInPain(target->player) == false)) + { + // a beyond mediocre guess - intentionally not DIRECTLY tied to maxarena because + // this was determined via trial and error and shouldn't change + fixed_t possiblearcmultiplier = ( + ((TICRATE/4)*FRACUNIT) + + (3*TICRATE* + FixedDiv( + FixedHypot(x - shot->x, y - shot->y), + 2*280*FRACUNIT + ) + ) + ); + + momx = FixedMul(target->momx, possiblearcmultiplier); + momy = FixedMul(target->momy, possiblearcmultiplier); + + mommag = FixedHypot(momx, momy); + mompoint = R_PointToAngle2(0, 0, momx, momy); + + fixed_t *predict = VS_PredictAroundArena( + arena, + target, + mommag, + mompoint, + shot->radius, + false, + 0 + ); + + momx = predict[0] - x; + momy = predict[1] - y; + } + } + else // unaimed random shot + { + fixed_t *predict = VS_RandomPointOnArena( + arena, + (P_MobjWasRemoved(shot) ? 0 : shot->radius) + ); + + x = predict[0]; + y = predict[1]; + } + + // handle reticule + fixed_t tempz = ((z == INT32_MIN && !P_MobjWasRemoved(source)) ? source->z : z); + + if (P_MobjWasRemoved(reticule) == false) + { + P_SetOrigin(reticule, x, y, tempz); + } + else + { + reticule = P_SpawnMobj(x, y, tempz, MT_SPIKEDTARGET); + reticule->renderflags |= RF_NOSPLATBILLBOARD; + reticule->destscale = 2*reticule->destscale; + reticule->radius = FixedMul(mobjinfo[MT_PLAYER].radius, mapobjectscale)/2; + //P_SetScale(reticule, reticule->destscale); -- intentionally not here, for animation + + if (P_MobjWasRemoved(shot) == false) + P_SetTarget(&reticule->target, shot); + else if (P_MobjWasRemoved(source) == false) + P_SetTarget(&reticule->target, source); + } + + if (z == INT32_MIN) + { + z = reticule->old_z = reticule->floorz; + } + + reticule->z = z + FixedMul(mobjinfo[MT_PLAYER].height, mapobjectscale); + + // handle reticle movement + if (momx != 0 || momy != 0) + { + UINT8 attempt = 0; + boolean lastsuccess = true; + // tries to immediately jump to the final location. + // if that fails, tries to xeno's paradox it: + // halve the distance and try TWO steps at this magnitude + while (attempt < 5) + { + if (P_TryMove(reticule, reticule->x + momx, reticule->y + momy, false, NULL)) + { + if (lastsuccess) + break; + + lastsuccess = true; + continue; + } + lastsuccess = false; + momx /= 2; + momy /= 2; + attempt++; + } + } + + // handle launching + if (P_MobjWasRemoved(shot) == false) + { + P_SetTarget(&shot->tracer, reticule); + + VS_PuyoJump(shot, reticule); + + P_Thrust(shot, shot->angle, 3*mapobjectscale); // needs this little extra kick just to make it, for some reason + shot->momz *= PUYOARCMULTIPLIER; + shot->flags |= (MF_NOGRAVITY|MF_SHOOTABLE); + if (P_RandomChance(PR_DECORATION, FRACUNIT/2)) + shot->flags2 |= MF2_BOSSNOTRAP; + } + + // handle finalisation + P_SetOrigin(reticule, reticule->x, reticule->y, z); + return reticule; +} + +static mobj_t *referencepuyo = NULL; +static mobj_t *bestpuyo = NULL; +fixed_t bestpuyodist = INT32_MAX; + +static inline BlockItReturn_t PIT_GetBestLaunchablePuyo(mobj_t *thing) +{ + if (thing->type != MT_BLENDEYE_PUYO) + return BMIT_CONTINUE; // not a puyo + + if (thing->cusval > 0) + return BMIT_CONTINUE; // a bottom puyo + + if (P_MobjWasRemoved(thing->tracer)) + return BMIT_CONTINUE; // a launched puyo + + fixed_t dist = FixedHypot(referencepuyo->x - thing->x, referencepuyo->y - thing->y); + + if (dist >= bestpuyodist) + return BMIT_CONTINUE; // too far away + + bestpuyo = thing; + bestpuyodist = dist; + + return BMIT_CONTINUE; // Still could be a better selection +} + +static void VS_FindBestPuyo(mobj_t *reference, mobj_t *source) +{ + INT32 bx, by, xl, xh, yl, yh; + + referencepuyo = reference; + bestpuyo = NULL; + bestpuyodist = INT32_MAX; + + yh = (unsigned)(source->y + (source->radius + MAXRADIUS) - bmaporgy)>>MAPBLOCKSHIFT; + yl = (unsigned)(source->y - (source->radius + MAXRADIUS) - bmaporgy)>>MAPBLOCKSHIFT; + xh = (unsigned)(source->x + (source->radius + MAXRADIUS) - bmaporgx)>>MAPBLOCKSHIFT; + xl = (unsigned)(source->x - (source->radius + MAXRADIUS) - bmaporgx)>>MAPBLOCKSHIFT; + + BMBOUNDFIX (xl, xh, yl, yh); + + for (by = yl; by <= yh; by++) + for (bx = xl; bx <= xh; bx++) + P_BlockThingsIterator(bx, by, PIT_GetBestLaunchablePuyo); +} + +void VS_PuyoThinker(mobj_t *mobj) +{ + if (!mobj->health) + return; + + if (mobj->z <= mobj->floorz) + { + if (mobj->flags & MF_NOCLIPHEIGHT) + { + mobj->z = mobj->floorz; + mobj->momx = mobj->momy = mobj->momz = 0; + mobj->flags &= ~(MF_NOCLIPHEIGHT); + //P_KillMobj(mobj); + P_SetMobjState(mobj, S_BLENDEYE_PUYO_LAND_1); + mobj->fuse = 20*TICRATE; + } + return; + } + + if (P_MobjWasRemoved(mobj->tracer) || (mobj->tracer->type == MT_SPIKEDTARGET)) // being thrown... + { + if (mobj->flags2 & MF2_BOSSNOTRAP) + mobj->rollangle -= ANG10; + else + mobj->rollangle += ANG10; + + mobj->momz -= (PUYOARCMULTIPLIER*FixedMul(gravity, mapobjectscale)); + } + else if (mobj->tracer->flags2 & MF2_AMBUSH) // being whirled + { + if (mobj->movefactor) + { + if (mobj->movefactor > 2 || ((mobj->state-states) == S_BLENDEYE_PUYO)) + { + if ((--mobj->movefactor) == 0) + P_SetMobjState(mobj, S_BLENDEYE_PUYO_SHOCK); + } + } + else if (mobj->cusval > 0) // popping and throwing the object on top of them + { + if ((--mobj->cusval) == 1) + { + P_InstaThrust(mobj, mobj->angle + ANGLE_90, 5*FRACUNIT); + mobj->momz = 6*FRACUNIT; + + VS_FindBestPuyo(mobj, mobj->tracer); + if (bestpuyo != NULL) + { + VS_PredictedPuyoShot( + (P_MobjWasRemoved(mobj->tracer->tracer) == false + ? mobj->tracer->tracer->tracer + : NULL + ), + mobj->tracer, + bestpuyo, + mobj->tracer->target, + NULL + ); + } + + P_KillMobj(mobj, mobj->tracer, mobj->tracer, DMG_NORMAL); + return; + } + } + fixed_t x = mobj->tracer->x, y = mobj->tracer->y, z = mobj->z+1; + fixed_t dist = mobj->cvmem; + + if (mobj->extravalue1 == mobj->tracer->extravalue1 || mobj->cvmem == mobj->tracer->cvmem) + ; + else if (mobj->z >= mobj->tracer->extravalue1) + dist = mobj->tracer->cvmem; + else + dist = mobj->cvmem + FixedMul(FixedDiv(mobj->z - mobj->extravalue1, mobj->tracer->extravalue1 - mobj->extravalue1), mobj->tracer->cvmem - mobj->cvmem); + + mobj->angle += FixedAngle(((3*TICRATE/4) - mobj->movefactor)*FRACUNIT); + x += P_ReturnThrustX(mobj, mobj->angle, dist/2); + y += P_ReturnThrustY(mobj, mobj->angle, dist/2); + if (mobj->flags2 & MF2_STRONGBOX) + { + z = mobj->extravalue1+1; + if (((leveltime & 1) == 1) != (mobj->cusval > 0)) + z += mobj->scale; + } + else // being thrown off the side of pinch mode + { + if (mobj->z >= mobj->tracer->extravalue1) + { + mobj_t *target = ((mobj->flags2 & MF2_BOSSNOTRAP) == 0) + ? mobj->tracer->target + : NULL; + mobj->flags2 &= ~MF2_BOSSNOTRAP; + VS_PredictedPuyoShot( + (P_MobjWasRemoved(mobj->tracer->tracer) == false + ? mobj->tracer->tracer->tracer + : NULL + ), + mobj->tracer, + mobj, + target, + NULL + ); + S_StartSound(NULL, sfx_mbs5b); + return; + } + z += mobj->scale; + } + P_MoveOrigin(mobj, x, y, z); + mobj->flags |= MF_NOGRAVITY; + } + else // being inserted into the blender + { + if (mobj->momz < 0) + { + if (mobj->z < mobj->extravalue1) + { + mobj->momz = 0; + mobj->flags |= MF_NOGRAVITY; + mobj->z = mobj->extravalue1; + S_StartSound(NULL, sfx_mbs42); + P_SetMobjState(mobj, S_BLENDEYE_PUYO_LAND_1); + } + } + else if (mobj->z > mobj->extravalue1) + mobj->flags &= ~MF_NOGRAVITY; + + if ( + (mobj->state-states) != S_BLENDEYE_PUYO_SHOCK + && mobj->tracer->movedir == BLENDEYE_EXPLODING + && ( + (mobj->tracer->movecount == 3) + || ( + mobj->tracer->movecount == 4 + && P_RandomChance(PR_DECORATION, FRACUNIT/2) + ) + ) + ) + { + P_SetMobjState(mobj, S_BLENDEYE_PUYO_SHOCK); + } + } + mobj->sprite = mobj->movedir; +} + void VS_PuyoDeath(mobj_t *mobj) { mobjtype_t dusttype = (encoremode ? MT_BLENDEYE_PUYO_DUST : MT_BLENDEYE_PUYO_DUST_COFFEE); diff --git a/src/p_inter.c b/src/p_inter.c index 45b38e215..11c2829ed 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -367,6 +367,12 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) /////ENEMIES & BOSSES!!///////////////////////////////// //////////////////////////////////////////////////////// + if (special->type == MT_BLENDEYE_MAIN) + { + if (!VS_BlendEye_Touched(special, toucher)) + return; + } + P_DamageMobj(toucher, special, special, 1, DMG_NORMAL); return; } @@ -2175,6 +2181,12 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget case MT_BATTLEUFO: Obj_BattleUFODeath(target); break; + case MT_BLENDEYE_MAIN: + VS_BlendEye_Death(target); + break; + case MT_BLENDEYE_GLASS: + VS_BlendEye_Glass_Death(target); + break; case MT_BLENDEYE_PUYO: VS_PuyoDeath(target); break; @@ -2221,7 +2233,6 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget P_InstaThrust(target, R_PointToAngle2(inflictor->x, inflictor->y, target->x, target->y)+ANGLE_90, 16<type == MT_SPIKE && target->info->deathstate != S_NULL) { @@ -2364,6 +2375,27 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget if (!sprflip) target->frame |= FF_VERTICALFLIP; } + else if (target->type == MT_BLENDEYE_GENERATOR && !P_MobjWasRemoved(inflictor)) + { + mobj_t *refobj = (inflictor->type == MT_INSTAWHIP) ? source : inflictor; + angle_t impactangle = R_PointToAngle2(target->x, target->y, refobj->x - refobj->momx, refobj->y - refobj->momy) - (target->angle + ANGLE_90); + + if (P_MobjWasRemoved(target->tracer) == false) + { + target->tracer->flags2 &= ~MF2_FRET; + target->tracer->flags |= MF_SHOOTABLE; + P_DamageMobj(target->tracer, inflictor, source, 1, DMG_NORMAL); + target->tracer->flags &= ~MF_SHOOTABLE; + } + + P_SetMobjState( + target, + ((impactangle < ANGLE_180) + ? target->info->deathstate + : target->info->xdeathstate + ) + ); + } else if (target->player) { P_SetPlayerMobjState(target, target->info->deathstate); @@ -3104,6 +3136,10 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da { return Obj_SpecialUFODamage(target, inflictor, source, damagetype); } + else if (target->type == MT_BLENDEYE_MAIN) + { + VS_BlendEye_Damage(target, inflictor, source, damage); + } if (damagetype & DMG_STEAL) { diff --git a/src/p_map.c b/src/p_map.c index 516b3ac67..79b9ba59e 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -33,6 +33,7 @@ #include "i_system.h" // SRB2kart #include "k_terrain.h" #include "k_objects.h" +#include "k_boss.h" #include "r_splats.h" @@ -658,6 +659,11 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) return BMIT_CONTINUE; // force no collide } + // Blend-Eye internal noclip + if ((thing->type == MT_BLENDEYE_GLASS || thing->type == MT_BLENDEYE_SHIELD || thing->type == MT_BLENDEYE_EGGBEATER) + && (tm.thing->type == MT_BLENDEYE_MAIN || tm.thing->type == MT_BLENDEYE_EYE || tm.thing->type == MT_BLENDEYE_PUYO)) + return BMIT_CONTINUE; + // When solid spikes move, assume they just popped up and teleport things on top of them to hurt. if (tm.thing->type == MT_SPIKE && tm.thing->flags & MF_SOLID) { @@ -783,6 +789,22 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) return BMIT_CONTINUE; } + if (thing->type == MT_BLENDEYE_EGGBEATER + && tm.thing->type == MT_PLAYER && tm.thing->player) + { + if (tm.thing->z > thing->z + thing->height) + { + return BMIT_CONTINUE; // overhead + } + + if (tm.thing->z + tm.thing->height < thing->z) + { + return BMIT_CONTINUE; // underneath + } + + VS_BlendEye_Eggbeater_Touched(thing, tm.thing); + } + if (thing->type == MT_SPB) { if (tm.thing->type != MT_PLAYER diff --git a/src/p_mobj.c b/src/p_mobj.c index 4288d3097..a86eb78df 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -4689,7 +4689,14 @@ boolean P_SupermanLook4Players(mobj_t *actor) if (!stop) return false; - P_SetTarget(&actor->target, playersinthegame[P_RandomKey(PR_UNDEFINED, stop)]->mo); + P_SetTarget( + &actor->target, + players[ + playersinthegame[ + P_RandomKey(PR_MOVINGTARGET, stop) + ] + ].mo + ); return true; } @@ -6857,16 +6864,19 @@ static boolean P_MobjBossThink(mobj_t *mobj) } else if (P_MobjWasRemoved(mobj)) return false; - else - switch (mobj->type) - { - // No SRB2Kart bosses... yet :) + else switch (mobj->type) + { + case MT_BLENDEYE_MAIN: + VS_BlendEye_Thinker(mobj); + break; default: // Generic SOC-made boss if (mobj->flags2 & MF2_SKULLFLY) P_SpawnGhostMobj(mobj); P_GenericBossThinker(mobj); break; - } + } + if (P_MobjWasRemoved(mobj)) + return false; if (mobj->flags2 & MF2_BOSSFLEE) { if (mobj->extravalue1) @@ -7061,6 +7071,11 @@ static boolean P_MobjDeadThink(mobj_t *mobj) } break; } + case MT_BLENDEYE_GENERATOR: + { + VS_BlendEye_Generator_DeadThinker(mobj); + break; + } default: break; } @@ -9976,6 +9991,24 @@ static boolean P_MobjRegularThink(mobj_t *mobj) break; } + case MT_BLENDEYE_EYE: + { + if (!VS_BlendEye_Eye_Thinker(mobj)) + { + return false; + } + break; + } + case MT_BLENDEYE_PUYO: + { + VS_PuyoThinker(mobj); + if (P_MobjWasRemoved(mobj)) + { + return false; + } + break; + } + default: // check mobj against possible water content, before movement code P_MobjCheckWater(mobj); @@ -10093,6 +10126,7 @@ static boolean P_CanFlickerFuse(mobj_t *mobj) case MT_POGOSPRING: case MT_KART_LEFTOVER: case MT_EMERALD: + case MT_BLENDEYE_PUYO: if (mobj->fuse <= TICRATE) { return true; @@ -11458,6 +11492,9 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) case MT_BALLSWITCH_BALL: Obj_BallSwitchInit(mobj); break; + case MT_BLENDEYE_MAIN: + VS_BlendEye_Init(mobj); + break; case MT_BLENDEYE_PUYO: mobj->sprite = mobj->movedir = P_RandomRange(PR_DECORATION, SPR_PUYA, SPR_PUYE); if (encoremode == false)