/* * threat.cpp * Functions that deal with threat and targetting * ____ _ * | _ \ ___ __ _| |_ __ ___ ___ * | |_) / _ \/ _` | | '_ ` _ \/ __| * | _ < __/ (_| | | | | | | \__ \ * |_| \_\___|\__,_|_|_| |_| |_|___/ * * Permission to use, modify and distribute is granted via the * Creative Commons - Attribution - Non Commercial - Share Alike 3.0 License * http://creativecommons.org/licenses/by-nc-sa/3.0/ * * Copyright (C) 2007-2012 Jason Mitchell, Randi Mitchell * Contributions by Tim Callahan, Jonathan Hseu * Based on Mordor (C) Brooke Paul, Brett J. Vickers, John P. Freeman * */ // Mud Includes #include "mud.h" #include "version.h" #include "commands.h" #include "effects.h" #include "specials.h" #include "calendar.h" #include "quests.h" #include "guilds.h" #include "property.h" #include "threat.h" // C++ includes #include <sstream> #include <iomanip> #include <locale> // C includes #include <time.h> //################################################################################ //# Threat Table //################################################################################ std::ostream& operator<<(std::ostream& out, const ThreatTable* table) { if(table) out << (*table); return(out); } std::ostream& operator<<(std::ostream& out, const ThreatTable& table) { for(ThreatEntry* threat : table.threatSet) { out << threat << std::endl; } return(out); } ThreatTable::ThreatTable(Creature* pParent) { setParent(pParent); totalThreat = 0; } ThreatTable::~ThreatTable() { clear(); } // Clear the threat table void ThreatTable::clear() { for(ThreatMap::value_type p : threatMap) { delete p.second; } threatMap.clear(); threatSet.clear(); } // Returns the size of the threat table ThreatMap::size_type ThreatTable::size() { return(threatMap.size()); } long ThreatTable::getTotalThreat() { return(totalThreat); } long ThreatTable::getThreat(Creature* target) { ThreatMap::iterator it = threatMap.find(target->getId()); if(it != threatMap.end()) { return((*it).second->getThreatValue()); } return(0); } //********************************************************************* //* AdjustThreat //********************************************************************* // Adjusts threat for the given target // If target already exists in the list update it, otherwise add it // Maintains a map of threat sorted by ID and a multiset sorted by threat amount long ThreatTable::adjustThreat(Creature* target, long modAmt, double threatFactor) { if(target->getId() == myParent->getId()) { std::cout << "Attempted to add " << target->getName() << " to their own threat list!" << std::endl; return(0); } bool newThreat = false; ThreatEntry* threat; ThreatSet::iterator it; // Find the place in the threat list it is, or would be ThreatMap::iterator mLb = threatMap.lower_bound(target->getId()); // If it's there, update it if(mLb != threatMap.end() && !(threatMap.key_comp()(target->getId(), mLb->first))) { threat = mLb->second; it = removeFromSet(threat); } else { // Otherwise make a new one and insert it into the position we just found threat = new ThreatEntry(target->getId()); threatMap.insert(mLb, ThreatMap::value_type(target->getId(), threat)); newThreat = true; } // Adjust the threat long endThreat = threat->adjustThreat((long)(modAmt*threatFactor)); threat->adjustContribution(modAmt); //std::cout << "Added Threat: " << ((long)(modAmt*threatFactor)) << " and Contribution: " << modAmt << std::endl; // Insert the threat into the set, if it's not a new threat use the hint from above if(newThreat) threatSet.insert(threat); else threatSet.insert(it, threat); return(endThreat); } bool ThreatTable::isEnemy(const Creature *target) { if(!target) { return(false); } ThreatMap::iterator it = threatMap.find(target->getId()); if(it == threatMap.end()) { return(false); } else { return(true); } } bool ThreatTable::hasEnemy() const { return(!threatMap.empty()); } //********************************************************************* //* RemoveFromSet //********************************************************************* // Removes the given threat from the threatSet ThreatSet::iterator ThreatTable::removeFromSet(ThreatEntry* threat) { ThreatSet::iterator it; // Remove the threat from set because we're going to be modifying the key value std::pair<ThreatSet::iterator, ThreatSet::iterator> p; p = threatSet.equal_range(threat); // We use the std::find algorithm because we want the exact threat // not an equivalent threat. Narrow it down first using equal range so we don't // search the entire threatSet it = std::find(p.first, p.second, threat); // We should have found the same threat assert((*it) == threat); assert(it != p.second); // Increment the iterator so it's still valid, it may be used as a hint later threatSet.erase(it++); return(it); } //********************************************************************* //* RemoveThreat //********************************************************************* // Completely removes the target from this threat list and returns the // amount of contribution they had before removal long ThreatTable::removeThreat(const bstring& pUid) { long toReturn = 0; ThreatMap::iterator mIt = threatMap.find(pUid); if(mIt == threatMap.end()) return(toReturn); ThreatEntry* threat = (*mIt).second; toReturn = threat->getContributionValue(); removeFromSet(threat); threatMap.erase(mIt); delete threat; return(toReturn); } long ThreatTable::removeThreat(Creature* target) { return(removeThreat(target->getId())); } //********************************************************************* //* GetTarget //********************************************************************* // Returns the Creature to attack based on threat and intelligent of // the parent. Returns NULL if it's not mad at anybody. Creature* ThreatTable::getTarget(bool sameRoom) { if(threatSet.empty()) { return(NULL); } if(myParent == NULL) { std::cerr << "Error: Null parent in ThreatTable::getTarget()" << std::endl; return(NULL); } Creature* toReturn = NULL; Creature* crt = NULL; ThreatSet::reverse_iterator it; for(it = threatSet.rbegin() ; it != threatSet.rend() ; ) { bstring uId = (*it++)->getUid(); crt = gServer->lookupCrtId(uId); if(!crt && uId.at(0) == 'M') { // If we're a monster and the server hasn't heard of them, they're either a pet // who has logged off, or a dead monster, either way remove them. removeThreat(uId); it = threatSet.rbegin(); continue; } // If we're looking for someone who isn't in the same room, we don't care if we can see them // however if we want the target in the current room, we must be able to see them! if(!crt || (sameRoom && (crt->getRoomParent() != myParent->getRoomParent() || !myParent->canSee(crt)))) continue; // The highest threat creature (in the same room if sameRoom is true) toReturn = crt; break; } return(toReturn); } void ThreatTable::setParent(Creature* pParent) { myParent = pParent; } //################################################################################ //# Threat Entry //################################################################################ ThreatEntry::ThreatEntry(bstring pUid) { threatValue = 0; contributionValue = 0; uId = pUid; lastMod = time(0); } std::ostream& operator<<(std::ostream& out, const ThreatEntry& threat) { MudObject* mo = gServer->lookupCrtId(threat.getUid()); out << "ID: " << threat.uId; if(mo) out << " (" << mo->getName() << ")"; else out << " (Unknown Target)"; out << " T: " << threat.threatValue << " C: " << threat.contributionValue; return(out); } std::ostream& operator<<(std::ostream& out, const ThreatEntry* threat) { if(threat) out << *threat; return(out); } long ThreatEntry::getThreatValue() { return threatValue; } long ThreatEntry::getContributionValue() { return contributionValue; } long ThreatEntry::adjustThreat(long modAmt) { threatValue += modAmt; lastMod = time(0); return(threatValue); } long ThreatEntry::adjustContribution(long modAmt) { contributionValue += modAmt; lastMod = time(0); return(contributionValue); } // ThreatPtr comparison bool ThreatPtrLess::operator()(const ThreatEntry* lhs, const ThreatEntry* rhs) const { return *lhs < *rhs; } bool ThreatEntry::operator< (const ThreatEntry& t) const { return(this->threatValue < t.threatValue); } const bstring& ThreatEntry::getUid() const { return(uId); } //********************************************************************* // doHeal //********************************************************************* // Heal a target and dish out threat as needed! int Creature::doHeal(Creature* target, int amt, double threatFactor) { if(threatFactor < 0.0) threatFactor = 1.0; int healed = target->hp.increase(amt); // If the target is a player/pet and they're in combat // then this counts towards the damage done/hatred on that monster if((target->isPlayer() || target->isPet()) && target->inCombat(false)) { for(Monster* mons : getRoomParent()->monsters) { if(mons->isEnemy(target)) { // If we're not on the enemy list, put us on at the end if(!mons->isEnemy(this)) { mons->addEnemy(this); this->printColor("^R%M gets angry at you!^x\n", mons); } // Add the amount of healing threat done to effort done mons->adjustThreat(this, healed, threatFactor); } } } return(healed); } //################################################################################ // Targeting Functions //################################################################################ void Creature::checkTarget(Creature* toTarget) { if(isPlayer() && !flagIsSet(P_NO_AUTO_TARGET) && getTarget() == NULL) { addTarget(toTarget); } } //********************************************************************* // addTarget //********************************************************************* Creature* Creature::addTarget(Creature* toTarget) { if(!toTarget) return(NULL); // We've already got them targetted! if(toTarget == myTarget) return(myTarget); clearTarget(); toTarget->addTargetingThis(this); myTarget = toTarget; Player* ply = getAsPlayer(); if(ply) { ply->printColor("You are now targeting %s.\n", myTarget->getCName()); } return(myTarget); } //********************************************************************* // addTargetingThis //********************************************************************* void Creature::addTargetingThis(Creature* targeter) { ASSERTLOG(targeter); Player* ply = getAsPlayer(); if(ply) { ply->printColor("%s is now targeting you!\n", targeter->getCName()); } targetingThis.push_back(targeter); } //********************************************************************* // clearTarget //********************************************************************* void Creature::clearTarget(bool clearTargetsList) { if(!myTarget) return; Player* ply = getAsPlayer(); if(ply) { ply->printColor("You are no longer targeting %s!\n", myTarget->getCName()); } if(clearTargetsList) myTarget->clearTargetingThis(this); myTarget = NULL; return; } //********************************************************************* // clearTargetingThis //********************************************************************* void Creature::clearTargetingThis(Creature* targeter) { ASSERTLOG(targeter); Player* ply = getAsPlayer(); if(ply) { ply->printColor("%s is no longer targeting you!\n", targeter->getCName()); } targetingThis.remove(targeter); } //********************************************************************* // cmdAssist //********************************************************************* int cmdAssist(Player* player, cmd* cmnd) { if(cmnd->num < 2) { player->print("Who would you like to assist?\n"); } Player* toAssist = player->getParent()->findPlayer(player, cmnd); if(!toAssist) { player->print("You don't see that person here.\n"); return(0); } player->print("You assist %M!\n", toAssist); if(!toAssist->flagIsSet(P_COMPACT)) toAssist->print("%M just assisted you!\n", player); player->clearTarget(); player->addTarget(toAssist->getTarget()); return(0); } //********************************************************************* // cmdTarget //********************************************************************* int cmdTarget(Player* player, cmd* cmnd) { if(cmnd->num < 2) { player->print("You are targeting: "); if(player->myTarget) player->print("%s\n", player->myTarget->getCName()); else player->print("No-one!\n"); if(player->isCt()) { player->print("People targeting you: "); int numTargets = 0; for(Creature* targetter : player->targetingThis) { if(numTargets++ != 0) player->print(", "); player->print("%s", targetter->getCName()); } if(numTargets == 0) player->print("Nobody!"); player->print("\n"); } return(0); } if(!strcasecmp(cmnd->str[1], "-c")) { player->print("Clearing target.\n"); player->clearTarget(); return(0); } lowercize(cmnd->str[1], 1); Creature* toTarget = player->getParent()->findCreature(player, cmnd); if(!toTarget) { player->print("You don't see that here.\n"); return(0); } player->addTarget(toTarget); return(0); } //********************************************************************* // getTarget //********************************************************************* Creature* Creature::getTarget() { return(myTarget); } //********************************************************************* // hasAttackableTarget //********************************************************************* bool Creature::hasAttackableTarget() { return(getTarget() && getTarget() != this && inSameRoom(getTarget()) && canSee(getTarget())); } //********************************************************************* // isAttackingTarget //********************************************************************* bool Creature::isAttackingTarget() { Creature* target = getTarget(); if(!target) return(false); Monster* mTarget = target->getAsMonster(); // Player auto combat only works vs monsters! if(!mTarget) return(false); return(mTarget->isEnemy(this)); }