Mostly Harmless

Legacy:XPawn Priority Bug

From Unreal Wiki, The Unreal Engine Documentation Site
Jump to: navigation, search

Overview

Goal

This page will describe, and will hopefully provide solutions to, the xPawn "priority bug" in UT2003 and UT2004.

Volume Priority

The priority variable found in PhysicsVolume determines which one of several overlapping PhysicsVolumes gets to apply its physics properties (Gravity, GroundFriction, ZoneVelocity and so on) on players and other actors in the overlapping area. The higher the value of priority, the higher the Priority. Every PhysicsVolume added, however, even if priority is 0, has a higher priority than the DefaultPhysicsVolume.

The Bug - In Brief

There is a bug in the implementation of the priority scheme in xPawn as it pertains to interactions with PhysicsVolumes. If the player is contained within overlapping volumes and one of the volumes has bWaterVolume set to True, splashing sounds are likely to be played when a Player walks, dodges, or whatever, regardless of which volume has priority.

Cause

How Volume Priority And Splashing Are Handled

PlayerReplicationInfo

The following code was extracted from an export of the PlayerReplicationInfo class, UT2004 build 3186

function UpdatePlayerLocation()
{
    local Volume V, Best;
    local Pawn P;
    local Controller C;
 
    C = Controller(Owner);
 
    if( C != None )
        P = C.Pawn;
 
    if( P == None )
    {
        PlayerVolume = None;
        PlayerZone = None;
        return;
    }
 
    if ( PlayerZone != P.Region.Zone )
		PlayerZone = P.Region.Zone;
 
    foreach P.TouchingActors( class'Volume', V )
    {
        if( V.LocationName == "")
            continue;
 
        if( (Best != None) && (V.LocationPriority <= Best.LocationPriority) )
            continue;
 
        if( V.Encompasses(P) )
            Best = V;
    }
    if ( PlayerVolume != Best )
		PlayerVolume = Best;
}

xPawn

The following code was extracted from an export of the xPawn class, UT2004 build 3186

simulated function FootStepping(int Side)
{
    local int SurfaceNum, i;
	local actor A;
	local material FloorMat;
	local vector HL,HN,Start,End;
 
    SurfaceNum = 0;
 
    for ( i=0; i<Touching.Length; i++ )
		if ( ((PhysicsVolume(Touching[i]) != None) && PhysicsVolume(Touching[i]).bWaterVolume)
			|| (FluidSurfaceInfo(Touching[i]) != None) )
		{
			if ( FRand() < 0.5 )
				PlaySound(sound'PlayerSounds.FootStepWater2', SLOT_Interact, FootstepVolume );
			else
				PlaySound(sound'PlayerSounds.FootStepWater1', SLOT_Interact, FootstepVolume );
			return;
		}
 
	if ( bIsCrouched || bIsWalking )
		return;
 
	if ( (Base!=None) && (!Base.IsA('LevelInfo')) && (Base.SurfaceType!=0) )
	{
		SurfaceNum = Base.SurfaceType;
	}
	else
	{
		Start = Location - Vect(0,0,1)*CollisionHeight;
		End = Start - Vect(0,0,16);
		A = Trace(hl,hn,End,Start,false,,FloorMat);
		if (FloorMat !=None)
			SurfaceNum = FloorMat.SurfaceType;
	}
	PlaySound(SoundFootsteps[SurfaceNum], SLOT_Interact, FootstepVolume,,400 );
}

PhysicsVolume

The following code was extracted from an export of the PhysicsVolume class, UT2004 build 3186

simulated function PostBeginPlay()
{
	super.PostBeginPlay();
 
	BACKUP_Gravity		= Gravity;
	BACKUP_bPainCausing	= bPainCausing;
	if( VolumeEffect == None && bWaterVolume )
		VolumeEffect = new(None) class'EFFECT_WaterVolume';
}
 
 
//
// (...)
//
 
 
event touch(Actor Other)
{
	local Pawn P;
	local bool bFoundPawn;
 
	Super.Touch(Other);
	if ( Other == None )
		return;
	if ( bNoInventory && (Pickup(Other) != None) && (Other.Owner == None) )
	{
		Other.LifeSpan = 1.5;
		return;
	}
	if ( bMoveProjectiles && (ZoneVelocity != vect(0,0,0)) )
	{
		if ( Other.Physics == PHYS_Projectile )
			Other.Velocity += ZoneVelocity;
		else if ( (Other.Base == None) && Other.IsA('Emitter') && (Other.Physics == PHYS_None) )
		{
			Other.SetPhysics(PHYS_Projectile);
			Other.Velocity += ZoneVelocity;
		}
	}
	if ( bPainCausing )
	{
		if ( Other.bDestroyInPainVolume )
		{
			Other.Destroy();
			return;
		}
		if ( Other.bCanBeDamaged && !Other.bStatic )
		{
			CausePainTo(Other);
			if ( Other == None )
				return;
			if ( PainTimer == None )
				PainTimer = Spawn(class'VolumeTimer', self);
			else if ( Pawn(Other) != None )
			{
				ForEach TouchingActors(class'Pawn', P)
					if ( (P != Other) && P.bCanBeDamaged )
					{
						bFoundPawn = true;
						break;
					}
				if ( !bFoundPawn )
					PainTimer.SetTimer(1.0,true);
			}
		}
	}
	if ( bWaterVolume && Other.CanSplash() )
		PlayEntrySplash(Other);
}
 
 
//
// (...)
//
 
 
function PlayEntrySplash(Actor Other)
{
	local float SplashSize;
	local actor splash;
 
	splashSize = FClamp(0.00003 * Other.Mass * (250 - 0.5 * FMax(-600,Other.Velocity.Z)), 0.1, 1.0 );
	if( EntrySound != None )
	{
		PlaySound(EntrySound, SLOT_Interact, splashSize);
		if ( Other.Instigator != None )
			MakeNoise(SplashSize);
	}
	if( EntryActor != None )
	{
		splash = Spawn(EntryActor);
		if ( splash != None )
			splash.SetDrawScale(splashSize);
	}
}
 
 
event untouch(Actor Other)
{
	if ( bWaterVolume && Other.CanSplash() )
		PlayExitSplash(Other);
}
 
 
function PlayExitSplash(Actor Other)
{
	local float SplashSize;
	local actor splash;
 
	splashSize = FClamp(0.003 * Other.Mass, 0.1, 1.0 );
	if( ExitSound != None )
		PlaySound(ExitSound, SLOT_Interact, splashSize);
	if( ExitActor != None )
	{
		splash = Spawn(ExitActor);
		if ( splash != None )
			splash.SetDrawScale(splashSize);
	}
}

The Problem

The most obvious problem is that the FootStepping() function in xPawn takes it upon itself to check for touching PhysicsVolumes, rather than using the PlayerVolume var in PlayerReplicationInfo, in order to determine if a "splash" should be "played". In doing this, it bypasses the priority check made in the UpdatePlayerLocation() function of PlayerReplicationInfo, thus eliminating the priority scheme for the "splashing" functionality.

Solutions

Proposal One: Avoid Overlapping Volumes

Note: Obviously, this is just a work-around...

Just like the title says: Avoid overlapping PhysicsVolumes.

Pros:

  • You avoid the issue all together

Cons:

  • You potentially sacrifice certain senarios–"dynamic" volumes attached to movers interacting with one another, for instance.

Proposal Two: Modify xPawn

One solution may be to add a new check to the simulated function FootStepping() in xPawn (or in your own new pawn class) that compares touching volumes with bWaterVolume set to True with PlayerVolume in PlayerReplicationInfo. If they are not one in the same, skip the splashing code.

Pros:

  • Will likely solve the problem

Cons:

  • By extending xPawn, you now essentially are forced into making a mod/mutator in order to impliment your changes. Pawn-type is set in the player class (by default, xPlayer) which is in turn is set by a GameInfo actor like xDeathMatch.