diff --git a/src/dehacked.c b/src/dehacked.c index be897b952..f5cf9cc44 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -2797,6 +2797,9 @@ static actionpointer_t actionpointers[] = {{A_PterabyteHover}, "A_PTERABYTEHOVER"}, {{A_RolloutSpawn}, "A_ROLLOUTSPAWN"}, {{A_RolloutRock}, "A_ROLLOUTROCK"}, + {{A_DragonbomberSpawn}, "A_DRAGONBOMERSPAWN"}, + {{A_DragonWing}, "A_DRAGONWING"}, + {{A_DragonSegment}, "A_DRAGONSEGMENT"}, {{NULL}, "NONE"}, // This NULL entry must be the last in the list @@ -5057,6 +5060,26 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit "S_PTERABYTE_SWOOPDOWN", "S_PTERABYTE_SWOOPUP", + // Dragonbomber + "S_DRAGONBOMBER", + "S_DRAGONWING1", + "S_DRAGONWING2", + "S_DRAGONWING3", + "S_DRAGONWING4", + "S_DRAGONTAIL_LOADED", + "S_DRAGONTAIL_EMPTY", + "S_DRAGONTAIL_EMPTYLOOP", + "S_DRAGONTAIL_RELOAD", + "S_DRAGONMINE", + "S_DRAGONMINE_LAND1", + "S_DRAGONMINE_LAND2", + "S_DRAGONMINE_SLOWFLASH1", + "S_DRAGONMINE_SLOWFLASH2", + "S_DRAGONMINE_SLOWLOOP", + "S_DRAGONMINE_FASTFLASH1", + "S_DRAGONMINE_FASTFLASH2", + "S_DRAGONMINE_FASTLOOP", + // Boss Explosion "S_BOSSEXPLODE", @@ -7728,6 +7751,10 @@ static const char *const MOBJTYPE_LIST[] = { // array length left dynamic for s "MT_PTERABYTESPAWNER", // Pterabyte spawner "MT_PTERABYTEWAYPOINT", // Pterabyte waypoint "MT_PTERABYTE", // Pterabyte + "MT_DRAGONBOMBER", // Dragonbomber + "MT_DRAGONWING", // Dragonbomber wing + "MT_DRAGONTAIL", // Dragonbomber tail segment + "MT_DRAGONMINE", // Dragonbomber mine // Generic Boss Items "MT_BOSSEXPLODE", diff --git a/src/hardware/hw_light.c b/src/hardware/hw_light.c index 491cb739f..8cd8143eb 100644 --- a/src/hardware/hw_light.c +++ b/src/hardware/hw_light.c @@ -184,6 +184,7 @@ light_t *t_lspr[NUMSPRITES] = &lspr[NOLIGHT], // SPR_CANG &lspr[NOLIGHT], // SPR_PYRE &lspr[NOLIGHT], // SPR_PTER + &lspr[NOLIGHT], // SPR_DRAB // Generic Boos Items &lspr[JETLIGHT_L], // SPR_JETF // Boss jet fumes diff --git a/src/info.c b/src/info.c index b88d02932..b12e7597e 100644 --- a/src/info.c +++ b/src/info.c @@ -72,6 +72,7 @@ char sprnames[NUMSPRITES + 1][5] = "CANG", // Canarivore gas "PYRE", // Pyre Fly "PTER", // Pterabyte + "DRAB", // Dragonbomber // Generic Boss Items "JETF", // Boss jet fumes @@ -1163,6 +1164,26 @@ state_t states[NUMSTATES] = {SPR_PTER, 4, 1, {NULL}, 0, 0, S_PTERABYTE_SWOOPDOWN}, // S_PTERABYTE_SWOOPDOWN {SPR_PTER, 0, 1, {NULL}, 0, 0, S_PTERABYTE_SWOOPUP}, // S_PTERABYTE_SWOOPUP + // Dragonbomber + {SPR_DRAB, 0, -1, {A_DragonbomberSpawn}, 6, 0, S_NULL}, // S_DRAGONBOMBER + {SPR_DRAB, FF_PAPERSPRITE|7, 1, {A_DragonWing}, 0, 0, S_DRAGONWING2}, // S_DRAGONWING1 + {SPR_DRAB, FF_PAPERSPRITE|8, 1, {A_DragonWing}, 0, 0, S_DRAGONWING3}, // S_DRAGONWING2 + {SPR_DRAB, FF_PAPERSPRITE|9, 1, {A_DragonWing}, 0, 0, S_DRAGONWING4}, // S_DRAGONWING3 + {SPR_DRAB, FF_PAPERSPRITE|10, 1, {A_DragonWing}, 0, 0, S_DRAGONWING1}, // S_DRAGONWING4 + {SPR_DRAB, 1, 1, {A_DragonSegment}, 0, 0, S_DRAGONTAIL_LOADED}, // S_DRAGONTAIL_LOADED + {SPR_DRAB, 2, 1, {A_DragonSegment}, 0, 0, S_DRAGONTAIL_EMPTYLOOP}, // S_DRAGONTAIL_EMPTY + {SPR_DRAB, 2, 0, {A_Repeat}, 3*TICRATE, S_DRAGONTAIL_EMPTY, S_DRAGONTAIL_RELOAD}, // S_DRAGONTAIL_EMPTYLOOP + {SPR_DRAB, 1, 0, {A_PlayActiveSound}, 0, 0, S_DRAGONTAIL_LOADED}, // S_DRAGONTAIL_RELOAD + {SPR_DRAB, 3, 1, {A_MinusCheck}, S_DRAGONMINE_LAND1, 0, S_DRAGONMINE}, // S_DRAGONMINE + {SPR_DRAB, 4, 0, {A_PlayActiveSound}, 0, 0, S_DRAGONMINE_LAND2}, // S_DRAGONMINE_LAND1 + {SPR_DRAB, 4, 2, {A_Thrust}, 0, 1, S_DRAGONMINE_SLOWFLASH1}, // S_DRAGONMINE_LAND2 + {SPR_DRAB, 5, 11, {NULL}, 0, 0, S_DRAGONMINE_SLOWFLASH2}, // S_DRAGONMINE_SLOWFLASH1 + {SPR_DRAB, FF_FULLBRIGHT|6, 1, {A_PlayAttackSound}, 0, 0, S_DRAGONMINE_SLOWLOOP}, // S_DRAGONMINE_SLOWFLASH2 + {SPR_DRAB, 5, 0, {A_Repeat}, 4, S_DRAGONMINE_SLOWFLASH1, S_DRAGONMINE_FASTFLASH1}, // S_DRAGONMINE_SLOWLOOP + {SPR_DRAB, 5, 3, {NULL}, 0, 0, S_DRAGONMINE_FASTFLASH2}, // S_DRAGONMINE_FASTFLASH1 + {SPR_DRAB, FF_FULLBRIGHT|6, 1, {A_PlayAttackSound}, 0, 0, S_DRAGONMINE_FASTLOOP}, // S_DRAGONMINE_FASTFLASH2 + {SPR_DRAB, 5, 0, {A_Repeat}, 5, S_DRAGONMINE_FASTFLASH1, S_DEATHSTATE}, // S_DRAGONMINE_FASTLOOP + // Boss Explosion {SPR_BOM2, FF_FULLBRIGHT|FF_ANIMATE, (5*7), {NULL}, 6, 5, S_NULL}, // S_BOSSEXPLODE @@ -5238,6 +5259,114 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, + { // MT_DRAGONBOMBER + 137, // doomednum + S_DRAGONBOMBER, // spawnstate + 1, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 6, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_XPLD_FLICKY, // deathstate + S_NULL, // xdeathstate + sfx_pop, // deathsound + 10*FRACUNIT, // speed + 28*FRACUNIT, // radius + 48*FRACUNIT, // height + 0, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_SHOOTABLE|MF_ENEMY|MF_NOGRAVITY|MF_BOUNCE|MF_RUNSPAWNFUNC, // flags + S_NULL // raisestate + }, + + { // MT_DRAGONWING + -1, // doomednum + S_DRAGONWING1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_XPLD_FLICKY, // deathstate + S_NULL, // xdeathstate + sfx_pop, // deathsound + 0, // speed + 12*FRACUNIT, // radius + 12*FRACUNIT, // height + 0, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOGRAVITY|MF_SCENERY|MF_NOBLOCKMAP|MF_NOCLIP, // flags + S_NULL // raisestate + }, + + { // MT_DRAGONTAIL + -1, // doomednum + S_DRAGONTAIL_LOADED, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + MT_DRAGONMINE, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_XPLD1, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 40*FRACUNIT, // height + 0, // display offset + 100, // mass + 0, // damage + sfx_tink, // activesound + MF_NOGRAVITY|MF_SLIDEME|MF_PAIN, // flags + S_DRAGONTAIL_EMPTY // raisestate + }, + + { // MT_DRAGONMINE + -1, // doomednum + S_DRAGONMINE, // spawnstate + 1, // spawnhealth + S_NULL, // seestate + sfx_s3k76, // seesound + 0, // reactiontime + sfx_s3k89, // attacksound + S_NULL, // painstate + 6, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_TNTBARREL_EXPL1, // deathstate + S_NULL, // xdeathstate + sfx_s3k6e, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 32*FRACUNIT, // height + 0, // display offset + 100, // mass + 0, // damage + sfx_s3k5d, // activesound + MF_SPECIAL|MF_SHOOTABLE, // flags + S_NULL // raisestate + }, + { // MT_BOSSEXPLODE -1, // doomednum S_BOSSEXPLODE, // spawnstate diff --git a/src/info.h b/src/info.h index fd9c45a1a..b933245c4 100644 --- a/src/info.h +++ b/src/info.h @@ -282,6 +282,9 @@ void A_SpawnPterabytes(); void A_PterabyteHover(); void A_RolloutSpawn(); void A_RolloutRock(); +void A_DragonbomberSpawn(); +void A_DragonWing(); +void A_DragonSegment(); // ratio of states to sprites to mobj types is roughly 6 : 1 : 1 #define NUMMOBJFREESLOTS 512 @@ -334,6 +337,7 @@ typedef enum sprite SPR_CANG, // Canarivore gas SPR_PYRE, // Pyre Fly SPR_PTER, // Pterabyte + SPR_DRAB, // Dragonbomber // Generic Boss Items SPR_JETF, // Boss jet fumes @@ -1355,6 +1359,26 @@ typedef enum state S_PTERABYTE_SWOOPDOWN, S_PTERABYTE_SWOOPUP, + // Dragonbomber + S_DRAGONBOMBER, + S_DRAGONWING1, + S_DRAGONWING2, + S_DRAGONWING3, + S_DRAGONWING4, + S_DRAGONTAIL_LOADED, + S_DRAGONTAIL_EMPTY, + S_DRAGONTAIL_EMPTYLOOP, + S_DRAGONTAIL_RELOAD, + S_DRAGONMINE, + S_DRAGONMINE_LAND1, + S_DRAGONMINE_LAND2, + S_DRAGONMINE_SLOWFLASH1, + S_DRAGONMINE_SLOWFLASH2, + S_DRAGONMINE_SLOWLOOP, + S_DRAGONMINE_FASTFLASH1, + S_DRAGONMINE_FASTFLASH2, + S_DRAGONMINE_FASTLOOP, + // Boss Explosion S_BOSSEXPLODE, @@ -4049,6 +4073,10 @@ typedef enum mobj_type MT_PTERABYTESPAWNER, // Pterabyte spawner MT_PTERABYTEWAYPOINT, // Pterabyte waypoint MT_PTERABYTE, // Pterabyte + MT_DRAGONBOMBER, // Dragonbomber + MT_DRAGONWING, // Dragonbomber wing + MT_DRAGONTAIL, // Dragonbomber tail segment + MT_DRAGONMINE, // Dragonbomber mine // Generic Boss Items MT_BOSSEXPLODE, diff --git a/src/p_enemy.c b/src/p_enemy.c index 7aba64e42..a77c2a5ec 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -312,6 +312,9 @@ void A_SpawnPterabytes(mobj_t *actor); void A_PterabyteHover(mobj_t *actor); void A_RolloutSpawn(mobj_t *actor); void A_RolloutRock(mobj_t *actor); +void A_DragonbomberSpawn(mobj_t *actor); +void A_DragonWing(mobj_t *actor); +void A_DragonSegment(mobj_t *actor); //for p_enemy.c @@ -14593,3 +14596,103 @@ void A_RolloutRock(mobj_t *actor) actor->flags2 ^= MF2_DONTDRAW; } + +// Function: A_DragonbomberSpawn +// +// Description: Spawns the body parts for Dragonbomber +// +// var1 = Tail segments to spawn +// var2 = unused +// +void A_DragonbomberSpawn(mobj_t *actor) +{ + INT32 locvar1 = var1; + INT32 locvar2 = var2; + UINT8 i; + mobj_t *mo = actor; + + #ifdef HAVE_BLUA + if (LUA_CallAction("A_DragonbomberSpawn", actor)) + return; + #endif + + for (i = 0; i < var1; i++) // spawn tail segments + { + mobj_t *segment; + fixed_t x, y; + x = P_ReturnThrustX(mo, mo->angle, -mo->radius << 1); + y = P_ReturnThrustY(mo, mo->angle, -mo->radius << 1); + segment = P_SpawnMobjFromMobj(mo, x, y, 0, MT_DRAGONTAIL); + P_SetTarget(&segment->target, mo); + P_SetTarget(&mo->tracer, segment); + segment->angle = mo->angle; + mo = segment; + } + for (i = 0; i < 2; i++) // spawn wings + { + mo = P_SpawnMobjFromMobj(actor, 0, 0, 0, MT_DRAGONWING); + P_SetTarget(&mo->target, actor); + mo->movedir = ANGLE_90 + i * ANGLE_180; + } +} + +// Function: A_DragonWing +// +// Description: Moves actor such that it is placed away from its target at a distance equal to the target's radius in the direction of its target's angle. +// The actor's movedir can be used to offset the angle. +// +// var1 = unused +// var2 = unused +// +void A_DragonWing(mobj_t *actor) +{ + INT32 locvar1 = var1; + INT32 locvar2 = var2; + mobj_t *target = actor->target; + fixed_t x, y; + + #ifdef HAVE_BLUA + if (LUA_CallAction("A_DragonWing", actor)) + return; + #endif + + if (target == NULL || !target->health) + { + P_RemoveMobj(actor); + return; + } + actor->angle = target->angle + actor->movedir; + x = target->x + P_ReturnThrustX(actor, actor->angle, -target->radius); + y = target->y + P_ReturnThrustY(actor, actor->angle, -target->radius); + P_TeleportMove(actor, x, y, target->z); +} + +// Function: A_DragonSegment +// +// Description: Moves actor such that it is placed away from its target at an absolute distance equal to the sum of the two mobjs' radii. +// +// var1 = unused +// var2 = unused +// +void A_DragonSegment(mobj_t *actor) +{ + INT32 locvar1 = var1; + INT32 locvar2 = var2; + mobj_t *target = actor->target; + fixed_t dist = P_AproxDistance(P_AproxDistance(actor->x - target->x, actor->y - target->y), actor->z - target->z); + fixed_t radius = actor->radius + target->radius; + angle_t hangle = R_PointToAngle2(target->x, target->y, actor->x, actor->y); + angle_t zangle = R_PointToAngle2(0, target->z, dist, actor->z); + fixed_t hdist = P_ReturnThrustX(target, zangle, radius); + fixed_t xdist = P_ReturnThrustX(target, hangle, hdist); + fixed_t ydist = P_ReturnThrustY(target, hangle, hdist); + fixed_t zdist = P_ReturnThrustY(target, zangle, radius); + + #ifdef HAVE_BLUA + if (LUA_CallAction("A_DragonSegment", actor)) + return; + #endif + + actor->angle = hangle; + P_TeleportMove(actor, target->x + xdist, target->y + ydist, target->z + zdist); +} \ No newline at end of file diff --git a/src/p_inter.c b/src/p_inter.c index cc7d702f3..f68cb90f0 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -2678,6 +2678,17 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget target->flags = (target->flags|MF_NOCLIPHEIGHT) & ~MF_NOGRAVITY; break; + case MT_DRAGONBOMBER: + { + mobj_t *segment = target; + while (segment->tracer != NULL) + { + P_KillMobj(segment->tracer, NULL, NULL, 0); + segment = segment->tracer; + } + break; + } + case MT_EGGMOBILE3: { mobj_t *mo2; diff --git a/src/p_map.c b/src/p_map.c index 4055f7082..132fe6f6d 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -1024,7 +1024,6 @@ static boolean PIT_CheckThing(mobj_t *thing) if ((thing->flags & MF_PUSHABLE) // not carrying a player && (tmthing->player->powers[pw_carry] == CR_NONE) // player is not already riding something && ((tmthing->eflags & MFE_VERTICALFLIP) == (thing->eflags & MFE_VERTICALFLIP)) - && (P_AproxDistance(thing->x - tmthing->x, thing->y - tmthing->y) < (thing->radius)) && (P_MobjFlip(tmthing)*tmthing->momz <= 0) && ((!(tmthing->eflags & MFE_VERTICALFLIP) && abs(thing->z + thing->height - tmthing->z) < (thing->height>>2)) || (tmthing->eflags & MFE_VERTICALFLIP && abs(tmthing->z + tmthing->height - thing->z) < (thing->height>>2)))) diff --git a/src/p_mobj.c b/src/p_mobj.c index d6ec977c2..b9770809e 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -9632,6 +9632,81 @@ void P_MobjThinker(mobj_t *mobj) } break; } + case MT_DRAGONBOMBER: + { +#define DRAGONTURNSPEED ANG2 + mobj->movecount = (mobj->movecount + 9) % 360; + P_SetObjectMomZ(mobj, 4*FINESINE(((mobj->movecount*ANG1) >> ANGLETOFINESHIFT) & FINEMASK), false); + if (mobj->threshold > 0) // are we dropping mines? + { + mobj->threshold--; + if (mobj->threshold == 0) // if the timer hits 0, look for a mine to drop! + { + mobj_t *segment = mobj; + while (segment->tracer != NULL && !P_MobjWasRemoved(segment->tracer) && segment->tracer->state == &states[segment->tracer->info->spawnstate]) + { + segment = segment->tracer; + } + if (segment != mobj) // found an unactivated segment? + { + mobj_t *mine = P_SpawnMobjFromMobj(segment, 0, 0, 0, segment->info->painchance); + mine->angle = segment->angle; + P_InstaThrust(mine, mobj->angle, P_AproxDistance(mobj->momx, mobj->momy) >> 1); + P_SetObjectMomZ(mine, -2*FRACUNIT, true); + S_StartSound(mine, mine->info->seesound); + P_SetMobjState(segment, segment->info->raisestate); + mobj->threshold = mobj->info->painchance; + } + } + } + if (mobj->target != NULL) // Are we chasing a player? + { + fixed_t dist = P_AproxDistance(mobj->x - mobj->target->x, mobj->y - mobj->target->y); + if (dist > 2000 * mobj->scale) // Not anymore! + P_SetTarget(&mobj->target, NULL); + else + { + fixed_t vspeed = FixedMul(mobj->info->speed >> 3, mobj->scale); + fixed_t z = mobj->target->z + (mobj->height >> 1) + (mobj->flags & MFE_VERTICALFLIP ? -128*mobj->scale : 128*mobj->scale + mobj->target->height); + angle_t diff = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y) - mobj->angle; + if (diff > ANGLE_180) + mobj->angle -= DRAGONTURNSPEED; + else + mobj->angle += DRAGONTURNSPEED; + if (!mobj->threshold && dist < 512 * mobj->scale) // Close enough to drop bombs + { + mobj->threshold = mobj->info->painchance; + } + mobj->momz += max(min(z - mobj->z, vspeed), -vspeed); + } + } + else // Can we find a player to chase? + { + if (mobj->tracer == NULL || mobj->tracer->state != &states[mobj->tracer->info->spawnstate] + || !P_LookForPlayers(mobj, true, false, 2000*mobj->scale)) // if not, circle around the spawnpoint + { + if (!mobj->spawnpoint) // unless we don't have one, in which case uhhh just circle around wherever we currently are I guess?? + mobj->angle += DRAGONTURNSPEED; + else + { + fixed_t vspeed = FixedMul(mobj->info->speed >> 3, mobj->scale); + fixed_t x = mobj->spawnpoint->x << FRACBITS; + fixed_t y = mobj->spawnpoint->y << FRACBITS; + fixed_t z = mobj->spawnpoint->z << FRACBITS; + fixed_t dist = P_AproxDistance(mobj->x - x, mobj->y - y); + angle_t diff = R_PointToAngle2(mobj->x, mobj->y, x, y) - mobj->angle; + if (diff > ANGLE_180) + mobj->angle -= DRAGONTURNSPEED; + else + mobj->angle += DRAGONTURNSPEED; + mobj->momz += max(min(z - mobj->z, vspeed), -vspeed); + } + } + } + P_InstaThrust(mobj, mobj->angle, FixedMul(mobj->info->speed, mobj->scale)); +#undef DRAGONTURNSPEED + } + break; case MT_SPINFIRE: if (mobj->flags & MF_NOGRAVITY) { diff --git a/src/p_user.c b/src/p_user.c index ddb333d6d..ef03c34ce 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -4425,7 +4425,9 @@ void P_DoJump(player_t *player, boolean soundandstate) } else if (player->powers[pw_carry] == CR_ROLLOUT) { - player->mo->momz = 9*FRACUNIT + player->mo->tracer->momz; + player->mo->momz = 9*FRACUNIT; + if (P_MobjFlip(player->mo->tracer)*player->mo->tracer->momz > 0) + player->mo->momz += player->mo->tracer->momz; player->powers[pw_carry] = CR_NONE; player->mo->tracer->flags |= MF_PUSHABLE; P_SetTarget(&player->mo->tracer->tracer, NULL);