This tutorial shows how a network of teleporter stations can be added to a game map: A set of teleporter stations is distributed throughout the map. Each station has a graphical user interface that the player can use to select the station that he wants to teleport to, and to activate the teleportation.
The tutorial involves the placement of new entities into the map, GUI scripting and map scripting. As such, it is also a gentle introduction into Cafu game scripting.
All tasks are demonstrated using the sample map BPWxBeta that is included with Cafu.
Each teleporter station is supposed to have a GUI that the player can use to control which other station he wants to teleport to, and to activate the teleportation.
We use the GUI editor component of CaWE in order to create the new GUI, an example is shown in the image.
The next section explains how the new GUI is scripted to properly react to player input:
> buttons should increment or decrement the number of the station that the player is being teleported to, and the big button below should actually trigger the teleportation.
When the GUI was saved in the GUI editor, it automatically wrote two files:
The first file contains the actual data that the GUI editor loads and saves, the second file is for your hand-made customizations. This is explained in detail in customscripts. In short, note that the first file is overwritten whenever the GUI editor saves a GUI file, whereas the second file is only created once as an empty stub file. The GUI editor touches the second file never again, so that you can use it for hand-written customizations.
We now edit file
Teleporter_main.cgui and enter the following script code:
dofile("Games/DeathMatch/GUIs/Teleporter_init.cgui"); function ButtonMinus:OnMouseEnter() self:set("borderColor", 1.0, 0.0, 0.0, 1.0); self:interpolate("textScale", 2.2, 2.4, 500); end function ButtonMinus:OnMouseLeave() self:set("borderColor", 0, 0.333333, 0.490196, 0.5); self:interpolate("textScale", 2.4, 2.2, 500); end function ButtonMinus:OnMouseButtonUp() local NodeNr=tonumber(DestNode:get("text")); if (NodeNr>1) then DestNode:set("text", NodeNr-1); end return true; end function ButtonPlus:OnMouseEnter() self:set("borderColor", 1.0, 0.0, 0.0, 1.0); self:interpolate("textScale", 2.2, 2.4, 500); end function ButtonPlus:OnMouseLeave() self:set("borderColor", 0, 0.333333, 0.490196, 0.5); self:interpolate("textScale", 2.4, 2.2, 500); end function ButtonPlus:OnMouseButtonUp() local NodeNr=tonumber(DestNode:get("text")); if (NodeNr<MAX_NODES) then DestNode:set("text", NodeNr+1); end return true; end function ButtonGo:OnMouseEnter() self:set("borderColor", 1.0, 0.0, 0.0, 1.0); self:interpolate("textScale", 0.5, 0.52, 500); end function ButtonGo:OnMouseLeave() self:set("borderColor", 0, 0.333333, 0.490196, 0.498039); self:interpolate("textScale", 0.52, 0.5, 500); end function ButtonGo:OnMouseButtonUp() local origNr=OUR_NODE_NR; local destNr=DestNode:get("text"); game.runMapCmd("teleport(" .. origNr .. ", " .. destNr .. ");"); return true; end -- This function is called as soon as the entity related to this GUI has been initialized. -- Note that our entities must have names like "teleporter_2_of_5" for this to work. function OnEntityInit() -- Figure out the total size of the teleportation network (number of nodes) -- that this station is in, and which number this node/station has. OUR_NODE_NR, MAX_NODES=string.match(gui:getEntityName(), "(%d+)_of_(%d+)"); OUR_NODE_NR=tonumber(OUR_NODE_NR) or 1; MAX_NODES =tonumber(MAX_NODES) or 3; InfoStation:set("text", "Station " .. OUR_NODE_NR); DestNode:set("text", (OUR_NODE_NR % MAX_NODES)+1); end
The first line of the script loads and runs the previously mentioned first file, that initializes the basics of the GUI. As such, the first line is crucial because it is responsibe for “connecting” both files.
After the GUI script is fully loaded, the Cafu Engine initializes the GUI by
OnInit()methods of all windows (they are defined in
OnInit2()methods of each window (of which we have none at all, but if we had, they were in
OnEntityInit()for 3D in-game GUIs such as ours.
OnEntityInit() method is very important as is determines the number of this teleporter station (or “node”) and the total number of nodes, and saves both values in global variables for future use.
It also updates the “Station XY” text of the
InfoStation window that indicates to the player at which station he is currently standing, and sets a reasonable default for the destination station that we're teleporting to.
All other methods are event handlers that the Cafu engine calls whenever the related event occurs. The
OnMouseLeave() methods are only used for adding some visual eye candy, and so we won't further discuss them.
The most interesting method is
ButtonGo:OnMouseButtonUp(), that is called when the player presses the button that is supposed to activate the teleporter.
However, note that this GUI script is like implementing the control software that runs on the teleporter station, just like a desktop program that would run on the station if the teleporter was real. As such, the GUI cannot implement the teleportation itself, but it must ask the map script to do it.
The GUI can use the
runMapCmd() function of the
game object in order to ask the game script to do something. We will later write a game script function
teleport(origNr, destNr) that does precisely that: Teleport a player from station
origNr to station
destNr, and thus when the player presses the button, we have this function called here.
The next task is quite simple: We place the new teleporters into the map. To do so, use the New Entity tool in order to create two new entities whereever you want to have a teleporter station:
static_detail_modelthat shows the big screen with the controls GUI, and
info_genericentity that indicates the source and destination point of the teleportation.
Note that you must name the
static_detail_model with a name of the form
somename_X_of_Y, where X is the number of this station and Y is the total number of the stations. This is because our GUI script above has been written to figure out these numbers from the name of the entity.
The final task is to write the above mentioned
teleport() function in the map script: For the BPWxBeta example map, we create or open
Games/DeathMatch/Worlds/BPWxBeta.lua, and enter the following code:
-- It's more readable to call wait(x) rather than coroutine.yield(x). wait=coroutine.yield; -- This function reloads and runs this script again. -- Useful for working with and testing the script "online", -- i.e. while the engine is running and without reloading the map! function reloadscript() dofile("Games/DeathMatch/Worlds/BPWxBeta.lua"); end -- This function is called by the teleporter GUI script when the user wants to teleport. -- origNr and destNr are numbers like 1 or 5 that indicate from and to which node in the -- teleportation network the teleportation should occur. -- -- TODO/FIXME: It would be nice if we could learn from an additional parameter which -- (player) entity was actually operating the teleporter controls (i.e. who pressed -- the "Go" button on the related GUI). function teleport(origNr, destNr) -- Prevent re-entrancy in the case that this method is called very quickly in succession. -- This can happen with the current EntHumanPlayerT code, which is sort of a bug. if (isTeleporting) then return true end local ox, oy, oz=_G["info_generic_" .. origNr]:GetOrigin(); local dx, dy, dz=_G["info_generic_" .. destNr]:GetOrigin(); Console.Print("Teleporting from node " .. origNr .. " to node " .. destNr .. ", "); Console.Print("which is at coordinate (" .. dx .. ", " .. dy .. ", " .. dz .. ").\n"); -- Teleport all entities with name "Player*" to the destination node. for PlayerNr=1, 99 do local PlayerEnt=_G["Player" .. PlayerNr]; if not PlayerEnt then break end local px, py, pz=PlayerEnt:GetOrigin(); local ax=px-ox; local ay=py-oy; local az=pz-oz; if (pz>oz and math.sqrt(ax*ax + ay*ay + az*az) < 2000.0) then -- VARIANT 1: -- When teleporting PlayerEnt to (dx+ax, dy+ay, dz+az), it is safe to teleport -- multiple players all at the same time (continuing the loop), as their -- relative positioning doesn't change. -- However, depending on the map geometry, teleporting to (dx+ax, dy+ay, dz+az) -- could teleport the player into solid matter, as it requires that at the dest -- there is as much free space (in the same absolute direction) as at the origin. -- PlayerEnt:SetOrigin(dx+ax, dy+ay, dz+az); -- VARIANT 2: -- When teleporting PlayerEnt to (dx, dy, dz+az), the results are much easier -- to foresee, but this requires teleporting only one player at a time. PlayerEnt:SetOrigin(dx, dy, dz+az); break; -- TODO/FIXME: We should check that the destination space is actually free -- from other players and entities before we relocate PlayerEnt there! end end isTeleporting=true; wait(0.5); isTeleporting=false; end