diff --git a/Engine/source/module/moduleDefinition.cpp b/Engine/source/module/moduleDefinition.cpp index a280e48f1..4e624c1e4 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 0fb12f7b0..0025582eb 100644 --- a/Engine/source/module/moduleDefinition.h +++ b/Engine/source/module/moduleDefinition.h @@ -115,6 +115,7 @@ private: 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 @@ public: 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 @@ protected: } 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 b0f67548b..71a7186a8 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 1bc3b9d6f..fa0e3a12f 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 a87a250cf..8c68494f8 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); @@ -81,19 +81,194 @@ 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"; + + %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 GameConnectionListener::onSetSpawnObjectTypeComplete( %this, %client ) +{ + %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. + error("Attempting to create a player for a client that already has one!"); + } + + // Spawn with the engine's Sim::spawnObject() function + %client.player = spawnObject(%client.spawnClass, %client.spawnDataBlock); + + 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(%client.player); + + // Store the client object on the player object for + // future reference + %client.player.client = %client; + + %client.setSpawnPoint(); + + // Give the client control of the camera if in the editor + if( $startWorldEditor ) + { + %control = %client.camera; + %control.mode = "Fly"; + EditorGui.syncCameraGui(); + } + else + %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")) + %client.setControlObject(%control); +} + +function GameConnectionListener::onSetSpawnObjectTypeFailed( %this, %client, %canContinueOnFail ) +{ + errorf("Failed to properly set Spawn Object Type for client: " @ %client); +} + +function GameConnection::setSpawnPoint( %this ) +{ + //baseline spawn point config rules with extention points + %this.playerSpawnGroups = "PlayerSpawnPoints PlayerDropPoints"; + %this.spawnPoint = ""; + %this.spawnLocation = "0 0 0"; + + %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 GameConnectionListener::onSetSpawnPointComplete( %this, %client ) +{ + %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 + // 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 " @ %client.spawnClass @ + " and datablock " @ %client.spawnDataBlock @ ".\n\nStarting as an Observer instead.", + ""); + } + else + { + MessageBoxOK("Spawn Failed", + "Unable to create a player with class " @ %client.spawnClass @ + ".\n\nStarting as an Observer instead.", + ""); + } + } + %client.onPostSpawn(); +} + +function GameConnectionListener::onSetSpawnPointFailed( %this, %client, %canContinueOnFail ) +{ + errorf("Failed to properly set Spawn Object Type for client: " @ %client); +} + +function GameConnection::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 +} + //----------------------------------------------------------------------------- // 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 +287,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 +307,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 +323,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 555448602..002891ffa 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/core/utility/scripts/helperFunctions.tscript b/Templates/BaseGame/game/core/utility/scripts/helperFunctions.tscript index 05438e600..e81d5cb76 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 diff --git a/Templates/BaseGame/game/data/ExampleModule/scripts/shared/ExampleGameMode.tscript b/Templates/BaseGame/game/data/ExampleModule/scripts/shared/ExampleGameMode.tscript index 9c6d7942e..0589d01e2 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