UT2kX PortalTeleporter: Improved Custom Teleporter Actor used in DM-Sateca-SE

  • Two Factor Authentication is now available on BeyondUnreal Forums. To configure it, visit your Profile and look for the "Two Step Verification" option on the left side. We can send codes via email (may be slower) or you can set up any TOTP Authenticator app on your phone (Authy, Google Authenticator, etc) to deliver codes. It is highly recommended that you configure this to keep your account safe.

stfx

New Member
Dec 10, 2013
26
0
0
This is the public release of the custom teleporter actor I specifically wrote for DM-Sateca-SE to make Quake style teleporters work smoothly but also fix some bugs in the standard UT2004 teleporters.

You can use it or modify it however you like as long as you give credit:)

Here are the changes in question:
  • Adds an offset to the destination location based on the position where you enter the portal.
  • Keeps player rotation pitch after teleporting instead of resetting it to 0.
  • Fixes wrong velocity in some cases if teleporters are not rotated 180° to each other.
  • Fixes FPS drop as PlayTeleportEffect was called twice.

However due to the way how the engine handles sending packets there is a slight delay online between setting the new location and the new rotation. This causes the player to be rotated wrongly for a split second which makes him not travel in a straight line and looks flickery.

The only way how to solve that afaik is to align both teleporters by making them look to each other. So if one teleporter points north the other needs to point south. If they are aligned like that then the rotation and velocity does not need to be changed on teleport.

Therefore I created one teleporter "PortalTeleporter" exactly for that case which only updates the location and works perfectly smooth even online. It also cashes the other teleporter after match start to make it faster when entering the teleporter but does no longer support teleporting to a random destination or other level.


Code:
//=============================================================================
// PortalTeleporter a.k.a VelocityTeleporter
// Copyright © 2014 by Dominique 'stfx' Grieshofer
//=============================================================================
class PortalTeleporter extends Teleporter
	placeable;

var() vector SourceLocation;
var() vector MinOffset;
var() vector MaxOffset;

var transient Teleporter OtherSideActor;

replication
{
	reliable if (Role == ROLE_Authority)
		OtherSideActor;
	reliable if (bNetInitial && (Role == ROLE_Authority))
		SourceLocation, MinOffset, MaxOffset; 
}

function PostBeginPlay()
{
	if (!(URL ~= "") && bEnabled)
        ForceGenerate();

	Super.PostBeginPlay();
}

simulated event ForceGenerate()
{
	foreach AllActors(class 'Teleporter', OtherSideActor)
		if (string(OtherSideActor.Tag)~=URL && OtherSideActor!=Self)
			break;
}

// Teleporter was touched by an actor.
simulated function PostTouch(Actor Other)
{
	local Pawn P;
	local Controller C;
	local vector offset;

	P = Pawn(Other);

	if (OtherSideActor == None || P == None)
		return;

	// Move the actor here.
	Disable('Touch');

    offset = P.Location - SourceLocation;
    offset.X = FClamp(offset.X, MinOffset.X, MaxOffset.X);
    offset.Y = FClamp(offset.Y, MinOffset.Y, MaxOffset.Y);
    offset.Z = FClamp(offset.Z, MinOffset.Z, MaxOffset.Z);

	if (!P.SetLocation(OtherSideActor.Location + offset))
	{
		log(self$" Teleport to "$offset$" failed for "$P);
		Enable('Touch');
		return;
	}

	//tell enemies about teleport
	if (Role == ROLE_Authority)
		For (C=Level.ControllerList; C!=None; C=C.NextController)
			if (C.Enemy == P)
				C.LineOfSightTo(P);

	if (P.Controller != None)
	{
		P.Controller.MoveTimer = -1.0;
		P.Anchor = self;
		P.SetMoveTarget(self);
	}

	Enable('Touch');

	P.PlayTeleportEffect(false, true);
	TriggerEvent(Event, self, P);
}

And finally here is the "RotatedPortalTeleporter" for reference which supports changing the rotation. But you should really only use the "PortalTeleporter" in combination with 180° rotated teleporters due to the issue with sending location/rotation packets online.

Code:
//=============================================================================
// RotatedPortalTeleporter
// Copyright © 2014 by Dominique 'stfx' Grieshofer
//=============================================================================
class RotatedPortalTeleporter extends Teleporter
	placeable;

var() bool bChangesOffset;
var() vector SourceLocation;
var() vector MinOffset;
var() vector MaxOffset;

replication
{
	reliable if (bNetInitial && (Role == ROLE_Authority))
		bChangesOffset, SourceLocation, MinOffset, MaxOffset; 
}

// Accept an actor that has teleported in.
simulated function bool Accept(Actor Incoming, Actor Source)
{
	local rotator newRot, velocityRot;
	local vector newOffset, newLocation;
	local Controller C;

	if (Incoming == None)
		return false;

	// Move the actor here.
	Disable('Touch');
	newRot = Incoming.Rotation;
	newLocation = Location;

	if (Source != None)
	{
		if (bChangesOffset)
		{
			velocityRot.Yaw = Rotation.Yaw - Source.Rotation.Yaw;
			newOffset = Incoming.Location - SourceLocation;
			newOffset.X = FClamp(newOffset.X, MinOffset.X, MaxOffset.X);
			newOffset.Y = FClamp(newOffset.Y, MinOffset.Y, MaxOffset.Y);
			newOffset.Z = FClamp(newOffset.Z, MinOffset.Z, MaxOffset.Z);
			newLocation += newOffset << velocityRot;
		}
		if (bChangesYaw)
		{
			newRot.Yaw += Rotation.Yaw - Source.Rotation.Yaw - 32768;
			velocityRot = rotator(Incoming.Velocity);
			velocityRot.Yaw += Rotation.Yaw - Source.Rotation.Yaw - 32768;
		}
	}

	if (Pawn(Incoming) != None)
	{
		//tell enemies about teleport
		if (Role == ROLE_Authority)
			For (C=Level.ControllerList; C!=None; C=C.NextController)
				if (C.Enemy == Incoming)
					C.LineOfSightTo(Incoming);

		if (!Pawn(Incoming).SetLocation(newLocation))
		{
			log(self$" Teleport to "$newOffset$" failed for "$Incoming);
			Enable('Touch');
			return false;
		}
		if ((Role == ROLE_Authority)
			|| (Level.TimeSeconds - LastFired > 0.5))
		{
			newRot.Pitch = Pawn(Incoming).GetViewRotation().Pitch;
			newRot.Roll = 0;

			Pawn(Incoming).SetRotation(newRot);
			Pawn(Incoming).SetViewRotation(newRot);
			Pawn(Incoming).ClientSetRotation(newRot);
			LastFired = Level.TimeSeconds;
		}
		if (Pawn(Incoming).Controller != None)
		{
			Pawn(Incoming).Controller.MoveTimer = -1.0;
			Pawn(Incoming).Anchor = self;
			Pawn(Incoming).SetMoveTarget(self);
		}
	}
	else
	{
		if (!Incoming.SetLocation(newLocation))
		{
			Enable('Touch');
			return false;
		}
		if (bChangesYaw)
			Incoming.SetRotation(newRot);
	}
	Enable('Touch');

	if (bChangesVelocity)
		Incoming.Velocity = TargetVelocity;
	else
	{
		if (bChangesYaw)
			Incoming.Velocity = VSize(Incoming.Velocity) * vector(velocityRot);
		if (bReversesX)
			Incoming.Velocity.X *= -1.0;
		if (bReversesY)
			Incoming.Velocity.Y *= -1.0;
		if (bReversesZ)
			Incoming.Velocity.Z *= -1.0;
	}	
	return true;
}

You can and should check out DM-Sateca-SE on how to set them up correctly in the map but here is a quick rundown:

1. Teleporter 1, source teleporters: Place 3 or so PortalTeleporters that the cover the box area where you teleport to teleporter 2.
2. Teleporter 1, destination teleporters: Place 1 PortalTeleporters a bit away from them where you teleport to from teleporter 2.
3. Teleporter 2: Do steps 1 and 2.
4. Set up the destination teleporters' Event->Tag to "Tele1" and "Tele2".
5. Set up the source teleporters' Teleporter->URL to "Tele1" and "Tele2".
6. Change the source teleporters' VelocityTeleporter->SourceLocation to the same location as the destination teleporter of the same teleporter.
7. Set up the source teleporters' VelocityTeleporter->MaxOffset and VelocityTeleporter->MinOffset to reflect the minimum/maximum allowed offset before teleport. For example if you set MinOffset.Y and MaxOffset.Y to 0 then the player's Y position will be the same as the destination teleporter's Y position after teleport.