bool C4Menu::HasMouse() { int32_t iPlayer = GetControllingPlayer(); if (iPlayer == NO_OWNER) return true; // free view dialog also has the mouse C4Player *pPlr = ::Players.Get(iPlayer); if (pPlr && pPlr->MouseControl) return true; return false; }
///////////////////////////////////////////////// /// Assistance: Unit treats another unit as an ally it can help /// /// @note Relations API Tier 1 /// /// Client-side counterpart: <tt>CGUnit_C::CanAssist(const CGUnit_C *this, const CGUnit_C *unit)</tt> /// Backbone of all spells which can target friendly units. /// Optional ignoreFlags parameter first appeared in TBC+ clients, backported for API unification between expansions. ///////////////////////////////////////////////// bool Unit::CanAssist(const Unit* unit, bool /*ignoreFlags*/) const { // Simple sanity check if (!unit) return false; // Original logic // We can't assist unselectable unit if (unit->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE)) return false; // Exclude non-friendlies at this point if (GetReactionTo(unit) < REP_FRIENDLY) return false; // Pre-WotLK: backbone of lua UnitIsPVP(), a member of unit class client-side auto isPvPUI = [](Unit const * self) { if (Unit const* master = self->GetMaster()) { if (self->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PLAYER)) return false; return master->IsPvP(); } return self->IsPvP(); }; // Detect player controlled unit and exit early if (unit->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) { const Player* thisPlayer = GetControllingPlayer(); const Player* unitPlayer = unit->GetControllingPlayer(); if (thisPlayer && unitPlayer) { if (thisPlayer->IsInDuelWith(unitPlayer)) return false; if (unitPlayer->IsPvPFreeForAll() && !thisPlayer->IsPvPFreeForAll()) return false; } return true; } // If we continue here, unit is an npc. Detect if we are an npc too, so we can exit early if (!HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) return true; // Pre-TBC: We are left with player assisting an npc case here: can assist friendly NPCs with PVP flag if (isPvPUI(unit)) return true; return false; }
///////////////////////////////////////////////// /// [Serverside] Opposition: Unit can target a target with a harmful spell /// /// @note Relations API Tier 3 /// /// This function is not intented to have client-side counterpart by original design. /// It utilizes SpellEntry for additional target filtering. /// Also an additional fine grained check needs to be done for AOE spells, because they /// need to skip PVP enabled targets in some special cases. (Chain spells, AOE) ///////////////////////////////////////////////// bool Unit::CanAttackSpell(Unit const* target, SpellEntry const* spellInfo, bool isAOE) const { if (spellInfo) { // inversealive is needed for some spells which need to be casted at dead targets (aoe) if (!target->isAlive() && !spellInfo->HasAttribute(SPELL_ATTR_EX2_CAN_TARGET_DEAD)) return false; } if (CanAttack(target)) { if (isAOE) { if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) { if (target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) { const Player* thisPlayer = GetControllingPlayer(); if (!thisPlayer) return true; const Player* unitPlayer = target->GetControllingPlayer(); if (!unitPlayer) return true; if (thisPlayer->IsInDuelWith(unitPlayer)) return true; if (unitPlayer->IsPvP() && (!isAOE || thisPlayer->IsPvP())) return true; if (thisPlayer->IsPvPFreeForAll() && unitPlayer->IsPvPFreeForAll()) return true; return false; } } } return true; } return false; }
///////////////////////////////////////////////// /// Group: Unit counts as being placed in the same group (party or raid) with another unit (for gameplay purposes) /// /// @note Relations API Tier 1 /// /// Based on client-side counterpart: <tt>static CGUnit_C::IsUnitInGroup(const CGUnit_C *this, const CGUnit_C *unit)</tt> /// Additionally contains optional detection of same group from UI standpoint datamined from other functions. /// Points of view are swapped to fit in with the rest of API, logic is preserved. ///////////////////////////////////////////////// bool Unit::IsInGroup(Unit const* other, bool party/* = false*/, bool UI/* = false*/) const { // Simple sanity check if (!other) return false; // Original logic adaptation for server (original function was operating as a local player PoV only) // Same unit is always in group with itself if (this == other) return true; // Only player controlled if (this->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED) && other->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) { // UI mode: only players and their current pets/charms are in the party UI if (UI) { const size_t comparisions = 3; const Player* thisPlayer[comparisions] = { nullptr, nullptr, nullptr }; const Player* otherPlayer[comparisions] = { nullptr, nullptr, nullptr }; auto getUIPlayerComparisions = [] (const Unit* unit, const Player* (&array)[comparisions]) { // In reverse order if (unit->GetTypeId() == TYPEID_PLAYER) array[0] = static_cast<const Player*>(unit); ObjectGuid const& summonerGuid = unit->GetSummonerGuid(); ObjectGuid const& charmerGuid = unit->GetCharmerGuid(); if (summonerGuid.IsPlayer()) array[1] = sObjectMgr.GetPlayer(summonerGuid); if (charmerGuid.IsPlayer()) array[2] = sObjectMgr.GetPlayer(charmerGuid); }; getUIPlayerComparisions(this, thisPlayer); getUIPlayerComparisions(other, otherPlayer); for (auto& i : thisPlayer) { if (i) { for (auto& j : otherPlayer) { if (j) { const Group* group = i->GetGroup(); if (i == j || (group && group == j->GetGroup() && (!party || group->SameSubGroup(i, j)))) return true; } } } } return false; } // Check if controlling players are in the same group (same logic as client, but not local) if (const Player* thisPlayer = GetControllingPlayer()) { if (const Player* otherPlayer = other->GetControllingPlayer()) { const Group* group = thisPlayer->GetGroup(); return (thisPlayer == otherPlayer || (group && group == otherPlayer->GetGroup() && (!party || group->SameSubGroup(thisPlayer, otherPlayer)))); } } } // NOTE: For future reference: server uses additional gameplay grouping logic for mobs (in combat and out of combat) - requires research for Tier 2 implementation return false; }
///////////////////////////////////////////////// /// Opposition: Unit treats another unit as an enemy it can attack (generic) /// /// @note Relations API Tier 1 /// /// Client-side counterpart: <tt>CGUnit_C::CanAttack(const CGUnit_C *this, const CGUnit_C *unit)</tt> /// Backbone of all spells which can target hostile units. ///////////////////////////////////////////////// bool Unit::CanAttack(const Unit* unit) const { // Simple sanity check if (!unit) return false; // Original logic // Creatures cannot attack player ghosts, unless it is a specially flagged ghost creature if (GetTypeId() == TYPEID_UNIT && unit->GetTypeId() == TYPEID_PLAYER && static_cast<const Player*>(unit)->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) { if (!(static_cast<const Creature*>(this)->GetCreatureInfo()->CreatureTypeFlags & CREATURE_TYPEFLAGS_GHOST_VISIBLE)) return false; } // We can't attack unit when at least one of these flags is present on it: const uint32 mask = (UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_ATTACKABLE_1 | UNIT_FLAG_NON_ATTACKABLE_2 | UNIT_FLAG_TAXI_FLIGHT | UNIT_FLAG_NOT_SELECTABLE); if (unit->HasFlag(UNIT_FIELD_FLAGS, mask)) return false; // Cross-check immunity and sanctuary flags: this <-> unit const bool thisPlayerControlled = HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); if (thisPlayerControlled) { if (unit->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PLAYER)) return false; } else if (unit->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC)) return false; const bool unitPlayerControlled = unit->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); if (unitPlayerControlled) { if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PLAYER)) return false; } else if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC)) return false; if (thisPlayerControlled || unitPlayerControlled) { if (thisPlayerControlled && unitPlayerControlled) { if (IsFriend(unit)) return false; const Player* thisPlayer = GetControllingPlayer(); if (!thisPlayer) return true; const Player* unitPlayer = unit->GetControllingPlayer(); if (!unitPlayer) return true; if (thisPlayer->IsInDuelWith(unitPlayer)) return true; if (unitPlayer->IsPvP()) return true; if (thisPlayer->IsPvPFreeForAll() && unitPlayer->IsPvPFreeForAll()) return true; return false; } return (!IsFriend(unit)); } return (IsEnemy(unit) || unit->IsEnemy(this)); }
///////////////////////////////////////////////// /// Get unit to unit reaction /// /// @note Relations API Tier 1 /// /// Client-side counterpart: <tt>CGUnit_C::UnitReaction(const CGUnit_C *this, const CGUnit_C *unit)</tt> ///////////////////////////////////////////////// ReputationRank Unit::GetReactionTo(Unit const* unit) const { // Simple sanity check if (!unit) return REP_NEUTRAL; // Original logic begins if (this == unit) return REP_FRIENDLY; if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) { const Player* thisPlayer = GetControllingPlayer(); if (unit->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) { const Player* unitPlayer = unit->GetControllingPlayer(); if (!thisPlayer || !unitPlayer) return REP_NEUTRAL; // Pre-TBC same player check: not present clientside in this order, but in for optimization (same result achieved through same group check below) if (thisPlayer == unitPlayer) return REP_FRIENDLY; if (unitPlayer->GetUInt32Value(PLAYER_DUEL_TEAM)) { // TODO: Dueling misses duel arbiter and temporary truce during countdown, fix me later... if (thisPlayer->IsInDuelWith(unitPlayer)) return REP_HOSTILE; } // Pre-WotLK group check: always, replaced with faction template check in WotLK if (thisPlayer->IsInGroup(unitPlayer)) return REP_FRIENDLY; // Pre-WotLK FFA check, known limitation: FFA doesn't work with totem elementals both client-side and server-side if (thisPlayer->IsPvPFreeForAll() && unitPlayer->IsPvPFreeForAll()) return REP_HOSTILE; } if (thisPlayer) { if (const FactionTemplateEntry* unitFactionTemplate = unit->GetFactionTemplateEntry()) { if (const ReputationRank* rank = thisPlayer->GetReputationMgr().GetForcedRankIfAny(unitFactionTemplate)) return (*rank); const FactionEntry* unitFactionEntry = sFactionStore.LookupEntry(unitFactionTemplate->faction); // If the faction has reputation ranks available, "at war" and contested PVP flags decide outcome if (unitFactionEntry && unitFactionEntry->HasReputation()) { // Pre-TBC contested check: not present clientside in this order, but in for optimization (same result achieved through faction to unit check below) if (thisPlayer->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_CONTESTED_PVP) && unitFactionTemplate->IsContestedGuardFaction()) return REP_HOSTILE; return thisPlayer->GetReputationMgr().IsAtWar(unitFactionEntry) ? REP_HOSTILE : REP_FRIENDLY; } } } } // Default fallback if player-specific checks didn't catch anything: facton to unit ReputationRank reaction = GetFactionReaction(GetFactionTemplateEntry(), unit); // Persuation support if (reaction > REP_HOSTILE && reaction < REP_HONORED && (unit->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PERSUADED) || GetPersuadedGuid() == unit->GetObjectGuid())) { if (const FactionTemplateEntry* unitFactionTemplate = unit->GetFactionTemplateEntry()) { const FactionEntry* unitFactionEntry = sFactionStore.LookupEntry(unitFactionTemplate->faction); if (unitFactionEntry && unitFactionEntry->HasReputation()) reaction = ReputationRank(int32(reaction) + 1); } } return reaction; }