1 /*
2 ** thingdef.cpp
3 **
4 ** Code pointers for Actor definitions
5 **
6 **---------------------------------------------------------------------------
7 ** Copyright 2002-2006 Christoph Oelckers
8 ** Copyright 2004-2006 Randy Heit
9 ** All rights reserved.
10 **
11 ** Redistribution and use in source and binary forms, with or without
12 ** modification, are permitted provided that the following conditions
13 ** are met:
14 **
15 ** 1. Redistributions of source code must retain the above copyright
16 ** notice, this list of conditions and the following disclaimer.
17 ** 2. Redistributions in binary form must reproduce the above copyright
18 ** notice, this list of conditions and the following disclaimer in the
19 ** documentation and/or other materials provided with the distribution.
20 ** 3. The name of the author may not be used to endorse or promote products
21 ** derived from this software without specific prior written permission.
22 ** 4. When not used as part of ZDoom or a ZDoom derivative, this code will be
23 ** covered by the terms of the GNU General Public License as published by
24 ** the Free Software Foundation; either version 2 of the License, or (at
25 ** your option) any later version.
26 **
27 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
28 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
29 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
30 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
31 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
32 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
36 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 **---------------------------------------------------------------------------
38 **
39 */
40
41 #include "gi.h"
42 #include "g_level.h"
43 #include "actor.h"
44 #include "info.h"
45 #include "sc_man.h"
46 #include "tarray.h"
47 #include "w_wad.h"
48 #include "templates.h"
49 #include "r_defs.h"
50 #include "a_pickups.h"
51 #include "s_sound.h"
52 #include "cmdlib.h"
53 #include "p_lnspec.h"
54 #include "p_effect.h"
55 #include "p_enemy.h"
56 #include "a_action.h"
57 #include "decallib.h"
58 #include "m_random.h"
59 #include "i_system.h"
60 #include "p_local.h"
61 #include "c_console.h"
62 #include "doomerrors.h"
63 #include "a_sharedglobal.h"
64 #include "thingdef/thingdef.h"
65 #include "v_video.h"
66 #include "v_font.h"
67 #include "doomstat.h"
68 #include "v_palette.h"
69 #include "g_shared/a_specialspot.h"
70 #include "actorptrselect.h"
71 #include "m_bbox.h"
72 #include "r_data/r_translate.h"
73 #include "p_trace.h"
74 #include "p_setup.h"
75 #include "gstrings.h"
76
77 AActor *SingleActorFromTID (int tid, AActor *defactor);
78
79 static FRandom pr_camissile ("CustomActorfire");
80 static FRandom pr_camelee ("CustomMelee");
81 static FRandom pr_cabullet ("CustomBullet");
82 static FRandom pr_cajump ("CustomJump");
83 static FRandom pr_cwbullet ("CustomWpBullet");
84 static FRandom pr_cwjump ("CustomWpJump");
85 static FRandom pr_cwpunch ("CustomWpPunch");
86 static FRandom pr_grenade ("ThrowGrenade");
87 static FRandom pr_crailgun ("CustomRailgun");
88 static FRandom pr_spawndebris ("SpawnDebris");
89 static FRandom pr_spawnitemex ("SpawnItemEx");
90 static FRandom pr_burst ("Burst");
91 static FRandom pr_monsterrefire ("MonsterRefire");
92 static FRandom pr_teleport("A_Teleport");
93
94 //==========================================================================
95 //
96 // ACustomInventory :: CallStateChain
97 //
98 // Executes the code pointers in a chain of states
99 // until there is no next state
100 //
101 //==========================================================================
102
CallStateChain(AActor * actor,FState * State)103 bool ACustomInventory::CallStateChain (AActor *actor, FState * State)
104 {
105 StateCallData StateCall;
106 bool result = false;
107 int counter = 0;
108
109 while (State != NULL)
110 {
111 // Assume success. The code pointer will set this to false if necessary
112 StateCall.State = State;
113 StateCall.Result = true;
114 if (State->CallAction(actor, this, &StateCall))
115 {
116 // collect all the results. Even one successful call signifies overall success.
117 result |= StateCall.Result;
118 }
119
120
121 // Since there are no delays it is a good idea to check for infinite loops here!
122 counter++;
123 if (counter >= 10000) break;
124
125 if (StateCall.State == State)
126 {
127 // Abort immediately if the state jumps to itself!
128 if (State == State->GetNextState())
129 {
130 return false;
131 }
132
133 // If both variables are still the same there was no jump
134 // so we must advance to the next state.
135 State = State->GetNextState();
136 }
137 else
138 {
139 State = StateCall.State;
140 }
141 }
142 return result;
143 }
144
145 //==========================================================================
146 //
147 // A_RearrangePointers
148 //
149 // Allow an actor to change its relationship to other actors by
150 // copying pointers freely between TARGET MASTER and TRACER.
151 // Can also assign null value, but does not duplicate A_ClearTarget.
152 //
153 //==========================================================================
154
155
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_RearrangePointers)156 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RearrangePointers)
157 {
158 ACTION_PARAM_START(4);
159 ACTION_PARAM_INT(ptr_target, 0);
160 ACTION_PARAM_INT(ptr_master, 1);
161 ACTION_PARAM_INT(ptr_tracer, 2);
162 ACTION_PARAM_INT(flags, 3);
163
164 // Rearrange pointers internally
165
166 // Fetch all values before modification, so that all fields can get original values
167 AActor
168 *gettarget = self->target,
169 *getmaster = self->master,
170 *gettracer = self->tracer;
171
172 switch (ptr_target) // pick the new target
173 {
174 case AAPTR_MASTER:
175 self->target = getmaster;
176 if (!(PTROP_UNSAFETARGET & flags)) VerifyTargetChain(self);
177 break;
178 case AAPTR_TRACER:
179 self->target = gettracer;
180 if (!(PTROP_UNSAFETARGET & flags)) VerifyTargetChain(self);
181 break;
182 case AAPTR_NULL:
183 self->target = NULL;
184 // THIS IS NOT "A_ClearTarget", so no other targeting info is removed
185 break;
186 }
187
188 // presently permitting non-monsters to set master
189 switch (ptr_master) // pick the new master
190 {
191 case AAPTR_TARGET:
192 self->master = gettarget;
193 if (!(PTROP_UNSAFEMASTER & flags)) VerifyMasterChain(self);
194 break;
195 case AAPTR_TRACER:
196 self->master = gettracer;
197 if (!(PTROP_UNSAFEMASTER & flags)) VerifyMasterChain(self);
198 break;
199 case AAPTR_NULL:
200 self->master = NULL;
201 break;
202 }
203
204 switch (ptr_tracer) // pick the new tracer
205 {
206 case AAPTR_TARGET:
207 self->tracer = gettarget;
208 break; // no verification deemed necessary; the engine never follows a tracer chain(?)
209 case AAPTR_MASTER:
210 self->tracer = getmaster;
211 break; // no verification deemed necessary; the engine never follows a tracer chain(?)
212 case AAPTR_NULL:
213 self->tracer = NULL;
214 break;
215 }
216 }
217
218 //==========================================================================
219 //
220 // A_TransferPointer
221 //
222 // Copy one pointer (MASTER, TARGET or TRACER) from this actor (SELF),
223 // or from this actor's MASTER, TARGET or TRACER.
224 //
225 // You can copy any one of that actor's pointers
226 //
227 // Assign the copied pointer to any one pointer in SELF,
228 // MASTER, TARGET or TRACER.
229 //
230 // Any attempt to make an actor point to itself will replace the pointer
231 // with a null value.
232 //
233 //==========================================================================
234
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_TransferPointer)235 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TransferPointer)
236 {
237 ACTION_PARAM_START(5);
238 ACTION_PARAM_INT(ptr_source, 0);
239 ACTION_PARAM_INT(ptr_recipient, 1);
240 ACTION_PARAM_INT(ptr_sourcefield, 2);
241 ACTION_PARAM_INT(ptr_recipientfield, 3);
242 ACTION_PARAM_INT(flags, 4);
243
244 AActor *source, *recipient;
245
246 // Exchange pointers with actors to whom you have pointers (or with yourself, if you must)
247
248 source = COPY_AAPTR(self, ptr_source);
249 COPY_AAPTR_NOT_NULL(self, recipient, ptr_recipient); // pick an actor to store the provided pointer value
250
251 // convert source from dataprovider to data
252
253 source = COPY_AAPTR(source, ptr_sourcefield);
254
255 if (source == recipient) source = NULL; // The recipient should not acquire a pointer to itself; will write NULL
256
257 if (ptr_recipientfield == AAPTR_DEFAULT) ptr_recipientfield = ptr_sourcefield; // If default: Write to same field as data was read from
258
259 ASSIGN_AAPTR(recipient, ptr_recipientfield, source, flags);
260 }
261
262 //==========================================================================
263 //
264 // A_CopyFriendliness
265 //
266 // Join forces with one of the actors you are pointing to (MASTER by default)
267 //
268 // Normal CopyFriendliness reassigns health. This function will not.
269 //
270 //==========================================================================
271
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_CopyFriendliness)272 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CopyFriendliness)
273 {
274 ACTION_PARAM_START(1);
275 ACTION_PARAM_INT(ptr_source, 0);
276
277 if (self->player) return;
278
279 AActor *source;
280 COPY_AAPTR_NOT_NULL(self, source, ptr_source);
281 self->CopyFriendliness(source, false, false); // No change in current target or health
282 }
283
284 //==========================================================================
285 //
286 // Simple flag changers
287 //
288 //==========================================================================
DEFINE_ACTION_FUNCTION(AActor,A_SetSolid)289 DEFINE_ACTION_FUNCTION(AActor, A_SetSolid)
290 {
291 self->flags |= MF_SOLID;
292 }
293
DEFINE_ACTION_FUNCTION(AActor,A_UnsetSolid)294 DEFINE_ACTION_FUNCTION(AActor, A_UnsetSolid)
295 {
296 self->flags &= ~MF_SOLID;
297 }
298
DEFINE_ACTION_FUNCTION(AActor,A_SetFloat)299 DEFINE_ACTION_FUNCTION(AActor, A_SetFloat)
300 {
301 self->flags |= MF_FLOAT;
302 }
303
DEFINE_ACTION_FUNCTION(AActor,A_UnsetFloat)304 DEFINE_ACTION_FUNCTION(AActor, A_UnsetFloat)
305 {
306 self->flags &= ~(MF_FLOAT|MF_INFLOAT);
307 }
308
309 //==========================================================================
310 //
311 // Customizable attack functions which use actor parameters.
312 //
313 //==========================================================================
DoAttack(AActor * self,bool domelee,bool domissile,int MeleeDamage,FSoundID MeleeSound,const PClass * MissileType,fixed_t MissileHeight)314 static void DoAttack (AActor *self, bool domelee, bool domissile,
315 int MeleeDamage, FSoundID MeleeSound, const PClass *MissileType,fixed_t MissileHeight)
316 {
317 if (self->target == NULL) return;
318
319 A_FaceTarget (self);
320 if (domelee && MeleeDamage>0 && self->CheckMeleeRange ())
321 {
322 int damage = pr_camelee.HitDice(MeleeDamage);
323 if (MeleeSound) S_Sound (self, CHAN_WEAPON, MeleeSound, 1, ATTN_NORM);
324 int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee);
325 P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self);
326 }
327 else if (domissile && MissileType != NULL)
328 {
329 // This seemingly senseless code is needed for proper aiming.
330 self->AddZ(MissileHeight + self->GetBobOffset() - 32*FRACUNIT);
331 AActor *missile = P_SpawnMissileXYZ (self->PosPlusZ(32*FRACUNIT), self, self->target, MissileType, false);
332 self->AddZ(-(MissileHeight + self->GetBobOffset() - 32*FRACUNIT));
333
334 if (missile)
335 {
336 // automatic handling of seeker missiles
337 if (missile->flags2&MF2_SEEKERMISSILE)
338 {
339 missile->tracer=self->target;
340 }
341 P_CheckMissileSpawn(missile, self->radius);
342 }
343 }
344 }
345
DEFINE_ACTION_FUNCTION(AActor,A_MeleeAttack)346 DEFINE_ACTION_FUNCTION(AActor, A_MeleeAttack)
347 {
348 int MeleeDamage = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeDamage, 0);
349 FSoundID MeleeSound = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeSound, 0);
350 DoAttack(self, true, false, MeleeDamage, MeleeSound, NULL, 0);
351 }
352
DEFINE_ACTION_FUNCTION(AActor,A_MissileAttack)353 DEFINE_ACTION_FUNCTION(AActor, A_MissileAttack)
354 {
355 const PClass *MissileType=PClass::FindClass((ENamedName) self->GetClass()->Meta.GetMetaInt (ACMETA_MissileName, NAME_None));
356 fixed_t MissileHeight= self->GetClass()->Meta.GetMetaFixed (ACMETA_MissileHeight, 32*FRACUNIT);
357 DoAttack(self, false, true, 0, 0, MissileType, MissileHeight);
358 }
359
DEFINE_ACTION_FUNCTION(AActor,A_ComboAttack)360 DEFINE_ACTION_FUNCTION(AActor, A_ComboAttack)
361 {
362 int MeleeDamage = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeDamage, 0);
363 FSoundID MeleeSound = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeSound, 0);
364 const PClass *MissileType=PClass::FindClass((ENamedName) self->GetClass()->Meta.GetMetaInt (ACMETA_MissileName, NAME_None));
365 fixed_t MissileHeight= self->GetClass()->Meta.GetMetaFixed (ACMETA_MissileHeight, 32*FRACUNIT);
366 DoAttack(self, true, true, MeleeDamage, MeleeSound, MissileType, MissileHeight);
367 }
368
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_BasicAttack)369 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_BasicAttack)
370 {
371 ACTION_PARAM_START(4);
372 ACTION_PARAM_INT(MeleeDamage, 0);
373 ACTION_PARAM_SOUND(MeleeSound, 1);
374 ACTION_PARAM_CLASS(MissileType, 2);
375 ACTION_PARAM_FIXED(MissileHeight, 3);
376
377 if (MissileType == NULL) return;
378 DoAttack(self, true, true, MeleeDamage, MeleeSound, MissileType, MissileHeight);
379 }
380
381 //==========================================================================
382 //
383 // Custom sound functions.
384 //
385 //==========================================================================
386
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_PlaySound)387 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlaySound)
388 {
389 ACTION_PARAM_START(5);
390 ACTION_PARAM_SOUND(soundid, 0);
391 ACTION_PARAM_INT(channel, 1);
392 ACTION_PARAM_FLOAT(volume, 2);
393 ACTION_PARAM_BOOL(looping, 3);
394 ACTION_PARAM_FLOAT(attenuation, 4);
395
396 if (!looping)
397 {
398 S_Sound (self, channel, soundid, volume, attenuation);
399 }
400 else
401 {
402 if (!S_IsActorPlayingSomething (self, channel&7, soundid))
403 {
404 S_Sound (self, channel | CHAN_LOOP, soundid, volume, attenuation);
405 }
406 }
407 }
408
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_StopSound)409 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_StopSound)
410 {
411 ACTION_PARAM_START(1);
412 ACTION_PARAM_INT(slot, 0);
413
414 S_StopSound(self, slot);
415 }
416
417 //==========================================================================
418 //
419 // These come from a time when DECORATE constants did not exist yet and
420 // the sound interface was less flexible. As a result the parameters are
421 // not optimal and these functions have been deprecated in favor of extending
422 // A_PlaySound and A_StopSound.
423 //
424 //==========================================================================
425
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_PlayWeaponSound)426 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlayWeaponSound)
427 {
428 ACTION_PARAM_START(1);
429 ACTION_PARAM_SOUND(soundid, 0);
430
431 S_Sound (self, CHAN_WEAPON, soundid, 1, ATTN_NORM);
432 }
433
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_PlaySoundEx)434 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlaySoundEx)
435 {
436 ACTION_PARAM_START(4);
437 ACTION_PARAM_SOUND(soundid, 0);
438 ACTION_PARAM_NAME(channel, 1);
439 ACTION_PARAM_BOOL(looping, 2);
440 ACTION_PARAM_INT(attenuation_raw, 3);
441
442 float attenuation;
443 switch (attenuation_raw)
444 {
445 case -1: attenuation = ATTN_STATIC; break; // drop off rapidly
446 default:
447 case 0: attenuation = ATTN_NORM; break; // normal
448 case 1:
449 case 2: attenuation = ATTN_NONE; break; // full volume
450 }
451
452 if (channel < NAME_Auto || channel > NAME_SoundSlot7)
453 {
454 channel = NAME_Auto;
455 }
456
457 if (!looping)
458 {
459 S_Sound (self, int(channel) - NAME_Auto, soundid, 1, attenuation);
460 }
461 else
462 {
463 if (!S_IsActorPlayingSomething (self, int(channel) - NAME_Auto, soundid))
464 {
465 S_Sound (self, (int(channel) - NAME_Auto) | CHAN_LOOP, soundid, 1, attenuation);
466 }
467 }
468 }
469
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_StopSoundEx)470 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_StopSoundEx)
471 {
472 ACTION_PARAM_START(1);
473 ACTION_PARAM_NAME(channel, 0);
474
475 if (channel > NAME_Auto && channel <= NAME_SoundSlot7)
476 {
477 S_StopSound (self, int(channel) - NAME_Auto);
478 }
479 }
480
481 //==========================================================================
482 //
483 // Generic seeker missile function
484 //
485 //==========================================================================
486 static FRandom pr_seekermissile ("SeekerMissile");
487 enum
488 {
489 SMF_LOOK = 1,
490 SMF_PRECISE = 2,
491 SMF_CURSPEED = 4,
492 };
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SeekerMissile)493 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SeekerMissile)
494 {
495 ACTION_PARAM_START(5);
496 ACTION_PARAM_INT(ang1, 0);
497 ACTION_PARAM_INT(ang2, 1);
498 ACTION_PARAM_INT(flags, 2);
499 ACTION_PARAM_INT(chance, 3);
500 ACTION_PARAM_INT(distance, 4);
501
502 if ((flags & SMF_LOOK) && (self->tracer == 0) && (pr_seekermissile()<chance))
503 {
504 self->tracer = P_RoughMonsterSearch (self, distance, true);
505 }
506 if (!P_SeekerMissile(self, clamp<int>(ang1, 0, 90) * ANGLE_1, clamp<int>(ang2, 0, 90) * ANGLE_1, !!(flags & SMF_PRECISE), !!(flags & SMF_CURSPEED)))
507 {
508 if (flags & SMF_LOOK)
509 { // This monster is no longer seekable, so let us look for another one next time.
510 self->tracer = NULL;
511 }
512 }
513 }
514
515 //==========================================================================
516 //
517 // Hitscan attack with a customizable amount of bullets (specified in damage)
518 //
519 //==========================================================================
DEFINE_ACTION_FUNCTION(AActor,A_BulletAttack)520 DEFINE_ACTION_FUNCTION(AActor, A_BulletAttack)
521 {
522 int i;
523 int bangle;
524 int slope;
525
526 if (!self->target) return;
527
528 A_FaceTarget (self);
529 bangle = self->angle;
530
531 slope = P_AimLineAttack (self, bangle, MISSILERANGE);
532
533 S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM);
534 for (i = self->GetMissileDamage (0, 1); i > 0; --i)
535 {
536 int angle = bangle + (pr_cabullet.Random2() << 20);
537 int damage = ((pr_cabullet()%5)+1)*3;
538 P_LineAttack(self, angle, MISSILERANGE, slope, damage,
539 NAME_Hitscan, NAME_BulletPuff);
540 }
541 }
542
543
544 //==========================================================================
545 //
546 // Do the state jump
547 //
548 //==========================================================================
DoJump(AActor * self,FState * CallingState,FState * jumpto,StateCallData * statecall)549 static void DoJump(AActor * self, FState * CallingState, FState *jumpto, StateCallData *statecall)
550 {
551 if (jumpto == NULL) return;
552
553 if (statecall != NULL)
554 {
555 statecall->State = jumpto;
556 }
557 else if (self->player != NULL && CallingState == self->player->psprites[ps_weapon].state)
558 {
559 P_SetPsprite(self->player, ps_weapon, jumpto);
560 }
561 else if (self->player != NULL && CallingState == self->player->psprites[ps_flash].state)
562 {
563 P_SetPsprite(self->player, ps_flash, jumpto);
564 }
565 else if (CallingState == self->state)
566 {
567 self->SetState (jumpto);
568 }
569 else
570 {
571 // something went very wrong. This should never happen.
572 assert(false);
573 }
574 }
575
576 // This is just to avoid having to directly reference the internally defined
577 // CallingState and statecall parameters in the code below.
578 #define ACTION_JUMP(offset) DoJump(self, CallingState, offset, statecall)
579
580 //==========================================================================
581 //
582 // State jump function
583 //
584 //==========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_Jump)585 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Jump)
586 {
587 ACTION_PARAM_START(3);
588 ACTION_PARAM_INT(count, 0);
589 ACTION_PARAM_INT(maxchance, 1);
590
591 if (count >= 2 && (maxchance >= 256 || pr_cajump() < maxchance))
592 {
593 int jumps = 2 + (count == 2? 0 : (pr_cajump() % (count - 1)));
594 ACTION_PARAM_STATE(jumpto, jumps);
595 ACTION_JUMP(jumpto);
596 }
597 ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
598 }
599
600 //==========================================================================
601 //
602 // State jump function
603 //
604 //==========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_JumpIfHealthLower)605 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfHealthLower)
606 {
607 ACTION_PARAM_START(3);
608 ACTION_PARAM_INT(health, 0);
609 ACTION_PARAM_STATE(jump, 1);
610 ACTION_PARAM_INT(ptr_selector, 2);
611
612 AActor *measured;
613
614 measured = COPY_AAPTR(self, ptr_selector);
615
616 if (measured && measured->health < health)
617 {
618 ACTION_JUMP(jump);
619 }
620
621 ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
622 }
623
624 //==========================================================================
625 //
626 // State jump function
627 //
628 //==========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_JumpIfTargetOutsideMeleeRange)629 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetOutsideMeleeRange)
630 {
631 ACTION_PARAM_START(1);
632 ACTION_PARAM_STATE(jump, 0);
633
634 if (!self->CheckMeleeRange())
635 {
636 ACTION_JUMP(jump);
637 }
638 ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
639 }
640
641 //==========================================================================
642 //
643 // State jump function
644 //
645 //==========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_JumpIfTargetInsideMeleeRange)646 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInsideMeleeRange)
647 {
648 ACTION_PARAM_START(1);
649 ACTION_PARAM_STATE(jump, 0);
650
651 if (self->CheckMeleeRange())
652 {
653 ACTION_JUMP(jump);
654 }
655 ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
656 }
657 //==========================================================================
658 //
659 // State jump function
660 //
661 //==========================================================================
DoJumpIfCloser(AActor * target,DECLARE_PARAMINFO)662 void DoJumpIfCloser(AActor *target, DECLARE_PARAMINFO)
663 {
664 ACTION_PARAM_START(3);
665 ACTION_PARAM_FIXED(dist, 0);
666 ACTION_PARAM_STATE(jump, 1);
667 ACTION_PARAM_BOOL(noz, 2);
668
669 ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
670
671 // No target - no jump
672 if (!target)
673 return;
674 if (self->AproxDistance(target) < dist &&
675 (noz ||
676 ((self->Z() > target->Z() && self->Z() - target->Top() < dist) ||
677 (self->Z() <= target->Z() && target->Z() - self->Top() < dist))))
678 {
679 ACTION_JUMP(jump);
680 }
681 }
682
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_JumpIfCloser)683 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfCloser)
684 {
685 AActor *target;
686
687 if (!self->player)
688 {
689 target = self->target;
690 }
691 else
692 {
693 // Does the player aim at something that can be shot?
694 P_BulletSlope(self, &target);
695 }
696 DoJumpIfCloser(target, PUSH_PARAMINFO);
697 }
698
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_JumpIfTracerCloser)699 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTracerCloser)
700 {
701 DoJumpIfCloser(self->tracer, PUSH_PARAMINFO);
702 }
703
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_JumpIfMasterCloser)704 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfMasterCloser)
705 {
706 DoJumpIfCloser(self->master, PUSH_PARAMINFO);
707 }
708
709 //==========================================================================
710 //
711 // State jump function
712 //
713 //==========================================================================
DoJumpIfInventory(AActor * owner,DECLARE_PARAMINFO)714 void DoJumpIfInventory(AActor * owner, DECLARE_PARAMINFO)
715 {
716 ACTION_PARAM_START(4);
717 ACTION_PARAM_CLASS(Type, 0);
718 ACTION_PARAM_INT(ItemAmount, 1);
719 ACTION_PARAM_STATE(JumpOffset, 2);
720 ACTION_PARAM_INT(setowner, 3);
721
722 ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
723
724 if (!Type) return;
725 COPY_AAPTR_NOT_NULL(owner, owner, setowner); // returns if owner ends up being NULL
726
727 AInventory *Item = owner->FindInventory(Type);
728
729 if (Item)
730 {
731 if (ItemAmount > 0)
732 {
733 if (Item->Amount >= ItemAmount)
734 ACTION_JUMP(JumpOffset);
735 }
736 else if (Item->Amount >= Item->MaxAmount)
737 {
738 ACTION_JUMP(JumpOffset);
739 }
740 }
741 }
742
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_JumpIfInventory)743 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInventory)
744 {
745 DoJumpIfInventory(self, PUSH_PARAMINFO);
746 }
747
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_JumpIfInTargetInventory)748 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInTargetInventory)
749 {
750 DoJumpIfInventory(self->target, PUSH_PARAMINFO);
751 }
752
753 //==========================================================================
754 //
755 // State jump function
756 //
757 //==========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_JumpIfArmorType)758 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfArmorType)
759 {
760 ACTION_PARAM_START(3);
761 ACTION_PARAM_NAME(Type, 0);
762 ACTION_PARAM_STATE(JumpOffset, 1);
763 ACTION_PARAM_INT(amount, 2);
764
765 ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
766
767 ABasicArmor * armor = (ABasicArmor *) self->FindInventory(NAME_BasicArmor);
768
769 if (armor && armor->ArmorType == Type && armor->Amount >= amount)
770 ACTION_JUMP(JumpOffset);
771 }
772
773 //==========================================================================
774 //
775 // Parameterized version of A_Explode
776 //
777 //==========================================================================
778
779 enum
780 {
781 XF_HURTSOURCE = 1,
782 XF_NOTMISSILE = 4,
783 };
784
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_Explode)785 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Explode)
786 {
787 ACTION_PARAM_START(8);
788 ACTION_PARAM_INT(damage, 0);
789 ACTION_PARAM_INT(distance, 1);
790 ACTION_PARAM_INT(flags, 2);
791 ACTION_PARAM_BOOL(alert, 3);
792 ACTION_PARAM_INT(fulldmgdistance, 4);
793 ACTION_PARAM_INT(nails, 5);
794 ACTION_PARAM_INT(naildamage, 6);
795 ACTION_PARAM_CLASS(pufftype, 7);
796
797 if (damage < 0) // get parameters from metadata
798 {
799 damage = self->GetClass()->Meta.GetMetaInt (ACMETA_ExplosionDamage, 128);
800 distance = self->GetClass()->Meta.GetMetaInt (ACMETA_ExplosionRadius, damage);
801 flags = !self->GetClass()->Meta.GetMetaInt (ACMETA_DontHurtShooter);
802 alert = false;
803 }
804 else
805 {
806 if (distance <= 0) distance = damage;
807 }
808 // NailBomb effect, from SMMU but not from its source code: instead it was implemented and
809 // generalized from the documentation at http://www.doomworld.com/eternity/engine/codeptrs.html
810
811 if (nails)
812 {
813 angle_t ang;
814 for (int i = 0; i < nails; i++)
815 {
816 ang = i*(ANGLE_MAX/nails);
817 // Comparing the results of a test wad with Eternity, it seems A_NailBomb does not aim
818 P_LineAttack (self, ang, MISSILERANGE, 0,
819 //P_AimLineAttack (self, ang, MISSILERANGE),
820 naildamage, NAME_Hitscan, pufftype);
821 }
822 }
823
824 P_RadiusAttack (self, self->target, damage, distance, self->DamageType, flags, fulldmgdistance);
825 P_CheckSplash(self, distance<<FRACBITS);
826 if (alert && self->target != NULL && self->target->player != NULL)
827 {
828 validcount++;
829 P_RecursiveSound (self->Sector, self->target, false, 0);
830 }
831 }
832
833 //==========================================================================
834 //
835 // A_RadiusThrust
836 //
837 //==========================================================================
838
839 enum
840 {
841 RTF_AFFECTSOURCE = 1,
842 RTF_NOIMPACTDAMAGE = 2,
843 RTF_NOTMISSILE = 4,
844 };
845
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_RadiusThrust)846 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RadiusThrust)
847 {
848 ACTION_PARAM_START(3);
849 ACTION_PARAM_INT(force, 0);
850 ACTION_PARAM_INT(distance, 1);
851 ACTION_PARAM_INT(flags, 2);
852 ACTION_PARAM_INT(fullthrustdistance, 3);
853
854 bool sourcenothrust = false;
855
856 if (force == 0) force = 128;
857 if (distance <= 0) distance = abs(force);
858
859 // Temporarily negate MF2_NODMGTHRUST on the shooter, since it renders this function useless.
860 if (!(flags & RTF_NOTMISSILE) && self->target != NULL && self->target->flags2 & MF2_NODMGTHRUST)
861 {
862 sourcenothrust = true;
863 self->target->flags2 &= ~MF2_NODMGTHRUST;
864 }
865
866 P_RadiusAttack (self, self->target, force, distance, self->DamageType, flags | RADF_NODAMAGE, fullthrustdistance);
867 P_CheckSplash(self, distance << FRACBITS);
868
869 if (sourcenothrust)
870 {
871 self->target->flags2 |= MF2_NODMGTHRUST;
872 }
873 }
874
875 //==========================================================================
876 //
877 // Execute a line special / script
878 //
879 //==========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_CallSpecial)880 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CallSpecial)
881 {
882 ACTION_PARAM_START(6);
883 ACTION_PARAM_INT(special, 0);
884 ACTION_PARAM_INT(arg1, 1);
885 ACTION_PARAM_INT(arg2, 2);
886 ACTION_PARAM_INT(arg3, 3);
887 ACTION_PARAM_INT(arg4, 4);
888 ACTION_PARAM_INT(arg5, 5);
889
890 bool res = !!P_ExecuteSpecial(special, NULL, self, false, arg1, arg2, arg3, arg4, arg5);
891
892 ACTION_SET_RESULT(res);
893 }
894
895 //==========================================================================
896 //
897 // The ultimate code pointer: Fully customizable missiles!
898 //
899 //==========================================================================
900 enum CM_Flags
901 {
902 CMF_AIMMODE = 3,
903 CMF_TRACKOWNER = 4,
904 CMF_CHECKTARGETDEAD = 8,
905
906 CMF_ABSOLUTEPITCH = 16,
907 CMF_OFFSETPITCH = 32,
908 CMF_SAVEPITCH = 64,
909
910 CMF_ABSOLUTEANGLE = 128
911 };
912
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_CustomMissile)913 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomMissile)
914 {
915 ACTION_PARAM_START(7);
916 ACTION_PARAM_CLASS(ti, 0);
917 ACTION_PARAM_FIXED(spawnheight, 1);
918 ACTION_PARAM_INT(spawnofs_xy, 2);
919 ACTION_PARAM_ANGLE(angle, 3);
920 ACTION_PARAM_INT(flags, 4);
921 ACTION_PARAM_ANGLE(pitch, 5);
922 ACTION_PARAM_INT(ptr, 6);
923
924 AActor *ref = COPY_AAPTR(self, ptr);
925
926 int aimmode = flags & CMF_AIMMODE;
927
928 AActor * targ;
929 AActor * missile;
930
931 if (ref != NULL || aimmode == 2)
932 {
933 if (ti)
934 {
935 angle_t ang = (self->angle - ANGLE_90) >> ANGLETOFINESHIFT;
936 fixed_t x = spawnofs_xy * finecosine[ang];
937 fixed_t y = spawnofs_xy * finesine[ang];
938 fixed_t z = spawnheight + self->GetBobOffset() - 32*FRACUNIT + (self->player? self->player->crouchoffset : 0);
939
940 fixedvec3 pos = self->Pos();
941 switch (aimmode)
942 {
943 case 0:
944 default:
945 // same adjustment as above (in all 3 directions this time) - for better aiming!
946 self->SetXYZ(self->Vec3Offset(x, y, z));
947 missile = P_SpawnMissileXYZ(self->PosPlusZ(32*FRACUNIT), self, ref, ti, false);
948 self->SetXYZ(pos);
949 break;
950
951 case 1:
952 missile = P_SpawnMissileXYZ(self->Vec3Offset(x, y, self->GetBobOffset() + spawnheight), self, ref, ti, false);
953 break;
954
955 case 2:
956 self->SetXYZ(self->Vec3Offset(x, y, 0));
957 missile = P_SpawnMissileAngleZSpeed(self, self->Z() + self->GetBobOffset() + spawnheight, ti, self->angle, 0, GetDefaultByType(ti)->Speed, self, false);
958 self->SetXYZ(pos);
959
960 flags |= CMF_ABSOLUTEPITCH;
961
962 break;
963 }
964
965 if (missile != NULL)
966 {
967 // Use the actual velocity instead of the missile's Speed property
968 // so that this can handle missiles with a high vertical velocity
969 // component properly.
970
971 fixed_t missilespeed;
972
973 if ( (CMF_ABSOLUTEPITCH|CMF_OFFSETPITCH) & flags)
974 {
975 if (CMF_OFFSETPITCH & flags)
976 {
977 FVector2 velocity (missile->velx, missile->vely);
978 pitch += R_PointToAngle2(0,0, (fixed_t)velocity.Length(), missile->velz);
979 }
980 ang = pitch >> ANGLETOFINESHIFT;
981 missilespeed = abs(FixedMul(finecosine[ang], missile->Speed));
982 missile->velz = FixedMul(finesine[ang], missile->Speed);
983 }
984 else
985 {
986 FVector2 velocity (missile->velx, missile->vely);
987 missilespeed = (fixed_t)velocity.Length();
988 }
989
990 if (CMF_SAVEPITCH & flags)
991 {
992 missile->pitch = pitch;
993 // In aimmode 0 and 1 without absolutepitch or offsetpitch, the pitch parameter
994 // contains the unapplied parameter. In that case, it is set as pitch without
995 // otherwise affecting the spawned actor.
996 }
997
998 missile->angle = (CMF_ABSOLUTEANGLE & flags) ? angle : missile->angle + angle ;
999
1000 ang = missile->angle >> ANGLETOFINESHIFT;
1001 missile->velx = FixedMul(missilespeed, finecosine[ang]);
1002 missile->vely = FixedMul(missilespeed, finesine[ang]);
1003
1004 // handle projectile shooting projectiles - track the
1005 // links back to a real owner
1006 if (self->isMissile(!!(flags & CMF_TRACKOWNER)))
1007 {
1008 AActor *owner = self ;//->target;
1009 while (owner->isMissile(!!(flags & CMF_TRACKOWNER)) && owner->target)
1010 owner = owner->target;
1011 targ = owner;
1012 missile->target = owner;
1013 // automatic handling of seeker missiles
1014 if (self->flags2 & missile->flags2 & MF2_SEEKERMISSILE)
1015 {
1016 missile->tracer = self->tracer;
1017 }
1018 }
1019 else if (missile->flags2 & MF2_SEEKERMISSILE)
1020 {
1021 // automatic handling of seeker missiles
1022 missile->tracer = self->target;
1023 }
1024 // we must redo the spectral check here because the owner is set after spawning so the FriendPlayer value may be wrong
1025 if (missile->flags4 & MF4_SPECTRAL)
1026 {
1027 if (missile->target != NULL)
1028 {
1029 missile->SetFriendPlayer(missile->target->player);
1030 }
1031 else
1032 {
1033 missile->FriendPlayer = 0;
1034 }
1035 }
1036 P_CheckMissileSpawn(missile, self->radius);
1037 }
1038 }
1039 }
1040 else if (flags & CMF_CHECKTARGETDEAD)
1041 {
1042 // Target is dead and the attack shall be aborted.
1043 if (self->SeeState != NULL && (self->health > 0 || !(self->flags3 & MF3_ISMONSTER)))
1044 self->SetState(self->SeeState);
1045 }
1046 }
1047
1048 //==========================================================================
1049 //
1050 // An even more customizable hitscan attack
1051 //
1052 //==========================================================================
1053 enum CBA_Flags
1054 {
1055 CBAF_AIMFACING = 1,
1056 CBAF_NORANDOM = 2,
1057 CBAF_EXPLICITANGLE = 4,
1058 CBAF_NOPITCH = 8,
1059 CBAF_NORANDOMPUFFZ = 16,
1060 };
1061
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_CustomBulletAttack)1062 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomBulletAttack)
1063 {
1064 ACTION_PARAM_START(8);
1065 ACTION_PARAM_ANGLE(spread_xy, 0);
1066 ACTION_PARAM_ANGLE(spread_z, 1);
1067 ACTION_PARAM_INT(numbullets, 2);
1068 ACTION_PARAM_INT(damageperbullet, 3);
1069 ACTION_PARAM_CLASS(pufftype, 4);
1070 ACTION_PARAM_FIXED(range, 5);
1071 ACTION_PARAM_INT(flags, 6);
1072 ACTION_PARAM_INT(ptr, 7);
1073
1074 AActor *ref = COPY_AAPTR(self, ptr);
1075
1076 if (range == 0)
1077 range = MISSILERANGE;
1078
1079 int i;
1080 int bangle;
1081 int bslope = 0;
1082 int laflags = (flags & CBAF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0;
1083
1084 if (ref != NULL || (flags & CBAF_AIMFACING))
1085 {
1086 if (!(flags & CBAF_AIMFACING))
1087 {
1088 A_Face(self, ref);
1089 }
1090 bangle = self->angle;
1091
1092 if (!pufftype) pufftype = PClass::FindClass(NAME_BulletPuff);
1093
1094 if (!(flags & CBAF_NOPITCH)) bslope = P_AimLineAttack (self, bangle, MISSILERANGE);
1095
1096 S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM);
1097 for (i = 0; i < numbullets; i++)
1098 {
1099 int angle = bangle;
1100 int slope = bslope;
1101
1102 if (flags & CBAF_EXPLICITANGLE)
1103 {
1104 angle += spread_xy;
1105 slope += spread_z;
1106 }
1107 else
1108 {
1109 angle += pr_cwbullet.Random2() * (spread_xy / 255);
1110 slope += pr_cwbullet.Random2() * (spread_z / 255);
1111 }
1112
1113 int damage = damageperbullet;
1114
1115 if (!(flags & CBAF_NORANDOM))
1116 damage *= ((pr_cabullet()%3)+1);
1117
1118 P_LineAttack(self, angle, range, slope, damage, NAME_Hitscan, pufftype, laflags);
1119 }
1120 }
1121 }
1122
1123 //==========================================================================
1124 //
1125 // A fully customizable melee attack
1126 //
1127 //==========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_CustomMeleeAttack)1128 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomMeleeAttack)
1129 {
1130 ACTION_PARAM_START(5);
1131 ACTION_PARAM_INT(damage, 0);
1132 ACTION_PARAM_SOUND(meleesound, 1);
1133 ACTION_PARAM_SOUND(misssound, 2);
1134 ACTION_PARAM_NAME(damagetype, 3);
1135 ACTION_PARAM_BOOL(bleed, 4);
1136
1137 if (damagetype == NAME_None)
1138 damagetype = NAME_Melee; // Melee is the default type
1139
1140 if (!self->target)
1141 return;
1142
1143 A_FaceTarget (self);
1144 if (self->CheckMeleeRange ())
1145 {
1146 if (meleesound)
1147 S_Sound (self, CHAN_WEAPON, meleesound, 1, ATTN_NORM);
1148 int newdam = P_DamageMobj (self->target, self, self, damage, damagetype);
1149 if (bleed)
1150 P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self);
1151 }
1152 else
1153 {
1154 if (misssound)
1155 S_Sound (self, CHAN_WEAPON, misssound, 1, ATTN_NORM);
1156 }
1157 }
1158
1159 //==========================================================================
1160 //
1161 // A fully customizable combo attack
1162 //
1163 //==========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_CustomComboAttack)1164 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomComboAttack)
1165 {
1166 ACTION_PARAM_START(6);
1167 ACTION_PARAM_CLASS(ti, 0);
1168 ACTION_PARAM_FIXED(spawnheight, 1);
1169 ACTION_PARAM_INT(damage, 2);
1170 ACTION_PARAM_SOUND(meleesound, 3);
1171 ACTION_PARAM_NAME(damagetype, 4);
1172 ACTION_PARAM_BOOL(bleed, 5);
1173
1174 if (!self->target)
1175 return;
1176
1177 A_FaceTarget (self);
1178 if (self->CheckMeleeRange())
1179 {
1180 if (damagetype == NAME_None)
1181 damagetype = NAME_Melee; // Melee is the default type
1182 if (meleesound)
1183 S_Sound (self, CHAN_WEAPON, meleesound, 1, ATTN_NORM);
1184 int newdam = P_DamageMobj (self->target, self, self, damage, damagetype);
1185 if (bleed)
1186 P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self);
1187 }
1188 else if (ti)
1189 {
1190 // This seemingly senseless code is needed for proper aiming.
1191 self->AddZ(spawnheight + self->GetBobOffset() - 32*FRACUNIT);
1192 AActor *missile = P_SpawnMissileXYZ (self->PosPlusZ(32*FRACUNIT), self, self->target, ti, false);
1193 self->AddZ(-(spawnheight + self->GetBobOffset() - 32*FRACUNIT));
1194
1195 if (missile)
1196 {
1197 // automatic handling of seeker missiles
1198 if (missile->flags2 & MF2_SEEKERMISSILE)
1199 {
1200 missile->tracer = self->target;
1201 }
1202 P_CheckMissileSpawn(missile, self->radius);
1203 }
1204 }
1205 }
1206
1207 //==========================================================================
1208 //
1209 // State jump function
1210 //
1211 //==========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_JumpIfNoAmmo)1212 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfNoAmmo)
1213 {
1214 ACTION_PARAM_START(1);
1215 ACTION_PARAM_STATE(jump, 0);
1216
1217 ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
1218 if (!ACTION_CALL_FROM_WEAPON()) return;
1219
1220 if (!self->player->ReadyWeapon->CheckAmmo(self->player->ReadyWeapon->bAltFire, false, true))
1221 {
1222 ACTION_JUMP(jump);
1223 }
1224
1225 }
1226
1227
1228 //==========================================================================
1229 //
1230 // An even more customizable hitscan attack
1231 //
1232 //==========================================================================
1233 enum FB_Flags
1234 {
1235 FBF_USEAMMO = 1,
1236 FBF_NORANDOM = 2,
1237 FBF_EXPLICITANGLE = 4,
1238 FBF_NOPITCH = 8,
1239 FBF_NOFLASH = 16,
1240 FBF_NORANDOMPUFFZ = 32,
1241 };
1242
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_FireBullets)1243 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireBullets)
1244 {
1245 ACTION_PARAM_START(7);
1246 ACTION_PARAM_ANGLE(spread_xy, 0);
1247 ACTION_PARAM_ANGLE(spread_z, 1);
1248 ACTION_PARAM_INT(numbullets, 2);
1249 ACTION_PARAM_INT(damageperbullet, 3);
1250 ACTION_PARAM_CLASS(pufftype, 4);
1251 ACTION_PARAM_INT(flags, 5);
1252 ACTION_PARAM_FIXED(range, 6);
1253
1254 if (!self->player) return;
1255
1256 player_t *player = self->player;
1257 AWeapon *weapon = player->ReadyWeapon;
1258
1259 int i;
1260 int bangle;
1261 int bslope = 0;
1262 int laflags = (flags & FBF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0;
1263
1264 if ((flags & FBF_USEAMMO) && weapon && ACTION_CALL_FROM_WEAPON())
1265 {
1266 if (!weapon->DepleteAmmo(weapon->bAltFire, true))
1267 return; // out of ammo
1268 }
1269
1270 if (range == 0)
1271 range = PLAYERMISSILERANGE;
1272
1273 if (!(flags & FBF_NOFLASH)) static_cast<APlayerPawn *>(self)->PlayAttacking2 ();
1274
1275 if (!(flags & FBF_NOPITCH)) bslope = P_BulletSlope(self);
1276 bangle = self->angle;
1277
1278 if (pufftype == NULL)
1279 pufftype = PClass::FindClass(NAME_BulletPuff);
1280
1281 if (weapon != NULL)
1282 {
1283 S_Sound(self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM);
1284 }
1285
1286 if ((numbullets == 1 && !player->refire) || numbullets == 0)
1287 {
1288 int damage = damageperbullet;
1289
1290 if (!(flags & FBF_NORANDOM))
1291 damage *= ((pr_cwbullet()%3)+1);
1292
1293 P_LineAttack(self, bangle, range, bslope, damage, NAME_Hitscan, pufftype, laflags);
1294 }
1295 else
1296 {
1297 if (numbullets < 0)
1298 numbullets = 1;
1299 for (i = 0; i < numbullets; i++)
1300 {
1301 int angle = bangle;
1302 int slope = bslope;
1303
1304 if (flags & FBF_EXPLICITANGLE)
1305 {
1306 angle += spread_xy;
1307 slope += spread_z;
1308 }
1309 else
1310 {
1311 angle += pr_cwbullet.Random2() * (spread_xy / 255);
1312 slope += pr_cwbullet.Random2() * (spread_z / 255);
1313 }
1314
1315 int damage = damageperbullet;
1316
1317 if (!(flags & FBF_NORANDOM))
1318 damage *= ((pr_cwbullet()%3)+1);
1319
1320 P_LineAttack(self, angle, range, slope, damage, NAME_Hitscan, pufftype, laflags);
1321 }
1322 }
1323 }
1324
1325
1326 //==========================================================================
1327 //
1328 // A_FireProjectile
1329 //
1330 //==========================================================================
1331 enum FP_Flags
1332 {
1333 FPF_AIMATANGLE = 1,
1334 FPF_TRANSFERTRANSLATION = 2,
1335 FPF_NOAUTOAIM = 4,
1336 };
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_FireCustomMissile)1337 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireCustomMissile)
1338 {
1339 ACTION_PARAM_START(7);
1340 ACTION_PARAM_CLASS(ti, 0);
1341 ACTION_PARAM_ANGLE(angle, 1);
1342 ACTION_PARAM_BOOL(useammo, 2);
1343 ACTION_PARAM_INT(spawnofs_xy, 3);
1344 ACTION_PARAM_FIXED(spawnheight, 4);
1345 ACTION_PARAM_INT(flags, 5);
1346 ACTION_PARAM_ANGLE(pitch, 6);
1347
1348 if (!self->player) return;
1349
1350
1351 player_t *player = self->player;
1352 AWeapon *weapon = player->ReadyWeapon;
1353 AActor *linetarget;
1354
1355 // Only use ammo if called from a weapon
1356 if (useammo && ACTION_CALL_FROM_WEAPON() && weapon)
1357 {
1358 if (!weapon->DepleteAmmo(weapon->bAltFire, true))
1359 return; // out of ammo
1360 }
1361
1362 if (ti)
1363 {
1364 angle_t ang = (self->angle - ANGLE_90) >> ANGLETOFINESHIFT;
1365 fixed_t x = spawnofs_xy * finecosine[ang];
1366 fixed_t y = spawnofs_xy * finesine[ang];
1367 fixed_t z = spawnheight;
1368 fixed_t shootangle = self->angle;
1369
1370 if (flags & FPF_AIMATANGLE) shootangle += angle;
1371
1372 // Temporarily adjusts the pitch
1373 fixed_t saved_player_pitch = self->pitch;
1374 self->pitch -= pitch;
1375 AActor * misl=P_SpawnPlayerMissile (self, x, y, z, ti, shootangle, &linetarget, NULL, false, (flags & FPF_NOAUTOAIM) != 0);
1376 self->pitch = saved_player_pitch;
1377
1378 // automatic handling of seeker missiles
1379 if (misl)
1380 {
1381 if (flags & FPF_TRANSFERTRANSLATION)
1382 misl->Translation = self->Translation;
1383 if (linetarget && (misl->flags2 & MF2_SEEKERMISSILE))
1384 misl->tracer = linetarget;
1385 if (!(flags & FPF_AIMATANGLE))
1386 {
1387 // This original implementation is to aim straight ahead and then offset
1388 // the angle from the resulting direction.
1389 FVector3 velocity(misl->velx, misl->vely, 0);
1390 fixed_t missilespeed = (fixed_t)velocity.Length();
1391 misl->angle += angle;
1392 angle_t an = misl->angle >> ANGLETOFINESHIFT;
1393 misl->velx = FixedMul (missilespeed, finecosine[an]);
1394 misl->vely = FixedMul (missilespeed, finesine[an]);
1395 }
1396 }
1397 }
1398 }
1399
1400
1401 //==========================================================================
1402 //
1403 // A_CustomPunch
1404 //
1405 // Berserk is not handled here. That can be done with A_CheckIfInventory
1406 //
1407 //==========================================================================
1408
1409 enum
1410 {
1411 CPF_USEAMMO = 1,
1412 CPF_DAGGER = 2,
1413 CPF_PULLIN = 4,
1414 CPF_NORANDOMPUFFZ = 8,
1415 CPF_NOTURN = 16,
1416 CPF_STEALARMOR = 32,
1417 };
1418
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_CustomPunch)1419 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomPunch)
1420 {
1421 ACTION_PARAM_START(8);
1422 ACTION_PARAM_INT(damage, 0);
1423 ACTION_PARAM_BOOL(norandom, 1);
1424 ACTION_PARAM_INT(flags, 2);
1425 ACTION_PARAM_CLASS(pufftype, 3);
1426 ACTION_PARAM_FIXED(range, 4);
1427 ACTION_PARAM_FIXED(lifesteal, 5);
1428 ACTION_PARAM_INT(lifestealmax, 6);
1429 ACTION_PARAM_CLASS(armorbonustype, 7);
1430 ACTION_PARAM_SOUND(MeleeSound, 8);
1431 ACTION_PARAM_SOUND(MissSound, 9);
1432
1433 if (!self->player) return;
1434
1435 player_t *player=self->player;
1436 AWeapon * weapon=player->ReadyWeapon;
1437
1438
1439 angle_t angle;
1440 int pitch;
1441 AActor * linetarget;
1442 int actualdamage;
1443
1444 if (!norandom)
1445 damage *= pr_cwpunch() % 8 + 1;
1446
1447 angle = self->angle + (pr_cwpunch.Random2() << 18);
1448 if (range == 0)
1449 range = MELEERANGE;
1450 pitch = P_AimLineAttack (self, angle, range, &linetarget);
1451
1452 // only use ammo when actually hitting something!
1453 if ((flags & CPF_USEAMMO) && linetarget && weapon && ACTION_CALL_FROM_WEAPON())
1454 {
1455 if (!weapon->DepleteAmmo(weapon->bAltFire, true))
1456 return; // out of ammo
1457 }
1458
1459 if (pufftype == NULL)
1460 pufftype = PClass::FindClass(NAME_BulletPuff);
1461 int puffFlags = LAF_ISMELEEATTACK | ((flags & CPF_NORANDOMPUFFZ) ? LAF_NORANDOMPUFFZ : 0);
1462
1463 P_LineAttack (self, angle, range, pitch, damage, NAME_Melee, pufftype, puffFlags, &linetarget, &actualdamage);
1464
1465 if (!linetarget)
1466 {
1467 if (MissSound) S_Sound(self, CHAN_WEAPON, MissSound, 1, ATTN_NORM);
1468 }
1469 else
1470 {
1471 if (lifesteal && !(linetarget->flags5 & MF5_DONTDRAIN))
1472 {
1473 if (flags & CPF_STEALARMOR)
1474 {
1475 if (armorbonustype == NULL)
1476 {
1477 armorbonustype = PClass::FindClass("ArmorBonus");
1478 }
1479 if (armorbonustype != NULL)
1480 {
1481 assert(armorbonustype->IsDescendantOf(RUNTIME_CLASS(ABasicArmorBonus)));
1482 ABasicArmorBonus *armorbonus = static_cast<ABasicArmorBonus *>(Spawn(armorbonustype, 0,0,0, NO_REPLACE));
1483 armorbonus->SaveAmount *= (actualdamage * lifesteal) >> FRACBITS;
1484 armorbonus->MaxSaveAmount = lifestealmax <= 0 ? armorbonus->MaxSaveAmount : lifestealmax;
1485 armorbonus->flags |= MF_DROPPED;
1486 armorbonus->ClearCounters();
1487
1488 if (!armorbonus->CallTryPickup(self))
1489 {
1490 armorbonus->Destroy ();
1491 }
1492 }
1493 }
1494 else
1495 {
1496 P_GiveBody (self, (actualdamage * lifesteal) >> FRACBITS, lifestealmax);
1497 }
1498 }
1499 if (weapon != NULL)
1500 {
1501 if (MeleeSound) S_Sound(self, CHAN_WEAPON, MeleeSound, 1, ATTN_NORM);
1502 else S_Sound (self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM);
1503 }
1504
1505 if (!(flags & CPF_NOTURN))
1506 {
1507 // turn to face target
1508 self->angle = self->AngleTo(linetarget);
1509 }
1510
1511 if (flags & CPF_PULLIN) self->flags |= MF_JUSTATTACKED;
1512 if (flags & CPF_DAGGER) P_DaggerAlert (self, linetarget);
1513 }
1514 }
1515
1516
1517 //==========================================================================
1518 //
1519 // customizable railgun attack function
1520 //
1521 //==========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_RailAttack)1522 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RailAttack)
1523 {
1524 ACTION_PARAM_START(17);
1525 ACTION_PARAM_INT(damage, 0);
1526 ACTION_PARAM_INT(spawnofs_xy, 1);
1527 ACTION_PARAM_BOOL(useammo, 2);
1528 ACTION_PARAM_COLOR(color1, 3);
1529 ACTION_PARAM_COLOR(color2, 4);
1530 ACTION_PARAM_INT(flags, 5);
1531 ACTION_PARAM_DOUBLE(maxdiff, 6);
1532 ACTION_PARAM_CLASS(pufftype, 7);
1533 ACTION_PARAM_ANGLE(spread_xy, 8);
1534 ACTION_PARAM_ANGLE(spread_z, 9);
1535 ACTION_PARAM_FIXED(range, 10);
1536 ACTION_PARAM_INT(duration, 11);
1537 ACTION_PARAM_DOUBLE(sparsity, 12);
1538 ACTION_PARAM_DOUBLE(driftspeed, 13);
1539 ACTION_PARAM_CLASS(spawnclass, 14);
1540 ACTION_PARAM_FIXED(spawnofs_z, 15);
1541 ACTION_PARAM_INT(SpiralOffset, 16);
1542
1543 if (range == 0) range = 8192*FRACUNIT;
1544 if (sparsity == 0) sparsity=1.0;
1545
1546 if (!self->player) return;
1547
1548 AWeapon *weapon = self->player->ReadyWeapon;
1549
1550 // only use ammo when actually hitting something!
1551 if (useammo && weapon != NULL && ACTION_CALL_FROM_WEAPON())
1552 {
1553 if (!weapon->DepleteAmmo(weapon->bAltFire, true))
1554 return; // out of ammo
1555 }
1556
1557 angle_t angle;
1558 angle_t slope;
1559
1560 if (flags & RAF_EXPLICITANGLE)
1561 {
1562 angle = spread_xy;
1563 slope = spread_z;
1564 }
1565 else
1566 {
1567 angle = pr_crailgun.Random2() * (spread_xy / 255);
1568 slope = pr_crailgun.Random2() * (spread_z / 255);
1569 }
1570
1571 P_RailAttack (self, damage, spawnofs_xy, spawnofs_z, color1, color2, maxdiff, flags, pufftype, angle, slope, range, duration, sparsity, driftspeed, spawnclass, SpiralOffset);
1572 }
1573
1574 //==========================================================================
1575 //
1576 // also for monsters
1577 //
1578 //==========================================================================
1579 enum
1580 {
1581 CRF_DONTAIM = 0,
1582 CRF_AIMPARALLEL = 1,
1583 CRF_AIMDIRECT = 2,
1584 CRF_EXPLICITANGLE = 4,
1585 };
1586
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_CustomRailgun)1587 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomRailgun)
1588 {
1589 ACTION_PARAM_START(17);
1590 ACTION_PARAM_INT(damage, 0);
1591 ACTION_PARAM_INT(spawnofs_xy, 1);
1592 ACTION_PARAM_COLOR(color1, 2);
1593 ACTION_PARAM_COLOR(color2, 3);
1594 ACTION_PARAM_INT(flags, 4);
1595 ACTION_PARAM_INT(aim, 5);
1596 ACTION_PARAM_DOUBLE(maxdiff, 6);
1597 ACTION_PARAM_CLASS(pufftype, 7);
1598 ACTION_PARAM_ANGLE(spread_xy, 8);
1599 ACTION_PARAM_ANGLE(spread_z, 9);
1600 ACTION_PARAM_FIXED(range, 10);
1601 ACTION_PARAM_INT(duration, 11);
1602 ACTION_PARAM_DOUBLE(sparsity, 12);
1603 ACTION_PARAM_DOUBLE(driftspeed, 13);
1604 ACTION_PARAM_CLASS(spawnclass, 14);
1605 ACTION_PARAM_FIXED(spawnofs_z, 15);
1606 ACTION_PARAM_INT(SpiralOffset, 16);
1607
1608 if (range == 0) range = 8192*FRACUNIT;
1609 if (sparsity == 0) sparsity = 1;
1610
1611 AActor *linetarget;
1612
1613 fixedvec3 savedpos = self->Pos();
1614 angle_t saved_angle = self->angle;
1615 fixed_t saved_pitch = self->pitch;
1616
1617 if (aim && self->target == NULL)
1618 {
1619 return;
1620 }
1621 // [RH] Andy Baker's stealth monsters
1622 if (self->flags & MF_STEALTH)
1623 {
1624 self->visdir = 1;
1625 }
1626
1627 self->flags &= ~MF_AMBUSH;
1628
1629
1630 if (aim)
1631 {
1632 self->angle = self->AngleTo(self->target);
1633 }
1634 self->pitch = P_AimLineAttack (self, self->angle, MISSILERANGE, &linetarget, ANGLE_1*60, 0, aim ? self->target : NULL);
1635 if (linetarget == NULL && aim)
1636 {
1637 // We probably won't hit the target, but aim at it anyway so we don't look stupid.
1638 fixedvec2 pos = self->Vec2To(self->target);
1639 TVector2<double> xydiff(pos.x, pos.y);
1640 double zdiff = (self->target->Z() + (self->target->height>>1)) -
1641 (self->Z() + (self->height>>1) - self->floorclip);
1642 self->pitch = int(atan2(zdiff, xydiff.Length()) * ANGLE_180 / -M_PI);
1643 }
1644 // Let the aim trail behind the player
1645 if (aim)
1646 {
1647 saved_angle = self->angle = self->AngleTo(self->target, -self->target->velx * 3, -self->target->vely * 3);
1648
1649 if (aim == CRF_AIMDIRECT)
1650 {
1651 // Tricky: We must offset to the angle of the current position
1652 // but then change the angle again to ensure proper aim.
1653 self->SetXY(self->Vec2Offset(
1654 spawnofs_xy * finecosine[self->angle],
1655 spawnofs_xy * finesine[self->angle]));
1656 spawnofs_xy = 0;
1657 self->angle = self->AngleTo(self->target,- self->target->velx * 3, -self->target->vely * 3);
1658 }
1659
1660 if (self->target->flags & MF_SHADOW)
1661 {
1662 angle_t rnd = pr_crailgun.Random2() << 21;
1663 self->angle += rnd;
1664 saved_angle = rnd;
1665 }
1666 }
1667
1668 angle_t angle = (self->angle - ANG90) >> ANGLETOFINESHIFT;
1669
1670 angle_t angleoffset;
1671 angle_t slopeoffset;
1672
1673 if (flags & CRF_EXPLICITANGLE)
1674 {
1675 angleoffset = spread_xy;
1676 slopeoffset = spread_z;
1677 }
1678 else
1679 {
1680 angleoffset = pr_crailgun.Random2() * (spread_xy / 255);
1681 slopeoffset = pr_crailgun.Random2() * (spread_z / 255);
1682 }
1683
1684 P_RailAttack (self, damage, spawnofs_xy, spawnofs_z, color1, color2, maxdiff, flags, pufftype, angleoffset, slopeoffset, range, duration, sparsity, driftspeed, spawnclass,SpiralOffset);
1685
1686 self->SetXYZ(savedpos);
1687 self->angle = saved_angle;
1688 self->pitch = saved_pitch;
1689 }
1690
1691 //===========================================================================
1692 //
1693 // DoGiveInventory
1694 //
1695 //===========================================================================
1696
DoGiveInventory(AActor * receiver,bool use_aaptr,DECLARE_PARAMINFO)1697 static void DoGiveInventory(AActor * receiver, bool use_aaptr, DECLARE_PARAMINFO)
1698 {
1699 ACTION_PARAM_START(2+use_aaptr);
1700 ACTION_PARAM_CLASS(mi, 0);
1701 ACTION_PARAM_INT(amount, 1);
1702
1703 if (use_aaptr)
1704 {
1705 ACTION_PARAM_INT(setreceiver, 2);
1706 COPY_AAPTR_NOT_NULL(receiver, receiver, setreceiver);
1707 }
1708
1709 bool res=true;
1710
1711 if (amount==0) amount=1;
1712 if (mi)
1713 {
1714 if (!mi->IsDescendantOf (RUNTIME_CLASS(AInventory)))
1715 {
1716 ACTION_SET_RESULT(false);
1717 return;
1718 }
1719
1720 AInventory *item = static_cast<AInventory *>(Spawn (mi, 0, 0, 0, NO_REPLACE));
1721 if (!item)
1722 {
1723 ACTION_SET_RESULT(false);
1724 return;
1725 }
1726 if (item->IsKindOf(RUNTIME_CLASS(AHealth)))
1727 {
1728 item->Amount *= amount;
1729 }
1730 else
1731 {
1732 item->Amount = amount;
1733 }
1734 item->flags |= MF_DROPPED;
1735 item->ClearCounters();
1736 if (!item->CallTryPickup (receiver))
1737 {
1738 item->Destroy ();
1739 res = false;
1740 }
1741 else res = true;
1742 }
1743 else res = false;
1744 ACTION_SET_RESULT(res);
1745
1746 }
1747
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_GiveInventory)1748 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveInventory)
1749 {
1750 DoGiveInventory(self, true, PUSH_PARAMINFO);
1751 }
1752
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_GiveToTarget)1753 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToTarget)
1754 {
1755 DoGiveInventory(self->target, true, PUSH_PARAMINFO);
1756 }
1757
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_GiveToChildren)1758 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToChildren)
1759 {
1760 TThinkerIterator<AActor> it;
1761 AActor * mo;
1762
1763 while ((mo = it.Next()))
1764 {
1765 if (mo->master == self) DoGiveInventory(mo, false, PUSH_PARAMINFO);
1766 }
1767 }
1768
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_GiveToSiblings)1769 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToSiblings)
1770 {
1771 TThinkerIterator<AActor> it;
1772 AActor * mo;
1773
1774 if (self->master != NULL)
1775 {
1776 while ((mo = it.Next()))
1777 {
1778 if (mo->master == self->master && mo != self) DoGiveInventory(mo, false, PUSH_PARAMINFO);
1779 }
1780 }
1781 }
1782
1783 //===========================================================================
1784 //
1785 // A_TakeInventory
1786 //
1787 //===========================================================================
1788
1789 enum
1790 {
1791 TIF_NOTAKEINFINITE = 1,
1792 };
1793
DoTakeInventory(AActor * receiver,bool use_aaptr,DECLARE_PARAMINFO)1794 void DoTakeInventory(AActor * receiver, bool use_aaptr, DECLARE_PARAMINFO)
1795 {
1796 ACTION_PARAM_START(3+use_aaptr);
1797 ACTION_PARAM_CLASS(item, 0);
1798 ACTION_PARAM_INT(amount, 1);
1799 ACTION_PARAM_INT(flags, 2);
1800
1801 if (!item)
1802 {
1803 ACTION_SET_RESULT(false);
1804 return;
1805 }
1806 if (use_aaptr)
1807 {
1808 ACTION_PARAM_INT(setreceiver, 3);
1809 COPY_AAPTR_NOT_NULL(receiver, receiver, setreceiver);
1810 }
1811
1812 bool res = receiver->TakeInventory(item, amount, true, (flags & TIF_NOTAKEINFINITE) != 0);
1813 ACTION_SET_RESULT(res);
1814 }
1815
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_TakeInventory)1816 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeInventory)
1817 {
1818 DoTakeInventory(self, true, PUSH_PARAMINFO);
1819 }
1820
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_TakeFromTarget)1821 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromTarget)
1822 {
1823 DoTakeInventory(self->target, true, PUSH_PARAMINFO);
1824 }
1825
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_TakeFromChildren)1826 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromChildren)
1827 {
1828 TThinkerIterator<AActor> it;
1829 AActor * mo;
1830
1831 while ((mo = it.Next()))
1832 {
1833 if (mo->master == self) DoTakeInventory(mo, false, PUSH_PARAMINFO);
1834 }
1835 }
1836
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_TakeFromSiblings)1837 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromSiblings)
1838 {
1839 TThinkerIterator<AActor> it;
1840 AActor * mo;
1841
1842 if (self->master != NULL)
1843 {
1844 while ((mo = it.Next()))
1845 {
1846 if (mo->master == self->master && mo != self) DoTakeInventory(mo, false, PUSH_PARAMINFO);
1847 }
1848 }
1849 }
1850
1851 //===========================================================================
1852 //
1853 // Common code for A_SpawnItem and A_SpawnItemEx
1854 //
1855 //===========================================================================
1856
1857 enum SIX_Flags
1858 {
1859 SIXF_TRANSFERTRANSLATION = 0x00000001,
1860 SIXF_ABSOLUTEPOSITION = 0x00000002,
1861 SIXF_ABSOLUTEANGLE = 0x00000004,
1862 SIXF_ABSOLUTEVELOCITY = 0x00000008,
1863 SIXF_SETMASTER = 0x00000010,
1864 SIXF_NOCHECKPOSITION = 0x00000020,
1865 SIXF_TELEFRAG = 0x00000040,
1866 SIXF_CLIENTSIDE = 0x00000080, // only used by Skulldronum
1867 SIXF_TRANSFERAMBUSHFLAG = 0x00000100,
1868 SIXF_TRANSFERPITCH = 0x00000200,
1869 SIXF_TRANSFERPOINTERS = 0x00000400,
1870 SIXF_USEBLOODCOLOR = 0x00000800,
1871 SIXF_CLEARCALLERTID = 0x00001000,
1872 SIXF_MULTIPLYSPEED = 0x00002000,
1873 SIXF_TRANSFERSCALE = 0x00004000,
1874 SIXF_TRANSFERSPECIAL = 0x00008000,
1875 SIXF_CLEARCALLERSPECIAL = 0x00010000,
1876 SIXF_TRANSFERSTENCILCOL = 0x00020000,
1877 SIXF_TRANSFERALPHA = 0x00040000,
1878 SIXF_TRANSFERRENDERSTYLE = 0x00080000,
1879 SIXF_SETTARGET = 0x00100000,
1880 SIXF_SETTRACER = 0x00200000,
1881 SIXF_NOPOINTERS = 0x00400000,
1882 SIXF_ORIGINATOR = 0x00800000,
1883 SIXF_TRANSFERSPRITEFRAME = 0x01000000,
1884 SIXF_TRANSFERROLL = 0x02000000,
1885 SIXF_ISTARGET = 0x04000000,
1886 SIXF_ISMASTER = 0x08000000,
1887 SIXF_ISTRACER = 0x10000000,
1888 };
1889
InitSpawnedItem(AActor * self,AActor * mo,int flags)1890 static bool InitSpawnedItem(AActor *self, AActor *mo, int flags)
1891 {
1892 if (mo == NULL)
1893 {
1894 return false;
1895 }
1896 AActor *originator = self;
1897
1898 if (!(mo->flags2 & MF2_DONTTRANSLATE))
1899 {
1900 if (flags & SIXF_TRANSFERTRANSLATION)
1901 {
1902 mo->Translation = self->Translation;
1903 }
1904 else if (flags & SIXF_USEBLOODCOLOR)
1905 {
1906 // [XA] Use the spawning actor's BloodColor to translate the newly-spawned object.
1907 PalEntry bloodcolor = self->GetBloodColor();
1908 mo->Translation = TRANSLATION(TRANSLATION_Blood, bloodcolor.a);
1909 }
1910 }
1911 if (flags & SIXF_TRANSFERPOINTERS)
1912 {
1913 mo->target = self->target;
1914 mo->master = self->master; // This will be overridden later if SIXF_SETMASTER is set
1915 mo->tracer = self->tracer;
1916 }
1917
1918 mo->angle = self->angle;
1919 if (flags & SIXF_TRANSFERPITCH)
1920 {
1921 mo->pitch = self->pitch;
1922 }
1923 if (!(flags & SIXF_ORIGINATOR))
1924 {
1925 while (originator && originator->isMissile())
1926 {
1927 originator = originator->target;
1928 }
1929 }
1930 if (flags & SIXF_TELEFRAG)
1931 {
1932 P_TeleportMove(mo, mo->Pos(), true);
1933 // This is needed to ensure consistent behavior.
1934 // Otherwise it will only spawn if nothing gets telefragged
1935 flags |= SIXF_NOCHECKPOSITION;
1936 }
1937 if (mo->flags3 & MF3_ISMONSTER)
1938 {
1939 if (!(flags & SIXF_NOCHECKPOSITION) && !P_TestMobjLocation(mo))
1940 {
1941 // The monster is blocked so don't spawn it at all!
1942 mo->ClearCounters();
1943 mo->Destroy();
1944 return false;
1945 }
1946 else if (originator && !(flags & SIXF_NOPOINTERS))
1947 {
1948 if (originator->flags3 & MF3_ISMONSTER)
1949 {
1950 // If this is a monster transfer all friendliness information
1951 mo->CopyFriendliness(originator, true);
1952 }
1953 else if (originator->player)
1954 {
1955 // A player always spawns a monster friendly to him
1956 mo->flags |= MF_FRIENDLY;
1957 mo->SetFriendPlayer(originator->player);
1958
1959 AActor * attacker=originator->player->attacker;
1960 if (attacker)
1961 {
1962 if (!(attacker->flags&MF_FRIENDLY) ||
1963 (deathmatch && attacker->FriendPlayer!=0 && attacker->FriendPlayer!=mo->FriendPlayer))
1964 {
1965 // Target the monster which last attacked the player
1966 mo->LastHeard = mo->target = attacker;
1967 }
1968 }
1969 }
1970 }
1971 }
1972 else if (!(flags & SIXF_TRANSFERPOINTERS))
1973 {
1974 // If this is a missile or something else set the target to the originator
1975 mo->target = originator ? originator : self;
1976 }
1977 if (flags & SIXF_NOPOINTERS)
1978 {
1979 //[MC]Intentionally eliminate pointers. Overrides TRANSFERPOINTERS, but is overridden by SETMASTER/TARGET/TRACER.
1980 mo->LastHeard = NULL; //Sanity check.
1981 mo->target = NULL;
1982 mo->master = NULL;
1983 mo->tracer = NULL;
1984 }
1985 if (flags & SIXF_SETMASTER)
1986 { // don't let it attack you (optional)!
1987 mo->master = originator;
1988 }
1989 if (flags & SIXF_SETTARGET)
1990 {
1991 mo->target = originator;
1992 }
1993 if (flags & SIXF_SETTRACER)
1994 {
1995 mo->tracer = originator;
1996 }
1997 if (flags & SIXF_TRANSFERSCALE)
1998 {
1999 mo->scaleX = self->scaleX;
2000 mo->scaleY = self->scaleY;
2001 }
2002 if (flags & SIXF_TRANSFERAMBUSHFLAG)
2003 {
2004 mo->flags = (mo->flags & ~MF_AMBUSH) | (self->flags & MF_AMBUSH);
2005 }
2006 if (flags & SIXF_CLEARCALLERTID)
2007 {
2008 self->RemoveFromHash();
2009 self->tid = 0;
2010 }
2011 if (flags & SIXF_TRANSFERSPECIAL)
2012 {
2013 mo->special = self->special;
2014 memcpy(mo->args, self->args, sizeof(self->args));
2015 }
2016 if (flags & SIXF_CLEARCALLERSPECIAL)
2017 {
2018 self->special = 0;
2019 memset(self->args, 0, sizeof(self->args));
2020 }
2021 if (flags & SIXF_TRANSFERSTENCILCOL)
2022 {
2023 mo->fillcolor = self->fillcolor;
2024 }
2025 if (flags & SIXF_TRANSFERALPHA)
2026 {
2027 mo->alpha = self->alpha;
2028 }
2029 if (flags & SIXF_TRANSFERRENDERSTYLE)
2030 {
2031 mo->RenderStyle = self->RenderStyle;
2032 }
2033
2034 if (flags & SIXF_TRANSFERSPRITEFRAME)
2035 {
2036 mo->sprite = self->sprite;
2037 mo->frame = self->frame;
2038 }
2039
2040 if (flags & SIXF_TRANSFERROLL)
2041 {
2042 mo->roll = self->roll;
2043 }
2044
2045 if (flags & SIXF_ISTARGET)
2046 {
2047 self->target = mo;
2048 }
2049 if (flags & SIXF_ISMASTER)
2050 {
2051 self->master = mo;
2052 }
2053 if (flags & SIXF_ISTRACER)
2054 {
2055 self->tracer = mo;
2056 }
2057 return true;
2058 }
2059
2060 //===========================================================================
2061 //
2062 // A_SpawnItem
2063 //
2064 // Spawns an item in front of the caller like Heretic's time bomb
2065 //
2066 //===========================================================================
2067
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SpawnItem)2068 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnItem)
2069 {
2070 ACTION_PARAM_START(5);
2071 ACTION_PARAM_CLASS(missile, 0);
2072 ACTION_PARAM_FIXED(distance, 1);
2073 ACTION_PARAM_FIXED(zheight, 2);
2074 ACTION_PARAM_BOOL(useammo, 3);
2075 ACTION_PARAM_BOOL(transfer_translation, 4);
2076
2077 if (!missile)
2078 {
2079 ACTION_SET_RESULT(false);
2080 return;
2081 }
2082
2083 // Don't spawn monsters if this actor has been massacred
2084 if (self->DamageType == NAME_Massacre && GetDefaultByType(missile)->flags3&MF3_ISMONSTER) return;
2085
2086 if (distance==0)
2087 {
2088 // use the minimum distance that does not result in an overlap
2089 distance=(self->radius+GetDefaultByType(missile)->radius)>>FRACBITS;
2090 }
2091
2092 if (ACTION_CALL_FROM_WEAPON())
2093 {
2094 // Used from a weapon so use some ammo
2095 AWeapon * weapon=self->player->ReadyWeapon;
2096
2097 if (!weapon) return;
2098 if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) return;
2099 }
2100
2101 AActor * mo = Spawn( missile, self->Vec3Angle(distance, self->angle, -self->floorclip + self->GetBobOffset() + zheight), ALLOW_REPLACE);
2102
2103 int flags = (transfer_translation ? SIXF_TRANSFERTRANSLATION : 0) + (useammo ? SIXF_SETMASTER : 0);
2104 bool res = InitSpawnedItem(self, mo, flags);
2105 ACTION_SET_RESULT(res); // for an inventory item's use state
2106 }
2107
2108 //===========================================================================
2109 //
2110 // A_SpawnItemEx
2111 //
2112 // Enhanced spawning function
2113 //
2114 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SpawnItemEx)2115 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnItemEx)
2116 {
2117 ACTION_PARAM_START(11);
2118 ACTION_PARAM_CLASS(missile, 0);
2119 ACTION_PARAM_FIXED(xofs, 1);
2120 ACTION_PARAM_FIXED(yofs, 2);
2121 ACTION_PARAM_FIXED(zofs, 3);
2122 ACTION_PARAM_FIXED(xvel, 4);
2123 ACTION_PARAM_FIXED(yvel, 5);
2124 ACTION_PARAM_FIXED(zvel, 6);
2125 ACTION_PARAM_ANGLE(angle, 7);
2126 ACTION_PARAM_INT(flags, 8);
2127 ACTION_PARAM_INT(chance, 9);
2128 ACTION_PARAM_INT(tid, 10);
2129
2130 if (!missile)
2131 {
2132 ACTION_SET_RESULT(false);
2133 return;
2134 }
2135
2136 if (chance > 0 && pr_spawnitemex()<chance) return;
2137
2138 // Don't spawn monsters if this actor has been massacred
2139 if (self->DamageType == NAME_Massacre && GetDefaultByType(missile)->flags3&MF3_ISMONSTER) return;
2140
2141 fixedvec2 pos;
2142
2143 if (!(flags & SIXF_ABSOLUTEANGLE))
2144 {
2145 angle += self->angle;
2146 }
2147
2148 angle_t ang = angle >> ANGLETOFINESHIFT;
2149
2150 if (flags & SIXF_ABSOLUTEPOSITION)
2151 {
2152 pos = self->Vec2Offset(xofs, yofs);
2153 }
2154 else
2155 {
2156 // in relative mode negative y values mean 'left' and positive ones mean 'right'
2157 // This is the inverse orientation of the absolute mode!
2158 pos = self->Vec2Offset(
2159 FixedMul(xofs, finecosine[ang]) + FixedMul(yofs, finesine[ang]),
2160 FixedMul(xofs, finesine[ang]) - FixedMul(yofs, finecosine[ang]));
2161 }
2162
2163 if (!(flags & SIXF_ABSOLUTEVELOCITY))
2164 {
2165 // Same orientation issue here!
2166 fixed_t newxvel = FixedMul(xvel, finecosine[ang]) + FixedMul(yvel, finesine[ang]);
2167 yvel = FixedMul(xvel, finesine[ang]) - FixedMul(yvel, finecosine[ang]);
2168 xvel = newxvel;
2169 }
2170
2171 AActor *mo = Spawn(missile, pos.x, pos.y, self->Z() - self->floorclip + self->GetBobOffset() + zofs, ALLOW_REPLACE);
2172 bool res = InitSpawnedItem(self, mo, flags);
2173 ACTION_SET_RESULT(res); // for an inventory item's use state
2174 if (res)
2175 {
2176 if (tid != 0)
2177 {
2178 assert(mo->tid == 0);
2179 mo->tid = tid;
2180 mo->AddToHash();
2181 }
2182 if (flags & SIXF_MULTIPLYSPEED)
2183 {
2184 mo->velx = FixedMul(xvel, mo->Speed);
2185 mo->vely = FixedMul(yvel, mo->Speed);
2186 mo->velz = FixedMul(zvel, mo->Speed);
2187 }
2188 else
2189 {
2190 mo->velx = xvel;
2191 mo->vely = yvel;
2192 mo->velz = zvel;
2193 }
2194 mo->angle = angle;
2195 }
2196 }
2197
2198 //===========================================================================
2199 //
2200 // A_ThrowGrenade
2201 //
2202 // Throws a grenade (like Hexen's fighter flechette)
2203 //
2204 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_ThrowGrenade)2205 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ThrowGrenade)
2206 {
2207 ACTION_PARAM_START(5);
2208 ACTION_PARAM_CLASS(missile, 0);
2209 ACTION_PARAM_FIXED(zheight, 1);
2210 ACTION_PARAM_FIXED(xyvel, 2);
2211 ACTION_PARAM_FIXED(zvel, 3);
2212 ACTION_PARAM_BOOL(useammo, 4);
2213
2214 if (missile == NULL) return;
2215
2216 if (ACTION_CALL_FROM_WEAPON())
2217 {
2218 // Used from a weapon, so use some ammo
2219 AWeapon *weapon = self->player->ReadyWeapon;
2220
2221 if (!weapon) return;
2222 if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) return;
2223 }
2224
2225
2226 AActor * bo;
2227
2228 bo = Spawn(missile,
2229 self->PosPlusZ(-self->floorclip + self->GetBobOffset() + zheight + 35*FRACUNIT + (self->player? self->player->crouchoffset : 0)),
2230 ALLOW_REPLACE);
2231 if (bo)
2232 {
2233 P_PlaySpawnSound(bo, self);
2234 if (xyvel != 0)
2235 bo->Speed = xyvel;
2236 bo->angle = self->angle + (((pr_grenade()&7) - 4) << 24);
2237
2238 angle_t pitch = angle_t(-self->pitch) >> ANGLETOFINESHIFT;
2239 angle_t angle = bo->angle >> ANGLETOFINESHIFT;
2240
2241 // There are two vectors we are concerned about here: xy and z. We rotate
2242 // them separately according to the shooter's pitch and then sum them to
2243 // get the final velocity vector to shoot with.
2244
2245 fixed_t xy_xyscale = FixedMul(bo->Speed, finecosine[pitch]);
2246 fixed_t xy_velz = FixedMul(bo->Speed, finesine[pitch]);
2247 fixed_t xy_velx = FixedMul(xy_xyscale, finecosine[angle]);
2248 fixed_t xy_vely = FixedMul(xy_xyscale, finesine[angle]);
2249
2250 pitch = angle_t(self->pitch) >> ANGLETOFINESHIFT;
2251 fixed_t z_xyscale = FixedMul(zvel, finesine[pitch]);
2252 fixed_t z_velz = FixedMul(zvel, finecosine[pitch]);
2253 fixed_t z_velx = FixedMul(z_xyscale, finecosine[angle]);
2254 fixed_t z_vely = FixedMul(z_xyscale, finesine[angle]);
2255
2256 bo->velx = xy_velx + z_velx + (self->velx >> 1);
2257 bo->vely = xy_vely + z_vely + (self->vely >> 1);
2258 bo->velz = xy_velz + z_velz;
2259
2260 bo->target = self;
2261 P_CheckMissileSpawn (bo, self->radius);
2262 }
2263 else ACTION_SET_RESULT(false);
2264 }
2265
2266
2267 //===========================================================================
2268 //
2269 // A_Recoil
2270 //
2271 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_Recoil)2272 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Recoil)
2273 {
2274 ACTION_PARAM_START(1);
2275 ACTION_PARAM_FIXED(xyvel, 0);
2276
2277 angle_t angle = self->angle + ANG180;
2278 angle >>= ANGLETOFINESHIFT;
2279 self->velx += FixedMul(xyvel, finecosine[angle]);
2280 self->vely += FixedMul(xyvel, finesine[angle]);
2281 }
2282
2283
2284 //===========================================================================
2285 //
2286 // A_SelectWeapon
2287 //
2288 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SelectWeapon)2289 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SelectWeapon)
2290 {
2291 ACTION_PARAM_START(1);
2292 ACTION_PARAM_CLASS(cls, 0);
2293
2294 if (cls == NULL || self->player == NULL)
2295 {
2296 ACTION_SET_RESULT(false);
2297 return;
2298 }
2299
2300 AWeapon * weaponitem = static_cast<AWeapon*>(self->FindInventory(cls));
2301
2302 if (weaponitem != NULL && weaponitem->IsKindOf(RUNTIME_CLASS(AWeapon)))
2303 {
2304 if (self->player->ReadyWeapon != weaponitem)
2305 {
2306 self->player->PendingWeapon = weaponitem;
2307 }
2308 }
2309 else ACTION_SET_RESULT(false);
2310
2311 }
2312
2313
2314 //===========================================================================
2315 //
2316 // A_Print
2317 //
2318 //===========================================================================
EXTERN_CVAR(Float,con_midtime)2319 EXTERN_CVAR(Float, con_midtime)
2320
2321 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Print)
2322 {
2323 ACTION_PARAM_START(3);
2324 ACTION_PARAM_STRING(text, 0);
2325 ACTION_PARAM_FLOAT(time, 1);
2326 ACTION_PARAM_NAME(fontname, 2);
2327
2328 if (text[0] == '$') text = GStrings(text+1);
2329 if (self->CheckLocalView (consoleplayer) ||
2330 (self->target!=NULL && self->target->CheckLocalView (consoleplayer)))
2331 {
2332 float saved = con_midtime;
2333 FFont *font = NULL;
2334
2335 if (fontname != NAME_None)
2336 {
2337 font = V_GetFont(fontname);
2338 }
2339 if (time > 0)
2340 {
2341 con_midtime = time;
2342 }
2343
2344 FString formatted = strbin1(text);
2345 C_MidPrint(font != NULL ? font : SmallFont, formatted.GetChars());
2346 con_midtime = saved;
2347 }
2348 ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains!
2349 }
2350
2351 //===========================================================================
2352 //
2353 // A_PrintBold
2354 //
2355 //===========================================================================
2356
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_PrintBold)2357 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PrintBold)
2358 {
2359 ACTION_PARAM_START(3);
2360 ACTION_PARAM_STRING(text, 0);
2361 ACTION_PARAM_FLOAT(time, 1);
2362 ACTION_PARAM_NAME(fontname, 2);
2363
2364 float saved = con_midtime;
2365 FFont *font = NULL;
2366
2367 if (text[0] == '$') text = GStrings(text+1);
2368 if (fontname != NAME_None)
2369 {
2370 font = V_GetFont(fontname);
2371 }
2372 if (time > 0)
2373 {
2374 con_midtime = time;
2375 }
2376
2377 FString formatted = strbin1(text);
2378 C_MidPrintBold(font != NULL ? font : SmallFont, formatted.GetChars());
2379 con_midtime = saved;
2380 ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains!
2381 }
2382
2383 //===========================================================================
2384 //
2385 // A_Log
2386 //
2387 //===========================================================================
2388
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_Log)2389 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Log)
2390 {
2391 ACTION_PARAM_START(1);
2392 ACTION_PARAM_STRING(text, 0);
2393
2394 if (text[0] == '$') text = GStrings(text+1);
2395 FString formatted = strbin1(text);
2396 Printf("%s\n", formatted.GetChars());
2397 ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains!
2398 }
2399
2400 //=========================================================================
2401 //
2402 // A_LogInt
2403 //
2404 //===========================================================================
2405
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_LogInt)2406 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LogInt)
2407 {
2408 ACTION_PARAM_START(1);
2409 ACTION_PARAM_INT(num, 0);
2410 Printf("%d\n", num);
2411 ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains!
2412 }
2413
2414 //===========================================================================
2415 //
2416 // A_SetTranslucent
2417 //
2418 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SetTranslucent)2419 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTranslucent)
2420 {
2421 ACTION_PARAM_START(2);
2422 ACTION_PARAM_FIXED(alpha, 0);
2423 ACTION_PARAM_INT(mode, 1);
2424
2425 mode = mode == 0 ? STYLE_Translucent : mode == 2 ? STYLE_Fuzzy : STYLE_Add;
2426
2427 self->RenderStyle.Flags &= ~STYLEF_Alpha1;
2428 self->alpha = clamp<fixed_t>(alpha, 0, FRACUNIT);
2429 self->RenderStyle = ERenderStyle(mode);
2430 }
2431
2432 //===========================================================================
2433 //
2434 // A_FadeIn
2435 //
2436 // Fades the actor in
2437 //
2438 //===========================================================================
2439
2440 enum FadeFlags
2441 {
2442 FTF_REMOVE = 1 << 0,
2443 FTF_CLAMP = 1 << 1,
2444 };
2445
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_FadeIn)2446 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeIn)
2447 {
2448 ACTION_PARAM_START(1);
2449 ACTION_PARAM_FIXED(reduce, 0);
2450 ACTION_PARAM_INT(flags, 1);
2451
2452 if (reduce == 0)
2453 {
2454 reduce = FRACUNIT / 10;
2455 }
2456 self->RenderStyle.Flags &= ~STYLEF_Alpha1;
2457 self->alpha += reduce;
2458
2459 if (self->alpha >= FRACUNIT)
2460 {
2461 if (flags & FTF_CLAMP)
2462 {
2463 self->alpha = FRACUNIT;
2464 }
2465 if (flags & FTF_REMOVE)
2466 {
2467 P_RemoveThing(self);
2468 }
2469 }
2470 }
2471
2472 //===========================================================================
2473 //
2474 // A_FadeOut
2475 //
2476 // fades the actor out and destroys it when done
2477 //
2478 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_FadeOut)2479 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeOut)
2480 {
2481 ACTION_PARAM_START(2);
2482 ACTION_PARAM_FIXED(reduce, 0);
2483 ACTION_PARAM_INT(flags, 1);
2484
2485 if (reduce == 0)
2486 {
2487 reduce = FRACUNIT/10;
2488 }
2489 self->RenderStyle.Flags &= ~STYLEF_Alpha1;
2490 self->alpha -= reduce;
2491 if (self->alpha <= 0)
2492 {
2493 if (flags & FTF_CLAMP)
2494 {
2495 self->alpha = 0;
2496 }
2497 if (flags & FTF_REMOVE)
2498 {
2499 P_RemoveThing(self);
2500 }
2501 }
2502 }
2503
2504 //===========================================================================
2505 //
2506 // A_FadeTo
2507 //
2508 // fades the actor to a specified transparency by a specified amount and
2509 // destroys it if so desired
2510 //
2511 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_FadeTo)2512 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeTo)
2513 {
2514 ACTION_PARAM_START(3);
2515 ACTION_PARAM_FIXED(target, 0);
2516 ACTION_PARAM_FIXED(amount, 1);
2517 ACTION_PARAM_INT(flags, 2);
2518
2519 self->RenderStyle.Flags &= ~STYLEF_Alpha1;
2520
2521 if (self->alpha > target)
2522 {
2523 self->alpha -= amount;
2524
2525 if (self->alpha < target)
2526 {
2527 self->alpha = target;
2528 }
2529 }
2530 else if (self->alpha < target)
2531 {
2532 self->alpha += amount;
2533
2534 if (self->alpha > target)
2535 {
2536 self->alpha = target;
2537 }
2538 }
2539 if (flags & FTF_CLAMP)
2540 {
2541 self->alpha = clamp(self->alpha, 0, FRACUNIT);
2542 }
2543 if (self->alpha == target && (flags & FTF_REMOVE))
2544 {
2545 P_RemoveThing(self);
2546 }
2547 }
2548
2549 //===========================================================================
2550 //
2551 // A_Scale(float scalex, optional float scaley)
2552 //
2553 // Scales the actor's graphics. If scaley is 0, use scalex.
2554 //
2555 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SetScale)2556 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetScale)
2557 {
2558 ACTION_PARAM_START(3);
2559 ACTION_PARAM_FIXED(scalex, 0);
2560 ACTION_PARAM_FIXED(scaley, 1);
2561 ACTION_PARAM_INT(ptr, 2);
2562
2563 AActor *ref = COPY_AAPTR(self, ptr);
2564
2565 if (!ref)
2566 {
2567 ACTION_SET_RESULT(false);
2568 return;
2569 }
2570
2571 ref->scaleX = scalex;
2572 ref->scaleY = scaley ? scaley : scalex;
2573 }
2574
2575 //===========================================================================
2576 //
2577 // A_SetMass(int mass)
2578 //
2579 // Sets the actor's mass.
2580 //
2581 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SetMass)2582 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetMass)
2583 {
2584 ACTION_PARAM_START(2);
2585 ACTION_PARAM_INT(mass, 0);
2586
2587 self->Mass = mass;
2588 }
2589
2590 //===========================================================================
2591 //
2592 // A_SpawnDebris
2593 //
2594 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SpawnDebris)2595 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnDebris)
2596 {
2597 int i;
2598 AActor * mo;
2599
2600 ACTION_PARAM_START(4);
2601 ACTION_PARAM_CLASS(debris, 0);
2602 ACTION_PARAM_BOOL(transfer_translation, 1);
2603 ACTION_PARAM_FIXED(mult_h, 2);
2604 ACTION_PARAM_FIXED(mult_v, 3);
2605
2606 if (debris == NULL) return;
2607
2608 // only positive values make sense here
2609 if (mult_v <= 0)
2610 mult_v = FRACUNIT;
2611 if (mult_h <= 0)
2612 mult_h = FRACUNIT;
2613
2614 for (i = 0; i < GetDefaultByType(debris)->health; i++)
2615 {
2616 fixed_t xo = ((pr_spawndebris() - 128) << 12);
2617 fixed_t yo = ((pr_spawndebris() - 128) << 12);
2618 fixed_t zo = (pr_spawndebris()*self->height / 256 + self->GetBobOffset());
2619 mo = Spawn(debris, self->Vec3Offset(xo, yo, zo), ALLOW_REPLACE);
2620 if (mo)
2621 {
2622 if (transfer_translation)
2623 {
2624 mo->Translation = self->Translation;
2625 }
2626 if (i < mo->GetClass()->ActorInfo->NumOwnedStates)
2627 {
2628 mo->SetState(mo->GetClass()->ActorInfo->OwnedStates + i);
2629 }
2630 mo->velz = FixedMul(mult_v, ((pr_spawndebris()&7)+5)*FRACUNIT);
2631 mo->velx = FixedMul(mult_h, pr_spawndebris.Random2()<<(FRACBITS-6));
2632 mo->vely = FixedMul(mult_h, pr_spawndebris.Random2()<<(FRACBITS-6));
2633 }
2634 }
2635 }
2636
2637 //===========================================================================
2638 //
2639 // A_SpawnParticle
2640 //
2641 //===========================================================================
2642 enum SPFflag
2643 {
2644 SPF_FULLBRIGHT = 1,
2645 SPF_RELPOS = 1 << 1,
2646 SPF_RELVEL = 1 << 2,
2647 SPF_RELACCEL = 1 << 3,
2648 SPF_RELANG = 1 << 4,
2649 };
2650
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SpawnParticle)2651 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnParticle)
2652 {
2653 ACTION_PARAM_START(15);
2654 ACTION_PARAM_COLOR(color, 0);
2655 ACTION_PARAM_INT(flags, 1);
2656 ACTION_PARAM_INT(lifetime, 2);
2657 ACTION_PARAM_INT(size, 3);
2658 ACTION_PARAM_ANGLE(angle, 4);
2659 ACTION_PARAM_FIXED(xoff, 5);
2660 ACTION_PARAM_FIXED(yoff, 6);
2661 ACTION_PARAM_FIXED(zoff, 7);
2662 ACTION_PARAM_FIXED(xvel, 8);
2663 ACTION_PARAM_FIXED(yvel, 9);
2664 ACTION_PARAM_FIXED(zvel, 10);
2665 ACTION_PARAM_FIXED(accelx, 11);
2666 ACTION_PARAM_FIXED(accely, 12);
2667 ACTION_PARAM_FIXED(accelz, 13);
2668 ACTION_PARAM_FIXED(startalphaf, 14);
2669 ACTION_PARAM_FIXED(fadestepf, 15);
2670
2671 BYTE startalpha = (BYTE)Scale(clamp(startalphaf, 0, FRACUNIT), 255, FRACUNIT);
2672 int fadestep = fadestepf < 0? -1 : Scale(clamp(fadestepf, 0, FRACUNIT), 255, FRACUNIT);
2673 lifetime = clamp<int>(lifetime, 0, 255); // Clamp to byte
2674 size = clamp<int>(size, 0, 65535); // Clamp to word
2675
2676 if (lifetime != 0)
2677 {
2678 const angle_t ang = (angle + ((flags & SPF_RELANG) ? self->angle : 0)) >> ANGLETOFINESHIFT;
2679 fixedvec3 pos;
2680 //[MC] Code ripped right out of A_SpawnItemEx.
2681 if (flags & SPF_RELPOS)
2682 {
2683 // in relative mode negative y values mean 'left' and positive ones mean 'right'
2684 // This is the inverse orientation of the absolute mode!
2685 const fixed_t xof1 = xoff;
2686 xoff = FixedMul(xof1, finecosine[ang]) + FixedMul(yoff, finesine[ang]);
2687 yoff = FixedMul(xof1, finesine[ang]) - FixedMul(yoff, finecosine[ang]);
2688 }
2689 if (flags & SPF_RELVEL)
2690 {
2691 const fixed_t newxvel = FixedMul(xvel, finecosine[ang]) + FixedMul(yvel, finesine[ang]);
2692 yvel = FixedMul(xvel, finesine[ang]) - FixedMul(yvel, finecosine[ang]);
2693 xvel = newxvel;
2694 }
2695 if (flags & SPF_RELACCEL)
2696 {
2697 fixed_t newaccelx = FixedMul(accelx, finecosine[ang]) + FixedMul(accely, finesine[ang]);
2698 accely = FixedMul(accelx, finesine[ang]) - FixedMul(accely, finecosine[ang]);
2699 accelx = newaccelx;
2700 }
2701 pos = self->Vec3Offset(xoff, yoff, zoff);
2702 P_SpawnParticle(pos.x, pos.y, pos.z, xvel, yvel, zvel, color, !!(flags & SPF_FULLBRIGHT), startalpha, lifetime, size, fadestep, accelx, accely, accelz);
2703 }
2704 }
2705
2706
2707 //===========================================================================
2708 //
2709 // A_CheckSight
2710 // jumps if no player can see this actor
2711 //
2712 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_CheckSight)2713 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckSight)
2714 {
2715 ACTION_PARAM_START(1);
2716 ACTION_PARAM_STATE(jump, 0);
2717
2718 ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
2719
2720 for (int i = 0; i < MAXPLAYERS; i++)
2721 {
2722 if (playeringame[i])
2723 {
2724 // Always check sight from each player.
2725 if (P_CheckSight(players[i].mo, self, SF_IGNOREVISIBILITY))
2726 {
2727 return;
2728 }
2729 // If a player is viewing from a non-player, then check that too.
2730 if (players[i].camera != NULL && players[i].camera->player == NULL &&
2731 P_CheckSight(players[i].camera, self, SF_IGNOREVISIBILITY))
2732 {
2733 return;
2734 }
2735 }
2736 }
2737
2738 ACTION_JUMP(jump);
2739 }
2740
2741 //===========================================================================
2742 //
2743 // A_CheckSightOrRange
2744 // Jumps if this actor is out of range of all players *and* out of sight.
2745 // Useful for maps with many multi-actor special effects.
2746 //
2747 //===========================================================================
DoCheckSightOrRange(AActor * self,AActor * camera,double range,bool twodi)2748 static bool DoCheckSightOrRange(AActor *self, AActor *camera, double range, bool twodi)
2749 {
2750 if (camera == NULL)
2751 {
2752 return false;
2753 }
2754 // Check distance first, since it's cheaper than checking sight.
2755 fixedvec2 pos = camera->Vec2To(self);
2756 fixed_t dz;
2757 fixed_t eyez = (camera->Top() - (camera->height>>2)); // same eye height as P_CheckSight
2758 if (eyez > self->Top())
2759 {
2760 dz = self->Top() - eyez;
2761 }
2762 else if (eyez < self->Z())
2763 {
2764 dz = self->Z() - eyez;
2765 }
2766 else
2767 {
2768 dz = 0;
2769 }
2770 double distance = ((double)pos.x * pos.x) + ((double)pos.y * pos.y) + (twodi == 0? ((double)dz * dz) : 0);
2771 if (distance <= range){
2772 // Within range
2773 return true;
2774 }
2775
2776 // Now check LOS.
2777 if (P_CheckSight(camera, self, SF_IGNOREVISIBILITY))
2778 { // Visible
2779 return true;
2780 }
2781 return false;
2782 }
2783
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_CheckSightOrRange)2784 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckSightOrRange)
2785 {
2786 ACTION_PARAM_START(3);
2787 double range = EvalExpressionF(ParameterIndex+0, self);
2788 ACTION_PARAM_STATE(jump, 1);
2789 ACTION_PARAM_BOOL(twodi, 2);
2790
2791 ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
2792
2793 range = range * range * (double(FRACUNIT) * FRACUNIT); // no need for square roots
2794 for (int i = 0; i < MAXPLAYERS; ++i)
2795 {
2796 if (playeringame[i])
2797 {
2798 // Always check from each player.
2799 if (DoCheckSightOrRange(self, players[i].mo, range, twodi))
2800 {
2801 return;
2802 }
2803 // If a player is viewing from a non-player, check that too.
2804 if (players[i].camera != NULL && players[i].camera->player == NULL &&
2805 DoCheckSightOrRange(self, players[i].camera, range, twodi))
2806 {
2807 return;
2808 }
2809 }
2810 }
2811 ACTION_JUMP(jump);
2812 }
2813
2814 //===========================================================================
2815 //
2816 // A_CheckRange
2817 // Jumps if this actor is out of range of all players.
2818 //
2819 //===========================================================================
DoCheckRange(AActor * self,AActor * camera,double range,bool twodi)2820 static bool DoCheckRange(AActor *self, AActor *camera, double range, bool twodi)
2821 {
2822 if (camera == NULL)
2823 {
2824 return false;
2825 }
2826 // Check distance first, since it's cheaper than checking sight.
2827 fixedvec2 pos = camera->Vec2To(self);
2828 fixed_t dz;
2829 fixed_t eyez = (camera->Top() - (camera->height>>2)); // same eye height as P_CheckSight
2830 if (eyez > self->Top())
2831 {
2832 dz = self->Top() - eyez;
2833 }
2834 else if (eyez < self->Z())
2835 {
2836 dz = self->Z() - eyez;
2837 }
2838 else
2839 {
2840 dz = 0;
2841 }
2842 double distance = ((double)pos.x * pos.x) + ((double)pos.y * pos.y) + (twodi == 0? ((double)dz * dz) : 0);
2843
2844 if (distance <= range){
2845 // Within range
2846 return true;
2847 }
2848 return false;
2849 }
2850
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_CheckRange)2851 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckRange)
2852 {
2853 ACTION_PARAM_START(3);
2854 double range = EvalExpressionF(ParameterIndex+0, self);
2855 ACTION_PARAM_STATE(jump, 1);
2856 ACTION_PARAM_BOOL(twodi, 2);
2857
2858 ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
2859
2860 range = range * range * (double(FRACUNIT) * FRACUNIT); // no need for square roots
2861 for (int i = 0; i < MAXPLAYERS; ++i)
2862 {
2863 if (playeringame[i])
2864 {
2865 // Always check from each player.
2866 if (DoCheckRange(self, players[i].mo, range, twodi))
2867 {
2868 return;
2869 }
2870 // If a player is viewing from a non-player, check that too.
2871 if (players[i].camera != NULL && players[i].camera->player == NULL &&
2872 DoCheckRange(self, players[i].camera, range, twodi))
2873 {
2874 return;
2875 }
2876 }
2877 }
2878 ACTION_JUMP(jump);
2879 }
2880
2881
2882 //===========================================================================
2883 //
2884 // Inventory drop
2885 //
2886 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_DropInventory)2887 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DropInventory)
2888 {
2889 ACTION_PARAM_START(1);
2890 ACTION_PARAM_CLASS(drop, 0);
2891
2892 if (drop)
2893 {
2894 AInventory * inv = self->FindInventory(drop);
2895 if (inv)
2896 {
2897 self->DropInventory(inv);
2898 }
2899 }
2900 }
2901
2902
2903 //===========================================================================
2904 //
2905 // A_SetBlend
2906 //
2907 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SetBlend)2908 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetBlend)
2909 {
2910 ACTION_PARAM_START(4);
2911 ACTION_PARAM_COLOR(color, 0);
2912 ACTION_PARAM_FLOAT(alpha, 1);
2913 ACTION_PARAM_INT(tics, 2);
2914 ACTION_PARAM_COLOR(color2, 3);
2915
2916 if (color == MAKEARGB(255,255,255,255)) color=0;
2917 if (color2 == MAKEARGB(255,255,255,255)) color2=0;
2918 if (!color2.a)
2919 color2 = color;
2920
2921 new DFlashFader(color.r/255.0f, color.g/255.0f, color.b/255.0f, alpha,
2922 color2.r/255.0f, color2.g/255.0f, color2.b/255.0f, 0,
2923 (float)tics/TICRATE, self);
2924 }
2925
2926
2927 //===========================================================================
2928 //
2929 // A_JumpIf
2930 //
2931 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_JumpIf)2932 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIf)
2933 {
2934 ACTION_PARAM_START(2);
2935 ACTION_PARAM_BOOL(expression, 0);
2936 ACTION_PARAM_STATE(jump, 1);
2937
2938 ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
2939 if (expression) ACTION_JUMP(jump);
2940
2941 }
2942
2943 //===========================================================================
2944 //
2945 // A_CountdownArg
2946 //
2947 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_CountdownArg)2948 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CountdownArg)
2949 {
2950 ACTION_PARAM_START(2);
2951 ACTION_PARAM_INT(cnt, 0);
2952 ACTION_PARAM_STATE(state, 1);
2953
2954 if (cnt<0 || cnt>=5) return;
2955 if (!self->args[cnt]--)
2956 {
2957 if (self->flags&MF_MISSILE)
2958 {
2959 P_ExplodeMissile(self, NULL, NULL);
2960 }
2961 else if (self->flags&MF_SHOOTABLE)
2962 {
2963 P_DamageMobj (self, NULL, NULL, self->health, NAME_None, DMG_FORCED);
2964 }
2965 else
2966 {
2967 // can't use "Death" as default parameter with current DECORATE parser.
2968 if (state == NULL) state = self->FindState(NAME_Death);
2969 self->SetState(state);
2970 }
2971 }
2972
2973 }
2974
2975 //============================================================================
2976 //
2977 // A_Burst
2978 //
2979 //============================================================================
2980
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_Burst)2981 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Burst)
2982 {
2983 ACTION_PARAM_START(1);
2984 ACTION_PARAM_CLASS(chunk, 0);
2985
2986 int i, numChunks;
2987 AActor * mo;
2988
2989 if (chunk == NULL) return;
2990
2991 self->velx = self->vely = self->velz = 0;
2992 self->height = self->GetDefault()->height;
2993
2994 // [RH] In Hexen, this creates a random number of shards (range [24,56])
2995 // with no relation to the size of the self shattering. I think it should
2996 // base the number of shards on the size of the dead thing, so bigger
2997 // things break up into more shards than smaller things.
2998 // An self with radius 20 and height 64 creates ~40 chunks.
2999 numChunks = MAX<int> (4, (self->radius>>FRACBITS)*(self->height>>FRACBITS)/32);
3000 i = (pr_burst.Random2()) % (numChunks/4);
3001 for (i = MAX (24, numChunks + i); i >= 0; i--)
3002 {
3003 fixed_t xo = (((pr_burst() - 128)*self->radius) >> 7);
3004 fixed_t yo = (((pr_burst() - 128)*self->radius) >> 7);
3005 fixed_t zo = (pr_burst()*self->height / 255 + self->GetBobOffset());
3006 mo = Spawn(chunk, self->Vec3Offset(xo, yo, zo), ALLOW_REPLACE);
3007
3008 if (mo)
3009 {
3010 mo->velz = FixedDiv(mo->Z() - self->Z(), self->height)<<2;
3011 mo->velx = pr_burst.Random2 () << (FRACBITS-7);
3012 mo->vely = pr_burst.Random2 () << (FRACBITS-7);
3013 mo->RenderStyle = self->RenderStyle;
3014 mo->alpha = self->alpha;
3015 mo->CopyFriendliness(self, true);
3016 }
3017 }
3018
3019 // [RH] Do some stuff to make this more useful outside Hexen
3020 if (self->flags4 & MF4_BOSSDEATH)
3021 {
3022 CALL_ACTION(A_BossDeath, self);
3023 }
3024 A_Unblock(self, true);
3025
3026 self->Destroy ();
3027 }
3028
3029 //===========================================================================
3030 //
3031 // A_CheckFloor
3032 // [GRB] Jumps if actor is standing on floor
3033 //
3034 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_CheckFloor)3035 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckFloor)
3036 {
3037 ACTION_PARAM_START(1);
3038 ACTION_PARAM_STATE(jump, 0);
3039
3040 ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
3041 if (self->Z() <= self->floorz)
3042 {
3043 ACTION_JUMP(jump);
3044 }
3045
3046 }
3047
3048 //===========================================================================
3049 //
3050 // A_CheckCeiling
3051 // [GZ] Totally copied on A_CheckFloor, jumps if actor touches ceiling
3052 //
3053
3054 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_CheckCeiling)3055 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckCeiling)
3056 {
3057 ACTION_PARAM_START(1);
3058 ACTION_PARAM_STATE(jump, 0);
3059
3060 ACTION_SET_RESULT(false);
3061 if (self->Top() >= self->ceilingz) // Height needs to be counted
3062 {
3063 ACTION_JUMP(jump);
3064 }
3065
3066 }
3067
3068 //===========================================================================
3069 //
3070 // A_Stop
3071 // resets all velocity of the actor to 0
3072 //
3073 //===========================================================================
DEFINE_ACTION_FUNCTION(AActor,A_Stop)3074 DEFINE_ACTION_FUNCTION(AActor, A_Stop)
3075 {
3076 self->velx = self->vely = self->velz = 0;
3077 if (self->player && self->player->mo == self && !(self->player->cheats & CF_PREDICTING))
3078 {
3079 self->player->mo->PlayIdle();
3080 self->player->velx = self->player->vely = 0;
3081 }
3082 }
3083
CheckStopped(AActor * self)3084 static void CheckStopped(AActor *self)
3085 {
3086 if (self->player != NULL &&
3087 self->player->mo == self &&
3088 !(self->player->cheats & CF_PREDICTING) &&
3089 !(self->velx | self->vely | self->velz))
3090 {
3091 self->player->mo->PlayIdle();
3092 self->player->velx = self->player->vely = 0;
3093 }
3094 }
3095
3096 //===========================================================================
3097 //
3098 // A_Respawn
3099 //
3100 //===========================================================================
3101
3102 extern void AF_A_RestoreSpecialPosition(DECLARE_PARAMINFO);
3103
3104 enum RS_Flags
3105 {
3106 RSF_FOG=1,
3107 RSF_KEEPTARGET=2,
3108 RSF_TELEFRAG=4,
3109 };
3110
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_Respawn)3111 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Respawn)
3112 {
3113 ACTION_PARAM_START(1);
3114 ACTION_PARAM_INT(flags, 0);
3115 bool oktorespawn = false;
3116 fixedvec3 pos = self->Pos();
3117 self->flags |= MF_SOLID;
3118 self->height = self->GetDefault()->height;
3119 self->radius = self->GetDefault()->radius;
3120 CALL_ACTION(A_RestoreSpecialPosition, self);
3121
3122 if (flags & RSF_TELEFRAG)
3123 {
3124 // [KS] DIE DIE DIE DIE erm *ahem* =)
3125 oktorespawn = P_TeleportMove(self, self->Pos(), true, false);
3126 }
3127 else
3128 {
3129 oktorespawn = P_CheckPosition(self, self->X(), self->Y(), true);
3130 }
3131
3132 if (oktorespawn)
3133 {
3134 AActor *defs = self->GetDefault();
3135 self->health = defs->health;
3136
3137 // [KS] Don't keep target, because it could be self if the monster committed suicide
3138 // ...Actually it's better off an option, so you have better control over monster behavior.
3139 if (!(flags & RSF_KEEPTARGET))
3140 {
3141 self->target = NULL;
3142 self->LastHeard = NULL;
3143 self->lastenemy = NULL;
3144 }
3145 else
3146 {
3147 // Don't attack yourself (Re: "Marine targets itself after suicide")
3148 if (self->target == self)
3149 self->target = NULL;
3150 if (self->lastenemy == self)
3151 self->lastenemy = NULL;
3152 }
3153
3154 self->flags = (defs->flags & ~MF_FRIENDLY) | (self->flags & MF_FRIENDLY);
3155 self->flags2 = defs->flags2;
3156 self->flags3 = (defs->flags3 & ~(MF3_NOSIGHTCHECK | MF3_HUNTPLAYERS)) | (self->flags3 & (MF3_NOSIGHTCHECK | MF3_HUNTPLAYERS));
3157 self->flags4 = (defs->flags4 & ~MF4_NOHATEPLAYERS) | (self->flags4 & MF4_NOHATEPLAYERS);
3158 self->flags5 = defs->flags5;
3159 self->flags6 = defs->flags6;
3160 self->flags7 = defs->flags7;
3161 self->SetState (self->SpawnState);
3162 self->renderflags &= ~RF_INVISIBLE;
3163
3164 if (flags & RSF_FOG)
3165 {
3166 P_SpawnTeleportFog(self, pos, true, true);
3167 P_SpawnTeleportFog(self, self->Pos(), false, true);
3168 }
3169 if (self->CountsAsKill())
3170 {
3171 level.total_monsters++;
3172 }
3173 }
3174 else
3175 {
3176 self->flags &= ~MF_SOLID;
3177 }
3178 }
3179
3180
3181 //==========================================================================
3182 //
3183 // A_PlayerSkinCheck
3184 //
3185 //==========================================================================
3186
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_PlayerSkinCheck)3187 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlayerSkinCheck)
3188 {
3189 ACTION_PARAM_START(1);
3190 ACTION_PARAM_STATE(jump, 0);
3191
3192 ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
3193 if (self->player != NULL &&
3194 skins[self->player->userinfo.GetSkin()].othergame)
3195 {
3196 ACTION_JUMP(jump);
3197 }
3198 }
3199
3200 //===========================================================================
3201 //
3202 // A_SetGravity
3203 //
3204 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SetGravity)3205 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetGravity)
3206 {
3207 ACTION_PARAM_START(1);
3208 ACTION_PARAM_FIXED(val, 0);
3209
3210 self->gravity = clamp<fixed_t> (val, 0, FRACUNIT*10);
3211 }
3212
3213
3214 // [KS] *** Start of my modifications ***
3215
3216 //===========================================================================
3217 //
3218 // A_ClearTarget
3219 //
3220 //===========================================================================
3221
DEFINE_ACTION_FUNCTION(AActor,A_ClearTarget)3222 DEFINE_ACTION_FUNCTION(AActor, A_ClearTarget)
3223 {
3224 self->target = NULL;
3225 self->LastHeard = NULL;
3226 self->lastenemy = NULL;
3227 }
3228
3229 //==========================================================================
3230 //
3231 // A_CheckLOF (state jump, int flags = CRF_AIM_VERT|CRF_AIM_HOR,
3232 // fixed range = 0, angle angle = 0, angle pitch = 0,
3233 // fixed offsetheight = 32, fixed offsetwidth = 0,
3234 // int ptr_target = AAPTR_DEFAULT (target) )
3235 //
3236 //==========================================================================
3237
3238 enum CLOF_flags
3239 {
3240 CLOFF_NOAIM_VERT = 0x00000001,
3241 CLOFF_NOAIM_HORZ = 0x00000002,
3242
3243 CLOFF_JUMPENEMY = 0x00000004,
3244 CLOFF_JUMPFRIEND = 0x00000008,
3245 CLOFF_JUMPOBJECT = 0x00000010,
3246 CLOFF_JUMPNONHOSTILE = 0x00000020,
3247
3248 CLOFF_SKIPENEMY = 0x00000040,
3249 CLOFF_SKIPFRIEND = 0x00000080,
3250 CLOFF_SKIPOBJECT = 0x00000100,
3251 CLOFF_SKIPNONHOSTILE = 0x00000200,
3252
3253 CLOFF_MUSTBESHOOTABLE = 0x00000400,
3254
3255 CLOFF_SKIPTARGET = 0x00000800,
3256 CLOFF_ALLOWNULL = 0x00001000,
3257 CLOFF_CHECKPARTIAL = 0x00002000,
3258
3259 CLOFF_MUSTBEGHOST = 0x00004000,
3260 CLOFF_IGNOREGHOST = 0x00008000,
3261
3262 CLOFF_MUSTBESOLID = 0x00010000,
3263 CLOFF_BEYONDTARGET = 0x00020000,
3264
3265 CLOFF_FROMBASE = 0x00040000,
3266 CLOFF_MUL_HEIGHT = 0x00080000,
3267 CLOFF_MUL_WIDTH = 0x00100000,
3268
3269 CLOFF_JUMP_ON_MISS = 0x00200000,
3270 CLOFF_AIM_VERT_NOOFFSET = 0x00400000,
3271
3272 CLOFF_SETTARGET = 0x00800000,
3273 CLOFF_SETMASTER = 0x01000000,
3274 CLOFF_SETTRACER = 0x02000000,
3275 };
3276
3277 struct LOFData
3278 {
3279 AActor *Self;
3280 AActor *Target;
3281 int Flags;
3282 bool BadActor;
3283 };
3284
CheckLOFTraceFunc(FTraceResults & trace,void * userdata)3285 ETraceStatus CheckLOFTraceFunc(FTraceResults &trace, void *userdata)
3286 {
3287 LOFData *data = (LOFData *)userdata;
3288 int flags = data->Flags;
3289
3290 if (trace.HitType != TRACE_HitActor)
3291 {
3292 return TRACE_Stop;
3293 }
3294 if (trace.Actor == data->Target)
3295 {
3296 if (flags & CLOFF_SKIPTARGET)
3297 {
3298 if (flags & CLOFF_BEYONDTARGET)
3299 {
3300 return TRACE_Skip;
3301 }
3302 return TRACE_Abort;
3303 }
3304 return TRACE_Stop;
3305 }
3306 if (flags & CLOFF_MUSTBESHOOTABLE)
3307 { // all shootability checks go here
3308 if (!(trace.Actor->flags & MF_SHOOTABLE))
3309 {
3310 return TRACE_Skip;
3311 }
3312 if (trace.Actor->flags2 & MF2_NONSHOOTABLE)
3313 {
3314 return TRACE_Skip;
3315 }
3316 }
3317 if ((flags & CLOFF_MUSTBESOLID) && !(trace.Actor->flags & MF_SOLID))
3318 {
3319 return TRACE_Skip;
3320 }
3321 if (flags & CLOFF_MUSTBEGHOST)
3322 {
3323 if (!(trace.Actor->flags3 & MF3_GHOST))
3324 {
3325 return TRACE_Skip;
3326 }
3327 }
3328 else if (flags & CLOFF_IGNOREGHOST)
3329 {
3330 if (trace.Actor->flags3 & MF3_GHOST)
3331 {
3332 return TRACE_Skip;
3333 }
3334 }
3335 if (
3336 ((flags & CLOFF_JUMPENEMY) && data->Self->IsHostile(trace.Actor)) ||
3337 ((flags & CLOFF_JUMPFRIEND) && data->Self->IsFriend(trace.Actor)) ||
3338 ((flags & CLOFF_JUMPOBJECT) && !(trace.Actor->flags3 & MF3_ISMONSTER)) ||
3339 ((flags & CLOFF_JUMPNONHOSTILE) && (trace.Actor->flags3 & MF3_ISMONSTER) && !data->Self->IsHostile(trace.Actor))
3340 )
3341 {
3342 return TRACE_Stop;
3343 }
3344 if (
3345 ((flags & CLOFF_SKIPENEMY) && data->Self->IsHostile(trace.Actor)) ||
3346 ((flags & CLOFF_SKIPFRIEND) && data->Self->IsFriend(trace.Actor)) ||
3347 ((flags & CLOFF_SKIPOBJECT) && !(trace.Actor->flags3 & MF3_ISMONSTER)) ||
3348 ((flags & CLOFF_SKIPNONHOSTILE) && (trace.Actor->flags3 & MF3_ISMONSTER) && !data->Self->IsHostile(trace.Actor))
3349 )
3350 {
3351 return TRACE_Skip;
3352 }
3353 data->BadActor = true;
3354 return TRACE_Abort;
3355 }
3356
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_CheckLOF)3357 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckLOF)
3358 {
3359 // Check line of fire
3360
3361 /*
3362 Not accounted for / I don't know how it works: FLOORCLIP
3363 */
3364
3365 AActor *target;
3366 fixedvec3 pos;
3367 fixed_t vx, vy, vz;
3368
3369 ACTION_PARAM_START(9);
3370
3371 ACTION_PARAM_STATE(jump, 0);
3372 ACTION_PARAM_INT(flags, 1);
3373 ACTION_PARAM_FIXED(range, 2);
3374 ACTION_PARAM_FIXED(minrange, 3);
3375 {
3376 ACTION_PARAM_ANGLE(angle, 4);
3377 ACTION_PARAM_ANGLE(pitch, 5);
3378 ACTION_PARAM_FIXED(offsetheight, 6);
3379 ACTION_PARAM_FIXED(offsetwidth, 7);
3380 ACTION_PARAM_INT(ptr_target, 8);
3381
3382 ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
3383
3384 target = COPY_AAPTR(self, ptr_target == AAPTR_DEFAULT ? AAPTR_TARGET|AAPTR_PLAYER_GETTARGET|AAPTR_NULL : ptr_target); // no player-support by default
3385
3386 if (flags & CLOFF_MUL_HEIGHT)
3387 {
3388 if (self->player != NULL)
3389 {
3390 // Synced with hitscan: self->player->mo->height is strangely conscientious about getting the right actor for player
3391 offsetheight = FixedMul(offsetheight, FixedMul (self->player->mo->height, self->player->crouchfactor));
3392 }
3393 else
3394 {
3395 offsetheight = FixedMul(offsetheight, self->height);
3396 }
3397 }
3398 if (flags & CLOFF_MUL_WIDTH)
3399 {
3400 offsetwidth = FixedMul(self->radius, offsetwidth);
3401 }
3402
3403 pos = self->PosPlusZ(offsetheight - self->floorclip);
3404
3405 if (!(flags & CLOFF_FROMBASE))
3406 { // default to hitscan origin
3407
3408 // Synced with hitscan: self->height is strangely NON-conscientious about getting the right actor for player
3409 pos.z += (self->height >> 1);
3410 if (self->player != NULL)
3411 {
3412 pos.z += FixedMul (self->player->mo->AttackZOffset, self->player->crouchfactor);
3413 }
3414 else
3415 {
3416 pos.z += 8*FRACUNIT;
3417 }
3418 }
3419
3420 if (target)
3421 {
3422 fixed_t xydist = self->Distance2D(target);
3423 fixed_t distance = P_AproxDistance(xydist, target->Z() - pos.z);
3424
3425 if (range && !(flags & CLOFF_CHECKPARTIAL))
3426 {
3427 if (distance > range) return;
3428 }
3429
3430 {
3431 angle_t ang;
3432
3433 if (flags & CLOFF_NOAIM_HORZ)
3434 {
3435 ang = self->angle;
3436 }
3437 else ang = self->AngleTo (target);
3438
3439 angle += ang;
3440
3441 ang >>= ANGLETOFINESHIFT;
3442
3443 fixedvec2 xy = self->Vec2Offset(
3444 FixedMul(offsetwidth, finesine[ang]),
3445 -FixedMul(offsetwidth, finecosine[ang]));
3446
3447 pos.x = xy.x;
3448 pos.y = xy.y;
3449 }
3450
3451 if (flags & CLOFF_NOAIM_VERT)
3452 {
3453 pitch += self->pitch;
3454 }
3455 else if (flags & CLOFF_AIM_VERT_NOOFFSET)
3456 {
3457 pitch -= R_PointToAngle2 (0,0, xydist, target->Z() - pos.z + offsetheight + target->height / 2);
3458 }
3459 else
3460 {
3461 pitch -= R_PointToAngle2 (0,0, xydist, target->Z() - pos.z + target->height / 2);
3462 }
3463 }
3464 else if (flags & CLOFF_ALLOWNULL)
3465 {
3466 angle += self->angle;
3467 pitch += self->pitch;
3468
3469 angle_t ang = self->angle >> ANGLETOFINESHIFT;
3470
3471 fixedvec2 xy = self->Vec2Offset(
3472 FixedMul(offsetwidth, finesine[ang]),
3473 -FixedMul(offsetwidth, finecosine[ang]));
3474
3475 pos.x = xy.x;
3476 pos.y = xy.y;
3477 }
3478 else return;
3479
3480 angle >>= ANGLETOFINESHIFT;
3481 pitch >>= ANGLETOFINESHIFT;
3482
3483 vx = FixedMul (finecosine[pitch], finecosine[angle]);
3484 vy = FixedMul (finecosine[pitch], finesine[angle]);
3485 vz = -finesine[pitch];
3486 }
3487
3488 /* Variable set:
3489
3490 jump, flags, target
3491 x1,y1,z1 (trace point of origin)
3492 vx,vy,vz (trace unit vector)
3493 range
3494 */
3495
3496 sector_t *sec = P_PointInSector(pos.x, pos.y);
3497
3498 if (range == 0)
3499 {
3500 range = (self->player != NULL) ? PLAYERMISSILERANGE : MISSILERANGE;
3501 }
3502
3503 FTraceResults trace;
3504 LOFData lof_data;
3505
3506 lof_data.Self = self;
3507 lof_data.Target = target;
3508 lof_data.Flags = flags;
3509 lof_data.BadActor = false;
3510
3511 Trace(pos.x, pos.y, pos.z, sec, vx, vy, vz, range, ActorFlags::FromInt(0xFFFFFFFF), ML_BLOCKEVERYTHING, self, trace, 0,
3512 CheckLOFTraceFunc, &lof_data);
3513
3514 if (trace.HitType == TRACE_HitActor ||
3515 ((flags & CLOFF_JUMP_ON_MISS) && !lof_data.BadActor && trace.HitType != TRACE_HitNone))
3516 {
3517 if (minrange > 0 && trace.Distance < minrange)
3518 {
3519 return;
3520 }
3521 if ((trace.HitType == TRACE_HitActor) && (trace.Actor != NULL) && !(lof_data.BadActor))
3522 {
3523 if (flags & (CLOFF_SETTARGET)) self->target = trace.Actor;
3524 if (flags & (CLOFF_SETMASTER)) self->master = trace.Actor;
3525 if (flags & (CLOFF_SETTRACER)) self->tracer = trace.Actor;
3526 }
3527
3528 ACTION_JUMP(jump);
3529 }
3530 }
3531
3532 //==========================================================================
3533 //
3534 // A_JumpIfTargetInLOS (state label, optional fixed fov, optional int flags,
3535 // optional fixed dist_max, optional fixed dist_close)
3536 //
3537 // Jumps if the actor can see its target, or if the player has a linetarget.
3538 // ProjectileTarget affects how projectiles are treated. If set, it will use
3539 // the target of the projectile for seekers, and ignore the target for
3540 // normal projectiles. If not set, it will use the missile's owner instead
3541 // (the default). ProjectileTarget is now flag JLOSF_PROJECTILE. dist_max
3542 // sets the maximum distance that actor can see, 0 means forever. dist_close
3543 // uses special behavior if certain flags are set, 0 means no checks.
3544 //
3545 //==========================================================================
3546
3547 enum JLOS_flags
3548 {
3549 JLOSF_PROJECTILE = 1,
3550 JLOSF_NOSIGHT = 1 << 1,
3551 JLOSF_CLOSENOFOV = 1 << 2,
3552 JLOSF_CLOSENOSIGHT = 1 << 3,
3553 JLOSF_CLOSENOJUMP = 1 << 4,
3554 JLOSF_DEADNOJUMP = 1 << 5,
3555 JLOSF_CHECKMASTER = 1 << 6,
3556 JLOSF_TARGETLOS = 1 << 7,
3557 JLOSF_FLIPFOV = 1 << 8,
3558 JLOSF_ALLYNOJUMP = 1 << 9,
3559 JLOSF_COMBATANTONLY = 1 << 10,
3560 JLOSF_NOAUTOAIM = 1 << 11,
3561 JLOSF_CHECKTRACER = 1 << 12,
3562 };
3563
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_JumpIfTargetInLOS)3564 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInLOS)
3565 {
3566 ACTION_PARAM_START(5);
3567 ACTION_PARAM_STATE(jump, 0);
3568 ACTION_PARAM_ANGLE(fov, 1);
3569 ACTION_PARAM_INT(flags, 2);
3570 ACTION_PARAM_FIXED(dist_max, 3);
3571 ACTION_PARAM_FIXED(dist_close, 4);
3572
3573 angle_t an;
3574 AActor *target, *viewport;
3575
3576 ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
3577
3578 bool doCheckSight;
3579
3580 if (!self->player)
3581 {
3582 if (flags & JLOSF_CHECKMASTER)
3583 {
3584 target = self->master;
3585 }
3586 else if ((self->flags & MF_MISSILE && (flags & JLOSF_PROJECTILE)) || (flags & JLOSF_CHECKTRACER))
3587 {
3588 if ((self->flags2 & MF2_SEEKERMISSILE) || (flags & JLOSF_CHECKTRACER))
3589 target = self->tracer;
3590 else
3591 target = NULL;
3592 }
3593 else
3594 {
3595 target = self->target;
3596 }
3597
3598 if (target == NULL)
3599 return; // [KS] Let's not call P_CheckSight unnecessarily in this case.
3600
3601 if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0))
3602 {
3603 return;
3604 }
3605
3606 doCheckSight = !(flags & JLOSF_NOSIGHT);
3607 }
3608 else
3609 {
3610 // Does the player aim at something that can be shot?
3611 P_AimLineAttack(self, self->angle, MISSILERANGE, &target, (flags & JLOSF_NOAUTOAIM) ? ANGLE_1/2 : 0);
3612
3613 if (!target) return;
3614
3615 switch (flags & (JLOSF_TARGETLOS|JLOSF_FLIPFOV))
3616 {
3617 case JLOSF_TARGETLOS|JLOSF_FLIPFOV:
3618 // target makes sight check, player makes fov check; player has verified fov
3619 fov = 0;
3620 // fall-through
3621 case JLOSF_TARGETLOS:
3622 doCheckSight = !(flags & JLOSF_NOSIGHT); // The target is responsible for sight check and fov
3623 break;
3624 default:
3625 // player has verified sight and fov
3626 fov = 0;
3627 // fall-through
3628 case JLOSF_FLIPFOV: // Player has verified sight, but target must verify fov
3629 doCheckSight = false;
3630 break;
3631 }
3632 }
3633
3634 // [FDARI] If target is not a combatant, don't jump
3635 if ( (flags & JLOSF_COMBATANTONLY) && (!target->player) && !(target->flags3 & MF3_ISMONSTER)) return;
3636
3637 // [FDARI] If actors share team, don't jump
3638 if ((flags & JLOSF_ALLYNOJUMP) && self->IsFriend(target)) return;
3639
3640 fixed_t distance = self->AproxDistance3D(target);
3641
3642 if (dist_max && (distance > dist_max)) return;
3643
3644 if (dist_close && (distance < dist_close))
3645 {
3646 if (flags & JLOSF_CLOSENOJUMP)
3647 return;
3648
3649 if (flags & JLOSF_CLOSENOFOV)
3650 fov = 0;
3651
3652 if (flags & JLOSF_CLOSENOSIGHT)
3653 doCheckSight = false;
3654 }
3655
3656 if (flags & JLOSF_TARGETLOS) { viewport = target; target = self; }
3657 else { viewport = self; }
3658
3659 if (doCheckSight && !P_CheckSight (viewport, target, SF_IGNOREVISIBILITY))
3660 return;
3661
3662 if (flags & JLOSF_FLIPFOV)
3663 {
3664 if (viewport == self) { viewport = target; target = self; }
3665 else { target = viewport; viewport = self; }
3666 }
3667
3668 if (fov && (fov < ANGLE_MAX))
3669 {
3670 an = viewport->AngleTo(target) - viewport->angle;
3671
3672 if (an > (fov / 2) && an < (ANGLE_MAX - (fov / 2)))
3673 {
3674 return; // [KS] Outside of FOV - return
3675 }
3676
3677 }
3678
3679 ACTION_JUMP(jump);
3680 }
3681
3682
3683 //==========================================================================
3684 //
3685 // A_JumpIfInTargetLOS (state label, optional fixed fov, optional int flags
3686 // optional fixed dist_max, optional fixed dist_close)
3687 //
3688 //==========================================================================
3689
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_JumpIfInTargetLOS)3690 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInTargetLOS)
3691 {
3692 ACTION_PARAM_START(5);
3693 ACTION_PARAM_STATE(jump, 0);
3694 ACTION_PARAM_ANGLE(fov, 1);
3695 ACTION_PARAM_INT(flags, 2);
3696 ACTION_PARAM_FIXED(dist_max, 3);
3697 ACTION_PARAM_FIXED(dist_close, 4);
3698
3699 angle_t an;
3700 AActor *target;
3701
3702 ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
3703
3704 if (flags & JLOSF_CHECKMASTER)
3705 {
3706 target = self->master;
3707 }
3708 else if (self->flags & MF_MISSILE && (flags & JLOSF_PROJECTILE))
3709 {
3710 if (self->flags2 & MF2_SEEKERMISSILE)
3711 target = self->tracer;
3712 else
3713 target = NULL;
3714 }
3715 else
3716 {
3717 target = self->target;
3718 }
3719
3720 if (!target) return; // [KS] Let's not call P_CheckSight unnecessarily in this case.
3721
3722 if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0)) return;
3723
3724 fixed_t distance = self->AproxDistance3D(target);
3725
3726 if (dist_max && (distance > dist_max)) return;
3727
3728 bool doCheckSight = !(flags & JLOSF_NOSIGHT);
3729
3730 if (dist_close && (distance < dist_close))
3731 {
3732 if (flags & JLOSF_CLOSENOJUMP)
3733 return;
3734
3735 if (flags & JLOSF_CLOSENOFOV)
3736 fov = 0;
3737
3738 if (flags & JLOSF_CLOSENOSIGHT)
3739 doCheckSight = false;
3740 }
3741
3742 if (fov && (fov < ANGLE_MAX))
3743 {
3744 an = target->AngleTo(self) - target->angle;
3745
3746 if (an > (fov / 2) && an < (ANGLE_MAX - (fov / 2)))
3747 {
3748 return; // [KS] Outside of FOV - return
3749 }
3750 }
3751
3752 if (doCheckSight && !P_CheckSight (target, self, SF_IGNOREVISIBILITY))
3753 return;
3754
3755 ACTION_JUMP(jump);
3756 }
3757
3758 //===========================================================================
3759 //
3760 // Modified code pointer from Skulltag
3761 //
3762 //===========================================================================
3763
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_CheckForReload)3764 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckForReload)
3765 {
3766 if ( self->player == NULL || self->player->ReadyWeapon == NULL )
3767 return;
3768
3769 ACTION_PARAM_START(2);
3770 ACTION_PARAM_INT(count, 0);
3771 ACTION_PARAM_STATE(jump, 1);
3772 ACTION_PARAM_BOOL(dontincrement, 2)
3773
3774 if (count <= 0) return;
3775
3776 AWeapon *weapon = self->player->ReadyWeapon;
3777
3778 int ReloadCounter = weapon->ReloadCounter;
3779 if(!dontincrement || ReloadCounter != 0)
3780 ReloadCounter = (weapon->ReloadCounter+1) % count;
3781 else // 0 % 1 = 1? So how do we check if the weapon was never fired? We should only do this when we're not incrementing the counter though.
3782 ReloadCounter = 1;
3783
3784 // If we have not made our last shot...
3785 if (ReloadCounter != 0)
3786 {
3787 // Go back to the refire frames, instead of continuing on to the reload frames.
3788 ACTION_JUMP(jump);
3789 }
3790 else
3791 {
3792 // We need to reload. However, don't reload if we're out of ammo.
3793 weapon->CheckAmmo(false, false);
3794 }
3795
3796 if (!dontincrement)
3797 weapon->ReloadCounter = ReloadCounter;
3798 }
3799
3800 //===========================================================================
3801 //
3802 // Resets the counter for the above function
3803 //
3804 //===========================================================================
3805
DEFINE_ACTION_FUNCTION(AActor,A_ResetReloadCounter)3806 DEFINE_ACTION_FUNCTION(AActor, A_ResetReloadCounter)
3807 {
3808 if ( self->player == NULL || self->player->ReadyWeapon == NULL )
3809 return;
3810
3811 AWeapon *weapon = self->player->ReadyWeapon;
3812 weapon->ReloadCounter = 0;
3813 }
3814
3815 //===========================================================================
3816 //
3817 // A_ChangeFlag
3818 //
3819 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_ChangeFlag)3820 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeFlag)
3821 {
3822 ACTION_PARAM_START(2);
3823 ACTION_PARAM_STRING(flagname, 0);
3824 ACTION_PARAM_BOOL(expression, 1);
3825
3826 const char *dot = strchr (flagname, '.');
3827 FFlagDef *fd;
3828 const PClass *cls = self->GetClass();
3829
3830 if (dot != NULL)
3831 {
3832 FString part1(flagname, dot-flagname);
3833 fd = FindFlag (cls, part1, dot+1);
3834 }
3835 else
3836 {
3837 fd = FindFlag (cls, flagname, NULL);
3838 }
3839
3840 if (fd != NULL)
3841 {
3842 bool kill_before, kill_after;
3843 INTBOOL item_before, item_after;
3844 INTBOOL secret_before, secret_after;
3845
3846 kill_before = self->CountsAsKill();
3847 item_before = self->flags & MF_COUNTITEM;
3848 secret_before = self->flags5 & MF5_COUNTSECRET;
3849
3850 if (fd->structoffset == -1)
3851 {
3852 HandleDeprecatedFlags(self, cls->ActorInfo, expression, fd->flagbit);
3853 }
3854 else
3855 {
3856 ActorFlags *flagp = (ActorFlags*) (((char*)self) + fd->structoffset);
3857
3858 // If these 2 flags get changed we need to update the blockmap and sector links.
3859 bool linkchange = flagp == &self->flags && (fd->flagbit == MF_NOBLOCKMAP || fd->flagbit == MF_NOSECTOR);
3860
3861 if (linkchange) self->UnlinkFromWorld();
3862 ModActorFlag(self, fd, expression);
3863 if (linkchange) self->LinkToWorld();
3864 }
3865 kill_after = self->CountsAsKill();
3866 item_after = self->flags & MF_COUNTITEM;
3867 secret_after = self->flags5 & MF5_COUNTSECRET;
3868 // Was this monster previously worth a kill but no longer is?
3869 // Or vice versa?
3870 if (kill_before != kill_after)
3871 {
3872 if (kill_after)
3873 { // It counts as a kill now.
3874 level.total_monsters++;
3875 }
3876 else
3877 { // It no longer counts as a kill.
3878 level.total_monsters--;
3879 }
3880 }
3881 // same for items
3882 if (item_before != item_after)
3883 {
3884 if (item_after)
3885 { // It counts as an item now.
3886 level.total_items++;
3887 }
3888 else
3889 { // It no longer counts as an item
3890 level.total_items--;
3891 }
3892 }
3893 // and secretd
3894 if (secret_before != secret_after)
3895 {
3896 if (secret_after)
3897 { // It counts as an secret now.
3898 level.total_secrets++;
3899 }
3900 else
3901 { // It no longer counts as an secret
3902 level.total_secrets--;
3903 }
3904 }
3905 }
3906 else
3907 {
3908 Printf("Unknown flag '%s' in '%s'\n", flagname, cls->TypeName.GetChars());
3909 }
3910 }
3911
3912 //===========================================================================
3913 //
3914 // A_CheckFlag
3915 //
3916 //===========================================================================
3917
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_CheckFlag)3918 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckFlag)
3919 {
3920 ACTION_PARAM_START(3);
3921 ACTION_PARAM_STRING(flagname, 0);
3922 ACTION_PARAM_STATE(jumpto, 1);
3923 ACTION_PARAM_INT(checkpointer, 2);
3924
3925 ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
3926
3927 AActor *owner;
3928
3929 COPY_AAPTR_NOT_NULL(self, owner, checkpointer);
3930
3931 if (CheckActorFlag(owner, flagname))
3932 {
3933 ACTION_JUMP(jumpto);
3934 }
3935 }
3936
3937 //===========================================================================
3938 //
3939 // A_RaiseMaster
3940 //
3941 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_RaiseMaster)3942 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RaiseMaster)
3943 {
3944 ACTION_PARAM_START(1);
3945 ACTION_PARAM_BOOL(copy, 0);
3946
3947 if (self->master != NULL)
3948 {
3949 if (copy)
3950 P_Thing_Raise(self->master, self);
3951 else
3952 P_Thing_Raise(self->master, NULL);
3953 }
3954 }
3955
3956 //===========================================================================
3957 //
3958 // A_RaiseChildren
3959 //
3960 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_RaiseChildren)3961 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RaiseChildren)
3962 {
3963 ACTION_PARAM_START(1);
3964 ACTION_PARAM_BOOL(copy, 0);
3965 TThinkerIterator<AActor> it;
3966 AActor *mo;
3967
3968 while ((mo = it.Next()) != NULL)
3969 {
3970 if (mo->master == self)
3971 {
3972 if (copy)
3973 P_Thing_Raise(mo, self);
3974 else
3975 P_Thing_Raise(mo, NULL);
3976 }
3977 }
3978 }
3979
3980 //===========================================================================
3981 //
3982 // A_RaiseSiblings
3983 //
3984 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_RaiseSiblings)3985 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RaiseSiblings)
3986 {
3987 ACTION_PARAM_START(1);
3988 ACTION_PARAM_BOOL(copy, 0);
3989 TThinkerIterator<AActor> it;
3990 AActor *mo;
3991
3992 if (self->master != NULL)
3993 {
3994 while ((mo = it.Next()) != NULL)
3995 {
3996 if (mo->master == self->master && mo != self)
3997 {
3998 if (copy)
3999 P_Thing_Raise(mo, self);
4000 else
4001 P_Thing_Raise(mo, NULL);
4002 }
4003 }
4004 }
4005 }
4006
4007 //===========================================================================
4008 //
4009 // [TP] A_FaceConsolePlayer
4010 //
4011 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_FaceConsolePlayer)4012 DEFINE_ACTION_FUNCTION_PARAMS (AActor, A_FaceConsolePlayer) {
4013 ACTION_PARAM_START (1);
4014 ACTION_PARAM_ANGLE (MaxTurnAngle, 0);
4015 // NOTE: It does nothing for zdoom.
4016 }
4017
4018 //===========================================================================
4019 //
4020 // A_MonsterRefire
4021 //
4022 // Keep firing unless target got out of sight
4023 //
4024 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_MonsterRefire)4025 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_MonsterRefire)
4026 {
4027 ACTION_PARAM_START(2);
4028 ACTION_PARAM_INT(prob, 0);
4029 ACTION_PARAM_STATE(jump, 1);
4030
4031 ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
4032 A_FaceTarget (self);
4033
4034 if (pr_monsterrefire() < prob)
4035 return;
4036
4037 if (self->target == NULL
4038 || P_HitFriend (self)
4039 || self->target->health <= 0
4040 || !P_CheckSight (self, self->target, SF_SEEPASTBLOCKEVERYTHING|SF_SEEPASTSHOOTABLELINES) )
4041 {
4042 ACTION_JUMP(jump);
4043 }
4044 }
4045
4046 //===========================================================================
4047 //
4048 // A_SetAngle
4049 //
4050 // Set actor's angle (in degrees).
4051 //
4052 //===========================================================================
4053 enum
4054 {
4055 SPF_FORCECLAMP = 1, // players always clamp
4056 SPF_INTERPOLATE = 2,
4057 };
4058
4059
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SetAngle)4060 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetAngle)
4061 {
4062 ACTION_PARAM_START(3);
4063 ACTION_PARAM_ANGLE(angle, 0);
4064 ACTION_PARAM_INT(flags, 1);
4065 ACTION_PARAM_INT(ptr, 2);
4066
4067 AActor *ref = COPY_AAPTR(self, ptr);
4068 if (ref == NULL)
4069 {
4070 ACTION_SET_RESULT(false);
4071 return;
4072 }
4073 ref->SetAngle(angle, !!(flags & SPF_INTERPOLATE));
4074 }
4075
4076 //===========================================================================
4077 //
4078 // A_SetPitch
4079 //
4080 // Set actor's pitch (in degrees).
4081 //
4082 //===========================================================================
4083
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SetPitch)4084 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetPitch)
4085 {
4086 ACTION_PARAM_START(3);
4087 ACTION_PARAM_ANGLE(pitch, 0);
4088 ACTION_PARAM_INT(flags, 1);
4089 ACTION_PARAM_INT(ptr, 2);
4090
4091 AActor *ref = COPY_AAPTR(self, ptr);
4092
4093 if (!ref)
4094 {
4095 ACTION_SET_RESULT(false);
4096 return;
4097 }
4098
4099 ref->SetPitch(pitch, !!(flags & SPF_INTERPOLATE), !!(flags & SPF_FORCECLAMP));
4100 }
4101
4102 //===========================================================================
4103 //
4104 // [Nash] A_SetRoll
4105 //
4106 // Set actor's roll (in degrees).
4107 //
4108 //===========================================================================
4109
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SetRoll)4110 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRoll)
4111 {
4112 ACTION_PARAM_START(3);
4113 ACTION_PARAM_ANGLE(roll, 0);
4114 ACTION_PARAM_INT(flags, 1);
4115 ACTION_PARAM_INT(ptr, 2);
4116
4117 AActor *ref = COPY_AAPTR(self, ptr);
4118
4119 if (!ref)
4120 {
4121 ACTION_SET_RESULT(false);
4122 return;
4123 }
4124 ref->SetRoll(roll, !!(flags & SPF_INTERPOLATE));
4125 }
4126
4127 //===========================================================================
4128 //
4129 // A_ScaleVelocity
4130 //
4131 // Scale actor's velocity.
4132 //
4133 //===========================================================================
4134
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_ScaleVelocity)4135 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ScaleVelocity)
4136 {
4137 ACTION_PARAM_START(2);
4138 ACTION_PARAM_FIXED(scale, 0);
4139 ACTION_PARAM_INT(ptr, 1);
4140
4141 AActor *ref = COPY_AAPTR(self, ptr);
4142
4143 if (!ref)
4144 {
4145 ACTION_SET_RESULT(false);
4146 return;
4147 }
4148
4149 INTBOOL was_moving = ref->velx | ref->vely | ref->velz;
4150
4151 ref->velx = FixedMul(ref->velx, scale);
4152 ref->vely = FixedMul(ref->vely, scale);
4153 ref->velz = FixedMul(ref->velz, scale);
4154
4155 // If the actor was previously moving but now is not, and is a player,
4156 // update its player variables. (See A_Stop.)
4157 if (was_moving)
4158 {
4159 CheckStopped(ref);
4160 }
4161 }
4162
4163 //===========================================================================
4164 //
4165 // A_ChangeVelocity
4166 //
4167 //===========================================================================
4168
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_ChangeVelocity)4169 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeVelocity)
4170 {
4171 ACTION_PARAM_START(5);
4172 ACTION_PARAM_FIXED(x, 0);
4173 ACTION_PARAM_FIXED(y, 1);
4174 ACTION_PARAM_FIXED(z, 2);
4175 ACTION_PARAM_INT(flags, 3);
4176 ACTION_PARAM_INT(ptr, 4);
4177
4178 AActor *ref = COPY_AAPTR(self, ptr);
4179
4180 if (!ref)
4181 {
4182 ACTION_SET_RESULT(false);
4183 return;
4184 }
4185
4186 INTBOOL was_moving = ref->velx | ref->vely | ref->velz;
4187
4188 fixed_t vx = x, vy = y, vz = z;
4189 fixed_t sina = finesine[ref->angle >> ANGLETOFINESHIFT];
4190 fixed_t cosa = finecosine[ref->angle >> ANGLETOFINESHIFT];
4191
4192 if (flags & 1) // relative axes - make x, y relative to actor's current angle
4193 {
4194 vx = DMulScale16(x, cosa, -y, sina);
4195 vy = DMulScale16(x, sina, y, cosa);
4196 }
4197 if (flags & 2) // discard old velocity - replace old velocity with new velocity
4198 {
4199 ref->velx = vx;
4200 ref->vely = vy;
4201 ref->velz = vz;
4202 }
4203 else // add new velocity to old velocity
4204 {
4205 ref->velx += vx;
4206 ref->vely += vy;
4207 ref->velz += vz;
4208 }
4209
4210 if (was_moving)
4211 {
4212 CheckStopped(ref);
4213 }
4214 }
4215
4216 //===========================================================================
4217 //
4218 // A_SetArg
4219 //
4220 //===========================================================================
4221
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SetArg)4222 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetArg)
4223 {
4224 ACTION_PARAM_START(2);
4225 ACTION_PARAM_INT(pos, 0);
4226 ACTION_PARAM_INT(value, 1);
4227
4228 // Set the value of the specified arg
4229 if ((size_t)pos < countof(self->args))
4230 {
4231 self->args[pos] = value;
4232 }
4233 }
4234
4235 //===========================================================================
4236 //
4237 // A_SetSpecial
4238 //
4239 //===========================================================================
4240
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SetSpecial)4241 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpecial)
4242 {
4243 ACTION_PARAM_START(6);
4244 ACTION_PARAM_INT(spec, 0);
4245 ACTION_PARAM_INT(arg0, 1);
4246 ACTION_PARAM_INT(arg1, 2);
4247 ACTION_PARAM_INT(arg2, 3);
4248 ACTION_PARAM_INT(arg3, 4);
4249 ACTION_PARAM_INT(arg4, 5);
4250
4251 self->special = spec;
4252 self->args[0] = arg0;
4253 self->args[1] = arg1;
4254 self->args[2] = arg2;
4255 self->args[3] = arg3;
4256 self->args[4] = arg4;
4257 }
4258
4259 //===========================================================================
4260 //
4261 // A_SetUserVar
4262 //
4263 //===========================================================================
4264
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SetUserVar)4265 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserVar)
4266 {
4267 ACTION_PARAM_START(2);
4268 ACTION_PARAM_NAME(varname, 0);
4269 ACTION_PARAM_INT(value, 1);
4270
4271 PSymbol *sym = self->GetClass()->Symbols.FindSymbol(varname, true);
4272 PSymbolVariable *var;
4273
4274 if (sym == NULL || sym->SymbolType != SYM_Variable ||
4275 !(var = static_cast<PSymbolVariable *>(sym))->bUserVar ||
4276 var->ValueType.Type != VAL_Int)
4277 {
4278 Printf("%s is not a user variable in class %s\n", varname.GetChars(),
4279 self->GetClass()->TypeName.GetChars());
4280 return;
4281 }
4282 // Set the value of the specified user variable.
4283 *(int *)(reinterpret_cast<BYTE *>(self) + var->offset) = value;
4284 }
4285
4286 //===========================================================================
4287 //
4288 // A_SetUserArray
4289 //
4290 //===========================================================================
4291
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SetUserArray)4292 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserArray)
4293 {
4294 ACTION_PARAM_START(3);
4295 ACTION_PARAM_NAME(varname, 0);
4296 ACTION_PARAM_INT(pos, 1);
4297 ACTION_PARAM_INT(value, 2);
4298
4299 PSymbol *sym = self->GetClass()->Symbols.FindSymbol(varname, true);
4300 PSymbolVariable *var;
4301
4302 if (sym == NULL || sym->SymbolType != SYM_Variable ||
4303 !(var = static_cast<PSymbolVariable *>(sym))->bUserVar ||
4304 var->ValueType.Type != VAL_Array || var->ValueType.BaseType != VAL_Int)
4305 {
4306 Printf("%s is not a user array in class %s\n", varname.GetChars(),
4307 self->GetClass()->TypeName.GetChars());
4308 return;
4309 }
4310 if (pos < 0 || pos >= var->ValueType.size)
4311 {
4312 Printf("%d is out of bounds in array %s in class %s\n", pos, varname.GetChars(),
4313 self->GetClass()->TypeName.GetChars());
4314 return;
4315 }
4316 // Set the value of the specified user array at index pos.
4317 ((int *)(reinterpret_cast<BYTE *>(self) + var->offset))[pos] = value;
4318 }
4319
4320 //===========================================================================
4321 //
4322 // A_Teleport([state teleportstate, [class targettype,
4323 // [class fogtype, [int flags, [fixed mindist,
4324 // [fixed maxdist]]]]]])
4325 //
4326 // Attempts to teleport to a targettype at least mindist away and at most
4327 // maxdist away (0 means unlimited). If successful, spawn a fogtype at old
4328 // location and place calling actor in teleportstate.
4329 //
4330 //===========================================================================
4331 enum T_Flags
4332 {
4333 TF_TELEFRAG = 0x00000001, // Allow telefrag in order to teleport.
4334 TF_RANDOMDECIDE = 0x00000002, // Randomly fail based on health. (A_Srcr2Decide)
4335 TF_FORCED = 0x00000004, // Forget what's in the way. TF_Telefrag takes precedence though.
4336 TF_KEEPVELOCITY = 0x00000008, // Preserve velocity.
4337 TF_KEEPANGLE = 0x00000010, // Keep angle.
4338 TF_USESPOTZ = 0x00000020, // Set the z to the spot's z, instead of the floor.
4339 TF_NOSRCFOG = 0x00000040, // Don't leave any fog behind when teleporting.
4340 TF_NODESTFOG = 0x00000080, // Don't spawn any fog at the arrival position.
4341 TF_USEACTORFOG = 0x00000100, // Use the actor's TeleFogSourceType and TeleFogDestType fogs.
4342 TF_NOJUMP = 0x00000200, // Don't jump after teleporting.
4343 TF_OVERRIDE = 0x00000400, // Ignore NOTELEPORT.
4344 TF_SENSITIVEZ = 0x00000800, // Fail if the actor wouldn't fit in the position (for Z).
4345 };
4346
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_Teleport)4347 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Teleport)
4348 {
4349 ACTION_PARAM_START(7);
4350 ACTION_PARAM_STATE(teleport_state, 0);
4351 ACTION_PARAM_CLASS(target_type, 1);
4352 ACTION_PARAM_CLASS(fog_type, 2);
4353 ACTION_PARAM_INT(flags, 3);
4354 ACTION_PARAM_FIXED(mindist, 4);
4355 ACTION_PARAM_FIXED(maxdist, 5);
4356 ACTION_PARAM_INT(ptr, 6);
4357
4358 AActor *ref = COPY_AAPTR(self, ptr);
4359
4360 if (!ref)
4361 {
4362 ACTION_SET_RESULT(false);
4363 return;
4364 }
4365
4366 if ((ref->flags2 & MF2_NOTELEPORT) && !(flags & TF_OVERRIDE))
4367 {
4368 ACTION_SET_RESULT(false);
4369 return;
4370 }
4371
4372 // Randomly choose not to teleport like A_Srcr2Decide.
4373 if (flags & TF_RANDOMDECIDE)
4374 {
4375 static const int chance[] =
4376 {
4377 192, 120, 120, 120, 64, 64, 32, 16, 0
4378 };
4379
4380 unsigned int chanceindex = ref->health / ((ref->SpawnHealth()/8 == 0) ? 1 : ref->SpawnHealth()/8);
4381
4382 if (chanceindex >= countof(chance))
4383 {
4384 chanceindex = countof(chance) - 1;
4385 }
4386
4387 if (pr_teleport() >= chance[chanceindex]) return;
4388 }
4389
4390 DSpotState *state = DSpotState::GetSpotState();
4391 if (state == NULL)
4392 {
4393 ACTION_SET_RESULT(false);
4394 return;
4395 }
4396
4397 if (target_type == NULL)
4398 {
4399 target_type = PClass::FindClass("BossSpot");
4400 }
4401
4402 AActor * spot = state->GetSpotWithMinMaxDistance(target_type, ref->X(), ref->Y(), mindist, maxdist);
4403 if (spot == NULL)
4404 {
4405 ACTION_SET_RESULT(false);
4406 return;
4407 }
4408
4409
4410 // [MC] By default, the function adjusts the actor's Z if it's below the floor or above the ceiling.
4411 // This can be an issue as actors designed to maintain specific z positions wind up teleporting
4412 // anyway when they should not, such as a floor rising above or ceiling lowering below the position
4413 // of the spot.
4414 if (flags & TF_SENSITIVEZ)
4415 {
4416 fixed_t posz = (flags & TF_USESPOTZ) ? spot->Z() : spot->floorz;
4417 if ((posz + ref->height > spot->ceilingz) || (posz < spot->floorz))
4418 {
4419 ACTION_SET_RESULT(false);
4420 return;
4421 }
4422 }
4423 fixedvec3 prev = ref->Pos();
4424 fixed_t aboveFloor = spot->Z() - spot->floorz;
4425 fixed_t finalz = spot->floorz + aboveFloor;
4426
4427 if (spot->Z() + ref->height > spot->ceilingz)
4428 finalz = spot->ceilingz - ref->height;
4429 else if (spot->Z() < spot->floorz)
4430 finalz = spot->floorz;
4431
4432 //Take precedence and cooperate with telefragging first.
4433 bool tele_result = P_TeleportMove(ref, spot->X(), spot->Y(), finalz, !!(flags & TF_TELEFRAG));
4434
4435 if (!tele_result && (flags & TF_FORCED))
4436 {
4437 //If for some reason the original move didn't work, regardless of telefrag, force it to move.
4438 ref->SetOrigin(spot->X(), spot->Y(), finalz, false);
4439 tele_result = true;
4440 }
4441
4442 AActor *fog1 = NULL, *fog2 = NULL;
4443 if (tele_result)
4444 {
4445 //If a fog type is defined in the parameter, or the user wants to use the actor's predefined fogs,
4446 //and if there's no desire to be fogless, spawn a fog based upon settings.
4447 if (fog_type || (flags & TF_USEACTORFOG))
4448 {
4449 if (!(flags & TF_NOSRCFOG))
4450 {
4451 if (flags & TF_USEACTORFOG)
4452 P_SpawnTeleportFog(ref, prev, true, true);
4453 else
4454 {
4455 fog1 = Spawn(fog_type, prev, ALLOW_REPLACE);
4456 if (fog1 != NULL)
4457 fog1->target = ref;
4458 }
4459 }
4460 if (!(flags & TF_NODESTFOG))
4461 {
4462 if (flags & TF_USEACTORFOG)
4463 P_SpawnTeleportFog(ref, ref->Pos(), false, true);
4464 else
4465 {
4466 fog2 = Spawn(fog_type, ref->Pos(), ALLOW_REPLACE);
4467 if (fog2 != NULL)
4468 fog2->target = ref;
4469 }
4470 }
4471 }
4472
4473 ref->SetZ((flags & TF_USESPOTZ) ? spot->Z() : ref->floorz, false);
4474
4475 if (!(flags & TF_KEEPANGLE))
4476 ref->angle = spot->angle;
4477
4478 if (!(flags & TF_KEEPVELOCITY))
4479 ref->velx = ref->vely = ref->velz = 0;
4480
4481 if (!(flags & TF_NOJUMP)) //The state jump should only happen with the calling actor.
4482 {
4483 ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
4484 if (teleport_state == NULL)
4485 {
4486 // Default to Teleport.
4487 teleport_state = self->FindState("Teleport");
4488 // If still nothing, then return.
4489 if (teleport_state == NULL)
4490 return;
4491 }
4492 ACTION_JUMP(teleport_state);
4493 return;
4494 }
4495 }
4496 ACTION_SET_RESULT(tele_result);
4497 }
4498
4499 //===========================================================================
4500 //
4501 // A_Turn
4502 //
4503 //===========================================================================
4504
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_Turn)4505 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Turn)
4506 {
4507 ACTION_PARAM_START(1);
4508 ACTION_PARAM_ANGLE(angle, 0);
4509 self->angle += angle;
4510 }
4511
4512 //===========================================================================
4513 //
4514 // A_Quake
4515 //
4516 //===========================================================================
4517
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_Quake)4518 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Quake)
4519 {
4520 ACTION_PARAM_START(5);
4521 ACTION_PARAM_INT(intensity, 0);
4522 ACTION_PARAM_INT(duration, 1);
4523 ACTION_PARAM_INT(damrad, 2);
4524 ACTION_PARAM_INT(tremrad, 3);
4525 ACTION_PARAM_SOUND(sound, 4);
4526 P_StartQuake(self, 0, intensity, duration, damrad, tremrad, sound);
4527 }
4528
4529 //===========================================================================
4530 //
4531 // A_QuakeEx
4532 //
4533 // Extended version of A_Quake. Takes individual axis into account and can
4534 // take a flag.
4535 //===========================================================================
4536
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_QuakeEx)4537 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_QuakeEx)
4538 {
4539 ACTION_PARAM_START(11);
4540 ACTION_PARAM_INT(intensityX, 0);
4541 ACTION_PARAM_INT(intensityY, 1);
4542 ACTION_PARAM_INT(intensityZ, 2);
4543 ACTION_PARAM_INT(duration, 3);
4544 ACTION_PARAM_INT(damrad, 4);
4545 ACTION_PARAM_INT(tremrad, 5);
4546 ACTION_PARAM_SOUND(sound, 6);
4547 ACTION_PARAM_INT(flags, 7);
4548 ACTION_PARAM_DOUBLE(mulWaveX, 8);
4549 ACTION_PARAM_DOUBLE(mulWaveY, 9);
4550 ACTION_PARAM_DOUBLE(mulWaveZ, 10);
4551 P_StartQuakeXYZ(self, 0, intensityX, intensityY, intensityZ, duration, damrad, tremrad, sound, flags, mulWaveX, mulWaveY, mulWaveZ);
4552 }
4553
4554 //===========================================================================
4555 //
4556 // A_Weave
4557 //
4558 //===========================================================================
4559
A_Weave(AActor * self,int xyspeed,int zspeed,fixed_t xydist,fixed_t zdist)4560 void A_Weave(AActor *self, int xyspeed, int zspeed, fixed_t xydist, fixed_t zdist)
4561 {
4562 fixed_t newX, newY;
4563 int weaveXY, weaveZ;
4564 int angle;
4565 fixed_t dist;
4566
4567 weaveXY = self->WeaveIndexXY & 63;
4568 weaveZ = self->WeaveIndexZ & 63;
4569 angle = (self->angle + ANG90) >> ANGLETOFINESHIFT;
4570
4571 if (xydist != 0 && xyspeed != 0)
4572 {
4573 dist = MulScale13(finesine[weaveXY << BOBTOFINESHIFT], xydist);
4574 newX = self->X() - FixedMul (finecosine[angle], dist);
4575 newY = self->Y() - FixedMul (finesine[angle], dist);
4576 weaveXY = (weaveXY + xyspeed) & 63;
4577 dist = MulScale13(finesine[weaveXY << BOBTOFINESHIFT], xydist);
4578 newX += FixedMul (finecosine[angle], dist);
4579 newY += FixedMul (finesine[angle], dist);
4580 if (!(self->flags5 & MF5_NOINTERACTION))
4581 {
4582 P_TryMove (self, newX, newY, true);
4583 }
4584 else
4585 {
4586 self->UnlinkFromWorld ();
4587 self->flags |= MF_NOBLOCKMAP;
4588 // the following 4 lines are for future-proofing this for both interpolation overhaul and line portals.
4589 // For portals we need to calculate the destination including the portal offset
4590 // and for interpolation we need to set the performed movement explicitly, because SetXY cannot do that.
4591 newX -= self->X();
4592 newY -= self->Y();
4593 self->SetXY(self->Vec2Offset(newX, newY));
4594 self->SetMovement(newX, newY, 0);
4595 self->LinkToWorld ();
4596 }
4597 self->WeaveIndexXY = weaveXY;
4598 }
4599 if (zdist != 0 && zspeed != 0)
4600 {
4601 self->AddZ(-MulScale13(finesine[weaveZ << BOBTOFINESHIFT], zdist));
4602 weaveZ = (weaveZ + zspeed) & 63;
4603 self->AddZ(MulScale13(finesine[weaveZ << BOBTOFINESHIFT], zdist));
4604 self->WeaveIndexZ = weaveZ;
4605 }
4606 }
4607
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_Weave)4608 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Weave)
4609 {
4610 ACTION_PARAM_START(4);
4611 ACTION_PARAM_INT(xspeed, 0);
4612 ACTION_PARAM_INT(yspeed, 1);
4613 ACTION_PARAM_FIXED(xdist, 2);
4614 ACTION_PARAM_FIXED(ydist, 3);
4615 A_Weave(self, xspeed, yspeed, xdist, ydist);
4616 }
4617
4618
4619
4620
4621 //===========================================================================
4622 //
4623 // A_LineEffect
4624 //
4625 // This allows linedef effects to be activated inside deh frames.
4626 //
4627 //===========================================================================
4628
4629
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_LineEffect)4630 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LineEffect)
4631 {
4632 ACTION_PARAM_START(2);
4633 ACTION_PARAM_INT(special, 0);
4634 ACTION_PARAM_INT(tag, 1);
4635
4636 line_t junk; maplinedef_t oldjunk;
4637 bool res = false;
4638 if (!(self->flags6 & MF6_LINEDONE)) // Unless already used up
4639 {
4640 if ((oldjunk.special = special)) // Linedef type
4641 {
4642 oldjunk.tag = tag; // Sector tag for linedef
4643 P_TranslateLineDef(&junk, &oldjunk); // Turn into native type
4644 res = !!P_ExecuteSpecial(junk.special, NULL, self, false, junk.args[0],
4645 junk.args[1], junk.args[2], junk.args[3], junk.args[4]);
4646 if (res && !(junk.flags & ML_REPEAT_SPECIAL)) // If only once,
4647 self->flags6 |= MF6_LINEDONE; // no more for this thing
4648 }
4649 }
4650 ACTION_SET_RESULT(res);
4651 }
4652
4653 //==========================================================================
4654 //
4655 // A Wolf3D-style attack codepointer
4656 //
4657 //==========================================================================
4658 enum WolfAttackFlags
4659 {
4660 WAF_NORANDOM = 1,
4661 WAF_USEPUFF = 2,
4662 };
4663
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_WolfAttack)4664 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_WolfAttack)
4665 {
4666 ACTION_PARAM_START(9);
4667 ACTION_PARAM_INT(flags, 0);
4668 ACTION_PARAM_SOUND(sound, 1);
4669 ACTION_PARAM_FIXED(snipe, 2);
4670 ACTION_PARAM_INT(maxdamage, 3);
4671 ACTION_PARAM_INT(blocksize, 4);
4672 ACTION_PARAM_INT(pointblank, 5);
4673 ACTION_PARAM_INT(longrange, 6);
4674 ACTION_PARAM_FIXED(runspeed, 7);
4675 ACTION_PARAM_CLASS(pufftype, 8);
4676
4677 if (!self->target)
4678 return;
4679
4680 // Enemy can't see target
4681 if (!P_CheckSight(self, self->target))
4682 return;
4683
4684 A_FaceTarget (self);
4685
4686
4687 // Target can dodge if it can see enemy
4688 angle_t angle = self->target->AngleTo(self) - self->target->angle;
4689 angle >>= 24;
4690 bool dodge = (P_CheckSight(self->target, self) && (angle>226 || angle<30));
4691
4692 // Distance check is simplistic
4693 fixedvec2 vec = self->Vec2To(self->target);
4694 fixed_t dx = abs (vec.x);
4695 fixed_t dy = abs (vec.y);
4696 fixed_t dist = dx > dy ? dx : dy;
4697
4698 // Some enemies are more precise
4699 dist = FixedMul(dist, snipe);
4700
4701 // Convert distance into integer number of blocks
4702 dist >>= FRACBITS;
4703 dist /= blocksize;
4704
4705 // Now for the speed accuracy thingie
4706 fixed_t speed = FixedMul(self->target->velx, self->target->velx)
4707 + FixedMul(self->target->vely, self->target->vely)
4708 + FixedMul(self->target->velz, self->target->velz);
4709 int hitchance = speed < runspeed ? 256 : 160;
4710
4711 // Distance accuracy (factoring dodge)
4712 hitchance -= dist * (dodge ? 16 : 8);
4713
4714 // While we're here, we may as well do something for this:
4715 if (self->target->flags & MF_SHADOW)
4716 {
4717 hitchance >>= 2;
4718 }
4719
4720 // The attack itself
4721 if (pr_cabullet() < hitchance)
4722 {
4723 // Compute position for spawning blood/puff
4724 angle = self->target->AngleTo(self);
4725
4726 fixedvec3 bloodpos = self->target->Vec3Angle(self->target->radius, angle, self->target->height >> 1);
4727
4728
4729 int damage = flags & WAF_NORANDOM ? maxdamage : (1 + (pr_cabullet() % maxdamage));
4730 if (dist >= pointblank)
4731 damage >>= 1;
4732 if (dist >= longrange)
4733 damage >>= 1;
4734 FName mod = NAME_None;
4735 bool spawnblood = !((self->target->flags & MF_NOBLOOD)
4736 || (self->target->flags2 & (MF2_INVULNERABLE|MF2_DORMANT)));
4737 if (flags & WAF_USEPUFF && pufftype)
4738 {
4739 AActor * dpuff = GetDefaultByType(pufftype->GetReplacement());
4740 mod = dpuff->DamageType;
4741
4742 if (dpuff->flags2 & MF2_THRUGHOST && self->target->flags3 & MF3_GHOST)
4743 damage = 0;
4744
4745 if ((0 && dpuff->flags3 & MF3_PUFFONACTORS) || !spawnblood)
4746 {
4747 spawnblood = false;
4748 P_SpawnPuff(self, pufftype, bloodpos, angle, 0);
4749 }
4750 }
4751 else if (self->target->flags3 & MF3_GHOST)
4752 damage >>= 2;
4753 if (damage)
4754 {
4755 int newdam = P_DamageMobj(self->target, self, self, damage, mod, DMG_THRUSTLESS);
4756 if (spawnblood)
4757 {
4758 P_SpawnBlood(bloodpos, angle, newdam > 0 ? newdam : damage, self->target);
4759 P_TraceBleed(newdam > 0 ? newdam : damage, self->target, self->AngleTo(dx, dy, self->target), 0);
4760 }
4761 }
4762 }
4763
4764 // And finally, let's play the sound
4765 S_Sound (self, CHAN_WEAPON, sound, 1, ATTN_NORM);
4766 }
4767
4768
4769 //==========================================================================
4770 //
4771 // A_Warp
4772 //
4773 //==========================================================================
4774
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_Warp)4775 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Warp)
4776 {
4777 ACTION_PARAM_START(10);
4778
4779 ACTION_PARAM_INT(destination_selector, 0);
4780 ACTION_PARAM_FIXED(xofs, 1);
4781 ACTION_PARAM_FIXED(yofs, 2);
4782 ACTION_PARAM_FIXED(zofs, 3);
4783 ACTION_PARAM_ANGLE(angle, 4);
4784 ACTION_PARAM_INT(flags, 5);
4785 ACTION_PARAM_STATE(success_state, 6);
4786 ACTION_PARAM_FIXED(heightoffset, 7);
4787 ACTION_PARAM_FIXED(radiusoffset, 8);
4788 ACTION_PARAM_ANGLE(pitch, 9);
4789
4790 AActor *reference;
4791
4792 if((flags & WARPF_USETID))
4793 {
4794 reference = SingleActorFromTID(destination_selector, self);
4795 }
4796 else
4797 {
4798 reference = COPY_AAPTR(self, destination_selector);
4799 }
4800
4801 //If there is no actor to warp to, fail.
4802 if (!reference)
4803 {
4804 ACTION_SET_RESULT(false);
4805 return;
4806 }
4807
4808 if (P_Thing_Warp(self, reference, xofs, yofs, zofs, angle, flags, heightoffset, radiusoffset, pitch))
4809 {
4810 if (success_state)
4811 {
4812 ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
4813 // in this case, you have the statejump to help you handle all the success anyway.
4814 ACTION_JUMP(success_state);
4815 return;
4816 }
4817
4818 ACTION_SET_RESULT(true);
4819 }
4820 else
4821 {
4822 ACTION_SET_RESULT(false);
4823 }
4824
4825 }
4826
4827 //==========================================================================
4828 //
4829 // ACS_Named* stuff
4830
4831 //
4832 // These are exactly like their un-named line special equivalents, except
4833 // they take strings instead of integers to indicate which script to run.
4834 // Some of these probably aren't very useful, but they are included for
4835 // the sake of completeness.
4836 //
4837 //==========================================================================
4838
DEFINE_ACTION_FUNCTION_PARAMS(AActor,ACS_NamedExecuteWithResult)4839 DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecuteWithResult)
4840 {
4841 ACTION_PARAM_START(5);
4842
4843 ACTION_PARAM_NAME(scriptname, 0);
4844 ACTION_PARAM_INT(arg1, 1);
4845 ACTION_PARAM_INT(arg2, 2);
4846 ACTION_PARAM_INT(arg3, 3);
4847 ACTION_PARAM_INT(arg4, 4);
4848
4849 bool res = !!P_ExecuteSpecial(ACS_ExecuteWithResult, NULL, self, false, -scriptname, arg1, arg2, arg3, arg4);
4850
4851 ACTION_SET_RESULT(res);
4852 }
4853
DEFINE_ACTION_FUNCTION_PARAMS(AActor,ACS_NamedExecute)4854 DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecute)
4855 {
4856 ACTION_PARAM_START(5);
4857
4858 ACTION_PARAM_NAME(scriptname, 0);
4859 ACTION_PARAM_INT(mapnum, 1);
4860 ACTION_PARAM_INT(arg1, 2);
4861 ACTION_PARAM_INT(arg2, 3);
4862 ACTION_PARAM_INT(arg3, 4);
4863
4864 bool res = !!P_ExecuteSpecial(ACS_Execute, NULL, self, false, -scriptname, mapnum, arg1, arg2, arg3);
4865
4866 ACTION_SET_RESULT(res);
4867 }
4868
DEFINE_ACTION_FUNCTION_PARAMS(AActor,ACS_NamedExecuteAlways)4869 DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecuteAlways)
4870 {
4871 ACTION_PARAM_START(5);
4872
4873 ACTION_PARAM_NAME(scriptname, 0);
4874 ACTION_PARAM_INT(mapnum, 1);
4875 ACTION_PARAM_INT(arg1, 2);
4876 ACTION_PARAM_INT(arg2, 3);
4877 ACTION_PARAM_INT(arg3, 4);
4878
4879 bool res = !!P_ExecuteSpecial(ACS_ExecuteAlways, NULL, self, false, -scriptname, mapnum, arg1, arg2, arg3);
4880
4881 ACTION_SET_RESULT(res);
4882 }
4883
DEFINE_ACTION_FUNCTION_PARAMS(AActor,ACS_NamedLockedExecute)4884 DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedLockedExecute)
4885 {
4886 ACTION_PARAM_START(5);
4887
4888 ACTION_PARAM_NAME(scriptname, 0);
4889 ACTION_PARAM_INT(mapnum, 1);
4890 ACTION_PARAM_INT(arg1, 2);
4891 ACTION_PARAM_INT(arg2, 3);
4892 ACTION_PARAM_INT(lock, 4);
4893
4894 bool res = !!P_ExecuteSpecial(ACS_LockedExecute, NULL, self, false, -scriptname, mapnum, arg1, arg2, lock);
4895
4896 ACTION_SET_RESULT(res);
4897 }
4898
DEFINE_ACTION_FUNCTION_PARAMS(AActor,ACS_NamedLockedExecuteDoor)4899 DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedLockedExecuteDoor)
4900 {
4901 ACTION_PARAM_START(5);
4902
4903 ACTION_PARAM_NAME(scriptname, 0);
4904 ACTION_PARAM_INT(mapnum, 1);
4905 ACTION_PARAM_INT(arg1, 2);
4906 ACTION_PARAM_INT(arg2, 3);
4907 ACTION_PARAM_INT(lock, 4);
4908
4909 bool res = !!P_ExecuteSpecial(ACS_LockedExecuteDoor, NULL, self, false, -scriptname, mapnum, arg1, arg2, lock);
4910
4911 ACTION_SET_RESULT(res);
4912 }
4913
DEFINE_ACTION_FUNCTION_PARAMS(AActor,ACS_NamedSuspend)4914 DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedSuspend)
4915 {
4916 ACTION_PARAM_START(2);
4917
4918 ACTION_PARAM_NAME(scriptname, 0);
4919 ACTION_PARAM_INT(mapnum, 1);
4920
4921 bool res = !!P_ExecuteSpecial(ACS_Suspend, NULL, self, false, -scriptname, mapnum, 0, 0, 0);
4922
4923 ACTION_SET_RESULT(res);
4924 }
4925
DEFINE_ACTION_FUNCTION_PARAMS(AActor,ACS_NamedTerminate)4926 DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedTerminate)
4927 {
4928 ACTION_PARAM_START(2);
4929
4930 ACTION_PARAM_NAME(scriptname, 0);
4931 ACTION_PARAM_INT(mapnum, 1);
4932
4933 bool res = !!P_ExecuteSpecial(ACS_Terminate, NULL, self, false, -scriptname, mapnum, 0, 0, 0);
4934
4935 ACTION_SET_RESULT(res);
4936 }
4937
4938
DoCheckSpecies(AActor * mo,FName filterSpecies,bool exclude)4939 static bool DoCheckSpecies(AActor *mo, FName filterSpecies, bool exclude)
4940 {
4941 FName actorSpecies = mo->GetSpecies();
4942 if (filterSpecies == NAME_None) return true;
4943 return exclude ? (actorSpecies != filterSpecies) : (actorSpecies == filterSpecies);
4944 }
4945
DoCheckClass(AActor * mo,const PClass * filterClass,bool exclude)4946 static bool DoCheckClass(AActor *mo, const PClass *filterClass, bool exclude)
4947 {
4948 const PClass *actorClass = mo->GetClass();
4949 if (filterClass == NULL) return true;
4950 return exclude ? (actorClass != filterClass) : (actorClass == filterClass);
4951 }
4952 //==========================================================================
4953 //
4954 // A_RadiusGive(item, distance, flags, amount, filter, species)
4955 //
4956 // Uses code roughly similar to A_Explode (but without all the compatibility
4957 // baggage and damage computation code) to give an item to all eligible mobjs
4958 // in range.
4959 //
4960 //==========================================================================
4961 enum RadiusGiveFlags
4962 {
4963 RGF_GIVESELF = 1 << 0,
4964 RGF_PLAYERS = 1 << 1,
4965 RGF_MONSTERS = 1 << 2,
4966 RGF_OBJECTS = 1 << 3,
4967 RGF_VOODOO = 1 << 4,
4968 RGF_CORPSES = 1 << 5,
4969 RGF_MASK = 2111,
4970 RGF_NOTARGET = 1 << 6,
4971 RGF_NOTRACER = 1 << 7,
4972 RGF_NOMASTER = 1 << 8,
4973 RGF_CUBE = 1 << 9,
4974 RGF_NOSIGHT = 1 << 10,
4975 RGF_MISSILES = 1 << 11,
4976 RGF_INCLUSIVE = 1 << 12,
4977 RGF_ITEMS = 1 << 13,
4978 RGF_KILLED = 1 << 14,
4979 RGF_EXFILTER = 1 << 15,
4980 RGF_EXSPECIES = 1 << 16,
4981 RGF_EITHER = 1 << 17,
4982 };
4983
DoRadiusGive(AActor * self,AActor * thing,const PClass * item,int amount,fixed_t distance,int flags,const PClass * filter,FName species,fixed_t mindist)4984 static bool DoRadiusGive(AActor *self, AActor *thing, const PClass *item, int amount, fixed_t distance, int flags, const PClass *filter, FName species, fixed_t mindist)
4985 {
4986 // [MC] We only want to make an exception for missiles here. Nothing else.
4987 bool missilePass = !!((flags & RGF_MISSILES) && thing->isMissile());
4988 if (thing == self)
4989 {
4990 if (!(flags & RGF_GIVESELF))
4991 return false;
4992 }
4993 else if (thing->isMissile())
4994 {
4995 if (!missilePass)
4996 return false;
4997 }
4998
4999 //[MC] Check for a filter, species, and the related exfilter/expecies/either flag(s).
5000 bool filterpass = DoCheckClass(thing, filter, !!(flags & RGF_EXFILTER)),
5001 speciespass = DoCheckSpecies(thing, species, !!(flags & RGF_EXSPECIES));
5002
5003 if ((flags & RGF_EITHER) ? (!(filterpass || speciespass)) : (!(filterpass && speciespass)))
5004 {
5005 if (thing != self) //Don't let filter and species obstruct RGF_GIVESELF.
5006 return false;
5007 }
5008
5009 //Check for target, master, and tracer flagging.
5010 bool targetPass = true;
5011 bool masterPass = true;
5012 bool tracerPass = true;
5013 bool ptrPass = false;
5014 if ((thing != self) && (flags & (RGF_NOTARGET | RGF_NOMASTER | RGF_NOTRACER)))
5015 {
5016 if ((thing == self->target) && (flags & RGF_NOTARGET))
5017 targetPass = false;
5018 if ((thing == self->master) && (flags & RGF_NOMASTER))
5019 masterPass = false;
5020 if ((thing == self->tracer) && (flags & RGF_NOTRACER))
5021 tracerPass = false;
5022
5023 ptrPass = (flags & RGF_INCLUSIVE) ? (targetPass || masterPass || tracerPass) : (targetPass && masterPass && tracerPass);
5024
5025 //We should not care about what the actor is here. It's safe to abort this actor.
5026 if (!ptrPass)
5027 return false;
5028 }
5029
5030 //Next, actor flag checking.
5031 bool selfPass = !!((flags & RGF_GIVESELF) && thing == self);
5032 bool corpsePass = !!((flags & RGF_CORPSES) && thing->flags & MF_CORPSE);
5033 bool killedPass = !!((flags & RGF_KILLED) && thing->flags6 & MF6_KILLED);
5034 bool monsterPass = !!((flags & RGF_MONSTERS) && thing->flags3 & MF3_ISMONSTER);
5035 bool objectPass = !!((flags & RGF_OBJECTS) && (thing->player == NULL) && (!(thing->flags3 & MF3_ISMONSTER))
5036 && ((thing->flags & MF_SHOOTABLE) || (thing->flags6 & MF6_VULNERABLE)));
5037 bool playerPass = !!((flags & RGF_PLAYERS) && (thing->player != NULL) && (thing->player->mo == thing));
5038 bool voodooPass = !!((flags & RGF_VOODOO) && (thing->player != NULL) && (thing->player->mo != thing));
5039 //Self calls priority over the rest of this.
5040 if (!selfPass)
5041 {
5042 //If it's specifically a monster/object/player/voodoo... Can be either or...
5043 if (monsterPass || objectPass || playerPass || voodooPass)
5044 {
5045 //...and is dead, without desire to give to the dead...
5046 if (((thing->health <= 0) && !(corpsePass || killedPass)))
5047 {
5048 //Skip!
5049 return false;
5050 }
5051 }
5052 }
5053
5054 bool itemPass = !!((flags & RGF_ITEMS) && thing->IsKindOf(RUNTIME_CLASS(AInventory)));
5055
5056 if (selfPass || monsterPass || corpsePass || killedPass || itemPass || objectPass || missilePass || playerPass || voodooPass)
5057 {
5058
5059 fixedvec3 diff = self->Vec3To(thing);
5060 diff.z += (thing->height - self->height) / 2;
5061 if (flags & RGF_CUBE)
5062 { // check if inside a cube
5063 double dx = fabs((double)(diff.x));
5064 double dy = fabs((double)(diff.y));
5065 double dz = fabs((double)(diff.z));
5066 double dist = (double)distance;
5067 double min = (double)mindist;
5068 if ((dx > dist || dy > dist || dz > dist) || (min && (dx < min && dy < min && dz < min)))
5069 {
5070 return false;
5071 }
5072 }
5073 else
5074 { // check if inside a sphere
5075 double distsquared = double(distance) * double(distance);
5076 double minsquared = double(mindist) * double(mindist);
5077 double lengthsquared = TVector3<double>(diff.x, diff.y, diff.z).LengthSquared();
5078 if (lengthsquared > distsquared || (minsquared && (lengthsquared < minsquared)))
5079 {
5080 return false;
5081 }
5082 }
5083
5084 if ((flags & RGF_NOSIGHT) || P_CheckSight(thing, self, SF_IGNOREVISIBILITY | SF_IGNOREWATERBOUNDARY))
5085 { // OK to give; target is in direct path, or the monster doesn't care about it being in line of sight.
5086 AInventory *gift = static_cast<AInventory *>(Spawn(item, 0, 0, 0, NO_REPLACE));
5087 if (gift->IsKindOf(RUNTIME_CLASS(AHealth)))
5088 {
5089 gift->Amount *= amount;
5090 }
5091 else
5092 {
5093 gift->Amount = amount;
5094 }
5095 gift->flags |= MF_DROPPED;
5096 gift->ClearCounters();
5097 if (!gift->CallTryPickup(thing))
5098 {
5099 gift->Destroy();
5100 return false;
5101 }
5102 else
5103 {
5104 return true;
5105 }
5106 }
5107 }
5108 return false;
5109 }
5110
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_RadiusGive)5111 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RadiusGive)
5112 {
5113 ACTION_PARAM_START(7);
5114 ACTION_PARAM_CLASS(item, 0);
5115 ACTION_PARAM_FIXED(distance, 1);
5116 ACTION_PARAM_INT(flags, 2);
5117 ACTION_PARAM_INT(amount, 3);
5118 ACTION_PARAM_CLASS(filter, 4);
5119 ACTION_PARAM_NAME(species, 5);
5120 ACTION_PARAM_FIXED(mindist, 6);
5121
5122 // We need a valid item, valid targets, and a valid range
5123 if (item == NULL || (flags & RGF_MASK) == 0 || !flags || distance <= 0 || mindist >= distance)
5124 {
5125 ACTION_SET_RESULT(false);
5126 return;
5127 }
5128
5129 if (amount == 0)
5130 {
5131 amount = 1;
5132 }
5133 AActor *thing;
5134 bool given = false;
5135 if (flags & RGF_MISSILES)
5136 {
5137 TThinkerIterator<AActor> it;
5138 while ((thing = it.Next()))
5139 {
5140 if (DoRadiusGive(self, thing, item, amount, distance, flags, filter, species, mindist)) given = true;
5141 }
5142 }
5143 else
5144 {
5145 FBlockThingsIterator it(FBoundingBox(self->X(), self->Y(), distance));
5146 while ((thing = it.Next()))
5147 {
5148 if (DoRadiusGive(self, thing, item, amount, distance, flags, filter, species, mindist)) given = true;
5149 }
5150 }
5151 ACTION_SET_RESULT(given);
5152 }
5153
5154 //===========================================================================
5155 //
5156 // A_CheckSpecies
5157 //
5158 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_CheckSpecies)5159 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckSpecies)
5160 {
5161 ACTION_PARAM_START(3);
5162 ACTION_PARAM_STATE(jump, 0);
5163 ACTION_PARAM_NAME(species, 1);
5164 ACTION_PARAM_INT(ptr, 2);
5165
5166 AActor *mobj = COPY_AAPTR(self, ptr);
5167
5168 ACTION_SET_RESULT(false);
5169 //Needs at least one state jump to work.
5170 if (!mobj)
5171 {
5172 return;
5173 }
5174
5175 if (jump && mobj->GetSpecies() == species)
5176 ACTION_JUMP(jump);
5177 }
5178
5179
5180 //==========================================================================
5181 //
5182 // A_SetTics
5183 //
5184 //==========================================================================
5185
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SetTics)5186 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTics)
5187 {
5188 ACTION_PARAM_START(1);
5189 ACTION_PARAM_INT(tics_to_set, 0);
5190
5191 if (stateowner != self && self->player != NULL && stateowner->IsKindOf(RUNTIME_CLASS(AWeapon)))
5192 { // Is this a weapon? Need to check psp states for a match, then. Blah.
5193 for (int i = 0; i < NUMPSPRITES; ++i)
5194 {
5195 if (self->player->psprites[i].state == CallingState)
5196 {
5197 self->player->psprites[i].tics = tics_to_set;
5198 return;
5199 }
5200 }
5201 }
5202 // Just set tics for self.
5203 self->tics = tics_to_set;
5204 }
5205
5206 //==========================================================================
5207 //
5208 // A_SetDamageType
5209 //
5210 //==========================================================================
5211
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SetDamageType)5212 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetDamageType)
5213 {
5214 ACTION_PARAM_START(1);
5215 ACTION_PARAM_NAME(damagetype, 0);
5216
5217 self->DamageType = damagetype;
5218 }
5219
5220 //==========================================================================
5221 //
5222 // A_DropItem
5223 //
5224 //==========================================================================
5225
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_DropItem)5226 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DropItem)
5227 {
5228 ACTION_PARAM_START(3);
5229 ACTION_PARAM_CLASS(spawntype, 0);
5230 ACTION_PARAM_INT(amount, 1);
5231 ACTION_PARAM_INT(chance, 2);
5232
5233 P_DropItem(self, spawntype, amount, chance);
5234 }
5235
5236 //==========================================================================
5237 //
5238 // A_SetSpeed
5239 //
5240 //==========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SetSpeed)5241 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpeed)
5242 {
5243 ACTION_PARAM_START(2);
5244 ACTION_PARAM_FIXED(speed, 0);
5245 ACTION_PARAM_INT(ptr, 1);
5246
5247 AActor *ref = COPY_AAPTR(self, ptr);
5248
5249 if (!ref)
5250 {
5251 ACTION_SET_RESULT(false);
5252 return;
5253 }
5254
5255 ref->Speed = speed;
5256 }
5257
5258 //==========================================================================
5259 //
5260 // A_SetFloatSpeed
5261 //
5262 //==========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SetFloatSpeed)5263 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetFloatSpeed)
5264 {
5265 ACTION_PARAM_START(2);
5266 ACTION_PARAM_FIXED(speed, 0);
5267 ACTION_PARAM_INT(ptr, 1);
5268
5269 AActor *ref = COPY_AAPTR(self, ptr);
5270
5271 if (!ref)
5272 {
5273 ACTION_SET_RESULT(false);
5274 return;
5275 }
5276
5277 ref->FloatSpeed = speed;
5278 }
5279
5280 //==========================================================================
5281 //
5282 // A_SetPainThreshold
5283 //
5284 //==========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SetPainThreshold)5285 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetPainThreshold)
5286 {
5287 ACTION_PARAM_START(2);
5288 ACTION_PARAM_INT(threshold, 0);
5289 ACTION_PARAM_INT(ptr, 1);
5290
5291 AActor *ref = COPY_AAPTR(self, ptr);
5292
5293 if (!ref)
5294 {
5295 ACTION_SET_RESULT(false);
5296 return;
5297 }
5298
5299 ref->PainThreshold = threshold;
5300 }
5301
5302 //===========================================================================
5303 //
5304 // Common A_Damage handler
5305 //
5306 // A_Damage* (int amount, str damagetype, int flags, str filter, str species)
5307 // Damages the specified actor by the specified amount. Negative values heal.
5308 // Flags: See below.
5309 // Filter: Specified actor is the only type allowed to be affected.
5310 // Species: Specified species is the only type allowed to be affected.
5311 //
5312 // Examples:
5313 // A_Damage(20,"Normal",DMSS_FOILINVUL,0,"DemonicSpecies") <--Only actors
5314 // with a species "DemonicSpecies" will be affected. Use 0 to not filter by actor.
5315 //
5316 //===========================================================================
5317
5318 enum DMSS
5319 {
5320 DMSS_FOILINVUL = 1, //Foil invulnerability
5321 DMSS_AFFECTARMOR = 2, //Make it affect armor
5322 DMSS_KILL = 4, //Damages them for their current health
5323 DMSS_NOFACTOR = 8, //Ignore DamageFactors
5324 DMSS_FOILBUDDHA = 16, //Can kill actors with Buddha flag, except the player.
5325 DMSS_NOPROTECT = 32, //Ignores PowerProtection entirely
5326 DMSS_EXFILTER = 64, //Changes filter into a blacklisted class instead of whitelisted.
5327 DMSS_EXSPECIES = 128, // ^ but with species instead.
5328 DMSS_EITHER = 256, //Allow either type or species to be affected.
5329 };
5330
DoDamage(AActor * dmgtarget,AActor * self,int amount,FName DamageType,int flags,const PClass * filter,FName species)5331 static void DoDamage(AActor *dmgtarget, AActor *self, int amount, FName DamageType, int flags, const PClass *filter, FName species)
5332 {
5333 bool filterpass = DoCheckClass(dmgtarget, filter, !!(flags & DMSS_EXFILTER)),
5334 speciespass = DoCheckSpecies(dmgtarget, species, !!(flags & DMSS_EXSPECIES));
5335 if ((flags & DMSS_EITHER) ? (filterpass || speciespass) : (filterpass && speciespass))
5336 {
5337 int dmgFlags = 0;
5338 if (flags & DMSS_FOILINVUL)
5339 dmgFlags |= DMG_FOILINVUL;
5340 if (flags & DMSS_FOILBUDDHA)
5341 dmgFlags |= DMG_FOILBUDDHA;
5342 if (flags & (DMSS_KILL | DMSS_NOFACTOR)) //Kill implies NoFactor
5343 dmgFlags |= DMG_NO_FACTOR;
5344 if (!(flags & DMSS_AFFECTARMOR) || (flags & DMSS_KILL)) //Kill overrides AffectArmor
5345 dmgFlags |= DMG_NO_ARMOR;
5346 if (flags & DMSS_KILL) //Kill adds the value of the damage done to it. Allows for more controlled extreme death types.
5347 amount += dmgtarget->health;
5348 if (flags & DMSS_NOPROTECT) //Ignore PowerProtection.
5349 dmgFlags |= DMG_NO_PROTECT;
5350
5351 if (amount > 0)
5352 P_DamageMobj(dmgtarget, self, self, amount, DamageType, dmgFlags); //Should wind up passing them through just fine.
5353
5354 else if (amount < 0)
5355 {
5356 amount = -amount;
5357 P_GiveBody(dmgtarget, amount);
5358 }
5359 }
5360 }
5361
5362 //===========================================================================
5363 //
5364 //
5365 //
5366 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_DamageSelf)5367 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageSelf)
5368 {
5369 ACTION_PARAM_START(5);
5370 ACTION_PARAM_INT(amount, 0);
5371 ACTION_PARAM_NAME(DamageType, 1);
5372 ACTION_PARAM_INT(flags, 2);
5373 ACTION_PARAM_CLASS(filter, 3);
5374 ACTION_PARAM_NAME(species, 4);
5375
5376 DoDamage(self, self, amount, DamageType, flags, filter, species);
5377 }
5378
5379 //===========================================================================
5380 //
5381 //
5382 //
5383 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_DamageTarget)5384 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageTarget)
5385 {
5386 ACTION_PARAM_START(5);
5387 ACTION_PARAM_INT(amount, 0);
5388 ACTION_PARAM_NAME(DamageType, 1);
5389 ACTION_PARAM_INT(flags, 2);
5390 ACTION_PARAM_CLASS(filter, 3);
5391 ACTION_PARAM_NAME(species, 4);
5392
5393 if (self->target != NULL)
5394 {
5395 DoDamage(self->target, self, amount, DamageType, flags, filter, species);
5396 }
5397 }
5398
5399 //===========================================================================
5400 //
5401 //
5402 //
5403 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_DamageTracer)5404 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageTracer)
5405 {
5406 ACTION_PARAM_START(5);
5407 ACTION_PARAM_INT(amount, 0);
5408 ACTION_PARAM_NAME(DamageType, 1);
5409 ACTION_PARAM_INT(flags, 2);
5410 ACTION_PARAM_CLASS(filter, 3);
5411 ACTION_PARAM_NAME(species, 4);
5412
5413 if (self->tracer != NULL)
5414 {
5415 DoDamage(self->tracer, self, amount, DamageType, flags, filter, species);
5416 }
5417 }
5418
5419 //===========================================================================
5420 //
5421 //
5422 //
5423 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_DamageMaster)5424 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageMaster)
5425 {
5426 ACTION_PARAM_START(5);
5427 ACTION_PARAM_INT(amount, 0);
5428 ACTION_PARAM_NAME(DamageType, 1);
5429 ACTION_PARAM_INT(flags, 2);
5430 ACTION_PARAM_CLASS(filter, 3);
5431 ACTION_PARAM_NAME(species, 4);
5432
5433 if (self->master != NULL)
5434 {
5435 DoDamage(self->master, self, amount, DamageType, flags, filter, species);
5436 }
5437 }
5438
5439 //===========================================================================
5440 //
5441 //
5442 //
5443 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_DamageChildren)5444 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageChildren)
5445 {
5446 ACTION_PARAM_START(5);
5447 ACTION_PARAM_INT(amount, 0);
5448 ACTION_PARAM_NAME(DamageType, 1);
5449 ACTION_PARAM_INT(flags, 2);
5450 ACTION_PARAM_CLASS(filter, 3);
5451 ACTION_PARAM_NAME(species, 4);
5452
5453 TThinkerIterator<AActor> it;
5454 AActor * mo;
5455
5456 while ( (mo = it.Next()) )
5457 {
5458 if (mo->master == self)
5459 {
5460 DoDamage(mo, self, amount, DamageType, flags, filter, species);
5461 }
5462 }
5463 }
5464
5465 //===========================================================================
5466 //
5467 //
5468 //
5469 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_DamageSiblings)5470 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageSiblings)
5471 {
5472 ACTION_PARAM_START(5);
5473 ACTION_PARAM_INT(amount, 0);
5474 ACTION_PARAM_NAME(DamageType, 1);
5475 ACTION_PARAM_INT(flags, 2);
5476 ACTION_PARAM_CLASS(filter, 3);
5477 ACTION_PARAM_NAME(species, 4);
5478
5479 TThinkerIterator<AActor> it;
5480 AActor * mo;
5481
5482 if (self->master != NULL)
5483 {
5484 while ((mo = it.Next()))
5485 {
5486 if (mo->master == self->master && mo != self)
5487 {
5488 DoDamage(mo, self, amount, DamageType, flags, filter, species);
5489 }
5490 }
5491 }
5492 }
5493
5494
5495 //===========================================================================
5496 //
5497 // A_Kill*(damagetype, int flags)
5498 //
5499 //===========================================================================
5500 enum KILS
5501 {
5502 KILS_FOILINVUL = 1 << 0,
5503 KILS_KILLMISSILES = 1 << 1,
5504 KILS_NOMONSTERS = 1 << 2,
5505 KILS_FOILBUDDHA = 1 << 3,
5506 KILS_EXFILTER = 1 << 4,
5507 KILS_EXSPECIES = 1 << 5,
5508 KILS_EITHER = 1 << 6,
5509 };
5510
DoKill(AActor * killtarget,AActor * self,FName damagetype,int flags,const PClass * filter,FName species)5511 static void DoKill(AActor *killtarget, AActor *self, FName damagetype, int flags, const PClass *filter, FName species)
5512 {
5513 bool filterpass = DoCheckClass(killtarget, filter, !!(flags & KILS_EXFILTER)),
5514 speciespass = DoCheckSpecies(killtarget, species, !!(flags & KILS_EXSPECIES));
5515 if ((flags & KILS_EITHER) ? (filterpass || speciespass) : (filterpass && speciespass)) //Check this first. I think it'll save the engine a lot more time this way.
5516 {
5517 int dmgFlags = DMG_NO_ARMOR | DMG_NO_FACTOR;
5518
5519 if (KILS_FOILINVUL)
5520 dmgFlags |= DMG_FOILINVUL;
5521 if (KILS_FOILBUDDHA)
5522 dmgFlags |= DMG_FOILBUDDHA;
5523
5524
5525 if ((killtarget->flags & MF_MISSILE) && (flags & KILS_KILLMISSILES))
5526 {
5527 //[MC] Now that missiles can set masters, lets put in a check to properly destroy projectiles. BUT FIRST! New feature~!
5528 //Check to see if it's invulnerable. Disregarded if foilinvul is on, but never works on a missile with NODAMAGE
5529 //since that's the whole point of it.
5530 if ((!(killtarget->flags2 & MF2_INVULNERABLE) || (flags & KILS_FOILINVUL)) &&
5531 (!(killtarget->flags7 & MF7_BUDDHA) || (flags & KILS_FOILBUDDHA)) &&
5532 !(killtarget->flags5 & MF5_NODAMAGE))
5533 {
5534 P_ExplodeMissile(killtarget, NULL, NULL);
5535 }
5536 }
5537 if (!(flags & KILS_NOMONSTERS))
5538 {
5539 P_DamageMobj(killtarget, self, self, killtarget->health, damagetype, dmgFlags);
5540 }
5541 }
5542 }
5543
5544
5545 //===========================================================================
5546 //
5547 // A_KillTarget(damagetype, int flags)
5548 //
5549 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_KillTarget)5550 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillTarget)
5551 {
5552 ACTION_PARAM_START(4);
5553 ACTION_PARAM_NAME(damagetype, 0);
5554 ACTION_PARAM_INT(flags, 1);
5555 ACTION_PARAM_CLASS(filter, 2);
5556 ACTION_PARAM_NAME(species, 3);
5557
5558 if (self->target != NULL)
5559 {
5560 DoKill(self->target, self, damagetype, flags, filter, species);
5561 }
5562 }
5563
5564 //===========================================================================
5565 //
5566 // A_KillTracer(damagetype, int flags)
5567 //
5568 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_KillTracer)5569 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillTracer)
5570 {
5571 ACTION_PARAM_START(4);
5572 ACTION_PARAM_NAME(damagetype, 0);
5573 ACTION_PARAM_INT(flags, 1);
5574 ACTION_PARAM_CLASS(filter, 2);
5575 ACTION_PARAM_NAME(species, 3);
5576
5577 if (self->tracer != NULL)
5578 {
5579 DoKill(self->tracer, self, damagetype, flags, filter, species);
5580 }
5581 }
5582
5583 //===========================================================================
5584 //
5585 // A_KillMaster(damagetype, int flags)
5586 //
5587 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_KillMaster)5588 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillMaster)
5589 {
5590 ACTION_PARAM_START(4);
5591 ACTION_PARAM_NAME(damagetype, 0);
5592 ACTION_PARAM_INT(flags, 1);
5593 ACTION_PARAM_CLASS(filter, 2);
5594 ACTION_PARAM_NAME(species, 3);
5595
5596 if (self->master != NULL)
5597 {
5598 DoKill(self->master, self, damagetype, flags, filter, species);
5599 }
5600 }
5601
5602 //===========================================================================
5603 //
5604 // A_KillChildren(damagetype, int flags)
5605 //
5606 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_KillChildren)5607 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillChildren)
5608 {
5609 ACTION_PARAM_START(4);
5610 ACTION_PARAM_NAME(damagetype, 0);
5611 ACTION_PARAM_INT(flags, 1);
5612 ACTION_PARAM_CLASS(filter, 2);
5613 ACTION_PARAM_NAME(species, 3);
5614
5615 TThinkerIterator<AActor> it;
5616 AActor *mo;
5617
5618 while ( (mo = it.Next()) )
5619 {
5620 if (mo->master == self)
5621 {
5622 DoKill(mo, self, damagetype, flags, filter, species);
5623 }
5624 }
5625 }
5626
5627 //===========================================================================
5628 //
5629 // A_KillSiblings(damagetype, int flags)
5630 //
5631 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_KillSiblings)5632 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillSiblings)
5633 {
5634 ACTION_PARAM_START(4);
5635 ACTION_PARAM_NAME(damagetype, 0);
5636 ACTION_PARAM_INT(flags, 1);
5637 ACTION_PARAM_CLASS(filter, 2);
5638 ACTION_PARAM_NAME(species, 3);
5639
5640 TThinkerIterator<AActor> it;
5641 AActor *mo;
5642
5643 if (self->master != NULL)
5644 {
5645 while ( (mo = it.Next()) )
5646 {
5647 if (mo->master == self->master && mo != self)
5648 {
5649 DoKill(mo, self, damagetype, flags, filter, species);
5650 }
5651 }
5652 }
5653 }
5654
5655 //===========================================================================
5656 //
5657 // DoRemove
5658 //
5659 //===========================================================================
5660
5661 enum RMVF_flags
5662 {
5663 RMVF_MISSILES = 1 << 0,
5664 RMVF_NOMONSTERS = 1 << 1,
5665 RMVF_MISC = 1 << 2,
5666 RMVF_EVERYTHING = 1 << 3,
5667 RMVF_EXFILTER = 1 << 4,
5668 RMVF_EXSPECIES = 1 << 5,
5669 RMVF_EITHER = 1 << 6,
5670 };
5671
DoRemove(AActor * removetarget,int flags,const PClass * filter,FName species)5672 static void DoRemove(AActor *removetarget, int flags, const PClass *filter, FName species)
5673 {
5674 bool filterpass = DoCheckClass(removetarget, filter, !!(flags & RMVF_EXFILTER)),
5675 speciespass = DoCheckSpecies(removetarget, species, !!(flags & RMVF_EXSPECIES));
5676 if ((flags & RMVF_EITHER) ? (filterpass || speciespass) : (filterpass && speciespass))
5677 {
5678 if ((flags & RMVF_EVERYTHING))
5679 {
5680 P_RemoveThing(removetarget);
5681 }
5682 if ((flags & RMVF_MISC) && !((removetarget->flags3 & MF3_ISMONSTER) && (removetarget->flags & MF_MISSILE)))
5683 {
5684 P_RemoveThing(removetarget);
5685 }
5686 if ((removetarget->flags3 & MF3_ISMONSTER) && !(flags & RMVF_NOMONSTERS))
5687 {
5688 P_RemoveThing(removetarget);
5689 }
5690 if ((removetarget->flags & MF_MISSILE) && (flags & RMVF_MISSILES))
5691 {
5692 P_RemoveThing(removetarget);
5693 }
5694 }
5695 }
5696
5697 //===========================================================================
5698 //
5699 // A_RemoveTarget
5700 //
5701 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_RemoveTarget)5702 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveTarget)
5703 {
5704 ACTION_PARAM_START(2);
5705 ACTION_PARAM_INT(flags, 0);
5706 ACTION_PARAM_CLASS(filter, 1);
5707 ACTION_PARAM_NAME(species, 2);
5708
5709 if (self->target != NULL)
5710 {
5711 DoRemove(self->target, flags, filter, species);
5712 }
5713 }
5714
5715 //===========================================================================
5716 //
5717 // A_RemoveTracer
5718 //
5719 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_RemoveTracer)5720 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveTracer)
5721 {
5722 ACTION_PARAM_START(2);
5723 ACTION_PARAM_INT(flags, 0);
5724 ACTION_PARAM_CLASS(filter, 1);
5725 ACTION_PARAM_NAME(species, 2);
5726
5727 if (self->tracer != NULL)
5728 {
5729 DoRemove(self->tracer, flags, filter, species);
5730 }
5731 }
5732
5733 //===========================================================================
5734 //
5735 // A_RemoveMaster
5736 //
5737 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_RemoveMaster)5738 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveMaster)
5739 {
5740 ACTION_PARAM_START(2);
5741 ACTION_PARAM_INT(flags, 0);
5742 ACTION_PARAM_CLASS(filter, 1);
5743 ACTION_PARAM_NAME(species, 2);
5744
5745 if (self->master != NULL)
5746 {
5747 DoRemove(self->master, flags, filter, species);
5748 }
5749 }
5750
5751 //===========================================================================
5752 //
5753 // A_RemoveChildren
5754 //
5755 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_RemoveChildren)5756 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveChildren)
5757 {
5758 TThinkerIterator<AActor> it;
5759 AActor *mo;
5760 ACTION_PARAM_START(4);
5761 ACTION_PARAM_BOOL(removeall, 0);
5762 ACTION_PARAM_INT(flags, 1);
5763 ACTION_PARAM_CLASS(filter, 2);
5764 ACTION_PARAM_NAME(species, 3);
5765
5766
5767 while ((mo = it.Next()) != NULL)
5768 {
5769 if (mo->master == self && (mo->health <= 0 || removeall))
5770 {
5771 DoRemove(mo, flags, filter, species);
5772 }
5773 }
5774 }
5775
5776 //===========================================================================
5777 //
5778 // A_RemoveSiblings
5779 //
5780 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_RemoveSiblings)5781 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveSiblings)
5782 {
5783 TThinkerIterator<AActor> it;
5784 AActor *mo;
5785 ACTION_PARAM_START(4);
5786 ACTION_PARAM_BOOL(removeall, 0);
5787 ACTION_PARAM_INT(flags, 1);
5788 ACTION_PARAM_CLASS(filter, 2);
5789 ACTION_PARAM_NAME(species, 3);
5790
5791 if (self->master != NULL)
5792 {
5793 while ((mo = it.Next()) != NULL)
5794 {
5795 if (mo->master == self->master && mo != self && (mo->health <= 0 || removeall))
5796 {
5797 DoRemove(mo, flags, filter, species);
5798 }
5799 }
5800 }
5801 }
5802
5803 //===========================================================================
5804 //
5805 // A_Remove
5806 //
5807 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_Remove)5808 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Remove)
5809 {
5810 ACTION_PARAM_START(4);
5811 ACTION_PARAM_INT(removee, 0);
5812 ACTION_PARAM_INT(flags, 1);
5813 ACTION_PARAM_CLASS(filter, 2);
5814 ACTION_PARAM_NAME(species, 3);
5815
5816 AActor *reference = COPY_AAPTR(self, removee);
5817 if (reference != NULL)
5818 {
5819 DoRemove(reference, flags, filter, species);
5820 }
5821 }
5822
5823 //===========================================================================
5824 //
5825 // A_SetTeleFog
5826 //
5827 // Sets the teleport fog(s) for the calling actor.
5828 // Takes a name of the classes for the source and destination.
5829 //===========================================================================
5830
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SetTeleFog)5831 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTeleFog)
5832 {
5833 ACTION_PARAM_START(2);
5834 ACTION_PARAM_CLASS(oldpos, 0);
5835 ACTION_PARAM_CLASS(newpos, 1);
5836
5837 self->TeleFogSourceType = oldpos;
5838 self->TeleFogDestType = newpos;
5839 }
5840
5841 //===========================================================================
5842 //
5843 // A_SwapTeleFog
5844 //
5845 // Switches the source and dest telefogs around.
5846 //===========================================================================
5847
DEFINE_ACTION_FUNCTION(AActor,A_SwapTeleFog)5848 DEFINE_ACTION_FUNCTION(AActor, A_SwapTeleFog)
5849 {
5850 if ((self->TeleFogSourceType != self->TeleFogDestType)) //Does nothing if they're the same.
5851 {
5852 const PClass *temp = self->TeleFogSourceType;
5853 self->TeleFogSourceType = self->TeleFogDestType;
5854 self->TeleFogDestType = temp;
5855 }
5856 }
5857
5858 //===========================================================================
5859 //
5860 // A_SetFloatBobPhase
5861 //
5862 // Changes the FloatBobPhase of the actor.
5863 //===========================================================================
5864
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SetFloatBobPhase)5865 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetFloatBobPhase)
5866 {
5867 ACTION_PARAM_START(1);
5868 ACTION_PARAM_INT(bob, 0);
5869
5870 //Respect float bob phase limits.
5871 if (self && (bob >= 0 && bob <= 63))
5872 self->FloatBobPhase = bob;
5873 }
5874
5875 //===========================================================================
5876 // A_SetHealth
5877 //
5878 // Changes the health of the actor.
5879 // Takes a pointer as well.
5880 //===========================================================================
5881
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SetHealth)5882 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetHealth)
5883 {
5884 ACTION_PARAM_START(2);
5885 ACTION_PARAM_INT(health, 0);
5886 ACTION_PARAM_INT(ptr, 1);
5887
5888 AActor *mobj = COPY_AAPTR(self, ptr);
5889
5890 if (!mobj)
5891 {
5892 return;
5893 }
5894
5895 player_t *player = mobj->player;
5896 if (player)
5897 {
5898 if (health <= 0)
5899 player->mo->health = mobj->health = player->health = 1; //Copied from the buddha cheat.
5900 else
5901 player->mo->health = mobj->health = player->health = health;
5902 }
5903 else if (mobj)
5904 {
5905 if (health <= 0)
5906 mobj->health = 1;
5907 else
5908 mobj->health = health;
5909 }
5910 }
5911
5912 //===========================================================================
5913 // A_ResetHealth
5914 //
5915 // Resets the health of the actor to default, except if their dead.
5916 // Takes a pointer.
5917 //===========================================================================
5918
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_ResetHealth)5919 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ResetHealth)
5920 {
5921 ACTION_PARAM_START(1);
5922 ACTION_PARAM_INT(ptr, 0);
5923
5924 AActor *mobj = COPY_AAPTR(self, ptr);
5925
5926 if (!mobj)
5927 {
5928 return;
5929 }
5930
5931 player_t *player = mobj->player;
5932 if (player && (player->mo->health > 0))
5933 {
5934 player->health = player->mo->health = player->mo->GetDefault()->health; //Copied from the resurrect cheat.
5935 }
5936 else if (mobj && (mobj->health > 0))
5937 {
5938 mobj->health = mobj->SpawnHealth();
5939 }
5940 }
5941
5942 //===========================================================================
5943 // A_JumpIfHigherOrLower
5944 //
5945 // Jumps if a target, master, or tracer is higher or lower than the calling
5946 // actor. Can also specify how much higher/lower the actor needs to be than
5947 // itself. Can also take into account the height of the actor in question,
5948 // depending on which it's checking. This means adding height of the
5949 // calling actor's self if the pointer is higher, or height of the pointer
5950 // if its lower.
5951 //===========================================================================
5952
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_JumpIfHigherOrLower)5953 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfHigherOrLower)
5954 {
5955 ACTION_PARAM_START(6);
5956 ACTION_PARAM_STATE(high, 0);
5957 ACTION_PARAM_STATE(low, 1);
5958 ACTION_PARAM_FIXED(offsethigh, 2);
5959 ACTION_PARAM_FIXED(offsetlow, 3);
5960 ACTION_PARAM_BOOL(includeHeight, 4);
5961 ACTION_PARAM_INT(ptr, 5);
5962
5963 AActor *mobj = COPY_AAPTR(self, ptr);
5964
5965
5966 if (!mobj || (mobj == self)) //AAPTR_DEFAULT is completely useless in this regard.
5967 {
5968 return;
5969 }
5970 ACTION_SET_RESULT(false); //No inventory jump chains please.
5971
5972 if ((high) && (mobj->Z() > ((includeHeight ? self->height : 0) + self->Z() + offsethigh)))
5973 ACTION_JUMP(high);
5974 else if ((low) && (mobj->Z() + (includeHeight ? mobj->height : 0)) < (self->Z() + offsetlow))
5975 ACTION_JUMP(low);
5976 }
5977
5978 //===========================================================================
5979 // A_SetSpecies(str species, ptr)
5980 //
5981 // Sets the species of the calling actor('s pointer).
5982 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SetSpecies)5983 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpecies)
5984 {
5985 ACTION_PARAM_START(2);
5986 ACTION_PARAM_NAME(species, 0);
5987 ACTION_PARAM_INT(ptr, 1);
5988 AActor *mobj = COPY_AAPTR(self, ptr);
5989 if (!mobj)
5990 {
5991 ACTION_SET_RESULT(false);
5992 return;
5993 }
5994
5995 mobj->Species = species;
5996 }
5997
5998 //===========================================================================
5999 //
6000 // A_SetRipperLevel(int level)
6001 //
6002 // Sets the ripper level of the calling actor.
6003 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SetRipperLevel)6004 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRipperLevel)
6005 {
6006 ACTION_PARAM_START(1);
6007 ACTION_PARAM_INT(level, 0);
6008 self->RipperLevel = level;
6009 }
6010
6011 //===========================================================================
6012 //
6013 // A_SetRipMin(int min)
6014 //
6015 // Sets the minimum level a ripper must be in order to rip through this actor.
6016 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SetRipMin)6017 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRipMin)
6018 {
6019 ACTION_PARAM_START(1);
6020 ACTION_PARAM_INT(min, 0);
6021 self->RipLevelMin = min;
6022 }
6023
6024 //===========================================================================
6025 //
6026 // A_SetRipMax(int max)
6027 //
6028 // Sets the minimum level a ripper must be in order to rip through this actor.
6029 //===========================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SetRipMax)6030 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRipMax)
6031 {
6032 ACTION_PARAM_START(1);
6033 ACTION_PARAM_INT(max, 0);
6034 self->RipLevelMax = max;
6035 }
6036
6037 //==========================================================================
6038 //
6039 // A_CheckProximity(jump, classname, distance, count, flags, ptr)
6040 //
6041 // Checks to see if a certain actor class is close to the
6042 // actor/pointer within distance, in numbers.
6043 //==========================================================================
6044 enum CPXFflags
6045 {
6046 CPXF_ANCESTOR = 1,
6047 CPXF_LESSOREQUAL = 1 << 1,
6048 CPXF_NOZ = 1 << 2,
6049 CPXF_COUNTDEAD = 1 << 3,
6050 CPXF_DEADONLY = 1 << 4,
6051 CPXF_EXACT = 1 << 5,
6052 CPXF_SETTARGET = 1 << 6,
6053 CPXF_SETMASTER = 1 << 7,
6054 CPXF_SETTRACER = 1 << 8,
6055 CPXF_FARTHEST = 1 << 9,
6056 CPXF_CLOSEST = 1 << 10,
6057 CPXF_SETONPTR = 1 << 11,
6058 CPXF_CHECKSIGHT = 1 << 12,
6059 };
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_CheckProximity)6060 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckProximity)
6061 {
6062 ACTION_PARAM_START(6);
6063 ACTION_PARAM_STATE(jump, 0);
6064 ACTION_PARAM_CLASS(classname, 1);
6065 ACTION_PARAM_FIXED(distance, 2);
6066 ACTION_PARAM_INT(count, 3);
6067 ACTION_PARAM_INT(flags, 4);
6068 ACTION_PARAM_INT(ptr, 5);
6069
6070 ACTION_SET_RESULT(false); //No inventory chain results please.
6071
6072 if (!jump)
6073 {
6074 if (!(flags & (CPXF_SETTARGET | CPXF_SETMASTER | CPXF_SETTRACER)))
6075 return;
6076 }
6077 AActor *ref = COPY_AAPTR(self, ptr);
6078
6079 //We need these to check out.
6080 if (!ref || !classname || (distance <= 0))
6081 return;
6082
6083 int counter = 0;
6084 bool result = false;
6085 fixed_t closer = distance, farther = 0, current = distance;
6086 const bool ptrWillChange = !!(flags & (CPXF_SETTARGET | CPXF_SETMASTER | CPXF_SETTRACER));
6087 const bool ptrDistPref = !!(flags & (CPXF_CLOSEST | CPXF_FARTHEST));
6088
6089 TThinkerIterator<AActor> it;
6090 AActor *mo, *dist = NULL;
6091
6092 //[MC] Process of elimination, I think, will get through this as quickly and
6093 //efficiently as possible.
6094 while ((mo = it.Next()))
6095 {
6096 if (mo == ref) //Don't count self.
6097 continue;
6098
6099 //Check inheritance for the classname. Taken partly from CheckClass DECORATE function.
6100 if (flags & CPXF_ANCESTOR)
6101 {
6102 if (!(mo->IsKindOf(classname)))
6103 continue;
6104 }
6105 //Otherwise, just check for the regular class name.
6106 else if (classname != mo->GetClass())
6107 continue;
6108
6109 //[MC]Make sure it's in range and respect the desire for Z or not. The function forces it to use
6110 //Z later for ensuring CLOSEST and FARTHEST flags are respected perfectly.
6111 //Ripped from sphere checking in A_RadiusGive (along with a number of things).
6112 if ((ref->AproxDistance(mo) < distance &&
6113 ((flags & CPXF_NOZ) ||
6114 ((ref->Z() > mo->Z() && ref->Z() - mo->Top() < distance) ||
6115 (ref->Z() <= mo->Z() && mo->Z() - ref->Top() < distance)))))
6116 {
6117 if ((flags & CPXF_CHECKSIGHT) && !(P_CheckSight(mo, ref, SF_IGNOREVISIBILITY | SF_IGNOREWATERBOUNDARY)))
6118 continue;
6119
6120 if (ptrWillChange)
6121 {
6122 current = ref->AproxDistance(mo);
6123
6124 if ((flags & CPXF_CLOSEST) && (current < closer))
6125 {
6126 dist = mo;
6127 closer = current; //This actor's closer. Set the new standard.
6128 }
6129 else if ((flags & CPXF_FARTHEST) && (current > farther))
6130 {
6131 dist = mo;
6132 farther = current;
6133 }
6134 else if (!dist)
6135 dist = mo; //Just get the first one and call it quits if there's nothing selected.
6136 }
6137
6138 if (mo->flags6 & MF6_KILLED)
6139 {
6140 if (!(flags & (CPXF_COUNTDEAD | CPXF_DEADONLY)))
6141 continue;
6142 counter++;
6143 }
6144 else
6145 {
6146 if (flags & CPXF_DEADONLY)
6147 continue;
6148 counter++;
6149 }
6150
6151 //Abort if the number of matching classes nearby is greater, we have obviously succeeded in our goal.
6152 if (counter > count)
6153 {
6154 result = (flags & (CPXF_LESSOREQUAL | CPXF_EXACT)) ? false : true;
6155
6156 //However, if we have one SET* flag and either the closest or farthest flags, keep the function going.
6157 if (ptrWillChange && ptrDistPref)
6158 continue;
6159 else
6160 break;
6161 }
6162 }
6163 }
6164
6165 if (ptrWillChange && dist != NULL)
6166 {
6167 if (flags & CPXF_SETONPTR)
6168 {
6169 if (flags & CPXF_SETTARGET) ref->target = dist;
6170 if (flags & CPXF_SETMASTER) ref->master = dist;
6171 if (flags & CPXF_SETTRACER) ref->tracer = dist;
6172 }
6173 else
6174 {
6175 if (flags & CPXF_SETTARGET) self->target = dist;
6176 if (flags & CPXF_SETMASTER) self->master = dist;
6177 if (flags & CPXF_SETTRACER) self->tracer = dist;
6178 }
6179 }
6180
6181 if (counter == count)
6182 result = true;
6183 else if (counter < count)
6184 result = !!((flags & CPXF_LESSOREQUAL) && !(flags & CPXF_EXACT));
6185
6186 if (!jump) return;
6187
6188 if (result)
6189 {
6190 ACTION_JUMP(jump);
6191 }
6192 }
6193
6194 /*===========================================================================
6195 A_CheckBlock
6196 (state block, int flags, int ptr)
6197
6198 Checks if something is blocking the actor('s pointer) 'ptr'.
6199
6200 The SET pointer flags only affect the caller, not the pointer.
6201 ===========================================================================*/
6202 enum CBF
6203 {
6204 CBF_NOLINES = 1 << 0, //Don't check actors.
6205 CBF_SETTARGET = 1 << 1, //Sets the caller/pointer's target to the actor blocking it. Actors only.
6206 CBF_SETMASTER = 1 << 2, //^ but with master.
6207 CBF_SETTRACER = 1 << 3, //^ but with tracer.
6208 CBF_SETONPTR = 1 << 4, //Sets the pointer change on the actor doing the checking instead of self.
6209 CBF_DROPOFF = 1 << 5, //Check for dropoffs.
6210 };
6211
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_CheckBlock)6212 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckBlock)
6213 {
6214 ACTION_PARAM_START(3);
6215 ACTION_PARAM_STATE(block, 0);
6216 ACTION_PARAM_INT(flags, 1);
6217 ACTION_PARAM_INT(ptr, 2);
6218
6219 AActor *mobj = COPY_AAPTR(self, ptr);
6220
6221 ACTION_SET_RESULT(false);
6222 //Needs at least one state jump to work.
6223 if (!mobj)
6224 {
6225 return;
6226 }
6227
6228 //Nothing to block it so skip the rest.
6229 bool checker = (flags & CBF_DROPOFF) ? P_CheckMove(mobj, mobj->X(), mobj->Y()) : P_TestMobjLocation(mobj);
6230 if (checker) return;
6231
6232 if (mobj->BlockingMobj)
6233 {
6234 AActor *setter = (flags & CBF_SETONPTR) ? mobj : self;
6235 if (setter)
6236 {
6237 if (flags & CBF_SETTARGET) setter->target = mobj->BlockingMobj;
6238 if (flags & CBF_SETMASTER) setter->master = mobj->BlockingMobj;
6239 if (flags & CBF_SETTRACER) setter->tracer = mobj->BlockingMobj;
6240 }
6241 }
6242
6243 //[MC] If modders don't want jumping, but just getting the pointer, only abort at
6244 //this point. I.e. A_CheckBlock("",CBF_SETTRACER) is like having CBF_NOLINES.
6245 //It gets the mobj blocking, if any, and doesn't jump at all.
6246 if (!block)
6247 return;
6248
6249 //[MC] Easiest way to tell if an actor is blocking it, use the pointers.
6250 if (mobj->BlockingMobj || (!(flags & CBF_NOLINES) && mobj->BlockingLine != NULL))
6251 {
6252 ACTION_JUMP(block);
6253 }
6254 }
6255
6256 //===========================================================================
6257 //
6258 // A_FaceMovementDirection(angle offset, bool pitch, ptr)
6259 //
6260 // Sets the actor('s pointer) to face the direction of travel.
6261 //===========================================================================
6262 enum FMDFlags
6263 {
6264 FMDF_NOPITCH = 1 << 0,
6265 FMDF_INTERPOLATE = 1 << 1,
6266 FMDF_NOANGLE = 1 << 2,
6267 };
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_FaceMovementDirection)6268 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FaceMovementDirection)
6269 {
6270 ACTION_PARAM_START(5);
6271 ACTION_PARAM_ANGLE(offset, 0);
6272 ACTION_PARAM_ANGLE(anglelimit, 1);
6273 ACTION_PARAM_ANGLE(pitchlimit, 2);
6274 ACTION_PARAM_INT(flags, 3);
6275 ACTION_PARAM_INT(ptr, 4);
6276
6277 AActor *mobj = COPY_AAPTR(self, ptr);
6278
6279 //Need an actor.
6280 if (!mobj || ((flags & FMDF_NOPITCH) && (flags & FMDF_NOANGLE)))
6281 {
6282 ACTION_SET_RESULT(false);
6283 return;
6284 }
6285
6286 //Don't bother calculating this if we don't have any horizontal movement.
6287 if (!(flags & FMDF_NOANGLE) && (mobj->velx != 0 || mobj->vely != 0))
6288 {
6289 angle_t current = mobj->angle;
6290 const angle_t angle = R_PointToAngle2(0, 0, mobj->velx, mobj->vely);
6291 //Done because using anglelimit directly causes a signed/unsigned mismatch.
6292 const angle_t limit = anglelimit;
6293
6294 //Code borrowed from A_Face*.
6295 if (limit > 0 && (absangle(current - angle) > limit))
6296 {
6297 if (current < angle)
6298 {
6299 // [MC] This may appear backwards, but I assure any who
6300 // reads this, it works.
6301 if (current - angle > ANGLE_180)
6302 current += limit + offset;
6303 else
6304 current -= limit + offset;
6305 mobj->SetAngle(current, !!(flags & FMDF_INTERPOLATE));
6306 }
6307 else if (current > angle)
6308 {
6309 if (angle - current > ANGLE_180)
6310 current -= limit + offset;
6311 else
6312 current += limit + offset;
6313 mobj->SetAngle(current, !!(flags & FMDF_INTERPOLATE));
6314 }
6315 else
6316 mobj->SetAngle(angle + ANGLE_180 + offset, !!(flags & FMDF_INTERPOLATE));
6317
6318 }
6319 else
6320 mobj->SetAngle(angle + offset, !!(flags & FMDF_INTERPOLATE));
6321 }
6322
6323 if (!(flags & FMDF_NOPITCH))
6324 {
6325 fixed_t current = mobj->pitch;
6326 const FVector2 velocity(mobj->velx, mobj->vely);
6327 const fixed_t pitch = R_PointToAngle2(0, 0, (fixed_t)velocity.Length(), -mobj->velz);
6328 if (pitchlimit > 0)
6329 {
6330 // [MC] angle_t for pitchlimit was required because otherwise
6331 // we would wind up with less than desirable turn rates that didn't
6332 // match that of A_SetPitch. We want consistency. Also, I didn't know
6333 // of a better way to convert from angle_t to fixed_t properly so I
6334 // used this instead.
6335 fixed_t plimit = fixed_t(pitchlimit);
6336
6337 if (abs(current - pitch) > plimit)
6338 {
6339 fixed_t max = 0;
6340
6341 if (current > pitch)
6342 {
6343 max = MIN(plimit, (current - pitch));
6344 current -= max;
6345 }
6346 else //if (current > pitch)
6347 {
6348 max = MIN(plimit, (pitch - current));
6349 current += max;
6350 }
6351 mobj->SetPitch(current, !!(flags & FMDF_INTERPOLATE));
6352 }
6353 else
6354 {
6355 mobj->SetPitch(pitch, !!(flags & FMDF_INTERPOLATE));
6356 }
6357
6358 }
6359 else
6360 {
6361 mobj->SetPitch(pitch, !!(flags & FMDF_INTERPOLATE));
6362 }
6363 }
6364 }
6365
6366