Author: beffy (c++programmer)

NEW! Download / view this tutorial as PDF!

Ah well, after reading a post in the GG forums on a lazy saturday, I decided to give it a try and script a little teleport... and here it is... ;-)

The basic idea was to combine 2 (or more) triggers, animated dts shapes for the basic effect, particle effects, a little script swapping the player location/transform and finally some "buzz" sound.

Okay, so lets start with the function doing the actual teleport... I've put it into "server/scripts/commands.cs" and it looks like this:

function serverCmdTeleportPlayer(%client, %clientId, %targetObj)
{
    %player = %clientId.player;
    %currPlayerPos = %player.getPosition();
    %targetPos = %targetObj.getPosition();
    %x = getWord(%targetPos, 0);
    %y = getWord(%targetPos, 1);
    %z = getWord(%targetPos, 2);
    // adjust z value to prevent player from falling through the terrain... :-P
    %z += 3.0;
    %finalPos = %x SPC %y SPC %z;
    echo("Transforing from" SPC %currPlayerPos SPC "to" SPC %finalPos);
    %player.setTransform(%finalPos);
}
Well, pretty basic, all it does is get the player's position and the position of the target object indicated by %targetObj, adjusts the z value to prevent the player from falling through the terrain and sends him to the %finalPos position.

Now lets look at the trigger, I've made a new file "fps/server/teleportTrigger.cs" which I execute in "fps/server/game.cs" (as always), and it looks like this:

datablock TriggerData(TeleportTrigger)
{
   tickPeriodMS = 500;
};
datablock AudioProfile(TeleportBuzz)
{
   fileName = "~/data/sound/fx/electricity.wav";
   description = AudioClose3d;
	preload = true;
};

function TeleportTrigger::onEnterTrigger(%data, %obj, %colObj)
{
   %checkname = %obj.getName();
   %client = %colObj.client;
   if(!%client)
   {
      echo("not a client!");
      return;
   }
   echo("Teleport client:" SPC %client);

   if(%checkname $= "TeleportTrigger1")
   {
      // if the player didn't recently beam over here... otherwise
      // he would be looping around between the two, I guess...
      if(!$from2to1)
      {
         %target = "TeleportTrigger2";
         CommandToClient(%client,'bottomprint',"Teleporter initializing... good luck... buahahaha!!",2,10);
         $teleSched = schedule(2000,0,"goScotty",%client,%target);
        	$teleSound = serverPlay3D(TeleportBuzz,%client.player.getTransform());
         %client.player.setCloaked(true);
         $from1to2 = true;
         $from2to1 = false;
      }
   }
   else
   {
      if(!$from1to2)
      {
         %target = "TeleportTrigger1";
         CommandToClient(%client,'bottomprint',"Teleporter initializing... good luck... buahahaha!!",2,10);
         $teleSched = schedule(2000,0,"goScotty",%client, %target);
        	$teleSound = serverPlay3D(TeleportBuzz,%client.player.getTransform());
         %client.player.setCloaked(true);
         $from2to1 = true;
         $from1to2 = false;
      }
   }
}

function TeleportTrigger::onLeaveTrigger(%data, %obj, %colObj)
{
   %client = %colObj.client;
   if(!%client)
   {
      echo("not a client!");
      return;
   }
   %checkname = %obj.getName();
   echo("TeleportTrigger::onLeaveTrigger called!");
   cancel($teleSched);
   alxStop($teleSound);
   %client.player.setCloaked(false);
   // if the player leaves the target trigger,
   // he can use it, too...
   if(%checkname $= "TeleportTrigger1")
   {
      $from2to1 = false;
   }
   else if(%checkname $= "TeleportTrigger2")
   {
      $from1to2 = false;
   }
}

function goScotty(%client, %target)
{
   // beam me up!
   commandToServer('TeleportPlayer', %client, %target);
}
function TeleportTrigger::onTickTrigger(%data, %obj)
{
}
I've got two trigger objects in my mission file named "TeleportTrigger1" and "TeleportTrigger2", furthermore there are the two shape files, which have their own datablock (btw., I took these shapefiles and their datablock file "fxShapes.cs" from the latest RWTA build).
Here is the datablock:
datablock StaticShapeData(MeshEffect)
{
   category = "Effects";
   shapeFile = "~/data/shapes/markers/flame.dts";
};
I use two vars "$from1to2" and "$from2to1" to keep track if the player already was teleported to the recent teleport, and if he was, he has to step out of it (onLeaveTrigger), before he can use it again... otherwise he would be trapped, I guess... ;-)
To make it more realistic and to not just move the player, I put a little timeout of two seconds in, and, what adds quite a lot to it, I set
	%client.player.setCloaked(true);
which kinda fades the player out and makes him semi-transparent, and after he's teleported, he "fades back in"... make sure to try it in 3rd person perspective...!! :-)
 
So here is how all that looks in my mission file:
   new StaticShape(TeleportEffect1) {
      position = "-27.6876 21.0268 100.362";
      rotation = "1 0 0 90.5273";
      scale = "1 1 1";
      dataBlock = "MeshEffect";
   };
   new ParticleEmitterNode(TeleportParticle1) {
      position = "-27.1392 22.8644 101.253";
      rotation = "1 0 0 0";
      scale = "1 1 1";
      dataBlock = "defaultParticleEmitterNode";
      emitter = "TeleportEmitter";
      velocity = "1";
   };
   new StaticShape(TeleportEffect2) {
      position = "-28.3129 125.469 100.391";
      rotation = "1 0 0 90.5273";
      scale = "1 1 1";
      dataBlock = "MeshEffect";
   };
   new ParticleEmitterNode(TeleportParticle2) {
      position = "-28.3129 127.069 101.391";
      rotation = "1 0 0 0";
      scale = "1 1 1";
      dataBlock = "defaultParticleEmitterNode";
      emitter = "TeleportEmitter";
      velocity = "1";
   };
   new Trigger(TeleportTrigger2) {
      position = "-29.4077 128.417 99.849";
      rotation = "1 0 0 0";
      scale = "3 2 2";
      dataBlock = "TeleportTrigger";
      polyhedron = "0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000
	  -1.0000000 0.0000000 0.0000000 0.0000000 1.0000000";
   };
   new Trigger(TeleportTrigger1) {
      position = "-29.2559 23.685 99.5474";
      rotation = "1 0 0 0";
      scale = "3 2 2";
      dataBlock = "TeleportTrigger";
      polyhedron = "0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000
	  -1.0000000 0.0000000 0.0000000 0.0000000 1.0000000";
   };
The ParticleEmitter looks like this, I keep all of my particle stuff in a file named "customParticles.cs", which is also executed in ... well, you guess it!
datablock ParticleData(TeleportParticle)
{
    dragCoefficient = 1.11437;
    gravityCoefficient = -0.735043;
    windCoefficient = 0;
    inheritedVelFactor = 0.483366;
    constantAcceleration = 0;
    lifetimeMS = 1056;
    lifetimeVarianceMS = 256;
    useInvAlpha = 0;
    spinRandomMin = -159;
    spinRandomMax = 172;
    textureName = "fps/data/shapes/rifle/smokeParticle";
    times[0] = 0;
    times[1] = 1;
    colors[0] = "0.102362 0.070866 0.000000 0.370079";
    colors[1] = "0.000000 0.102362 0.000000 0.740157";
    sizes[0] = 6.08863;
    sizes[1] = 0;
};

datablock ParticleEmitterData(TeleportEmitter)
{
    ejectionPeriodMS = 10;
    periodVarianceMS = 2;
    ejectionVelocity = 2.75;
    velocityVariance = 1.62;
    ejectionOffset = 0;
    thetaMin = 47;
    thetaMax = 90;
    phiReferenceVel = 144;
    phiVariance = 360;
    overrideAdvances = 0;
    orientParticles= 0;
    orientOnVelocity = 1;
    particles = "TeleportParticle";
};

Okay, so here are the "step-by-step" instructions...
 
1) download the zip :-P
2) put flame.dts and flame.png in "fps\data\shapes\markers"
3) put electricity.wav in "\fps\data\sound\fx"
4) put fxShapes.cs, teleportTrigger.cs and customParticles.cs in "fps\server\scripts"
5) put
exec("./teleportTrigger.cs");
exec("./customParticles.cs");
exec("./fxShapes.cs");
in "fps\server\scripts\game.cs"
6) put the function serverCmdTeleportPlayer(%client, %targetObj) in "fps\server\scripts\commands.cs"
7) add all the objects needed to your mission file (use the world editor by pressing F11 -> F4)
Note: it may be hard to add and place the particle emitters in the editor, so if you have problems, simply add the shapes and the triggers with the editor, then save your mission and add the emitters manually - you can copy the positions of the triggers and then adjust them later in the editor).
8) Note: if you're missing some of the font profiles the GUI is using, you could either copy them from the "defaultProfiles.cs" provided with the zip or you can use different profiles for your GUI, of course...
 
Finally, here's a screenshot:

Click to enlarge!


Well, as always, hope you enjoyed it - happy teleporting! :-))

EDIT (08. July 2002, 8pm): fixed some multiplayer problems, tutorial and zip file have been updated!

UPDATE (08. July 2002, 11:40pm):
Okay folks, to increase the fun factor I've written a new trigger file which supports multiple triggers - as many as you want! :-)
And the best thing is, you don't have to change anything in the script, you simply execute it at startup (as always), and you add your trigger, shape and particle objects in the editor (press F11, if you've already added the objects like I described above, you can simply select a group of them [trigger, particle effect and shape] in the top right editor pane by holding SHIFT, then CTRL-C, CTRL-V, rename them from "TeleportTrigger2",... to "TeleportTriggerX",...), and that's it!
The only important thing is that the triggers have to be named "TeleportTrigger1" ... "TeleportTriggerN", cause the script is iterating over the MissionGroup to find them through a string compare function!

Of course, you have to change your trigger datablock from
   dataBlock = "TeleportTrigger";
to
   dataBlock = "MultiTeleportTrigger";
So here is the new script, but it's also included in the updated zip file (multiTeleportTrigger.cs)!
$numTeleports = 0;

datablock TriggerData(MultiTeleportTrigger)
{
   tickPeriodMS = 500;
};

function MultiTeleportTrigger::onEnterTrigger(%data, %obj, %colObj)
{
   if($numTeleports == 0)
   {
      // search for Triggers by their name once, so every trigger
      // which has "TeleportTrigger" in its name is counted
      $numTeleports = getMultiTriggerCount("TeleportTrigger");
      echo("$numTeleports:" SPC $numTeleports);
   }

   %client = %colObj.client;
   if(!%client)
   {
      echo("not a client!");
      return;
   }
   %checkname = %obj.getName();
   // if the player didn't recently beam over here... otherwise
   // he would be looping around between the two, I guess...
   if(%checkname !$= $currMultiTeleTrigger)
   {
      // pick random number, the teleport names start from 1,
      // so if the random number is zero, simply take the last teleport:
      %rand = getRandom($numTeleports) == 0 ? 1 : $numTeleports;
      %target = "TeleportTrigger" @ %rand;
      // we don't want to stay where we are...
      if(%target $= %checkName)
      {
         // ... so increase or decrease the random number by 1
         %rand = %rand+1 > $numTeleports ? %rand-1 : %rand+1;
         %target = "TeleportTrigger" @ %rand;
      }
      echo("*** TELEPORT TARGET:" SPC %target);
      CommandToClient(%client,'bottomprint',"Teleporter initializing... good luck... buahahaha!!",2,10);
      $teleSched = schedule(2000,0,"goScotty",%client,%target);
     	$teleSound = serverPlay3D(TeleportBuzz,%client.player.getTransform());
      %client.player.setCloaked(true);
      // save the target - until the teleported client leaves it, then reset
      $currMultiTeleTrigger = %target;
   }
}

function MultiTeleportTrigger::onLeaveTrigger(%data, %obj, %colObj)
{
   %client = %colObj.client;
   if(!%client)
   {
      echo("not a client!");
      return;
   }
   %checkname = %obj.getName();
   cancel($teleSched);
   alxStop($teleSound);
   %client.player.setCloaked(false);
   // if the player leaves the target trigger,
   // he can use it again, too... so reset the global var
   if(%checkname $= $currMultiTeleTrigger)
   {
      $currMultiTeleTrigger = "";
   }
}
function MultiTeleportTrigger::onTickTrigger(%data, %obj)
{
}
// *********************************
// Helper functions
// *********************************

// do the teleport
function goScotty(%client, %target)
{
   echo("goScotty called!");
   // beam me up!
   commandToServer('TeleportPlayer', %client, %target);
}

// find the number of teleport triggers by name comparison
function getMultiTriggerCount(%name)
{
   %dataGroup = "MissionGroup";
   %triggerCount = 0;
   for(%i = 0; %i < %dataGroup.getCount(); %i++)
   {
      %obj = %dataGroup.getObject(%i);
      if(%obj.getClassName() !$= "Trigger")
      {
         // no trigger!
         continue;
      }
      if((strStr(%obj.getDatablock().getName(), %name) != -1) && isObject(%obj))
      {
         echo(%i SPC "Found Trigger:" SPC %obj.getName());
         %triggerCount++;
      }
   }
   return %triggerCount;
}