/* orxonox - the future of 3D-vertical-scrollers Copyright (C) 2004 orx This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. ### File Specific: main-programmer: Patrick Boenzli co-programmer: Christian Meyer co-programmer: Benjamin Grauer */ #define DEBUG_SPECIAL_MODULE DEBUG_MODULE_WORLD #include "world.h" #include "shell_command.h" #include "resource_manager.h" #include "state.h" #include "p_node.h" #include "world_entity.h" #include "player.h" #include "camera.h" #include "environment.h" #include "terrain.h" #include "test_entity.h" #include "terrain.h" #include "light.h" #include "load_param.h" #include "shell.h" #include "fast_factory.h" #include "animation_player.h" #include "particle_engine.h" #include "graphics_engine.h" #include "physics_engine.h" #include "fields.h" #include "md2Model.h" #include "glmenu_imagescreen.h" #include "game_loader.h" #include "animation3d.h" #include "substring.h" #include "factory.h" #include "weapons/projectile.h" #include "event_handler.h" #include "sound_engine.h" #include "ogg_player.h" #include "class_list.h" #include "cd_engine.h" #include "npcs/npc_test1.h" #include "shader.h" #include "playable.h" #include "network_manager.h" #include "playable.h" SHELL_COMMAND(speed, World, setSpeed); SHELL_COMMAND(togglePNodeVisibility, World, togglePNodeVisibility); SHELL_COMMAND(toggleBVVisibility, World, toggleBVVisibility); using namespace std; //! This creates a Factory to fabricate a World CREATE_FACTORY(World, CL_WORLD); World::World(const TiXmlElement* root) { this->constuctorInit("", -1); this->path = NULL; this->loadParams(root); } /** * remove the World from memory * * delete everything explicitly, that isn't contained in the parenting tree! * things contained in the tree are deleted automaticaly */ World::~World () { delete this->shell; PRINTF(3)("World::~World() - deleting current world\n"); delete this->localPlayer; // delete all the initialized Engines. FastFactory::flushAll(true); delete LightManager::getInstance(); delete ParticleEngine::getInstance(); delete AnimationPlayer::getInstance(); delete PhysicsEngine::getInstance(); // external engines initialized by the orxonox-class get deleted SoundEngine::getInstance()->flushAllBuffers(); SoundEngine::getInstance()->flushAllSources(); if (State::getObjectManager() == &this->objectManager) State::setObjectManager(NULL); // erease everything that is left. delete PNode::getNullParent(); //secondary cleanup of PNodes; const std::list* nodeList = ClassList::getList(CL_PARENT_NODE); if (nodeList != NULL) while (!nodeList->empty()) delete nodeList->front(); Shader::suspendShader(); // unload the resources !! ResourceManager::getInstance()->unloadAllByPriority(RP_LEVEL); } /** * initializes the world. * @param name the name of the world * @param worldID the ID of this world * * set all stuff here that is world generic and does not use to much memory * because the real init() function StoryEntity::init() will be called * shortly before start of the game. * since all worlds are initiated/referenced before they will be started. * NO LEVEL LOADING HERE - NEVER! */ void World::constuctorInit(const char* name, int worldID) { this->setClassID(CL_WORLD, "World"); this->setName(name); this->gameTime = 0.0f; this->setSpeed(1.0); this->music = NULL; this->shell = NULL; this->localPlayer = NULL; this->localCamera = NULL; this->showPNodes = false; this->showBV = false; } /** * loads the parameters of a World from an XML-element * @param root the XML-element to load from */ void World::loadParams(const TiXmlElement* root) { PRINTF(4)("Creating a World\n"); LoadParam(root, "identifier", this, World, setStoryID) .describe("Sets the StoryID of this world"); LoadParam(root, "nextid", this, World, setNextStoryID) .describe("Sets the ID of the next world"); LoadParam(root, "path", this, World, setPath) .describe("The Filename of this World (relative from the data-dir)"); } /** * this is executed just before load * * since the load function sometimes needs data, that has been initialized * before the load and after the proceeding storyentity has finished */ ErrorMessage World::preLoad() { State::setObjectManager(&this->objectManager); this->cycle = 0; /* init the world interface */ this->shell = new Shell(); LightManager::getInstance(); PNode::getNullParent(); AnimationPlayer::getInstance(); // initializes the animationPlayer ParticleEngine::getInstance(); PhysicsEngine::getInstance(); this->localCamera = new Camera(); this->localCamera->setName ("World-Camera"); State::setCamera(this->localCamera, this->localCamera->getTarget()); GraphicsEngine::getInstance()->displayFPS(true); this->displayLoadScreen(); } /** * loads the World by initializing all resources, and set their default values. */ ErrorMessage World::load() { PRINTF(3)("> Loading world: '%s'\n", getPath()); TiXmlElement* element; GameLoader* loader = GameLoader::getInstance(); if( getPath() == NULL) { PRINTF(1)("World has no path specified for loading"); return (ErrorMessage){213,"Path not specified","World::load()"}; } TiXmlDocument* XMLDoc = new TiXmlDocument( getPath()); // load the campaign document if( !XMLDoc->LoadFile()) { // report an error PRINTF(1)("loading XML File: %s @ %s:l%d:c%d\n", XMLDoc->ErrorDesc(), this->getPath(), XMLDoc->ErrorRow(), XMLDoc->ErrorCol()); delete XMLDoc; return (ErrorMessage){213,"XML File parsing error","World::load()"}; } // check basic validity TiXmlElement* root = XMLDoc->RootElement(); assert( root != NULL); if( root == NULL || root->Value() == NULL || strcmp( root->Value(), "WorldDataFile")) { // report an error PRINTF(1)("Specified XML File is not an orxonox world data file (WorldDataFile element missing)\n"); delete XMLDoc; return (ErrorMessage){213,"Path not a WorldDataFile","World::load()"}; } // load the parameters // name const char* string = grabParameter( root, "name"); if( string == NULL) { PRINTF(2)("World is missing a proper 'name'\n"); this->setName("Unknown"); } else { this->setName(string); } //////////////// // LOADSCREEN // //////////////// element = root->FirstChildElement("LoadScreen"); if (element == NULL) { PRINTF(2)("no LoadScreen specified, loading default\n"); glmis->setBackgroundImage("pictures/load_screen.jpg"); this->glmis->setMaximum(8); this->glmis->draw(); } else { this->glmis->loadParams(element); this->glmis->draw(); } this->glmis->draw(); //////////////////////// // find WorldEntities // //////////////////////// element = root->FirstChildElement("WorldEntities"); if( element == NULL) { PRINTF(1)("World is missing 'WorldEntities'\n"); } else { element = element->FirstChildElement(); // load Players/Objects/Whatever PRINTF(4)("Loading WorldEntities\n"); while( element != NULL) { BaseObject* created = Factory::fabricate(element); if( created != NULL ) { if(created->isA(CL_WORLD_ENTITY)) this->spawn(dynamic_cast(created)); printf("Created a %s: %s\n", created->getClassName(), created->getName()); } // if we load a 'Player' we use it as localPlayer //todo do this more elegant if( element->Value() != NULL && !strcmp( element->Value(), "SkyBox")) this->sky = dynamic_cast(created); if( element->Value() != NULL && !strcmp( element->Value(), "Terrain")) { terrain = dynamic_cast(created); CDEngine::getInstance()->setTerrain(terrain); } element = element->NextSiblingElement(); glmis->step(); //! @todo temporary } PRINTF(4)("Done loading WorldEntities\n"); } ////////////////////////////// // LOADING ADDITIONAL STUFF // ////////////////////////////// LoadParamXML(root, "LightManager", LightManager::getInstance(), LightManager, loadParams); LoadParamXML(root, "ParticleEngine", ParticleEngine::getInstance(), ParticleEngine, loadParams); // LoadParamXML(root, "PhysicsEngine", PhysicsEngine::getInstance(), PhysicsEngine, loadParams); // free the XML data delete XMLDoc; /* GENERIC LOADING PROCESS FINISHED */ // Create a Player this->localPlayer = new Player(); Playable* playable; const list* playableList = ClassList::getList(CL_PLAYABLE); if (playableList != NULL) { playable = dynamic_cast(playableList->front()); this->localPlayer->setControllable(playable); } // bind camera playable->addChild (this->localCamera); // //localCamera->setParent(TrackNode::getInstance()); // tn->addChild(this->localCamera); localCamera->setClipRegion(1, 10000.0); // localCamera->lookAt(playable); // this->localPlayer->setParentMode(PNODE_ALL); if (sky != NULL) { this->sky->setParent(this->localCamera); this->sky->setParentMode(PNODE_MOVEMENT); } this->localCamera->getTarget()->getParent()->debugNode(0); SoundEngine::getInstance()->setListener(this->localCamera); //////////// // STATIC // //////////// // TestEntity* testEntity = new TestEntity(); // testEntity->setRelCoor(Vector(570, 10, -15)); // testEntity->setRelDir(Quaternion(M_PI, Vector(0, 1, 0))); // this->spawn(testEntity); for(int i = 0; i < 100; i++) { WorldEntity* tmp = new NPCTest1(); char npcChar[10]; sprintf (npcChar, "NPC_%d", i); tmp->setName(npcChar); tmp->setAbsCoor(((float)rand()/RAND_MAX) * 5000, 50/*+ (float)rand()/RAND_MAX*20*/, ((float)rand()/RAND_MAX -.5) *30); this->spawn(tmp); } this->music = NULL; //(OggPlayer*)ResourceManager::getInstance()->load("sound/00-luke_grey_-_hypermode.ogg", OGG, RP_LEVEL); //music->playback(); } ErrorMessage World::postLoad() { this->releaseLoadScreen(); } /** * initializes a new World shortly before start * * this is the function, that will be loaded shortly before the world is * started */ ErrorMessage World::preStart() { this->bPause = false; /* update the object position before game start - so there are no wrong coordinates used in the first processing */ PNode::getNullParent()->updateNode (0.001f); PNode::getNullParent()->updateNode (0.001f); } /** * starts the World */ ErrorMessage World::start() { this->bQuitWorld = false; this->mainLoop(); } /** * stops the world. This happens, when the player decides to end the Level. */ ErrorMessage World::stop() { PRINTF(3)("World::stop() - got stop signal\n"); this->bQuitWorld= true; } /** * pauses the Game */ ErrorMessage World::pause() { this->isPaused = true; } /** * ends the pause Phase */ ErrorMessage World::resume() { this->isPaused = false; } /** * destroys the World */ ErrorMessage World::destroy() { } /** * shows the loading screen */ void World::displayLoadScreen () { PRINTF(3)("World::displayLoadScreen - start\n"); //GLMenuImageScreen* this->glmis = new GLMenuImageScreen(); this->glmis->setMaximum(8); PRINTF(3)("World::displayLoadScreen - end\n"); } /** * @brief removes the loadscreen, and changes over to the game * * @todo take out the delay */ void World::releaseLoadScreen () { PRINTF(3)("World::releaseLoadScreen - start\n"); this->glmis->setValue(this->glmis->getMaximum()); PRINTF(3)("World::releaseLoadScreen - end\n"); delete this->glmis; } /** * this returns the current game time * @returns elapsed game time */ double World::getGameTime() { return this->gameTime; } /** * synchronize local data with remote data */ void World::synchronize () { // Get remote input // Update synchronizables /* NetworkManager::getInstance()->synchronize();*/ } /** * run all input processing the command node is the central input event dispatcher. the node uses the even-queue from sdl and has its own event-passing-queue. */ void World::handleInput () { EventHandler::getInstance()->process(); // remoteinput } void World::tick(std::list entityList, float dt) { std::list::iterator entity; for (entity = entityList.begin(); entity != entityList.end(); entity++) (*entity)->tick(dt); } /** * advance the timeline this calculates the time used to process one frame (with all input handling, drawing, etc) the time is mesured in ms and passed to all world-entities and other classes that need a heart-beat. */ void World::tick () { Uint32 currentFrame = SDL_GetTicks(); if(!this->bPause) { this->dt = currentFrame - this->lastFrame; if( this->dt > 10) { float fps = 1000/dt; // temporary, only for showing how fast the text-engine is char tmpChar[20]; sprintf(tmpChar, "fps: %4.0f", fps); } else { /* the frame-rate is limited to 100 frames per second, all other things are for nothing. */ PRINTF(3)("fps = 1000 - frame rate is adjusted\n"); SDL_Delay(10-dt); this->dt = 10; } this->dtS = (float)this->dt / 1000.0 * this->speed; this->gameTime += this->dtS; this->tick(this->objectManager.getObjectList(OM_DEAD_TICK), this->dtS); this->tick(this->objectManager.getObjectList(OM_COMMON), this->dtS); this->tick(this->objectManager.getObjectList(OM_GROUP_00), this->dtS); this->tick(this->objectManager.getObjectList(OM_GROUP_01), this->dtS); this->tick(this->objectManager.getObjectList(OM_GROUP_01_PROJ), this->dtS); /* update tick the rest */ this->localCamera->tick(this->dtS); // tick the engines AnimationPlayer::getInstance()->tick(this->dtS); // if (this->cycle > 5) PhysicsEngine::getInstance()->tick(this->dtS); ParticleEngine::getInstance()->tick(this->dtS); /** actualy the Graphics Engine should tick the world not the other way around... but since we like the things not too complicated we got it this way around until there is need or time to do it the other way around. @todo: GraphicsEngine ticks world: separation of processes and data... bensch: in my opinion the GraphicsEngine could draw the world, but not tick it, beceause graphics have nothing(or at least not much) to do with Motion. */ GraphicsEngine::getInstance()->tick(this->dtS); } this->lastFrame = currentFrame; } /** * this function gives the world a consistant state after ticking (updating the world state) this will give a constistant state to the whole system. */ void World::update() { GraphicsEngine::getInstance()->update(this->dtS); PNode::getNullParent()->updateNode (this->dtS); SoundEngine::getInstance()->update(); //music->update(); } void World::collide() { CDEngine::getInstance()->checkCollisions(this->objectManager.getObjectList(OM_GROUP_00), this->objectManager.getObjectList(OM_GROUP_01_PROJ)); CDEngine::getInstance()->checkCollisions(this->objectManager.getObjectList(OM_GROUP_01), this->objectManager.getObjectList(OM_COMMON)); } /** * render the current frame clear all buffers and draw the world */ void World::display () { // clear buffer glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); // set camera this->localCamera->apply (); // draw world this->draw(); // draw HUD /** @todo draw HUD */ // flip buffers GraphicsEngine::swapBuffers(); //SDL_Surface* screen = Orxonox::getInstance()->getScreen (); //SDL_Flip (screen); } /** * runs through all entities calling their draw() methods */ void World::draw () { GraphicsEngine* engine = GraphicsEngine::getInstance(); engine->draw(State::getObjectManager()->getObjectList(OM_ENVIRON_NOTICK)); engine->draw(State::getObjectManager()->getObjectList(OM_ENVIRON)); engine->draw(State::getObjectManager()->getObjectList(OM_COMMON)); engine->draw(State::getObjectManager()->getObjectList(OM_GROUP_00)); engine->draw(State::getObjectManager()->getObjectList(OM_GROUP_01)); engine->draw(State::getObjectManager()->getObjectList(OM_GROUP_01_PROJ)); // { // if( entity->isVisible() ) entity->draw(); //FIXME // if( unlikely( this->showBV)) entity->drawBVTree(3, 226); // to draw the bounding boxes of the objects at level 2 for debug purp // entity = iterator->nextElement(); // } ParticleEngine::getInstance()->draw(); if (unlikely(this->showPNodes)) PNode::getNullParent()->debugDraw(0); engine->draw(); //TextEngine::getInstance()->draw(); } /** * \brief main loop of the world: executing all world relevant function * * in this loop we synchronize (if networked), handle input events, give the heart-beat to * all other member-entities of the world (tick to player, enemies etc.), checking for * collisions drawing everything to the screen. */ void World::mainLoop() { this->lastFrame = SDL_GetTicks (); PRINTF(3)("World::mainLoop() - Entering main loop\n"); while(!this->bQuitWorld) /* @todo implement pause */ { ++this->cycle; // Network this->synchronize (); // Process input this->handleInput (); if( this->bQuitWorld) break; // Process time this->tick (); // Process collision this->collide (); // Update the state this->update (); // Draw this->display (); } PRINTF(3)("World::mainLoop() - Exiting the main loop\n"); } /** * add and spawn a new entity to this world * @param entity to be added */ void World::spawn(WorldEntity* entity) { // this->entities->add (entity); entity->postSpawn (); } void World::setPath( const char* name) { if (this->path) delete this->path; if (ResourceManager::isFile(name)) { this->path = new char[strlen(name)+1]; strcpy(this->path, name); } else { this->path = new char[strlen(ResourceManager::getInstance()->getDataDir()) + strlen(name) +1]; sprintf(this->path, "%s%s", ResourceManager::getInstance()->getDataDir(), name); } } const char* World::getPath( void) { return path; }