//======================================================================== // BaseGameLogic.cpp : defines game logic class // // Part of the GameCode4 Application // // GameCode4 is the sample application that encapsulates much of the source code // discussed in "Game Coding Complete - 4th Edition" by Mike McShaffry and David // "Rez" Graham, published by Charles River Media. // ISBN-10: 1133776574 | ISBN-13: 978-1133776574 // // If this source code has found it's way to you, and you think it has helped you // in any way, do the authors a favor and buy a new copy of the book - there are // detailed explanations in it that compliment this code well. Buy a copy at Amazon.com // by clicking here: // http://www.amazon.com/gp/product/1133776574/ref=olp_product_details?ie=UTF8&me=&seller= // // There's a companion web site at http://www.mcshaffry.com/GameCode/ // // The source code is managed and maintained through Google Code: // http://code.google.com/p/gamecode4/ // // (c) Copyright 2012 Michael L. McShaffry and David Graham // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser GPL v3 // as published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See // http://www.gnu.org/licenses/lgpl-3.0.txt for more details. // // You should have received a copy of the GNU Lesser GPL v3 // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // //======================================================================== #include "../Unix/GameCodeStd.h" #include "../EventManager/Events.h" // only for EvtData_Game_State #include "../ResourceCache/XmlResource.h" #include "../UserInterface/HumanView.h" // [rez] not ideal, but the loading sequence needs to know if this is a human view #include "BaseGameLogic.h" //======================================================================== // // LevelManager implementation // //======================================================================== bool LevelManager::Initialize(std::vector &levels) { std::vector::iterator i = levels.begin(); while (i != levels.end()) { m_Levels.push_back(*i); ++i; } return true; } //======================================================================== // // BaseGameLogic implementation // //======================================================================== BaseGameLogic::BaseGameLogic() { m_Lifetime = 0; m_State = BGS_Initializing; m_bProxy = false; m_ExpectedPlayers = 0; m_ExpectedRemotePlayers = 0; m_ExpectedAI = 0; m_HumanPlayersAttached = 0; m_AIPlayersAttached = 0; m_HumanGamesLoaded = 0; m_pLevelManager = GCC_NEW LevelManager; // GCC_ASSERT(m_pProcessManager && m_pLevelManager); // TODO(munnellg) - book passes levels directly, but my compiler doesn't like that. Find out why std::vector levels = g_pApp->m_ResCache->Match("world/*.xml"); m_pLevelManager->Initialize(levels); } BaseGameLogic::~BaseGameLogic() { // MrMike: 12-Apr-2009 // Added this to explicitly remove views from the game logic list. while (!m_gameViews.empty()) m_gameViews.pop_front(); SAFE_DELETE(m_pLevelManager); // SAFE_DELETE(m_pProcessManager); SAFE_DELETE(m_pActorFactory); // destroy all actors for (auto it = m_actors.begin(); it != m_actors.end(); ++it) it->second->Destroy(); m_actors.clear(); // IEventManager::Get()->VRemoveListener(MakeDelegate(this, &BaseGameLogic::RequestDestroyActorDelegate), EvtData_Request_Destroy_Actor::sk_EventType); } bool BaseGameLogic::Init(void) { m_pActorFactory = VCreateActorFactory(); // m_pPathingGraph.reset(CreatePathingGraph()); // IEventManager::Get()->VAddListener(MakeDelegate(this, &BaseGameLogic::RequestDestroyActorDelegate), EvtData_Request_Destroy_Actor::sk_EventType); return true; } bool BaseGameLogic::VLoadGame(const char* levelResource) { // Grab the root XML node TiXmlElement* pRoot = XmlResourceLoader::LoadAndReturnRootXmlElement(levelResource); if (!pRoot) { GCC_ERROR("Failed to find level resource file: " + std::string(levelResource)); return false; } // pre and post load scripts const char* preLoadScript = NULL; const char* postLoadScript = NULL; // parse the pre & post script attributes TiXmlElement* pScriptElement = pRoot->FirstChildElement("Script"); if (pScriptElement) { preLoadScript = pScriptElement->Attribute("preLoad"); postLoadScript = pScriptElement->Attribute("postLoad"); } // load the pre-load script if there is one if (preLoadScript) { Resource resource(preLoadScript); std::shared_ptr pResourceHandle = g_pApp->m_ResCache->GetHandle(&resource); } // load all initial actors TiXmlElement* pActorsNode = pRoot->FirstChildElement("StaticActors"); if (pActorsNode) { for (TiXmlElement* pNode = pActorsNode->FirstChildElement(); pNode; pNode = pNode->NextSiblingElement()) { const char* actorResource = pNode->Attribute("resource"); StrongActorPtr pActor = VCreateActor(actorResource, pNode); if (pActor) { // fire an event letting everyone else know that we created a new actor std::shared_ptr pNewActorEvent(GCC_NEW EvtData_New_Actor(pActor->GetId())); IEventManager::Get()->VQueueEvent(pNewActorEvent); } } } // initialize all human views for (auto it = m_gameViews.begin(); it != m_gameViews.end(); ++it) { std::shared_ptr pView = *it; if (pView->VGetType() == GameView_Human) { std::shared_ptr pHumanView = static_pointer_cast(pView); pHumanView->LoadGame(pRoot); } } // register script events from the engine // [mrmike] this was moved to the constructor post-press, since this function can be called when new levels are loaded by the game or editor // RegisterEngineScriptEvents(); // call the delegate load function if (!VLoadGameDelegate(pRoot)) return false; // no error message here because it's assumed VLoadGameDelegate() kicked out the error // load the post-load script if there is one if (postLoadScript) { Resource resource(postLoadScript); std::shared_ptr pResourceHandle = g_pApp->m_ResCache->GetHandle(&resource); // this actually loads the XML file from the zip file } // trigger the Environment Loaded Game event - only then can player actors and AI be spawned! if (m_bProxy) { std::shared_ptr pNewGameEvent(GCC_NEW EvtData_Remote_Environment_Loaded); IEventManager::Get()->VTriggerEvent(pNewGameEvent); } else { std::shared_ptr pNewGameEvent(GCC_NEW EvtData_Environment_Loaded); IEventManager::Get()->VTriggerEvent(pNewGameEvent); } return true; } void BaseGameLogic::VSetProxy() { m_bProxy = true; // IEventManager::Get()->VAddListener(MakeDelegate(this, &BaseGameLogic::RequestNewActorDelegate), EvtData_Request_New_Actor::sk_EventType); // m_pPhysics.reset(CreateNullPhysics()); } StrongActorPtr BaseGameLogic::VCreateActor( const std::string &actorResource, TiXmlElement* overrides, std::optional initialTransform, const ActorId serversActorId ) { GCC_ASSERT(m_pActorFactory); if (!m_bProxy && serversActorId != INVALID_ACTOR_ID) return StrongActorPtr(); if (m_bProxy && serversActorId == INVALID_ACTOR_ID) return StrongActorPtr(); StrongActorPtr pActor = m_pActorFactory->CreateActor(actorResource.c_str(), overrides, initialTransform, serversActorId); if (pActor) { m_actors.insert(std::make_pair(pActor->GetId(), pActor)); if (!m_bProxy && (m_State==BGS_SpawningPlayersActors || m_State==BGS_Running)) { std::shared_ptr pNewActor(GCC_NEW EvtData_Request_New_Actor(actorResource, initialTransform, pActor->GetId())); IEventManager::Get()->VTriggerEvent(pNewActor); } return pActor; } else { return StrongActorPtr(); } } void BaseGameLogic::VDestroyActor(const ActorId actorId) { std::shared_ptr pEvent(GCC_NEW EvtData_Destroy_Actor(actorId)); IEventManager::Get()->VTriggerEvent(pEvent); auto findIt = m_actors.find(actorId); if (findIt != m_actors.end()) { findIt->second->Destroy(); m_actors.erase(findIt); } } WeakActorPtr BaseGameLogic::VGetActor(const ActorId actorId) { ActorMap::iterator findIt = m_actors.find(actorId); if (findIt != m_actors.end()) return findIt->second; return WeakActorPtr(); } void BaseGameLogic::VModifyActor(const ActorId actorId, TiXmlElement *override) { GCC_ASSERT(m_pActorFactory); if (!m_pActorFactory) return; auto findIt = m_actors.find(actorId); if (findIt != m_actors.end()) { m_pActorFactory->ModifyActor(findIt->second, override); } } void BaseGameLogic::VOnUpdate(float time, float elapsedTime) { int deltaMilliseconds = int(elapsedTime * 1000.0f); m_Lifetime += elapsedTime; switch(m_State) { case BGS_Initializing: { // If we get to here we're ready to attach players VChangeState(BGS_MainMenu); break; } case BGS_MainMenu: { break; } case BGS_LoadingGameEnvironment: { /*** // [mrmike] This was modified a little from what you see in the book - VLoadGame() is now called from // BaseGameLogic::VChangeState() // if (!g_pApp->VLoadGame()) { GCC_ERROR("The game failed to load."); g_pApp->AbortGame(); } ***/ break; } case BGS_WaitingForPlayersToLoadEnvironment: { if (m_ExpectedPlayers + m_ExpectedRemotePlayers <= m_HumanGamesLoaded) { VChangeState(BGS_SpawningPlayersActors); } break; } case BGS_SpawningPlayersActors: { VChangeState(BGS_Running); break; } case BGS_WaitingForPlayers: { if (m_ExpectedPlayers + m_ExpectedRemotePlayers == m_HumanPlayersAttached) { // The server sends us the level name as a part of the login message. // We have to wait until it arrives before loading the level if (!g_pApp->m_Options.m_Level.empty()) { VChangeState(BGS_LoadingGameEnvironment); } } break; } case BGS_Running: { // m_pProcessManager->UpdateProcesses(deltaMilliseconds); // if (m_pPhysics && !m_bProxy) // { // m_pPhysics->VOnUpdate(elapsedTime); // m_pPhysics->VSyncVisibleScene(); // } break; } default: GCC_ERROR("Unrecognized state."); } // update all game views for (GameViewList::iterator it = m_gameViews.begin(); it != m_gameViews.end(); ++it) { (*it)->VOnUpdate(deltaMilliseconds); } // update game actors // for (ActorMap::const_iterator it = m_actors.begin(); it != m_actors.end(); ++it) // { // it->second->Update(deltaMilliseconds); // } } // // BaseGameLogic::VChangeState - Chapter 19, page 710 // void BaseGameLogic::VChangeState(BaseGameState newState) { // TODO(munnellg) - implement if (newState==BGS_WaitingForPlayers) { // Get rid of the Main Menu m_gameViews.pop_front(); // Note: Split screen support would require this to change! m_ExpectedPlayers = 1; m_ExpectedRemotePlayers = g_pApp->m_Options.m_expectedPlayers - 1; m_ExpectedAI = g_pApp->m_Options.m_numAIs; if (!g_pApp->m_Options.m_gameHost.empty()) { VSetProxy(); m_ExpectedAI = 0; // the server will create these m_ExpectedRemotePlayers = 0; // the server will create these // TODO(munnellg) - implement // if (!g_pApp->AttachAsClient()) // { // // Throw up a main menu // VChangeState(BGS_MainMenu); // return // } } // else if (m_ExpectedRemotePlayers > 0) // { // TODO(munnellg) - implement for network games // } } else if (newState == BGS_LoadingGameEnvironment) { m_State = newState; if (!g_pApp->VLoadGame()) { GCC_ERROR("The game failed to load."); g_pApp->AbortGame(); } else { VChangeState(BGS_WaitingForPlayersToLoadEnvironment); // we must wait for all human player to report their environments are loaded. return; } } m_State = newState; } // Chapter 19/20 refactor work // - move physics pointer into BaseGameLogic // - but make the inherited logic choose the implementation void BaseGameLogic::VRenderDiagnostics() { if (m_RenderDiagnostics) { // TODO(munnellg) - uncomment this when we have physics // m_pPhysics->VRenderDiagnostics(); } } void BaseGameLogic::VAddView(std::shared_ptr pView, ActorId actorId) { // This makes sure that all views have a non-zero view id. int viewId = static_cast(m_gameViews.size()); m_gameViews.push_back(pView); pView->VOnAttach(viewId, actorId); pView->VOnRestore(); } void BaseGameLogic::VRemoveView(std::shared_ptr pView) { m_gameViews.remove(pView); } ActorFactory* BaseGameLogic::VCreateActorFactory(void) { return GCC_NEW ActorFactory; }