//-------------------------------------------------------------------------
/*
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 "spider.h"
#include "exhumed.h"
#include "engine.h"
#include "runlist.h"
#include "move.h"
#include "sequence.h"
#include "random.h"
#include "sound.h"
#include "trigdat.h"
#include <assert.h>

BEGIN_PS_NS

short SpiderCount = 0;

#define kMaxSpiders		100

struct Spider
{
    short nHealth;
    short nFrame;
    short nAction;
    short nSprite;
    short nTarget;
    short nRun;
};

Spider SpiderList[kMaxSpiders];

static actionSeq ActionSeq[] = {
    {16, 0},
    {8,  0},
    {32, 0},
    {24, 0},
    {0,  0},
    {40, 1},
    {41, 1}
};

static SavegameHelper sgh("spider",
    SV(SpiderCount),
    SA(SpiderList),
    nullptr);

void InitSpider()
{
    SpiderCount = 0;
}

int BuildSpider(int nSprite, int x, int y, int z, short nSector, int nAngle)
{
    int nSpider = SpiderCount++;
    if (nSpider >= kMaxSpiders) {
        return -1;
    }

    if (nSprite == -1)
    {
        nSprite = insertsprite(nSector, 99);
    }
    else
    {
        changespritestat(nSprite, 99);
        x = sprite[nSprite].x;
        y = sprite[nSprite].y;
        z = sprite[nSprite].z;
        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].shade = -12;
    sprite[nSprite].clipdist = 15;
    sprite[nSprite].xvel = 0;
    sprite[nSprite].yvel = 0;
    sprite[nSprite].zvel = 0;
    sprite[nSprite].xrepeat = 40;
    sprite[nSprite].yrepeat = 40;
    sprite[nSprite].pal = sector[sprite[nSprite].sectnum].ceilingpal;
    sprite[nSprite].xoffset = 0;
    sprite[nSprite].yoffset = 0;
    sprite[nSprite].ang = nAngle;
    sprite[nSprite].picnum = 1;
    sprite[nSprite].hitag = 0;
    sprite[nSprite].lotag = runlist_HeadRun() + 1;
    sprite[nSprite].extra = -1;

    //	GrabTimeSlot(3);

    SpiderList[nSpider].nAction = 0;
    SpiderList[nSpider].nFrame = 0;
    SpiderList[nSpider].nSprite = nSprite;
    SpiderList[nSpider].nTarget = -1;
    SpiderList[nSpider].nHealth = 160;

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

    SpiderList[nSpider].nRun = runlist_AddRunRec(NewRun, nSpider | 0xC0000);

    nCreaturesLeft++;

    return nSpider | 0xC0000;
}

void FuncSpider(int a, int nDamage, int nRun)
{
    short nSpider = RunData[nRun].nVal;
    assert(nSpider >= 0 && nSpider < kMaxSpiders);

    int nVel = 0;

    short nSprite = SpiderList[nSpider].nSprite;
    short nAction = SpiderList[nSpider].nAction;

    int nMessage = a & kMessageMask;

    switch (nMessage)
    {
        default:
        {
            DebugOut("unknown msg %d for Spider\n", nMessage);
            break;
        }

        case 0x20000:
        {
            nVel = 6;

            if (SpiderList[nSpider].nHealth)
            {
                if (sprite[nSprite].cstat & 8)
                {
                    sprite[nSprite].z = sector[sprite[nSprite].sectnum].ceilingz + GetSpriteHeight(nSprite);
                }
                else
                {
                    Gravity(nSprite);
                }
            }

            int nSeq = SeqOffsets[kSeqSpider] + ActionSeq[nAction].a;

            sprite[nSprite].picnum = seq_GetSeqPicnum2(nSeq, SpiderList[nSpider].nFrame);

            seq_MoveSequence(nSprite, nSeq, SpiderList[nSpider].nFrame);

            int nFrameFlag = FrameFlag[SeqBase[nSeq] + SpiderList[nSpider].nFrame];

            SpiderList[nSpider].nFrame++;
            if (SpiderList[nSpider].nFrame >= SeqSize[nSeq]) {
                SpiderList[nSpider].nFrame = 0;
            }

            short nTarget = SpiderList[nSpider].nTarget;

            if (nTarget <= -1 || sprite[nTarget].cstat & 0x101)
            {
                switch (nAction)
                {
                default:
                    return;

                case 0:
                {
                    if ((nSpider & 0x1F) == (totalmoves & 0x1F))
                    {
                        if (nTarget < 0) {
                            nTarget = FindPlayer(nSprite, 100);
                        }

                        if (nTarget >= 0)
                        {
                            SpiderList[nSpider].nAction = 1;
                                SpiderList[nSpider].nFrame = 0;
                            SpiderList[nSpider].nTarget = nTarget;

                            sprite[nSprite].xvel = Cos(sprite[nSprite].ang);
                            sprite[nSprite].yvel = sintable[sprite[nSprite].ang]; // NOTE - not angle masking here in original code
                            return;
                        }
                    }

                    break;
                }
                case 1:
                {
                    if (nTarget >= 0) {
                            nVel++;
                    }
    goto case_3;
                    break;
                }
                case 4:
                {
                        if (!SpiderList[nSpider].nFrame)
                    {
                            SpiderList[nSpider].nFrame  = 0;
                        SpiderList[nSpider].nAction = 1;
                    }
                    fallthrough__;
                }
                case 3:
                {
    case_3:
                    short nSector = sprite[nSprite].sectnum;

                    if (sprite[nSprite].cstat & 8)
                    {
                        sprite[nSprite].zvel = 0;
                        sprite[nSprite].z = sector[nSector].ceilingz + (tilesiz[sprite[nSprite].picnum].y << 5);

                        if (sector[nSector].ceilingstat & 1)
                        {
                            sprite[nSprite].cstat ^= 8;
                            sprite[nSprite].zvel = 1;

                            SpiderList[nSpider].nAction = 3;
                                SpiderList[nSpider].nFrame  = 0;
                        }
                    }

                    if ((totalmoves & 0x1F) == (nSpider & 0x1F))
                    {
                        PlotCourseToSprite(nSprite, nTarget);

                        if (RandomSize(3))
                        {
                            sprite[nSprite].xvel = Cos(sprite[nSprite].ang);
                            sprite[nSprite].yvel = Sin(sprite[nSprite].ang);
                        }
                        else
                        {
                            sprite[nSprite].xvel = 0;
                            sprite[nSprite].yvel = 0;
                        }

                        if (SpiderList[nSpider].nAction == 1 && RandomBit())
                        {
                            if (sprite[nSprite].cstat & 8)
                            {
                                    sprite[nSprite].cstat ^= 8;
                                sprite[nSprite].zvel = 1;
                                sprite[nSprite].z = sector[nSector].ceilingz + GetSpriteHeight(nSprite);
                            }
                            else
                            {
                                sprite[nSprite].zvel = -5120;
                            }

                            SpiderList[nSpider].nAction = 3;
                                SpiderList[nSpider].nFrame = 0;

                            if (!RandomSize(3)) {
                                D3PlayFX(StaticSound[kSound29], nSprite);
                            }
                        }
                    }
                    break;
                }
                case 5:
                {
                        if (!SpiderList[nSpider].nFrame)
                    {
                        runlist_DoSubRunRec(sprite[nSprite].owner);
                        runlist_FreeRun(sprite[nSprite].lotag - 1);
                            runlist_SubRunRec(SpiderList[nSpider].nRun);
                        sprite[nSprite].cstat = 0x8000;
                        mydeletesprite(nSprite);
                    }
                    return;
                }
                case 2:
                {
                    if (nTarget != -1)
                    {
                        if (nFrameFlag & 0x80)
                        {
                            runlist_DamageEnemy(nTarget, nSprite, 3);
                            D3PlayFX(StaticSound[kSound38], nSprite);
                        }

                        if (PlotCourseToSprite(nSprite, nTarget) < 1024) {
                            return;
                        }

                        SpiderList[nSpider].nAction = 1;
                    }
                    else
                    {
                        SpiderList[nSpider].nAction = 0;
                        sprite[nSprite].xvel = 0;
                        sprite[nSprite].yvel = 0;
                    }

                        SpiderList[nSpider].nFrame = 0;
                    break;
                }
                }
            }
            else
            {
                SpiderList[nSpider].nTarget = -1;
                SpiderList[nSpider].nAction = 0;
                SpiderList[nSpider].nFrame = 0;

                sprite[nSprite].xvel = 0;
                sprite[nSprite].yvel = 0;
            }

            int nMov = movesprite(nSprite, sprite[nSprite].xvel << nVel, sprite[nSprite].yvel << nVel, sprite[nSprite].zvel, 1280, -1280, CLIPMASK0);

            if (!nMov)
                return;

            if (nMov & 0x10000
                && sprite[nSprite].zvel < 0
                && (hihit & 0xC000) != 0xC000
                && !((sector[sprite[nSprite].sectnum].ceilingstat) & 1))
            {
                sprite[nSprite].cstat |= 8;
                sprite[nSprite].z = GetSpriteHeight(nSprite) + sector[sprite[nSprite].sectnum].ceilingz;
                sprite[nSprite].zvel = 0;

                SpiderList[nSpider].nAction = 1;
                SpiderList[nSpider].nFrame = 0;
                return;
            }
            else
            {
                switch (nMov & 0xC000)
                {
                case 0x8000:
                {
                    sprite[nSprite].ang = (sprite[nSprite].ang + 256) & 0x7EF;
                    sprite[nSprite].xvel = Cos(sprite[nSprite].ang);
                    sprite[nSprite].yvel = Sin(sprite[nSprite].ang);
                    return;
                }
                case 0xC000:
                {
                    if ((nMov & 0x3FFF) == nTarget)
                    {
                        int nAng = getangle(sprite[nTarget].x - sprite[nSprite].x, sprite[nTarget].y - sprite[nSprite].y);
                        if (AngleDiff(sprite[nSprite].ang, nAng) < 64)
                        {
                            SpiderList[nSpider].nAction = 2;
                                SpiderList[nSpider].nFrame = 0;
                        }
                    }
                    return;
                }
                default:
                    break;
                }

                if (SpiderList[nSpider].nAction == 3)
                {
                    SpiderList[nSpider].nAction = 1;
                    SpiderList[nSpider].nFrame = 0;
                }
                return;
            }

            return;
        }

        case 0x90000:
        {
            seq_PlotSequence(a & 0xFFFF, SeqOffsets[kSeqSpider] + ActionSeq[nAction].a, SpiderList[nSpider].nFrame, ActionSeq[nAction].b);
            break;
        }

        case 0xA0000:
        {
            if (SpiderList[nSpider].nHealth <= 0)
                return;

            nDamage = runlist_CheckRadialDamage(nSprite);
            // fall through
            fallthrough__;
        }

        case 0x80000:
        {
            if (!nDamage)
                return;

            short nTarget = a & 0xFFFF;

            SpiderList[nSpider].nHealth -= nDamage;
            if (SpiderList[nSpider].nHealth > 0)
            {
                /*
                NOTE:
                    nTarget check was added, but should we return if it's invalid instead
                    or should code below (action set, b set) happen?
                    Other AI doesn't show consistency in this regard (see Scorpion code)
                */
                if (nTarget > -1 && sprite[nTarget].statnum == 100)
                {
                    SpiderList[nSpider].nTarget = nTarget;
                }

                SpiderList[nSpider].nAction = 4;
                SpiderList[nSpider].nFrame  = 0;
            }
            else
            {
                // creature is dead, make some chunks
                SpiderList[nSpider].nHealth = 0;
                SpiderList[nSpider].nAction = 5;
                SpiderList[nSpider].nFrame  = 0;

                sprite[nSprite].cstat &= 0xFEFE;

                nCreaturesLeft--;

                for (int i = 0; i < 7; i++)
                {
                    BuildCreatureChunk(nSprite, seq_GetSeqPicnum(kSeqSpider, i + 41, 0));
                }
            }

            return;
        }
        }
}
END_PS_NS
