/* * ORXONOX - the hottest 3D action shooter ever to exist * > www.orxonox.net < * * * License notice: * * 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 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: * Martin Polak * Fabian 'x3n' Landau * Co-authors: * Johannes Sager * */ #include "WeaponMode.h" #include "core/CoreIncludes.h" #include "core/XMLPort.h" #include "controllers/Controller.h" #include "worldentities/pawns/Pawn.h" #include "Munition.h" #include "Weapon.h" #include "WeaponPack.h" #include "WeaponSystem.h" #include "WeaponSlot.h" #include "sound/WorldSound.h" namespace orxonox { RegisterAbstractClass(WeaponMode).inheritsFrom(); WeaponMode::WeaponMode(Context* context) : BaseObject(context) { RegisterObject(WeaponMode); this->weapon_ = nullptr; this->mode_ = WeaponSystem::WEAPON_MODE_UNASSIGNED; this->munition_ = nullptr; this->initialMunition_ = 0; this->initialMagazines_ = 0; this->munitionPerShot_ = 1; this->reloadTime_ = 0.25; this->bReloading_ = false; this->bAutoReload_ = true; this->bParallelReload_ = true; this->chargeable_ = false; // most weapons are not chargeable this->charges_ = 0; // always start with no charges this->maxCharges_ = 100; // default maximum charges one can have are 100 this->reloadTimer_.setTimer(0.0f, false, createExecutor(createFunctor(&WeaponMode::reloaded, this))); this->reloadTimer_.stopTimer(); this->damage_ = 0; this->healthdamage_ = 0; this->shielddamage_ = 0; this->muzzleOffset_ = Vector3::ZERO; this->muzzlePosition_ = Vector3::ZERO; this->muzzleOrientation_ = Quaternion::IDENTITY; hudImageString_ = "Orxonox/WSHUD_WM_Unknown"; this->fireSoundPath_ = BLANKSTRING; this->fireSoundVolume_ = 1.0; this->fireSounds_.clear(); this->reloadSoundPath_ = BLANKSTRING; this->reloadSoundVolume_ = 1.0; this->reloadSound_ = nullptr; } WeaponMode::~WeaponMode() { if (this->isInitialized()) { for (auto sound : fireSounds_) { sound->destroy(); } } } void WeaponMode::XMLPort(Element& xmlelement, XMLPort::Mode mode) { SUPER(WeaponMode, XMLPort, xmlelement, mode); XMLPortParam(WeaponMode, "mode", setMode, getMode, xmlelement, mode); XMLPortParam(WeaponMode, "munitiontype", setMunitionName, getMunitionName, xmlelement, mode); XMLPortParam(WeaponMode, "initialmunition", setInitialMunition, getInitialMunition, xmlelement, mode); XMLPortParam(WeaponMode, "initialmagazines", setInitialMagazines, getInitialMagazines, xmlelement, mode); XMLPortParam(WeaponMode, "munitionpershot", setMunitionPerShot, getMunitionPerShot, xmlelement, mode); XMLPortParam(WeaponMode, "reloadtime", setReloadTime, getReloadTime, xmlelement, mode); XMLPortParam(WeaponMode, "autoreload", setAutoReload, getAutoReload, xmlelement, mode).description("If true, the weapon reloads the magazine automatically"); XMLPortParam(WeaponMode, "parallelreload", setParallelReload, getParallelReload, xmlelement, mode).description("If true, the weapon reloads in parallel to the magazine reloading"); XMLPortParam(WeaponMode, "damage", setDamage, getDamage, xmlelement, mode); XMLPortParam(WeaponMode, "healthdamage", setHealthDamage, getHealthDamage, xmlelement, mode); XMLPortParam(WeaponMode, "shielddamage", setShieldDamage, getShieldDamage, xmlelement, mode); XMLPortParam(WeaponMode, "muzzleoffset", setMuzzleOffset, getMuzzleOffset, xmlelement, mode); } bool WeaponMode::fire(float* reloadTime) { (*reloadTime) = this->reloadTime_; // Fireing is only possible if this weapon mode is not reloading and there is enough munition if (!this->bReloading_ && this->munition_ && this->munition_->takeMunition(this->munitionPerShot_, this)) { float tempReloadtime = this->reloadTime_; if (this->bAutoReload_ && this->munition_->needReload(this)) { if (this->munition_->reload(this)) { // If true, the weapon reloads in parallel to the magazine reloading if (this->bParallelReload_) { // The time needed to reload is the maximum of the reload time of the weapon mode and the magazine. tempReloadtime = std::max(this->reloadTime_, this->munition_->getReloadTime()); } else { // The time needed to reload is the sum of the reload time of the weapon mode and the magazine. tempReloadtime = this->reloadTime_ + this->munition_->getReloadTime(); } playReloadSound(); } } // For stacked munition, a reload sound is played after every fired projectile if (this->munition_->getMunitionDeployment() == MunitionDeployment::Stack) { playReloadSound(); } // Mark this weapon mode as reloading and start the reload timer this->bReloading_ = true; this->reloadTimer_.setInterval(tempReloadtime); this->reloadTimer_.startTimer(); // Play the fire sound and fire the weapon mode this->playFireSound(); this->fire(); return true; } else { return false; } } bool WeaponMode::push(float* reloadTime) { if( this->chargeable_) // chargeable weapons are supposed to charge on push { this->munition_ = this->weapon_->getWeaponPack()->getWeaponSystem()->getMunition(&this->munitiontype_); // updates the pointer to the munition(which we use in the next step) if(this->charges_ < this->maxCharges_ && this->bReloading_ == false && this->munition_->canTakeMunition(1, this)) // charges up unless: { // - we are fully charged this->charges_ += 1; // - we are reloading } // - we have no munition return false; } else // normal (not chargeable) weapons are supposed to fire on push { return fire(reloadTime); } } bool WeaponMode::release(float* reloadTime) { if( this->chargeable_) // chargeable weapons are supposed to fire on release { return fire(reloadTime); } else // normal (not chargeable) weapons should do nothing on release { return false; } } bool WeaponMode::reload() { if (this->munition_ && this->munition_->reload(this)) { if (!this->bParallelReload_) { this->bReloading_ = true; this->reloadTimer_.setInterval(this->reloadTime_ + this->munition_->getReloadTime()); this->reloadTimer_.startTimer(); } return true; } return false; } void WeaponMode::setMunitionType(Identifier* identifier) { this->munitionname_ = identifier->getName(); this->munitiontype_ = identifier; this->updateMunition(); } void WeaponMode::setMunitionName(const std::string& munitionname) { this->munitionname_ = munitionname; Identifier* identifier = ClassByString(this->munitionname_); if (identifier) this->munitiontype_ = identifier; else orxout(internal_warning) << "No munition class defined in WeaponMode " << this->getName() << endl; this->updateMunition(); } void WeaponMode::updateMunition() { if (this->munitiontype_ && this->weapon_ && this->weapon_->getWeaponPack() && this->weapon_->getWeaponPack()->getWeaponSystem()) { this->munition_ = this->weapon_->getWeaponPack()->getWeaponSystem()->getMunition(&this->munitiontype_); if (this->munition_) { // Add the initial magazines this->munition_->addMagazines(this->initialMagazines_); // Maybe we have to reload (if this munition is used the first time or if there weren't any magazines available before) if (this->munition_->needReload(this)) this->munition_->reload(this, false); // Add the initial munition if (this->initialMunition_ > 0 && this->munition_->getNumMunitionInCurrentMagazine(this) == this->munition_->getMaxMunitionPerMagazine()) { // The current magazine is still full, so let's just add another magazine to // the stack and reduce the current magazine to the given amount of munition unsigned int initialmunition = this->initialMunition_; if (initialmunition > this->munition_->getMaxMunitionPerMagazine()) initialmunition = this->munition_->getMaxMunitionPerMagazine(); this->munition_->takeMunition(this->munition_->getMaxMunitionPerMagazine() - initialmunition, this); this->munition_->addMagazines(1); } else { // The current magazine isn't full, add the munition directly this->munition_->addMunition(this->initialMunition_); } } } else { this->munition_ = nullptr; } } void WeaponMode::reloaded() { this->bReloading_ = false; } void WeaponMode::computeMuzzleParameters(const Vector3& target) { if (this->weapon_) { this->muzzlePosition_ = this->weapon_->getWorldPosition() + this->weapon_->getWorldOrientation() * this->muzzleOffset_; Vector3 muzzleDirection; muzzleDirection = target - this->muzzlePosition_; this->muzzleOrientation_ = (this->weapon_->getWorldOrientation() * WorldEntity::FRONT).getRotationTo(muzzleDirection) * this->weapon_->getWorldOrientation(); } else { this->muzzlePosition_ = this->muzzleOffset_; this->muzzleOrientation_ = Quaternion::IDENTITY; } } Vector3 WeaponMode::getMuzzleDirection() const { if (this->weapon_) return (this->getMuzzleOrientation() * WorldEntity::FRONT); else return WorldEntity::FRONT; } void WeaponMode::setFireSound(const std::string& soundPath, const float soundVolume) { fireSoundPath_ = soundPath; fireSoundVolume_ = soundVolume; } const std::string& WeaponMode::getFireSound() { return fireSoundPath_; } void WeaponMode::setReloadSound(const std::string& soundPath, const float soundVolume) { reloadSoundPath_ = soundPath; reloadSoundVolume_ = soundVolume; } const std::string& WeaponMode::getReloadSound() { return reloadSoundPath_; } void WeaponMode::playFireSound() { WorldSound* unusedSound = nullptr; if (!GameMode::isMaster()) { return; } // If no sound path or no weapon was specified, then no sound is played. if (fireSoundPath_ == BLANKSTRING || !this->getWeapon()) { return; } // Search in the sound list for a WorldSound that may be used. It must be an idle WorldSound instance (i.e. it is not playing a sound now) for (auto sound : fireSounds_) { if( sound && !(sound->isPlaying())) { // Unused sound found unusedSound = sound; break; } } // If no unused sound was found, create a new one and add it to the list if (!unusedSound) { unusedSound = new WorldSound(this->getContext()); fireSounds_.push_back(unusedSound); unusedSound->setLooping(false); unusedSound->setSource(fireSoundPath_); unusedSound->setVolume(fireSoundVolume_); this->getWeapon()->attach(unusedSound); } // Play the fire sound unusedSound->play(); } void WeaponMode::playReloadSound() { if (!GameMode::isMaster()) { return; } // If no sound path or no weapon was specified, then no sound is played. if (reloadSoundPath_ == BLANKSTRING || !this->getWeapon()) { return; } // Create a reload WorldSound if not done yet if (!reloadSound_) { reloadSound_ = new WorldSound(this->getContext()); reloadSound_->setSource(reloadSoundPath_); reloadSound_->setVolume(reloadSoundVolume_); this->getWeapon()->attach(reloadSound_); } // Play the reload sound reloadSound_->play(); } }