From b721c035f8e30891c73a844319547a44fc93adbc Mon Sep 17 00:00:00 2001
From: lachwright <lachlanwright17@gmail.com>
Date: Mon, 18 Nov 2019 05:23:59 +0800
Subject: [PATCH] Add Dragonbomber enemy

---
 src/dehacked.c          |  26 ++++++++
 src/hardware/hw_light.c |   1 +
 src/info.c              | 128 ++++++++++++++++++++++++++++++++++++++++
 src/info.h              |  27 +++++++++
 src/p_enemy.c           | 102 ++++++++++++++++++++++++++++++++
 src/p_inter.c           |  11 ++++
 src/p_mobj.c            |  77 ++++++++++++++++++++++++
 7 files changed, 372 insertions(+)

diff --git a/src/dehacked.c b/src/dehacked.c
index fff9dbee8..5e29c8def 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -2781,6 +2781,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
@@ -5041,6 +5044,25 @@ 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_LAND",
+	"S_DRAGONMINE_GROUND1",
+	"S_DRAGONMINE_FLASH1",
+	"S_DRAGONMINE_LOOP1",
+	"S_DRAGONMINE_GROUND2",
+	"S_DRAGONMINE_FLASH2",
+	"S_DRAGONMINE_LOOP2",
+
 	// Boss Explosion
 	"S_BOSSEXPLODE",
 
@@ -7712,6 +7734,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 ed29fe2bf..52b04f4a0 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,25 @@ 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_LAND, 0, S_DRAGONMINE}, // S_DRAGONMINE
+	{SPR_DRAB,                4,  2,   {A_PlayActiveSound}, 0, 0, S_DRAGONMINE_GROUND1},         // S_DRAGONMINE_LAND
+	{SPR_DRAB,                5, 11,                {NULL}, 0, 0, S_DRAGONMINE_FLASH1},          // S_DRAGONMINE_GROUND1
+	{SPR_DRAB,  FF_FULLBRIGHT|6,  1,   {A_PlayAttackSound}, 0, 0, S_DRAGONMINE_LOOP1},           // S_DRAGONMINE_FLASH1
+	{SPR_DRAB,                5,  0,            {A_Repeat}, 4, S_DRAGONMINE_GROUND1, S_DRAGONMINE_GROUND2}, // S_DRAGONMINE_LOOP1
+	{SPR_DRAB,                5,  3,                {NULL}, 0, 0, S_DRAGONMINE_FLASH2},          // S_DRAGONMINE_GROUND2
+	{SPR_DRAB,  FF_FULLBRIGHT|6,  1,   {A_PlayAttackSound}, 0, 0, S_DRAGONMINE_LOOP2},           // S_DRAGONMINE_FLASH2
+	{SPR_DRAB,                5,  0,            {A_Repeat}, 5, S_DRAGONMINE_GROUND2, S_DEATHSTATE}, // S_DRAGONMINE_LOOP2
+
 	// Boss Explosion
 	{SPR_BOM2, FF_FULLBRIGHT|FF_ANIMATE, (5*7), {NULL}, 6, 5, S_NULL}, // S_BOSSEXPLODE
 
@@ -5238,6 +5258,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 bbb6a21fc..7838b4f71 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,25 @@ 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_LAND,
+	S_DRAGONMINE_GROUND1,
+	S_DRAGONMINE_FLASH1,
+	S_DRAGONMINE_LOOP1,
+	S_DRAGONMINE_GROUND2,
+	S_DRAGONMINE_FLASH2,
+	S_DRAGONMINE_LOOP2,
+
 	// Boss Explosion
 	S_BOSSEXPLODE,
 
@@ -4049,6 +4072,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 eedbecaa7..18da9c7e6 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
 
@@ -14597,3 +14600,102 @@ 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_DragonSegment", 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
+
+	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 b0a401b10..7da987815 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_mobj.c b/src/p_mobj.c
index 5d6a8a10f..dd960f5ad 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -9622,6 +9622,83 @@ 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 && 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));
+								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?
+					{
+						CONS_Printf("%s\n", "Chase state");
+						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?
+					{
+						CONS_Printf("%s\n", "Search state");
+						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)
 				{