1 // Emacs style mode select -*- C++ -*-
2 // Emacs style mode select -*- C++ -*-
3 //-----------------------------------------------------------------------------
4 //
5 // $Id:$
6 //
7 // Copyright (C) 1993-1996 by id Software, Inc.
8 //
9 // This source is available for distribution and/or modification
10 // only under the terms of the DOOM Source Code License as
11 // published by id Software. All rights reserved.
12 //
13 // The source is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
16 // for more details.
17 //
18 // $Log:$
19 //
20 // DESCRIPTION:
21 // Moving object handling. Spawn functions.
22 //
23 //-----------------------------------------------------------------------------
24
25 // HEADER FILES ------------------------------------------------------------
26
27 #include "templates.h"
28 #include "i_system.h"
29 #include "m_random.h"
30 #include "doomdef.h"
31 #include "p_local.h"
32 #include "p_lnspec.h"
33 #include "p_effect.h"
34 #include "p_terrain.h"
35 #include "st_stuff.h"
36 #include "hu_stuff.h"
37 #include "s_sound.h"
38 #include "doomstat.h"
39 #include "v_video.h"
40 #include "c_cvars.h"
41 #include "c_dispatch.h"
42 #include "b_bot.h" //Added by MC:
43 #include "stats.h"
44 #include "a_hexenglobal.h"
45 #include "a_sharedglobal.h"
46 #include "gi.h"
47 #include "sbar.h"
48 #include "p_acs.h"
49 #include "cmdlib.h"
50 #include "decallib.h"
51 #include "ravenshared.h"
52 #include "a_action.h"
53 #include "a_keys.h"
54 #include "p_conversation.h"
55 #include "thingdef/thingdef.h"
56 #include "g_game.h"
57 #include "teaminfo.h"
58 #include "r_data/r_translate.h"
59 #include "r_sky.h"
60 #include "g_level.h"
61 #include "d_event.h"
62 #include "colormatcher.h"
63 #include "v_palette.h"
64 #include "p_enemy.h"
65 #include "gstrings.h"
66 #include "farchive.h"
67 #include "r_data/colormaps.h"
68 #include "r_renderer.h"
69
70 // MACROS ------------------------------------------------------------------
71
72 #define WATER_SINK_FACTOR 3
73 #define WATER_SINK_SMALL_FACTOR 4
74 #define WATER_SINK_SPEED (FRACUNIT/2)
75 #define WATER_JUMP_SPEED (FRACUNIT*7/2)
76
77 // EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
78
79 void G_PlayerReborn (int player);
80
81 // PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
82
83 static void PlayerLandedOnThing (AActor *mo, AActor *onmobj);
84
85 // EXTERNAL DATA DECLARATIONS ----------------------------------------------
86
87 extern int BotWTG;
88 EXTERN_CVAR (Int, cl_rockettrails)
89
90 // PRIVATE DATA DEFINITIONS ------------------------------------------------
91
92 static FRandom pr_explodemissile ("ExplodeMissile");
93 FRandom pr_bounce ("Bounce");
94 static FRandom pr_reflect ("Reflect");
95 static FRandom pr_nightmarerespawn ("NightmareRespawn");
96 static FRandom pr_botspawnmobj ("BotSpawnActor");
97 static FRandom pr_spawnmapthing ("SpawnMapThing");
98 static FRandom pr_spawnpuff ("SpawnPuff");
99 static FRandom pr_spawnblood ("SpawnBlood");
100 static FRandom pr_splatter ("BloodSplatter");
101 static FRandom pr_takedamage ("TakeDamage");
102 static FRandom pr_splat ("FAxeSplatter");
103 static FRandom pr_ripperblood ("RipperBlood");
104 static FRandom pr_chunk ("Chunk");
105 static FRandom pr_checkmissilespawn ("CheckMissileSpawn");
106 static FRandom pr_spawnmissile ("SpawnMissile");
107 static FRandom pr_missiledamage ("MissileDamage");
108 static FRandom pr_multiclasschoice ("MultiClassChoice");
109 static FRandom pr_rockettrail("RocketTrail");
110 static FRandom pr_uniquetid("UniqueTID");
111
112 // PUBLIC DATA DEFINITIONS -------------------------------------------------
113
114 FRandom pr_spawnmobj ("SpawnActor");
115
116 CUSTOM_CVAR (Float, sv_gravity, 800.f, CVAR_SERVERINFO|CVAR_NOSAVE)
117 {
118 level.gravity = self;
119 }
120
121 CVAR (Bool, cl_missiledecals, true, CVAR_ARCHIVE)
122 CVAR (Bool, addrocketexplosion, false, CVAR_ARCHIVE)
123 CVAR (Int, cl_pufftype, 0, CVAR_ARCHIVE);
124 CVAR (Int, cl_bloodtype, 0, CVAR_ARCHIVE);
125
126 // CODE --------------------------------------------------------------------
127
128 IMPLEMENT_POINTY_CLASS (AActor)
DECLARE_POINTER(target)129 DECLARE_POINTER (target)
130 DECLARE_POINTER (lastenemy)
131 DECLARE_POINTER (tracer)
132 DECLARE_POINTER (goal)
133 DECLARE_POINTER (LastLookActor)
134 DECLARE_POINTER (Inventory)
135 DECLARE_POINTER (LastHeard)
136 DECLARE_POINTER (master)
137 DECLARE_POINTER (Poisoner)
138 END_POINTERS
139
140 AActor::~AActor ()
141 {
142 // Please avoid calling the destructor directly (or through delete)!
143 // Use Destroy() instead.
144 }
145
Serialize(FArchive & arc)146 void AActor::Serialize (FArchive &arc)
147 {
148 Super::Serialize (arc);
149
150 if (arc.IsStoring ())
151 {
152 arc.WriteSprite (sprite);
153 }
154 else
155 {
156 sprite = arc.ReadSprite ();
157 }
158
159 arc << __pos.x
160 << __pos.y
161 << __pos.z
162 << angle
163 << frame
164 << scaleX
165 << scaleY
166 << RenderStyle
167 << renderflags
168 << picnum
169 << floorpic
170 << ceilingpic
171 << TIDtoHate
172 << LastLookPlayerNumber
173 << LastLookActor
174 << effects
175 << alpha
176 << fillcolor
177 << pitch
178 << roll
179 << Sector
180 << floorz
181 << ceilingz
182 << dropoffz
183 << floorsector
184 << ceilingsector
185 << radius
186 << height
187 << projectilepassheight
188 << velx
189 << vely
190 << velz
191 << tics
192 << state
193 << Damage;
194 if (SaveVersion >= 4530)
195 {
196 P_SerializeTerrain(arc, floorterrain);
197 }
198 if (SaveVersion >= 3227)
199 {
200 arc << projectileKickback;
201 }
202 arc << flags
203 << flags2
204 << flags3
205 << flags4
206 << flags5
207 << flags6;
208 if (SaveVersion >= 4504)
209 {
210 arc << flags7;
211 }
212 if (SaveVersion >= 4512)
213 {
214 arc << weaponspecial;
215 }
216 arc << special1
217 << special2
218 << health
219 << movedir
220 << visdir
221 << movecount
222 << strafecount
223 << target
224 << lastenemy
225 << LastHeard
226 << reactiontime
227 << threshold
228 << player
229 << SpawnPoint[0] << SpawnPoint[1] << SpawnPoint[2]
230 << SpawnAngle;
231 if (SaveVersion >= 4506)
232 {
233 arc << StartHealth;
234 }
235 arc << skillrespawncount
236 << tracer
237 << floorclip
238 << tid
239 << special;
240 if (P_IsACSSpecial(special))
241 {
242 P_SerializeACSScriptNumber(arc, args[0], false);
243 }
244 else
245 {
246 arc << args[0];
247 }
248 arc << args[1] << args[2] << args[3] << args[4];
249 if (SaveVersion >= 3427)
250 {
251 arc << accuracy << stamina;
252 }
253 arc << goal
254 << waterlevel
255 << MinMissileChance
256 << SpawnFlags
257 << Inventory
258 << InventoryID;
259 if (SaveVersion < 4513)
260 {
261 SDWORD id;
262 arc << id;
263 }
264 arc << FloatBobPhase
265 << Translation
266 << SeeSound
267 << AttackSound
268 << PainSound
269 << DeathSound
270 << ActiveSound
271 << UseSound
272 << BounceSound
273 << WallBounceSound
274 << CrushPainSound
275 << Speed
276 << FloatSpeed
277 << Mass
278 << PainChance
279 << SpawnState
280 << SeeState
281 << MeleeState
282 << MissileState
283 << MaxDropOffHeight
284 << MaxStepHeight
285 << BounceFlags
286 << bouncefactor
287 << wallbouncefactor
288 << bouncecount
289 << maxtargetrange
290 << meleethreshold
291 << meleerange
292 << DamageType;
293 if (SaveVersion >= 4501)
294 {
295 arc << DamageTypeReceived;
296 }
297 if (SaveVersion >= 3237)
298 {
299 arc
300 << PainType
301 << DeathType;
302 }
303 arc << gravity
304 << FastChaseStrafeCount
305 << master
306 << smokecounter
307 << BlockingMobj
308 << BlockingLine
309 << VisibleToTeam // [BB]
310 << pushfactor
311 << Species
312 << Score;
313 if (SaveVersion >= 3113)
314 {
315 arc << DesignatedTeam;
316 }
317 arc << lastpush << lastbump
318 << PainThreshold
319 << DamageFactor;
320 if (SaveVersion >= 4516)
321 {
322 arc << DamageMultiply;
323 }
324 else
325 {
326 DamageMultiply = FRACUNIT;
327 }
328 arc << WeaveIndexXY << WeaveIndexZ
329 << PoisonDamageReceived << PoisonDurationReceived << PoisonPeriodReceived << Poisoner
330 << PoisonDamage << PoisonDuration << PoisonPeriod;
331 if (SaveVersion >= 3235)
332 {
333 arc << PoisonDamageType << PoisonDamageTypeReceived;
334 }
335 arc << ConversationRoot << Conversation;
336 if (SaveVersion >= 4509)
337 {
338 arc << FriendPlayer;
339 }
340 if (SaveVersion >= 4517)
341 {
342 arc << TeleFogSourceType
343 << TeleFogDestType;
344 }
345 if (SaveVersion >= 4518)
346 {
347 arc << RipperLevel
348 << RipLevelMin
349 << RipLevelMax;
350 }
351
352 {
353 FString tagstr;
354 if (arc.IsStoring() && Tag != NULL && Tag->Len() > 0) tagstr = *Tag;
355 arc << tagstr;
356 if (arc.IsLoading())
357 {
358 if (tagstr.Len() == 0) Tag = NULL;
359 else Tag = mStringPropertyData.Alloc(tagstr);
360 }
361 }
362
363 if (arc.IsLoading ())
364 {
365 touching_sectorlist = NULL;
366 LinkToWorld (Sector);
367 AddToHash ();
368 SetShade (fillcolor);
369 if (player)
370 {
371 if (playeringame[player - players] &&
372 player->cls != NULL &&
373 !(flags4 & MF4_NOSKIN) &&
374 state->sprite == GetDefaultByType (player->cls)->SpawnState->sprite)
375 { // Give player back the skin
376 sprite = skins[player->userinfo.GetSkin()].sprite;
377 }
378 if (Speed == 0)
379 {
380 Speed = GetDefault()->Speed;
381 }
382 }
383 PrevX = X();
384 PrevY = Y();
385 PrevZ = Z();
386 PrevAngle = angle;
387 UpdateWaterLevel(Z(), false);
388 }
389 }
390
391
AActor()392 AActor::AActor () throw()
393 {
394 }
395
AActor(const AActor & other)396 AActor::AActor (const AActor &other) throw()
397 : DThinker()
398 {
399 memcpy (&snext, &other.snext, (BYTE *)&this[1] - (BYTE *)&snext);
400 }
401
operator =(const AActor & other)402 AActor &AActor::operator= (const AActor &other)
403 {
404 memcpy (&snext, &other.snext, (BYTE *)&this[1] - (BYTE *)&snext);
405 return *this;
406 }
407
408 //==========================================================================
409 //
410 // AActor::InStateSequence
411 //
412 // Checks whether the current state is in a contiguous sequence that
413 // starts with basestate
414 //
415 //==========================================================================
416
InStateSequence(FState * newstate,FState * basestate)417 bool AActor::InStateSequence(FState * newstate, FState * basestate)
418 {
419 if (basestate == NULL) return false;
420
421 FState * thisstate = basestate;
422 do
423 {
424 if (newstate == thisstate) return true;
425 basestate = thisstate;
426 thisstate = thisstate->GetNextState();
427 }
428 while (thisstate == basestate+1);
429 return false;
430 }
431
432 //==========================================================================
433 //
434 // AActor::GetTics
435 //
436 // Get the actual duration of the next state
437 // We are using a state flag now to indicate a state that should be
438 // accelerated in Fast mode or slowed in Slow mode.
439 //
440 //==========================================================================
441
GetTics(FState * newstate)442 int AActor::GetTics(FState * newstate)
443 {
444 int tics = newstate->GetTics();
445 if (isFast() && newstate->Fast)
446 {
447 return tics - (tics>>1);
448 }
449 else if (isSlow() && newstate->Slow)
450 {
451 return tics<<1;
452 }
453 return tics;
454 }
455
456 //==========================================================================
457 //
458 // AActor::SetState
459 //
460 // Returns true if the mobj is still present.
461 //
462 //==========================================================================
463
SetState(FState * newstate,bool nofunction)464 bool AActor::SetState (FState *newstate, bool nofunction)
465 {
466 if (debugfile && player && (player->cheats & CF_PREDICTING))
467 fprintf (debugfile, "for pl %td: SetState while predicting!\n", player-players);
468 do
469 {
470 if (newstate == NULL)
471 {
472 state = NULL;
473 Destroy ();
474 return false;
475 }
476 int prevsprite, newsprite;
477
478 if (state != NULL)
479 {
480 prevsprite = state->sprite;
481 }
482 else
483 {
484 prevsprite = -1;
485 }
486 state = newstate;
487 tics = GetTics(newstate);
488 renderflags = (renderflags & ~RF_FULLBRIGHT) | ActorRenderFlags::FromInt (newstate->GetFullbright());
489 newsprite = newstate->sprite;
490 if (newsprite != SPR_FIXED)
491 { // okay to change sprite and/or frame
492 if (!newstate->GetSameFrame())
493 { // okay to change frame
494 frame = newstate->GetFrame();
495 }
496 if (newsprite != SPR_NOCHANGE)
497 { // okay to change sprite
498 if (!(flags4 & MF4_NOSKIN) && newsprite == SpawnState->sprite)
499 { // [RH] If the new sprite is the same as the original sprite, and
500 // this actor is attached to a player, use the player's skin's
501 // sprite. If a player is not attached, do not change the sprite
502 // unless it is different from the previous state's sprite; a
503 // player may have been attached, died, and respawned elsewhere,
504 // and we do not want to lose the skin on the body. If it wasn't
505 // for Dehacked, I would move sprite changing out of the states
506 // altogether, since actors rarely change their sprites after
507 // spawning.
508 if (player != NULL && skins != NULL)
509 {
510 sprite = skins[player->userinfo.GetSkin()].sprite;
511 }
512 else if (newsprite != prevsprite)
513 {
514 sprite = newsprite;
515 }
516 }
517 else
518 {
519 sprite = newsprite;
520 }
521 }
522 }
523
524 if (!nofunction && newstate->CallAction(this, this))
525 {
526 // Check whether the called action function resulted in destroying the actor
527 if (ObjectFlags & OF_EuthanizeMe)
528 return false;
529 }
530 newstate = newstate->GetNextState();
531 } while (tics == 0);
532
533 if (Renderer != NULL)
534 {
535 Renderer->StateChanged(this);
536 }
537 return true;
538 }
539
540 //============================================================================
541 //
542 // AActor :: AddInventory
543 //
544 //============================================================================
545
AddInventory(AInventory * item)546 void AActor::AddInventory (AInventory *item)
547 {
548 // Check if it's already attached to an actor
549 if (item->Owner != NULL)
550 {
551 // Is it attached to us?
552 if (item->Owner == this)
553 return;
554
555 // No, then remove it from the other actor first
556 item->Owner->RemoveInventory (item);
557 }
558
559 item->Owner = this;
560 item->Inventory = Inventory;
561 Inventory = item;
562
563 // Each item receives an unique ID when added to an actor's inventory.
564 // This is used by the DEM_INVUSE command to identify the item. Simply
565 // using the item's position in the list won't work, because ticcmds get
566 // run sometime in the future, so by the time it runs, the inventory
567 // might not be in the same state as it was when DEM_INVUSE was sent.
568 Inventory->InventoryID = InventoryID++;
569 }
570
571 //============================================================================
572 //
573 // AActor :: RemoveInventory
574 //
575 //============================================================================
576
RemoveInventory(AInventory * item)577 void AActor::RemoveInventory(AInventory *item)
578 {
579 AInventory *inv, **invp;
580
581 if (item != NULL && item->Owner != NULL) // can happen if the owner was destroyed by some action from an item's use state.
582 {
583 invp = &item->Owner->Inventory;
584 for (inv = *invp; inv != NULL; invp = &inv->Inventory, inv = *invp)
585 {
586 if (inv == item)
587 {
588 *invp = item->Inventory;
589 item->DetachFromOwner();
590 item->Owner = NULL;
591 item->Inventory = NULL;
592 break;
593 }
594 }
595 }
596 }
597
598 //============================================================================
599 //
600 // AActor :: TakeInventory
601 //
602 //============================================================================
603
TakeInventory(const PClass * itemclass,int amount,bool fromdecorate,bool notakeinfinite)604 bool AActor::TakeInventory(const PClass *itemclass, int amount, bool fromdecorate, bool notakeinfinite)
605 {
606 AInventory *item = FindInventory(itemclass);
607
608 if (item == NULL)
609 return false;
610
611 if (!fromdecorate)
612 {
613 item->Amount -= amount;
614 if (item->Amount <= 0)
615 {
616 item->DepleteOrDestroy();
617 }
618 // It won't be used in non-decorate context, so return false here
619 return false;
620 }
621
622 bool result = false;
623 if (item->Amount > 0)
624 {
625 result = true;
626 }
627
628 if (item->IsKindOf(RUNTIME_CLASS(AHexenArmor)))
629 return false;
630
631 // Do not take ammo if the "no take infinite/take as ammo depletion" flag is set
632 // and infinite ammo is on
633 if (notakeinfinite &&
634 ((dmflags & DF_INFINITE_AMMO) || (player && player->cheats & CF_INFINITEAMMO)) &&
635 item->IsKindOf(RUNTIME_CLASS(AAmmo)))
636 {
637 // Nothing to do here, except maybe res = false;? Would it make sense?
638 }
639 else if (!amount || amount>=item->Amount)
640 {
641 item->DepleteOrDestroy();
642 }
643 else item->Amount-=amount;
644
645 return result;
646 }
647
648
649 //============================================================================
650 //
651 // AActor :: DestroyAllInventory
652 //
653 //============================================================================
654
DestroyAllInventory()655 void AActor::DestroyAllInventory ()
656 {
657 while (Inventory != NULL)
658 {
659 AInventory *item = Inventory;
660 item->Destroy ();
661 assert (item != Inventory);
662 }
663 }
664
665 //============================================================================
666 //
667 // AActor :: FirstInv
668 //
669 // Returns the first item in this actor's inventory that has IF_INVBAR set.
670 //
671 //============================================================================
672
FirstInv()673 AInventory *AActor::FirstInv ()
674 {
675 if (Inventory == NULL)
676 {
677 return NULL;
678 }
679 if (Inventory->ItemFlags & IF_INVBAR)
680 {
681 return Inventory;
682 }
683 return Inventory->NextInv ();
684 }
685
686 //============================================================================
687 //
688 // AActor :: UseInventory
689 //
690 // Attempts to use an item. If the use succeeds, one copy of the item is
691 // removed from the inventory. If all copies are removed, then the item is
692 // destroyed.
693 //
694 //============================================================================
695
UseInventory(AInventory * item)696 bool AActor::UseInventory (AInventory *item)
697 {
698 // No using items if you're dead.
699 if (health <= 0)
700 {
701 return false;
702 }
703 // Don't use it if you don't actually have any of it.
704 if (item->Amount <= 0 || (item->ObjectFlags & OF_EuthanizeMe))
705 {
706 return false;
707 }
708 if (!item->Use (false))
709 {
710 return false;
711 }
712
713 if (dmflags2 & DF2_INFINITE_INVENTORY)
714 return true;
715
716 if (--item->Amount <= 0)
717 {
718 item->DepleteOrDestroy ();
719 }
720 return true;
721 }
722
723 //===========================================================================
724 //
725 // AActor :: DropInventory
726 //
727 // Removes a single copy of an item and throws it out in front of the actor.
728 //
729 //===========================================================================
730
DropInventory(AInventory * item)731 AInventory *AActor::DropInventory (AInventory *item)
732 {
733 angle_t an;
734 AInventory *drop = item->CreateTossable ();
735
736 if (drop == NULL)
737 {
738 return NULL;
739 }
740 an = angle >> ANGLETOFINESHIFT;
741 drop->SetOrigin(PosPlusZ(10*FRACUNIT), false);
742 drop->angle = angle;
743 drop->velx = velx + 5 * finecosine[an];
744 drop->vely = vely + 5 * finesine[an];
745 drop->velz = velz + FRACUNIT;
746 drop->flags &= ~MF_NOGRAVITY; // Don't float
747 drop->ClearCounters(); // do not count for statistics again
748 return drop;
749 }
750
751 //============================================================================
752 //
753 // AActor :: FindInventory
754 //
755 //============================================================================
756
FindInventory(const PClass * type,bool subclass)757 AInventory *AActor::FindInventory (const PClass *type, bool subclass)
758 {
759 AInventory *item;
760
761 if (type == NULL) return NULL;
762
763 assert (type->ActorInfo != NULL);
764 for (item = Inventory; item != NULL; item = item->Inventory)
765 {
766 if (!subclass)
767 {
768 if (item->GetClass() == type)
769 {
770 break;
771 }
772 }
773 else
774 {
775 if (item->IsKindOf(type))
776 {
777 break;
778 }
779 }
780 }
781 return item;
782 }
783
FindInventory(FName type)784 AInventory *AActor::FindInventory (FName type)
785 {
786 return FindInventory(PClass::FindClass(type));
787 }
788
789 //============================================================================
790 //
791 // AActor :: GiveInventoryType
792 //
793 //============================================================================
794
GiveInventoryType(const PClass * type)795 AInventory *AActor::GiveInventoryType (const PClass *type)
796 {
797 AInventory *item = NULL;
798
799 if (type != NULL)
800 {
801 item = static_cast<AInventory *>(Spawn (type, 0,0,0, NO_REPLACE));
802 if (!item->CallTryPickup (this))
803 {
804 item->Destroy ();
805 return NULL;
806 }
807 }
808 return item;
809 }
810
811 //============================================================================
812 //
813 // AActor :: GiveAmmo
814 //
815 // Returns true if the ammo was added, false if not.
816 //
817 //============================================================================
818
GiveAmmo(const PClass * type,int amount)819 bool AActor::GiveAmmo (const PClass *type, int amount)
820 {
821 if (type != NULL)
822 {
823 AInventory *item = static_cast<AInventory *>(Spawn (type, 0, 0, 0, NO_REPLACE));
824 if (item)
825 {
826 item->Amount = amount;
827 item->flags |= MF_DROPPED;
828 if (!item->CallTryPickup (this))
829 {
830 item->Destroy ();
831 return false;
832 }
833 return true;
834 }
835 }
836 return false;
837 }
838
839 //============================================================================
840 //
841 // AActor :: ClearInventory
842 //
843 // Clears the inventory of a single actor.
844 //
845 //============================================================================
846
ClearInventory()847 void AActor::ClearInventory()
848 {
849 // In case destroying an inventory item causes another to be destroyed
850 // (e.g. Weapons destroy their sisters), keep track of the pointer to
851 // the next inventory item rather than the next inventory item itself.
852 // For example, if a weapon is immediately followed by its sister, the
853 // next weapon we had tracked would be to the sister, so it is now
854 // invalid and we won't be able to find the complete inventory by
855 // following it.
856 //
857 // When we destroy an item, we leave invp alone, since the destruction
858 // process will leave it pointing to the next item we want to check. If
859 // we don't destroy an item, then we move invp to point to its Inventory
860 // pointer.
861 //
862 // It should be safe to assume that an item being destroyed will only
863 // destroy items further down in the chain, because if it was going to
864 // destroy something we already processed, we've already destroyed it,
865 // so it won't have anything to destroy.
866
867 AInventory **invp = &Inventory;
868
869 while (*invp != NULL)
870 {
871 AInventory *inv = *invp;
872 if (!(inv->ItemFlags & IF_UNDROPPABLE))
873 {
874 // For the sake of undroppable weapons, never remove ammo once
875 // it has been acquired; just set its amount to 0.
876 if (inv->IsKindOf(RUNTIME_CLASS(AAmmo)))
877 {
878 AAmmo *ammo = static_cast<AAmmo*>(inv);
879 ammo->Amount = 0;
880 invp = &inv->Inventory;
881 }
882 else
883 {
884 inv->Destroy ();
885 }
886 }
887 else if (inv->GetClass() == RUNTIME_CLASS(AHexenArmor))
888 {
889 AHexenArmor *harmor = static_cast<AHexenArmor *> (inv);
890 harmor->Slots[3] = harmor->Slots[2] = harmor->Slots[1] = harmor->Slots[0] = 0;
891 invp = &inv->Inventory;
892 }
893 else
894 {
895 invp = &inv->Inventory;
896 }
897 }
898 if (player != NULL)
899 {
900 player->ReadyWeapon = NULL;
901 player->PendingWeapon = WP_NOCHANGE;
902 player->psprites[ps_weapon].state = NULL;
903 player->psprites[ps_flash].state = NULL;
904 }
905 }
906
907 //============================================================================
908 //
909 // AActor :: CopyFriendliness
910 //
911 // Makes this actor hate (or like) the same things another actor does.
912 //
913 //============================================================================
914
CopyFriendliness(AActor * other,bool changeTarget,bool resetHealth)915 void AActor::CopyFriendliness (AActor *other, bool changeTarget, bool resetHealth)
916 {
917 level.total_monsters -= CountsAsKill();
918 TIDtoHate = other->TIDtoHate;
919 LastLookActor = other->LastLookActor;
920 LastLookPlayerNumber = other->LastLookPlayerNumber;
921 flags = (flags & ~MF_FRIENDLY) | (other->flags & MF_FRIENDLY);
922 flags3 = (flags3 & ~(MF3_NOSIGHTCHECK | MF3_HUNTPLAYERS)) | (other->flags3 & (MF3_NOSIGHTCHECK | MF3_HUNTPLAYERS));
923 flags4 = (flags4 & ~(MF4_NOHATEPLAYERS | MF4_BOSSSPAWNED)) | (other->flags4 & (MF4_NOHATEPLAYERS | MF4_BOSSSPAWNED));
924 FriendPlayer = other->FriendPlayer;
925 DesignatedTeam = other->DesignatedTeam;
926 if (changeTarget && other->target != NULL && !(other->target->flags3 & MF3_NOTARGET) && !(other->target->flags7 & MF7_NEVERTARGET))
927 {
928 // LastHeard must be set as well so that A_Look can react to the new target if called
929 LastHeard = target = other->target;
930 }
931 if (resetHealth) health = SpawnHealth();
932 level.total_monsters += CountsAsKill();
933 }
934
935 //============================================================================
936 //
937 // AActor :: ObtainInventory
938 //
939 // Removes the items from the other actor and puts them in this actor's
940 // inventory. The actor receiving the inventory must not have any items.
941 //
942 //============================================================================
943
ObtainInventory(AActor * other)944 void AActor::ObtainInventory (AActor *other)
945 {
946 assert (Inventory == NULL);
947
948 Inventory = other->Inventory;
949 InventoryID = other->InventoryID;
950 other->Inventory = NULL;
951 other->InventoryID = 0;
952
953 if (other->IsKindOf(RUNTIME_CLASS(APlayerPawn)) && this->IsKindOf(RUNTIME_CLASS(APlayerPawn)))
954 {
955 APlayerPawn *you = static_cast<APlayerPawn *>(other);
956 APlayerPawn *me = static_cast<APlayerPawn *>(this);
957 me->InvFirst = you->InvFirst;
958 me->InvSel = you->InvSel;
959 you->InvFirst = NULL;
960 you->InvSel = NULL;
961 }
962
963 AInventory *item = Inventory;
964 while (item != NULL)
965 {
966 item->Owner = this;
967 item = item->Inventory;
968 }
969 }
970
971 //============================================================================
972 //
973 // AActor :: CheckLocalView
974 //
975 // Returns true if this actor is local for the player. Here, local means the
976 // player is either looking out this actor's eyes, or this actor is the player
977 // and the player is looking out the eyes of something non-"sentient."
978 //
979 //============================================================================
980
CheckLocalView(int playernum) const981 bool AActor::CheckLocalView (int playernum) const
982 {
983 if (players[playernum].camera == this)
984 {
985 return true;
986 }
987 if (players[playernum].mo != this || players[playernum].camera == NULL)
988 {
989 return false;
990 }
991 if (players[playernum].camera->player == NULL &&
992 !(players[playernum].camera->flags3 & MF3_ISMONSTER))
993 {
994 return true;
995 }
996 return false;
997 }
998
999 //============================================================================
1000 //
1001 // AActor :: IsVisibleToPlayer
1002 //
1003 // Returns true if this actor should be seen by the console player.
1004 //
1005 //============================================================================
1006
IsVisibleToPlayer() const1007 bool AActor::IsVisibleToPlayer() const
1008 {
1009 // [BB] Safety check. This should never be NULL. Nevertheless, we return true to leave the default ZDoom behavior unaltered.
1010 if ( players[consoleplayer].camera == NULL )
1011 return true;
1012
1013 if (VisibleToTeam != 0 && teamplay &&
1014 (signed)(VisibleToTeam-1) != players[consoleplayer].userinfo.GetTeam())
1015 return false;
1016
1017 const player_t* pPlayer = players[consoleplayer].camera->player;
1018
1019 if(pPlayer && pPlayer->mo && GetClass()->ActorInfo->VisibleToPlayerClass.Size() > 0)
1020 {
1021 bool visible = false;
1022 for(unsigned int i = 0;i < GetClass()->ActorInfo->VisibleToPlayerClass.Size();++i)
1023 {
1024 const PClass *cls = GetClass()->ActorInfo->VisibleToPlayerClass[i];
1025 if(cls && pPlayer->mo->GetClass()->IsDescendantOf(cls))
1026 {
1027 visible = true;
1028 break;
1029 }
1030 }
1031 if(!visible)
1032 return false;
1033 }
1034
1035 // [BB] Passed all checks.
1036 return true;
1037 }
1038
1039 //============================================================================
1040 //
1041 // AActor :: ConversationAnimation
1042 //
1043 // Plays a conversation-related animation:
1044 // 0 = greeting
1045 // 1 = "yes"
1046 // 2 = "no"
1047 //
1048 //============================================================================
1049
ConversationAnimation(int animnum)1050 void AActor::ConversationAnimation (int animnum)
1051 {
1052 FState * state = NULL;
1053 switch (animnum)
1054 {
1055 case 0:
1056 state = FindState(NAME_Greetings);
1057 break;
1058 case 1:
1059 state = FindState(NAME_Yes);
1060 break;
1061 case 2:
1062 state = FindState(NAME_No);
1063 break;
1064 }
1065 if (state != NULL) SetState(state);
1066 }
1067
1068 //============================================================================
1069 //
1070 // AActor :: Touch
1071 //
1072 // Something just touched this actor. Normally used only for inventory items,
1073 // but some Strife monsters also use it.
1074 //
1075 //============================================================================
1076
Touch(AActor * toucher)1077 void AActor::Touch (AActor *toucher)
1078 {
1079 }
1080
1081 //============================================================================
1082 //
1083 // AActor :: Grind
1084 //
1085 // Handles the an actor being crushed by a door, crusher or polyobject.
1086 // Originally part of P_DoCrunch(), it has been made into its own actor
1087 // function so that it could be called from a polyobject without hassle.
1088 // Bool items is true if it should destroy() dropped items, false otherwise.
1089 //============================================================================
1090
Grind(bool items)1091 bool AActor::Grind(bool items)
1092 {
1093 // crunch bodies to giblets
1094 if ((flags & MF_CORPSE) && !(flags3 & MF3_DONTGIB) && (health <= 0))
1095 {
1096 FState * state = FindState(NAME_Crush);
1097
1098 // In Heretic and Chex Quest we don't change the actor's sprite, just its size.
1099 if (state == NULL && gameinfo.dontcrunchcorpses)
1100 {
1101 flags &= ~MF_SOLID;
1102 flags3 |= MF3_DONTGIB;
1103 height = radius = 0;
1104 return false;
1105 }
1106
1107 bool isgeneric = false;
1108 // ZDoom behavior differs from standard as crushed corpses cannot be raised.
1109 // The reason for the change was originally because of a problem with players,
1110 // see rh_log entry for February 21, 1999. Don't know if it is still relevant.
1111 if (state == NULL // Only use the default crushed state if:
1112 && !(flags & MF_NOBLOOD) // 1. the monster bleeeds,
1113 && (i_compatflags & COMPATF_CORPSEGIBS) // 2. the compat setting is on,
1114 && player == NULL) // 3. and the thing isn't a player.
1115 {
1116 isgeneric = true;
1117 state = FindState(NAME_GenericCrush);
1118 if (state != NULL && (sprites[state->sprite].numframes <= 0))
1119 state = NULL; // If one of these tests fails, do not use that state.
1120 }
1121 if (state != NULL && !(flags & MF_ICECORPSE))
1122 {
1123 if (this->flags4 & MF4_BOSSDEATH)
1124 {
1125 CALL_ACTION(A_BossDeath, this);
1126 }
1127 flags &= ~MF_SOLID;
1128 flags3 |= MF3_DONTGIB;
1129 height = radius = 0;
1130 SetState (state);
1131 if (isgeneric) // Not a custom crush state, so colorize it appropriately.
1132 {
1133 S_Sound (this, CHAN_BODY, "misc/fallingsplat", 1, ATTN_IDLE);
1134 PalEntry bloodcolor = GetBloodColor();
1135 if (bloodcolor!=0) Translation = TRANSLATION(TRANSLATION_Blood, bloodcolor.a);
1136 }
1137 return false;
1138 }
1139 if (!(flags & MF_NOBLOOD))
1140 {
1141 if (this->flags4 & MF4_BOSSDEATH)
1142 {
1143 CALL_ACTION(A_BossDeath, this);
1144 }
1145
1146 const PClass *i = PClass::FindClass("RealGibs");
1147
1148 if (i != NULL)
1149 {
1150 i = i->GetReplacement();
1151
1152 const AActor *defaults = GetDefaultByType (i);
1153 if (defaults->SpawnState == NULL ||
1154 sprites[defaults->SpawnState->sprite].numframes == 0)
1155 {
1156 i = NULL;
1157 }
1158 }
1159 if (i == NULL)
1160 {
1161 // if there's no gib sprite don't crunch it.
1162 flags &= ~MF_SOLID;
1163 flags3 |= MF3_DONTGIB;
1164 height = radius = 0;
1165 return false;
1166 }
1167
1168 AActor *gib = Spawn (i, Pos(), ALLOW_REPLACE);
1169 if (gib != NULL)
1170 {
1171 gib->RenderStyle = RenderStyle;
1172 gib->alpha = alpha;
1173 gib->height = 0;
1174 gib->radius = 0;
1175
1176 PalEntry bloodcolor = GetBloodColor();
1177 if (bloodcolor != 0)
1178 gib->Translation = TRANSLATION(TRANSLATION_Blood, bloodcolor.a);
1179 }
1180 S_Sound (this, CHAN_BODY, "misc/fallingsplat", 1, ATTN_IDLE);
1181 }
1182 if (flags & MF_ICECORPSE)
1183 {
1184 tics = 1;
1185 velx = vely = velz = 0;
1186 }
1187 else if (player)
1188 {
1189 flags |= MF_NOCLIP;
1190 flags3 |= MF3_DONTGIB;
1191 renderflags |= RF_INVISIBLE;
1192 }
1193 else
1194 {
1195 Destroy ();
1196 }
1197 return false; // keep checking
1198 }
1199
1200 // killough 11/98: kill touchy things immediately
1201 if (flags6 & MF6_TOUCHY && (flags6 & MF6_ARMED || IsSentient()))
1202 {
1203 flags6 &= ~MF6_ARMED; // Disarm
1204 P_DamageMobj (this, NULL, NULL, health, NAME_Crush, DMG_FORCED); // kill object
1205 return true; // keep checking
1206 }
1207
1208 if (!(flags & MF_SOLID) || (flags & MF_NOCLIP))
1209 {
1210 return false;
1211 }
1212
1213 if (!(flags & MF_SHOOTABLE))
1214 {
1215 return false; // assume it is bloody gibs or something
1216 }
1217 return true;
1218 }
1219
1220 //============================================================================
1221 //
1222 // AActor :: Massacre
1223 //
1224 // Called by the massacre cheat to kill monsters. Returns true if the monster
1225 // was killed and false if it was already dead.
1226 //============================================================================
1227
Massacre()1228 bool AActor::Massacre ()
1229 {
1230 int prevhealth;
1231
1232 if (health > 0)
1233 {
1234 flags |= MF_SHOOTABLE;
1235 flags2 &= ~(MF2_DORMANT|MF2_INVULNERABLE);
1236 do
1237 {
1238 prevhealth = health;
1239 P_DamageMobj (this, NULL, NULL, TELEFRAG_DAMAGE, NAME_Massacre);
1240 }
1241 while (health != prevhealth && health > 0); //abort if the actor wasn't hurt.
1242 return health <= 0;
1243 }
1244 return false;
1245 }
1246
1247 //----------------------------------------------------------------------------
1248 //
1249 // PROC P_ExplodeMissile
1250 //
1251 //----------------------------------------------------------------------------
1252
P_ExplodeMissile(AActor * mo,line_t * line,AActor * target)1253 void P_ExplodeMissile (AActor *mo, line_t *line, AActor *target)
1254 {
1255 if (mo->flags3 & MF3_EXPLOCOUNT)
1256 {
1257 if (++mo->special2 < mo->special1)
1258 {
1259 return;
1260 }
1261 }
1262 mo->velx = mo->vely = mo->velz = 0;
1263 mo->effects = 0; // [RH]
1264 mo->flags &= ~MF_SHOOTABLE;
1265
1266 FState *nextstate=NULL;
1267
1268 if (target != NULL && ((target->flags & (MF_SHOOTABLE|MF_CORPSE)) || (target->flags6 & MF6_KILLED)) )
1269 {
1270 if (mo->flags7 & MF7_HITTARGET) mo->target = target;
1271 if (mo->flags7 & MF7_HITMASTER) mo->master = target;
1272 if (mo->flags7 & MF7_HITTRACER) mo->tracer = target;
1273 if (target->flags & MF_NOBLOOD) nextstate = mo->FindState(NAME_Crash);
1274 if (nextstate == NULL) nextstate = mo->FindState(NAME_Death, NAME_Extreme);
1275 }
1276 if (nextstate == NULL) nextstate = mo->FindState(NAME_Death);
1277
1278 if (line != NULL && line->special == Line_Horizon && !(mo->flags3 & MF3_SKYEXPLODE))
1279 {
1280 // [RH] Don't explode missiles on horizon lines.
1281 mo->Destroy ();
1282 return;
1283 }
1284
1285 if (line != NULL && cl_missiledecals)
1286 {
1287 fixedvec3 pos = mo->PosRelative(line);
1288 int side = P_PointOnLineSidePrecise (pos.x, pos.y, line);
1289 if (line->sidedef[side] == NULL)
1290 side ^= 1;
1291 if (line->sidedef[side] != NULL)
1292 {
1293 FDecalBase *base = mo->DecalGenerator;
1294 if (base != NULL)
1295 {
1296 // Find the nearest point on the line, and stick a decal there
1297 fixed_t x, y, z;
1298 SQWORD num, den;
1299
1300 den = (SQWORD)line->dx*line->dx + (SQWORD)line->dy*line->dy;
1301 if (den != 0)
1302 {
1303 SDWORD frac;
1304
1305 num = (SQWORD)(pos.x-line->v1->x)*line->dx+(SQWORD)(pos.y-line->v1->y)*line->dy;
1306 if (num <= 0)
1307 {
1308 frac = 0;
1309 }
1310 else if (num >= den)
1311 {
1312 frac = 1<<30;
1313 }
1314 else
1315 {
1316 frac = (SDWORD)(num / (den>>30));
1317 }
1318
1319 x = line->v1->x + MulScale30 (line->dx, frac);
1320 y = line->v1->y + MulScale30 (line->dy, frac);
1321 z = pos.z;
1322
1323 F3DFloor * ffloor=NULL;
1324 if (line->sidedef[side^1] != NULL)
1325 {
1326 sector_t * backsector = line->sidedef[side^1]->sector;
1327 extsector_t::xfloor &xf = backsector->e->XFloor;
1328 // find a 3D-floor to stick to
1329 for(unsigned int i=0;i<xf.ffloors.Size();i++)
1330 {
1331 F3DFloor * rover=xf.ffloors[i];
1332
1333 if ((rover->flags&(FF_EXISTS|FF_SOLID|FF_RENDERSIDES))==(FF_EXISTS|FF_SOLID|FF_RENDERSIDES))
1334 {
1335 if (z<=rover->top.plane->ZatPoint(x, y) && z>=rover->bottom.plane->ZatPoint( x, y))
1336 {
1337 ffloor=rover;
1338 break;
1339 }
1340 }
1341 }
1342 }
1343
1344 DImpactDecal::StaticCreate (base->GetDecal (),
1345 x, y, z, line->sidedef[side], ffloor);
1346 }
1347 }
1348 }
1349 }
1350
1351 // play the sound before changing the state, so that AActor::Destroy can call S_RelinkSounds on it and the death state can override it.
1352 if (mo->DeathSound)
1353 {
1354 S_Sound (mo, CHAN_VOICE, mo->DeathSound, 1,
1355 (mo->flags3 & MF3_FULLVOLDEATH) ? ATTN_NONE : ATTN_NORM);
1356 }
1357
1358 mo->SetState (nextstate);
1359 if (!(mo->ObjectFlags & OF_EuthanizeMe))
1360 {
1361 // The rest only applies if the missile actor still exists.
1362 // [RH] Change render style of exploding rockets
1363 if (mo->flags5 & MF5_DEHEXPLOSION)
1364 {
1365 if (deh.ExplosionStyle == 255)
1366 {
1367 if (addrocketexplosion)
1368 {
1369 mo->RenderStyle = STYLE_Add;
1370 mo->alpha = FRACUNIT;
1371 }
1372 else
1373 {
1374 mo->RenderStyle = STYLE_Translucent;
1375 mo->alpha = FRACUNIT*2/3;
1376 }
1377 }
1378 else
1379 {
1380 mo->RenderStyle = ERenderStyle(deh.ExplosionStyle);
1381 mo->alpha = deh.ExplosionAlpha;
1382 }
1383 }
1384
1385 if (mo->flags4 & MF4_RANDOMIZE)
1386 {
1387 mo->tics -= (pr_explodemissile() & 3) * TICRATE / 35;
1388 if (mo->tics < 1)
1389 mo->tics = 1;
1390 }
1391
1392 mo->flags &= ~MF_MISSILE;
1393
1394 }
1395 }
1396
1397
PlayBounceSound(bool onfloor)1398 void AActor::PlayBounceSound(bool onfloor)
1399 {
1400 if (!onfloor && (BounceFlags & BOUNCE_NoWallSound))
1401 {
1402 return;
1403 }
1404
1405 if (!(BounceFlags & BOUNCE_Quiet))
1406 {
1407 if (BounceFlags & BOUNCE_UseSeeSound)
1408 {
1409 S_Sound (this, CHAN_VOICE, SeeSound, 1, ATTN_IDLE);
1410 }
1411 else if (onfloor || WallBounceSound <= 0)
1412 {
1413 S_Sound (this, CHAN_VOICE, BounceSound, 1, ATTN_IDLE);
1414 }
1415 else
1416 {
1417 S_Sound (this, CHAN_VOICE, WallBounceSound, 1, ATTN_IDLE);
1418 }
1419 }
1420 }
1421
1422 //----------------------------------------------------------------------------
1423 //
1424 // PROC P_FloorBounceMissile
1425 //
1426 // Returns true if the missile was destroyed
1427 //----------------------------------------------------------------------------
1428
FloorBounceMissile(secplane_t & plane)1429 bool AActor::FloorBounceMissile (secplane_t &plane)
1430 {
1431 if (Z() <= floorz && P_HitFloor (this))
1432 {
1433 // Landed in some sort of liquid
1434 if (BounceFlags & BOUNCE_ExplodeOnWater)
1435 {
1436 if (flags & MF_MISSILE)
1437 P_ExplodeMissile(this, NULL, NULL);
1438 else
1439 Die(NULL, NULL);
1440 return true;
1441 }
1442 if (!(BounceFlags & BOUNCE_CanBounceWater))
1443 {
1444 Destroy ();
1445 return true;
1446 }
1447 }
1448
1449 if (plane.c < 0)
1450 { // on ceiling
1451 if (!(BounceFlags & BOUNCE_Ceilings))
1452 return true;
1453 }
1454 else
1455 { // on floor
1456 if (!(BounceFlags & BOUNCE_Floors))
1457 return true;
1458 }
1459
1460 // The amount of bounces is limited
1461 if (bouncecount>0 && --bouncecount==0)
1462 {
1463 if (flags & MF_MISSILE)
1464 P_ExplodeMissile(this, NULL, NULL);
1465 else
1466 Die(NULL, NULL);
1467 return true;
1468 }
1469
1470 fixed_t dot = TMulScale16 (velx, plane.a, vely, plane.b, velz, plane.c);
1471
1472 if (BounceFlags & (BOUNCE_HereticType | BOUNCE_MBF))
1473 {
1474 velx -= MulScale15 (plane.a, dot);
1475 vely -= MulScale15 (plane.b, dot);
1476 velz -= MulScale15 (plane.c, dot);
1477 angle = R_PointToAngle2 (0, 0, velx, vely);
1478 if (!(BounceFlags & BOUNCE_MBF)) // Heretic projectiles die, MBF projectiles don't.
1479 {
1480 flags |= MF_INBOUNCE;
1481 SetState (FindState(NAME_Death));
1482 flags &= ~MF_INBOUNCE;
1483 return false;
1484 }
1485 else velz = FixedMul(velz, bouncefactor);
1486 }
1487 else // Don't run through this for MBF-style bounces
1488 {
1489 // The reflected velocity keeps only about 70% of its original speed
1490 velx = FixedMul (velx - MulScale15 (plane.a, dot), bouncefactor);
1491 vely = FixedMul (vely - MulScale15 (plane.b, dot), bouncefactor);
1492 velz = FixedMul (velz - MulScale15 (plane.c, dot), bouncefactor);
1493 angle = R_PointToAngle2 (0, 0, velx, vely);
1494 }
1495
1496 PlayBounceSound(true);
1497
1498 // Set bounce state
1499 if (BounceFlags & BOUNCE_UseBounceState)
1500 {
1501 FName names[2];
1502 FState *bouncestate;
1503
1504 names[0] = NAME_Bounce;
1505 names[1] = plane.c < 0 ? NAME_Ceiling : NAME_Floor;
1506 bouncestate = FindState(2, names);
1507 if (bouncestate != NULL)
1508 {
1509 SetState(bouncestate);
1510 }
1511 }
1512
1513 if (BounceFlags & BOUNCE_MBF) // Bring it to rest below a certain speed
1514 {
1515 if (abs(velz) < (fixed_t)(Mass * GetGravity() / 64))
1516 velz = 0;
1517 }
1518 else if (BounceFlags & (BOUNCE_AutoOff|BOUNCE_AutoOffFloorOnly))
1519 {
1520 if (plane.c > 0 || (BounceFlags & BOUNCE_AutoOff))
1521 {
1522 // AutoOff only works when bouncing off a floor, not a ceiling (or in compatibility mode.)
1523 if (!(flags & MF_NOGRAVITY) && (velz < 3*FRACUNIT))
1524 BounceFlags &= ~BOUNCE_TypeMask;
1525 }
1526 }
1527 return false;
1528 }
1529
1530 //----------------------------------------------------------------------------
1531 //
1532 // PROC P_ThrustMobj
1533 //
1534 //----------------------------------------------------------------------------
1535
P_ThrustMobj(AActor * mo,angle_t angle,fixed_t move)1536 void P_ThrustMobj (AActor *mo, angle_t angle, fixed_t move)
1537 {
1538 angle >>= ANGLETOFINESHIFT;
1539 mo->velx += FixedMul (move, finecosine[angle]);
1540 mo->vely += FixedMul (move, finesine[angle]);
1541 }
1542
1543 //----------------------------------------------------------------------------
1544 //
1545 // FUNC P_FaceMobj
1546 //
1547 // Returns 1 if 'source' needs to turn clockwise, or 0 if 'source' needs
1548 // to turn counter clockwise. 'delta' is set to the amount 'source'
1549 // needs to turn.
1550 //
1551 //----------------------------------------------------------------------------
1552
P_FaceMobj(AActor * source,AActor * target,angle_t * delta)1553 int P_FaceMobj (AActor *source, AActor *target, angle_t *delta)
1554 {
1555 angle_t diff;
1556 angle_t angle1;
1557 angle_t angle2;
1558
1559 angle1 = source->angle;
1560 angle2 = source->AngleTo(target);
1561 if (angle2 > angle1)
1562 {
1563 diff = angle2 - angle1;
1564 if (diff > ANGLE_180)
1565 {
1566 *delta = ANGLE_MAX - diff;
1567 return 0;
1568 }
1569 else
1570 {
1571 *delta = diff;
1572 return 1;
1573 }
1574 }
1575 else
1576 {
1577 diff = angle1 - angle2;
1578 if (diff > ANGLE_180)
1579 {
1580 *delta = ANGLE_MAX - diff;
1581 return 1;
1582 }
1583 else
1584 {
1585 *delta = diff;
1586 return 0;
1587 }
1588 }
1589 }
1590
1591 //----------------------------------------------------------------------------
1592 //
1593 // CanSeek
1594 //
1595 // Checks if a seeker missile can home in on its target
1596 //
1597 //----------------------------------------------------------------------------
1598
CanSeek(AActor * target) const1599 bool AActor::CanSeek(AActor *target) const
1600 {
1601 if (target->flags5 & MF5_CANTSEEK) return false;
1602 if ((flags2 & MF2_DONTSEEKINVISIBLE) &&
1603 ((target->flags & MF_SHADOW) ||
1604 (target->renderflags & RF_INVISIBLE) ||
1605 !target->RenderStyle.IsVisible(target->alpha)
1606 )
1607 ) return false;
1608 return true;
1609 }
1610
1611 //----------------------------------------------------------------------------
1612 //
1613 // FUNC P_SeekerMissile
1614 //
1615 // The missile's tracer field must be the target. Returns true if
1616 // target was tracked, false if not.
1617 //
1618 //----------------------------------------------------------------------------
1619
P_SeekerMissile(AActor * actor,angle_t thresh,angle_t turnMax,bool precise,bool usecurspeed)1620 bool P_SeekerMissile (AActor *actor, angle_t thresh, angle_t turnMax, bool precise, bool usecurspeed)
1621 {
1622 int dir;
1623 int dist;
1624 angle_t delta;
1625 angle_t angle;
1626 AActor *target;
1627 fixed_t speed;
1628
1629 speed = !usecurspeed ? actor->Speed : xs_CRoundToInt(TVector3<double>(actor->velx, actor->vely, actor->velz).Length());
1630 target = actor->tracer;
1631 if (target == NULL || !actor->CanSeek(target))
1632 {
1633 return false;
1634 }
1635 if (!(target->flags & MF_SHOOTABLE))
1636 { // Target died
1637 actor->tracer = NULL;
1638 return false;
1639 }
1640 if (speed == 0)
1641 { // Technically, we're not seeking since our speed is 0, but the target *is* seekable.
1642 return true;
1643 }
1644 dir = P_FaceMobj (actor, target, &delta);
1645 if (delta > thresh)
1646 {
1647 delta >>= 1;
1648 if (delta > turnMax)
1649 {
1650 delta = turnMax;
1651 }
1652 }
1653 if (dir)
1654 { // Turn clockwise
1655 actor->angle += delta;
1656 }
1657 else
1658 { // Turn counter clockwise
1659 actor->angle -= delta;
1660 }
1661 angle = actor->angle>>ANGLETOFINESHIFT;
1662
1663 if (!precise)
1664 {
1665 actor->velx = FixedMul (speed, finecosine[angle]);
1666 actor->vely = FixedMul (speed, finesine[angle]);
1667
1668 if (!(actor->flags3 & (MF3_FLOORHUGGER|MF3_CEILINGHUGGER)))
1669 {
1670 if (actor->Top() < target->Z() ||
1671 target->Top() < actor->Z())
1672 { // Need to seek vertically
1673 dist = actor->AproxDistance (target) / speed;
1674 if (dist < 1)
1675 {
1676 dist = 1;
1677 }
1678 actor->velz = ((target->Z() + target->height / 2) - (actor->Z() + actor->height / 2)) / dist;
1679 }
1680 }
1681 }
1682 else
1683 {
1684 angle_t pitch = 0;
1685 if (!(actor->flags3 & (MF3_FLOORHUGGER|MF3_CEILINGHUGGER)))
1686 { // Need to seek vertically
1687 fixedvec2 vec = actor->Vec2To(target);
1688 double dist = MAX(1.0, TVector2<double>(vec.x, vec.y).Length());
1689 // Aim at a player's eyes and at the middle of the actor for everything else.
1690 fixed_t aimheight = target->height/2;
1691 if (target->IsKindOf(RUNTIME_CLASS(APlayerPawn)))
1692 {
1693 aimheight = static_cast<APlayerPawn *>(target)->ViewHeight;
1694 }
1695 pitch = R_PointToAngle2(0, actor->Z() + actor->height/2, xs_CRoundToInt(dist), target->Z() + aimheight);
1696 pitch >>= ANGLETOFINESHIFT;
1697 }
1698
1699 fixed_t xyscale = FixedMul(speed, finecosine[pitch]);
1700 actor->velz = FixedMul(speed, finesine[pitch]);
1701 actor->velx = FixedMul(xyscale, finecosine[angle]);
1702 actor->vely = FixedMul(xyscale, finesine[angle]);
1703 }
1704
1705 return true;
1706 }
1707
1708
1709 //
1710 // P_XYMovement
1711 //
1712 // Returns the actor's old floorz.
1713 //
1714 #define STOPSPEED 0x1000
1715 #define CARRYSTOPSPEED (STOPSPEED*32/3)
1716
P_XYMovement(AActor * mo,fixed_t scrollx,fixed_t scrolly)1717 fixed_t P_XYMovement (AActor *mo, fixed_t scrollx, fixed_t scrolly)
1718 {
1719 static int pushtime = 0;
1720 bool bForceSlide = scrollx || scrolly;
1721 angle_t angle;
1722 fixed_t ptryx, ptryy;
1723 player_t *player;
1724 fixed_t xmove, ymove;
1725 const secplane_t * walkplane;
1726 static const int windTab[3] = {2048*5, 2048*10, 2048*25};
1727 int steps, step, totalsteps;
1728 fixed_t startx, starty;
1729 fixed_t oldfloorz = mo->floorz;
1730 fixed_t oldz = mo->Z();
1731
1732 fixed_t maxmove = (mo->waterlevel < 1) || (mo->flags & MF_MISSILE) ||
1733 (mo->player && mo->player->crouchoffset<-10*FRACUNIT) ? MAXMOVE : MAXMOVE/4;
1734
1735 if (mo->flags2 & MF2_WINDTHRUST && mo->waterlevel < 2 && !(mo->flags & MF_NOCLIP))
1736 {
1737 int special = mo->Sector->special;
1738 switch (special)
1739 {
1740 case 40: case 41: case 42: // Wind_East
1741 P_ThrustMobj (mo, 0, windTab[special-40]);
1742 break;
1743 case 43: case 44: case 45: // Wind_North
1744 P_ThrustMobj (mo, ANG90, windTab[special-43]);
1745 break;
1746 case 46: case 47: case 48: // Wind_South
1747 P_ThrustMobj (mo, ANG270, windTab[special-46]);
1748 break;
1749 case 49: case 50: case 51: // Wind_West
1750 P_ThrustMobj (mo, ANG180, windTab[special-49]);
1751 break;
1752 }
1753 }
1754
1755 // [RH] No need to clamp these now. However, wall running needs it so
1756 // that large thrusts can't propel an actor through a wall, because wall
1757 // running depends on the player's original movement continuing even after
1758 // it gets blocked.
1759 if ((mo->player != NULL && (i_compatflags & COMPATF_WALLRUN)) || (mo->waterlevel >= 1) ||
1760 (mo->player != NULL && mo->player->crouchfactor < FRACUNIT*3/4))
1761 {
1762 // preserve the direction instead of clamping x and y independently.
1763 xmove = clamp (mo->velx, -maxmove, maxmove);
1764 ymove = clamp (mo->vely, -maxmove, maxmove);
1765
1766 fixed_t xfac = FixedDiv(xmove, mo->velx);
1767 fixed_t yfac = FixedDiv(ymove, mo->vely);
1768 fixed_t fac = MIN(xfac, yfac);
1769
1770 xmove = mo->velx = FixedMul(mo->velx, fac);
1771 ymove = mo->vely = FixedMul(mo->vely, fac);
1772 }
1773 else
1774 {
1775 xmove = mo->velx;
1776 ymove = mo->vely;
1777 }
1778 // [RH] Carrying sectors didn't work with low speeds in BOOM. This is
1779 // because BOOM relied on the speed being fast enough to accumulate
1780 // despite friction. If the speed is too low, then its movement will get
1781 // cancelled, and it won't accumulate to the desired speed.
1782 mo->flags4 &= ~MF4_SCROLLMOVE;
1783 if (abs(scrollx) > CARRYSTOPSPEED)
1784 {
1785 scrollx = FixedMul (scrollx, CARRYFACTOR);
1786 mo->velx += scrollx;
1787 mo->flags4 |= MF4_SCROLLMOVE;
1788 }
1789 if (abs(scrolly) > CARRYSTOPSPEED)
1790 {
1791 scrolly = FixedMul (scrolly, CARRYFACTOR);
1792 mo->vely += scrolly;
1793 mo->flags4 |= MF4_SCROLLMOVE;
1794 }
1795 xmove += scrollx;
1796 ymove += scrolly;
1797
1798 if ((xmove | ymove) == 0)
1799 {
1800 if (mo->flags & MF_SKULLFLY)
1801 {
1802 // the skull slammed into something
1803 mo->flags &= ~MF_SKULLFLY;
1804 mo->velx = mo->vely = mo->velz = 0;
1805 if (!(mo->flags2 & MF2_DORMANT))
1806 {
1807 if (mo->SeeState != NULL) mo->SetState (mo->SeeState);
1808 else mo->SetIdle();
1809 }
1810 else
1811 {
1812 mo->SetIdle();
1813 mo->tics = -1;
1814 }
1815 }
1816 return oldfloorz;
1817 }
1818
1819 player = mo->player;
1820
1821 // [RH] Adjust player movement on sloped floors
1822 fixed_t startxmove = xmove;
1823 fixed_t startymove = ymove;
1824 walkplane = P_CheckSlopeWalk (mo, xmove, ymove);
1825
1826 // [RH] Take smaller steps when moving faster than the object's size permits.
1827 // Moving as fast as the object's "diameter" is bad because it could skip
1828 // some lines because the actor could land such that it is just touching the
1829 // line. For Doom to detect that the line is there, it needs to actually cut
1830 // through the actor.
1831
1832 {
1833 maxmove = mo->radius - FRACUNIT;
1834
1835 if (maxmove <= 0)
1836 { // gibs can have radius 0, so don't divide by zero below!
1837 maxmove = MAXMOVE;
1838 }
1839
1840 const fixed_t xspeed = abs (xmove);
1841 const fixed_t yspeed = abs (ymove);
1842
1843 steps = 1;
1844
1845 if (xspeed > yspeed)
1846 {
1847 if (xspeed > maxmove)
1848 {
1849 steps = 1 + xspeed / maxmove;
1850 }
1851 }
1852 else
1853 {
1854 if (yspeed > maxmove)
1855 {
1856 steps = 1 + yspeed / maxmove;
1857 }
1858 }
1859 }
1860
1861 // P_SlideMove needs to know the step size before P_CheckSlopeWalk
1862 // because it also calls P_CheckSlopeWalk on its clipped steps.
1863 fixed_t onestepx = startxmove / steps;
1864 fixed_t onestepy = startymove / steps;
1865
1866 startx = mo->X();
1867 starty = mo->Y();
1868 step = 1;
1869 totalsteps = steps;
1870
1871 // [RH] Instead of doing ripping damage each step, do it each tic.
1872 // This makes it compatible with Heretic and Hexen, which only did
1873 // one step for their missiles with ripping damage (excluding those
1874 // that don't use P_XYMovement). It's also more intuitive since it
1875 // makes the damage done dependant on the amount of time the projectile
1876 // spends inside a target rather than on the projectile's size. The
1877 // last actor ripped through is recorded so that if the projectile
1878 // passes through more than one actor this tic, each one takes damage
1879 // and not just the first one.
1880 pushtime++;
1881
1882 FCheckPosition tm(!!(mo->flags2 & MF2_RIP));
1883
1884
1885 do
1886 {
1887 if (i_compatflags & COMPATF_WALLRUN) pushtime++;
1888 tm.PushTime = pushtime;
1889
1890 ptryx = startx + Scale (xmove, step, steps);
1891 ptryy = starty + Scale (ymove, step, steps);
1892
1893 /* if (mo->player)
1894 Printf ("%d,%d/%d: %d %d %d %d %d %d %d\n", level.time, step, steps, startxmove, Scale(xmove,step,steps), startymove, Scale(ymove,step,steps), mo->x, mo->y, mo->z);
1895 */
1896 // [RH] If walking on a slope, stay on the slope
1897 // killough 3/15/98: Allow objects to drop off
1898 fixed_t startvelx = mo->velx, startvely = mo->vely;
1899
1900 if (!P_TryMove (mo, ptryx, ptryy, true, walkplane, tm))
1901 {
1902 // blocked move
1903 AActor *BlockingMobj = mo->BlockingMobj;
1904 line_t *BlockingLine = mo->BlockingLine;
1905
1906 if (!(mo->flags & MF_MISSILE) && (mo->BounceFlags & BOUNCE_MBF)
1907 && (BlockingMobj != NULL ? P_BounceActor(mo, BlockingMobj, false) : P_BounceWall(mo)))
1908 {
1909 // Do nothing, relevant actions already done in the condition.
1910 // This allows to avoid setting velocities to 0 in the final else of this series.
1911 }
1912 else if ((mo->flags2 & (MF2_SLIDE|MF2_BLASTED) || bForceSlide) && !(mo->flags&MF_MISSILE))
1913 { // try to slide along it
1914 if (BlockingMobj == NULL)
1915 { // slide against wall
1916 if (BlockingLine != NULL &&
1917 mo->player && mo->waterlevel && mo->waterlevel < 3 &&
1918 (mo->player->cmd.ucmd.forwardmove | mo->player->cmd.ucmd.sidemove) &&
1919 mo->BlockingLine->sidedef[1] != NULL)
1920 {
1921 mo->velz = WATER_JUMP_SPEED;
1922 }
1923 // If the blocked move executed any push specials that changed the
1924 // actor's velocity, do not attempt to slide.
1925 if (mo->velx == startvelx && mo->vely == startvely)
1926 {
1927 if (player && (i_compatflags & COMPATF_WALLRUN))
1928 {
1929 // [RH] Here is the key to wall running: The move is clipped using its full speed.
1930 // If the move is done a second time (because it was too fast for one move), it
1931 // is still clipped against the wall at its full speed, so you effectively
1932 // execute two moves in one tic.
1933 P_SlideMove (mo, mo->velx, mo->vely, 1);
1934 }
1935 else
1936 {
1937 P_SlideMove (mo, onestepx, onestepy, totalsteps);
1938 }
1939 if ((mo->velx | mo->vely) == 0)
1940 {
1941 steps = 0;
1942 }
1943 else
1944 {
1945 if (!player || !(i_compatflags & COMPATF_WALLRUN))
1946 {
1947 xmove = mo->velx;
1948 ymove = mo->vely;
1949 onestepx = xmove / steps;
1950 onestepy = ymove / steps;
1951 P_CheckSlopeWalk (mo, xmove, ymove);
1952 }
1953 startx = mo->X() - Scale (xmove, step, steps);
1954 starty = mo->Y() - Scale (ymove, step, steps);
1955 }
1956 }
1957 else
1958 {
1959 steps = 0;
1960 }
1961 }
1962 else
1963 { // slide against another actor
1964 fixed_t tx, ty;
1965 tx = 0, ty = onestepy;
1966 walkplane = P_CheckSlopeWalk (mo, tx, ty);
1967 if (P_TryMove (mo, mo->X() + tx, mo->Y() + ty, true, walkplane, tm))
1968 {
1969 mo->velx = 0;
1970 }
1971 else
1972 {
1973 tx = onestepx, ty = 0;
1974 walkplane = P_CheckSlopeWalk (mo, tx, ty);
1975 if (P_TryMove (mo, mo->X() + tx, mo->Y() + ty, true, walkplane, tm))
1976 {
1977 mo->vely = 0;
1978 }
1979 else
1980 {
1981 mo->velx = mo->vely = 0;
1982 }
1983 }
1984 if (player && player->mo == mo)
1985 {
1986 if (mo->velx == 0)
1987 player->velx = 0;
1988 if (mo->vely == 0)
1989 player->vely = 0;
1990 }
1991 steps = 0;
1992 }
1993 }
1994 else if (mo->flags & MF_MISSILE)
1995 {
1996 steps = 0;
1997 if (BlockingMobj)
1998 {
1999 if (mo->BounceFlags & BOUNCE_Actors)
2000 {
2001 // Bounce test and code moved to P_BounceActor
2002 if (!P_BounceActor(mo, BlockingMobj, false))
2003 { // Struck a player/creature
2004 P_ExplodeMissile (mo, NULL, BlockingMobj);
2005 }
2006 return oldfloorz;
2007 }
2008 }
2009 else
2010 {
2011 // Struck a wall
2012 if (P_BounceWall (mo))
2013 {
2014 mo->PlayBounceSound(false);
2015 return oldfloorz;
2016 }
2017 }
2018 if (BlockingMobj && (BlockingMobj->flags2 & MF2_REFLECTIVE))
2019 {
2020 bool seeker = (mo->flags2 & MF2_SEEKERMISSILE) ? true : false;
2021 // Don't change the angle if there's THRUREFLECT on the monster.
2022 if (!(BlockingMobj->flags7 & MF7_THRUREFLECT))
2023 {
2024 angle = BlockingMobj->AngleTo(mo);
2025 bool dontReflect = (mo->AdjustReflectionAngle(BlockingMobj, angle));
2026 // Change angle for deflection/reflection
2027
2028 if (!dontReflect)
2029 {
2030 bool tg = (mo->target != NULL);
2031 bool blockingtg = (BlockingMobj->target != NULL);
2032 if ((BlockingMobj->flags7 & MF7_AIMREFLECT) && (tg | blockingtg))
2033 {
2034 AActor *origin = tg ? mo->target : BlockingMobj->target;
2035
2036 float speed = (float)(mo->Speed);
2037 //dest->x - source->x
2038 fixedvec3 vect = mo->Vec3To(origin);
2039 vect.z += origin->height / 2;
2040 TVector3<double> velocity(vect.x, vect.y, vect.z);
2041 velocity.Resize(speed);
2042 mo->velx = (fixed_t)(velocity.X);
2043 mo->vely = (fixed_t)(velocity.Y);
2044 mo->velz = (fixed_t)(velocity.Z);
2045 }
2046 else
2047 {
2048 if ((BlockingMobj->flags7 & MF7_MIRRORREFLECT) && (tg | blockingtg))
2049 {
2050 mo->angle += ANGLE_180;
2051 mo->velx = -mo->velx / 2;
2052 mo->vely = -mo->vely / 2;
2053 mo->velz = -mo->velz / 2;
2054 }
2055 else
2056 {
2057 mo->angle = angle;
2058 angle >>= ANGLETOFINESHIFT;
2059 mo->velx = FixedMul(mo->Speed >> 1, finecosine[angle]);
2060 mo->vely = FixedMul(mo->Speed >> 1, finesine[angle]);
2061 mo->velz = -mo->velz / 2;
2062 }
2063 }
2064 }
2065 else
2066 {
2067 goto explode;
2068 }
2069 }
2070 if (mo->flags2 & MF2_SEEKERMISSILE)
2071 {
2072 mo->tracer = mo->target;
2073 }
2074 mo->target = BlockingMobj;
2075 return oldfloorz;
2076 }
2077 explode:
2078 // explode a missile
2079 if (!(mo->flags3 & MF3_SKYEXPLODE))
2080 {
2081 if (tm.ceilingline &&
2082 tm.ceilingline->backsector &&
2083 tm.ceilingline->backsector->GetTexture(sector_t::ceiling) == skyflatnum &&
2084 mo->Z() >= tm.ceilingline->backsector->ceilingplane.ZatPoint(mo))
2085 {
2086 // Hack to prevent missiles exploding against the sky.
2087 // Does not handle sky floors.
2088 mo->Destroy ();
2089 return oldfloorz;
2090 }
2091 // [RH] Don't explode on horizon lines.
2092 if (mo->BlockingLine != NULL && mo->BlockingLine->special == Line_Horizon)
2093 {
2094 mo->Destroy ();
2095 return oldfloorz;
2096 }
2097 }
2098 P_ExplodeMissile (mo, mo->BlockingLine, BlockingMobj);
2099 return oldfloorz;
2100 }
2101 else
2102 {
2103 mo->velx = mo->vely = 0;
2104 steps = 0;
2105 }
2106 }
2107 else
2108 {
2109 if (mo->X() != ptryx || mo->Y() != ptryy)
2110 {
2111 // If the new position does not match the desired position, the player
2112 // must have gone through a teleporter, so stop moving right now if it
2113 // was a regular teleporter. If it was a line-to-line or fogless teleporter,
2114 // the move should continue, but startx and starty need to change.
2115 if (mo->velx == 0 && mo->vely == 0)
2116 {
2117 step = steps;
2118 }
2119 else
2120 {
2121 startx = mo->X() - Scale (xmove, step, steps);
2122 starty = mo->Y() - Scale (ymove, step, steps);
2123 }
2124 }
2125 }
2126 } while (++step <= steps);
2127
2128 // Friction
2129
2130 if (player && player->mo == mo && player->cheats & CF_NOVELOCITY)
2131 { // debug option for no sliding at all
2132 mo->velx = mo->vely = 0;
2133 player->velx = player->vely = 0;
2134 return oldfloorz;
2135 }
2136
2137 if (mo->flags & (MF_MISSILE | MF_SKULLFLY))
2138 { // no friction for missiles
2139 return oldfloorz;
2140 }
2141
2142 if (mo->Z() > mo->floorz && !(mo->flags2 & MF2_ONMOBJ) &&
2143 !mo->IsNoClip2() &&
2144 (!(mo->flags2 & MF2_FLY) || !(mo->flags & MF_NOGRAVITY)) &&
2145 !mo->waterlevel)
2146 { // [RH] Friction when falling is available for larger aircontrols
2147 if (player != NULL && level.airfriction != FRACUNIT)
2148 {
2149 mo->velx = FixedMul (mo->velx, level.airfriction);
2150 mo->vely = FixedMul (mo->vely, level.airfriction);
2151
2152 if (player->mo == mo) // Not voodoo dolls
2153 {
2154 player->velx = FixedMul (player->velx, level.airfriction);
2155 player->vely = FixedMul (player->vely, level.airfriction);
2156 }
2157 }
2158 return oldfloorz;
2159 }
2160
2161 // killough 8/11/98: add bouncers
2162 // killough 9/15/98: add objects falling off ledges
2163 // killough 11/98: only include bouncers hanging off ledges
2164 if ((mo->flags & MF_CORPSE) || (mo->BounceFlags & BOUNCE_MBF && mo->Z() > mo->dropoffz) || (mo->flags6 & MF6_FALLING))
2165 { // Don't stop sliding if halfway off a step with some velocity
2166 if (mo->velx > FRACUNIT/4 || mo->velx < -FRACUNIT/4 || mo->vely > FRACUNIT/4 || mo->vely < -FRACUNIT/4)
2167 {
2168 if (mo->floorz > mo->Sector->floorplane.ZatPoint(mo))
2169 {
2170 if (mo->dropoffz != mo->floorz) // 3DMidtex or other special cases that must be excluded
2171 {
2172 unsigned i;
2173 for(i=0;i<mo->Sector->e->XFloor.ffloors.Size();i++)
2174 {
2175 // Sliding around on 3D floors looks extremely bad so
2176 // if the floor comes from one in the current sector stop sliding the corpse!
2177 F3DFloor * rover=mo->Sector->e->XFloor.ffloors[i];
2178 if (!(rover->flags&FF_EXISTS)) continue;
2179 if (rover->flags&FF_SOLID && rover->top.plane->ZatPoint(mo) == mo->floorz) break;
2180 }
2181 if (i==mo->Sector->e->XFloor.ffloors.Size())
2182 return oldfloorz;
2183 }
2184 }
2185 }
2186 }
2187
2188 // killough 11/98:
2189 // Stop voodoo dolls that have come to rest, despite any
2190 // moving corresponding player:
2191 if (mo->velx > -STOPSPEED && mo->velx < STOPSPEED
2192 && mo->vely > -STOPSPEED && mo->vely < STOPSPEED
2193 && (!player || (player->mo != mo)
2194 || !(player->cmd.ucmd.forwardmove | player->cmd.ucmd.sidemove)))
2195 {
2196 // if in a walking frame, stop moving
2197 // killough 10/98:
2198 // Don't affect main player when voodoo dolls stop:
2199 if (player && player->mo == mo && !(player->cheats & CF_PREDICTING))
2200 {
2201 player->mo->PlayIdle ();
2202 }
2203
2204 mo->velx = mo->vely = 0;
2205 mo->flags4 &= ~MF4_SCROLLMOVE;
2206
2207 // killough 10/98: kill any bobbing velocity too (except in voodoo dolls)
2208 if (player && player->mo == mo)
2209 player->velx = player->vely = 0;
2210 }
2211 else
2212 {
2213 // phares 3/17/98
2214 // Friction will have been adjusted by friction thinkers for icy
2215 // or muddy floors. Otherwise it was never touched and
2216 // remained set at ORIG_FRICTION
2217 //
2218 // killough 8/28/98: removed inefficient thinker algorithm,
2219 // instead using touching_sectorlist in P_GetFriction() to
2220 // determine friction (and thus only when it is needed).
2221 //
2222 // killough 10/98: changed to work with new bobbing method.
2223 // Reducing player velocity is no longer needed to reduce
2224 // bobbing, so ice works much better now.
2225
2226 fixed_t friction = P_GetFriction (mo, NULL);
2227
2228 mo->velx = FixedMul (mo->velx, friction);
2229 mo->vely = FixedMul (mo->vely, friction);
2230
2231 // killough 10/98: Always decrease player bobbing by ORIG_FRICTION.
2232 // This prevents problems with bobbing on ice, where it was not being
2233 // reduced fast enough, leading to all sorts of kludges being developed.
2234
2235 if (player && player->mo == mo) // Not voodoo dolls
2236 {
2237 player->velx = FixedMul (player->velx, ORIG_FRICTION);
2238 player->vely = FixedMul (player->vely, ORIG_FRICTION);
2239 }
2240 }
2241 return oldfloorz;
2242 }
2243
2244 // Move this to p_inter ***
P_MonsterFallingDamage(AActor * mo)2245 void P_MonsterFallingDamage (AActor *mo)
2246 {
2247 int damage;
2248 int vel;
2249
2250 if (!(level.flags2 & LEVEL2_MONSTERFALLINGDAMAGE))
2251 return;
2252 if (mo->floorsector->Flags & SECF_NOFALLINGDAMAGE)
2253 return;
2254
2255 vel = abs(mo->velz);
2256 if (vel > 35*FRACUNIT)
2257 { // automatic death
2258 damage = TELEFRAG_DAMAGE;
2259 }
2260 else
2261 {
2262 damage = ((vel - (23*FRACUNIT))*6)>>FRACBITS;
2263 }
2264 damage = TELEFRAG_DAMAGE; // always kill 'em
2265 P_DamageMobj (mo, NULL, NULL, damage, NAME_Falling);
2266 }
2267
2268 //
2269 // P_ZMovement
2270 //
2271
P_ZMovement(AActor * mo,fixed_t oldfloorz)2272 void P_ZMovement (AActor *mo, fixed_t oldfloorz)
2273 {
2274 fixed_t dist;
2275 fixed_t delta;
2276 fixed_t oldz = mo->Z();
2277 fixed_t grav = mo->GetGravity();
2278
2279 //
2280 // check for smooth step up
2281 //
2282 if (mo->player && mo->player->mo == mo && mo->Z() < mo->floorz)
2283 {
2284 mo->player->viewheight -= mo->floorz - mo->Z();
2285 mo->player->deltaviewheight = mo->player->GetDeltaViewHeight();
2286 }
2287
2288 mo->AddZ(mo->velz);
2289
2290 //
2291 // apply gravity
2292 //
2293 if (mo->Z() > mo->floorz && !(mo->flags & MF_NOGRAVITY))
2294 {
2295 fixed_t startvelz = mo->velz;
2296
2297 if (mo->waterlevel == 0 || (mo->player &&
2298 !(mo->player->cmd.ucmd.forwardmove | mo->player->cmd.ucmd.sidemove)))
2299 {
2300 // [RH] Double gravity only if running off a ledge. Coming down from
2301 // an upward thrust (e.g. a jump) should not double it.
2302 if (mo->velz == 0 && oldfloorz > mo->floorz && mo->Z() == oldfloorz)
2303 {
2304 mo->velz -= grav + grav;
2305 }
2306 else
2307 {
2308 mo->velz -= grav;
2309 }
2310 }
2311 if (mo->player == NULL)
2312 {
2313 if (mo->waterlevel >= 1)
2314 {
2315 fixed_t sinkspeed;
2316
2317 if ((mo->flags & MF_SPECIAL) && !(mo->flags3 & MF3_ISMONSTER))
2318 { // Pickup items don't sink if placed and drop slowly if dropped
2319 sinkspeed = (mo->flags & MF_DROPPED) ? -WATER_SINK_SPEED / 8 : 0;
2320 }
2321 else
2322 {
2323 sinkspeed = -WATER_SINK_SPEED;
2324
2325 // If it's not a player, scale sinkspeed by its mass, with
2326 // 100 being equivalent to a player.
2327 if (mo->player == NULL)
2328 {
2329 sinkspeed = Scale(sinkspeed, clamp(mo->Mass, 1, 4000), 100);
2330 }
2331 }
2332 if (mo->velz < sinkspeed)
2333 { // Dropping too fast, so slow down toward sinkspeed.
2334 mo->velz -= MAX(sinkspeed*2, -FRACUNIT*8);
2335 if (mo->velz > sinkspeed)
2336 {
2337 mo->velz = sinkspeed;
2338 }
2339 }
2340 else if (mo->velz > sinkspeed)
2341 { // Dropping too slow/going up, so trend toward sinkspeed.
2342 mo->velz = startvelz + MAX(sinkspeed/3, -FRACUNIT*8);
2343 if (mo->velz < sinkspeed)
2344 {
2345 mo->velz = sinkspeed;
2346 }
2347 }
2348 }
2349 }
2350 else
2351 {
2352 if (mo->waterlevel > 1)
2353 {
2354 fixed_t sinkspeed = -WATER_SINK_SPEED;
2355
2356 if (mo->velz < sinkspeed)
2357 {
2358 mo->velz = (startvelz < sinkspeed) ? startvelz : sinkspeed;
2359 }
2360 else
2361 {
2362 mo->velz = startvelz + ((mo->velz - startvelz) >>
2363 (mo->waterlevel == 1 ? WATER_SINK_SMALL_FACTOR : WATER_SINK_FACTOR));
2364 }
2365 }
2366 }
2367 }
2368
2369 // Hexen compatibility handling for floatbobbing. Ugh...
2370 // Hexen yanked all items to the floor, except those being spawned at map start in the air.
2371 // Those were kept at their original height.
2372 // Do this only if the item was actually spawned by the map above ground to avoid problems.
2373 if (mo->special1 > 0 && (mo->flags2 & MF2_FLOATBOB) && (ib_compatflags & BCOMPATF_FLOATBOB))
2374 {
2375 mo->SetZ(mo->floorz + mo->special1);
2376 }
2377
2378
2379 //
2380 // adjust height
2381 //
2382 if ((mo->flags & MF_FLOAT) && !(mo->flags2 & MF2_DORMANT) && mo->target)
2383 { // float down towards target if too close
2384 if (!(mo->flags & (MF_SKULLFLY | MF_INFLOAT)))
2385 {
2386 dist = mo->AproxDistance (mo->target);
2387 delta = (mo->target->Z() + (mo->height>>1)) - mo->Z();
2388 if (delta < 0 && dist < -(delta*3))
2389 mo->AddZ(-mo->FloatSpeed);
2390 else if (delta > 0 && dist < (delta*3))
2391 mo->AddZ(mo->FloatSpeed);
2392 }
2393 }
2394 if (mo->player && (mo->flags & MF_NOGRAVITY) && (mo->Z() > mo->floorz))
2395 {
2396 if (!mo->IsNoClip2())
2397 {
2398 mo->AddZ(finesine[(FINEANGLES/80*level.maptime)&FINEMASK]/8);
2399 }
2400 mo->velz = FixedMul (mo->velz, FRICTION_FLY);
2401 }
2402 if (mo->waterlevel && !(mo->flags & MF_NOGRAVITY))
2403 {
2404 mo->velz = FixedMul (mo->velz, mo->Sector->friction);
2405 }
2406
2407 //
2408 // clip movement
2409 //
2410 if (mo->Z() <= mo->floorz)
2411 { // Hit the floor
2412 if ((!mo->player || !(mo->player->cheats & CF_PREDICTING)) &&
2413 mo->Sector->SecActTarget != NULL &&
2414 mo->Sector->floorplane.ZatPoint(mo) == mo->floorz)
2415 { // [RH] Let the sector do something to the actor
2416 mo->Sector->SecActTarget->TriggerAction (mo, SECSPAC_HitFloor);
2417 }
2418 P_CheckFor3DFloorHit(mo);
2419 // [RH] Need to recheck this because the sector action might have
2420 // teleported the actor so it is no longer below the floor.
2421 if (mo->Z() <= mo->floorz)
2422 {
2423 if ((mo->flags & MF_MISSILE) && !(mo->flags & MF_NOCLIP))
2424 {
2425 mo->SetZ(mo->floorz);
2426 if (mo->BounceFlags & BOUNCE_Floors)
2427 {
2428 mo->FloorBounceMissile (mo->floorsector->floorplane);
2429 /* if (!(mo->flags6 & MF6_CANJUMP)) */ return;
2430 }
2431 else if (mo->flags3 & MF3_NOEXPLODEFLOOR)
2432 {
2433 P_HitFloor (mo);
2434 mo->velz = 0;
2435 return;
2436 }
2437 else if (mo->flags3 & MF3_FLOORHUGGER)
2438 { // Floor huggers can go up steps
2439 return;
2440 }
2441 else
2442 {
2443 if (mo->floorpic == skyflatnum && !(mo->flags3 & MF3_SKYEXPLODE))
2444 {
2445 // [RH] Just remove the missile without exploding it
2446 // if this is a sky floor.
2447 mo->Destroy ();
2448 return;
2449 }
2450 P_HitFloor (mo);
2451 P_ExplodeMissile (mo, NULL, NULL);
2452 return;
2453 }
2454 }
2455 else if (mo->BounceFlags & BOUNCE_MBF && mo->velz) // check for MBF-like bounce on non-missiles
2456 {
2457 mo->FloorBounceMissile(mo->floorsector->floorplane);
2458 }
2459 if (mo->flags3 & MF3_ISMONSTER) // Blasted mobj falling
2460 {
2461 if (mo->velz < -(23*FRACUNIT))
2462 {
2463 P_MonsterFallingDamage (mo);
2464 }
2465 }
2466 mo->SetZ(mo->floorz);
2467 if (mo->velz < 0)
2468 {
2469 const fixed_t minvel = -8*FRACUNIT; // landing speed from a jump with normal gravity
2470
2471 // Spawn splashes, etc.
2472 P_HitFloor (mo);
2473 if (mo->DamageType == NAME_Ice && mo->velz < minvel)
2474 {
2475 mo->tics = 1;
2476 mo->velx = 0;
2477 mo->vely = 0;
2478 mo->velz = 0;
2479 return;
2480 }
2481 // Let the actor do something special for hitting the floor
2482 mo->HitFloor ();
2483 if (mo->player)
2484 {
2485 if (mo->player->jumpTics < 0 || mo->velz < minvel)
2486 { // delay any jumping for a short while
2487 mo->player->jumpTics = 7;
2488 }
2489 if (mo->velz < minvel && !(mo->flags & MF_NOGRAVITY))
2490 {
2491 // Squat down.
2492 // Decrease viewheight for a moment after hitting the ground (hard),
2493 // and utter appropriate sound.
2494 PlayerLandedOnThing (mo, NULL);
2495 }
2496 }
2497 mo->velz = 0;
2498 }
2499 if (mo->flags & MF_SKULLFLY)
2500 { // The skull slammed into something
2501 mo->velz = -mo->velz;
2502 }
2503 mo->Crash();
2504 }
2505 }
2506
2507 if (mo->flags2 & MF2_FLOORCLIP)
2508 {
2509 mo->AdjustFloorClip ();
2510 }
2511
2512 if (mo->Top() > mo->ceilingz)
2513 { // hit the ceiling
2514 if ((!mo->player || !(mo->player->cheats & CF_PREDICTING)) &&
2515 mo->Sector->SecActTarget != NULL &&
2516 mo->Sector->ceilingplane.ZatPoint(mo) == mo->ceilingz)
2517 { // [RH] Let the sector do something to the actor
2518 mo->Sector->SecActTarget->TriggerAction (mo, SECSPAC_HitCeiling);
2519 }
2520 P_CheckFor3DCeilingHit(mo);
2521 // [RH] Need to recheck this because the sector action might have
2522 // teleported the actor so it is no longer above the ceiling.
2523 if (mo->Top() > mo->ceilingz)
2524 {
2525 mo->SetZ(mo->ceilingz - mo->height);
2526 if (mo->BounceFlags & BOUNCE_Ceilings)
2527 { // ceiling bounce
2528 mo->FloorBounceMissile (mo->ceilingsector->ceilingplane);
2529 /*if (!(mo->flags6 & MF6_CANJUMP))*/ return;
2530 }
2531 if (mo->flags & MF_SKULLFLY)
2532 { // the skull slammed into something
2533 mo->velz = -mo->velz;
2534 }
2535 if (mo->velz > 0)
2536 mo->velz = 0;
2537 if ((mo->flags & MF_MISSILE) && !(mo->flags & MF_NOCLIP))
2538 {
2539 if (mo->flags3 & MF3_CEILINGHUGGER)
2540 {
2541 return;
2542 }
2543 if (mo->ceilingpic == skyflatnum && !(mo->flags3 & MF3_SKYEXPLODE))
2544 {
2545 mo->Destroy ();
2546 return;
2547 }
2548 P_ExplodeMissile (mo, NULL, NULL);
2549 return;
2550 }
2551 }
2552 }
2553 P_CheckFakeFloorTriggers (mo, oldz);
2554 }
2555
P_CheckFakeFloorTriggers(AActor * mo,fixed_t oldz,bool oldz_has_viewheight)2556 void P_CheckFakeFloorTriggers (AActor *mo, fixed_t oldz, bool oldz_has_viewheight)
2557 {
2558 if (mo->player && (mo->player->cheats & CF_PREDICTING))
2559 {
2560 return;
2561 }
2562 sector_t *sec = mo->Sector;
2563 assert (sec != NULL);
2564 if (sec == NULL)
2565 {
2566 return;
2567 }
2568 if (sec->heightsec != NULL && sec->SecActTarget != NULL)
2569 {
2570 sector_t *hs = sec->heightsec;
2571 fixed_t waterz = hs->floorplane.ZatPoint(mo);
2572 fixed_t newz;
2573 fixed_t viewheight;
2574
2575 if (mo->player != NULL)
2576 {
2577 viewheight = mo->player->viewheight;
2578 }
2579 else
2580 {
2581 viewheight = mo->height / 2;
2582 }
2583
2584 if (oldz > waterz && mo->Z() <= waterz)
2585 { // Feet hit fake floor
2586 sec->SecActTarget->TriggerAction (mo, SECSPAC_HitFakeFloor);
2587 }
2588
2589 newz = mo->Z() + viewheight;
2590 if (!oldz_has_viewheight)
2591 {
2592 oldz += viewheight;
2593 }
2594
2595 if (oldz <= waterz && newz > waterz)
2596 { // View went above fake floor
2597 sec->SecActTarget->TriggerAction (mo, SECSPAC_EyesSurface);
2598 }
2599 else if (oldz > waterz && newz <= waterz)
2600 { // View went below fake floor
2601 sec->SecActTarget->TriggerAction (mo, SECSPAC_EyesDive);
2602 }
2603
2604 if (!(hs->MoreFlags & SECF_FAKEFLOORONLY))
2605 {
2606 waterz = hs->ceilingplane.ZatPoint(mo);
2607 if (oldz <= waterz && newz > waterz)
2608 { // View went above fake ceiling
2609 sec->SecActTarget->TriggerAction (mo, SECSPAC_EyesAboveC);
2610 }
2611 else if (oldz > waterz && newz <= waterz)
2612 { // View went below fake ceiling
2613 sec->SecActTarget->TriggerAction (mo, SECSPAC_EyesBelowC);
2614 }
2615 }
2616 }
2617 }
2618
2619 //===========================================================================
2620 //
2621 // PlayerLandedOnThing
2622 //
2623 //===========================================================================
2624
PlayerLandedOnThing(AActor * mo,AActor * onmobj)2625 static void PlayerLandedOnThing (AActor *mo, AActor *onmobj)
2626 {
2627 bool grunted;
2628
2629 if (!mo->player)
2630 return;
2631
2632 if (mo->player->mo == mo)
2633 {
2634 mo->player->deltaviewheight = mo->velz >> 3;
2635 }
2636
2637 if (mo->player->cheats & CF_PREDICTING)
2638 return;
2639
2640 P_FallingDamage (mo);
2641
2642 // [RH] only make noise if alive
2643 if (!mo->player->morphTics && mo->health > 0)
2644 {
2645 grunted = false;
2646 // Why should this number vary by gravity?
2647 if (mo->health > 0 && mo->velz < -mo->player->mo->GruntSpeed)
2648 {
2649 S_Sound (mo, CHAN_VOICE, "*grunt", 1, ATTN_NORM);
2650 grunted = true;
2651 }
2652 if (onmobj != NULL || !Terrains[P_GetThingFloorType (mo)].IsLiquid)
2653 {
2654 if (!grunted || !S_AreSoundsEquivalent (mo, "*grunt", "*land"))
2655 {
2656 S_Sound (mo, CHAN_AUTO, "*land", 1, ATTN_NORM);
2657 }
2658 }
2659 }
2660 // mo->player->centering = true;
2661 }
2662
2663
2664
2665 //
2666 // P_NightmareRespawn
2667 //
P_NightmareRespawn(AActor * mobj)2668 void P_NightmareRespawn (AActor *mobj)
2669 {
2670 fixed_t x, y, z;
2671 AActor *mo;
2672 AActor *info = mobj->GetDefault();
2673
2674 mobj->skillrespawncount++;
2675
2676 // spawn the new monster (assume the spawn will be good)
2677 if (info->flags & MF_SPAWNCEILING)
2678 z = ONCEILINGZ;
2679 else if (info->flags2 & MF2_SPAWNFLOAT)
2680 z = FLOATRANDZ;
2681 else
2682 z = ONFLOORZ;
2683
2684 // spawn it
2685 x = mobj->SpawnPoint[0];
2686 y = mobj->SpawnPoint[1];
2687 mo = AActor::StaticSpawn(RUNTIME_TYPE(mobj), x, y, z, NO_REPLACE, true);
2688
2689 if (z == ONFLOORZ)
2690 {
2691 mo->AddZ(mobj->SpawnPoint[2]);
2692 if (mo->Z() < mo->floorz)
2693 { // Do not respawn monsters in the floor, even if that's where they
2694 // started. The initial P_ZMovement() call would have put them on
2695 // the floor right away, but we need them on the floor now so we
2696 // can use P_CheckPosition() properly.
2697 mo->SetZ(mo->floorz);
2698 }
2699 if (mo->Top() > mo->ceilingz)
2700 {
2701 mo->SetZ(mo->ceilingz - mo->height);
2702 }
2703 }
2704 else if (z == ONCEILINGZ)
2705 {
2706 mo->AddZ(-mobj->SpawnPoint[2]);
2707 }
2708
2709 // If there are 3D floors, we need to find floor/ceiling again.
2710 P_FindFloorCeiling(mo, FFCF_SAMESECTOR | FFCF_ONLY3DFLOORS | FFCF_3DRESTRICT);
2711
2712 if (z == ONFLOORZ)
2713 {
2714 if (mo->Z() < mo->floorz)
2715 { // Do not respawn monsters in the floor, even if that's where they
2716 // started. The initial P_ZMovement() call would have put them on
2717 // the floor right away, but we need them on the floor now so we
2718 // can use P_CheckPosition() properly.
2719 mo->SetZ(mo->floorz);
2720 }
2721 if (mo->Top() > mo->ceilingz)
2722 { // Do the same for the ceiling.
2723 mo->SetZ(mo->ceilingz - mo->height);
2724 }
2725 }
2726
2727 // something is occupying its position?
2728 if (!P_CheckPosition(mo, mo->X(), mo->Y(), true))
2729 {
2730 //[GrafZahl] MF_COUNTKILL still needs to be checked here.
2731 mo->ClearCounters();
2732 mo->Destroy ();
2733 return; // no respawn
2734 }
2735
2736 z = mo->Z();
2737
2738 // inherit attributes from deceased one
2739 mo->SpawnPoint[0] = mobj->SpawnPoint[0];
2740 mo->SpawnPoint[1] = mobj->SpawnPoint[1];
2741 mo->SpawnPoint[2] = mobj->SpawnPoint[2];
2742 mo->SpawnAngle = mobj->SpawnAngle;
2743 mo->SpawnFlags = mobj->SpawnFlags & ~MTF_DORMANT; // It wasn't dormant when it died, so it's not dormant now, either.
2744 mo->angle = ANG45 * (mobj->SpawnAngle/45);
2745
2746 mo->HandleSpawnFlags ();
2747 mo->reactiontime = 18;
2748 mo->CopyFriendliness (mobj, false);
2749 mo->Translation = mobj->Translation;
2750
2751 mo->skillrespawncount = mobj->skillrespawncount;
2752
2753 mo->PrevZ = z; // Do not interpolate Z position if we changed it since spawning.
2754
2755 // spawn a teleport fog at old spot because of removal of the body?
2756 P_SpawnTeleportFog(mobj, mobj->PosPlusZ(TELEFOGHEIGHT), true, true);
2757
2758 // spawn a teleport fog at the new spot
2759 P_SpawnTeleportFog(mobj, x, y, z + TELEFOGHEIGHT, false, true);
2760
2761 // remove the old monster
2762 mobj->Destroy ();
2763 }
2764
2765
2766 AActor *AActor::TIDHash[128];
2767
2768 //
2769 // P_ClearTidHashes
2770 //
2771 // Clears the tid hashtable.
2772 //
2773
ClearTIDHashes()2774 void AActor::ClearTIDHashes ()
2775 {
2776 memset(TIDHash, 0, sizeof(TIDHash));
2777 }
2778
2779 //
2780 // P_AddMobjToHash
2781 //
2782 // Inserts an mobj into the correct chain based on its tid.
2783 // If its tid is 0, this function does nothing.
2784 //
AddToHash()2785 void AActor::AddToHash ()
2786 {
2787 if (tid == 0)
2788 {
2789 iprev = NULL;
2790 inext = NULL;
2791 return;
2792 }
2793 else
2794 {
2795 int hash = TIDHASH (tid);
2796
2797 inext = TIDHash[hash];
2798 iprev = &TIDHash[hash];
2799 TIDHash[hash] = this;
2800 if (inext)
2801 {
2802 inext->iprev = &inext;
2803 }
2804 }
2805 }
2806
2807 //
2808 // P_RemoveMobjFromHash
2809 //
2810 // Removes an mobj from its hash chain.
2811 //
RemoveFromHash()2812 void AActor::RemoveFromHash ()
2813 {
2814 if (tid != 0 && iprev)
2815 {
2816 *iprev = inext;
2817 if (inext)
2818 {
2819 inext->iprev = iprev;
2820 }
2821 iprev = NULL;
2822 inext = NULL;
2823 }
2824 tid = 0;
2825 }
2826
2827 //==========================================================================
2828 //
2829 // P_IsTIDUsed
2830 //
2831 // Returns true if there is at least one actor with the specified TID
2832 // (dead or alive).
2833 //
2834 //==========================================================================
2835
P_IsTIDUsed(int tid)2836 bool P_IsTIDUsed(int tid)
2837 {
2838 AActor *probe = AActor::TIDHash[tid & 127];
2839 while (probe != NULL)
2840 {
2841 if (probe->tid == tid)
2842 {
2843 return true;
2844 }
2845 probe = probe->inext;
2846 }
2847 return false;
2848 }
2849
2850 //==========================================================================
2851 //
2852 // P_FindUniqueTID
2853 //
2854 // Returns an unused TID. If start_tid is 0, then a random TID will be
2855 // chosen. Otherwise, it will perform a linear search starting from
2856 // start_tid. If limit is non-0, then it will not check more than <limit>
2857 // number of TIDs. Returns 0 if no suitable TID was found.
2858 //
2859 //==========================================================================
2860
P_FindUniqueTID(int start_tid,int limit)2861 int P_FindUniqueTID(int start_tid, int limit)
2862 {
2863 int tid;
2864
2865 if (start_tid != 0)
2866 { // Do a linear search.
2867 if (start_tid > INT_MAX-limit+1)
2868 { // If 'limit+start_tid-1' overflows, clamp 'limit' to INT_MAX
2869 limit = INT_MAX;
2870 }
2871 else
2872 {
2873 limit += start_tid-1;
2874 }
2875 for (tid = start_tid; tid <= limit; ++tid)
2876 {
2877 if (tid != 0 && !P_IsTIDUsed(tid))
2878 {
2879 return tid;
2880 }
2881 }
2882 // Nothing free found.
2883 return 0;
2884 }
2885 // Do a random search. To try and be a bit more performant, this
2886 // actually does several linear searches. In the case of *very*
2887 // dense TID usage, this could potentially perform worse than doing
2888 // a complete linear scan starting at 1. However, you would need
2889 // to use an absolutely ridiculous number of actors before this
2890 // becomes a real concern.
2891 if (limit == 0)
2892 {
2893 limit = INT_MAX;
2894 }
2895 for (int i = 0; i < limit; i += 5)
2896 {
2897 // Use a positive starting TID.
2898 tid = pr_uniquetid.GenRand32() & INT_MAX;
2899 tid = P_FindUniqueTID(tid == 0 ? 1 : tid, 5);
2900 if (tid != 0)
2901 {
2902 return tid;
2903 }
2904 }
2905 // Nothing free found.
2906 return 0;
2907 }
2908
CCMD(utid)2909 CCMD(utid)
2910 {
2911 Printf("%d\n",
2912 P_FindUniqueTID(argv.argc() > 1 ? atoi(argv[1]) : 0,
2913 (argv.argc() > 2 && atoi(argv[2]) >= 0) ? atoi(argv[2]) : 0));
2914 }
2915
2916 //==========================================================================
2917 //
2918 // AActor :: GetMissileDamage
2919 //
2920 // If the actor's damage amount is an expression, evaluate it and return
2921 // the result. Otherwise, return ((random() & mask) + add) * damage.
2922 //
2923 //==========================================================================
2924
GetMissileDamage(int mask,int add)2925 int AActor::GetMissileDamage (int mask, int add)
2926 {
2927 if ((Damage & 0xC0000000) == 0x40000000)
2928 {
2929 return EvalExpressionI (Damage & 0x3FFFFFFF, this);
2930 }
2931 if (Damage == 0)
2932 {
2933 return 0;
2934 }
2935 else if (mask == 0)
2936 {
2937 return add * Damage;
2938 }
2939 else
2940 {
2941 return ((pr_missiledamage() & mask) + add) * Damage;
2942 }
2943 }
2944
Howl()2945 void AActor::Howl ()
2946 {
2947 int howl = GetClass()->Meta.GetMetaInt(AMETA_HowlSound);
2948 if (!S_IsActorPlayingSomething(this, CHAN_BODY, howl))
2949 {
2950 S_Sound (this, CHAN_BODY, howl, 1, ATTN_NORM);
2951 }
2952 }
2953
HitFloor()2954 void AActor::HitFloor ()
2955 {
2956 }
2957
Slam(AActor * thing)2958 bool AActor::Slam (AActor *thing)
2959 {
2960 flags &= ~MF_SKULLFLY;
2961 velx = vely = velz = 0;
2962 if (health > 0)
2963 {
2964 if (!(flags2 & MF2_DORMANT))
2965 {
2966 int dam = GetMissileDamage (7, 1);
2967 int newdam = P_DamageMobj (thing, this, this, dam, NAME_Melee);
2968 P_TraceBleed (newdam > 0 ? newdam : dam, thing, this);
2969 // The charging monster may have died by the target's actions here.
2970 if (health > 0)
2971 {
2972 if (SeeState != NULL) SetState (SeeState);
2973 else SetIdle();
2974 }
2975 }
2976 else
2977 {
2978 SetIdle();
2979 tics = -1;
2980 }
2981 }
2982 return false; // stop moving
2983 }
2984
SpecialBlastHandling(AActor * source,fixed_t strength)2985 bool AActor::SpecialBlastHandling (AActor *source, fixed_t strength)
2986 {
2987 return true;
2988 }
2989
SpecialMissileHit(AActor * victim)2990 int AActor::SpecialMissileHit (AActor *victim)
2991 {
2992 return -1;
2993 }
2994
AdjustReflectionAngle(AActor * thing,angle_t & angle)2995 bool AActor::AdjustReflectionAngle (AActor *thing, angle_t &angle)
2996 {
2997 if (flags2 & MF2_DONTREFLECT) return true;
2998 if (thing->flags7 & MF7_THRUREFLECT) return false;
2999 // Change angle for reflection
3000 if (thing->flags4&MF4_SHIELDREFLECT)
3001 {
3002 // Shield reflection (from the Centaur
3003 if (absangle(angle - thing->angle)>>24 > 45)
3004 return true; // Let missile explode
3005
3006 if (thing->IsKindOf (RUNTIME_CLASS(AHolySpirit))) // shouldn't this be handled by another flag???
3007 return true;
3008
3009 if (pr_reflect () < 128)
3010 angle += ANGLE_45;
3011 else
3012 angle -= ANGLE_45;
3013
3014 }
3015 else if (thing->flags4&MF4_DEFLECT)
3016 {
3017 // deflect (like the Heresiarch)
3018 if(pr_reflect() < 128)
3019 angle += ANG45;
3020 else
3021 angle -= ANG45;
3022 }
3023 else
3024 {
3025 angle += ANGLE_1 * ((pr_reflect() % 16) - 8);
3026 }
3027 //Always check for AIMREFLECT, no matter what else is checked above.
3028 if (thing->flags7 & MF7_AIMREFLECT)
3029 {
3030 if (this->target != NULL)
3031 {
3032 A_Face(this, this->target);
3033 }
3034 else if (thing->target != NULL)
3035 {
3036 A_Face(this, thing->target);
3037 }
3038 }
3039
3040 return false;
3041 }
3042
PlayActiveSound()3043 void AActor::PlayActiveSound ()
3044 {
3045 if (ActiveSound && !S_IsActorPlayingSomething (this, CHAN_VOICE, -1))
3046 {
3047 S_Sound (this, CHAN_VOICE, ActiveSound, 1,
3048 (flags3 & MF3_FULLVOLACTIVE) ? ATTN_NONE : ATTN_IDLE);
3049 }
3050 }
3051
IsOkayToAttack(AActor * link)3052 bool AActor::IsOkayToAttack (AActor *link)
3053 {
3054 if (!(player // Original AActor::IsOkayToAttack was only for players
3055 // || (flags & MF_FRIENDLY) // Maybe let friendly monsters use the function as well?
3056 || (flags5 & MF5_SUMMONEDMONSTER) // AMinotaurFriend has its own version, generalized to other summoned monsters
3057 || (flags2 & MF2_SEEKERMISSILE))) // AHolySpirit and AMageStaffFX2 as well, generalized to other seeker missiles
3058 { // Normal monsters and other actors always return false.
3059 return false;
3060 }
3061 // Standard things to eliminate: an actor shouldn't attack itself,
3062 // or a non-shootable, dormant, non-player-and-non-monster actor.
3063 if (link == this) return false;
3064 if (!(link->player||(link->flags3 & MF3_ISMONSTER)))return false;
3065 if (!(link->flags & MF_SHOOTABLE)) return false;
3066 if (link->flags2 & MF2_DORMANT) return false;
3067
3068 // An actor shouldn't attack friendly actors. The reference depends
3069 // on the type of actor: for a player's actor, itself; for a projectile,
3070 // its target; and for a summoned minion, its tracer.
3071 AActor * Friend = NULL;
3072 if (player) Friend = this;
3073 else if (flags5 & MF5_SUMMONEDMONSTER) Friend = tracer;
3074 else if (flags2 & MF2_SEEKERMISSILE) Friend = target;
3075 else if ((flags & MF_FRIENDLY) && FriendPlayer) Friend = players[FriendPlayer-1].mo;
3076
3077 // Friend checks
3078 if (link == Friend) return false;
3079 if (Friend == NULL) return false;
3080 if (Friend->IsFriend(link)) return false;
3081 if ((link->flags5 & MF5_SUMMONEDMONSTER) // No attack against minions on the same side
3082 && (link->tracer == Friend)) return false;
3083 if (multiplayer && !deathmatch // No attack against fellow players in coop
3084 && link->player && Friend->player) return false;
3085 if (((flags & link->flags) & MF_FRIENDLY) // No friendly infighting amongst minions
3086 && IsFriend(link)) return false;
3087
3088 // Now that all the actor checks are made, the line of sight can be checked
3089 if (P_CheckSight (this, link))
3090 {
3091 // AMageStaffFX2::IsOkayToAttack had an extra check here, generalized with a flag,
3092 // to only allow the check to succeed if the enemy was in a ~84� FOV of the player
3093 if (flags3 & MF3_SCREENSEEKER)
3094 {
3095 angle_t angle = Friend->AngleTo(link) - Friend->angle;
3096 angle >>= 24;
3097 if (angle>226 || angle<30)
3098 {
3099 return true;
3100 }
3101 }
3102 // Other actors are not concerned by this check
3103 else return true;
3104 }
3105 // The sight check was failed, or the angle wasn't right for a screenseeker
3106 return false;
3107 }
3108
SetShade(DWORD rgb)3109 void AActor::SetShade (DWORD rgb)
3110 {
3111 PalEntry *entry = (PalEntry *)&rgb;
3112 fillcolor = rgb | (ColorMatcher.Pick (entry->r, entry->g, entry->b) << 24);
3113 }
3114
SetShade(int r,int g,int b)3115 void AActor::SetShade (int r, int g, int b)
3116 {
3117 fillcolor = MAKEARGB(ColorMatcher.Pick (r, g, b), r, g, b);
3118 }
3119
SetPitch(int p,bool interpolate,bool forceclamp)3120 void AActor::SetPitch(int p, bool interpolate, bool forceclamp)
3121 {
3122 if (player != NULL || forceclamp)
3123 { // clamp the pitch we set
3124 int min, max;
3125
3126 if (player != NULL)
3127 {
3128 min = player->MinPitch;
3129 max = player->MaxPitch;
3130 }
3131 else
3132 {
3133 min = -ANGLE_90 + (1 << ANGLETOFINESHIFT);
3134 max = ANGLE_90 - (1 << ANGLETOFINESHIFT);
3135 }
3136 p = clamp<int>(p, min, max);
3137 }
3138 if (p != pitch)
3139 {
3140 pitch = p;
3141 if (player != NULL && interpolate)
3142 {
3143 player->cheats |= CF_INTERPVIEW;
3144 }
3145 }
3146 }
3147
SetAngle(angle_t ang,bool interpolate)3148 void AActor::SetAngle(angle_t ang, bool interpolate)
3149 {
3150 if (ang != angle)
3151 {
3152 angle = ang;
3153 if (player != NULL && interpolate)
3154 {
3155 player->cheats |= CF_INTERPVIEW;
3156 }
3157 }
3158 }
3159
SetRoll(angle_t r,bool interpolate)3160 void AActor::SetRoll(angle_t r, bool interpolate)
3161 {
3162 if (r != roll)
3163 {
3164 roll = r;
3165 if (player != NULL && interpolate)
3166 {
3167 player->cheats |= CF_INTERPVIEW;
3168 }
3169 }
3170 }
3171
3172 //
3173 // P_MobjThinker
3174 //
Tick()3175 void AActor::Tick ()
3176 {
3177 // [RH] Data for Heretic/Hexen scrolling sectors
3178 static const BYTE HexenScrollDirs[8] = { 64, 0, 192, 128, 96, 32, 224, 160 };
3179 static const BYTE HexenSpeedMuls[3] = { 5, 10, 25 };
3180 static const SBYTE HexenScrollies[24][2] =
3181 {
3182 { 0, 1 }, { 0, 2 }, { 0, 4 },
3183 { -1, 0 }, { -2, 0 }, { -4, 0 },
3184 { 0, -1 }, { 0, -2 }, { 0, -4 },
3185 { 1, 0 }, { 2, 0 }, { 4, 0 },
3186 { 1, 1 }, { 2, 2 }, { 4, 4 },
3187 { -1, 1 }, { -2, 2 }, { -4, 4 },
3188 { -1, -1 }, { -2, -2 }, { -4, -4 },
3189 { 1, -1 }, { 2, -2 }, { 4, -4 }
3190 };
3191
3192 static const BYTE HereticScrollDirs[4] = { 6, 9, 1, 4 };
3193 static const BYTE HereticSpeedMuls[5] = { 5, 10, 25, 30, 35 };
3194
3195
3196 AActor *onmo;
3197 int i;
3198
3199 //assert (state != NULL);
3200 if (state == NULL)
3201 {
3202 Printf("Actor of type %s at (%f,%f) left without a state\n", GetClass()->TypeName.GetChars(),
3203 X()/65536., Y()/65536.);
3204 Destroy();
3205 return;
3206 }
3207
3208 // This is necessary to properly interpolate movement outside this function
3209 // like from an ActorMover
3210 PrevX = X();
3211 PrevY = Y();
3212 PrevZ = Z();
3213 PrevAngle = angle;
3214
3215 if (flags5 & MF5_NOINTERACTION)
3216 {
3217 // only do the minimally necessary things here to save time:
3218 // Check the time freezer
3219 // apply velocity
3220 // ensure that the actor is not linked into the blockmap
3221
3222 if (!(flags5 & MF5_NOTIMEFREEZE))
3223 {
3224 //Added by MC: Freeze mode.
3225 if (bglobal.freeze || level.flags2 & LEVEL2_FROZEN)
3226 {
3227 // Boss cubes shouldn't be accelerated by timefreeze
3228 if (flags6 & MF6_BOSSCUBE)
3229 {
3230 special2++;
3231 }
3232 return;
3233 }
3234 }
3235
3236 UnlinkFromWorld ();
3237 flags |= MF_NOBLOCKMAP;
3238 SetXYZ(Vec3Offset(velx, vely, velz));
3239 SetMovement(velx, vely, velz);
3240 LinkToWorld ();
3241 }
3242 else
3243 {
3244 AInventory * item = Inventory;
3245
3246 // Handle powerup effects here so that the order is controlled
3247 // by the order in the inventory, not the order in the thinker table
3248 while (item != NULL && item->Owner == this)
3249 {
3250 item->DoEffect();
3251 item = item->Inventory;
3252 }
3253
3254 if (flags & MF_UNMORPHED)
3255 {
3256 return;
3257 }
3258
3259 if (!(flags5 & MF5_NOTIMEFREEZE))
3260 {
3261 // Boss cubes shouldn't be accelerated by timefreeze
3262 if (flags6 & MF6_BOSSCUBE)
3263 {
3264 special2++;
3265 }
3266 //Added by MC: Freeze mode.
3267 if (bglobal.freeze && !(player && player->Bot == NULL))
3268 {
3269 return;
3270 }
3271
3272 // Apply freeze mode.
3273 if ((level.flags2 & LEVEL2_FROZEN) && (player == NULL || player->timefreezer == 0))
3274 {
3275 return;
3276 }
3277 }
3278
3279
3280 if (effects & FX_ROCKET)
3281 {
3282 if (++smokecounter == 4)
3283 {
3284 // add some smoke behind the rocket
3285 smokecounter = 0;
3286 AActor *th = Spawn("RocketSmokeTrail", Vec3Offset(-velx, -vely, -velz), ALLOW_REPLACE);
3287 if (th)
3288 {
3289 th->tics -= pr_rockettrail()&3;
3290 if (th->tics < 1) th->tics = 1;
3291 if (!(cl_rockettrails & 2)) th->renderflags |= RF_INVISIBLE;
3292 }
3293 }
3294 }
3295 else if (effects & FX_GRENADE)
3296 {
3297 if (++smokecounter == 8)
3298 {
3299 smokecounter = 0;
3300 angle_t moveangle = R_PointToAngle2(0,0,velx,vely);
3301 fixed_t xo = -FixedMul(finecosine[(moveangle) >> ANGLETOFINESHIFT], radius * 2) + (pr_rockettrail() << 10);
3302 fixed_t yo = -FixedMul(finesine[(moveangle) >> ANGLETOFINESHIFT], radius * 2) + (pr_rockettrail() << 10);
3303 AActor * th = Spawn("GrenadeSmokeTrail", Vec3Offset(xo, yo, - (height>>3) * (velz>>16) + (2*height)/3), ALLOW_REPLACE);
3304 if (th)
3305 {
3306 th->tics -= pr_rockettrail()&3;
3307 if (th->tics < 1) th->tics = 1;
3308 if (!(cl_rockettrails & 2)) th->renderflags |= RF_INVISIBLE;
3309 }
3310 }
3311 }
3312
3313 fixed_t oldz = Z();
3314
3315 // [RH] Give the pain elemental vertical friction
3316 // This used to be in APainElemental::Tick but in order to use
3317 // A_PainAttack with other monsters it has to be here
3318 if (flags4 & MF4_VFRICTION)
3319 {
3320 if (health >0)
3321 {
3322 if (abs (velz) < FRACUNIT/4)
3323 {
3324 velz = 0;
3325 flags4 &= ~MF4_VFRICTION;
3326 }
3327 else
3328 {
3329 velz = FixedMul (velz, 0xe800);
3330 }
3331 }
3332 }
3333
3334 // [RH] Pulse in and out of visibility
3335 if (effects & FX_VISIBILITYPULSE)
3336 {
3337 if (visdir > 0)
3338 {
3339 alpha += 0x800;
3340 if (alpha >= OPAQUE)
3341 {
3342 alpha = OPAQUE;
3343 visdir = -1;
3344 }
3345 }
3346 else
3347 {
3348 alpha -= 0x800;
3349 if (alpha <= TRANSLUC25)
3350 {
3351 alpha = TRANSLUC25;
3352 visdir = 1;
3353 }
3354 }
3355 }
3356 else if (flags & MF_STEALTH)
3357 {
3358 // [RH] Fade a stealth monster in and out of visibility
3359 RenderStyle.Flags &= ~STYLEF_Alpha1;
3360 if (visdir > 0)
3361 {
3362 alpha += 2*FRACUNIT/TICRATE;
3363 if (alpha > OPAQUE)
3364 {
3365 alpha = OPAQUE;
3366 visdir = 0;
3367 }
3368 }
3369 else if (visdir < 0)
3370 {
3371 alpha -= 3*FRACUNIT/TICRATE/2;
3372 if (alpha < 0)
3373 {
3374 alpha = 0;
3375 visdir = 0;
3376 }
3377 }
3378 }
3379
3380 if (bglobal.botnum && !demoplayback &&
3381 ((flags & (MF_SPECIAL|MF_MISSILE)) || (flags3 & MF3_ISMONSTER)))
3382 {
3383 BotSupportCycles.Clock();
3384 bglobal.m_Thinking = true;
3385 for (i = 0; i < MAXPLAYERS; i++)
3386 {
3387 if (!playeringame[i] || players[i].Bot == NULL)
3388 continue;
3389
3390 if (flags3 & MF3_ISMONSTER)
3391 {
3392 if (health > 0
3393 && !players[i].Bot->enemy
3394 && player ? !IsTeammate (players[i].mo) : true
3395 && AproxDistance (players[i].mo) < MAX_MONSTER_TARGET_DIST
3396 && P_CheckSight (players[i].mo, this, SF_SEEPASTBLOCKEVERYTHING))
3397 { //Probably a monster, so go kill it.
3398 players[i].Bot->enemy = this;
3399 }
3400 }
3401 else if (flags & MF_SPECIAL)
3402 { //Item pickup time
3403 //clock (BotWTG);
3404 players[i].Bot->WhatToGet (this);
3405 //unclock (BotWTG);
3406 BotWTG++;
3407 }
3408 else if (flags & MF_MISSILE)
3409 {
3410 if (!players[i].Bot->missile && (flags3 & MF3_WARNBOT))
3411 { //warn for incoming missiles.
3412 if (target != players[i].mo && players[i].Bot->Check_LOS (this, ANGLE_90))
3413 players[i].Bot->missile = this;
3414 }
3415 }
3416 }
3417 bglobal.m_Thinking = false;
3418 BotSupportCycles.Unclock();
3419 }
3420
3421 //End of MC
3422
3423 // [RH] Consider carrying sectors here
3424 fixed_t cummx = 0, cummy = 0;
3425 if ((level.Scrolls != NULL || player != NULL) && !(flags & MF_NOCLIP) && !(flags & MF_NOSECTOR))
3426 {
3427 fixed_t height, waterheight; // killough 4/4/98: add waterheight
3428 const msecnode_t *node;
3429 int countx, county;
3430
3431 // killough 3/7/98: Carry things on floor
3432 // killough 3/20/98: use new sector list which reflects true members
3433 // killough 3/27/98: fix carrier bug
3434 // killough 4/4/98: Underwater, carry things even w/o gravity
3435
3436 // Move objects only if on floor or underwater,
3437 // non-floating, and clipped.
3438
3439 countx = county = 0;
3440
3441 for (node = touching_sectorlist; node; node = node->m_tnext)
3442 {
3443 const sector_t *sec = node->m_sector;
3444 fixed_t scrollx, scrolly;
3445
3446 if (level.Scrolls != NULL)
3447 {
3448 const FSectorScrollValues *scroll = &level.Scrolls[sec - sectors];
3449 scrollx = scroll->ScrollX;
3450 scrolly = scroll->ScrollY;
3451 }
3452 else
3453 {
3454 scrollx = scrolly = 0;
3455 }
3456
3457 if (player != NULL)
3458 {
3459 int scrolltype = sec->special;
3460
3461 if (scrolltype >= Scroll_North_Slow &&
3462 scrolltype <= Scroll_SouthWest_Fast)
3463 { // Hexen scroll special
3464 scrolltype -= Scroll_North_Slow;
3465 if (i_compatflags&COMPATF_RAVENSCROLL)
3466 {
3467 angle_t fineangle = HexenScrollDirs[scrolltype / 3] * 32;
3468 fixed_t carryspeed = DivScale32 (HexenSpeedMuls[scrolltype % 3], 32*CARRYFACTOR);
3469 scrollx += FixedMul (carryspeed, finecosine[fineangle]);
3470 scrolly += FixedMul (carryspeed, finesine[fineangle]);
3471 }
3472 else
3473 {
3474 // Use speeds that actually match the scrolling textures!
3475 scrollx -= HexenScrollies[scrolltype][0] << (FRACBITS-1);
3476 scrolly += HexenScrollies[scrolltype][1] << (FRACBITS-1);
3477 }
3478 }
3479 else if (scrolltype >= Carry_East5 &&
3480 scrolltype <= Carry_West35)
3481 { // Heretic scroll special
3482 scrolltype -= Carry_East5;
3483 BYTE dir = HereticScrollDirs[scrolltype / 5];
3484 fixed_t carryspeed = DivScale32 (HereticSpeedMuls[scrolltype % 5], 32*CARRYFACTOR);
3485 if (scrolltype<=Carry_East35 && !(i_compatflags&COMPATF_RAVENSCROLL))
3486 {
3487 // Use speeds that actually match the scrolling textures!
3488 carryspeed = (1 << ((scrolltype%5) + FRACBITS-1));
3489 }
3490 scrollx += carryspeed * ((dir & 3) - 1);
3491 scrolly += carryspeed * (((dir & 12) >> 2) - 1);
3492 }
3493 else if (scrolltype == dScroll_EastLavaDamage)
3494 { // Special Heretic scroll special
3495 if (i_compatflags&COMPATF_RAVENSCROLL)
3496 {
3497 scrollx += DivScale32 (28, 32*CARRYFACTOR);
3498 }
3499 else
3500 {
3501 // Use a speed that actually matches the scrolling texture!
3502 scrollx += DivScale32 (12, 32*CARRYFACTOR);
3503 }
3504 }
3505 else if (scrolltype == Scroll_StrifeCurrent)
3506 { // Strife scroll special
3507 int anglespeed = tagManager.GetFirstSectorTag(sec) - 100;
3508 fixed_t carryspeed = DivScale32 (anglespeed % 10, 16*CARRYFACTOR);
3509 angle_t fineangle = (anglespeed / 10) << (32-3);
3510 fineangle >>= ANGLETOFINESHIFT;
3511 scrollx += FixedMul (carryspeed, finecosine[fineangle]);
3512 scrolly += FixedMul (carryspeed, finesine[fineangle]);
3513 }
3514 }
3515
3516 if ((scrollx | scrolly) == 0)
3517 {
3518 continue;
3519 }
3520 sector_t *heightsec = sec->GetHeightSec();
3521 if (flags & MF_NOGRAVITY && heightsec == NULL)
3522 {
3523 continue;
3524 }
3525 height = sec->floorplane.ZatPoint (this);
3526 if (Z() > height)
3527 {
3528 if (heightsec == NULL)
3529 {
3530 continue;
3531 }
3532
3533 waterheight = heightsec->floorplane.ZatPoint (this);
3534 if (waterheight > height && Z() >= waterheight)
3535 {
3536 continue;
3537 }
3538 }
3539
3540 cummx += scrollx;
3541 cummy += scrolly;
3542 if (scrollx) countx++;
3543 if (scrolly) county++;
3544 }
3545
3546 // Some levels designed with Boom in mind actually want things to accelerate
3547 // at neighboring scrolling sector boundaries. But it is only important for
3548 // non-player objects.
3549 if (player != NULL || !(i_compatflags & COMPATF_BOOMSCROLL))
3550 {
3551 if (countx > 1)
3552 {
3553 cummx /= countx;
3554 }
3555 if (county > 1)
3556 {
3557 cummy /= county;
3558 }
3559 }
3560 }
3561
3562 // [RH] If standing on a steep slope, fall down it
3563 if ((flags & MF_SOLID) && !(flags & (MF_NOCLIP|MF_NOGRAVITY)) &&
3564 !(flags & MF_NOBLOCKMAP) &&
3565 velz <= 0 &&
3566 floorz == Z())
3567 {
3568 secplane_t floorplane;
3569
3570 // Check 3D floors as well
3571 floorplane = P_FindFloorPlane(floorsector, X(), Y(), floorz);
3572
3573 if (floorplane.c < STEEPSLOPE &&
3574 floorplane.ZatPoint (this) <= floorz)
3575 {
3576 const msecnode_t *node;
3577 bool dopush = true;
3578
3579 if (floorplane.c > STEEPSLOPE*2/3)
3580 {
3581 for (node = touching_sectorlist; node; node = node->m_tnext)
3582 {
3583 const sector_t *sec = node->m_sector;
3584 if (sec->floorplane.c >= STEEPSLOPE)
3585 {
3586 if (floorplane.ZatPoint (this) >= Z() - MaxStepHeight)
3587 {
3588 dopush = false;
3589 break;
3590 }
3591 }
3592 }
3593 }
3594 if (dopush)
3595 {
3596 velx += floorplane.a;
3597 vely += floorplane.b;
3598 }
3599 }
3600 }
3601
3602 // [RH] Missiles moving perfectly vertical need some X/Y movement, or they
3603 // won't hurt anything. Don't do this if damage is 0! That way, you can
3604 // still have missiles that go straight up and down through actors without
3605 // damaging anything.
3606 if ((flags & MF_MISSILE) && (velx|vely) == 0 && Damage != 0)
3607 {
3608 velx = 1;
3609 }
3610
3611 // Handle X and Y velocities
3612 BlockingMobj = NULL;
3613 fixed_t oldfloorz = P_XYMovement (this, cummx, cummy);
3614 if (ObjectFlags & OF_EuthanizeMe)
3615 { // actor was destroyed
3616 return;
3617 }
3618 if ((velx | vely) == 0) // Actors at rest
3619 {
3620 if (flags2 & MF2_BLASTED)
3621 { // Reset to not blasted when velocities are gone
3622 flags2 &= ~MF2_BLASTED;
3623 }
3624 if ((flags6 & MF6_TOUCHY) && !IsSentient())
3625 { // Arm a mine which has come to rest
3626 flags6 |= MF6_ARMED;
3627 }
3628
3629 }
3630 if (velz || BlockingMobj || Z() != floorz)
3631 { // Handle Z velocity and gravity
3632 if (((flags2 & MF2_PASSMOBJ) || (flags & MF_SPECIAL)) && !(i_compatflags & COMPATF_NO_PASSMOBJ))
3633 {
3634 if (!(onmo = P_CheckOnmobj (this)))
3635 {
3636 P_ZMovement (this, oldfloorz);
3637 flags2 &= ~MF2_ONMOBJ;
3638 }
3639 else
3640 {
3641 if (player)
3642 {
3643 if (velz < (fixed_t)(level.gravity * Sector->gravity * -655.36f)
3644 && !(flags&MF_NOGRAVITY))
3645 {
3646 PlayerLandedOnThing (this, onmo);
3647 }
3648 }
3649 if (onmo->Top() - Z() <= MaxStepHeight)
3650 {
3651 if (player && player->mo == this)
3652 {
3653 player->viewheight -= onmo->Top() - Z();
3654 fixed_t deltaview = player->GetDeltaViewHeight();
3655 if (deltaview > player->deltaviewheight)
3656 {
3657 player->deltaviewheight = deltaview;
3658 }
3659 }
3660 SetZ(onmo->Top());
3661 }
3662 // Check for MF6_BUMPSPECIAL
3663 // By default, only players can activate things by bumping into them
3664 // We trigger specials as long as we are on top of it and not just when
3665 // we land on it. This could be considered as gravity making us continually
3666 // bump into it, but it also avoids having to worry about walking on to
3667 // something without dropping and not triggering anything.
3668 if ((onmo->flags6 & MF6_BUMPSPECIAL) && ((player != NULL)
3669 || ((onmo->activationtype & THINGSPEC_MonsterTrigger) && (flags3 & MF3_ISMONSTER))
3670 || ((onmo->activationtype & THINGSPEC_MissileTrigger) && (flags & MF_MISSILE))
3671 ) && (level.maptime > onmo->lastbump)) // Leave the bumper enough time to go away
3672 {
3673 if (player == NULL || !(player->cheats & CF_PREDICTING))
3674 {
3675 if (P_ActivateThingSpecial(onmo, this))
3676 onmo->lastbump = level.maptime + TICRATE;
3677 }
3678 }
3679 if (velz != 0 && (BounceFlags & BOUNCE_Actors))
3680 {
3681 P_BounceActor(this, onmo, true);
3682 }
3683 else
3684 {
3685 flags2 |= MF2_ONMOBJ;
3686 velz = 0;
3687 Crash();
3688 }
3689 }
3690 }
3691 else
3692 {
3693 P_ZMovement (this, oldfloorz);
3694 }
3695
3696 if (ObjectFlags & OF_EuthanizeMe)
3697 return; // actor was destroyed
3698 }
3699 else if (Z() <= floorz)
3700 {
3701 Crash();
3702 }
3703
3704 UpdateWaterLevel (oldz);
3705
3706 // [RH] Don't advance if predicting a player
3707 if (player && (player->cheats & CF_PREDICTING))
3708 {
3709 return;
3710 }
3711
3712 // Check for poison damage, but only once per PoisonPeriod tics (or once per second if none).
3713 if (PoisonDurationReceived && (level.time % (PoisonPeriodReceived ? PoisonPeriodReceived : TICRATE) == 0))
3714 {
3715 P_DamageMobj(this, NULL, Poisoner, PoisonDamageReceived, PoisonDamageTypeReceived ? PoisonDamageTypeReceived : (FName)NAME_Poison, 0);
3716
3717 --PoisonDurationReceived;
3718
3719 // Must clear damage when duration is done, otherwise it
3720 // could be added to with ADDITIVEPOISONDAMAGE.
3721 if (!PoisonDurationReceived) PoisonDamageReceived = 0;
3722 }
3723 }
3724
3725 assert (state != NULL);
3726 if (state == NULL)
3727 {
3728 Destroy();
3729 return;
3730 }
3731 if ((flags7 & MF7_HANDLENODELAY) && !(flags2 & MF2_DORMANT))
3732 {
3733 flags7 &= ~MF7_HANDLENODELAY;
3734 if (state->GetNoDelay())
3735 {
3736 // For immediately spawned objects with the NoDelay flag set for their
3737 // Spawn state, explicitly call the current state's function.
3738 if (state->CallAction(this, this) && (ObjectFlags & OF_EuthanizeMe))
3739 return; // freed itself
3740 }
3741 }
3742 // cycle through states, calling action functions at transitions
3743 if (tics != -1)
3744 {
3745 // [RH] Use tics <= 0 instead of == 0 so that spawnstates
3746 // of 0 tics work as expected.
3747 if (--tics <= 0)
3748 {
3749 if (!SetState(state->GetNextState()))
3750 return; // freed itself
3751 }
3752 }
3753 else
3754 {
3755 int respawn_monsters = G_SkillProperty(SKILLP_Respawn);
3756 // check for nightmare respawn
3757 if (!(flags5 & MF5_ALWAYSRESPAWN))
3758 {
3759 if (!respawn_monsters || !(flags3 & MF3_ISMONSTER) || (flags2 & MF2_DORMANT) || (flags5 & MF5_NEVERRESPAWN))
3760 return;
3761
3762 int limit = G_SkillProperty (SKILLP_RespawnLimit);
3763 if (limit > 0 && skillrespawncount >= limit)
3764 return;
3765 }
3766
3767 movecount++;
3768
3769 if (movecount < respawn_monsters)
3770 return;
3771
3772 if (level.time & 31)
3773 return;
3774
3775 if (pr_nightmarerespawn() > 4)
3776 return;
3777
3778 P_NightmareRespawn (this);
3779 }
3780 }
3781
3782 //==========================================================================
3783 //
3784 // AActor :: CheckSectorTransition
3785 //
3786 // Fire off some sector triggers if the actor has changed sectors.
3787 //
3788 //==========================================================================
3789
CheckSectorTransition(sector_t * oldsec)3790 void AActor::CheckSectorTransition(sector_t *oldsec)
3791 {
3792 if (oldsec != Sector)
3793 {
3794 if (oldsec->SecActTarget != NULL)
3795 {
3796 oldsec->SecActTarget->TriggerAction(this, SECSPAC_Exit);
3797 }
3798 if (Sector->SecActTarget != NULL)
3799 {
3800 int act = SECSPAC_Enter;
3801 if (Z() <= Sector->floorplane.ZatPoint(this))
3802 {
3803 act |= SECSPAC_HitFloor;
3804 }
3805 if (Z() + height >= Sector->ceilingplane.ZatPoint(this))
3806 {
3807 act |= SECSPAC_HitCeiling;
3808 }
3809 if (Sector->heightsec != NULL && Z() == Sector->heightsec->floorplane.ZatPoint(this))
3810 {
3811 act |= SECSPAC_HitFakeFloor;
3812 }
3813 Sector->SecActTarget->TriggerAction(this, act);
3814 }
3815 if (Z() == floorz)
3816 {
3817 P_CheckFor3DFloorHit(this);
3818 }
3819 if (Top() == ceilingz)
3820 {
3821 P_CheckFor3DCeilingHit(this);
3822 }
3823 }
3824 }
3825
3826 //==========================================================================
3827 //
3828 // AActor::UpdateWaterLevel
3829 //
3830 // Returns true if actor should splash
3831 //
3832 //==========================================================================
3833
UpdateWaterLevel(fixed_t oldz,bool dosplash)3834 bool AActor::UpdateWaterLevel (fixed_t oldz, bool dosplash)
3835 {
3836 BYTE lastwaterlevel = waterlevel;
3837 fixed_t fh = FIXED_MIN;
3838 bool reset=false;
3839
3840 waterlevel = 0;
3841
3842 if (Sector == NULL)
3843 {
3844 return false;
3845 }
3846
3847 if (Sector->MoreFlags & SECF_UNDERWATER) // intentionally not SECF_UNDERWATERMASK
3848 {
3849 waterlevel = 3;
3850 }
3851 else
3852 {
3853 const sector_t *hsec = Sector->GetHeightSec();
3854 if (hsec != NULL)
3855 {
3856 fh = hsec->floorplane.ZatPoint (this);
3857 //if (hsec->MoreFlags & SECF_UNDERWATERMASK) // also check Boom-style non-swimmable sectors
3858 {
3859 if (Z() < fh)
3860 {
3861 waterlevel = 1;
3862 if (Z() + height/2 < fh)
3863 {
3864 waterlevel = 2;
3865 if ((player && Z() + player->viewheight <= fh) ||
3866 (Z() + height <= fh))
3867 {
3868 waterlevel = 3;
3869 }
3870 }
3871 }
3872 else if (!(hsec->MoreFlags & SECF_FAKEFLOORONLY) && (Top() > hsec->ceilingplane.ZatPoint (this)))
3873 {
3874 waterlevel = 3;
3875 }
3876 else
3877 {
3878 waterlevel = 0;
3879 }
3880 }
3881 // even non-swimmable deep water must be checked here to do the splashes correctly
3882 // But the water level must be reset when this function returns
3883 if (!(hsec->MoreFlags&SECF_UNDERWATERMASK))
3884 {
3885 reset = true;
3886 }
3887 }
3888 else
3889 {
3890 // Check 3D floors as well!
3891 for(unsigned int i=0;i<Sector->e->XFloor.ffloors.Size();i++)
3892 {
3893 F3DFloor* rover=Sector->e->XFloor.ffloors[i];
3894
3895 if (!(rover->flags & FF_EXISTS)) continue;
3896 if(!(rover->flags & FF_SWIMMABLE) || rover->flags & FF_SOLID) continue;
3897
3898 fixed_t ff_bottom=rover->bottom.plane->ZatPoint(this);
3899 fixed_t ff_top=rover->top.plane->ZatPoint(this);
3900
3901 if(ff_top <= Z() || ff_bottom > (Z() + (height >> 1))) continue;
3902
3903 fh=ff_top;
3904 if (Z() < fh)
3905 {
3906 waterlevel = 1;
3907 if (Z() + height/2 < fh)
3908 {
3909 waterlevel = 2;
3910 if ((player && Z() + player->viewheight <= fh) ||
3911 (Z() + height <= fh))
3912 {
3913 waterlevel = 3;
3914 }
3915 }
3916 }
3917
3918 break;
3919 }
3920 }
3921 }
3922
3923 // some additional checks to make deep sectors like Boom's splash without setting
3924 // the water flags.
3925 if (boomwaterlevel == 0 && waterlevel != 0 && dosplash)
3926 {
3927 P_HitWater(this, Sector, FIXED_MIN, FIXED_MIN, fh, true);
3928 }
3929 boomwaterlevel = waterlevel;
3930 if (reset)
3931 {
3932 waterlevel = lastwaterlevel;
3933 }
3934 return false; // we did the splash ourselves
3935 }
3936
3937
3938 //==========================================================================
3939 //
3940 // P_SpawnMobj
3941 //
3942 //==========================================================================
3943
StaticSpawn(const PClass * type,fixed_t ix,fixed_t iy,fixed_t iz,replace_t allowreplacement,bool SpawningMapThing)3944 AActor *AActor::StaticSpawn (const PClass *type, fixed_t ix, fixed_t iy, fixed_t iz, replace_t allowreplacement, bool SpawningMapThing)
3945 {
3946 if (type == NULL)
3947 {
3948 I_Error ("Tried to spawn a class-less actor\n");
3949 }
3950
3951 if (type->ActorInfo == NULL)
3952 {
3953 I_Error ("%s is not an actor\n", type->TypeName.GetChars());
3954 }
3955
3956 if (allowreplacement)
3957 type = type->GetReplacement();
3958
3959
3960 AActor *actor;
3961
3962 actor = static_cast<AActor *>(const_cast<PClass *>(type)->CreateNew ());
3963
3964 // Set default dialogue
3965 actor->ConversationRoot = GetConversation(actor->GetClass()->TypeName);
3966 if (actor->ConversationRoot != -1)
3967 {
3968 actor->Conversation = StrifeDialogues[actor->ConversationRoot];
3969 }
3970 else
3971 {
3972 actor->Conversation = NULL;
3973 }
3974
3975 actor->PrevX = ix;
3976 actor->PrevY = iy;
3977 actor->PrevZ = iz;
3978 actor->SetXYZ(ix, iy, iz);
3979 actor->picnum.SetInvalid();
3980 actor->health = actor->SpawnHealth();
3981
3982 // Actors with zero gravity need the NOGRAVITY flag set.
3983 if (actor->gravity == 0) actor->flags |= MF_NOGRAVITY;
3984
3985 FRandom &rng = bglobal.m_Thinking ? pr_botspawnmobj : pr_spawnmobj;
3986
3987 if (actor->isFast() && actor->flags3 & MF3_ISMONSTER)
3988 actor->reactiontime = 0;
3989
3990 if (actor->flags3 & MF3_ISMONSTER)
3991 {
3992 actor->LastLookPlayerNumber = rng() % MAXPLAYERS;
3993 actor->TIDtoHate = 0;
3994 }
3995
3996 // Set the state, but do not use SetState, because action
3997 // routines can't be called yet. If the spawnstate has an action
3998 // routine, it will not be called.
3999 FState *st = actor->SpawnState;
4000 actor->state = st;
4001 actor->tics = st->GetTics();
4002
4003 actor->sprite = st->sprite;
4004 actor->frame = st->GetFrame();
4005 actor->renderflags = (actor->renderflags & ~RF_FULLBRIGHT) | ActorRenderFlags::FromInt (st->GetFullbright());
4006 actor->touching_sectorlist = NULL; // NULL head of sector list // phares 3/13/98
4007 if (G_SkillProperty(SKILLP_FastMonsters))
4008 actor->Speed = actor->GetClass()->Meta.GetMetaFixed(AMETA_FastSpeed, actor->Speed);
4009 actor->DamageMultiply = FRACUNIT;
4010
4011
4012 // set subsector and/or block links
4013 actor->LinkToWorld (SpawningMapThing);
4014
4015 actor->dropoffz = // killough 11/98: for tracking dropoffs
4016 actor->floorz = actor->Sector->floorplane.ZatPoint (ix, iy);
4017 actor->ceilingz = actor->Sector->ceilingplane.ZatPoint (ix, iy);
4018
4019 // The z-coordinate needs to be set once before calling P_FindFloorCeiling
4020 // For FLOATRANDZ just use the floor here.
4021 if (iz == ONFLOORZ || iz == FLOATRANDZ)
4022 {
4023 actor->SetZ(actor->floorz, false);
4024 }
4025 else if (iz == ONCEILINGZ)
4026 {
4027 actor->SetZ(actor->ceilingz - actor->height);
4028 }
4029
4030 if (SpawningMapThing || !type->IsDescendantOf (RUNTIME_CLASS(APlayerPawn)))
4031 {
4032 // Check if there's something solid to stand on between the current position and the
4033 // current sector's floor. For map spawns this must be delayed until after setting the
4034 // z-coordinate.
4035 if (!SpawningMapThing)
4036 {
4037 P_FindFloorCeiling(actor, FFCF_ONLYSPAWNPOS);
4038 }
4039 else
4040 {
4041 actor->floorsector = actor->Sector;
4042 actor->floorpic = actor->floorsector->GetTexture(sector_t::floor);
4043 actor->floorterrain = actor->floorsector->GetTerrain(sector_t::floor);
4044 actor->ceilingsector = actor->Sector;
4045 actor->ceilingpic = actor->ceilingsector->GetTexture(sector_t::ceiling);
4046 }
4047 }
4048 else if (!(actor->flags5 & MF5_NOINTERACTION))
4049 {
4050 P_FindFloorCeiling (actor);
4051 }
4052 else
4053 {
4054 actor->floorpic = actor->Sector->GetTexture(sector_t::floor);
4055 actor->floorterrain = actor->Sector->GetTerrain(sector_t::floor);
4056 actor->floorsector = actor->Sector;
4057 actor->ceilingpic = actor->Sector->GetTexture(sector_t::ceiling);
4058 actor->ceilingsector = actor->Sector;
4059 }
4060
4061 actor->SpawnPoint[0] = ix;
4062 actor->SpawnPoint[1] = iy;
4063
4064 if (iz == ONFLOORZ)
4065 {
4066 actor->SetZ(actor->floorz);
4067 }
4068 else if (iz == ONCEILINGZ)
4069 {
4070 actor->SetZ(actor->ceilingz - actor->height);
4071 }
4072 else if (iz == FLOATRANDZ)
4073 {
4074 fixed_t space = actor->ceilingz - actor->height - actor->floorz;
4075 if (space > 48*FRACUNIT)
4076 {
4077 space -= 40*FRACUNIT;
4078 actor->SetZ(MulScale8 (space, rng()) + actor->floorz + 40*FRACUNIT);
4079 }
4080 else
4081 {
4082 actor->SetZ(actor->floorz);
4083 }
4084 }
4085 else
4086 {
4087 actor->SpawnPoint[2] = (actor->Z() - actor->Sector->floorplane.ZatPoint(actor));
4088 }
4089
4090 if (actor->FloatBobPhase == (BYTE)-1) actor->FloatBobPhase = rng(); // Don't make everything bob in sync (unless deliberately told to do)
4091 if (actor->flags2 & MF2_FLOORCLIP)
4092 {
4093 actor->AdjustFloorClip ();
4094 }
4095 else
4096 {
4097 actor->floorclip = 0;
4098 }
4099 actor->UpdateWaterLevel (actor->Z(), false);
4100 if (!SpawningMapThing)
4101 {
4102 actor->BeginPlay ();
4103 if (actor->ObjectFlags & OF_EuthanizeMe)
4104 {
4105 return NULL;
4106 }
4107 }
4108 if (level.flags & LEVEL_NOALLIES && !actor->player)
4109 {
4110 actor->flags &= ~MF_FRIENDLY;
4111 }
4112 // [RH] Count monsters whenever they are spawned.
4113 if (actor->CountsAsKill())
4114 {
4115 level.total_monsters++;
4116 }
4117 // [RH] Same, for items
4118 if (actor->flags & MF_COUNTITEM)
4119 {
4120 level.total_items++;
4121 }
4122 // And for secrets
4123 if (actor->flags5 & MF5_COUNTSECRET)
4124 {
4125 level.total_secrets++;
4126 }
4127 return actor;
4128 }
4129
Spawn(const char * type,fixed_t x,fixed_t y,fixed_t z,replace_t allowreplacement)4130 AActor *Spawn (const char *type, fixed_t x, fixed_t y, fixed_t z, replace_t allowreplacement)
4131 {
4132 FName classname(type, true);
4133 if (classname == NAME_None)
4134 {
4135 I_Error("Attempt to spawn actor of unknown type '%s'\n", type);
4136 }
4137 return Spawn(classname, x, y, z, allowreplacement);
4138 }
4139
Spawn(FName classname,fixed_t x,fixed_t y,fixed_t z,replace_t allowreplacement)4140 AActor *Spawn (FName classname, fixed_t x, fixed_t y, fixed_t z, replace_t allowreplacement)
4141 {
4142 const PClass *cls = PClass::FindClass(classname);
4143 if (cls == NULL)
4144 {
4145 I_Error("Attempt to spawn actor of unknown type '%s'\n", classname.GetChars());
4146 }
4147 return AActor::StaticSpawn (cls, x, y, z, allowreplacement);
4148 }
4149
LevelSpawned()4150 void AActor::LevelSpawned ()
4151 {
4152 if (tics > 0 && !(flags4 & MF4_SYNCHRONIZED))
4153 {
4154 tics = 1 + (pr_spawnmapthing() % tics);
4155 }
4156 // [RH] Clear MF_DROPPED flag if the default version doesn't have it set.
4157 // (AInventory::BeginPlay() makes all inventory items spawn with it set.)
4158 if (!(GetDefault()->flags & MF_DROPPED))
4159 {
4160 flags &= ~MF_DROPPED;
4161 }
4162 HandleSpawnFlags ();
4163 }
4164
HandleSpawnFlags()4165 void AActor::HandleSpawnFlags ()
4166 {
4167 if (SpawnFlags & MTF_AMBUSH)
4168 {
4169 flags |= MF_AMBUSH;
4170 }
4171 if (SpawnFlags & MTF_DORMANT)
4172 {
4173 Deactivate (NULL);
4174 }
4175 if (SpawnFlags & MTF_STANDSTILL)
4176 {
4177 flags4 |= MF4_STANDSTILL;
4178 }
4179 if (SpawnFlags & MTF_FRIENDLY)
4180 {
4181 flags |= MF_FRIENDLY;
4182 // Friendlies don't count as kills!
4183 if (flags & MF_COUNTKILL)
4184 {
4185 flags &= ~MF_COUNTKILL;
4186 level.total_monsters--;
4187 }
4188 }
4189 if (SpawnFlags & MTF_SHADOW)
4190 {
4191 flags |= MF_SHADOW;
4192 RenderStyle = STYLE_Translucent;
4193 alpha = TRANSLUC25;
4194 }
4195 else if (SpawnFlags & MTF_ALTSHADOW)
4196 {
4197 RenderStyle = STYLE_None;
4198 }
4199 if (SpawnFlags & MTF_SECRET)
4200 {
4201 if (!(flags5 & MF5_COUNTSECRET))
4202 {
4203 //Printf("Secret %s in sector %i!\n", GetTag(), Sector->sectornum);
4204 flags5 |= MF5_COUNTSECRET;
4205 level.total_secrets++;
4206 }
4207 }
4208 }
4209
BeginPlay()4210 void AActor::BeginPlay ()
4211 {
4212 // If the actor is spawned with the dormant flag set, clear it, and use
4213 // the normal deactivation logic to make it properly dormant.
4214 if (flags2 & MF2_DORMANT)
4215 {
4216 flags2 &= ~MF2_DORMANT;
4217 Deactivate (NULL);
4218 }
4219 }
4220
PostBeginPlay()4221 void AActor::PostBeginPlay ()
4222 {
4223 if (Renderer != NULL)
4224 {
4225 Renderer->StateChanged(this);
4226 }
4227 PrevAngle = angle;
4228 flags7 |= MF7_HANDLENODELAY;
4229 }
4230
MarkPrecacheSounds() const4231 void AActor::MarkPrecacheSounds() const
4232 {
4233 SeeSound.MarkUsed();
4234 AttackSound.MarkUsed();
4235 PainSound.MarkUsed();
4236 DeathSound.MarkUsed();
4237 ActiveSound.MarkUsed();
4238 UseSound.MarkUsed();
4239 BounceSound.MarkUsed();
4240 WallBounceSound.MarkUsed();
4241 CrushPainSound.MarkUsed();
4242 }
4243
isFast()4244 bool AActor::isFast()
4245 {
4246 if (flags5&MF5_ALWAYSFAST) return true;
4247 if (flags5&MF5_NEVERFAST) return false;
4248 return !!G_SkillProperty(SKILLP_FastMonsters);
4249 }
4250
isSlow()4251 bool AActor::isSlow()
4252 {
4253 return !!G_SkillProperty(SKILLP_SlowMonsters);
4254 }
4255
Activate(AActor * activator)4256 void AActor::Activate (AActor *activator)
4257 {
4258 if ((flags3 & MF3_ISMONSTER) && (health > 0 || (flags & MF_ICECORPSE)))
4259 {
4260 if (flags2 & MF2_DORMANT)
4261 {
4262 flags2 &= ~MF2_DORMANT;
4263 FState *state = FindState(NAME_Active);
4264 if (state != NULL)
4265 {
4266 SetState(state);
4267 }
4268 else
4269 {
4270 tics = 1;
4271 }
4272 }
4273 }
4274 }
4275
Deactivate(AActor * activator)4276 void AActor::Deactivate (AActor *activator)
4277 {
4278 if ((flags3 & MF3_ISMONSTER) && (health > 0 || (flags & MF_ICECORPSE)))
4279 {
4280 if (!(flags2 & MF2_DORMANT))
4281 {
4282 flags2 |= MF2_DORMANT;
4283 FState *state = FindState(NAME_Inactive);
4284 if (state != NULL)
4285 {
4286 SetState(state);
4287 }
4288 else
4289 {
4290 tics = -1;
4291 }
4292 }
4293 }
4294 }
4295
4296 //
4297 // P_RemoveMobj
4298 //
4299
Destroy()4300 void AActor::Destroy ()
4301 {
4302 // [RH] Destroy any inventory this actor is carrying
4303 DestroyAllInventory ();
4304
4305 // [RH] Unlink from tid chain
4306 RemoveFromHash ();
4307
4308 // unlink from sector and block lists
4309 UnlinkFromWorld ();
4310 flags |= MF_NOSECTOR|MF_NOBLOCKMAP;
4311
4312 // Delete all nodes on the current sector_list phares 3/16/98
4313 P_DelSector_List();
4314
4315 // Transform any playing sound into positioned, non-actor sounds.
4316 S_RelinkSound (this, NULL);
4317
4318 Super::Destroy ();
4319 }
4320
4321 //===========================================================================
4322 //
4323 // AdjustFloorClip
4324 //
4325 //===========================================================================
4326
AdjustFloorClip()4327 void AActor::AdjustFloorClip ()
4328 {
4329 if (flags3 & MF3_SPECIALFLOORCLIP)
4330 {
4331 return;
4332 }
4333
4334 fixed_t oldclip = floorclip;
4335 fixed_t shallowestclip = FIXED_MAX;
4336 const msecnode_t *m;
4337
4338 // possibly standing on a 3D-floor
4339 if (Sector->e->XFloor.ffloors.Size() && Z()>Sector->floorplane.ZatPoint(this)) floorclip=0;
4340
4341 // [RH] clip based on shallowest floor player is standing on
4342 // If the sector has a deep water effect, then let that effect
4343 // do the floorclipping instead of the terrain type.
4344 for (m = touching_sectorlist; m; m = m->m_tnext)
4345 {
4346 sector_t *hsec = m->m_sector->GetHeightSec();
4347 if (hsec == NULL && m->m_sector->floorplane.ZatPoint (this) == Z())
4348 {
4349 fixed_t clip = Terrains[m->m_sector->GetTerrain(sector_t::floor)].FootClip;
4350 if (clip < shallowestclip)
4351 {
4352 shallowestclip = clip;
4353 }
4354 }
4355 }
4356 if (shallowestclip == FIXED_MAX)
4357 {
4358 floorclip = 0;
4359 }
4360 else
4361 {
4362 floorclip = shallowestclip;
4363 }
4364 if (player && player->mo == this && oldclip != floorclip)
4365 {
4366 player->viewheight -= oldclip - floorclip;
4367 player->deltaviewheight = player->GetDeltaViewHeight();
4368 }
4369 }
4370
4371 //
4372 // P_SpawnPlayer
4373 // Called when a player is spawned on the level.
4374 // Most of the player structure stays unchanged between levels.
4375 //
4376 EXTERN_CVAR (Bool, chasedemo)
4377
4378 extern bool demonew;
4379
P_SpawnPlayer(FPlayerStart * mthing,int playernum,int flags)4380 APlayerPawn *P_SpawnPlayer (FPlayerStart *mthing, int playernum, int flags)
4381 {
4382 player_t *p;
4383 APlayerPawn *mobj, *oldactor;
4384 BYTE state;
4385 fixed_t spawn_x, spawn_y, spawn_z;
4386 angle_t spawn_angle;
4387
4388 // not playing?
4389 if ((unsigned)playernum >= (unsigned)MAXPLAYERS || !playeringame[playernum])
4390 return NULL;
4391
4392 // Old lerp data needs to go
4393 if (playernum == consoleplayer)
4394 {
4395 P_PredictionLerpReset();
4396 }
4397
4398 p = &players[playernum];
4399
4400 if (p->cls == NULL)
4401 {
4402 // [GRB] Pick a class from player class list
4403 if (PlayerClasses.Size () > 1)
4404 {
4405 int type;
4406
4407 if (!deathmatch || !multiplayer)
4408 {
4409 type = SinglePlayerClass[playernum];
4410 }
4411 else
4412 {
4413 type = p->userinfo.GetPlayerClassNum();
4414 if (type < 0)
4415 {
4416 type = pr_multiclasschoice() % PlayerClasses.Size ();
4417 }
4418 }
4419 p->CurrentPlayerClass = type;
4420 }
4421 else
4422 {
4423 p->CurrentPlayerClass = 0;
4424 }
4425 p->cls = PlayerClasses[p->CurrentPlayerClass].Type;
4426 }
4427
4428 if (( dmflags2 & DF2_SAME_SPAWN_SPOT ) &&
4429 ( p->playerstate == PST_REBORN ) &&
4430 ( deathmatch == false ) &&
4431 ( gameaction != ga_worlddone ) &&
4432 ( p->mo != NULL ) &&
4433 ( !(p->mo->Sector->Flags & SECF_NORESPAWN) ) &&
4434 ( NULL != p->attacker ) && // don't respawn on damaging floors
4435 ( p->mo->Sector->damageamount < TELEFRAG_DAMAGE )) // this really should be a bit smarter...
4436 {
4437 spawn_x = p->mo->X();
4438 spawn_y = p->mo->Y();
4439 spawn_z = p->mo->Z();
4440
4441 spawn_angle = p->mo->angle;
4442 }
4443 else
4444 {
4445 spawn_x = mthing->x;
4446 spawn_y = mthing->y;
4447
4448 // Allow full angular precision but avoid roundoff errors for multiples of 45 degrees.
4449 if (mthing->angle % 45 != 0)
4450 {
4451 spawn_angle = mthing->angle * (ANG45 / 45);
4452 }
4453 else
4454 {
4455 spawn_angle = ANG45 * (mthing->angle / 45);
4456 }
4457 if (i_compatflags2 & COMPATF2_BADANGLES)
4458 {
4459 spawn_angle += 1 << ANGLETOFINESHIFT;
4460 }
4461
4462 if (GetDefaultByType(p->cls)->flags & MF_SPAWNCEILING)
4463 spawn_z = ONCEILINGZ;
4464 else if (GetDefaultByType(p->cls)->flags2 & MF2_SPAWNFLOAT)
4465 spawn_z = FLOATRANDZ;
4466 else
4467 spawn_z = ONFLOORZ;
4468 }
4469
4470 mobj = static_cast<APlayerPawn *>
4471 (Spawn (p->cls, spawn_x, spawn_y, spawn_z, NO_REPLACE));
4472
4473 if (level.flags & LEVEL_USEPLAYERSTARTZ)
4474 {
4475 if (spawn_z == ONFLOORZ)
4476 mobj->AddZ(mthing->z);
4477 else if (spawn_z == ONCEILINGZ)
4478 mobj->AddZ(-mthing->z);
4479 P_FindFloorCeiling(mobj, FFCF_SAMESECTOR | FFCF_ONLY3DFLOORS | FFCF_3DRESTRICT);
4480 }
4481
4482 mobj->FriendPlayer = playernum + 1; // [RH] players are their own friends
4483 oldactor = p->mo;
4484 p->mo = mobj;
4485 mobj->player = p;
4486 state = p->playerstate;
4487 if (state == PST_REBORN || state == PST_ENTER)
4488 {
4489 G_PlayerReborn (playernum);
4490 }
4491 else if (oldactor != NULL && oldactor->player == p && !(flags & SPF_TEMPPLAYER))
4492 {
4493 // Move the voodoo doll's inventory to the new player.
4494 mobj->ObtainInventory (oldactor);
4495 FBehavior::StaticStopMyScripts (oldactor); // cancel all ENTER/RESPAWN scripts for the voodoo doll
4496 }
4497
4498 // [GRB] Reset skin
4499 p->userinfo.SkinNumChanged(R_FindSkin (skins[p->userinfo.GetSkin()].name, p->CurrentPlayerClass));
4500
4501 if (!(mobj->flags2 & MF2_DONTTRANSLATE))
4502 {
4503 // [RH] Be sure the player has the right translation
4504 R_BuildPlayerTranslation (playernum);
4505
4506 // [RH] set color translations for player sprites
4507 mobj->Translation = TRANSLATION(TRANSLATION_Players,playernum);
4508 }
4509
4510 mobj->angle = spawn_angle;
4511 mobj->pitch = mobj->roll = 0;
4512 mobj->health = p->health;
4513
4514 // [RH] Set player sprite based on skin
4515 if (!(mobj->flags4 & MF4_NOSKIN))
4516 {
4517 mobj->sprite = skins[p->userinfo.GetSkin()].sprite;
4518 }
4519
4520 p->DesiredFOV = p->FOV = 90.f;
4521 p->camera = p->mo;
4522 p->playerstate = PST_LIVE;
4523 p->refire = 0;
4524 p->damagecount = 0;
4525 p->bonuscount = 0;
4526 p->morphTics = 0;
4527 p->MorphedPlayerClass = 0;
4528 p->MorphStyle = 0;
4529 p->MorphExitFlash = NULL;
4530 p->extralight = 0;
4531 p->fixedcolormap = NOFIXEDCOLORMAP;
4532 p->fixedlightlevel = -1;
4533 p->viewheight = mobj->ViewHeight;
4534 p->inconsistant = 0;
4535 p->attacker = NULL;
4536 p->spreecount = 0;
4537 p->multicount = 0;
4538 p->lastkilltime = 0;
4539 p->BlendR = p->BlendG = p->BlendB = p->BlendA = 0.f;
4540 p->mo->ResetAirSupply(false);
4541 p->Uncrouch();
4542 p->MinPitch = p->MaxPitch = 0; // will be filled in by PostBeginPlay()/netcode
4543 p->MUSINFOactor = NULL;
4544 p->MUSINFOtics = -1;
4545
4546 p->velx = p->vely = 0; // killough 10/98: initialize bobbing to 0.
4547
4548 for (int ii = 0; ii < MAXPLAYERS; ++ii)
4549 {
4550 if (playeringame[ii] && players[ii].camera == oldactor)
4551 {
4552 players[ii].camera = mobj;
4553 }
4554 }
4555
4556 // [RH] Allow chasecam for demo watching
4557 if ((demoplayback || demonew) && chasedemo)
4558 p->cheats = CF_CHASECAM;
4559
4560 // setup gun psprite
4561 if (!(flags & SPF_TEMPPLAYER))
4562 { // This can also start a script so don't do it for the dummy player.
4563 P_SetupPsprites (p, !!(flags & SPF_WEAPONFULLYUP));
4564 }
4565
4566 if (deathmatch)
4567 { // Give all cards in death match mode.
4568 p->mo->GiveDeathmatchInventory ();
4569 }
4570 else if ((multiplayer || (level.flags2 & LEVEL2_ALLOWRESPAWN)) && state == PST_REBORN && oldactor != NULL)
4571 { // Special inventory handling for respawning in coop
4572 p->mo->FilterCoopRespawnInventory (oldactor);
4573 }
4574 if (oldactor != NULL)
4575 { // Remove any inventory left from the old actor. Coop handles
4576 // it above, but the other modes don't.
4577 oldactor->DestroyAllInventory();
4578 }
4579 // [BC] Handle temporary invulnerability when respawned
4580 if ((state == PST_REBORN || state == PST_ENTER) &&
4581 (dmflags2 & DF2_YES_RESPAWN_INVUL) &&
4582 (multiplayer || alwaysapplydmflags))
4583 {
4584 APowerup *invul = static_cast<APowerup*>(p->mo->GiveInventoryType (RUNTIME_CLASS(APowerInvulnerable)));
4585 invul->EffectTics = 3*TICRATE;
4586 invul->BlendColor = 0; // don't mess with the view
4587 invul->ItemFlags |= IF_UNDROPPABLE; // Don't drop this
4588 p->mo->effects |= FX_RESPAWNINVUL; // [RH] special effect
4589 }
4590
4591 if (StatusBar != NULL && (playernum == consoleplayer || StatusBar->GetPlayer() == playernum))
4592 {
4593 StatusBar->AttachToPlayer (p);
4594 }
4595
4596 if (multiplayer)
4597 {
4598 unsigned an = mobj->angle >> ANGLETOFINESHIFT;
4599 Spawn ("TeleportFog", mobj->Vec3Offset(20*finecosine[an], 20*finesine[an], TELEFOGHEIGHT), ALLOW_REPLACE);
4600 }
4601
4602 // "Fix" for one of the starts on exec.wad MAP01: If you start inside the ceiling,
4603 // drop down below it, even if that means sinking into the floor.
4604 if (mobj->Top() > mobj->ceilingz)
4605 {
4606 mobj->SetZ(mobj->ceilingz - mobj->height, false);
4607 }
4608
4609 // [BC] Do script stuff
4610 if (!(flags & SPF_TEMPPLAYER))
4611 {
4612 if (state == PST_ENTER || (state == PST_LIVE && !savegamerestore))
4613 {
4614 FBehavior::StaticStartTypedScripts (SCRIPT_Enter, p->mo, true);
4615 }
4616 else if (state == PST_REBORN)
4617 {
4618 assert (oldactor != NULL);
4619
4620 // before relocating all pointers to the player all sound targets
4621 // pointing to the old actor have to be NULLed. Otherwise all
4622 // monsters who last targeted this player will wake up immediately
4623 // after the player has respawned.
4624 AActor *th;
4625 TThinkerIterator<AActor> it;
4626 while ((th = it.Next()))
4627 {
4628 if (th->LastHeard == oldactor) th->LastHeard = NULL;
4629 }
4630 for(int i = 0; i < numsectors; i++)
4631 {
4632 if (sectors[i].SoundTarget == oldactor) sectors[i].SoundTarget = NULL;
4633 }
4634
4635 DObject::StaticPointerSubstitution (oldactor, p->mo);
4636 // PointerSubstitution() will also affect the bodyque, so undo that now.
4637 for (int ii=0; ii < BODYQUESIZE; ++ii)
4638 if (bodyque[ii] == p->mo)
4639 bodyque[ii] = oldactor;
4640 FBehavior::StaticStartTypedScripts (SCRIPT_Respawn, p->mo, true);
4641 }
4642 }
4643 return mobj;
4644 }
4645
4646
4647 //
4648 // P_SpawnMapThing
4649 // The fields of the mapthing should
4650 // already be in host byte order.
4651 //
4652 // [RH] position is used to weed out unwanted start spots
P_SpawnMapThing(FMapThing * mthing,int position)4653 AActor *P_SpawnMapThing (FMapThing *mthing, int position)
4654 {
4655 const PClass *i;
4656 int mask;
4657 AActor *mobj;
4658 fixed_t x, y, z;
4659
4660 if (mthing->EdNum == 0 || mthing->EdNum == -1)
4661 return NULL;
4662
4663 // find which type to spawn
4664 FDoomEdEntry *mentry = mthing->info;
4665
4666 if (mentry == NULL)
4667 {
4668 // [RH] Don't die if the map tries to spawn an unknown thing
4669 Printf ("Unknown type %i at (%i, %i)\n",
4670 mthing->EdNum,
4671 mthing->x>>FRACBITS, mthing->y>>FRACBITS);
4672 mentry = DoomEdMap.CheckKey(0);
4673 if (mentry == NULL) // we need a valid entry for the rest of this function so if we can't find a default, let's exit right away.
4674 {
4675 return NULL;
4676 }
4677 }
4678 if (mentry->Type == NULL && mentry->Special <= 0)
4679 {
4680 // has been explicitly set to not spawning anything.
4681 return NULL;
4682 }
4683
4684 // copy args to mapthing so that we have them in one place for the rest of this function
4685 if (mentry->ArgsDefined > 0)
4686 {
4687 if (mentry->Type!= NULL) mthing->special = mentry->Special;
4688 memcpy(mthing->args, mentry->Args, sizeof(mthing->args[0]) * mentry->ArgsDefined);
4689 }
4690
4691 int pnum = -1;
4692 if (mentry->Type == NULL)
4693 {
4694
4695 switch (mentry->Special)
4696 {
4697 case SMT_DeathmatchStart:
4698 {
4699 // count deathmatch start positions
4700 FPlayerStart start(mthing, 0);
4701 deathmatchstarts.Push(start);
4702 return NULL;
4703 }
4704
4705 case SMT_PolyAnchor:
4706 case SMT_PolySpawn:
4707 case SMT_PolySpawnCrush:
4708 case SMT_PolySpawnHurt:
4709 {
4710 polyspawns_t *polyspawn = new polyspawns_t;
4711 polyspawn->next = polyspawns;
4712 polyspawn->x = mthing->x;
4713 polyspawn->y = mthing->y;
4714 polyspawn->angle = mthing->angle;
4715 polyspawn->type = mentry->Special;
4716 polyspawns = polyspawn;
4717 if (mentry->Special != SMT_PolyAnchor)
4718 po_NumPolyobjs++;
4719 return NULL;
4720 }
4721
4722 case SMT_Player1Start:
4723 case SMT_Player2Start:
4724 case SMT_Player3Start:
4725 case SMT_Player4Start:
4726 case SMT_Player5Start:
4727 case SMT_Player6Start:
4728 case SMT_Player7Start:
4729 case SMT_Player8Start:
4730 pnum = mentry->Special - SMT_Player1Start;
4731 break;
4732
4733 // Sound sequence override will be handled later
4734 default:
4735 break;
4736
4737 }
4738 }
4739
4740 if (pnum == -1 || (level.flags & LEVEL_FILTERSTARTS))
4741 {
4742 // check for appropriate game type
4743 if (deathmatch)
4744 {
4745 mask = MTF_DEATHMATCH;
4746 }
4747 else if (multiplayer)
4748 {
4749 mask = MTF_COOPERATIVE;
4750 }
4751 else
4752 {
4753 mask = MTF_SINGLE;
4754 }
4755 if (!(mthing->flags & mask))
4756 {
4757 return NULL;
4758 }
4759
4760 mask = G_SkillProperty(SKILLP_SpawnFilter);
4761 if (!(mthing->SkillFilter & mask))
4762 {
4763 return NULL;
4764 }
4765
4766 // Check class spawn masks. Now with player classes available
4767 // this is enabled for all games.
4768 if (!multiplayer)
4769 { // Single player
4770 int spawnmask = players[consoleplayer].GetSpawnClass();
4771 if (spawnmask != 0 && (mthing->ClassFilter & spawnmask) == 0)
4772 { // Not for current class
4773 return NULL;
4774 }
4775 }
4776 else if (!deathmatch)
4777 { // Cooperative
4778 mask = 0;
4779 for (int i = 0; i < MAXPLAYERS; i++)
4780 {
4781 if (playeringame[i])
4782 {
4783 int spawnmask = players[i].GetSpawnClass();
4784 if (spawnmask != 0)
4785 mask |= spawnmask;
4786 else
4787 mask = -1;
4788 }
4789 }
4790 if (mask != -1 && (mthing->ClassFilter & mask) == 0)
4791 {
4792 return NULL;
4793 }
4794 }
4795 }
4796
4797 if (pnum != -1)
4798 {
4799 // [RH] Only spawn spots that match position.
4800 if (mthing->args[0] != position)
4801 return NULL;
4802
4803 // save spots for respawning in network games
4804 FPlayerStart start(mthing, pnum+1);
4805 playerstarts[pnum] = start;
4806 AllPlayerStarts.Push(start);
4807 if (!deathmatch && !(level.flags2 & LEVEL2_RANDOMPLAYERSTARTS))
4808 {
4809 return P_SpawnPlayer(&start, pnum, (level.flags2 & LEVEL2_PRERAISEWEAPON) ? SPF_WEAPONFULLYUP : 0);
4810 }
4811 return NULL;
4812 }
4813
4814 // [RH] sound sequence overriders
4815 if (mentry->Type == NULL && mentry->Special == SMT_SSeqOverride)
4816 {
4817 int type = mthing->args[0];
4818 if (type == 255) type = -1;
4819 if (type > 63)
4820 {
4821 Printf ("Sound sequence %d out of range\n", type);
4822 }
4823 else
4824 {
4825 P_PointInSector (mthing->x, mthing->y)->seqType = type;
4826 }
4827 return NULL;
4828 }
4829
4830 // [RH] If the thing's corresponding sprite has no frames, also map
4831 // it to the unknown thing.
4832 // Handle decorate replacements explicitly here
4833 // to check for missing frames in the replacement object.
4834 i = mentry->Type->GetReplacement();
4835
4836 const AActor *defaults = GetDefaultByType (i);
4837 if (defaults->SpawnState == NULL ||
4838 sprites[defaults->SpawnState->sprite].numframes == 0)
4839 {
4840 // We don't load mods for shareware games so we'll just ignore
4841 // missing actors. Heretic needs this since the shareware includes
4842 // the retail weapons in Deathmatch.
4843 if (gameinfo.flags & GI_SHAREWARE)
4844 return NULL;
4845
4846 Printf ("%s at (%i, %i) has no frames\n",
4847 i->TypeName.GetChars(), mthing->x>>FRACBITS, mthing->y>>FRACBITS);
4848 i = PClass::FindClass("Unknown");
4849 }
4850
4851 const AActor *info = GetDefaultByType (i);
4852
4853 // don't spawn keycards and players in deathmatch
4854 if (deathmatch && info->flags & MF_NOTDMATCH)
4855 return NULL;
4856
4857 // [RH] don't spawn extra weapons in coop if so desired
4858 if (multiplayer && !deathmatch && (dmflags & DF_NO_COOP_WEAPON_SPAWN))
4859 {
4860 if (GetDefaultByType(i)->flags7 & MF7_WEAPONSPAWN)
4861 {
4862 if ((mthing->flags & (MTF_DEATHMATCH|MTF_SINGLE)) == MTF_DEATHMATCH)
4863 return NULL;
4864 }
4865 }
4866
4867 // don't spawn any monsters if -nomonsters
4868 if (((level.flags2 & LEVEL2_NOMONSTERS) || (dmflags & DF_NO_MONSTERS)) && info->flags3 & MF3_ISMONSTER )
4869 {
4870 return NULL;
4871 }
4872
4873 // [RH] Other things that shouldn't be spawned depending on dmflags
4874 if (deathmatch || alwaysapplydmflags)
4875 {
4876 if (dmflags & DF_NO_HEALTH)
4877 {
4878 if (i->IsDescendantOf (RUNTIME_CLASS(AHealth)))
4879 return NULL;
4880 if (i->TypeName == NAME_Berserk)
4881 return NULL;
4882 if (i->TypeName == NAME_Megasphere)
4883 return NULL;
4884 }
4885 if (dmflags & DF_NO_ITEMS)
4886 {
4887 // if (i->IsDescendantOf (RUNTIME_CLASS(AArtifact)))
4888 // return;
4889 }
4890 if (dmflags & DF_NO_ARMOR)
4891 {
4892 if (i->IsDescendantOf (RUNTIME_CLASS(AArmor)))
4893 return NULL;
4894 if (i->TypeName == NAME_Megasphere)
4895 return NULL;
4896 }
4897 }
4898
4899 // spawn it
4900 x = mthing->x;
4901 y = mthing->y;
4902
4903 if (info->flags & MF_SPAWNCEILING)
4904 z = ONCEILINGZ;
4905 else if (info->flags2 & MF2_SPAWNFLOAT)
4906 z = FLOATRANDZ;
4907 else
4908 z = ONFLOORZ;
4909
4910 mobj = AActor::StaticSpawn (i, x, y, z, NO_REPLACE, true);
4911
4912 if (z == ONFLOORZ)
4913 {
4914 mobj->AddZ(mthing->z);
4915 if ((mobj->flags2 & MF2_FLOATBOB) && (ib_compatflags & BCOMPATF_FLOATBOB))
4916 {
4917 mobj->special1 = mthing->z;
4918 }
4919 }
4920 else if (z == ONCEILINGZ)
4921 mobj->AddZ(-mthing->z);
4922
4923 mobj->SpawnPoint[0] = mthing->x;
4924 mobj->SpawnPoint[1] = mthing->y;
4925 mobj->SpawnPoint[2] = mthing->z;
4926 mobj->SpawnAngle = mthing->angle;
4927 mobj->SpawnFlags = mthing->flags;
4928 if (mthing->FloatbobPhase >= 0 && mthing->FloatbobPhase < 64) mobj->FloatBobPhase = mthing->FloatbobPhase;
4929 if (mthing->gravity < 0) mobj->gravity = -mthing->gravity;
4930 else if (mthing->gravity > 0) mobj->gravity = FixedMul(mobj->gravity, mthing->gravity);
4931 else mobj->flags &= ~MF_NOGRAVITY;
4932
4933 // For Hexen floatbob 'compatibility' we do not really want to alter the floorz.
4934 if (mobj->special1 == 0 || !(mobj->flags2 & MF2_FLOATBOB) || !(ib_compatflags & BCOMPATF_FLOATBOB))
4935 {
4936 P_FindFloorCeiling(mobj, FFCF_SAMESECTOR | FFCF_ONLY3DFLOORS | FFCF_3DRESTRICT);
4937 }
4938
4939 // if the actor got args defined either in DECORATE or MAPINFO we must ignore the map's properties.
4940 if (!(mobj->flags2 & MF2_ARGSDEFINED))
4941 {
4942 // [RH] Set the thing's special
4943 mobj->special = mthing->special;
4944 for(int j=0;j<5;j++) mobj->args[j]=mthing->args[j];
4945 }
4946
4947 // [RH] Add ThingID to mobj and link it in with the others
4948 mobj->tid = mthing->thingid;
4949 mobj->AddToHash ();
4950
4951 mobj->PrevAngle = mobj->angle = (DWORD)((mthing->angle * CONST64(0x100000000)) / 360);
4952
4953 // Check if this actor's mapthing has a conversation defined
4954 if (mthing->Conversation > 0)
4955 {
4956 // Make sure that this does not partially overwrite the default dialogue settings.
4957 int root = GetConversation(mthing->Conversation);
4958 if (root != -1)
4959 {
4960 mobj->ConversationRoot = root;
4961 mobj->Conversation = StrifeDialogues[mobj->ConversationRoot];
4962 }
4963 }
4964
4965 // Set various UDMF options
4966 if (mthing->alpha != -1)
4967 mobj->alpha = mthing->alpha;
4968 if (mthing->RenderStyle != STYLE_Count)
4969 mobj->RenderStyle = (ERenderStyle)mthing->RenderStyle;
4970 if (mthing->scaleX)
4971 mobj->scaleX = FixedMul(mthing->scaleX, mobj->scaleX);
4972 if (mthing->scaleY)
4973 mobj->scaleY = FixedMul(mthing->scaleY, mobj->scaleY);
4974 if (mthing->pitch)
4975 mobj->pitch = ANGLE_1 * mthing->pitch;
4976 if (mthing->roll)
4977 mobj->roll = ANGLE_1 * mthing->roll;
4978 if (mthing->score)
4979 mobj->Score = mthing->score;
4980 if (mthing->fillcolor)
4981 mobj->fillcolor = mthing->fillcolor;
4982
4983 mobj->BeginPlay ();
4984 if (!(mobj->ObjectFlags & OF_EuthanizeMe))
4985 {
4986 mobj->LevelSpawned ();
4987 }
4988
4989 if (mthing->health > 0)
4990 mobj->health *= mthing->health;
4991 else
4992 mobj->health = -mthing->health;
4993 if (mthing->health == 0)
4994 mobj->Die(NULL, NULL);
4995 else if (mthing->health != 1)
4996 mobj->StartHealth = mobj->health;
4997
4998 return mobj;
4999 }
5000
5001
5002
5003 //
5004 // GAME SPAWN FUNCTIONS
5005 //
5006
5007
5008 //
5009 // P_SpawnPuff
5010 //
5011
P_SpawnPuff(AActor * source,const PClass * pufftype,fixed_t x,fixed_t y,fixed_t z,angle_t dir,int updown,int flags,AActor * vict)5012 AActor *P_SpawnPuff (AActor *source, const PClass *pufftype, fixed_t x, fixed_t y, fixed_t z, angle_t dir, int updown, int flags, AActor *vict)
5013 {
5014 AActor *puff;
5015
5016 if (!(flags & PF_NORANDOMZ))
5017 z += pr_spawnpuff.Random2 () << 10;
5018
5019 puff = Spawn (pufftype, x, y, z, ALLOW_REPLACE);
5020 if (puff == NULL) return NULL;
5021
5022 if ((puff->flags4 & MF4_RANDOMIZE) && puff->tics > 0)
5023 {
5024 puff->tics -= pr_spawnpuff() & 3;
5025 if (puff->tics < 1)
5026 puff->tics = 1;
5027 }
5028
5029 //Moved puff creation and target/master/tracer setting to here.
5030 if (puff && vict)
5031 {
5032 if (puff->flags7 & MF7_HITTARGET) puff->target = vict;
5033 if (puff->flags7 & MF7_HITMASTER) puff->master = vict;
5034 if (puff->flags7 & MF7_HITTRACER) puff->tracer = vict;
5035 }
5036 // [BB] If the puff came from a player, set the target of the puff to this player.
5037 if ( puff && (puff->flags5 & MF5_PUFFGETSOWNER))
5038 puff->target = source;
5039
5040
5041 if (source != NULL) puff->angle = puff->AngleTo(source);
5042
5043 // If a puff has a crash state and an actor was not hit,
5044 // it will enter the crash state. This is used by the StrifeSpark
5045 // and BlasterPuff.
5046 FState *crashstate;
5047 if (!(flags & PF_HITTHING) && (crashstate = puff->FindState(NAME_Crash)) != NULL)
5048 {
5049 puff->SetState (crashstate);
5050 }
5051 else if ((flags & PF_HITTHINGBLEED) && (crashstate = puff->FindState(NAME_Death, NAME_Extreme, true)) != NULL)
5052 {
5053 puff->SetState (crashstate);
5054 }
5055 else if ((flags & PF_MELEERANGE) && puff->MeleeState != NULL)
5056 {
5057 // handle the hard coded state jump of Doom's bullet puff
5058 // in a more flexible manner.
5059 puff->SetState (puff->MeleeState);
5060 }
5061
5062 if (!(flags & PF_TEMPORARY))
5063 {
5064 if (cl_pufftype && updown != 3 && (puff->flags4 & MF4_ALLOWPARTICLES))
5065 {
5066 P_DrawSplash2 (32, x, y, z, dir, updown, 1);
5067 puff->renderflags |= RF_INVISIBLE;
5068 }
5069
5070 if ((flags & PF_HITTHING) && puff->SeeSound)
5071 { // Hit thing sound
5072 S_Sound (puff, CHAN_BODY, puff->SeeSound, 1, ATTN_NORM);
5073 }
5074 else if (puff->AttackSound)
5075 {
5076 S_Sound (puff, CHAN_BODY, puff->AttackSound, 1, ATTN_NORM);
5077 }
5078 }
5079
5080 return puff;
5081 }
5082
5083
5084
5085 //---------------------------------------------------------------------------
5086 //
5087 // P_SpawnBlood
5088 //
5089 //---------------------------------------------------------------------------
5090
P_SpawnBlood(fixed_t x,fixed_t y,fixed_t z,angle_t dir,int damage,AActor * originator)5091 void P_SpawnBlood (fixed_t x, fixed_t y, fixed_t z, angle_t dir, int damage, AActor *originator)
5092 {
5093 AActor *th;
5094 PalEntry bloodcolor = originator->GetBloodColor();
5095 const PClass *bloodcls = originator->GetBloodType();
5096
5097 int bloodtype = cl_bloodtype;
5098
5099 if (bloodcls != NULL && !(GetDefaultByType(bloodcls)->flags4 & MF4_ALLOWPARTICLES))
5100 bloodtype = 0;
5101
5102 if (bloodcls != NULL)
5103 {
5104 z += pr_spawnblood.Random2 () << 10;
5105 th = Spawn (bloodcls, x, y, z, NO_REPLACE); // GetBloodType already performed the replacement
5106 th->velz = FRACUNIT*2;
5107 th->angle = dir;
5108 // [NG] Applying PUFFGETSOWNER to the blood will make it target the owner
5109 if (th->flags5 & MF5_PUFFGETSOWNER) th->target = originator;
5110 if (gameinfo.gametype & GAME_DoomChex)
5111 {
5112 th->tics -= pr_spawnblood() & 3;
5113
5114 if (th->tics < 1)
5115 th->tics = 1;
5116 }
5117 // colorize the blood
5118 if (bloodcolor != 0 && !(th->flags2 & MF2_DONTTRANSLATE))
5119 {
5120 th->Translation = TRANSLATION(TRANSLATION_Blood, bloodcolor.a);
5121 }
5122
5123 // Moved out of the blood actor so that replacing blood is easier
5124 if (gameinfo.gametype & GAME_DoomStrifeChex)
5125 {
5126 if (gameinfo.gametype == GAME_Strife)
5127 {
5128 if (damage > 13)
5129 {
5130 FState *state = th->FindState(NAME_Spray);
5131 if (state != NULL)
5132 {
5133 th->SetState (state);
5134 goto statedone;
5135 }
5136 }
5137 else damage += 2;
5138 }
5139 int advance = 0;
5140 if (damage <= 12 && damage >= 9)
5141 {
5142 advance = 1;
5143 }
5144 else if (damage < 9)
5145 {
5146 advance = 2;
5147 }
5148
5149 PClass *cls = th->GetClass();
5150
5151 while (cls != RUNTIME_CLASS(AActor))
5152 {
5153 FActorInfo *ai = cls->ActorInfo;
5154 int checked_advance = advance;
5155 if (ai->OwnsState(th->SpawnState))
5156 {
5157 for (; checked_advance > 0; --checked_advance)
5158 {
5159 // [RH] Do not set to a state we do not own.
5160 if (ai->OwnsState(th->SpawnState + checked_advance))
5161 {
5162 th->SetState(th->SpawnState + checked_advance);
5163 goto statedone;
5164 }
5165 }
5166 }
5167 cls = cls->ParentClass;
5168 }
5169 }
5170
5171 statedone:
5172 if (!(bloodtype <= 1)) th->renderflags |= RF_INVISIBLE;
5173 }
5174
5175 if (bloodtype >= 1)
5176 P_DrawSplash2 (40, x, y, z, dir, 2, bloodcolor);
5177 }
5178
5179 //---------------------------------------------------------------------------
5180 //
5181 // PROC P_BloodSplatter
5182 //
5183 //---------------------------------------------------------------------------
5184
P_BloodSplatter(fixed_t x,fixed_t y,fixed_t z,AActor * originator)5185 void P_BloodSplatter (fixed_t x, fixed_t y, fixed_t z, AActor *originator)
5186 {
5187 PalEntry bloodcolor = originator->GetBloodColor();
5188 const PClass *bloodcls = originator->GetBloodType(1);
5189
5190 int bloodtype = cl_bloodtype;
5191
5192 if (bloodcls != NULL && !(GetDefaultByType(bloodcls)->flags4 & MF4_ALLOWPARTICLES))
5193 bloodtype = 0;
5194
5195 if (bloodcls != NULL)
5196 {
5197 AActor *mo;
5198
5199 mo = Spawn(bloodcls, x, y, z, NO_REPLACE); // GetBloodType already performed the replacement
5200 mo->target = originator;
5201 mo->velx = pr_splatter.Random2 () << 10;
5202 mo->vely = pr_splatter.Random2 () << 10;
5203 mo->velz = 3*FRACUNIT;
5204
5205 // colorize the blood!
5206 if (bloodcolor!=0 && !(mo->flags2 & MF2_DONTTRANSLATE))
5207 {
5208 mo->Translation = TRANSLATION(TRANSLATION_Blood, bloodcolor.a);
5209 }
5210
5211 if (!(bloodtype <= 1)) mo->renderflags |= RF_INVISIBLE;
5212 }
5213 if (bloodtype >= 1)
5214 {
5215 P_DrawSplash2 (40, x, y, z, R_PointToAngle2 (x, y, originator->X(), originator->Y()), 2, bloodcolor);
5216 }
5217 }
5218
5219 //===========================================================================
5220 //
5221 // P_BloodSplatter2
5222 //
5223 //===========================================================================
5224
P_BloodSplatter2(fixed_t x,fixed_t y,fixed_t z,AActor * originator)5225 void P_BloodSplatter2 (fixed_t x, fixed_t y, fixed_t z, AActor *originator)
5226 {
5227 PalEntry bloodcolor = originator->GetBloodColor();
5228 const PClass *bloodcls = originator->GetBloodType(2);
5229
5230 int bloodtype = cl_bloodtype;
5231
5232 if (bloodcls != NULL && !(GetDefaultByType(bloodcls)->flags4 & MF4_ALLOWPARTICLES))
5233 bloodtype = 0;
5234
5235 if (bloodcls != NULL)
5236 {
5237 AActor *mo;
5238
5239 x += ((pr_splat()-128)<<11);
5240 y += ((pr_splat()-128)<<11);
5241
5242 mo = Spawn (bloodcls, x, y, z, NO_REPLACE); // GetBloodType already performed the replacement
5243 mo->target = originator;
5244
5245 // colorize the blood!
5246 if (bloodcolor != 0 && !(mo->flags2 & MF2_DONTTRANSLATE))
5247 {
5248 mo->Translation = TRANSLATION(TRANSLATION_Blood, bloodcolor.a);
5249 }
5250
5251 if (!(bloodtype <= 1)) mo->renderflags |= RF_INVISIBLE;
5252 }
5253 if (bloodtype >= 1)
5254 {
5255 P_DrawSplash2 (100, x, y, z, R_PointToAngle2 (0, 0, originator->X() - x, originator->Y() - y), 2, bloodcolor);
5256 }
5257 }
5258
5259 //---------------------------------------------------------------------------
5260 //
5261 // PROC P_RipperBlood
5262 //
5263 //---------------------------------------------------------------------------
5264
P_RipperBlood(AActor * mo,AActor * bleeder)5265 void P_RipperBlood (AActor *mo, AActor *bleeder)
5266 {
5267 PalEntry bloodcolor = bleeder->GetBloodColor();
5268 const PClass *bloodcls = bleeder->GetBloodType();
5269
5270 fixed_t xo = (pr_ripperblood.Random2() << 12);
5271 fixed_t yo = (pr_ripperblood.Random2() << 12);
5272 fixed_t zo = (pr_ripperblood.Random2() << 12);
5273 fixedvec3 pos = mo->Vec3Offset(xo, yo, zo);
5274
5275 int bloodtype = cl_bloodtype;
5276
5277 if (bloodcls != NULL && !(GetDefaultByType(bloodcls)->flags4 & MF4_ALLOWPARTICLES))
5278 bloodtype = 0;
5279
5280 if (bloodcls != NULL)
5281 {
5282 AActor *th;
5283 th = Spawn (bloodcls, pos, NO_REPLACE); // GetBloodType already performed the replacement
5284 // [NG] Applying PUFFGETSOWNER to the blood will make it target the owner
5285 if (th->flags5 & MF5_PUFFGETSOWNER) th->target = bleeder;
5286 if (gameinfo.gametype == GAME_Heretic)
5287 th->flags |= MF_NOGRAVITY;
5288 th->velx = mo->velx >> 1;
5289 th->vely = mo->vely >> 1;
5290 th->tics += pr_ripperblood () & 3;
5291
5292 // colorize the blood!
5293 if (bloodcolor!=0 && !(th->flags2 & MF2_DONTTRANSLATE))
5294 {
5295 th->Translation = TRANSLATION(TRANSLATION_Blood, bloodcolor.a);
5296 }
5297
5298 if (!(bloodtype <= 1)) th->renderflags |= RF_INVISIBLE;
5299 }
5300 if (bloodtype >= 1)
5301 {
5302 P_DrawSplash2 (28, pos.x, pos.y, pos.z, 0, 0, bloodcolor);
5303 }
5304 }
5305
5306 //---------------------------------------------------------------------------
5307 //
5308 // FUNC P_GetThingFloorType
5309 //
5310 //---------------------------------------------------------------------------
5311
P_GetThingFloorType(AActor * thing)5312 int P_GetThingFloorType (AActor *thing)
5313 {
5314 if (thing->floorterrain >= 0)
5315 {
5316 return thing->floorterrain;
5317 }
5318 else
5319 {
5320 return thing->Sector->GetTerrain(sector_t::floor);
5321 }
5322 }
5323
5324 //---------------------------------------------------------------------------
5325 //
5326 // FUNC P_HitWater
5327 //
5328 // Returns true if hit liquid and splashed, false if not.
5329 //---------------------------------------------------------------------------
5330
P_HitWater(AActor * thing,sector_t * sec,fixed_t x,fixed_t y,fixed_t z,bool checkabove,bool alert,bool force)5331 bool P_HitWater (AActor * thing, sector_t * sec, fixed_t x, fixed_t y, fixed_t z, bool checkabove, bool alert, bool force)
5332 {
5333 if (thing->flags3 & MF3_DONTSPLASH)
5334 return false;
5335
5336 if (thing->player && (thing->player->cheats & CF_PREDICTING))
5337 return false;
5338
5339 AActor *mo = NULL;
5340 FSplashDef *splash;
5341 int terrainnum;
5342 sector_t *hsec = NULL;
5343
5344 if (x == FIXED_MIN) x = thing->X();
5345 if (y == FIXED_MIN) y = thing->Y();
5346 if (z == FIXED_MIN) z = thing->Z();
5347 // don't splash above the object
5348 if (checkabove)
5349 {
5350 fixed_t compare_z = thing->Z() + (thing->height >> 1);
5351 // Missiles are typically small and fast, so they might
5352 // end up submerged by the move that calls P_HitWater.
5353 if (thing->flags & MF_MISSILE)
5354 compare_z -= thing->velz;
5355 if (z > compare_z)
5356 return false;
5357 }
5358
5359 #if 0 // needs some rethinking before activation
5360
5361 // This avoids spawning splashes on invisible self referencing sectors.
5362 // For network consistency do this only in single player though because
5363 // it is not guaranteed that all players have GL nodes loaded.
5364 if (!multiplayer && thing->subsector->sector != thing->subsector->render_sector)
5365 {
5366 fixed_t zs = thing->subsector->sector->floorplane.ZatPoint(x, y);
5367 fixed_t zr = thing->subsector->render_sector->floorplane.ZatPoint(x, y);
5368
5369 if (zs > zr && thing->z >= zs) return false;
5370 }
5371 #endif
5372
5373 // 'force' means, we want this sector's terrain, no matter what.
5374 if (!force)
5375 {
5376 for (unsigned int i = 0; i<sec->e->XFloor.ffloors.Size(); i++)
5377 {
5378 F3DFloor * rover = sec->e->XFloor.ffloors[i];
5379 if (!(rover->flags & FF_EXISTS)) continue;
5380 fixed_t planez = rover->top.plane->ZatPoint(x, y);
5381 if (z > planez - FRACUNIT / 2 && z < planez + FRACUNIT / 2) // allow minor imprecisions
5382 {
5383 if (rover->flags & (FF_SOLID | FF_SWIMMABLE))
5384 {
5385 terrainnum = rover->model->GetTerrain(rover->top.isceiling);
5386 goto foundone;
5387 }
5388 }
5389 planez = rover->bottom.plane->ZatPoint(x, y);
5390 if (planez < z && !(planez < thing->floorz)) return false;
5391 }
5392 }
5393 hsec = sec->GetHeightSec();
5394 if (force || hsec == NULL || !(hsec->MoreFlags & SECF_CLIPFAKEPLANES))
5395 {
5396 terrainnum = sec->GetTerrain(sector_t::floor);
5397 }
5398 else
5399 {
5400 terrainnum = hsec->GetTerrain(sector_t::floor);
5401 }
5402 foundone:
5403
5404 int splashnum = Terrains[terrainnum].Splash;
5405 bool smallsplash = false;
5406 const secplane_t *plane;
5407
5408 if (splashnum == -1)
5409 return Terrains[terrainnum].IsLiquid;
5410
5411 // don't splash when touching an underwater floor
5412 if (thing->waterlevel>=1 && z<=thing->floorz) return Terrains[terrainnum].IsLiquid;
5413
5414 plane = hsec != NULL? &sec->heightsec->floorplane : &sec->floorplane;
5415
5416 // Don't splash for living things with small vertical velocities.
5417 // There are levels where the constant splashing from the monsters gets extremely annoying
5418 if (((thing->flags3&MF3_ISMONSTER || thing->player) && thing->velz >= -6*FRACUNIT) && !force)
5419 return Terrains[terrainnum].IsLiquid;
5420
5421 splash = &Splashes[splashnum];
5422
5423 // Small splash for small masses
5424 if (thing->Mass < 10)
5425 smallsplash = true;
5426
5427 if (smallsplash && splash->SmallSplash)
5428 {
5429 mo = Spawn (splash->SmallSplash, x, y, z, ALLOW_REPLACE);
5430 if (mo) mo->floorclip += splash->SmallSplashClip;
5431 }
5432 else
5433 {
5434 if (splash->SplashChunk)
5435 {
5436 mo = Spawn (splash->SplashChunk, x, y, z, ALLOW_REPLACE);
5437 mo->target = thing;
5438 if (splash->ChunkXVelShift != 255)
5439 {
5440 mo->velx = pr_chunk.Random2() << splash->ChunkXVelShift;
5441 }
5442 if (splash->ChunkYVelShift != 255)
5443 {
5444 mo->vely = pr_chunk.Random2() << splash->ChunkYVelShift;
5445 }
5446 mo->velz = splash->ChunkBaseZVel + (pr_chunk() << splash->ChunkZVelShift);
5447 }
5448 if (splash->SplashBase)
5449 {
5450 mo = Spawn (splash->SplashBase, x, y, z, ALLOW_REPLACE);
5451 }
5452 if (thing->player && !splash->NoAlert && alert)
5453 {
5454 P_NoiseAlert (thing, thing, true);
5455 }
5456 }
5457 if (mo)
5458 {
5459 S_Sound (mo, CHAN_ITEM, smallsplash ?
5460 splash->SmallSplashSound : splash->NormalSplashSound,
5461 1, ATTN_IDLE);
5462 }
5463 else
5464 {
5465 S_Sound (x, y, z, CHAN_ITEM, smallsplash ?
5466 splash->SmallSplashSound : splash->NormalSplashSound,
5467 1, ATTN_IDLE);
5468 }
5469
5470 // Don't let deep water eat missiles
5471 return plane == &sec->floorplane ? Terrains[terrainnum].IsLiquid : false;
5472 }
5473
5474 //---------------------------------------------------------------------------
5475 //
5476 // FUNC P_HitFloor
5477 //
5478 // Returns true if hit liquid and splashed, false if not.
5479 //---------------------------------------------------------------------------
5480
P_HitFloor(AActor * thing)5481 bool P_HitFloor (AActor *thing)
5482 {
5483 const msecnode_t *m;
5484
5485 // killough 11/98: touchy objects explode on impact
5486 // Allow very short drops to be safe, so that a touchy can be summoned without exploding.
5487 if (thing->flags6 & MF6_TOUCHY && ((thing->flags6 & MF6_ARMED) || thing->IsSentient()) && ((thing->velz) < (-5 * FRACUNIT)))
5488 {
5489 thing->flags6 &= ~MF6_ARMED; // Disarm
5490 P_DamageMobj (thing, NULL, NULL, thing->health, NAME_Crush, DMG_FORCED); // kill object
5491 return false;
5492 }
5493
5494 if (thing->flags3 & MF3_DONTSPLASH)
5495 return false;
5496
5497 // don't splash if landing on the edge above water/lava/etc....
5498 for (m = thing->touching_sectorlist; m; m = m->m_tnext)
5499 {
5500 if (thing->Z() == m->m_sector->floorplane.ZatPoint(thing))
5501 {
5502 break;
5503 }
5504
5505 // Check 3D floors
5506 for(unsigned int i=0;i<m->m_sector->e->XFloor.ffloors.Size();i++)
5507 {
5508 F3DFloor * rover = m->m_sector->e->XFloor.ffloors[i];
5509 if (!(rover->flags & FF_EXISTS)) continue;
5510 if (rover->flags & (FF_SOLID|FF_SWIMMABLE))
5511 {
5512 if (rover->top.plane->ZatPoint(thing) == thing->Z())
5513 {
5514 return P_HitWater (thing, m->m_sector);
5515 }
5516 }
5517 }
5518 }
5519 if (m == NULL || m->m_sector->GetHeightSec() != NULL)
5520 {
5521 return false;
5522 }
5523
5524 return P_HitWater (thing, m->m_sector);
5525 }
5526
5527 //---------------------------------------------------------------------------
5528 //
5529 // P_CheckSplash
5530 //
5531 // Checks for splashes caused by explosions
5532 //
5533 //---------------------------------------------------------------------------
5534
P_CheckSplash(AActor * self,fixed_t distance)5535 void P_CheckSplash(AActor *self, fixed_t distance)
5536 {
5537 if (self->Z() <= self->floorz + (distance<<FRACBITS) && self->floorsector == self->Sector && self->Sector->GetHeightSec() == NULL)
5538 {
5539 // Explosion splashes never alert monsters. This is because A_Explode has
5540 // a separate parameter for that so this would get in the way of proper
5541 // behavior.
5542 P_HitWater (self, self->Sector, self->X(), self->Y(), self->floorz, false, false);
5543 }
5544 }
5545
5546 //---------------------------------------------------------------------------
5547 //
5548 // FUNC P_CheckMissileSpawn
5549 //
5550 // Returns true if the missile is at a valid spawn point, otherwise
5551 // explodes it and returns false.
5552 //
5553 //---------------------------------------------------------------------------
5554
P_CheckMissileSpawn(AActor * th,fixed_t maxdist)5555 bool P_CheckMissileSpawn (AActor* th, fixed_t maxdist)
5556 {
5557 // [RH] Don't decrement tics if they are already less than 1
5558 if ((th->flags4 & MF4_RANDOMIZE) && th->tics > 0)
5559 {
5560 th->tics -= pr_checkmissilespawn() & 3;
5561 if (th->tics < 1)
5562 th->tics = 1;
5563 }
5564
5565 if (maxdist > 0)
5566 {
5567 // move a little forward so an angle can be computed if it immediately explodes
5568 TVector3<double> advance(FIXED2DBL(th->velx), FIXED2DBL(th->vely), FIXED2DBL(th->velz));
5569 double maxsquared = FIXED2DBL(maxdist);
5570 maxsquared *= maxsquared;
5571
5572 // Keep halving the advance vector until we get something less than maxdist
5573 // units away, since we still want to spawn the missile inside the shooter.
5574 do
5575 {
5576 advance *= 0.5f;
5577 }
5578 while (TVector2<double>(advance).LengthSquared() >= maxsquared);
5579 th->SetXYZ(
5580 th->X() + FLOAT2FIXED(advance.X),
5581 th->Y() + FLOAT2FIXED(advance.Y),
5582 th->Z() + FLOAT2FIXED(advance.Z));
5583 }
5584
5585 FCheckPosition tm(!!(th->flags2 & MF2_RIP));
5586
5587 // killough 8/12/98: for non-missile objects (e.g. grenades)
5588 //
5589 // [GZ] MBF excludes non-missile objects from the P_TryMove test
5590 // and subsequent potential P_ExplodeMissile call. That is because
5591 // in MBF, a projectile is not an actor with the MF_MISSILE flag
5592 // but an actor with either or both the MF_MISSILE and MF_BOUNCES
5593 // flags, and a grenade is identified by not having MF_MISSILE.
5594 // Killough wanted grenades not to explode directly when spawned,
5595 // therefore they can be fired safely even when humping a wall as
5596 // they will then just drop on the floor at their shooter's feet.
5597 //
5598 // However, ZDoom does allow non-missiles to be shot as well, so
5599 // Killough's check for non-missiles is inadequate here. So let's
5600 // replace it by a check for non-missile and MBF bounce type.
5601 // This should allow MBF behavior where relevant without altering
5602 // established ZDoom behavior for crazy stuff like a cacodemon cannon.
5603 bool MBFGrenade = (!(th->flags & MF_MISSILE) || (th->BounceFlags & BOUNCE_MBF));
5604
5605 // killough 3/15/98: no dropoff (really = don't care for missiles)
5606 if (!(P_TryMove (th, th->X(), th->Y(), false, NULL, tm, true)))
5607 {
5608 // [RH] Don't explode ripping missiles that spawn inside something
5609 if (th->BlockingMobj == NULL || !(th->flags2 & MF2_RIP) || (th->BlockingMobj->flags5 & MF5_DONTRIP))
5610 {
5611 // If this is a monster spawned by A_CustomMissile subtract it from the counter.
5612 th->ClearCounters();
5613 // [RH] Don't explode missiles that spawn on top of horizon lines
5614 if (th->BlockingLine != NULL && th->BlockingLine->special == Line_Horizon)
5615 {
5616 th->Destroy ();
5617 }
5618 else if (MBFGrenade && th->BlockingLine != NULL)
5619 {
5620 P_BounceWall(th);
5621 }
5622 else
5623 {
5624 P_ExplodeMissile (th, NULL, th->BlockingMobj);
5625 }
5626 return false;
5627 }
5628 }
5629 return true;
5630 }
5631
5632
5633 //---------------------------------------------------------------------------
5634 //
5635 // FUNC P_PlaySpawnSound
5636 //
5637 // Plays a missiles spawn sound. Location depends on the
5638 // MF_SPAWNSOUNDSOURCE flag.
5639 //
5640 //---------------------------------------------------------------------------
5641
P_PlaySpawnSound(AActor * missile,AActor * spawner)5642 void P_PlaySpawnSound(AActor *missile, AActor *spawner)
5643 {
5644 if (missile->SeeSound != 0)
5645 {
5646 if (!(missile->flags & MF_SPAWNSOUNDSOURCE))
5647 {
5648 S_Sound (missile, CHAN_VOICE, missile->SeeSound, 1, ATTN_NORM);
5649 }
5650 else if (spawner != NULL)
5651 {
5652 S_Sound (spawner, CHAN_WEAPON, missile->SeeSound, 1, ATTN_NORM);
5653 }
5654 else
5655 {
5656 // If there is no spawner use the spawn position.
5657 // But not in a silenced sector.
5658 if (!(missile->Sector->Flags & SECF_SILENT))
5659 S_Sound (missile->X(), missile->Y(), missile->Z(), CHAN_WEAPON, missile->SeeSound, 1, ATTN_NORM);
5660 }
5661 }
5662 }
5663
GetDefaultSpeed(const PClass * type)5664 static fixed_t GetDefaultSpeed(const PClass *type)
5665 {
5666 if (type == NULL) return 0;
5667 else if (G_SkillProperty(SKILLP_FastMonsters))
5668 return type->Meta.GetMetaFixed(AMETA_FastSpeed, GetDefaultByType(type)->Speed);
5669 else
5670 return GetDefaultByType(type)->Speed;
5671 }
5672
5673 //---------------------------------------------------------------------------
5674 //
5675 // FUNC P_SpawnMissile
5676 //
5677 // Returns NULL if the missile exploded immediately, otherwise returns
5678 // a mobj_t pointer to the missile.
5679 //
5680 //---------------------------------------------------------------------------
5681
P_SpawnMissile(AActor * source,AActor * dest,const PClass * type,AActor * owner)5682 AActor *P_SpawnMissile (AActor *source, AActor *dest, const PClass *type, AActor *owner)
5683 {
5684 if (source == NULL)
5685 {
5686 return NULL;
5687 }
5688 return P_SpawnMissileXYZ (source->X(), source->Y(), source->Z() + 32*FRACUNIT + source->GetBobOffset(),
5689 source, dest, type, true, owner);
5690 }
5691
P_SpawnMissileZ(AActor * source,fixed_t z,AActor * dest,const PClass * type)5692 AActor *P_SpawnMissileZ (AActor *source, fixed_t z, AActor *dest, const PClass *type)
5693 {
5694 if (source == NULL)
5695 {
5696 return NULL;
5697 }
5698 return P_SpawnMissileXYZ (source->X(), source->Y(), z, source, dest, type);
5699 }
5700
P_SpawnMissileXYZ(fixed_t x,fixed_t y,fixed_t z,AActor * source,AActor * dest,const PClass * type,bool checkspawn,AActor * owner)5701 AActor *P_SpawnMissileXYZ (fixed_t x, fixed_t y, fixed_t z,
5702 AActor *source, AActor *dest, const PClass *type, bool checkspawn, AActor *owner)
5703 {
5704 if (source == NULL)
5705 {
5706 return NULL;
5707 }
5708
5709 if (dest == NULL)
5710 {
5711 Printf ("P_SpawnMissilyXYZ: Tried to shoot %s from %s with no dest\n",
5712 type->TypeName.GetChars(), source->GetClass()->TypeName.GetChars());
5713 return NULL;
5714 }
5715
5716 if (z != ONFLOORZ && z != ONCEILINGZ)
5717 {
5718 z -= source->floorclip;
5719 }
5720
5721 AActor *th = Spawn (type, x, y, z, ALLOW_REPLACE);
5722
5723 P_PlaySpawnSound(th, source);
5724
5725 // record missile's originator
5726 if (owner == NULL) owner = source;
5727 th->target = owner;
5728
5729 float speed = (float)(th->Speed);
5730
5731 // [RH]
5732 // Hexen calculates the missile velocity based on the source's location.
5733 // Would it be more useful to base it on the actual position of the
5734 // missile?
5735 // Answer: No, because this way, you can set up sets of parallel missiles.
5736
5737 fixedvec3 fixvel = source->Vec3To(dest);
5738 FVector3 velocity(fixvel.x, fixvel.y, fixvel.z);
5739 // Floor and ceiling huggers should never have a vertical component to their velocity
5740 if (th->flags3 & (MF3_FLOORHUGGER|MF3_CEILINGHUGGER))
5741 {
5742 velocity.Z = 0;
5743 }
5744 // [RH] Adjust the trajectory if the missile will go over the target's head.
5745 else if (z - source->Z() >= dest->height)
5746 {
5747 velocity.Z += (dest->height - z + source->Z());
5748 }
5749 velocity.Resize (speed);
5750 th->velx = (fixed_t)(velocity.X);
5751 th->vely = (fixed_t)(velocity.Y);
5752 th->velz = (fixed_t)(velocity.Z);
5753
5754 // invisible target: rotate velocity vector in 2D
5755 // [RC] Now monsters can aim at invisible player as if they were fully visible.
5756 if (dest->flags & MF_SHADOW && !(source->flags6 & MF6_SEEINVISIBLE))
5757 {
5758 angle_t an = pr_spawnmissile.Random2 () << 20;
5759 an >>= ANGLETOFINESHIFT;
5760
5761 fixed_t newx = DMulScale16 (th->velx, finecosine[an], -th->vely, finesine[an]);
5762 fixed_t newy = DMulScale16 (th->velx, finesine[an], th->vely, finecosine[an]);
5763 th->velx = newx;
5764 th->vely = newy;
5765 }
5766
5767 th->angle = R_PointToAngle2 (0, 0, th->velx, th->vely);
5768
5769 if (th->flags4 & MF4_SPECTRAL)
5770 {
5771 th->SetFriendPlayer(owner->player);
5772 }
5773
5774 return (!checkspawn || P_CheckMissileSpawn (th, source->radius)) ? th : NULL;
5775 }
5776
P_OldSpawnMissile(AActor * source,AActor * owner,AActor * dest,const PClass * type)5777 AActor * P_OldSpawnMissile(AActor * source, AActor * owner, AActor * dest, const PClass *type)
5778 {
5779 if (source == NULL)
5780 {
5781 return NULL;
5782 }
5783 angle_t an;
5784 fixed_t dist;
5785 AActor *th = Spawn (type, source->PosPlusZ(4*8*FRACUNIT), ALLOW_REPLACE);
5786
5787 P_PlaySpawnSound(th, source);
5788 th->target = owner; // record missile's originator
5789
5790 th->angle = an = source->AngleTo(dest);
5791 an >>= ANGLETOFINESHIFT;
5792 th->velx = FixedMul (th->Speed, finecosine[an]);
5793 th->vely = FixedMul (th->Speed, finesine[an]);
5794
5795 dist = source->AproxDistance (dest);
5796 if (th->Speed) dist = dist / th->Speed;
5797
5798 if (dist < 1)
5799 dist = 1;
5800
5801 th->velz = (dest->Z() - source->Z()) / dist;
5802
5803 if (th->flags4 & MF4_SPECTRAL)
5804 {
5805 th->SetFriendPlayer(owner->player);
5806 }
5807
5808 P_CheckMissileSpawn(th, source->radius);
5809 return th;
5810 }
5811
5812 //---------------------------------------------------------------------------
5813 //
5814 // FUNC P_SpawnMissileAngle
5815 //
5816 // Returns NULL if the missile exploded immediately, otherwise returns
5817 // a mobj_t pointer to the missile.
5818 //
5819 //---------------------------------------------------------------------------
5820
P_SpawnMissileAngle(AActor * source,const PClass * type,angle_t angle,fixed_t velz)5821 AActor *P_SpawnMissileAngle (AActor *source, const PClass *type,
5822 angle_t angle, fixed_t velz)
5823 {
5824 if (source == NULL)
5825 {
5826 return NULL;
5827 }
5828 return P_SpawnMissileAngleZSpeed (source, source->Z() + 32*FRACUNIT + source->GetBobOffset(),
5829 type, angle, velz, GetDefaultSpeed (type));
5830 }
5831
P_SpawnMissileAngleZ(AActor * source,fixed_t z,const PClass * type,angle_t angle,fixed_t velz)5832 AActor *P_SpawnMissileAngleZ (AActor *source, fixed_t z,
5833 const PClass *type, angle_t angle, fixed_t velz)
5834 {
5835 return P_SpawnMissileAngleZSpeed (source, z, type, angle, velz,
5836 GetDefaultSpeed (type));
5837 }
5838
P_SpawnMissileZAimed(AActor * source,fixed_t z,AActor * dest,const PClass * type)5839 AActor *P_SpawnMissileZAimed (AActor *source, fixed_t z, AActor *dest, const PClass *type)
5840 {
5841 if (source == NULL)
5842 {
5843 return NULL;
5844 }
5845 angle_t an;
5846 fixed_t dist;
5847 fixed_t speed;
5848 fixed_t velz;
5849
5850 an = source->angle;
5851
5852 if (dest->flags & MF_SHADOW)
5853 {
5854 an += pr_spawnmissile.Random2() << 20;
5855 }
5856 dist = source->AproxDistance (dest);
5857 speed = GetDefaultSpeed (type);
5858 dist /= speed;
5859 velz = dist != 0 ? (dest->Z() - source->Z())/dist : speed;
5860 return P_SpawnMissileAngleZSpeed (source, z, type, an, velz, speed);
5861 }
5862
5863 //---------------------------------------------------------------------------
5864 //
5865 // FUNC P_SpawnMissileAngleSpeed
5866 //
5867 // Returns NULL if the missile exploded immediately, otherwise returns
5868 // a mobj_t pointer to the missile.
5869 //
5870 //---------------------------------------------------------------------------
5871
P_SpawnMissileAngleSpeed(AActor * source,const PClass * type,angle_t angle,fixed_t velz,fixed_t speed)5872 AActor *P_SpawnMissileAngleSpeed (AActor *source, const PClass *type,
5873 angle_t angle, fixed_t velz, fixed_t speed)
5874 {
5875 if (source == NULL)
5876 {
5877 return NULL;
5878 }
5879 return P_SpawnMissileAngleZSpeed (source, source->Z() + 32*FRACUNIT + source->GetBobOffset(),
5880 type, angle, velz, speed);
5881 }
5882
P_SpawnMissileAngleZSpeed(AActor * source,fixed_t z,const PClass * type,angle_t angle,fixed_t velz,fixed_t speed,AActor * owner,bool checkspawn)5883 AActor *P_SpawnMissileAngleZSpeed (AActor *source, fixed_t z,
5884 const PClass *type, angle_t angle, fixed_t velz, fixed_t speed, AActor *owner, bool checkspawn)
5885 {
5886 if (source == NULL)
5887 {
5888 return NULL;
5889 }
5890 AActor *mo;
5891
5892 if (z != ONFLOORZ && z != ONCEILINGZ)
5893 {
5894 z -= source->floorclip;
5895 }
5896
5897 mo = Spawn (type, source->X(), source->Y(), z, ALLOW_REPLACE);
5898
5899 P_PlaySpawnSound(mo, source);
5900 if (owner == NULL) owner = source;
5901 mo->target = owner;
5902 mo->angle = angle;
5903 angle >>= ANGLETOFINESHIFT;
5904 mo->velx = FixedMul (speed, finecosine[angle]);
5905 mo->vely = FixedMul (speed, finesine[angle]);
5906 mo->velz = velz;
5907
5908 if (mo->flags4 & MF4_SPECTRAL)
5909 {
5910 mo->SetFriendPlayer(owner->player);
5911 }
5912
5913 return (!checkspawn || P_CheckMissileSpawn(mo, source->radius)) ? mo : NULL;
5914 }
5915
5916 /*
5917 ================
5918 =
5919 = P_SpawnPlayerMissile
5920 =
5921 = Tries to aim at a nearby monster
5922 ================
5923 */
5924
P_SpawnPlayerMissile(AActor * source,const PClass * type)5925 AActor *P_SpawnPlayerMissile (AActor *source, const PClass *type)
5926 {
5927 if (source == NULL)
5928 {
5929 return NULL;
5930 }
5931 return P_SpawnPlayerMissile (source, 0, 0, 0, type, source->angle);
5932 }
5933
P_SpawnPlayerMissile(AActor * source,const PClass * type,angle_t angle)5934 AActor *P_SpawnPlayerMissile (AActor *source, const PClass *type, angle_t angle)
5935 {
5936 return P_SpawnPlayerMissile (source, 0, 0, 0, type, angle);
5937 }
5938
P_SpawnPlayerMissile(AActor * source,fixed_t x,fixed_t y,fixed_t z,const PClass * type,angle_t angle,AActor ** pLineTarget,AActor ** pMissileActor,bool nofreeaim,bool noautoaim)5939 AActor *P_SpawnPlayerMissile (AActor *source, fixed_t x, fixed_t y, fixed_t z,
5940 const PClass *type, angle_t angle, AActor **pLineTarget, AActor **pMissileActor,
5941 bool nofreeaim, bool noautoaim)
5942 {
5943 static const int angdiff[3] = { -(1<<26), 1<<26, 0 };
5944 angle_t an = angle;
5945 angle_t pitch;
5946 AActor *linetarget;
5947 AActor *defaultobject = GetDefaultByType(type);
5948 int vrange = nofreeaim ? ANGLE_1*35 : 0;
5949
5950 if (source == NULL)
5951 {
5952 return NULL;
5953 }
5954 if (source->player && source->player->ReadyWeapon && ((source->player->ReadyWeapon->WeaponFlags & WIF_NOAUTOAIM) || noautoaim))
5955 {
5956 // Keep exactly the same angle and pitch as the player's own aim
5957 an = angle;
5958 pitch = source->pitch;
5959 linetarget = NULL;
5960 }
5961 else // see which target is to be aimed at
5962 {
5963 // [XA] If MaxTargetRange is defined in the spawned projectile, use this as the
5964 // maximum range for the P_AimLineAttack call later; this allows MaxTargetRange
5965 // to function as a "maximum tracer-acquisition range" for seeker missiles.
5966 fixed_t linetargetrange = defaultobject->maxtargetrange > 0 ? defaultobject->maxtargetrange*64 : 16*64*FRACUNIT;
5967
5968 int i = 2;
5969 do
5970 {
5971 an = angle + angdiff[i];
5972 pitch = P_AimLineAttack (source, an, linetargetrange, &linetarget, vrange);
5973
5974 if (source->player != NULL &&
5975 !nofreeaim &&
5976 level.IsFreelookAllowed() &&
5977 source->player->userinfo.GetAimDist() <= ANGLE_1/2)
5978 {
5979 break;
5980 }
5981 } while (linetarget == NULL && --i >= 0);
5982
5983 if (linetarget == NULL)
5984 {
5985 an = angle;
5986 if (nofreeaim || !level.IsFreelookAllowed())
5987 {
5988 pitch = 0;
5989 }
5990 }
5991 }
5992 if (pLineTarget) *pLineTarget = linetarget;
5993
5994 if (z != ONFLOORZ && z != ONCEILINGZ)
5995 {
5996 // Doom spawns missiles 4 units lower than hitscan attacks for players.
5997 z += source->Z() + (source->height>>1) - source->floorclip;
5998 if (source->player != NULL) // Considering this is for player missiles, it better not be NULL.
5999 {
6000 z += FixedMul (source->player->mo->AttackZOffset - 4*FRACUNIT, source->player->crouchfactor);
6001 }
6002 else
6003 {
6004 z += 4*FRACUNIT;
6005 }
6006 // Do not fire beneath the floor.
6007 if (z < source->floorz)
6008 {
6009 z = source->floorz;
6010 }
6011 }
6012 fixedvec2 pos = source->Vec2Offset(x, y);
6013 AActor *MissileActor = Spawn (type, pos.x, pos.y, z, ALLOW_REPLACE);
6014 if (pMissileActor) *pMissileActor = MissileActor;
6015 P_PlaySpawnSound(MissileActor, source);
6016 MissileActor->target = source;
6017 MissileActor->angle = an;
6018
6019 fixed_t vx, vy, vz, speed;
6020
6021 vx = FixedMul (finecosine[pitch>>ANGLETOFINESHIFT], finecosine[an>>ANGLETOFINESHIFT]);
6022 vy = FixedMul (finecosine[pitch>>ANGLETOFINESHIFT], finesine[an>>ANGLETOFINESHIFT]);
6023 vz = -finesine[pitch>>ANGLETOFINESHIFT];
6024 speed = MissileActor->Speed;
6025
6026 FVector3 vec(vx, vy, vz);
6027
6028 if (MissileActor->flags3 & (MF3_FLOORHUGGER|MF3_CEILINGHUGGER))
6029 {
6030 vec.Z = 0;
6031 }
6032 vec.Resize(speed);
6033 MissileActor->velx = (fixed_t)vec.X;
6034 MissileActor->vely = (fixed_t)vec.Y;
6035 MissileActor->velz = (fixed_t)vec.Z;
6036
6037 if (MissileActor->flags4 & MF4_SPECTRAL)
6038 {
6039 MissileActor->SetFriendPlayer(source->player);
6040 }
6041 if (P_CheckMissileSpawn (MissileActor, source->radius))
6042 {
6043 return MissileActor;
6044 }
6045 return NULL;
6046 }
6047
GetTeam()6048 int AActor::GetTeam()
6049 {
6050 if (player)
6051 {
6052 return player->userinfo.GetTeam();
6053 }
6054
6055 int myTeam = DesignatedTeam;
6056
6057 // Check for monsters that belong to a player on the team but aren't part of the team themselves.
6058 if (myTeam == TEAM_NONE && FriendPlayer != 0)
6059 {
6060 myTeam = players[FriendPlayer - 1].userinfo.GetTeam();
6061 }
6062 return myTeam;
6063
6064 }
6065
IsTeammate(AActor * other)6066 bool AActor::IsTeammate (AActor *other)
6067 {
6068 if (!other)
6069 {
6070 return false;
6071 }
6072 else if (!deathmatch && player && other->player)
6073 {
6074 return true;
6075 }
6076 else if (teamplay)
6077 {
6078 int myTeam = GetTeam();
6079 int otherTeam = other->GetTeam();
6080
6081 return (myTeam != TEAM_NONE && myTeam == otherTeam);
6082 }
6083 return false;
6084 }
6085
6086 //==========================================================================
6087 //
6088 // AActor :: GetSpecies
6089 //
6090 // Species is defined as the lowest base class that is a monster
6091 // with no non-monster class in between. If the actor specifies an explicit
6092 // species (i.e. not 'None'), that is used. This is virtualized, so special
6093 // monsters can change this behavior if they like.
6094 //
6095 //==========================================================================
6096
GetSpecies()6097 FName AActor::GetSpecies()
6098 {
6099 if (Species != NAME_None)
6100 {
6101 return Species;
6102 }
6103
6104 const PClass *thistype = GetClass();
6105
6106 if (GetDefaultByType(thistype)->flags3 & MF3_ISMONSTER)
6107 {
6108 while (thistype->ParentClass)
6109 {
6110 if (GetDefaultByType(thistype->ParentClass)->flags3 & MF3_ISMONSTER)
6111 thistype = thistype->ParentClass;
6112 else
6113 break;
6114 }
6115 }
6116 return Species = thistype->TypeName; // [GZ] Speeds up future calls.
6117 }
6118
6119 //==========================================================================
6120 //
6121 // AActor :: IsFriend
6122 //
6123 // Checks if two monsters have to be considered friendly.
6124 //
6125 //==========================================================================
6126
IsFriend(AActor * other)6127 bool AActor::IsFriend (AActor *other)
6128 {
6129 if (flags & other->flags & MF_FRIENDLY)
6130 {
6131 if (deathmatch && teamplay)
6132 return IsTeammate(other) ||
6133 (FriendPlayer != 0 && other->FriendPlayer != 0 &&
6134 players[FriendPlayer-1].mo->IsTeammate(players[other->FriendPlayer-1].mo));
6135
6136 return !deathmatch ||
6137 FriendPlayer == other->FriendPlayer ||
6138 FriendPlayer == 0 ||
6139 other->FriendPlayer == 0 ||
6140 players[FriendPlayer-1].mo->IsTeammate(players[other->FriendPlayer-1].mo);
6141 }
6142 return false;
6143 }
6144
6145 //==========================================================================
6146 //
6147 // AActor :: IsHostile
6148 //
6149 // Checks if two monsters have to be considered hostile under any circumstances
6150 //
6151 //==========================================================================
6152
IsHostile(AActor * other)6153 bool AActor::IsHostile (AActor *other)
6154 {
6155 // Both monsters are non-friendlies so hostilities depend on infighting settings
6156 if (!((flags | other->flags) & MF_FRIENDLY)) return false;
6157
6158 // Both monsters are friendly and belong to the same player if applicable.
6159 if (flags & other->flags & MF_FRIENDLY)
6160 {
6161 if (deathmatch && teamplay)
6162 return !IsTeammate(other) &&
6163 !(FriendPlayer != 0 && other->FriendPlayer != 0 &&
6164 players[FriendPlayer-1].mo->IsTeammate(players[other->FriendPlayer-1].mo));
6165
6166 return deathmatch &&
6167 FriendPlayer != other->FriendPlayer &&
6168 FriendPlayer !=0 &&
6169 other->FriendPlayer != 0 &&
6170 !players[FriendPlayer-1].mo->IsTeammate(players[other->FriendPlayer-1].mo);
6171 }
6172 return true;
6173 }
6174
DoSpecialDamage(AActor * target,int damage,FName damagetype)6175 int AActor::DoSpecialDamage (AActor *target, int damage, FName damagetype)
6176 {
6177 if (target->player && target->player->mo == target && damage < 1000 &&
6178 (target->player->cheats & CF_GODMODE || target->player->cheats & CF_GODMODE2))
6179 {
6180 return -1;
6181 }
6182 else
6183 {
6184 if (target->player)
6185 {
6186 // Only do this for old style poison damage.
6187 if (PoisonDamage > 0 && PoisonDuration == INT_MIN)
6188 {
6189 P_PoisonPlayer (target->player, this, this->target, PoisonDamage);
6190 damage >>= 1;
6191 }
6192 }
6193
6194 return damage;
6195 }
6196 }
6197
TakeSpecialDamage(AActor * inflictor,AActor * source,int damage,FName damagetype)6198 int AActor::TakeSpecialDamage (AActor *inflictor, AActor *source, int damage, FName damagetype)
6199 {
6200 FState *death;
6201
6202 // If the actor does not have a corresponding death state, then it does not take damage.
6203 // Note that DeathState matches every kind of damagetype, so an actor has that, it can
6204 // be hurt with any type of damage. Exception: Massacre damage always succeeds, because
6205 // it needs to work.
6206
6207 // Always kill if there is a regular death state or no death states at all.
6208 if (FindState (NAME_Death) != NULL || !HasSpecialDeathStates() || damagetype == NAME_Massacre)
6209 {
6210 return damage;
6211 }
6212
6213 if (inflictor && inflictor->DeathType != NAME_None)
6214 damagetype = inflictor->DeathType;
6215
6216 if (damagetype == NAME_Ice)
6217 {
6218 death = FindState (NAME_Death, NAME_Ice, true);
6219 if (death == NULL && !deh.NoAutofreeze && !(flags4 & MF4_NOICEDEATH) &&
6220 (player || (flags3 & MF3_ISMONSTER)))
6221 {
6222 death = FindState(NAME_GenericFreezeDeath);
6223 }
6224 }
6225 else
6226 {
6227 death = FindState (NAME_Death, damagetype);
6228 }
6229 return (death == NULL) ? -1 : damage;
6230 }
6231
GibHealth()6232 int AActor::GibHealth()
6233 {
6234 return -abs(GetClass()->Meta.GetMetaInt (AMETA_GibHealth, FixedMul(SpawnHealth(), gameinfo.gibfactor)));
6235 }
6236
Crash()6237 void AActor::Crash()
6238 {
6239 // [RC] Weird that this forces the Crash state regardless of flag.
6240 if(!(flags6 & MF6_DONTCORPSE))
6241 {
6242 if (((flags & MF_CORPSE) || (flags6 & MF6_KILLED)) &&
6243 !(flags3 & MF3_CRASHED) &&
6244 !(flags & MF_ICECORPSE))
6245 {
6246 FState *crashstate = NULL;
6247
6248 if (DamageType != NAME_None)
6249 {
6250 if (health < GibHealth())
6251 { // Extreme death
6252 FName labels[] = { NAME_Crash, NAME_Extreme, DamageType };
6253 crashstate = FindState (3, labels, true);
6254 }
6255 if (crashstate == NULL)
6256 { // Normal death
6257 crashstate = FindState(NAME_Crash, DamageType, true);
6258 }
6259 }
6260 if (crashstate == NULL)
6261 {
6262 if (health < GibHealth())
6263 { // Extreme death
6264 crashstate = FindState (NAME_Crash, NAME_Extreme);
6265 }
6266 else
6267 { // Normal death
6268 crashstate = FindState (NAME_Crash);
6269 }
6270 }
6271 if (crashstate != NULL) SetState(crashstate);
6272 // Set MF3_CRASHED regardless of the presence of a crash state
6273 // so this code doesn't have to be executed repeatedly.
6274 flags3 |= MF3_CRASHED;
6275 }
6276 }
6277 }
6278
SetIdle(bool nofunction)6279 void AActor::SetIdle(bool nofunction)
6280 {
6281 FState *idle = FindState (NAME_Idle);
6282 if (idle == NULL) idle = SpawnState;
6283 SetState(idle, nofunction);
6284 }
6285
SpawnHealth()6286 int AActor::SpawnHealth()
6287 {
6288 int defhealth = StartHealth ? StartHealth : GetDefault()->health;
6289 if (!(flags3 & MF3_ISMONSTER) || defhealth == 0)
6290 {
6291 return defhealth;
6292 }
6293 else if (flags & MF_FRIENDLY)
6294 {
6295 int adj = FixedMul(defhealth, G_SkillProperty(SKILLP_FriendlyHealth));
6296 return (adj <= 0) ? 1 : adj;
6297 }
6298 else
6299 {
6300 int adj = FixedMul(defhealth, G_SkillProperty(SKILLP_MonsterHealth));
6301 return (adj <= 0) ? 1 : adj;
6302 }
6303 }
6304
GetRaiseState()6305 FState *AActor::GetRaiseState()
6306 {
6307 if (!(flags & MF_CORPSE))
6308 {
6309 return NULL; // not a monster
6310 }
6311
6312 if (tics != -1 && // not lying still yet
6313 !state->GetCanRaise()) // or not ready to be raised yet
6314 {
6315 return NULL;
6316 }
6317
6318 if (IsKindOf(RUNTIME_CLASS(APlayerPawn)))
6319 {
6320 return NULL; // do not resurrect players
6321 }
6322
6323 return FindState(NAME_Raise);
6324 }
6325
Revive()6326 void AActor::Revive()
6327 {
6328 AActor *info = GetDefault();
6329 flags = info->flags;
6330 flags2 = info->flags2;
6331 flags3 = info->flags3;
6332 flags4 = info->flags4;
6333 flags5 = info->flags5;
6334 flags6 = info->flags6;
6335 flags7 = info->flags7;
6336 DamageType = info->DamageType;
6337 health = SpawnHealth();
6338 target = NULL;
6339 lastenemy = NULL;
6340
6341 // [RH] If it's a monster, it gets to count as another kill
6342 if (CountsAsKill())
6343 {
6344 level.total_monsters++;
6345 }
6346 }
6347
GetDropItems()6348 FDropItem *AActor::GetDropItems()
6349 {
6350 unsigned int index = GetClass()->Meta.GetMetaInt (ACMETA_DropItems) - 1;
6351
6352 if (index < DropItemList.Size())
6353 {
6354 return DropItemList[index];
6355 }
6356 return NULL;
6357 }
6358
GetGravity() const6359 fixed_t AActor::GetGravity() const
6360 {
6361 if (flags & MF_NOGRAVITY) return 0;
6362 return fixed_t(level.gravity * Sector->gravity * FIXED2FLOAT(gravity) * 81.92);
6363 }
6364
6365 // killough 11/98:
6366 // Whether an object is "sentient" or not. Used for environmental influences.
6367 // (left precisely the same as MBF even though it doesn't make much sense.)
IsSentient() const6368 bool AActor::IsSentient() const
6369 {
6370 return health > 0 && SeeState != NULL;
6371 }
6372
6373
6374 FSharedStringArena AActor::mStringPropertyData;
6375
GetTag(const char * def) const6376 const char *AActor::GetTag(const char *def) const
6377 {
6378 if (Tag != NULL)
6379 {
6380 const char *tag = Tag->GetChars();
6381 if (tag[0] == '$')
6382 {
6383 return GStrings(tag + 1);
6384 }
6385 else
6386 {
6387 return tag;
6388 }
6389 }
6390 else if (def)
6391 {
6392 return def;
6393 }
6394 else
6395 {
6396 return GetClass()->TypeName.GetChars();
6397 }
6398 }
6399
SetTag(const char * def)6400 void AActor::SetTag(const char *def)
6401 {
6402 if (def == NULL || *def == 0)
6403 {
6404 Tag = NULL;
6405 }
6406 else
6407 {
6408 Tag = mStringPropertyData.Alloc(def);
6409 }
6410 }
6411
6412
ClearCounters()6413 void AActor::ClearCounters()
6414 {
6415 if (CountsAsKill() && health > 0)
6416 {
6417 level.total_monsters--;
6418 flags &= ~MF_COUNTKILL;
6419 }
6420 // Same, for items
6421 if (flags & MF_COUNTITEM)
6422 {
6423 level.total_items--;
6424 flags &= ~MF_COUNTITEM;
6425 }
6426 // And finally for secrets
6427 if (flags5 & MF5_COUNTSECRET)
6428 {
6429 level.total_secrets--;
6430 flags5 &= ~MF5_COUNTSECRET;
6431 }
6432 }
6433
6434
6435 //----------------------------------------------------------------------------
6436 //
6437 // DropItem handling
6438 //
6439 //----------------------------------------------------------------------------
6440 FDropItemPtrArray DropItemList;
6441
FreeDropItemChain(FDropItem * chain)6442 void FreeDropItemChain(FDropItem *chain)
6443 {
6444 while (chain != NULL)
6445 {
6446 FDropItem *next = chain->Next;
6447 delete chain;
6448 chain = next;
6449 }
6450 }
6451
Clear()6452 void FDropItemPtrArray::Clear()
6453 {
6454 for (unsigned int i = 0; i < Size(); ++i)
6455 {
6456 FreeDropItemChain ((*this)[i]);
6457 }
6458 TArray<FDropItem *>::Clear();
6459 }
6460
StoreDropItemChain(FDropItem * chain)6461 int StoreDropItemChain(FDropItem *chain)
6462 {
6463 return DropItemList.Push (chain) + 1;
6464 }
6465
PrintMiscActorInfo(AActor * query)6466 void PrintMiscActorInfo(AActor *query)
6467 {
6468 if (query)
6469 {
6470 int flagi;
6471 int querystyle = STYLE_Count;
6472 for (int style = STYLE_None; style < STYLE_Count; ++style)
6473 { // Check for a legacy render style that matches.
6474 if (LegacyRenderStyles[style] == query->RenderStyle)
6475 {
6476 querystyle = style;
6477 break;
6478 }
6479 }
6480 static const char * renderstyles[]= {"None", "Normal", "Fuzzy", "SoulTrans",
6481 "OptFuzzy", "Stencil", "Translucent", "Add", "Shaded", "TranslucentStencil",
6482 "Shadow", "Subtract", "AddStencil", "AddShaded"};
6483
6484 Printf("%s @ %p has the following flags:\n flags: %x", query->GetTag(), query, query->flags.GetValue());
6485 for (flagi = 0; flagi <= 31; flagi++)
6486 if (query->flags & ActorFlags::FromInt(1<<flagi)) Printf(" %s", FLAG_NAME(1<<flagi, flags));
6487 Printf("\n flags2: %x", query->flags2.GetValue());
6488 for (flagi = 0; flagi <= 31; flagi++)
6489 if (query->flags2 & ActorFlags2::FromInt(1<<flagi)) Printf(" %s", FLAG_NAME(1<<flagi, flags2));
6490 Printf("\n flags3: %x", query->flags3.GetValue());
6491 for (flagi = 0; flagi <= 31; flagi++)
6492 if (query->flags3 & ActorFlags3::FromInt(1<<flagi)) Printf(" %s", FLAG_NAME(1<<flagi, flags3));
6493 Printf("\n flags4: %x", query->flags4.GetValue());
6494 for (flagi = 0; flagi <= 31; flagi++)
6495 if (query->flags4 & ActorFlags4::FromInt(1<<flagi)) Printf(" %s", FLAG_NAME(1<<flagi, flags4));
6496 Printf("\n flags5: %x", query->flags5.GetValue());
6497 for (flagi = 0; flagi <= 31; flagi++)
6498 if (query->flags5 & ActorFlags5::FromInt(1<<flagi)) Printf(" %s", FLAG_NAME(1<<flagi, flags5));
6499 Printf("\n flags6: %x", query->flags6.GetValue());
6500 for (flagi = 0; flagi <= 31; flagi++)
6501 if (query->flags6 & ActorFlags6::FromInt(1<<flagi)) Printf(" %s", FLAG_NAME(1<<flagi, flags6));
6502 Printf("\n flags7: %x", query->flags7.GetValue());
6503 for (flagi = 0; flagi <= 31; flagi++)
6504 if (query->flags7 & ActorFlags7::FromInt(1<<flagi)) Printf(" %s", FLAG_NAME(1<<flagi, flags7));
6505 Printf("\nBounce flags: %x\nBounce factors: f:%f, w:%f",
6506 query->BounceFlags.GetValue(), FIXED2FLOAT(query->bouncefactor),
6507 FIXED2FLOAT(query->wallbouncefactor));
6508 /*for (flagi = 0; flagi < 31; flagi++)
6509 if (query->BounceFlags & 1<<flagi) Printf(" %s", flagnamesb[flagi]);*/
6510 Printf("\nRender style = %i:%s, alpha %f\nRender flags: %x",
6511 querystyle, (querystyle < STYLE_Count ? renderstyles[querystyle] : "Unknown"),
6512 FIXED2FLOAT(query->alpha), query->renderflags.GetValue());
6513 /*for (flagi = 0; flagi < 31; flagi++)
6514 if (query->renderflags & 1<<flagi) Printf(" %s", flagnamesr[flagi]);*/
6515 Printf("\nSpecial+args: %s(%i, %i, %i, %i, %i)\nspecial1: %i, special2: %i.",
6516 (query->special ? LineSpecialsInfo[query->special]->name : "None"),
6517 query->args[0], query->args[1], query->args[2], query->args[3],
6518 query->args[4], query->special1, query->special2);
6519 Printf("\nTID: %d", query->tid);
6520 Printf("\nCoord= x: %f, y: %f, z:%f, floor:%f, ceiling:%f.",
6521 FIXED2FLOAT(query->X()), FIXED2FLOAT(query->Y()), FIXED2FLOAT(query->Z()),
6522 FIXED2FLOAT(query->floorz), FIXED2FLOAT(query->ceilingz));
6523 Printf("\nSpeed= %f, velocity= x:%f, y:%f, z:%f, combined:%f.\n",
6524 FIXED2FLOAT(query->Speed), FIXED2FLOAT(query->velx), FIXED2FLOAT(query->vely), FIXED2FLOAT(query->velz),
6525 sqrt(pow(FIXED2FLOAT(query->velx), 2) + pow(FIXED2FLOAT(query->vely), 2) + pow(FIXED2FLOAT(query->velz), 2)));
6526 }
6527 }
6528