Torque3D/Templates/Modules/FPSGameplay/scripts/server/deathMatchGame.cs
Areloch 28c4bad74d Catches FPSGameplay module up to new gamemode and module paradigm
Adds additional args for callGamemodeFunction
Adds default Observer camera datablock
2019-10-04 21:00:58 -05:00

710 lines
No EOL
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::onCreateGame()
{
// Note: The Game object will be cleaned up by MissionCleanup. Therefore its lifetime is
// limited to that of the mission.
new ScriptObject(DeathMatchGame){};
return DeathMatchGame;
}
//-----------------------------------------------------------------------------
// The server has started up so do some game start up
//-----------------------------------------------------------------------------
function DeathMatchGame::onMissionStart(%this)
{
//set up the game and game variables
%this.initGameVars();
%this.Duration = 30 * 60;
%this.EndGameScore = 20;
%this.EndGamePause = 10;
%this.AllowCycling = false; // Is mission cycling allowed?
//echo (%game @"\c4 -> "@ %game.class @" -> GameCore::onStartGame");
if (%this.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 (%this.Duration)
%this.Schedule = schedule(%this.Duration * 1000, "onGameDurationEnd");
%this.Running = true;
}
function DeathMatchGame::onMissionEnded(%this)
{
if (!%this.Running)
{
error("endGame: No game running!");
return;
}
// Stop any game timers
cancel(%this.Schedule);
for (%clientIndex = 0; %clientIndex < ClientGroup.getCount(); %clientIndex++)
{
%cl = ClientGroup.getObject(%clientIndex);
commandToClient(%cl, 'GameEnd', %this.EndGamePause);
}
%this.Running = false;
%this.Cycling = false;
}
function DeathMatchGame::onMissionReset(%this)
{
// Called by resetMission(), after all the temporary mission objects
// have been deleted.
%this.initGameVars();
%this.Duration = %this.duration;
%this.EndGameScore = %this.endgameScore;
%this.EndGamePause = %this.endgamePause;
}
function DeathMatchGame::initGameVars(%this)
{
//-----------------------------------------------------------------------------
// 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 %this.defaultPlayerClass and %this.defaultPlayerDataBlock as empty strings ("")
// to spawn a the %this.defaultCameraClass as the control object.
%this.defaultPlayerClass = "Player";
%this.defaultPlayerDataBlock = "DefaultPlayerData";
%this.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
//-----------------------------------------------------------------------------
%this.defaultCameraClass = "Camera";
%this.defaultCameraDataBlock = "Observer";
%this.defaultCameraSpawnGroups = "CameraSpawnPoints PlayerSpawnPoints PlayerDropPoints";
// Set the gameplay parameters
%this.Duration = 30 * 60;
%this.EndGameScore = 20;
%this.EndGamePause = 10;
%this.AllowCycling = false; // Is mission cycling allowed?
}
function DeathMatchGame::onGameDurationEnd(%this)
{
// This "redirect" is here so that we can abort the game cycle if
// the %this.Duration variable has been cleared, without having
// to have a function to cancel the schedule.
if (%this.Duration && !(EditorIsActive() && GuiEditorIsActive()))
%this.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 - %this.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(%this.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(%client.camera))
%client.camera.delete();
}
function DeathMatchGame::onInitialControlSet(%this)
{
}
//-----------------------------------------------------------------------------
// 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(%this.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(%this, %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);
}
}
function DeathMatchGame::onDeath(%this, %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))
{
%this.incDeaths( %client, 1, true );
%this.incScore( %client, -1, false );
}
else
{
%this.incDeaths( %client, 1, false );
%this.incScore( %sourceClient, 1, true );
%this.incKills( %sourceClient, 1, false );
// If the game may be ended by a client getting a particular score, check that now.
if ( %this.EndGameScore > 0 && %sourceClient.kills >= %this.EndGameScore )
%this.cycleGame();
}
}
// ----------------------------------------------------------------------------
// Spawning
// ----------------------------------------------------------------------------
function DeathMatchGame::spawnPlayer(%this, %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 = %this.DefaultPlayerClass;
%spawnDataBlock = %this.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 = %this.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(%this.DefaultPlayerClass, %this.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(%this, %client)
{
//echo (%game @"\c4 -> "@ %game.class @" -> GameCore::spawnObserver");
// Position the camera on one of our observer spawn points
%client.camera.setTransform(%this.pickObserverSpawnPoint());
// Set control to the camera
%client.setControlObject(%client.camera);
}
function DeathMatchGame::pickObserverSpawnPoint(%this)
{
//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";
}
// ----------------------------------------------------------------------------
// Scoring
// ----------------------------------------------------------------------------
function DeathMatchGame::incKills(%this, %client, %kill, %dontMessageAll)
{
%client.kills += %kill;
if( !%dontMessageAll )
messageAll('MsgClientScoreChanged', "", %client.score, %client.kills, %client.deaths, %client);
}
function DeathMatchGame::incDeaths(%this, %client, %death, %dontMessageAll)
{
%client.deaths += %death;
if( !%dontMessageAll )
messageAll('MsgClientScoreChanged', "", %client.score, %client.kills, %client.deaths, %client);
}
function DeathMatchGame::incScore(%this, %client, %score, %dontMessageAll)
{
%client.score += %score;
if( !%dontMessageAll )
messageAll('MsgClientScoreChanged', "", %client.score, %client.kills, %client.deaths, %client);
}
function DeathMatchGame::getScore(%this, %client) { return %client.score; }
function DeathMatchGame::getKills(%this, %client) { return %client.kills; }
function DeathMatchGame::getDeaths(%this, %client) { return %client.deaths; }
function DeathMatchGame::getTeamScore(%this, %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;
}
// -----------------------------------------------------------------------------
// Messages
// -----------------------------------------------------------------------------
// 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 );
}
// ----------------------------------------------------------------------------
// 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);
}