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