/* 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: Silvan Nellen co-programmer: Benjamin Knecht */ #include "playable.h" #include "key_mapper.h" #include "player.h" #include "state.h" #include "camera.h" #include "util/loading/load_param.h" #include "power_ups/weapon_power_up.h" #include "power_ups/param_power_up.h" #include "game_rules.h" #include "dot_emitter.h" #include "sprite_particles.h" #include "shared_network_data.h" #include "effects/explosion.h" #include "kill.cc" #include "shell_command.h" SHELL_COMMAND_STATIC(orxoWeapon, Playable, Playable::addSomeWeapons_CHEAT) ->setAlias("orxoWeapon"); Playable::Playable() : weaponMan(this), supportedPlaymodes(Playable::Full3D), playmode(Playable::Full3D) { this->setClassID(CL_PLAYABLE, "Playable"); PRINTF(4)("PLAYABLE INIT\n"); this->toList(OM_GROUP_01); // the reference to the Current Player is NULL, because we dont have one at the beginning. this->currentPlayer = NULL; this->bFire = false; this->oldFlags = 0; this->setSynchronized(true); this->score = 0; this->collider = NULL; this->enterRadius = 10.0f; this->bDead = false; this->subscribeReaction(CREngine::CR_PHYSICS_GROUND_WALK, CL_BSP_ENTITY); registerVar( new SynchronizeableInt( &score, &score, "score" ) ); registerVar( new SynchronizeableBool( &bFire, &bFire, "bFire", PERMISSION_OWNER)); } /** * @brief destroys the Playable */ Playable::~Playable() { // THE DERIVED CLASS MUST UNSUBSCRIBE THE PLAYER THROUGH // this->setPlayer(NULL); // IN ITS DESTRUCTOR. assert(this->currentPlayer == NULL); } /** * @brief loads Playable parameters onto the Playable * @param root the XML-root to load from */ void Playable::loadParams(const TiXmlElement* root) { WorldEntity::loadParams(root); LoadParam(root, "abs-dir", this, Playable, setPlayDirection); LoadParam(root, "enter-radius", this, Playable, setEnterRadius) .describe("The Distance one can enter the ship from."); } /** * @brief picks up a powerup * @param powerUp the PowerUp that should be picked. * @returns true on success (pickup was picked, false otherwise) * * This function also checks if the Pickup can be picked up by this Playable */ bool Playable::pickup(PowerUp* powerUp) { if(powerUp->isA(CL_WEAPON_POWER_UP)) { return dynamic_cast(powerUp)->process(&this->getWeaponManager()); } else if(powerUp->isA(CL_PARAM_POWER_UP)) { ParamPowerUp* ppu = dynamic_cast(powerUp); switch(ppu->getType()) { case POWERUP_PARAM_HEALTH: this->increaseHealth(ppu->getValue()); return true; case POWERUP_PARAM_MAX_HEALTH: this->increaseHealthMax(ppu->getValue()); return true; default: /// EVERYTHING THAT IS NOT HANDLED /// FIXME return false; } } return false; } /** * @brief adds a Weapon to the Playable. * @param weapon the Weapon to add. * @param configID the Configuration ID to add this weapon to. * @param slotID the slotID to add the Weapon to. */ bool Playable::addWeapon(Weapon* weapon, int configID, int slotID) { if(this->weaponMan.addWeapon(weapon, configID, slotID)) { this->weaponConfigChanged(); return true; } else { if (weapon != NULL) PRINTF(2)("Unable to add Weapon (%s::%s) to %s::%s\n", weapon->getClassName(), weapon->getName(), this->getClassName(), this->getName()); else PRINTF(2)("No weapon defined\n"); return false; } } /** * @brief removes a Weapon. * @param weapon the Weapon to remove. */ void Playable::removeWeapon(Weapon* weapon) { this->weaponMan.removeWeapon(weapon); this->weaponConfigChanged(); } /** * @brief jumps to the next WeaponConfiguration */ void Playable::nextWeaponConfig() { this->weaponMan.nextWeaponConfig(); this->weaponConfigChanged(); } /** * @brief moves to the last WeaponConfiguration */ void Playable::previousWeaponConfig() { this->weaponMan.previousWeaponConfig(); this->weaponConfigChanged(); } /** * @brief tells the Player, that the Weapon-Configuration has changed. * * TODO remove this * This function is needed, so that the WeponManager of this Playable can easily update the HUD */ void Playable::weaponConfigChanged() { if (this->currentPlayer != NULL) this->currentPlayer->weaponConfigChanged(); } /** * @brief a Cheat that gives us some Weapons */ void Playable::addSomeWeapons_CHEAT() { if (State::getPlayer() != NULL) { Playable* playable = State::getPlayer()->getPlayable(); if (playable != NULL) { PRINTF(2)("ADDING WEAPONS - you cheater\n"); playable->addWeapon(Weapon::createWeapon(CL_HYPERBLASTER)); playable->addWeapon(Weapon::createWeapon(CL_TURRET)); playable->addWeapon(Weapon::createWeapon(CL_AIMING_TURRET)); playable->addWeapon(Weapon::createWeapon(CL_CANNON)); playable->addWeapon(Weapon::createWeapon(CL_TARGETING_TURRET)); PRINTF(2)("ADDING WEAPONS FINISHED\n"); } } } /** * @brief subscribe to all events the controllable needs * @param player the player that shall controll this Playable * @returns false on error true otherwise. */ bool Playable::setPlayer(Player* player) { // if we already have a Player inside do nothing if (this->currentPlayer != NULL && player != NULL) { return false; } // eject the Player if player == NULL if (this->currentPlayer != NULL && player == NULL) { PRINTF(4)("Player gets ejected\n"); // unsubscibe all events. std::vector::iterator ev; for (ev = this->events.begin(); ev != events.end(); ev++) player->unsubscribeEvent(ES_GAME, (*ev)); // leave the entity this->leave(); // eject the current Player. Player* ejectPlayer = this->currentPlayer; this->currentPlayer = NULL; // eject the Player. ejectPlayer->setPlayable(NULL); return true; } // get the new Player inside if (this->currentPlayer == NULL && player != NULL) { PRINTF(4)("New Player gets inside\n"); this->currentPlayer = player; if (this->currentPlayer->getPlayable() != this) this->currentPlayer->setPlayable(this); /*EventHandler*/ std::vector::iterator ev; for (ev = this->events.begin(); ev != events.end(); ev++) player->subscribeEvent(ES_GAME, (*ev)); this->enter(); return true; } return false; } /** * @brief attaches the current Camera to this Playable * * this function can be derived, so that any Playable can make the attachment differently. */ void Playable::attachCamera() { State::getCameraNode()->setParentSoft(this); State::getCameraTargetNode()->setParentSoft(this); } /** * @brief detaches the Camera * @see void Playable::attachCamera() */ void Playable::detachCamera() { } /** * @brief sets the CameraMode. * @param cameraMode: the Mode to switch to. */ void Playable::setCameraMode(unsigned int cameraMode) { State::getCamera()->setViewMode((Camera::ViewMode)cameraMode); } /** * @brief sets the Playmode * @param playmode the Playmode * @returns true on success, false otherwise */ bool Playable::setPlaymode(Playable::Playmode playmode) { if (!this->playmodeSupported(playmode)) return false; else { this->enterPlaymode(playmode); this->playmode = playmode; return true; } } /** * @brief This function look, that the Playable rotates to the given rotation. * @param angle the Angle around * @param dirX directionX * @param dirY directionY * @param dirZ directionZ * @param speed how fast to turn there. */ void Playable::setPlayDirection(float angle, float dirX, float dirY, float dirZ, float speed) { this->setPlayDirection(Quaternion(angle, Vector(dirX, dirY, dirZ)), speed); } /** * @brief all Playable will enter the Playmode Differently, say here how to do it. * @param playmode the Playmode to enter. * * In this function all the actions that are required to enter the Playmode are described. * e.g: camera, rotation, wait cycle and so on... * * on enter of this function the playmode is still the old playmode. */ void Playable::enterPlaymode(Playable::Playmode playmode) { switch(playmode) { default: this->attachCamera(); break; case Playable::Horizontal: this->setCameraMode(Camera::ViewTop); break; case Playable::Vertical: this->setCameraMode(Camera::ViewLeft); break; case Playable::FromBehind: this->setCameraMode(Camera::ViewBehind); break; } } /** * @brief helps us colliding Playables * @param entity the Entity to collide * @param location where the collision occured. */ void Playable::collidesWith(WorldEntity* entity, const Vector& location) { if (entity == collider) return; collider = entity; if ( entity->isA(CL_PROJECTILE) && ( !State::isOnline() || SharedNetworkData::getInstance()->isGameServer() ) ) { this->decreaseHealth(entity->getHealth() *(float)rand()/(float)RAND_MAX); // EXTREME HACK if (this->getHealth() <= 0.0f) { // this->destory(); if( State::getGameRules() != NULL) State::getGameRules()->registerKill(Kill(entity, this)); } } } void Playable::respawn() { PRINTF(0)("Playable respawn\n"); // only if this is the spaceship of the player if( this == State::getPlayer()->getPlayable()) State::getGameRules()->onPlayerSpawn(); this->reset(); this->bDead = false; } void Playable::destroy() { Explosion::explode(dynamic_cast(this), Vector(1.0f, 1.0f, 1.0f)); if( !this->bDead) { PRINTF(0)("Playable dies\n"); // only if this is the spaceship of the player if (State::isOnline()) { if( this == State::getPlayer()->getPlayable()) State::getGameRules()->onPlayerDeath(); // this->toList(OM_GROUP_05); //HACK: moves the entity to an unknown place far far away: in the future, GameRules will look for that this->setAbsCoor(-2000.0, -2000.0, -2000.0); //explosion hack } this->bDead = true; } } /** * @brief add an event to the event list of events this Playable can capture * @param eventType the Type of event to add */ void Playable::registerEvent(int eventType) { this->events.push_back(eventType); if (this->currentPlayer != NULL) this->currentPlayer->subscribeEvent(ES_GAME, eventType); } /** * @brief remove an event to the event list this Playable can capture. * @param event the event to unregister. */ void Playable::unregisterEvent(int eventType) { std::vector::iterator rmEvent = std::find(this->events.begin(), this->events.end(), eventType); this->events.erase(rmEvent); if (this->currentPlayer != NULL) this->currentPlayer->unsubscribeEvent(ES_GAME, eventType); } /** * @brief ticks a Playable * @param dt: the passed time since the last Tick */ void Playable::tick(float dt) { this->weaponMan.tick(dt); if (this->bFire) weaponMan.fire(); } /** * @brief processes Playable events. * @param event the Captured Event. */ void Playable::process(const Event &event) { if( event.type == KeyMapper::PEV_FIRE1) this->bFire = event.bPressed; else if( event.type == KeyMapper::PEV_NEXT_WEAPON && event.bPressed) { this->nextWeaponConfig(); } else if ( event.type == KeyMapper::PEV_PREVIOUS_WEAPON && event.bPressed) this->previousWeaponConfig(); } /** * @brief converts a string into a Playable::Playmode. * @param playmode the string naming the Playable::Playmode to convert. * @returns the Playable::Playmode converted from playmode. */ Playable::Playmode Playable::stringToPlaymode(const std::string& playmode) { if (playmode == Playable::playmodeNames[0]) return Playable::Vertical; if (playmode == Playable::playmodeNames[1]) return Playable::Horizontal; if (playmode == Playable::playmodeNames[2]) return Playable::FromBehind; if (playmode == Playable::playmodeNames[3]) return Playable::Full3D; if (playmode == Playable::playmodeNames[4]) return Playable::FirstPerson; return Playable::Full3D; } /** * @brief converts a playmode into a string. * @param playmode the Playable::Playmode to convert. * @returns the String. */ const std::string& Playable::playmodeToString(Playable::Playmode playmode) { switch(playmode) { case Playable::Vertical: return Playable::playmodeNames[0]; case Playable::Horizontal: return Playable::playmodeNames[1]; case Playable::FromBehind: return Playable::playmodeNames[2]; case Playable::Full3D: return Playable::playmodeNames[3]; case Playable::FirstPerson: return Playable::playmodeNames[4]; default: return Playable::playmodeNames[3]; } } /** * @brief PlaymodeNames */ const std::string Playable::playmodeNames[] = { "Vertical", "Horizontal", "FromBehind", "Full3D", "FirstPerson" };