//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 sirlemonhead, Nuke.YKT
This file is part of PCExhumed.
PCExhumed is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.
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.
*/
//-------------------------------------------------------------------------
#include "ns.h"
#include "aistuff.h"
#include "exhumed.h"
#include "engine.h"
#include "sequence.h"
#include "sound.h"
#include "player.h"
#include <assert.h>

BEGIN_PS_NS

struct Rex
{
    short nHealth;
    short nFrame;
    short nAction;
    short nSprite;
    short nTarget;
    short nCount;
    short nChannel;
};


TArray<Rex> RexList;

static actionSeq RexSeq[] = {
    {29, 0},
    {0,  0},
    {0,  0},
    {37, 0},
    {9,  0},
    {18, 0},
    {27, 1},
    {28, 1}
};

FSerializer& Serialize(FSerializer& arc, const char* keyname, Rex& w, Rex* def)
{
    if (arc.BeginObject(keyname))
    {
        arc("health", w.nHealth)
            ("frame", w.nFrame)
            ("action", w.nAction)
            ("sprite", w.nSprite)
            ("target", w.nTarget)
            ("count", w.nCount)
            ("channel", w.nChannel)
            .EndObject();
    }
    return arc;
}

void SerializeRex(FSerializer& arc)
{
    arc("rex", RexList);
}

void InitRexs()
{
    RexList.Clear();
}

int BuildRex(short nSprite, int x, int y, int z, short nSector, short nAngle, int nChannel)
{
    int nRex = RexList.Reserve(1);

    if (nSprite == -1)
    {
        nSprite = insertsprite(nSector, 119);
    }
    else
    {
        changespritestat(nSprite, 119);
        x = sprite[nSprite].x;
        y = sprite[nSprite].y;
        z = sector[sprite[nSprite].sectnum].floorz;
        nAngle = sprite[nSprite].ang;
    }

    assert(nSprite >= 0 && nSprite < kMaxSprites);

    sprite[nSprite].x = x;
    sprite[nSprite].y = y;
    sprite[nSprite].z = z;
    sprite[nSprite].cstat = 0x101;
    sprite[nSprite].clipdist = 80;
    sprite[nSprite].shade = -12;
    sprite[nSprite].xrepeat = 64;
    sprite[nSprite].yrepeat = 64;
    sprite[nSprite].picnum = 1;
    sprite[nSprite].pal = sector[sprite[nSprite].sectnum].ceilingpal;
    sprite[nSprite].xoffset = 0;
    sprite[nSprite].yoffset = 0;
    sprite[nSprite].ang = nAngle;
    sprite[nSprite].xvel = 0;
    sprite[nSprite].yvel = 0;
    sprite[nSprite].zvel = 0;
    sprite[nSprite].lotag = runlist_HeadRun() + 1;
    sprite[nSprite].extra = -1;
    sprite[nSprite].hitag = 0;

    GrabTimeSlot(3);

    RexList[nRex].nAction = 0;
    RexList[nRex].nHealth = 4000;
    RexList[nRex].nFrame  = 0;
    RexList[nRex].nSprite = nSprite;
    RexList[nRex].nTarget = -1;
    RexList[nRex].nCount = 0;

    RexList[nRex].nChannel = nChannel;

    sprite[nSprite].owner = runlist_AddRunRec(sprite[nSprite].lotag - 1, nRex | 0x180000);

    // this isn't stored anywhere.
    runlist_AddRunRec(NewRun, nRex | 0x180000);

    nCreaturesTotal++;

    return nRex | 0x180000;
}

void FuncRex(int a, int nDamage, int nRun)
{
    short nRex = RunData[nRun].nVal;
    assert(nRex >= 0 && nRex < (int)RexList.Size());

    short nAction = RexList[nRex].nAction;
    short nSprite = RexList[nRex].nSprite;

    bool bVal = false;

    int nMessage = a & kMessageMask;

    switch (nMessage)
    {
        default:
        {
            Printf("unknown msg %d for Rex\n", nMessage);
            return;
        }

        case 0xA0000:
        {
            if (nAction == 5)
            {
                nDamage = runlist_CheckRadialDamage(nSprite);
            }
            // fall through to case 0x80000
            fallthrough__;
        }

        case 0x80000:
        {
            if (nDamage)
            {
                short nTarget = a & 0xFFFF;
                if (nTarget >= 0 && sprite[nTarget].statnum == 100)
                {
                    RexList[nRex].nTarget = nTarget;
                }

                if (RexList[nRex].nAction == 5 && RexList[nRex].nHealth > 0)
                {
                    RexList[nRex].nHealth -= nDamage;

                    if (RexList[nRex].nHealth <= 0)
                    {  
                        sprite[nSprite].xvel = 0;
                        sprite[nSprite].yvel = 0;
                        sprite[nSprite].zvel = 0;
                        sprite[nSprite].cstat &= 0xFEFE;
                        
                        RexList[nRex].nHealth = 0;
                        
                        nCreaturesKilled++;

                        if (nAction < 6)
                        {
                            RexList[nRex].nAction = 6;
                            RexList[nRex].nFrame  = 0;
                        }
                    }
                }
            }
            return;
        }

        case 0x90000:
        {
            seq_PlotSequence(a & 0xFFFF, SeqOffsets[kSeqRex] + RexSeq[nAction].a, RexList[nRex].nFrame, RexSeq[nAction].b);
            return;
        }

        case 0x20000:
        {
            Gravity(nSprite);

            int nSeq = SeqOffsets[kSeqRex] + RexSeq[nAction].a;

            sprite[nSprite].picnum = seq_GetSeqPicnum2(nSeq, RexList[nRex].nFrame);

            int ecx = 2;

            if (nAction != 2) {
                ecx = 1;
            }

            // moves the mouth open and closed as it's idle?
            while (--ecx != -1)
            {
                seq_MoveSequence(nSprite, nSeq, RexList[nRex].nFrame);

                RexList[nRex].nFrame++;
                if (RexList[nRex].nFrame >= SeqSize[nSeq])
                {
                    RexList[nRex].nFrame = 0;
                    bVal = true;
                }
            }

            int nFlag = FrameFlag[SeqBase[nSeq] + RexList[nRex].nFrame];

            short nTarget = RexList[nRex].nTarget;

            switch (nAction)
            {
                default:
                    return;

                case 0:
                {
                    if (!RexList[nRex].nCount)
                    {
                        if ((nRex & 0x1F) == (totalmoves & 0x1F))
                        {
                            if (nTarget < 0)
                            {
                                short nAngle = sprite[nSprite].ang; // make backup of this variable
                                RexList[nRex].nTarget = FindPlayer(nSprite, 60);
                                sprite[nSprite].ang = nAngle;
                            }
                            else
                            {
                                RexList[nRex].nCount = 60;
                            }
                        }
                    }
                    else
                    {
                        RexList[nRex].nCount--;
                        if (RexList[nRex].nCount <= 0)
                        {
                            RexList[nRex].nAction = 1;
                            RexList[nRex].nFrame  = 0;

                            sprite[nSprite].xvel = bcos(sprite[nSprite].ang, -2);
                            sprite[nSprite].yvel = bsin(sprite[nSprite].ang, -2);

                            D3PlayFX(StaticSound[kSound48], nSprite);

                            RexList[nRex].nCount = 30;
                        }
                    }

                    return;
                }

                case 1:
                {
                    if (RexList[nRex].nCount > 0)
                    {
                        RexList[nRex].nCount--;
                    }

                    if ((nRex & 0x0F) == (totalmoves & 0x0F))
                    {
                        if (!RandomSize(1))
                        {
                            RexList[nRex].nAction = 5;
                            RexList[nRex].nFrame  = 0;
                            sprite[nSprite].xvel = 0;
                            sprite[nSprite].yvel = 0;
                            return;
                        }
                        else
                        {
                            if (((PlotCourseToSprite(nSprite, nTarget) >> 8) >= 60) || RexList[nRex].nCount > 0)
                            {
                                int nAngle = sprite[nSprite].ang & 0xFFF8;
                                sprite[nSprite].xvel = bcos(nAngle, -2);
                                sprite[nSprite].yvel = bsin(nAngle, -2);
                            }
                            else
                            {
                                RexList[nRex].nAction = 2;
                                RexList[nRex].nCount = 240;
                                D3PlayFX(StaticSound[kSound48], nSprite);
                                RexList[nRex].nFrame = 0;
                                return;
                            }
                        }
                    }

                    int nMov = MoveCreatureWithCaution(nSprite);

                    switch ((nMov & 0xC000))
                    {
                        case 0xC000:
                            {
                            if ((nMov & 0x3FFF) == nTarget)
                            {
                                PlotCourseToSprite(nSprite, nTarget);
                                RexList[nRex].nAction = 4;
                                RexList[nRex].nFrame  = 0;
                                break;
                            }
                            fallthrough__;
                        }
                        case 0x8000:
                        {
                            sprite[nSprite].ang = (sprite[nSprite].ang + 256) & kAngleMask;
                            sprite[nSprite].xvel = bcos(sprite[nSprite].ang, -2);
                            sprite[nSprite].yvel = bsin(sprite[nSprite].ang, -2);
                            RexList[nRex].nAction = 1;
                            RexList[nRex].nFrame  = 0;
                            nAction = 1;
                            break;
                    }
                    }

                    break;
                }

                case 2:
                {
                    RexList[nRex].nCount--;
                    if (RexList[nRex].nCount > 0)
                    {
                        PlotCourseToSprite(nSprite, nTarget);

                        sprite[nSprite].xvel = bcos(sprite[nSprite].ang, -1);
                        sprite[nSprite].yvel = bsin(sprite[nSprite].ang, -1);

                        int nMov = MoveCreatureWithCaution(nSprite);

                        switch (nMov & 0xC000)
                        {
                        case 0x8000:
                            {
                            SetQuake(nSprite, 25);
                            RexList[nRex].nCount = 60;

                            sprite[nSprite].ang = (sprite[nSprite].ang + 256) & kAngleMask;
                            sprite[nSprite].xvel = bcos(sprite[nSprite].ang, -2);
                            sprite[nSprite].yvel = bsin(sprite[nSprite].ang, -2);
                            RexList[nRex].nAction = 1;
                                RexList[nRex].nFrame  = 0;
                            nAction = 1;
                            break;
                            }
                            case 0xC000:
                            {
                            RexList[nRex].nAction = 3;
                                RexList[nRex].nFrame  = 0;

                                short nSprite2 = nMov & 0x3FFF;

                            if (sprite[nSprite2].statnum && sprite[nSprite2].statnum < 107)
                            {
                                short nAngle = sprite[nSprite].ang;

                                runlist_DamageEnemy(nSprite2, nSprite, 15);

                                    int xVel = bcos(nAngle) * 15;
                                    int yVel = bsin(nAngle) * 15;

                                if (sprite[nSprite2].statnum == 100)
                                {
                                    short nPlayer = GetPlayerFromSprite(nSprite2);
                                        nXDamage[nPlayer] += (xVel << 4);
                                        nYDamage[nPlayer] += (yVel << 4);
                                    sprite[nSprite2].zvel = -3584;
                                }
                                else
                                {
                                        sprite[nSprite2].xvel += (xVel >> 3);
                                        sprite[nSprite2].yvel += (yVel >> 3);
                                    sprite[nSprite2].zvel = -2880;
                                }
                            }

                            RexList[nRex].nCount >>= 2;
                            return;
                        }
                    }
                    }
                    else
                    {
                        RexList[nRex].nAction = 1;
                        RexList[nRex].nFrame  = 0;
                        RexList[nRex].nCount = 90;
                    }

                    return;
                }

                case 3:
                {
                    if (bVal)
                    {
                        RexList[nRex].nAction = 2;
                    }
                    return;
                }

                case 4:
                {
                    if (nTarget != -1)
                    {
                        if (PlotCourseToSprite(nSprite, nTarget) < 768)
                        {
                            if (nFlag & 0x80)
                            {
                                runlist_DamageEnemy(nTarget, nSprite, 15);
                            }

                            break;
                        }
                    }

                    RexList[nRex].nAction = 1;
                    break;
                }

                case 5:
                {
                    if (bVal)
                    {
                        RexList[nRex].nAction = 1;
                        RexList[nRex].nCount = 15;
                    }
                    return;
                }

                case 6:
                {
                    if (bVal)
                    {
                        RexList[nRex].nAction = 7;
                        RexList[nRex].nFrame  = 0;
                        runlist_ChangeChannel(RexList[nRex].nChannel, 1);
                    }
                    return;
                }

                case 7:
                {
                    sprite[nSprite].cstat &= 0xFEFE;
                    return;
                }
            }

            // break-ed
            if (nAction > 0)
            {
                if ((nTarget != -1) && (!(sprite[nTarget].cstat & 0x101)))
                {
                    RexList[nRex].nAction = 0;
                    RexList[nRex].nFrame  = 0;
                    RexList[nRex].nCount = 0;
                    RexList[nRex].nTarget = -1;
                    sprite[nSprite].xvel = 0;
                    sprite[nSprite].yvel = 0;
                }
            }
            return;
        }
    }
}
END_PS_NS
