Torque3D/Templates/Modules/FPSGameplay/scripts/server/deathMatchGame.cs
Areloch d680dc9934 Initial implementation of the new Base Game Template and some starting modules.
This makes some tweaks to the engine to support this, specifically, it tweaks the hardcoded shaderpaths to defer to a pref variable, so none of the shader paths are hardcoded.

Also tweaks how post effects read in texture files, removing a bizzare filepath interpretation choice, where if the file path didn't start with "/" it forcefully appended the script's file path. This made it impossible to have images not in the same dir as the script file defining the post effect.

This was changed and the existing template's post effects tweaked for now to just add "./" to those few paths impacted, as well as the perf vars to support the non-hardcoded shader paths in the engine.
2017-02-24 02:40:56 -06:00

725 lines
25 KiB
C#

//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// DeathmatchGame
// ----------------------------------------------------------------------------
// Depends on methods found in gameCore.cs. Those added here are specific to
// this game type and/or over-ride the "default" game functionaliy.
//
// The desired Game Type must be added to each mission's LevelInfo object.
// - gameType = "Deathmatch";
// If this information is missing then the GameCore will default to Deathmatch.
// ----------------------------------------------------------------------------
function DeathMatchGame::initGameVars(%game)
{
//echo (%game @"\c4 -> "@ %game.class @" -> DeathMatchGame::initGameVars");
//-----------------------------------------------------------------------------
// What kind of "player" is spawned is either controlled directly by the
// SpawnSphere or it defaults back to the values set here. This also controls
// which SimGroups to attempt to select the spawn sphere's from by walking down
// the list of SpawnGroups till it finds a valid spawn object.
// These override the values set in core/scripts/server/spawn.cs
//-----------------------------------------------------------------------------
// Leave $Game::defaultPlayerClass and $Game::defaultPlayerDataBlock as empty strings ("")
// to spawn a the $Game::defaultCameraClass as the control object.
$Game::defaultPlayerClass = "Player";
$Game::defaultPlayerDataBlock = "DefaultPlayerData";
$Game::defaultPlayerSpawnGroups = "PlayerSpawnPoints PlayerDropPoints";
//-----------------------------------------------------------------------------
// What kind of "camera" is spawned is either controlled directly by the
// SpawnSphere or it defaults back to the values set here. This also controls
// which SimGroups to attempt to select the spawn sphere's from by walking down
// the list of SpawnGroups till it finds a valid spawn object.
// These override the values set in core/scripts/server/spawn.cs
//-----------------------------------------------------------------------------
$Game::defaultCameraClass = "Camera";
$Game::defaultCameraDataBlock = "Observer";
$Game::defaultCameraSpawnGroups = "CameraSpawnPoints PlayerSpawnPoints PlayerDropPoints";
// Set the gameplay parameters
%game.duration = 30 * 60;
%game.endgameScore = 20;
%game.endgamePause = 10;
%game.allowCycling = false; // Is mission cycling allowed?
}
function DeathMatchGame::onGameDurationEnd(%game)
{
// This "redirect" is here so that we can abort the game cycle if
// the $Game::Duration variable has been cleared, without having
// to have a function to cancel the schedule.
if ($Game::Duration && !(EditorIsActive() && GuiEditorIsActive()))
Game.onGameDurationEnd();
}
function DeathMatchGame::onClientEnterGame(%this, %client)
{
// This function currently relies on some helper functions defined in
// core/scripts/spawn.cs. For custom spawn behaviors one can either
// override the properties on the SpawnSphere's or directly override the
// functions themselves.
//echo (%game @"\c4 -> "@ %game.class @" -> GameCore::onClientEntergame");
// Sync the client's clocks to the server's
commandToClient(%client, 'SyncClock', $Sim::Time - $Game::StartTime);
//Set the player name based on the client's connection data
%client.setPlayerName(%client.connectData);
// Find a spawn point for the camera
// This function currently relies on some helper functions defined in
// core/scripts/server/spawn.cs. For custom spawn behaviors one can either
// override the properties on the SpawnSphere's or directly override the
// functions themselves.
%cameraSpawnPoint = pickCameraSpawnPoint($Game::DefaultCameraSpawnGroups);
// Spawn a camera for this client using the found %spawnPoint
%client.spawnCamera(%cameraSpawnPoint);
// Setup game parameters, the onConnect method currently starts
// everyone with a 0 score.
%client.score = 0;
%client.kills = 0;
%client.deaths = 0;
// weaponHUD
%client.RefreshWeaponHud(0, "", "");
// Prepare the player object.
%this.preparePlayer(%client);
// Inform the client of all the other clients
%count = ClientGroup.getCount();
for (%cl = 0; %cl < %count; %cl++)
{
%other = ClientGroup.getObject(%cl);
if ((%other != %client))
{
// These should be "silent" versions of these messages...
messageClient(%client, 'MsgClientJoin', "",
%other.playerName,
%other,
%other.sendGuid,
%other.team,
%other.score,
%other.kills,
%other.deaths,
%other.isAIControlled(),
%other.isAdmin,
%other.isSuperAdmin);
}
}
// Inform the client we've joined up
messageClient(%client,
'MsgClientJoin', '\c2Welcome to the Torque demo app %1.',
%client.playerName,
%client,
%client.sendGuid,
%client.team,
%client.score,
%client.kills,
%client.deaths,
%client.isAiControlled(),
%client.isAdmin,
%client.isSuperAdmin);
// Inform all the other clients of the new guy
messageAllExcept(%client, -1, 'MsgClientJoin', '\c1%1 joined the game.',
%client.playerName,
%client,
%client.sendGuid,
%client.team,
%client.score,
%client.kills,
%client.deaths,
%client.isAiControlled(),
%client.isAdmin,
%client.isSuperAdmin);
}
function DeathMatchGame::onClientLeaveGame(%this, %client)
{
// Cleanup the camera
if (isObject(%this.camera))
%this.camera.delete();
}
//-----------------------------------------------------------------------------
// The server has started up so do some game start up
//-----------------------------------------------------------------------------
function DeathMatchGame::onMissionStart(%this)
{
//set up the game and game variables
%this.initGameVars();
$Game::Duration = %this.duration;
$Game::EndGameScore = %this.endgameScore;
$Game::EndGamePause = %this.endgamePause;
//echo (%game @"\c4 -> "@ %game.class @" -> GameCore::onStartGame");
if ($Game::Running)
{
error("startGame: End the game first!");
return;
}
// Inform the client we're starting up
for (%clientIndex = 0; %clientIndex < ClientGroup.getCount(); %clientIndex++)
{
%cl = ClientGroup.getObject(%clientIndex);
commandToClient(%cl, 'GameStart');
// Other client specific setup..
%cl.score = 0;
%cl.kills = 0;
%cl.deaths = 0;
}
// Start the game timer
if ($Game::Duration)
$Game::Schedule = %this.schedule($Game::Duration * 1000, "onGameDurationEnd");
$Game::Running = true;
$Game = %this;
}
function DeathMatchGame::onMissionEnded(%this)
{
if (!$Game::Running)
{
error("endGame: No game running!");
return;
}
// Stop any game timers
cancel($Game::Schedule);
for (%clientIndex = 0; %clientIndex < ClientGroup.getCount(); %clientIndex++)
{
%cl = ClientGroup.getObject(%clientIndex);
commandToClient(%cl, 'GameEnd', $Game::EndGamePause);
}
$Game::Running = false;
$Game::Cycling = false;
$Game = "";
}
function DeathMatchGame::onMissionReset(%this)
{
// Called by resetMission(), after all the temporary mission objects
// have been deleted.
%this.initGameVars();
$Game::Duration = %this.duration;
$Game::EndGameScore = %this.endgameScore;
$Game::EndGamePause = %this.endgamePause;
}
//-----------------------------------------------------------------------------
// Functions that implement game-play
// These are here for backwards compatibilty only, games and/or mods should
// really be overloading the server and mission functions listed ubove.
//-----------------------------------------------------------------------------
// Added this stage to creating a player so game types can override it easily.
// This is a good place to initiate team selection.
function DeathMatchGame::preparePlayer(%this, %client)
{
//echo (%game @"\c4 -> "@ %game.class @" -> GameCore::preparePlayer");
// Find a spawn point for the player
// This function currently relies on some helper functions defined in
// core/scripts/spawn.cs. For custom spawn behaviors one can either
// override the properties on the SpawnSphere's or directly override the
// functions themselves.
%playerSpawnPoint = pickPlayerSpawnPoint($Game::DefaultPlayerSpawnGroups);
// Spawn a camera for this client using the found %spawnPoint
//%client.spawnPlayer(%playerSpawnPoint);
%this.spawnPlayer(%client, %playerSpawnPoint);
// Starting equipment
%this.loadOut(%client.player);
}
function DeathMatchGame::loadOut(%game, %player)
{
//echo (%game @"\c4 -> "@ %game.class @" -> GameCore::loadOut");
%player.clearWeaponCycle();
%player.setInventory(Ryder, 1);
%player.setInventory(RyderClip, %player.maxInventory(RyderClip));
%player.setInventory(RyderAmmo, %player.maxInventory(RyderAmmo)); // Start the gun loaded
%player.addToWeaponCycle(Ryder);
%player.setInventory(Lurker, 1);
%player.setInventory(LurkerClip, %player.maxInventory(LurkerClip));
%player.setInventory(LurkerAmmo, %player.maxInventory(LurkerAmmo)); // Start the gun loaded
%player.addToWeaponCycle(Lurker);
%player.setInventory(LurkerGrenadeLauncher, 1);
%player.setInventory(LurkerGrenadeAmmo, %player.maxInventory(LurkerGrenadeAmmo));
%player.addToWeaponCycle(LurkerGrenadeLauncher);
%player.setInventory(ProxMine, %player.maxInventory(ProxMine));
%player.addToWeaponCycle(ProxMine);
%player.setInventory(DeployableTurret, %player.maxInventory(DeployableTurret));
%player.addToWeaponCycle(DeployableTurret);
if (%player.getDatablock().mainWeapon.image !$= "")
{
%player.mountImage(%player.getDatablock().mainWeapon.image, 0);
}
else
{
%player.mountImage(Ryder, 0);
}
}
// Customized kill message for falling deaths
function sendMsgClientKilled_Impact( %msgType, %client, %sourceClient, %damLoc )
{
messageAll( %msgType, '%1 fell to his death!', %client.playerName );
}
// Customized kill message for suicides
function sendMsgClientKilled_Suicide( %msgType, %client, %sourceClient, %damLoc )
{
messageAll( %msgType, '%1 takes his own life!', %client.playerName );
}
// Default death message
function sendMsgClientKilled_Default( %msgType, %client, %sourceClient, %damLoc )
{
if ( %sourceClient == %client )
sendMsgClientKilled_Suicide(%client, %sourceClient, %damLoc);
else if ( %sourceClient.team !$= "" && %sourceClient.team $= %client.team )
messageAll( %msgType, '%1 killed by %2 - friendly fire!', %client.playerName, %sourceClient.playerName );
else
messageAll( %msgType, '%1 gets nailed by %2!', %client.playerName, %sourceClient.playerName );
}
function DeathMatchGame::onDeath(%game, %client, %sourceObject, %sourceClient, %damageType, %damLoc)
{
//echo (%game @"\c4 -> "@ %game.class @" -> GameCore::onDeath");
// clear the weaponHUD
%client.RefreshWeaponHud(0, "", "");
// Clear out the name on the corpse
%client.player.setShapeName("");
// Switch the client over to the death cam and unhook the player object.
if (isObject(%client.camera) && isObject(%client.player))
{
%client.camera.setMode("Corpse", %client.player);
%client.setControlObject(%client.camera);
}
%client.player = 0;
// Display damage appropriate kill message
%sendMsgFunction = "sendMsgClientKilled_" @ %damageType;
if ( !isFunction( %sendMsgFunction ) )
%sendMsgFunction = "sendMsgClientKilled_Default";
call( %sendMsgFunction, 'MsgClientKilled', %client, %sourceClient, %damLoc );
// Dole out points and check for win
if (( %damageType $= "Suicide" || %sourceClient == %client ) && isObject(%sourceClient))
{
%game.incDeaths( %client, 1, true );
%game.incScore( %client, -1, false );
}
else
{
%game.incDeaths( %client, 1, false );
%game.incScore( %sourceClient, 1, true );
%game.incKills( %sourceClient, 1, false );
// If the game may be ended by a client getting a particular score, check that now.
if ( $Game::EndGameScore > 0 && %sourceClient.kills >= $Game::EndGameScore )
%game.cycleGame();
}
}
// ----------------------------------------------------------------------------
// Scoring
// ----------------------------------------------------------------------------
function DeathMatchGame::incKills(%game, %client, %kill, %dontMessageAll)
{
%client.kills += %kill;
if( !%dontMessageAll )
messageAll('MsgClientScoreChanged', "", %client.score, %client.kills, %client.deaths, %client);
}
function DeathMatchGame::incDeaths(%game, %client, %death, %dontMessageAll)
{
%client.deaths += %death;
if( !%dontMessageAll )
messageAll('MsgClientScoreChanged', "", %client.score, %client.kills, %client.deaths, %client);
}
function DeathMatchGame::incScore(%game, %client, %score, %dontMessageAll)
{
%client.score += %score;
if( !%dontMessageAll )
messageAll('MsgClientScoreChanged', "", %client.score, %client.kills, %client.deaths, %client);
}
function DeathMatchGame::getScore(%client) { return %client.score; }
function DeathMatchGame::getKills(%client) { return %client.kills; }
function DeathMatchGame::getDeaths(%client) { return %client.deaths; }
function DeathMatchGame::getTeamScore(%client)
{
%score = %client.score;
if ( %client.team !$= "" )
{
// Compute team score
for (%i = 0; %i < ClientGroup.getCount(); %i++)
{
%other = ClientGroup.getObject(%i);
if ((%other != %client) && (%other.team $= %client.team))
%score += %other.score;
}
}
return %score;
}
// ----------------------------------------------------------------------------
// Spawning
// ----------------------------------------------------------------------------
function DeathMatchGame::spawnPlayer(%game, %client, %spawnPoint, %noControl)
{
//echo (%game @"\c4 -> "@ %game.class @" -> GameCore::spawnPlayer");
if (isObject(%client.player))
{
// The client should not already have a player. Assigning
// a new one could result in an uncontrolled player object.
error("Attempting to create a player for a client that already has one!");
}
// Attempt to treat %spawnPoint as an object
if (getWordCount(%spawnPoint) == 1 && isObject(%spawnPoint))
{
// Defaults
%spawnClass = $Game::DefaultPlayerClass;
%spawnDataBlock = $Game::DefaultPlayerDataBlock;
// Overrides by the %spawnPoint
if (isDefined("%spawnPoint.spawnClass"))
{
%spawnClass = %spawnPoint.spawnClass;
%spawnDataBlock = %spawnPoint.spawnDatablock;
}
else if (isDefined("%spawnPoint.spawnDatablock"))
{
// This may seem redundant given the above but it allows
// the SpawnSphere to override the datablock without
// overriding the default player class
%spawnDataBlock = %spawnPoint.spawnDatablock;
}
%spawnProperties = %spawnPoint.spawnProperties;
%spawnScript = %spawnPoint.spawnScript;
// Spawn with the engine's Sim::spawnObject() function
%player = spawnObject(%spawnClass, %spawnDatablock, "",
%spawnProperties, %spawnScript);
// If we have an object do some initial setup
if (isObject(%player))
{
// Pick a location within the spawn sphere.
%spawnLocation = %game.pickPointInSpawnSphere(%player, %spawnPoint);
%player.setTransform(%spawnLocation);
}
else
{
// If we weren't able to create the player object then warn the user
// When the player clicks OK in one of these message boxes, we will fall through
// to the "if (!isObject(%player))" check below.
if (isDefined("%spawnDatablock"))
{
MessageBoxOK("Spawn Player Failed",
"Unable to create a player with class " @ %spawnClass @
" and datablock " @ %spawnDatablock @ ".\n\nStarting as an Observer instead.",
"");
}
else
{
MessageBoxOK("Spawn Player Failed",
"Unable to create a player with class " @ %spawnClass @
".\n\nStarting as an Observer instead.",
"");
}
}
}
else
{
// Create a default player
%player = spawnObject($Game::DefaultPlayerClass, $Game::DefaultPlayerDataBlock);
if (!%player.isMemberOfClass("Player"))
warn("Trying to spawn a class that does not derive from Player.");
// Treat %spawnPoint as a transform
%player.setTransform(%spawnPoint);
}
// If we didn't actually create a player object then bail
if (!isObject(%player))
{
// Make sure we at least have a camera
%client.spawnCamera(%spawnPoint);
return;
}
// Update the default camera to start with the player
if (isObject(%client.camera) && !isDefined("%noControl"))
{
if (%player.getClassname() $= "Player")
%client.camera.setTransform(%player.getEyeTransform());
else
%client.camera.setTransform(%player.getTransform());
}
// Add the player object to MissionCleanup so that it
// won't get saved into the level files and will get
// cleaned up properly
MissionCleanup.add(%player);
// Store the client object on the player object for
// future reference
%player.client = %client;
// If the player's client has some owned turrets, make sure we let them
// know that we're a friend too.
if (%client.ownedTurrets)
{
for (%i=0; %i<%client.ownedTurrets.getCount(); %i++)
{
%turret = %client.ownedTurrets.getObject(%i);
%turret.addToIgnoreList(%player);
}
}
// Player setup...
if (%player.isMethod("setShapeName"))
%player.setShapeName(%client.playerName);
if (%player.isMethod("setEnergyLevel"))
%player.setEnergyLevel(%player.getDataBlock().maxEnergy);
if (!isDefined("%client.skin"))
{
// Determine which character skins are not already in use
%availableSkins = %player.getDatablock().availableSkins; // TAB delimited list of skin names
%count = ClientGroup.getCount();
for (%cl = 0; %cl < %count; %cl++)
{
%other = ClientGroup.getObject(%cl);
if (%other != %client)
{
%availableSkins = strreplace(%availableSkins, %other.skin, "");
%availableSkins = strreplace(%availableSkins, "\t\t", ""); // remove empty fields
}
}
// Choose a random, unique skin for this client
%count = getFieldCount(%availableSkins);
%client.skin = addTaggedString( getField(%availableSkins, getRandom(%count)) );
}
%player.setSkinName(%client.skin);
// Give the client control of the player
%client.player = %player;
// Give the client control of the camera if in the editor
if( $startWorldEditor )
{
%control = %client.camera;
%control.mode = "Fly";
EditorGui.syncCameraGui();
}
else
%control = %player;
// Allow the player/camera to receive move data from the GameConnection. Without this
// the user is unable to control the player/camera.
if (!isDefined("%noControl"))
%client.setControlObject(%control);
}
function DeathMatchGame::pickPointInSpawnSphere(%this, %objectToSpawn, %spawnSphere)
{
%SpawnLocationFound = false;
%attemptsToSpawn = 0;
while(!%SpawnLocationFound && (%attemptsToSpawn < 5))
{
%sphereLocation = %spawnSphere.getTransform();
// Attempt to spawn the player within the bounds of the spawnsphere.
%angleY = mDegToRad(getRandom(0, 100) * m2Pi());
%angleXZ = mDegToRad(getRandom(0, 100) * m2Pi());
%sphereLocation = setWord( %sphereLocation, 0, getWord(%sphereLocation, 0) + (mCos(%angleY) * mSin(%angleXZ) * getRandom(-%spawnSphere.radius, %spawnSphere.radius)));
%sphereLocation = setWord( %sphereLocation, 1, getWord(%sphereLocation, 1) + (mCos(%angleXZ) * getRandom(-%spawnSphere.radius, %spawnSphere.radius)));
%SpawnLocationFound = true;
// Now have to check that another object doesn't already exist at this spot.
// Use the bounding box of the object to check if where we are about to spawn in is
// clear.
%boundingBoxSize = %objectToSpawn.getDatablock().boundingBox;
%searchRadius = getWord(%boundingBoxSize, 0);
%boxSizeY = getWord(%boundingBoxSize, 1);
// Use the larger dimention as the radius to search
if (%boxSizeY > %searchRadius)
%searchRadius = %boxSizeY;
// Search a radius about the area we're about to spawn for players.
initContainerRadiusSearch( %sphereLocation, %searchRadius, $TypeMasks::PlayerObjectType );
while ( (%objectNearExit = containerSearchNext()) != 0 )
{
// If any player is found within this radius, mark that we need to look
// for another spot.
%SpawnLocationFound = false;
break;
}
// If the attempt at finding a clear spawn location failed
// try no more than 5 times.
%attemptsToSpawn++;
}
// If we couldn't find a spawn location after 5 tries, spawn the object
// At the center of the sphere and give a warning.
if (!%SpawnLocationFound)
{
%sphereLocation = %spawnSphere.getTransform();
warn("WARNING: Could not spawn player after" SPC %attemptsToSpawn
SPC "tries in spawnsphere" SPC %spawnSphere SPC "without overlapping another player. Attempting spawn in center of sphere.");
}
return %sphereLocation;
}
// ----------------------------------------------------------------------------
// Observer
// ----------------------------------------------------------------------------
function DeathMatchGame::spawnObserver(%game, %client)
{
//echo (%game @"\c4 -> "@ %game.class @" -> GameCore::spawnObserver");
// Position the camera on one of our observer spawn points
%client.camera.setTransform(%game.pickObserverSpawnPoint());
// Set control to the camera
%client.setControlObject(%client.camera);
}
function DeathMatchGame::pickObserverSpawnPoint(%game)
{
//echo (%game @"\c4 -> "@ %game.class @" -> GameCore::pickObserverSpawnPoint");
%groupName = "MissionGroup/ObserverSpawnPoints";
%group = nameToID(%groupName);
if (%group != -1)
{
%count = %group.getCount();
if (%count != 0)
{
%index = getRandom(%count-1);
%spawn = %group.getObject(%index);
return %spawn.getTransform();
}
else
error("No spawn points found in "@ %groupName);
}
else
error("Missing spawn points group "@ %groupName);
// Could be no spawn points, in which case we'll stick the
// player at the center of the world.
return "0 0 300 1 0 0 0";
}
// ----------------------------------------------------------------------------
// Server
// ----------------------------------------------------------------------------
// Called by GameCore::cycleGame() when we need to destroy the server
// because we're done playing. We don't want to call destroyServer()
// directly so we can first check that we're about to destroy the
// correct server session.
function DeathMatchGame::DestroyServer(%serverSession)
{
if (%serverSession == $Server::Session)
{
if (isObject(LocalClientConnection))
{
// We're a local connection so issue a disconnect. The server will
// be automatically destroyed for us.
disconnect();
}
else
{
// We're a stand alone server
destroyServer();
}
}
}
// ----------------------------------------------------------------------------
// weapon HUD
// ----------------------------------------------------------------------------
function GameConnection::setAmmoAmountHud(%client, %amount, %amountInClips )
{
commandToClient(%client, 'SetAmmoAmountHud', %amount, %amountInClips);
}
function GameConnection::RefreshWeaponHud(%client, %amount, %preview, %ret, %zoomRet, %amountInClips)
{
commandToClient(%client, 'RefreshWeaponHud', %amount, %preview, %ret, %zoomRet, %amountInClips);
}