/* 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: Benjamin Grauer 2005-07-15: Benjamin Grauer: restructurating the entire Class */ #define DEBUG_SPECIAL_MODULE DEBUG_MODULE_WEAPON #include "weapon.h" #include "fast_factory.h" #include "world_entities/projectiles/projectile.h" #include "util/loading/resource_manager.h" #include "class_list.h" #include "util/loading/load_param.h" #include "state.h" #include "animation3d.h" #include "sound_source.h" #include "sound_buffer.h" #include "glgui_bar.h" //////////////////// // INITAILISATION // // SETTING VALUES // //////////////////// /** * standard constructor * * creates a new weapon */ Weapon::Weapon () { this->init(); } /** * standard deconstructor */ Weapon::~Weapon () { for (int i = 0; i < WS_STATE_COUNT; i++) if (this->animation[i] && ClassList::exists(animation[i], CL_ANIMATION)) //!< @todo this should check animation3D delete this->animation[i]; for (int i = 0; i < WA_ACTION_COUNT; i++) if (this->soundBuffers[i] != NULL && ClassList::exists(this->soundBuffers[i], CL_SOUND_BUFFER)) ResourceManager::getInstance()->unload(this->soundBuffers[i]); if (ClassList::exists(this->soundSource, CL_SOUND_SOURCE)) delete this->soundSource; } /** * initializes the Weapon with ALL default values * * This Sets the default values of the Weapon */ void Weapon::init() { this->currentState = WS_INACTIVE; //< Normaly the Weapon is Inactive this->requestedAction = WA_NONE; //< No action is requested by default this->stateDuration = 0.0; //< All the States have zero duration for (int i = 0; i < WS_STATE_COUNT; i++) //< Every State has: { this->times[i] = 0.0; //< An infinitesimal duration this->animation[i] = NULL; //< No animation } for (int i = 0; i < WA_ACTION_COUNT; i++) this->soundBuffers[i] = NULL; //< No Sounds this->soundSource = new SoundSource(this); //< Every Weapon has exacty one SoundSource. this->emissionPoint.setParent(this); //< One EmissionPoint, that is a PNode connected to the weapon. You can set this to the exitting point of the Projectiles this->emissionPoint.setName("EmissionPoint"); this->emissionPoint.addNodeFlags(PNODE_PROHIBIT_DELETE_WITH_PARENT); this->defaultTarget = NULL; //< Nothing is Targeted by default. this->projectile = CL_NULL; //< No Projectile Class is Connected to this weapon this->projectileFactory = NULL; //< No Factory generating Projectiles is selected. this->hideInactive = true; //< The Weapon will be hidden if it is inactive (by default) this->minCharge = 1.0; //< The minimum charge the Weapon can hold is 1 unit. this->maxCharge = 1.0; //< The maximum charge is also one unit. this->energy = 10; //< The secondary Buffer (before we have to reload) this->energyMax = 10.0; //< How much energy can be carried this->capability = WTYPE_ALL; //< The Weapon has all capabilities @see W_Capability. this->energyWidget = NULL; // set this object to be synchronized over network //this->setSynchronized(true); } /** * loads the Parameters of a Weapon * @param root the XML-Element to load the Weapons settings from */ void Weapon::loadParams(const TiXmlElement* root) { WorldEntity::loadParams(root); LoadParam(root, "projectile", this, Weapon, setProjectileTypeC) .describe("Sets the name of the Projectile to load onto the Entity"); LoadParam(root, "emission-point", this, Weapon, setEmissionPoint) .describe("Sets the Point of emission of this weapon"); LoadParam(root, "state-duration", this, Weapon, setStateDuration) .describe("Sets the duration of a given state (1: state-Name; 2: duration in seconds)"); LoadParam(root, "action-sound", this, Weapon, setActionSound) .describe("Sets a given sound to an action (1: action-Name; 2: name of the sound (relative to the Data-Path))"); } /** * sets the Projectile to use for this weapon. * @param projectile The ID of the Projectile to use * @returns true, if it was sucessfull, false on error * * be aware, that this function does not create Factories, as this is job of Projecitle/Bullet-classes. * What it does, is telling the Weapon what Projectiles it can Emit. */ void Weapon::setProjectileType(ClassID projectile) { if (projectile == CL_NULL) return; this->projectile = projectile; this->projectileFactory = FastFactory::searchFastFactory(projectile); if (this->projectileFactory == NULL) { PRINTF(1)("unable to find FastFactory for the Projectile.\n"); return; } else { // grabbing Parameters from the Projectile to have them at hand here. Projectile* pj = dynamic_cast(this->projectileFactory->resurrect()); this->minCharge = pj->getMinEnergy(); this->maxCharge = pj->getHealthMax(); this->chargeable = pj->isChageable(); this->projectileFactory->kill(pj); } } /** * @see bool Weapon::setProjectile(ClassID projectile) * @param projectile the Name of the Projectile. */ void Weapon::setProjectileTypeC(const char* projectile) { if (projectile == NULL) return; FastFactory* tmpFac = FastFactory::searchFastFactory(projectile); if (tmpFac != NULL) { this->setProjectileType(tmpFac->getStoredID()); } else { PRINTF(1)("Projectile %s does not exist for weapon %s\n", projectile, this->getName()); } } /** * prepares Projectiles of the Weapon * @param count how many Projectiles to create (they will be stored in the ProjectileFactory) */ void Weapon::prepareProjectiles(unsigned int count) { if (likely(this->projectileFactory != NULL)) projectileFactory->prepare(count); else PRINTF(2)("unable to create %d projectile for Weapon %s (%s)\n", count, this->getName(), this->getClassName()); } /** * resurects and returns a Projectile * @returns a Projectile on success, NULL on error * * errors: 1. (ProjectileFastFactory not Found) * 2. No more Projectiles availiable. */ Projectile* Weapon::getProjectile() { if (likely (this->projectileFactory != NULL)) { Projectile* pj = dynamic_cast(this->projectileFactory->resurrect()); pj->toList((OM_LIST)(this->getOMListNumber()+1)); return pj; } else { PRINTF(2)("No projectile defined for Weapon %s(%s) can't return any\n", this->getName(), this->getClassName()); return NULL; } } /** * sets the emissionPoint's relative position from the Weapon * @param point the Point relative to the mass-point of the Weapon */ void Weapon::setEmissionPoint(const Vector& point) { this->emissionPoint.setRelCoor(point); } /** * assigns a Sound-file to an action * @param action the action the sound should be assigned too * @param soundFile the soundFile's relative position to the data-directory (will be looked for by the ResourceManager) */ void Weapon::setActionSound(WeaponAction action, const char* soundFile) { if (action >= WA_ACTION_COUNT) return; if (this->soundBuffers[action] != NULL) ResourceManager::getInstance()->unload(this->soundBuffers[action]); else if (soundFile != NULL) { this->soundBuffers[action] = (SoundBuffer*)ResourceManager::getInstance()->load(soundFile, WAV); if (this->soundBuffers[action] != NULL) { PRINTF(4)("Loaded sound %s to action %s.\n", soundFile, actionToChar(action)); } else { PRINTF(2)("Failed to load sound %s to %s.\n.", soundFile, actionToChar(action)); } } else this->soundBuffers[action] = NULL; } /** * creates/returns an Animation3D for a certain State. * @param state what State should the Animation be created/returned for * @param node the node this Animation should apply to. (NULL is fine if the animation was already created) * @returns The created animation.Animation(), NULL on error (or if the animation does not yet exist). * * This function does only generate the Animation Object, and if set it will * automatically be executed, when a certain State is reached. * What this does not do, is set keyframes, you have to operate on the returned animation. */ Animation3D* Weapon::getAnimation(WeaponState state, PNode* node) { if (state >= WS_STATE_COUNT) // if the state is not known return NULL; if (unlikely(this->animation[state] == NULL)) // if the animation does not exist yet create it. { if (likely(node != NULL)) return this->animation[state] = new Animation3D(node); else { PRINTF(2)("Node not defined for the Creation of the 3D-animation of state %s\n", stateToChar(state)); return NULL; } } else return this->animation[state]; } GLGuiWidget* Weapon::getEnergyWidget() { if (this->energyWidget == NULL) { this->energyWidget = new GLGuiBar; this->energyWidget->setSize2D( 20, 100); this->energyWidget->setMaximum(this->getEnergyMax()); this->energyWidget->setValue(this->getEnergy()); } return this->energyWidget; } void Weapon::updateWidgets() { if (this->energyWidget != NULL) { this->energyWidget->setMaximum(this->energyMax); this->energyWidget->setValue(this->energy); } } ///////////////// // EXECUTION // // GAME ACTION // ///////////////// /** * request an action that should be executed, * @param action the next action to take * * This function must be called instead of the actions (like fire/reload...) * to make all the checks needed to have a usefull WeaponSystem. */ void Weapon::requestAction(WeaponAction action) { if (likely(this->isActive())) { if (this->requestedAction != WA_NONE) return; PRINTF(5)("next action will be %s in %f seconds\n", actionToChar(action), this->stateDuration); this->requestedAction = action; } //else else if (unlikely(action == WA_ACTIVATE)) { this->currentState = WS_ACTIVATING; this->requestedAction = WA_ACTIVATE; } } /** * adds energy to the Weapon * @param energyToAdd The amount of energy * @returns the amount of energy we did not pick up, because the weapon is already full */ float Weapon::increaseEnergy(float energyToAdd) { float maxAddEnergy = this->energyMax - this->energy; if (maxAddEnergy >= energyToAdd) { this->energy += energyToAdd; return 0.0; } else { this->energy += maxAddEnergy; return energyToAdd - maxAddEnergy; } } //////////////////////////////////////////////////////////// // WEAPON INTERNALS // // These are functions, that no other Weapon should over- // // write. No class has direct Access to them, as it is // // quite a complicated process, handling a Weapon from // // the outside // //////////////////////////////////////////////////////////// /** * executes an action, and with it starts a new State. * @return true, if it worked, false otherwise * * This function checks, wheter the possibility of executing an action is valid, * and does all the necessary stuff, to execute them. If an action does not succeed, * it tries to go around it. (ex. shoot->noAmo->reload()->wait until shoot comes again) */ bool Weapon::execute() { #if DEBUG > 4 PRINTF(4)("trying to execute action %s\n", actionToChar(this->requestedAction)); this->debug(); #endif WeaponAction action = this->requestedAction; this->requestedAction = WA_NONE; switch (action) { case WA_SHOOT: return this->fireW(); break; case WA_CHARGE: return this->chargeW(); break; case WA_RELOAD: return this->reloadW(); break; case WA_DEACTIVATE: return this->deactivateW(); break; case WA_ACTIVATE: return this->activateW(); break; } } /** * checks and activates the Weapon. * @return true on success. */ bool Weapon::activateW() { // if (this->currentState == WS_INACTIVE) { // play Sound if (likely(this->soundBuffers[WA_ACTIVATE] != NULL)) this->soundSource->play(this->soundBuffers[WA_ACTIVATE]); this->updateWidgets(); // activate PRINTF(4)("Activating the Weapon %s\n", this->getName()); this->activate(); // setting up for next action this->enterState(WS_ACTIVATING); } } /** * checks and deactivates the Weapon * @return true on success. */ bool Weapon::deactivateW() { // if (this->currentState != WS_INACTIVE) { PRINTF(4)("Deactivating the Weapon %s\n", this->getName()); // play Sound if (this->soundBuffers[WA_DEACTIVATE] != NULL) this->soundSource->play(this->soundBuffers[WA_DEACTIVATE]); // deactivate this->deactivate(); this->enterState(WS_DEACTIVATING); } } /** * checks and charges the Weapon * @return true on success. */ bool Weapon::chargeW() { if ( this->currentState != WS_INACTIVE && this->energy >= this->minCharge) { // playing Sound if (this->soundBuffers[WA_CHARGE] != NULL) this->soundSource->play(this->soundBuffers[WA_CHARGE]); // charge this->charge(); // setting up for the next state this->enterState(WS_CHARGING); } else // deactivate the Weapon if we do not have enough energy { this->requestAction(WA_RELOAD); } } /** * checks and fires the Weapon * @return true on success. */ bool Weapon::fireW() { //if (likely(this->currentState != WS_INACTIVE)) if (this->minCharge <= this->energy) { // playing Sound if (this->soundBuffers[WA_SHOOT] != NULL) this->soundSource->play(this->soundBuffers[WA_SHOOT]); this->updateWidgets(); // fire this->energy -= this->minCharge; this->fire(); // setting up for the next state this->enterState(WS_SHOOTING); } else // reload if we still have the charge { this->requestAction(WA_RELOAD); this->execute(); } } /** * checks and Reloads the Weapon * @return true on success. */ bool Weapon::reloadW() { PRINTF(4)("Reloading Weapon %s\n", this->getName()); if (this->ammoContainer.get() != NULL && unlikely(this->energy + this->ammoContainer->getStoredEnergy() < this->minCharge)) { this->requestAction(WA_DEACTIVATE); this->execute(); return false; } if (this->soundBuffers[WA_RELOAD] != NULL) this->soundSource->play(this->soundBuffers[WA_RELOAD]); if (this->ammoContainer.get() != NULL) this->ammoContainer->fillWeapon(this); else { this->energy = this->energyMax; } this->updateWidgets(); this->reload(); this->enterState(WS_RELOADING); } /** * enters the requested State, plays back animations updates the timing. * @param state the state to enter. */ inline void Weapon::enterState(WeaponState state) { PRINTF(4)("ENTERING STATE %s\n", stateToChar(state)); // playing animation if availiable if (likely(this->animation[state] != NULL)) this->animation[state]->replay(); this->stateDuration += this->times[state]; this->currentState = state; } /////////////////// // WORLD-ENTITY // // FUNCTIONALITY // /////////////////// /** * tick signal for time dependent/driven stuff */ bool Weapon::tickW(float dt) { //printf("%s ", stateToChar(this->currentState)); // setting up the timing properties this->stateDuration -= dt; if (this->stateDuration <= 0.0) { if (unlikely (this->currentState == WS_DEACTIVATING)) { this->currentState = WS_INACTIVE; return false; } else this->currentState = WS_IDLE; if (this->requestedAction != WA_NONE) { this->stateDuration = -dt; this->execute(); } } return true; } ////////////////////// // HELPER FUNCTIONS // ////////////////////// /** * checks wether all the Weapons functions are valid, and if it is possible to go to action with it. * @todo IMPLEMENT the Weapons Check */ bool Weapon::check() const { bool retVal = true; // if (this->projectile == NULL) { PRINTF(1)("There was no projectile assigned to the Weapon.\n"); retVal = false; } return retVal; } /** * some nice debugging information about this Weapon */ void Weapon::debug() const { PRINT(0)("Weapon-Debug %s, state: %s (duration: %fs), nextAction: %s\n", this->getName(), Weapon::stateToChar(this->currentState), this->stateDuration, Weapon::actionToChar(requestedAction)); PRINT(0)("Energy: max: %f; current: %f; chargeMin: %f, chargeMax %f\n", this->energyMax, this->energy, this->minCharge, this->maxCharge); } //////////////////////////////////////////////////////// // static Definitions (transormators for readability) // //////////////////////////////////////////////////////// /** * Converts a String into an Action. * @param action the String input holding the Action. * @return The Action if known, WA_NONE otherwise. */ WeaponAction Weapon::charToAction(const char* action) { if (!strcmp(action, "none")) return WA_NONE; else if (!strcmp(action, "shoot")) return WA_SHOOT; else if (!strcmp(action, "charge")) return WA_CHARGE; else if (!strcmp(action, "reload")) return WA_RELOAD; else if (!strcmp(action, "acitvate")) return WA_ACTIVATE; else if (!strcmp(action, "deactivate")) return WA_DEACTIVATE; else if (!strcmp(action, "special1")) return WA_SPECIAL1; else { PRINTF(2)("action %s could not be identified.\n", action); return WA_NONE; } } /** * converts an action into a String * @param action the action to convert * @return a String matching the name of the action */ const char* Weapon::actionToChar(WeaponAction action) { switch (action) { case WA_SHOOT: return "shoot"; break; case WA_CHARGE: return "charge"; break; case WA_RELOAD: return "reload"; break; case WA_ACTIVATE: return "activate"; break; case WA_DEACTIVATE: return "deactivate"; break; case WA_SPECIAL1: return "special1"; break; default: return "none"; break; } } /** * Converts a String into a State. * @param state the String input holding the State. * @return The State if known, WS_NONE otherwise. */ WeaponState Weapon::charToState(const char* state) { if (!strcmp(state, "none")) return WS_NONE; else if (!strcmp(state, "shooting")) return WS_SHOOTING; else if (!strcmp(state, "charging")) return WS_CHARGING; else if (!strcmp(state, "reloading")) return WS_RELOADING; else if (!strcmp(state, "activating")) return WS_ACTIVATING; else if (!strcmp(state, "deactivating")) return WS_DEACTIVATING; else if (!strcmp(state, "inactive")) return WS_INACTIVE; else if (!strcmp(state, "idle")) return WS_IDLE; else { PRINTF(2)("state %s could not be identified.\n", state); return WS_NONE; } } /** * converts a State into a String * @param state the state to convert * @return a String matching the name of the state */ const char* Weapon::stateToChar(WeaponState state) { switch (state) { case WS_SHOOTING: return "shooting"; break; case WS_CHARGING: return "charging"; break; case WS_RELOADING: return "reloading"; break; case WS_ACTIVATING: return "activating"; break; case WS_DEACTIVATING: return "deactivating"; break; case WS_IDLE: return "idle"; break; case WS_INACTIVE: return "inactive"; break; default: return "none"; break; } }