diff --git a/src/p_floor.c b/src/p_floor.c
index 8f51698cc..000da302f 100644
--- a/src/p_floor.c
+++ b/src/p_floor.c
@@ -2520,6 +2520,29 @@ void T_CameraScanner(elevator_t *elevator)
 	}
 }
 
+void T_PlaneDisplace(planedisplace_t *pd)
+{
+	sector_t *control, *target;
+	INT32 direction;
+	fixed_t diff;
+
+	control = &sectors[pd->control];
+	target = &sectors[pd->affectee];
+
+	if (control->floorheight == pd->last_height)
+		return; // no change, no movement
+
+	direction = (control->floorheight > pd->last_height) ? 1 : -1;
+	diff = FixedMul(control->floorheight-pd->last_height, pd->speed);
+
+	if (pd->type == pd_floor || pd->type == pd_both)
+		T_MovePlane(target, INT32_MAX/2, target->floorheight+diff, 0, 0, direction); // move floor
+	if (pd->type == pd_ceiling || pd->type == pd_both)
+		T_MovePlane(target, INT32_MAX/2, target->ceilingheight+diff, 0, 1, direction); // move ceiling
+
+	pd->last_height = control->floorheight;
+}
+
 //
 // EV_DoFloor
 //
diff --git a/src/p_spec.c b/src/p_spec.c
index 5afa38dd3..703d49f9a 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -108,7 +108,7 @@ static void P_AddFakeFloorsByLine(size_t line, ffloortype_e ffloorflags, thinker
 static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec);
 static void Add_Friction(INT32 friction, INT32 movefactor, INT32 affectee, INT32 referrer);
 static void P_AddSpikeThinker(sector_t *sec, INT32 referrer);
-static void P_AddPlaneDisplaceThinker(INT32 type, fixed_t speed, INT32 control, INT32 affectee)
+static void P_AddPlaneDisplaceThinker(INT32 type, fixed_t speed, INT32 control, INT32 affectee);
 
 
 //SoM: 3/7/2000: New sturcture without limits.
@@ -5055,6 +5055,33 @@ static inline void P_AddBridgeThinker(line_t *sourceline, sector_t *sec)
 }
 */
 
+/**
+  * Adds a plane displacement thinker.
+  * Whenever the "control" sector moves,
+  * the "affectee" sector's floor or ceiling plane moves too!
+  *
+  * \param speed            Rate of movement relative to control sector
+  * \param control          Control sector.
+  * \param affectee         Target sector.
+  * \sa P_SpawnSpecials, T_PlaneDisplace
+  * \author Monster Iestyn
+  */
+static void P_AddPlaneDisplaceThinker(INT32 type, fixed_t speed, INT32 control, INT32 affectee)
+{
+	planedisplace_t *displace;
+
+	// create and initialize new displacement thinker
+	displace = Z_Calloc(sizeof (*displace), PU_LEVSPEC, NULL);
+	P_AddThinker(&displace->thinker);
+
+	displace->thinker.function.acp1 = (actionf_p1)T_PlaneDisplace;
+	displace->affectee = affectee;
+	displace->control = control;
+	displace->last_height = sectors[control].floorheight;
+	displace->speed = speed;
+	displace->type = type;
+}
+
 /** Adds a Mario block thinker, which changes the block's texture between blank
   * and ? depending on whether it has contents.
   * Needed in case objects respawn inside.