//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 1993-2008 Raven Software
// Copyright(C) 2005-2014 Simon Howard
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//


#include "h2def.h"
#include "i_system.h"
#include "p_local.h"


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

//==================================================================
//
//      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;

    // [AM] Store old sector heights for interpolation.
    sector->oldfloorheight = sector->floorheight;
    sector->oldceilingheight = sector->ceilingheight;
    sector->oldgametic = gametic;

    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;

        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;
                        sector->ceilingheight += speed;
                        flag = P_ChangeSector(sector, crush);
#if 0
                        if (flag == true)
                        {
                            sector->ceilingheight = lastpos;
                            P_ChangeSector(sector, crush);
                            return RES_CRUSHED;
                        }
#endif
                    }
                    break;
            }
            break;

    }
    return RES_OK;
}

//==================================================================
//
//      MOVE A FLOOR TO IT'S DESTINATION (UP OR DOWN)
//
//==================================================================
void T_MoveFloor(thinker_t *thinker)
{
    floormove_t *floor = (floormove_t *) thinker;
    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 *) & 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 * line, 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 = Z_Malloc(sizeof(*floor), PU_LEVSPEC, 0);
        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 *) & 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

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 = Z_Malloc(sizeof(*floor), PU_LEVSPEC, 0);
    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 *) & 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 * line, 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(thinker_t *thinker)
{
	pillar_t *pillar = (pillar_t *) thinker;
    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 *) & pillar->sector->soundorg);
        P_TagFinished(pillar->sector->tag);
        P_RemoveThinker(&pillar->thinker);
    }
}

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

int EV_BuildPillar(line_t * line, 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 = Z_Malloc(sizeof(*pillar), PU_LEVSPEC, 0);
        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 *) & pillar->sector->soundorg,
                         SEQ_PLATFORM + pillar->sector->seqType);
    }
    return rtn;
}

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

int EV_OpenPillar(line_t * line, 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 = Z_Malloc(sizeof(*pillar), PU_LEVSPEC, 0);
        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 *) & pillar->sector->soundorg,
                         SEQ_PLATFORM + pillar->sector->seqType);
    }
    return rtn;
}

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

int EV_FloorCrushStop(line_t * line, byte * args)
{
    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 *) & 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(thinker_t *thinker)
{
	floorWaggle_t *waggle = (floorWaggle_t *) thinker;
    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 = Z_Malloc(sizeof(*waggle), PU_LEVSPEC, 0);
        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;
}
