code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT
Copyright (C) NoOne

This file is part of NBlood.

NBlood 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.
*/
//-------------------------------------------------------------------------
#ifdef NOONE_EXTENSIONS
#pragma once
#include "nnexts.h"
#include "ai.h"
#include "eventq.h"

#define kGenDudeDefaultSeq 11520
#define kGenDudeMaxSlaves 7
#define kGenDudeTransformStatus -222
#define kGenDudeUpdTimeRate 10
#define kGenDudeMaxMeleeDist 2048
#define kGenDudeMinDispesion 200
#define kGenDudeMaxDispersion 3500
#define kGenDudeKlabsAng 56

enum {
kGenDudeSeqIdleL            = 0,
kGenDudeSeqDeathDefault     = 1,
kGenDudeSeqDeathExplode     = 2,
kGenDudeSeqBurning          = 3,
kGenDudeSeqElectocuted      = 4,
kGenDudeSeqRecoil           = 5,
kGenDudeSeqAttackNormalL    = 6,
kGenDudeSeqAttackThrow      = 7,
kGenDudeSeqAttackNormalDW   = 8,
kGenDudeSeqMoveL            = 9,
kGenDudeSeqAttackPunch      = 10,
kGenDudeSeqReserved1        = 11,
kGenDudeSeqReserved2        = 12,
kGenDudeSeqMoveW            = 13,
kGenDudeSeqMoveD            = 14,
kGenDudeSeqDeathBurn1       = 15,
kGenDudeSeqDeathBurn2       = 16,
kGenDudeSeqIdleW            = 17,
kGenDudeSeqTransform        = 18,
kGenDudeSeqReserved3        = 19,
kGenDudeSeqReserved4        = 20,
kGenDudeSeqReserved5        = 21,
kGenDudeSeqReserved6        = 22,
kGenDudeSeqReserved7        = 23,
kGenDudeSeqReserved8        = 24,
kGenDudeSeqMax                  ,
};

enum {
kGenDudeSndTargetSpot       = 0,
kGenDudeSndGotHit           = 1,
kGenDudeSndDeathNormal      = 2,
kGenDudeSndBurning          = 3,
kGenDudeSndDeathExplode     = 4,
kGenDudeSndTargetDead       = 5,
kGenDudeSndChasing          = 6,
kGenDudeSndAttackNormal     = 7,
kGenDudeSndAttackThrow      = 8,
kGenDudeSndAttackMelee      = 9,
kGenDudeSndTransforming     = 10,
kGenDudeSndMax                  ,
};

enum {
kGenDudePropertyAll         = 0,
kGenDudePropertyWeapon      = 1,
kGenDudePropertyDmgScale    = 2,
kGenDudePropertyMass        = 3,
kGenDudePropertyAttack      = 4,
kGenDudePropertyStates      = 5,
kGenDudePropertyLeech       = 6,
kGenDudePropertySlaves      = 7,
kGenDudePropertySpriteSize  = 8,
kGenDudePropertyInitVals    = 9,
kGenDudePropertyMax            ,
};

enum {
kGenDudeWeaponNone          = -1,
kGenDudeWeaponHitscan       = 0,
kGenDudeWeaponMissile       = 1,
kGenDudeWeaponThrow         = 2,
kGenDudeWeaponSummon        = 3,
kGenDudeWeaponKamikaze      = 4,
kGenDudeWeaponMax              ,
};

extern AISTATE genDudeIdleL;
extern AISTATE genDudeIdleW;
extern AISTATE genDudeSearchL;
extern AISTATE genDudeSearchW;
extern AISTATE genDudeGotoL;
extern AISTATE genDudeGotoW;
extern AISTATE genDudeDodgeL;
extern AISTATE genDudeDodgeD;
extern AISTATE genDudeDodgeW;
extern AISTATE genDudeDodgeShortL;
extern AISTATE genDudeDodgeShortD;
extern AISTATE genDudeDodgeShortW;
extern AISTATE genDudeDodgeShorterL;
extern AISTATE genDudeDodgeShorterD;
extern AISTATE genDudeDodgeShorterW;
extern AISTATE genDudeChaseL;
extern AISTATE genDudeChaseD;
extern AISTATE genDudeChaseW;
extern AISTATE genDudeFireL;
extern AISTATE genDudeFireD;
extern AISTATE genDudeFireW;
extern AISTATE genDudeRecoilL;
extern AISTATE genDudeRecoilD;
extern AISTATE genDudeRecoilW;
extern AISTATE genDudeThrow;
extern AISTATE genDudeThrow2;
extern AISTATE genDudePunch;
extern AISTATE genDudeRecoilTesla;
extern AISTATE genDudeSearchNoWalkL;
extern AISTATE genDudeSearchNoWalkW;
extern AISTATE genDudeChaseNoWalkL;
extern AISTATE genDudeChaseNoWalkD;
extern AISTATE genDudeChaseNoWalkW;

struct GENDUDESND
{
    int defaultSndId;
    int randomRange;
    int sndIdOffset;  // relative to data3
    bool aiPlaySound; // false = sfxStart3DSound();
    bool interruptable;
};

extern GENDUDESND gCustomDudeSnd[];

// temporary, until normal DUDEEXTRA gets refactored
struct GENDUDEEXTRA {
    unsigned short initVals[3];             // xrepeat, yrepeat, clipdist
    unsigned short availDeaths[kDamageMax]; // list of seqs with deaths for each damage type
    unsigned int moveSpeed;
    unsigned int fireDist;                  // counts from sprite size
    unsigned int throwDist;                 // counts from sprite size
    unsigned short curWeapon;               // data1 duplicate to avoid potential problems when changing data dynamically
    unsigned short weaponType;
    unsigned short baseDispersion;
    unsigned short slaveCount;              // how many dudes is summoned
    signed short nLifeLeech;                // spritenum of dropped dude's leech
    signed short slave[kGenDudeMaxSlaves];  // index of the ones dude is summon
    signed short dmgControl[kDamageMax];    // depends of current weapon, drop armor item, sprite yrepeat and surface type
    bool updReq[kGenDudePropertyMax];       // update requests
    bool sndPlaying;                        // indicate if sound of AISTATE currently playing
    bool forcePunch;                        // indicate if there is no fire trigger in punch state seq
    bool isMelee;
    bool canBurn;                           // can turn in Burning dude or not
    bool canElectrocute;
    bool canAttack;
    bool canRecoil;
    bool canWalk;
    bool canDuck;
    bool canSwim;
    bool canFly;
};

extern GENDUDEEXTRA gGenDudeExtra[kMaxSprites];

GENDUDEEXTRA* genDudeExtra(spritetype* pGenDude);
XSPRITE* getNextIncarnation(XSPRITE* pXSprite);
void killDudeLeech(spritetype* pLeech);
void removeLeech(spritetype* pLeech, bool delSprite = true);
void removeDudeStuff(spritetype* pSprite);
spritetype* leechIsDropped(spritetype* pSprite);
bool spriteIsUnderwater(spritetype* pSprite, bool oldWay = false);
bool playGenDudeSound(spritetype* pSprite, int mode);
void aiGenDudeMoveForward(spritetype* pSprite, XSPRITE* pXSprite);
void aiGenDudeChooseDirection(spritetype* pSprite, XSPRITE* pXSprite, int a3, int aXvel = -1, int aYvel = -1);
void aiGenDudeNewState(spritetype* pSprite, AISTATE* pAIState);
int getGenDudeMoveSpeed(spritetype* pSprite, int which, bool mul, bool shift);
int checkAttackState(spritetype* pSprite, XSPRITE* pXSprite);
bool doExplosion(spritetype* pSprite, int nType);
spritetype* genDudeSpawn(XSPRITE* pXSource, spritetype* pSprite, int nDist);
void genDudeTransform(spritetype* pSprite);
void dudeLeechOperate(spritetype* pSprite, XSPRITE* pXSprite, EVENT a3);
int getDodgeChance(spritetype* pSprite);
int getRecoilChance(spritetype* pSprite);
bool dudeIsMelee(XSPRITE* pXSprite);
void updateTargetOfSlaves(spritetype* pSprite);
void updateTargetOfLeech(spritetype* pSprite);
bool canSwim(spritetype* pSprite);
bool canDuck(spritetype* pSprite);
bool canWalk(spritetype* pSprite);
short inDodge(AISTATE* aiState);
bool inIdle(AISTATE* aiState);
bool inAttack(AISTATE* aiState);
short inRecoil(AISTATE* aiState);
short inSearch(AISTATE* aiState);
short inChase(AISTATE* aiState);
short inDuck(AISTATE* aiState);
int genDudeSeqStartId(XSPRITE* pXSprite);
int getRangeAttackDist(spritetype* pSprite, int minDist = 3000, int maxDist = 80000);
int getDispersionModifier(spritetype* pSprite, int minDisp, int maxDisp);
void scaleDamage(XSPRITE* pXSprite);
bool genDudePrepare(spritetype* pSprite, int propId);
void genDudeUpdate(spritetype* pSprite);
bool genDudeAdjustSlope(spritetype* pSprite, XSPRITE* pXSprite, int dist, int weaponType, int by = 64);
void genDudePostDeath(spritetype* pSprite, DAMAGE_TYPE damageType, int damage);
void aiGenDudeInitSprite(spritetype* pSprite, XSPRITE* pXSprite);
#endif

repo_name: orochi/nblood

license: mit


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT

This file is part of NBlood.

NBlood 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 "compat.h"
#include "build.h"
#include "pragmas.h"
#include "mmulti.h"
#include "common_game.h"

#include "actor.h"
#include "ai.h"
#include "aizomba.h"
#include "blood.h"
#include "db.h"
#include "dude.h"
#include "eventq.h"
#include "levels.h"
#include "player.h"
#include "seq.h"
#include "sfx.h"
#include "trig.h"

static void HackSeqCallback(int, int);
static void StandSeqCallback(int, int);
static void thinkSearch(spritetype *, XSPRITE *);
static void thinkGoto(spritetype *, XSPRITE *);
static void thinkChase(spritetype *, XSPRITE *);
static void thinkPonder(spritetype *, XSPRITE *);
static void myThinkTarget(spritetype *, XSPRITE *);
static void myThinkSearch(spritetype *, XSPRITE *);
static void entryEZombie(spritetype *, XSPRITE *);
static void entryAIdle(spritetype *, XSPRITE *);
static void entryEStand(spritetype *, XSPRITE *);

static int nHackClient = seqRegisterClient(HackSeqCallback);
static int nStandClient = seqRegisterClient(StandSeqCallback);

AISTATE zombieAIdle = { kAiStateIdle, 0, -1, 0, entryAIdle, NULL, aiThinkTarget, NULL };
AISTATE zombieAChase = { kAiStateChase, 8, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
AISTATE zombieAPonder = { kAiStateOther, 0, -1, 0, NULL, aiMoveTurn, thinkPonder, NULL };
AISTATE zombieAGoto = { kAiStateMove, 8, -1, 1800, NULL, aiMoveForward, thinkGoto, &zombieAIdle };
AISTATE zombieAHack = { kAiStateChase, 6, nHackClient, 80, NULL, NULL, NULL, &zombieAPonder };
AISTATE zombieASearch = { kAiStateSearch, 8, -1, 1800, NULL, aiMoveForward, thinkSearch, &zombieAIdle };
AISTATE zombieARecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &zombieAPonder };
AISTATE zombieATeslaRecoil = { kAiStateRecoil, 4, -1, 0, NULL, NULL, NULL, &zombieAPonder };
AISTATE zombieARecoil2 = { kAiStateRecoil, 1, -1, 360, NULL, NULL, NULL, &zombieAStand };
AISTATE zombieAStand = { kAiStateMove, 11, nStandClient, 0, NULL, NULL, NULL, &zombieAPonder };
AISTATE zombieEIdle = { kAiStateIdle, 12, -1, 0, NULL, NULL, aiThinkTarget, NULL };
AISTATE zombieEUp2 = { kAiStateMove, 0, -1, 1, entryEZombie, NULL, NULL, &zombieASearch };
AISTATE zombieEUp = { kAiStateMove, 9, -1, 180, entryEStand, NULL, NULL, &zombieEUp2 };
AISTATE zombie2Idle = { kAiStateIdle, 0, -1, 0, entryAIdle, NULL, myThinkTarget, NULL };
AISTATE zombie2Search = { kAiStateSearch, 8, -1, 1800, NULL, NULL, myThinkSearch, &zombie2Idle };
AISTATE zombieSIdle = { kAiStateIdle, 10, -1, 0, NULL, NULL, aiThinkTarget, NULL };
AISTATE zombie13AC2C = { kAiStateOther, 11, nStandClient, 0, entryEZombie, NULL, NULL, &zombieAPonder };

static void HackSeqCallback(int, int nXSprite)
{
    XSPRITE *pXSprite = &xsprite[nXSprite];
    int nSprite = pXSprite->reference;
    spritetype *pSprite = &sprite[nSprite];
    spritetype *pTarget = &sprite[pXSprite->target];
    DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
    DUDEINFO *pDudeInfoT = getDudeInfo(pTarget->type);
    int tx = pXSprite->targetX-pSprite->x;
    int ty = pXSprite->targetY-pSprite->y;
    int UNUSED(nDist) = approxDist(tx, ty);
    int nAngle = getangle(tx, ty);
    int height = (pSprite->yrepeat*pDudeInfo->eyeHeight)<<2;
    int height2 = (pTarget->yrepeat*pDudeInfoT->eyeHeight)<<2;
    int dz = height-height2;
    int dx = Cos(nAngle)>>16;
    int dy = Sin(nAngle)>>16;
    sfxPlay3DSound(pSprite, 1101, 1, 0);
    actFireVector(pSprite, 0, 0, dx, dy, dz, kVectorAxe);
}

static void StandSeqCallback(int, int nXSprite)
{
    XSPRITE *pXSprite = &xsprite[nXSprite];
    int nSprite = pXSprite->reference;
    sfxPlay3DSound(&sprite[nSprite], 1102, -1, 0);
}

static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
{
    aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
    aiLookForTarget(pSprite, pXSprite);
}

static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite)
{
    dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
    DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
    int dx = pXSprite->targetX-pSprite->x;
    int dy = pXSprite->targetY-pSprite->y;
    int nAngle = getangle(dx, dy);
    int nDist = approxDist(dx, dy);
    aiChooseDirection(pSprite, pXSprite, nAngle);
    if (nDist < 921 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
        aiNewState(pSprite, pXSprite, &zombieASearch);
    aiThinkTarget(pSprite, pXSprite);
}

static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite)
{
    if (pXSprite->target == -1)
    {
        aiNewState(pSprite, pXSprite, &zombieASearch);
        return;
    }
    dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
    DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
    dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
    spritetype *pTarget = &sprite[pXSprite->target];
    XSPRITE *pXTarget = &xsprite[pTarget->extra];
    int dx = pTarget->x-pSprite->x;
    int dy = pTarget->y-pSprite->y;
    aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
    if (pXTarget->health == 0)
    {
        aiNewState(pSprite, pXSprite, &zombieASearch);
        return;
    }
    if (IsPlayerSprite(pTarget) && (powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], kPwUpShadowCloak) > 0 || powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], kPwUpDeathMaskUseless) > 0))
    {
        aiNewState(pSprite, pXSprite, &zombieAGoto);
        return;
    }
    int nDist = approxDist(dx, dy);
    if (nDist <= pDudeInfo->seeDist)
    {
        int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
        int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
        if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
        {
            if (klabs(nDeltaAngle) <= pDudeInfo->periphery)
            {
                aiSetTarget(pXSprite, pXSprite->target);
                if (nDist < 0x400 && klabs(nDeltaAngle) < 85)
                    aiNewState(pSprite, pXSprite, &zombieAHack);
                return;
            }
        }
    }

    aiNewState(pSprite, pXSprite, &zombieAGoto);
    pXSprite->target = -1;
}

static void thinkPonder(spritetype *pSprite, XSPRITE *pXSprite)
{
    if (pXSprite->target == -1)
    {
        aiNewState(pSprite, pXSprite, &zombieASearch);
        return;
    }
    dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
    DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
    dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
    spritetype *pTarget = &sprite[pXSprite->target];
    XSPRITE *pXTarget = &xsprite[pTarget->extra];
    int dx = pTarget->x-pSprite->x;
    int dy = pTarget->y-pSprite->y;
    aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
    if (pXTarget->health == 0)
    {
        aiNewState(pSprite, pXSprite, &zombieASearch);
        return;
    }
    if (IsPlayerSprite(pTarget) && (powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], kPwUpShadowCloak) > 0 || powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], kPwUpDeathMaskUseless) > 0))
    {
        aiNewState(pSprite, pXSprite, &zombieAGoto);
        return;
    }
    int nDist = approxDist(dx, dy);
    if (nDist <= pDudeInfo->seeDist)
    {
        int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
        int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
        if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
        {
            if (klabs(nDeltaAngle) <= pDudeInfo->periphery)
            {
                aiSetTarget(pXSprite, pXSprite->target);
                if (nDist < 0x400)
                {
                    if (klabs(nDeltaAngle) < 85)
                    {
                        sfxPlay3DSound(pSprite, 1101, 1, 0);
                        aiNewState(pSprite, pXSprite, &zombieAHack);
                    }
                    return;
                }
            }
        }
    }

    aiNewState(pSprite, pXSprite, &zombieAChase);
}

static void myThinkTarget(spritetype *pSprite, XSPRITE *pXSprite)
{
    dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
    DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
    int nOwner = spriRangeIsFine(pSprite->owner) ? pSprite->owner : -1;
    for (int p = connecthead; p >= 0; p = connectpoint2[p])
    {
        PLAYER *pPlayer = &gPlayer[p];
        if (nOwner == pPlayer->nSprite || pPlayer->pXSprite->health == 0 || powerupCheck(pPlayer, kPwUpShadowCloak) > 0)
            continue;
        int x = pPlayer->pSprite->x;
        int y = pPlayer->pSprite->y;
        int z = pPlayer->pSprite->z;
        int nSector = pPlayer->pSprite->sectnum;
        int dx = x-pSprite->x;
        int dy = y-pSprite->y;
        int nDist = approxDist(dx, dy);
        if (nDist > pDudeInfo->seeDist && nDist > pDudeInfo->hearDist)
            continue;
        if (!cansee(x, y, z, nSector, pSprite->x, pSprite->y, pSprite->z-((pDudeInfo->eyeHeight*pSprite->yrepeat)<<2), pSprite->sectnum))
            continue;
        int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
        if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
        {
            aiSetTarget(pXSprite, pPlayer->nSprite);
            aiActivateDude(pSprite, pXSprite);
        }
        else if (nDist < pDudeInfo->hearDist)
        {
            aiSetTarget(pXSprite, x, y, z);
            aiActivateDude(pSprite, pXSprite);
        }
        else
            continue;
        break;
    }
}

static void myThinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
{
    aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
    myThinkTarget(pSprite, pXSprite);
}

static void entryEZombie(spritetype *pSprite, XSPRITE *pXSprite)
{
    UNREFERENCED_PARAMETER(pXSprite);
    pSprite->type = kDudeZombieAxeNormal;
    pSprite->flags |= 1;
}

static void entryAIdle(spritetype *pSprite, XSPRITE *pXSprite)
{
    UNREFERENCED_PARAMETER(pSprite);
    pXSprite->target = -1;
}

static void entryEStand(spritetype *pSprite, XSPRITE *pXSprite)
{
    sfxPlay3DSound(pSprite, 1100, -1, 0);
    pSprite->ang = getangle(pXSprite->targetX-pSprite->x, pXSprite->targetY-pSprite->y);
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT

This file is part of NBlood.

NBlood 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.
*/
//-------------------------------------------------------------------------
#pragma once
#include "ai.h"

extern AISTATE zombieAIdle;
extern AISTATE zombieAChase;
extern AISTATE zombieAPonder;
extern AISTATE zombieAGoto;
extern AISTATE zombieAHack;
extern AISTATE zombieASearch;
extern AISTATE zombieARecoil;
extern AISTATE zombieATeslaRecoil;
extern AISTATE zombieARecoil2;
extern AISTATE zombieAStand;
extern AISTATE zombieEIdle;
extern AISTATE zombieEUp2;
extern AISTATE zombieEUp;
extern AISTATE zombie2Idle;
extern AISTATE zombie2Search;
extern AISTATE zombieSIdle;
extern AISTATE zombie13AC2C;


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT

This file is part of NBlood.

NBlood 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 "compat.h"
#include "build.h"
#include "pragmas.h"
#include "mmulti.h"
#include "common_game.h"

#include "actor.h"
#include "ai.h"
#include "aizombf.h"
#include "blood.h"
#include "db.h"
#include "dude.h"
#include "eventq.h"
#include "levels.h"
#include "player.h"
#include "seq.h"
#include "sfx.h"
#include "trig.h"

static void HackSeqCallback(int, int);
static void PukeSeqCallback(int, int);
static void ThrowSeqCallback(int, int);
static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite);
static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite);
static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite);

static int nHackClient = seqRegisterClient(HackSeqCallback);
static int nPukeClient = seqRegisterClient(PukeSeqCallback);
static int nThrowClient = seqRegisterClient(ThrowSeqCallback);

AISTATE zombieFIdle = { kAiStateIdle, 0, -1, 0, NULL, NULL, aiThinkTarget, NULL };
AISTATE zombieFChase = { kAiStateChase, 8, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
AISTATE zombieFGoto = { kAiStateMove, 8, -1, 600, NULL, aiMoveForward, thinkGoto, &zombieFIdle };
AISTATE zombieFDodge = { kAiStateMove, 8, -1, 0, NULL, aiMoveDodge, thinkChase, &zombieFChase };
AISTATE zombieFHack = { kAiStateChase, 6, nHackClient, 120, NULL, NULL, NULL, &zombieFChase };
AISTATE zombieFPuke = { kAiStateChase, 9, nPukeClient, 120, NULL, NULL, NULL, &zombieFChase };
AISTATE zombieFThrow = { kAiStateChase, 6, nThrowClient, 120, NULL, NULL, NULL, &zombieFChase };
AISTATE zombieFSearch = { kAiStateSearch, 8, -1, 1800, NULL, aiMoveForward, thinkSearch, &zombieFIdle };
AISTATE zombieFRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &zombieFChase };
AISTATE zombieFTeslaRecoil = { kAiStateRecoil, 4, -1, 0, NULL, NULL, NULL, &zombieFChase };

static void HackSeqCallback(int, int nXSprite)
{
    if (nXSprite <= 0 || nXSprite >= kMaxXSprites)
        return;
    XSPRITE *pXSprite = &xsprite[nXSprite];
    int nSprite = pXSprite->reference;
    if (nXSprite < 0 || nXSprite >= kMaxSprites)
        return;
    spritetype *pSprite = &sprite[nSprite];
    if (pSprite->type != kDudeZombieButcher)
        return;
    spritetype *pTarget = &sprite[pXSprite->target];
    DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
    int height = (pDudeInfo->eyeHeight*pSprite->yrepeat);
    DUDEINFO *pDudeInfoT = getDudeInfo(pTarget->type);
    int height2 = (pDudeInfoT->eyeHeight*pTarget->yrepeat);
    actFireVector(pSprite, 0, 0, Cos(pSprite->ang)>>16, Sin(pSprite->ang)>>16, height-height2, kVectorCleaver);
}

static void PukeSeqCallback(int, int nXSprite)
{
    XSPRITE *pXSprite = &xsprite[nXSprite];
    int nSprite = pXSprite->reference;
    spritetype *pSprite = &sprite[nSprite];
    spritetype *pTarget = &sprite[pXSprite->target];
    DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
    DUDEINFO *pDudeInfoT = getDudeInfo(pTarget->type);
    int height = (pDudeInfo->eyeHeight*pSprite->yrepeat);
    int height2 = (pDudeInfoT->eyeHeight*pTarget->yrepeat);
    int tx = pXSprite->targetX-pSprite->x;
    int ty = pXSprite->targetY-pSprite->y;
    int UNUSED(nDist) = approxDist(tx, ty);
    int nAngle = getangle(tx, ty);
    int dx = Cos(nAngle)>>16;
    int dy = Sin(nAngle)>>16;
    sfxPlay3DSound(pSprite, 1203, 1, 0);
    actFireMissile(pSprite, 0, -(height-height2), dx, dy, 0, kMissilePukeGreen);
}

static void ThrowSeqCallback(int, int nXSprite)
{
    XSPRITE *pXSprite = &xsprite[nXSprite];
    int nSprite = pXSprite->reference;
    spritetype *pSprite = &sprite[nSprite];
    actFireMissile(pSprite, 0, -getDudeInfo(pSprite->type)->eyeHeight, Cos(pSprite->ang)>>16, Sin(pSprite->ang)>>16, 0, kMissileButcherKnife);
}

static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
{
    aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
    aiThinkTarget(pSprite, pXSprite);
}

static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite)
{
    dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
    DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
    int dx = pXSprite->targetX-pSprite->x;
    int dy = pXSprite->targetY-pSprite->y;
    int nAngle = getangle(dx, dy);
    int nDist = approxDist(dx, dy);
    aiChooseDirection(pSprite, pXSprite, nAngle);
    if (nDist < 512 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
        aiNewState(pSprite, pXSprite, &zombieFSearch);
    aiThinkTarget(pSprite, pXSprite);
}

static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite)
{
    if (pXSprite->target == -1)
    {
        aiNewState(pSprite, pXSprite, &zombieFGoto);
        return;
    }
    dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
    DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
    dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
    spritetype *pTarget = &sprite[pXSprite->target];
    XSPRITE *pXTarget = &xsprite[pTarget->extra];
    int dx = pTarget->x-pSprite->x;
    int dy = pTarget->y-pSprite->y;
    aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
    if (pXTarget->health == 0)
    {
        aiNewState(pSprite, pXSprite, &zombieFSearch);
        return;
    }
    if (IsPlayerSprite(pTarget) && (powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], kPwUpShadowCloak) > 0 || powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], kPwUpDeathMaskUseless) > 0))
    {
        aiNewState(pSprite, pXSprite, &zombieFSearch);
        return;
    }
    int nDist = approxDist(dx, dy);
    if (nDist <= pDudeInfo->seeDist)
    {
        int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
        int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
        if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
        {
            if (klabs(nDeltaAngle) <= pDudeInfo->periphery)
            {
                aiSetTarget(pXSprite, pXSprite->target);
                if (nDist < 0x1400 && nDist > 0xe00 && klabs(nDeltaAngle) < 85)
                {
                    int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
                    switch (hit)
                    {
                    case -1:
                        aiNewState(pSprite, pXSprite, &zombieFThrow);
                        break;
                    case 3:
                        if (pSprite->type != sprite[gHitInfo.hitsprite].type)
                            aiNewState(pSprite, pXSprite, &zombieFThrow);
                        else
                            aiNewState(pSprite, pXSprite, &zombieFDodge);
                        break;
                    default:
                        aiNewState(pSprite, pXSprite, &zombieFThrow);
                        break;
                    }
                }
                else if (nDist < 0x1400 && nDist > 0x600 && klabs(nDeltaAngle) < 85)
                {
                    int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
                    switch (hit)
                    {
                    case -1:
                        aiNewState(pSprite, pXSprite, &zombieFPuke);
                        break;
                    case 3:
                        if (pSprite->type != sprite[gHitInfo.hitsprite].type)
                            aiNewState(pSprite, pXSprite, &zombieFPuke);
                        else
                            aiNewState(pSprite, pXSprite, &zombieFDodge);
                        break;
                    default:
                        aiNewState(pSprite, pXSprite, &zombieFPuke);
                        break;
                    }
                }
                else if (nDist < 0x400 && klabs(nDeltaAngle) < 85)
                {
                    int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
                    switch (hit)
                    {
                    case -1:
                        aiNewState(pSprite, pXSprite, &zombieFHack);
                        break;
                    case 3:
                        if (pSprite->type != sprite[gHitInfo.hitsprite].type)
                            aiNewState(pSprite, pXSprite, &zombieFHack);
                        else
                            aiNewState(pSprite, pXSprite, &zombieFDodge);
                        break;
                    default:
                        aiNewState(pSprite, pXSprite, &zombieFHack);
                        break;
                    }
                }
                return;
            }
        }
    }

    aiNewState(pSprite, pXSprite, &zombieFSearch);
    pXSprite->target = -1;
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT

This file is part of NBlood.

NBlood 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.
*/
//-------------------------------------------------------------------------
#pragma once
#include "ai.h"

extern AISTATE zombieFIdle;
extern AISTATE zombieFChase;
extern AISTATE zombieFGoto;
extern AISTATE zombieFDodge;
extern AISTATE zombieFHack;
extern AISTATE zombieFPuke;
extern AISTATE zombieFThrow;
extern AISTATE zombieFSearch;
extern AISTATE zombieFRecoil;
extern AISTATE zombieFTeslaRecoil;


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT

This file is part of NBlood.

NBlood 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 "build.h"
#include "fx_man.h"
#include "common_game.h"
//#include "blood.h"
#include "view.h"
#include "config.h"
#include "db.h"
#include "player.h"
#include "resource.h"
#include "sound.h"

#define kMaxAmbChannel 64

struct AMB_CHANNEL
{
    int at0;
    int at4;
    int at8;
    DICTNODE *atc;
    char *at10;
    int at14;
    int at18;
};

AMB_CHANNEL ambChannels[kMaxAmbChannel];
int nAmbChannels = 0;

void ambProcess(void)
{
    if (!SoundToggle)
        return;
    for (int nSprite = headspritestat[kStatAmbience]; nSprite >= 0; nSprite = nextspritestat[nSprite])
    {
        spritetype *pSprite = &sprite[nSprite];
        if (pSprite->owner < 0 || pSprite->owner >= kMaxAmbChannel)
            continue;
        int nXSprite = pSprite->extra;
        if (nXSprite > 0 && nXSprite < kMaxXSprites)
        {
            XSPRITE *pXSprite = &xsprite[nXSprite];
            if (pXSprite->state)
            {
                int dx = pSprite->x-gMe->pSprite->x;
                int dy = pSprite->y-gMe->pSprite->y;
                int dz = pSprite->z-gMe->pSprite->z;
                dx >>= 4;
                dy >>= 4;
                dz >>= 8;
                int nDist = ksqrt(dx*dx+dy*dy+dz*dz);
                int vs = ClipHigh(mulscale16(pXSprite->data4, pXSprite->busy), 127);
                ambChannels[pSprite->owner].at4 += ClipRange(scale(nDist, pXSprite->data1, pXSprite->data2, vs, 0), 0, vs);
            }
        }
    }
    AMB_CHANNEL *pChannel = ambChannels;
    for (int i = 0; i < nAmbChannels; i++, pChannel++)
    {
        if (pChannel->at0 > 0)
            FX_SetPan(pChannel->at0, pChannel->at4, pChannel->at4, pChannel->at4);
        else
        {
            int end = ClipLow(pChannel->at14-1, 0);
            pChannel->at0 = FX_PlayLoopedRaw(pChannel->at10, pChannel->at14, pChannel->at10, pChannel->at10+end, sndGetRate(pChannel->at18), 0,
                pChannel->at4, pChannel->at4, pChannel->at4, pChannel->at4, fix16_one, (intptr_t)&pChannel->at0);
        }
        pChannel->at4 = 0;
    }
}

void ambKillAll(void)
{
    AMB_CHANNEL *pChannel = ambChannels;
    for (int i = 0; i < nAmbChannels; i++, pChannel++)
    {
        if (pChannel->at0 > 0)
        {
            FX_EndLooping(pChannel->at0);
            FX_StopSound(pChannel->at0);
        }
        if (pChannel->atc)
        {
            gSoundRes.Unlock(pChannel->atc);
            pChannel->atc = NULL;
        }
    }
    nAmbChannels = 0;
}

void ambInit(void)
{
    ambKillAll();
    memset(ambChannels, 0, sizeof(ambChannels));
    for (int nSprite = headspritestat[kStatAmbience]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
        if (sprite[nSprite].extra <= 0 || sprite[nSprite].extra >= kMaxXSprites) continue;

        XSPRITE *pXSprite = &xsprite[sprite[nSprite].extra];
        if (pXSprite->data1 >= pXSprite->data2) continue;

        int i; AMB_CHANNEL *pChannel = ambChannels;
        for (i = 0; i < nAmbChannels; i++, pChannel++)
            if (pXSprite->data3 == pChannel->at8) break;

        if (i == nAmbChannels) {

            if (i >= kMaxAmbChannel) {
                sprite[nSprite].owner = -1;
                continue;
            }

            int nSFX = pXSprite->data3;
            DICTNODE *pSFXNode = gSoundRes.Lookup(nSFX, "SFX");
            if (!pSFXNode) {
                //ThrowError("Missing sound #%d used in ambient sound generator %d\n", nSFX);
                viewSetSystemMessage("Missing sound #%d used in ambient sound generator #%d\n", nSFX, nSprite);
                actPostSprite(nSprite, kStatDecoration);
                continue;
            }

            SFX *pSFX = (SFX*)gSoundRes.Load(pSFXNode);
            DICTNODE *pRAWNode = gSoundRes.Lookup(pSFX->rawName, "RAW");
            if (!pRAWNode) {
                //ThrowError("Missing RAW sound \"%s\" used in ambient sound generator %d\n", pSFX->rawName, nSFX);
                viewSetSystemMessage("Missing RAW sound \"%s\" used in ambient sound generator #%d\n", pSFX->rawName, nSprite);
                actPostSprite(nSprite, kStatDecoration);
                continue;
            }

            if (pRAWNode->size > 0) {
                pChannel->at14 = pRAWNode->size;
                pChannel->at8 = nSFX;
                pChannel->atc = pRAWNode;
                pChannel->at14 = pRAWNode->size;
                pChannel->at10 = (char*)gSoundRes.Lock(pRAWNode);
                pChannel->at18 = pSFX->format;
                nAmbChannels++;
            }

        }

        sprite[nSprite].owner = i;
    }
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT

This file is part of NBlood.

NBlood 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.
*/
//-------------------------------------------------------------------------
#pragma once

void ambProcess(void);
void ambKillAll(void);
void ambInit(void);


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 sirlemonhead, Nuke.YKT

This file is part of NBlood.

NBlood 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 "compat.h"
#ifdef WITHKPLIB
#include "kplib.h"
#endif
#include "common_game.h"
#include "resource.h"
#include "misc.h"
#include "globals.h"
#include "sound.h"

static Resource gBarfRes;

#define kMaxCmdLineDefines  5
#define kMaxDefines         1000
#define kMaxParseLevels     5
static int nCmdDefines = 0;
//static int nDefines = 0;

static int gParseLevel = 0;
int dword_44CE0[kMaxParseLevels] = { 0, 0, 0, 0, 0 };

// FIXME
unsigned int nBytes = 0;
char buffer[1024];
int scriptValue = 0;

char scriptBuffer[256];

struct define_t
{
    char *_text;
    int   _value;
};

define_t gCmdDefines[kMaxCmdLineDefines];

void sub_11DF0(char *fileName, char flags, int ID);
void sub_11C10(char *pzScriptDir, char *fileName, char flags, int ID);

struct tag_t {
    const char *_value;
    uint8_t _index;
};

enum eTags
{
    kTag0,
    kTagEnd,
    kTagString,
    kTagConstant,
    kTag4, // string constant?
    kTagComma,
    kTagSemiColon,
    kTagColon,
    kTagEquals,
    kTagHash,
    kTagComment,
    kTagInclude,
    kTagResource,
    kTagAs,
    kTagPreload,
    kTagPrelock,
    kTagData,
    kTagLoad,
    kTagEmit,
    kTagIfDef,
    kTagEndif,
    kTagElse
};

tag_t tags[] =
{
    { ",", kTagComma },
    { ";", kTagSemiColon },
    { ":", kTagColon },
    { "=", kTagEquals },
    { "#", kTagHash },
    { "//", kTagComment },
    { "INCLUDE", kTagInclude },
    { "RESOURCE", kTagResource },
    { "AS", kTagAs },
    { "PRELOAD", kTagPreload },
    { "PRELOCK", kTagPrelock },
    { "DATA", kTagData },
    { "LOAD", kTagLoad },
    { "EMIT", kTagEmit },
    { "%ifdef", kTagIfDef },
    { "%endif", kTagEndif },
    { "%else", kTagElse }
};

const int kTagCount = sizeof(tags) / sizeof(tag_t);

int qsort_compar(const void *a, const void *b)
{
    return Bstrcasecmp((const char*)a, (const char*)b);
}

void SortTags()
{
    qsort(tags, kTagCount, sizeof(tag_t), qsort_compar);
}

void AddCmdDefine(char *text, int value)
{
    dassert(nCmdDefines < kMaxCmdLineDefines);

    gCmdDefines[nCmdDefines]._text = (char*)Resource::Alloc(strlen(text) + 1);

    strcpy(gCmdDefines[nCmdDefines]._text, text);
    gCmdDefines[nCmdDefines]._value = value;

    nCmdDefines++;
}

// 174 bytes
struct RFS
{
private:
    char *_ptr;         // [0]
    char _curChar;      // [4]
    char *_pUnknown2;   // [5]  - some sort of pointer into _ptr?
    char *_pStartLine;  // [9]
    char *_pEnd;        // [13]
    char *_pMark;       // [17]
    char _unknown6;     // [21]
    int  _unknown7;     // [22]
    int  _curLine;      // [26]
    char _fileName[BMAX_PATH]; // [30]

public:
    int Open(const char *fileName);
    void Close();
    void Increment();
    void SkipBeyondValue(char value);
    uint8_t GetNextTag();
    void ScriptError(const char *message);
    void SetMark();
    void UnsetMark();
};

int RFS::Open(const char *fileName)
{
    strcpy(_fileName, fileName);

    buildvfs_fd hFile = kopen4loadfrommod(fileName, 0);
    if (hFile == buildvfs_fd_invalid) {
        initprintf("BARF: Error opening file %s", _fileName);
        return 1;
    }

    int fileSize = kfilelength(hFile);
    _ptr = (char*)Resource::Alloc(fileSize);
    if (_ptr == NULL) {
        initprintf("BARF: Not enough memory to read %s", _fileName);
        kclose(hFile);
        return 1;
    }

    kread(hFile, _ptr, fileSize);
    kclose(hFile);

    _curLine = 0;
    _pUnknown2 = _ptr;
    _curChar = '\n';
    _pEnd = &_ptr[fileSize - 1];

    return 0;
}

void RFS::Close()
{
    if (_ptr) {
/* BUG - the original code called nfree but this should be a Resource::Free()
        _nfree(_ptr);
*/
        Resource::Free(_ptr);
    }
}

void RFS::Increment()
{
    if (_curChar == '\n') {
        _curLine++;
        _pStartLine = _pUnknown2;
    }

    if (_pUnknown2 >= _pEnd) {
        _curChar = 0;
    }
    else {
        _curChar = *_pUnknown2; // grabs the next char
        _pUnknown2++; // increment pointer into char data
    }
}

void RFS::SkipBeyondValue(char nVal)
{
    while (_curChar && _curChar != nVal) {
        Increment();
    }
}

void RFS::SetMark()
{
    _pMark = _pUnknown2;
    _unknown6 = _curChar;
    _unknown7 = _curLine;
}

// inverse of the above function
void RFS::UnsetMark()
{
    _pUnknown2 = _pMark;
    _curChar = _unknown6;
    _curLine = _unknown7;
}

void RFS::ScriptError(const char *message)
{
    // TODO

    char *p = _pStartLine;
    while (*p != '\n')
    {
        if (isprint(*p))
            putchar(*p);
        else
            putchar(' ');
        p++;
    }

    putchar('\n');

    p = _pStartLine;

    while (p < _pMark)
    {
        putchar(' ');
        p++;
    }

    puts("^");

    initprintf("Error in %s line %d: %s\n\n", _fileName, _curLine, message);
}

uint8_t RFS::GetNextTag()
{
    // skip any space characters
    do {
        Increment();
    } while (isspace(_curChar));

    if (_curChar == '\0') {
        return kTagEnd;
    }

    SetMark();

    // Path A
    if (_curChar == '"')
    {
        Increment();

        int i = 0;

        // section 1
        while (1) {

            if (_curChar == '\0' || _curChar == '"') {
                scriptBuffer[i] = '\0';
                return kTagString;
            }

            if (i == 256) {
                ScriptError("String exceeds maximum string length");
                break;
            }

            scriptBuffer[i] = _curChar;
            i++;
            Increment();
        }

        // section 2
        while (1)
        {
            if (_curChar == '\0' || _curChar == '"') {
                return kTag0;
            }

            Increment();
        }
    }
    else
    {
        scriptValue = 0;
        bool isNegative = false; // or 'isSigned' ?

        // is it a negative number?
        if (_curChar == '-')
        {
            Increment();

            isNegative = true;

            if (!isdigit(_curChar)) {
                UnsetMark();
            }
        }

        if (isdigit(_curChar))
        {
            // left path
            if (_curChar == '0')
            {
                Increment();

                // handle a hex value
                if (toupper(_curChar) == 'X')
                {
                    // orange loop
                    while (1)
                    {
                        Increment();
                        if (!isxdigit(_curChar)) { // isxdigit() checks for a hex value
                            break;
                        }

                        // hex version of atoi?
                        scriptValue *= 16;
                        if (!isdigit(_curChar)) {
                            scriptValue += toupper(_curChar) - 55;
                        }
                        else {
                            scriptValue += _curChar - '0';
                        }

                        SetMark();
                    }

                    UnsetMark();
                    if (isNegative) {
                        scriptValue = -scriptValue;
                    }

                    return kTagConstant;
                }

                UnsetMark();
            }

            // the loop
            while (isdigit(_curChar))
            {
                // atoi implementation
                scriptValue = scriptValue * 10 + _curChar - '0';
                SetMark();
                Increment();
            }

            UnsetMark();
            if (isNegative) {
                scriptValue = -scriptValue;
            }

            return kTagConstant;
        }
        else
        {
            // BLUEISH PATH
            int ebp = 0; // v11
            int i = 0;

            // blue loop #1
            while (1)
            {
                scriptBuffer[ebp] = _curChar;
                ebp++;
                int eax = -1;

                // blue loop #2
                for (i = 0; i < kTagCount; i++)
                {
                    //if (eax >= 0) {
                    if (eax == 0) {
                        break;
                    }

                    // eax = strnicmp(tags[i]._value, scriptBuffer, ebp);
                    eax = Bstrncasecmp(scriptBuffer, tags[i]._value, ebp);

                    //if (eax >= 0) {
                    if (eax == 0) {
                        break;
                    }
                }

                if (eax > 0 || i == kTagCount) {
                    break;
                }

                if ((eax == 0) && ((int)strlen(tags[i]._value) == ebp))
                {
                    scriptBuffer[ebp] = 0;
                    return tags[i]._index;
                }

                Increment();
            }

            UnsetMark();

            i = 0;

            while (isalnum(_curChar))
            {
                scriptBuffer[i] = _curChar;
                SetMark();
                i++;
                Increment();
            }

            UnsetMark();
            scriptBuffer[i] = 0;
            return kTag4;
        }
    }

    //	qAssert(1==0); // TODO - what to return here
}

void ParseScript(const char *scriptFileName)
{
    char text[256];
    char char256_1[256];
    char char256_2[256];
    char fileName[BMAX_PATH];
    char inp[BMAX_PATH];
    char zScriptDirectory[BMAX_PATH], zTemp1[BMAX_PATH], zTemp2[BMAX_PATH];

    int const bakpathsearchmode = pathsearchmode;
    pathsearchmode = 1;

    SplitPath(scriptFileName, zScriptDirectory, zTemp1, zTemp2);

    RFS rfs;

    // AddExtension(name, ".RFS");
    if (rfs.Open(scriptFileName))
    {
        pathsearchmode = bakpathsearchmode;
        return;
    }

    gParseLevel = 0;
    dword_44CE0[0] = 0;

    bool parsing = true;

    while (parsing)
    {
        // START LOOP. to be fixed later
        START:

        uint8_t tag = rfs.GetNextTag();

        switch (tag)
        {
            default:
            {
                break;
            }
            case kTagEnd:
            {
                parsing = false;
                break;
            }
            case kTagComment:
            {
                // skip to next line
                rfs.SkipBeyondValue('\n');
                break;
            }
            case kTagEmit: // minty/light green colour
            {
                tag = rfs.GetNextTag();
                if (tag != kTag4)
                {
                    rfs.ScriptError("Symbol name expected");
                    rfs.SkipBeyondValue(';');
                    break;
                }
                else
                {
                    strcpy(char256_2, scriptBuffer);
                    tag = rfs.GetNextTag();
                    if (tag != kTagEquals)
                    {
                        rfs.ScriptError("Missing '='");
                        rfs.SkipBeyondValue(';');
                        break;
                    }

                    tag = rfs.GetNextTag();
                    if (tag != kTagConstant)
                    {
                        rfs.ScriptError("Constant expected");
                        rfs.SkipBeyondValue(';');
                        break;
                    }
                    else
                    {
                        //AddDefine(char256_2, scriptValue);
                        rfs.SkipBeyondValue('\n');
                    }
                }
            }
            case kTagResource: // really light blue..
            {
                if (kTagString != rfs.GetNextTag()) {
                    rfs.ScriptError("String constant exected");
                    rfs.SkipBeyondValue('\n');
                    break;
                }

                strcpy(inp, scriptBuffer);
                char nFlags = 0;
                int ID = 0;
                bool isDefine = false;

                tag = rfs.GetNextTag();
                if (tag == kTagAs)
                {
                    tag = rfs.GetNextTag();
                    if (tag == kTag4)
                    {
                        strcpy(text, scriptBuffer);

                        if (rfs.GetNextTag() != kTagEquals)
                        {
                            rfs.ScriptError("Missing '='");
                            rfs.SkipBeyondValue(';');
                            break;
                        }

                        isDefine = true;
                        tag = rfs.GetNextTag();
                    }

                    if (tag != kTagConstant)
                    {
                        rfs.ScriptError("Constant expected");
                        rfs.SkipBeyondValue(';');
                        break;
                    }

                    if (isDefine) {
                        //AddDefine(text, scriptValue);
                    }

                    ID = scriptValue;
                    nFlags |= DICT_ID;
                    tag = rfs.GetNextTag();
                }

                //if (!bNoEncrypt) {
                //	nFlags |= kResFlagIsEncrypted;
                //}

                while (tag == kTagComma)
                {
                    tag = rfs.GetNextTag();

                    if (tag == kTagPreload) {
                        nFlags |= DICT_LOAD;
                    }
                    else if (tag == kTagPrelock) {
                        nFlags |= DICT_LOCK;
                    }
                    else {
                        rfs.ScriptError("Unrecognized flag");
                        rfs.SkipBeyondValue(';');
                        goto START; // FIXME
                    }

                    tag = rfs.GetNextTag();
                }

                if (tag != kTagSemiColon)
                {
                    rfs.ScriptError("';' expected");
                    rfs.SkipBeyondValue(';');
                    break;
                }

                if (dword_44CE0[gParseLevel] == 0) {
                    sub_11C10(zScriptDirectory, inp, nFlags, ID);
                }

                break;
            }
            case kTagIfDef: // purplish colour
            {
                tag = rfs.GetNextTag();
                if (tag != kTag4)
                {
                    rfs.ScriptError("Parameter error in ifdef");
                    rfs.SkipBeyondValue('\n');
                    break;
                }
                else
                {
                    rfs.SetMark();
                    strcpy(char256_1, scriptBuffer);

                    bool bGotDefine = false;

                    // check if this was defined via command prompt arguments
                    for (int i = 0; i < nCmdDefines; i++)
                    {
                        if (Bstrcasecmp(gCmdDefines[i]._text, char256_1) == 0) { // string is equivalent
                            bGotDefine = true;
                            break;
                        }
                    }

                    // loc_11FC3:
                    gParseLevel++;
                    dassert(gParseLevel < kMaxParseLevels);

                    if (bGotDefine) {
                        dword_44CE0[gParseLevel] = dword_44CE0[gParseLevel - 1];
                    }
                    else {
                        dword_44CE0[gParseLevel] = 1;
                    }
                }
                break;
            }
            case kTagElse: // pinky colour
            {
                if (gParseLevel)
                {
                    // loc_12066:
                    if (dword_44CE0[gParseLevel - 1] == 0) {
                        if (dword_44CE0[gParseLevel] == 0) {
                            dword_44CE0[gParseLevel] = 1;
                        }

                        rfs.SkipBeyondValue('\n');
                        break;
                    }
                }
                else {
                    rfs.ScriptError("Unexpected else");
                    rfs.SkipBeyondValue('\n');
                    break;
                }
                break;
            }
            case kTagEndif: // poo coloured
            {
                if (gParseLevel) {
                    gParseLevel--;
                    rfs.SkipBeyondValue('\n');
                    break;
                }
                else
                {
                    rfs.ScriptError("Unexpected Endif");
                    rfs.SkipBeyondValue('\n');
                    break;
                }
            }
            case kTagHash: // gold colour
            {
                tag = rfs.GetNextTag();
                if (tag == kTagInclude)
                {
                    tag = rfs.GetNextTag();
                    if (tag != kTagString)
                    {
                        rfs.ScriptError("String constant exected");
                        // why no SkipBeyondValue?
                        break;
                    }
                    else
                    {
                        ParseScript(scriptBuffer);
                    }
                }
                break;
            }
            case kTagData: // eg:        data "AMB1.SFX" as 1:	80, 0x10000, 0x0, 1, -1, "amb1";
            {
                // green coloured section
                if (rfs.GetNextTag() != kTagString) {
                    rfs.ScriptError("String constant expected");
                    rfs.SkipBeyondValue(';');
                    break;
                }

                // eg strcpy(fileName, "AMB1.SFX");
                strcpy(fileName, scriptBuffer);

                char nFlags = 0;
                int ID = 0;

                bool isDefine = false;

                tag = rfs.GetNextTag();

                // process an ID section
                if (tag == kTagAs)
                {
                    tag = rfs.GetNextTag();
                    if (tag == kTag4)
                    {
                        strcpy(fileName, scriptBuffer);

                        tag = rfs.GetNextTag();
                        if (tag != kTagEquals) {
                            rfs.ScriptError("Missing '='");
                            rfs.SkipBeyondValue(';');
                            break;
                        }

                        isDefine = true;
                        tag = rfs.GetNextTag();
                    }

                    if (tag != kTagConstant)
                    {
                        rfs.ScriptError("Constant Expected");
                        rfs.SkipBeyondValue(';');
                        break;
                    }
                    else {
                        if (isDefine) {
                        //    AddDefine(fileName, scriptValue);
                        }

                        nFlags |= DICT_ID;
                        ID = scriptValue;
                        tag = rfs.GetNextTag();
                    }
                }

                if (tag == kTagComma)
                {
                    // process all sections on this line that are comma separated
                    while (1)
                    {
                        tag = rfs.GetNextTag();
                        if (tag == kTagPreload)
                        {
                            nFlags |= DICT_LOAD;
                            tag = rfs.GetNextTag();

                            if (tag == kTagComma) {
                                continue;
                            }
                        }
                        else if (tag == kTagPrelock)
                        {
                            nFlags |= DICT_LOCK;
                            tag = rfs.GetNextTag();

                            if (tag == kTagComma) {
                                continue;
                            }
                        }
                        else
                        {
                            rfs.ScriptError("Unrecognized flag");
                            rfs.SkipBeyondValue(';');
                            goto START; // FIXME
                        }
                    }
                }

                // loc_12471:
                if (tag != kTagColon) // marked orange in IDA
                {
                    while (1)
                    {
                        if (tag == kTagPreload)
                        {
                            nFlags |= DICT_LOAD;
                            tag = rfs.GetNextTag();

                            if (tag == kTagColon) {
                                break;
                            }
                        }
                        else if (tag == kTagPrelock)
                        {
                            nFlags |= DICT_LOCK;
                            tag = rfs.GetNextTag();

                            if (tag == kTagColon) {
                                break;
                            }
                        }
                        else {
                            rfs.ScriptError("':' expected");
                            rfs.SkipBeyondValue(';');
                            goto START; // FIXME
                        }
                    }
                }

                nBytes = 0;

                // yellow loop
                while (1)
                {
                    tag = rfs.GetNextTag();

                    switch (tag)
                    {
                        case kTagString:
                        {
                            memcpy(&buffer[nBytes], scriptBuffer, strlen(scriptBuffer) + 1);
                            nBytes += strlen(scriptBuffer) + 1;
                            break;
                        }
                        case kTagConstant:
                        {
                            memcpy(&buffer[nBytes], &scriptValue, sizeof(scriptValue));
                            nBytes += sizeof(scriptValue);
                            break;
                        }
                        default:
                        {
                            rfs.ScriptError("Constant expected");
                            rfs.SkipBeyondValue(';');
                            goto START; // FIXME
                        }
                    }

                    tag = rfs.GetNextTag();
                    if (tag != kTagComma) {
                        break;
                    }
                }

                if (tag != kTagSemiColon) {
                    rfs.ScriptError("Semicolon expected");
                    rfs.SkipBeyondValue(';');
                    break;
                }
                else
                {
                    if (dword_44CE0[gParseLevel] == 0) {
                        sub_11DF0(fileName, nFlags, ID);
                    }
                }
                break;
            }
        }
    }

    //CreateHeader();
    rfs.Close();
    pathsearchmode = bakpathsearchmode;
}

void sub_11C10(char *pzScriptDir, char *fileName, char flags, int ID)
{
    char zDirectory[BMAX_PATH];
    char zFilename[BMAX_PATH];
    char zType[BMAX_PATH];
    BDIR* dirr;
    struct Bdirent* dirent;
    dirr = Bopendir("./");
    if (dirr)
    {
        while ((dirent = Breaddir(dirr)))
        {
            if (!Bwildmatch(dirent->name, fileName))
                continue;

            SplitPath(dirent->name, zDirectory, zFilename, zType);

            if (!Bstrcasecmp(zType, "RAW") || !Bstrcasecmp(zType, "SFX") || !Bstrcasecmp(zType, "MID") || !Bstrcasecmp(zType, "TMB"))
                gSoundRes.AddExternalResource(zFilename, zType, ID, flags, dirent->name);
            else
                gSysRes.AddExternalResource(zFilename, zType, ID, flags, dirent->name);
        }
        Bclosedir(dirr);
    }
    dirr = Bopendir(pzScriptDir);
    if (dirr)
    {
        while ((dirent = Breaddir(dirr)))
        {
            if (!Bwildmatch(dirent->name, fileName))
                continue;

            SplitPath(dirent->name, zDirectory, zFilename, zType);

            if (!Bstrcasecmp(zType, "RAW") || !Bstrcasecmp(zType, "SFX") || !Bstrcasecmp(zType, "MID") || !Bstrcasecmp(zType, "TMB"))
                gSoundRes.AddExternalResource(zFilename, zType, ID, flags, dirent->name);
            else
                gSysRes.AddExternalResource(zFilename, zType, ID, flags, dirent->name);
        }
        Bclosedir(dirr);
    }
}

void sub_11DF0(char *filePath, char flags, int ID)
{
    char zDirectory[BMAX_PATH];
    char zFilename[BMAX_PATH];
    char zType[BMAX_PATH];
    //buildvfs_fd handle;

    SplitPath(filePath, zDirectory, zFilename, zType);

    if (!Bstrcasecmp(zType, "RAW") || !Bstrcasecmp(zType, "SFX") || !Bstrcasecmp(zType, "MID") || !Bstrcasecmp(zType, "TMB"))
        gSoundRes.AddFromBuffer(zFilename, zType, buffer, nBytes, ID, flags);
    else
        gSysRes.AddFromBuffer(zFilename, zType, buffer, nBytes, ID, flags);
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 sirlemonhead, Nuke.YKT

This file is part of NBlood.

NBlood 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.
*/
//-------------------------------------------------------------------------
#pragma once

void ParseScript(const char* scriptFileName);