diff --git a/src/deh_tables.c b/src/deh_tables.c
index 8dbe314cc1cd79ad38969a64860274baf4a8ed44..bed81e60c9ba0e48cbaadec26d489153b3edf8b8 100644
--- a/src/deh_tables.c
+++ b/src/deh_tables.c
@@ -198,6 +198,7 @@ actionpointer_t actionpointers[] =
 	{{A_Boss3TakeDamage},        "A_BOSS3TAKEDAMAGE"},
 	{{A_Boss3Path},              "A_BOSS3PATH"},
 	{{A_Boss3ShockThink},        "A_BOSS3SHOCKTHINK"},
+	{{A_Shockwave},              "A_SHOCKWAVE"},
 	{{A_LinedefExecute},         "A_LINEDEFEXECUTE"},
 	{{A_PlaySeeSound},           "A_PLAYSEESOUND"},
 	{{A_PlayAttackSound},        "A_PLAYATTACKSOUND"},
diff --git a/src/info.h b/src/info.h
index 1b7a201cea519d8eef3d041839917153f93ff9cc..04c048dd8a68b09bbf024cb80d76be98d5d96d28 100644
--- a/src/info.h
+++ b/src/info.h
@@ -151,6 +151,7 @@ enum actionnum
 	A_BOSS3TAKEDAMAGE,
 	A_BOSS3PATH,
 	A_BOSS3SHOCKTHINK,
+	A_SHOCKWAVE,
 	A_LINEDEFEXECUTE,
 	A_PLAYSEESOUND,
 	A_PLAYATTACKSOUND,
@@ -414,6 +415,7 @@ void A_Boss1Spikeballs();
 void A_Boss3TakeDamage();
 void A_Boss3Path();
 void A_Boss3ShockThink();
+void A_Shockwave();
 void A_LinedefExecute();
 void A_PlaySeeSound();
 void A_PlayAttackSound();
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 87aa5ff2f80e7f61fd092ab2fa4f1dedac9d3370..efa413a444c049c9f78a43894a46e9eea139cb64 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -173,6 +173,7 @@ void A_Boss1Spikeballs(mobj_t *actor);
 void A_Boss3TakeDamage(mobj_t *actor);
 void A_Boss3Path(mobj_t *actor);
 void A_Boss3ShockThink(mobj_t *actor);
+void A_Shockwave(mobj_t *actor);
 void A_LinedefExecute(mobj_t *actor);
 void A_PlaySeeSound(mobj_t *actor);
 void A_PlayAttackSound(mobj_t *actor);
@@ -8326,6 +8327,56 @@ void A_Boss3ShockThink(mobj_t *actor)
 	}
 }
 
+// Function: A_Shockwave
+//
+// Description: Spawns a shockwave of objects. Best used to spawn objects that call A_Boss3ShockThink.
+//
+// var1 = object spawned
+// var2 = amount of objects spawned
+//
+void A_Shockwave(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+	INT32 i;
+
+	angle_t ang = 0, interval;
+	mobj_t *shock = NULL, *sfirst = NULL, *sprev = NULL;
+
+	if (LUA_CallAction(A_SHOCKWAVE, actor))
+		return;
+
+	if (locvar2 == 0)
+		locvar2 = 24; // a sensible default, just in case
+
+	interval = FixedAngle((360 << FRACBITS) / locvar2);
+
+	for (i = 0; i < locvar2; i++)
+	{
+		shock = P_SpawnMobj(actor->x, actor->y, actor->z, locvar1);
+		P_SetTarget(&shock->target, actor);
+		shock->fuse = shock->info->painchance;
+
+		if (i % 2 == 0)
+			P_SetMobjState(shock, shock->state->nextstate);
+
+		if (!sprev)
+			sfirst = shock;
+		else
+		{
+			if (i == locvar2 - 1)
+				P_SetTarget(&shock->hnext, sfirst);
+			P_SetTarget(&sprev->hnext, shock);
+		}
+
+		P_Thrust(shock, ang, shock->info->speed);
+		ang += interval;
+		sprev = shock;
+	}
+	
+	S_StartSound(actor, shock->info->seesound);
+}
+
 // Function: A_LinedefExecute
 //
 // Description: Object's location is used to set the calling sector. The tag used is var1. Optionally, if var2 is set, the actor's angle (multiplied by var2) is added to the tag number as well.