From c5ae9af0ae96855c6de3ac7765da0e92c32eae00 Mon Sep 17 00:00:00 2001 From: JeffR Date: Sat, 21 Dec 2024 02:11:35 -0600 Subject: [PATCH 1/2] Core now spawns a ControlObject directly, with callbacks allowing overriding of values to control what spawns (and what occurs afterwards) to tie several tracking variables to a given client connection. By default this list of variables would be: %this.spawnClass = "Camera"; %this.spawnDBType = "CameraData"; %this.spawnDataBlock = "Observer"; %this.playerSpawnGroups = "PlayerSpawnPoints PlayerDropPoints"; %this.spawnPoint = ""; %this.spawnLocation = "0 0 0"; Also adds several callbacks so that these values can be overridden by modules and gamemodes, kicked off from the %client.spawnControlObject(); command : callOnModules("setSpawnObjectType", "Game", %this); callGamemodeFunction("setSpawnObjectType", %this); callOnModules("setSpawnPoint", "Game", %this); callGamemodeFunction("setSpawnPoint", %this); callOnModules("onPostSpawn", "Game", %this); callGamemodeFunction("onPostSpawn", %this); This is to ensure that a game mode can supersede modules, but even modules can dictate spawn behaviors for minimalist implementations and drop-in interop support --- .../scripts/server/connectionToClient.tscript | 120 ++++++++++++++++-- .../scripts/server/levelDownload.tscript | 2 + .../scripts/shared/ExampleGameMode.tscript | 96 -------------- 3 files changed, 110 insertions(+), 108 deletions(-) diff --git a/Templates/BaseGame/game/core/clientServer/scripts/server/connectionToClient.tscript b/Templates/BaseGame/game/core/clientServer/scripts/server/connectionToClient.tscript index a87a250cf6..10d47f8c15 100644 --- a/Templates/BaseGame/game/core/clientServer/scripts/server/connectionToClient.tscript +++ b/Templates/BaseGame/game/core/clientServer/scripts/server/connectionToClient.tscript @@ -27,7 +27,7 @@ // anything else will be sent back as an error to the client. // All the connect args are passed also to onConnectRequest // -function GameConnection::onConnectRequest( %client, %netAddress, %name ) +function GameConnection::onConnectRequest( %this, %netAddress, %name ) { echo("Connect request from: " @ %netAddress); if($Server::PlayerCount >= $pref::Server::MaxPlayers) @@ -47,11 +47,11 @@ function GameConnection::onConnect( %this, %clientData ) sendLoadInfoToClient(%this); // Simulated client lag for testing... - // %client.setSimulatedNetParams(0.1, 30); + // %this.setSimulatedNetParams(0.1, 30); // Get the client's unique id: - // %authInfo = %client.getAuthInfo(); - // %client.guid = getField(%authInfo, 3); + // %authInfo = %this.getAuthInfo(); + // %this.guid = getField(%authInfo, 3); %this.guid = 0; addToServerGuidList(%this.guid); @@ -86,14 +86,110 @@ function GameConnection::onConnect( %this, %clientData ) $Server::PlayerCount++; } +function GameConnection::spawnControlObject( %this ) +{ + //baseline controlObject spawn type with extention points + %this.spawnClass = "Camera"; + %this.spawnDBType = "CameraData"; + %this.spawnDataBlock = "Observer"; + callOnModules("setSpawnObjectType", "Game", %this); + callGamemodeFunction("setSpawnObjectType", %this); +} + +function GameConnection::onSetSpawnObjectTypeComplete( %this ) +{ + if (isObject(%this.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!"); + } + + // Spawn with the engine's Sim::spawnObject() function + %this.player = spawnObject(%this.spawnClass, %this.spawnDataBlock); + + if (!%this.player.isMemberOfClass(%this.spawnClass)) + warn("Trying to spawn a class that does not derive from "@ %this.spawnClass); + + // 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(%this.player); + + // Store the client object on the player object for + // future reference + %this.player.client = %this; + + %this.setSpawnPoint(); + + // Give the client control of the camera if in the editor + if( $startWorldEditor ) + { + %control = %this.camera; + %control.mode = "Fly"; + EditorGui.syncCameraGui(); + } + else + %control = %this.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")) + %this.setControlObject(%control); +} + +function GameConnection::setSpawnPoint( %this ) +{ + //baseline spawn point config rules with extention points + %this.playerSpawnGroups = "PlayerSpawnPoints PlayerDropPoints"; + %this.spawnPoint = ""; + %this.spawnLocation = "0 0 0"; + callOnModules("setSpawnPoint", "Game", %this); + callGamemodeFunction("setSpawnPoint", %this); +} + +function GameConnection::onSetSpawnPointComplete( %this ) +{ + if (isObject(%this.player)) + %this.player.setTransform(%this.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("%this.spawnDataBlock")) + { + MessageBoxOK("Spawn Failed", + "Unable to create a player with class " @ %this.spawnClass @ + " and datablock " @ %this.spawnDataBlock @ ".\n\nStarting as an Observer instead.", + ""); + } + else + { + MessageBoxOK("Spawn Failed", + "Unable to create a player with class " @ %this.spawnClass @ + ".\n\nStarting as an Observer instead.", + ""); + } + } + %this.onPostSpawn(); +} + +function GameConnection::onPostSpawn( %this ) +{ + //post controlObject create extention points + callOnModules("onPostSpawn", "Game", %this); + callGamemodeFunction("onPostSpawn", %this); +} + //----------------------------------------------------------------------------- // A player's name could be obtained from the auth server, but for // now we use the one passed from the client. // %realName = getField( %authInfo, 0 ); // -function GameConnection::setPlayerName(%client,%name) +function GameConnection::setPlayerName(%this,%name) { - %client.sendGuid = 0; + %this.sendGuid = 0; // Minimum length requirements %name = trim( strToPlayerName( %name ) ); @@ -112,8 +208,8 @@ function GameConnection::setPlayerName(%client,%name) } // Tag the name with the "smurf" color: - %client.nameBase = %name; - %client.playerName = addTaggedString("\cp\c8" @ %name @ "\co"); + %this.nameBase = %name; + %this.playerName = addTaggedString("\cp\c8" @ %name @ "\co"); } function isNameUnique(%name) @@ -132,7 +228,7 @@ function isNameUnique(%name) //----------------------------------------------------------------------------- // This function is called when a client drops for any reason // -function GameConnection::onDrop(%client, %reason) +function GameConnection::onDrop(%this, %reason) { %entityIds = parseMissionGroupForIds("Entity", ""); %entityCount = getWordCount(%entityIds); @@ -148,15 +244,15 @@ function GameConnection::onDrop(%client, %reason) %entityIds = %entityIds SPC %child.getID(); } - %entity.notify("onClientDisconnect", %client); + %entity.notify("onClientDisconnect", %this); } if($missionRunning) { - %hasGameMode = callGamemodeFunction("onClientLeaveGame", %client); + %hasGameMode = callGamemodeFunction("onClientLeaveGame", %this); } - removeFromServerGuidList( %client.guid ); + removeFromServerGuidList( %this.guid ); $Server::PlayerCount--; } diff --git a/Templates/BaseGame/game/core/clientServer/scripts/server/levelDownload.tscript b/Templates/BaseGame/game/core/clientServer/scripts/server/levelDownload.tscript index 5554486021..002891ffae 100644 --- a/Templates/BaseGame/game/core/clientServer/scripts/server/levelDownload.tscript +++ b/Templates/BaseGame/game/core/clientServer/scripts/server/levelDownload.tscript @@ -178,6 +178,8 @@ function serverCmdMissionStartPhase3Ack(%client, %seq) %client.currentPhase = 3; %hasGameMode = callGamemodeFunction("onClientEnterGame", %client); + + %client.spawnControlObject(); //if that also failed, just spawn a camera if(%hasGameMode == 0) diff --git a/Templates/BaseGame/game/data/ExampleModule/scripts/shared/ExampleGameMode.tscript b/Templates/BaseGame/game/data/ExampleModule/scripts/shared/ExampleGameMode.tscript index 9c6d7942ee..0589d01e2e 100644 --- a/Templates/BaseGame/game/data/ExampleModule/scripts/shared/ExampleGameMode.tscript +++ b/Templates/BaseGame/game/data/ExampleModule/scripts/shared/ExampleGameMode.tscript @@ -51,16 +51,6 @@ function ExampleGameMode::onMissionReset(%this) function ExampleGameMode::initGameVars(%this) { - //----------------------------------------------------------------------------- - // 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"; } function ExampleGameMode::onGameDurationEnd(%this) @@ -82,15 +72,6 @@ function ExampleGameMode::onClientEnterGame(%this, %client) //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 = %this.pickCameraSpawnPoint(%this.DefaultCameraSpawnGroups); - // Spawn a camera for this client using the found %spawnPoint - %this.spawnCamera(%client, %cameraSpawnPoint); - // Inform the client of all the other clients %count = ClientGroup.getCount(); for (%cl = 0; %cl < %count; %cl++) @@ -175,81 +156,4 @@ function ExampleGameMode::onSubsceneUnloaded(%this) { echo("==================================="); echo("ExampleGameMode - Subscene is unloaded"); -} - -function ExampleGameMode::spawnCamera(%this, %client, %spawnPoint) -{ - // Set the control object to the default camera - if (!isObject(%client.camera)) - { - if (%this.defaultCameraClass !$= "") - %client.camera = spawnObject(%this.defaultCameraClass, %this.defaultCameraDataBlock); - } - - // If we have a camera then set up some properties - if (isObject(%client.camera)) - { - MissionCleanup.add( %client.camera ); - %client.camera.scopeToClient(%client); - - %client.setControlObject(%client.camera); - - if(!isObject(%spawnPoint)) - %spawnPoint = %this.pickCameraSpawnPoint(%this.defaultCameraSpawnGroups); - - if (isObject(%spawnPoint)) - { - // Attempt to treat %spawnPoint as an object - if (getWordCount(%spawnPoint) == 1 && isObject(%spawnPoint)) - { - %client.camera.setTransform(%spawnPoint.getTransform()); - } - else - { - // Treat %spawnPoint as an AxisAngle transform - %client.camera.setTransform(%spawnPoint); - } - } - } -} - -//----------------------------------------------------------------------------- -// pickCameraSpawnPoint() is responsible for finding a valid spawn point for a -// camera. -//----------------------------------------------------------------------------- -function ExampleGameMode::pickCameraSpawnPoint(%this, %spawnGroups) -{ - // Walk through the groups until we find a valid object - for (%i = 0; %i < getWordCount(%spawnGroups); %i++) - { - %group = getWord(%spawnGroups, %i); - - %count = getWordCount(%group); - - if (isObject(%group)) - %spawnPoint = %group.getRandom(); - - if (isObject(%spawnPoint)) - return %spawnPoint; - } - - // Didn't find a spawn point by looking for the groups - // so let's return the "default" SpawnSphere - // First create it if it doesn't already exist - if (!isObject(DefaultCameraSpawnSphere)) - { - %spawn = new SpawnSphere(DefaultCameraSpawnSphere) - { - dataBlock = "SpawnSphereMarker"; - spawnClass = $Game::DefaultCameraClass; - spawnDatablock = $Game::DefaultCameraDataBlock; - }; - - // Add it to the MissionCleanup group so that it - // doesn't get saved to the Mission (and gets cleaned - // up of course) - MissionCleanup.add(%spawn); - } - - return DefaultCameraSpawnSphere; } \ No newline at end of file From 46f6f6a9dab2f957bc1f767b7fb9c5fd26bee2fb Mon Sep 17 00:00:00 2001 From: JeffR Date: Fri, 3 Jan 2025 00:37:25 -0600 Subject: [PATCH 2/2] Added field to ModuleDefinition for priority, which can be used to process/sort them in priority order Added logic to ModuleManager's findModules method to allow priority sorting as well as pre-filtering by a given module group Adjusts the %isFine argument for the onMapLoadFailed callback events to %canContinueOnFail for a bit more clarity on what the arg conveys Shifts the setSpawnObjectType, setSpawnPoint and onPostSpawn call stack to utilize an event manager to allow the setup process for spawners and gamemode prepwork to run in it's own time, if needbe. Such as if a gamemode has to generate a map and there's no guarantees on when it'll b e done for one client vs another Added getModulesAndGameModesList, callOnObjectList and getNumCanCallOnObjectList utility functions --- Engine/source/module/moduleDefinition.cpp | 5 +- Engine/source/module/moduleDefinition.h | 6 + .../module/moduleManager_ScriptBinding.h | 19 ++- .../clientServer/Core_ClientServer.tscript | 8 +- .../scripts/server/connectionToClient.tscript | 129 ++++++++++++++---- .../utility/scripts/helperFunctions.tscript | 84 ++++++++++++ 6 files changed, 220 insertions(+), 31 deletions(-) diff --git a/Engine/source/module/moduleDefinition.cpp b/Engine/source/module/moduleDefinition.cpp index a280e48f14..4e624c1e49 100644 --- a/Engine/source/module/moduleDefinition.cpp +++ b/Engine/source/module/moduleDefinition.cpp @@ -68,7 +68,8 @@ mModuleId(StringTable->EmptyString()), mLoadCount( 0 ), mScopeSet( 0 ), mLocked( false ), - mpModuleManager( NULL ) + mpModuleManager( NULL ), + mPriority(0.0f) { // Set Vector Associations. VECTOR_SET_ASSOCIATION( mDependencies ); @@ -111,6 +112,8 @@ void ModuleDefinition::initPersistFields() /// Misc. addProtectedField( "Signature", TypeString, 0, &defaultProtectedNotSetFn, &getSignature, &defaultProtectedNotWriteFn, "A unique signature of the module definition based upon its Id, version and build. This is read-only and is available only after the module has been registered by a module manager." ); + addProtectedField( "Priority", TypeF32, 0, &setPriority, &defaultProtectedGetFn, &defaultProtectedNotWriteFn, "A numeric value indicating execution priority for certain callback commands. 0 has the highest priority and is then sorted from there ascending in value. This is read-only and is available only after the module has been registered by a module manager."); + } //----------------------------------------------------------------------------- diff --git a/Engine/source/module/moduleDefinition.h b/Engine/source/module/moduleDefinition.h index 0fb12f7b00..0025582eb2 100644 --- a/Engine/source/module/moduleDefinition.h +++ b/Engine/source/module/moduleDefinition.h @@ -115,6 +115,7 @@ class ModuleDefinition : public SimSet SimObjectId mScopeSet; bool mLocked; ModuleManager* mpModuleManager; + F32 mPriority; private: inline bool checkUnlocked( void ) const { if ( mLocked ) { Con::warnf("Ignoring changes for locked module definition."); } return !mLocked; } @@ -195,6 +196,9 @@ class ModuleDefinition : public SimSet inline bool getModuleLocked( void ) const { return mLocked; } inline ModuleManager* getModuleManager( void ) const { return mpModuleManager; } + inline void setPriority(const F32 pPriority) { if (checkUnlocked()) { mPriority = pPriority; } } + inline F32 getPriority(void) const { return mPriority; } + using Parent::save; bool save( void ); @@ -332,6 +336,8 @@ class ModuleDefinition : public SimSet } static bool writeDependencies( void* obj, StringTableEntry pFieldName ) { return static_cast(obj)->getDependencies().size() > 0; } static const char* getSignature(void* obj, const char* data) { return static_cast(obj)->getSignature(); } + + static bool setPriority(void* obj, const char* index, const char* data) { static_cast(obj)->setPriority((F32)dAtof(data)); return false; } }; #endif // _MODULE_DEFINITION_H diff --git a/Engine/source/module/moduleManager_ScriptBinding.h b/Engine/source/module/moduleManager_ScriptBinding.h index b0f67548bb..71a7186a83 100644 --- a/Engine/source/module/moduleManager_ScriptBinding.h +++ b/Engine/source/module/moduleManager_ScriptBinding.h @@ -150,8 +150,14 @@ DefineEngineMethod(ModuleManager, findModuleByFilePath, String, (const char* fil } //----------------------------------------------------------------------------- +static S32 QSORT_CALLBACK _findModulesSortByPriority(ModuleDefinition* const* a, ModuleDefinition* const* b) +{ + F32 diff = (*a)->getPriority() - (*b)->getPriority(); + return diff > 0 ? 1 : diff < 0 ? -1 : 0; +} -DefineEngineMethod(ModuleManager, findModules, String, (bool loadedOnly), (true), + +DefineEngineMethod(ModuleManager, findModules, String, (bool loadedOnly, bool sortByPriority, const char* moduleGroup), (true, false, ""), "Find all the modules registered with the specified loaded state.\n" "@param loadedOnly Whether to return only modules that are loaded or not.\n" "@return A list of space - separated module definition object Ids.\n") @@ -174,12 +180,23 @@ DefineEngineMethod(ModuleManager, findModules, String, (bool loadedOnly), (true) char* pReturnBuffer = Con::getReturnBuffer( bufferSize ); char* pBufferWrite = pReturnBuffer; + if (sortByPriority) + moduleDefinitions.sort(_findModulesSortByPriority); + + StringTableEntry moduleGroupStr = StringTable->insert(moduleGroup); + // Iterate module definitions. for ( ModuleManager::typeConstModuleDefinitionVector::const_iterator moduleDefinitionItr = moduleDefinitions.begin(); moduleDefinitionItr != moduleDefinitions.end(); ++moduleDefinitionItr ) { // Fetch module definition. const ModuleDefinition* pModuleDefinition = *moduleDefinitionItr; + if(moduleGroupStr != StringTable->EmptyString()) + { + if (pModuleDefinition->getModuleGroup() != moduleGroupStr) + continue; + } + // Format module definition. const U32 offset = dSprintf( pBufferWrite, bufferSize, "%d ", pModuleDefinition->getId() ); pBufferWrite += offset; diff --git a/Templates/BaseGame/game/core/clientServer/Core_ClientServer.tscript b/Templates/BaseGame/game/core/clientServer/Core_ClientServer.tscript index 1bc3b9d6f2..fa0e3a12ff 100644 --- a/Templates/BaseGame/game/core/clientServer/Core_ClientServer.tscript +++ b/Templates/BaseGame/game/core/clientServer/Core_ClientServer.tscript @@ -27,10 +27,10 @@ function Core_ClientServer::finishMapLoad(%this) Core_ClientServer.GetEventManager().postEvent( "mapLoadComplete" ); } -function Core_ClientServer::FailMapLoad(%this, %moduleName, %isFine) +function Core_ClientServer::FailMapLoad(%this, %moduleName, %canContinueOnFail) { Core_ClientServer.failedModuleName = %moduleName; - Core_ClientServer.GetEventManager().postEvent( "mapLoadFail", %isFine ); + Core_ClientServer.GetEventManager().postEvent( "mapLoadFail", %canContinueOnFail ); } function Core_ClientServerListener::onMapLoadComplete(%this) @@ -50,9 +50,9 @@ function Core_ClientServerListener::onMapLoadComplete(%this) } } -function Core_ClientServerListener::onmapLoadFail(%this, %isFine) +function Core_ClientServerListener::onMapLoadFail(%this, %canContinueOnFail) { - if (%isFine) + if (%canContinueOnFail) { %this.onMapLoadComplete(); return; diff --git a/Templates/BaseGame/game/core/clientServer/scripts/server/connectionToClient.tscript b/Templates/BaseGame/game/core/clientServer/scripts/server/connectionToClient.tscript index 10d47f8c15..8c68494f87 100644 --- a/Templates/BaseGame/game/core/clientServer/scripts/server/connectionToClient.tscript +++ b/Templates/BaseGame/game/core/clientServer/scripts/server/connectionToClient.tscript @@ -81,24 +81,64 @@ function GameConnection::onConnect( %this, %clientData ) %this.connectData = %clientData; + //Signal and listener logic for the spawn config/processing here + %this.GetEventManager().registerEvent("setSpawnObjectTypeComplete"); + %this.GetEventManager().registerEvent("setSpawnObjectTypeFailed"); + %this.GetEventManager().registerEvent("setSpawnPointComplete"); + %this.GetEventManager().registerEvent("setSpawnPointFailed"); + %this.GetEventManager().registerEvent("postSpawnComplete"); + + %this.listener = new ScriptMsgListener() { + class = GameConnectionListener; + }; + %this.GetEventManager().subscribe( %this.listener, "setSpawnObjectTypeComplete" ); + %this.GetEventManager().subscribe( %this.listener, "setSpawnObjectTypeFailed" ); + %this.GetEventManager().subscribe( %this.listener, "setSpawnPointComplete" ); + %this.GetEventManager().subscribe( %this.listener, "setSpawnPointFailed" ); + %this.GetEventManager().subscribe( %this.listener, "postSpawnComplete" ); + callGamemodeFunction("onClientConnect", %this); $Server::PlayerCount++; } +function GameConnection::GetEventManager(%this) +{ + if( !isObject( %this.eventManager ) ) + %this.eventManager = new EventManager() { + queue = "GameConnectionEventManager"; + }; + + return %this.eventManager; +} + function GameConnection::spawnControlObject( %this ) { //baseline controlObject spawn type with extention points %this.spawnClass = "Camera"; %this.spawnDBType = "CameraData"; %this.spawnDataBlock = "Observer"; - callOnModules("setSpawnObjectType", "Game", %this); - callGamemodeFunction("setSpawnObjectType", %this); + + %this.numModsNeedingLoaded = 0; + %this.moduleLoadedDone = 0; + %modulesIDList = getModulesAndGameModesList(true, "Game"); + + %this.numModsNeedingLoaded = getNumCanCallOnObjectList("setSpawnObjectType", %modulesIDList); + + if (%this.numModsNeedingLoaded) + callOnObjectList("setSpawnObjectType", %modulesIdList, %this); + else + %this.GetEventManager().onSetSpawnObjectTypeComplete(); //just jump to progress } -function GameConnection::onSetSpawnObjectTypeComplete( %this ) +function GameConnectionListener::onSetSpawnObjectTypeComplete( %this, %client ) { - if (isObject(%this.player)) + %client.moduleLoadedDone++; + + if (%client.moduleLoadedDone < %client.numModsNeedingLoaded) + return; //continue to wait + + if (isObject(%client.player)) { // The client should not already have a player. Assigning // a new one could result in an uncontrolled player object. @@ -106,36 +146,41 @@ function GameConnection::onSetSpawnObjectTypeComplete( %this ) } // Spawn with the engine's Sim::spawnObject() function - %this.player = spawnObject(%this.spawnClass, %this.spawnDataBlock); + %client.player = spawnObject(%client.spawnClass, %client.spawnDataBlock); - if (!%this.player.isMemberOfClass(%this.spawnClass)) - warn("Trying to spawn a class that does not derive from "@ %this.spawnClass); + if (!%client.player.isMemberOfClass(%client.spawnClass)) + warn("Trying to spawn a class that does not derive from "@ %client.spawnClass); // 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(%this.player); + MissionCleanup.add(%client.player); // Store the client object on the player object for // future reference - %this.player.client = %this; + %client.player.client = %client; - %this.setSpawnPoint(); + %client.setSpawnPoint(); // Give the client control of the camera if in the editor if( $startWorldEditor ) { - %control = %this.camera; + %control = %client.camera; %control.mode = "Fly"; EditorGui.syncCameraGui(); } else - %control = %this.player; + %control = %client.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")) - %this.setControlObject(%control); + %client.setControlObject(%control); +} + +function GameConnectionListener::onSetSpawnObjectTypeFailed( %this, %client, %canContinueOnFail ) +{ + errorf("Failed to properly set Spawn Object Type for client: " @ %client); } function GameConnection::setSpawnPoint( %this ) @@ -144,14 +189,27 @@ function GameConnection::setSpawnPoint( %this ) %this.playerSpawnGroups = "PlayerSpawnPoints PlayerDropPoints"; %this.spawnPoint = ""; %this.spawnLocation = "0 0 0"; - callOnModules("setSpawnPoint", "Game", %this); - callGamemodeFunction("setSpawnPoint", %this); + + %this.numModsNeedingLoaded = 0; + %this.moduleLoadedDone = 0; + %modulesIDList = getModulesAndGameModesList(true, "Game"); + + %this.numModsNeedingLoaded = getNumCanCallOnObjectList("setSpawnPoint", %modulesIDList); + + if (%this.numModsNeedingLoaded) + callOnObjectList("setSpawnPoint", %modulesIdList, %this); + else + %this.GetEventManager().onSetSpawnPointComplete(); } -function GameConnection::onSetSpawnPointComplete( %this ) +function GameConnectionListener::onSetSpawnPointComplete( %this, %client ) { - if (isObject(%this.player)) - %this.player.setTransform(%this.spawnLocation); + %client.moduleLoadedDone++; + if (%client.moduleLoadedDone < %client.numModsNeedingLoaded) + return; //continue to wait + + if (isObject(%client.player)) + %client.player.setTransform(%client.spawnLocation); else { // If we weren't able to create the player object then warn the user @@ -160,26 +218,47 @@ function GameConnection::onSetSpawnPointComplete( %this ) if (isDefined("%this.spawnDataBlock")) { MessageBoxOK("Spawn Failed", - "Unable to create a player with class " @ %this.spawnClass @ - " and datablock " @ %this.spawnDataBlock @ ".\n\nStarting as an Observer instead.", + "Unable to create a player with class " @ %client.spawnClass @ + " and datablock " @ %client.spawnDataBlock @ ".\n\nStarting as an Observer instead.", ""); } else { MessageBoxOK("Spawn Failed", - "Unable to create a player with class " @ %this.spawnClass @ + "Unable to create a player with class " @ %client.spawnClass @ ".\n\nStarting as an Observer instead.", ""); } } - %this.onPostSpawn(); + %client.onPostSpawn(); +} + +function GameConnectionListener::onSetSpawnPointFailed( %this, %client, %canContinueOnFail ) +{ + errorf("Failed to properly set Spawn Object Type for client: " @ %client); } function GameConnection::onPostSpawn( %this ) { - //post controlObject create extention points - callOnModules("onPostSpawn", "Game", %this); - callGamemodeFunction("onPostSpawn", %this); + %this.numModsNeedingLoaded = 0; + %this.moduleLoadedDone = 0; + %modulesIDList = getModulesAndGameModesList(true, "Game"); + + %this.numModsNeedingLoaded = getNumCanCallOnObjectList("onPostSpawn", %modulesIDList); + + if (%this.numModsNeedingLoaded) + callOnObjectList("onPostSpawn", %modulesIdList, %this); + else + %this.GetEventManager().onPostSpawnComplete(); +} + +function GameConnectionListener::onPostSpawnComplete(%this, %client) +{ + %client.moduleLoadedDone++; + if (%client.moduleLoadedDone < %client.numModsNeedingLoaded) + return; //continue to wait + + //Continue on. Room for special handling here if needbe but not expressly required } //----------------------------------------------------------------------------- diff --git a/Templates/BaseGame/game/core/utility/scripts/helperFunctions.tscript b/Templates/BaseGame/game/core/utility/scripts/helperFunctions.tscript index 05438e6000..e81d5cb76d 100644 --- a/Templates/BaseGame/game/core/utility/scripts/helperFunctions.tscript +++ b/Templates/BaseGame/game/core/utility/scripts/helperFunctions.tscript @@ -698,4 +698,88 @@ function playSoundAsset(%soundAssetId,%pos) } AssetDatabase.releaseAsset(%soundAssetId); return %handle; +} + +//------------------------------------------------------------------------------ +function getModulesAndGameModesList(%usePriority, %group) +{ + %modulesList = ModuleDatabase.findModules(true, %usePriority, %group); + %modulesIDList = ""; + + for(%i=0; %i < getWordCount(%modulesList); %i++) + { + %module = getWord(%modulesList, %i); + + %modulesIDList = %modulesIDList SPC %module.ModuleId; + } + + %gamemodeList = getGameModesList(); + %gameModeCount = %gamemodeList.count(); + for(%i=0; %i < %gameModeCount; %i++) + { + %gameModeObj = %gamemodeList.getKey(%i); + %active = %gamemodeList.getValue(%i); + + if(!isObject(%gameModeObj) || !%active) + continue; + + %modulesIDList = %modulesIDList SPC %gameModeObj; + } + + %modulesIDList = strreplace(%modulesIDList, " "," "); + %modulesIDList = trim(%modulesIDList); + return %modulesIDList; +} + +function callOnObjectList(%functionName, %objectsList, %var0, %var1, %var2, %var3, %var4, %var5, %var6) +{ + //Get our modules so we can exec any specific client-side loading/handling + %echoList = "Called List:"; + for(%i=0; %i < getWordCount(%objectsList); %i++) + { + %obj = getWord(%objectsList, %i); + %objName = %obj.getName(); + + if(!isObject(%obj)) + { + //could be a moduleID we're trying to call against, so try a lookup + %module = ModuleDatabase.findModule(%obj); + if(isObject(%module)) + { + %obj = %module.scopeSet; + %objName = %module.ModuleId; + } + } + + %echoList = %echoList SPC %objName; + + // match this to i/o signature + if(isObject(%obj) && %obj.isMethod(%functionName)) + %obj.call(%functionName, %var0, %var1, %var2, %var3, %var4, %var5, %var6); + } + + if ($reportModuleOrder) + warn(%echoList); +} + +function getNumCanCallOnObjectList(%functionName, %objectsList) +{ + %numberWithFunction = 0; + for(%i=0; %i < getWordCount(%objectsList); %i++) + { + %obj = getWord(%objectsList, %i); + if(!isObject(%obj)) + { + //could be a moduleID we're trying to call against, so try a lookup + %module = ModuleDatabase.findModule(%obj); + if(isObject(%module)) + %obj = %module.scopeSet; + } + + // match this to i/o signature + if(isObject(%obj) && %obj.isMethod(%functionName)) + %numberWithFunction++; + } + + return %numberWithFunction; } \ No newline at end of file