/ source / blood / src / aispid.cpp
aispid.cpp
  1  //-------------------------------------------------------------------------
  2  /*
  3  Copyright (C) 2010-2019 EDuke32 developers and contributors
  4  Copyright (C) 2019 Nuke.YKT
  5  
  6  This file is part of NBlood.
  7  
  8  NBlood is free software; you can redistribute it and/or
  9  modify it under the terms of the GNU General Public License version 2
 10  as published by the Free Software Foundation.
 11  
 12  This program is distributed in the hope that it will be useful,
 13  but WITHOUT ANY WARRANTY; without even the implied warranty of
 14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 15  
 16  See the GNU General Public License for more details.
 17  
 18  You should have received a copy of the GNU General Public License
 19  along with this program; if not, write to the Free Software
 20  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 21  */
 22  //-------------------------------------------------------------------------
 23  #include "compat.h"
 24  #include "build.h"
 25  #include "pragmas.h"
 26  #include "mmulti.h"
 27  #include "common_game.h"
 28  
 29  #include "actor.h"
 30  #include "ai.h"
 31  #include "aispid.h"
 32  #include "blood.h"
 33  #include "db.h"
 34  #include "dude.h"
 35  #include "endgame.h"
 36  #include "eventq.h"
 37  #include "levels.h"
 38  #include "player.h"
 39  #include "seq.h"
 40  #include "sfx.h"
 41  #include "trig.h"
 42  
 43  static void SpidBiteSeqCallback(int, int);
 44  static void SpidJumpSeqCallback(int, int);
 45  static void SpidBirthSeqCallback(int, int);
 46  static void thinkSearch(spritetype *, XSPRITE *);
 47  static void thinkGoto(spritetype *, XSPRITE *);
 48  static void thinkChase(spritetype *, XSPRITE *);
 49  
 50  static int nBiteClient = seqRegisterClient(SpidBiteSeqCallback);
 51  static int nJumpClient = seqRegisterClient(SpidJumpSeqCallback);
 52  static int nBirthClient = seqRegisterClient(SpidBirthSeqCallback);
 53  
 54  AISTATE spidIdle = { kAiStateIdle, 0, -1, 0, NULL, NULL, aiThinkTarget, NULL };
 55  AISTATE spidChase = { kAiStateChase, 7, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
 56  AISTATE spidDodge = { kAiStateMove, 7, -1, 90, NULL, aiMoveDodge, NULL, &spidChase };
 57  AISTATE spidGoto = { kAiStateMove, 7, -1, 600, NULL, aiMoveForward, thinkGoto, &spidIdle };
 58  AISTATE spidSearch = { kAiStateSearch, 7, -1, 1800, NULL, aiMoveForward, thinkSearch, &spidIdle };
 59  AISTATE spidBite = { kAiStateChase, 6, nBiteClient, 60, NULL, NULL, NULL, &spidChase };
 60  AISTATE spidJump = { kAiStateChase, 8, nJumpClient, 60, NULL, aiMoveForward, NULL, &spidChase };
 61  AISTATE spidBirth = { kAiStateOther, 0, nBirthClient, 60, NULL, NULL, NULL, &spidIdle };
 62  
 63  static char SpidPoisonPlayer(XSPRITE *pXDude, int nBlind, int max)
 64  {
 65      dassert(pXDude != NULL);
 66      int nDude = pXDude->reference;
 67      spritetype *pDude = &sprite[nDude];
 68      if (IsPlayerSprite(pDude))
 69      {
 70          nBlind <<= 4;
 71          max <<= 4;
 72          PLAYER *pPlayer = &gPlayer[pDude->type-kDudePlayer1];
 73          if (pPlayer->blindEffect < max)
 74          {
 75              pPlayer->blindEffect = ClipHigh(pPlayer->blindEffect+nBlind, max);
 76              return 1;
 77          }
 78      }
 79      return 0;
 80  }
 81  
 82  static void SpidBiteSeqCallback(int, int nXSprite)
 83  {
 84      XSPRITE *pXSprite = &xsprite[nXSprite];
 85      int nSprite = pXSprite->reference;
 86      spritetype *pSprite = &sprite[nSprite];
 87      int dx = Cos(pSprite->ang)>>16;
 88      int dy = Sin(pSprite->ang)>>16;
 89      dx += Random2(2000);
 90      dy += Random2(2000);
 91      int dz = Random2(2000);
 92      dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
 93      dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
 94      spritetype *pTarget = &sprite[pXSprite->target];
 95      XSPRITE *pXTarget = &xsprite[pTarget->extra];
 96      if (IsPlayerSprite(pTarget)) {
 97          
 98          int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
 99          if (hit == 3 && IsPlayerSprite(&sprite[gHitInfo.hitsprite])) {
100              dz += pTarget->z - pSprite->z;
101              PLAYER *pPlayer = &gPlayer[pTarget->type - kDudePlayer1];
102              switch (pSprite->type) {
103                  case kDudeSpiderBrown:
104                      actFireVector(pSprite, 0, 0, dx, dy, dz, kVectorSpiderBite);
105                      if (IsPlayerSprite(pTarget) && !pPlayer->godMode && powerupCheck(pPlayer, kPwUpDeathMask) <= 0 && Chance(0x4000))
106                          powerupActivate(pPlayer, kPwUpDeliriumShroom);
107                      break;
108                  case kDudeSpiderRed:
109                      actFireVector(pSprite, 0, 0, dx, dy, dz, kVectorSpiderBite);
110                      if (Chance(0x5000)) SpidPoisonPlayer(pXTarget, 4, 16);
111                      break;
112                  case kDudeSpiderBlack:
113                      actFireVector(pSprite, 0, 0, dx, dy, dz, kVectorSpiderBite);
114                      SpidPoisonPlayer(pXTarget, 8, 16);
115                      break;
116                  case kDudeSpiderMother: {
117                      actFireVector(pSprite, 0, 0, dx, dy, dz, kVectorSpiderBite);
118  
119                      dx += Random2(2000);
120                      dy += Random2(2000);
121                      dz += Random2(2000);
122                      actFireVector(pSprite, 0, 0, dx, dy, dz, kVectorSpiderBite);
123                      SpidPoisonPlayer(pXTarget, 8, 16);
124                      break;
125                  }
126              }
127          }
128  
129      }
130  }
131  
132  static void SpidJumpSeqCallback(int, int nXSprite)
133  {
134      XSPRITE *pXSprite = &xsprite[nXSprite];
135      int nSprite = pXSprite->reference;
136      spritetype *pSprite = &sprite[nSprite];
137      int dx = Cos(pSprite->ang)>>16;
138      int dy = Sin(pSprite->ang)>>16;
139      dx += Random2(200);
140      dy += Random2(200);
141      int dz = Random2(200);
142      dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
143      dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
144      spritetype *pTarget = &sprite[pXSprite->target];
145      if (IsPlayerSprite(pTarget)) {
146          dz += pTarget->z-pSprite->z;
147          switch (pSprite->type) {
148              case kDudeSpiderBrown:
149              case kDudeSpiderBlack:
150                  xvel[nSprite] = dx << 16;
151                  yvel[nSprite] = dy << 16;
152                  zvel[nSprite] = dz << 16;
153                  break;
154              case kDudeSpiderRed:
155                  break;
156          }
157      }
158  }
159  
160  static void SpidBirthSeqCallback(int, int nXSprite)
161  {
162      XSPRITE *pXSprite = &xsprite[nXSprite];
163      int nSprite = pXSprite->reference;
164      spritetype *pSprite = &sprite[nSprite];
165      dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
166      DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
167      dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
168      spritetype *pTarget = &sprite[pXSprite->target];
169      DUDEEXTRA_STATS *pDudeExtraE = &gDudeExtra[pSprite->extra].stats;
170      int dx = pXSprite->targetX-pSprite->x;
171      int dy = pXSprite->targetY-pSprite->y;
172      int nAngle = getangle(dx, dy);
173      int nDist = approxDist(dx, dy);
174      
175      spritetype *pSpawn = NULL;
176      if (IsPlayerSprite(pTarget) && pDudeExtraE->birthCounter < 10) {
177          if (klabs(pSprite->ang-nAngle) < pDudeInfo->periphery) {
178              if (nDist < 0x1a00 && nDist > 0x1400)
179                  pSpawn = actSpawnDude(pSprite, kDudeSpiderRed, pSprite->clipdist, 0);
180              else if ((nDist < 0xc00) || (nDist < 0x1400 && nDist > 0xc00))
181                  pSpawn = actSpawnDude(pSprite, kDudeSpiderBrown, pSprite->clipdist, 0);
182          }
183          
184          if (pSpawn) {
185              if (gGameOptions.nRandomizerMode && !VanillaMode())
186                  dbRandomizerMode(pSpawn);
187              if (dbIsBannedSprite(pSpawn, NULL)) { // if spawned sprite is banned, remove sprite
188                  evKill(pSpawn->index, 3);
189                  if (sprite[pSpawn->index].extra > 0)
190                      seqKill(3, sprite[pSpawn->index].extra);
191                  DeleteSprite(pSpawn->index);
192                  return;
193              }
194              pDudeExtraE->birthCounter++;
195              if ((pSpawn->type == kDudeSpiderRed) || (pSpawn->type == kDudeSpiderBrown) || VanillaMode())
196                  pSpawn->owner = nSprite;
197              gKillMgr.AddCount(pSpawn);
198          }
199      }
200  
201  }
202  
203  static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
204  {
205      aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
206      aiThinkTarget(pSprite, pXSprite);
207  }
208  
209  static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite)
210  {
211      dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
212      DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
213      int dx = pXSprite->targetX-pSprite->x;
214      int dy = pXSprite->targetY-pSprite->y;
215      int nAngle = getangle(dx, dy);
216      int nDist = approxDist(dx, dy);
217      aiChooseDirection(pSprite, pXSprite, nAngle);
218      if (nDist < 512 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
219          aiNewState(pSprite, pXSprite, &spidSearch);
220      aiThinkTarget(pSprite, pXSprite);
221  }
222  
223  static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite)
224  {
225      if (pXSprite->target == -1)
226      {
227          aiNewState(pSprite, pXSprite, &spidGoto);
228          return;
229      }
230      dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
231      DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
232      dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
233      spritetype *pTarget = &sprite[pXSprite->target];
234      XSPRITE *pXTarget = &xsprite[pTarget->extra];
235      int dx = pTarget->x-pSprite->x;
236      int dy = pTarget->y-pSprite->y;
237      aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
238      if (pXTarget->health == 0)
239      {
240          aiNewState(pSprite, pXSprite, &spidSearch);
241          return;
242      }
243      if (IsPlayerSprite(pTarget) && powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], kPwUpShadowCloak) > 0)
244      {
245          aiNewState(pSprite, pXSprite, &spidSearch);
246          return;
247      }
248      int nDist = approxDist(dx, dy);
249      if (nDist <= pDudeInfo->seeDist) {
250          int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
251          int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
252          if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum)) {
253              if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery) {
254                  aiSetTarget(pXSprite, pXSprite->target);
255                  
256                  switch (pSprite->type) {
257                      case kDudeSpiderRed:
258                          if (nDist < 0x399 && klabs(nDeltaAngle) < 85)
259                              aiNewState(pSprite, pXSprite, &spidBite);
260                          break;
261                      case kDudeSpiderBrown:
262                      case kDudeSpiderBlack:
263                          if (nDist < 0x733 && nDist > 0x399 && klabs(nDeltaAngle) < 85)
264                              aiNewState(pSprite, pXSprite, &spidJump);
265                          else if (nDist < 0x399 && klabs(nDeltaAngle) < 85)
266                              aiNewState(pSprite, pXSprite, &spidBite);
267                          break;
268                      case kDudeSpiderMother:
269                          if (nDist < 0x733 && nDist > 0x399 && klabs(nDeltaAngle) < 85)
270                              aiNewState(pSprite, pXSprite, &spidJump);
271                          else if (Chance(0x8000))
272                              aiNewState(pSprite, pXSprite, &spidBirth);
273                          break;
274                  }
275  
276                  return;
277              }
278          }
279      }
280  
281      aiNewState(pSprite, pXSprite, &spidGoto);
282      pXSprite->target = -1;
283  }