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