1 //-------------------------------------------------------------------------
2 /*
3 Copyright (C) 2010-2019 EDuke32 developers and contributors
4 Copyright (C) 2019 Nuke.YKT
5 Copyright (C) NoOne
6 
7 This file is part of NBlood.
8 
9 NBlood is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License version 2
11 as published by the Free Software Foundation.
12 
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 
17 See the GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
22 */
23 //-------------------------------------------------------------------------
24 
25 
26 ///////////////////////////////////////////////////////////////////
27 // This file provides modern features for mappers.
28 // For full documentation please visit http://cruo.bloodgame.ru/xxsystem
29 ///////////////////////////////////////////////////////////////////
30 
31 
32 #ifdef NOONE_EXTENSIONS
33 #include <random>
34 #include "nnexts.h"
35 #include "aiunicult.h"
36 #include "triggers.h"
37 #include "sectorfx.h"
38 #include "globals.h"
39 #include "endgame.h"
40 #include "weapon.h"
41 #include "mmulti.h"
42 #include "view.h"
43 #include "tile.h"
44 #include "trig.h"
45 #include "sfx.h"
46 #include "seq.h"
47 #include "ai.h"
48 
49 
50 bool gAllowTrueRandom = false;
51 bool gEventRedirectsUsed = false;
52 SPRITEMASS gSpriteMass[];   // cache for getSpriteMassBySize();
53 short gProxySpritesList[];  // list of additional sprites which can be triggered by Proximity
54 short gProxySpritesCount;   // current count
55 short gSightSpritesList[];  // list of additional sprites which can be triggered by Sight
56 short gSightSpritesCount;   // current count
57 short gPhysSpritesList[];   // list of additional sprites which can be affected by physics
58 short gPhysSpritesCount;    // current count
59 short gImpactSpritesList[];
60 short gImpactSpritesCount;
61 
62 
63 TRPLAYERCTRL gPlayerCtrl[kMaxPlayers];
64 
65 TRCONDITION gCondition[kMaxTrackingConditions];
66 short gTrackingCondsCount;
67 
68 std::default_random_engine gStdRandom;
69 
70 VECTORINFO_EXTRA gVectorInfoExtra[] = {
71     1207,1207,      1001,1001,      4001,4002,
72     431,431,        1002,1002,      359,359,
73     521,521,        513,513,        499,499,
74     9012,9014,      1101,1101,      1207,1207,
75     499,495,        495,496,        9013,499,
76     1307,1308,      499,499,        499,499,
77     499,499,        499,499,        351,351,
78     0,0,            357,499
79 };
80 
81 MISSILEINFO_EXTRA gMissileInfoExtra[] = {
82     1207, 1207,    false, false, false, false, false, true, false,     true,
83     420, 420,      false, true, true, false, false, false, false,      true,
84     471, 471,      false, false, false, false, false, false, true,     false,
85     421, 421,      false, true, false, true, false, false, false,      false,
86     1309, 351,     false, true, false, false, false, false, false,     true,
87     480, 480,      false, true, false, true, false, false, false,      false,
88     470, 470,      false, false, false, false, false, false, true,     true,
89     489, 490,      false, false, false, false, false, true, false,     true,
90     462, 351,      false, true, false, false, false, false, false,     true,
91     1203, 172,     false, false, true, false, false, false, false,     true,
92     0,0,           false, false, true, false, false, false, false,     true,
93     1457, 249,     false, false, false, false, false, true, false,     true,
94     480, 489,      false, true, false, true, false, false, false,      false,
95     480, 489,      false, false, false, true, false, false, false,     false,
96     480, 489,      false, false, false, true, false, false, false,     false,
97     491, 491,      true, true, true, true, true, true, true,           true,
98     520, 520,      false, false, false, false, false, true, false,     true,
99     520, 520,      false, false, false, false, false, true, false,     true,
100 };
101 
102 THINGINFO_EXTRA gThingInfoExtra[] = {
103     true,   true,   true,   false,  false,
104     false,  false,  false,  false,  false,
105     false,  false,  false,  false,  false,
106     true,   false,  false,  true,   true,
107     true,   true,   false,  false,  false,
108     false,  false,  true,   true,   true,
109     true,   true,   true,   true,   true,
110     true,
111 };
112 
113 DUDEINFO_EXTRA gDudeInfoExtra[] = {
114 
115     { false,  false,  1, -1, -1, -1, -1, -1 },      // 200
116     { false,  false,  0, 9, 13, 13, 17, 14 },       // 201
117     { false,  false,  0, 9, 13, 13, 17, 14 },       // 202
118     { false,  true,   0, 8, 0, 8, -1, -1 },         // 203
119     { false,  false,  0, 8, 0, 8, -1, -1 },         // 204
120     { false,  true,   1, -1, -1, -1, -1, -1 },      // 205
121     { true,   true,   0, 0, 0, 0, -1, -1 },         // 206
122     { true,   false,  0, 0, 0, 0, -1, -1 },         // 207
123     { true,   false,  1, -1, -1, -1, -1, -1 },      // 208
124     { true,   false,  1, -1, -1, -1, -1, -1 },      // 209
125     { true,   true,   0, 0, 0, 0, -1, -1 },         // 210
126     { false,  true,   0, 8, 0, 8, -1, -1 },         // 211
127     { false,  true,   0, 6, 0, 6, -1, -1 },         // 212
128     { false,  true,   0, 7, 0, 7, -1, -1 },         // 213
129     { false,  true,   0, 7, 0, 7, -1, -1 },         // 214
130     { false,  true,   0, 7, 0, 7, -1, -1 },         // 215
131     { false,  true,   0, 7, 0, 7, -1, -1 },         // 216
132     { false,  true,   0, 9, 10, 10, -1, -1 },       // 217
133     { false,  true,   0, 0, 0, 0, -1, -1 },         // 218
134     { true,  false,  7, 7, 7, 7, -1, -1 },          // 219
135     { false,  true,   0, 7, 0, 7, -1, -1 },         // 220
136     { false,  false,  -1, -1, -1, -1, -1, -1 },     // 221
137     { false,  true,   -1, -1, -1, -1, -1, -1 },     // 222
138     { false,  false,  -1, -1, -1, -1, -1, -1 },     // 223
139     { false,  true,   -1, -1, -1, -1, -1, -1 },     // 224
140     { false,  false,  -1, -1, -1, -1, -1, -1 },     // 225
141     { false,  false,  -1, -1, -1, -1, -1, -1 },     // 226
142     { false,  false,  0, 7, 0, 7, -1, -1 },         // 227
143     { false,  false,  0, 7, 0, 7, -1, -1 },         // 228
144     { false,  false,  0, 8, 0, 8, -1, -1 },         // 229
145     { false,  false,  0, 9, 13, 13, 17, 14 },       // 230
146     { false,  false,  -1, -1, -1, -1, -1, -1 },     // 231
147     { false,  false,  -1, -1, -1, -1, -1, -1 },     // 232
148     { false,  false,  -1, -1, -1, -1, -1, -1 },     // 233
149     { false,  false,  -1, -1, -1, -1, -1, -1 },     // 234
150     { false,  false,  -1, -1, -1, -1, -1, -1 },     // 235
151     { false,  false,  -1, -1, -1, -1, -1, -1 },     // 236
152     { false,  false,  -1, -1, -1, -1, -1, -1 },     // 237
153     { false,  false,  -1, -1, -1, -1, -1, -1 },     // 238
154     { false,  false,  -1, -1, -1, -1, -1, -1 },     // 239
155     { false,  false,  -1, -1, -1, -1, -1, -1 },     // 240
156     { false,  false,  -1, -1, -1, -1, -1, -1 },     // 241
157     { false,  false,  -1, -1, -1, -1, -1, -1 },     // 242
158     { false,  false,  -1, -1, -1, -1, -1, -1 },     // 243
159     { false,  true,   -1, -1, -1, -1, -1, -1 },     // 244
160     { false,  true,   0, 6, 0, 6, -1, -1 },         // 245
161     { false,  false,  0, 9, 13, 13, 17, 14 },       // 246
162     { false,  false,  0, 9, 13, 13, 14, 14 },       // 247
163     { false,  false,  0, 9, 13, 13, 14, 14 },       // 248
164     { false,  false,  0, 9, 13, 13, 17, 14 },       // 249
165     { false,  true,   0, 6, 8, 8, 10, 9 },          // 250
166     { false,  true,   0, 8, 9, 9, 11, 10 },         // 251
167     { false,  false,  -1, -1, -1, -1, -1, -1 },     // 252
168     { false,  false,  -1, -1, -1, -1, -1, -1 },     // 253
169     { false,  false,  0, 9, 17, 13, 17, 14 },       // 254
170     { false,  false,  -1, -1, -1, -1, -1, -1 },     // 255
171 
172 };
173 
174 
175 AISTATE genPatrolStates[] = {
176 
177     //-------------------------------------------------------------------------------
178 
179     { kAiStatePatrolWaitL, 0, -1, 0, NULL, NULL, aiPatrolThink, NULL },
180     { kAiStatePatrolWaitL, 7, -1, 0, NULL, NULL, aiPatrolThink, NULL },
181 
182     { kAiStatePatrolMoveL, 9, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL },
183     { kAiStatePatrolMoveL, 8, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL },
184     { kAiStatePatrolMoveL, 0, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL },
185     { kAiStatePatrolMoveL, 6, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL },
186     { kAiStatePatrolMoveL, 7, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL },
187 
188     //-------------------------------------------------------------------------------
189 
190     { kAiStatePatrolWaitW, 0, -1, 0, NULL, NULL, aiPatrolThink, NULL },
191     { kAiStatePatrolWaitW, 10, -1, 0, NULL, NULL, aiPatrolThink, NULL },
192     { kAiStatePatrolWaitW, 13, -1, 0, NULL, NULL, aiPatrolThink, NULL },
193     { kAiStatePatrolWaitW, 17, -1, 0, NULL, NULL, aiPatrolThink, NULL },
194     { kAiStatePatrolWaitW, 8, -1, 0, NULL, NULL, aiPatrolThink, NULL },
195     { kAiStatePatrolWaitW, 9, -1, 0, NULL, NULL, aiPatrolThink, NULL },
196 
197     { kAiStatePatrolMoveW, 0, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL },
198     { kAiStatePatrolMoveW, 10, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL },
199     { kAiStatePatrolMoveW, 13, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL },
200     { kAiStatePatrolMoveW, 8, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL },
201     { kAiStatePatrolMoveW, 9, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL },
202     { kAiStatePatrolMoveW, 7, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL },
203     { kAiStatePatrolMoveW, 6, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL },
204 
205     //-------------------------------------------------------------------------------
206 
207     { kAiStatePatrolWaitC, 17, -1, 0, NULL, NULL, aiPatrolThink, NULL },
208     { kAiStatePatrolWaitC, 11, -1, 0, NULL, NULL, aiPatrolThink, NULL },
209     { kAiStatePatrolWaitC, 10, -1, 0, NULL, NULL, aiPatrolThink, NULL },
210     { kAiStatePatrolWaitC, 14, -1, 0, NULL, NULL, aiPatrolThink, NULL },
211 
212     { kAiStatePatrolMoveC, 14, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL },
213     { kAiStatePatrolMoveC, 10, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL },
214     { kAiStatePatrolMoveC, 9, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL },
215 
216     //-------------------------------------------------------------------------------
217 
218 };
219 
220 
221 // for actor.cpp
222 //-------------------------------------------------------------------------
223 
nnExtIsImmune(spritetype * pSprite,int dmgType,int minScale)224 bool nnExtIsImmune(spritetype* pSprite, int dmgType, int minScale) {
225 
226     if (dmgType >= kDmgFall && dmgType < kDmgMax && pSprite->extra >= 0 && xsprite[pSprite->extra].locked != 1) {
227         if (pSprite->type >= kThingBase && pSprite->type < kThingMax)
228             return (thingInfo[pSprite->type - kThingBase].dmgControl[dmgType] <= minScale);
229         else if (IsDudeSprite(pSprite)) {
230             if (IsPlayerSprite(pSprite)) return (gPlayer[pSprite->type - kDudePlayer1].damageControl[dmgType]);
231             else if (pSprite->type == kDudeModernCustom) return (gGenDudeExtra[pSprite->index].dmgControl[dmgType] <= minScale);
232             else return (getDudeInfo(pSprite->type)->at70[dmgType] <= minScale);
233         }
234     }
235 
236     return true;
237 }
238 
nnExtEraseModernStuff(spritetype * pSprite,XSPRITE * pXSprite)239 bool nnExtEraseModernStuff(spritetype* pSprite, XSPRITE* pXSprite) {
240 
241     bool erased = false;
242     switch (pSprite->type) {
243         // erase all modern types if the map is not extended
244         case kModernCustomDudeSpawn:
245         case kModernRandomTX:
246         case kModernSequentialTX:
247         case kModernSeqSpawner:
248         case kModernObjPropertiesChanger:
249         case kModernObjPicnumChanger:
250         case kModernObjSizeChanger:
251         case kModernDudeTargetChanger:
252         case kModernSectorFXChanger:
253         case kModernObjDataChanger:
254         case kModernSpriteDamager:
255         case kModernObjDataAccumulator:
256         case kModernEffectSpawner:
257         case kModernWindGenerator:
258         case kModernPlayerControl:
259         case kModernCondition:
260         case kModernConditionFalse:
261             pSprite->type = kSpriteDecoration;
262             erased = true;
263             break;
264         case kItemModernMapLevel:
265         case kDudeModernCustom:
266         case kDudeModernCustomBurning:
267         case kModernThingTNTProx:
268         case kModernThingEnemyLifeLeech:
269             pSprite->type = kSpriteDecoration;
270             changespritestat(pSprite->index, kStatDecoration);
271             erased = true;
272             break;
273         // also erase some modernized vanilla types which was not active
274         case kMarkerWarpDest:
275             if (pSprite->statnum == kStatMarker) break;
276             pSprite->type = kSpriteDecoration;
277             erased = true;
278             break;
279     }
280 
281     if (pXSprite->Sight) {
282         pXSprite->Sight = false; // it does not work in vanilla at all
283         erased = true;
284     }
285 
286     if (pXSprite->Proximity) {
287         // proximity works only for things and dudes in vanilla
288         switch (pSprite->statnum) {
289             case kStatThing:
290             case kStatDude:
291                 break;
292             default:
293                 pXSprite->Proximity = false;
294                 erased = true;
295                 break;
296         }
297     }
298 
299     return erased;
300 }
301 
nnExtTriggerObject(int objType,int objIndex,int command)302 void nnExtTriggerObject(int objType, int objIndex, int command) {
303     switch (objType) {
304         case OBJ_SECTOR:
305             if (!xsectRangeIsFine(sector[objIndex].extra)) break;
306             trTriggerSector(objIndex, &xsector[sector[objIndex].extra], command);
307             break;
308         case OBJ_WALL:
309             if (!xwallRangeIsFine(wall[objIndex].extra)) break;
310             trTriggerWall(objIndex, &xwall[wall[objIndex].extra], command);
311             break;
312         case OBJ_SPRITE:
313             if (!xspriRangeIsFine(sprite[objIndex].extra)) break;
314             trTriggerSprite(objIndex, &xsprite[sprite[objIndex].extra], command);
315             break;
316     }
317 
318     return;
319 }
320 
nnExtResetGlobals()321 void nnExtResetGlobals() {
322     gAllowTrueRandom = gEventRedirectsUsed = false;
323 
324     // reset counters
325     gProxySpritesCount = gSightSpritesCount = gPhysSpritesCount = gImpactSpritesCount = 0;
326 
327     // fill arrays with negative values to avoid index 0 situation
328     memset(gSightSpritesList, -1, sizeof(gSightSpritesList));   memset(gProxySpritesList, -1, sizeof(gProxySpritesList));
329     memset(gPhysSpritesList, -1, sizeof(gPhysSpritesList));     memset(gImpactSpritesList, -1, sizeof(gImpactSpritesList));
330 
331     // reset tracking conditions, if any
332     if (gTrackingCondsCount > 0) {
333         for (int i = 0; i < gTrackingCondsCount; i++) {
334             TRCONDITION* pCond = &gCondition[i];
335             for (int k = 0; k < pCond->length; k++) {
336                 pCond->obj[k].index = pCond->obj[k].cmd = 0;
337                 pCond->obj[k].type = -1;
338             }
339 
340             pCond->length = 0;
341         }
342 
343         gTrackingCondsCount = 0;
344     }
345 }
346 
nnExtInitModernStuff(bool bSaveLoad)347 void nnExtInitModernStuff(bool bSaveLoad) {
348 
349     nnExtResetGlobals();
350 
351     // use true random only for single player mode, otherwise use Blood's default one.
352     if (gGameOptions.nGameType == 0 && !VanillaMode() && !DemoRecordStatus()) {
353 
354         gStdRandom.seed(std::random_device()());
355 
356         // since true random is not working if compiled with old mingw versions, we should
357         // check if it works in game and if not - switch to using in-game random function.
358         for (int i = kMaxRandomizeRetries; i >= 0; i--) {
359             std::uniform_int_distribution<int> dist_a_b(0, 100);
360             if (gAllowTrueRandom || i <= 0) break;
361             else if (dist_a_b(gStdRandom) != 0)
362                 gAllowTrueRandom = true;
363         }
364 
365     }
366 
367     if (!gAllowTrueRandom) initprintf("> STD randomness is not available, using in-game random function(s).\n");
368     else initprintf("> Using STD randomness function(s).\n");
369 
370     for (int i = 0; i < kMaxXSprites; i++) {
371 
372         if (xsprite[i].reference < 0) continue;
373         XSPRITE* pXSprite = &xsprite[i];  spritetype* pSprite = &sprite[pXSprite->reference];
374 
375         switch (pSprite->type) {
376             case kModernRandomTX:
377             case kModernSequentialTX:
378                 if (pXSprite->command == kCmdLink) gEventRedirectsUsed = true;
379                 break;
380             case kDudeModernCustom:
381             case kDudeModernCustomBurning:
382                 getSpriteMassBySize(pSprite); // create mass cache
383                 break;
384             case kModernCondition:
385             case kModernConditionFalse:
386                 if (bSaveLoad) break;
387                 else if (!pXSprite->rxID) condError(pXSprite,"\nThe condition must have RX ID!\nSPRITE #%d", pSprite->index);
388                 else if (!pXSprite->txID && !pSprite->flags) {
389                     consoleSysMsg("The condition must have TX ID or hitag to be set: RX ID %d, SPRITE #%d", pXSprite->rxID, pSprite->index);
390                 }
391                 break;
392         }
393 
394         // init after loading save file
395         if (bSaveLoad) {
396 
397             // add in list of physics affected sprites
398             if (pXSprite->physAttr != 0) {
399                 //xvel[pSprite->index] = yvel[pSprite->index] = zvel[pSprite->index] = 0;
400 
401                 gPhysSpritesList[gPhysSpritesCount++] = pSprite->index; // add sprite index
402                 getSpriteMassBySize(pSprite); // create mass cache
403             }
404 
405             if (pXSprite->data3 != pXSprite->sysData1) {
406                 switch (pSprite->statnum) {
407                 case kStatDude:
408                     switch (pSprite->type) {
409                     case kDudeModernCustom:
410                     case kDudeModernCustomBurning:
411                         pXSprite->data3 = pXSprite->sysData1; // move sndStartId back from sysData1 to data3
412                         break;
413                     }
414                     break;
415                 }
416             }
417 
418         } else {
419 
420             // auto set going On and going Off if both are empty
421             if (pXSprite->txID && !pXSprite->triggerOn && !pXSprite->triggerOff)
422                 pXSprite->triggerOn = pXSprite->triggerOff = true;
423 
424             // copy custom start health to avoid overwrite by kThingBloodChunks
425             if (IsDudeSprite(pSprite))
426                 pXSprite->sysData2 = pXSprite->data4;
427 
428             // check reserved statnums
429             if (pSprite->statnum >= kStatModernBase && pSprite->statnum < kStatModernMax) {
430                 bool sysStat = true;
431                 switch (pSprite->statnum) {
432                     case kStatModernDudeTargetChanger:
433                         sysStat = (pSprite->type != kModernDudeTargetChanger);
434                         break;
435                     case kStatModernCondition:
436                         sysStat = (pSprite->type != kModernCondition && pSprite->type != kModernConditionFalse);
437                         break;
438                     case kStatModernEventRedirector:
439                         sysStat = (pSprite->type != kModernRandomTX && pSprite->type != kModernSequentialTX);
440                         break;
441                     case kStatModernPlayerLinker:
442                     case kStatModernQavScene:
443                         sysStat = (pSprite->type != kModernPlayerControl);
444                         break;
445                 }
446 
447                 if (sysStat)
448                     ThrowError("Sprite status list number %d on sprite #%d is in a range of reserved (%d - %d)!", pSprite->index, pSprite->statnum, kStatModernBase, kStatModernMax);
449             }
450 
451             switch (pSprite->type) {
452                 case kModernRandomTX:
453                 case kModernSequentialTX:
454                     if (pXSprite->command != kCmdLink) break;
455                     // add statnum for faster redirects search
456                     changespritestat(pSprite->index, kStatModernEventRedirector);
457                     break;
458                 case kModernWindGenerator:
459                     pSprite->cstat &= ~CSTAT_SPRITE_BLOCK;
460                     break;
461                 case kModernDudeTargetChanger:
462                 case kModernObjDataAccumulator:
463                 case kModernRandom:
464                 case kModernRandom2:
465                     pSprite->cstat &= ~CSTAT_SPRITE_BLOCK;
466                     pSprite->cstat |= CSTAT_SPRITE_INVISIBLE;
467                     switch (pSprite->type) {
468                         // add statnum for faster dude searching
469                         case kModernDudeTargetChanger:
470                             changespritestat(pSprite->index, kStatModernDudeTargetChanger);
471                             if (pXSprite->busyTime <= 0) pXSprite->busyTime = 5;
472                             pXSprite->command = kCmdLink;
473                             break;
474                         // remove kStatItem status from random item generators
475                         case kModernRandom:
476                         case kModernRandom2:
477                             changespritestat(pSprite->index, kStatDecoration);
478                             break;
479                     }
480                     break;
481                 case kModernThingTNTProx:
482                     pXSprite->Proximity = true;
483                     break;
484                 case kDudeModernCustom:
485                     if (pXSprite->txID <= 0) break;
486                     for (int nSprite = headspritestat[kStatDude], found = 0; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
487                         XSPRITE* pXSpr = &xsprite[sprite[nSprite].extra];
488                         if (pXSpr->rxID != pXSprite->txID) continue;
489                         else if (found) ThrowError("\nCustom dude (TX ID %d):\nOnly one incarnation allowed per channel!", pXSprite->txID);
490                         changespritestat(nSprite, kStatInactive);
491                         nSprite = headspritestat[kStatDude];
492                         found++;
493                     }
494                     break;
495                 case kDudePodMother:
496                 case kDudeTentacleMother:
497                     pXSprite->state = 1;
498                     break;
499                 case kModernPlayerControl:
500                     switch (pXSprite->command) {
501                         case kCmdLink:
502                             if (pXSprite->data1 < 1 || pXSprite->data1 >= kMaxPlayers)
503                                 ThrowError("\nPlayer Control (SPRITE #%d):\nPlayer out of a range (data1 = %d)", pSprite->index, pXSprite->data1);
504 
505                             //if (numplayers < pXSprite->data1)
506                                 //ThrowError("\nPlayer Control (SPRITE #%d):\n There is no player #%d", pSprite->index, pXSprite->data1);
507 
508                             if (pXSprite->rxID && pXSprite->rxID != kChannelLevelStart)
509                                 ThrowError("\nPlayer Control (SPRITE #%d) with Link command should have no RX ID!", pSprite->index, pXSprite->data1);
510 
511                             if (pXSprite->txID && pXSprite->txID < kChannelUser)
512                                 ThrowError("\nPlayer Control (SPRITE #%d):\nTX ID should be in range of %d and %d!", pSprite->index, kChannelUser, kChannelMax);
513 
514                             // only one linker per player allowed
515                             for (int nSprite = headspritestat[kStatModernPlayerLinker]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
516                                 XSPRITE* pXCtrl = &xsprite[sprite[nSprite].extra];
517                                 if (pXSprite->data1 == pXCtrl->data1)
518                                     ThrowError("\nPlayer Control (SPRITE #%d):\nPlayer %d already linked with different player control sprite #%d!", pSprite->index, pXSprite->data1, nSprite);
519                             }
520                             pXSprite->sysData1 = -1;
521                             pSprite->cstat &= ~CSTAT_SPRITE_BLOCK;
522                             changespritestat(pSprite->index, kStatModernPlayerLinker);
523                             break;
524                         case 67: // play qav animation
525                             if (pXSprite->txID && !pXSprite->waitTime) pXSprite->waitTime = 1;
526                             changespritestat(pSprite->index, kStatModernQavScene);
527                             break;
528                     }
529                     break;
530                 case kModernCondition:
531                 case kModernConditionFalse:
532                     if (pXSprite->busyTime > 0) {
533 
534                         if (pXSprite->waitTime > 0) {
535                             pXSprite->busyTime += ClipHigh(((pXSprite->waitTime * 120) / 10), 4095); pXSprite->waitTime = 0;
536                             consoleSysMsg("Summing busyTime and waitTime for tracking condition #%d, RX ID %d. Result = %d ticks", pSprite->index, pXSprite->rxID, pXSprite->busyTime);
537                         }
538 
539                         pXSprite->busy = pXSprite->busyTime;
540                     }
541 
542                     if (pXSprite->waitTime && pXSprite->command >= kCmdNumberic)
543                         condError(pXSprite, "Delay is not available when using numberic commands (%d - %d)", kCmdNumberic, 255);
544 
545                     pXSprite->Decoupled = false; // must go through operateSprite always
546                     pXSprite->Sight     = pXSprite->Impact  = pXSprite->Touch   = pXSprite->triggerOff     = false;
547                     pXSprite->Proximity = pXSprite->Push    = pXSprite->Vector  = pXSprite->triggerOn      = false;
548                     pXSprite->state = pXSprite->restState = 0;
549 
550                     pXSprite->targetX = pXSprite->targetY = pXSprite->targetZ = pXSprite->target = pXSprite->sysData2 = -1;
551                     changespritestat(pSprite->index, kStatModernCondition);
552                     int oldStat = pSprite->cstat; pSprite->cstat = 0x30;
553 
554                     if (oldStat & CSTAT_SPRITE_BLOCK)
555                         pSprite->cstat |= CSTAT_SPRITE_BLOCK;
556 
557                     if (oldStat & 0x2000) pSprite->cstat |= 0x2000;
558                     else if (oldStat & 0x4000) pSprite->cstat |= 0x4000;
559 
560                     pSprite->cstat |= CSTAT_SPRITE_INVISIBLE;
561                     break;
562             }
563 
564             // the following trigger flags are senseless to have together
565             if ((pXSprite->Touch && (pXSprite->Proximity || pXSprite->Sight) && pXSprite->DudeLockout)
566                     || (pXSprite->Touch && pXSprite->Proximity && !pXSprite->Sight)) pXSprite->Touch = false;
567 
568             if (pXSprite->Proximity && pXSprite->Sight && pXSprite->DudeLockout)
569                 pXSprite->Proximity = false;
570 
571             // very quick fix for floor sprites with Touch trigger flag if their Z is equals sector floorz / ceilgz
572             if (pSprite->sectnum >= 0 && pXSprite->Touch && (pSprite->cstat & CSTAT_SPRITE_ALIGNMENT_FLOOR)) {
573                 if (pSprite->z == sector[pSprite->sectnum].floorz) pSprite->z--;
574                 else if (pSprite->z == sector[pSprite->sectnum].ceilingz) pSprite->z++;
575             }
576         }
577 
578         // make Proximity flag work not just for dudes and things...
579         if (pXSprite->Proximity && gProxySpritesCount < kMaxSuperXSprites) {
580             switch (pSprite->statnum) {
581                 case kStatFX:           case kStatExplosion:            case kStatItem:
582                 case kStatPurge:        case kStatSpares:               case kStatFlare:
583                 case kStatInactive:     case kStatFree:                 case kStatMarker:
584                 case kStatPathMarker:   case kStatThing:                case kStatDude:
585                 case kStatModernPlayerLinker:
586                     break;
587                 default:
588                     gProxySpritesList[gProxySpritesCount++] = pSprite->index;
589                     if (gProxySpritesCount == kMaxSuperXSprites)
590                         ThrowError("Max (%d) *additional* Proximity sprites reached!", kMaxSuperXSprites);
591                     break;
592             }
593         }
594 
595         // make Sight flag work not just for dudes and things...
596         if (pXSprite->Sight && gSightSpritesCount < kMaxSuperXSprites) {
597             switch (pSprite->statnum) {
598                 case kStatFX:           case kStatExplosion:            case kStatItem:
599                 case kStatPurge:        case kStatSpares:               case kStatFlare:
600                 case kStatInactive:     case kStatFree:                 case kStatMarker:
601                 case kStatPathMarker:   case kStatModernPlayerLinker:
602                     break;
603                 default:
604                     gSightSpritesList[gSightSpritesCount++] = pSprite->index;
605                     if (gSightSpritesCount == kMaxSuperXSprites)
606                         ThrowError("Max (%d) Sight sprites reached!", kMaxSuperXSprites);
607                     break;
608             }
609         }
610 
611         // make Impact flag work for sprites that affected by explosions...
612         if (pXSprite->Impact && gImpactSpritesCount < kMaxSuperXSprites) {
613             switch (pSprite->statnum) {
614                 case kStatFX:           case kStatExplosion:            case kStatItem:
615                 case kStatPurge:        case kStatSpares:               case kStatFlare:
616                 case kStatInactive:     case kStatFree:                 case kStatMarker:
617                 case kStatPathMarker:   case kStatModernPlayerLinker:
618                     break;
619                 default:
620                     gImpactSpritesList[gImpactSpritesCount++] = pSprite->index;
621                     if (gImpactSpritesCount == kMaxSuperXSprites)
622                         ThrowError("Max (%d) *additional* Impact sprites reached!", kMaxSuperXSprites);
623                     break;
624             }
625         }
626     }
627 
628     if (!bSaveLoad) {
629 
630         // let's try to find "else" and "else if" of conditions here
631         spritetype* pCond = NULL; XSPRITE* pXCond = NULL;
632         bool found = false; int rx = 0; int sum1 = 0; int sum2 = 0;
633 
634         for (int i = headspritestat[kStatModernCondition]; i >= 0;) {
635             pCond = &sprite[i]; pXCond = &xsprite[pCond->extra];
636             sum1 = pXCond->locked + pXCond->busyTime + pXCond->waitTime + pXCond->data1;
637             if (!found) rx = pXCond->rxID;
638 
639             for (int a = i; a >= 0; a = nextspritestat[a], found = false) {
640                 spritetype* pCond2 = &sprite[a]; XSPRITE* pXCond2 = &xsprite[pCond2->extra];
641                 sum2 = pXCond2->locked + pXCond2->busyTime + pXCond2->waitTime + pXCond2->data1;
642 
643                 if (pXCond2->rxID != rx || pCond2->index == pCond->index || sum1 != sum2) continue;
644                 else if ((pCond2->type != pCond->type) ^ (pCond2->cstat != pCond->cstat)) {
645                     initprintf("> ELSE IF found for condition #%d (RX ID: %d, CONDID: %d)\n", i, rx, pXCond->data1);
646                     pXCond2->rxID = pXCond2->busyTime = 0;
647                     pXCond->sysData2 = pCond2->index;
648                     i = a; found = true;
649                     break;
650                 }
651 
652             }
653 
654             if (!found) i = nextspritestat[i];
655         }
656 
657     }
658 
659     // collect objects for tracking conditions
660     for (int i = headspritestat[kStatModernCondition]; i >= 0; i = nextspritestat[i]) {
661         spritetype* pSprite = &sprite[i]; XSPRITE* pXSprite = &xsprite[pSprite->extra];
662 
663         if (pXSprite->busyTime <= 0 || pXSprite->isTriggered) continue;
664         else if (gTrackingCondsCount >= kMaxTrackingConditions)
665             ThrowError("\nMax (%d) tracking conditions reached!", kMaxTrackingConditions);
666 
667         int count = 0;
668         TRCONDITION* pCond = &gCondition[gTrackingCondsCount];
669 
670         for (int i = 0; i < kMaxXSprites; i++) {
671             if (!spriRangeIsFine(xsprite[i].reference) || xsprite[i].txID != pXSprite->rxID || xsprite[i].reference == pSprite->index)
672                 continue;
673 
674             XSPRITE* pXSpr = &xsprite[i]; spritetype* pSpr = &sprite[pXSpr->reference];
675             int index = pXSpr->reference; int cmd = pXSpr->command;
676             switch (pSpr->type) {
677                 case kSwitchToggle: // exceptions
678                 case kSwitchOneWay: // exceptions
679                     continue;
680                 case kModernPlayerControl:
681                     if (pSpr->statnum != kStatModernPlayerLinker || !bSaveLoad) break;
682                     // assign player sprite after savegame loading
683                     index = pXSpr->sysData1;
684                     cmd = xsprite[sprite[index].extra].command;
685                     break;
686             }
687 
688             if (pSpr->type == kModernCondition || pSpr->type == kModernConditionFalse)
689                 condError(pXSprite, "Tracking condition always must be first in condition sequence!");
690 
691             if (count >= kMaxTracedObjects)
692                 condError(pXSprite, "Max(% d) objects to track reached for condition # % d, rx id : % d!");
693 
694             pCond->obj[count].type = OBJ_SPRITE;
695             pCond->obj[count].index = index;
696             pCond->obj[count++].cmd = cmd;
697         }
698 
699         for (int i = 0; i < kMaxXSectors; i++) {
700             if (!sectRangeIsFine(xsector[i].reference) || xsector[i].txID != pXSprite->rxID) continue;
701             else if (count >= kMaxTracedObjects)
702                 condError(pXSprite, "Max(% d) objects to track reached for condition # % d, rx id : % d!");
703 
704             pCond->obj[count].type = OBJ_SECTOR;
705             pCond->obj[count].index = xsector[i].reference;
706             pCond->obj[count++].cmd = xsector[i].command;
707         }
708 
709         for (int i = 0; i < kMaxXWalls; i++) {
710             if (!wallRangeIsFine(xwall[i].reference) || xwall[i].txID != pXSprite->rxID)
711                 continue;
712 
713             walltype* pWall = &wall[xwall[i].reference];
714             switch (pWall->type) {
715                 case kSwitchToggle: // exceptions
716                 case kSwitchOneWay: // exceptions
717                     continue;
718             }
719 
720             if (count >= kMaxTracedObjects)
721                 condError(pXSprite, "Max(% d) objects to track reached for condition # % d, rx id : % d!");
722 
723             pCond->obj[count].type = OBJ_WALL;
724             pCond->obj[count].index = xwall[i].reference;
725             pCond->obj[count++].cmd = xwall[i].command;
726         }
727 
728         if (count == 0)
729             consoleSysMsg("No objects to track found for condition #%d, rx id: %d!", pSprite->index, pXSprite->rxID);
730 
731         pCond->length = count;
732         pCond->xindex = pSprite->extra;
733         gTrackingCondsCount++;
734 
735     }
736 }
737 
738 
739 // The following functions required for random event features
740 //-------------------------
nnExtRandom(int a,int b)741 int nnExtRandom(int a, int b) {
742     if (!gAllowTrueRandom) return Random(((b + 1) - a)) + a;
743     // used for better randomness in single player
744     std::uniform_int_distribution<int> dist_a_b(a, b);
745     return dist_a_b(gStdRandom);
746 }
747 
GetDataVal(spritetype * pSprite,int data)748 int GetDataVal(spritetype* pSprite, int data) {
749     dassert(xspriRangeIsFine(pSprite->extra));
750 
751     switch (data) {
752         case 0: return xsprite[pSprite->extra].data1;
753         case 1: return xsprite[pSprite->extra].data2;
754         case 2: return xsprite[pSprite->extra].data3;
755         case 3: return xsprite[pSprite->extra].data4;
756     }
757 
758     return -1;
759 }
760 
761 // tries to get random data field of sprite
randomGetDataValue(XSPRITE * pXSprite,int randType)762 int randomGetDataValue(XSPRITE* pXSprite, int randType) {
763     if (pXSprite == NULL) return -1;
764     int random = 0; int bad = 0; int maxRetries = kMaxRandomizeRetries;
765 
766     int rData[4];
767     rData[0] = pXSprite->data1; rData[2] = pXSprite->data3;
768     rData[1] = pXSprite->data2; rData[3] = pXSprite->data4;
769     // randomize only in case if at least 2 data fields fits.
770     for (int i = 0; i < 4; i++) {
771         switch (randType) {
772         case kRandomizeItem:
773             if (rData[i] >= kItemWeaponBase && rData[i] < kItemMax) break;
774             else bad++;
775             break;
776         case kRandomizeDude:
777             if (rData[i] >= kDudeBase && rData[i] < kDudeMax) break;
778             else bad++;
779             break;
780         case kRandomizeTX:
781             if (rData[i] > kChannelZero && rData[i] < kChannelUserMax) break;
782             else bad++;
783             break;
784         default:
785             bad++;
786             break;
787         }
788     }
789 
790     if (bad < 3) {
791         // try randomize few times
792         while (maxRetries > 0) {
793             random = nnExtRandom(0, 3);
794             if (rData[random] > 0) return rData[random];
795             else maxRetries--;
796         }
797     }
798 
799     return -1;
800 }
801 
802 // this function drops random item using random pickup generator(s)
randomDropPickupObject(spritetype * pSource,short prevItem)803 spritetype* randomDropPickupObject(spritetype* pSource, short prevItem) {
804     spritetype* pSprite2 = NULL; int selected = -1; int maxRetries = 9;
805     if (xspriRangeIsFine(pSource->extra)) {
806         XSPRITE* pXSource = &xsprite[pSource->extra];
807         while ((selected = randomGetDataValue(pXSource, kRandomizeItem)) == prevItem) if (maxRetries-- <= 0) break;
808         if (selected > 0) {
809             pSprite2 = actDropObject(pSource, selected);
810             if (pSprite2 != NULL) {
811 
812                 pXSource->dropMsg = pSprite2->type; // store dropped item type in dropMsg
813                 pSprite2->x = pSource->x;
814                 pSprite2->y = pSource->y;
815                 pSprite2->z = pSource->z;
816 
817                 if ((pSource->flags & kModernTypeFlag1) && (pXSource->txID > 0 || (pXSource->txID != 3 && pXSource->lockMsg > 0)) &&
818                     dbInsertXSprite(pSprite2->index) > 0) {
819 
820                     XSPRITE* pXSprite2 = &xsprite[pSprite2->extra];
821 
822                     // inherit spawn sprite trigger settings, so designer can send command when item picked up.
823                     pXSprite2->txID = pXSource->txID;
824                     pXSprite2->command = pXSource->command;
825                     pXSprite2->triggerOn = pXSource->triggerOn;
826                     pXSprite2->triggerOff = pXSource->triggerOff;
827 
828                     pXSprite2->Pickup = true;
829 
830                 }
831             }
832         }
833     }
834     return pSprite2;
835 }
836 
837 // this function spawns random dude using dudeSpawn
randomSpawnDude(spritetype * pSource)838 spritetype* randomSpawnDude(spritetype* pSource) {
839     spritetype* pSprite2 = NULL; int selected = -1;
840     if (xspriRangeIsFine(pSource->extra)) {
841         XSPRITE* pXSource = &xsprite[pSource->extra];
842         if ((selected = randomGetDataValue(pXSource, kRandomizeDude)) > 0)
843             pSprite2 = actSpawnDude(pSource, selected, -1, 0);
844     }
845     return pSprite2;
846 }
847 //-------------------------
848 
nnExtProcessSuperSprites()849 void nnExtProcessSuperSprites() {
850 
851     // process tracking conditions
852     if (gTrackingCondsCount > 0) {
853         for (int i = 0; i < gTrackingCondsCount; i++) {
854 
855             TRCONDITION* pCond = &gCondition[i]; XSPRITE* pXCond = &xsprite[pCond->xindex];
856             if (pCond->length > 0 && !pXCond->locked && !pXCond->isTriggered && ++pXCond->busy >= pXCond->busyTime) {
857 
858                 pXCond->busy = 0;
859                 for (int k = 0; k < pCond->length; k++) {
860 
861                     EVENT evn;
862                     evn.index = pCond->obj[k].index;   evn.cmd = pCond->obj[k].cmd;
863                     evn.type = pCond->obj[k].type;     evn.funcID = kCallbackMax;
864                     useCondition(&sprite[pXCond->reference], pXCond, evn);
865 
866                 }
867 
868             }
869         }
870     }
871 
872     // process additional proximity sprites
873     if (gProxySpritesCount > 0) {
874         for (int i = 0; i < gProxySpritesCount; i++) {
875             if (sprite[gProxySpritesList[i]].extra < 0) continue;
876 
877             XSPRITE* pXProxSpr = &xsprite[sprite[gProxySpritesList[i]].extra];
878             if (!pXProxSpr->Proximity || (!pXProxSpr->Interrutable && pXProxSpr->state != pXProxSpr->restState) || pXProxSpr->locked == 1
879                 || pXProxSpr->isTriggered) continue;  // don't process locked or triggered sprites
880 
881             int x = sprite[gProxySpritesList[i]].x;	int y = sprite[gProxySpritesList[i]].y;
882             int z = sprite[gProxySpritesList[i]].z;	int index = sprite[gProxySpritesList[i]].index;
883             int sectnum = sprite[gProxySpritesList[i]].sectnum;
884 
885             if (!pXProxSpr->DudeLockout) {
886 
887                 for (int nAffected = headspritestat[kStatDude]; nAffected >= 0; nAffected = nextspritestat[nAffected]) {
888 
889                     if ((sprite[nAffected].flags & 32) || xsprite[sprite[nAffected].extra].health <= 0) continue;
890                     else if (CheckProximity(&sprite[nAffected], x, y, z, sectnum, 96)) {
891                         trTriggerSprite(index, pXProxSpr, kCmdSpriteProximity);
892                         break;
893                     }
894                 }
895 
896             } else {
897 
898                 for (int a = connecthead; a >= 0; a = connectpoint2[a]) {
899                     if (gPlayer[a].pXSprite->health > 0 && CheckProximity(gPlayer[a].pSprite, x, y, z, sectnum, 96)) {
900                         trTriggerSprite(index, pXProxSpr, kCmdSpriteProximity);
901                         break;
902                     }
903                 }
904 
905             }
906         }
907     }
908 
909     // process sight sprites (for players only)
910     if (gSightSpritesCount > 0) {
911         for (int i = 0; i < gSightSpritesCount; i++) {
912             if (sprite[gSightSpritesList[i]].extra < 0) continue;
913 
914             XSPRITE* pXSightSpr = &xsprite[sprite[gSightSpritesList[i]].extra];
915             if (!pXSightSpr->Sight || (!pXSightSpr->Interrutable && pXSightSpr->state != pXSightSpr->restState) || pXSightSpr->locked == 1 ||
916                 pXSightSpr->isTriggered) continue; // don't process locked or triggered sprites
917 
918             int x = sprite[gSightSpritesList[i]].x;	int y = sprite[gSightSpritesList[i]].y;
919             int z = sprite[gSightSpritesList[i]].z;	int index = sprite[gSightSpritesList[i]].index;
920             int sectnum = sprite[gSightSpritesList[i]].sectnum;
921 
922             for (int a = connecthead; a >= 0; a = connectpoint2[a]) {
923                 spritetype* pPlaySprite = gPlayer[a].pSprite;
924                 if (gPlayer[a].pXSprite->health > 0 && cansee(x, y, z, sectnum, pPlaySprite->x, pPlaySprite->y, pPlaySprite->z, pPlaySprite->sectnum)) {
925                     trTriggerSprite(index, pXSightSpr, kCmdSpriteSight);
926                     break;
927                 }
928             }
929         }
930     }
931 
932     // process Debris sprites for movement
933     if (gPhysSpritesCount > 0) {
934         for (int i = 0; i < gPhysSpritesCount; i++) {
935             if (gPhysSpritesList[i] == -1) continue;
936             else if (sprite[gPhysSpritesList[i]].statnum == kStatFree || (sprite[gPhysSpritesList[i]].flags & kHitagFree) != 0) {
937                 gPhysSpritesList[i] = -1;
938                 continue;
939             }
940 
941             XSPRITE* pXDebris = &xsprite[sprite[gPhysSpritesList[i]].extra];
942             if (!(pXDebris->physAttr & kPhysMove) && !(pXDebris->physAttr & kPhysGravity)) {
943                 gPhysSpritesList[i] = -1;
944                 continue;
945             }
946 
947             spritetype* pDebris = &sprite[gPhysSpritesList[i]];
948             int idx = pDebris->index;
949 
950             XSECTOR* pXSector = (sector[pDebris->sectnum].extra >= 0) ? &xsector[sector[pDebris->sectnum].extra] : NULL;
951             viewBackupSpriteLoc(idx, pDebris);
952 
953             bool uwater = false;
954             int mass = gSpriteMass[pDebris->extra].mass;
955             int airVel = gSpriteMass[pDebris->extra].airVel;
956 
957             int top, bottom;
958             GetSpriteExtents(pDebris, &top, &bottom);
959 
960             if (pXSector != NULL) {
961 
962                 if ((uwater = pXSector->Underwater) != 0) airVel <<= 6;
963                 if (pXSector->panVel != 0 && getflorzofslope(pDebris->sectnum, pDebris->x, pDebris->y) <= bottom) {
964 
965                     int angle = pXSector->panAngle; int speed = 0;
966                     if (pXSector->panAlways || pXSector->state || pXSector->busy) {
967                         speed = pXSector->panVel << 9;
968                         if (!pXSector->panAlways && pXSector->busy)
969                             speed = mulscale16(speed, pXSector->busy);
970                     }
971                     if (sector[pDebris->sectnum].floorstat & 64)
972                         angle = (angle + GetWallAngle(sector[pDebris->sectnum].wallptr) + 512) & 2047;
973                     int dx = mulscale30(speed, Cos(angle));
974                     int dy = mulscale30(speed, Sin(angle));
975                     xvel[idx] += dx;
976                     yvel[idx] += dy;
977 
978                 }
979 
980             }
981 
982             actAirDrag(pDebris, airVel);
983 
984             if (pXDebris->physAttr & kPhysDebrisTouch) {
985                 PLAYER* pPlayer = NULL;
986                 for (int a = connecthead; a != -1; a = connectpoint2[a]) {
987                     pPlayer = &gPlayer[a];
988                     if ((gSpriteHit[pPlayer->pSprite->extra].hit & 0xc000) == 0xc000  && (gSpriteHit[pPlayer->pSprite->extra].hit & 0x3fff) == idx) {
989 
990                             int nSpeed = approxDist(xvel[pPlayer->pSprite->index], yvel[pPlayer->pSprite->index]);
991                             nSpeed = ClipLow(nSpeed - mulscale(nSpeed, mass, 6), 0x9000 - (mass << 3));
992 
993                             xvel[idx] += mulscale30(nSpeed, Cos(pPlayer->pSprite->ang));
994                             yvel[idx] += mulscale30(nSpeed, Sin(pPlayer->pSprite->ang));
995 
996                             gSpriteHit[pDebris->extra].hit = pPlayer->pSprite->index | 0xc000;
997 
998                     }
999                 }
1000             }
1001 
1002             if (pXDebris->physAttr & kPhysGravity) pXDebris->physAttr |= kPhysFalling;
1003             if ((pXDebris->physAttr & kPhysFalling) || xvel[idx] || yvel[idx] || zvel[idx] || velFloor[pDebris->sectnum] || velCeil[pDebris->sectnum])
1004                 debrisMove(i);
1005 
1006             if (xvel[idx] || yvel[idx])
1007                 pXDebris->goalAng = getangle(xvel[idx], yvel[idx]) & 2047;
1008 
1009             int ang = pDebris->ang & 2047;
1010             if ((uwater = spriteIsUnderwater(pDebris)) == false) evKill(idx, 3, kCallbackEnemeyBubble);
1011             else if (Chance(0x1000 - mass)) {
1012 
1013                 if (zvel[idx] > 0x100) debrisBubble(idx);
1014                 if (ang == pXDebris->goalAng) {
1015                    pXDebris->goalAng = (pDebris->ang + Random3(kAng60)) & 2047;
1016                    debrisBubble(idx);
1017                 }
1018 
1019             }
1020 
1021             int angStep = ClipLow(mulscale8(1, ((abs(xvel[idx]) + abs(yvel[idx])) >> 5)), (uwater) ? 1 : 0);
1022             if (ang < pXDebris->goalAng) pDebris->ang = ClipHigh(ang + angStep, pXDebris->goalAng);
1023             else if (ang > pXDebris->goalAng) pDebris->ang = ClipLow(ang - angStep, pXDebris->goalAng);
1024 
1025             int nSector = pDebris->sectnum;
1026             int cz = getceilzofslope(nSector, pDebris->x, pDebris->y);
1027             int fz = getflorzofslope(nSector, pDebris->x, pDebris->y);
1028 
1029             GetSpriteExtents(pDebris, &top, &bottom);
1030             if (fz >= bottom && gLowerLink[nSector] < 0 && !(sector[nSector].ceilingstat & 0x1)) pDebris->z += ClipLow(cz - top, 0);
1031             if (cz <= top && gUpperLink[nSector] < 0 && !(sector[nSector].floorstat & 0x1)) pDebris->z += ClipHigh(fz - bottom, 0);
1032 
1033         }
1034     }
1035 
1036 }
1037 
1038 // this function plays sound predefined in missile info
sfxPlayMissileSound(spritetype * pSprite,int missileId)1039 void sfxPlayMissileSound(spritetype* pSprite, int missileId) {
1040     MISSILEINFO_EXTRA* pMissType = &gMissileInfoExtra[missileId - kMissileBase];
1041     sfxPlay3DSound(pSprite, Chance(0x5000) ? pMissType->fireSound[0] : pMissType->fireSound[1], -1, 0);
1042 }
1043 
1044 // this function plays sound predefined in vector info
sfxPlayVectorSound(spritetype * pSprite,int vectorId)1045 void sfxPlayVectorSound(spritetype* pSprite, int vectorId) {
1046     VECTORINFO_EXTRA* pVectorData = &gVectorInfoExtra[vectorId];
1047     sfxPlay3DSound(pSprite, Chance(0x5000) ? pVectorData->fireSound[0] : pVectorData->fireSound[1], -1, 0);
1048 }
1049 
getSpriteMassBySize(spritetype * pSprite)1050 int getSpriteMassBySize(spritetype* pSprite) {
1051     int mass = 0; int seqId = -1; int clipDist = pSprite->clipdist; Seq* pSeq = NULL;
1052     if (pSprite->extra < 0) {
1053         ThrowError("getSpriteMassBySize: pSprite->extra < 0");
1054 
1055     } else if (IsDudeSprite(pSprite)) {
1056 
1057         switch (pSprite->type) {
1058         case kDudePodMother: // fake dude, no seq
1059             break;
1060         case kDudeModernCustom:
1061         case kDudeModernCustomBurning:
1062             seqId = xsprite[pSprite->extra].data2;
1063             clipDist = gGenDudeExtra[pSprite->index].initVals[2];
1064             break;
1065         default:
1066             seqId = getDudeInfo(pSprite->type)->seqStartID;
1067             break;
1068         }
1069 
1070     } else  {
1071 
1072         seqId = seqGetID(3, pSprite->extra);
1073 
1074     }
1075 
1076     SPRITEMASS* cached = &gSpriteMass[pSprite->extra];
1077     if (((seqId >= 0 && seqId == cached->seqId) || pSprite->picnum == cached->picnum) && pSprite->xrepeat == cached->xrepeat &&
1078         pSprite->yrepeat == cached->yrepeat && clipDist == cached->clipdist) {
1079         return cached->mass;
1080     }
1081 
1082     short picnum = pSprite->picnum;
1083     short massDiv = 30;  short addMul = 2; short subMul = 2;
1084 
1085     if (seqId >= 0) {
1086         DICTNODE* hSeq = gSysRes.Lookup(seqId, "SEQ");
1087         if (hSeq)
1088         {
1089             pSeq = (Seq*)gSysRes.Load(hSeq);
1090             picnum = seqGetTile(&pSeq->frames[0]);
1091         } else
1092             picnum = pSprite->picnum;
1093     }
1094 
1095     clipDist = ClipLow(pSprite->clipdist, 1);
1096     short x = tilesiz[picnum].x;        short y = tilesiz[picnum].y;
1097     short xrepeat = pSprite->xrepeat; 	short yrepeat = pSprite->yrepeat;
1098 
1099     // take surface type into account
1100     switch (tileGetSurfType(pSprite->index + 0xc000)) {
1101         case 1:  massDiv = 16; break; // stone
1102         case 2:  massDiv = 18; break; // metal
1103         case 3:  massDiv = 21; break; // wood
1104         case 4:  massDiv = 25; break; // flesh
1105         case 5:  massDiv = 28; break; // water
1106         case 6:  massDiv = 26; break; // dirt
1107         case 7:  massDiv = 27; break; // clay
1108         case 8:  massDiv = 35; break; // snow
1109         case 9:  massDiv = 22; break; // ice
1110         case 10: massDiv = 37; break; // leaves
1111         case 11: massDiv = 33; break; // cloth
1112         case 12: massDiv = 36; break; // plant
1113         case 13: massDiv = 24; break; // goo
1114         case 14: massDiv = 23; break; // lava
1115     }
1116 
1117     mass = ((x + y) * (clipDist / 2)) / massDiv;
1118 
1119     if (xrepeat > 64) mass += ((xrepeat - 64) * addMul);
1120     else if (xrepeat < 64 && mass > 0) {
1121         for (int i = 64 - xrepeat; i > 0; i--) {
1122             if ((mass -= subMul) <= 100 && subMul-- <= 1) {
1123                 mass -= i;
1124                 break;
1125             }
1126         }
1127     }
1128 
1129     if (yrepeat > 64) mass += ((yrepeat - 64) * addMul);
1130     else if (yrepeat < 64 && mass > 0) {
1131         for (int i = 64 - yrepeat; i > 0; i--) {
1132             if ((mass -= subMul) <= 100 && subMul-- <= 1) {
1133                 mass -= i;
1134                 break;
1135             }
1136         }
1137     }
1138 
1139     if (mass <= 0) cached->mass = 1 + Random(10);
1140     else cached->mass = ClipRange(mass, 1, 65535);
1141 
1142     cached->airVel = ClipRange(400 - cached->mass, 32, 400);
1143     cached->fraction = ClipRange(60000 - (cached->mass << 7), 8192, 60000);
1144 
1145     cached->xrepeat = pSprite->xrepeat;             cached->yrepeat = pSprite->yrepeat;
1146     cached->picnum = pSprite->picnum;               cached->seqId = seqId;
1147     cached->clipdist = pSprite->clipdist;
1148 
1149     return cached->mass;
1150 }
1151 
debrisGetIndex(int nSprite)1152 int debrisGetIndex(int nSprite) {
1153     if (sprite[nSprite].extra < 0 || xsprite[sprite[nSprite].extra].physAttr == 0)
1154         return -1;
1155 
1156     for (int i = 0; i < gPhysSpritesCount; i++) {
1157         if (gPhysSpritesList[i] != nSprite) continue;
1158         return i;
1159     }
1160 
1161     return -1;
1162 }
1163 
debrisGetFreeIndex(void)1164 int debrisGetFreeIndex(void) {
1165     for (int i = 0; i < kMaxSuperXSprites; i++) {
1166         if (gPhysSpritesList[i] == -1 || sprite[gPhysSpritesList[i]].statnum == kStatFree) return i;
1167 
1168         else if ((sprite[gPhysSpritesList[i]].flags & kHitagFree) || sprite[gPhysSpritesList[i]].extra < 0) return i;
1169         else if (xsprite[sprite[gPhysSpritesList[i]].extra].physAttr == 0) return i;
1170     }
1171 
1172     return -1;
1173 }
1174 
debrisConcuss(int nOwner,int listIndex,int x,int y,int z,int dmg)1175 void debrisConcuss(int nOwner, int listIndex, int x, int y, int z, int dmg) {
1176     spritetype* pSprite = (gPhysSpritesList[listIndex] >= 0) ? &sprite[gPhysSpritesList[listIndex]] : NULL;
1177     if (pSprite != NULL && xspriRangeIsFine(pSprite->extra)) {
1178         int dx = pSprite->x - x; int dy = pSprite->y - y; int dz = (pSprite->z - z) >> 4;
1179         dmg = scale(0x40000, dmg, 0x40000 + dx * dx + dy * dy + dz * dz);
1180 
1181         int size = (tilesiz[pSprite->picnum].x * pSprite->xrepeat * tilesiz[pSprite->picnum].y * pSprite->yrepeat) >> 1;
1182         if (xsprite[pSprite->extra].physAttr & kPhysDebrisExplode) {
1183             if (gSpriteMass[pSprite->extra].mass > 0) {
1184                 int t = scale(dmg, size, gSpriteMass[pSprite->extra].mass);
1185 
1186                 xvel[pSprite->index] += mulscale16(t, dx);
1187                 yvel[pSprite->index] += mulscale16(t, dy);
1188                 zvel[pSprite->index] += mulscale16(t, dz);
1189             }
1190 
1191             if (pSprite->type >= kThingBase && pSprite->type < kThingMax && !xsprite[pSprite->extra].locked)
1192                 changespritestat(pSprite->index, kStatThing); // if it was a thing, return it's statnum back
1193         }
1194 
1195 
1196         actDamageSprite(nOwner, pSprite, kDamageExplode, dmg);
1197         return;
1198     }
1199 }
1200 
debrisBubble(int nSprite)1201 void debrisBubble(int nSprite) {
1202 
1203     spritetype* pSprite = &sprite[nSprite];
1204 
1205     int top, bottom;
1206     GetSpriteExtents(pSprite, &top, &bottom);
1207     for (int i = 0; i < 1 + Random(5); i++) {
1208 
1209         int nDist = (pSprite->xrepeat * (tilesiz[pSprite->picnum].x >> 1)) >> 2;
1210         int nAngle = Random(2048);
1211         int x = pSprite->x + mulscale30(nDist, Cos(nAngle));
1212         int y = pSprite->y + mulscale30(nDist, Sin(nAngle));
1213         int z = bottom - Random(bottom - top);
1214         spritetype* pFX = gFX.fxSpawn((FX_ID)(FX_23 + Random(3)), pSprite->sectnum, x, y, z, 0);
1215         if (pFX) {
1216             xvel[pFX->index] = xvel[nSprite] + Random2(0x1aaaa);
1217             yvel[pFX->index] = yvel[nSprite] + Random2(0x1aaaa);
1218             zvel[pFX->index] = zvel[nSprite] + Random2(0x1aaaa);
1219         }
1220 
1221     }
1222 
1223     if (Chance(0x2000))
1224         evPost(nSprite, 3, 0, kCallbackEnemeyBubble);
1225 }
1226 
debrisMove(int listIndex)1227 void debrisMove(int listIndex) {
1228 
1229     if (!(sprite[gPhysSpritesList[listIndex]].extra > 0 && sprite[gPhysSpritesList[listIndex]].extra < kMaxXSprites)) {
1230         gPhysSpritesList[listIndex] = -1;
1231         return;
1232     } else if (!(sprite[gPhysSpritesList[listIndex]].sectnum >= 0 && sprite[gPhysSpritesList[listIndex]].sectnum < kMaxSectors)) {
1233         gPhysSpritesList[listIndex] = -1;
1234         return;
1235     }
1236 
1237     int nSprite = gPhysSpritesList[listIndex];
1238     int nXSprite = sprite[nSprite].extra;       XSPRITE* pXDebris = &xsprite[nXSprite];
1239     spritetype* pSprite = &sprite[nSprite];     int nSector = pSprite->sectnum;
1240 
1241     int top, bottom, i;
1242     GetSpriteExtents(pSprite, &top, &bottom);
1243 
1244     int moveHit = 0;
1245     int floorDist = (bottom - pSprite->z) >> 2;
1246     int ceilDist = (pSprite->z - top) >> 2;
1247     int clipDist = pSprite->clipdist << 2;
1248     int mass = gSpriteMass[nXSprite].mass;
1249 
1250     bool uwater = false, depth = false;
1251     int tmpFraction = gSpriteMass[pSprite->extra].fraction;
1252     if (sector[nSector].extra >= 0 && xsector[sector[nSector].extra].Underwater) {
1253         tmpFraction >>= 1;
1254         uwater = true;
1255     }
1256 
1257     if (xvel[nSprite] || yvel[nSprite]) {
1258 
1259         short oldcstat = pSprite->cstat;
1260         pSprite->cstat &= ~(CSTAT_SPRITE_BLOCK | CSTAT_SPRITE_BLOCK_HITSCAN);
1261 
1262         moveHit = gSpriteHit[nXSprite].hit = ClipMove((int*)&pSprite->x, (int*)&pSprite->y, (int*)&pSprite->z, &nSector, xvel[nSprite] >> 12,
1263             yvel[nSprite] >> 12, clipDist, ceilDist, floorDist, CLIPMASK0);
1264 
1265         pSprite->cstat = oldcstat;
1266         if (pSprite->sectnum != nSector) {
1267             if (!sectRangeIsFine(nSector)) return;
1268             else ChangeSpriteSect(nSprite, nSector);
1269         }
1270 
1271         if (sector[nSector].type >= kSectorPath && sector[nSector].type <= kSectorRotate) {
1272             short nSector2 = nSector;
1273             if (pushmove_old(&pSprite->x, &pSprite->y, &pSprite->z, &nSector2, clipDist, ceilDist, floorDist, CLIPMASK0) != -1)
1274                 nSector = nSector2;
1275         }
1276 
1277         if ((gSpriteHit[nXSprite].hit & 0xc000) == 0x8000) {
1278             i = moveHit = gSpriteHit[nXSprite].hit & 0x3fff;
1279             actWallBounceVector((int*)&xvel[nSprite], (int*)&yvel[nSprite], i, tmpFraction);
1280         }
1281 
1282     } else if (!FindSector(pSprite->x, pSprite->y, pSprite->z, &nSector)) {
1283         return;
1284     }
1285 
1286     if (pSprite->sectnum != nSector) {
1287         dassert(nSector >= 0 && nSector < kMaxSectors);
1288         ChangeSpriteSect(nSprite, nSector);
1289         nSector = pSprite->sectnum;
1290     }
1291 
1292     if (sector[nSector].extra > 0) {
1293         uwater = xsector[sector[nSector].extra].Underwater;
1294         depth = xsector[sector[nSector].extra].Depth;
1295     }
1296 
1297     if (zvel[nSprite])
1298         pSprite->z += zvel[nSprite] >> 8;
1299 
1300     int ceilZ, ceilHit, floorZ, floorHit;
1301     GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, clipDist, CLIPMASK0, PARALLAXCLIP_CEILING | PARALLAXCLIP_FLOOR);
1302     GetSpriteExtents(pSprite, &top, &bottom);
1303 
1304     if ((pXDebris->physAttr & kPhysDebrisSwim) && uwater) {
1305 
1306         int vc = 0;
1307         int cz = getceilzofslope(nSector, pSprite->x, pSprite->y);
1308         int fz = getflorzofslope(nSector, pSprite->x, pSprite->y);
1309         int div = ClipLow(bottom - top, 1);
1310 
1311         if (gLowerLink[nSector] >= 0) cz += (cz < 0) ? 0x500 : -0x500;
1312         if (top > cz && (!(pXDebris->physAttr & kPhysDebrisFloat) || fz <= bottom << 2))
1313             zvel[nSprite] -= divscale8((bottom - ceilZ) >> 6, mass);
1314 
1315         if (fz < bottom)
1316             vc = 58254 + ((bottom - fz) * -80099) / div;
1317 
1318         if (vc) {
1319             pSprite->z += ((vc << 2) >> 1) >> 8;
1320             zvel[nSprite] += vc;
1321         }
1322 
1323     } else if ((pXDebris->physAttr & kPhysGravity) && bottom < floorZ) {
1324 
1325         pSprite->z += 455;
1326         zvel[nSprite] += 58254;
1327 
1328     }
1329 
1330     if ((i = CheckLink(pSprite)) != 0) {
1331         GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, clipDist, CLIPMASK0, PARALLAXCLIP_CEILING | PARALLAXCLIP_FLOOR);
1332         if (!(pSprite->cstat & CSTAT_SPRITE_INVISIBLE)) {
1333             switch (i) {
1334                 case kMarkerUpWater:
1335                 case kMarkerUpGoo:
1336                     int pitch = (150000 - (gSpriteMass[pSprite->extra].mass << 9)) + Random3(8192);
1337                     sfxPlay3DSoundCP(pSprite, 720, -1, 0, pitch, 75 - Random(40));
1338                     if (!spriteIsUnderwater(pSprite)) {
1339                         evKill(pSprite->index, 3, kCallbackEnemeyBubble);
1340                     } else {
1341                         evPost(pSprite->index, 3, 0, kCallbackEnemeyBubble);
1342                         for (int i = 2; i <= 5; i++) {
1343                             if (Chance(0x5000 * i))
1344                                 evPost(pSprite->index, 3, Random(5), kCallbackEnemeyBubble);
1345                         }
1346                     }
1347                     break;
1348             }
1349         }
1350     }
1351 
1352     GetSpriteExtents(pSprite, &top, &bottom);
1353 
1354     if (floorZ <= bottom) {
1355 
1356         gSpriteHit[nXSprite].florhit = floorHit;
1357         int v30 = zvel[nSprite] - velFloor[pSprite->sectnum];
1358 
1359         if (v30 > 0) {
1360 
1361             pXDebris->physAttr |= kPhysFalling;
1362             actFloorBounceVector((int*)&xvel[nSprite], (int*)&yvel[nSprite], (int*)&v30, pSprite->sectnum, tmpFraction);
1363             zvel[nSprite] = v30;
1364 
1365             if (klabs(zvel[nSprite]) < 0x10000) {
1366                 zvel[nSprite] = velFloor[pSprite->sectnum];
1367                 pXDebris->physAttr &= ~kPhysFalling;
1368             }
1369 
1370             moveHit = floorHit;
1371             spritetype* pFX = NULL; spritetype* pFX2 = NULL;
1372             switch (tileGetSurfType(floorHit)) {
1373             case kSurfLava:
1374                 if ((pFX = gFX.fxSpawn(FX_10, pSprite->sectnum, pSprite->x, pSprite->y, floorZ, 0)) == NULL) break;
1375                 for (i = 0; i < 7; i++) {
1376                     if ((pFX2 = gFX.fxSpawn(FX_14, pFX->sectnum, pFX->x, pFX->y, pFX->z, 0)) == NULL) continue;
1377                     xvel[pFX2->index] = Random2(0x6aaaa);
1378                     yvel[pFX2->index] = Random2(0x6aaaa);
1379                     zvel[pFX2->index] = -Random(0xd5555);
1380                 }
1381                 break;
1382             case kSurfWater:
1383                 gFX.fxSpawn(FX_9, pSprite->sectnum, pSprite->x, pSprite->y, floorZ, 0);
1384                 break;
1385             }
1386 
1387         } else if (zvel[nSprite] == 0) {
1388 
1389             pXDebris->physAttr &= ~kPhysFalling;
1390 
1391         }
1392 
1393     } else {
1394 
1395         gSpriteHit[nXSprite].florhit = 0;
1396         if (pXDebris->physAttr & kPhysGravity)
1397             pXDebris->physAttr |= kPhysFalling;
1398 
1399     }
1400 
1401     if (top <= ceilZ) {
1402 
1403         gSpriteHit[nXSprite].ceilhit = moveHit = ceilHit;
1404         pSprite->z += ClipLow(ceilZ - top, 0);
1405         if (zvel[nSprite] <= 0 && (pXDebris->physAttr & kPhysFalling))
1406             zvel[nSprite] = mulscale16(-zvel[nSprite], 0x2000);
1407 
1408     } else {
1409 
1410         gSpriteHit[nXSprite].ceilhit = 0;
1411         GetSpriteExtents(pSprite, &top, &bottom);
1412 
1413     }
1414 
1415     if (moveHit && pXDebris->Impact && !pXDebris->locked && !pXDebris->isTriggered && (pXDebris->state == pXDebris->restState || pXDebris->Interrutable)) {
1416         if (pSprite->type >= kThingBase && pSprite->type < kThingMax)
1417             changespritestat(nSprite, kStatThing);
1418 
1419         trTriggerSprite(pSprite->index, pXDebris, kCmdToggle);
1420 
1421     }
1422 
1423     if (!xvel[nSprite] && !yvel[nSprite]) return;
1424     else if ((floorHit & 0xc000) == 0xc000) {
1425 
1426         int nHitSprite = floorHit & 0x3fff;
1427         if ((sprite[nHitSprite].cstat & 0x30) == 0) {
1428             xvel[nSprite] += mulscale(4, pSprite->x - sprite[nHitSprite].x, 2);
1429             yvel[nSprite] += mulscale(4, pSprite->y - sprite[nHitSprite].y, 2);
1430             return;
1431         }
1432     }
1433 
1434     pXDebris->height = ClipLow(floorZ - bottom, 0) >> 8;
1435     if (uwater || pXDebris->height >= 0x100)
1436         return;
1437 
1438     int nDrag = 0x2a00;
1439     if (pXDebris->height > 0)
1440         nDrag -= scale(nDrag, pXDebris->height, 0x100);
1441 
1442     xvel[nSprite] -= mulscale16r(xvel[nSprite], nDrag);
1443     yvel[nSprite] -= mulscale16r(yvel[nSprite], nDrag);
1444     if (approxDist(xvel[nSprite], yvel[nSprite]) < 0x1000)
1445         xvel[nSprite] = yvel[nSprite] = 0;
1446 
1447 }
1448 
1449 
1450 
ceilIsTooLow(spritetype * pSprite)1451 bool ceilIsTooLow(spritetype* pSprite) {
1452     if (pSprite != NULL) {
1453 
1454         sectortype* pSector = &sector[pSprite->sectnum];
1455         int a = pSector->ceilingz - pSector->floorz;
1456         int top, bottom;
1457         GetSpriteExtents(pSprite, &top, &bottom);
1458         int b = top - bottom;
1459         if (a > b) return true;
1460     }
1461 
1462     return false;
1463 }
1464 
aiSetGenIdleState(spritetype * pSprite,XSPRITE * pXSprite)1465 void aiSetGenIdleState(spritetype* pSprite, XSPRITE* pXSprite) {
1466     switch (pSprite->type) {
1467     case kDudeModernCustom:
1468     case kDudeModernCustomBurning:
1469         aiGenDudeNewState(pSprite, &genIdle);
1470         break;
1471     default:
1472         aiNewState(pSprite, pXSprite, &genIdle);
1473         break;
1474     }
1475 }
1476 
1477 // this function stops wind on all TX sectors affected by WindGen after it goes off state.
windGenStopWindOnSectors(XSPRITE * pXSource)1478 void windGenStopWindOnSectors(XSPRITE* pXSource) {
1479     spritetype* pSource = &sprite[pXSource->reference];
1480     if (pXSource->txID <= 0 && xsectRangeIsFine(sector[pSource->sectnum].extra)) {
1481         xsector[sector[pSource->sectnum].extra].windVel = 0;
1482         return;
1483     }
1484 
1485     for (int i = bucketHead[pXSource->txID]; i < bucketHead[pXSource->txID + 1]; i++) {
1486         if (rxBucket[i].type != OBJ_SECTOR) continue;
1487         XSECTOR* pXSector = &xsector[sector[rxBucket[i].index].extra];
1488         if ((pXSector->state == 1 && !pXSector->windAlways)
1489             || ((pSource->flags & kModernTypeFlag1) && !(pSource->flags & kModernTypeFlag2))) {
1490                 pXSector->windVel = 0;
1491         }
1492     }
1493 
1494     // check redirected TX buckets
1495     int rx = -1; XSPRITE* pXRedir = NULL;
1496     while ((pXRedir = evrListRedirectors(OBJ_SPRITE, sprite[pXSource->reference].extra, pXRedir, &rx)) != NULL) {
1497         for (int i = bucketHead[rx]; i < bucketHead[rx + 1]; i++) {
1498             if (rxBucket[i].type != OBJ_SECTOR) continue;
1499             XSECTOR* pXSector = &xsector[sector[rxBucket[i].index].extra];
1500             if ((pXSector->state == 1 && !pXSector->windAlways) || (pSource->flags & kModernTypeFlag2))
1501                 pXSector->windVel = 0;
1502         }
1503     }
1504 }
1505 
1506 
trPlayerCtrlStartScene(XSPRITE * pXSource,PLAYER * pPlayer,bool force)1507 void trPlayerCtrlStartScene(XSPRITE* pXSource, PLAYER* pPlayer, bool force) {
1508 
1509     int nSource = pXSource->reference; TRPLAYERCTRL* pCtrl = &gPlayerCtrl[pPlayer->nPlayer];
1510 
1511     if (pCtrl->qavScene.index >= 0 && !force) return;
1512 
1513     QAV* pQav = playerQavSceneLoad(pXSource->data2);
1514     if (pQav != NULL) {
1515 
1516         // save current weapon
1517         pXSource->dropMsg = pPlayer->curWeapon;
1518 
1519         short nIndex = pCtrl->qavScene.index;
1520         if (nIndex > -1 && nIndex != nSource && sprite[nIndex].extra >= 0)
1521             pXSource->dropMsg = xsprite[sprite[nIndex].extra].dropMsg;
1522 
1523         if (nIndex < 0)
1524             WeaponLower(pPlayer);
1525 
1526         pXSource->sysData1 = ClipLow((pQav->at10 * pXSource->waitTime) / 4, 0); // how many times animation should be played
1527 
1528         pCtrl->qavScene.index = nSource;
1529         pCtrl->qavScene.qavResrc = pQav;
1530         pCtrl->qavScene.dummy = -1;
1531 
1532         pCtrl->qavScene.qavResrc->Preload();
1533 
1534         pPlayer->sceneQav = pXSource->data2;
1535         pPlayer->weaponTimer = pCtrl->qavScene.qavResrc->at10;
1536         pPlayer->qavCallback = (pXSource->data3 > 0) ? ClipRange(pXSource->data3 - 1, 0, 32) : -1;
1537         pPlayer->qavLoop = false;
1538 
1539     }
1540 
1541 }
1542 
trPlayerCtrlStopScene(PLAYER * pPlayer)1543 void trPlayerCtrlStopScene(PLAYER* pPlayer) {
1544 
1545     TRPLAYERCTRL* pCtrl = &gPlayerCtrl[pPlayer->nPlayer];
1546     int scnIndex = pCtrl->qavScene.index; XSPRITE* pXSource = NULL;
1547     if (spriRangeIsFine(scnIndex)) {
1548         pXSource = &xsprite[sprite[scnIndex].extra];
1549         pXSource->sysData1 = 0;
1550     }
1551 
1552     if (pCtrl->qavScene.index >= 0) {
1553         pCtrl->qavScene.index = -1;
1554         pCtrl->qavScene.qavResrc = NULL;
1555         pPlayer->sceneQav = -1;
1556 
1557         // restore weapon
1558         if (pPlayer->pXSprite->health > 0) {
1559             int oldWeapon = (pXSource && pXSource->dropMsg != 0) ? pXSource->dropMsg : 1;
1560             pPlayer->input.newWeapon = pPlayer->curWeapon = oldWeapon;
1561             WeaponRaise(pPlayer);
1562         }
1563     }
1564 
1565 }
1566 
trPlayerCtrlLink(XSPRITE * pXSource,PLAYER * pPlayer,bool checkCondition)1567 void trPlayerCtrlLink(XSPRITE* pXSource, PLAYER* pPlayer, bool checkCondition) {
1568 
1569     // save player's sprite index to let the tracking condition know it after savegame loading...
1570     pXSource->sysData1                  = pPlayer->nSprite;
1571 
1572     pPlayer->pXSprite->txID             = pXSource->txID;
1573     pPlayer->pXSprite->command          = kCmdToggle;
1574     pPlayer->pXSprite->triggerOn        = pXSource->triggerOn;
1575     pPlayer->pXSprite->triggerOff       = pXSource->triggerOff;
1576     pPlayer->pXSprite->busyTime         = pXSource->busyTime;
1577     pPlayer->pXSprite->waitTime         = pXSource->waitTime;
1578     pPlayer->pXSprite->restState        = pXSource->restState;
1579 
1580     pPlayer->pXSprite->Push             = pXSource->Push;
1581     pPlayer->pXSprite->Impact           = pXSource->Impact;
1582     pPlayer->pXSprite->Vector           = pXSource->Vector;
1583     pPlayer->pXSprite->Touch            = pXSource->Touch;
1584     pPlayer->pXSprite->Sight            = pXSource->Sight;
1585     pPlayer->pXSprite->Proximity        = pXSource->Proximity;
1586 
1587     pPlayer->pXSprite->Decoupled        = pXSource->Decoupled;
1588     pPlayer->pXSprite->Interrutable     = pXSource->Interrutable;
1589     pPlayer->pXSprite->DudeLockout      = pXSource->DudeLockout;
1590 
1591     pPlayer->pXSprite->data1            = pXSource->data1;
1592     pPlayer->pXSprite->data2            = pXSource->data2;
1593     pPlayer->pXSprite->data3            = pXSource->data3;
1594     pPlayer->pXSprite->data4            = pXSource->data4;
1595 
1596     pPlayer->pXSprite->key              = pXSource->key;
1597     pPlayer->pXSprite->dropMsg          = pXSource->dropMsg;
1598 
1599     // let's check if there is tracking condition expecting objects with this TX id
1600     if (checkCondition && pXSource->txID >= kChannelUser) {
1601         for (int i = 0; i < gTrackingCondsCount; i++) {
1602 
1603             TRCONDITION* pCond = &gCondition[i];
1604             if (xsprite[pCond->xindex].rxID != pXSource->txID)
1605                 continue;
1606 
1607             // search for player control sprite and replace it with actual player sprite
1608             for (int k = 0; k < pCond->length; k++) {
1609                 if (pCond->obj[k].type != OBJ_SPRITE || pCond->obj[k].index != pXSource->reference) continue;
1610                 pCond->obj[k].index = pPlayer->nSprite;
1611                 pCond->obj[k].cmd = pPlayer->pXSprite->command;
1612                 break;
1613             }
1614 
1615         }
1616     }
1617 }
1618 
trPlayerCtrlSetRace(XSPRITE * pXSource,PLAYER * pPlayer)1619 void trPlayerCtrlSetRace(XSPRITE* pXSource, PLAYER* pPlayer) {
1620     playerSetRace(pPlayer, pXSource->data2);
1621     switch (pPlayer->lifeMode) {
1622         case kModeHuman:
1623         case kModeBeast:
1624             playerSizeReset(pPlayer);
1625             break;
1626         case kModeHumanShrink:
1627             playerSizeShrink(pPlayer, 2);
1628             break;
1629         case kModeHumanGrown:
1630             playerSizeGrow(pPlayer, 2);
1631             break;
1632     }
1633 }
1634 
trPlayerCtrlSetMoveSpeed(XSPRITE * pXSource,PLAYER * pPlayer)1635 void trPlayerCtrlSetMoveSpeed(XSPRITE* pXSource, PLAYER* pPlayer) {
1636 
1637     int speed = ClipRange(pXSource->data2, 0, 500);
1638     for (int i = 0; i < kModeMax; i++) {
1639         for (int a = 0; a < kPostureMax; a++) {
1640             POSTURE* curPosture = &pPlayer->pPosture[i][a]; POSTURE* defPosture = &gPostureDefaults[i][a];
1641             curPosture->frontAccel = (defPosture->frontAccel * speed) / kPercFull;
1642             curPosture->sideAccel = (defPosture->sideAccel * speed) / kPercFull;
1643             curPosture->backAccel = (defPosture->backAccel * speed) / kPercFull;
1644         }
1645     }
1646 }
1647 
trPlayerCtrlSetJumpHeight(XSPRITE * pXSource,PLAYER * pPlayer)1648 void trPlayerCtrlSetJumpHeight(XSPRITE* pXSource, PLAYER* pPlayer) {
1649 
1650     int jump = ClipRange(pXSource->data3, 0, 500);
1651     for (int i = 0; i < kModeMax; i++) {
1652         POSTURE* curPosture = &pPlayer->pPosture[i][kPostureStand]; POSTURE* defPosture = &gPostureDefaults[i][kPostureStand];
1653         curPosture->normalJumpZ = (defPosture->normalJumpZ * jump) / kPercFull;
1654         curPosture->pwupJumpZ = (defPosture->pwupJumpZ * jump) / kPercFull;
1655     }
1656 }
1657 
trPlayerCtrlSetScreenEffect(XSPRITE * pXSource,PLAYER * pPlayer)1658 void trPlayerCtrlSetScreenEffect(XSPRITE* pXSource, PLAYER* pPlayer) {
1659 
1660     int eff = ClipLow(pXSource->data2, 0); int time = (eff > 0) ? pXSource->data3 : 0;
1661     switch (eff) {
1662         case 0: // clear all
1663         case 1: // tilting
1664             pPlayer->tiltEffect = ClipRange(time, 0, 220);
1665             if (eff) break;
1666             fallthrough__;
1667         case 2: // pain
1668             pPlayer->painEffect = ClipRange(time, 0, 2048);
1669             if (eff) break;
1670             fallthrough__;
1671         case 3: // blind
1672             pPlayer->blindEffect = ClipRange(time, 0, 2048);
1673             if (eff) break;
1674             fallthrough__;
1675         case 4: // pickup
1676             pPlayer->pickupEffect = ClipRange(time, 0, 2048);
1677             if (eff) break;
1678             fallthrough__;
1679         case 5: // quakeEffect
1680             pPlayer->quakeEffect = ClipRange(time, 0, 2048);
1681             if (eff) break;
1682             fallthrough__;
1683         case 6: // visibility
1684             pPlayer->visibility = ClipRange(time, 0, 2048);
1685             if (eff) break;
1686             fallthrough__;
1687         case 7: // delirium
1688             pPlayer->pwUpTime[kPwUpDeliriumShroom] = ClipHigh(time << 1, 432000);
1689             break;
1690     }
1691 
1692 }
1693 
trPlayerCtrlSetLookAngle(XSPRITE * pXSource,PLAYER * pPlayer)1694 void trPlayerCtrlSetLookAngle(XSPRITE* pXSource, PLAYER* pPlayer) {
1695 
1696     CONSTEXPR int upAngle = 289; CONSTEXPR int downAngle = -347;
1697     CONSTEXPR double lookStepUp = 4.0 * upAngle / 60.0;
1698     CONSTEXPR double lookStepDown = -4.0 * downAngle / 60.0;
1699 
1700     int look = pXSource->data2 << 5;
1701     if (look > 0) pPlayer->q16look = fix16_min(mulscale8(F16(lookStepUp), look), F16(upAngle));
1702     else if (look < 0) pPlayer->q16look = -fix16_max(mulscale8(F16(lookStepDown), abs(look)), F16(downAngle));
1703     else pPlayer->q16look = 0;
1704 
1705 }
1706 
trPlayerCtrlEraseStuff(XSPRITE * pXSource,PLAYER * pPlayer)1707 void trPlayerCtrlEraseStuff(XSPRITE* pXSource, PLAYER* pPlayer) {
1708 
1709     switch (pXSource->data2) {
1710         case 0: // erase all
1711             fallthrough__;
1712         case 1: // erase weapons
1713             WeaponLower(pPlayer);
1714 
1715             for (int i = 0; i < 14; i++) {
1716                 pPlayer->hasWeapon[i] = false;
1717                 // also erase ammo
1718                 if (i < 12) pPlayer->ammoCount[i] = 0;
1719             }
1720 
1721             pPlayer->hasWeapon[1] = true;
1722             pPlayer->curWeapon = 0;
1723             pPlayer->nextWeapon = 1;
1724 
1725             WeaponRaise(pPlayer);
1726             if (pXSource->data2) break;
1727             fallthrough__;
1728         case 2: // erase all armor
1729             for (int i = 0; i < 3; i++) pPlayer->armor[i] = 0;
1730             if (pXSource->data2) break;
1731             fallthrough__;
1732         case 3: // erase all pack items
1733             for (int i = 0; i < 5; i++) {
1734                 pPlayer->packSlots[i].isActive = false;
1735                 pPlayer->packSlots[i].curAmount = 0;
1736             }
1737             pPlayer->packItemId = -1;
1738             if (pXSource->data2) break;
1739             fallthrough__;
1740         case 4: // erase all keys
1741             for (int i = 0; i < 8; i++) pPlayer->hasKey[i] = false;
1742             if (pXSource->data2) break;
1743             fallthrough__;
1744         case 5: // erase powerups
1745             for (int i = 0; i < kMaxPowerUps; i++) pPlayer->pwUpTime[i] = 0;
1746             break;
1747     }
1748 }
1749 
trPlayerCtrlGiveStuff(XSPRITE * pXSource,PLAYER * pPlayer,TRPLAYERCTRL * pCtrl)1750 void trPlayerCtrlGiveStuff(XSPRITE* pXSource, PLAYER* pPlayer, TRPLAYERCTRL* pCtrl) {
1751 
1752     int weapon = pXSource->data3;
1753     switch (pXSource->data2) {
1754         case 1: // give N weapon and default ammo for it
1755         case 2: // give just N ammo for selected weapon
1756             if (weapon <= 0 || weapon > 13) {
1757                 consoleSysMsg("Weapon #%d is out of a weapons range!");
1758                 break;
1759             } else if (pXSource->data2 == 2 && pXSource->data4 == 0) {
1760                 consoleSysMsg("Zero ammo for weapon #%d is specyfied!");
1761                 break;
1762             }
1763             switch (weapon) {
1764                 case 11: // remote bomb
1765                 case 12: // prox bomb
1766                     pPlayer->hasWeapon[weapon] = true;
1767                     weapon--;
1768                     pPlayer->ammoCount[weapon] = ClipHigh(pPlayer->ammoCount[weapon] + ((pXSource->data2 == 2) ? pXSource->data4 : 1), gAmmoInfo[weapon].max);
1769                     weapon++;
1770                     break;
1771                 default:
1772                     for (int i = 0; i < 11; i++) {
1773                         if (gWeaponItemData[i].type != weapon) continue;
1774 
1775                         WEAPONITEMDATA* pWeaponData = &gWeaponItemData[i]; int nAmmoType = pWeaponData->ammoType;
1776                         switch (pXSource->data2) {
1777                             case 1:
1778                                 pPlayer->hasWeapon[weapon] = true;
1779                                 if (pPlayer->ammoCount[nAmmoType] >= pWeaponData->count) break;
1780                                 pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType] + pWeaponData->count, gAmmoInfo[nAmmoType].max);
1781                                 break;
1782                             case 2:
1783                                 pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType] + pXSource->data4, gAmmoInfo[nAmmoType].max);
1784                                 break;
1785                         }
1786                         break;
1787                     }
1788                     break;
1789             }
1790             if (pPlayer->hasWeapon[weapon] && pXSource->data4 == 0) { // switch on it
1791                 pPlayer->nextWeapon = 0;
1792 
1793                 if (pPlayer->sceneQav >= 0 && spriRangeIsFine(pCtrl->qavScene.index)) {
1794                     XSPRITE* pXScene = &xsprite[sprite[pCtrl->qavScene.index].extra];
1795                     pXScene->dropMsg = weapon;
1796                 } else if (pPlayer->curWeapon != weapon) {
1797                     pPlayer->input.newWeapon = weapon;
1798                     WeaponRaise(pPlayer);
1799                 }
1800             }
1801             break;
1802     }
1803 }
1804 
trPlayerCtrlUsePackItem(XSPRITE * pXSource,PLAYER * pPlayer,int evCmd)1805 void trPlayerCtrlUsePackItem(XSPRITE* pXSource, PLAYER* pPlayer, int evCmd) {
1806     unsigned int invItem = pXSource->data2 - 1;
1807     switch (evCmd) {
1808         case kCmdOn:
1809             if (!pPlayer->packSlots[invItem].isActive) packUseItem(pPlayer, invItem);
1810             break;
1811         case kCmdOff:
1812             if (pPlayer->packSlots[invItem].isActive) packUseItem(pPlayer, invItem);
1813             break;
1814         default:
1815             packUseItem(pPlayer, invItem);
1816             break;
1817     }
1818 
1819     switch (pXSource->data4) {
1820         case 2: // both
1821         case 0: // switch on it
1822             if (pPlayer->packSlots[invItem].curAmount > 0) pPlayer->packItemId = invItem;
1823             if (!pXSource->data4) break;
1824             fallthrough__;
1825         case 1: // force remove after use
1826             pPlayer->packSlots[invItem].isActive = false;
1827             pPlayer->packSlots[invItem].curAmount = 0;
1828             break;
1829     }
1830 }
1831 
useObjResizer(XSPRITE * pXSource,short objType,int objIndex)1832 void useObjResizer(XSPRITE* pXSource, short objType, int objIndex) {
1833     switch (objType) {
1834         // for sectors
1835     case 6:
1836         if (valueIsBetween(pXSource->data1, -1, 32767))
1837             sector[objIndex].floorxpanning = ClipRange(pXSource->data1, 0, 255);
1838 
1839         if (valueIsBetween(pXSource->data2, -1, 32767))
1840             sector[objIndex].floorypanning = ClipRange(pXSource->data2, 0, 255);
1841 
1842         if (valueIsBetween(pXSource->data3, -1, 32767))
1843             sector[objIndex].ceilingxpanning = ClipRange(pXSource->data3, 0, 255);
1844 
1845         if (valueIsBetween(pXSource->data4, -1, 65535))
1846             sector[objIndex].ceilingypanning = ClipRange(pXSource->data4, 0, 255);
1847         break;
1848         // for sprites
1849     case OBJ_SPRITE: {
1850 
1851         bool fit = false;
1852         // resize by seq scaling
1853         if (sprite[pXSource->reference].flags & kModernTypeFlag1) {
1854 
1855             if (valueIsBetween(pXSource->data1, -255, 32767)) {
1856                 int mulDiv = (valueIsBetween(pXSource->data2, 0, 257)) ? pXSource->data2 : 256;
1857                 if (pXSource->data1 > 0) xsprite[sprite[objIndex].extra].scale = mulDiv * ClipHigh(pXSource->data1, 25);
1858                 else if (pXSource->data1 < 0) xsprite[sprite[objIndex].extra].scale = mulDiv / ClipHigh(abs(pXSource->data1), 25);
1859                 else xsprite[sprite[objIndex].extra].scale = 0;
1860                 fit = true;
1861             }
1862 
1863         // resize by repeats
1864         } else {
1865 
1866             if (valueIsBetween(pXSource->data1, -1, 32767)) {
1867                 sprite[objIndex].xrepeat = ClipRange(pXSource->data1, 0, 255);
1868                 fit = true;
1869             }
1870 
1871             if (valueIsBetween(pXSource->data2, -1, 32767)) {
1872                 sprite[objIndex].yrepeat = ClipRange(pXSource->data2, 0, 255);
1873                 fit = true;
1874             }
1875 
1876         }
1877 
1878         if (fit && (sprite[objIndex].type == kDudeModernCustom || sprite[objIndex].type == kDudeModernCustomBurning)) {
1879 
1880             // request properties update for custom dude
1881             gGenDudeExtra[objIndex].updReq[kGenDudePropertySpriteSize] = true;
1882             gGenDudeExtra[objIndex].updReq[kGenDudePropertyAttack] = true;
1883             gGenDudeExtra[objIndex].updReq[kGenDudePropertyMass] = true;
1884             gGenDudeExtra[objIndex].updReq[kGenDudePropertyDmgScale] = true;
1885             evPost(objIndex, 3, kGenDudeUpdTimeRate, kCallbackGenDudeUpdate);
1886 
1887         }
1888 
1889         if (valueIsBetween(pXSource->data3, -1, 32767))
1890             sprite[objIndex].xoffset = ClipRange(pXSource->data3, 0, 255);
1891 
1892         if (valueIsBetween(pXSource->data4, -1, 65535))
1893             sprite[objIndex].yoffset = ClipRange(pXSource->data4, 0, 255);
1894         break;
1895     }
1896     case OBJ_WALL:
1897         if (valueIsBetween(pXSource->data1, -1, 32767))
1898             wall[objIndex].xrepeat = ClipRange(pXSource->data1, 0, 255);
1899 
1900         if (valueIsBetween(pXSource->data2, -1, 32767))
1901             wall[objIndex].yrepeat = ClipRange(pXSource->data2, 0, 255);
1902 
1903         if (valueIsBetween(pXSource->data3, -1, 32767))
1904             wall[objIndex].xpanning = ClipRange(pXSource->data3, 0, 255);
1905 
1906         if (valueIsBetween(pXSource->data4, -1, 65535))
1907             wall[objIndex].ypanning = ClipRange(pXSource->data4, 0, 255);
1908         break;
1909     }
1910 
1911 }
1912 
usePropertiesChanger(XSPRITE * pXSource,short objType,int objIndex)1913 void usePropertiesChanger(XSPRITE* pXSource, short objType, int objIndex) {
1914 
1915     spritetype* pSource = &sprite[pXSource->reference];
1916 
1917     switch (objType) {
1918         case OBJ_WALL: {
1919             walltype* pWall = &wall[objIndex]; int old = -1;
1920 
1921             // data3 = set wall hitag
1922             if (valueIsBetween(pXSource->data3, -1, 32767)) {
1923                 if ((pSource->flags & kModernTypeFlag1)) pWall->hitag = pWall->hitag |= pXSource->data3;
1924                 else pWall->hitag = pXSource->data3;
1925             }
1926 
1927             // data4 = set wall cstat
1928             if (valueIsBetween(pXSource->data4, -1, 65535)) {
1929                 old = pWall->cstat;
1930 
1931                 // set new cstat
1932                 if ((pSource->flags & kModernTypeFlag1)) pWall->cstat = pWall->cstat |= pXSource->data4; // relative
1933                 else pWall->cstat = pXSource->data4; // absolute
1934 
1935                 // and hanlde exceptions
1936                 if ((old & 0x2) && !(pWall->cstat & 0x2)) pWall->cstat |= 0x2; // kWallBottomSwap
1937                 if ((old & 0x4) && !(pWall->cstat & 0x4)) pWall->cstat |= 0x4; // kWallBottomOrg, kWallOutsideOrg
1938                 if ((old & 0x20) && !(pWall->cstat & 0x20)) pWall->cstat |= 0x20; // kWallOneWay
1939 
1940                 if (old & 0xc000) {
1941 
1942                     if (!(pWall->cstat & 0xc000))
1943                         pWall->cstat |= 0xc000; // kWallMoveMask
1944 
1945                     if ((old & 0x0) && !(pWall->cstat & 0x0)) pWall->cstat |= 0x0; // kWallMoveNone
1946                     else if ((old & 0x4000) && !(pWall->cstat & 0x4000)) pWall->cstat |= 0x4000; // kWallMoveForward
1947                     else if ((old & 0x8000) && !(pWall->cstat & 0x8000)) pWall->cstat |= 0x8000; // kWallMoveBackward
1948 
1949                 }
1950             }
1951         }
1952         break;
1953         case OBJ_SPRITE: {
1954             spritetype* pSprite = &sprite[objIndex]; bool thing2debris = false;
1955             XSPRITE* pXSprite = &xsprite[pSprite->extra]; int old = -1;
1956 
1957             // data3 = set sprite hitag
1958             if (valueIsBetween(pXSource->data3, -1, 32767)) {
1959                 old = pSprite->flags;
1960 
1961                 // set new hitag
1962                 if ((pSource->flags & kModernTypeFlag1)) pSprite->flags = pSource->flags |= pXSource->data3; // relative
1963                 else pSprite->flags = pXSource->data3;  // absolute
1964 
1965                 // and handle exceptions
1966                 if ((old & kHitagFree) && !(pSprite->flags & kHitagFree)) pSprite->flags |= kHitagFree;
1967                 if ((old & kHitagRespawn) && !(pSprite->flags & kHitagRespawn)) pSprite->flags |= kHitagRespawn;
1968 
1969                 // prepare things for different (debris) physics.
1970                 if (pSprite->statnum == kStatThing && debrisGetFreeIndex() >= 0) thing2debris = true;
1971 
1972             }
1973 
1974             // data2 = sprite physics settings
1975             if (valueIsBetween(pXSource->data2, -1, 32767) || thing2debris) {
1976                 switch (pSprite->statnum) {
1977                 case kStatDude: // dudes already treating in game
1978                 case kStatFree:
1979                 case kStatMarker:
1980                 case kStatPathMarker:
1981                     break;
1982                 default:
1983                     // store physics attributes in xsprite to avoid setting hitag for modern types!
1984                     int flags = (pXSprite->physAttr != 0) ? pXSprite->physAttr : 0;
1985                     int oldFlags = flags;
1986 
1987                     if (thing2debris) {
1988 
1989                         // converting thing to debris
1990                         if ((pSprite->flags & kPhysMove) != 0) flags |= kPhysMove;
1991                         else flags &= ~kPhysMove;
1992 
1993                         if ((pSprite->flags & kPhysGravity) != 0) flags |= (kPhysGravity | kPhysFalling);
1994                         else flags &= ~(kPhysGravity | kPhysFalling);
1995 
1996                         pSprite->flags &= ~(kPhysMove | kPhysGravity | kPhysFalling);
1997                         xvel[objIndex] = yvel[objIndex] = zvel[objIndex] = 0;
1998                         pXSprite->restState = pXSprite->state;
1999 
2000                     } else {
2001 
2002                         static char digits[6];
2003                         memset(digits, 0, sizeof(digits));
2004                         sprintf(digits, "%d", pXSource->data2);
2005                         for (int i = 0; i < sizeof(digits); i++)
2006                             digits[i] = (digits[i] >= 48 && digits[i] <= 57) ? (digits[i] - 57) + 9 : 0;
2007 
2008                         // first digit of data2: set main physics attributes
2009                         switch (digits[0]) {
2010                             case 0:
2011                                 flags &= ~kPhysMove;
2012                                 flags &= ~(kPhysGravity | kPhysFalling);
2013                                 break;
2014                             case 1:
2015                                 flags |= kPhysMove;
2016                                 flags &= ~(kPhysGravity | kPhysFalling);
2017                                 break;
2018                             case 2:
2019                                 flags &= ~kPhysMove;
2020                                 flags |= (kPhysGravity | kPhysFalling);
2021                                 break;
2022                             case 3:
2023                                 flags |= kPhysMove;
2024                                 flags |= (kPhysGravity | kPhysFalling);
2025                                 break;
2026                         }
2027 
2028                         // second digit of data2: touch physics flags
2029                         switch (digits[1]) {
2030                             case 0:
2031                                 flags &= ~kPhysDebrisTouch;
2032                                 break;
2033                             case 1:
2034                                 flags |= kPhysDebrisTouch;
2035                                 break;
2036                         }
2037 
2038                         // third digit of data2: weapon physics flags
2039                         switch (digits[2]) {
2040                             case 0:
2041                                 flags &= ~kPhysDebrisVector;
2042                                 flags &= ~kPhysDebrisExplode;
2043                                 break;
2044                             case 1:
2045                                 flags |= kPhysDebrisVector;
2046                                 flags &= ~kPhysDebrisExplode;
2047                                 break;
2048                             case 2:
2049                                 flags &= ~kPhysDebrisVector;
2050                                 flags |= kPhysDebrisExplode;
2051                                 break;
2052                             case 3:
2053                                 flags |= kPhysDebrisVector;
2054                                 flags |= kPhysDebrisExplode;
2055                                 break;
2056                         }
2057 
2058                         // fourth digit of data2: swimming / flying physics flags
2059                         switch (digits[3]) {
2060                             case 0:
2061                                 flags &= ~kPhysDebrisSwim;
2062                                 flags &= ~kPhysDebrisFly;
2063                                 flags &= ~kPhysDebrisFloat;
2064                                 break;
2065                             case 1:
2066                                 flags |= kPhysDebrisSwim;
2067                                 flags &= ~kPhysDebrisFly;
2068                                 flags &= ~kPhysDebrisFloat;
2069                                 break;
2070                             case 2:
2071                                 flags |= kPhysDebrisSwim;
2072                                 flags |= kPhysDebrisFloat;
2073                                 flags &= ~kPhysDebrisFly;
2074                                 break;
2075                             case 3:
2076                                 flags |= kPhysDebrisFly;
2077                                 flags &= ~kPhysDebrisSwim;
2078                                 flags &= ~kPhysDebrisFloat;
2079                                 break;
2080                             case 4:
2081                                 flags |= kPhysDebrisFly;
2082                                 flags |= kPhysDebrisFloat;
2083                                 flags &= ~kPhysDebrisSwim;
2084                                 break;
2085                             case 5:
2086                                 flags |= kPhysDebrisSwim;
2087                                 flags |= kPhysDebrisFly;
2088                                 flags &= ~kPhysDebrisFloat;
2089                                 break;
2090                             case 6:
2091                                 flags |= kPhysDebrisSwim;
2092                                 flags |= kPhysDebrisFly;
2093                                 flags |= kPhysDebrisFloat;
2094                                 break;
2095                         }
2096 
2097                     }
2098 
2099                     int nIndex = debrisGetIndex(objIndex); // check if there is no sprite in list
2100 
2101                     // adding physics sprite in list
2102                     if ((flags & kPhysGravity) != 0 || (flags & kPhysMove) != 0) {
2103 
2104                         if (oldFlags == 0)
2105                             xvel[objIndex] = yvel[objIndex] = zvel[objIndex] = 0;
2106 
2107                         if (nIndex != -1) {
2108 
2109                             pXSprite->physAttr = flags; // just update physics attributes
2110 
2111                         } else if ((nIndex = debrisGetFreeIndex()) < 0) {
2112 
2113                             viewSetSystemMessage("Max (%d) Physics affected sprites reached!", kMaxSuperXSprites);
2114 
2115                         } else {
2116 
2117                             pXSprite->physAttr = flags; // update physics attributes
2118 
2119                             // allow things to became debris, so they use different physics...
2120                             if (pSprite->statnum == kStatThing) changespritestat(objIndex, 0);
2121 
2122                             // set random goal ang for swimming so they start turning
2123                             if ((flags & kPhysDebrisSwim) && !xvel[objIndex] && !yvel[objIndex] && !zvel[objIndex])
2124                                 pXSprite->goalAng = (pSprite->ang + Random3(kAng45)) & 2047;
2125 
2126                             if (pXSprite->physAttr & kPhysDebrisVector)
2127                                 pSprite->cstat |= CSTAT_SPRITE_BLOCK_HITSCAN;
2128 
2129                             gPhysSpritesList[nIndex] = objIndex;
2130                             if (nIndex >= gPhysSpritesCount) gPhysSpritesCount++;
2131                             getSpriteMassBySize(pSprite); // create physics cache
2132 
2133                         }
2134 
2135                     // removing physics from sprite in list (don't remove sprite from list)
2136                     } else if (nIndex != -1) {
2137 
2138                         pXSprite->physAttr = flags;
2139                         xvel[objIndex] = yvel[objIndex] = zvel[objIndex] = 0;
2140                         if (pSprite->lotag >= kThingBase && pSprite->lotag < kThingMax)
2141                             changespritestat(objIndex, kStatThing);  // if it was a thing - restore statnum
2142 
2143                     }
2144 
2145                     break;
2146                 }
2147             }
2148 
2149             // data4 = sprite cstat
2150             if (valueIsBetween(pXSource->data4, -1, 65535)) {
2151 
2152                 old = pSprite->cstat;
2153 
2154                 // set new cstat
2155                 if ((pSource->flags & kModernTypeFlag1)) pSprite->cstat |= pXSource->data4; // relative
2156                 else pSprite->cstat = pXSource->data4; // absolute
2157 
2158                 // and handle exceptions
2159                 if ((old & 0x1000) && !(pSprite->cstat & 0x1000)) pSprite->cstat |= 0x1000; //kSpritePushable
2160                 if ((old & 0x80) && !(pSprite->cstat & 0x80)) pSprite->cstat |= 0x80; // kSpriteOriginAlign
2161 
2162                 if (old & 0x6000) {
2163 
2164                     if (!(pSprite->cstat & 0x6000))
2165                         pSprite->cstat |= 0x6000; // kSpriteMoveMask
2166 
2167                     if ((old & 0x0) && !(pSprite->cstat & 0x0)) pSprite->cstat |= 0x0; // kSpriteMoveNone
2168                     else if ((old & 0x2000) && !(pSprite->cstat & 0x2000)) pSprite->cstat |= 0x2000; // kSpriteMoveForward, kSpriteMoveFloor
2169                     else if ((old & 0x4000) && !(pSprite->cstat & 0x4000)) pSprite->cstat |= 0x4000; // kSpriteMoveReverse, kSpriteMoveCeiling
2170 
2171                 }
2172 
2173             }
2174         }
2175         break;
2176         case OBJ_SECTOR: {
2177 
2178             sectortype* pSector = &sector[objIndex];
2179             XSECTOR* pXSector = &xsector[sector[objIndex].extra];
2180 
2181             // data1 = sector underwater status and depth level
2182             if (pXSource->data1 >= 0 && pXSource->data1 < 2) {
2183 
2184                 pXSector->Underwater = (pXSource->data1) ? true : false;
2185 
2186                 spritetype* pLower = (gLowerLink[objIndex] >= 0) ? &sprite[gLowerLink[objIndex]] : NULL;
2187                 XSPRITE* pXLower = NULL; spritetype* pUpper = NULL; XSPRITE* pXUpper = NULL;
2188 
2189                 if (pLower) {
2190 
2191                     pXLower = &xsprite[pLower->extra];
2192 
2193                     // must be sure we found exact same upper link
2194                     for (int i = 0; i < kMaxSectors; i++) {
2195                         if (gUpperLink[i] < 0 || xsprite[sprite[gUpperLink[i]].extra].data1 != pXLower->data1) continue;
2196                         pUpper = &sprite[gUpperLink[i]]; pXUpper = &xsprite[pUpper->extra];
2197                         break;
2198                     }
2199                 }
2200 
2201                 // treat sectors that have links, so warp can detect underwater status properly
2202                 if (pLower) {
2203                     if (pXSector->Underwater) {
2204                         switch (pLower->type) {
2205                             case kMarkerLowStack:
2206                             case kMarkerLowLink:
2207                                 pXLower->sysData1 = pLower->type;
2208                                 pLower->type = kMarkerLowWater;
2209                                 break;
2210                             default:
2211                                 if (pSector->ceilingpicnum < 4080 || pSector->ceilingpicnum > 4095) pXLower->sysData1 = kMarkerLowLink;
2212                                 else pXLower->sysData1 = kMarkerLowStack;
2213                                 break;
2214                         }
2215                     }
2216                     else if (pXLower->sysData1 > 0) pLower->type = pXLower->sysData1;
2217                     else if (pSector->ceilingpicnum < 4080 || pSector->ceilingpicnum > 4095) pLower->type = kMarkerLowLink;
2218                     else pLower->type = kMarkerLowStack;
2219                 }
2220 
2221                 if (pUpper) {
2222                     if (pXSector->Underwater) {
2223                         switch (pUpper->type) {
2224                             case kMarkerUpStack:
2225                             case kMarkerUpLink:
2226                                 pXUpper->sysData1 = pUpper->type;
2227                                 pUpper->type = kMarkerUpWater;
2228                                 break;
2229                             default:
2230                                 if (pSector->floorpicnum < 4080 || pSector->floorpicnum > 4095) pXUpper->sysData1 = kMarkerUpLink;
2231                                 else pXUpper->sysData1 = kMarkerUpStack;
2232                                 break;
2233                         }
2234                     }
2235                     else if (pXUpper->sysData1 > 0) pUpper->type = pXUpper->sysData1;
2236                     else if (pSector->floorpicnum < 4080 || pSector->floorpicnum > 4095) pUpper->type = kMarkerUpLink;
2237                     else pUpper->type = kMarkerUpStack;
2238                 }
2239 
2240                 // search for dudes in this sector and change their underwater status
2241                 for (int nSprite = headspritesect[objIndex]; nSprite >= 0; nSprite = nextspritesect[nSprite]) {
2242 
2243                     spritetype* pSpr = &sprite[nSprite];
2244                     if (pSpr->statnum != kStatDude || !IsDudeSprite(pSpr) || !xspriRangeIsFine(pSpr->extra))
2245                         continue;
2246 
2247                     PLAYER* pPlayer = getPlayerById(pSpr->type);
2248                     if (pXSector->Underwater) {
2249                         if (pLower)
2250                             xsprite[pSpr->extra].medium = (pLower->type == kMarkerUpGoo) ? kMediumGoo : kMediumWater;
2251 
2252                         if (pPlayer) {
2253                             int waterPal = kMediumWater;
2254                             if (pLower) {
2255                                 if (pXLower->data2 > 0) waterPal = pXLower->data2;
2256                                 else if (pLower->type == kMarkerUpGoo) waterPal = kMediumGoo;
2257                             }
2258 
2259                             pPlayer->nWaterPal = waterPal;
2260                             pPlayer->posture = kPostureSwim;
2261                             pPlayer->pXSprite->burnTime = 0;
2262                         }
2263 
2264                     } else {
2265 
2266                         xsprite[pSpr->extra].medium = kMediumNormal;
2267                         if (pPlayer) {
2268                             pPlayer->posture = (!pPlayer->input.buttonFlags.crouch) ? kPostureStand : kPostureCrouch;
2269                             pPlayer->nWaterPal = 0;
2270                         }
2271 
2272                     }
2273                 }
2274             }
2275             else if (pXSource->data1 > 9) pXSector->Depth = 7;
2276             else if (pXSource->data1 > 1) pXSector->Depth = pXSource->data1 - 2;
2277 
2278 
2279             // data2 = sector visibility
2280             if (valueIsBetween(pXSource->data2, -1, 32767))
2281                 sector[objIndex].visibility = ClipRange(pXSource->data2, 0 , 234);
2282 
2283             // data3 = sector ceil cstat
2284             if (valueIsBetween(pXSource->data3, -1, 32767)) {
2285                 if ((pSource->flags & kModernTypeFlag1)) sector[objIndex].ceilingstat |= pXSource->data3;
2286                 else sector[objIndex].ceilingstat = pXSource->data3;
2287             }
2288 
2289             // data4 = sector floor cstat
2290             if (valueIsBetween(pXSource->data4, -1, 65535)) {
2291                 if ((pSource->flags & kModernTypeFlag1)) sector[objIndex].floorstat |= pXSource->data4;
2292                 else sector[objIndex].floorstat = pXSource->data4;
2293             }
2294         }
2295         break;
2296         // no TX id
2297         case -1:
2298             // data2 = global visibility
2299             if (valueIsBetween(pXSource->data2, -1, 32767))
2300                 gVisibility = ClipRange(pXSource->data2, 0, 4096);
2301         break;
2302     }
2303 
2304 }
2305 
useTeleportTarget(XSPRITE * pXSource,spritetype * pSprite)2306 void useTeleportTarget(XSPRITE* pXSource, spritetype* pSprite) {
2307     spritetype* pSource = &sprite[pXSource->reference]; PLAYER* pPlayer = getPlayerById(pSprite->type);
2308     XSECTOR* pXSector = (sector[pSource->sectnum].extra >= 0) ? &xsector[sector[pSource->sectnum].extra] : NULL;
2309     bool isDude = (!pPlayer && IsDudeSprite(pSprite));
2310 
2311     if (pSprite->sectnum != pSource->sectnum)
2312         changespritesect(pSprite->index, pSource->sectnum);
2313 
2314     pSprite->x = pSource->x; pSprite->y = pSource->y; pSprite->z = pSource->z;
2315 
2316     // make sure sprites aren't in the floor or ceiling
2317     int zTop, zBot; GetSpriteExtents(pSprite, &zTop, &zBot);
2318     pSprite->z += ClipLow(sector[pSprite->sectnum].ceilingz - zTop, 0);
2319     pSprite->z += ClipHigh(sector[pSprite->sectnum].floorz - zBot, 0);
2320 
2321     if (pSource->flags & kModernTypeFlag1) // force telefrag
2322         TeleFrag(pSprite->index, pSource->sectnum);
2323 
2324 
2325     if (pSprite->flags & kPhysGravity)
2326         pSprite->flags |= kPhysFalling;
2327 
2328     if (pXSector) {
2329 
2330         if (pXSector->Enter && (pPlayer || (isDude && !pXSector->dudeLockout)))
2331             trTriggerSector(pSource->sectnum, pXSector, kCmdSectorEnter);
2332 
2333         if (pXSector->Underwater) {
2334             spritetype* pLink = (gLowerLink[pSource->sectnum] >= 0) ? &sprite[gLowerLink[pSource->sectnum]] : NULL;
2335             if (pLink) {
2336 
2337                 // must be sure we found exact same upper link
2338                 for (int i = 0; i < kMaxSectors; i++) {
2339                     if (gUpperLink[i] < 0 || xsprite[sprite[gUpperLink[i]].extra].data1 != xsprite[pLink->extra].data1) continue;
2340                     pLink = &sprite[gUpperLink[i]];
2341                     break;
2342                 }
2343 
2344             }
2345 
2346             if (pLink)
2347                 xsprite[pSprite->extra].medium = (pLink->type == kMarkerUpGoo) ? kMediumGoo : kMediumWater;
2348 
2349             if (pPlayer) {
2350                 int waterPal = kMediumWater;
2351                 if (pLink) {
2352                     if (xsprite[pLink->extra].data2 > 0) waterPal = xsprite[pLink->extra].data2;
2353                     else if (pLink->type == kMarkerUpGoo) waterPal = kMediumGoo;
2354                 }
2355 
2356                 pPlayer->nWaterPal = waterPal;
2357                 pPlayer->posture = kPostureSwim;
2358                 pPlayer->pXSprite->burnTime = 0;
2359             }
2360 
2361         } else {
2362 
2363             xsprite[pSprite->extra].medium = kMediumNormal;
2364             if (pPlayer) {
2365                 pPlayer->posture = (!pPlayer->input.buttonFlags.crouch) ? kPostureStand : kPostureCrouch;
2366                 pPlayer->nWaterPal = 0;
2367             }
2368 
2369         }
2370     }
2371 
2372     if (pSprite->statnum == kStatDude && IsDudeSprite(pSprite) && !IsPlayerSprite(pSprite)) {
2373         XSPRITE* pXDude = &xsprite[pSprite->extra];
2374         int x = pXDude->targetX; int y = pXDude->targetY; int z = pXDude->targetZ;
2375         int target = pXDude->target;
2376 
2377         aiInitSprite(pSprite);
2378 
2379         if (target >= 0) {
2380             pXDude->targetX = x; pXDude->targetY = y; pXDude->targetZ = z;
2381             pXDude->target = target; aiActivateDude(pSprite, pXDude);
2382         }
2383     }
2384 
2385     if (pXSource->data2 == 1) {
2386 
2387         if (pPlayer) pPlayer->q16ang = fix16_from_int(pSource->ang);
2388         else if (isDude) xsprite[pSprite->extra].goalAng = pSprite->ang = pSource->ang;
2389         else pSprite->ang = pSource->ang;
2390     }
2391 
2392     if (pXSource->data3 == 1)
2393         xvel[pSprite->index] = yvel[pSprite->index] = zvel[pSprite->index] = 0;
2394 
2395     viewBackupSpriteLoc(pSprite->index, pSprite);
2396 
2397     if (pXSource->data4 > 0)
2398         sfxPlay3DSound(pSource, pXSource->data4, -1, 0);
2399 
2400     if (pPlayer) {
2401         playerResetInertia(pPlayer);
2402         if (pXSource->data2 == 1)
2403             pPlayer->zViewVel = pPlayer->zWeaponVel = 0;
2404     }
2405 
2406 }
2407 
2408 
useEffectGen(XSPRITE * pXSource,spritetype * pSprite)2409 void useEffectGen(XSPRITE* pXSource, spritetype* pSprite) {
2410     if (pSprite == NULL) pSprite = &sprite[pXSource->reference];
2411     int fxId = (pXSource->data3 <= 0) ? pXSource->data2 : pXSource->data2 + Random(pXSource->data3 + 1);
2412     if (xspriRangeIsFine(pSprite->extra) && valueIsBetween(fxId, 0, kFXMax)) {
2413         int pos, top, bottom; GetSpriteExtents(pSprite, &top, &bottom);
2414         spritetype* pSource = &sprite[pXSource->reference];
2415         spritetype* pEffect = NULL;
2416 
2417         // select where exactly effect should be spawned
2418         switch (pXSource->data4) {
2419             case 1:
2420                 pos = bottom;
2421                 break;
2422             case 2: // middle
2423                 pos = pSprite->z + (tilesiz[pSprite->picnum].y / 2 + picanm[pSprite->picnum].yofs);
2424                 break;
2425             case 3:
2426             case 4:
2427                 if (!sectRangeIsFine(pSprite->sectnum)) pos = top;
2428                 else pos = (pXSource->data4 == 3) ? sector[pSprite->sectnum].floorz : sector[pSprite->sectnum].ceilingz;
2429                 break;
2430             default:
2431                 pos = top;
2432                 break;
2433         }
2434 
2435         if ((pEffect = gFX.fxSpawn((FX_ID)fxId, pSprite->sectnum, pSprite->x, pSprite->y, pos, 0)) != NULL) {
2436 
2437             pEffect->owner = pSource->index;
2438 
2439             if (pSource->flags & kModernTypeFlag1) {
2440                 pEffect->pal = pSource->pal;
2441                 pEffect->xoffset = pSource->xoffset;
2442                 pEffect->yoffset = pSource->yoffset;
2443                 pEffect->xrepeat = pSource->xrepeat;
2444                 pEffect->yrepeat = pSource->yrepeat;
2445                 pEffect->shade = pSource->shade;
2446             }
2447 
2448             if (pSource->flags & kModernTypeFlag2) {
2449                 pEffect->cstat = pSource->cstat;
2450                 if (pEffect->cstat & CSTAT_SPRITE_INVISIBLE)
2451                     pEffect->cstat &= ~CSTAT_SPRITE_INVISIBLE;
2452             }
2453 
2454             if (pEffect->cstat & CSTAT_SPRITE_ONE_SIDED)
2455                 pEffect->cstat &= ~CSTAT_SPRITE_ONE_SIDED;
2456 
2457         }
2458     }
2459 }
2460 
2461 
useSectorWindGen(XSPRITE * pXSource,sectortype * pSector)2462 void useSectorWindGen(XSPRITE* pXSource, sectortype* pSector) {
2463 
2464     spritetype* pSource = &sprite[pXSource->reference];
2465     XSECTOR* pXSector = NULL; int nXSector = 0;
2466 
2467     if (pSector != NULL) {
2468         pXSector = &xsector[pSector->extra];
2469         nXSector = sector[pXSector->reference].extra;
2470     } else if (xsectRangeIsFine(sector[pSource->sectnum].extra)) {
2471         pXSector = &xsector[sector[pSource->sectnum].extra];
2472         nXSector = sector[pXSector->reference].extra;
2473     } else {
2474         int nXSector = dbInsertXSector(pSource->sectnum);
2475         pXSector = &xsector[nXSector]; pXSector->windAlways = 1;
2476     }
2477 
2478     if ((pSource->flags & kModernTypeFlag1))
2479         pXSector->panAlways = pXSector->windAlways = 1;
2480 
2481     short windVel = ClipRange(pXSource->data2, 0, 32767);
2482     switch (pXSource->data1) {
2483         default:
2484             pXSector->windVel = windVel;
2485             break;
2486         case 1:
2487         case 3:
2488             pXSector->windVel = nnExtRandom(0, windVel);
2489             break;
2490     }
2491 
2492     int ang = pSource->ang;
2493     if (pXSource->data4 <= 0) {
2494         switch (pXSource->data1) {
2495         case 2:
2496         case 3:
2497             while (pSource->ang == ang)
2498                 pSource->ang = nnExtRandom(-kAng360, kAng360) & 2047;
2499             break;
2500         }
2501     }
2502     else if (pSource->cstat & 0x2000) pSource->ang += pXSource->data4;
2503     else if (pSource->cstat & 0x4000) pSource->ang -= pXSource->data4;
2504     else if (pXSource->sysData1 == 0) {
2505         if ((ang += pXSource->data4) >= kAng180) pXSource->sysData1 = 1;
2506         pSource->ang = ClipHigh(ang, kAng180);
2507     } else {
2508         if ((ang -= pXSource->data4) <= -kAng180) pXSource->sysData1 = 0;
2509         pSource->ang = ClipLow(ang, -kAng180);
2510     }
2511 
2512     pXSector->windAng = pSource->ang;
2513 
2514     if (pXSource->data3 > 0 && pXSource->data3 < 4) {
2515         switch (pXSource->data3) {
2516         case 1:
2517             pXSector->panFloor = true;
2518             pXSector->panCeiling = false;
2519             break;
2520         case 2:
2521             pXSector->panFloor = false;
2522             pXSector->panCeiling = true;
2523             break;
2524         case 3:
2525             pXSector->panFloor = true;
2526             pXSector->panCeiling = true;
2527             break;
2528         }
2529 
2530         short oldPan = pXSector->panVel;
2531         pXSector->panAngle = pXSector->windAng;
2532         pXSector->panVel = pXSector->windVel;
2533 
2534         // add to panList if panVel was set to 0 previously
2535         if (oldPan == 0 && pXSector->panVel != 0 && panCount < kMaxXSectors) {
2536 
2537             int i;
2538             for (i = 0; i < panCount; i++) {
2539                 if (panList[i] != nXSector) continue;
2540                 break;
2541             }
2542 
2543             if (i == panCount)
2544                 panList[panCount++] = nXSector;
2545         }
2546 
2547     }
2548 }
2549 
useSpriteDamager(XSPRITE * pXSource,int objType,int objIndex)2550 void useSpriteDamager(XSPRITE* pXSource, int objType, int objIndex) {
2551 
2552     spritetype* pSource = &sprite[pXSource->reference];
2553     sectortype* pSector = &sector[pSource->sectnum];
2554 
2555     int top, bottom, i;
2556     bool floor, ceil, wall, enter;
2557 
2558     switch (objType) {
2559         case OBJ_SPRITE:
2560             damageSprites(pXSource, &sprite[objIndex]);
2561             break;
2562         case OBJ_SECTOR:
2563             GetSpriteExtents(pSource, &top, &bottom);
2564             floor = (bottom >= pSector->floorz);    ceil = (top <= pSector->ceilingz);
2565             wall = (pSource->cstat & 0x10);         enter = (!floor && !ceil && !wall);
2566             for (i = headspritesect[objIndex]; i != -1; i = nextspritesect[i]) {
2567                 if (!IsDudeSprite(&sprite[i]) || !xspriRangeIsFine(sprite[i].extra))
2568                     continue;
2569                 else if (enter)
2570                     damageSprites(pXSource, &sprite[i]);
2571                 else if (floor && (gSpriteHit[sprite[i].extra].florhit & 0xc000) == 0x4000 && (gSpriteHit[sprite[i].extra].florhit & 0x3fff) == objIndex)
2572                     damageSprites(pXSource, &sprite[i]);
2573                 else if (ceil && (gSpriteHit[sprite[i].extra].ceilhit & 0xc000) == 0x4000 && (gSpriteHit[sprite[i].extra].ceilhit & 0x3fff) == objIndex)
2574                     damageSprites(pXSource, &sprite[i]);
2575                 else if (wall && (gSpriteHit[sprite[i].extra].hit & 0xc000) == 0x8000 && sectorofwall(gSpriteHit[sprite[i].extra].hit & 0x3fff) == objIndex)
2576                     damageSprites(pXSource, &sprite[i]);
2577             }
2578             break;
2579         case -1:
2580             for (i = headspritestat[kStatDude]; i != -1; i = nextspritestat[i]) {
2581                 if (sprite[i].statnum != kStatDude) continue;
2582                 switch (pXSource->data1) {
2583                     case 667:
2584                         if (IsPlayerSprite(&sprite[i])) continue;
2585                         damageSprites(pXSource, &sprite[i]);
2586                         break;
2587                     case 668:
2588                         if (!IsPlayerSprite(&sprite[i])) continue;
2589                         damageSprites(pXSource, &sprite[i]);
2590                         break;
2591                     default:
2592                         damageSprites(pXSource, &sprite[i]);
2593                         break;
2594                 }
2595             }
2596             break;
2597     }
2598 }
2599 
damageSprites(XSPRITE * pXSource,spritetype * pSprite)2600 void damageSprites(XSPRITE* pXSource, spritetype* pSprite) {
2601     spritetype* pSource = &sprite[pXSource->reference];
2602     if (!IsDudeSprite(pSprite) || !xspriRangeIsFine(pSprite->extra) || xsprite[pSprite->extra].health <= 0 || pXSource->data3 < 0)
2603         return;
2604 
2605 
2606     XSPRITE* pXSprite = &xsprite[pSprite->extra]; PLAYER* pPlayer = getPlayerById(pSprite->type);
2607     int dmgType = (pXSource->data2 >= kDmgFall) ? ClipHigh(pXSource->data2, kDmgElectric) : -1;
2608     int dmg = pXSprite->health << 4; int armor[3];
2609 
2610     bool godMode = (pPlayer && ((dmgType >= 0 && pPlayer->damageControl[dmgType]) || powerupCheck(pPlayer, kPwUpDeathMask) || pPlayer->godMode
2611                             /*|| seqGetID(3, pSprite->extra) == getDudeInfo(pSprite->type)->seqStartID + 16*/)); // kneeling
2612 
2613     if (godMode || pXSprite->locked) return;
2614     else if (pXSource->data3) {
2615         if (pSource->flags & kModernTypeFlag1) dmg = ClipHigh(pXSource->data3 << 1, 65535);
2616         else if (pXSprite->sysData2 > 0) dmg = (ClipHigh(pXSprite->sysData2 << 4, 65535) * pXSource->data3) / kPercFull;
2617         else dmg = ((getDudeInfo(pSprite->type)->startHealth << 4) * pXSource->data3) / kPercFull;
2618     }
2619 
2620     if (dmgType >= kDmgFall) {
2621         if (dmg < pXSprite->health << 4) {
2622             if (nnExtIsImmune(pSprite, dmgType, 0)) {
2623                 consoleSysMsg("Dude type %d is immune to damage type %d!", pSprite->type, dmgType);
2624                 return;
2625             }
2626 
2627             if (pPlayer) {
2628 
2629                 playerDamageArmor(pPlayer, (DAMAGE_TYPE)dmgType, dmg);
2630                 for (int i = 0; i < 3; armor[i] = pPlayer->armor[i], pPlayer->armor[i] = 0, i++);
2631                 actDamageSprite(pSource->index, pSprite, (DAMAGE_TYPE)dmgType, dmg);
2632                 for (int i = 0; i < 3; pPlayer->armor[i] = armor[i], i++);
2633 
2634             } else {
2635 
2636                 actDamageSprite(pSource->index, pSprite, (DAMAGE_TYPE)dmgType, dmg);
2637 
2638             }
2639 
2640         }
2641         else if (!pPlayer) actKillDude(pSource->index, pSprite, (DAMAGE_TYPE)dmgType, dmg);
2642         else playerDamageSprite(pSource->index, pPlayer, (DAMAGE_TYPE)dmgType, dmg);
2643     }
2644     else if ((pXSprite->health = ClipLow(pXSprite->health - dmg, 1)) > 16) return;
2645     else if (!pPlayer) actKillDude(pSource->index, pSprite, kDamageBullet, dmg);
2646     else playerDamageSprite(pSource->index, pPlayer, kDamageBullet, dmg);
2647     return;
2648 }
2649 
useSeqSpawnerGen(XSPRITE * pXSource,int objType,int index)2650 void useSeqSpawnerGen(XSPRITE* pXSource, int objType, int index) {
2651 
2652     if (pXSource->data2 > 0 && !gSysRes.Lookup(pXSource->data2, "SEQ")) {
2653         consoleSysMsg("Missing sequence #%d", pXSource->data2);
2654         return;
2655     }
2656 
2657     spritetype* pSource = &sprite[pXSource->reference];
2658     switch (objType) {
2659         case OBJ_SECTOR:
2660             if (pXSource->data2 <= 0) {
2661                 if (pXSource->data3 == 3 || pXSource->data3 == 1)
2662                     seqKill(2, sector[index].extra);
2663                 if (pXSource->data3 == 3 || pXSource->data3 == 2)
2664                     seqKill(1, sector[index].extra);
2665             } else {
2666                 if (pXSource->data3 == 3 || pXSource->data3 == 1)
2667                     seqSpawn(pXSource->data2, 2, sector[index].extra, -1);
2668                 if (pXSource->data3 == 3 || pXSource->data3 == 2)
2669                     seqSpawn(pXSource->data2, 1, sector[index].extra, -1);
2670             }
2671             return;
2672         case OBJ_WALL:
2673             if (pXSource->data2 <= 0) {
2674                 if (pXSource->data3 == 3 || pXSource->data3 == 1)
2675                     seqKill(0, wall[index].extra);
2676                 if ((pXSource->data3 == 3 || pXSource->data3 == 2) && (wall[index].cstat & CSTAT_WALL_MASKED))
2677                     seqKill(4, wall[index].extra);
2678             } else {
2679 
2680                 if (pXSource->data3 == 3 || pXSource->data3 == 1)
2681                     seqSpawn(pXSource->data2, 0, wall[index].extra, -1);
2682                 if (pXSource->data3 == 3 || pXSource->data3 == 2) {
2683 
2684                     if (wall[index].nextwall < 0) {
2685                         if (pXSource->data3 == 3)
2686                             seqSpawn(pXSource->data2, 0, wall[index].extra, -1);
2687 
2688                     } else {
2689                         if (!(wall[index].cstat & CSTAT_WALL_MASKED))
2690                             wall[index].cstat |= CSTAT_WALL_MASKED;
2691 
2692                         seqSpawn(pXSource->data2, 4, wall[index].extra, -1);
2693                     }
2694                 }
2695 
2696                 if (pXSource->data4 > 0) {
2697 
2698                     int cx, cy, cz;
2699                     cx = (wall[index].x + wall[wall[index].point2].x) >> 1;
2700                     cy = (wall[index].y + wall[wall[index].point2].y) >> 1;
2701                     int nSector = sectorofwall(index);
2702                     int32_t ceilZ, floorZ;
2703                     getzsofslope(nSector, cx, cy, &ceilZ, &floorZ);
2704                     int32_t ceilZ2, floorZ2;
2705                     getzsofslope(wall[index].nextsector, cx, cy, &ceilZ2, &floorZ2);
2706                     ceilZ = ClipLow(ceilZ, ceilZ2);
2707                     floorZ = ClipHigh(floorZ, floorZ2);
2708                     cz = (ceilZ + floorZ) >> 1;
2709 
2710                     sfxPlay3DSound(cx, cy, cz, pXSource->data4, nSector);
2711 
2712                 }
2713 
2714             }
2715             return;
2716         case OBJ_SPRITE:
2717             if (pXSource->data2 <= 0) seqKill(3, sprite[index].extra);
2718             else if (sectRangeIsFine(sprite[index].sectnum)) {
2719                     if (pXSource->data3 > 0) {
2720                         int nSprite = InsertSprite(sprite[index].sectnum, kStatDecoration);
2721                         int top, bottom; GetSpriteExtents(&sprite[index], &top, &bottom);
2722                         sprite[nSprite].x = sprite[index].x;
2723                         sprite[nSprite].y = sprite[index].y;
2724                         switch (pXSource->data3) {
2725                             default:
2726                                 sprite[nSprite].z = sprite[index].z;
2727                                 break;
2728                             case 2:
2729                                 sprite[nSprite].z = bottom;
2730                                 break;
2731                             case 3:
2732                                 sprite[nSprite].z = top;
2733                                 break;
2734                             case 4:
2735                                 sprite[nSprite].z = sprite[index].z + (tilesiz[sprite[index].picnum].y / 2 + picanm[sprite[index].picnum].yofs);
2736                                 break;
2737                             case 5:
2738                             case 6:
2739                                 if (!sectRangeIsFine(sprite[index].sectnum)) sprite[nSprite].z = top;
2740                                 else sprite[nSprite].z = (pXSource->data3 == 5) ? sector[sprite[nSprite].sectnum].floorz : sector[sprite[nSprite].sectnum].ceilingz;
2741                                 break;
2742                         }
2743 
2744                         if (nSprite >= 0) {
2745 
2746                             int nXSprite = dbInsertXSprite(nSprite);
2747                             seqSpawn(pXSource->data2, 3, nXSprite, -1);
2748                             if (pSource->flags & kModernTypeFlag1) {
2749 
2750                                 sprite[nSprite].pal = pSource->pal;
2751                                 sprite[nSprite].shade = pSource->shade;
2752                                 sprite[nSprite].xrepeat = pSource->xrepeat;
2753                                 sprite[nSprite].yrepeat = pSource->yrepeat;
2754                                 sprite[nSprite].xoffset = pSource->xoffset;
2755                                 sprite[nSprite].yoffset = pSource->yoffset;
2756 
2757                             }
2758 
2759                             if (pSource->flags & kModernTypeFlag2) {
2760 
2761                                 sprite[nSprite].cstat |= pSource->cstat;
2762 
2763                             }
2764 
2765                             // should be: the more is seqs, the shorter is timer
2766                             evPost(nSprite, OBJ_SPRITE, 1000, kCallbackRemove);
2767                         }
2768                     } else {
2769 
2770                         seqSpawn(pXSource->data2, 3, sprite[index].extra, -1);
2771 
2772                     }
2773                     if (pXSource->data4 > 0)
2774                         sfxPlay3DSound(&sprite[index], pXSource->data4, -1, 0);
2775             }
2776             return;
2777     }
2778 }
2779 
condSerialize(int objType,int objIndex)2780 int condSerialize(int objType, int objIndex) {
2781     switch (objType) {
2782         case OBJ_SECTOR: return kCondSerialSector + objIndex;
2783         case OBJ_WALL:   return kCondSerialWall + objIndex;
2784         case OBJ_SPRITE: return kCondSerialSprite + objIndex;
2785     }
2786     ThrowError("Unknown object type %d, index %d", objType, objIndex)
2787     return -1;
2788 }
2789 
condUnserialize(int serial,int * objType,int * objIndex)2790 void condUnserialize(int serial, int* objType, int* objIndex) {
2791     if (serial >= kCondSerialSector && serial < kCondSerialWall) {
2792 
2793         *objIndex = serial - kCondSerialSector;
2794         *objType = OBJ_SECTOR;
2795 
2796     } else if (serial >= kCondSerialWall && serial < kCondSerialSprite) {
2797 
2798         *objIndex = serial - kCondSerialWall;
2799         *objType = OBJ_WALL;
2800 
2801     } else if (serial >= kCondSerialSprite && serial < kCondSerialMax) {
2802 
2803         *objIndex = serial - kCondSerialSprite;
2804         *objType = OBJ_SPRITE;
2805 
2806     } else {
2807 
2808         ThrowError("%d is not condition serial!");
2809 
2810     }
2811 }
2812 
condPush(XSPRITE * pXSprite,int objType,int objIndex)2813 bool condPush(XSPRITE* pXSprite, int objType, int objIndex) {
2814     pXSprite->targetX = condSerialize(objType, objIndex);
2815     return true;
2816 }
2817 
condRestore(XSPRITE * pXSprite)2818 bool condRestore(XSPRITE* pXSprite) {
2819     pXSprite->targetX = pXSprite->targetY;
2820     return true;
2821 }
2822 
2823 // normal comparison
condCmp(int val,int arg1,int arg2,int comOp)2824 bool condCmp(int val, int arg1, int arg2, int comOp) {
2825     if (comOp & 0x2000) return (comOp & CSTAT_SPRITE_BLOCK) ? (val > arg1) : (val >= arg1); // blue sprite
2826     else if (comOp & 0x4000) return (comOp & CSTAT_SPRITE_BLOCK) ? (val < arg1) : (val <= arg1); // green sprite
2827     else if (comOp & CSTAT_SPRITE_BLOCK) {
2828         if (arg1 > arg2) ThrowError("Value of argument #1 (%d) must be less than value of argument #2 (%d)", arg1, arg2);
2829         return (val >= arg1 && val <= arg2);
2830     }
2831     else return (val == arg1);
2832 }
2833 
2834 // no extra comparison (val always = 0)?
condCmpne(int arg1,int arg2,int comOp)2835 bool condCmpne(int arg1, int arg2, int comOp) {
2836 
2837     if (comOp & 0x2000) return (comOp & CSTAT_SPRITE_BLOCK) ? (0 > arg1) : (0 >= arg1); // blue sprite
2838     else if (comOp & 0x4000) return (comOp & CSTAT_SPRITE_BLOCK) ? (0 < arg1) : (0 <= arg1); // green sprite
2839     else if (comOp & CSTAT_SPRITE_BLOCK) {
2840         if (arg1 > arg2) ThrowError("Value of argument #1 (%d) must be less than value of argument #2 (%d)", arg1, arg2);
2841         return (0 >= arg1 && 0 <= arg2);
2842     }
2843     else return (0 == arg1);
2844 
2845 }
2846 
2847 // bool comparison
condCmpb(int val,int arg1,int arg2,int comOp)2848 bool condCmpb(int val, int arg1, int arg2, int comOp) {
2849 
2850     arg1 = ClipRange(arg1, 0, 1); arg2 = ClipRange(arg2, 0, 1);
2851     if (comOp & 0x2000) return (comOp & CSTAT_SPRITE_BLOCK) ? (val > 0) : (val >= 0); // blue sprite
2852     else if (comOp & 0x4000) return (comOp & CSTAT_SPRITE_BLOCK) ? (val < arg1) : (val <= arg1); // green sprite
2853     else if (comOp & CSTAT_SPRITE_BLOCK) {
2854         if (arg1 > arg2) ThrowError("Value of argument #1 (%d) must be less than value of argument #2 (%d)", arg1, arg2);
2855         return (val >= arg1 && val <= arg2);
2856     }
2857     else return (val == arg1);
2858 
2859 }
2860 
condError(XSPRITE * pXCond,const char * pzFormat,...)2861 void condError(XSPRITE* pXCond, const char* pzFormat, ...) {
2862 
2863     char buffer[256]; char buffer2[512];
2864     Bsprintf(buffer, "\nCONDITION RX: %d, TX: %d, SPRITE: #%d RETURNS:\n----------\n\n", pXCond->rxID, pXCond->txID, pXCond->reference);
2865     va_list args;
2866     va_start(args, pzFormat);
2867     vsprintf(buffer2, pzFormat, args);
2868     ThrowError(Bstrcat(buffer, buffer2));
2869 }
2870 
2871 
condCheckMixed(XSPRITE * pXCond,EVENT event,int cmpOp,bool PUSH)2872 bool condCheckMixed(XSPRITE* pXCond, EVENT event, int cmpOp, bool PUSH) {
2873 
2874     UNREFERENCED_PARAMETER(PUSH);
2875 
2876     //int var = -1;
2877     int cond = pXCond->data1 - kCondMixedBase; int arg1 = pXCond->data2;
2878     int arg2 = pXCond->data3; int arg3 = pXCond->data4;
2879 
2880     int objType = -1; int objIndex = -1;
2881     condUnserialize(pXCond->targetX, &objType, &objIndex);
2882 
2883     switch (cond) {
2884         case 0:  return (objType == OBJ_SECTOR && sectRangeIsFine(objIndex)); // is a sector?
2885         case 5:  return (objType == OBJ_WALL && wallRangeIsFine(objIndex));   // is a wall?
2886         case 10: return (objType == OBJ_SPRITE && spriRangeIsFine(objIndex)); // is a sprite?
2887         case 15: // x-index is fine?
2888             switch (objType) {
2889                 case OBJ_WALL: return xwallRangeIsFine(wall[objIndex].extra);
2890                 case OBJ_SPRITE: return xspriRangeIsFine(sprite[objIndex].extra);
2891                 case OBJ_SECTOR: return xsectRangeIsFine(sector[objIndex].extra);
2892             }
2893             break;
2894         case 20: // type in a range?
2895             switch (objType) {
2896                 case OBJ_WALL:
2897                     return condCmp(wall[objIndex].type, arg1, arg2, cmpOp);
2898                 case OBJ_SPRITE:
2899                     return condCmp((sprite[objIndex].type != kThingBloodChunks) ? sprite[objIndex].type : sprite[objIndex].inittype, arg1, arg2, cmpOp);
2900                 case OBJ_SECTOR:
2901                     return condCmp(sector[objIndex].type, arg1, arg2, cmpOp);
2902             }
2903             break;
2904         case 24:
2905         case 25: case 26: case 27:
2906         case 28: case 29: case 30:
2907         case 31: case 32: case 33:
2908             switch (objType) {
2909                 case OBJ_WALL: {
2910                     walltype* pObj = &wall[objIndex];
2911                     switch (cond) {
2912                         //case 23: // compare picnum size in %?
2913 
2914                             //return condCmp(tilesiz[pObj->picnum].x, arg1, arg2, cmpOp);
2915                         case 24: return condCmp(surfType[wall[objIndex].picnum], arg1, arg2, cmpOp);
2916                         case 25: return condCmp(pObj->picnum, arg1, arg2, cmpOp);
2917                         case 26: return condCmp(pObj->pal, arg1, arg2, cmpOp);
2918                         case 27: return condCmp(pObj->shade, arg1, arg2, cmpOp);
2919                         case 28: return (pObj->cstat & arg1);
2920                         case 29: return (pObj->hitag & arg1);
2921                         case 30: return condCmp(pObj->xrepeat, arg1, arg2, cmpOp);
2922                         case 31: return condCmp(pObj->xpanning, arg1, arg2, cmpOp);
2923                         case 32: return condCmp(pObj->yrepeat, arg1, arg2, cmpOp);
2924                         case 33: return condCmp(pObj->ypanning, arg1, arg2, cmpOp);
2925                     }
2926                     break;
2927                 }
2928                 case OBJ_SPRITE: {
2929                     spritetype* pObj = &sprite[objIndex];
2930                     switch (cond) {
2931                         case 24: return condCmp(surfType[sprite[objIndex].picnum], arg1, arg2, cmpOp);
2932                         case 25: return condCmp(pObj->picnum, arg1, arg2, cmpOp);
2933                         case 26: return condCmp(pObj->pal, arg1, arg2, cmpOp);
2934                         case 27: return condCmp(pObj->shade, arg1, arg2, cmpOp);
2935                         case 28: return (pObj->cstat & arg1);
2936                         case 29: return (pObj->hitag & arg1);
2937                         case 30: return condCmp(pObj->xrepeat, arg1, arg2, cmpOp);
2938                         case 31: return condCmp(pObj->xoffset, arg1, arg2, cmpOp);
2939                         case 32: return condCmp(pObj->yrepeat, arg1, arg2, cmpOp);
2940                         case 33: return condCmp(pObj->yoffset, arg1, arg2, cmpOp);
2941                     }
2942                     break;
2943                 }
2944                 case OBJ_SECTOR: {
2945                     sectortype* pObj = &sector[objIndex];
2946                     switch (cond) {
2947                         case 24:
2948                             switch (arg3) {
2949                                 default: return (condCmp(surfType[sector[objIndex].floorpicnum], arg1, arg2, cmpOp) || condCmp(surfType[sector[objIndex].ceilingpicnum], arg1, arg2, cmpOp));
2950                                 case 1: return condCmp(surfType[sector[objIndex].floorpicnum], arg1, arg2, cmpOp);
2951                                 case 2: return condCmp(surfType[sector[objIndex].ceilingpicnum], arg1, arg2, cmpOp);
2952                             }
2953                             break;
2954                         case 25:
2955                             switch (arg3) {
2956                                 default: return (condCmp(pObj->floorpicnum, arg1, arg2, cmpOp) || condCmp(pObj->ceilingpicnum, arg1, arg2, cmpOp));
2957                                 case 1:  return condCmp(pObj->floorpicnum, arg1, arg2, cmpOp);
2958                                 case 2:  return condCmp(pObj->ceilingpicnum, arg1, arg2, cmpOp);
2959                             }
2960                             break;
2961                         case 26:
2962                             switch (arg3) {
2963                                 default: return (condCmp(pObj->floorpal, arg1, arg2, cmpOp) || condCmp(pObj->ceilingpal, arg1, arg2, cmpOp));
2964                                 case 1:  return condCmp(pObj->floorpal, arg1, arg2, cmpOp);
2965                                 case 2:  return condCmp(pObj->ceilingpal, arg1, arg2, cmpOp);
2966                             }
2967                             break;
2968                         case 27:
2969                             switch (arg3) {
2970                                 default: return (condCmp(pObj->floorshade, arg1, arg2, cmpOp) || condCmp(pObj->ceilingshade, arg1, arg2, cmpOp));
2971                                 case 1:  return condCmp(pObj->floorshade, arg1, arg2, cmpOp);
2972                                 case 2:  return condCmp(pObj->ceilingshade, arg1, arg2, cmpOp);
2973                             }
2974                             break;
2975                         case 28:
2976                             switch (arg3) {
2977                                 default: return ((pObj->floorstat & arg1) || (pObj->ceilingshade & arg1));
2978                                 case 1:  return (pObj->floorstat & arg1);
2979                                 case 2:  return (pObj->ceilingshade & arg1);
2980                             }
2981                             break;
2982                         case 29: return (pObj->hitag & arg1);
2983                         case 30: return condCmp(pObj->floorxpanning, arg1, arg2, cmpOp);
2984                         case 31: return condCmp(pObj->ceilingxpanning, arg1, arg2, cmpOp);
2985                         case 32: return condCmp(pObj->floorypanning, arg1, arg2, cmpOp);
2986                         case 33: return condCmp(pObj->ceilingypanning, arg1, arg2, cmpOp);
2987                     }
2988                     break;
2989                 }
2990             }
2991             break;
2992         case 41:  case 42:  case 43:
2993         case 44:  case 50:  case 51:
2994         case 52:  case 53:  case 54:
2995         case 55:  case 56:  case 57:
2996         case 58:  case 59:  case 70:
2997         case 71:
2998             switch (objType) {
2999                 case OBJ_WALL: {
3000                     XWALL* pXObj = (xwallRangeIsFine(wall[objIndex].extra)) ? &xwall[wall[objIndex].extra] : NULL;
3001                     if (!pXObj) return condCmpne(arg1, arg2, cmpOp);
3002                     switch (cond) {
3003                         case 41: return condCmp(pXObj->data, arg1, arg2, cmpOp);
3004                         case 50: return condCmp(pXObj->rxID, arg1, arg2, cmpOp);
3005                         case 51: return condCmp(pXObj->txID, arg1, arg2, cmpOp);
3006                         case 52: return condCmpb(pXObj->locked, arg1, arg2, cmpOp);
3007                         case 53: return condCmpb(pXObj->triggerOn, arg1, arg2, cmpOp);
3008                         case 54: return condCmpb(pXObj->triggerOff, arg1, arg2, cmpOp);
3009                         case 55: return condCmpb(pXObj->triggerOnce, arg1, arg2, cmpOp);
3010                         case 56: return condCmpb(pXObj->isTriggered, arg1, arg2, cmpOp);
3011                         case 57: return condCmpb(pXObj->state, arg1, arg2, cmpOp);
3012                         case 58: return condCmp((kPercFull * pXObj->busy) / 65536, arg1, arg2, cmpOp);
3013                         case 59: return condCmpb(pXObj->dudeLockout, arg1, arg2, cmpOp);
3014                         case 70:
3015                             switch (arg3) {
3016                                 default: return (condCmp(seqGetID(0, wall[objIndex].extra), arg1, arg2, cmpOp) || condCmp(seqGetID(4, wall[objIndex].extra), arg1, arg2, cmpOp));
3017                                 case 1:  return condCmp(seqGetID(0, wall[objIndex].extra), arg1, arg2, cmpOp);
3018                                 case 2:  return condCmp(seqGetID(4, wall[objIndex].extra), arg1, arg2, cmpOp);
3019                             }
3020                             break;
3021                         case 71:
3022                             switch (arg3) {
3023                                 default: return (condCmp(seqGetStatus(0, wall[objIndex].extra), arg1, arg2, cmpOp) || condCmp(seqGetStatus(4, wall[objIndex].extra), arg1, arg2, cmpOp));
3024                                 case 1:  return condCmp(seqGetStatus(0, wall[objIndex].extra), arg1, arg2, cmpOp);
3025                                 case 2:  return condCmp(seqGetStatus(4, wall[objIndex].extra), arg1, arg2, cmpOp);
3026                             }
3027                             break;
3028                     }
3029                     break;
3030                 }
3031                 case OBJ_SPRITE: {
3032                     XSPRITE* pXObj = (xspriRangeIsFine(sprite[objIndex].extra)) ? &xsprite[sprite[objIndex].extra] : NULL;
3033                     if (!pXObj) return condCmpne(arg1, arg2, cmpOp);
3034                     switch (cond) {
3035                         case 41: case 42:
3036                         case 43: case 44:
3037                             return condCmp(getDataFieldOfObject(OBJ_SPRITE, objIndex, 1 + cond - 41), arg1, arg2, cmpOp);
3038                         case 50: return condCmp(pXObj->rxID, arg1, arg2, cmpOp);
3039                         case 51: return condCmp(pXObj->txID, arg1, arg2, cmpOp);
3040                         case 52: return condCmpb(pXObj->locked, arg1, arg2, cmpOp);
3041                         case 53: return condCmpb(pXObj->triggerOn, arg1, arg2, cmpOp);
3042                         case 54: return condCmpb(pXObj->triggerOff, arg1, arg2, cmpOp);
3043                         case 55: return condCmpb(pXObj->triggerOnce, arg1, arg2, cmpOp);
3044                         case 56: return condCmpb(pXObj->isTriggered, arg1, arg2, cmpOp);
3045                         case 57: return condCmpb(pXObj->state, arg1, arg2, cmpOp);
3046                         case 58: return condCmp((kPercFull * pXObj->busy) / 65536, arg1, arg2, cmpOp);
3047                         case 59: return condCmpb(pXObj->DudeLockout, arg1, arg2, cmpOp);
3048                         case 70: return condCmp(seqGetID(3, sprite[objIndex].extra), arg1, arg2, cmpOp);
3049                         case 71: return condCmp(seqGetStatus(3, sprite[objIndex].extra), arg1, arg2, cmpOp);
3050                     }
3051                     break;
3052                 }
3053                 case OBJ_SECTOR: {
3054                     XSECTOR* pXObj = (xsectRangeIsFine(sector[objIndex].extra)) ? &xsector[sector[objIndex].extra] : NULL;
3055                     if (!pXObj) return condCmpne(arg1, arg2, cmpOp);
3056                     switch (cond) {
3057                         case 41: return condCmp(pXObj->data, arg1, arg2, cmpOp);
3058                         case 50: return condCmp(pXObj->rxID, arg1, arg2, cmpOp);
3059                         case 51: return condCmp(pXObj->txID, arg1, arg2, cmpOp);
3060                         case 52: return condCmpb(pXObj->locked, arg1, arg2, cmpOp);
3061                         case 53: return condCmpb(pXObj->triggerOn, arg1, arg2, cmpOp);
3062                         case 54: return condCmpb(pXObj->triggerOff, arg1, arg2, cmpOp);
3063                         case 55: return condCmpb(pXObj->triggerOnce, arg1, arg2, cmpOp);
3064                         case 56: return condCmpb(pXObj->isTriggered, arg1, arg2, cmpOp);
3065                         case 57: return condCmpb(pXObj->state, arg1, arg2, cmpOp);
3066                         case 58: return condCmp((kPercFull * pXObj->busy) / 65536, arg1, arg2, cmpOp);
3067                         case 59: return condCmpb(pXObj->dudeLockout, arg1, arg2, cmpOp);
3068                         case 70:
3069                             switch (arg3) {
3070                                 default: return (condCmp(seqGetID(1, wall[objIndex].extra), arg1, arg2, cmpOp) || condCmp(seqGetID(2, wall[objIndex].extra), arg1, arg2, cmpOp));
3071                                 case 1:  return condCmp(seqGetID(1, wall[objIndex].extra), arg1, arg2, cmpOp);
3072                                 case 2:  return condCmp(seqGetID(2, wall[objIndex].extra), arg1, arg2, cmpOp);
3073                             }
3074                             break;
3075                         case 71:
3076                             switch (arg3) {
3077                                 default: return (condCmp(seqGetStatus(1, wall[objIndex].extra), arg1, arg2, cmpOp) || condCmp(seqGetStatus(2, wall[objIndex].extra), arg1, arg2, cmpOp));
3078                                 case 1:  return condCmp(seqGetStatus(1, wall[objIndex].extra), arg1, arg2, cmpOp);
3079                                 case 2:  return condCmp(seqGetStatus(2, wall[objIndex].extra), arg1, arg2, cmpOp);
3080                             }
3081                             break;
3082                     }
3083                     break;
3084                 }
3085             }
3086             break;
3087         case 99: return condCmp(event.cmd, arg1, arg2, cmpOp);  // this codition received specified command?
3088     }
3089 
3090     condError(pXCond, "Mixed: Unexpected condition id (%d)!", cond);
3091     return false;
3092 }
3093 
condCheckSector(XSPRITE * pXCond,int cmpOp,bool PUSH)3094 bool condCheckSector(XSPRITE* pXCond, int cmpOp, bool PUSH) {
3095 
3096     int var = -1;
3097     int cond = pXCond->data1 - kCondSectorBase; int arg1 = pXCond->data2;
3098     int arg2 = pXCond->data3; //int arg3 = pXCond->data4;
3099 
3100     int objType = -1; int objIndex = -1;
3101     condUnserialize(pXCond->targetX, &objType, &objIndex);
3102 
3103     if (objType != OBJ_SECTOR || !sectRangeIsFine(objIndex))
3104         condError(pXCond, "Sector conditions:\nObject #%d (objType: %d) is not a sector!", objIndex, objType);
3105 
3106     sectortype* pSect = &sector[objIndex];
3107     XSECTOR* pXSect = (xsectRangeIsFine(pSect->extra)) ? &xsector[pSect->extra] : NULL;
3108 
3109     if (cond < (kCondRange >> 1)) {
3110         switch (cond) {
3111         default: break;
3112         case 0: return condCmp(pSect->visibility, arg1, arg2, cmpOp);
3113         case 5: return condCmp(pSect->floorheinum, arg1, arg2, cmpOp);
3114         case 6: return condCmp(pSect->ceilingheinum, arg1, arg2, cmpOp);
3115         case 10: // required sprite type is in current sector?
3116             for (var = headspritesect[objIndex]; var >= 0; var = nextspritesect[var]) {
3117                 if (!condCmp(sprite[var].type, arg1, arg2, cmpOp)) continue;
3118                 else if (PUSH) condPush(pXCond, OBJ_SPRITE, var);
3119                 return true;
3120             }
3121             return false;
3122         }
3123     } else if (pXSect) {
3124         switch (cond) {
3125             default: break;
3126             case 50: return pXSect->Underwater;
3127             case 51: return condCmp(pXSect->Depth, arg1, arg2, cmpOp);
3128             case 55: // compare floor height (in %)
3129             case 56: { // compare ceil height (in %)
3130                 int h = 0; int curH = 0;
3131                 switch (pSect->type) {
3132                 case kSectorZMotion:
3133                 case kSectorRotate:
3134                 case kSectorSlide:
3135                     if (cond == 60) {
3136                         h = ClipLow(abs(pXSect->onFloorZ - pXSect->offFloorZ), 1);
3137                         curH = abs(pSect->floorz - pXSect->offFloorZ);
3138                     } else {
3139                         h = ClipLow(abs(pXSect->onCeilZ - pXSect->offCeilZ), 1);
3140                         curH = abs(pSect->ceilingz - pXSect->offCeilZ);
3141                     }
3142                     return condCmp((kPercFull * curH) / h, arg1, arg2, cmpOp);
3143                 default:
3144                     condError(pXCond, "Sector conditions:\nUsupported sector type %d", pSect->type);
3145                     return false;
3146                 }
3147             }
3148             case 57: // this sector in movement?
3149                 return !pXSect->unused1;
3150         }
3151     } else {
3152         switch (cond) {
3153             default: return false;
3154             case 55:
3155             case 56:
3156                 return condCmpne(arg1, arg2, cmpOp);
3157         }
3158     }
3159 
3160     condError(pXCond, "Sector conditions: Unexpected condition id (%d)!", cond);
3161     return false;
3162 }
3163 
condCheckWall(XSPRITE * pXCond,int cmpOp,bool PUSH)3164 bool condCheckWall(XSPRITE* pXCond, int cmpOp, bool PUSH) {
3165 
3166     UNREFERENCED_PARAMETER(PUSH);
3167 
3168     int var = -1;
3169     int cond = pXCond->data1 - kCondWallBase; int arg1 = pXCond->data2;
3170     int arg2 = pXCond->data3; //int arg3 = pXCond->data4;
3171 
3172     int objType = -1; int objIndex = -1;
3173     condUnserialize(pXCond->targetX, &objType, &objIndex);
3174 
3175     if (objType != OBJ_WALL || !wallRangeIsFine(objIndex))
3176         condError(pXCond, "Wall conditions:\nObject #%d (objType: %d) is not a wall!", objIndex, objType);
3177 
3178     walltype* pWall = &wall[objIndex];
3179     //XWALL* pXWall = (xwallRangeIsFine(pWall->extra)) ? &xwall[pWall->extra] : NULL;
3180 
3181     if (cond < (kCondRange >> 1)) {
3182         switch (cond) {
3183             default: break;
3184             case 0:
3185                 return condCmp(pWall->overpicnum, arg1, arg2, cmpOp);
3186             case 5:
3187                 if (!sectRangeIsFine((var = sectorofwall(objIndex)))) return false;
3188                 else if (PUSH) condPush(pXCond, OBJ_SECTOR, var);
3189                 return true;
3190             case 10: // this wall is a mirror?                          // must be as constants here
3191                 return (pWall->type != kWallStack && condCmp(pWall->picnum, 4080, (4080 + 16) - 1, 0));
3192             case 15:
3193                 if (!sectRangeIsFine(pWall->nextsector)) return false;
3194                 else if (PUSH) condPush(pXCond, OBJ_SECTOR, pWall->nextsector);
3195                 return true;
3196             case 20:
3197                 if (!wallRangeIsFine(pWall->nextwall)) return false;
3198                 else if (PUSH) condPush(pXCond, OBJ_WALL, pWall->nextwall);
3199                 return true;
3200             case 25: // next wall belongs to sector?
3201                 if (!sectRangeIsFine(var = sectorofwall(pWall->nextwall))) return false;
3202                 else if (PUSH) condPush(pXCond, OBJ_SECTOR, var);
3203                 return true;
3204             /*case 57: // someone touching this wall?
3205                 for (int i = headspritestat[kStatDude]; i >= 0; i = nextspritestat[i]) {
3206                     if (!xspriRangeIsFine(sprite[i].extra) || (gSpriteHit[sprite[i].extra].hit & 0xc000) != 0x8000) continue;
3207                     else if ((gSpriteHit[sprite[i].extra].hit & 0x3fff) != objIndex) continue;
3208                     else if (PUSH) {
3209                         condPush(pXCond, OBJ_SPRITE, i);
3210                         return true;
3211                     }
3212                 }
3213                 return false;*/
3214         }
3215     }
3216 
3217     condError(pXCond, "Wall conditions: Unexpected condition id (%d)!", cond);
3218     return false;
3219 }
3220 
condCheckPlayer(XSPRITE * pXCond,int cmpOp,bool PUSH)3221 bool condCheckPlayer(XSPRITE* pXCond, int cmpOp, bool PUSH) {
3222 
3223     int var = -1; PLAYER* pPlayer = NULL;
3224     int cond = pXCond->data1 - kCondPlayerBase; int arg1 = pXCond->data2;
3225     int arg2 = pXCond->data3; int arg3 = pXCond->data4;
3226 
3227     int objType = -1; int objIndex = -1;
3228     condUnserialize(pXCond->targetX, &objType, &objIndex);
3229 
3230     if (objType == OBJ_SPRITE) {
3231         for (int i = 0; i < kMaxPlayers; i++) {
3232             if (objIndex != gPlayer[i].nSprite) continue;
3233             pPlayer = &gPlayer[i];
3234             break;
3235         }
3236     }
3237 
3238     spritetype* pSpr = NULL;
3239     if (spriRangeIsFine(objIndex) && pPlayer) pSpr = pPlayer->pSprite;
3240     else condError(pXCond, "\nPlayer conditions:\nObject #%d (objType: %d) is not a player!", objIndex, objType);
3241 
3242     switch (cond) {
3243         case 0: // check if this player is connected
3244             if (!condCmp(pPlayer->nPlayer + 1, arg1, arg2, cmpOp) || !spriRangeIsFine(pPlayer->nSprite)) return false;
3245             else if (PUSH) condPush(pXCond, OBJ_SPRITE, pPlayer->nSprite);
3246             return (pPlayer->nPlayer >= 0);
3247         case 1: return condCmp((gGameOptions.nGameType != 3) ? 0 : pPlayer->teamId + 1, arg1, arg2, cmpOp); // compare team
3248         case 2: return (arg1 > 0 && arg1 < 8 && pPlayer->hasKey[arg1 - 1]);
3249         case 3: return (arg1 > 0 && arg1 < 15 && pPlayer->hasWeapon[arg1 - 1]);
3250         case 4: return condCmp(pPlayer->curWeapon, arg1, arg2, cmpOp);
3251         case 5: return (arg1 > 0 && arg1 < 6 && condCmp(pPlayer->packSlots[arg1 - 1].curAmount, arg2, arg3, cmpOp));
3252         case 6: return (arg1 > 0 && arg1 < 6 && pPlayer->packSlots[arg1 - 1].isActive);
3253         case 7: return condCmp(pPlayer->packItemId + 1, arg1, arg2, cmpOp);
3254         case 8: // check for powerup amount in %
3255             if (arg3 > 0 && arg3 < 30) var = (12 + arg3) - 1; // allowable powerups
3256             else condError(pXCond, "Unexpected powerup #%d", arg3);
3257             return condCmp((kPercFull * pPlayer->pwUpTime[var]) / gPowerUpInfo[var].bonusTime, arg1, arg2, cmpOp);
3258         case 9:
3259             if (!spriRangeIsFine(pPlayer->fraggerId)) return false;
3260             else if (PUSH) condPush(pXCond, OBJ_SPRITE, pPlayer->fraggerId);
3261             return true;
3262         case 10: // check keys pressed
3263             switch (arg1) {
3264             case 1:  return (pPlayer->input.forward > 0);            // forward
3265             case 2:  return (pPlayer->input.forward < 0);            // backward
3266             case 3:  return (pPlayer->input.strafe > 0);             // left
3267             case 4:  return (pPlayer->input.strafe < 0);             // right
3268             case 5:  return (pPlayer->input.buttonFlags.jump);       // jump
3269             case 6:  return (pPlayer->input.buttonFlags.crouch);     // crouch
3270             case 7:  return (pPlayer->input.buttonFlags.shoot);      // normal fire weapon
3271             case 8:  return (pPlayer->input.buttonFlags.shoot2);     // alt fire weapon
3272             default:
3273                 condError(pXCond, "Player conditions:\nSpecify a correct key!");
3274                 break;
3275             }
3276             return false;
3277         case 11: return (pPlayer->isRunning);
3278         case 12: return (pPlayer->fallScream); // falling in abyss?
3279         case 13: return condCmp(pPlayer->lifeMode + 1, arg1, arg2, cmpOp);
3280         case 14: return condCmp(pPlayer->posture + 1, arg1, arg2, cmpOp);
3281         case 46: return condCmp(pPlayer->sceneQav, arg1, arg2, cmpOp);
3282         case 47: return (pPlayer->godMode || powerupCheck(pPlayer, kPwUpDeathMask));
3283         case 48: return isShrinked(pSpr);
3284         case 49: return isGrown(pSpr);
3285     }
3286 
3287     condError(pXCond, "Player conditions:\nUnexpected condition #%d!", cond);
3288     return false;
3289 }
3290 
condCheckDude(XSPRITE * pXCond,int cmpOp,bool PUSH)3291 bool condCheckDude(XSPRITE* pXCond, int cmpOp, bool PUSH) {
3292 
3293     int var = -1; //PLAYER* pPlayer = NULL;
3294     int cond = pXCond->data1 - kCondDudeBase; int arg1 = pXCond->data2;
3295     int arg2 = pXCond->data3; //int arg3 = pXCond->data4;
3296 
3297     int objType = -1; int objIndex = -1;
3298     condUnserialize(pXCond->targetX, &objType, &objIndex);
3299 
3300     if (objType != OBJ_SPRITE || !spriRangeIsFine(objIndex))
3301         condError(pXCond, "Dude conditions:\nObject #%d (objType: %d) is not a dude!", objIndex, objType);
3302 
3303     spritetype* pSpr = &sprite[objIndex]; int nType = pSpr->type;
3304 
3305     if (nType == kThingBloodChunks || !xspriRangeIsFine(pSpr->extra)) {
3306 
3307         nType = pSpr->inittype;
3308         if (nType >= kDudeBase && nType <= kDudeMax && (nType < kDudePlayer1 || nType > kDudePlayer8)) return false;
3309         else condError(pXCond, "Dude conditions:\nObject #%d (objType: %d) is not an enemy!", objIndex, objType);
3310 
3311     } else if (IsDudeSprite(pSpr)) {
3312 
3313         XSPRITE* pXSpr = &xsprite[pSpr->extra];
3314 
3315         if (pSpr->flags & kHitagRespawn || pSpr->statnum == kStatRespawn) return false;
3316         else if (IsPlayerSprite(pSpr)) condError(pXCond, "Dude conditions:\nObject #%d (objType: %d) is not an enemy!", objIndex, objType);
3317 
3318         switch (cond) {
3319             default: break;
3320             case 0: // dude have any targets?
3321                 if (!spriRangeIsFine(pXSpr->target)) return false;
3322                 else if (!IsDudeSprite(&sprite[pXSpr->target]) && sprite[pXSpr->target].type != kMarkerPath) return false;
3323                 else if (PUSH) condPush(pXCond, OBJ_SPRITE, pXSpr->target);
3324                 return true;
3325             case 1: return aiFightDudeIsAffected(pXSpr); // dude affected by ai fight?
3326             case 2: // distance to the target in a range?
3327             case 3: // is the target visible?
3328             case 4: // is the target visible with periphery?
3329             {
3330                 DUDEINFO* pInfo = getDudeInfo(pSpr->type);
3331                 int eyeAboveZ = pInfo->eyeHeight * pSpr->yrepeat << 2;
3332                 if (!spriRangeIsFine(pXSpr->target))
3333                     condError(pXCond, "Dude #%d have no target!", objIndex);
3334 
3335                 spritetype* pTrgt = &sprite[pXSpr->target];
3336                 int dx = pTrgt->x - pSpr->x; int dy = pTrgt->y - pSpr->y;
3337 
3338                 switch (cond) {
3339                     case 2:
3340                         var = condCmp(approxDist(dx, dy), arg1 * 512, arg2 * 512, cmpOp);
3341                         break;
3342                     case 3:
3343                     case 4:
3344                         var = cansee(pSpr->x, pSpr->y, pSpr->z, pSpr->sectnum, pTrgt->x, pTrgt->y, pTrgt->z - eyeAboveZ, pTrgt->sectnum);
3345                         if (cond == 4 && var > 0) {
3346                             var = ((1024 + getangle(dx, dy) - pSpr->ang) & 2047) - 1024;
3347                             var = (klabs(var) < ((arg1 <= 0) ? pInfo->periphery : ClipHigh(arg1, 2048)));
3348                         }
3349                         break;
3350                 }
3351 
3352                 if (var <= 0) return false;
3353                 else if (PUSH) condPush(pXCond, OBJ_SPRITE, pXSpr->target);
3354                 return true;
3355 
3356             }
3357             case 5: return pXSpr->dudeFlag4;
3358             case 6: return pXSpr->dudeDeaf;
3359             case 7: return pXSpr->dudeGuard;
3360             case 8: return pXSpr->dudeAmbush;
3361             case 9: return (pXSpr->unused1 & kDudeFlagStealth);
3362             case 10: // check if the marker is busy with another dude
3363             case 11: // check if the marker is reached
3364                 if (!pXSpr->dudeFlag4 || !spriRangeIsFine(pXSpr->target) || sprite[pXSpr->target].type != kMarkerPath) return false;
3365                 switch (cond) {
3366                     case 9:
3367                         var = aiPatrolMarkerBusy(pSpr->index, pXSpr->target);
3368                         if (!spriRangeIsFine(var)) return false;
3369                         else if (PUSH) condPush(pXCond, OBJ_SPRITE, pXSpr->target);
3370                         break;
3371                     case 10:
3372                         if (!aiPatrolMarkerReached(pSpr, pXSpr)) return false;
3373                         else if (PUSH) condPush(pXCond, OBJ_SPRITE, pXSpr->target);
3374                         break;
3375                 }
3376                 return true;
3377             case 12: // compare spot progress value in %
3378                 if (!pXSpr->dudeFlag4 || !spriRangeIsFine(pXSpr->target) || sprite[pXSpr->target].type != kMarkerPath) var = 0;
3379                 else if (pXSpr->data3 < 0 || pXSpr->data3 > kMaxPatrolSpotValue) var = 0;
3380                 else var = (kPercFull * pXSpr->data3) / kMaxPatrolSpotValue;
3381                 return condCmp(var, arg1, arg2, cmpOp);
3382             case 15: return getDudeInfo(pSpr->type)->lockOut; // dude allowed to interact with objects?
3383             case 20: // kDudeModernCustom conditions
3384             case 21:
3385             case 22:
3386                 switch (pSpr->type) {
3387                 case kDudeModernCustom:
3388                 case kDudeModernCustomBurning:
3389                     switch (cond) {
3390                         case 20: // life leech is thrown?
3391                             var = genDudeExtra(pSpr)->nLifeLeech;
3392                             if (!spriRangeIsFine(var)) return false;
3393                             else if (PUSH) condPush(pXCond, OBJ_SPRITE, var);
3394                             return true;
3395                         case 21: // life leech is destroyed?
3396                             var = genDudeExtra(pSpr)->nLifeLeech;
3397                             if (!spriRangeIsFine(var) && pSpr->owner == kMaxSprites - 1) return true;
3398                             else if (PUSH) condPush(pXCond, OBJ_SPRITE, var);
3399                             return false;
3400                         case 22: // are required amount of dudes is summoned?
3401                             return condCmp(gGenDudeExtra[pSpr->index].slaveCount, arg1, arg2, cmpOp);
3402                             break;
3403                     }
3404                     fallthrough__;
3405                 default:
3406                     condError(pXCond, "Dude #%d is not a Custom Dude!", objIndex);
3407                     return false;
3408                 }
3409         }
3410 
3411     }
3412 
3413     condError(pXCond, "Dude conditions:\nUnexpected condition #%d!", cond);
3414     return false;
3415 }
3416 
condCheckSprite(XSPRITE * pXCond,int cmpOp,bool PUSH)3417 bool condCheckSprite(XSPRITE* pXCond, int cmpOp, bool PUSH) {
3418 
3419     UNREFERENCED_PARAMETER(PUSH);
3420 
3421     int var = -1, var2 = -1, var3 = -1; PLAYER* pPlayer = NULL; bool retn = false;
3422     int cond = pXCond->data1 - kCondSpriteBase; int arg1 = pXCond->data2;
3423     int arg2 = pXCond->data3; int arg3 = pXCond->data4;
3424 
3425     int objType = -1; int objIndex = -1;
3426     condUnserialize(pXCond->targetX, &objType, &objIndex);
3427 
3428     if (objType != OBJ_SPRITE || !spriRangeIsFine(objIndex))
3429         condError(pXCond, "Sprite condition %d:\nObject #%d (objType: %d) is not a sprite!", cond, objIndex, objType);
3430 
3431     spritetype* pSpr = &sprite[objIndex];
3432     XSPRITE* pXSpr = (xspriRangeIsFine(pSpr->extra)) ? &xsprite[pSpr->extra] : NULL;
3433 
3434     if (cond < (kCondRange >> 1)) {
3435         switch (cond) {
3436             default: break;
3437             case 0: return condCmp((pSpr->ang & 2047), arg1, arg2, cmpOp);
3438             case 5: return condCmp(pSpr->statnum, arg1, arg2, cmpOp);
3439             case 6: return ((pSpr->flags & kHitagRespawn) || pSpr->statnum == kStatRespawn);
3440             case 7: return condCmp(spriteGetSlope(pSpr->index), arg1, arg2, cmpOp);
3441             case 10: return condCmp(pSpr->clipdist, arg1, arg2, cmpOp);
3442             case 15:
3443                 if (!spriRangeIsFine(pSpr->owner)) return false;
3444                 else if (PUSH) condPush(pXCond, OBJ_SPRITE, pSpr->owner);
3445                 return true;
3446             case 20: // stays in a sector?
3447                 if (!sectRangeIsFine(pSpr->sectnum)) return false;
3448                 else if (PUSH) condPush(pXCond, OBJ_SECTOR, pSpr->sectnum);
3449                 return true;
3450             case 25:
3451                 switch (arg1) {
3452                     case 0: return (xvel[pSpr->index] || yvel[pSpr->index] || zvel[pSpr->index]);
3453                     case 1: return (xvel[pSpr->index]);
3454                     case 2: return (yvel[pSpr->index]);
3455                     case 3: return (zvel[pSpr->index]);
3456                 }
3457                 break;
3458             case 30:
3459                 if (!spriteIsUnderwater(pSpr) && !spriteIsUnderwater(pSpr, true)) return false;
3460                 else if (PUSH) condPush(pXCond, OBJ_SECTOR, pSpr->sectnum);
3461                 return true;
3462             case 35: // hitscan: ceil?
3463             case 36: // hitscan: floor?
3464             case 37: // hitscan: wall?
3465             case 38: // hitscan: sprite?
3466                 switch (arg1) {
3467                     case  0: arg1 = CLIPMASK0 | CLIPMASK1; break;
3468                     case  1: arg1 = CLIPMASK0; break;
3469                     case  2: arg1 = CLIPMASK1; break;
3470                 }
3471 
3472                 var3 = spriteGetSlope(pSpr->index);
3473                 var2 = pSpr->cstat;  pSpr->cstat = 0;
3474                 if ((pPlayer = getPlayerById(pSpr->type)) != NULL)
3475                     var = HitScan(pSpr, pPlayer->zWeapon - pSpr->z, pPlayer->aim.dx, pPlayer->aim.dy, pPlayer->aim.dz, arg1, arg3 << 1);
3476                 else if (IsDudeSprite(pSpr))
3477                     var = HitScan(pSpr, pSpr->z, Cos(pSpr->ang) >> 16, Sin(pSpr->ang) >> 16, (!xspriRangeIsFine(pSpr->extra)) ? 0 : gDudeSlope[pSpr->extra], arg1, arg3 << 1);
3478                 else if (var2 & CSTAT_SPRITE_ALIGNMENT_FLOOR) {
3479 
3480                     if (var3 == 0) {
3481                         if (var2 & 0x0008) var = 0x10000;
3482                         else var = -0x10000;
3483                     }
3484                     else if (var3 > 0) var3 = ClipLow(34816 - abs(var3), 0);
3485                     else var3 = -ClipLow(34816 - abs(var3), 0);
3486 
3487                     var = HitScan(pSpr, pSpr->z, Cos(pSpr->ang) >> 16, Sin(pSpr->ang) >> 16, var3, arg1, arg3 << 1);
3488 
3489                 } else {
3490 
3491                     var = HitScan(pSpr, pSpr->z, Cos(pSpr->ang) >> 16, Sin(pSpr->ang) >> 16, 0, arg1, arg3 << 1);
3492 
3493                 }
3494                 pSpr->cstat = var2;
3495 
3496                 if (var < 0) return retn;
3497                 switch (cond) {
3498                     case 35: retn = (var == 1); break;
3499                     case 36: retn = (var == 2); break;
3500                     case 37: retn = (var == 0 || var == 4); break;
3501                     case 38: retn = (var == 3); break;
3502                 }
3503 
3504                 if (!PUSH) return retn;
3505                 switch (var) {
3506                     case 0: case 4: condPush(pXCond, OBJ_WALL, gHitInfo.hitwall);       break;
3507                     case 1: case 2: condPush(pXCond, OBJ_SECTOR, gHitInfo.hitsect);     break;
3508                     case 3:         condPush(pXCond, OBJ_SPRITE, gHitInfo.hitsprite);   break;
3509                 }
3510                 return retn;
3511             case 45: // this sprite is a target of some dude?
3512                 for (int nSprite = headspritestat[kStatDude]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
3513                     if (pSpr->index == nSprite) continue;
3514 
3515                     spritetype* pDude = &sprite[nSprite];
3516                     if (IsDudeSprite(pDude) && xspriRangeIsFine(pDude->extra)) {
3517                         XSPRITE* pXDude = &xsprite[pDude->extra];
3518                         if (pXDude->health <= 0 || pXDude->target != pSpr->index) continue;
3519                         else if (PUSH) condPush(pXCond, OBJ_SPRITE, nSprite);
3520                         return true;
3521                     }
3522                 }
3523                 return false;
3524         }
3525     } else if (pXSpr) {
3526         switch (cond) {
3527             default: break;
3528             case 50: // compare hp (in %)
3529                 if (IsDudeSprite(pSpr)) var = (pXSpr->sysData2 > 0) ? ClipRange(pXSpr->sysData2 << 4, 1, 65535) : getDudeInfo(pSpr->type)->startHealth << 4;
3530                 else if (pSpr->type == kThingBloodChunks) return condCmpne(arg1, arg2, cmpOp);
3531                 else if (pSpr->type >= kThingBase && pSpr->type < kThingMax) var = thingInfo[pSpr->type - kThingBase].startHealth << 4;
3532                 return condCmp((kPercFull * pXSpr->health) / ClipLow(var, 1), arg1, arg2, cmpOp);
3533             case 55: // touching ceil of sector?
3534                 if ((gSpriteHit[pSpr->extra].ceilhit & 0xc000) != 0x4000) return false;
3535                 else if (PUSH) condPush(pXCond, OBJ_SECTOR, gSpriteHit[pSpr->extra].ceilhit & 0x3fff);
3536                 return true;
3537             case 56: // touching floor of sector?
3538                 if ((gSpriteHit[pSpr->extra].florhit & 0xc000) != 0x4000) return false;
3539                 else if (PUSH) condPush(pXCond, OBJ_SECTOR, gSpriteHit[pSpr->extra].florhit & 0x3fff);
3540                 return true;
3541             case 57: // touching walls of sector?
3542                 if ((gSpriteHit[pSpr->extra].hit & 0xc000) != 0x8000) return false;
3543                 else if (PUSH) condPush(pXCond, OBJ_WALL, gSpriteHit[pSpr->extra].hit & 0x3fff);
3544                 return true;
3545             case 58: // touching another sprite?
3546                 switch (arg3) {
3547                     case 0:
3548                     case 1:
3549                         if ((gSpriteHit[pSpr->extra].florhit & 0xc000) == 0xc000) var = gSpriteHit[pSpr->extra].florhit & 0x3fff;
3550                         if (arg3 || var >= 0) break;
3551                         fallthrough__;
3552                     case 2:
3553                         if ((gSpriteHit[pSpr->extra].hit & 0xc000) == 0xc000) var = gSpriteHit[pSpr->extra].hit & 0x3fff;
3554                         if (arg3 || var >= 0) break;
3555                         fallthrough__;
3556                     case 3:
3557                         if ((gSpriteHit[pSpr->extra].ceilhit & 0xc000) == 0xc000) var = gSpriteHit[pSpr->extra].ceilhit & 0x3fff;
3558                         break;
3559                 }
3560                 if (var < 0) { // check if something touching this sprite
3561                     for (int i = kMaxXSprites - 1, idx = i; i > 0; idx = xsprite[--i].reference) {
3562                         if (idx < 0 || (sprite[idx].flags & kHitagRespawn)) continue;
3563                         switch (arg3) {
3564                             case 0:
3565                             case 1:
3566                                 if ((gSpriteHit[i].ceilhit & 0xc000) == 0xc000 && (gSpriteHit[i].ceilhit & 0x3fff) == objIndex) var = idx;
3567                                 if (arg3 || var >= 0) break;
3568                                 fallthrough__;
3569                             case 2:
3570                                 if ((gSpriteHit[i].hit & 0xc000) == 0xc000 && (gSpriteHit[i].hit & 0x3fff) == objIndex) var = idx;
3571                                 if (arg3 || var >= 0) break;
3572                                 fallthrough__;
3573                             case 3:
3574                                 if ((gSpriteHit[i].florhit & 0xc000) == 0xc000 && (gSpriteHit[i].florhit & 0x3fff) == objIndex) var = idx;
3575                                 break;
3576                         }
3577                     }
3578                 }
3579                 if (var < 0) return false;
3580                 else if (PUSH) condPush(pXCond, OBJ_SPRITE, var);
3581                 return true;
3582             case 65: // compare burn time (in %)
3583                 var = (IsDudeSprite(pSpr)) ? 2400 : 1200;
3584                 if (!condCmp((kPercFull * pXSpr->burnTime) / var, arg1, arg2, cmpOp)) return false;
3585                 else if (PUSH && spriRangeIsFine(pXSpr->burnSource)) condPush(pXCond, OBJ_SPRITE, pXSpr->burnSource);
3586                 return true;
3587             case 66: // any flares stuck in this sprite?
3588                 for (int nSprite = headspritestat[kStatFlare]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
3589                     spritetype* pFlare = &sprite[nSprite];
3590                     if (!xspriRangeIsFine(pFlare->extra) || (pFlare->flags & kHitagFree))
3591                         continue;
3592 
3593                     XSPRITE* pXFlare = &xsprite[pFlare->extra];
3594                     if (!spriRangeIsFine(pXFlare->target) || pXFlare->target != objIndex) continue;
3595                     else if (PUSH) condPush(pXCond, OBJ_SPRITE, nSprite);
3596                     return true;
3597                 }
3598                 return false;
3599             case 70:
3600                 return condCmp(getSpriteMassBySize(pSpr), arg1, arg2, cmpOp); // mass of the sprite in a range?
3601         }
3602     } else {
3603         switch (cond) {
3604             default: return false;
3605             case 50:
3606             case 65:
3607             case 70:
3608                 return condCmpne(arg1, arg2, cmpOp);
3609         }
3610     }
3611 
3612     condError(pXCond, "Sprite conditions: Unexpected condition id (%d)!", cond);
3613     return false;
3614 }
3615 
3616 // this updates index of object in all conditions
condUpdateObjectIndex(int objType,int oldIndex,int newIndex)3617 void condUpdateObjectIndex(int objType, int oldIndex, int newIndex) {
3618 
3619     // update index in tracking conditions first
3620     for (int i = 0; i < gTrackingCondsCount; i++) {
3621 
3622         TRCONDITION* pCond = &gCondition[i];
3623         for (int k = 0; k < pCond->length; k++) {
3624             if (pCond->obj[k].type != objType || pCond->obj[k].index != oldIndex) continue;
3625             pCond->obj[k].index = newIndex;
3626             break;
3627         }
3628 
3629     }
3630 
3631     int oldSerial = condSerialize(objType, oldIndex);
3632     int newSerial = condSerialize(objType, newIndex);
3633 
3634     // then update serials
3635     for (int nSpr = headspritestat[kStatModernCondition]; nSpr >= 0; nSpr = nextspritestat[nSpr]) {
3636 
3637         XSPRITE* pXCond = &xsprite[sprite[nSpr].extra];
3638         if (pXCond->targetX == oldSerial) pXCond->targetX = newSerial;
3639         if (pXCond->targetY == oldSerial) pXCond->targetY = newSerial;
3640 
3641     }
3642 
3643     return;
3644 }
3645 
valueIsBetween(int val,int min,int max)3646 bool valueIsBetween(int val, int min, int max) {
3647     return (val > min && val < max);
3648 }
3649 
modernTypeSetSpriteState(int nSprite,XSPRITE * pXSprite,int nState)3650 char modernTypeSetSpriteState(int nSprite, XSPRITE* pXSprite, int nState) {
3651     if ((pXSprite->busy & 0xffff) == 0 && pXSprite->state == nState)
3652         return 0;
3653 
3654     pXSprite->busy = nState << 16; pXSprite->state = nState;
3655 
3656     evKill(nSprite, 3);
3657     if (pXSprite->restState != nState && pXSprite->waitTime > 0)
3658         evPost(nSprite, 3, (pXSprite->waitTime * 120) / 10, pXSprite->restState ? kCmdOn : kCmdOff);
3659 
3660     if (pXSprite->txID != 0 && ((pXSprite->triggerOn && pXSprite->state) || (pXSprite->triggerOff && !pXSprite->state)))
3661         modernTypeSendCommand(nSprite, pXSprite->txID, (COMMAND_ID)pXSprite->command);
3662 
3663     return 1;
3664 }
3665 
modernTypeSendCommand(int nSprite,int destChannel,COMMAND_ID command)3666 void modernTypeSendCommand(int nSprite, int destChannel, COMMAND_ID command) {
3667     switch (command) {
3668     case kCmdLink:
3669         evSend(nSprite, 3, destChannel, kCmdModernUse); // just send command to change properties
3670         return;
3671     case kCmdUnlock:
3672         evSend(nSprite, 3, destChannel, command); // send normal command first
3673         evSend(nSprite, 3, destChannel, kCmdModernUse);  // then send command to change properties
3674         return;
3675     default:
3676         evSend(nSprite, 3, destChannel, kCmdModernUse); // send first command to change properties
3677         evSend(nSprite, 3, destChannel, command); // then send normal command
3678         return;
3679     }
3680 }
3681 
3682 // this function used by various new modern types.
modernTypeTrigger(int destObjType,int destObjIndex,EVENT event)3683 void modernTypeTrigger(int destObjType, int destObjIndex, EVENT event) {
3684 
3685     if (event.type != OBJ_SPRITE) return;
3686     spritetype* pSource = &sprite[event.index];
3687 
3688     if (!xspriRangeIsFine(pSource->extra)) return;
3689     XSPRITE* pXSource = &xsprite[pSource->extra];
3690 
3691     switch (destObjType) {
3692         case OBJ_SECTOR:
3693             if (!xsectRangeIsFine(sector[destObjIndex].extra)) return;
3694             break;
3695         case OBJ_WALL:
3696             if (!xwallRangeIsFine(wall[destObjIndex].extra)) return;
3697             break;
3698         case OBJ_SPRITE:
3699             if (!xspriRangeIsFine(sprite[destObjIndex].extra)) return;
3700             else if (sprite[destObjIndex].flags & kHitagFree) return;
3701 
3702             /*switch (pSource->type) {
3703                 case kModernEffectSpawner:
3704                 case kModernWindGenerator:
3705                     switch (sprite[destObjIndex].type) {
3706                         case kModernEffectSpawner:
3707                         case kModernWindGenerator:
3708                             viewSetSystemMessage("SRC %d, DEST %d", Numsprites, sprite[destObjIndex].type);
3709                             break;
3710                     }
3711                     break;
3712             }*/
3713 
3714             // allow redirect events received from some modern types.
3715             // example: it allows to spawn FX effect if event was received from kModernEffectGen
3716             // on many TX channels instead of just one.
3717             switch (sprite[destObjIndex].type) {
3718                 case kModernRandomTX:
3719                 case kModernSequentialTX:
3720                     spritetype* pSpr = &sprite[destObjIndex]; XSPRITE* pXSpr = &xsprite[pSpr->extra];
3721                     if (pXSpr->command != kCmdLink) break; // no redirect mode detected
3722                     else if (!pXSpr->locked) {
3723                         switch (pSpr->type) {
3724                             case kModernRandomTX:
3725                                 useRandomTx(pXSpr, (COMMAND_ID)pXSource->command, false); // set random TX id
3726                                 break;
3727                             case kModernSequentialTX:
3728                                 if (pSpr->flags & kModernTypeFlag1) {
3729                                     seqTxSendCmdAll(pXSpr, pSource->index, (COMMAND_ID)pXSource->command, true);
3730                                     return;
3731                                 }
3732                                 useSequentialTx(pXSpr, (COMMAND_ID)pXSource->command, false); // set next TX id
3733                                 break;
3734                         }
3735                         if (pXSpr->txID > 0 && pXSpr->txID < kChannelUserMax) {
3736                             modernTypeSendCommand(pSource->index, pXSpr->txID, (COMMAND_ID)pXSource->command);
3737                         }
3738                         return;
3739                     }
3740                     break;
3741             }
3742             break;
3743         default:
3744             return;
3745     }
3746 
3747     switch (pSource->type) {
3748         // allows teleport any sprite from any location to the source destination
3749         case kMarkerWarpDest:
3750             if (destObjType != OBJ_SPRITE) break;
3751             useTeleportTarget(pXSource, &sprite[destObjIndex]);
3752             break;
3753         // changes slope of sprite or sector
3754         case kModernSlopeChanger:
3755             switch (destObjType) {
3756                 case OBJ_SPRITE:
3757                 case OBJ_SECTOR:
3758                     useSlopeChanger(pXSource, destObjType, destObjIndex);
3759                     break;
3760             }
3761             break;
3762         case kModernSpriteDamager:
3763         // damages xsprite via TX ID or xsprites in a sector
3764             switch (destObjType) {
3765                 case OBJ_SPRITE:
3766                 case OBJ_SECTOR:
3767                     useSpriteDamager(pXSource, destObjType, destObjIndex);
3768                     break;
3769             }
3770             break;
3771         // can spawn any effect passed in data2 on it's or txID sprite
3772         case kModernEffectSpawner:
3773             if (destObjType != OBJ_SPRITE || pXSource->data2 < 0 || pXSource->data2 >= kFXMax) break;
3774             useEffectGen(pXSource, &sprite[destObjIndex]);
3775             break;
3776         // takes data2 as SEQ ID and spawns it on it's or TX ID object
3777         case kModernSeqSpawner:
3778             useSeqSpawnerGen(pXSource, destObjType, destObjIndex);
3779             break;
3780         // creates wind on TX ID sector
3781         case kModernWindGenerator:
3782             if (destObjType != OBJ_SECTOR || pXSource->data2 < 0) break;
3783             useSectorWindGen(pXSource, &sector[destObjIndex]);
3784             break;
3785         // size and pan changer of sprite/wall/sector via TX ID
3786         case kModernObjSizeChanger:
3787             useObjResizer(pXSource, destObjType, destObjIndex);
3788             break;
3789         // iterate data filed value of destination object
3790         case kModernObjDataAccumulator:
3791             useIncDecGen(pXSource, destObjType, destObjIndex);
3792             break;
3793         // change data field value of destination object
3794         case kModernObjDataChanger:
3795             useDataChanger(pXSource, destObjType, destObjIndex);
3796             break;
3797         // change sector lighting dynamically
3798         case kModernSectorFXChanger:
3799             if (destObjType != OBJ_SECTOR) break;
3800             useSectorLigthChanger(pXSource, &xsector[sector[destObjIndex].extra]);
3801             break;
3802         // change target of dudes and make it fight
3803         case kModernDudeTargetChanger:
3804             if (destObjType != OBJ_SPRITE) break;
3805             useTargetChanger(pXSource, &sprite[destObjIndex]);
3806             break;
3807         // change picture and palette of TX ID object
3808         case kModernObjPicnumChanger:
3809             usePictureChanger(pXSource, destObjType, destObjIndex);
3810             break;
3811         // change various properties
3812         case kModernObjPropertiesChanger:
3813             usePropertiesChanger(pXSource, destObjType, destObjIndex);
3814             break;
3815         // updated vanilla sound gen that now allows to play sounds on TX ID sprites
3816         case kGenModernSound:
3817             if (destObjType != OBJ_SPRITE) break;
3818             useSoundGen(pXSource, &sprite[destObjIndex]);
3819             break;
3820     }
3821 }
3822 
3823 // the following functions required for kModernDudeTargetChanger
3824 //---------------------------------------
aiFightGetTargetInRange(spritetype * pSprite,int minDist,int maxDist,short data,short teamMode)3825 spritetype* aiFightGetTargetInRange(spritetype* pSprite, int minDist, int maxDist, short data, short teamMode) {
3826     DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type); XSPRITE* pXSprite = &xsprite[pSprite->extra];
3827     spritetype* pTarget = NULL; XSPRITE* pXTarget = NULL; spritetype* cTarget = NULL;
3828     for (int nSprite = headspritestat[kStatDude]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
3829         pTarget = &sprite[nSprite];  pXTarget = &xsprite[pTarget->extra];
3830         if (!aiFightDudeCanSeeTarget(pXSprite, pDudeInfo, pTarget)) continue;
3831 
3832         int dist = aiFightGetTargetDist(pSprite, pDudeInfo, pTarget);
3833         if (dist < minDist || dist > maxDist) continue;
3834         else if (pXSprite->target == pTarget->index) return pTarget;
3835         else if (!IsDudeSprite(pTarget) || pTarget->index == pSprite->index || IsPlayerSprite(pTarget)) continue;
3836         else if (IsBurningDude(pTarget) || !IsKillableDude(pTarget) || pTarget->owner == pSprite->index) continue;
3837         else if ((teamMode == 1 && aiFightIsMateOf(pXSprite, pXTarget)) || aiFightMatesHaveSameTarget(pXSprite, pTarget, 1)) continue;
3838         else if (data == 666 || pXTarget->data1 == data) {
3839 
3840             if (pXSprite->target > 0) {
3841                 cTarget = &sprite[pXSprite->target];
3842                 int fineDist1 = aiFightGetFineTargetDist(pSprite, cTarget);
3843                 int fineDist2 = aiFightGetFineTargetDist(pSprite, pTarget);
3844                 if (fineDist1 < fineDist2)
3845                     continue;
3846             }
3847             return pTarget;
3848         }
3849     }
3850 
3851     return NULL;
3852 }
3853 
aiFightTargetIsPlayer(XSPRITE * pXSprite)3854 spritetype* aiFightTargetIsPlayer(XSPRITE* pXSprite) {
3855 
3856     if (pXSprite->target >= 0) {
3857         if (IsPlayerSprite(&sprite[pXSprite->target]))
3858             return &sprite[pXSprite->target];
3859     }
3860 
3861     return NULL;
3862 }
aiFightGetMateTargets(XSPRITE * pXSprite)3863 spritetype* aiFightGetMateTargets(XSPRITE* pXSprite) {
3864     int rx = pXSprite->rxID; spritetype* pMate = NULL; XSPRITE* pXMate = NULL;
3865 
3866     for (int i = bucketHead[rx]; i < bucketHead[rx + 1]; i++) {
3867         if (rxBucket[i].type == OBJ_SPRITE) {
3868             pMate = &sprite[rxBucket[i].index];
3869             if (pMate->extra < 0 || pMate->index == sprite[pXSprite->reference].index || !IsDudeSprite(pMate))
3870                 continue;
3871 
3872             pXMate = &xsprite[pMate->extra];
3873             if (pXMate->target > -1) {
3874                 if (!IsPlayerSprite(&sprite[pXMate->target]))
3875                     return &sprite[pXMate->target];
3876             }
3877 
3878         }
3879     }
3880 
3881     return NULL;
3882 }
3883 
aiFightMatesHaveSameTarget(XSPRITE * pXLeader,spritetype * pTarget,int allow)3884 bool aiFightMatesHaveSameTarget(XSPRITE* pXLeader, spritetype* pTarget, int allow) {
3885     int rx = pXLeader->rxID; spritetype* pMate = NULL; XSPRITE* pXMate = NULL;
3886 
3887     for (int i = bucketHead[rx]; i < bucketHead[rx + 1]; i++) {
3888 
3889         if (rxBucket[i].type != OBJ_SPRITE)
3890             continue;
3891 
3892         pMate = &sprite[rxBucket[i].index];
3893         if (pMate->extra < 0 || pMate->index == sprite[pXLeader->reference].index || !IsDudeSprite(pMate))
3894             continue;
3895 
3896         pXMate = &xsprite[pMate->extra];
3897         if (pXMate->target == pTarget->index && allow-- <= 0)
3898             return true;
3899     }
3900 
3901     return false;
3902 
3903 }
3904 
aiFightDudeCanSeeTarget(XSPRITE * pXDude,DUDEINFO * pDudeInfo,spritetype * pTarget)3905 bool aiFightDudeCanSeeTarget(XSPRITE* pXDude, DUDEINFO* pDudeInfo, spritetype* pTarget) {
3906     spritetype* pDude = &sprite[pXDude->reference];
3907     int dx = pTarget->x - pDude->x; int dy = pTarget->y - pDude->y;
3908 
3909     // check target
3910     if (approxDist(dx, dy) < pDudeInfo->seeDist) {
3911         int eyeAboveZ = pDudeInfo->eyeHeight * pDude->yrepeat << 2;
3912 
3913         // is there a line of sight to the target?
3914         if (cansee(pDude->x, pDude->y, pDude->z, pDude->sectnum, pTarget->x, pTarget->y, pTarget->z - eyeAboveZ, pTarget->sectnum)) {
3915             /*int nAngle = getangle(dx, dy);
3916             int losAngle = ((1024 + nAngle - pDude->ang) & 2047) - 1024;
3917 
3918             // is the target visible?
3919             if (klabs(losAngle) < 2048) // 360 deg periphery here*/
3920             return true;
3921         }
3922 
3923     }
3924 
3925     return false;
3926 
3927 }
3928 
3929 // this function required if monsters in genIdle ai state. It wakes up monsters
3930 // when kModernDudeTargetChanger goes to off state, so they won't ignore the world.
aiFightActivateDudes(int rx)3931 void aiFightActivateDudes(int rx) {
3932     for (int i = bucketHead[rx]; i < bucketHead[rx + 1]; i++) {
3933         if (rxBucket[i].type != OBJ_SPRITE) continue;
3934         spritetype* pDude = &sprite[rxBucket[i].index]; XSPRITE* pXDude = &xsprite[pDude->extra];
3935         if (!IsDudeSprite(pDude) || pXDude->aiState->stateType != kAiStateGenIdle) continue;
3936         aiInitSprite(pDude);
3937     }
3938 }
3939 
3940 
3941 // this function sets target to -1 for all dudes that hunting for nSprite
aiFightFreeTargets(int nSprite)3942 void aiFightFreeTargets(int nSprite) {
3943     for (int nTarget = headspritestat[kStatDude]; nTarget >= 0; nTarget = nextspritestat[nTarget]) {
3944         if (!IsDudeSprite(&sprite[nTarget]) || sprite[nTarget].extra < 0) continue;
3945         else if (xsprite[sprite[nTarget].extra].target == nSprite)
3946             aiSetTarget(&xsprite[sprite[nTarget].extra], sprite[nTarget].x, sprite[nTarget].y, sprite[nTarget].z);
3947     }
3948 
3949     return;
3950 }
3951 
3952 // this function sets target to -1 for all targets that hunting for dudes affected by selected kModernDudeTargetChanger
aiFightFreeAllTargets(XSPRITE * pXSource)3953 void aiFightFreeAllTargets(XSPRITE* pXSource) {
3954     if (pXSource->txID <= 0) return;
3955     for (int i = bucketHead[pXSource->txID]; i < bucketHead[pXSource->txID + 1]; i++) {
3956         if (rxBucket[i].type == OBJ_SPRITE && sprite[rxBucket[i].index].extra >= 0)
3957             aiFightFreeTargets(rxBucket[i].index);
3958     }
3959 
3960     return;
3961 }
3962 
3963 
aiFightDudeIsAffected(XSPRITE * pXDude)3964 bool aiFightDudeIsAffected(XSPRITE* pXDude) {
3965     if (pXDude->rxID <= 0 || pXDude->locked == 1) return false;
3966     for (int nSprite = headspritestat[kStatModernDudeTargetChanger]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
3967         XSPRITE* pXSprite = (sprite[nSprite].extra >= 0) ? &xsprite[sprite[nSprite].extra] : NULL;
3968         if (pXSprite == NULL || pXSprite->txID <= 0 || pXSprite->state != 1) continue;
3969         for (int i = bucketHead[pXSprite->txID]; i < bucketHead[pXSprite->txID + 1]; i++) {
3970             if (rxBucket[i].type != OBJ_SPRITE) continue;
3971 
3972             spritetype* pSprite = &sprite[rxBucket[i].index];
3973             if (pSprite->extra < 0 || !IsDudeSprite(pSprite)) continue;
3974             else if (pSprite->index == sprite[pXDude->reference].index) return true;
3975         }
3976     }
3977     return false;
3978 }
3979 
aiFightIsMateOf(XSPRITE * pXDude,XSPRITE * pXSprite)3980 bool aiFightIsMateOf(XSPRITE* pXDude, XSPRITE* pXSprite) {
3981     return (pXDude->rxID == pXSprite->rxID);
3982 }
3983 
3984 // this function tells if there any dude found for kModernDudeTargetChanger
aiFightGetDudesForBattle(XSPRITE * pXSprite)3985 bool aiFightGetDudesForBattle(XSPRITE* pXSprite) {
3986 
3987     for (int i = bucketHead[pXSprite->txID]; i < bucketHead[pXSprite->txID + 1]; i++) {
3988         if (rxBucket[i].type != OBJ_SPRITE) continue;
3989         else if (IsDudeSprite(&sprite[rxBucket[i].index]) &&
3990             xsprite[sprite[rxBucket[i].index].extra].health > 0) return true;
3991     }
3992 
3993     // check redirected TX buckets
3994     int rx = -1; XSPRITE* pXRedir = NULL;
3995     while ((pXRedir = evrListRedirectors(OBJ_SPRITE, sprite[pXSprite->reference].extra, pXRedir, &rx)) != NULL) {
3996         for (int i = bucketHead[rx]; i < bucketHead[rx + 1]; i++) {
3997             if (rxBucket[i].type != OBJ_SPRITE) continue;
3998             else if (IsDudeSprite(&sprite[rxBucket[i].index]) &&
3999                 xsprite[sprite[rxBucket[i].index].extra].health > 0) return true;
4000         }
4001     }
4002     return false;
4003 }
4004 
aiFightAlarmDudesInSight(spritetype * pSprite,int max)4005 void aiFightAlarmDudesInSight(spritetype* pSprite, int max) {
4006     spritetype* pDude = NULL; XSPRITE* pXDude = NULL;
4007     XSPRITE* pXSprite = &xsprite[pSprite->extra];
4008     DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
4009     for (int nSprite = headspritestat[kStatDude]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
4010         pDude = &sprite[nSprite];
4011         if (pDude->index == pSprite->index || !IsDudeSprite(pDude) || pDude->extra < 0)
4012             continue;
4013         pXDude = &xsprite[pDude->extra];
4014         if (aiFightDudeCanSeeTarget(pXSprite, pDudeInfo, pDude)) {
4015             if (pXDude->target != -1 || pXDude->rxID > 0)
4016                 continue;
4017 
4018             aiSetTarget(pXDude, pDude->x, pDude->y, pDude->z);
4019             aiActivateDude(pDude, pXDude);
4020             if (max-- < 1)
4021                 break;
4022         }
4023     }
4024 }
4025 
aiFightUnitCanFly(spritetype * pDude)4026 bool aiFightUnitCanFly(spritetype* pDude) {
4027     return (IsDudeSprite(pDude) && gDudeInfoExtra[pDude->type - kDudeBase].flying);
4028 }
4029 
aiFightIsMeleeUnit(spritetype * pDude)4030 bool aiFightIsMeleeUnit(spritetype* pDude) {
4031     if (pDude->type == kDudeModernCustom) return (pDude->extra >= 0 && dudeIsMelee(&xsprite[pDude->extra]));
4032     else return (IsDudeSprite(pDude) && gDudeInfoExtra[pDude->type - kDudeBase].melee);
4033 }
4034 
aiFightGetTargetDist(spritetype * pSprite,DUDEINFO * pDudeInfo,spritetype * pTarget)4035 int aiFightGetTargetDist(spritetype* pSprite, DUDEINFO* pDudeInfo, spritetype* pTarget) {
4036     int x = pTarget->x; int y = pTarget->y;
4037     int dx = x - pSprite->x; int dy = y - pSprite->y;
4038 
4039     int dist = approxDist(dx, dy);
4040     if (dist <= pDudeInfo->meleeDist) return 0;
4041     if (dist >= pDudeInfo->seeDist) return 13;
4042     if (dist <= pDudeInfo->seeDist / 12) return 1;
4043     if (dist <= pDudeInfo->seeDist / 11) return 2;
4044     if (dist <= pDudeInfo->seeDist / 10) return 3;
4045     if (dist <= pDudeInfo->seeDist / 9) return 4;
4046     if (dist <= pDudeInfo->seeDist / 8) return 5;
4047     if (dist <= pDudeInfo->seeDist / 7) return 6;
4048     if (dist <= pDudeInfo->seeDist / 6) return 7;
4049     if (dist <= pDudeInfo->seeDist / 5) return 8;
4050     if (dist <= pDudeInfo->seeDist / 4) return 9;
4051     if (dist <= pDudeInfo->seeDist / 3) return 10;
4052     if (dist <= pDudeInfo->seeDist / 2) return 11;
4053     return 12;
4054 }
4055 
aiFightGetFineTargetDist(spritetype * pSprite,spritetype * pTarget)4056 int aiFightGetFineTargetDist(spritetype* pSprite, spritetype* pTarget) {
4057     int x = pTarget->x; int y = pTarget->y;
4058     int dx = x - pSprite->x; int dy = y - pSprite->y;
4059 
4060     int dist = approxDist(dx, dy);
4061     return dist;
4062 }
4063 
sectorInMotion(int nSector)4064 int sectorInMotion(int nSector) {
4065 
4066     for (int i = 0; i < kMaxBusyCount; i++) {
4067         if (gBusy->at0 == nSector) return i;
4068     }
4069 
4070     return -1;
4071 }
4072 
sectorKillSounds(int nSector)4073 void sectorKillSounds(int nSector) {
4074     for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite]) {
4075         if (sprite[nSprite].type != kSoundSector) continue;
4076         sfxKill3DSound(&sprite[nSprite]);
4077     }
4078 }
4079 
sectorPauseMotion(int nSector)4080 void sectorPauseMotion(int nSector) {
4081 
4082     if (!xsectRangeIsFine(sector[nSector].extra)) return;
4083     XSECTOR* pXSector = &xsector[sector[nSector].extra];
4084     pXSector->unused1 = 1;
4085 
4086     evKill(nSector, OBJ_SECTOR);
4087 
4088     sectorKillSounds(nSector);
4089     if ((pXSector->busy == 0 && !pXSector->state) || (pXSector->busy == 65536 && pXSector->state))
4090         SectorEndSound(nSector, xsector[sector[nSector].extra].state);
4091 
4092     return;
4093 }
4094 
sectorContinueMotion(int nSector,EVENT event)4095 void sectorContinueMotion(int nSector, EVENT event) {
4096 
4097     if (!xsectRangeIsFine(sector[nSector].extra)) return;
4098     else if (gBusyCount >= kMaxBusyCount) {
4099         consoleSysMsg("Failed to continue motion for sector #%d. Max (%d) busy objects count reached!", nSector, kMaxBusyCount);
4100         return;
4101     }
4102 
4103     XSECTOR* pXSector = &xsector[sector[nSector].extra];
4104     pXSector->unused1 = 0;
4105 
4106     int busyTimeA = pXSector->busyTimeA;    int waitTimeA = pXSector->waitTimeA;
4107     int busyTimeB = pXSector->busyTimeB;    int waitTimeB = pXSector->waitTimeB;
4108     if (sector[nSector].type == kSectorPath) {
4109         if (!spriRangeIsFine(pXSector->marker0)) return;
4110         busyTimeA = busyTimeB = xsprite[sprite[pXSector->marker0].extra].busyTime;
4111         waitTimeA = waitTimeB = xsprite[sprite[pXSector->marker0].extra].waitTime;
4112     }
4113 
4114     if (!pXSector->interruptable && event.cmd != kCmdSectorMotionContinue
4115         && ((!pXSector->state && pXSector->busy) || (pXSector->state && pXSector->busy != 65536))) {
4116 
4117             event.cmd = kCmdSectorMotionContinue;
4118 
4119     } else if (event.cmd == kCmdToggle) {
4120 
4121         event.cmd = (pXSector->state) ? kCmdOn : kCmdOff;
4122 
4123     }
4124 
4125     //viewSetSystemMessage("%d / %d", pXSector->busy, pXSector->state);
4126 
4127     int nDelta = 1;
4128     switch (event.cmd) {
4129         case kCmdOff:
4130             if (pXSector->busy == 0) {
4131                 if (pXSector->reTriggerB && waitTimeB) evPost(nSector, OBJ_SECTOR, (waitTimeB * 120) / 10, kCmdOff);
4132                 return;
4133             }
4134             pXSector->state = 1;
4135             nDelta = 65536 / ClipLow((busyTimeB * 120) / 10, 1);
4136             break;
4137         case kCmdOn:
4138             if (pXSector->busy == 65536) {
4139                 if (pXSector->reTriggerA && waitTimeA) evPost(nSector, OBJ_SECTOR, (waitTimeA * 120) / 10, kCmdOn);
4140                 return;
4141             }
4142             pXSector->state = 0;
4143             nDelta = 65536 / ClipLow((busyTimeA * 120) / 10, 1);
4144             break;
4145         case kCmdSectorMotionContinue:
4146             nDelta = 65536 / ClipLow((((pXSector->state) ? busyTimeB : busyTimeA) * 120) / 10, 1);
4147             break;
4148     }
4149 
4150     //bool crush = pXSector->Crush;
4151     int busyFunc = BUSYID_0;
4152     switch (sector[nSector].type) {
4153         case kSectorZMotion:
4154             busyFunc = BUSYID_2;
4155             break;
4156         case kSectorZMotionSprite:
4157             busyFunc = BUSYID_1;
4158             break;
4159         case kSectorSlideMarked:
4160         case kSectorSlide:
4161             busyFunc = BUSYID_3;
4162             break;
4163         case kSectorRotateMarked:
4164         case kSectorRotate:
4165             busyFunc = BUSYID_4;
4166             break;
4167         case kSectorRotateStep:
4168             busyFunc = BUSYID_5;
4169             break;
4170         case kSectorPath:
4171             busyFunc = BUSYID_7;
4172             break;
4173         default:
4174             ThrowError("Unsupported sector type %d", sector[nSector].type);
4175             break;
4176     }
4177 
4178     SectorStartSound(nSector, pXSector->state);
4179     nDelta = (pXSector->state) ? -nDelta : nDelta;
4180     gBusy[gBusyCount].at0 = nSector;
4181     gBusy[gBusyCount].at4 = nDelta;
4182     gBusy[gBusyCount].at8 = pXSector->busy;
4183     gBusy[gBusyCount].atc = (BUSYID)busyFunc;
4184     gBusyCount++;
4185     return;
4186 
4187 }
4188 
modernTypeOperateSector(int nSector,sectortype * pSector,XSECTOR * pXSector,EVENT event)4189 bool modernTypeOperateSector(int nSector, sectortype* pSector, XSECTOR* pXSector, EVENT event) {
4190 
4191     if (event.cmd >= kCmdLock && event.cmd <= kCmdToggleLock) {
4192 
4193         switch (event.cmd) {
4194             case kCmdLock:
4195                 pXSector->locked = 1;
4196                 break;
4197             case kCmdUnlock:
4198                 pXSector->locked = 0;
4199                 break;
4200             case kCmdToggleLock:
4201                 pXSector->locked = pXSector->locked ^ 1;
4202                 break;
4203         }
4204 
4205         switch (pSector->type) {
4206             case kSectorCounter:
4207                 if (pXSector->locked != 1) break;
4208                 SetSectorState(nSector, pXSector, 0);
4209                 evPost(nSector, 6, 0, kCallbackCounterCheck);
4210                 break;
4211         }
4212 
4213         return true;
4214 
4215     // continue motion of the paused sector
4216     } else if (pXSector->unused1) {
4217 
4218         switch (event.cmd) {
4219             case kCmdOff:
4220             case kCmdOn:
4221             case kCmdToggle:
4222             case kCmdSectorMotionContinue:
4223                 sectorContinueMotion(nSector, event);
4224                 return true;
4225         }
4226 
4227     // pause motion of the sector
4228     } else if (event.cmd == kCmdSectorMotionPause) {
4229 
4230         sectorPauseMotion(nSector);
4231         return true;
4232 
4233     }
4234 
4235     return false;
4236 
4237 }
4238 
modernTypeOperateSprite(int nSprite,spritetype * pSprite,XSPRITE * pXSprite,EVENT event)4239 bool modernTypeOperateSprite(int nSprite, spritetype* pSprite, XSPRITE* pXSprite, EVENT event) {
4240 
4241     if (event.cmd >= kCmdLock && event.cmd <= kCmdToggleLock) {
4242         switch (event.cmd) {
4243             case kCmdLock:
4244                 pXSprite->locked = 1;
4245                 break;
4246             case kCmdUnlock:
4247                 pXSprite->locked = 0;
4248                 break;
4249             case kCmdToggleLock:
4250                 pXSprite->locked = pXSprite->locked ^ 1;
4251                 break;
4252         }
4253 
4254         switch (pSprite->type) {
4255             case kModernCondition:
4256             case kModernConditionFalse:
4257                 pXSprite->restState = 0;
4258                 if (pXSprite->busyTime <= 0) break;
4259                 else if (!pXSprite->locked) pXSprite->busy = 0;
4260                 break;
4261         }
4262 
4263         return true;
4264     } else if (event.cmd == kCmdDudeFlagsSet) {
4265 
4266         if (event.type != OBJ_SPRITE) {
4267 
4268             viewSetSystemMessage("Only sprites could use command #%d", event.cmd);
4269             return true;
4270 
4271         } else if (xspriRangeIsFine(sprite[event.index].extra)) {
4272 
4273             XSPRITE* pXSource = &xsprite[sprite[event.index].extra];
4274             pXSprite->dudeFlag4 = pXSource->dudeFlag4;
4275             pXSprite->dudeAmbush = pXSource->dudeAmbush;
4276             pXSprite->dudeGuard = pXSource->dudeGuard;
4277             pXSprite->dudeDeaf = pXSource->dudeDeaf;
4278             pXSprite->unused1 = pXSource->unused1;
4279 
4280             if (pXSource->unused1 & kDudeFlagStealth) pXSprite->unused1 |= kDudeFlagStealth;
4281             else pXSprite->unused1 &= ~kDudeFlagStealth;
4282 
4283         }
4284 
4285     }
4286 
4287     if (pSprite->statnum == kStatDude && IsDudeSprite(pSprite)) {
4288 
4289         switch (event.cmd) {
4290             case kCmdOff:
4291                 if (pXSprite->state) SetSpriteState(nSprite, pXSprite, 0);
4292                 break;
4293             case kCmdOn:
4294                 if (!pXSprite->state) SetSpriteState(nSprite, pXSprite, 1);
4295                 if (IsPlayerSprite(pSprite) || pXSprite->health <= 0) break;
4296                 else if (pXSprite->aiState->stateType >= kAiStatePatrolBase && pXSprite->aiState->stateType < kAiStatePatrolMax)
4297                     break;
4298 
4299                 switch (pXSprite->aiState->stateType) {
4300                     case kAiStateIdle:
4301                     case kAiStateGenIdle:
4302                         aiActivateDude(pSprite, pXSprite);
4303                         break;
4304                 }
4305                 break;
4306             case kCmdDudeFlagsSet:
4307                 if (xspriRangeIsFine(sprite[event.index].extra)) {
4308                     if (!pXSprite->dudeFlag4) {
4309 
4310                         if (pXSprite->aiState->stateType < kAiStatePatrolBase || pXSprite->aiState->stateType >= kAiStatePatrolMax) break;
4311                         else aiPatrolStop(pSprite, -1);
4312 
4313                     } else {
4314 
4315                         if (pXSprite->aiState->stateType >= kAiStatePatrolBase && pXSprite->aiState->stateType < kAiStatePatrolMax) break;
4316                         else if (spriteIsUnderwater(pSprite)) aiPatrolState(pSprite, kAiStatePatrolWaitW);
4317                         else aiPatrolState(pSprite, kAiStatePatrolWaitL);
4318 
4319 
4320                         pXSprite->data3 = 0;
4321 
4322                     }
4323 
4324                     //viewSetSystemMessage("%d / %d / %d", pSprite->type, pXSprite->dudeFlag4, pXSprite->dudeGuard);
4325                 }
4326                 break;
4327             default:
4328                 if (!pXSprite->state) evPost(nSprite, 3, 0, kCmdOn);
4329                 else evPost(nSprite, 3, 0, kCmdOff);
4330                 break;
4331         }
4332 
4333         return true;
4334     }
4335 
4336     switch (pSprite->type) {
4337         default:
4338             return false; // no modern type found to work with, go normal OperateSprite();
4339         case kModernCondition:
4340         case kModernConditionFalse:
4341             if (!pXSprite->isTriggered) useCondition(pSprite, pXSprite, event);
4342             return true;
4343         // add spawn random dude feature - works only if at least 2 data fields are not empty.
4344         case kMarkerDudeSpawn:
4345             if (gGameOptions.nMonsterSettings && pXSprite->data1 >= kDudeBase && pXSprite->data1 < kDudeVanillaMax) {
4346 
4347                 spritetype* pSpawn = NULL;
4348                 if ((pSpawn = randomSpawnDude(pSprite)) == NULL
4349                     && (pSpawn = actSpawnDude(pSprite, pXSprite->data1, -1, 0)) == NULL) {
4350                         return true;
4351                 }
4352 
4353                 XSPRITE* pXSpawn = &xsprite[pSpawn->extra];
4354                 gKillMgr.sub_263E0(1);
4355                 if (IsBurningDude(pSpawn)) {
4356                     pXSpawn->health = getDudeInfo(pXSprite->data1)->startHealth << 4;
4357                     pXSpawn->burnTime = 10;
4358                     pXSpawn->target = -1;
4359                     if (!pXSpawn->dudeFlag4)
4360                         aiActivateDude(pSpawn, pXSpawn);
4361                 } else if ((pSprite->flags & kModernTypeFlag3) && !pXSpawn->dudeFlag4) {
4362                     aiActivateDude(pSpawn, pXSpawn);
4363                 }
4364 
4365             }
4366             return true;
4367         case kModernRandomTX: // random Event Switch takes random data field and uses it as TX ID
4368         case kModernSequentialTX: // sequential Switch takes values from data fields starting from data1 and uses it as TX ID
4369             if (pXSprite->command == kCmdLink) return true; // work as event redirector
4370             switch (pSprite->type) {
4371                 case kModernRandomTX:
4372                     useRandomTx(pXSprite, (COMMAND_ID)pXSprite->command, true);
4373                     break;
4374                 case kModernSequentialTX:
4375                     if (!(pSprite->flags & kModernTypeFlag1)) useSequentialTx(pXSprite, (COMMAND_ID)pXSprite->command, true);
4376                     else seqTxSendCmdAll(pXSprite, pSprite->index, (COMMAND_ID)pXSprite->command, false);
4377                     break;
4378             }
4379             return true;
4380         case kModernSpriteDamager:
4381             switch (event.cmd) {
4382                 case kCmdOff:
4383                     if (pXSprite->state == 1) SetSpriteState(nSprite, pXSprite, 0);
4384                     break;
4385                 case kCmdOn:
4386                     evKill(nSprite, 3); // queue overflow protect
4387                     if (pXSprite->state == 0) SetSpriteState(nSprite, pXSprite, 1);
4388                     fallthrough__;
4389                 case kCmdRepeat:
4390                     if (pXSprite->txID > 0) modernTypeSendCommand(nSprite, pXSprite->txID, (COMMAND_ID)pXSprite->command);
4391                     else if (pXSprite->data1 == 0 && sectRangeIsFine(pSprite->sectnum)) useSpriteDamager(pXSprite, OBJ_SECTOR, pSprite->sectnum);
4392                     else if (pXSprite->data1 >= 666 && pXSprite->data1 < 669) useSpriteDamager(pXSprite, -1, -1);
4393                     else {
4394 
4395                         PLAYER* pPlayer = getPlayerById(pXSprite->data1);
4396                         if (pPlayer != NULL)
4397                             useSpriteDamager(pXSprite, OBJ_SPRITE, pPlayer->pSprite->index);
4398                     }
4399 
4400                     if (pXSprite->busyTime > 0)
4401                         evPost(nSprite, 3, pXSprite->busyTime, kCmdRepeat);
4402                     break;
4403                 default:
4404                     if (pXSprite->state == 0) evPost(nSprite, 3, 0, kCmdOn);
4405                     else evPost(nSprite, 3, 0, kCmdOff);
4406                     break;
4407             }
4408             return true;
4409         case kMarkerWarpDest:
4410             if (pXSprite->txID <= 0) {
4411 
4412                 PLAYER* pPlayer = getPlayerById(pXSprite->data1);
4413                 if (pPlayer != NULL && SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1) == 1)
4414                     useTeleportTarget(pXSprite, pPlayer->pSprite);
4415                 return true;
4416             }
4417             fallthrough__;
4418         case kModernObjPropertiesChanger:
4419             if (pXSprite->txID <= 0) {
4420                 if (SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1) == 1)
4421                     usePropertiesChanger(pXSprite, -1, -1);
4422                 return true;
4423             }
4424             fallthrough__;
4425         case kModernSlopeChanger:
4426         case kModernObjSizeChanger:
4427         case kModernObjPicnumChanger:
4428         case kModernSectorFXChanger:
4429         case kModernObjDataChanger:
4430             modernTypeSetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1);
4431             return true;
4432         case kModernCustomDudeSpawn:
4433             if (gGameOptions.nMonsterSettings && genDudeSpawn(pSprite, -1) != NULL) gKillMgr.sub_263E0(1);
4434             return true;
4435         case kModernSeqSpawner:
4436         case kModernEffectSpawner:
4437             switch (event.cmd) {
4438                 case kCmdOff:
4439                     if (pXSprite->state == 1) SetSpriteState(nSprite, pXSprite, 0);
4440                     break;
4441                 case kCmdOn:
4442                     evKill(nSprite, 3); // queue overflow protect
4443                     if (pXSprite->state == 0) SetSpriteState(nSprite, pXSprite, 1);
4444                     if (pSprite->type == kModernSeqSpawner) seqSpawnerOffSameTx(pXSprite);
4445                     fallthrough__;
4446                 case kCmdRepeat:
4447                     if (pXSprite->txID > 0) modernTypeSendCommand(nSprite, pXSprite->txID, (COMMAND_ID)pXSprite->command);
4448                     else if (pSprite->type == kModernSeqSpawner) useSeqSpawnerGen(pXSprite, 3, pSprite->index);
4449                     else useEffectGen(pXSprite, NULL);
4450 
4451                     if (pXSprite->busyTime > 0)
4452                         evPost(nSprite, 3, ClipLow((int(pXSprite->busyTime) + Random2(pXSprite->data1)) * 120 / 10, 0), kCmdRepeat);
4453                     break;
4454                 default:
4455                     if (pXSprite->state == 0) evPost(nSprite, 3, 0, kCmdOn);
4456                     else evPost(nSprite, 3, 0, kCmdOff);
4457                     break;
4458             }
4459             return true;
4460         case kModernWindGenerator:
4461             switch (event.cmd) {
4462                 case kCmdOff:
4463                     windGenStopWindOnSectors(pXSprite);
4464                     if (pXSprite->state == 1) SetSpriteState(nSprite, pXSprite, 0);
4465                     break;
4466                 case kCmdOn:
4467                     evKill(nSprite, 3); // queue overflow protect
4468                     if (pXSprite->state == 0) SetSpriteState(nSprite, pXSprite, 1);
4469                     fallthrough__;
4470                 case kCmdRepeat:
4471                     if (pXSprite->txID > 0) modernTypeSendCommand(nSprite, pXSprite->txID, (COMMAND_ID)pXSprite->command);
4472                     else useSectorWindGen(pXSprite, NULL);
4473 
4474                     if (pXSprite->busyTime > 0) evPost(nSprite, 3, pXSprite->busyTime, kCmdRepeat);
4475                     break;
4476                 default:
4477                     if (pXSprite->state == 0) evPost(nSprite, 3, 0, kCmdOn);
4478                     else evPost(nSprite, 3, 0, kCmdOff);
4479                     break;
4480             }
4481             return true;
4482         case kModernDudeTargetChanger:
4483 
4484             // this one is required if data4 of generator was dynamically changed
4485             // it turns monsters in normal idle state instead of genIdle, so they not ignore the world.
4486             if (pXSprite->dropMsg == 3 && 3 != pXSprite->data4)
4487                 aiFightActivateDudes(pXSprite->txID);
4488 
4489             switch (event.cmd) {
4490                 case kCmdOff:
4491                     if (pXSprite->data4 == 3) aiFightActivateDudes(pXSprite->txID);
4492                     if (pXSprite->state == 1) SetSpriteState(nSprite, pXSprite, 0);
4493                     break;
4494                 case kCmdOn:
4495                     evKill(nSprite, 3); // queue overflow protect
4496                     if (pXSprite->state == 0) SetSpriteState(nSprite, pXSprite, 1);
4497                     fallthrough__;
4498                 case kCmdRepeat:
4499                     if (pXSprite->txID <= 0 || !aiFightGetDudesForBattle(pXSprite)) {
4500                         aiFightFreeAllTargets(pXSprite);
4501                         evPost(nSprite, 3, 0, kCmdOff);
4502                         break;
4503                     } else {
4504                         modernTypeSendCommand(nSprite, pXSprite->txID, (COMMAND_ID)pXSprite->command);
4505                     }
4506 
4507                     if (pXSprite->busyTime > 0) evPost(nSprite, 3, pXSprite->busyTime, kCmdRepeat);
4508                     break;
4509                 default:
4510                     if (pXSprite->state == 0) evPost(nSprite, 3, 0, kCmdOn);
4511                     else evPost(nSprite, 3, 0, kCmdOff);
4512                     break;
4513             }
4514             pXSprite->dropMsg = pXSprite->data4;
4515             return true;
4516         case kModernObjDataAccumulator:
4517             switch (event.cmd) {
4518                 case kCmdOff:
4519                     if (pXSprite->state == 1) SetSpriteState(nSprite, pXSprite, 0);
4520                     break;
4521                 case kCmdOn:
4522                     evKill(nSprite, 3); // queue overflow protect
4523                     if (pXSprite->state == 0) SetSpriteState(nSprite, pXSprite, 1);
4524                     fallthrough__;
4525                 case kCmdRepeat:
4526                     // force OFF after *all* TX objects reach the goal value
4527                     if (pSprite->flags == kModernTypeFlag0 && incDecGoalValueIsReached(pXSprite)) {
4528                         evPost(nSprite, 3, 0, kCmdOff);
4529                         break;
4530                     }
4531 
4532                     modernTypeSendCommand(nSprite, pXSprite->txID, (COMMAND_ID)pXSprite->command);
4533                     if (pXSprite->busyTime > 0) evPost(nSprite, 3, pXSprite->busyTime, kCmdRepeat);
4534                     break;
4535                 default:
4536                     if (pXSprite->state == 0) evPost(nSprite, 3, 0, kCmdOn);
4537                     else evPost(nSprite, 3, 0, kCmdOff);
4538                     break;
4539             }
4540             return true;
4541         case kModernRandom:
4542         case kModernRandom2:
4543             switch (event.cmd) {
4544                 case kCmdOff:
4545                     if (pXSprite->state == 1) SetSpriteState(nSprite, pXSprite, 0);
4546                     break;
4547                 case kCmdOn:
4548                     evKill(nSprite, 3); // queue overflow protect
4549                     if (pXSprite->state == 0) SetSpriteState(nSprite, pXSprite, 1);
4550                     fallthrough__;
4551                 case kCmdRepeat:
4552                     useRandomItemGen(pSprite, pXSprite);
4553                     if (pXSprite->busyTime > 0)
4554                         evPost(nSprite, 3, (120 * pXSprite->busyTime) / 10, kCmdRepeat);
4555                     break;
4556                 default:
4557                     if (pXSprite->state == 0) evPost(nSprite, 3, 0, kCmdOn);
4558                     else evPost(nSprite, 3, 0, kCmdOff);
4559                     break;
4560             }
4561             return true;
4562         case kModernThingTNTProx:
4563             if (pSprite->statnum != kStatRespawn) {
4564                 switch (event.cmd) {
4565                 case kCmdSpriteProximity:
4566                     if (pXSprite->state) break;
4567                     sfxPlay3DSound(pSprite, 452, 0, 0);
4568                     evPost(nSprite, 3, 30, kCmdOff);
4569                     pXSprite->state = 1;
4570                     fallthrough__;
4571                 case kCmdOn:
4572                     sfxPlay3DSound(pSprite, 451, 0, 0);
4573                     pXSprite->Proximity = 1;
4574                     break;
4575                 default:
4576                     actExplodeSprite(pSprite);
4577                     break;
4578                 }
4579             }
4580             return true;
4581         case kModernThingEnemyLifeLeech:
4582             dudeLeechOperate(pSprite, pXSprite, event);
4583             return true;
4584         case kModernPlayerControl: { // WIP
4585             PLAYER* pPlayer = NULL; int cmd = (event.cmd >= kCmdNumberic) ? event.cmd : pXSprite->command;
4586             if ((pPlayer = getPlayerById(pXSprite->data1)) == NULL
4587                     || ((cmd < 67 || cmd > 68) && !modernTypeSetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1)))
4588                         return true;
4589 
4590             TRPLAYERCTRL* pCtrl = &gPlayerCtrl[pPlayer->nPlayer];
4591 
4592             /// !!! COMMANDS OF THE CURRENT SPRITE, NOT OF THE EVENT !!! ///
4593             if ((cmd -= kCmdNumberic) < 0) return true;
4594             else if (pPlayer->pXSprite->health <= 0) {
4595 
4596                 switch (cmd) {
4597                     case 36:
4598                         actHealDude(pPlayer->pXSprite, ((pXSprite->data2 > 0) ? ClipHigh(pXSprite->data2, 200) : getDudeInfo(pPlayer->pSprite->type)->startHealth), 200);
4599                         pPlayer->curWeapon = 1;
4600                         break;
4601                 }
4602 
4603                 return true;
4604 
4605             }
4606 
4607             switch (cmd) {
4608                 case 0: // 64 (player life form)
4609                     if (pXSprite->data2 < kModeHuman || pXSprite->data2 > kModeHumanGrown) break;
4610                     else trPlayerCtrlSetRace(pXSprite, pPlayer);
4611                     break;
4612                 case 1: // 65 (move speed and jump height)
4613                     // player movement speed (for all races and postures)
4614                     if (valueIsBetween(pXSprite->data2, -1, 32767))
4615                         trPlayerCtrlSetMoveSpeed(pXSprite, pPlayer);
4616 
4617                     // player jump height (for all races and stand posture only)
4618                     if (valueIsBetween(pXSprite->data3, -1, 32767))
4619                         trPlayerCtrlSetJumpHeight(pXSprite, pPlayer);
4620                     break;
4621                 case 2: // 66 (player screen effects)
4622                     if (pXSprite->data3 < 0) break;
4623                     else trPlayerCtrlSetScreenEffect(pXSprite, pPlayer);
4624                     break;
4625                 case 3: // 67 (start playing qav scene)
4626                     trPlayerCtrlStartScene(pXSprite, pPlayer, (pXSprite->data4 == 1) ? true : false);
4627                     break;
4628                 case 4: // 68 (stop playing qav scene)
4629                     if (pXSprite->data2 > 0 && pXSprite->data2 != pPlayer->sceneQav) break;
4630                     else trPlayerCtrlStopScene(pPlayer);
4631                     break;
4632                 case 5: // 69 (set player look angle, TO-DO: if tx > 0, take a look on TX ID sprite)
4633                     //data4 is reserved
4634                     if (pXSprite->data4 != 0) break;
4635                     else if (valueIsBetween(pXSprite->data2, -128, 128))
4636                         trPlayerCtrlSetLookAngle(pXSprite, pPlayer);
4637                     break;
4638                 case 6: // 70 (erase player stuff...)
4639                     if (pXSprite->data2 < 0) break;
4640                     else trPlayerCtrlEraseStuff(pXSprite, pPlayer);
4641                     break;
4642                 case 7: // 71 (give something to player...)
4643                     if (pXSprite->data2 <= 0) break;
4644                     else trPlayerCtrlGiveStuff(pXSprite, pPlayer, pCtrl);
4645                     break;
4646                 case 8: // 72 (use inventory item)
4647                     if (pXSprite->data2 < 1 || pXSprite->data2 > 5) break;
4648                     else trPlayerCtrlUsePackItem(pXSprite, pPlayer, event.cmd);
4649                     break;
4650                 case 9: // 73 (set player's sprite angle, TO-DO: if tx > 0, take a look on TX ID sprite)
4651                     //data4 is reserved
4652                     if (pXSprite->data4 != 0) break;
4653                     else if (pSprite->flags & kModernTypeFlag1) pPlayer->q16ang = fix16_from_int(pSprite->ang);
4654                     else if (valueIsBetween(pXSprite->data2, -kAng360, kAng360)) pPlayer->q16ang = fix16_from_int(pXSprite->data2);
4655                     break;
4656                 case 10: // 74 (print the book)
4657                     // data2: RFF TXT id
4658                     // data3: background tile
4659                     // data4: font base tile
4660                     // pal: font / background palette
4661                     // hitag:
4662                     // d1: 0: print whole text at a time, 1: print line by line, 2: word by word, 3: letter by letter
4663                     // d2: 1: force pause the game (sp only)
4664                     // d3: 1: inherit palette for font, 2: inherit palette for background, 3: both
4665                     // busyTime: speed of word/letter/line printing
4666                     // waitTime: if TX ID > 0 and TX ID object is book reader, trigger it?
4667                     break;
4668 
4669             }
4670         }
4671         return true;
4672         case kGenModernSound:
4673             switch (event.cmd) {
4674             case kCmdOff:
4675                 if (pXSprite->state == 1) SetSpriteState(nSprite, pXSprite, 0);
4676                 break;
4677             case kCmdOn:
4678                 evKill(nSprite, 3); // queue overflow protect
4679                 if (pXSprite->state == 0) SetSpriteState(nSprite, pXSprite, 1);
4680                 fallthrough__;
4681             case kCmdRepeat:
4682                 if (pXSprite->txID)  modernTypeSendCommand(nSprite, pXSprite->txID, (COMMAND_ID)pXSprite->command);
4683                 else useSoundGen(pXSprite, pSprite);
4684 
4685                 if (pXSprite->busyTime > 0)
4686                     evPost(nSprite, 3, (120 * pXSprite->busyTime) / 10, kCmdRepeat);
4687                 break;
4688             default:
4689                 if (pXSprite->state == 0) evPost(nSprite, 3, 0, kCmdOn);
4690                 else evPost(nSprite, 3, 0, kCmdOff);
4691                 break;
4692             }
4693             return true;
4694         case kGenModernMissileUniversal:
4695             switch (event.cmd) {
4696                 case kCmdOff:
4697                     if (pXSprite->state == 1) SetSpriteState(nSprite, pXSprite, 0);
4698                     break;
4699                 case kCmdOn:
4700                     evKill(nSprite, 3); // queue overflow protect
4701                     if (pXSprite->state == 0) SetSpriteState(nSprite, pXSprite, 1);
4702                     fallthrough__;
4703                 case kCmdRepeat:
4704                     useUniMissileGen(3, pSprite->extra);
4705                     if (pXSprite->txID) evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command);
4706                     if (pXSprite->busyTime > 0) evPost(nSprite, 3, (120 * pXSprite->busyTime) / 10, kCmdRepeat);
4707                     break;
4708                 default:
4709                     if (pXSprite->state == 0) evPost(nSprite, 3, 0, kCmdOn);
4710                     else evPost(nSprite, 3, 0, kCmdOff);
4711                     break;
4712             }
4713             return true;
4714     }
4715 }
4716 
modernTypeOperateWall(int nWall,walltype * pWall,XWALL * pXWall,EVENT event)4717 bool modernTypeOperateWall(int nWall, walltype* pWall, XWALL* pXWall, EVENT event) {
4718 
4719     switch (pWall->type) {
4720         case kSwitchOneWay:
4721             switch (event.cmd) {
4722                 case kCmdOff:
4723                     SetWallState(nWall, pXWall, 0);
4724                     break;
4725                 case kCmdOn:
4726                     SetWallState(nWall, pXWall, 1);
4727                     break;
4728                 default:
4729                     SetWallState(nWall, pXWall, pXWall->restState ^ 1);
4730                     break;
4731             }
4732             return true;
4733         default:
4734             return false; // no modern type found to work with, go normal OperateWall();
4735     }
4736 
4737 }
4738 
txIsRanged(XSPRITE * pXSource)4739 bool txIsRanged(XSPRITE* pXSource) {
4740     if (pXSource->data1 > 0 && pXSource->data2 <= 0 && pXSource->data3 <= 0 && pXSource->data4 > 0) {
4741         if (pXSource->data1 > pXSource->data4) {
4742             // data1 must be less than data4
4743             int tmp = pXSource->data1; pXSource->data1 = pXSource->data4;
4744             pXSource->data4 = tmp;
4745         }
4746         return true;
4747     }
4748     return false;
4749 }
4750 
seqTxSendCmdAll(XSPRITE * pXSource,int nIndex,COMMAND_ID cmd,bool modernSend)4751 void seqTxSendCmdAll(XSPRITE* pXSource, int nIndex, COMMAND_ID cmd, bool modernSend) {
4752 
4753     bool ranged = txIsRanged(pXSource);
4754     if (ranged) {
4755         for (pXSource->txID = pXSource->data1; pXSource->txID <= pXSource->data4; pXSource->txID++) {
4756             if (pXSource->txID <= 0 || pXSource->txID >= kChannelUserMax) continue;
4757             else if (!modernSend) evSend(nIndex, 3, pXSource->txID, cmd);
4758             else modernTypeSendCommand(nIndex, pXSource->txID, cmd);
4759         }
4760     } else {
4761         for (int i = 0; i <= 3; i++) {
4762             pXSource->txID = GetDataVal(&sprite[pXSource->reference], i);
4763             if (pXSource->txID <= 0 || pXSource->txID >= kChannelUserMax) continue;
4764             else if (!modernSend) evSend(nIndex, 3, pXSource->txID, cmd);
4765             else modernTypeSendCommand(nIndex, pXSource->txID, cmd);
4766         }
4767     }
4768 
4769     pXSource->txID = pXSource->sysData1 = 0;
4770     return;
4771 }
4772 
useRandomTx(XSPRITE * pXSource,COMMAND_ID cmd,bool setState)4773 void useRandomTx(XSPRITE* pXSource, COMMAND_ID cmd, bool setState) {
4774 
4775     UNREFERENCED_PARAMETER(cmd);
4776 
4777     spritetype* pSource = &sprite[pXSource->reference];
4778     int tx = 0; int maxRetries = kMaxRandomizeRetries;
4779 
4780     if (txIsRanged(pXSource)) {
4781         while (maxRetries-- >= 0) {
4782             if ((tx = nnExtRandom(pXSource->data1, pXSource->data4)) != pXSource->txID)
4783                 break;
4784         }
4785     } else {
4786         while (maxRetries-- >= 0) {
4787             if ((tx = randomGetDataValue(pXSource, kRandomizeTX)) > 0 && tx != pXSource->txID)
4788                 break;
4789         }
4790     }
4791 
4792     pXSource->txID = (tx > 0 && tx < kChannelUserMax) ? tx : 0;
4793     if (setState)
4794         SetSpriteState(pSource->index, pXSource, pXSource->state ^ 1);
4795         //evSend(pSource->index, OBJ_SPRITE, pXSource->txID, (COMMAND_ID)pXSource->command);
4796 }
4797 
useSequentialTx(XSPRITE * pXSource,COMMAND_ID cmd,bool setState)4798 void useSequentialTx(XSPRITE* pXSource, COMMAND_ID cmd, bool setState) {
4799 
4800     spritetype* pSource = &sprite[pXSource->reference];
4801     bool range = txIsRanged(pXSource); int cnt = 3; int tx = 0;
4802 
4803     if (range) {
4804 
4805         // make sure sysData is correct as we store current index of TX ID here.
4806         if (pXSource->sysData1 < pXSource->data1) pXSource->sysData1 = pXSource->data1;
4807         else if (pXSource->sysData1 > pXSource->data4) pXSource->sysData1 = pXSource->data4;
4808 
4809     } else {
4810 
4811         // make sure sysData is correct as we store current index of data field here.
4812         if (pXSource->sysData1 > 3) pXSource->sysData1 = 0;
4813         else if (pXSource->sysData1 < 0) pXSource->sysData1 = 3;
4814 
4815     }
4816 
4817     switch (cmd) {
4818         case kCmdOff:
4819             if (!range) {
4820                 while (cnt-- >= 0) { // skip empty data fields
4821                     if (pXSource->sysData1-- < 0) pXSource->sysData1 = 3;
4822                     if ((tx = GetDataVal(pSource, pXSource->sysData1)) <= 0) continue;
4823                     else break;
4824                 }
4825             } else {
4826                 if (--pXSource->sysData1 < pXSource->data1) pXSource->sysData1 = pXSource->data4;
4827                 tx = pXSource->sysData1;
4828             }
4829             break;
4830         default:
4831             if (!range) {
4832                 while (cnt-- >= 0) { // skip empty data fields
4833                     if (pXSource->sysData1 > 3) pXSource->sysData1 = 0;
4834                     if ((tx = GetDataVal(pSource, pXSource->sysData1++)) <= 0) continue;
4835                     else break;
4836                 }
4837             } else {
4838                 tx = pXSource->sysData1;
4839                 if (pXSource->sysData1 >= pXSource->data4) {
4840                     pXSource->sysData1 = pXSource->data1;
4841                     break;
4842                 }
4843                 pXSource->sysData1++;
4844             }
4845             break;
4846     }
4847 
4848     pXSource->txID = (tx > 0 && tx < kChannelUserMax) ? tx : 0;
4849     if (setState)
4850         SetSpriteState(pSource->index, pXSource, pXSource->state ^ 1);
4851         //evSend(pSource->index, OBJ_SPRITE, pXSource->txID, (COMMAND_ID)pXSource->command);
4852 }
4853 
useCondition(spritetype * pSource,XSPRITE * pXSource,EVENT event)4854 int useCondition(spritetype* pSource, XSPRITE* pXSource, EVENT event) {
4855 
4856     int objType = event.type; int objIndex = event.index;
4857     bool srcIsCondition = false;
4858     if (objType == OBJ_SPRITE && objIndex != pSource->index)
4859         srcIsCondition = (sprite[objIndex].type == kModernCondition || sprite[objIndex].type == kModernConditionFalse);
4860 
4861     // if it's a tracking condition, it must ignore all the commands sent from objects
4862     if (pXSource->busyTime > 0 && event.funcID != kCallbackMax) return -1;
4863     else if (!srcIsCondition) { // save object serials in the stack and make copy of initial object
4864 
4865         pXSource->targetX = pXSource->targetY = condSerialize(objType, objIndex);
4866 
4867     } else { // or grab serials of objects from previous conditions
4868 
4869         pXSource->targetX = xsprite[sprite[objIndex].extra].targetX;
4870         pXSource->targetY = xsprite[sprite[objIndex].extra].targetY;
4871 
4872     }
4873 
4874     int cond = pXSource->data1; bool ok = false; bool RVRS = (pSource->type == kModernConditionFalse);
4875     bool RSET = (pXSource->command == kCmdNumberic + 36); bool PUSH = (pXSource->command == kCmdNumberic);
4876     int comOp = pSource->cstat; // comparison operator
4877 
4878     if (pXSource->restState == 0) {
4879 
4880         if (cond == 0) ok = true; // dummy
4881         else if (cond >= kCondMixedBase && cond < kCondMixedMax) ok = condCheckMixed(pXSource, event, comOp, PUSH);
4882         else if (cond >= kCondWallBase && cond < kCondWallMax) ok = condCheckWall(pXSource, comOp, PUSH);
4883         else if (cond >= kCondSectorBase && cond < kCondSectorMax) ok = condCheckSector(pXSource, comOp, PUSH);
4884         else if (cond >= kCondPlayerBase && cond < kCondPlayerMax) ok = condCheckPlayer(pXSource, comOp, PUSH);
4885         else if (cond >= kCondDudeBase && cond < kCondDudeMax) ok = condCheckDude(pXSource, comOp, PUSH);
4886         else if (cond >= kCondSpriteBase && cond < kCondSpriteMax) ok = condCheckSprite(pXSource, comOp, PUSH);
4887         else condError(pXSource,"Unexpected condition id %d!", cond);
4888 
4889         pXSource->state = (ok ^ RVRS);
4890 
4891         if (pXSource->waitTime > 0 && pXSource->state > 0) {
4892 
4893             pXSource->restState = 1;
4894             evKill(pSource->index, OBJ_SPRITE);
4895             evPost(pSource->index, OBJ_SPRITE, (pXSource->waitTime * 120) / 10, kCmdRepeat);
4896             return -1;
4897 
4898         }
4899 
4900     } else if (event.cmd == kCmdRepeat) {
4901 
4902         pXSource->restState = 0;
4903 
4904     } else {
4905 
4906         return -1;
4907 
4908     }
4909 
4910     // IF
4911     if (pXSource->state) {
4912 
4913         pXSource->isTriggered = (pXSource->triggerOnce) ? true : false;
4914 
4915         if (RSET) condRestore(pXSource); // reset focus to the initial object
4916 
4917         // send command to rx bucket
4918         if (pXSource->txID)
4919             evSend(pSource->index, OBJ_SPRITE, pXSource->txID, (COMMAND_ID)pXSource->command);
4920 
4921         if (pSource->flags) {
4922 
4923             // send it for object currently in the focus
4924             if (pSource->flags & kModernTypeFlag1) {
4925                 condUnserialize(pXSource->targetX, &objType, &objIndex);
4926                 nnExtTriggerObject(objType, objIndex, pXSource->command);
4927             }
4928 
4929             // send it for initial object
4930             if ((pSource->flags & kModernTypeFlag2) && (pXSource->targetX != pXSource->targetY || !(pSource->hitag & kModernTypeFlag1))) {
4931                 condUnserialize(pXSource->targetY, &objType, &objIndex);
4932                 nnExtTriggerObject(objType, objIndex, pXSource->command);
4933             }
4934 
4935         }
4936 
4937     // ELSE
4938     } else if (pXSource->sysData2 >= 0) {
4939 
4940         pSource = &sprite[pXSource->sysData2]; pXSource = &xsprite[pSource->extra];
4941         useCondition(pSource, pXSource, event);
4942 
4943         if (pXSource->isTriggered) pXSource->sysData2 = -1;
4944 
4945     }
4946 
4947     return pXSource->state;
4948 }
4949 
useRandomItemGen(spritetype * pSource,XSPRITE * pXSource)4950 void useRandomItemGen(spritetype* pSource, XSPRITE* pXSource) {
4951     // let's first search for previously dropped items and remove it
4952     if (pXSource->dropMsg > 0) {
4953         for (short nItem = headspritestat[kStatItem]; nItem >= 0; nItem = nextspritestat[nItem]) {
4954             spritetype* pItem = &sprite[nItem];
4955             if ((unsigned int)pItem->type == pXSource->dropMsg && pItem->x == pSource->x && pItem->y == pSource->y && pItem->z == pSource->z) {
4956                 gFX.fxSpawn((FX_ID)29, pSource->sectnum, pSource->x, pSource->y, pSource->z, 0);
4957                 pItem->type = kSpriteDecoration;
4958                 actPostSprite(nItem, kStatFree);
4959                 break;
4960             }
4961         }
4962     }
4963 
4964     // then drop item
4965     spritetype* pDrop = randomDropPickupObject(pSource, pXSource->dropMsg);
4966 
4967     // check if generator affected by physics
4968     if (pDrop != NULL && debrisGetIndex(pSource->index) != -1 && (pDrop->extra >= 0 || dbInsertXSprite(pDrop->index) > 0)) {
4969         int nIndex = debrisGetFreeIndex();
4970         if (nIndex >= 0) {
4971             xsprite[pDrop->extra].physAttr |= kPhysMove | kPhysGravity | kPhysFalling; // must fall always
4972             pSource->cstat &= ~CSTAT_SPRITE_BLOCK;
4973 
4974             gPhysSpritesList[nIndex] = pDrop->index;
4975             if (nIndex >= gPhysSpritesCount) gPhysSpritesCount++;
4976             getSpriteMassBySize(pDrop); // create mass cache
4977         }
4978     }
4979 }
4980 
useUniMissileGen(int,int nXSprite)4981 void useUniMissileGen(int, int nXSprite) {
4982 
4983     XSPRITE* pXSprite = &xsprite[nXSprite]; int dx = 0, dy = 0, dz = 0;
4984     spritetype* pSprite = &sprite[pXSprite->reference];
4985 
4986     if (pXSprite->data1 < kMissileBase || pXSprite->data1 >= kMissileMax)
4987         return;
4988 
4989     if (pSprite->cstat & 32) {
4990         if (pSprite->cstat & 8) dz = 0x4000;
4991         else dz = -0x4000;
4992     } else {
4993         dx = Cos(pSprite->ang) >> 16;
4994         dy = Sin(pSprite->ang) >> 16;
4995         dz = pXSprite->data3 << 6; // add slope controlling
4996         if (dz > 0x10000) dz = 0x10000;
4997         else if (dz < -0x10000) dz = -0x10000;
4998     }
4999 
5000     spritetype* pMissile = NULL;
5001     pMissile = actFireMissile(pSprite, 0, 0, dx, dy, dz, pXSprite->data1);
5002     if (pMissile != NULL) {
5003 
5004         // inherit some properties of the generator
5005         if (pSprite->flags & kModernTypeFlag1) {
5006 
5007             pMissile->xrepeat = pSprite->xrepeat;
5008             pMissile->yrepeat = pSprite->yrepeat;
5009 
5010             pMissile->pal = pSprite->pal;
5011             pMissile->shade = pSprite->shade;
5012 
5013         }
5014 
5015         // add velocity controlling
5016         if (pXSprite->data2 > 0) {
5017 
5018             int velocity = pXSprite->data2 << 12;
5019             xvel[pMissile->index] = mulscale(velocity, dx, 14);
5020             yvel[pMissile->index] = mulscale(velocity, dy, 14);
5021             zvel[pMissile->index] = mulscale(velocity, dz, 14);
5022 
5023         }
5024 
5025         // add bursting for missiles
5026         if (pMissile->type != kMissileFlareAlt && pXSprite->data4 > 0)
5027             evPost(pMissile->index, 3, ClipHigh(pXSprite->data4, 500), kCallbackMissileBurst);
5028 
5029     }
5030 
5031 }
5032 
useSoundGen(XSPRITE * pXSource,spritetype * pSprite)5033 void useSoundGen(XSPRITE* pXSource, spritetype* pSprite) {
5034     //spritetype* pSource = &sprite[pXSource->reference];
5035     int pitch = pXSource->data4 << 1; if (pitch < 2000) pitch = 0;
5036     sfxPlay3DSoundCP(pSprite, pXSource->data2, -1, 0, pitch, pXSource->data3);
5037 }
5038 
useIncDecGen(XSPRITE * pXSource,short objType,int objIndex)5039 void useIncDecGen(XSPRITE* pXSource, short objType, int objIndex) {
5040     char buffer[5]; int data = -65535; short tmp = 0; int dataIndex = 0;
5041     sprintf(buffer, "%d", abs(pXSource->data1)); int len = strlen(buffer);
5042 
5043     for (int i = 0; i < len; i++) {
5044         dataIndex = (buffer[i] - 52) + 4;
5045         if ((data = getDataFieldOfObject(objType, objIndex, dataIndex)) == -65535) {
5046             consoleSysMsg("\nWrong index of data (%c) for IncDec Gen #%d! Only 1, 2, 3 and 4 indexes allowed!\n", buffer[i], objIndex);
5047             continue;
5048         }
5049         spritetype* pSource = &sprite[pXSource->reference];
5050 
5051         if (pXSource->data2 < pXSource->data3) {
5052 
5053             data = ClipRange(data, pXSource->data2, pXSource->data3);
5054             if ((data += pXSource->data4) >= pXSource->data3) {
5055                 switch (pSource->flags) {
5056                 case kModernTypeFlag0:
5057                 case kModernTypeFlag1:
5058                     if (data > pXSource->data3) data = pXSource->data3;
5059                     break;
5060                 case kModernTypeFlag2:
5061                     if (data > pXSource->data3) data = pXSource->data3;
5062                     if (!incDecGoalValueIsReached(pXSource)) break;
5063                     tmp = pXSource->data3;
5064                     pXSource->data3 = pXSource->data2;
5065                     pXSource->data2 = tmp;
5066                     break;
5067                 case kModernTypeFlag3:
5068                     if (data > pXSource->data3) data = pXSource->data2;
5069                     break;
5070                 }
5071             }
5072 
5073         } else if (pXSource->data2 > pXSource->data3) {
5074 
5075             data = ClipRange(data, pXSource->data3, pXSource->data2);
5076             if ((data -= pXSource->data4) <= pXSource->data3) {
5077                 switch (pSource->flags) {
5078                 case kModernTypeFlag0:
5079                 case kModernTypeFlag1:
5080                     if (data < pXSource->data3) data = pXSource->data3;
5081                     break;
5082                 case kModernTypeFlag2:
5083                     if (data < pXSource->data3) data = pXSource->data3;
5084                     if (!incDecGoalValueIsReached(pXSource)) break;
5085                     tmp = pXSource->data3;
5086                     pXSource->data3 = pXSource->data2;
5087                     pXSource->data2 = tmp;
5088                     break;
5089                 case kModernTypeFlag3:
5090                     if (data < pXSource->data3) data = pXSource->data2;
5091                     break;
5092                 }
5093             }
5094         }
5095 
5096         pXSource->sysData1 = data;
5097         setDataValueOfObject(objType, objIndex, dataIndex, data);
5098     }
5099 
5100 }
5101 
5102 
sprite2sectorSlope(spritetype * pSprite,sectortype * pSector,char rel,bool forcez)5103 void sprite2sectorSlope(spritetype* pSprite, sectortype* pSector, char rel, bool forcez) {
5104 
5105     int slope = 0, z = 0;
5106     switch (rel) {
5107         default:
5108             z = getflorzofslope(pSprite->sectnum, pSprite->x, pSprite->y);
5109             if ((pSprite->cstat & CSTAT_SPRITE_ALIGNMENT_FLOOR) && pSprite->extra > 0 && xsprite[pSprite->extra].Touch) z--;
5110             slope = pSector->floorheinum;
5111             break;
5112         case 1:
5113             z = getceilzofslope(pSprite->sectnum, pSprite->x, pSprite->y);
5114             if ((pSprite->cstat & CSTAT_SPRITE_ALIGNMENT_FLOOR) && pSprite->extra > 0 && xsprite[pSprite->extra].Touch) z++;
5115             slope = pSector->ceilingheinum;
5116             break;
5117     }
5118 
5119     spriteSetSlope(pSprite->index, slope);
5120     if (forcez) pSprite->z = z;
5121 }
5122 
useSlopeChanger(XSPRITE * pXSource,int objType,int objIndex)5123 void useSlopeChanger(XSPRITE* pXSource, int objType, int objIndex) {
5124 
5125     int slope, oslope, i;
5126     spritetype* pSource = &sprite[pXSource->reference];
5127     bool flag2 = (pSource->flags & kModernTypeFlag2);
5128 
5129     if (pSource->flags & kModernTypeFlag1) slope = ClipRange(pXSource->data2, -32767, 32767);
5130     else slope = (32767 / kPercFull) * ClipRange(pXSource->data2, -kPercFull, kPercFull);
5131 
5132     if (objType == OBJ_SECTOR) {
5133 
5134         sectortype* pSect = &sector[objIndex];
5135 
5136         switch (pXSource->data1) {
5137         case 2:
5138         case 0:
5139             if (slope == 0) pSect->floorstat &= ~0x0002;
5140             else if (!(pSect->floorstat & 0x0002))
5141                 pSect->floorstat |= 0x0002;
5142 
5143             // just set floor slope
5144             if (flag2) {
5145 
5146                 pSect->floorheinum = slope;
5147 
5148             } else {
5149 
5150                 // force closest floor aligned sprites to inherit slope of the sector's floor
5151                 for (i = headspritesect[objIndex], oslope = pSect->floorheinum; i != -1; i = nextspritesect[i]) {
5152                     if (!(sprite[i].cstat & CSTAT_SPRITE_ALIGNMENT_FLOOR)) continue;
5153                     else if (getflorzofslope(objIndex, sprite[i].x, sprite[i].y) - kSlopeDist <= sprite[i].z) {
5154 
5155                         sprite2sectorSlope(&sprite[i], &sector[objIndex], 0, true);
5156 
5157                         // set new slope of floor
5158                         pSect->floorheinum = slope;
5159 
5160                         // force sloped sprites to be on floor slope z
5161                         sprite2sectorSlope(&sprite[i], &sector[objIndex], 0, true);
5162 
5163                         // restore old slope for next sprite
5164                         pSect->floorheinum = oslope;
5165 
5166                     }
5167                 }
5168 
5169                 // finally set new slope of floor
5170                 pSect->floorheinum = slope;
5171 
5172             }
5173 
5174             if (pXSource->data1 == 0) break;
5175             fallthrough__;
5176         case 1:
5177             if (slope == 0) pSect->ceilingstat &= ~0x0002;
5178             else if (!(pSect->ceilingstat & 0x0002))
5179                 pSect->ceilingstat |= 0x0002;
5180 
5181             // just set ceiling slope
5182             if (flag2) {
5183 
5184                 pSect->ceilingheinum = slope;
5185 
5186             } else {
5187 
5188                 // force closest floor aligned sprites to inherit slope of the sector's ceiling
5189                 for (i = headspritesect[objIndex], oslope = pSect->ceilingheinum; i != -1; i = nextspritesect[i]) {
5190                     if (!(sprite[i].cstat & CSTAT_SPRITE_ALIGNMENT_FLOOR)) continue;
5191                     else if (getceilzofslope(objIndex, sprite[i].x, sprite[i].y) + kSlopeDist >= sprite[i].z) {
5192 
5193                         sprite2sectorSlope(&sprite[i], &sector[objIndex], 1, true);
5194 
5195                         // set new slope of ceiling
5196                         pSect->ceilingheinum = slope;
5197 
5198                         // force sloped sprites to be on ceiling slope z
5199                         sprite2sectorSlope(&sprite[i], &sector[objIndex], 1, true);
5200 
5201                         // restore old slope for next sprite
5202                         pSect->ceilingheinum = oslope;
5203 
5204                     }
5205                 }
5206 
5207                 // finally set new slope of ceiling
5208                 pSect->ceilingheinum = slope;
5209 
5210             }
5211             break;
5212         }
5213 
5214         // let's give a little impulse to the physics sprites...
5215         for (i = headspritesect[objIndex]; i != -1; i = nextspritesect[i]) {
5216 
5217             if (sprite[i].extra > 0 && xsprite[sprite[i].extra].physAttr > 0) {
5218                 xsprite[sprite[i].extra].physAttr |= kPhysFalling;
5219                 zvel[i]++;
5220 
5221             } else if ((sprite[i].statnum == kStatThing || sprite[i].statnum == kStatDude) && (sprite[i].flags & kPhysGravity)) {
5222                 sprite[i].flags |= kPhysFalling;
5223                 zvel[i]++;
5224             }
5225 
5226         }
5227 
5228     } else if (objType == OBJ_SPRITE) {
5229 
5230         spritetype* pSpr = &sprite[objIndex];
5231         if (!(pSpr->cstat & CSTAT_SPRITE_ALIGNMENT_FLOOR)) pSpr->cstat |= CSTAT_SPRITE_ALIGNMENT_FLOOR;
5232         if ((pSpr->cstat & CSTAT_SPRITE_ALIGNMENT_SLOPE) != CSTAT_SPRITE_ALIGNMENT_SLOPE)
5233             pSpr->cstat |= CSTAT_SPRITE_ALIGNMENT_SLOPE;
5234 
5235         switch (pXSource->data4) {
5236             case 1:
5237             case 2:
5238             case 3:
5239                 if (!sectRangeIsFine(pSpr->sectnum)) break;
5240                 switch (pXSource->data4) {
5241                     case 1: sprite2sectorSlope(pSpr, &sector[pSpr->sectnum], 0, flag2); break;
5242                     case 2: sprite2sectorSlope(pSpr, &sector[pSpr->sectnum], 1, flag2); break;
5243                     case 3:
5244                         if (getflorzofslope(pSpr->sectnum, pSpr->x, pSpr->y) - kSlopeDist <= pSpr->z) sprite2sectorSlope(pSpr, &sector[pSpr->sectnum], 0, flag2);
5245                         if (getceilzofslope(pSpr->sectnum, pSpr->x, pSpr->y) + kSlopeDist >= pSpr->z) sprite2sectorSlope(pSpr, &sector[pSpr->sectnum], 1, flag2);
5246                         break;
5247                 }
5248                 break;
5249             default:
5250                 spriteSetSlope(objIndex, slope);
5251                 break;
5252         }
5253     }
5254 }
5255 
useDataChanger(XSPRITE * pXSource,int objType,int objIndex)5256 void useDataChanger(XSPRITE* pXSource, int objType, int objIndex) {
5257 
5258     spritetype* pSource = &sprite[pXSource->reference];
5259     switch (objType) {
5260         case OBJ_SECTOR:
5261             if ((pSource->flags & kModernTypeFlag1) || (pXSource->data1 != -1 && pXSource->data1 != 32767))
5262                 setDataValueOfObject(objType, objIndex, 1, pXSource->data1);
5263             break;
5264         case OBJ_SPRITE:
5265             if ((pSource->flags & kModernTypeFlag1) || (pXSource->data1 != -1 && pXSource->data1 != 32767))
5266                 setDataValueOfObject(objType, objIndex, 1, pXSource->data1);
5267 
5268             if ((pSource->flags & kModernTypeFlag1) || (pXSource->data2 != -1 && pXSource->data2 != 32767))
5269                 setDataValueOfObject(objType, objIndex, 2, pXSource->data2);
5270 
5271             if ((pSource->flags & kModernTypeFlag1) || (pXSource->data3 != -1 && pXSource->data3 != 32767))
5272                 setDataValueOfObject(objType, objIndex, 3, pXSource->data3);
5273 
5274             if ((pSource->flags & kModernTypeFlag1) || pXSource->data4 != 65535)
5275                 setDataValueOfObject(objType, objIndex, 4, pXSource->data4);
5276             break;
5277         case OBJ_WALL:
5278             if ((pSource->flags & kModernTypeFlag1) || (pXSource->data1 != -1 && pXSource->data1 != 32767))
5279                 setDataValueOfObject(objType, objIndex, 1, pXSource->data1);
5280             break;
5281     }
5282 }
5283 
useSectorLigthChanger(XSPRITE * pXSource,XSECTOR * pXSector)5284 void useSectorLigthChanger(XSPRITE* pXSource, XSECTOR* pXSector) {
5285 
5286     spritetype* pSource = &sprite[pXSource->reference];
5287     if (valueIsBetween(pXSource->data1, -1, 32767))
5288         pXSector->wave = ClipHigh(pXSource->data1, 11);
5289 
5290     int oldAmplitude = pXSector->amplitude;
5291     if (valueIsBetween(pXSource->data2, -128, 128))
5292         pXSector->amplitude = pXSource->data2;
5293 
5294     if (valueIsBetween(pXSource->data3, -1, 32767))
5295         pXSector->freq = ClipHigh(pXSource->data3, 255);
5296 
5297     if (valueIsBetween(pXSource->data4, -1, 65535))
5298         pXSector->phase = ClipHigh(pXSource->data4, 255);
5299 
5300     if (pSource->flags) {
5301         if (pSource->flags != kModernTypeFlag1) {
5302 
5303             pXSector->shadeAlways   = (pSource->flags & 0x0001) ? true : false;
5304             pXSector->shadeFloor    = (pSource->flags & 0x0002) ? true : false;
5305             pXSector->shadeCeiling  = (pSource->flags & 0x0004) ? true : false;
5306             pXSector->shadeWalls    = (pSource->flags & 0x0008) ? true : false;
5307 
5308         } else {
5309 
5310             pXSector->shadeAlways   = true;
5311 
5312         }
5313     }
5314 
5315     // add to shadeList if amplitude was set to 0 previously
5316     if (oldAmplitude != pXSector->amplitude && shadeCount < kMaxXSectors) {
5317 
5318         bool found = false;
5319         for (int i = 0; i < shadeCount; i++) {
5320             if (shadeList[i] != sector[pXSector->reference].extra) continue;
5321             found = true;
5322             break;
5323         }
5324 
5325         if (!found)
5326             shadeList[shadeCount++] = sector[pXSector->reference].extra;
5327     }
5328 }
5329 
useTargetChanger(XSPRITE * pXSource,spritetype * pSprite)5330 void useTargetChanger(XSPRITE* pXSource, spritetype* pSprite) {
5331 
5332 
5333     if (!IsDudeSprite(pSprite) || pSprite->statnum != kStatDude) {
5334         switch (pSprite->type) { // can be dead dude turned in gib
5335             // make current target and all other dudes not attack this dude anymore
5336         case kThingBloodBits:
5337         case kThingBloodChunks:
5338             aiFightFreeTargets(pSprite->index);
5339             return;
5340         default:
5341             return;
5342         }
5343     }
5344 
5345 
5346     //spritetype* pSource = &sprite[pXSource->reference];
5347 
5348     XSPRITE* pXSprite = &xsprite[pSprite->extra];
5349     spritetype* pTarget = NULL; XSPRITE* pXTarget = NULL; int receiveHp = 33 + Random(33);
5350     DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type); int matesPerEnemy = 1;
5351 
5352     // dude is burning?
5353     if (pXSprite->burnTime > 0 && spriRangeIsFine(pXSprite->burnSource)) {
5354 
5355         if (IsBurningDude(pSprite)) return;
5356         else {
5357             spritetype* pBurnSource = &sprite[pXSprite->burnSource];
5358             if (pBurnSource->extra >= 0) {
5359                 if (pXSource->data2 == 1 && aiFightIsMateOf(pXSprite, &xsprite[pBurnSource->extra])) {
5360                     pXSprite->burnTime = 0;
5361 
5362                     // heal dude a bit in case of friendly fire
5363                     int startHp = (pXSprite->sysData2 > 0) ? ClipRange(pXSprite->sysData2 << 4, 1, 65535) : pDudeInfo->startHealth << 4;
5364                     if (pXSprite->health < startHp) actHealDude(pXSprite, receiveHp, startHp);
5365                 } else if (xsprite[pBurnSource->extra].health <= 0) {
5366                     pXSprite->burnTime = 0;
5367                 }
5368             }
5369         }
5370     }
5371 
5372     spritetype* pPlayer = aiFightTargetIsPlayer(pXSprite);
5373     // special handling for player(s) if target changer data4 > 2.
5374     if (pPlayer != NULL) {
5375         if (pXSource->data4 == 3) {
5376             aiSetTarget(pXSprite, pSprite->x, pSprite->y, pSprite->z);
5377             aiSetGenIdleState(pSprite, pXSprite);
5378             if (pSprite->type == kDudeModernCustom && leechIsDropped(pSprite))
5379                 removeLeech(leechIsDropped(pSprite));
5380         } else if (pXSource->data4 == 4) {
5381             aiSetTarget(pXSprite, pPlayer->x, pPlayer->y, pPlayer->z);
5382             if (pSprite->type == kDudeModernCustom && leechIsDropped(pSprite))
5383                 removeLeech(leechIsDropped(pSprite));
5384         }
5385     }
5386 
5387     int maxAlarmDudes = 8 + Random(8);
5388     if (pXSprite->target > -1 && sprite[pXSprite->target].extra > -1 && pPlayer == NULL) {
5389         pTarget = &sprite[pXSprite->target]; pXTarget = &xsprite[pTarget->extra];
5390 
5391         if (aiFightUnitCanFly(pSprite) && aiFightIsMeleeUnit(pTarget) && !aiFightUnitCanFly(pTarget))
5392             pSprite->flags |= 0x0002;
5393         else if (aiFightUnitCanFly(pSprite))
5394             pSprite->flags &= ~0x0002;
5395 
5396         if (!IsDudeSprite(pTarget) || pXTarget->health < 1 || !aiFightDudeCanSeeTarget(pXSprite, pDudeInfo, pTarget)) {
5397             aiSetTarget(pXSprite, pSprite->x, pSprite->y, pSprite->z);
5398         }
5399         // dude attack or attacked by target that does not fit by data id?
5400         else if (pXSource->data1 != 666 && pXTarget->data1 != pXSource->data1) {
5401             if (aiFightDudeIsAffected(pXTarget)) {
5402 
5403                 // force stop attack target
5404                 aiSetTarget(pXSprite, pSprite->x, pSprite->y, pSprite->z);
5405                 if (pXSprite->burnSource == pTarget->index) {
5406                     pXSprite->burnTime = 0;
5407                     pXSprite->burnSource = -1;
5408                 }
5409 
5410                 // force stop attack dude
5411                 aiSetTarget(pXTarget, pTarget->x, pTarget->y, pTarget->z);
5412                 if (pXTarget->burnSource == pSprite->index) {
5413                     pXTarget->burnTime = 0;
5414                     pXTarget->burnSource = -1;
5415                 }
5416             }
5417 
5418         }
5419         else if (pXSource->data2 == 1 && aiFightIsMateOf(pXSprite, pXTarget)) {
5420             spritetype* pMate = pTarget; XSPRITE* pXMate = pXTarget;
5421 
5422             // heal dude
5423             int startHp = (pXSprite->sysData2 > 0) ? ClipRange(pXSprite->sysData2 << 4, 1, 65535) : pDudeInfo->startHealth << 4;
5424             if (pXSprite->health < startHp) actHealDude(pXSprite, receiveHp, startHp);
5425 
5426             // heal mate
5427             startHp = (pXMate->sysData2 > 0) ? ClipRange(pXMate->sysData2 << 4, 1, 65535) : getDudeInfo(pMate->type)->startHealth << 4;
5428             if (pXMate->health < startHp) actHealDude(pXMate, receiveHp, startHp);
5429 
5430             if (pXMate->target > -1 && sprite[pXMate->target].extra >= 0) {
5431                 pTarget = &sprite[pXMate->target];
5432                 // force mate stop attack dude, if he does
5433                 if (pXMate->target == pSprite->index) {
5434                     aiSetTarget(pXMate, pMate->x, pMate->y, pMate->z);
5435                 } else if (!aiFightIsMateOf(pXSprite, &xsprite[pTarget->extra])) {
5436                     // force dude to attack same target that mate have
5437                     aiSetTarget(pXSprite, pTarget->index);
5438                     return;
5439 
5440                 } else {
5441                     // force mate to stop attack another mate
5442                     aiSetTarget(pXMate, pMate->x, pMate->y, pMate->z);
5443                 }
5444             }
5445 
5446             // force dude stop attack mate, if target was not changed previously
5447             if (pXSprite->target == pMate->index)
5448                 aiSetTarget(pXSprite, pSprite->x, pSprite->y, pSprite->z);
5449 
5450 
5451         }
5452         // check if targets aims player then force this target to fight with dude
5453         else if (aiFightTargetIsPlayer(pXTarget) != NULL) {
5454             aiSetTarget(pXTarget, pSprite->index);
5455         }
5456 
5457         int mDist = 3; if (aiFightIsMeleeUnit(pSprite)) mDist = 2;
5458         if (pXSprite->target >= 0 && aiFightGetTargetDist(pSprite, pDudeInfo, &sprite[pXSprite->target]) < mDist) {
5459             if (!isActive(pSprite->index)) aiActivateDude(pSprite, pXSprite);
5460             return;
5461         }
5462         // lets try to look for target that fits better by distance
5463         else if (((int)gFrameClock & 256) != 0 && (pXSprite->target < 0 || aiFightGetTargetDist(pSprite, pDudeInfo, pTarget) >= mDist)) {
5464             pTarget = aiFightGetTargetInRange(pSprite, 0, mDist, pXSource->data1, pXSource->data2);
5465             if (pTarget != NULL) {
5466                 pXTarget = &xsprite[pTarget->extra];
5467 
5468                 // Make prev target not aim in dude
5469                 if (pXSprite->target > -1) {
5470                     spritetype* prvTarget = &sprite[pXSprite->target];
5471                     aiSetTarget(&xsprite[prvTarget->extra], prvTarget->x, prvTarget->y, prvTarget->z);
5472                     if (!isActive(pTarget->index))
5473                         aiActivateDude(pTarget, pXTarget);
5474                 }
5475 
5476                 // Change target for dude
5477                 aiSetTarget(pXSprite, pTarget->index);
5478                 if (!isActive(pSprite->index))
5479                     aiActivateDude(pSprite, pXSprite);
5480 
5481                 // ...and change target of target to dude to force it fight
5482                 if (pXSource->data3 > 0 && pXTarget->target != pSprite->index) {
5483                     aiSetTarget(pXTarget, pSprite->index);
5484                     if (!isActive(pTarget->index))
5485                         aiActivateDude(pTarget, pXTarget);
5486                 }
5487 
5488                 return;
5489             }
5490         }
5491     }
5492 
5493     if ((pXSprite->target < 0 || pPlayer != NULL) && ((int)gFrameClock & 32) != 0) {
5494         // try find first target that dude can see
5495         for (int nSprite = headspritestat[kStatDude]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
5496 
5497             pTarget = &sprite[nSprite]; pXTarget = &xsprite[pTarget->extra];
5498 
5499             if (pXTarget->target == pSprite->index) {
5500                 aiSetTarget(pXSprite, pTarget->index);
5501                 return;
5502             }
5503 
5504             // skip non-dudes and players
5505             if (!IsDudeSprite(pTarget) || (IsPlayerSprite(pTarget) && pXSource->data4 > 0) || pTarget->owner == pSprite->index) continue;
5506             // avoid self aiming, those who dude can't see, and those who dude own
5507             else if (!aiFightDudeCanSeeTarget(pXSprite, pDudeInfo, pTarget) || pSprite->index == pTarget->index) continue;
5508             // if Target Changer have data1 = 666, everyone can be target, except AI team mates.
5509             else if (pXSource->data1 != 666 && pXSource->data1 != pXTarget->data1) continue;
5510             // don't attack immortal, burning dudes and mates
5511             if (IsBurningDude(pTarget) || !IsKillableDude(pTarget) || (pXSource->data2 == 1 && aiFightIsMateOf(pXSprite, pXTarget)))
5512                 continue;
5513 
5514             if (pXSource->data2 == 0 || (pXSource->data2 == 1 && !aiFightMatesHaveSameTarget(pXSprite, pTarget, matesPerEnemy))) {
5515 
5516                 // Change target for dude
5517                 aiSetTarget(pXSprite, pTarget->index);
5518                 if (!isActive(pSprite->index))
5519                     aiActivateDude(pSprite, pXSprite);
5520 
5521                 // ...and change target of target to dude to force it fight
5522                 if (pXSource->data3 > 0 && pXTarget->target != pSprite->index) {
5523                     aiSetTarget(pXTarget, pSprite->index);
5524                     if (pPlayer == NULL && !isActive(pTarget->index))
5525                         aiActivateDude(pTarget, pXTarget);
5526 
5527                     if (pXSource->data3 == 2)
5528                         aiFightAlarmDudesInSight(pTarget, maxAlarmDudes);
5529                 }
5530 
5531                 return;
5532             }
5533 
5534             break;
5535         }
5536     }
5537 
5538     // got no target - let's ask mates if they have targets
5539     if ((pXSprite->target < 0 || pPlayer != NULL) && pXSource->data2 == 1 && ((int)gFrameClock & 64) != 0) {
5540         spritetype* pMateTarget = NULL;
5541         if ((pMateTarget = aiFightGetMateTargets(pXSprite)) != NULL && pMateTarget->extra > 0) {
5542             XSPRITE* pXMateTarget = &xsprite[pMateTarget->extra];
5543             if (aiFightDudeCanSeeTarget(pXSprite, pDudeInfo, pMateTarget)) {
5544                 if (pXMateTarget->target < 0) {
5545                     aiSetTarget(pXMateTarget, pSprite->index);
5546                     if (IsDudeSprite(pMateTarget) && !isActive(pMateTarget->index))
5547                         aiActivateDude(pMateTarget, pXMateTarget);
5548                 }
5549 
5550                 aiSetTarget(pXSprite, pMateTarget->index);
5551                 if (!isActive(pSprite->index))
5552                     aiActivateDude(pSprite, pXSprite);
5553                 return;
5554 
5555                 // try walk in mate direction in case if not see the target
5556             } else if (pXMateTarget->target >= 0 && aiFightDudeCanSeeTarget(pXSprite, pDudeInfo, &sprite[pXMateTarget->target])) {
5557                 spritetype* pMate = &sprite[pXMateTarget->target];
5558                 pXSprite->target = pMateTarget->index;
5559                 pXSprite->targetX = pMate->x;
5560                 pXSprite->targetY = pMate->y;
5561                 pXSprite->targetZ = pMate->z;
5562                 if (!isActive(pSprite->index))
5563                     aiActivateDude(pSprite, pXSprite);
5564                 return;
5565             }
5566         }
5567     }
5568 }
5569 
usePictureChanger(XSPRITE * pXSource,int objType,int objIndex)5570 void usePictureChanger(XSPRITE* pXSource, int objType, int objIndex) {
5571 
5572     //spritetype* pSource = &sprite[pXSource->reference];
5573 
5574     switch (objType) {
5575         case OBJ_SECTOR:
5576             if (valueIsBetween(pXSource->data1, -1, 32767))
5577                 sector[objIndex].floorpicnum = pXSource->data1;
5578 
5579             if (valueIsBetween(pXSource->data2, -1, 32767))
5580                 sector[objIndex].ceilingpicnum = pXSource->data2;
5581 
5582             if (valueIsBetween(pXSource->data3, -1, 32767))
5583                 sector[objIndex].floorpal = pXSource->data3;
5584 
5585             if (valueIsBetween(pXSource->data4, -1, 65535))
5586                 sector[objIndex].ceilingpal = pXSource->data4;
5587             break;
5588         case OBJ_SPRITE:
5589             if (valueIsBetween(pXSource->data1, -1, 32767))
5590                 sprite[objIndex].picnum = pXSource->data1;
5591 
5592             if (pXSource->data2 >= 0) sprite[objIndex].shade = (pXSource->data2 > 127) ? 127 : pXSource->data2;
5593             else if (pXSource->data2 < -1) sprite[objIndex].shade = (pXSource->data2 < -127) ? -127 : pXSource->data2;
5594 
5595             if (valueIsBetween(pXSource->data3, -1, 32767))
5596                 sprite[objIndex].pal = pXSource->data3;
5597             break;
5598         case OBJ_WALL:
5599             if (valueIsBetween(pXSource->data1, -1, 32767))
5600                 wall[objIndex].picnum = pXSource->data1;
5601 
5602             if (valueIsBetween(pXSource->data2, -1, 32767))
5603                 wall[objIndex].overpicnum = pXSource->data2;
5604 
5605             if (valueIsBetween(pXSource->data3, -1, 32767))
5606                 wall[objIndex].pal = pXSource->data3;
5607             break;
5608     }
5609 }
5610 
5611 //---------------------------------------
5612 
5613 // player related
playerQavSceneLoad(int qavId)5614 QAV* playerQavSceneLoad(int qavId) {
5615     QAV* pQav = NULL; DICTNODE* hQav = gSysRes.Lookup(qavId, "QAV");
5616 
5617     if (hQav) pQav = (QAV*)gSysRes.Lock(hQav);
5618     else viewSetSystemMessage("Failed to load QAV animation #%d", qavId);
5619 
5620     return pQav;
5621 }
5622 
playerQavSceneProcess(PLAYER * pPlayer,QAVSCENE * pQavScene)5623 void playerQavSceneProcess(PLAYER* pPlayer, QAVSCENE* pQavScene) {
5624     int nIndex = pQavScene->index;
5625     if (xspriRangeIsFine(sprite[nIndex].extra)) {
5626 
5627         XSPRITE* pXSprite = &xsprite[sprite[nIndex].extra];
5628         if (pXSprite->waitTime > 0 && --pXSprite->sysData1 <= 0) {
5629             if (pXSprite->txID >= kChannelUser) {
5630 
5631                 XSPRITE* pXSpr = NULL;
5632                 for (int i = bucketHead[pXSprite->txID]; i < bucketHead[pXSprite->txID + 1]; i++) {
5633                     if (rxBucket[i].type == OBJ_SPRITE) {
5634 
5635                         spritetype* pSpr = &sprite[rxBucket[i].index];
5636                         if (pSpr->index == nIndex || !xspriRangeIsFine(pSpr->extra))
5637                             continue;
5638 
5639                         pXSpr = &xsprite[pSpr->extra];
5640                         if (pSpr->type == kModernPlayerControl && pXSpr->command == 67) {
5641                             if (pXSpr->data2 == pXSprite->data2 || pXSpr->locked) continue;
5642                             else trPlayerCtrlStartScene(pXSpr, pPlayer, true);
5643                             return;
5644                         }
5645 
5646                     }
5647 
5648                     nnExtTriggerObject(rxBucket[i].type, rxBucket[i].index, pXSprite->command);
5649 
5650                 }
5651             } //else {
5652 
5653                 trPlayerCtrlStopScene(pPlayer);
5654 
5655             //}
5656 
5657         } else {
5658 
5659             playerQavScenePlay(pPlayer);
5660             pPlayer->weaponTimer = ClipLow(pPlayer->weaponTimer -= 4, 0);
5661 
5662         }
5663     } else {
5664 
5665         pQavScene->index = pPlayer->sceneQav = -1;
5666         pQavScene->qavResrc = NULL;
5667     }
5668 }
5669 
playerQavSceneDraw(PLAYER * pPlayer,int a2,int a3,int a4,int a5)5670 void playerQavSceneDraw(PLAYER* pPlayer, int a2, int a3, int a4, int a5) {
5671     if (pPlayer == NULL || pPlayer->sceneQav == -1) return;
5672 
5673     QAVSCENE* pQavScene = &gPlayerCtrl[pPlayer->nPlayer].qavScene;
5674     spritetype* pSprite = &sprite[pQavScene->index];
5675 
5676     if (pQavScene->qavResrc != NULL) {
5677 
5678         QAV* pQAV = pQavScene->qavResrc;
5679         int v4 = (pPlayer->weaponTimer == 0) ? (int)totalclock % pQAV->at10 : pQAV->at10 - pPlayer->weaponTimer;
5680 
5681         int flags = 2; int nInv = powerupCheck(pPlayer, kPwUpShadowCloak);
5682         if (nInv >= 120 * 8 || (nInv != 0 && ((int)totalclock & 32))) {
5683             a2 = -128; flags |= 1;
5684         }
5685 
5686         // draw as weapon
5687         if (!(pSprite->flags & kModernTypeFlag1)) {
5688 
5689             pQAV->x = a3; pQAV->y = a4;
5690             pQAV->Draw(v4, flags, a2, a5);
5691 
5692             // draw fullscreen (currently 4:3 only)
5693         } else {
5694 
5695             int wx1 = windowxy1.x, wy1 = windowxy1.y, wx2 = windowxy2.x, wy2 = windowxy2.y;
5696 
5697             windowxy2.x = xdim - 1; windowxy2.y = ydim - 1;
5698             windowxy1.x = windowxy1.y = 0;
5699 
5700             pQAV->Draw(v4, flags, a2, a5);
5701 
5702             windowxy1.x = wx1; windowxy1.y = wy1;
5703             windowxy2.x = wx2; windowxy2.y = wy2;
5704 
5705         }
5706 
5707     }
5708 
5709 }
5710 
playerQavScenePlay(PLAYER * pPlayer)5711 void playerQavScenePlay(PLAYER* pPlayer) {
5712     if (pPlayer == NULL) return;
5713 
5714     QAVSCENE* pQavScene = &gPlayerCtrl[pPlayer->nPlayer].qavScene;
5715     if (pPlayer->sceneQav == -1 && pQavScene->index >= 0)
5716         pPlayer->sceneQav = xsprite[sprite[pQavScene->index].extra].data2;
5717 
5718     if (pQavScene->qavResrc != NULL) {
5719         QAV* pQAV = pQavScene->qavResrc;
5720         pQAV->nSprite = pPlayer->pSprite->index;
5721         int nTicks = pQAV->at10 - pPlayer->weaponTimer;
5722         pQAV->Play(nTicks - 4, nTicks, pPlayer->qavCallback, pPlayer);
5723     }
5724 }
5725 
playerQavSceneReset(PLAYER * pPlayer)5726 void playerQavSceneReset(PLAYER* pPlayer) {
5727     QAVSCENE* pQavScene = &gPlayerCtrl[pPlayer->nPlayer].qavScene;
5728     pQavScene->index = pQavScene->dummy = pPlayer->sceneQav = -1;
5729     pQavScene->qavResrc = NULL;
5730 }
5731 
playerSizeShrink(PLAYER * pPlayer,int divider)5732 bool playerSizeShrink(PLAYER* pPlayer, int divider) {
5733     pPlayer->pXSprite->scale = 256 / divider;
5734     playerSetRace(pPlayer, kModeHumanShrink);
5735     return true;
5736 }
5737 
playerSizeGrow(PLAYER * pPlayer,int multiplier)5738 bool playerSizeGrow(PLAYER* pPlayer, int multiplier) {
5739     pPlayer->pXSprite->scale = 256 * multiplier;
5740     playerSetRace(pPlayer, kModeHumanGrown);
5741     return true;
5742 }
5743 
playerSizeReset(PLAYER * pPlayer)5744 bool playerSizeReset(PLAYER* pPlayer) {
5745     playerSetRace(pPlayer, kModeHuman);
5746     pPlayer->pXSprite->scale = 0;
5747     return true;
5748 }
5749 
playerDeactivateShrooms(PLAYER * pPlayer)5750 void playerDeactivateShrooms(PLAYER* pPlayer) {
5751     powerupDeactivate(pPlayer, kPwUpGrowShroom);
5752     pPlayer->pwUpTime[kPwUpGrowShroom] = 0;
5753 
5754     powerupDeactivate(pPlayer, kPwUpShrinkShroom);
5755     pPlayer->pwUpTime[kPwUpShrinkShroom] = 0;
5756 }
5757 
5758 
5759 
getPlayerById(short id)5760 PLAYER* getPlayerById(short id) {
5761 
5762     // relative to connected players
5763     if (id >= 1 && id <= kMaxPlayers) {
5764         id = id - 1;
5765         for (int i = connecthead; i >= 0; i = connectpoint2[i]) {
5766             if (id == gPlayer[i].nPlayer)
5767                 return &gPlayer[i];
5768         }
5769 
5770     // absolute sprite type
5771     } else if (id >= kDudePlayer1 && id <= kDudePlayer8) {
5772         for (int i = connecthead; i >= 0; i = connectpoint2[i]) {
5773             if (id == gPlayer[i].pSprite->type)
5774                 return &gPlayer[i];
5775         }
5776     }
5777 
5778     //viewSetSystemMessage("There is no player id #%d", id);
5779     return NULL;
5780 }
5781 
5782 // misc functions
IsBurningDude(spritetype * pSprite)5783 bool IsBurningDude(spritetype* pSprite) {
5784     if (pSprite == NULL) return false;
5785     switch (pSprite->type) {
5786     case kDudeBurningInnocent:
5787     case kDudeBurningCultist:
5788     case kDudeBurningZombieAxe:
5789     case kDudeBurningZombieButcher:
5790     case kDudeBurningTinyCaleb:
5791     case kDudeBurningBeast:
5792     case kDudeModernCustomBurning:
5793         return true;
5794     }
5795 
5796     return false;
5797 }
5798 
IsKillableDude(spritetype * pSprite)5799 bool IsKillableDude(spritetype* pSprite) {
5800     switch (pSprite->type) {
5801     case kDudeGargoyleStatueFlesh:
5802     case kDudeGargoyleStatueStone:
5803         return false;
5804     default:
5805         if (!IsDudeSprite(pSprite) || xsprite[pSprite->extra].locked == 1) return false;
5806         return true;
5807     }
5808 }
5809 
isGrown(spritetype * pSprite)5810 bool isGrown(spritetype* pSprite) {
5811     if (powerupCheck(&gPlayer[pSprite->type - kDudePlayer1], kPwUpGrowShroom) > 0) return true;
5812     else if (pSprite->extra >= 0 && xsprite[pSprite->extra].scale >= 512) return true;
5813     else return false;
5814 }
5815 
isShrinked(spritetype * pSprite)5816 bool isShrinked(spritetype* pSprite) {
5817     if (powerupCheck(&gPlayer[pSprite->type - kDudePlayer1], kPwUpShrinkShroom) > 0) return true;
5818     else if (pSprite->extra >= 0 && xsprite[pSprite->extra].scale > 0 && xsprite[pSprite->extra].scale <= 128) return true;
5819     else return false;
5820 }
5821 
isActive(int nSprite)5822 bool isActive(int nSprite) {
5823     if (sprite[nSprite].extra < 0 || sprite[nSprite].extra >= kMaxXSprites)
5824         return false;
5825 
5826     XSPRITE* pXDude = &xsprite[sprite[nSprite].extra];
5827     switch (pXDude->aiState->stateType) {
5828     case kAiStateIdle:
5829     case kAiStateGenIdle:
5830     case kAiStateSearch:
5831     case kAiStateMove:
5832     case kAiStateOther:
5833         return false;
5834     default:
5835         return true;
5836     }
5837 }
5838 
getDataFieldOfObject(int objType,int objIndex,int dataIndex)5839 int getDataFieldOfObject(int objType, int objIndex, int dataIndex) {
5840     int data = -65535;
5841     switch (objType) {
5842         case OBJ_SPRITE:
5843             switch (dataIndex) {
5844                 case 1: return xsprite[sprite[objIndex].extra].data1;
5845                 case 2: return xsprite[sprite[objIndex].extra].data2;
5846                 case 3:
5847                     switch (sprite[objIndex].type) {
5848                         case kDudeModernCustom: return xsprite[sprite[objIndex].extra].sysData1;
5849                         default: return xsprite[sprite[objIndex].extra].data3;
5850                     }
5851                 case 4:return xsprite[sprite[objIndex].extra].data4;
5852                 default: return data;
5853             }
5854         case OBJ_SECTOR: return xsector[sector[objIndex].extra].data;
5855         case OBJ_WALL: return xwall[wall[objIndex].extra].data;
5856         default: return data;
5857     }
5858 }
5859 
setDataValueOfObject(int objType,int objIndex,int dataIndex,int value)5860 bool setDataValueOfObject(int objType, int objIndex, int dataIndex, int value) {
5861     switch (objType) {
5862         case OBJ_SPRITE: {
5863             XSPRITE* pXSprite = &xsprite[sprite[objIndex].extra];
5864 
5865             // exceptions
5866             if (IsDudeSprite(&sprite[objIndex]) && pXSprite->health <= 0) return true;
5867             switch (sprite[objIndex].type) {
5868                 case kThingBloodBits:
5869                 case kThingBloodChunks:
5870                 case kThingZombieHead:
5871                     return true;
5872                     break;
5873             }
5874 
5875             switch (dataIndex) {
5876                 case 1:
5877                     xsprite[sprite[objIndex].extra].data1 = value;
5878                     switch (sprite[objIndex].type) {
5879                         case kSwitchCombo:
5880                             if (value == xsprite[sprite[objIndex].extra].data2) SetSpriteState(objIndex, &xsprite[sprite[objIndex].extra], 1);
5881                             else SetSpriteState(objIndex, &xsprite[sprite[objIndex].extra], 0);
5882                             break;
5883                         case kDudeModernCustom:
5884                         case kDudeModernCustomBurning:
5885                             gGenDudeExtra[objIndex].updReq[kGenDudePropertyWeapon] = true;
5886                             gGenDudeExtra[objIndex].updReq[kGenDudePropertyDmgScale] = true;
5887                             evPost(objIndex, 3, kGenDudeUpdTimeRate, kCallbackGenDudeUpdate);
5888                             break;
5889                     }
5890                     return true;
5891                 case 2:
5892                     xsprite[sprite[objIndex].extra].data2 = value;
5893                     switch (sprite[objIndex].type) {
5894                         case kDudeModernCustom:
5895                         case kDudeModernCustomBurning:
5896                             gGenDudeExtra[objIndex].updReq[kGenDudePropertySpriteSize] = true;
5897                             gGenDudeExtra[objIndex].updReq[kGenDudePropertyMass] = true;
5898                             gGenDudeExtra[objIndex].updReq[kGenDudePropertyDmgScale] = true;
5899                             gGenDudeExtra[objIndex].updReq[kGenDudePropertyStates] = true;
5900                             gGenDudeExtra[objIndex].updReq[kGenDudePropertyAttack] = true;
5901                             evPost(objIndex, 3, kGenDudeUpdTimeRate, kCallbackGenDudeUpdate);
5902                             break;
5903                     }
5904                     return true;
5905                 case 3:
5906                     xsprite[sprite[objIndex].extra].data3 = value;
5907                     switch (sprite[objIndex].type) {
5908                         case kDudeModernCustom:
5909                         case kDudeModernCustomBurning:
5910                             xsprite[sprite[objIndex].extra].sysData1 = value;
5911                             break;
5912                     }
5913                     return true;
5914                 case 4:
5915                     xsprite[sprite[objIndex].extra].data4 = value;
5916                     return true;
5917                 default:
5918                     return false;
5919             }
5920         }
5921         case OBJ_SECTOR:
5922             xsector[sector[objIndex].extra].data = value;
5923             return true;
5924         case OBJ_WALL:
5925             xwall[wall[objIndex].extra].data = value;
5926             return true;
5927         default:
5928             return false;
5929     }
5930 }
5931 
5932 /// patrol functions
5933 // ------------------------------------------------
5934 
aiInPatrolState(AISTATE * pAiState)5935 AISTATE* aiInPatrolState(AISTATE* pAiState) {
5936 
5937     for (int i = 0; i < kPatrolStateSize; i++) {
5938         if (pAiState == &genPatrolStates[i])
5939             return pAiState;
5940     }
5941     return NULL;
5942 
5943 }
5944 
aiPatrolCrouching(AISTATE * pAiState)5945 bool aiPatrolCrouching(AISTATE* pAiState) {
5946     return (pAiState->stateType == kAiStatePatrolWaitC || pAiState->stateType == kAiStatePatrolMoveC);
5947 }
5948 
aiPatrolWaiting(AISTATE * pAiState)5949 bool aiPatrolWaiting(AISTATE* pAiState) {
5950     return (pAiState->stateType == kAiStatePatrolWaitL || pAiState->stateType == kAiStatePatrolWaitW || pAiState->stateType == kAiStatePatrolWaitC);
5951 }
5952 
aiPatrolMoving(AISTATE * pAiState)5953 bool aiPatrolMoving(AISTATE* pAiState) {
5954     return (pAiState->stateType == kAiStatePatrolMoveL || pAiState->stateType == kAiStatePatrolMoveW || pAiState->stateType == kAiStatePatrolMoveC);
5955 }
5956 
aiPatrolState(spritetype * pSprite,int state)5957 void aiPatrolState(spritetype* pSprite, int state) {
5958 
5959     dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
5960     XSPRITE* pXSprite = &xsprite[pSprite->extra];
5961 
5962     int seq = -1, i, start, end; bool crouch;
5963     DUDEINFO_EXTRA* pExtra = &gDudeInfoExtra[pSprite->type - kDudeBase];
5964     switch (state) {
5965         case kAiStatePatrolWaitL:
5966             seq = pExtra->idlgseqofs;
5967             start = 0; end = 2;
5968             crouch = false;
5969             break;
5970         case kAiStatePatrolMoveL:
5971             seq = pExtra->mvegseqofs;
5972             start = 2; end = 7;
5973             crouch = false;
5974             break;
5975         case kAiStatePatrolWaitW:
5976             seq = pExtra->idlwseqofs;
5977             start = 7; end = 13;
5978             crouch = false;
5979             break;
5980         case kAiStatePatrolMoveW:
5981             seq = pExtra->mvewseqofs;
5982             start = 13; end = 20;
5983             crouch = false;
5984             break;
5985         case kAiStatePatrolWaitC:
5986             seq = pExtra->idlcseqofs;
5987             start = 20; end = 24;
5988             crouch = true;
5989             break;
5990         case kAiStatePatrolMoveC:
5991             seq = pExtra->mvecseqofs;
5992             start = 24; end = kPatrolStateSize;
5993             crouch = true;
5994             break;
5995     }
5996 
5997     if (seq < 0)
5998         return aiPatrolStop(pSprite, -1);
5999 
6000     for (i = start; i < end; i++) {
6001 
6002         AISTATE* curState = &genPatrolStates[i];
6003         if (curState->stateType != state || seq != curState->seqId) continue;
6004         aiChooseDirection(pSprite, pXSprite, getangle(pXSprite->targetX - pSprite->x, pXSprite->targetY - pSprite->y));
6005         if (pSprite->type == kDudeModernCustom) aiGenDudeNewState(pSprite, &genPatrolStates[i]);
6006         else aiNewState(pSprite, pXSprite, &genPatrolStates[i]);
6007 
6008         if (crouch) pXSprite->unused1 |= kDudeFlagCrouch;
6009         else pXSprite->unused1 &= ~kDudeFlagCrouch;
6010 
6011         // these don't have idle crouch seq for some reason...
6012         if (state == kAiStatePatrolWaitC && (pSprite->type == kDudeCultistTesla || pSprite->type == kDudeCultistTNT)) {
6013             seqKill(OBJ_SPRITE, pSprite->extra);
6014             pSprite->picnum = 3385; // set idle picnum
6015         }
6016 
6017         return;
6018 
6019     }
6020 
6021     if (i == end) {
6022         viewSetSystemMessage("No patrol state #%d found for dude #%d (type = %d)", state, pSprite->index, pSprite->type);
6023         aiPatrolStop(pSprite, -1);
6024     }
6025 }
6026 
6027 // check if some dude already follows the given marker
aiPatrolMarkerBusy(int nExcept,int nMarker)6028 int aiPatrolMarkerBusy(int nExcept, int nMarker) {
6029     for (int i = headspritestat[kStatDude]; i != -1; i = nextspritestat[i]) {
6030         if (!IsDudeSprite(&sprite[i]) || sprite[i].index == nExcept || !xspriRangeIsFine(sprite[i].extra))
6031             continue;
6032 
6033         XSPRITE* pXDude = &xsprite[sprite[i].extra];
6034         if (pXDude->health > 0 && pXDude->target >= 0 && sprite[pXDude->target].type == kMarkerPath && pXDude->target == nMarker)
6035             return sprite[i].index;
6036     }
6037 
6038     return -1;
6039 }
6040 
aiPatrolMarkerReached(spritetype * pSprite,XSPRITE * pXSprite)6041 bool aiPatrolMarkerReached(spritetype* pSprite, XSPRITE* pXSprite) {
6042 
6043     if (pSprite->type >= kDudeBase && pSprite->type < kDudeMax) {
6044 
6045         DUDEINFO_EXTRA* pExtra = &gDudeInfoExtra[pSprite->type - kDudeBase];
6046         if (spriRangeIsFine(pXSprite->target) && sprite[pXSprite->target].type == kMarkerPath) {
6047             short okDist = ClipLow(sprite[pXSprite->target].clipdist << 1, 4);
6048             if (spriteIsUnderwater(pSprite) || pExtra->flying)
6049                 return CheckProximity(&sprite[pXSprite->target], pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, okDist);
6050 
6051             // ignore z of marker for ground
6052             spritetype* pMarker = &sprite[pXSprite->target];
6053             int oX = klabs(pMarker->x - pSprite->x) >> 4;
6054             int oY = klabs(pMarker->y - pSprite->y) >> 4;
6055             return (approxDist(oX, oY) <= okDist);
6056         }
6057 
6058     }
6059 
6060     return false;
6061 
6062 }
6063 
aiPatrolSetMarker(spritetype * pSprite,XSPRITE * pXSprite)6064 void aiPatrolSetMarker(spritetype* pSprite, XSPRITE* pXSprite) {
6065 
6066     int path = -1; int next = -1; int i = 0;
6067     if (pXSprite->target <= 0) {
6068 
6069         long closest = 20000000; // select closest marker that dude can see
6070         for (i = headspritestat[kStatPathMarker]; i != -1; i = nextspritestat[i]) {
6071 
6072             long dist = 0;
6073             int dx = sprite[i].x - pSprite->x;
6074             int dy = sprite[i].y - pSprite->y;
6075             int eyeAboveZ = (getDudeInfo(pSprite->type)->eyeHeight * pSprite->yrepeat) << 2;
6076             if (cansee(sprite[i].x, sprite[i].y, sprite[i].z, sprite[i].sectnum, pSprite->x, pSprite->y, pSprite->z - eyeAboveZ, pSprite->sectnum)
6077                 && (dist = approxDist(dx, dy)) <= closest && xspriRangeIsFine(sprite[i].extra) && !xsprite[sprite[i].extra].locked
6078                 && !xsprite[sprite[i].extra].DudeLockout) {
6079 
6080                 closest = dist;
6081                 path = i;
6082 
6083             }
6084 
6085         }
6086 
6087     // set next marker
6088     } else if (sprite[pXSprite->target].type == kMarkerPath && xspriRangeIsFine(sprite[pXSprite->target].extra)) {
6089 
6090         int total = 0, random = 0; next = xsprite[sprite[pXSprite->target].extra].data2;
6091         for (i = headspritestat[kStatPathMarker]; i != -1; i = nextspritestat[i]) {
6092             if (!xspriRangeIsFine(sprite[i].extra) || xsprite[sprite[i].extra].data1 != next) continue;
6093             else if (!xsprite[sprite[i].extra].locked && !xsprite[sprite[i].extra].DudeLockout)
6094                 total++;
6095         }
6096 
6097         if (total <= 0) {
6098             //viewSetSystemMessage("Follow: No markers with id #%d found for dude #%d!", next, pSprite->index);
6099             return;
6100         }
6101 
6102         random = nnExtRandom(0, total);
6103         for (i = headspritestat[kStatPathMarker]; i >= 0; i = nextspritestat[i]) {
6104             if (sprite[i].index == pXSprite->target || !xspriRangeIsFine(sprite[i].extra) || xsprite[sprite[i].extra].data1 != next) continue;
6105             else if (xsprite[sprite[i].extra].locked || xsprite[sprite[i].extra].DudeLockout) continue;
6106             else if (total > 1 && (random != total-- || (aiPatrolMarkerBusy(pSprite->index, sprite[i].index) >= 0 && !Chance(0x0500)))) continue;
6107             path = sprite[i].index;
6108             break;
6109         }
6110     }
6111 
6112     if (!spriRangeIsFine(path))
6113         return;
6114 
6115     pXSprite->target = path;
6116     pXSprite->targetX = sprite[path].x;
6117     pXSprite->targetY = sprite[path].y;
6118     pXSprite->targetZ = sprite[path].z;
6119     sprite[path].owner = pSprite->index;
6120 
6121 }
6122 
aiPatrolStop(spritetype * pSprite,int target,bool alarm)6123 void aiPatrolStop(spritetype* pSprite, int target, bool alarm) {
6124     if (xspriRangeIsFine(pSprite->extra)) {
6125 
6126         XSPRITE* pXSprite = &xsprite[pSprite->extra];
6127         pXSprite->data3 = 0; // reset spot progress
6128         pXSprite->unused1 &= kDudeFlagCrouch; // reset the crouch status
6129         if (pXSprite->health <= 0)
6130             return;
6131 
6132         if (pXSprite->target >= 0 && sprite[pXSprite->target].type == kMarkerPath) {
6133             if (target < 0) pSprite->ang = sprite[pXSprite->target].ang & 2047;
6134             pXSprite->target = -1;
6135         }
6136 
6137         bool flag4 = pXSprite->dudeFlag4;
6138         pXSprite->dudeFlag4 = 0;
6139         if (spriRangeIsFine(target) && IsDudeSprite(&sprite[target]) && xspriRangeIsFine(sprite[target].extra)) {
6140 
6141             aiSetTarget(pXSprite, target);
6142             aiActivateDude(pSprite, pXSprite);
6143             if (alarm)
6144                 aiPatrolAlarm(pSprite, Chance(0x0500));
6145 
6146         } else {
6147 
6148 
6149             aiInitSprite(pSprite);
6150             aiSetTarget(pXSprite, pXSprite->targetX, pXSprite->targetY, pXSprite->targetZ);
6151 
6152 
6153         }
6154         pXSprite->dudeFlag4 = flag4; // this must be kept so enemy can patrol after respawn again
6155     }
6156     return;
6157 }
6158 
aiPatrolMoveZ(spritetype * pSprite,XSPRITE * pXSprite)6159 void aiPatrolMoveZ(spritetype* pSprite, XSPRITE* pXSprite) {
6160     if (!spriRangeIsFine(pXSprite->target))
6161         return;
6162 
6163     DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
6164     spritetype* pTarget = &sprite[pXSprite->target];
6165 
6166     int z = pSprite->z + pDudeInfo->eyeHeight;
6167     int nAng = ((pXSprite->goalAng + 1024 - pSprite->ang) & 2047) - 1024;
6168     int nTurnRange = (pDudeInfo->angSpeed << 2) >> 4;
6169     pSprite->ang = (pSprite->ang + ClipRange(nAng, -nTurnRange, nTurnRange)) & 2047;
6170     if (klabs(nAng) > 341) {
6171         pSprite->ang = (pSprite->ang + 512) & 2047;
6172         return;
6173     }
6174 
6175     int dz = (pTarget->z - z) * 5;
6176     zvel[pSprite->index] = dz;
6177 
6178 }
6179 
aiPatrolMove(spritetype * pSprite,XSPRITE * pXSprite)6180 void aiPatrolMove(spritetype* pSprite, XSPRITE* pXSprite) {
6181 
6182     if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
6183         return;
6184 
6185     int dudeIdx;
6186     switch (pSprite->type) {
6187         default:                        dudeIdx = pSprite->type - kDudeBase;        break;
6188         case kDudeCultistShotgunProne:  dudeIdx = kDudeCultistShotgun - kDudeBase;  break;
6189         case kDudeCultistTommyProne:    dudeIdx = kDudeCultistTommy - kDudeBase;    break;
6190     }
6191 
6192     DUDEINFO_EXTRA* pExtra = &gDudeInfoExtra[dudeIdx];
6193     if (pExtra->flying || spriteIsUnderwater(pSprite)) {
6194         pSprite->flags &= ~kPhysGravity;
6195         aiPatrolMoveZ(pSprite, pXSprite);
6196 
6197     } else if (!pExtra->flying) {
6198         pSprite->flags |= kPhysGravity | kPhysFalling;
6199     }
6200 
6201     if (pSprite->type == kDudeModernCustom) {
6202 
6203         aiGenDudeMoveForward(pSprite, pXSprite);
6204 
6205     } else {
6206 
6207         DUDEINFO* pDudeInfo = &dudeInfo[dudeIdx];
6208         int nTurnRange = (pDudeInfo->angSpeed << 2) >> 4;
6209         int nAng = ((pXSprite->goalAng + 1024 - pSprite->ang) & 2047) - 1024;
6210         int frontSpeed = pDudeInfo->frontSpeed;
6211 
6212         pSprite->ang = (pSprite->ang + ClipRange(nAng, -nTurnRange, nTurnRange)) & 2047;
6213         if (klabs(nAng) <= 341) {
6214             xvel[pSprite->index] += mulscale30(frontSpeed, Cos(pSprite->ang));
6215             yvel[pSprite->index] += mulscale30(frontSpeed, Sin(pSprite->ang));
6216         }
6217 
6218     }
6219 
6220     int vel = (aiPatrolCrouching(pXSprite->aiState)) ? kMaxPatrolCrouchVelocity : kMaxPatrolVelocity;
6221     xvel[pSprite->index] = ClipRange(xvel[pSprite->index], -vel, vel);
6222     yvel[pSprite->index] = ClipRange(yvel[pSprite->index], -vel, vel);
6223     return;
6224 }
6225 
aiPatrolAlarm(spritetype * pSprite,bool chain)6226 void aiPatrolAlarm(spritetype* pSprite, bool chain) {
6227 
6228     static short chainChance = 0;
6229     if (chainChance <= 0) chainChance = 0x1000;
6230     XSPRITE* pXSprite = &xsprite[pSprite->extra];
6231     DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
6232     spritetype* pDude = NULL; XSPRITE* pXDude = NULL;
6233     int target = pXSprite->target;
6234     for (int nSprite = headspritestat[kStatDude]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
6235         pDude = &sprite[nSprite];
6236         if (pDude->index == pSprite->index || !IsDudeSprite(pDude) || IsPlayerSprite(pDude) || pDude->extra < 0) continue;
6237         //else if (pDude->type == kDudeGargoyleStatueFlesh || pDude->type == kDudeGargoyleStatueStone)
6238             //continue;
6239 
6240         pXDude = &xsprite[pDude->extra];
6241         if (pXDude->health > 0 && approxDist(pDude->x - pSprite->x, pDude->y - pSprite->y) < pDudeInfo->seeDist) {
6242             if (aiInPatrolState(pXDude->aiState)) aiPatrolStop(pDude, pXDude->target);
6243             if (pXDude->target >= 0 || pXDude->target == pXSprite->target)
6244                 continue;
6245 
6246             if (spriRangeIsFine(target)) aiSetTarget(pXDude, target);
6247             else aiSetTarget(pXDude, pXSprite->targetX, pXSprite->targetY, pXSprite->targetZ);
6248 
6249             aiActivateDude(pDude, pXDude);
6250             if (chain) {
6251                 aiPatrolAlarm(pDude, Chance(chainChance));
6252                 chainChance -= 0x0100;
6253             }
6254 
6255             consoleSysMsg("Dude #%d alarms dude #%d", pSprite->index, pDude->index);
6256         }
6257     }
6258 
6259 }
6260 
isTouchingSprite(int nXSprite1,int nXSprite2)6261 bool isTouchingSprite(int nXSprite1, int nXSprite2) {
6262 
6263     if (!xspriRangeIsFine(nXSprite1) || !xspriRangeIsFine(nXSprite2))
6264         return false;
6265 
6266     int nHSprite = -1;
6267     if ((gSpriteHit[nXSprite1].hit & 0xc000) == 0xc000) nHSprite = gSpriteHit[nXSprite1].hit & 0x3fff;
6268     else if ((gSpriteHit[nXSprite1].florhit & 0xc000) == 0xc000) nHSprite = gSpriteHit[nXSprite1].florhit & 0x3fff;
6269     else if ((gSpriteHit[nXSprite1].ceilhit & 0xc000) == 0xc000) nHSprite = gSpriteHit[nXSprite1].ceilhit & 0x3fff;
6270     return (spriRangeIsFine(nHSprite) && sprite[nHSprite].extra == nXSprite2);
6271 }
6272 
aiCanCrouch(spritetype * pSprite)6273 bool aiCanCrouch(spritetype* pSprite) {
6274 
6275     if (pSprite->type >= kDudeBase && pSprite->type < kDudeVanillaMax)
6276         return (gDudeInfoExtra[pSprite->type - kDudeBase].idlcseqofs >= 0 && gDudeInfoExtra[pSprite->type - kDudeBase].mvecseqofs >= 0);
6277     else if (pSprite->type == kDudeModernCustom || pSprite->type == kDudeModernCustomBurning)
6278         return gGenDudeExtra[pSprite->index].canDuck;
6279 
6280     return false;
6281 
6282 }
6283 
6284 
readyForCrit(spritetype * pHunter,spritetype * pVictim)6285 bool readyForCrit(spritetype* pHunter, spritetype* pVictim) {
6286 
6287     if (!(pHunter->type >= kDudeBase && pHunter->type < kDudeMax) || !(pVictim->type >= kDudeBase && pVictim->type < kDudeMax))
6288         return false;
6289 
6290     int x, y, dx, dy, nDist;
6291     x = pVictim->x;
6292     y = pVictim->y;
6293 
6294 
6295     dx = x - pHunter->x;
6296     dy = y - pHunter->y;
6297     if ((nDist = approxDist(dx, dy)) >= (6000 / ClipLow(gGameOptions.nDifficulty >> 1, 1)))
6298         return false;
6299 
6300     DUDEINFO* pDudeInfo = getDudeInfo(pVictim->type);
6301     int nDeltaAngle = ((getangle(dx, dy) + 1024 - pVictim->ang) & 2047) - 1024;
6302     return (klabs(nDeltaAngle) < (pDudeInfo->periphery >> 1));
6303 }
6304 
aiPatrolSearchTargets(spritetype * pSprite,XSPRITE * pXSprite)6305 int aiPatrolSearchTargets(spritetype* pSprite, XSPRITE* pXSprite) {
6306 
6307     dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
6308     DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
6309     int i, x, y, z, dx, dy, nDist, eyeAboveZ, target = -1, seeDist, hearDist, seeChance, hearChance;
6310     bool stealth = (pXSprite->unused1 & kDudeFlagStealth);
6311     PLAYER* pPlayer = NULL;
6312 
6313     // search for targets
6314     for (i = headspritestat[kStatDude]; i >= 0; i = nextspritestat[i]) {
6315 
6316         target = -1;
6317         seeChance = hearChance = 0x0000;
6318 
6319         spritetype* pSpr = &sprite[i];
6320         if (!xspriRangeIsFine(pSpr->extra))
6321             continue;
6322 
6323         XSPRITE* pXSpr = &xsprite[pSpr->extra];
6324         if (pSprite->index == pSpr->index || pSprite->owner == pSpr->index || pXSpr->health == 0)
6325             continue;
6326 
6327         x = pSpr->x;
6328         y = pSpr->y;
6329         z = pSpr->z;
6330 
6331         dx = x - pSprite->x;
6332         dy = y - pSprite->y;
6333         nDist = approxDist(dx, dy);
6334 
6335         hearDist = pDudeInfo->hearDist >> 1;
6336         seeDist = (stealth) ? pDudeInfo->seeDist / 3 : pDudeInfo->seeDist >> 1;
6337 
6338         eyeAboveZ = (pDudeInfo->eyeHeight * pSprite->yrepeat) << 2;
6339         if (nDist > seeDist || !cansee(x, y, z, pSpr->sectnum, pSprite->x, pSprite->y, pSprite->z - eyeAboveZ, pSprite->sectnum)) continue;
6340         else if ((pPlayer = getPlayerById(pSpr->type)) == NULL) { // check if this dude is a target for some others
6341 
6342             if (pXSpr->target != pSprite->index) continue;
6343             else return pSpr->index;
6344 
6345         }
6346 
6347         bool invisible = (powerupCheck(pPlayer, kPwUpShadowCloak) > 0);
6348         int periphery = ClipLow(pDudeInfo->periphery, kAng90);
6349         int nDeltaAngle = 1024;
6350 
6351         if (!invisible) {
6352 
6353             if (stealth) {
6354                 switch (pPlayer->lifeMode) {
6355                 case kModeHuman:
6356                 case kModeHumanShrink:
6357                     if (pPlayer->lifeMode == kModeHumanShrink) {
6358                         seeDist -= mulscale8(164, seeDist);
6359                         hearDist -= mulscale8(164, hearDist);
6360                     }
6361                     if (pPlayer->posture == kPostureCrouch) {
6362                         seeDist -= mulscale8(64, seeDist);
6363                         hearDist -= mulscale8(128, hearDist);
6364                     }
6365                     break;
6366                 case kModeHumanGrown:
6367                     if (pPlayer->posture != kPostureCrouch) {
6368                         seeDist += mulscale8(72, seeDist);
6369                         hearDist += mulscale8(64, hearDist);
6370                     } else {
6371                         seeDist += mulscale8(48, seeDist);
6372                     }
6373                     break;
6374                 }
6375             }
6376 
6377             seeDist = ClipLow(seeDist, 0);
6378             hearDist = ClipLow(hearDist, 0);
6379             if (!pXSprite->dudeDeaf)
6380                 hearChance = mulscale8(1, ClipLow(((hearDist - nDist) + (abs(xvel[pSpr->index]) + abs(yvel[pSpr->index]) + abs(zvel[pSpr->index]))) >> 6, 0));
6381 
6382             if (!pXSprite->dudeGuard) {
6383                 seeChance = 100 - mulscale8(ClipRange(5 - gGameOptions.nDifficulty, 1, 4), nDist >> 1);
6384                 nDeltaAngle = ((getangle(dx, dy) + 1024 - pSprite->ang) & 2047) - 1024;
6385             }
6386 
6387         }
6388 
6389         if (!isTouchingSprite(pSprite->extra, pSpr->extra) && !isTouchingSprite(pSpr->extra, pSprite->extra)) {
6390 
6391             if (nDist < hearDist && hearChance > 0) {
6392                 consoleSysMsg("Patrol dude #%d hearing the Player #%d.", pSprite->index, pPlayer->nPlayer + 1);
6393                 pXSprite->data3 += hearChance;
6394                 if (!stealth) {
6395                     target = pSpr->index;
6396                     break;
6397                 }
6398             }
6399 
6400             if (nDist < seeDist && klabs(nDeltaAngle) < periphery && seeChance > 0) {
6401                 consoleSysMsg("Patrol dude #%d seeing the Player #%d.", pSprite->index, pPlayer->nPlayer + 1);
6402                 pXSprite->data3 += seeChance;
6403                 if (!stealth) {
6404                     target = pSpr->index;
6405                     break;
6406                 }
6407             }
6408 
6409             if ((pXSprite->data3 = ClipRange(pXSprite->data3, 0, kMaxPatrolSpotValue)) == kMaxPatrolSpotValue) {
6410                 target = pSpr->index;
6411                 break;
6412             }
6413 
6414 
6415         } else {
6416 
6417             consoleSysMsg("Patrol dude #%d spot the Player #%d via touch.", pSprite->index, pPlayer->nPlayer + 1);
6418             if (invisible) pPlayer->pwUpTime[kPwUpShadowCloak] = 0;
6419             target = pSpr->index;
6420             break;
6421 
6422         }
6423 
6424         //int perc = (100 * ClipHigh(pXSprite->data3, kMaxPatrolSpotValue)) / kMaxPatrolSpotValue;
6425         //viewSetSystemMessage("%d / %d / %d / %d", hearChance, seeDist, seeChance, perc);
6426 
6427     }
6428 
6429     if (target >= 0) return target;
6430     else pXSprite->data3 -= ClipLow(((100 * pXSprite->data3) / kMaxPatrolSpotValue) >> 2, 3);
6431     return -1;
6432 }
6433 
aiPatrolThink(spritetype * pSprite,XSPRITE * pXSprite)6434 void aiPatrolThink(spritetype* pSprite, XSPRITE* pXSprite) {
6435 
6436     dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
6437 
6438     int nTarget = -1;
6439     if ((nTarget = aiPatrolSearchTargets(pSprite, pXSprite)) != -1) {
6440         aiPatrolStop(pSprite, nTarget, pXSprite->dudeAmbush);
6441         return;
6442     }
6443 
6444     int omarker = pXSprite->target;
6445     spritetype* pMarker = NULL; XSPRITE* pXMarker = NULL;
6446     if (spriRangeIsFine(omarker)) {
6447         pMarker = &sprite[omarker];
6448         pXMarker = &xsprite[sprite[omarker].extra];
6449     }
6450 
6451     DUDEINFO_EXTRA* pExtra = &gDudeInfoExtra[pSprite->type - kDudeBase];
6452     bool crouch = aiPatrolCrouching(pXSprite->aiState), uwater = spriteIsUnderwater(pSprite);
6453     bool isFinal = (spriRangeIsFine(omarker) && omarker == pXSprite->target && xsprite[sprite[omarker].extra].data2 < 0);
6454 
6455     if (pSprite->type == kDudeModernCustom && ((uwater && !canSwim(pSprite)) || !canWalk(pSprite))) aiPatrolStop(pSprite, -1);
6456     else if (omarker <= 0) aiPatrolSetMarker(pSprite, pXSprite);
6457     else if (aiPatrolWaiting(pXSprite->aiState)) {
6458 
6459         if (pXSprite->stateTimer > 0 || (pXMarker && pXMarker->data1 == pXMarker->data2))
6460             return;
6461 
6462         aiPatrolSetMarker(pSprite, pXSprite);
6463         if (isFinal) {
6464             aiPatrolStop(pSprite, -1);
6465             return;
6466         }
6467 
6468     } else if (aiPatrolMarkerReached(pSprite, pXSprite)) {
6469 
6470         pMarker = &sprite[omarker];
6471         pXMarker = &xsprite[sprite[omarker].extra];
6472 
6473         if (pMarker->owner == pSprite->index)
6474             pMarker->owner = -1;
6475 
6476         if (pMarker->flags > 0) {
6477             if ((pMarker->flags & kModernTypeFlag1) && (pMarker->flags & kModernTypeFlag2))
6478                 crouch = !crouch;
6479             else if (pMarker->flags & kModernTypeFlag2)
6480                 crouch = false;
6481             else if (pMarker->flags & kModernTypeFlag1 && aiCanCrouch(pSprite))
6482                 crouch = true;
6483         }
6484 
6485         trTriggerSprite(omarker, pXMarker, kCmdToggle); // trigger it!
6486 
6487         if (pXMarker->waitTime > 0 || pXMarker->data1 == pXMarker->data2) {
6488 
6489             if (uwater) aiPatrolState(pSprite, kAiStatePatrolWaitW);
6490             else if (crouch) aiPatrolState(pSprite, kAiStatePatrolWaitC);
6491             else aiPatrolState(pSprite, kAiStatePatrolWaitL);
6492 
6493             xvel[pSprite->index] = yvel[pSprite->index] = 0;
6494             if (pExtra->flying) zvel[pSprite->index] = 0;
6495             pSprite->ang = sprite[pXMarker->reference].ang & 2047;
6496             if (pXMarker->waitTime)
6497                 pXSprite->stateTimer = (pXMarker->waitTime * 120) / 10;
6498 
6499             return;
6500 
6501         } else {
6502 
6503             aiPatrolSetMarker(pSprite, pXSprite);
6504 
6505         }
6506 
6507         // final marker reached, just make enemy to be normal
6508         if (isFinal) {
6509             aiPatrolStop(pSprite, -1);
6510             return;
6511         }
6512 
6513     }
6514 
6515     if (uwater) aiPatrolState(pSprite, kAiStatePatrolMoveW);
6516     else if (crouch) aiPatrolState(pSprite, kAiStatePatrolMoveC);
6517     else aiPatrolState(pSprite, kAiStatePatrolMoveL);
6518     return;
6519 
6520 }
6521 // ------------------------------------------------
6522 
listTx(XSPRITE * pXRedir,int tx)6523 int listTx(XSPRITE* pXRedir, int tx) {
6524     if (txIsRanged(pXRedir)) {
6525         if (tx == -1) tx = pXRedir->data1;
6526         else if (tx < pXRedir->data4) tx++;
6527         else tx = -1;
6528     } else {
6529         if (tx == -1) {
6530             for (int i = 0; i <= 3; i++) {
6531                 if ((tx = GetDataVal(&sprite[pXRedir->reference], i)) <= 0) continue;
6532                 else return tx;
6533             }
6534         } else {
6535             int saved = tx; bool savedFound = false;
6536             for (int i = 0; i <= 3; i++) {
6537                 tx = GetDataVal(&sprite[pXRedir->reference], i);
6538                 if (savedFound && tx > 0) return tx;
6539                 else if (tx != saved) continue;
6540                 else savedFound = true;
6541             }
6542         }
6543 
6544         tx = -1;
6545     }
6546 
6547     return tx;
6548 }
6549 
evrIsRedirector(int nSprite)6550 XSPRITE* evrIsRedirector(int nSprite) {
6551     if (spriRangeIsFine(nSprite)) {
6552         switch (sprite[nSprite].type) {
6553         case kModernRandomTX:
6554         case kModernSequentialTX:
6555             if (xspriRangeIsFine(sprite[nSprite].extra) && xsprite[sprite[nSprite].extra].command == kCmdLink
6556                 && !xsprite[sprite[nSprite].extra].locked) return &xsprite[sprite[nSprite].extra];
6557             break;
6558         }
6559     }
6560 
6561     return NULL;
6562 }
6563 
evrListRedirectors(int objType,int objXIndex,XSPRITE * pXRedir,int * tx)6564 XSPRITE* evrListRedirectors(int objType, int objXIndex, XSPRITE* pXRedir, int* tx) {
6565     if (!gEventRedirectsUsed) return NULL;
6566     else if (pXRedir && (*tx = listTx(pXRedir, *tx)) != -1)
6567         return pXRedir;
6568 
6569     int id = 0;
6570     switch (objType) {
6571         case OBJ_SECTOR:
6572             if (!xsectRangeIsFine(objXIndex)) return NULL;
6573             id = xsector[objXIndex].txID;
6574             break;
6575         case OBJ_SPRITE:
6576             if (!xspriRangeIsFine(objXIndex)) return NULL;
6577             id = xsprite[objXIndex].txID;
6578             break;
6579         case OBJ_WALL:
6580             if (!xwallRangeIsFine(objXIndex)) return NULL;
6581             id = xwall[objXIndex].txID;
6582             break;
6583         default:
6584             return NULL;
6585     }
6586 
6587     int nIndex = (pXRedir) ? pXRedir->reference : -1; bool prevFound = false;
6588     for (int i = bucketHead[id]; i < bucketHead[id + 1]; i++) {
6589         if (rxBucket[i].type != OBJ_SPRITE) continue;
6590         XSPRITE* pXSpr = evrIsRedirector(rxBucket[i].index);
6591         if (!pXSpr) continue;
6592         else if (prevFound || nIndex == -1) { *tx = listTx(pXSpr, *tx); return pXSpr; }
6593         else if (nIndex != pXSpr->reference) continue;
6594         else prevFound = true;
6595     }
6596 
6597     *tx = -1;
6598     return NULL;
6599 }
6600 
6601 // this function checks if all TX objects have the same value
incDecGoalValueIsReached(XSPRITE * pXSprite)6602 bool incDecGoalValueIsReached(XSPRITE* pXSprite) {
6603 
6604     if (pXSprite->data3 != pXSprite->sysData1) return false;
6605     char buffer[5]; sprintf(buffer, "%d", abs(pXSprite->data1)); int len = strlen(buffer); int rx = -1;
6606     for (int i = bucketHead[pXSprite->txID]; i < bucketHead[pXSprite->txID + 1]; i++) {
6607         if (rxBucket[i].type == OBJ_SPRITE && evrIsRedirector(rxBucket[i].index)) continue;
6608         for (int a = 0; a < len; a++) {
6609             if (getDataFieldOfObject(rxBucket[i].type, rxBucket[i].index, (buffer[a] - 52) + 4) != pXSprite->data3)
6610                 return false;
6611         }
6612     }
6613 
6614     XSPRITE* pXRedir = NULL; // check redirected TX buckets
6615     while ((pXRedir = evrListRedirectors(OBJ_SPRITE, sprite[pXSprite->reference].extra, pXRedir, &rx)) != NULL) {
6616         for (int i = bucketHead[rx]; i < bucketHead[rx + 1]; i++) {
6617             for (int a = 0; a < len; a++) {
6618                 if (getDataFieldOfObject(rxBucket[i].type, rxBucket[i].index, (buffer[a] - 52) + 4) != pXSprite->data3)
6619                     return false;
6620             }
6621         }
6622     }
6623 
6624     return true;
6625 }
6626 
seqSpawnerOffSameTx(XSPRITE * pXSource)6627 void seqSpawnerOffSameTx(XSPRITE* pXSource) {
6628 
6629     for (int i = 0; i < kMaxXSprites; i++) {
6630 
6631         XSPRITE* pXSprite = &xsprite[i];
6632         if (pXSprite->reference != pXSource->reference && spriRangeIsFine(pXSprite->reference)) {
6633             if (sprite[pXSprite->reference].type != kModernSeqSpawner) continue;
6634             else if (pXSprite->txID == pXSource->txID && pXSprite->state == 1) {
6635                 evKill(pXSprite->reference, OBJ_SPRITE);
6636                 pXSprite->state = 0;
6637             }
6638         }
6639 
6640     }
6641 
6642 }
6643 
6644 // this function can be called via sending numbered command to TX kChannelModernEndLevelCustom
6645 // it allows to set custom next level instead of taking it from INI file.
levelEndLevelCustom(int nLevel)6646 void levelEndLevelCustom(int nLevel) {
6647 
6648     gGameOptions.uGameFlags |= 1;
6649 
6650     if (nLevel >= 16 || nLevel < 0) {
6651         gGameOptions.uGameFlags |= 2;
6652         gGameOptions.nLevel = 0;
6653         return;
6654     }
6655 
6656     gNextLevel = nLevel;
6657 }
6658 
callbackUniMissileBurst(int nSprite)6659 void callbackUniMissileBurst(int nSprite) // 22
6660 {
6661     dassert(nSprite >= 0 && nSprite < kMaxSprites);
6662     if (sprite[nSprite].statnum != kStatProjectile) return;
6663     spritetype* pSprite = &sprite[nSprite];
6664     int nAngle = getangle(xvel[nSprite], yvel[nSprite]);
6665     int nRadius = 0x55555;
6666 
6667     for (int i = 0; i < 8; i++)
6668     {
6669         spritetype* pBurst = actSpawnSprite(pSprite, 5);
6670 
6671         pBurst->type = pSprite->type;
6672         pBurst->shade = pSprite->shade;
6673         pBurst->picnum = pSprite->picnum;
6674 
6675         pBurst->cstat = pSprite->cstat;
6676         if ((pBurst->cstat & CSTAT_SPRITE_BLOCK)) {
6677             pBurst->cstat &= ~CSTAT_SPRITE_BLOCK; // we don't want missiles impact each other
6678             evPost(pBurst->index, 3, 100, kCallbackMissileSpriteBlock); // so set blocking flag a bit later
6679         }
6680 
6681         pBurst->pal = pSprite->pal;
6682         pBurst->clipdist = pSprite->clipdist / 4;
6683         pBurst->flags = pSprite->flags;
6684         pBurst->xrepeat = pSprite->xrepeat / 2;
6685         pBurst->yrepeat = pSprite->yrepeat / 2;
6686         pBurst->ang = ((pSprite->ang + missileInfo[pSprite->type - kMissileBase].angleOfs) & 2047);
6687         pBurst->owner = pSprite->owner;
6688 
6689         actBuildMissile(pBurst, pBurst->extra, pSprite->index);
6690 
6691         int nAngle2 = (i << 11) / 8;
6692         int dx = 0;
6693         int dy = mulscale30r(nRadius, Sin(nAngle2));
6694         int dz = mulscale30r(nRadius, -Cos(nAngle2));
6695         if (i & 1)
6696         {
6697             dy >>= 1;
6698             dz >>= 1;
6699         }
6700         RotateVector(&dx, &dy, nAngle);
6701         xvel[pBurst->index] += dx;
6702         yvel[pBurst->index] += dy;
6703         zvel[pBurst->index] += dz;
6704         evPost(pBurst->index, 3, 960, kCallbackRemove);
6705     }
6706     evPost(nSprite, 3, 0, kCallbackRemove);
6707 }
6708 
6709 
callbackMakeMissileBlocking(int nSprite)6710 void callbackMakeMissileBlocking(int nSprite) // 23
6711 {
6712     dassert(nSprite >= 0 && nSprite < kMaxSprites);
6713     if (sprite[nSprite].statnum != kStatProjectile) return;
6714     sprite[nSprite].cstat |= CSTAT_SPRITE_BLOCK;
6715 }
6716 
callbackGenDudeUpdate(int nSprite)6717 void callbackGenDudeUpdate(int nSprite) // 24
6718 {
6719     if (spriRangeIsFine(nSprite))
6720         genDudeUpdate(&sprite[nSprite]);
6721 }
6722 #endif
6723 
6724 ///////////////////////////////////////////////////////////////////
6725 // This file provides modern features for mappers.
6726 // For full documentation please visit http://cruo.bloodgame.ru/xxsystem
6727 ///////////////////////////////////////////////////////////////////