/* 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 */ #define DEBUG_SPECIAL_MODULE DEBUG_MODULE_WORLD_ENTITY #include "world_entity.h" #include "shell_command.h" #include "util/loading/resource_manager.h" #include "resource_obj.h" #include "md2/md2Model.h" #include "md3/md3_model.h" #include "aabb_tree_node.h" #include "util/loading/load_param.h" #include "loading/load_param_xml.h" #include "util/loading/factory.h" #include "obb_tree.h" #include "elements/glgui_energywidget.h" #include "elements/glgui_energywidgetvertical.h" #include "state.h" #include "camera.h" #include "collision_handle.h" #include "collision_event.h" #include "game_rules.h" #include "kill.h" #include "debug.h" #include "track/track.h" #include "projectiles/projectile.h" SHELL_COMMAND(model, WorldEntity, loadModel) ->describe("sets the Model of the WorldEntity") ->defaultValues("models/ships/fighter.obj", 1.0f); SHELL_COMMAND(debugEntity, WorldEntity, debugWE); ObjectListDefinition(WorldEntity); /** * Loads the WordEntity-specific Part of any derived Class * * @param root: Normally NULL, as the Derived Entities define a loadParams Function themeselves, * that can calls WorldEntities loadParams for itself. */ WorldEntity::WorldEntity() : Synchronizeable() { this->registerObject(this, WorldEntity::_objectList); this->obbTree = NULL; this->aabbNode = NULL; this->healthWidget = NULL; this->healthMax = 1.0f; this->health = 1.0f; this->damage = 0.0f; // no damage dealt by a default entity this->scaling = 1.0f; /* OSOLETE */ this->bVisible = true; this->bCollide = true; this->objectListNumber = OM_INIT; this->lastObjectListNumber = OM_INIT; // reset all collision handles to NULL == unsubscribed state for(int i = 0; i < CREngine::CR_NUMBER; ++i) this->collisionHandles[i] = NULL; this->bReactive = false; this->bOnGround = false; // Track of this entity this->entityTrack = NULL; // registering default reactions: this->subscribeReaction(CREngine::CR_OBJECT_DAMAGE, /* WorldEntity::staticClassID(), */ Projectile::staticClassID()); this->toList(OM_NULL); registerVar( new SynchronizeableString( &this->md2TextureFileName, &this->md2TextureFileName, "md2TextureFileName", PERMISSION_MASTER_SERVER ) ); modelFileName_handle = registerVarId( new SynchronizeableString( &modelFileName, &modelFileName, "modelFileName", PERMISSION_MASTER_SERVER ) ); scaling_handle = registerVarId( new SynchronizeableFloat( &scaling, &scaling, "scaling", PERMISSION_MASTER_SERVER ) ); list_handle = registerVarId( new SynchronizeableInt( (int*)&objectListNumber, &list_write, "list", PERMISSION_MASTER_SERVER ) ); health_handle = registerVarId( new SynchronizeableFloat( &this->health, &this->health_write, "health", PERMISSION_MASTER_SERVER ) ); healthMax_handle = registerVarId( new SynchronizeableFloat( &this->healthMax, &this->healthMax_write, "maxHealth", PERMISSION_MASTER_SERVER ) ); } /** * standard destructor */ WorldEntity::~WorldEntity () { State::getObjectManager()->toList(this, OM_INIT); // Delete the model (unregister it with the ResourceManager) for (unsigned int i = 0; i < this->models.size(); i++) this->setModel(NULL, i); // Delete the obbTree if( this->obbTree != NULL) delete this->obbTree; if (this->healthWidget != NULL) delete this->healthWidget; this->unsubscribeReaction(); } /** * loads the WorldEntity Specific Parameters. * @param root: the XML-Element to load the Data From */ void WorldEntity::loadParams(const TiXmlElement* root) { // Do the PNode loading stuff PNode::loadParams(root); LoadParam(root, "md2texture", this, WorldEntity, loadMD2Texture) .describe("the fileName of the texture, that should be loaded onto this world-entity. (must be relative to the data-dir)") .defaultValues(""); // Model Loading LoadParam(root, "model", this, WorldEntity, loadModel) .describe("the fileName of the model, that should be loaded onto this world-entity. (must be relative to the data-dir)") .defaultValues("", 1.0f, 0); // Entity Attributes LoadParam(root, "maxHealth", this, WorldEntity, setHealthMax) .describe("The Maximum health that can be loaded onto this entity") .defaultValues(1.0f); LoadParam(root, "health", this, WorldEntity, setHealth) .describe("The Health the WorldEntity has at this moment") .defaultValues(1.0f); LoadParam(root, "list", this, WorldEntity, toListS); // Track LoadParamXML(root, "Track", this, WorldEntity, addTrack) .describe("creates and adds a track to this WorldEntity"); } /** * this functions adds a track to this workd entity. This can be usefull, if you like this WE to follow a some waypoints. * here the track is created and further initializing left for the Track itself */ void WorldEntity::addTrack(const TiXmlElement* root) { this->entityTrack = new Track(); this->setParent(this->entityTrack->getTrackNode()); this->entityTrack->getTrackNode()->setParentMode(PNODE_ALL); LOAD_PARAM_START_CYCLE(root, element); { PRINTF(4)("element is: %s\n", element->Value()); Factory::fabricate(element); } LOAD_PARAM_END_CYCLE(element); } /** * loads a Model onto a WorldEntity * @param fileName the name of the model to load * @param scaling the Scaling of the model * * FIXME * @todo: separate the obb tree generation from the model */ void WorldEntity::loadModel(const std::string& fileName, float scaling, unsigned int modelNumber, unsigned int obbTreeDepth) { this->modelLODName = fileName; this->scaling = scaling; std::string name = fileName; if ( name.find( Resources::ResourceManager::getInstance()->mainGlobalPath().name() ) == 0 ) { name.erase(Resources::ResourceManager::getInstance()->mainGlobalPath().name().size()); } this->modelFileName = name; if (!fileName.empty()) { // search for the special character # in the LoadParam if (fileName.find('#') != std::string::npos) { PRINTF(4)("Found # in %s... searching for LOD's\n", fileName.c_str()); std::string lodFile = fileName; unsigned int offset = lodFile.find('#'); for (unsigned int i = 0; i < 3; i++) { lodFile[offset] = 48+(int)i; if (Resources::ResourceManager::getInstance()->checkFileInMainPath( lodFile)) this->loadModel(lodFile, scaling, i); } return; } if (this->scaling <= 0.0) { PRINTF(1)("YOU GAVE ME A CRAPY SCALE resetting to 1.0\n"); this->scaling = 1.0; } /// LOADING AN OBJ FILE if(fileName.find(".obj") != std::string::npos) { PRINTF(4)("fetching OBJ file: %s\n", fileName.c_str()); StaticModel* model = new StaticModel(); *model = ResourceOBJ(fileName, this->scaling); if (model->getVertexCount() > 0) { this->setModel(model, modelNumber); if( modelNumber == 0 /* FIXME && !this->isA(CL_WEAPON) */) this->buildObbTree(obbTreeDepth); } else delete model; } /// LOADING AN MD2-model else if(fileName.find(".md2") != std::string::npos) { PRINTF(4)("fetching MD2 file: %s\n", fileName.c_str()); Model* m = new MD2Model(fileName, this->md2TextureFileName, this->scaling); //this->setModel((Model*)ResourceManager::getInstance()->load(fileName, MD2, RP_CAMPAIGN), 0); this->setModel(m, 0); if( m != NULL) this->buildObbTree(obbTreeDepth); } /// LOADING AN MD3-MODEL. else if(fileName.find(".md3") != std::string::npos) { PRINTF(4)("fetching MD3 file: %s\n", fileName.c_str()); // Model* m = new md3::MD3Model(fileName, this->scaling); // this->setModel(m, 0); // if( m != NULL) // this->buildObbTree(obbTreeDepth); } } else { this->setModel(NULL); } } /** * sets a specific Model for the Object. * @param model The Model to set * @param modelNumber the n'th model in the List to get. */ void WorldEntity::setModel(Model* model, unsigned int modelNumber) { if (this->models.size() <= modelNumber) this->models.resize(modelNumber+1, NULL); if (this->models[modelNumber] != NULL) { delete this->models[modelNumber]; } this->models[modelNumber] = model; } /** * builds the obb-tree * @param depth the depth to calculate */ bool WorldEntity::buildObbTree(int depth) { if( this->obbTree != NULL) { delete this->obbTree; this->obbTree = NULL; } if (this->models[0] != NULL) this->obbTree = new OBBTree(depth, models[0]->getModelInfo(), this); else { PRINTF(1)("could not create obb-tree, because no model was loaded yet\n"); this->obbTree = NULL; return false; } // create the axis aligned bounding box if( this->aabbNode != NULL) { delete this->aabbNode; this->aabbNode = NULL; } if( this->models[0] != NULL) { this->aabbNode = new AABBTreeNode(); this->aabbNode->spawnBVTree(this->models[0]); } else { PRINTF(1)("could not create aabb bounding box, because no model was loaded yet\n"); this->aabbNode = NULL; return false; } return true; } /** * subscribes this world entity to a collision reaction * @param type the type of reaction to subscribe to * @param target1 a filter target (classID) */ void WorldEntity::subscribeReaction(CREngine::CRType type, const ClassID& target1) { this->subscribeReaction(type); // add the target filter this->collisionHandles[type]->addTarget(target1); } /** * subscribes this world entity to a collision reaction * @param type the type of reaction to subscribe to * @param target1 a filter target (classID) */ void WorldEntity::subscribeReaction(CREngine::CRType type, const ClassID& target1, const ClassID& target2) { this->subscribeReaction(type); // add the target filter this->collisionHandles[type]->addTarget(target1); this->collisionHandles[type]->addTarget(target2); } /** * subscribes this world entity to a collision reaction * @param type the type of reaction to subscribe to * @param target1 a filter target (classID) */ void WorldEntity::subscribeReaction(CREngine::CRType type, const ClassID& target1, const ClassID& target2, const ClassID& target3) { this->subscribeReaction(type); // add the target filter this->collisionHandles[type]->addTarget(target1); this->collisionHandles[type]->addTarget(target2); this->collisionHandles[type]->addTarget(target3); } /** * subscribes this world entity to a collision reaction * @param type the type of reaction to subscribe to * @param target1 a filter target (classID) */ void WorldEntity::subscribeReaction(CREngine::CRType type, const ClassID& target1, const ClassID& target2, const ClassID& target3, const ClassID& target4) { this->subscribeReaction(type); // add the target filter this->collisionHandles[type]->addTarget(target1); this->collisionHandles[type]->addTarget(target2); this->collisionHandles[type]->addTarget(target3); this->collisionHandles[type]->addTarget(target4); } /** * subscribes this world entity to a collision reaction * @param type the type of reaction to subscribe to * @param nrOfTargets number of target filters * @param ... the targets as classIDs */ void WorldEntity::subscribeReaction(CREngine::CRType type) { if( this->collisionHandles[type] != NULL) { PRINTF(2)("Registering for a CollisionReaction already subscribed to! Skipping\n"); return; } this->collisionHandles[type] = CREngine::getInstance()->subscribeReaction(this, type); // now there is at least one collision reaction subscribed this->bReactive = true; } /** * unsubscribes a specific reaction from the worldentity * @param type the reaction to unsubscribe */ void WorldEntity::unsubscribeReaction(CREngine::CRType type) { if( this->collisionHandles[type] == NULL) return; CREngine::getInstance()->unsubscribeReaction(this->collisionHandles[type]); this->collisionHandles[type] = NULL; // check if there is still any handler registered for(int i = 0; i < CREngine::CR_NUMBER; ++i) { if( this->collisionHandles[i] != NULL) { this->bReactive = true; return; } } this->bReactive = false; } /** * unsubscribes all collision reactions */ void WorldEntity::unsubscribeReaction() { for( int i = 0; i < CREngine::CR_NUMBER; i++) this->unsubscribeReaction((CREngine::CRType)i); // there are no reactions subscribed from now on this->bReactive = false; } /** * registers a new collision event to this world entity * @param entityA entity of the collision * @param entityB entity of the collision * @param bvA colliding bounding volume of entityA * @param bvB colliding bounding volume of entityA */ bool WorldEntity::registerCollision(WorldEntity* entityA, WorldEntity* entityB, BoundingVolume* bvA, BoundingVolume* bvB) { PRINTF(5)("registering collision of type: %s vs %s\n", entityA->getClassCName(), entityB->getClassCName()); // is there any handler listening? if( !this->bReactive) return false; // get a collision event CollisionEvent* c = CREngine::getInstance()->popCollisionEventObject(); assert(c != NULL); // if this should fail: we got not enough precached CollisionEvents: alter value in cr_defs.h c->collide(COLLISION_TYPE_OBB, entityA, entityB, bvA, bvB); for( int i = 0; i < CREngine::CR_NUMBER; ++i) if( this->collisionHandles[i] != NULL) this->collisionHandles[i]->registerCollisionEvent(c); return true; } /** * registers a new collision event to this woeld entity * @param entity the entity that collides * @param plane it stands on * @param position it collides on the plane */ bool WorldEntity::registerCollision(int type, WorldEntity* entity, WorldEntity* groundEntity, Vector normal, Vector position, bool bInWall) { // is there any handler listening? if( !this->bReactive) return false; // get a collision event CollisionEvent* c = CREngine::getInstance()->popCollisionEventObject(); assert(c != NULL); // if this should fail: we got not enough precached CollisionEvents: alter value in cr_defs.h c->collide(type, entity, groundEntity, normal, position, bInWall); for( int i = 0; i < CREngine::CR_NUMBER; ++i) if( this->collisionHandles[i] != NULL) this->collisionHandles[i]->registerCollisionEvent(c); return true; } /** * @brief moves this entity to the List OM_List * @param list the list to set this Entity to. * * this is the same as a call to State::getObjectManager()->toList(entity , list); * directly, but with an easier interface. * * @todo inline this (peut etre) */ void WorldEntity::toList(OM_LIST list) { State::getObjectManager()->toList(this, list); } void WorldEntity::toListS(const std::string& listName) { OM_LIST id = ObjectManager::StringToOMList(listName); if (id != OM_NULL) this->toList(id); else PRINTF(2)("List %s not found\n", listName.c_str()); } void WorldEntity::toReflectionList() { State::getObjectManager()->toReflectionList( this ); } void removeFromReflectionList() { /// TODO /// State::getObject } /** * sets the character attributes of a worldentity * @param character attributes * * these attributes don't have to be set, only use them, if you need them */ //void WorldEntity::setCharacterAttributes(CharacterAttributes* charAttr) //{} /** * this function is called, when two entities collide * @param entity: the world entity with whom it collides * * Implement behaviour like damage application or other miscellaneous collision stuff in this function */ void WorldEntity::collidesWith(WorldEntity* entity, const Vector& location) { /** * THIS IS A DEFAULT COLLISION-Effect. * IF YOU WANT TO CREATE A SPECIFIC COLLISION ON EACH OBJECT * USE:: * if (entity->isA(CL_WHAT_YOU_ARE_LOOKING_FOR)) { printf "dothings"; }; * * You can always define a default Action.... don't be affraid just test it :) */ // PRINTF(3)("collision %s vs %s @ (%f,%f,%f)\n", this->getClassCName(), entity->getClassCName(), location.x, location.y, location.z); } /** * this function is called, when two entities collide * @param entity: the world entity with whom it collides * * Implement behaviour like damage application or other miscellaneous collision stuff in this function */ void WorldEntity::collidesWithGround(const Vector& location) { PRINTF(0)("BSP_GROUND: %s collides \n", this->getClassCName() ); } void WorldEntity::collidesWithGround(const Vector& feet, const Vector& ray_1, const Vector& ray_2) { // PRINTF(0)("BSP_GROUND: Player collides \n", this->getClassCName() ); Vector v = this->getAbsDirX(); v.x *= 10.1; v.y *= 10.1; v.z *= 10.1; Vector u = Vector(0.0,-20.0,0.0); if(!(this->getAbsCoor().x == ray_2.x && this->getAbsCoor().y == ray_2.y && this->getAbsCoor().z == ray_2.z) ) { this->setAbsCoor(ray_2 - v); } else { if(ray_1.x == this->getAbsCoor().x + v.x && ray_1.y == this->getAbsCoor().y + v.y + 0.1 && ray_1.z ==this->getAbsCoor().z + v.z) { this->setAbsCoor(feet -u ); } this->setAbsCoor(ray_2 - v); } } /** * this is called immediately after the Entity has been constructed, initialized and then Spawned into the World * */ void WorldEntity::postSpawn () {} /** * this method is called by the world if the WorldEntity leaves the game */ void WorldEntity::leaveWorld () {} /** * resets the WorldEntity to its initial values. eg. used for multiplayer games: respawning */ void WorldEntity::reset() { this->setHealth( this->getHealthMax() ); } /** * this method is called every frame * @param time: the time in seconds that has passed since the last tick * * Handle all stuff that should update with time inside this method (movement, animation, etc.) */ void WorldEntity::tick(float time) { } /** * the entity is drawn onto the screen with this function * * This is a central function of an entity: call it to let the entity painted to the screen. * Just override this function with whatever you want to be drawn. */ void WorldEntity::draw() const { //PRINTF(0)("(%s::%s)\n", this->getClassCName(), this->getName()); // assert(!unlikely(this->models.empty())); { glMatrixMode(GL_MODELVIEW); glPushMatrix(); /* translate */ glTranslatef (this->getAbsCoor ().x, this->getAbsCoor ().y, this->getAbsCoor ().z); Vector tmpRot = this->getAbsDir().getSpacialAxis(); glRotatef (this->getAbsDir().getSpacialAxisAngle(), tmpRot.x, tmpRot.y, tmpRot.z ); // This Draws the LOD's float cameraDistance = State::getCamera()->distance(this); if (cameraDistance > 30 && this->models.size() >= 3 && this->models[2] != NULL) { this->models[2]->draw(); } else if (cameraDistance > 10 && this->models.size() >= 2 && this->models[1] != NULL) { this->models[1]->draw(); } else if (this->models.size() >= 1 && this->models[0] != NULL) { this->models[0]->draw(); } // if( this->aabbNode != NULL) // this->aabbNode->drawBV(0, DRAW_BV_POLYGON, Vector(1, 0.6, 0.2), true); glPopMatrix(); } } /** * @param health the Health to add. * @returns the health left (this->healthMax - health+this->health) */ float WorldEntity::increaseHealth(float health) { this->health += health; if (this->health > this->healthMax) { float retHealth = this->healthMax - this->health; this->health = this->healthMax; this->updateHealthWidget(); return retHealth; } this->updateHealthWidget(); return 0.0; } /** * @param health the Health to be removed * @returns 0.0 or the rest, that was not substracted (bellow 0.0) */ float WorldEntity::decreaseHealth(float health) { this->health -= health; if (this->health < 0) { float retHealth = -this->health; this->health = 0.0f; this->updateHealthWidget(); return retHealth; } this->updateHealthWidget(); return 0.0; } /** * @param maxHealth the maximal health that can be loaded onto the entity. */ void WorldEntity::setHealthMax(float healthMax) { this->healthMax = healthMax; if (this->health > this->healthMax) { PRINTF(3)("new maxHealth is bigger as the old health. Did you really intend to do this for (%s::%s)\n", this->getClassCName(), this->getCName()); this->health = this->healthMax; } this->updateHealthWidget(); } /** * @brief creates the HealthWidget * * since not all entities need an HealthWidget, it is only created on request. */ void WorldEntity::createHealthWidget() { if (this->healthWidget == NULL) { this->healthWidget = new OrxGui::GLGuiEnergyWidgetVertical(); this->healthWidget->setDisplayedName("Electronics"); this->healthWidget->setSize2D(30,400); this->healthWidget->setAbsCoor2D(100,200); this->updateHealthWidget(); } else PRINTF(3)("Allready created the HealthWidget for %s::%s\n", this->getClassCName(), this->getCName()); } void WorldEntity::increaseHealthMax(float increaseHealth) { this->healthMax += increaseHealth; this->updateHealthWidget(); } OrxGui::GLGuiWidget* WorldEntity::getHealthWidget() { this->createHealthWidget(); return this->healthWidget; } /** * @param visibility shows or hides the health-bar * (creates the widget if needed) */ void WorldEntity::setHealthWidgetVisibilit(bool visibility) { if (visibility) { if (this->healthWidget != NULL) this->healthWidget->show(); else { this->createHealthWidget(); this->updateHealthWidget(); this->healthWidget->show(); } } else if (this->healthWidget != NULL) this->healthWidget->hide(); } /** * hit the world entity with * @param damage damage to be dealt */ void WorldEntity::hit(float damage, WorldEntity* killer) { this->decreaseHealth(damage); PRINTF(5)("Hit me: %s::%s now only %f/%f health\n", this->getClassCName(), this->getCName(), this->getHealth(), this->getHealthMax()); if( this->getHealth() > 0) { // any small explosion animaitions } else { this->destroy( killer ); } } /** * destoys the world entity */ void WorldEntity::destroy(WorldEntity* killer) { this->toList(OM_DEAD); } /** * @brief updates the HealthWidget */ void WorldEntity::updateHealthWidget() { if (this->healthWidget != NULL) { this->healthWidget->setMaximum(this->healthMax); this->healthWidget->setValue(this->health); } } /** * DEBUG-DRAW OF THE BV-Tree. * @param depth What depth to draw * @param drawMode the mode to draw this entity under */ void WorldEntity::drawBVTree(int depth, int drawMode) const { glMatrixMode(GL_MODELVIEW); glPushMatrix(); /* translate */ glTranslatef (this->getAbsCoor ().x, this->getAbsCoor ().y, this->getAbsCoor ().z); /* rotate */ Vector tmpRot = this->getAbsDir().getSpacialAxis(); glRotatef (this->getAbsDir().getSpacialAxisAngle(), tmpRot.x, tmpRot.y, tmpRot.z ); if (this->obbTree) this->obbTree->drawBV(depth, drawMode); glPopMatrix(); } /** * Debug the WorldEntity */ void WorldEntity::debugEntity() const { PRINT(0)("WorldEntity %s::%s (DEBUG)\n", this->getClassCName(), this->getCName()); this->debugNode(); PRINT(0)("List: %s ; ModelCount %d - ", ObjectManager::OMListToString(this->objectListNumber).c_str(), this->models.size()); for (unsigned int i = 0; i < this->models.size(); i++) { if (models[i] != NULL) PRINT(0)(" : %d:%s", i, this->models[i]->getCName()); } PRINT(0)("\n"); } /** * handler for changes on registred vars * @param id id's which changed */ void WorldEntity::varChangeHandler( std::list< int > & id ) { if ( std::find( id.begin(), id.end(), modelFileName_handle ) != id.end() || std::find( id.begin(), id.end(), scaling_handle ) != id.end() ) { loadModel( modelFileName, scaling ); } if ( std::find( id.begin(), id.end(), list_handle ) != id.end() ) { this->toList( (OM_LIST)list_write ); } if ( std::find( id.begin(), id.end(), health_handle ) != id.end() ) { this->setHealth( health_write ); } if ( std::find( id.begin(), id.end(), healthMax_handle ) != id.end() ) { this->setHealthMax( healthMax_write ); } PNode::varChangeHandler( id ); }