Jump to content
Search In
  • More options...
Find results that contain...
Find results in...
Sign in to follow this  
Gez

Hexen mystery: poison clouds, mushrooms, and repulsion

Recommended Posts

Some of you probably you this trick:


The poison cloud spawned from a cleric's flechette can be blasted away with a disc of repulsion, creating a projectile that deals a lot of damage. (Explained because of the actor's mass of MAXINT, since collision damage inflicted by blasted things depends on their mass.)

But it gets weird. This is the poison cloud definition:
{		// MT_POISONCLOUD
-1,		// doomednum
S_POISONCLOUD1,		// spawnstate
1000,		// spawnhealth
S_NULL,		// seestate
SFX_NONE,		// seesound
8,		// reactiontime
SFX_NONE,		// attacksound
S_NULL,		// painstate
0,		// painchance
SFX_NONE,		// painsound
S_NULL,		// meleestate
S_NULL,		// missilestate
S_NULL,		// crashstate
S_NULL,		// deathstate
S_NULL,		// xdeathstate
SFX_POISONSHROOM_DEATH,		// deathsound
0,		// speed
1,		// radius
1,		// height
MAXINT,		// mass
0,		// damage
SFX_NONE,		// activesound
MF_NOGRAVITY|MF_NOBLOCKMAP|MF_SHADOW|MF_NOCLIP|MF_DROPOFF,		// flags
MF2_NODMGTHRUST		// flags2
 },
Here's the function that determines whether something can be blasted or not:
// Blast all mobj things away
void P_BlastRadius(player_t *player)
{
	mobj_t *mo;
	mobj_t *pmo=player->mo;
	thinker_t *think;
	fixed_t dist;

	S_StartSound(pmo, SFX_ARTIFACT_BLAST);
	P_NoiseAlert(player->mo, player->mo);

	for(think = thinkercap.next; think != &thinkercap; think = think->next)
	{
		if(think->function != P_MobjThinker)
		{ // Not a mobj thinker
			continue;
		}
		mo = (mobj_t *)think;
		if((mo == pmo) || (mo->flags2&MF2_BOSS))
		{ // Not a valid monster
			continue;
		}
		if ((mo->type == MT_POISONCLOUD) ||		// poison cloud
			(mo->type == MT_HOLY_FX) ||			// holy fx
			(mo->flags&MF_ICECORPSE))			// frozen corpse
		{
			// Let these special cases go
		}
		else if ((mo->flags&MF_COUNTKILL) &&
			(mo->health <= 0))
		{
			continue;
		}
		else if (!(mo->flags&MF_COUNTKILL) &&
			!(mo->player) &&
			!(mo->flags&MF_MISSILE))
		{	// Must be monster, player, or missile
			continue;
		}
		if (mo->flags2&MF2_DORMANT)
		{
			continue;		// no dormant creatures
		}
		if ((mo->type == MT_WRAITHB) && (mo->flags2&MF2_DONTDRAW))
		{
			continue;		// no underground wraiths
		}
		if ((mo->type == MT_SPLASHBASE) ||
			(mo->type == MT_SPLASH))
		{
			continue;
		}
		if(mo->type == MT_SERPENT || mo->type == MT_SERPENTLEADER)
		{
			continue;
		}
		dist = P_AproxDistance(pmo->x - mo->x, pmo->y - mo->y);
		if(dist > BLAST_RADIUS_DIST)
		{ // Out of range
			continue;
		}
		P_BlastMobj(pmo, mo, BLAST_FULLSTRENGTH);
	}
}
The relevant part here is:
		if ((mo->type == MT_POISONCLOUD) ||		// poison cloud
			(mo->type == MT_HOLY_FX) ||			// holy fx
			(mo->flags&MF_ICECORPSE))			// frozen corpse
		{
			// Let these special cases go
		}
The function doing the actual blasting does not have any special case handling for MT_POISONCLOUD, so it's treated like all others when it gets there. But here it is for the curious:
void P_BlastMobj(mobj_t *source, mobj_t *victim, fixed_t strength)
{
	angle_t angle,ang;
	mobj_t *mo;
	fixed_t x,y,z;

	angle = R_PointToAngle2(source->x, source->y, victim->x, victim->y);
	angle >>= ANGLETOFINESHIFT;
	if (strength < BLAST_FULLSTRENGTH)
	{
		victim->momx = FixedMul(strength, finecosine[angle]);
		victim->momy = FixedMul(strength, finesine[angle]);
		if (victim->player)
		{
			// Players handled automatically
		}
		else
		{
			victim->flags2 |= MF2_SLIDE;
			victim->flags2 |= MF2_BLASTED;
		}
	}
	else		// full strength blast from artifact
	{
		if (victim->flags&MF_MISSILE)
		{
			switch(victim->type)
			{
				case MT_SORCBALL1:	// don't blast sorcerer balls
				case MT_SORCBALL2:
				case MT_SORCBALL3:
					return;
					break;
				case MT_MSTAFF_FX2:	// Reflect to originator
					victim->special1 = (int)victim->target;	
					victim->target = source;
					break;
				default:
					break;
			}
		}
		if (victim->type == MT_HOLY_FX)
		{
			if ((mobj_t *)(victim->special1) == source)
			{
				victim->special1 = (int)victim->target;	
				victim->target = source;
			}
		}
		victim->momx = FixedMul(BLAST_SPEED, finecosine[angle]);
		victim->momy = FixedMul(BLAST_SPEED, finesine[angle]);

		// Spawn blast puff
		ang = R_PointToAngle2(victim->x, victim->y, source->x, source->y);
		ang >>= ANGLETOFINESHIFT;
		x = victim->x + FixedMul(victim->radius+FRACUNIT, finecosine[ang]);
		y = victim->y + FixedMul(victim->radius+FRACUNIT, finesine[ang]);
		z = victim->z - victim->floorclip + (victim->height>>1);
		mo=P_SpawnMobj(x, y, z, MT_BLASTEFFECT);
		if (mo)
		{
			mo->momx = victim->momx;
			mo->momy = victim->momy;
		}

		if (victim->flags&MF_MISSILE)
		{
			victim->momz = 8*FRACUNIT;
			mo->momz = victim->momz;
		}
		else
		{
			victim->momz = (1000/victim->info->mass)<<FRACBITS;
		}
		if (victim->player)
		{
			// Players handled automatically
		}
		else
		{
			victim->flags2 |= MF2_SLIDE;
			victim->flags2 |= MF2_BLASTED;
		}
	}
}
This MT_POISONCLOUD actor is spawned by one function, this one:
void A_PoisonBagInit(mobj_t *actor)
{
	mobj_t *mo;

	mo = P_SpawnMobj(actor->x, actor->y, actor->z+28*FRACUNIT,
		MT_POISONCLOUD);
	if(!mo)
	{
		return;
	}
	mo->momx = 1; // missile objects must move to impact other objects
	mo->special1 = 24+(P_Random()&7);
	mo->special2 = 0;
	mo->target = actor->target;
	mo->radius = 20*FRACUNIT;
	mo->height = 30*FRACUNIT;
	mo->flags &= ~MF_NOCLIP;
}
There are two states which call this function; here's one, the activated cleric's flechette:
{SPR_PSBG,32768,18,NULL,S_POISONBAG2,0,0},	// S_POISONBAG1
{SPR_PSBG,32769,4,NULL,S_POISONBAG3,0,0},	// S_POISONBAG2
{SPR_PSBG,2,3,NULL,S_POISONBAG4,0,0},	// S_POISONBAG3
{SPR_PSBG,2,1,A_PoisonBagInit,S_NULL,0,0},	// S_POISONBAG4
And here's the other, the poison mushroom:
{SPR_SHRM,0,5,A_PoisonShroom,S_ZPOISONSHROOM_P2,0,0},	// S_ZPOISONSHROOM1
{SPR_SHRM,0,6,NULL,S_ZPOISONSHROOM_P2,0,0},	// S_ZPOISONSHROOM_P1
{SPR_SHRM,1,8,A_Pain,S_ZPOISONSHROOM1,0,0},	// S_ZPOISONSHROOM_P2
{SPR_SHRM,2,5,NULL,S_ZPOISONSHROOM_X2,0,0},	// S_ZPOISONSHROOM_X1
{SPR_SHRM,3,5,NULL,S_ZPOISONSHROOM_X3,0,0},	// S_ZPOISONSHROOM_X2
{SPR_SHRM,4,5,A_PoisonBagInit,S_ZPOISONSHROOM_X4,0,0},	// S_ZPOISONSHROOM_X3
{SPR_SHRM,5,-1,NULL,S_NULL,0,0},	// S_ZPOISONSHROOM_X4
So now the question is: why cannot the cloud blasting trick work when the poison cloud is spawned from a mushroom rather than a flechette?

It's the same actor which is spawned with the same function and treated in the same way by the P_BlastRadius() function. Where is the difference?

Share this post


Link to post

The only differences I can think of would be maybe the ownership or the height the poison cloud spawns at? But there doesn't seem to be any code where that would matter..

Perhaps using a slow, but hopefully fool-proof method, you could figure out the difference:
Choose either poison cloud spawner of the two, the mushroom or the flechette. Start changing its relevant code little by little towards the other one, testing after each change, untill both poison cloud spawners act exactly the same. If its possible to 'transform' the code of one to the other, then maybe you'll spot which change affects the blasting..

..If the code that is resulting in the different behaviour for one or the other is not specific to them, then this method probably doesn't work.

Share this post


Link to post

The interesting thing is that the graphic of something being hit by the disc does appear on a mushroom spawned poison cloud when a disc is used on one and flies off as if the cloud was affected.

Share this post


Link to post
Worst said:

The only differences I can think of would be maybe the ownership or the height the poison cloud spawns at? But there doesn't seem to be any code where that would matter..

There is no ownership. The height is the same (28 units above the spawner's origin). The only thing that's taken from the spawner is the target -- but there are no target checks for MT_POISONCLOUD in P_BlastRadius!

Worst said:

Perhaps using a slow, but hopefully fool-proof method, you could figure out the difference:

I can't compile the Hexen source code.

Vermil said:

The interesting thing is that the graphic of something being hit by the disc does appear on a mushroom spawned poison cloud when a disc is used on one and flies off as if the cloud was affected.

Well, it IS affected, and that was a deliberate design decision from Raven Software given the "// Let these special cases go" comment.

What's odd is that if it is spawned from a dying mushroom, then it is not affected. This discrepancy must have an explanation somewhere, but I don't see where.

Share this post


Link to post
Graf Zahl said:

Maybe it gets stuck in the mushroom? The dead Mushroom's MF_SOLID flag is never cleared.


You are right. That is why a mushroom created cloud doesn't move when hit by a disc; it is stuck in the mushroom when it spawns.

Remove the MF_SOLID flag from the mushroom and a mushroom spawned poison cloud can be moved around by the disc.

I'd say it was intentional by Raven, abeit, maybe not the best way to do it.

Share this post


Link to post

Maybe, though the shroom is 20 units high and the cloud is spawned 28 units above the spawner's origin, so there is 8 units of vacuum between the "dead" mushroom and the cloud. So if it gets stuck, it's because of poorly-written over/under physics code rather than a deliberate decision; if it was wanted the shroom would have been 28 or more units tall.

{		// MT_ZPOISONSHROOM
8104,		// doomednum
S_ZPOISONSHROOM1,		// spawnstate
30,		// spawnhealth
S_NULL,		// seestate
SFX_NONE,		// seesound
8,		// reactiontime
SFX_NONE,		// attacksound
S_ZPOISONSHROOM_P1,		// painstate
255,		// painchance
SFX_POISONSHROOM_PAIN,		// painsound
S_NULL,		// meleestate
S_NULL,		// missilestate
S_NULL,		// crashstate
S_ZPOISONSHROOM_X1,		// deathstate
S_NULL,		// xdeathstate
SFX_POISONSHROOM_DEATH,		// deathsound
0,		// speed
6*FRACUNIT,		// radius
20*FRACUNIT,		// height
MAXINT,		// mass
0,		// damage
SFX_NONE,		// activesound
MF_SHOOTABLE|MF_SOLID|MF_NOBLOOD,		// flags
0		// flags2
 },

Share this post


Link to post

Wheter or not it actually was intentional. Making a mushroom spawned poison cloud not get stuck in the mushroom would impact game play.

Share this post


Link to post

Height is not relevant here because both actors involved don't use the MF2_PASSMOBJ flag so for the collision detection code they are infinitely tall.

The reason this does not happen in ZDoom is because of a Boom fix concerning the collision detection between solid and non-solid things.

Well, at least now I know how to fix it...

Share this post


Link to post

Amusingly, Doomsday has introduced this mistake somewhere in the 1.9 betas.

In 1.8.6 the cloud spawned by a mushroom "correctly" doesn't move when shot by a disc, but in 1.9 beta 6.9 it does.

Share this post


Link to post

Its exactly the same issue in Doomsday and yes, it will be fixed and compat-optioned.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  
×