//-------------------------------------------------------------------------
/*
Copyright (C) 1997, 2005 - 3D Realms Entertainment

This file is part of Shadow Warrior version 1.2

Shadow Warrior 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.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

Original Source: 1997 - Frank Maddin and Jim Norwood
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------
#include "ns.h"

#include "build.h"

#include "names2.h"
#include "panel.h"
#include "tags.h"
#include "sector.h"
#include "ai.h"
#include "player.h"
#include "game.h"

BEGIN_SW_NS

void ScaleSectorObject(SECTOR_OBJECT*);

//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------

short DoSectorObjectSetScale(short match)
{
    SECTOR_OBJECT* sop;

    for (sop = SectorObject; sop < &SectorObject[MAX_SECTOR_OBJECTS]; sop++)
    {
        if (SO_EMPTY(sop))
            continue;

        if (sop->match_event == match)
        {
            sop->flags |= (SOBJ_DYNAMIC);
            sop->PreMoveAnimator = ScaleSectorObject;

            switch (sop->scale_active_type)
            {
            case SO_SCALE_RANDOM_POINT:
                if (sop->scale_type == SO_SCALE_HOLD || sop->scale_type == SO_SCALE_NONE)
                {
                    // if holding start it up
                    sop->scale_type = sop->scale_active_type;
                }
                else
                {
                    // if moving set to hold
                    sop->scale_type = SO_SCALE_HOLD;
                }
                break;

            case SO_SCALE_DEST:

                sop->scale_type = sop->scale_active_type;

                if (sop->scale_dist == sop->scale_dist_max)
                {
                    // make it negative
                    if (sop->scale_speed > 0)
                        sop->scale_speed = -sop->scale_speed;
                }
                else if (sop->scale_dist == sop->scale_dist_min)
                {
                    // make it positive
                    if (sop->scale_speed < 0)
                        sop->scale_speed = -sop->scale_speed;
                }
                else
                {
                    // make it positive
                    if (sop->scale_speed < 0)
                        sop->scale_speed = -sop->scale_speed;
                }
                break;

            case SO_SCALE_RANDOM:
            case SO_SCALE_CYCLE:
                if (sop->scale_type == SO_SCALE_HOLD)
                {
                    // if holding start it up
                    sop->scale_type = sop->scale_active_type;
                }
                else
                {
                    // if moving set to hold
                    sop->scale_type = SO_SCALE_HOLD;
                }
                break;
            }
        }
    }
    return 0;
}

//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------

short DoSOevent(short match, short state)
{
    SECTOR_OBJECT* sop;
    short vel_adj=0, spin_adj=0;

    for (sop = SectorObject; sop < &SectorObject[MAX_SECTOR_OBJECTS]; sop++)
    {
        if (SO_EMPTY(sop))
            continue;

        if (sop->match_event == match)
        {
            if ((sop->flags & SOBJ_WAIT_FOR_EVENT))
            {
                if (sop->save_vel > 0 || sop->save_spin_speed > nullAngle)
                {
                    sop->flags &= ~(SOBJ_WAIT_FOR_EVENT);
                    sop->vel = sop->save_vel;
                    sop->spin_speed = sop->save_spin_speed;
                }
            }

            auto me_act = sop->match_event_actor;
            if (me_act == nullptr)
                continue;

            // toggle
            if (state == -1)
            {
                if (TEST_BOOL3(me_act))
                {
                    RESET_BOOL3(me_act);
                    state = 0;
                }
                else
                {
                    SET_BOOL3(me_act);
                    state = 1;
                }
            }

            if (state == 1)
            {
                spin_adj = (int)SP_TAG3(me_act);
                vel_adj = SP_TAG7(me_act);
            }
            else if (state == 0)
            {
                spin_adj = -(int)SP_TAG3(me_act);
                vel_adj = -SP_TAG7(me_act);
            }

            sop->spin_speed += DAngle::fromBuild(spin_adj);

            if (TEST_BOOL1(me_act))
                sop->vel_tgt += vel_adj;
            else
                sop->vel += vel_adj;

            if (TEST_BOOL2(me_act))
            {
                sop->dir *= -1;
            }
        }
    }
    return 0;
}


//---------------------------------------------------------------------------
//
// SCALING - PreAnimator
//
//---------------------------------------------------------------------------

void ScaleSectorObject(SECTOR_OBJECT* sop)
{
    switch (sop->scale_type)
    {
    case SO_SCALE_NONE:
        break;

    case SO_SCALE_HOLD:
        break;

    // to dest
    case SO_SCALE_DEST:
        sop->scale_dist += sop->scale_speed;

        if (sop->scale_dist > sop->scale_dist_max)
        {
            sop->scale_dist = sop->scale_dist_max;
            sop->scale_type = SO_SCALE_HOLD;
        }
        else if (sop->scale_dist < sop->scale_dist_min)
        {
            sop->scale_dist = sop->scale_dist_min;
            sop->scale_type = SO_SCALE_HOLD;
        }

        break;

    // random direction change
    case SO_SCALE_RANDOM:

        sop->scale_dist += sop->scale_speed;
        if (sop->scale_dist > sop->scale_dist_max)
        {
            sop->scale_speed *= -1;
            sop->scale_dist = sop->scale_dist_max;
        }
        else if (sop->scale_dist < sop->scale_dist_min)
        {
            sop->scale_speed *= -1;
            sop->scale_dist = sop->scale_dist_min;
        }

        if (RANDOM_P2(1024) < sop->scale_rand_freq<<3)
        {
            sop->scale_speed *= -1;
        }

        break;

    // cycle through max and min
    case SO_SCALE_CYCLE:
        sop->scale_dist += sop->scale_speed;

        if (sop->scale_dist > sop->scale_dist_max)
        {
            sop->scale_speed *= -1;
            sop->scale_dist = sop->scale_dist_max;
        }
        else if (sop->scale_dist < sop->scale_dist_min)
        {
            sop->scale_speed *= -1;
            sop->scale_dist = sop->scale_dist_min;
        }
        break;
    }
}

//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------

DVector2 ScaleRandomPoint(SECTOR_OBJECT* sop, int k, DAngle ang, const DVector2& pos)
{
    sop->scale_point_dist[k] += sop->scale_point_speed[k];
    if (sop->scale_point_dist[k] > sop->scale_point_dist_max)
    {
        sop->scale_point_speed[k] *= -1;
        sop->scale_point_dist[k] = sop->scale_point_dist_max;
    }
    else if (sop->scale_point_dist[k] < sop->scale_point_dist_min)
    {
        sop->scale_point_speed[k] *= -1;
        sop->scale_point_dist[k] = sop->scale_point_dist_min;
    }

    if (RANDOM_P2(1024) < sop->scale_point_rand_freq)
    {
        sop->scale_point_speed[k] *= -1;
    }

    // change up speed at random
    if (RANDOM_P2(1024) < (sop->scale_point_rand_freq / 2))
    {
        double half = sop->scale_point_base_speed / 2;
        double quart = sop->scale_point_base_speed / 4;
        sop->scale_point_speed[k] = sop->scale_point_base_speed + (RandomRangeF(half) - quart);
    }

    double xmul = (sop->scale_point_dist[k] * sop->scale_x_mult) * (1 / 256.);
    double ymul = (sop->scale_point_dist[k] * sop->scale_y_mult) * (1 / 256.);

    DVector2 ret;

    ret.X = pos.X + xmul * ang.Cos();
    ret.Y = pos.Y + ymul * ang.Sin();
    return ret;
}

//---------------------------------------------------------------------------
//
// Morph point - move point around
//
//---------------------------------------------------------------------------

void MorphTornado(SECTOR_OBJECT* sop)
{
    sectortype* *sectp;
    int j;

    // z direction
    ASSERT(sop->op_main_sector != nullptr);
    sop->morph_z += sop->morph_z_speed;

    // move vector
    if (sop->morph_wall_point == nullptr)
        return;

    // place at correct x,y offset from center
    DVector2 pos = sop->pmid - sop->morph_off;
    DVector2 spos = pos;

    // move it from last x,y
    DVector2 mpos = pos + sop->morph_ang.ToVector() * sop->morph_speed;

    // bound check radius
    if ((sop->pmid - mpos).Length() > sop->morph_dist_max + sop->scale_dist)
    {
        // find and reverse angle
        sop->morph_ang = (mpos - sop->pmid).Angle() + DAngle180;

        // move back some from last point
        mpos = spos + sop->morph_ang.ToVector() * sop->morph_speed * 2;
    }

    // save x,y back as offset info
    sop->morph_off = sop->pmid - mpos;

    if ((RANDOM_P2(1024<<4)>>4) < sop->morph_rand_freq)
        sop->morph_ang = RandomAngle();

    // move it x,y
    dragpoint(sop->morph_wall_point, mpos);

    // bound the Z
    double ceilz = sop->op_main_sector->ceilingz;
    double florz = sop->op_main_sector->floorz;

    for (sectp = sop->sectp, j = 0; *sectp; sectp++, j++)
    {
        if ((*sectp)->hasU() &&
            ((*sectp)->flags & SECTFU_SO_SLOPE_CEILING_TO_POINT))
        {
            if (sop->morph_z > florz)
            {
                sop->morph_z_speed *= -1;
                sop->morph_z = florz;
            }
            else if (sop->morph_z < ceilz)
            {
                sop->morph_z_speed *= -1;
                sop->morph_z = ceilz;
            }

            alignceilslope(*sectp, DVector3(mpos, sop->morph_z));
        }
    }
}

//---------------------------------------------------------------------------
//
// moves center point around and aligns slope
//
//---------------------------------------------------------------------------

void MorphFloor(SECTOR_OBJECT* sop)
{
    sectortype* *sectp;
    int j;

    // z direction
    ASSERT(sop->op_main_sector != nullptr);
    sop->morph_z -= sop->morph_z_speed;

    // move vector
    if (sop->morph_wall_point == nullptr)
        return;

    // place at correct x,y offset from center
    auto pos = sop->pmid - sop->morph_off;

    // move it from last x,y
    DVector2 mpos = pos + sop->morph_ang.ToVector() * sop->morph_speed;

    // save x,y back as offset info
    sop->morph_off = sop->pmid - mpos;

    // bound check radius
    if ((sop->pmid - mpos).Length() > sop->morph_dist_max)
    {
        // go in the other direction
        sop->morph_ang = sop->morph_ang + DAngle180;

        // back it up and save it off
        mpos = pos + sop->morph_ang.ToVector() * sop->morph_speed;
        sop->morph_off = sop->pmid - mpos;
    }

    if ((RANDOM_P2(1024<<4)>>4) < sop->morph_rand_freq)
        sop->morph_ang = RandomAngle();

    // move x,y point "just like in build"
    dragpoint(sop->morph_wall_point, mpos);

    // bound the Z
    double florz = sop->op_main_sector->floorz;

    const double MORPH_FLOOR_ZRANGE = 300;

    if (sop->morph_z > MORPH_FLOOR_ZRANGE)
    {
        sop->morph_z = MORPH_FLOOR_ZRANGE;
        sop->morph_z_speed *= -1;
    }
    else if (sop->morph_z < -MORPH_FLOOR_ZRANGE)
    {
        sop->morph_z = -MORPH_FLOOR_ZRANGE;
        sop->morph_z_speed *= -1;
    }

    for (sectp = sop->sectp, j = 0; *sectp; sectp++, j++)
    {
        if ((*sectp)->hasU() &&
            ((*sectp)->flags & SECTFU_SO_SLOPE_CEILING_TO_POINT))
        {
            alignflorslope(*sectp, DVector3(mpos, florz + sop->morph_z));
        }
    }
}

//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------

void SOBJ_AlignFloorToPoint(SECTOR_OBJECT* sop, const DVector3& pos)
{
    sectortype* *sectp;
    int j;

    for (sectp = sop->sectp, j = 0; *sectp; sectp++, j++)
    {
        if ((*sectp)->hasU() &&
            ((*sectp)->flags & SECTFU_SO_SLOPE_CEILING_TO_POINT))
        {
            alignflorslope(*sectp, pos);
        }
    }
}

//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------

void SOBJ_AlignCeilingToPoint(SECTOR_OBJECT* sop, const DVector3& pos)
{
    sectortype* *sectp;
    int j;

    for (sectp = sop->sectp, j = 0; *sectp; sectp++, j++)
    {
        if ((*sectp)->hasU() &&
            ((*sectp)->flags & SECTFU_SO_SLOPE_CEILING_TO_POINT))
        {
            alignceilslope(*sectp, pos);
        }
    }
}

//---------------------------------------------------------------------------
//
// moves center point around and aligns slope
//
//---------------------------------------------------------------------------

void SpikeFloor(SECTOR_OBJECT* sop)
{
    // z direction
    ASSERT(sop->op_main_sector != nullptr);
    sop->morph_z -= sop->morph_z_speed;

    // move vector
    if (sop->morph_wall_point == nullptr)
        return;

    // place at correct x,y offset from center
    auto pos = sop->pmid - sop->morph_off;

    // bound the Z

    const double MORPH_FLOOR_ZRANGE = 300;

    if (sop->morph_z > MORPH_FLOOR_ZRANGE)
    {
        sop->morph_z = MORPH_FLOOR_ZRANGE;
        sop->morph_z_speed *= -1;
    }
    else if (sop->morph_z < -MORPH_FLOOR_ZRANGE)
    {
        sop->morph_z = -MORPH_FLOOR_ZRANGE;
        sop->morph_z_speed *= -1;
    }
    pos.Z = sop->op_main_sector->floorz + sop->morph_z;

    SOBJ_AlignFloorToPoint(sop, pos);
}

//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------

#include "saveable.h"

static saveable_code saveable_morph_code[] =
{
    SAVE_CODE(ScaleSectorObject),
    SAVE_CODE(MorphTornado),
    SAVE_CODE(MorphFloor),
    SAVE_CODE(SpikeFloor),
};

saveable_module saveable_morph =
{
    // code
    saveable_morph_code,
    SIZ(saveable_morph_code),

    // data
    nullptr,0
};
END_SW_NS
