/* * 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. * * Authors: * Martin Polak * Fabian 'x3n' Landau * Co-authors: * Fabien Vultier * */ #include "Munition.h" #include "core/CoreIncludes.h" #include "core/command/Executor.h" #include "core/XMLPort.h" namespace orxonox { RegisterClass(Munition); Munition::Munition(Context* context) : BaseObject(context) { RegisterObject(Munition); this->maxMunitionPerMagazine_ = 10; this->maxMagazines_ = 10; this->unassignedMagazines_ = 10; this->deployment_ = MunitionDeployment::Stack; this->bAllowMunitionRefilling_ = true; this->bAllowMultiMunitionRemovementUnderflow_ = true; this->reloadTime_ = 0.5f; this->lastFilledWeaponMode_ = nullptr; } Munition::~Munition() { for (const auto& mapEntry : this->assignedMagazines_) delete mapEntry.second; } void Munition::XMLPort(Element& xmlelement, XMLPort::Mode mode) { SUPER(Munition, XMLPort, xmlelement, mode); XMLPortParam(Munition, "initialmagazines", setNumMagazines, getNumMagazines, xmlelement, mode); XMLPortParam(Munition, "maxmagazines", setMaxMagazines, getMaxMagazines, xmlelement, mode); XMLPortParam(Munition, "munitionpermagazine", setMaxMunitionPerMagazine, getMaxMunitionPerMagazine, xmlelement, mode); } Munition::Magazine* Munition::getMagazine(WeaponMode* user) const { if (deployment_ == MunitionDeployment::Separate) { // For separated magazines we definitively need a given user if (!user) return nullptr; // Use the map to get the magazine assigned to the given user std::map::const_iterator it = this->assignedMagazines_.find(user); if (it != this->assignedMagazines_.end()) return it->second; } else { // We don't use separate magazines for each user, so just take the first magazine if (this->assignedMagazines_.size() > 0) return this->assignedMagazines_.begin()->second; } return nullptr; } unsigned int Munition::getNumMunition(WeaponMode* user) const { Magazine* magazine = this->getMagazine(user); if (magazine) { if (deployment_ == MunitionDeployment::Stack) // With stacked munition every magazine contributes to the total amount return this->maxMunitionPerMagazine_ * this->unassignedMagazines_ + magazine->munition_; else // Wihtout stacked munition we just consider the current magazine return magazine->munition_; } return 0; } unsigned int Munition::getNumMunitionInCurrentMagazine(WeaponMode* user) const { // In contrast to getNumMunition() we really just consider the current magazine, even if we're stacking munition Magazine* magazine = this->getMagazine(user); if (magazine) return magazine->munition_; else return 0; } unsigned int Munition::getNumMagazines() const { if (deployment_ == MunitionDeployment::Stack) { // If we stack munition and the current magazine is still full, it counts too Magazine* magazine = this->getMagazine(0); if (magazine && magazine->munition_ == this->maxMunitionPerMagazine_) return this->unassignedMagazines_ + 1; } return this->unassignedMagazines_; } void Munition::setNumMagazines(unsigned int numMagazines) { this->unassignedMagazines_ = numMagazines; } unsigned int Munition::getMaxMunition() const { if (deployment_ == MunitionDeployment::Stack) return this->maxMunitionPerMagazine_ * this->maxMagazines_; else return this->maxMunitionPerMagazine_; } bool Munition::canTakeMunition(unsigned int amount, WeaponMode* user) const { Magazine* magazine = this->getMagazine(user); if (magazine && magazine->bLoaded_) { unsigned int munition = magazine->munition_; // If we stack munition, we don't care about the current magazine - we just need enough munition in total if (deployment_ == MunitionDeployment::Stack) { munition += this->maxMunitionPerMagazine_ * this->unassignedMagazines_; } if (munition == 0) { // Absolutely no munition - no chance to take munition return false; } else if (this->bAllowMultiMunitionRemovementUnderflow_) { // We're not empty AND we allow underflow, so this will always work return true; } else { // We don't allow underflow, so we have to check the amount return (munition >= amount); } } return false; } bool Munition::takeMunition(unsigned int amount, WeaponMode* user) { if (!this->canTakeMunition(amount, user)) return false; Magazine* magazine = this->getMagazine(user); if (magazine && magazine->bLoaded_) { if (magazine->munition_ >= amount) { // Enough munition magazine->munition_ -= amount; return true; } else { // Not enough munition if (deployment_ == MunitionDeployment::Stack) { // We stack munition, so just take what we can and then load the next magazine amount -= magazine->munition_; magazine->munition_ = 0; if (this->reload(nullptr)) // Successfully reloaded, continue recursively return this->takeMunition(amount, 0); else // We don't have more magazines, so let's just hope we allow underflow return this->bAllowMultiMunitionRemovementUnderflow_; } else { // We don't stack, so we can only take munition if this is allowed if (magazine->munition_ > 0 && this->bAllowMultiMunitionRemovementUnderflow_) { magazine->munition_ = 0; return true; } } } } return false; } bool Munition::canReload() const { // As long as we have enough magazines (and don't stack munition) we can reload return (this->unassignedMagazines_ > 0 && deployment_ != MunitionDeployment::Stack); } bool Munition::needReload(WeaponMode* user) const { Magazine* magazine = this->getMagazine(user); if (magazine) { if (deployment_ == MunitionDeployment::Stack) // With stacked munition, we never have to reload return false; else // We need to reload if an already loaded magazine is empty return (magazine->bLoaded_ && magazine->munition_ == 0); } else // No magazine - we definitively need to reload return true; } bool Munition::reload(WeaponMode* user, bool bUseReloadTime) { // Don't reload if we're already reloading Magazine* magazine = this->getMagazine(user); if (magazine && !magazine->bLoaded_) return false; // Check if we actually can reload if (this->unassignedMagazines_ == 0) return false; // If we use separate magazines for each user, we definitively need a user given if (deployment_ == MunitionDeployment::Separate && !user) return false; // If we don't use separate magazines, set user to 0 if (deployment_ != MunitionDeployment::Separate) { user = nullptr; } // Remove the current magazine for the given user std::map::iterator it = this->assignedMagazines_.find(user); if (it != this->assignedMagazines_.end()) { if (it->first == lastFilledWeaponMode_) { lastFilledWeaponMode_ = nullptr; } delete it->second; this->assignedMagazines_.erase(it); } // Load a new magazine this->assignedMagazines_[user] = new Magazine(this, bUseReloadTime); this->unassignedMagazines_--; return true; } bool Munition::canAddMunition(unsigned int amount) const { // TODO: 'amount' is not used if (!this->bAllowMunitionRefilling_) return false; if (deployment_ == MunitionDeployment::Stack) { // If we stack munition, we can always add munition until we reach the limit return (this->getNumMunition(0) < this->getMaxMunition()); } else { // Return true if any of the current magazines is not full (loading counts as full although it returns 0 munition) for (const auto& mapEntry : this->assignedMagazines_) if (mapEntry.second->munition_ < this->maxMunitionPerMagazine_ && mapEntry.second->bLoaded_) return true; } return false; } bool Munition::addMunition(unsigned int amount) { if (!this->canAddMunition(amount)) return false; if (deployment_ == MunitionDeployment::Stack) { // Stacking munition means, if a magazine gets full, the munition adds to a new magazine Magazine* magazine = this->getMagazine(nullptr); if (magazine) { // Add the whole amount magazine->munition_ += amount; // Add new magazines if the current magazine is overfull while (magazine->munition_ > this->maxMunitionPerMagazine_) { magazine->munition_ -= this->maxMunitionPerMagazine_; this->unassignedMagazines_++; } // If we reached the limit, reduce both magazines and munition to the maximum if (this->unassignedMagazines_ >= this->maxMagazines_) { this->unassignedMagazines_ = this->maxMagazines_; magazine->munition_ = this->maxMunitionPerMagazine_; } return true; } // Something went wrong return false; } else { std::map::iterator it; // If the pointer to the weapon mode whose magazine got munition added to is nullptr, then set the iterator to the beginning of the map // Otherwise set it to the next weapon mode if (lastFilledWeaponMode_ == nullptr) { it = this->assignedMagazines_.begin(); } else { it = this->assignedMagazines_.find(lastFilledWeaponMode_); ++ it; } // Share the munition equally to the current magazines bool firstLoop = true; while (amount > 0) { bool change = false; while (it != this->assignedMagazines_.end()) { // Add munition if the magazine isn't full (but only to loaded magazines) if (amount > 0 && it->second->munition_ < this->maxMunitionPerMagazine_ && it->second->bLoaded_) { it->second->munition_++; amount--; lastFilledWeaponMode_ = it->first; change = true; } ++it; } // If there was no change in a loop, all magazines are full (or locked due to loading) // Because the first loop does not start at the beginning of the map we have to treat is separately if (!change && !firstLoop) { break; } it = this->assignedMagazines_.begin(); firstLoop = false; } return true; } } bool Munition::canAddMagazines(unsigned int amount) const { // TODO: 'amount' is not used if (deployment_ == MunitionDeployment::Stack) // If we stack munition, we can always add new magazines because they contribute directly to the munition return (this->getNumMunition(0) < this->getMaxMunition()); else // If we don't stack munition, we're more limited return ((this->assignedMagazines_.size() + this->unassignedMagazines_) < this->maxMagazines_); } unsigned int Munition::addMagazines(unsigned int amount) { unsigned int addedMagazines = 0; if (!this->canAddMagazines(amount)) return 0; // Calculate how many magazines are needed int needed_magazines = this->maxMagazines_ - this->unassignedMagazines_ - this->assignedMagazines_.size(); // If zero or less magazines are needed, we definitively don't need more magazines (unless we stack munition - then a magazine contributes directly to the munition) if (needed_magazines <= 0 && deployment_ != MunitionDeployment::Stack) return 0; if (amount <= static_cast(needed_magazines)) { // We need more magazines than we get, so just add them this->unassignedMagazines_ += amount; addedMagazines = amount; } else { // We get more magazines than we need, so just add the needed amount this->unassignedMagazines_ += needed_magazines; addedMagazines = needed_magazines; if (deployment_ == MunitionDeployment::Stack) { // We stack munition, so the additional amount contributes directly to the munition of the current magazine Magazine* magazine = this->getMagazine(0); if (magazine) magazine->munition_ = this->maxMunitionPerMagazine_; } } // Reload as many empty magazines as possible // Double loop and break is needed because the reload function changes the assigned magazines. This may confuse the iterator. for (unsigned int i = 0; i < addedMagazines; ++i) { for (const auto& mapEntry : this->assignedMagazines_) { if (needReload(mapEntry.first)) { reload(mapEntry.first); break; } } } return addedMagazines; } bool Munition::canRemoveMagazines(unsigned int amount) const { if (deployment_ == MunitionDeployment::Stack) { if (this->unassignedMagazines_ >= amount) { // We have enough magazines return true; } else if (this->unassignedMagazines_ == amount - 1) { // We lack one magazine, check if the current magazine is still full, if yes we're fine Magazine* magazine = this->getMagazine(0); if (magazine && magazine->munition_ == this->maxMunitionPerMagazine_) return true; } else { // We don't have enough magazines return false; } } else { // In case we're not stacking munition, just check the number of magazines return (this->unassignedMagazines_ >= amount); } return false; } bool Munition::removeMagazines(unsigned int amount) { if (!this->canRemoveMagazines(amount)) return false; if (this->unassignedMagazines_ >= amount) { // We have enough magazines, just remove the amount this->unassignedMagazines_ -= amount; } else if (deployment_ == MunitionDeployment::Stack) { // We don't have enough magazines, but we're stacking munition, so additionally remove the bullets from the current magazine this->unassignedMagazines_ = 0; Magazine* magazine = this->getMagazine(0); if (magazine) magazine->munition_ = 0; } return true; } bool Munition::dropMagazine(WeaponMode* user) { // If we use separate magazines, we need a user if (deployment_ == MunitionDeployment::Separate && !user) return false; // If we don't use separate magazines, set user to 0 if (deployment_ != MunitionDeployment::Separate) user = nullptr; // Remove the current magazine for the given user std::map::iterator it = this->assignedMagazines_.find(user); if (it != this->assignedMagazines_.end()) { if (it->first == lastFilledWeaponMode_) { lastFilledWeaponMode_ = nullptr; } delete it->second; this->assignedMagazines_.erase(it); return true; } return false; } ///////////////////// // Magazine struct // ///////////////////// Munition::Magazine::Magazine(Munition* munition, bool bUseReloadTime) { this->munition_ = 0; this->bLoaded_ = false; if (bUseReloadTime && munition->reloadTime_ > 0 && munition->deployment_ != MunitionDeployment::Stack) { const ExecutorPtr executor = createExecutor(createFunctor(&Magazine::loaded, this)); executor->setDefaultValues(munition); this->loadTimer_.setTimer(munition->reloadTime_, false, executor); } else this->loaded(munition); } void Munition::Magazine::loaded(Munition* munition) { this->bLoaded_ = true; this->munition_ = munition->maxMunitionPerMagazine_; } }