/* 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: */ #define DEBUG_SPECIAL_MODULE DEBUG_MODULE_WORLD_ENTITY #include "generic_npc.h" #include "util/loading/factory.h" #include "util/loading/load_param.h" #include "interactive_model.h" #include "md2/md2Model.h" #include "sound_buffer.h" #include "resource_sound_buffer.h" #include "sound/resource_sound_buffer.h" #include "bsp_entity.h" #include "class_id_DEPRECATED.h" ObjectListDefinitionID(GenericNPC, CL_GENERIC_NPC); CREATE_FACTORY(GenericNPC); #include "script_class.h" CREATE_SCRIPTABLE_CLASS(GenericNPC, // Move addMethod("walkTo", Executor3(&GenericNPC::walkTo)) ->addMethod("runTo", Executor3(&GenericNPC::runTo)) ->addMethod("turnTo", Executor1(&GenericNPC::turnTo)) ->addMethod("finalGoalReached", Executor0ret(&GenericNPC::finalGoalReached)) ->addMethod("stop", Executor0(&GenericNPC::stop)) ->addMethod("resume", Executor0(&GenericNPC::resume)) ->addMethod("playAnimation", Executor2(&GenericNPC::playAnimation)) // Display ->addMethod("hide", Executor0(&WorldEntity::hide)) ->addMethod("unhide", Executor0(&WorldEntity::unhide)) // Coordinates ->addMethod("getAbsCoorX", Executor0ret(&PNode::getAbsCoorX)) ->addMethod("getAbsCoorY", Executor0ret(&PNode::getAbsCoorY)) ->addMethod("getAbsCoorZ", Executor0ret(&PNode::getAbsCoorZ)) ->addMethod("setAbsCoor", Executor3(&PNode::setAbsCoor)) ->addMethod("setAbsDir", Executor4(&PNode::setAbsDir)) ); /** * constructor */ GenericNPC::GenericNPC(const TiXmlElement* root) : NPC(root) { this->init(); if (root != NULL) this->loadParams(root); } /** * deconstructor */ GenericNPC::~GenericNPC () {} /** * initializing the npc enity */ void GenericNPC::init() { this->registerObject(this, GenericNPC::_objectList); this->toList(OM_GROUP_00); this->soundBuffer = OrxSound::ResourceSoundBuffer("sound/rain.wav"); time = 30.0f; this->behaviourList = new std::list; // collision reaction registration this->subscribeReaction(CREngine::CR_PHYSICS_GROUND_WALK, BspEntity::staticClassID()); } /** * loads the Settings of a MD2Creature from an XML-element. * @param root the XML-element to load the MD2Creature's properties from */ void GenericNPC::loadParams(const TiXmlElement* root) { NPC::loadParams(root); } /** * sets the animation of this npc * @param anumationIndex: the animation index * @param anumPlaybackMode: the playback mode */ void GenericNPC::setAnimation(int animationIndex, int animPlaybackMode) { if( likely(this->getModel(0) != NULL)) ((InteractiveModel*)this->getModel(0))->setAnimation(animationIndex, animPlaybackMode); } /** * @returns the current animation number */ int GenericNPC::getAnimation() { if( likely(this->getModel(0) != NULL)) return ((InteractiveModel*)this->getModel(0))->getAnimation(); else return -1; } /** * @returns true if animation is finished */ bool GenericNPC::isAnimationFinished() { if( likely(this->getModel(0) != NULL)) return ((InteractiveModel*)this->getModel(0))->isAnimationFinished(); else return false; } /** * sets the animation speed of this entity */ void GenericNPC::setAnimationSpeed(float speed) { if( likely(this->getModel(0) != NULL)) ((InteractiveModel*)this->getModel(0))->setAnimationSpeed(speed); } /** * sets the animation of this npc * @param anumationIndex: the animation index * @param anumPlaybackMode: the playback mode */ void GenericNPC::playAnimation(int animationIndex, int animPlaybackMode) { if( likely(this->getModel(0) != NULL)) ((InteractiveModel*)this->getModel(0))->setAnimation(animationIndex, animPlaybackMode); } /** * play a sound * @param filename: name of the file */ void GenericNPC::playSound(const std::string& filename) {} /** * stops the generic animation */ void GenericNPC::stop() { this->animationStack.push(this->behaviourList); this->behaviourList = new std::list; if( this->getAnimation() != STAND) this->setAnimation(STAND, MD2_ANIM_LOOP); } /** * continue the generic animation */ void GenericNPC::resume() { if( this->animationStack.size() == 0) return; delete this->behaviourList; this->behaviourList = this->animationStack.top(); this->animationStack.pop(); } /** * each animation has to be initialized here */ /** * */ void GenericNPC::initNPC() { if (!this->behaviourList->empty()) { GenericNPC::Anim currentAnimation = this->behaviourList->front(); switch(this->behaviourList->front().type) { case Walk: { if( this->getAnimation() != RUN) this->setAnimation(RUN, MD2_ANIM_LOOP); Vector dir = (currentAnimation.v - this->getAbsCoor()); dir.y = 0.0f; dir.normalize(); this->setAbsDir(Quaternion(dir, Vector(0.0, 1.0, 0.0)) * Quaternion(-M_PI_2, Vector(0.0, 1.0, 0.0))); this->setAnimationSpeed(0.5f); } break; case Run: { if( this->getAnimation() != RUN) this->setAnimation(RUN, MD2_ANIM_LOOP); Vector dir = (currentAnimation.v - this->getAbsCoor()).getNormalized(); dir.y = 0.0f; dir.getNormalized(); this->setAbsDir(Quaternion(dir, Vector(0.0, 1.0, 0.0)) * Quaternion(-M_PI_2, Vector(0.0, 1.0, 0.0))); this->setAnimationSpeed(1.0f); } break; case Crouch: { if( this->getAnimation() != CROUCH_WALK) this->setAnimation(CROUCH_WALK, MD2_ANIM_LOOP); Vector dir = (currentAnimation.v - this->getAbsCoor()).getNormalized(); dir.y = 0.0f; dir.getNormalized(); this->setAbsDir(Quaternion(dir, Vector(0.0, 1.0, 0.0)) * Quaternion(-M_PI_2, Vector(0.0, 1.0, 0.0))); this->setAnimationSpeed(1.0f); } break; case LookAt: if( this->getAnimation() != STAND) this->setAnimation(STAND, MD2_ANIM_LOOP); break; case Shoot: if( this->getAnimation() != STAND) this->setAnimation(STAND, MD2_ANIM_LOOP); break; default: if( this->getAnimation() != STAND) this->setAnimation(STAND, MD2_ANIM_LOOP); break; } } } void GenericNPC::nextStep() { if (!this->behaviourList->empty()) this->behaviourList->pop_front(); else return; if (!this->behaviourList->empty()) { GenericNPC::Anim currentAnimation = this->behaviourList->front(); switch( currentAnimation.type) { case Walk: { if( this->getAnimation() != RUN) this->setAnimation(RUN, MD2_ANIM_LOOP); Vector dir = (currentAnimation.v - this->getAbsCoor()); dir.y = 0.0f; dir.getNormalized(); this->setAbsDirSoft(Quaternion(dir, Vector(0.0, 1.0, 0.0)) * Quaternion(-M_PI_2, Vector(0.0, 1.0, 0.0)), 4.0); this->setAnimationSpeed(0.5f); } break; case Run: { if( this->getAnimation() != RUN) this->setAnimation(RUN, MD2_ANIM_LOOP); Vector dir = (currentAnimation.v - this->getAbsCoor()).getNormalized(); dir.y = 0.0f; dir.getNormalized(); this->setAbsDirSoft(Quaternion(dir, Vector(0.0, 1.0, 0.0)) * Quaternion(-M_PI_2, Vector(0.0, 1.0, 0.0)), 4.0); this->setAnimationSpeed(1.0f); } break; case Crouch: { if( this->getAnimation() != CROUCH_WALK) this->setAnimation(CROUCH_WALK, MD2_ANIM_LOOP); Vector dir = (currentAnimation.v - this->getAbsCoor()).getNormalized(); dir.y = 0.0f; dir.getNormalized(); this->setAbsDirSoft(Quaternion(dir, Vector(0.0, 1.0, 0.0)) * Quaternion(-M_PI_2, Vector(0.0, 1.0, 0.0)), 4.0); this->setAnimationSpeed(1.0f); } break; case LookAt: { if( this->getAnimation() != STAND) this->setAnimation(STAND, MD2_ANIM_LOOP); } break; case Shoot: if( this->getAnimation() != STAND) this->setAnimation(STAND, MD2_ANIM_LOOP); break; default: if( this->getAnimation() != STAND) this->setAnimation(STAND, MD2_ANIM_LOOP); break; } } else { this->setAnimation(STAND, MD2_ANIM_LOOP); } } void GenericNPC::walkTo(const Vector& coordinate) { GenericNPC::Anim anim; anim.v = coordinate; anim.type = Walk; anim.speed = 30.0f; if( this->behaviourList->empty()) { this->behaviourList->push_back(anim); this->initNPC(); } else this->behaviourList->push_back(anim); } void GenericNPC::walkTo(float x, float y, float z) { //printf("Walking to %f, %f, %f \n",x,y,z); this->walkTo(Vector(x,y,z)); } /* running functions */ void GenericNPC::runTo(const Vector& coordinate) { GenericNPC::Anim anim; anim.v = coordinate; anim.type = Run; anim.speed = 60.0f; if( this->behaviourList->empty()) { this->behaviourList->push_back(anim); this->initNPC(); } else this->behaviourList->push_back(anim); } void GenericNPC::runTo(float x, float y, float z) { this->runTo(Vector(x,y,z)); } /* couching functinos */ void GenericNPC::crouchTo(const Vector& coordinate) { GenericNPC::Anim anim; anim.v = coordinate; anim.type = Crouch; if( this->behaviourList->empty()) { this->behaviourList->push_back(anim); this->initNPC(); } else this->behaviourList->push_back(anim); } void GenericNPC::crouchTo(float x, float y, float z) { this->crouchTo(Vector(x,y,z)); } void GenericNPC::turnTo(float degreeInY) { GenericNPC::Anim anim; anim.q = Quaternion(Vector(0,1,0), degreeInY); anim.type = TurnTo; if( this->behaviourList->empty()) { this->behaviourList->push_back(anim); this->initNPC(); } else this->behaviourList->push_back(anim); } /** * lookat a world entity * @param worldEntity: the worldentity to look at */ void GenericNPC::lookAt(WorldEntity* worldEntity) { GenericNPC::Anim anim; anim.entity = worldEntity; anim.type = LookAt; if( this->behaviourList->empty()) { this->behaviourList->push_back(anim); this->initNPC(); } else this->behaviourList->push_back(anim); } /** * talk to a world entity and play a sound/music/voice * @param worldEntity: entity * @param dialogNr: sound nr to be played (from the xml load tags) */ void GenericNPC::talkTo(WorldEntity* worldEntity, int dialogNr) {} /** * world entity to shoot at if there is any weapon on the npc * @param entity: entity to shoot entity */ void GenericNPC::shootAt(WorldEntity* entity) {} /** * tick this world entity * @param time: time in seconds expirded since the last tick */ void GenericNPC::tick (float dt) { if( likely(this->getModel(0) != NULL)) ((InteractiveModel*)this->getModel(0))->tick(dt); if (!this->behaviourList->empty()) { GenericNPC::Anim currentAnimation = this->behaviourList->front(); switch( currentAnimation.type) { case Walk: { Vector dest = currentAnimation.v - this->getAbsCoor(); dest.y = 0.0f; if (dest.len() < .5) { this->nextStep(); } else { Vector move = dest.getNormalized() * currentAnimation.speed * dt; this->shiftCoor(move); } } break; case Run: { Vector dest = currentAnimation.v - this->getAbsCoor(); dest.y = 0.0f; if (dest.len() < .5) this->nextStep(); else { this->shiftCoor(dest.getNormalized() * currentAnimation.speed * dt); } } break; case Crouch: { Vector dest = currentAnimation.v - this->getAbsCoor(); dest.y = 0.0f; if (dest.len() < .5) this->nextStep(); else { this->shiftCoor(dest.getNormalized() * currentAnimation.speed * dt); } } break; case TurnTo: //Quaternion direction = this-> break; case LookAt: break; case Shoot: break; default: break; } } // physical falling of the player if( !this->isOnGround()) { this->fallVelocity += 300.0f * dt; //velocity -= Vector(0.0, 1.0, 0.0) * this->fallVelocity; // PRINTF(0)("%s is not on ground\n", this->getName()); this->shiftCoor(Vector(0, -this->fallVelocity * dt,0)); } else { this->fallVelocity = 0.0f; } } void GenericNPC::destroy(WorldEntity* killer) { int randi = (int)(5.0f * (float)rand()/(float)RAND_MAX); this->setAnimationSpeed(1.0f); if( randi == 1) this->setAnimation(DEATH_FALLBACK, MD2_ANIM_ONCE); else if( randi == 2) this->setAnimation(DEATH_FALLFORWARD, MD2_ANIM_ONCE); else if( randi == 3) this->setAnimation(DEATH_FALLBACKSLOW, MD2_ANIM_ONCE); else if( randi == 4) this->setAnimation(CROUCH_DEATH, MD2_ANIM_ONCE); else this->setAnimation(DEATH_FALLBACK, MD2_ANIM_ONCE); }