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 = §or[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 = §or[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 = §or[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 = §or[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 = §or[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, §or[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 = §or[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], §or[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], §or[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], §or[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], §or[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, §or[pSpr->sectnum], 0, flag2); break;
5242 case 2: sprite2sectorSlope(pSpr, §or[pSpr->sectnum], 1, flag2); break;
5243 case 3:
5244 if (getflorzofslope(pSpr->sectnum, pSpr->x, pSpr->y) - kSlopeDist <= pSpr->z) sprite2sectorSlope(pSpr, §or[pSpr->sectnum], 0, flag2);
5245 if (getceilzofslope(pSpr->sectnum, pSpr->x, pSpr->y) + kSlopeDist >= pSpr->z) sprite2sectorSlope(pSpr, §or[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 ///////////////////////////////////////////////////////////////////