logo
Main

Forums

Downloads

Unreal-Netiquette

Donate for Oldunreal:
Donate

borderline

Links to our wiki:
Wiki

Walkthrough

Links

Tutorials

Unreal Reference

Usermaps

borderline

Contact us:
Submit News
Page Index Toggle Pages: 1 [2]  Send TopicPrint
Very Hot Topic (More than 25 Replies) Real crouching bug: telefragging when getting up (Read 1828 times)
Masterkent
Developer Team
Offline



Posts: 1050
Location: Russia
Joined: Apr 5th, 2013
Gender: Male
Re: Real crouching bug: telefragging when getting up
Reply #15 - Jan 4th, 2018 at 5:22pm
Print Post  
Here's the preliminary implementation (can be compiled and tested):
PlayerPawn (without defaultproperties)
RealCrouchController.uc (new auxiliary class)

It does not resolve issue 6; other issues are addressed.

I'll comment the changes later.
  
Back to top
 
IP Logged
 
Masterkent
Developer Team
Offline



Posts: 1050
Location: Russia
Joined: Apr 5th, 2013
Gender: Male
Re: Real crouching bug: telefragging when getting up
Reply #16 - Jan 6th, 2018 at 6:08pm
Print Post  
A few commentaries to the code. The first part is about changes in class PlayerPawn.

Code
Select All
function ClientUpdatePosition()
{
	local SavedMove CurrentMove;
	local int realbRun, realbDuck;
-	local bool bRealJump,bRealCrouch;
+	local bool bRealJump;

	realbRun = bRun;
	realbDuck = bDuck;
	bRealJump = bPressedJump;
-	bRealCrouch = bIsReducedCrouch;
	CurrentMove = SavedMoves;
	while ( CurrentMove != None )
	{
		if ( CurrentMove.TimeStamp <= CurrentTimeStamp )
		{
			SavedMoves = CurrentMove.NextMove;
			CurrentMove.NextMove = FreeMoves;
			FreeMoves = CurrentMove;
			FreeMoves.Clear();
			CurrentMove = SavedMoves;
		}
		else
		{
			CrouchCheckTime = -1;
-			SetCrouch(CurrentMove.bIsReducedCrouch);
			MoveAutonomous(CurrentMove.Delta, CurrentMove.bRun, CurrentMove.bDuck, CurrentMove.bPressedJump, CurrentMove.DodgeMove, CurrentMove.Acceleration, rot(0,0,0));
			CurrentMove = CurrentMove.NextMove;
		}
	}
	bDuck = realbDuck;
	bRun = realbRun;
	bPressedJump = bRealJump;
	CrouchCheckTime = -1;
-	SetCrouch(bRealCrouch);
	bUpdatePosition = false;
	//log("Client adjusted "$self$" stamp "$CurrentTimeStamp$" location "$Location$" dodge "$DodgeDir);
} 


I don't see reasons to call SetCrouch from ClientUpdatePosition; moreover, when I logged the value of CurrentMove.bIsReducedCrouch, it was always equal to false.

Code
Select All
event UpdateEyeHeight(float DeltaTime)
{
	local float smooth, bound;

+	if (bIsReducedCrouch && IsInState('PlayerWalking'))
+		BaseEyeHeight = default.BaseEyeHeight + CollisionHeight - default.CollisionHeight;
+
	smooth = FMin(1.0, 10.0 * DeltaTime/Level.TimeDilation);
	// smooth up/down stairs
	If( (IsInState('PlayerSwimming') || Physics==PHYS_Walking) && !bJustLanded )
	{
		EyeHeight = (EyeHeight - Location.Z + OldLocation.Z) * (1 - smooth) + ( ShakeVert + BaseEyeHeight) * smooth;
		bound = -0.5 * CollisionHeight;
		if (EyeHeight < bound)
			EyeHeight = bound;
		else
		{
-			bound = CollisionHeight + FMin(FMax(0.0,(OldLocation.Z - Location.Z)), MaxStepHeight);
+			bound = FMin(FMax(0.0,(OldLocation.Z - Location.Z)), MaxStepHeight);
+			if (bIsReducedCrouch)
+				bound += default.CollisionHeight;
+			else
+				bound += CollisionHeight;
			if ( EyeHeight > bound )
				EyeHeight = bound; 


This change is necessary for smooth movement of player's camera when the player begins to crouch.

Code
Select All
simulated function bool AdjustHitLocation(out vector HitLocation, vector TraceDir)
{
	local float adjZ, maxZ;

	TraceDir = Normal(TraceDir);
	HitLocation = HitLocation + 0.5 * CollisionRadius * TraceDir;
-	if ( BaseEyeHeight == Default.BaseEyeHeight )
+	if (BaseEyeHeight == default.BaseEyeHeight || bIsCrouching && bIsReducedCrouch)
		return true;
 


When the player is crouching and the collision height is reduced, there is no need in cutting the top part of the collision cylinder.

Code
Select All
event PlayerCalcView(out actor ViewActor, out vector CameraLocation, out rotator CameraRotation )
{
	local Pawn PTarget;

	if ( ViewTarget != None )
	{
		ViewActor = ViewTarget;
		CameraLocation = ViewTarget.Location;
		CameraRotation = ViewTarget.Rotation;
		PTarget = Pawn(ViewTarget);
		if ( PTarget != None )
		{
			if ( Level.NetMode == NM_Client )
			{
				if ( PTarget.bIsPlayer )
					PTarget.ViewRotation = TargetViewRotation;
				PTarget.EyeHeight = TargetEyeHeight;
				if ( PTarget.Weapon != None )
					PTarget.Weapon.PlayerViewOffset = TargetWeaponViewOffset;
			}
			if ( PTarget.bIsPlayer )
				CameraRotation = PTarget.ViewRotation;
			if ( !bBehindView )
				CameraLocation.Z += PTarget.EyeHeight;
+			else
+				CameraLocation.Z += PTarget.PrePivot.Z - PTarget.default.PrePivot.Z;
		}
		if ( bBehindView )
			CalcBehindView(CameraLocation, CameraRotation, 180);
		return;
	}

	ViewActor = Self;
	CameraLocation = Location;

	if ( bBehindView ) //up and behind
+	{
+		CameraLocation.Z += PrePivot.Z - default.PrePivot.Z;
		CalcBehindView(CameraLocation, CameraRotation, 150);
+	}
	else 


This change prevents the camera from jumping up and down when crouching and standing up while using 3rd person view.

In state PlayerWalking:
Code
Select All
	function AnimEnd()
	{
		local name MyAnimGroup;

		bAnimTransition = false;
		if ( Physics == PHYS_Spider )
		{
			if ( VSize(Velocity)<10 )
				PlayDuck();
			else PlayCrawling();
		}
		else if (Physics == PHYS_Walking)
		{
....
		}
+		else if (bIsReducedCrouch)
+			PlayDuck();
		else
			PlayInAir();
	} 


PlayInAir animation doesn't look appropriate when the player is crouching in air.

In state PlayerWalking:
Code
Select All
	function ProcessMove(float DeltaTime, vector NewAccel, eDodgeDir DodgeMove, rotator DeltaRot)
	{
		local vector OldAccel;

		OldAccel = Acceleration;
		Acceleration = NewAccel;
		bIsTurning = ( Abs(DeltaRot.Yaw/DeltaTime) > 5000 );
		if ( (DodgeMove == DODGE_Active) && (Physics == PHYS_Falling) )
			DodgeDir = DODGE_Active;
		else if ( (DodgeMove != DODGE_None) && (DodgeMove < DODGE_Active) )
			Dodge(DodgeMove);

		if ( bPressedJump )
			DoJump();
		if ( (Physics == PHYS_Walking) && (GetAnimGroup(AnimSequence) != 'Dodge') )
		{
			if ( !bIsCrouching )
			{
-				if ( bDuck != 0 && TryToDuck(true) )
+				if ( bDuck != 0 && TryToDuck(true) && IsInState('PlayerWalking') ) // Note: TryToDuck may change the state (e.g. to PlayerSwimming)
				{
					bIsCrouching = true;
					PlayDuck();
				}
			}
			else if ( bDuck == 0 && TryToDuck(false) )
			{
				OldAccel = vect(0,0,0);
				bIsCrouching = false;
			} 


TryToDuck may change player's state to PlayerSwimming (because new player location may reside a water zone), then changing the value of bIsCrouching to true produces weird effects.

In state PlayerWalking:
Code
Select All
	function BeginState()
	{
		WalkBob = vect(0,0,0);
		DodgeDir = DODGE_None;
-		bIsCrouching = false;
+		bIsCrouching = bIsReducedCrouch;
		bIsTurning = false;
		bPressedJump = false;
		if (Physics != PHYS_Falling)
			SetPhysics(PHYS_Walking);
		if ( !IsAnimating() )
-			PlayWaiting();
+		{
+			if (bIsCrouching)
+				PlayDuck();
+			else
+				PlayWaiting();
+		}
	} 


A crouching player may enter a water zone and leave it while holding the crouch key. A correct and smooth transition from PlayerSwimming to PlayerWalking needs setting the value of bIsCrouching to the value that would correspond to bIsReducedCrouch which reflects whether the height of player's collision cylinder is reduced.

In state PlayerWalking:
Code
Select All
	function EndState()
	{
		WalkBob = vect(0,0,0);
		bIsCrouching = false;
-		SetCrouch(false);
	} 


Restoring the original collision height and location when leaving the PlayerWalking state may cause undesirable effects, because the state can be changed due to location change implied by real crouching. Uncrouching may imply changing the location back to its previous value which in turn may imply the need in changing the state back to PlayerWalking. This way we can end up in having a series of state changes like PlayerWalking -> PlayerSwimming -> PlayerWalking -> PlayerSwimming -> PlayerWalking -> ...

In state FeigningDeath:
Code
Select All
	function Rise()
	{
		if ( !bRising )
		{
			Enable('AnimEnd');
-			BaseEyeHeight = Default.BaseEyeHeight;
			bRising = true;
-			PlayRising();
+			if (TryToDuck(false))
+			{
+				BaseEyeHeight = default.BaseEyeHeight;
+				PlayRising();
+			}
+			else
+			{
+				PlayDuck();
+				BaseEyeHeight = default.BaseEyeHeight + CollisionHeight - default.CollisionHeight;
+			}
		}
	} 


If the player cannot stand up due to insufficient space, rising should be performed as a transition from feigning death to crouching.

In state FeigningDeath:
Code
Select All
+	simulated function bool ShouldHaveReducedHeight()
+	{
+		return !bRising;
+	} 


This function is supposed to override the corresponding global function (see below). True result indicates that the collision cylinder should have a reduced height, false result indicates that an attempt to restore the original CollisionHeight should be done (CollisionHeight is changed as a part of standing up; standing up is possible only if there is enough space).

In state FeigningDeath:
Code
Select All
	function BeginState()
	{
		local rotator NewRot;
		if ( carriedDecoration != None )
			DropDecoration();
		NewRot = Rotation;
		NewRot.Pitch = 0;
		SetRotation(NewRot);
		BaseEyeHeight = -0.5 * CollisionHeight;
		bIsCrouching = false;
		bPressedJump = false;
		bRising = false;
		Disable('AnimEnd');
		PlayFeignDeath();
		PlayerReplicationInfo.bFeigningDeath = true;
+		TryToDuck(true);
	} 


If crouching implies that CollisionHeight should be reduced, it would be logical to reduce CollisionHeight when the player is feigning death too. Since players cannot move when feigning death, I do not suggest to choose a smaller value of CollisionHeight than the one used for crouching. Maintaining the same CollisionHeight as would be used for crouching does not allow to block the player in a way so that he would not be able to quit feigning death and either stand up or begin to crouch at least.

In state PlayerSwimming:
Code
Select All
	event UpdateEyeHeight(float DeltaTime)
	{
		local float smooth, bound;

		// smooth up/down stairs
		if ( !bJustLanded )
		{
			smooth = FMin(1.0, 10.0 * DeltaTime/Level.TimeDilation);
			EyeHeight = (EyeHeight - Location.Z + OldLocation.Z) * (1 - smooth) + ( ShakeVert + BaseEyeHeight) * smooth;
			bound = -0.5 * CollisionHeight;
			if (EyeHeight < bound)
				EyeHeight = bound;
			else
			{
				bound = CollisionHeight + FClamp((OldLocation.Z - Location.Z), 0.0, MaxStepHeight);
+				if (bIsReducedCrouch)
+					bound += default.CollisionHeight - CollisionHeight;
				if ( EyeHeight > bound )
					EyeHeight = bound;
 


This change is necessary for smooth movement of player's camera when the player begins to crouch.

In state PlayerSwimming:
Code
Select All
	function BeginState()
	{
		Disable('Timer');
		if (!IsAnimating())
			TweenToWaiting(0.3);
+		else if (bIsReducedCrouch)
+			TweenToSwimming(0.2);
		//log("player swimming");
	} 


This change is needed for smooth animation transition.

In function SetCrouch:
Code
Select All
	bIsReducedCrouch = bCrouching;
+	class'RealCrouchController'.static.GetRealCrouchController(self);
 


This function creates new RC controller if the player has no such a controller yet. The RC controller serves two purposes. Firstly, it checks whether the player's collision cylinder definitely must have a reduced CollisionHeight or full CollisionHeight and if the current value does not match the required one, then the controller changes CollisionHeight correspondingly. This check is done on every tick. Since PlayerPawn.PlayerTick is widely overridden in various states and may be overridden in subclasses, using the Tick event of a separate actor appears to be more simple and reliable solution.

Secondly, RC controller implements speculative move operations, it lets us determine whether the player can be moved from "here" to "there". Basically, it imitates the player's collision cylinder.

In function SetCrouch:
Code
Select All
	if (bCrouching)
	{
		// Update collision size, prepivot height and location.
-		OldHeight = CollisionHeight;
-		SetCollisionSize(CollisionRadius,CollisionHeight*CrouchHeightPct);
-		OldHeight = OldHeight-CollisionHeight;
-		PrePivot.Z+=OldHeight;
-		EyeHeight+=OldHeight;
-		Move(vect(0,0,-1)*OldHeight);
+		NewCollisionHeight = default.CollisionHeight * CrouchHeightPct;
+		MoveDistance = CollisionHeight - NewCollisionHeight;
+		if (MoveDistance <= 0)
+			return;
+
+		VerticalOffset = default.CollisionHeight - NewCollisionHeight;
+		SetCollisionSize(CollisionRadius, NewCollisionHeight);
+		PrePivot.Z = default.PrePivot.Z + VerticalOffset;
+		EyeHeight += MoveDistance;
+		Move(vect(0, 0, -1) * MoveDistance);
	} 


default.CollisionHeight is a reliable basis, while CollisionHeight may have an outdated value assigned through server-to-client replication. It's worth noting though that using default.CollisionHeight limits the freedom of changing CollisionHeight by third party mods.

In function SetCrouch:
Code
Select All
	else
	{
		// Reset collision size, prepivot height and location.
-		OldHeight = CollisionHeight;
-		NewHeight = CollisionHeight/CrouchHeightPct;
-		OldHeight = NewHeight-OldHeight;
-		PrePivot.Z-=OldHeight;
-		OldHeight*=0.5f;
-		EyeHeight-=OldHeight;
-		Move(vect(0,0,2)*OldHeight);
-		SetLocation(Location);
-		SetCollisionSize(CollisionRadius,NewHeight);
+		NewCollisionHeight = default.CollisionHeight;
+		MoveDistance = NewCollisionHeight - CollisionHeight;
+
+		// Use the cached result when indirectly calling from TryToDuck or a newly calculated result otherwise
+		if (MoveDistance <= 0 || !class'RealCrouchController'.static.CanStandUp(self, NewCollisionHeight))
+			return;
+
+		PrePivot.Z = default.PrePivot.Z;
+		EyeHeight -= MoveDistance;
+
+		bOldBlockActors = bBlockActors;
+		bOldBlockPlayers = bBlockPlayers;
+		bOldCollideWorld = bCollideWorld;
+
+		SetCollision(bCollideActors, false, false);
+		bCollideWorld = false;
+		SetCollisionSize(CollisionRadius, NewCollisionHeight);
+		Move(vect(0, 0, 1) * MoveDistance);
+		SetLocation(Location);
+		if (CarriedDecoration != none)
+		{
+			CarriedDecoration.SetPhysics(PHYS_None);
+			CarriedDecoration.SetBase(self);
+		}
+		SetCollision(bCollideActors, bOldBlockActors, bOldBlockPlayers);
+		bCollideWorld = bOldCollideWorld;
	} 


Generally, SetCrouch is supposed to be an internal function in the new implementation, mods should not call it directly. In case if someone calls it nevertheless, SetCrouch(false) performs an extra check if the player can stand up (such a check is supposed to be done by TryToDuck).

Once the player is known to be able to stand up, the player is moved to the new location. During the move operation the player cannot be blocked by level geometry or other actors and cannot encroach on other actors (the cause of telefragging). SetLocation(Location) ensures that the player touches all actors that might be touched when moving the top bound of the player in a usual way. The call to Move moves the player and the CarriedDecoration (if any); it's possible to use only SetLocation, but then CarriedDecoration has to be moved separately in order to resolve issue 6.1. I don't see an easy resolution for issue 6 in general, having only the currently available set of C++ functions.

Changing CollisionHeight and Location could be done in a much more clean way if we had a function that could perform these two operations as a single transaction:

Code
Select All
// Effects: the function moves the actor by (ZOffset * vect(0, 0, 1)) and changes its CollisionHeight
//     to (CollisionHeight + ZOffset) - hence the collision top is moved by (ZOffset * vect(0, 0, 2)).
//     Other actors can be touched as if Move(ZOffset * vect(0, 0, 2)) was called instead, but the bottom
//     of the collision cylinder does not change its relative position to the level and the actor cannot be
//     blocked by the level geometry or other actors during the move operation. A possible overlapping with
//     other actors does not cause calls to EncroachingOn or EncroachedBy. Moving the actor out of world is
//     allowed.
// Precondition: ZOffset shall not be less than -CollisionRadius
//
native final function bool MoveCollisionTop(float ZOffset); 



Code
Select All
simulated final function bool TryToDuck(bool bCrouching)
{
-	local vector Dummy,Offset,Start;

	if (!Level.bSupportsRealCrouching || bIsReducedCrouch == bCrouching)
		return true;
	if (bCrouching)
	{
		SetCrouch(true);
		return true;
	}
	// Make sure there is space to stand up
-	Start = Location;
-	Start.Z = Location.Z+CollisionHeight-0.01f;
-	Offset.Z = ((CollisionHeight/CrouchHeightPct)-CollisionHeight);
-	if( Trace(Dummy,Dummy,Start+Offset,Start,true,vect(1.f,1.f,0.f)*CollisionRadius)!=None )
-		return false; // Wasnt enough space to uncrouch.
+	if (!class'RealCrouchController'.static.CanStandUp(self, default.CollisionHeight))
+		return false; // Not enough space to uncrouch
	SetCrouch(false);
	return true;
} 


The method based on the Trace function may give false-positive and false-negative results. CanStandUp uses a more reliable method based on a speculative move operation (see below).

Code
Select All
simulated function bool ShouldHaveReducedHeight()
{
	return bIsCrouching || bIsReducedCrouch && bDuck != 0;
} 


The result of this function is used by RealCrouchController.Tick to determine if the player should be forced to crouch or try to uncrouch (see below).

Code
Select All
+simulated function bool IsHeadShot(vector HitLocation, vector TraceDir)
+{
+	return !bIsReducedCrouch && super.IsHeadShot(HitLocation, TraceDir);
+} 


Crouching is known as a trick that prevents headshots. Although it's more likely a dev's oversight than a planned behavior, I think that people have widely used this trick for years, and disallowing it in case of real crouching wouldn't be appreciated by gamers. Tweaking AdjustHitLocation as shown above would allow headshots for crouching players, and this definition of IsHeadShot is supposed to negate such an effect, so that old trick would still work.


Now about new class RealCrouchController.

Code
Select All
static function RealCrouchController GetRealCrouchController(PlayerPawn Player)
{
	local RealCrouchController CrouchController;
	foreach Player.ChildActors(class'RealCrouchController', CrouchController)
		break;
	if (CrouchController == none || CrouchController.bDeleteMe)
		return Player.Spawn(class'RealCrouchController', Player);
	return CrouchController;
} 


Finding the existing controller can be optimized by adding a new property in class PlayerPawn:

Code
Select All
var RealCrouhController RealCrouchController; 


I used ChildActors for testing purposes, because adding new variables in PlayerPawn by means of UnrealEd doesn't work well.

In function CanStandUp:
Code
Select All
	if (Player.Level.TimeSeconds == CrouchController.TimeStamp)
		return !CrouchController.bIsBlocked;

	if (Player.CollisionHeight >= TargetCollisionHeight)
		return true;
	if (!Player.bCollideActors && !Player.bCollideWorld)
		return true;
 


A speculative move operation is relatively slow, so it makes sense to avoid unnecessary calculations, when the result can be evaluated using more simple methods. Firstly, CollisionHeight most likely won't be changed many times during the same tick, so we can cache the result of CanStandUp and use it within the same tick later. Secondly, if all collision between the player and other objects is disabled (unusual case), the move operation would succeed anyway.

Code
Select All
	CrouchController.SetCollision(false, false, false);
	CrouchController.SetCollisionSize(Player.CollisionRadius, Player.CollisionHeight);
	CrouchController.bCollideWorld = false;
	CrouchController.Move(Player.Location - CrouchController.Location);
	if (Player.bBlockActors)
		CrouchController.SetCollision(true, false, false);
	CrouchController.bCollideWorld = Player.bCollideWorld;

	CrouchController.bIsBlocked = false;
	CrouchController.BumpedActor = none;

	DstLocation = Player.Location + vect(0, 0, 2) * (TargetCollisionHeight - Player.CollisionHeight);

	// Checking if the player would hit non-Brush actors, Brush actors, or the level geometry
	CrouchController.Move(DstLocation - CrouchController.Location);
	if (CrouchController.bIsBlocked)
		return CrouchController.CanStandUp_Cache(false);

	while (
		CrouchController.Location != DstLocation &&
		CrouchController.BumpedActor != none &&
		CrouchController.BumpedActor.bCollideActors &&
		!CrouchController.BumpedActor.bBlockPlayers &&
		i < 16)
	{
		CrouchController.BumpedActor.SetCollision(false, CrouchController.BumpedActor.bBlockActors, false);
		CrouchController.Move(DstLocation - CrouchController.Location);
		CrouchController.BumpedActor.SetCollision(true, CrouchController.BumpedActor.bBlockActors, false);

		if (CrouchController.bIsBlocked)
			return CrouchController.CanStandUp_Cache(false);

		CrouchController.BumpedActor = none;
		++i;
	}

	return CrouchController.CanStandUp_Cache(CrouchController.Location == DstLocation);
} 


All this complicated stuff is supposed to calculate what entities would block the player if the player tried to stand up. Blocking PlayerPawns by other actors is implemented in a special way, so simulating its movement with an aux actor is a rather tricky thing. Such tricks would be unnecessary if we had a function like this:

Code
Select All
// Effects: the function determines what would happen if Move(Delta) was called instead:
//     - the return value is the same as would be returned by Move(Delta);
//     - if FromLocation is not specified, the initial location is assumed to be the current Location;
//       otherwise, the initial location before the move operation is assumed to be FromLocation;
//     - ResultingLocation is equal to the value that the actor's Location would have after calling Move(Delta);
//     - if during the move operation the actor would be blocked by the level geometry or other actor,
//       then BlockedByActor is set to Level or that blocking actor correspondingly;
//       otherwise, BlockedByActor is set to none.
// Note: the function has no side effects; in particular, it does not invoke events Touch/UnTouch, ZoneChange, etc.
//
native final function bool SpeculativeMove(vector Delta, optional vector FromLocation, optional out vector ResultingLocation, optional out Actor BlockedByActor);
 


This is how CanStandUp could be implemented then:

Code
Select All
static final function bool CanStandUp(PlayerPawn Player, float TargetCollisionHeight)
{
	local RealCrouchController CrouchController;
	local vector TopOffset;
	local Actor BlockedBy;

	CrouchController = GetRealCrouchController(Player);
	if (CrouchController == none) // should be always false
		return true;

	if (Player.Level.TimeSeconds == CrouchController.TimeStamp)
		return !CrouchController.bIsBlocked;

	if (Player.CollisionHeight >= TargetCollisionHeight)
		return true;
	if (!Player.bCollideActors && !Player.bCollideWorld)
		return true;

	CrouchController.bIsBlocked = false;
	TopOffset = vect(0, 0, 2) * (TargetCollisionHeight - Player.CollisionHeight);

	return CrouchController.CanStandUp_Cache(SpeculativeMove(TopOffset,,, BlockedBy) && BlockedBy == none);
} 


Code
Select All
event Tick(float DeltaTime)
{
	local PlayerPawn Player;

	Player = PlayerPawn(Owner);
	if (Player == none || Player.bDeleteMe)
	{
		Destroy();
		return;
	}

	if (Player.bIsReducedCrouch != Player.ShouldHaveReducedHeight())
		Player.TryToDuck(!Player.bIsReducedCrouch);
} 


This function periodically checks if the player should be forced to crouch or should try to stand up and adjust the player correspondingly. It's useful mostly in states that differ from PlayerWalking.
  
Back to top
 
IP Logged
 
Smirftsch
Forum Administrator
*****
Online



Posts: 7703
Location: at home
Joined: Apr 30th, 1998
Gender: Male
Re: Real crouching bug: telefragging when getting up
Reply #17 - Jan 7th, 2018 at 7:35am
Print Post  
thank you very much for the work you put into this!!!

I'll integrate it asap Smiley

Edited:
Added current version but will have a look yet for the native function you suggested.


Edited:
It seems that MoveActor is having already an almost suitable flag: bTest. If I pass this through as optional bool in move() it may suffice already for the most part.
« Last Edit: Jan 7th, 2018 at 11:30am by Smirftsch »  

Sometimes you have to lose a fight to win the war.
Back to top
WWWICQ  
IP Logged
 
Shadow_Code
New Member
*
Offline


Oldunreal member

Posts: 10
Joined: Jun 9th, 2018
Re: Real crouching bug: telefragging when getting up
Reply #18 - Jul 6th, 2018 at 1:35pm
Print Post  
There's a couple of bugs with true crouch:

Releasing crouch/standing up while on a mover will result in you falling through it. This is problematic enough to render this feature unusable. I haven't attempted to determine the cause yet, partly because trying to fix it may prove impossible (for those of us without access to the source) as a result of use of final functions. I am going to try though.

Stand up time is very inconsistent. Sometimes you can release crouch and you'll stand immediately. Other times you'll only stand up after three or so seconds have passed, though this only happens while moving. I'm not sure if you're technically standing during this time, or the eyeheight simply hasn't updated yet.

I'll take a crack at these anyhow, with my limited access.

Edit: just read that you're aware of these bugs and more.

  
Back to top
 
IP Logged
 
Masterkent
Developer Team
Offline



Posts: 1050
Location: Russia
Joined: Apr 5th, 2013
Gender: Male
Re: Real crouching bug: telefragging when getting up
Reply #19 - Jul 8th, 2018 at 12:30pm
Print Post  
Shadow_Code wrote on Jul 6th, 2018 at 1:35pm:
Stand up time is very inconsistent. Sometimes you can release crouch and you'll stand immediately. Other times you'll only stand up after three or so seconds have passed, though this only happens while moving. I'm not sure if you're technically standing during this time, or the eyeheight simply hasn't updated yet.

This applies to the classic implementation too (not only real crouching is affected). While moving, you can stand up only on completion of the crawling animation. Duration of the protracted crawling depends on the number of remaining frames at the moment when you release the Duck key. I created a new topic for issues with animation: https://www.oldunreal.com/cgi-bin/yabb2/YaBB.pl?num=1531052033
  
Back to top
 
IP Logged
 
.:..:
Board Moderator
Developer Team
*****
Offline



Posts: 1429
Location: Finland
Joined: Aug 16th, 2005
Gender: Male
Re: Real crouching bug: telefragging when getting up
Reply #20 - Jul 8th, 2018 at 1:47pm
Print Post  
Disregarding that 100 lines of code above, wouldn't it be simplier to just (this would also replace TryToDuck function):
Code
Select All
simulated function bool SetCrouch( bool bCrouching, optional bool bForce )
{
	local float OldHeight,NewHeight,ZDiff;
	local vector Pos,Extent,HitNormal;

	if( !Level.bSupportsRealCrouching || bIsReducedCrouch==bCrouching )
		return true; // No can do.
	if( CrouchCheckTime==Level.TimeSeconds && !bForce )
		return false; // Avoid any possible runaway loops.

	OldHeight = CollisionHeight;
	if( bCrouching )
		NewHeight = CollisionHeight*CrouchHeightPct;
	else NewHeight = CollisionHeight/CrouchHeightPct;
	ZDiff = (OldHeight-NewHeight);

	Pos = Location;
	Pos.Z-=ZDiff;
	Extent.X = CollisionRadius;
	Extent.Y = CollisionRadius;
	Extent.Z = NewHeight;

	if( !bForce )
	{
		CrouchCheckTime = Level.TimeSeconds;
		if( PointCheck(Pos,HitNormal,true,Extent)!=None ) // New position was blocked.
			return false;
	}

	bIsReducedCrouch = bCrouching;

	// Update collision size, prepivot height and location.
	PrePivot.Z+=ZDiff;
	//EyeHeight+=ZDiff;
	if( bCrouching )
	{
		SetCollisionSize(CollisionRadius,NewHeight);
		Move(Pos-Location);
	}
	else
	{
		Move(Pos-Location);
		SetCollisionSize(CollisionRadius,NewHeight);
	}
	return true;
} 


About EyeHeight, just add the half collision offset up from current Location.Z in CalcCamera/Inventory.CalcView:
Code
Select All
event PlayerCalcView(out actor ViewActor, out vector CameraLocation, out rotator CameraRotation )
{
...
	else
	{
		// First-person view.
		CameraRotation = ViewRotation;
		CameraLocation.Z += GetEyeOffset();
		CameraLocation += WalkBob;
	}
}
simulated function float GetEyeOffset()
{
	local float Z;

	if( Viewport(Player)!=None )
		Z = EyeHeight;
	else Z = BaseEyeHeight;

	if( bIsReducedCrouch )
		Z+=(CollisionHeight/CrouchHeightPct)-CollisionHeight;
	return Z;
} 


I just dont want code to become too messy.
  

Shivaxi wrote on Jul 25th, 2013 at 12:50pm:
...and now im stuck trying to fix everything you broke for the next 227 release xD Tongue

(ಠ_ಠ)
Back to top
ICQYIM  
IP Logged
 
.:..:
Board Moderator
Developer Team
*****
Offline



Posts: 1429
Location: Finland
Joined: Aug 16th, 2005
Gender: Male
Re: Real crouching bug: telefragging when getting up
Reply #21 - Jul 8th, 2018 at 2:53pm
Print Post  
Actually to take into account crouching in partially submerged waterlevel could simply do this:
Code
Select All
simulated function bool SetCrouch( bool bCrouching, optional bool bForce )
{
...
	if( !bForce )
	{
		CrouchCheckTime = Level.TimeSeconds;
		if( PointCheck(Pos,HitNormal,true,Extent)!=None || (bCrouching && Level.GetLocZone(Pos,Self).Zone.bWaterZone) ) // New position was blocked.
			return false;
	}
...
} 

  

Shivaxi wrote on Jul 25th, 2013 at 12:50pm:
...and now im stuck trying to fix everything you broke for the next 227 release xD Tongue

(ಠ_ಠ)
Back to top
ICQYIM  
IP Logged
 
Masterkent
Developer Team
Offline



Posts: 1050
Location: Russia
Joined: Apr 5th, 2013
Gender: Male
Re: Real crouching bug: telefragging when getting up
Reply #22 - Jul 9th, 2018 at 5:36pm
Print Post  
.:..: wrote on Jul 8th, 2018 at 1:47pm:
Disregarding that 100 lines of code above, wouldn't it be simplier to just (this would also replace TryToDuck function):
Code
Select All
simulated function bool SetCrouch( bool bCrouching, optional bool bForce )
{
	local float OldHeight,NewHeight,ZDiff;
	local vector Pos,Extent,HitNormal;

	if( !Level.bSupportsRealCrouching || bIsReducedCrouch==bCrouching )
		return true; // No can do.
	if( CrouchCheckTime==Level.TimeSeconds && !bForce )
		return false; // Avoid any possible runaway loops.

	OldHeight = CollisionHeight;
	if( bCrouching )
		NewHeight = CollisionHeight*CrouchHeightPct;
	else NewHeight = CollisionHeight/CrouchHeightPct;
	ZDiff = (OldHeight-NewHeight);

	Pos = Location;
	Pos.Z-=ZDiff;
	Extent.X = CollisionRadius;
	Extent.Y = CollisionRadius;
	Extent.Z = NewHeight;

	if( !bForce )
	{
		CrouchCheckTime = Level.TimeSeconds;
		if( PointCheck(Pos,HitNormal,true,Extent)!=None ) // New position was blocked.
			return false;
	}

	bIsReducedCrouch = bCrouching;

	// Update collision size, prepivot height and location.
	PrePivot.Z+=ZDiff;
	//EyeHeight+=ZDiff;
	if( bCrouching )
	{
		SetCollisionSize(CollisionRadius,NewHeight);
		Move(Pos-Location);
	}
	else
	{
		Move(Pos-Location);
		SetCollisionSize(CollisionRadius,NewHeight);
	}
	return true;
} 


In short, such a code leads to issues 2 and 11 mentioned here. Additionally, it may be prone to issue 4 or inability to stand up within blocking actors - depending on how exactly PointCheck would work.

In detail:

Quote:
Code
Select All
	if( bCrouching )
		NewHeight = CollisionHeight*CrouchHeightPct;
	else NewHeight = CollisionHeight/CrouchHeightPct; 


In network game, such assignments would work well only as long as replication of actual value of CollisionHeight is done after the corresponding update of CollisionHeight client-side. In the worst case, you may get a sequence like this:

1) CollisionHeight * CrouchHeightPct is assigned to CollisionHeight server-side
2) server-side value of CollisionHeight is replicated to client
3) CollisionHeight * CrouchHeightPct is assigned to CollisionHeight client-side

so the final client-side value of CollisionHeight becomes CollisionHeight * Square(CrouchHeightPct) relative to the initial server-side value of CollisionHeight. This may happen, in particular, when state PlayerWalking is first entered server-side (without client-side prediction) and then client-side state is adjusted to match the server-side state by means of ClientAdjustPosition. Example: holding a Duck key during transition FeigningDeath -> PlayerWalking.

A similar thing may also happen with the division by CrouchHeightPct: when you try to stand up under a moving object, your client may decide that the object stops blocking you after receiving the updated value of CollisionHeight from the server which took the same decision much earlier.

Quote:
Code
Select All
	if( bCrouching )
	{
		SetCollisionSize(CollisionRadius,NewHeight);
		Move(Pos-Location);
	}
	else
	{
		Move(Pos-Location);
		SetCollisionSize(CollisionRadius,NewHeight);
	} 


This implementation implies that you can miss triggers above you when standing up. When the upper bound of your collision cylinder is moved by means of SetCollisionSize, intersection with non-blocking actors is not checked and the Touch event will not be invoked for any actor whose collision cylinder becomes overlapping with yours.

And yes, I don't like all those tricks with SetLocation and would rather prefer using something like

Code
Select All
native(283) final function bool SetCollisionSize( float NewRadius, float NewHeight, optional bool bUpdateTouchList ); 


or

Code
Select All
native(xxx) final function UpdateTouchList(); 


instead. Either of these things would let me simplify my code a lot. So far I have to use a messy workaround based on SetLocation just to update the touch list.

Quote:
About EyeHeight, just add the half collision offset up from current Location.Z in CalcCamera/Inventory.CalcView

Then camera may be placed too close to the ceiling, especially when CrouchHeightPct == 0.5 (eyes would be located right on the top of the collision cylinder).

Quote:
Code
Select All
	if( !bForce )
	{
		CrouchCheckTime = Level.TimeSeconds;
		if( PointCheck(Pos,HitNormal,true,Extent)!=None || (bCrouching && Level.GetLocZone(Pos,Self).Zone.bWaterZone) ) // New position was blocked.
			return false;
	} 


What's the purpose of bForce? Why can an attempt to crouch fail?

And it's unclear what exactly PointCheck is supposed to do, I couldn't find such a function in 227j. Obviously, the full-height volume of the PlayerPawn would be blocked by the crouching PlayerPawn itself, so the given player would have to be ignored (as a potentially blocking actor) somehow. Additionally, we should take into account that the player should be blocked by BlockPlayer actors and should not be blocked by BlockMonsters actors, so PointCheck also would have to check the collision flags correspondingly. Finally, a player can freely move through blocking actors which already overlap with the player (f.e., when several players are spawned at the same location without telefragging, they do not get stuck inside each other) and ideally such a non-blocking behavior should be preserved in case of attempts to stand up too.
  
Back to top
 
IP Logged
 
Smirftsch
Forum Administrator
*****
Online



Posts: 7703
Location: at home
Joined: Apr 30th, 1998
Gender: Male
Re: Real crouching bug: telefragging when getting up
Reply #23 - Jul 12th, 2018 at 7:26am
Print Post  
Since I was already given this code, I can answer these I suppose Smiley

Current suggestion is this:
Code (C++)
Select All
void AActor::execPointCheck( FFrame& Stack, RESULT_DECL )
{
	guard(AActor::execPointCheck);
	P_GET_VECTOR(Point);
	P_GET_VECTOR_REF(HitNormal);
	P_GET_UBOOL_OPTX(bTraceActors,1);
	P_GET_VECTOR_OPTX(Extent,FVector(0,0,0));
	P_FINISH;

	FCheckResult Hit;
	if( XLevel->SinglePointCheck(Hit,Point,Extent,0,Level,bTraceActors,this) )
		*(AActor**)Result = NULL;
	else
	{
		if( HitNormal )
			*HitNormal = Hit.Normal;
		*(AActor**)Result = Hit.Actor ? Hit.Actor : Level;
	}

	unguardexec;
}
 



Code
Select All
simulated function bool SetCrouch( bool bCrouching, optional bool bForce )
{
	local float OldHeight,NewHeight,ZDiff;
	local vector Pos,Extent,HitNormal;

	if( !Level.bSupportsRealCrouching || bIsReducedCrouch==bCrouching )
		return true; // No can do.
	if( CrouchCheckTime==Level.TimeSeconds && !bForce )
		return false; // Avoid any possible runaway loops.

	OldHeight = CollisionHeight;
	if( bCrouching )
		NewHeight = CollisionHeight*CrouchHeightPct;
	else NewHeight = CollisionHeight/CrouchHeightPct;
	ZDiff = (OldHeight-NewHeight);

	Pos = Location;
	Pos.Z-=ZDiff;
	Extent.X = CollisionRadius;
	Extent.Y = CollisionRadius;
	Extent.Z = NewHeight;

	if( !bForce )
	{
		CrouchCheckTime = Level.TimeSeconds;
		if( PointCheck(Pos,HitNormal,true,Extent)!=None ) // New position was blocked.
			return false;
	}

	bIsReducedCrouch = bCrouching;

	// Update collision size, prepivot height and location.
	PrePivot.Z+=ZDiff;
	if( bCrouching )
	{
		SetCollisionSize(CollisionRadius,NewHeight);
		Move(Pos-Location);
	}
	else
	{
		Move(Pos-Location);
		SetCollisionSize(CollisionRadius,NewHeight);
	}
	return true;
}
simulated function float GetEyeOffset()
{
	local float Z;

	if( Viewport(Player)!=None )
		Z = EyeHeight;
	else Z = BaseEyeHeight;

	if( bIsReducedCrouch )
		Z+=(CollisionHeight/CrouchHeightPct)-CollisionHeight;
	return Z;
}
 



I'm honest that I'd have to work me really into all this to really understand everything, it became very complex.
Right now I couldn't tell the "better" way although I can't deny that dots way of simplicity is very appealing, so there is maybe a way to make use of it to reduce complexity, perhaps combining your work?
  

Sometimes you have to lose a fight to win the war.
Back to top
WWWICQ  
IP Logged
 
Masterkent
Developer Team
Offline



Posts: 1050
Location: Russia
Joined: Apr 5th, 2013
Gender: Male
Re: Real crouching bug: telefragging when getting up
Reply #24 - Jul 12th, 2018 at 12:29pm
Print Post  
Smirftsch wrote on Jul 12th, 2018 at 7:26am:
Current suggestion is this:
Code (C++)
Select All
void AActor::execPointCheck( FFrame& Stack, RESULT_DECL )
{
	guard(AActor::execPointCheck);
	P_GET_VECTOR(Point);
	P_GET_VECTOR_REF(HitNormal);
	P_GET_UBOOL_OPTX(bTraceActors,1);
	P_GET_VECTOR_OPTX(Extent,FVector(0,0,0));
	P_FINISH;

	FCheckResult Hit;
	if( XLevel->SinglePointCheck(Hit,Point,Extent,0,Level,bTraceActors,this) )
		*(AActor**)Result = NULL;
	else
	{
		if( HitNormal )
			*HitNormal = Hit.Normal;
		*(AActor**)Result = Hit.Actor ? Hit.Actor : Level;
	}

	unguardexec;
}
 


That doesn't explain the exact behavior of execPointCheck, because I don't have any specification of SinglePointCheck nor its internals.

If you want to check if a certain cylinder includes or has intersections with blocking actors that may prevent standing up, an actor A shall be considered as blocking if and only if it meets all of the conditions below:

1) A does not overlap with the player's collision cylinder (while the player is still crouching),
2) A overlaps with the cylinder that corresponds to the full-height collision cylinder of the player after standing up,
3) A.bCollideActors is true,
4) AActor::IsBlockedBy returns TRUE when invoked for the given player with pointer to A passed as the argument.

If PointCheck(Pos,HitNormal,true,Extent) returns none if and only if there is no any such actor A and the checked cylinder does not overlap with solid brushes, then it can serve as a correctly working replacement for using RealCrouchController.CanStandUp. Otherwise, you can expect that it won't work correctly under some circumstances.

Smirftsch wrote on Jul 12th, 2018 at 7:26am:
so there is maybe a way to make use of it to reduce complexity, perhaps combining your work?

It was always a matter of insufficient API. If you add a native function that may check for presence of blocking actors as described above, then UScript-level implementation of CanStandUp becomes trivial. And if you add a native function that could move only the upper bound of the collision cylinder (while preserving the lower bound at the same altitude) with proper Touch/UnTouch detection, then the definition of SetCrouch can be reduced to

Code
Select All
simulated final function SetCrouch(bool bCrouching)
{
	local float NewCollisionHeight;

	if (bIsReducedCrouch == bCrouching || CrouchCheckTime == Level.TimeSeconds)
		return; // No-op.
	CrouchCheckTime = Level.TimeSeconds; // Avoid any possible runaway loops.

	bIsReducedCrouch = bCrouching;
	class'RealCrouchController'.static.GetRealCrouchController(self);

	if (bCrouching)
	{
		NewCollisionHeight = default.CollisionHeight * CrouchHeightPct;
		PrePivot.Z = default.PrePivot.Z + default.CollisionHeight - NewCollisionHeight;
	}
	else
	{
		NewCollisionHeight = default.CollisionHeight;
		PrePivot.Z = default.PrePivot.Z;
	}
	EyeHeight += CollisionHeight - NewCollisionHeight;
	SetCollisionHeight(NewCollisionHeight);
} 

« Last Edit: Jul 12th, 2018 at 5:28pm by Masterkent »  
Back to top
 
IP Logged
 
.:..:
Board Moderator
Developer Team
*****
Offline



Posts: 1429
Location: Finland
Joined: Aug 16th, 2005
Gender: Male
Re: Real crouching bug: telefragging when getting up
Reply #25 - Jul 14th, 2018 at 7:28am
Print Post  
Well PointCheck works as you described, if you use bTraceActors false, it will only check blocking actors. It is what engine uses for checking telefrags etc.

- About triggers not getting active, I wouldn't bother about that as triggers will fire as soon as you make any other move (or perhaps could make SetCollisionSize check for touch/untouch).

- bForce, for purpose of forcing player into uncrouch mode when leaving PlayerWalking state (I don't want to have crouching underwater as you cant crouch underwater/in air by default either).

- Yeah I agree with replication delay issue with CollisionHeight, could indeed use default collision height, BUT perhaps also take into account DrawScale changes (*(DrawScale/Default.DrawScale))? Incase some mod scales player.
  

Shivaxi wrote on Jul 25th, 2013 at 12:50pm:
...and now im stuck trying to fix everything you broke for the next 227 release xD Tongue

(ಠ_ಠ)
Back to top
ICQYIM  
IP Logged
 
Masterkent
Developer Team
Offline



Posts: 1050
Location: Russia
Joined: Apr 5th, 2013
Gender: Male
Re: Real crouching bug: telefragging when getting up
Reply #26 - Jul 14th, 2018 at 9:25am
Print Post  
.:..: wrote on Jul 14th, 2018 at 7:28am:
Well PointCheck works as you described, if you use bTraceActors false, it will only check blocking actors. It is what engine uses for checking telefrags etc.

It's possible to telefrag initially overlapping actors, that makes me doubtful about the given function. If I test it myself, I can tell how well it fits in this case.

.:..: wrote on Jul 14th, 2018 at 7:28am:
- About triggers not getting active, I wouldn't bother about that as triggers will fire as soon as you make any other move

Even if further moves had such an effect, it would be weird to stay in a laser grid or a hot vapour released from a pipe without getting any damage until you start moving. But, as far as I remember, Move doesn't invoke Touch for initially overlapping actors (including the case when overlapping happened due to expansion by means of SetCollisionSize).

.:..: wrote on Jul 14th, 2018 at 7:28am:
(or perhaps could make SetCollisionSize check for touch/untouch)

Such a combination of Move and SetCollisionSize would still work imperfectly due to moving the bottom of the collision cylinder (potentially invoking redundant calls to FootZoneChange and Touch/UnTouch on triggers placed near the floor), but such an approximation is good enough. The best implementation would perform modifications of Location and CollisionHeight as a single action, so that only relocation of the top bound would determine invocations of Touch/UnTouch and ZoneChange/HeadZoneChange.

.:..: wrote on Jul 14th, 2018 at 7:28am:
- bForce, for purpose of forcing player into uncrouch mode when leaving PlayerWalking state (I don't want to have crouching underwater as you cant crouch underwater/in air by default either).

What if we don't have enough space to uncrouch? Do you think that placing your head inside a ceiling is better than swimming with reduced collision cylinder?

.:..: wrote on Jul 14th, 2018 at 7:28am:
BUT perhaps also take into account DrawScale changes (*(DrawScale/Default.DrawScale))? Incase some mod scales player.

Good idea.
  
Back to top
 
IP Logged
 
Shadow_Code
New Member
*
Offline


Oldunreal member

Posts: 10
Joined: Jun 9th, 2018
Re: Real crouching bug: telefragging when getting up
Reply #27 - Jul 15th, 2018 at 3:27pm
Print Post  
Odd bug, not exclusive to Real Crouch. Crawl into water, and you're sent flying into the air at roughly 1000 Velocity.Z.

Easy way to test this is to crawl down the collapsed pillar and into the water at the start of Chizra, where you drop down and there's a sleeping Skaarj.

Does anyone have any idea what in the world is going on here?
  
Back to top
 
IP Logged
 
Page Index Toggle Pages: 1 [2] 
Send TopicPrint
Bookmarks: del.icio.us Digg Facebook Google Google+ Linked in reddit StumbleUpon Twitter Yahoo