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 
30 #include "actor.h"
31 #include "ai.h"
32 #include "aitchern.h"
33 #include "blood.h"
34 #include "db.h"
35 #include "dude.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 sub_71A90(int, int);
44 static void sub_71BD4(int, int);
45 static void sub_720AC(int, int);
46 static void sub_72580(spritetype *, XSPRITE *);
47 static void sub_725A4(spritetype *, XSPRITE *);
48 static void sub_72850(spritetype *, XSPRITE *);
49 static void sub_72934(spritetype *, XSPRITE *);
50 
51 static int dword_279B54 = seqRegisterClient(sub_71BD4);
52 static int dword_279B58 = seqRegisterClient(sub_720AC);
53 static int dword_279B5C = seqRegisterClient(sub_71A90);
54 
55 AISTATE tchernobogIdle = { kAiStateIdle, 0, -1, 0, NULL, NULL, sub_725A4, NULL };
56 AISTATE tchernobogSearch = { kAiStateSearch, 8, -1, 1800, NULL, aiMoveForward, sub_72580, &tchernobogIdle };
57 AISTATE tchernobogChase = { kAiStateChase, 8, -1, 0, NULL, aiMoveForward, sub_72934, NULL };
58 AISTATE tchernobogRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &tchernobogSearch };
59 AISTATE tcherno13A9B8 = { kAiStateMove, 8, -1, 600, NULL, aiMoveForward, sub_72850, &tchernobogIdle };
60 AISTATE tcherno13A9D4 = { kAiStateMove, 6, dword_279B54, 60, NULL, NULL, NULL, &tchernobogChase };
61 AISTATE tcherno13A9F0 = { kAiStateChase, 6, dword_279B58, 60, NULL, NULL, NULL, &tchernobogChase };
62 AISTATE tcherno13AA0C = { kAiStateChase, 7, dword_279B5C, 60, NULL, NULL, NULL, &tchernobogChase };
63 AISTATE tcherno13AA28 = { kAiStateChase, 8, -1, 60, NULL, aiMoveTurn, NULL, &tchernobogChase };
64 
sub_71A90(int,int nXSprite)65 static void sub_71A90(int, int nXSprite)
66 {
67     XSPRITE *pXSprite = &xsprite[nXSprite];
68     int nSprite = pXSprite->reference;
69     spritetype *pSprite = &sprite[nSprite];
70     spritetype *pTarget = &sprite[pXSprite->target];
71     XSPRITE *pXTarget = &xsprite[pTarget->extra];
72     int nTarget = pTarget->index;
73     int nOwner = actSpriteIdToOwnerId(nSprite);
74     if (pXTarget->burnTime == 0)
75         evPost(nTarget, 3, 0, kCallbackFXFlameLick);
76     actBurnSprite(nOwner, pXTarget, 40);
77     if (Chance(0x6000))
78         aiNewState(pSprite, pXSprite, &tcherno13A9D4);
79 }
80 
sub_71BD4(int,int nXSprite)81 static void sub_71BD4(int, int nXSprite)
82 {
83     XSPRITE *pXSprite = &xsprite[nXSprite];
84     int nSprite = pXSprite->reference;
85     spritetype *pSprite = &sprite[nSprite];
86     DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
87     int height = pSprite->yrepeat*pDudeInfo->eyeHeight;
88     ///dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
89     if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites)) {
90         consoleSysMsg("pXSprite->target >= 0 && pXSprite->target < kMaxSprites");
91         return;
92     }
93     int x = pSprite->x;
94     int y = pSprite->y;
95     int z = height;
96     TARGETTRACK tt = { 0x10000, 0x10000, 0x100, 0x55, 0x100000 };
97     Aim aim;
98     aim.dx = Cos(pSprite->ang)>>16;
99     aim.dy = Sin(pSprite->ang)>>16;
100     aim.dz = gDudeSlope[nXSprite];
101     int nClosest = 0x7fffffff;
102     for (short nSprite2 = headspritestat[kStatDude]; nSprite2 >= 0; nSprite2 = nextspritestat[nSprite2])
103     {
104         spritetype *pSprite2 = &sprite[nSprite2];
105         if (pSprite == pSprite2 || !(pSprite2->flags&8))
106             continue;
107         int x2 = pSprite2->x;
108         int y2 = pSprite2->y;
109         int z2 = pSprite2->z;
110         int nDist = approxDist(x2-x, y2-y);
111         if (nDist == 0 || nDist > 0x2800)
112             continue;
113         if (tt.at10)
114         {
115             int t = divscale(nDist, tt.at10, 12);
116             x2 += (xvel[nSprite2]*t)>>12;
117             y2 += (yvel[nSprite2]*t)>>12;
118             z2 += (zvel[nSprite2]*t)>>8;
119         }
120         int tx = x+mulscale30(Cos(pSprite->ang), nDist);
121         int ty = y+mulscale30(Sin(pSprite->ang), nDist);
122         int tz = z+mulscale(gDudeSlope[nXSprite], nDist, 10);
123         int tsr = mulscale(9460, nDist, 10);
124         int top, bottom;
125         GetSpriteExtents(pSprite2, &top, &bottom);
126         if (tz-tsr > bottom || tz+tsr < top)
127             continue;
128         int dx = (tx-x2)>>4;
129         int dy = (ty-y2)>>4;
130         int dz = (tz-z2)>>8;
131         int nDist2 = ksqrt(dx*dx+dy*dy+dz*dz);
132         if (nDist2 < nClosest)
133         {
134             int nAngle = getangle(x2-x, y2-y);
135             int nDeltaAngle = ((nAngle-pSprite->ang+1024)&2047)-1024;
136             if (klabs(nDeltaAngle) <= tt.at8)
137             {
138                 int tz = pSprite2->z-pSprite->z;
139                 if (cansee(x, y, z, pSprite->sectnum, x2, y2, z2, pSprite2->sectnum))
140                 {
141                     nClosest = nDist2;
142                     aim.dx = Cos(nAngle)>>16;
143                     aim.dy = Sin(nAngle)>>16;
144                     aim.dz = divscale(tz, nDist, 10);
145                 }
146                 else
147                     aim.dz = tz;
148             }
149         }
150     }
151     actFireMissile(pSprite, -350, 0, aim.dx, aim.dy, aim.dz, kMissileFireballTchernobog);
152     actFireMissile(pSprite, 350, 0, aim.dx, aim.dy, aim.dz, kMissileFireballTchernobog);
153 }
154 
sub_720AC(int,int nXSprite)155 static void sub_720AC(int, int nXSprite)
156 {
157     XSPRITE *pXSprite = &xsprite[nXSprite];
158     int nSprite = pXSprite->reference;
159     spritetype *pSprite = &sprite[nSprite];
160     ///dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
161     if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites)) {
162         consoleSysMsg("pXSprite->target >= 0 && pXSprite->target < kMaxSprites");
163         return;
164     }
165     DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
166     int height = pSprite->yrepeat*pDudeInfo->eyeHeight;
167     int ax, ay, az;
168     ax = Cos(pSprite->ang)>>16;
169     ay = Sin(pSprite->ang)>>16;
170     int x = pSprite->x;
171     int y = pSprite->y;
172     int z = height;
173     TARGETTRACK tt = { 0x10000, 0x10000, 0x100, 0x55, 0x100000 };
174     Aim aim;
175     aim.dx = ax;
176     aim.dy = ay;
177     aim.dz = gDudeSlope[nXSprite];
178     int nClosest = 0x7fffffff;
179     az = 0;
180     for (short nSprite2 = headspritestat[kStatDude]; nSprite2 >= 0; nSprite2 = nextspritestat[nSprite2])
181     {
182         spritetype *pSprite2 = &sprite[nSprite2];
183         if (pSprite == pSprite2 || !(pSprite2->flags&8))
184             continue;
185         int x2 = pSprite2->x;
186         int y2 = pSprite2->y;
187         int z2 = pSprite2->z;
188         int nDist = approxDist(x2-x, y2-y);
189         if (nDist == 0 || nDist > 0x2800)
190             continue;
191         if (tt.at10)
192         {
193             int t = divscale(nDist, tt.at10, 12);
194             x2 += (xvel[nSprite2]*t)>>12;
195             y2 += (yvel[nSprite2]*t)>>12;
196             z2 += (zvel[nSprite2]*t)>>8;
197         }
198         int tx = x+mulscale30(Cos(pSprite->ang), nDist);
199         int ty = y+mulscale30(Sin(pSprite->ang), nDist);
200         int tz = z+mulscale(gDudeSlope[nXSprite], nDist, 10);
201         int tsr = mulscale(9460, nDist, 10);
202         int top, bottom;
203         GetSpriteExtents(pSprite2, &top, &bottom);
204         if (tz-tsr > bottom || tz+tsr < top)
205             continue;
206         int dx = (tx-x2)>>4;
207         int dy = (ty-y2)>>4;
208         int dz = (tz-z2)>>8;
209         int nDist2 = ksqrt(dx*dx+dy*dy+dz*dz);
210         if (nDist2 < nClosest)
211         {
212             int nAngle = getangle(x2-x, y2-y);
213             int nDeltaAngle = ((nAngle-pSprite->ang+1024)&2047)-1024;
214             if (klabs(nDeltaAngle) <= tt.at8)
215             {
216                 int tz = pSprite2->z-pSprite->z;
217                 if (cansee(x, y, z, pSprite->sectnum, x2, y2, z2, pSprite2->sectnum))
218                 {
219                     nClosest = nDist2;
220                     aim.dx = Cos(nAngle)>>16;
221                     aim.dy = Sin(nAngle)>>16;
222                     aim.dz = divscale(tz, nDist, 10);
223                 }
224                 else
225                     aim.dz = tz;
226             }
227         }
228     }
229     actFireMissile(pSprite, 350, 0, aim.dx, aim.dy, -aim.dz, kMissileFireballTchernobog);
230     actFireMissile(pSprite, -350, 0, ax, ay, az, kMissileFireballTchernobog);
231 }
232 
sub_72580(spritetype * pSprite,XSPRITE * pXSprite)233 static void sub_72580(spritetype *pSprite, XSPRITE *pXSprite)
234 {
235     aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
236     aiThinkTarget(pSprite, pXSprite);
237 }
238 
sub_725A4(spritetype * pSprite,XSPRITE * pXSprite)239 static void sub_725A4(spritetype *pSprite, XSPRITE *pXSprite)
240 {
241     ///dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
242     if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax)) {
243         consoleSysMsg("pSprite->type >= kDudeBase && pSprite->type < kDudeMax");
244         return;
245     }
246     DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
247     DUDEEXTRA_at6_u2 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u2;
248     if (pDudeExtraE->at4 && pDudeExtraE->at0 < 10)
249         pDudeExtraE->at0++;
250     else if (pDudeExtraE->at0 >= 10 && pDudeExtraE->at4)
251     {
252         pXSprite->goalAng += 256;
253         POINT3D *pTarget = &baseSprite[pSprite->index];
254         aiSetTarget(pXSprite, pTarget->x, pTarget->y, pTarget->z);
255         aiNewState(pSprite, pXSprite, &tcherno13AA28);
256         return;
257     }
258     if (Chance(pDudeInfo->alertChance))
259     {
260         for (int p = connecthead; p >= 0; p = connectpoint2[p])
261         {
262             PLAYER *pPlayer = &gPlayer[p];
263             if (pPlayer->pXSprite->health == 0 || powerupCheck(pPlayer, kPwUpShadowCloak) > 0)
264                 continue;
265             int x = pPlayer->pSprite->x;
266             int y = pPlayer->pSprite->y;
267             int z = pPlayer->pSprite->z;
268             int nSector = pPlayer->pSprite->sectnum;
269             int dx = x-pSprite->x;
270             int dy = y-pSprite->y;
271             int nDist = approxDist(dx, dy);
272             if (nDist > pDudeInfo->seeDist && nDist > pDudeInfo->hearDist)
273                 continue;
274             if (!cansee(x, y, z, nSector, pSprite->x, pSprite->y, pSprite->z-((pDudeInfo->eyeHeight*pSprite->yrepeat)<<2), pSprite->sectnum))
275                 continue;
276             int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
277             if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
278             {
279                 pDudeExtraE->at0 = 0;
280                 aiSetTarget(pXSprite, pPlayer->nSprite);
281                 aiActivateDude(pSprite, pXSprite);
282             }
283             else if (nDist < pDudeInfo->hearDist)
284             {
285                 pDudeExtraE->at0 = 0;
286                 aiSetTarget(pXSprite, x, y, z);
287                 aiActivateDude(pSprite, pXSprite);
288             }
289             else
290                 continue;
291             break;
292         }
293     }
294 }
295 
sub_72850(spritetype * pSprite,XSPRITE * pXSprite)296 static void sub_72850(spritetype *pSprite, XSPRITE *pXSprite)
297 {
298     ///dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
299     if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax)) {
300         consoleSysMsg("pSprite->type >= kDudeBase && pSprite->type < kDudeMax");
301         return;
302     }
303     DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
304     int dx = pXSprite->targetX-pSprite->x;
305     int dy = pXSprite->targetY-pSprite->y;
306     int nAngle = getangle(dx, dy);
307     int nDist = approxDist(dx, dy);
308     aiChooseDirection(pSprite, pXSprite, nAngle);
309     if (nDist < 512 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
310         aiNewState(pSprite, pXSprite, &tchernobogSearch);
311     aiThinkTarget(pSprite, pXSprite);
312 }
313 
sub_72934(spritetype * pSprite,XSPRITE * pXSprite)314 static void sub_72934(spritetype *pSprite, XSPRITE *pXSprite)
315 {
316     if (pXSprite->target == -1)
317     {
318         aiNewState(pSprite, pXSprite, &tcherno13A9B8);
319         return;
320     }
321     ///dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
322     if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax)) {
323         consoleSysMsg("pSprite->type >= kDudeBase && pSprite->type < kDudeMax");
324         return;
325     }
326     DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
327     ///dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
328     if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites)) {
329         consoleSysMsg("pXSprite->target >= 0 && pXSprite->target < kMaxSprites");
330         return;
331     }
332     spritetype *pTarget = &sprite[pXSprite->target];
333     XSPRITE *pXTarget = &xsprite[pTarget->extra];
334     int dx = pTarget->x-pSprite->x;
335     int dy = pTarget->y-pSprite->y;
336     aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
337     if (pXTarget->health == 0)
338     {
339         aiNewState(pSprite, pXSprite, &tchernobogSearch);
340         return;
341     }
342     if (IsPlayerSprite(pTarget) && powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], kPwUpShadowCloak) > 0)
343     {
344         aiNewState(pSprite, pXSprite, &tchernobogSearch);
345         return;
346     }
347     int nDist = approxDist(dx, dy);
348     if (nDist <= pDudeInfo->seeDist)
349     {
350         int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
351         int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
352         if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
353         {
354             if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
355             {
356                 aiSetTarget(pXSprite, pXSprite->target);
357                 if (nDist < 0x1f00 && nDist > 0xd00 && klabs(nDeltaAngle) < 85)
358                     aiNewState(pSprite, pXSprite, &tcherno13AA0C);
359                 else if (nDist < 0xd00 && nDist > 0xb00 && klabs(nDeltaAngle) < 85)
360                     aiNewState(pSprite, pXSprite, &tcherno13A9D4);
361                 else if (nDist < 0xb00 && nDist > 0x500 && klabs(nDeltaAngle) < 85)
362                     aiNewState(pSprite, pXSprite, &tcherno13A9F0);
363                 return;
364             }
365         }
366     }
367 
368     aiNewState(pSprite, pXSprite, &tcherno13A9B8);
369     pXSprite->target = -1;
370 }
371