2008-08-23 08:48:19 +00:00
/*
* * a_randomspawner . cpp
* * A thing that randomly spawns one item in a list of many , before disappearing .
2009-09-15 14:16:55 +00:00
* * bouncecount is used to keep track of recursions ( so as to prevent infinite loops ) .
* * Species is used to store the index of the spawned actor ' s name .
2008-08-23 08:48:19 +00:00
*/
2008-06-22 09:13:19 +00:00
# include "actor.h"
# include "info.h"
# include "m_random.h"
# include "p_local.h"
# include "p_enemy.h"
# include "s_sound.h"
# include "statnums.h"
# include "gstrings.h"
# include "a_action.h"
# include "thingdef/thingdef.h"
2015-09-19 15:29:59 +00:00
# include "v_text.h"
2008-06-22 09:13:19 +00:00
2008-08-23 08:48:19 +00:00
# define MAX_RANDOMSPAWNERS_RECURSION 32 // Should be largely more than enough, honestly.
2008-06-22 09:13:19 +00:00
static FRandom pr_randomspawn ( " RandomSpawn " ) ;
2016-01-17 18:50:34 +00:00
static bool IsMonster ( DDropItem * di )
2015-09-19 15:29:07 +00:00
{
const PClass * pclass = PClass : : FindClass ( di - > Name ) ;
if ( NULL = = pclass )
{
return false ;
}
2015-10-11 06:28:38 +00:00
return 0 ! = ( GetDefaultByType ( pclass ) - > flags3 & MF3_ISMONSTER ) ;
2015-09-19 15:29:07 +00:00
}
2008-06-22 09:13:19 +00:00
class ARandomSpawner : public AActor
{
2008-08-09 11:35:42 +00:00
DECLARE_CLASS ( ARandomSpawner , AActor )
2008-06-22 09:13:19 +00:00
2009-09-15 14:16:55 +00:00
// To handle "RandomSpawning" missiles, the code has to be split in two parts.
// If the following code is not done in BeginPlay, missiles will use the
// random spawner's velocity (0...) instead of their own.
void BeginPlay ( )
2008-06-22 09:13:19 +00:00
{
2010-03-25 20:38:00 +00:00
DDropItem * di ; // di will be our drop item list iterator
DDropItem * drop ; // while drop stays as the reference point.
int n = 0 ;
2013-02-20 02:47:01 +00:00
bool nomonsters = ( dmflags & DF_NO_MONSTERS ) | | ( level . flags2 & LEVEL2_NOMONSTERS ) ;
2008-06-22 09:13:19 +00:00
2009-09-15 14:16:55 +00:00
Super : : BeginPlay ( ) ;
2008-09-21 18:02:38 +00:00
drop = di = GetDropItems ( ) ;
2008-06-22 09:13:19 +00:00
if ( di ! = NULL )
{
while ( di ! = NULL )
{
if ( di - > Name ! = NAME_None )
{
2015-09-19 15:29:07 +00:00
if ( ! nomonsters | | ! IsMonster ( di ) )
2013-02-20 02:47:01 +00:00
{
2013-06-07 03:31:30 +00:00
if ( di - > Amount < 0 ) di - > Amount = 1 ; // default value is -1, we need a positive value.
n + = di - > Amount ; // this is how we can weight the list.
2013-02-20 02:47:01 +00:00
}
2008-06-22 09:13:19 +00:00
di = di - > Next ;
}
}
2013-02-20 02:47:01 +00:00
if ( n = = 0 )
{ // Nothing left to spawn. They must have all been monsters, and monsters are disabled.
Destroy ( ) ;
return ;
}
2008-06-22 09:13:19 +00:00
// Then we reset the iterator to the start position...
di = drop ;
// Take a random number...
n = pr_randomspawn ( n ) ;
// And iterate in the array up to the random number chosen.
2013-02-22 01:34:16 +00:00
while ( n > - 1 & & di ! = NULL )
2008-06-22 09:13:19 +00:00
{
2013-02-22 01:34:16 +00:00
if ( di - > Name ! = NAME_None & &
2015-09-19 15:29:07 +00:00
( ! nomonsters | | ! IsMonster ( di ) ) )
2008-06-22 09:13:19 +00:00
{
2010-03-25 20:38:00 +00:00
n - = di - > Amount ;
2013-02-22 01:34:16 +00:00
if ( ( di - > Next ! = NULL ) & & ( n > - 1 ) )
di = di - > Next ;
else
n = - 1 ;
}
else
{
di = di - > Next ;
2008-06-22 09:13:19 +00:00
}
}
// So now we can spawn the dropped item.
2013-02-22 01:34:16 +00:00
if ( di = = NULL | | bouncecount > = MAX_RANDOMSPAWNERS_RECURSION ) // Prevents infinite recursions
2009-09-15 14:16:55 +00:00
{
2016-01-19 12:26:05 +00:00
Spawn ( " Unknown " , Pos ( ) , NO_REPLACE ) ; // Show that there's a problem.
2013-02-20 02:47:01 +00:00
Destroy ( ) ;
return ;
2009-09-15 14:16:55 +00:00
}
2010-03-25 20:38:00 +00:00
else if ( pr_randomspawn ( ) < = di - > Probability ) // prob 255 = always spawn, prob 0 = almost never spawn.
2008-06-22 09:13:19 +00:00
{
2009-09-15 14:16:55 +00:00
// Handle replacement here so as to get the proper speed and flags for missiles
2010-03-24 02:49:37 +00:00
PClassActor * cls ;
cls = PClass : : FindActor ( di - > Name ) ;
2009-11-08 02:51:22 +00:00
if ( cls ! = NULL )
{
2010-03-24 02:49:37 +00:00
PClassActor * rep = cls - > GetReplacement ( ) ;
2009-11-08 02:51:22 +00:00
if ( rep ! = NULL )
{
cls = rep ;
}
}
if ( cls ! = NULL )
2009-06-07 16:38:19 +00:00
{
2009-09-15 14:16:55 +00:00
Species = cls - > TypeName ;
2009-11-08 02:51:22 +00:00
AActor * defmobj = GetDefaultByType ( cls ) ;
2009-09-15 14:16:55 +00:00
this - > Speed = defmobj - > Speed ;
this - > flags | = ( defmobj - > flags & MF_MISSILE ) ;
this - > flags2 | = ( defmobj - > flags2 & MF2_SEEKERMISSILE ) ;
this - > flags4 | = ( defmobj - > flags4 & MF4_SPECTRAL ) ;
2009-06-07 16:38:19 +00:00
}
2009-11-08 02:51:22 +00:00
else
{
2015-09-19 15:29:59 +00:00
Printf ( TEXTCOLOR_RED " Unknown item class %s to drop from a random spawner \n " , di - > Name . GetChars ( ) ) ;
2009-11-08 02:51:22 +00:00
Species = NAME_None ;
}
2009-09-15 14:16:55 +00:00
}
}
}
// The second half of random spawning. Now that the spawner is initialized, the
// real actor can be created. If the following code were in BeginPlay instead,
// missiles would not have yet obtained certain information that is absolutely
// necessary to them -- such as their source and destination.
void PostBeginPlay ( )
{
2012-03-07 01:03:56 +00:00
Super : : PostBeginPlay ( ) ;
2012-07-14 03:04:41 +00:00
2010-04-03 04:07:17 +00:00
AActor * newmobj = NULL ;
2009-09-15 14:16:55 +00:00
bool boss = false ;
2012-07-14 03:04:41 +00:00
2010-04-03 04:07:17 +00:00
if ( Species = = NAME_None )
{
Destroy ( ) ;
return ;
}
PClassActor * cls = PClass : : FindActor ( Species ) ;
2009-09-15 14:16:55 +00:00
if ( this - > flags & MF_MISSILE & & target & & target - > target ) // Attempting to spawn a missile.
{
2010-04-03 04:07:17 +00:00
if ( ( tracer = = NULL ) & & ( flags2 & MF2_SEEKERMISSILE ) )
{
tracer = target - > target ;
}
2016-01-19 12:26:05 +00:00
newmobj = P_SpawnMissileXYZ ( Pos ( ) , target , target - > target , cls , false ) ;
2009-09-15 14:16:55 +00:00
}
2016-01-19 12:43:11 +00:00
else
{
newmobj = Spawn ( cls , Pos ( ) , NO_REPLACE ) ;
2010-04-03 04:07:17 +00:00
}
2009-09-15 14:16:55 +00:00
if ( newmobj ! = NULL )
{
// copy everything relevant
newmobj - > SpawnAngle = newmobj - > angle = angle ;
2010-04-26 01:49:29 +00:00
newmobj - > SpawnPoint [ 2 ] = SpawnPoint [ 2 ] ;
2009-09-15 14:16:55 +00:00
newmobj - > special = special ;
newmobj - > args [ 0 ] = args [ 0 ] ;
newmobj - > args [ 1 ] = args [ 1 ] ;
newmobj - > args [ 2 ] = args [ 2 ] ;
newmobj - > args [ 3 ] = args [ 3 ] ;
newmobj - > args [ 4 ] = args [ 4 ] ;
2009-09-16 15:57:08 +00:00
newmobj - > special1 = special1 ;
newmobj - > special2 = special2 ;
2013-08-28 09:14:48 +00:00
newmobj - > SpawnFlags = SpawnFlags & ~ MTF_SECRET ; // MTF_SECRET needs special treatment to avoid incrementing the secret counter twice. It had already been processed for the spawner itself.
2009-09-15 14:16:55 +00:00
newmobj - > HandleSpawnFlags ( ) ;
2013-08-28 09:14:48 +00:00
newmobj - > SpawnFlags = SpawnFlags ;
2009-09-15 14:16:55 +00:00
newmobj - > tid = tid ;
newmobj - > AddToHash ( ) ;
newmobj - > velx = velx ;
newmobj - > vely = vely ;
newmobj - > velz = velz ;
newmobj - > master = master ; // For things such as DamageMaster/DamageChildren, transfer mastery.
newmobj - > target = target ;
newmobj - > tracer = tracer ;
newmobj - > CopyFriendliness ( this , false ) ;
// This handles things such as projectiles with the MF4_SPECTRAL flag that have
// a health set to -2 after spawning, for internal reasons.
if ( health ! = SpawnHealth ( ) ) newmobj - > health = health ;
if ( ! ( flags & MF_DROPPED ) ) newmobj - > flags & = ~ MF_DROPPED ;
2010-04-26 01:49:29 +00:00
// Handle special altitude flags
2009-09-15 14:16:55 +00:00
if ( newmobj - > flags & MF_SPAWNCEILING )
{
2016-01-19 12:26:05 +00:00
newmobj - > SetZ ( newmobj - > ceilingz - newmobj - > height - SpawnPoint [ 2 ] ) ;
2009-09-15 14:16:55 +00:00
}
else if ( newmobj - > flags2 & MF2_SPAWNFLOAT )
{
fixed_t space = newmobj - > ceilingz - newmobj - > height - newmobj - > floorz ;
if ( space > 48 * FRACUNIT )
2009-06-07 16:38:19 +00:00
{
2009-09-15 14:16:55 +00:00
space - = 40 * FRACUNIT ;
2016-01-19 12:26:05 +00:00
newmobj - > SetZ ( MulScale8 ( space , pr_randomspawn ( ) ) + newmobj - > floorz + 40 * FRACUNIT ) ;
2009-06-07 16:38:19 +00:00
}
2016-01-19 12:26:05 +00:00
newmobj - > AddZ ( SpawnPoint [ 2 ] ) ;
2008-06-22 09:13:19 +00:00
}
2009-09-15 14:16:55 +00:00
if ( newmobj - > flags & MF_MISSILE )
2013-03-21 03:06:04 +00:00
P_CheckMissileSpawn ( newmobj , 0 ) ;
2009-09-15 14:16:55 +00:00
// Bouncecount is used to count how many recursions we're in.
if ( newmobj - > IsKindOf ( PClass : : FindClass ( " RandomSpawner " ) ) )
newmobj - > bouncecount = + + bouncecount ;
// If the spawned actor has either of those flags, it's a boss.
if ( ( newmobj - > flags4 & MF4_BOSSDEATH ) | | ( newmobj - > flags2 & MF2_BOSS ) )
boss = true ;
// If a replaced actor has either of those same flags, it's also a boss.
2010-03-24 02:49:37 +00:00
AActor * rep = GetDefaultByType ( GetClass ( ) - > GetReplacee ( ) ) ;
2009-11-08 02:51:22 +00:00
if ( rep & & ( ( rep - > flags4 & MF4_BOSSDEATH ) | | ( rep - > flags2 & MF2_BOSS ) ) )
2009-09-15 14:16:55 +00:00
boss = true ;
2008-06-22 09:13:19 +00:00
}
2013-02-20 02:47:01 +00:00
if ( boss )
this - > tracer = newmobj ;
else // "else" because a boss-replacing spawner must wait until it can call A_BossDeath.
Destroy ( ) ;
2008-06-22 09:13:19 +00:00
}
2008-08-23 08:48:19 +00:00
void Tick ( ) // This function is needed for handling boss replacers
{
Super : : Tick ( ) ;
2009-09-15 14:16:55 +00:00
if ( tracer = = NULL | | tracer - > health < = 0 )
2008-08-23 08:48:19 +00:00
{
2010-02-12 06:04:57 +00:00
A_BossDeath ( this ) ;
2008-08-23 08:48:19 +00:00
Destroy ( ) ;
}
}
2008-06-22 09:13:19 +00:00
} ;
2008-08-09 11:35:42 +00:00
IMPLEMENT_CLASS ( ARandomSpawner )