diff --git a/.gitignore b/.gitignore index 8f53d915b4a..09e6dac1e7a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ build*/ +bin/ .directory .mailmap *.orig diff --git a/src/common/Collision/Maps/MapDefines.h b/src/common/Collision/Maps/MapDefines.h index 1877d3eec1c..1aa33a50b3f 100644 --- a/src/common/Collision/Maps/MapDefines.h +++ b/src/common/Collision/Maps/MapDefines.h @@ -41,11 +41,11 @@ struct MmapTileHeader // All padding fields must be handled and initialized to ensure mmaps_generator will produce binary-identical *.mmtile files static_assert(sizeof(MmapTileHeader) == 20, "MmapTileHeader size is not correct, adjust the padding field size"); static_assert(sizeof(MmapTileHeader) == (sizeof(MmapTileHeader::mmapMagic) + - sizeof(MmapTileHeader::dtVersion) + - sizeof(MmapTileHeader::mmapVersion) + - sizeof(MmapTileHeader::size) + - sizeof(MmapTileHeader::usesLiquids) + - sizeof(MmapTileHeader::padding)), "MmapTileHeader has uninitialized padding fields"); + sizeof(MmapTileHeader::dtVersion) + + sizeof(MmapTileHeader::mmapVersion) + + sizeof(MmapTileHeader::size) + + sizeof(MmapTileHeader::usesLiquids) + + sizeof(MmapTileHeader::padding)), "MmapTileHeader has uninitialized padding fields"); enum NavArea { diff --git a/src/server/game/AI/ScriptedAI/ScriptedGossip.h b/src/server/game/AI/ScriptedAI/ScriptedGossip.h index 4dab95bbe55..5b0449e7ce7 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedGossip.h +++ b/src/server/game/AI/ScriptedAI/ScriptedGossip.h @@ -43,6 +43,7 @@ enum eTradeskill TRADESKILL_SKINNING = 13, TRADESKILL_JEWLCRAFTING = 14, TRADESKILL_INSCRIPTION = 15, + TRADESKILL_LINGUISTICS = 16, TRADESKILL_LEVEL_NONE = 0, TRADESKILL_LEVEL_APPRENTICE = 1, diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 85f71594802..dabff9d00fc 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -260,7 +260,7 @@ Creature::Creature(bool isWorldObject): Unit(isWorldObject), MapObject(), m_grou m_AlreadyCallAssistance(false), m_AlreadySearchedAssistance(false), m_cannotReachTarget(false), m_cannotReachTimer(0), m_meleeDamageSchoolMask(SPELL_SCHOOL_MASK_NORMAL), m_originalEntry(0), m_homePosition(), m_transportHomePosition(), m_creatureInfo(nullptr), m_creatureData(nullptr), m_stringIds(), _waypointPathId(0), _currentWaypointNodeInfo(0, 0), - m_formation(nullptr), m_triggerJustAppeared(true), m_respawnCompatibilityMode(false), _lastDamagedTime(0), + m_formation(nullptr), m_triggerJustAppeared(true), m_respawnCompatibilityMode(false), m_assistCheckTime(0), _lastDamagedTime(0), _regenerateHealth(true), _regenerateHealthLock(false), _isMissingCanSwimFlagOutOfCombat(false) { m_regenTimer = CREATURE_REGEN_INTERVAL; @@ -804,6 +804,34 @@ void Creature::Update(uint32 diff) m_boundaryCheckTime -= diff; } + if (IsEngaged() && IsAlive() && !IsPet() && !IsCharmed() && !IsTotem() && !IsTrigger()) + { + // Only check for creatures that can actually call for help + if (!GetCharmerOrOwnerGUID().IsPlayer()) + { + if (diff >= m_assistCheckTime) + { + float radius = sWorld->getFloatConfig(CONFIG_CREATURE_FAMILY_ASSISTANCE_RADIUS); + if (radius > 0.0f) + { + // Use CallForHelp which directly engages nearby creatures + CallForHelp(radius); + } + + // Set next assistance check time + m_assistCheckTime = sWorld->getIntConfig(CONFIG_CREATURE_FAMILY_ASSISTANCE_DELAY); + } + else + m_assistCheckTime -= diff; + } + } + else + { + // Reset timer when not in combat + if (m_assistCheckTime > 0) + m_assistCheckTime = 0; + } + // if periodic combat pulse is enabled and we are both in combat and in a dungeon, do this now if (m_combatPulseDelay > 0 && IsEngaged() && GetMap()->IsDungeon()) { @@ -2330,6 +2358,7 @@ void Creature::CallAssistance() float radius = sWorld->getFloatConfig(CONFIG_CREATURE_FAMILY_ASSISTANCE_RADIUS); + if (radius > 0) { std::list assistList; @@ -2420,8 +2449,49 @@ bool Creature::CanAssistTo(Unit const* u, Unit const* enemy, bool checkfaction / } // skip non hostile to caster enemy creatures + // Modified: Allow assistance against enemies who are either hostile OR have attacked faction members + // This allows NPCs to defend against neutral players who initiate combat if (!IsHostileTo(enemy)) - return false; + { + // If enemy is a player and has recently damaged faction members, allow assistance + if (enemy->GetTypeId() == TYPEID_PLAYER) + { + // Check if the player is in combat with faction members + if (!enemy->IsInCombat()) + return false; + + // Additional check: ensure the enemy has actually attacked our faction + bool hasAttackedFaction = false; + if (Unit* victim = enemy->GetVictim()) + { + if (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->GetFaction() == GetFaction()) + hasAttackedFaction = true; + } + + // Also check combat manager for any faction targets + if (!hasAttackedFaction) + { + std::vector combatTargets; + for (auto const& pair : enemy->GetCombatManager().GetPvECombatRefs()) + { + if (Unit* target = pair.second->GetOther(enemy)) + { + if (target->GetTypeId() == TYPEID_UNIT && target->ToCreature()->GetFaction() == GetFaction()) + { + hasAttackedFaction = true; + break; + } + } + } + } + + if (!hasAttackedFaction) + return false; + } + else + // For non-player enemies, keep the original hostile check + return false; + } return true; } @@ -3266,6 +3336,19 @@ void Creature::AtEngage(Unit* target) } } + // Initialize assistance check timer to trigger first check after 5 seconds + if (!IsPet() && !IsCharmed() && !GetCharmerOrOwnerGUID().IsPlayer() && !IsTotem() && !IsTrigger()) + { + m_assistCheckTime = sWorld->getIntConfig(CONFIG_CREATURE_FAMILY_ASSISTANCE_DELAY); + } + + // Call for assistance from nearby creatures + // Only do this for non-player controlled creatures in normal world content + if (!IsPet() && !IsCharmed() && !GetCharmerOrOwnerGUID().IsPlayer() && !IsTotem() && !IsTrigger()) + { + CallAssistance(); + } + if (CreatureAI* ai = AI()) ai->JustEngagedWith(target); if (CreatureGroup* formation = GetFormation()) @@ -3280,6 +3363,9 @@ void Creature::AtDisengage() if (IsAlive() && HasDynamicFlag(UNIT_DYNFLAG_TAPPED)) ReplaceAllDynamicFlags(GetCreatureTemplate()->dynamicflags); + // Reset assistance timer when leaving combat + m_assistCheckTime = 0; + if (IsPet() || IsGuardian()) // update pets' speed for catchup OOC speed { UpdateSpeed(MOVE_RUN); diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index e8c5a3e78e8..a88fbe4fecf 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -454,6 +454,7 @@ class TC_GAME_API Creature : public Unit, public GridObject, public Ma // Regenerate health bool _regenerateHealth; // Set on creation bool _regenerateHealthLock; // Dynamically set + uint32 m_assistCheckTime; bool _isMissingCanSwimFlagOutOfCombat; }; diff --git a/src/server/game/Entities/Item/ItemTemplate.h b/src/server/game/Entities/Item/ItemTemplate.h index 6787e040804..0fc7cf303c3 100644 --- a/src/server/game/Entities/Item/ItemTemplate.h +++ b/src/server/game/Entities/Item/ItemTemplate.h @@ -467,7 +467,8 @@ enum ItemSubclassRecipe ITEM_SUBCLASS_ENCHANTING_FORMULA = 8, ITEM_SUBCLASS_FISHING_MANUAL = 9, ITEM_SUBCLASS_JEWELCRAFTING_RECIPE = 10, - ITEM_SUBCLASS_INSCRIPTION_TECHNIQUE = 11 + ITEM_SUBCLASS_INSCRIPTION_TECHNIQUE = 11, + ITEM_SUBCLASS_LINGUISTICS_BOOK = 12 }; #define MAX_ITEM_SUBCLASS_RECIPE 12 diff --git a/src/server/game/Quests/QuestDef.h b/src/server/game/Quests/QuestDef.h index ffe7312aa77..a34cbc0e37b 100644 --- a/src/server/game/Quests/QuestDef.h +++ b/src/server/game/Quests/QuestDef.h @@ -96,7 +96,8 @@ enum QuestTradeSkill QUEST_TRSKILL_MINING = 11, QUEST_TRSKILL_FISHING = 12, QUEST_TRSKILL_SKINNING = 13, - QUEST_TRSKILL_JEWELCRAFTING = 14 + QUEST_TRSKILL_JEWELCRAFTING = 14, + QUEST_TRSKILL_LINGUISTICS = 15 }; enum QuestStatus : uint8 diff --git a/src/server/game/Spells/SpellMgr.h b/src/server/game/Spells/SpellMgr.h index 0253d7014cd..5698f218fbf 100644 --- a/src/server/game/Spells/SpellMgr.h +++ b/src/server/game/Spells/SpellMgr.h @@ -559,7 +559,7 @@ bool IsPrimaryProfessionSkill(uint32 skill); inline bool IsProfessionSkill(uint32 skill) { - return IsPrimaryProfessionSkill(skill) || skill == SKILL_FISHING || skill == SKILL_COOKING || skill == SKILL_FIRST_AID; + return IsPrimaryProfessionSkill(skill) || skill == SKILL_FISHING || skill == SKILL_COOKING || skill == SKILL_FIRST_AID || skill == SKILL_LINGUISTICS; } inline bool IsProfessionOrRidingSkill(uint32 skill) diff --git a/src/server/shared/SharedDefines.h b/src/server/shared/SharedDefines.h index 321f65c3d2e..4c9ac7ebd96 100644 --- a/src/server/shared/SharedDefines.h +++ b/src/server/shared/SharedDefines.h @@ -1327,7 +1327,16 @@ enum SpellCustomErrors SPELL_CUSTOM_ERROR_MAX_NUMBER_OF_RECRUITS = 96, // You already have the max number of recruits. SPELL_CUSTOM_ERROR_MAX_NUMBER_OF_VOLUNTEERS = 97, // You already have the max number of volunteers. SPELL_CUSTOM_ERROR_FROSTMOURNE_RENDERED_RESURRECT = 98, // Frostmourne has rendered you unable to resurrect. - SPELL_CUSTOM_ERROR_CANT_MOUNT_WITH_SHAPESHIFT = 99 // You can't mount while affected by that shapeshift. + SPELL_CUSTOM_ERROR_CANT_MOUNT_WITH_SHAPESHIFT = 99, // You can't mount while affected by that shapeshift. + SPELL_CUSTOM_ERROR_INTRO_QUEST_ACTIVE = 100,// You cannot use that until you've completed the introduction quest. + SPELL_CUSTOM_ERROR_STORMWIND_NEUTRAL = 101,// You must be at least Neutral with Stormwind to use that. + SPELL_CUSTOM_ERROR_IRONFORGEORGNOME_NEUTRAL = 102,// You must be at least Neutral with Ironforge or Gnomeregan Exiles to use that. + SPELL_CUSTOM_ERROR_DARNASSUS_NEUTRAL = 103,// You must be at least Neutral with Darnassus to use that. + SPELL_CUSTOM_ERROR_EXODAR_NEUTRAL = 104,// You must be at least Neutral with Exodar to use that. + SPELL_CUSTOM_ERROR_ORGRIMMARORDARKSPEAR_NEUTRAL = 105,// You must be at least Neutral with Orgrimmar or Darkspear Trolls to use that. + SPELL_CUSTOM_ERROR_THUNDERBLUFF_NEUTRAL = 106,// You must be at least Neutral with Thunder Bluff to use that. + SPELL_CUSTOM_ERROR_UNDERCITY_NEUTRAL = 107,// You must be at least Neutral with Undercity to use that. + SPELL_CUSTOM_ERROR_SILVERMOONCITY_NEUTRAL = 108 // You must be at least Neutral with Silvermoon City to use that. }; enum StealthType @@ -2925,7 +2934,8 @@ enum QuestSort QUEST_SORT_JEWELCRAFTING = 373, QUEST_SORT_NOBLEGARDEN = 374, QUEST_SORT_PILGRIMS_BOUNTY = 375, - QUEST_SORT_LOVE_IS_IN_THE_AIR = 376 + QUEST_SORT_LOVE_IS_IN_THE_AIR = 376, + QUEST_SORT_LINGUISTICS = 377 }; constexpr uint8 ClassByQuestSort(int32 QuestSort) @@ -3099,7 +3109,8 @@ enum SkillType SKILL_PET_WASP = 785, SKILL_PET_EXOTIC_RHINO = 786, SKILL_PET_EXOTIC_CORE_HOUND = 787, - SKILL_PET_EXOTIC_SPIRIT_BEAST = 788 + SKILL_PET_EXOTIC_SPIRIT_BEAST = 788, + SKILL_LINGUISTICS = 789 }; #define MAX_SKILL_TYPE 789 @@ -3133,6 +3144,7 @@ constexpr uint32 SkillByQuestSort(int32 QuestSort) case QUEST_SORT_FIRST_AID: return SKILL_FIRST_AID; case QUEST_SORT_JEWELCRAFTING: return SKILL_JEWELCRAFTING; case QUEST_SORT_INSCRIPTION: return SKILL_INSCRIPTION; + case QUEST_SORT_LINGUISTICS: return SKILL_LINGUISTICS; } return 0; }