raze/source/games/exhumed/src/queen.cpp

1569 lines
37 KiB
C++
Raw Normal View History

//-------------------------------------------------------------------------
/*
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 "player.h"
2019-09-21 15:47:55 +00:00
#include "sound.h"
#include "names.h"
#include <assert.h>
BEGIN_PS_NS
enum
{
2021-10-15 20:55:03 +00:00
kMaxQueens = 1,
kMaxEggs = 10,
kMaxTails = 7
};
short QueenCount = 0;
static actionSeq QueenSeq[] = {
{0, 0},
{0, 0},
{9, 0},
{36, 0},
{18, 0},
{27, 0},
{45, 0},
{45, 0},
{54, 1},
{53, 1},
{55, 1}
};
static actionSeq HeadSeq[] = {
{56, 1},
{65, 0},
{65, 0},
{65, 0},
{65, 0},
{65, 0},
{74, 0},
{82, 0},
{90, 0}
};
static actionSeq EggSeq[] = {
{19, 1},
{18, 1},
{0, 0},
{9, 0},
{23, 1},
};
struct Queen
{
short nHealth;
short nFrame;
short nAction;
short nSprite;
short nTarget;
short field_A;
short field_C;
short field_10;
short field_12;
};
struct Egg
{
short nHealth;
2020-11-29 23:40:25 +00:00
short nFrame;
short nAction;
short nSprite;
short nRunPtr;
short nTarget;
short field_C;
short field_E;
};
struct Head
{
short nHealth;
2020-11-29 23:40:25 +00:00
short nFrame;
short nAction;
short nSprite;
short field_8;
short nTarget;
short field_C;
2020-11-29 23:40:25 +00:00
short tails;
};
2020-11-29 23:40:25 +00:00
FreeListArray<Egg, kMaxEggs> QueenEgg;
int nQHead = 0;
short nHeadVel;
short nVelShift;
short tailspr[kMaxTails];
short QueenChan[kMaxQueens];
Queen QueenList[kMaxQueens];
Head QueenHead;
int MoveQX[25];
int MoveQY[25];
int MoveQZ[25];
short MoveQS[25];
short MoveQA[25];
2020-11-29 23:40:25 +00:00
FSerializer& Serialize(FSerializer& arc, const char* keyname, Queen& w, Queen* def)
{
if (arc.BeginObject(keyname))
{
arc("health", w.nHealth)
("frame", w.nFrame)
("action", w.nAction)
("sprite", w.nSprite)
("target", w.nTarget)
("ata", w.field_A)
("atc", w.field_C)
("at10", w.field_10)
("at12", w.field_12)
.EndObject();
}
return arc;
}
2020-11-29 23:40:25 +00:00
FSerializer& Serialize(FSerializer& arc, const char* keyname, Egg& w, Egg* def)
{
if (arc.BeginObject(keyname))
{
arc("health", w.nHealth)
("frame", w.nFrame)
("action", w.nAction)
("sprite", w.nSprite)
("target", w.nTarget)
("runptr", w.nRunPtr)
2020-11-29 23:40:25 +00:00
("atc", w.field_C)
("ate", w.field_E)
.EndObject();
}
return arc;
}
2020-11-29 23:40:25 +00:00
FSerializer& Serialize(FSerializer& arc, const char* keyname, Head& w, Head* def)
{
if (arc.BeginObject(keyname))
{
arc("health", w.nHealth)
("frame", w.nFrame)
("action", w.nAction)
("sprite", w.nSprite)
("target", w.nTarget)
("at8", w.field_8)
("atc", w.field_C)
("tails", w.tails)
.EndObject();
}
return arc;
}
2020-11-29 23:40:25 +00:00
void SerializeQueen(FSerializer& arc)
{
if (arc.BeginObject("queen"))
{
arc("count", QueenCount);
if (QueenCount == 0) // only save the rest if we got a queen. There can only be one.
{
for (int i = 0; i < kMaxEggs; i++)
{
QueenEgg[i].nRunPtr = -1;
}
2020-11-29 23:40:25 +00:00
arc("qhead", nQHead)
("headvel", nHeadVel)
("velshift", nVelShift)
("head", QueenHead)
.Array("tailspr", tailspr, countof(tailspr))
("queenchan", QueenChan[0])
("queen", QueenList[0])
("eggs", QueenEgg)
.Array("moveqx", MoveQX, countof(MoveQX))
.Array("moveqy", MoveQY, countof(MoveQY))
.Array("moveqz", MoveQZ, countof(MoveQZ))
.Array("moveqa", MoveQA, countof(MoveQA))
.Array("moveqs", MoveQS, countof(MoveQS));
}
arc.EndObject();
}
}
void InitQueens()
{
QueenCount = 1;
2020-11-29 23:40:25 +00:00
QueenEgg.Clear();
for (int i = 0; i < kMaxEggs; i++)
{
QueenEgg[i].nRunPtr = -1;
}
}
int GrabEgg()
{
2020-11-29 23:40:25 +00:00
auto egg = QueenEgg.Get();
if (egg == -1) return -1;
return egg;
}
void BlowChunks(int nSprite)
{
for (int i = 0; i < 4; i++)
{
BuildCreatureChunk(nSprite, seq_GetSeqPicnum(16, i + 41, 0));
}
}
void DestroyEgg(short nEgg)
{
short nSprite = QueenEgg[nEgg].nSprite;
2021-10-15 20:55:03 +00:00
auto pSprite = &sprite[nSprite];
if (QueenEgg[nEgg].nAction != 4)
{
BuildAnim(-1, 34, 0, pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, pSprite->xrepeat, 4);
}
else
{
for (int i = 0; i < 4; i++)
{
BuildCreatureChunk(nSprite, seq_GetSeqPicnum(kSeqQueenEgg, (i % 2) + 24, 0));
}
}
runlist_DoSubRunRec(pSprite->owner);
runlist_DoSubRunRec(pSprite->lotag - 1);
runlist_SubRunRec(QueenEgg[nEgg].nRunPtr);
QueenEgg[nEgg].nRunPtr = -1;
mydeletesprite(nSprite);
2020-11-29 23:40:25 +00:00
QueenEgg.Release(nEgg);
}
void DestroyAllEggs()
{
for (int i = 0; i < kMaxEggs; i++)
{
if (QueenEgg[i].nRunPtr > -1)
{
DestroyEgg(i);
}
}
}
void SetHeadVel(short nSprite)
{
2021-10-15 20:55:03 +00:00
auto pSprite = &sprite[nSprite];
short nAngle = pSprite->ang;
pSprite->xvel = bcos(nAngle, nVelShift);
pSprite->yvel = bsin(nAngle, nVelShift);
}
int QueenAngleChase(short nSprite, short nSprite2, int val1, int val2)
{
short nAngle;
2021-10-15 20:55:03 +00:00
spritetype* pSprite = &sprite[nSprite];
if (nSprite2 < 0)
{
pSprite->zvel = 0;
nAngle = pSprite->ang;
}
else
{
2021-10-15 20:55:03 +00:00
spritetype* pSprite2 = &sprite[nSprite2];
int nTileY = (tileHeight(pSprite2->picnum) * pSprite2->yrepeat) * 2;
int nMyAngle = GetMyAngle(pSprite2->x - pSprite->x, pSprite2->y - pSprite->y);
int edx = ((pSprite2->z - nTileY) - pSprite->z) >> 8;
uint32_t xDiff = abs(pSprite2->x - pSprite->x);
uint32_t yDiff = abs(pSprite2->y - pSprite->y);
uint32_t sqrtVal = xDiff * xDiff + yDiff * yDiff;
if (sqrtVal > INT_MAX)
{
DPrintf(DMSG_WARNING, "%s %d: overflow\n", __func__, __LINE__);
sqrtVal = INT_MAX;
}
int nSqrt = ksqrt(sqrtVal);
int var_14 = GetMyAngle(nSqrt, edx);
int nAngDelta = AngleDelta(pSprite->ang, nMyAngle, 1024);
if (abs(nAngDelta) > 127)
{
2021-10-15 20:55:03 +00:00
val1 /= abs(nAngDelta >> 7);
if (val1 < 256)
val1 = 256;
}
if (abs(nAngDelta) > val2)
{
if (nAngDelta < 0)
nAngDelta = -val2;
else
nAngDelta = val2;
}
nAngle = (nAngDelta + pSprite->ang) & kAngleMask;
pSprite->zvel = (AngleDelta(pSprite->zvel, var_14, 24) + pSprite->zvel) & kAngleMask;
}
pSprite->ang = nAngle;
int da = pSprite->zvel;
int x = abs(bcos(da));
int v26 = x * ((val1 * bcos(nAngle)) >> 14);
int v27 = x * ((val1 * bsin(nAngle)) >> 14);
uint32_t xDiff = abs((int32_t)(v26 >> 8));
uint32_t yDiff = abs((int32_t)(v27 >> 8));
uint32_t sqrtNum = xDiff * xDiff + yDiff * yDiff;
if (sqrtNum > INT_MAX)
{
DPrintf(DMSG_WARNING, "%s %d: overflow\n", __func__, __LINE__);
sqrtNum = INT_MAX;
}
int nSqrt = ksqrt(sqrtNum) * bsin(da);
return movesprite(nSprite, v26 >> 2, v27 >> 2, bsin(bobangle, -5) + (nSqrt >> 13), 0, 0, CLIPMASK1);
}
int DestroyTailPart()
{
2020-11-29 23:40:25 +00:00
if (!QueenHead.tails) {
return 0;
}
2020-11-29 23:40:25 +00:00
short nSprite = tailspr[--QueenHead.tails];
BlowChunks(nSprite);
BuildExplosion(nSprite);
for (int i = 0; i < 5; i++)
{
short nHeight = GetSpriteHeight(nSprite);
BuildLavaLimb(&exhumedActors[nSprite], i, nHeight);
}
mydeletesprite(nSprite);
return 1;
}
void BuildTail()
{
short nSprite = QueenHead.nSprite;
2021-10-15 20:55:03 +00:00
auto pSprite = &sprite[nSprite];
int x = pSprite->x;
int y = pSprite->x;
int z = pSprite->x;
short nSector = pSprite->sectnum;
int i;
for (i = 0; i < kMaxTails; i++)
{
short nTailSprite = insertsprite(nSector, 121);
auto pTailSprite = &sprite[nTailSprite];
tailspr[i] = nTailSprite;
if (nTailSprite < 0) {
2019-11-24 09:03:19 +00:00
I_Error("Can't create queen's tail!\n");
}
pTailSprite->lotag = runlist_HeadRun() + 1;
pTailSprite->owner = runlist_AddRunRec(sprite[nTailSprite].lotag - 1, (i + 1), 0x1B0000);
pTailSprite->shade = -12;
pTailSprite->x = x;
pTailSprite->y = y;
pTailSprite->hitag = 0;
pTailSprite->cstat = 0;
pTailSprite->clipdist = 100;
pTailSprite->xrepeat = 80;
pTailSprite->yrepeat = 80;
pTailSprite->picnum = 1;
pTailSprite->pal = sector[sprite[nTailSprite].sectnum].ceilingpal;
pTailSprite->xoffset = 0;
pTailSprite->yoffset = 0;
pTailSprite->z = z;
pTailSprite->extra = -1;
}
for (i = 0; i < 24 + 1; i++)
{
MoveQX[i] = x;
MoveQZ[i] = z;
MoveQY[i] = y;
assert(nSector >= 0 && nSector < kMaxSectors);
MoveQS[i] = nSector;
}
nQHead = 0;
2020-11-29 23:40:25 +00:00
QueenHead.tails = 7;
}
void BuildQueenEgg(short nQueen, int nVal)
{
int nEgg = GrabEgg();
if (nEgg < 0) {
return;
}
short nSprite = QueenList[nQueen].nSprite;
2021-10-15 20:55:03 +00:00
auto pSprite = &sprite[nSprite];
int x = pSprite->x;
int y = pSprite->y;
short nSector = pSprite->sectnum;
int nFloorZ = sector[nSector].floorz;
short nAngle = pSprite->ang;
int nSprite2 = insertsprite(nSector, 121);
assert(nSprite2 >= 0 && nSprite2 < kMaxSprites);
2021-10-15 20:55:03 +00:00
auto pSprite2 = &sprite[nSprite2];
pSprite2->x = x;
pSprite2->y = y;
pSprite2->z = nFloorZ;
pSprite2->pal = 0;
pSprite2->clipdist = 50;
pSprite2->xoffset = 0;
pSprite2->yoffset = 0;
pSprite2->shade = -12;
pSprite2->picnum = 1;
pSprite2->ang = (RandomSize(9) + (nAngle - 256)) & kAngleMask;
pSprite2->backuppos();
if (!nVal)
{
pSprite2->xrepeat = 30;
pSprite2->yrepeat = 30;
pSprite2->xvel = bcos(pSprite2->ang);
pSprite2->yvel = bsin(pSprite2->ang);
pSprite2->zvel = -6000;
pSprite2->cstat = 0;
}
else
{
pSprite2->xrepeat = 60;
pSprite2->yrepeat = 60;
pSprite2->xvel = 0;
pSprite2->yvel = 0;
pSprite2->zvel = -2000;
pSprite2->cstat = 0x101;
}
pSprite2->lotag = runlist_HeadRun() + 1;
pSprite2->extra = -1;
pSprite2->hitag = 0;
GrabTimeSlot(3);
QueenEgg[nEgg].nHealth = 200;
2020-11-29 23:40:25 +00:00
QueenEgg[nEgg].nFrame = 0;
QueenEgg[nEgg].nSprite = nSprite2;
QueenEgg[nEgg].field_E = nVal;
QueenEgg[nEgg].nTarget = QueenList[nQueen].nTarget;
if (nVal)
{
nVal = 4;
QueenEgg[nEgg].field_C = 200;
}
QueenEgg[nEgg].nAction = nVal;
pSprite2->owner = runlist_AddRunRec(pSprite2->lotag - 1, nEgg, 0x1D0000);
QueenEgg[nEgg].nRunPtr = runlist_AddRunRec(NewRun, nEgg, 0x1D0000);
}
2021-10-15 20:55:03 +00:00
void AIQueenEgg::Tick(RunListEvent* ev)
{
short nEgg = RunData[ev->nRun].nObjIndex;
2021-10-15 20:55:03 +00:00
Egg* pEgg = &QueenEgg[nEgg];
short nSprite = pEgg->nSprite;
2021-10-15 20:55:03 +00:00
auto pSprite = &sprite[nSprite];
short nAction = pEgg->nAction;
short nTarget;
bool bVal = false;
2021-10-15 20:55:03 +00:00
if (pEgg->nHealth <= 0)
{
DestroyEgg(nEgg);
return;
}
if (nAction == 0 || nAction == 4) {
Gravity(nSprite);
}
short nSeq = SeqOffsets[kSeqQueenEgg] + EggSeq[nAction].a;
pSprite->picnum = seq_GetSeqPicnum2(nSeq, pEgg->nFrame);
if (nAction != 4)
{
2021-10-15 20:55:03 +00:00
seq_MoveSequence(nSprite, nSeq, pEgg->nFrame);
pEgg->nFrame++;
if (pEgg->nFrame >= SeqSize[nSeq])
{
2021-10-15 20:55:03 +00:00
pEgg->nFrame = 0;
bVal = true;
}
nTarget = UpdateEnemy(&pEgg->nTarget);
pEgg->nTarget = nTarget;
if (nTarget >= 0 && (sprite[nTarget].cstat & 0x101) == 0)
{
pEgg->nTarget = -1;
pEgg->nAction = 0;
}
else
{
nTarget = FindPlayer(-nSprite, 1000);
pEgg->nTarget = nTarget;
}
}
switch (nAction)
{
case 0:
{
int nMov = MoveCreature(nSprite);
if (!nMov) {
break;
}
2021-10-15 20:55:03 +00:00
if (nMov & 0x20000)
{
2021-10-15 20:55:03 +00:00
if (!RandomSize(1))
{
pEgg->nAction = 1;
pEgg->nFrame = 0;
}
else
{
DestroyEgg(nEgg);
}
2021-10-15 20:55:03 +00:00
}
else
{
short nAngle;
2021-10-15 20:55:03 +00:00
switch (nMov & 0xC000)
{
default:
return;
case 0x8000:
nAngle = GetWallNormal(nMov & 0x3FFF);
break;
case 0xC000:
nAngle = sprite[nMov & 0x3FFF].ang;
break;
}
2021-10-15 20:55:03 +00:00
pSprite->ang = nAngle;
pSprite->xvel = bcos(nAngle, -1);
pSprite->yvel = bsin(nAngle, -1);
}
2021-10-15 20:55:03 +00:00
break;
}
2021-10-15 20:55:03 +00:00
case 1:
{
if (bVal)
{
pEgg->nAction = 3;
pSprite->cstat = 0x101;
}
break;
}
2021-10-15 20:55:03 +00:00
case 2:
case 3:
{
int nMov = QueenAngleChase(nSprite, nTarget, nHeadVel, 64);
2021-10-15 20:55:03 +00:00
switch (nMov & 0xC000)
{
case 0xC000:
if (sprite[nMov & 0x3FFF].statnum != 121)
{
2021-10-15 20:55:03 +00:00
runlist_DamageEnemy(nMov & 0x3FFF, nSprite, 5);
}
fallthrough__;
case 0x8000:
pSprite->ang += (RandomSize(9) + 768);
pSprite->ang &= kAngleMask;
pSprite->xvel = bcos(pSprite->ang, -3);
pSprite->yvel = bsin(pSprite->ang, -3);
pSprite->zvel = -RandomSize(5);
break;
}
2021-10-15 20:55:03 +00:00
return;
}
2021-10-15 20:55:03 +00:00
case 4:
{
int nMov = MoveCreature(nSprite);
2021-10-15 20:55:03 +00:00
if (nMov & 0x20000)
{
pSprite->zvel = -(pSprite->zvel - 256);
if (pSprite->zvel < -512)
{
pSprite->zvel = 0;
}
}
2021-10-15 20:55:03 +00:00
pEgg->field_C--;
if (pEgg->field_C <= 0)
{
short nWaspSprite = BuildWasp(-2, pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, pSprite->ang);
pSprite->z = sprite[nWaspSprite].z;
2021-10-15 20:55:03 +00:00
DestroyEgg(nEgg);
}
break;
}
}
}
2021-10-15 20:55:03 +00:00
void AIQueenEgg::RadialDamage(RunListEvent* ev)
{
short nEgg = RunData[ev->nRun].nObjIndex;
2021-10-15 20:55:03 +00:00
Egg* pEgg = &QueenEgg[nEgg];
short nSprite = pEgg->nSprite;
auto pSprite = &sprite[nSprite];
2021-10-15 20:55:03 +00:00
if (sprite[nRadialSpr].statnum != 121 && (pSprite->cstat & 0x101) != 0)
{
int nDamage = runlist_CheckRadialDamage(nSprite);
2021-10-15 20:55:03 +00:00
pEgg->nHealth -= nDamage;
}
}
2021-10-15 20:55:03 +00:00
void AIQueenEgg::Damage(RunListEvent* ev)
{
short nEgg = RunData[ev->nRun].nObjIndex;
2021-10-15 20:55:03 +00:00
Egg* pEgg = &QueenEgg[nEgg];
2021-10-15 20:55:03 +00:00
if (ev->nDamage != 0 && pEgg->nHealth > 0)
{
pEgg->nHealth -= ev->nDamage;
2021-10-15 20:55:03 +00:00
if (pEgg->nHealth <= 0)
DestroyEgg(nEgg);
}
}
2021-10-15 20:55:03 +00:00
void AIQueenEgg::Draw(RunListEvent* ev)
{
short nEgg = RunData[ev->nRun].nObjIndex;
2021-10-15 20:55:03 +00:00
Egg* pEgg = &QueenEgg[nEgg];
seq_PlotSequence(ev->nParam, SeqOffsets[kSeqQueenEgg] + EggSeq[pEgg->nAction].a, pEgg->nFrame, EggSeq[pEgg->nAction].b);
2021-10-15 20:55:03 +00:00
}
2021-10-15 20:55:03 +00:00
void FuncQueenEgg(int nObject, int nMessage, int nDamage, int nRun)
{
AIQueenEgg ai;
runlist_DispatchEvent(&ai, nObject, nMessage, nDamage, nRun);
}
void BuildQueenHead(short nQueen)
{
short nSprite = QueenList[nQueen].nSprite;
auto pSprite = &sprite[nSprite];
int x = pSprite->x;
int y = pSprite->y;
short nAngle = pSprite->ang;
short nSector = pSprite->sectnum;
int z = sector[nSector].floorz;
int nSprite2 = insertsprite(nSector, 121);
assert(nSprite2 >= 0 && nSprite2 < kMaxSprites);
2021-10-15 20:55:03 +00:00
auto pSprite2 = &sprite[nSprite2];
pSprite2->x = x;
pSprite2->y = y;
pSprite2->z = z;
pSprite2->clipdist = 70;
pSprite2->xrepeat = 80;
pSprite2->yrepeat = 80;
pSprite2->cstat = 0;
pSprite2->picnum = 1;
pSprite2->shade = -12;
pSprite2->pal = 0;
pSprite2->xoffset = 0;
pSprite2->yoffset = 0;
pSprite2->ang = nAngle;
nVelShift = 2;
SetHeadVel(nSprite2);
pSprite2->zvel = -8192;
pSprite2->lotag = runlist_HeadRun() + 1;
pSprite2->hitag = 0;
pSprite2->extra = -1;
GrabTimeSlot(3);
QueenHead.nHealth = 800;
QueenHead.nAction = 0;
QueenHead.nTarget = QueenList[nQueen].nTarget;
2020-11-29 23:40:25 +00:00
QueenHead.nFrame = 0;
QueenHead.nSprite = nSprite2;
QueenHead.field_C = 0;
pSprite2->owner = runlist_AddRunRec(pSprite2->lotag - 1, 0, 0x1B0000);
QueenHead.field_8 = runlist_AddRunRec(NewRun, 0, 0x1B0000);
2020-11-29 23:40:25 +00:00
QueenHead.tails = 0;
}
2021-10-15 20:55:03 +00:00
void AIQueenHead::Tick(RunListEvent* ev)
{
short nHead = RunData[ev->nRun].nObjIndex;
short nSprite = QueenHead.nSprite;
2021-10-15 20:55:03 +00:00
auto pSprite = &sprite[nSprite];
int nSector = pSprite->sectnum;
assert(nSector >= 0 && nSector < kMaxSectors);
short nAction = QueenHead.nAction;
short nTarget, nHd;
int var_14 = 0;
2021-10-15 20:55:03 +00:00
if (nAction == 0) {
Gravity(nSprite);
}
short nSeq = SeqOffsets[kSeqQueen] + HeadSeq[QueenHead.nAction].a;
seq_MoveSequence(nSprite, nSeq, QueenHead.nFrame);
pSprite->picnum = seq_GetSeqPicnum2(nSeq, QueenHead.nFrame);
QueenHead.nFrame++;
if (QueenHead.nFrame >= SeqSize[nSeq])
{
2021-10-15 20:55:03 +00:00
QueenHead.nFrame = 0;
var_14 = 1;
}
2021-10-15 20:55:03 +00:00
nTarget = QueenHead.nTarget;
2021-10-15 20:55:03 +00:00
if (nTarget > -1)
{
if (!(sprite[nTarget].cstat & 0x101))
{
nTarget = -1;
QueenHead.nTarget = nTarget;
}
}
else
{
nTarget = FindPlayer(nSprite, 1000);
QueenHead.nTarget = nTarget;
}
2021-10-15 20:55:03 +00:00
switch (nAction)
{
case 0:
if (QueenHead.field_C > 0)
{
QueenHead.field_C--;
if (QueenHead.field_C == 0)
{
BuildTail();
2021-10-15 20:55:03 +00:00
QueenHead.nAction = 6;
nHeadVel = 800;
pSprite->cstat = 0x101;
}
else if (QueenHead.field_C < 60)
{
2021-10-15 20:55:03 +00:00
pSprite->shade--;
}
2021-10-15 20:55:03 +00:00
}
else
{
int nMov = MoveCreature(nSprite);
2021-10-15 20:55:03 +00:00
// original BUG - this line doesn't exist in original code?
short nNewAng = pSprite->ang;
2021-10-15 20:55:03 +00:00
switch (nMov & 0xFC000)
{
2021-10-15 20:55:03 +00:00
default:
return;
case 0xC000:
nNewAng = sprite[nMov & 0x3FFF].ang;
break;
case 0x8000:
nNewAng = GetWallNormal(nMov & 0x3FFF);
break;
case 0x20000:
pSprite->zvel = -(pSprite->zvel >> 1);
if (pSprite->zvel > -256)
{
2021-10-15 20:55:03 +00:00
nVelShift = 100;
pSprite->zvel = 0;
}
2021-10-15 20:55:03 +00:00
break;
}
2021-10-15 20:55:03 +00:00
// original BUG - var_18 isn't being set if the check above == 0x20000 ?
pSprite->ang = nNewAng;
nVelShift++;
if (nVelShift < 5)
{
2021-10-15 20:55:03 +00:00
SetHeadVel(nSprite);
}
2021-10-15 20:55:03 +00:00
else
{
2021-10-15 20:55:03 +00:00
pSprite->xvel = 0;
pSprite->yvel = 0;
2021-10-15 20:55:03 +00:00
if (pSprite->zvel == 0)
{
QueenHead.field_C = 120;
}
}
}
2021-10-15 20:55:03 +00:00
break;
2021-10-15 20:55:03 +00:00
case 6:
if (var_14)
{
QueenHead.nAction = 1;
QueenHead.nFrame = 0;
break;
}
fallthrough__;
2021-10-15 20:55:03 +00:00
case 1:
if ((sprite[nTarget].z - 51200) > pSprite->z)
{
QueenHead.nAction = 4;
QueenHead.nFrame = 0;
}
else
{
pSprite->z -= 2048;
goto __MOVEQS;
}
break;
2021-10-15 20:55:03 +00:00
case 4:
case 7:
case 8:
if (var_14)
{
int nRnd = RandomSize(2);
2021-10-15 20:55:03 +00:00
if (nRnd == 0)
{
QueenHead.nAction = 4;
}
else if (nRnd == 1)
{
QueenHead.nAction = 7;
}
else
{
QueenHead.nAction = 8;
}
}
2021-10-15 20:55:03 +00:00
if (nTarget > -1)
{
int nMov = QueenAngleChase(nSprite, nTarget, nHeadVel, 64);
2021-10-15 20:55:03 +00:00
switch (nMov & 0xC000)
{
case 0x8000:
break;
case 0xC000:
if ((nMov & 0x3FFF) == nTarget)
{
runlist_DamageEnemy(nTarget, nSprite, 10);
D3PlayFX(StaticSound[kSoundQTail] | 0x2000, nSprite);
2021-10-15 20:55:03 +00:00
pSprite->ang += RandomSize(9) + 768;
pSprite->ang &= kAngleMask;
2021-10-15 20:55:03 +00:00
pSprite->zvel = (-20) - RandomSize(6);
2021-10-15 20:55:03 +00:00
SetHeadVel(nSprite);
}
break;
}
}
2021-10-15 20:55:03 +00:00
// switch break. MoveQS stuff?
__MOVEQS:
MoveQX[nQHead] = pSprite->x;
MoveQY[nQHead] = pSprite->y;
MoveQZ[nQHead] = pSprite->z;
assert(pSprite->sectnum >= 0 && pSprite->sectnum < kMaxSectors);
MoveQS[nQHead] = pSprite->sectnum;
MoveQA[nQHead] = pSprite->ang;
2021-10-15 20:55:03 +00:00
nHd = nQHead;
2021-10-15 20:55:03 +00:00
for (int i = 0; i < QueenHead.tails; i++)
{
nHd -= 3;
if (nHd < 0) {
nHd += (24 + 1); // TODO - enum/define for these
//assert(nHd < 24 && nHd >= 0);
}
2021-10-15 20:55:03 +00:00
int var_20 = MoveQS[nHd];
short nTSprite = tailspr[i];
2021-10-15 20:55:03 +00:00
if (var_20 != sprite[nTSprite].sectnum)
{
assert(var_20 >= 0 && var_20 < kMaxSectors);
mychangespritesect(nTSprite, var_20);
}
2021-10-15 20:55:03 +00:00
sprite[nTSprite].x = MoveQX[nHd];
sprite[nTSprite].y = MoveQY[nHd];
sprite[nTSprite].z = MoveQZ[nHd];
sprite[nTSprite].ang = MoveQA[nHd];
}
2021-10-15 20:55:03 +00:00
nQHead++;
if (nQHead >= 25)
{
nQHead = 0;
}
2021-10-15 20:55:03 +00:00
break;
2021-10-15 20:55:03 +00:00
case 5:
QueenHead.field_C--;
if (QueenHead.field_C <= 0)
{
QueenHead.field_C = 3;
2021-10-15 20:55:03 +00:00
if (QueenHead.tails--)
{
if (QueenHead.tails >= 15 || QueenHead.tails < 10)
{
int x = pSprite->x;
int y = pSprite->y;
int z = pSprite->z;
short nSector = pSprite->sectnum;
int nAngle = RandomSize(11) & kAngleMask;
2021-10-15 20:55:03 +00:00
pSprite->xrepeat = 127 - QueenHead.tails;
pSprite->yrepeat = 127 - QueenHead.tails;
2021-10-15 20:55:03 +00:00
pSprite->cstat = 0x8000;
// DEMO-TODO: in disassembly angle was used without masking and thus causing OOB issue.
// This behavior probably would be needed emulated for demo compatibility
int dx = bcos(nAngle, 10);
int dy = bsin(nAngle, 10);
int dz = (RandomSize(5) - RandomSize(5)) << 7;
2021-10-15 20:55:03 +00:00
movesprite(nSprite, dx, dy, dz, 0, 0, CLIPMASK1);
2021-10-15 20:55:03 +00:00
BlowChunks(nSprite);
BuildExplosion(nSprite);
2021-10-15 20:55:03 +00:00
mychangespritesect(nSprite, nSector);
2021-10-15 20:55:03 +00:00
pSprite->x = x;
pSprite->y = y;
pSprite->z = z;
if (QueenHead.tails < 10) {
for (int i = (10 - QueenHead.tails) * 2; i > 0; i--)
{
BuildLavaLimb(&exhumedActors[nSprite], i, GetSpriteHeight(nSprite));
}
}
2021-10-15 20:55:03 +00:00
}
}
else
{
2021-10-15 20:55:03 +00:00
BuildExplosion(nSprite);
2021-10-15 20:55:03 +00:00
int i;
for (i = 0; i < 10; i++)
{
2021-10-15 20:55:03 +00:00
BlowChunks(nSprite);
}
2021-10-15 20:55:03 +00:00
for (i = 0; i < 20; i++)
{
BuildLavaLimb(&exhumedActors[nSprite], i, GetSpriteHeight(nSprite));
}
2021-10-15 20:55:03 +00:00
runlist_SubRunRec(pSprite->owner);
runlist_SubRunRec(QueenHead.field_8);
mydeletesprite(nSprite);
runlist_ChangeChannel(QueenChan[0], 1);
}
2021-10-15 20:55:03 +00:00
}
break;
}
}
2021-10-15 20:55:03 +00:00
void AIQueenHead::RadialDamage(RunListEvent* ev)
{
short nHead = RunData[ev->nRun].nObjIndex;
2021-10-15 20:55:03 +00:00
short nSprite = QueenHead.nSprite;
auto pSprite = &sprite[nSprite];
if (sprite[nRadialSpr].statnum != 121 && (pSprite->cstat & 0x101) != 0)
{
ev->nDamage = runlist_CheckRadialDamage(nSprite);
if (ev->nDamage) Damage(ev);
}
}
2021-10-15 20:55:03 +00:00
void AIQueenHead::Damage(RunListEvent* ev)
{
short nHead = RunData[ev->nRun].nObjIndex;
2021-10-15 20:55:03 +00:00
short nSprite = QueenHead.nSprite;
auto pSprite = &sprite[nSprite];
if (QueenHead.nHealth > 0 && ev->nDamage != 0)
{
QueenHead.nHealth -= ev->nDamage;
if (!RandomSize(4))
{
QueenHead.nTarget = ev->nParam;
2021-10-15 20:55:03 +00:00
QueenHead.nAction = 7;
QueenHead.nFrame = 0;
}
if (QueenHead.nHealth <= 0)
{
if (DestroyTailPart())
{
2021-10-15 20:55:03 +00:00
QueenHead.nHealth = 200;
nHeadVel += 100;
}
else
{
2021-10-15 20:55:03 +00:00
QueenHead.nAction = 5;
QueenHead.nFrame = 0;
QueenHead.field_C = 0;
QueenHead.tails = 80;
pSprite->cstat = 0;
}
}
2021-10-15 20:55:03 +00:00
}
}
2021-10-15 20:55:03 +00:00
void AIQueenHead::Draw(RunListEvent* ev)
{
short nHead = RunData[ev->nRun].nObjIndex;
2021-10-15 20:55:03 +00:00
short nAction = QueenHead.nAction;
short nSeq = SeqOffsets[kSeqQueen];
int edx;
if (nHead == 0)
{
edx = HeadSeq[nAction].b;
nSeq += HeadSeq[nAction].a;
}
2021-10-15 20:55:03 +00:00
else
{
edx = 1;
nSeq += 73;
}
seq_PlotSequence(ev->nParam, nSeq, QueenHead.nFrame, edx);
2021-10-15 20:55:03 +00:00
}
void FuncQueenHead(int nObject, int nMessage, int nDamage, int nRun)
{
AIQueenHead ai;
runlist_DispatchEvent(&ai, nObject, nMessage, nDamage, nRun);
}
void BuildQueen(int nSprite, int x, int y, int z, int nSector, int nAngle, int nChannel)
{
QueenCount--;
short nQueen = QueenCount;
if (nQueen < 0) {
return;
}
2021-10-15 20:55:03 +00:00
auto pSprite = &sprite[nSprite];
if (nSprite == -1)
{
nSprite = insertsprite(nSector, 121);
2021-10-15 20:55:03 +00:00
pSprite = &sprite[nSprite];
}
else
{
changespritestat(nSprite, 121);
x = pSprite->x;
y = pSprite->y;
z = sector[pSprite->sectnum].floorz;
nAngle = pSprite->ang;
}
assert(nSprite >= 0 && nSprite < kMaxSprites);
pSprite->x = x;
pSprite->y = y;
pSprite->z = z;
pSprite->cstat = 0x101;
pSprite->pal = 0;
pSprite->shade = -12;
pSprite->clipdist = 100;
pSprite->xrepeat = 80;
pSprite->yrepeat = 80;
pSprite->xoffset = 0;
pSprite->yoffset = 0;
pSprite->picnum = 1;
pSprite->ang = nAngle;
pSprite->xvel = 0;
pSprite->yvel = 0;
pSprite->zvel = 0;
pSprite->lotag = runlist_HeadRun() + 1;
pSprite->extra = -1;
pSprite->hitag = 0;
GrabTimeSlot(3);
QueenList[nQueen].nAction = 0;
QueenList[nQueen].nHealth = 4000;
QueenList[nQueen].nFrame = 0;
QueenList[nQueen].nSprite = nSprite;
QueenList[nQueen].nTarget = -1;
QueenList[nQueen].field_A = 0;
QueenList[nQueen].field_10 = 5;
QueenList[nQueen].field_C = 0;
QueenChan[nQueen] = nChannel;
nHeadVel = 800;
pSprite->owner = runlist_AddRunRec(pSprite->lotag - 1, nQueen, 0x1A0000);
runlist_AddRunRec(NewRun, nQueen, 0x1A0000);
nCreaturesTotal++;
}
void SetQueenSpeed(short nSprite, int nSpeed)
{
2021-10-15 20:55:03 +00:00
auto pSprite = &sprite[nSprite];
pSprite->xvel = bcos(pSprite->ang, -(2 - nSpeed));
pSprite->yvel = bsin(pSprite->ang, -(2 - nSpeed));
}
2021-10-15 20:55:03 +00:00
void AIQueen::Tick(RunListEvent* ev)
{
short nQueen = RunData[ev->nRun].nObjIndex;
assert(nQueen >= 0 && nQueen < kMaxQueens);
short nSprite = QueenList[nQueen].nSprite;
2021-10-15 20:55:03 +00:00
auto pSprite = &sprite[nSprite];
short nAction = QueenList[nQueen].nAction;
short si = QueenList[nQueen].field_A;
short nTarget = QueenList[nQueen].nTarget;
bool bVal = false;
2021-10-15 20:55:03 +00:00
if (si < 3) {
Gravity(nSprite);
}
short nSeq = SeqOffsets[kSeqQueen] + QueenSeq[nAction].a;
pSprite->picnum = seq_GetSeqPicnum2(nSeq, QueenList[nQueen].nFrame);
seq_MoveSequence(nSprite, nSeq, QueenList[nQueen].nFrame);
QueenList[nQueen].nFrame++;
if (QueenList[nQueen].nFrame >= SeqSize[nSeq])
{
2021-10-15 20:55:03 +00:00
QueenList[nQueen].nFrame = 0;
bVal = true;
}
2021-10-15 20:55:03 +00:00
short nFlag = FrameFlag[SeqBase[nSeq] + QueenList[nQueen].nFrame];
2021-10-15 20:55:03 +00:00
if (nTarget > -1)
{
if (nAction < 7)
{
2021-10-15 20:55:03 +00:00
if (!(pSprite->cstat & 0x101))
{
nTarget = -1;
QueenList[nQueen].nTarget = -1;
QueenList[nQueen].nAction = 0;
}
2021-10-15 20:55:03 +00:00
}
}
switch (nAction)
{
case 0:
{
if (nTarget < 0)
{
nTarget = FindPlayer(nSprite, 60);
}
2021-10-15 20:55:03 +00:00
if (nTarget >= 0)
{
QueenList[nQueen].nAction = QueenList[nQueen].field_A + 1;
QueenList[nQueen].nFrame = 0;
QueenList[nQueen].nTarget = nTarget;
QueenList[nQueen].field_C = RandomSize(7);
2021-10-15 20:55:03 +00:00
SetQueenSpeed(nSprite, si);
}
break;
}
2021-10-15 20:55:03 +00:00
case 6:
{
if (bVal)
{
BuildQueenEgg(nQueen, 1);
QueenList[nQueen].nAction = 3;
QueenList[nQueen].field_C = RandomSize(6) + 60;
}
2021-10-15 20:55:03 +00:00
break;
}
2021-10-15 20:55:03 +00:00
case 1:
case 2:
case 3:
{
QueenList[nQueen].field_C--;
2021-10-15 20:55:03 +00:00
if ((nQueen & 0x1F) == (totalmoves & 0x1F))
{
if (si < 2)
{
2021-10-15 20:55:03 +00:00
if (QueenList[nQueen].field_C <= 0)
{
2021-10-15 20:55:03 +00:00
QueenList[nQueen].nFrame = 0;
pSprite->xvel = 0;
pSprite->yvel = 0;
QueenList[nQueen].nAction = si + 4;
QueenList[nQueen].field_C = RandomSize(6) + 30;
break;
}
else
{
if (QueenList[nQueen].field_10 < 5)
{
2021-10-15 20:55:03 +00:00
QueenList[nQueen].field_10++;
}
2021-10-15 20:55:03 +00:00
// then to PLOTSPRITE
}
}
2021-10-15 20:55:03 +00:00
else
{
2021-10-15 20:55:03 +00:00
if (QueenList[nQueen].field_C <= 0)
{
2021-10-15 20:55:03 +00:00
if (WaspCount() < 100)
{
2021-10-15 20:55:03 +00:00
QueenList[nQueen].nAction = 6;
QueenList[nQueen].nFrame = 0;
break;
}
2021-10-15 20:55:03 +00:00
else
{
2021-10-15 20:55:03 +00:00
QueenList[nQueen].field_C = 30000;
// then to PLOTSPRITE
}
}
2021-10-15 20:55:03 +00:00
}
2021-10-15 20:55:03 +00:00
// loc_35B4B
PlotCourseToSprite(nSprite, nTarget);
SetQueenSpeed(nSprite, si);
}
2021-10-15 20:55:03 +00:00
int nMov = MoveCreatureWithCaution(nSprite);
2021-10-15 20:55:03 +00:00
switch (nMov & 0xC000)
{
case 0xC000:
if ((si == 2) && ((nMov & 0x3FFF) == nTarget))
{
runlist_DamageEnemy(nTarget, nSprite, 5);
break;
}
fallthrough__;
case 0x8000:
pSprite->ang += 256;
pSprite->ang &= kAngleMask;
2021-10-15 20:55:03 +00:00
SetQueenSpeed(nSprite, si);
break;
}
2021-10-15 20:55:03 +00:00
// loc_35BD2
if (nAction && nTarget != -1)
{
if (!(sprite[nTarget].cstat & 0x101))
{
QueenList[nQueen].nAction = 0;
QueenList[nQueen].nFrame = 0;
QueenList[nQueen].field_C = 100;
QueenList[nQueen].nTarget = -1;
2021-10-15 20:55:03 +00:00
pSprite->xvel = 0;
pSprite->yvel = 0;
}
}
2021-10-15 20:55:03 +00:00
break;
}
2021-10-15 20:55:03 +00:00
case 4:
case 5:
{
if (bVal && QueenList[nQueen].field_10 <= 0)
{
QueenList[nQueen].nAction = 0;
QueenList[nQueen].field_C = 15;
}
else
{
if (nFlag & 0x80)
{
QueenList[nQueen].field_10--;
2021-10-15 20:55:03 +00:00
PlotCourseToSprite(nSprite, nTarget);
2021-10-15 20:55:03 +00:00
if (!si)
{
2021-10-15 20:55:03 +00:00
BuildBullet(nSprite, 12, 0, 0, -1, pSprite->ang, nTarget + 10000, 1);
}
2021-10-15 20:55:03 +00:00
else
{
2021-10-15 20:55:03 +00:00
BuildQueenEgg(nQueen, 0);
}
2021-10-15 20:55:03 +00:00
}
}
2021-10-15 20:55:03 +00:00
break;
}
case 7:
{
if (bVal)
{
QueenList[nQueen].nAction = 0;
QueenList[nQueen].nFrame = 0;
}
2021-10-15 20:55:03 +00:00
break;
}
2021-10-15 20:55:03 +00:00
case 8:
case 9:
{
if (bVal)
{
if (nAction == 9)
{
QueenList[nQueen].field_C--;
if (QueenList[nQueen].field_C <= 0)
{
pSprite->cstat = 0;
2021-10-15 20:55:03 +00:00
for (int i = 0; i < 20; i++)
{
short nChunkSprite = BuildCreatureChunk(nSprite, seq_GetSeqPicnum(kSeqQueen, 57, 0)) & 0xFFFF;
2021-10-15 20:55:03 +00:00
sprite[nChunkSprite].picnum = kQueenChunk + (i % 3);
sprite[nChunkSprite].xrepeat = 100;
sprite[nChunkSprite].yrepeat = 100;
}
2021-10-15 20:55:03 +00:00
short nChunkSprite = BuildCreatureChunk(nSprite, seq_GetSeqPicnum(kSeqQueen, 57, 0));
2021-10-15 20:55:03 +00:00
sprite[nChunkSprite].picnum = kTile3126;
sprite[nChunkSprite].yrepeat = 100;
sprite[nChunkSprite].xrepeat = 100;
2021-10-15 20:55:03 +00:00
PlayFXAtXYZ(
StaticSound[kSound40],
pSprite->x,
pSprite->y,
pSprite->z,
pSprite->sectnum);
2021-10-15 20:55:03 +00:00
BuildQueenHead(nQueen);
2021-10-15 20:55:03 +00:00
QueenList[nQueen].nAction++;
}
}
2021-10-15 20:55:03 +00:00
else
QueenList[nQueen].nAction++;
}
2021-10-15 20:55:03 +00:00
break;
}
2021-10-15 20:55:03 +00:00
case 10:
{
pSprite->cstat &= 0xFEFE;
break;
}
}
}
2021-10-15 20:55:03 +00:00
void AIQueen::RadialDamage(RunListEvent* ev)
{
short nQueen = RunData[ev->nRun].nObjIndex;
2021-10-15 20:55:03 +00:00
assert(nQueen >= 0 && nQueen < kMaxQueens);
short nSprite = QueenList[nQueen].nSprite;
auto pSprite = &sprite[nSprite];
2021-10-15 20:55:03 +00:00
if (sprite[nRadialSpr].statnum != 121 && (pSprite->cstat & 0x101) != 0)
{
ev->nDamage = runlist_CheckRadialDamage(nSprite);
if (ev->nDamage) Damage(ev);
}
}
2021-10-15 20:55:03 +00:00
void AIQueen::Damage(RunListEvent* ev)
{
short nQueen = RunData[ev->nRun].nObjIndex;
2021-10-15 20:55:03 +00:00
assert(nQueen >= 0 && nQueen < kMaxQueens);
2021-10-15 20:55:03 +00:00
short nSprite = QueenList[nQueen].nSprite;
auto pSprite = &sprite[nSprite];
short si = QueenList[nQueen].field_A;
2021-10-15 20:55:03 +00:00
if (QueenList[nQueen].nHealth > 0)
{
QueenList[nQueen].nHealth -= dmgAdjust(ev->nDamage);
2021-10-15 20:55:03 +00:00
if (QueenList[nQueen].nHealth <= 0)
{
pSprite->xvel = 0;
pSprite->yvel = 0;
pSprite->zvel = 0;
2021-10-15 20:55:03 +00:00
QueenList[nQueen].field_A++;
2021-10-15 20:55:03 +00:00
switch (QueenList[nQueen].field_A)
{
case 1:
QueenList[nQueen].nHealth = 4000;
QueenList[nQueen].nAction = 7;
2021-10-15 20:55:03 +00:00
BuildAnim(-1, 36, 0, pSprite->x, pSprite->y, pSprite->z - 7680, pSprite->sectnum, pSprite->xrepeat, 4);
break;
case 2:
QueenList[nQueen].nHealth = 4000;
QueenList[nQueen].nAction = 7;
DestroyAllEggs();
break;
case 3:
QueenList[nQueen].nAction = 8;
QueenList[nQueen].nHealth = 0;
QueenList[nQueen].field_C = 5;
nCreaturesKilled++;
break;
}
2021-10-15 20:55:03 +00:00
QueenList[nQueen].nFrame = 0;
}
2021-10-15 20:55:03 +00:00
else
{
if (si > 0 && !RandomSize(4))
{
QueenList[nQueen].nAction = 7;
QueenList[nQueen].nFrame = 0;
}
}
2021-10-15 20:55:03 +00:00
}
}
2021-10-15 20:55:03 +00:00
void AIQueen::Draw(RunListEvent* ev)
{
short nQueen = RunData[ev->nRun].nObjIndex;
2021-10-15 20:55:03 +00:00
assert(nQueen >= 0 && nQueen < kMaxQueens);
short nAction = QueenList[nQueen].nAction;
seq_PlotSequence(ev->nParam, SeqOffsets[kSeqQueen] + QueenSeq[nAction].a, QueenList[nQueen].nFrame, QueenSeq[nAction].b);
2021-10-15 20:55:03 +00:00
}
void FuncQueen(int nObject, int nMessage, int nDamage, int nRun)
{
AIQueen ai;
runlist_DispatchEvent(&ai, nObject, nMessage, nDamage, nRun);
}
END_PS_NS