shithub: hexen

ref: 74a4498e93ff77506e3d30b6f32c0c3f936c86db
dir: /p_floor.c/

View raw version

//**************************************************************************
//**
//** p_floor.c : Heretic 2 : Raven Software, Corp.
//**
//** $Revision: 472 $
//** $Date: 2009-05-26 15:45:18 +0300 (Tue, 26 May 2009) $
//**
//**************************************************************************

#include "h2stdinc.h"
#include "h2def.h"
#include "p_local.h"
#include "soundst.h"

//==================================================================
//
// FLOORS
//
//==================================================================

extern fixed_t FloatBobOffsets[64];


//==================================================================
//
// Move a plane (floor or ceiling) and check for crushing
//
//==================================================================

result_e T_MovePlane(sector_t *sector,fixed_t speed, fixed_t dest,
			int crush, int floorOrCeiling, int direction)
{
	boolean flag;
	fixed_t lastpos;

	switch (floorOrCeiling)
	{
	case 0:	// FLOOR
		switch (direction)
		{
		case -1: // DOWN
			if (sector->floorheight - speed < dest)
			{
				lastpos = sector->floorheight;
				sector->floorheight = dest;
				flag = P_ChangeSector(sector,crush);
				if (flag == true)
				{
					sector->floorheight = lastpos;
					P_ChangeSector(sector, crush);
					//return RES_CRUSHED;
				}
				return RES_PASTDEST;
			}
			else
			{
				lastpos = sector->floorheight;
				sector->floorheight -= speed;
				flag = P_ChangeSector(sector, crush);
				if (flag == true)
				{
					sector->floorheight = lastpos;
					P_ChangeSector(sector, crush);
					return RES_CRUSHED;
				}
			}
			break;

		case 1:	// UP
			if (sector->floorheight + speed > dest)
			{
				lastpos = sector->floorheight;
				sector->floorheight = dest;
				flag = P_ChangeSector(sector, crush);
				if (flag == true)
				{
					sector->floorheight = lastpos;
					P_ChangeSector(sector, crush);
					//return RES_CRUSHED;
				}
				return RES_PASTDEST;
			}
			else	// COULD GET CRUSHED
			{
				lastpos = sector->floorheight;
				sector->floorheight += speed;
				flag = P_ChangeSector(sector, crush);
				if (flag == true)
				{
					//if (crush == true)
					//{
					//	return RES_CRUSHED;
					//}
					sector->floorheight = lastpos;
					P_ChangeSector(sector, crush);
					return RES_CRUSHED;
				}
			}
			break;
		}
		break;	/* END OF THE FLOOR CASE */

	case 1:	// CEILING
		switch (direction)
		{
		case -1: // DOWN
			if (sector->ceilingheight - speed < dest)
			{
				lastpos = sector->ceilingheight;
				sector->ceilingheight = dest;
				flag = P_ChangeSector(sector, crush);
				if (flag == true)
				{
					sector->ceilingheight = lastpos;
					P_ChangeSector(sector, crush);
					//return RES_CRUSHED;
				}
				return RES_PASTDEST;
			}
			else	// COULD GET CRUSHED
			{
				lastpos = sector->ceilingheight;
				sector->ceilingheight -= speed;
				flag = P_ChangeSector(sector, crush);
				if (flag == true)
				{
					//if (crush == true)
					//{
					//	return RES_CRUSHED;
					//}
					sector->ceilingheight = lastpos;
					P_ChangeSector(sector, crush);
					return RES_CRUSHED;
				}
			}
			break;

		case 1:	// UP
			if (sector->ceilingheight + speed > dest)
			{
				lastpos = sector->ceilingheight;
				sector->ceilingheight = dest;
				flag = P_ChangeSector(sector, crush);
				if (flag == true)
				{
					sector->ceilingheight = lastpos;
					P_ChangeSector(sector, crush);
					//return RES_CRUSHED;
				}
				return RES_PASTDEST;
			}
			else
			{
				lastpos = sector->ceilingheight;
				USED(lastpos);
				sector->ceilingheight += speed;
				flag = P_ChangeSector(sector, crush);
				USED(flag);
				#if 0
				if (flag == true)
				{
					sector->ceilingheight = lastpos;
					P_ChangeSector(sector, crush);
					return RES_CRUSHED;
				}
				#endif
			}
			break;
		}
		break;	/* END OF THE CEILING CASE */
	}

	return RES_OK;
}

//==================================================================
//
// MOVE A FLOOR TO IT'S DESTINATION (UP OR DOWN)
//
//==================================================================

void T_MoveFloor(floormove_t *floor)
{
	result_e	res;

	if (floor->resetDelayCount)
	{
		floor->resetDelayCount--;
		if (!floor->resetDelayCount)
		{
			floor->floordestheight = floor->resetHeight;
			floor->direction = -floor->direction;
			floor->resetDelay = 0;
			floor->delayCount = 0;
			floor->delayTotal = 0;
		}
	}
	if (floor->delayCount)
	{
		floor->delayCount--;
		if (!floor->delayCount && floor->textureChange)
		{
			floor->sector->floorpic += floor->textureChange;
		}
		return;
	}

	res = T_MovePlane(floor->sector,floor->speed,
			floor->floordestheight, floor->crush, 0, floor->direction);

	if (floor->type == FLEV_RAISEBUILDSTEP)
	{
		if ((floor->direction == 1 && floor->sector->floorheight >= floor->stairsDelayHeight) ||
		    (floor->direction == -1 && floor->sector->floorheight <= floor->stairsDelayHeight))
		{
			floor->delayCount = floor->delayTotal;
			floor->stairsDelayHeight += floor->stairsDelayHeightDelta;
		}
	}
	if (res == RES_PASTDEST)
	{
		SN_StopSequence((mobj_t *)(void *)&floor->sector->soundorg);
		if (floor->delayTotal)
		{
			floor->delayTotal = 0;
		}
		if (floor->resetDelay)
		{
		//	floor->resetDelayCount = floor->resetDelay;
		//	floor->resetDelay = 0;
			return;
		}
		floor->sector->specialdata = NULL;
		/*
		if (floor->direction == 1)
		{
			switch (floor->type)
			{
			case donutRaise:
				floor->sector->special = floor->newspecial;
				floor->sector->floorpic = floor->texture;
			default:
				break;
			}
		}
		else if (floor->direction == -1)
		{
			switch (floor->type)
			{
			case lowerAndChange:
				floor->sector->special = floor->newspecial;
				floor->sector->floorpic = floor->texture;
			default:
				break;
			}
		}
		*/
		if (floor->textureChange)
		{
			floor->sector->floorpic -= floor->textureChange;
		}
		P_TagFinished(floor->sector->tag);
		P_RemoveThinker(&floor->thinker);
	}
}

//==================================================================
//
// HANDLE FLOOR TYPES
//
//==================================================================

int EV_DoFloor(line_t*, byte *args, floor_e floortype)
{
	int		secnum;
	int		rtn;
	sector_t	*sec;
	floormove_t	*floor = NULL;

	secnum = -1;
	rtn = 0;
	while ((secnum = P_FindSectorFromTag(args[0], secnum)) >= 0)
	{
		sec = &sectors[secnum];

		// ALREADY MOVING?  IF SO, KEEP GOING...
		if (sec->specialdata)
			continue;

		//
		// new floor thinker
		//
		rtn = 1;
		floor = (floormove_t *) Z_Malloc (sizeof(*floor), PU_LEVSPEC, NULL);
		memset(floor, 0, sizeof(*floor));
		P_AddThinker (&floor->thinker);
		sec->specialdata = floor;
		floor->thinker.function = T_MoveFloor;
		floor->type = floortype;
		floor->crush = 0;
		floor->speed = args[1]*(FRACUNIT/8);
		if (floortype == FLEV_LOWERTIMES8INSTANT ||
			floortype == FLEV_RAISETIMES8INSTANT)
		{
			floor->speed = 2000<<FRACBITS;
		}
		switch (floortype)
		{
		case FLEV_LOWERFLOOR:
			floor->direction = -1;
			floor->sector = sec;
			floor->floordestheight = P_FindHighestFloorSurrounding(sec);
			break;
		case FLEV_LOWERFLOORTOLOWEST:
			floor->direction = -1;
			floor->sector = sec;
			floor->floordestheight = P_FindLowestFloorSurrounding(sec);
			break;
		case FLEV_LOWERFLOORBYVALUE:
			floor->direction = -1;
			floor->sector = sec;
			floor->floordestheight = floor->sector->floorheight - args[2]*FRACUNIT;
			break;
		case FLEV_LOWERTIMES8INSTANT:
		case FLEV_LOWERBYVALUETIMES8:
			floor->direction = -1;
			floor->sector = sec;
			floor->floordestheight = floor->sector->floorheight - args[2]*FRACUNIT*8;
			break;
		case FLEV_RAISEFLOORCRUSH:
			floor->crush = args[2]; // arg[2] = crushing value
			floor->direction = 1;
			floor->sector = sec;
			floor->floordestheight = sec->ceilingheight - 8*FRACUNIT;
			break;
		case FLEV_RAISEFLOOR:
			floor->direction = 1;
			floor->sector = sec;
			floor->floordestheight = P_FindLowestCeilingSurrounding(sec);
			if (floor->floordestheight > sec->ceilingheight)
				floor->floordestheight = sec->ceilingheight;
			break;
		case FLEV_RAISEFLOORTONEAREST:
			floor->direction = 1;
			floor->sector = sec;
			floor->floordestheight = P_FindNextHighestFloor(sec, sec->floorheight);
			break;
		case FLEV_RAISEFLOORBYVALUE:
			floor->direction = 1;
			floor->sector = sec;
			floor->floordestheight = floor->sector->floorheight + args[2]*FRACUNIT;
			break;
		case FLEV_RAISETIMES8INSTANT:
		case FLEV_RAISEBYVALUETIMES8:
			floor->direction = 1;
			floor->sector = sec;
			floor->floordestheight = floor->sector->floorheight + args[2]*FRACUNIT*8;
			break;
		case FLEV_MOVETOVALUETIMES8:
			floor->sector = sec;
			floor->floordestheight = args[2]*FRACUNIT*8;
			if (args[3])
			{
				floor->floordestheight = -floor->floordestheight;
			}
			if (floor->floordestheight > floor->sector->floorheight)
			{
				floor->direction = 1;
			}
			else if (floor->floordestheight < floor->sector->floorheight)
			{
				floor->direction = -1;
			}
			else
			{ // already at lowest position
				rtn = 0;
			}
			break;
		default:
			rtn = 0;
			break;
		}
	}
	if (rtn)
	{
		SN_StartSequence((mobj_t *)(void *)&floor->sector->soundorg,
				 SEQ_PLATFORM + floor->sector->seqType);
	}
	return rtn;
}

//============================================================================
//
// EV_DoFloorAndCeiling
//
//============================================================================

int EV_DoFloorAndCeiling(line_t *line, byte *args, boolean raise)
{
	boolean	floor, ceiling;
	int		secnum;
	sector_t	*sec;

	if (raise)
	{
		floor = EV_DoFloor(line, args, FLEV_RAISEFLOORBYVALUE);
		secnum = -1;
		while ((secnum = P_FindSectorFromTag(args[0], secnum)) >= 0)
		{
			sec = &sectors[secnum];
			sec->specialdata = NULL;
		}
		ceiling = EV_DoCeiling(line, args, CLEV_RAISEBYVALUE);
	}
	else
	{
		floor = EV_DoFloor(line, args, FLEV_LOWERFLOORBYVALUE);
		secnum = -1;
		while ((secnum = P_FindSectorFromTag(args[0], secnum)) >= 0)
		{
			sec = &sectors[secnum];
			sec->specialdata = NULL;
		}
		ceiling = EV_DoCeiling(line, args, CLEV_LOWERBYVALUE);
	}
	return (floor|ceiling);
}

// ===== Build Stairs Private Data =====

#define STAIR_SECTOR_TYPE	26
#define STAIR_QUEUE_SIZE	32

static struct
{
	sector_t *sector;
	int	type;
	int	height;
} StairQueue[STAIR_QUEUE_SIZE];

static int	QueueHead;
static int	QueueTail;

static int	StepDelta;
static int	Direction;
static int	Speed;
static int	Texture;
static int	StartDelay;
static int	StartDelayDelta;
static int	TextureChange;
static int	StartHeight;

//==========================================================================
//
// QueueStairSector
//
//==========================================================================

static void QueueStairSector(sector_t *sec, int type, int height)
{
	if ((QueueTail + 1) % STAIR_QUEUE_SIZE == QueueHead)
	{
		I_Error("BuildStairs:  Too many branches located.\n");
	}
	StairQueue[QueueTail].sector = sec;
	StairQueue[QueueTail].type = type;
	StairQueue[QueueTail].height = height;

	QueueTail = (QueueTail + 1) % STAIR_QUEUE_SIZE;
}

//==========================================================================
//
// DequeueStairSector
//
//==========================================================================

static sector_t *DequeueStairSector(int *type, int *height)
{
	sector_t	*sec;

	if (QueueHead == QueueTail)
	{ // queue is empty
		return NULL;
	}
	*type = StairQueue[QueueHead].type;
	*height = StairQueue[QueueHead].height;
	sec = StairQueue[QueueHead].sector;
	QueueHead = (QueueHead + 1) % STAIR_QUEUE_SIZE;

	return sec;
}

//==========================================================================
//
// ProcessStairSector
//
//==========================================================================

static void ProcessStairSector(sector_t *sec, int type, int height,
			stairs_e stairsType, int delay, int resetDelay)
{
	int		i;
	sector_t	*tsec;
	floormove_t	*floor;

	//
	// new floor thinker
	//
	height += StepDelta;
	floor = (floormove_t *) Z_Malloc(sizeof(*floor), PU_LEVSPEC, NULL);
	memset(floor, 0, sizeof(*floor));
	P_AddThinker(&floor->thinker);
	sec->specialdata = floor;
	floor->thinker.function = T_MoveFloor;
	floor->type = FLEV_RAISEBUILDSTEP;
	floor->direction = Direction;
	floor->sector = sec;
	floor->floordestheight = height;
	switch (stairsType)
	{
	case STAIRS_NORMAL:
		floor->speed = Speed;
		if (delay)
		{
			floor->delayTotal = delay;
			floor->stairsDelayHeight = sec->floorheight + StepDelta;
			floor->stairsDelayHeightDelta = StepDelta;
		}
		floor->resetDelay = resetDelay;
		floor->resetDelayCount = resetDelay;
		floor->resetHeight = sec->floorheight;
		break;
	case STAIRS_SYNC:
		floor->speed = FixedMul(Speed, FixedDiv(height - StartHeight, StepDelta));
		floor->resetDelay = delay; //arg4
		floor->resetDelayCount = delay;
		floor->resetHeight = sec->floorheight;
		break;
	/*
	case STAIRS_PHASED:
		floor->floordestheight = sec->floorheight + StepDelta;
		floor->speed = Speed;
		floor->delayCount = StartDelay;
		StartDelay += StartDelayDelta;
		floor->textureChange = TextureChange;
		floor->resetDelayCount = StartDelay;
		break;
	*/
	default:
		break;
	}
	SN_StartSequence((mobj_t *)(void *)&sec->soundorg, SEQ_PLATFORM + sec->seqType);
	//
	// Find next sector to raise
	// Find nearby sector with sector special equal to type
	//
	for (i = 0; i < sec->linecount; i++)
	{
		if (!((sec->lines[i])->flags & ML_TWOSIDED))
		{
			continue;
		}
		tsec = (sec->lines[i])->frontsector;
		if (tsec->special == type + STAIR_SECTOR_TYPE && !tsec->specialdata
			&& tsec->floorpic == Texture && tsec->validcount != validcount)
		{
			QueueStairSector(tsec, type^1, height);
			tsec->validcount = validcount;
			//tsec->special = 0;
		}
		tsec = (sec->lines[i])->backsector;
		if (tsec->special == type + STAIR_SECTOR_TYPE && !tsec->specialdata
			&& tsec->floorpic == Texture && tsec->validcount != validcount)
		{
			QueueStairSector(tsec, type^1, height);
			tsec->validcount = validcount;
			//tsec->special = 0;
		}
	}
}

//==================================================================
//
// BUILD A STAIRCASE!
//
// Direction is either positive or negative, denoting build stairs
// up or down.
//==================================================================

int EV_BuildStairs(line_t*, byte *args, int direction,
					stairs_e stairsType)
{
	int		secnum;
	int		height;
	int		delay;
	int		resetDelay;
	sector_t	*sec;
	sector_t	*qSec;
	int		type;

	// Set global stairs variables
	TextureChange = 0;
	Direction = direction;
	StepDelta = Direction*(args[2]*FRACUNIT);
	Speed = args[1]*(FRACUNIT/8);
	resetDelay = args[4];
	delay = args[3];
	if (stairsType == STAIRS_PHASED)
	{
		StartDelayDelta = args[3];
		StartDelay = StartDelayDelta;
		resetDelay = StartDelayDelta;
		delay = 0;
		TextureChange = args[4];
	}

	secnum = -1;

	validcount++;
	while ((secnum = P_FindSectorFromTag(args[0], secnum)) >= 0)
	{
		sec = &sectors[secnum];

		Texture = sec->floorpic;
		StartHeight = sec->floorheight;

		// ALREADY MOVING?  IF SO, KEEP GOING...
		if (sec->specialdata)
			continue;

		QueueStairSector(sec, 0, sec->floorheight);
		sec->special = 0;
	}
	while ((qSec = DequeueStairSector(&type, &height)) != NULL)
	{
		ProcessStairSector(qSec, type, height, stairsType, delay, resetDelay);
	}
	return 1;
}

//=========================================================================
//
// T_BuildPillar
//
//=========================================================================

void T_BuildPillar(pillar_t *pillar)
{
	result_e	res1;
	result_e	res2;

	// First, raise the floor
	res1 = T_MovePlane(pillar->sector, pillar->floorSpeed,
			   pillar->floordest, pillar->crush, 0,
			   pillar->direction); // floorOrCeiling, direction
	// Then, lower the ceiling
	res2 = T_MovePlane(pillar->sector, pillar->ceilingSpeed,
			   pillar->ceilingdest, pillar->crush, 1,
			   -pillar->direction);
	if (res1 == RES_PASTDEST && res2 == RES_PASTDEST)
	{
		pillar->sector->specialdata = NULL;
		SN_StopSequence((mobj_t *)(void *)&pillar->sector->soundorg);
		P_TagFinished(pillar->sector->tag);
		P_RemoveThinker(&pillar->thinker);
	}
}

//=========================================================================
//
// EV_BuildPillar
//
//=========================================================================

int EV_BuildPillar(line_t*, byte *args, boolean crush)
{
	int secnum;
	sector_t *sec;
	pillar_t *pillar;
	int newHeight;
	int rtn;

	rtn = 0;
	secnum = -1;
	while ((secnum = P_FindSectorFromTag(args[0], secnum)) >= 0)
	{
		sec = &sectors[secnum];
		if (sec->specialdata)
			continue; // already moving
		if (sec->floorheight == sec->ceilingheight)
		{ // pillar is already closed
			continue;
		}
		rtn = 1;
		if (!args[2])
		{
			newHeight = sec->floorheight +
				((sec->ceilingheight - sec->floorheight)/2);
		}
		else
		{
			newHeight = sec->floorheight + (args[2]<<FRACBITS);
		}

		pillar = (pillar_t *) Z_Malloc(sizeof(*pillar), PU_LEVSPEC, NULL);
		sec->specialdata = pillar;
		P_AddThinker(&pillar->thinker);
		pillar->thinker.function = T_BuildPillar;
		pillar->sector = sec;
		if (!args[2])
		{
			pillar->ceilingSpeed = pillar->floorSpeed = args[1]*(FRACUNIT/8);
		}
		else if (newHeight-sec->floorheight > sec->ceilingheight - newHeight)
		{
			pillar->floorSpeed = args[1]*(FRACUNIT/8);
			pillar->ceilingSpeed = FixedMul(sec->ceilingheight - newHeight,
				FixedDiv(pillar->floorSpeed, newHeight - sec->floorheight));
		}
		else
		{
			pillar->ceilingSpeed = args[1]*(FRACUNIT/8);
			pillar->floorSpeed = FixedMul(newHeight - sec->floorheight,
				FixedDiv(pillar->ceilingSpeed, sec->ceilingheight - newHeight));
		}
		pillar->floordest = newHeight;
		pillar->ceilingdest = newHeight;
		pillar->direction = 1;
		pillar->crush = crush*args[3];
		SN_StartSequence((mobj_t *)(void *)&pillar->sector->soundorg,
				 SEQ_PLATFORM + pillar->sector->seqType);
	}
	return rtn;
}

//=========================================================================
//
// EV_OpenPillar
//
//=========================================================================

int EV_OpenPillar(line_t*, byte *args)
{
	int secnum;
	sector_t *sec;
	pillar_t *pillar;
	int rtn;

	rtn = 0;
	secnum = -1;
	while ((secnum = P_FindSectorFromTag(args[0], secnum)) >= 0)
	{
		sec = &sectors[secnum];
		if (sec->specialdata)
			continue; // already moving
		if (sec->floorheight != sec->ceilingheight)
		{ // pillar isn't closed
			continue;
		}
		rtn = 1;
		pillar = (pillar_t *) Z_Malloc(sizeof(*pillar), PU_LEVSPEC, NULL);
		sec->specialdata = pillar;
		P_AddThinker(&pillar->thinker);
		pillar->thinker.function = T_BuildPillar;
		pillar->sector = sec;
		if (!args[2])
		{
			pillar->floordest = P_FindLowestFloorSurrounding(sec);
		}
		else
		{
			pillar->floordest = sec->floorheight - (args[2]<<FRACBITS);
		}
		if (!args[3])
		{
			pillar->ceilingdest = P_FindHighestCeilingSurrounding(sec);
		}
		else
		{
			pillar->ceilingdest = sec->ceilingheight + (args[3]<<FRACBITS);
		}
		if (sec->floorheight - pillar->floordest >= 
					pillar->ceilingdest - sec->ceilingheight)
		{
			pillar->floorSpeed = args[1]*(FRACUNIT/8);
			pillar->ceilingSpeed = FixedMul(sec->ceilingheight - pillar->ceilingdest,
				FixedDiv(pillar->floorSpeed, pillar->floordest - sec->floorheight));
		}
		else
		{
			pillar->ceilingSpeed = args[1]*(FRACUNIT/8);
			pillar->floorSpeed = FixedMul(pillar->floordest - sec->floorheight,
				FixedDiv(pillar->ceilingSpeed, sec->ceilingheight - pillar->ceilingdest));
		}
		pillar->direction = -1; // open the pillar
		SN_StartSequence((mobj_t *)(void *)&pillar->sector->soundorg,
				 SEQ_PLATFORM + pillar->sector->seqType);
	}
	return rtn;
}

//=========================================================================
//
// EV_FloorCrushStop
//
//=========================================================================

int EV_FloorCrushStop(line_t*, byte*)
{
	thinker_t *think;
	floormove_t *floor;
	boolean rtn;

	rtn = 0;
	for (think = thinkercap.next; think != &thinkercap; think = think->next)
	{
		if (think->function != T_MoveFloor)
		{
			continue;
		}
		floor = (floormove_t *)think;
		if (floor->type != FLEV_RAISEFLOORCRUSH)
		{
			continue;
		}
		// Completely remove the crushing floor
		SN_StopSequence((mobj_t *)(void *)&floor->sector->soundorg);
		floor->sector->specialdata = NULL;
		P_TagFinished(floor->sector->tag);
		P_RemoveThinker(&floor->thinker);
		rtn = 1;
	}
	return rtn;
}

//==========================================================================
//
// T_FloorWaggle
//
//==========================================================================

#define WGLSTATE_EXPAND		1
#define WGLSTATE_STABLE		2
#define WGLSTATE_REDUCE		3

void T_FloorWaggle(floorWaggle_t *waggle)
{
	switch (waggle->state)
	{
	case WGLSTATE_EXPAND:
		if ((waggle->scale += waggle->scaleDelta) >= waggle->targetScale)
		{
			waggle->scale = waggle->targetScale;
			waggle->state = WGLSTATE_STABLE;
		}
		break;
	case WGLSTATE_REDUCE:
		if ((waggle->scale -= waggle->scaleDelta) <= 0)
		{ // Remove
			waggle->sector->floorheight = waggle->originalHeight;
			P_ChangeSector(waggle->sector, true);
			waggle->sector->specialdata = NULL;
			P_TagFinished(waggle->sector->tag);
			P_RemoveThinker(&waggle->thinker);
			return;
		}
		break;
	case WGLSTATE_STABLE:
		if (waggle->ticker != -1)
		{
			if (!--waggle->ticker)
			{
				waggle->state = WGLSTATE_REDUCE;
			}
		}
		break;
	}
	waggle->accumulator += waggle->accDelta;
	waggle->sector->floorheight = waggle->originalHeight +
		FixedMul(FloatBobOffsets[(waggle->accumulator>>FRACBITS) & 63], waggle->scale);
	P_ChangeSector(waggle->sector, true);
}

//==========================================================================
//
// EV_StartFloorWaggle
//
//==========================================================================

boolean EV_StartFloorWaggle(int tag, int height, int speed, int offset,
								int timer)
{
	int sectorIndex;
	sector_t *sector;
	floorWaggle_t *waggle;
	boolean retCode;

	retCode = false;
	sectorIndex = -1;
	while ((sectorIndex = P_FindSectorFromTag(tag, sectorIndex)) >= 0)
	{
		sector = &sectors[sectorIndex];
		if (sector->specialdata)
		{ // Already busy with another thinker
			continue;
		}
		retCode = true;
		waggle = (floorWaggle_t *) Z_Malloc(sizeof(*waggle), PU_LEVSPEC, NULL);
		sector->specialdata = waggle;
		waggle->thinker.function = T_FloorWaggle;
		waggle->sector = sector;
		waggle->originalHeight = sector->floorheight;
		waggle->accumulator = offset*FRACUNIT;
		waggle->accDelta = speed<<10;
		waggle->scale = 0;
		waggle->targetScale = height<<10;
		waggle->scaleDelta = waggle->targetScale / (35 + ((3*35)*height)/255);
		waggle->ticker = timer ? timer*35 : -1;
		waggle->state = WGLSTATE_EXPAND;
		P_AddThinker(&waggle->thinker);
	}
	return retCode;
}