diff --git a/src/game/DBCStructure.h b/src/game/DBCStructure.h index 6ce5485a6..c703fafba 100644 --- a/src/game/DBCStructure.h +++ b/src/game/DBCStructure.h @@ -396,7 +396,6 @@ struct AchievementCriteriaEntry uint32 goldInCopper; // 4 } quest_reward_money; - // ACHIEVEMENT_CRITERIA_TYPE_LOOT_MONEY = 67 struct { @@ -535,7 +534,7 @@ struct AreaTableEntry int32 area_level; // 10 m_ExplorationLevel DBCString area_name; // 11 m_AreaName_lang uint32 team; // 12 m_factionGroupMask - // 13-16 m_liquidTypeID[4] + uint32 LiquidTypeOverride[4]; // 13-16 m_liquidTypeID[4] // 17 m_minElevation // 18 m_ambient_multiplier // 19 m_lightid @@ -685,9 +684,9 @@ struct ChrClassesEntry //uint32 flags2; // 8 m_flags (0x08 HasRelicSlot) uint32 CinematicSequence; // 9 m_cinematicSequenceID uint32 expansion; // 10 m_required_expansion - //uint32 // 11 - //uint32 // 12 - //uint32 // 13 + uint32 apPerStr; // 11 attack power per strength + uint32 apPerAgi; // 12 attack power per agility + uint32 rapPerAgi; // 13 ranged attack power per agility }; struct ChrRacesEntry @@ -833,6 +832,34 @@ struct CurrencyTypesEntry float GetPrecision() const { return HasPrecision() ? CURRENCY_PRECISION : 1.0f; } }; +struct DestructibleModelDataEntry +{ + uint32 m_ID; // 0 m_ID + uint32 intactDisplayId; // 1 + // uint32 unk2; // 2 + // uint32 unk3; // 3 + // uint32 unk4; // 4 + uint32 damagedDisplayId; // 5 + // uint32 unk6; // 6 + // uint32 unk7; // 7 + // uint32 unk8; // 8 + // uint32 unk9; // 9 + uint32 destroyedDisplayId; // 10 + // uint32 unk11; // 11 + // uint32 unk12; // 12 + // uint32 unk13; // 13 + // uint32 unk14; // 14 + uint32 rebuildingDisplayId; // 15 + // uint32 unk16; // 16 + // uint32 unk17; // 17 + // uint32 unk18; // 18 + // uint32 unk19; // 19 + //uint32 smokeDisplayId; // 20 + // uint32 unk21; // 21 + // uint32 unk22; // 22 + // uint32 unk23; // 23 +}; + struct DungeonEncounterEntry { uint32 Id; // 0 m_ID @@ -961,15 +988,15 @@ struct FactionTemplateEntry struct GameObjectDisplayInfoEntry { - uint32 Displayid; // 0 m_ID - //DBCString filename; // 1 - //uint32 unk1[10]; // 2-11 - float minX; // 1 - float minY; // 13 - float minZ; // 14 - float maxX; // 15 - float maxY; // 16 - float maxZ; // 17 + uint32 Displayid; // 0 m_ID + char* filename; // 1 m_modelName + // uint32 unknown2[10]; // 2-11 m_Sound + float geoBoxMinX; // 12 m_geoBoxMinX (use first value as interact dist, mostly in hacks way) + float geoBoxMinY; // 13 m_geoBoxMinY + float geoBoxMinZ; // 14 m_geoBoxMinZ + float geoBoxMaxX; // 15 m_geoBoxMaxX + float geoBoxMaxY; // 16 m_geoBoxMaxY + float geoBoxMaxZ; // 17 m_geoBoxMaxZ //uint32 transport; // 18 //uint32 unk; // 19 //uint32 unk1; // 20 @@ -1249,6 +1276,29 @@ struct ItemSetEntry m_target_level_max };*/ +struct LiquidTypeEntry +{ + uint32 Id; // 0 + //char* Name; // 1 + //uint32 Flags; // 2 Water: 1|2|4|8, Magma: 8|16|32|64, Slime: 2|64|256, WMO Ocean: 1|2|4|8|512 + uint32 Type; // 3 0: Water, 1: Ocean, 2: Magma, 3: Slime + //uint32 SoundId; // 4 Reference to SoundEntries.dbc + uint32 SpellId; // 5 Reference to Spell.dbc + //float MaxDarkenDepth; // 6 Only oceans got values here! + //float FogDarkenIntensity; // 7 Only oceans got values here! + //float AmbDarkenIntensity; // 8 Only oceans got values here! + //float DirDarkenIntensity; // 9 Only oceans got values here! + //uint32 LightID; // 10 Only Slime (6) and Magma (7) + //float ParticleScale; // 11 0: Slime, 1: Water/Ocean, 4: Magma + //uint32 ParticleMovement; // 12 + //uint32 ParticleTexSlots; // 13 + //uint32 LiquidMaterialID; // 14 + //char* Texture[6]; // 15-20 + //uint32 Color[2]; // 21-22 + //float Unk1[18]; // 23-40 Most likely these are attributes for the shaders. Water: (23, TextureTilesPerBlock),(24, Rotation) Magma: (23, AnimationX),(24, AnimationY) + //uint32 Unk2[4]; // 41-44 +}; + #define MAX_LOCK_CASE 8 struct LockEntry @@ -1496,13 +1546,6 @@ struct ScalingStatValuesEntry return spellBonus; return 0; } - - uint32 getFeralBonus(uint32 mask) const // removed in 3.2.x? - { - if (mask & 0x00010000) // not used? - return 0; - return 0; - } }; /*struct SkillLineCategoryEntry{ @@ -1732,7 +1775,7 @@ struct SpellEffectEntry int32 EffectMiscValueB; // 118-120 m_effectMiscValueB float EffectPointsPerComboPoint; // 124-126 m_effectPointsPerCombo uint32 EffectRadiusIndex; // 94-96 m_effectRadiusIndex - spellradius.dbc - //uint32 EffectRadiusMaxIndex; // 97-99 4.0.0 + uint32 EffectRadiusMaxIndex; // 97-99 4.0.0 float EffectRealPointsPerLevel; // 79-81 m_effectRealPointsPerLevel ClassFamilyMask EffectSpellClassMask; // 127-129 m_effectSpellClassMask uint32 EffectTriggerSpell; // 121-123 m_effectTriggerSpell @@ -1745,6 +1788,14 @@ struct SpellEffectEntry // helpers int32 CalculateSimpleValue() const { return EffectBasePoints; } + + uint32 GetRadiusIndex() const + { + if (EffectRadiusIndex != 0) + return EffectRadiusIndex; + + return EffectRadiusMaxIndex; + } }; // SpellEquippedItems.dbc @@ -1800,16 +1851,18 @@ struct SpellReagentsEntry // SpellScaling.dbc struct SpellScalingEntry { - //uint32 Id; // 0 m_ID - uint32 castTimeMin; // 1 - uint32 castTimeMax; // 2 - uint32 castScalingMaxLevel; // 3 + //uint32 Id; // 0 m_ID + int32 castTimeMin; // 1 + int32 castTimeMax; // 2 + int32 castScalingMaxLevel; // 3 int32 playerClass; // 4 (index * 100) + charLevel => gtSpellScaling.dbc float coeff1[3]; // 5-7 float coeff2[3]; // 8-10 float coeff3[3]; // 11-13 - float unkMult; // 14 some coefficient, mostly 1.0f - uint32 unkLevel; // 15 some level + float coefBase; // 14 some coefficient, mostly 1.0f + int32 coefLevelBase; // 15 some level + + bool IsScalableEffect(SpellEffectIndex i) const { return coeff1[i] != 0.0f; }; }; // SpellShapeshift.dbc diff --git a/src/game/DBCfmt.h b/src/game/DBCfmt.h index 153342324..ffd54d580 100644 --- a/src/game/DBCfmt.h +++ b/src/game/DBCfmt.h @@ -21,7 +21,7 @@ const char Achievementfmt[]="niiissiiiiisii"; const char AchievementCriteriafmt[]="niiiiiiiixsiiiiixxxxxxx"; -const char AreaTableEntryfmt[]="iiinixxxxxisixxxxxxxxxxxxx"; +const char AreaTableEntryfmt[]="iiinixxxxxisiiiiixxxxxxxxx"; const char AreaGroupEntryfmt[]="niiiiiii"; const char AreaTriggerEntryfmt[]="nifffxxxfffff"; const char ArmorLocationfmt[]="nfffff"; @@ -33,7 +33,7 @@ const char CharStartOutfitEntryfmt[]="diiiiiiiiiiiiiiiiiiiiiiiiixxxxxxxxxxxxxxxx const char CharTitlesEntryfmt[]="nxsxix"; const char ChatChannelsEntryfmt[]="iixsx"; // ChatChannelsEntryfmt, index not used (more compact store) -const char ChrClassesEntryfmt[]="nixsxxxixiixxx"; +const char ChrClassesEntryfmt[]="nixsxxxixiiiii"; const char ChrRacesEntryfmt[]="nxixiixixxxxixsxxxxxixxx"; const char ChrClassesXPowerTypesfmt[]="nii"; const char CinematicSequencesEntryfmt[]="nxxxxxxxxx"; @@ -41,6 +41,7 @@ const char CreatureDisplayInfofmt[]="nxxifxxxxxxxxxxxx"; const char CreatureDisplayInfoExtrafmt[]="nixxxxxxxxxxxxxxxxxxx"; const char CreatureFamilyfmt[]="nfifiiiiixsx"; const char CreatureSpellDatafmt[]="niiiixxxx"; +const char DestructibleModelDataFmt[] = "nixxxixxxxixxxxixxxxxxxx"; const char DungeonEncounterfmt[]="niiiisxx"; const char CreatureTypefmt[]="nxx"; const char CurrencyTypesfmt[]="nisxxxxiiix"; @@ -50,7 +51,7 @@ const char EmotesEntryfmt[]="nxxiiixx"; const char EmotesTextEntryfmt[]="nxixxxxxxxxxxxxxxxx"; const char FactionEntryfmt[]="niiiiiiiiiiiiiiiiiiffixsxx"; const char FactionTemplateEntryfmt[]="niiiiiiiiiiiii"; -const char GameObjectDisplayInfofmt[]="nxxxxxxxxxxxffffffxxx"; +const char GameObjectDisplayInfofmt[]="nsxxxxxxxxxxffffffxxx"; const char GemPropertiesEntryfmt[]="nixxix"; const char GlyphPropertiesfmt[]="niii"; const char GlyphSlotfmt[]="nii"; @@ -81,6 +82,7 @@ const char ItemLimitCategoryEntryfmt[]="nxii"; const char ItemRandomPropertiesfmt[]="nxiiiiis"; const char ItemRandomSuffixfmt[]="nsxiiiiiiiiii"; const char ItemSetEntryfmt[]="dsxxxxxxxxxxxxxxxxxiiiiiiiiiiiiiiiiii"; +const char LiquidTypefmt[] = "nxxixixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; const char LockEntryfmt[]="niiiiiiiiiiiiiiiiiiiiiiiixxxxxxxx"; const char MailTemplateEntryfmt[]="nxs"; const char MapEntryfmt[]="nsiiiisissififfiiiii"; @@ -110,7 +112,7 @@ const char SpellCastingRequirementsEntryfmt[]="dixxixi"; const char SpellCategoriesEntryfmt[]="diiiiii"; const char SpellClassOptionsEntryfmt[]="dxiiiix"; const char SpellCooldownsEntryfmt[]="diii"; -const char SpellEffectEntryfmt[]="difiiixfiiiiiifixfiiiiiiiix"; +const char SpellEffectEntryfmt[]="difiiixfiiiiiifiifiiiiiiiix"; const char SpellEquippedItemsEntryfmt[]="diii"; const char SpellInterruptsEntryfmt[]="dixixi"; const char SpellLevelsEntryfmt[]="diii"; diff --git a/src/game/GameObject.cpp b/src/game/GameObject.cpp index 29301ddde..47ae2a2ae 100644 --- a/src/game/GameObject.cpp +++ b/src/game/GameObject.cpp @@ -39,10 +39,13 @@ #include "OutdoorPvP/OutdoorPvP.h" #include "Util.h" #include "ScriptMgr.h" +#include "vmap/GameObjectModel.h" #include "SQLStorages.h" #include GameObject::GameObject() : WorldObject(), + loot(this), + m_model(NULL), m_goInfo(NULL), m_displayInfo(NULL) { @@ -54,7 +57,7 @@ GameObject::GameObject() : WorldObject(), m_valuesCount = GAMEOBJECT_END; m_respawnTime = 0; m_respawnDelayTime = 25; - m_lootState = GO_NOT_READY; + m_lootState = GO_READY; m_spawnedByDefault = true; m_useTimes = 0; m_spellId = 0; @@ -70,6 +73,7 @@ GameObject::GameObject() : WorldObject(), GameObject::~GameObject() { + delete m_model; } void GameObject::AddToWorld() @@ -78,7 +82,13 @@ void GameObject::AddToWorld() if (!IsInWorld()) GetMap()->GetObjectsStore().insert(GetObjectGuid(), (GameObject*)this); + if (m_model) + GetMap()->InsertGameObjectModel(*m_model); + Object::AddToWorld(); + + // After Object::AddToWorld so that for initial state the GO is added to the world (and hence handled correctly) + UpdateCollisionState(); } void GameObject::RemoveFromWorld() @@ -102,6 +112,9 @@ void GameObject::RemoveFromWorld() } } + if (m_model && GetMap()->ContainsGameObjectModel(*m_model)) + GetMap()->RemoveGameObjectModel(*m_model); + GetMap()->GetObjectsStore().erase(GetObjectGuid(), (GameObject*)NULL); } @@ -162,6 +175,18 @@ bool GameObject::Create(uint32 guidlow, uint32 name_id, Map* map, uint32 phaseMa SetGoArtKit(0); // unknown what this is SetGoAnimProgress(animprogress); + switch (GetGoType()) + { + case GAMEOBJECT_TYPE_TRAP: + case GAMEOBJECT_TYPE_FISHINGNODE: + m_lootState = GO_NOT_READY; // Initialize Traps and Fishingnode delayed in ::Update + break; + case GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING: + ForceGameObjectHealth(GetMaxHealth(), NULL); + SetUInt32Value(GAMEOBJECT_PARENTROTATION, m_goInfo->destructibleBuilding.destructibleData); + break; + } + // Notify the battleground or outdoor pvp script if (map->IsBattleGroundOrArena()) ((BattleGroundMap*)map)->GetBG()->HandleGameObjectCreate(this); @@ -191,16 +216,16 @@ void GameObject::Update(uint32 update_diff, uint32 p_time) { switch (GetGoType()) { - case GAMEOBJECT_TYPE_TRAP: + case GAMEOBJECT_TYPE_TRAP: // Initialized delayed to be able to use GetOwner() { // Arming Time for GAMEOBJECT_TYPE_TRAP (6) Unit* owner = GetOwner(); - if (owner && ((Player*)owner)->isInCombat()) + if (owner && owner->isInCombat()) m_cooldownTime = time(NULL) + GetGOInfo()->trap.startDelay; m_lootState = GO_READY; break; } - case GAMEOBJECT_TYPE_FISHINGNODE: + case GAMEOBJECT_TYPE_FISHINGNODE: // Keep not ready for some delay { // fishing code (bobber ready) if (time(NULL) > m_respawnTime - FISHING_BOBBER_READY_TIME) @@ -219,13 +244,10 @@ void GameObject::Update(uint32 update_diff, uint32 p_time) m_lootState = GO_READY; // can be successfully open with some chance } - return; - } - default: - m_lootState = GO_READY; // for other GO is same switched without delay to GO_READY break; + } } - // NO BREAK for switch (m_lootState) + break; } case GO_READY: { @@ -305,32 +327,17 @@ void GameObject::Update(uint32 update_diff, uint32 p_time) } } - // Note: this hack with search required until GO casting not implemented - // search unfriendly creature - if (owner && goInfo->trap.charges > 0) // hunter trap - { - MaNGOS::AnyUnfriendlyUnitInObjectRangeCheck u_check(this, owner, radius); - MaNGOS::UnitSearcher checker(ok, u_check); - Cell::VisitGridObjects(this, checker, radius); - if (!ok) - Cell::VisitWorldObjects(this, checker, radius); - } - else // environmental trap - { - // environmental damage spells already have around enemies targeting but this not help in case nonexistent GO casting support - - // affect only players - Player* p_ok = NULL; - MaNGOS::AnyPlayerInObjectRangeCheck p_check(this, radius); - MaNGOS::PlayerSearcher checker(p_ok, p_check); - Cell::VisitWorldObjects(this, checker, radius); - ok = p_ok; - } + // Should trap trigger? + MaNGOS::AnyUnfriendlyUnitInObjectRangeCheck u_check(this, radius); + MaNGOS::UnitSearcher checker(ok, u_check); + Cell::VisitAllObjects(this, checker, radius); if (ok) { Unit* caster = owner ? owner : ok; + // Code below should be refactored into GO::Use, but not clear how to handle caster/victim for non AoE spells + caster->CastSpell(ok, goInfo->trap.spellId, true, NULL, NULL, GetObjectGuid()); // use template cooldown if provided m_cooldownTime = time(NULL) + (goInfo->trap.cooldown ? goInfo->trap.cooldown : uint32(4)); @@ -506,7 +513,6 @@ void GameObject::AddUniqueUse(Player* player) m_firstUser = player->GetObjectGuid(); m_UniqueUsers.insert(player->GetObjectGuid()); - } void GameObject::Delete() @@ -522,19 +528,6 @@ void GameObject::Delete() AddObjectToRemoveList(); } -void GameObject::getFishLoot(Loot* fishloot, Player* loot_owner) -{ - fishloot->clear(); - - uint32 zone, subzone; - GetZoneAndAreaId(zone, subzone); - - // if subzone loot exist use it - if (!fishloot->FillLoot(subzone, LootTemplates_Fishing, loot_owner, true, (subzone != zone)) && subzone != zone) - // else use zone loot (if zone diff. from subzone, must exist in like case) - fishloot->FillLoot(zone, LootTemplates_Fishing, loot_owner, true); -} - void GameObject::SaveToDB() { // this should only be used when the gameobject has already been loaded @@ -672,7 +665,6 @@ struct GameObjectRespawnDeleteWorker uint32 i_guid; }; - void GameObject::DeleteFromDB() { if (!HasStaticDBSpawnData()) @@ -938,6 +930,23 @@ GameObject* GameObject::LookupFishingHoleAround(float range) return ok; } +bool GameObject::IsCollisionEnabled() const +{ + if (!isSpawned()) + return false; + + // TODO: Possible that this function must consider multiple checks + switch (GetGoType()) + { + case GAMEOBJECT_TYPE_DOOR: + case GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING: + return GetGoState() != GO_STATE_ACTIVE && GetGoState() != GO_STATE_ACTIVE_ALTERNATIVE; + + default: + return true; + } +} + void GameObject::ResetDoorOrButton() { if (m_lootState == GO_READY || m_lootState == GO_JUST_DEACTIVATED) @@ -1048,10 +1057,8 @@ void GameObject::Use(Unit* user) // TODO: possible must be moved to loot release (in different from linked triggering) if (GetGOInfo()->chest.eventId) { - DEBUG_LOG("Chest ScriptStart id %u for GO %u", GetGOInfo()->chest.eventId, GetGUIDLow()); - - if (!sScriptMgr.OnProcessEvent(GetGOInfo()->chest.eventId, user, this, true)) - GetMap()->ScriptsStart(sEventScripts, GetGOInfo()->chest.eventId, user, this); + DEBUG_LOG("Chest ScriptStart id %u for %s (opened by %s)", GetGOInfo()->chest.eventId, GetGuidStr().c_str(), user->GetGuidStr().c_str()); + StartEvents_Event(GetMap(), GetGOInfo()->chest.eventId, user, this); } return; @@ -1200,10 +1207,8 @@ void GameObject::Use(Unit* user) if (info->goober.eventId) { - DEBUG_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "Goober ScriptStart id %u for GO entry %u (GUID %u).", info->goober.eventId, GetEntry(), GetGUIDLow()); - - if (!sScriptMgr.OnProcessEvent(info->goober.eventId, player, this, true)) - GetMap()->ScriptsStart(sEventScripts, info->goober.eventId, player, this); + DEBUG_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "Goober ScriptStart id %u for %s (Used by %s).", info->goober.eventId, GetGuidStr().c_str(), player->GetGuidStr().c_str()); + StartEvents_Event(GetMap(), info->goober.eventId, player, this); } // possible quest objective for active quests @@ -1250,10 +1255,7 @@ void GameObject::Use(Unit* user) player->SendCinematicStart(info->camera.cinematicId); if (info->camera.eventID) - { - if (!sScriptMgr.OnProcessEvent(info->camera.eventID, player, this, true)) - GetMap()->ScriptsStart(sEventScripts, info->camera.eventID, player, this); - } + StartEvents_Event(GetMap(), info->camera.eventID, player, this); return; } @@ -1711,9 +1713,9 @@ bool GameObject::IsHostileTo(Unit const* unit) const if (Unit const* targetOwner = unit->GetCharmerOrOwner()) return IsHostileTo(targetOwner); - // for not set faction case (wild object) use hostile case + // for not set faction case: be hostile towards player, not hostile towards not-players if (!GetGOInfo()->faction) - return true; + return unit->IsControlledByPlayer(); // faction base cases FactionTemplateEntry const* tester_faction = sFactionTemplateStore.LookupEntry(GetGOInfo()->faction); @@ -1784,10 +1786,48 @@ bool GameObject::IsFriendlyTo(Unit const* unit) const return tester_faction->IsFriendlyTo(*target_faction); } +void GameObject::SetLootState(LootState state) +{ + m_lootState = state; + UpdateCollisionState(); +} + +void GameObject::SetGoState(GOState state) +{ + SetByteValue(GAMEOBJECT_BYTES_1, 0, state); + UpdateCollisionState(); +} + void GameObject::SetDisplayId(uint32 modelId) { SetUInt32Value(GAMEOBJECT_DISPLAYID, modelId); m_displayInfo = sGameObjectDisplayInfoStore.LookupEntry(modelId); + UpdateModel(); +} + +void GameObject::SetPhaseMask(uint32 newPhaseMask, bool update) +{ + WorldObject::SetPhaseMask(newPhaseMask, update); + UpdateCollisionState(); +} + +void GameObject::UpdateCollisionState() const +{ + if (!m_model || !IsInWorld()) + return; + + m_model->enable(IsCollisionEnabled() ? GetPhaseMask() : 0); +} + +void GameObject::UpdateModel() +{ + if (m_model && IsInWorld() && GetMap()->ContainsGameObjectModel(*m_model)) + GetMap()->RemoveGameObjectModel(*m_model); + delete m_model; + + m_model = GameObjectModel::construct(this); + if (m_model) + GetMap()->InsertGameObjectModel(*m_model); } void GameObject::StartGroupLoot(Group* group, uint32 timer) @@ -1872,11 +1912,14 @@ void GameObject::SetLootRecipient(Unit* pUnit) float GameObject::GetObjectBoundingRadius() const { - // FIXME: - // 1. This is clearly hack way because GameObjectDisplayInfoEntry have 6 floats related to GO sizes, but better that use DEFAULT_WORLD_OBJECT_SIZE - // 2. In some cases this must be only interactive size, not GO size, current way can affect creature target point auto-selection in strange ways for big underground/virtual GOs - /*if (m_displayInfo) - return fabs(m_displayInfo->unknown12) * GetObjectScale();*/ + if (m_displayInfo) + { + float dx = m_displayInfo->geoBoxMaxX - m_displayInfo->geoBoxMinX; + float dy = m_displayInfo->geoBoxMaxY - m_displayInfo->geoBoxMinY; + float dz = m_displayInfo->geoBoxMaxZ - m_displayInfo->geoBoxMinZ; + + return (std::abs(dx) + std::abs(dy) + std::abs(dz)) / 2 * GetObjectScale(); + } return DEFAULT_WORLD_OBJECT_SIZE; } @@ -2150,23 +2193,132 @@ void GameObject::TickCapturePoint() } if (eventId) - { - // Notify the battleground or outdoor pvp script - if (BattleGround* bg = (*capturingPlayers.begin())->GetBattleGround()) - { - // Allow only certain events to be handled by other script engines - if (bg->HandleEvent(eventId, this)) - return; - } - else if (OutdoorPvP* outdoorPvP = sOutdoorPvPMgr.GetScript((*capturingPlayers.begin())->GetCachedZoneId())) - { - // Allow only certain events to be handled by other script engines - if (outdoorPvP->HandleEvent(eventId, this)) - return; - } - - // Send script event to SD2 and database as well - this can be used for summoning creatures, casting specific spells or spawning GOs - if (!sScriptMgr.OnProcessEvent(eventId, this, this, true)) - GetMap()->ScriptsStart(sEventScripts, eventId, this, this); - } + StartEvents_Event(GetMap(), eventId, this, this, true, *capturingPlayers.begin()); +} + +// //////////////////////////////////////////////////////////////////////////////////////////////// +// Destructible GO handling +// //////////////////////////////////////////////////////////////////////////////////////////////// +void GameObject::DealGameObjectDamage(uint32 damage, uint32 spell, Unit* caster) +{ + MANGOS_ASSERT(GetGoType() == GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING); + MANGOS_ASSERT(spell && sSpellStore.LookupEntry(spell) && caster); + + if (!damage) + return; + + ForceGameObjectHealth(-int32(damage), caster); + + WorldPacket data(SMSG_DESTRUCTIBLE_BUILDING_DAMAGE, 9+9+9+4+4); + data << GetPackGUID(); + data << caster->GetPackGUID(); + data << caster->GetCharmerOrOwnerOrSelf()->GetPackGUID(); + data << uint32(damage); + data << uint32(spell); + SendMessageToSet(&data, false); +} + +void GameObject::RebuildGameObject(uint32 spell, Unit* caster) +{ + MANGOS_ASSERT(GetGoType() == GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING); + MANGOS_ASSERT(caster); + + ForceGameObjectHealth(0, caster); +} + +void GameObject::ForceGameObjectHealth(int32 diff, Unit* caster) +{ + MANGOS_ASSERT(GetGoType() == GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING); + MANGOS_ASSERT(caster || diff >= 0); + + if (diff < 0) // Taken damage + { + DEBUG_FILTER_LOG(LOG_FILTER_DAMAGE, "DestructibleGO: %s taken damage %u dealt by %s", GetGuidStr().c_str(), uint32(-diff), caster->GetGuidStr().c_str()); + + if (m_useTimes > uint32(-diff)) + m_useTimes += diff; + else + m_useTimes = 0; + } + else if (diff == 0 && GetMaxHealth()) // Rebuild - TODO: Rebuilding over time with special display-id? + { + DEBUG_FILTER_LOG(LOG_FILTER_DAMAGE, "DestructibleGO: %s start rebuild by %s", GetGuidStr().c_str(), caster->GetGuidStr().c_str()); + + m_useTimes = GetMaxHealth(); + // Start Event if exist + if (caster && m_goInfo->destructibleBuilding.rebuildingEvent) + StartEvents_Event(GetMap(), m_goInfo->destructibleBuilding.rebuildingEvent, this, caster->GetCharmerOrOwnerOrSelf(), true, caster->GetCharmerOrOwnerOrSelf()); + } + else // Set to value + m_useTimes = uint32(diff); + + uint32 newDisplayId = 0xFFFFFFFF; // Set to invalid -1 to track if we switched to a change state + DestructibleModelDataEntry const* destructibleInfo = sDestructibleModelDataStore.LookupEntry(m_goInfo->destructibleBuilding.destructibleData); + + // Get Current State - Note about order: Important for GetMaxHealth() == 0 + if (m_useTimes == GetMaxHealth()) // Full Health + { + DEBUG_FILTER_LOG(LOG_FILTER_DAMAGE, "DestructibleGO: %s set to full health %u", GetGuidStr().c_str(), m_useTimes); + + RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_UNK_9 | GO_FLAG_UNK_10 | GO_FLAG_UNK_11); + newDisplayId = m_goInfo->displayId; + + // Start Event if exist + if (caster && m_goInfo->destructibleBuilding.intactEvent) + StartEvents_Event(GetMap(), m_goInfo->destructibleBuilding.intactEvent, this, caster->GetCharmerOrOwnerOrSelf(), true, caster->GetCharmerOrOwnerOrSelf()); + } + else if (m_useTimes == 0) // Destroyed + { + if (!HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_UNK_11)) // Was not destroyed before + { + DEBUG_FILTER_LOG(LOG_FILTER_DAMAGE, "DestructibleGO: %s got destroyed", GetGuidStr().c_str()); + + RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_UNK_9 | GO_FLAG_UNK_10); + SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_UNK_11); + + // Get destroyed DisplayId + if ((!m_goInfo->destructibleBuilding.destroyedDisplayId || m_goInfo->destructibleBuilding.destroyedDisplayId == 1) && destructibleInfo) + newDisplayId = destructibleInfo->destroyedDisplayId; + else + newDisplayId = m_goInfo->destructibleBuilding.destroyedDisplayId; + + if (!newDisplayId) // No proper destroyed display ID exists, fetch damaged + { + if ((!m_goInfo->destructibleBuilding.damagedDisplayId || m_goInfo->destructibleBuilding.damagedDisplayId == 1) && destructibleInfo) + newDisplayId = destructibleInfo->damagedDisplayId; + else + newDisplayId = m_goInfo->destructibleBuilding.damagedDisplayId; + } + + // Start Event if exist + if (caster && m_goInfo->destructibleBuilding.destroyedEvent) + StartEvents_Event(GetMap(), m_goInfo->destructibleBuilding.destroyedEvent, this, caster->GetCharmerOrOwnerOrSelf(), true, caster->GetCharmerOrOwnerOrSelf()); + } + } + else if (m_useTimes <= m_goInfo->destructibleBuilding.damagedNumHits) // Damaged + { + if (!HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_UNK_10)) // Was not damaged before + { + DEBUG_FILTER_LOG(LOG_FILTER_DAMAGE, "DestructibleGO: %s got damaged (health now %u)", GetGuidStr().c_str(), m_useTimes); + + SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_UNK_10); + + // Get damaged DisplayId + if ((!m_goInfo->destructibleBuilding.damagedDisplayId || m_goInfo->destructibleBuilding.damagedDisplayId == 1) && destructibleInfo) + newDisplayId = destructibleInfo->damagedDisplayId; + else + newDisplayId = m_goInfo->destructibleBuilding.damagedDisplayId; + + // Start Event if exist + if (caster && m_goInfo->destructibleBuilding.damagedEvent) + StartEvents_Event(GetMap(), m_goInfo->destructibleBuilding.damagedEvent, this, caster->GetCharmerOrOwnerOrSelf(), true, caster->GetCharmerOrOwnerOrSelf()); + } + } + + // Set display Id + if (newDisplayId != 0xFFFFFFFF && newDisplayId != GetDisplayId() && newDisplayId) + SetDisplayId(newDisplayId); + + // Set health + SetGoAnimProgress(GetMaxHealth() ? m_useTimes * 255 / GetMaxHealth() : 255); } diff --git a/src/shared/revision_nr.h b/src/shared/revision_nr.h index 72fa1d678..65a6a9ac1 100644 --- a/src/shared/revision_nr.h +++ b/src/shared/revision_nr.h @@ -1,4 +1,4 @@ #ifndef __REVISION_NR_H__ #define __REVISION_NR_H__ - #define REVISION_NR "12552" + #define REVISION_NR "12553" #endif // __REVISION_NR_H__