Okay code done, seems to work flawless. Things which should be checked if how the PlayFootStep() stuff reacts on movers, but i guess their Brush is part of the BSP geometry anyway, so this might not be an issue (at least it seemed to work right for elevators).
Next step would be to dig up a nice system for when you are running over a decoration, let's say a wooden table. I guess i'll probably add a property like var() Sound FootStepSound; for my HXDecorations, but i'm not quite sure how exactly to figure out whether i'm on a decoration or over bsp geometry. Maybe just using the actor which is not Pawn/PlayerPawn himself would work fine, but i'm not sure nice this is if are walking over a datacube for example. Dunno.
Anyway for GetWallMaterial() and GetFloorMaterial() i recommend just calling it on demand (is there for GetFloorMaterial() even a demand?), not on each PlayerTick() which is pointless and also wastes bandwidth, since the WallMaterial and FloorMatrial is replicated to the server which is point less anyway. So i recommend not using these variables anymore. For the footsteps the way better approach is like the ScriptedPawn does:
Code: Select all
local name mat;
// [..]
mat = GetFloorMaterial();
// [..]
switch(mat)
// [..]
Another cool thing for footstep sounds is to vary their volume slightly:
Code: Select all
// HX: slightly vary playback volume
randomPlaybackVolumeMultiplier = 0.9 + 0.2 * FRand();
PlaySound(stepSound, SLOT_Interact, volume*randomPlaybackVolumeMultiplier, , range, pitch);
However i recommand just changing the parameter of PlaySound(), not volume directly as this is also used for the AISendEvent() unless you want that. However you should just change the first for network play, since the soundvolume for the local player is different then on the server so the pawns suddenly reacting to a sound which was more silent but on the server loader is a bit weired.
However the code for the fixed TraceTexture()
Code: Select all
// Find the FBspNode for Point.
INT FindNode( UModel* Model, INT iPlane, FVector Point )
{
guard(FindNode);
check(Model);
INT Index = iPlane;
while ( Index != INDEX_NONE )
{
FBspNode& Node = Model->Nodes(Index);
if ( Node.iSurf != INDEX_NONE )
{
FBspSurf& Surf = Model->Surfs(Node.iSurf);
//debugf( TEXT("FindNode: Testing: FBspNode(%i) with FBspSurf(%i).Texture %s."), Index, Node.iSurf, Surf.Texture ? Surf.Texture->GetName() : TEXT("None") );
UBOOL Valid = 1;
FLOAT OldPlaneDot = 0.0f;
// Check for NumVertices == 0
if ( Node.NumVertices )
{
for ( INT i = 0; i < Node.NumVertices && Valid; i++ )
{
// Get Points.
FVector& A = Model->Points(Model->Verts(Node.iVertPool + i).pVertex);
FVector& B = Model->Points(Model->Verts(Node.iVertPool + ((i+1) % Node.NumVertices)).pVertex);
// Now build a plane.
FPlane Plane( A, (B-A)^FVector( Node.Plane.X, Node.Plane.Y, Node.Plane.Z ) );
// Now check Point against this plane.
FLOAT PlaneDot = Plane.PlaneDot( Point );
// Sign changed, check next FBspNode.
if ( OldPlaneDot * PlaneDot < 0.0f )
{
Valid = 0;
}
// Sign stayed the same, everything fine.
else
{
OldPlaneDot = PlaneDot;
}
}
// Passed check.
if ( Valid )
{
//debugf( TEXT("FindNode: Passed.") );
return Index;
}
//else
//{
//debugf( TEXT("FindNode: Failed.") );
//}
}
//else
//{
//debugf( TEXT("FindNode: Skipping: Node.NumVertices == 0.") );
//}
}
//else
//{
//debugf( TEXT("FindNode: Skipping: Node.iSurf == INDEX_NONE.") );
//}
Index = Node.iPlane;
}
return INDEX_NONE;
unguard;
}
//native(3220) final iterator function TraceTextureHan( class<Actor> BaseClass, out actor Actor, out name TexName, out name TexGroup, out int Flags, out vector HitLoc, out vector HitNorm, vector End, optional vector Start, optional vector Extent )
void AHXPlayerPawn::execTraceTextureHan(FFrame& Stack, RESULT_DECL)
{
guard(AHXPlayerPawn::execTraceTextureHan);
P_GET_OBJECT( UClass, BaseClass );
P_GET_OBJECT_REF( AActor, Actor );
P_GET_NAME_REF( TexName );
P_GET_NAME_REF( TexGroup );
P_GET_INT_REF( Flags );
P_GET_VECTOR_REF( HitLoc );
P_GET_VECTOR_REF( HitNorm );
P_GET_VECTOR( End );
P_GET_VECTOR_OPTX( Start, Location );
P_GET_VECTOR_OPTX( Extent, FVector( 0.0f, 0.0f, 0.0f ) );
P_FINISH;
FMemMark MemMark( GMem );
if ( !BaseClass )
BaseClass = AActor::StaticClass();
FCheckResult* Res = XLevel->MultiLineCheck( GMem, End, Start, Extent, 1, Level, 0 );
PRE_ITERATOR
if ( Res )
{
*Actor = Res->Actor;
*HitLoc = Res->Location;
*HitNorm = Res->Normal;
*TexName = NAME_None;
*TexGroup = NAME_None;
*Flags = 0;
if ( Res->Actor == Level )
{
Res->Item = FindNode( XLevel->Model, Res->Item, Res->Location );
if ( Res->Item != INDEX_NONE )
{
FBspNode& Node = XLevel->Model->Nodes(Res->Item);
if ( Node.iSurf != INDEX_NONE )
{
FBspSurf& Surf = XLevel->Model->Surfs(Node.iSurf);
if ( Surf.Texture )
{
*TexName = Surf.Texture->GetFName();
if ( Surf.Texture->GetOuter() )
{
*TexGroup = Surf.Texture->GetOuter()->GetFName();
}
}
*Flags = Surf.PolyFlags;
}
}
}
Res = (FCheckResult *)Res->Next;
}
else
{
Stack.Code = &Stack.Node->Script( wEndOffset + 1 );
*Actor = NULL;
break;
}
POST_ITERATOR
MemMark.Pop();
unguardexec;
}
As for changes in GetFloorMaterial() i recommend to change it this way, since the Tex* parameters are just set if the trace hit the Level. The second one is some conveniant testing exec function is used:
Code: Select all
function name GetFloorMaterial()
{
local vector EndTrace, HitLocation, HitNormal;
local actor TraceTarget;
local int texFlags;
local name texName, texGroup;
// trace down to our feet
EndTrace = Location - CollisionHeight * 2 * vect(0,0,1);
foreach TraceTextureHan(class'Actor', TraceTarget, TexName, TexGroup, TexFlags, HitLocation, HitNormal, EndTrace)
{
if ( TraceTarget == Level )
break;
}
return TexGroup;
}
exec function TT2()
{
local vector EndTrace, HitLocation, HitNormal;
local actor TraceTarget;
local int texFlags;
local name texName, texGroup;
// trace down to our feet
EndTrace = Location - CollisionHeight * 3 * vect(0,0,1);
Log( "----[TraceTextureHan]-----------------------------------------------", 'TraceTexture' );
foreach TraceTextureHan(class'Actor', TraceTarget, TexName, TexGroup, TexFlags, HitLocation, HitNormal, EndTrace)
{
Log( "Target =" @ TraceTarget @",TexName ="@ TexName @"TexGroup ="@TexGroup@"TexFlags ="@TexFlags, 'TraceTexture' );
if ( (TraceTarget == Level) || TraceTarget.IsA('Mover') )
{
FloorMaterial = texGroup;
PlayFootStep();
Log( "======================================================================", 'TraceTexture' );
}
else
Log( "----------------------------------------------------------------------", 'TraceTexture' );
}
}
I guess i could do a native package, and i could throw in some other small usefull native and non native stuff in, since i have a nice collection of these things in my hx source. However i'll release the source (which might limit the things i put in, sorry for that, but some parts could easily be abused for cheating once the source is available, sorry about that), and i recommend every mod author to take the code and put it into their own projects, since i might update this package frequently, but i will try to maintain backwards compatiblity. However for netplay this sucks, and one should really put this stuff in their own mods.
Another suggestion for revision: Add the static keyword to ExtensionObject.StringToName() since there is absolutly no reason around to rely on an instance of an XObj for doing StringToName() conversations. Same for CarriageReturn().