aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sql/updates/hotfixes/master/2024_03_09_00_hotfixes.sql42
-rw-r--r--src/server/database/Database/Implementation/HotfixDatabase.cpp6
-rw-r--r--src/server/database/Database/Implementation/HotfixDatabase.h4
-rw-r--r--src/server/game/DataStores/DB2LoadInfo.h20
-rw-r--r--src/server/game/DataStores/DB2Stores.cpp10
-rw-r--r--src/server/game/DataStores/DB2Stores.h1
-rw-r--r--src/server/game/DataStores/DB2Structure.h18
-rw-r--r--src/server/game/DataStores/DBCEnums.h21
-rw-r--r--src/server/game/DataStores/GameTables.cpp6
-rw-r--r--src/server/game/Entities/Creature/Creature.cpp7
-rw-r--r--src/server/game/Entities/Creature/CreatureData.h2
-rw-r--r--src/server/game/Entities/GameObject/GameObject.cpp43
-rw-r--r--src/server/game/Entities/GameObject/GameObject.h10
-rw-r--r--src/server/game/Entities/GameObject/GameObjectData.h23
-rw-r--r--src/server/game/Entities/Player/Player.cpp105
-rw-r--r--src/server/game/Entities/Unit/Unit.cpp38
-rw-r--r--src/server/game/Entities/Unit/Unit.h10
-rw-r--r--src/server/game/Entities/Unit/Vignette.cpp128
-rw-r--r--src/server/game/Entities/Unit/Vignette.h57
-rw-r--r--src/server/game/Globals/ObjectMgr.cpp6
-rw-r--r--src/server/game/Maps/Map.cpp54
-rw-r--r--src/server/game/Maps/Map.h13
-rw-r--r--src/server/game/Server/Packets/VignettePackets.cpp59
-rw-r--r--src/server/game/Server/Packets/VignettePackets.h58
-rw-r--r--src/server/game/Server/Protocol/Opcodes.cpp2
-rw-r--r--src/server/game/Spells/Auras/SpellAuraEffects.cpp10
-rw-r--r--src/server/game/Spells/Auras/SpellAuraEffects.h1
27 files changed, 743 insertions, 11 deletions
diff --git a/sql/updates/hotfixes/master/2024_03_09_00_hotfixes.sql b/sql/updates/hotfixes/master/2024_03_09_00_hotfixes.sql
new file mode 100644
index 00000000000..c890f0ee9e1
--- /dev/null
+++ b/sql/updates/hotfixes/master/2024_03_09_00_hotfixes.sql
@@ -0,0 +1,42 @@
+--
+-- Table structure for table `vignette`
+--
+DROP TABLE IF EXISTS `vignette`;
+CREATE TABLE `vignette` (
+ `ID` int unsigned NOT NULL DEFAULT '0',
+ `Name` text,
+ `PlayerConditionID` int unsigned NOT NULL DEFAULT '0',
+ `VisibleTrackingQuestID` int unsigned NOT NULL DEFAULT '0',
+ `QuestFeedbackEffectID` int unsigned NOT NULL DEFAULT '0',
+ `Flags` int NOT NULL DEFAULT '0',
+ `MaxHeight` float NOT NULL DEFAULT '0',
+ `MinHeight` float NOT NULL DEFAULT '0',
+ `VignetteType` tinyint NOT NULL DEFAULT '0',
+ `RewardQuestID` int NOT NULL DEFAULT '0',
+ `UiWidgetSetID` int NOT NULL DEFAULT '0',
+ `VerifiedBuild` int NOT NULL DEFAULT '0',
+ PRIMARY KEY (`ID`,`VerifiedBuild`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+--
+-- Table structure for table `vignette_locale`
+--
+DROP TABLE IF EXISTS `vignette_locale`;
+CREATE TABLE `vignette_locale` (
+ `ID` int unsigned NOT NULL DEFAULT '0',
+ `locale` varchar(4) NOT NULL,
+ `Name_lang` text,
+ `VerifiedBuild` int NOT NULL DEFAULT '0',
+ PRIMARY KEY (`ID`,`locale`,`VerifiedBuild`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
+/*!50500 PARTITION BY LIST COLUMNS(locale)
+(PARTITION deDE VALUES IN ('deDE') ENGINE = InnoDB,
+ PARTITION esES VALUES IN ('esES') ENGINE = InnoDB,
+ PARTITION esMX VALUES IN ('esMX') ENGINE = InnoDB,
+ PARTITION frFR VALUES IN ('frFR') ENGINE = InnoDB,
+ PARTITION itIT VALUES IN ('itIT') ENGINE = InnoDB,
+ PARTITION koKR VALUES IN ('koKR') ENGINE = InnoDB,
+ PARTITION ptBR VALUES IN ('ptBR') ENGINE = InnoDB,
+ PARTITION ruRU VALUES IN ('ruRU') ENGINE = InnoDB,
+ PARTITION zhCN VALUES IN ('zhCN') ENGINE = InnoDB,
+ PARTITION zhTW VALUES IN ('zhTW') ENGINE = InnoDB) */;
diff --git a/src/server/database/Database/Implementation/HotfixDatabase.cpp b/src/server/database/Database/Implementation/HotfixDatabase.cpp
index 010fb0498e2..55281c309ea 100644
--- a/src/server/database/Database/Implementation/HotfixDatabase.cpp
+++ b/src/server/database/Database/Implementation/HotfixDatabase.cpp
@@ -1931,6 +1931,12 @@ void HotfixDatabaseConnection::DoPrepareStatements()
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
PREPARE_MAX_ID_STMT(HOTFIX_SEL_VEHICLE_SEAT, "SELECT MAX(ID) + 1 FROM vehicle_seat", CONNECTION_SYNCH);
+ // Vignette.db2
+ PrepareStatement(HOTFIX_SEL_VIGNETTE, "SELECT ID, Name, PlayerConditionID, VisibleTrackingQuestID, QuestFeedbackEffectID, Flags, MaxHeight, "
+ "MinHeight, VignetteType, RewardQuestID, UiWidgetSetID FROM vignette WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
+ PREPARE_MAX_ID_STMT(HOTFIX_SEL_VIGNETTE, "SELECT MAX(ID) + 1 FROM vignette", CONNECTION_SYNCH);
+ PREPARE_LOCALE_STMT(HOTFIX_SEL_VIGNETTE, "SELECT ID, Name_lang FROM vignette_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
+
// WmoAreaTable.db2
PrepareStatement(HOTFIX_SEL_WMO_AREA_TABLE, "SELECT AreaName, ID, WmoID, NameSetID, WmoGroupID, SoundProviderPref, SoundProviderPrefUnderwater, "
"AmbienceID, UwAmbience, ZoneMusic, UwZoneMusic, IntroSound, UwIntroSound, AreaTableID, Flags FROM wmo_area_table"
diff --git a/src/server/database/Database/Implementation/HotfixDatabase.h b/src/server/database/Database/Implementation/HotfixDatabase.h
index 2df1c260a97..45600062398 100644
--- a/src/server/database/Database/Implementation/HotfixDatabase.h
+++ b/src/server/database/Database/Implementation/HotfixDatabase.h
@@ -1114,6 +1114,10 @@ enum HotfixDatabaseStatements : uint32
HOTFIX_SEL_VEHICLE_SEAT,
HOTFIX_SEL_VEHICLE_SEAT_MAX_ID,
+ HOTFIX_SEL_VIGNETTE,
+ HOTFIX_SEL_VIGNETTE_MAX_ID,
+ HOTFIX_SEL_VIGNETTE_LOCALE,
+
HOTFIX_SEL_WMO_AREA_TABLE,
HOTFIX_SEL_WMO_AREA_TABLE_MAX_ID,
HOTFIX_SEL_WMO_AREA_TABLE_LOCALE,
diff --git a/src/server/game/DataStores/DB2LoadInfo.h b/src/server/game/DataStores/DB2LoadInfo.h
index d051c0fa472..843d3256632 100644
--- a/src/server/game/DataStores/DB2LoadInfo.h
+++ b/src/server/game/DataStores/DB2LoadInfo.h
@@ -6415,6 +6415,26 @@ struct VehicleSeatLoadInfo
static constexpr DB2LoadInfo Instance{ Fields, 66, &VehicleSeatMeta::Instance, HOTFIX_SEL_VEHICLE_SEAT };
};
+struct VignetteLoadInfo
+{
+ static constexpr DB2FieldMeta Fields[11] =
+ {
+ { false, FT_INT, "ID" },
+ { false, FT_STRING, "Name" },
+ { false, FT_INT, "PlayerConditionID" },
+ { false, FT_INT, "VisibleTrackingQuestID" },
+ { false, FT_INT, "QuestFeedbackEffectID" },
+ { true, FT_INT, "Flags" },
+ { false, FT_FLOAT, "MaxHeight" },
+ { false, FT_FLOAT, "MinHeight" },
+ { true, FT_BYTE, "VignetteType" },
+ { true, FT_INT, "RewardQuestID" },
+ { true, FT_INT, "UiWidgetSetID" },
+ };
+
+ static constexpr DB2LoadInfo Instance{ Fields, 11, &VignetteMeta::Instance, HOTFIX_SEL_VIGNETTE };
+};
+
struct WmoAreaTableLoadInfo
{
static constexpr DB2FieldMeta Fields[15] =
diff --git a/src/server/game/DataStores/DB2Stores.cpp b/src/server/game/DataStores/DB2Stores.cpp
index 8c849f43061..8bd70407d92 100644
--- a/src/server/game/DataStores/DB2Stores.cpp
+++ b/src/server/game/DataStores/DB2Stores.cpp
@@ -370,6 +370,7 @@ DB2Storage<UnitConditionEntry> sUnitConditionStore("UnitConditi
DB2Storage<UnitPowerBarEntry> sUnitPowerBarStore("UnitPowerBar.db2", &UnitPowerBarLoadInfo::Instance);
DB2Storage<VehicleEntry> sVehicleStore("Vehicle.db2", &VehicleLoadInfo::Instance);
DB2Storage<VehicleSeatEntry> sVehicleSeatStore("VehicleSeat.db2", &VehicleSeatLoadInfo::Instance);
+DB2Storage<VignetteEntry> sVignetteStore("Vignette.db2", &VignetteLoadInfo::Instance);
DB2Storage<WMOAreaTableEntry> sWMOAreaTableStore("WMOAreaTable.db2", &WmoAreaTableLoadInfo::Instance);
DB2Storage<WorldEffectEntry> sWorldEffectStore("WorldEffect.db2", &WorldEffectLoadInfo::Instance);
DB2Storage<WorldMapOverlayEntry> sWorldMapOverlayStore("WorldMapOverlay.db2", &WorldMapOverlayLoadInfo::Instance);
@@ -519,9 +520,6 @@ namespace
std::unordered_map<uint32, std::unordered_set<uint32>> _pvpStatIdsByMap;
}
-template<typename T>
-constexpr std::size_t GetCppRecordSize(DB2Storage<T> const&) { return sizeof(T); }
-
void LoadDB2(std::bitset<TOTAL_LOCALES>& availableDb2Locales, std::vector<std::string>& errlist, StorageMap& stores, DB2StorageBase* storage, std::string const& db2Path,
LocaleConstant defaultLocale, std::size_t cppRecordSize)
{
@@ -638,7 +636,10 @@ uint32 DB2Manager::LoadStores(std::string const& dataPath, LocaleConstant defaul
if (!availableDb2Locales[defaultLocale])
return 0;
-#define LOAD_DB2(store) LoadDB2(availableDb2Locales, loadErrors, _stores, &(store), db2Path, defaultLocale, GetCppRecordSize(store))
+ auto LOAD_DB2 = [&]<typename T>(DB2Storage<T>& store)
+ {
+ LoadDB2(availableDb2Locales, loadErrors, _stores, &store, db2Path, defaultLocale, sizeof(T));
+ };
LOAD_DB2(sAchievementStore);
LOAD_DB2(sAchievementCategoryStore);
@@ -969,6 +970,7 @@ uint32 DB2Manager::LoadStores(std::string const& dataPath, LocaleConstant defaul
LOAD_DB2(sUnitPowerBarStore);
LOAD_DB2(sVehicleStore);
LOAD_DB2(sVehicleSeatStore);
+ LOAD_DB2(sVignetteStore);
LOAD_DB2(sWMOAreaTableStore);
LOAD_DB2(sWorldEffectStore);
LOAD_DB2(sWorldMapOverlayStore);
diff --git a/src/server/game/DataStores/DB2Stores.h b/src/server/game/DataStores/DB2Stores.h
index 91609057a35..97c8dc502a4 100644
--- a/src/server/game/DataStores/DB2Stores.h
+++ b/src/server/game/DataStores/DB2Stores.h
@@ -298,6 +298,7 @@ TC_GAME_API extern DB2Storage<UnitConditionEntry> sUnitConditi
TC_GAME_API extern DB2Storage<UnitPowerBarEntry> sUnitPowerBarStore;
TC_GAME_API extern DB2Storage<VehicleEntry> sVehicleStore;
TC_GAME_API extern DB2Storage<VehicleSeatEntry> sVehicleSeatStore;
+TC_GAME_API extern DB2Storage<VignetteEntry> sVignetteStore;
TC_GAME_API extern DB2Storage<WorldEffectEntry> sWorldEffectStore;
TC_GAME_API extern DB2Storage<WorldMapOverlayEntry> sWorldMapOverlayStore;
TC_GAME_API extern DB2Storage<WorldStateExpressionEntry> sWorldStateExpressionStore;
diff --git a/src/server/game/DataStores/DB2Structure.h b/src/server/game/DataStores/DB2Structure.h
index 455b3f78ea8..5a6d8144dfe 100644
--- a/src/server/game/DataStores/DB2Structure.h
+++ b/src/server/game/DataStores/DB2Structure.h
@@ -4571,6 +4571,24 @@ struct VehicleSeatEntry
inline bool IsEjectable() const { return HasFlag(VEHICLE_SEAT_FLAG_B_EJECTABLE); }
};
+struct VignetteEntry
+{
+ uint32 ID;
+ LocalizedString Name;
+ uint32 PlayerConditionID;
+ uint32 VisibleTrackingQuestID;
+ uint32 QuestFeedbackEffectID;
+ int32 Flags;
+ float MaxHeight;
+ float MinHeight;
+ int8 VignetteType;
+ int32 RewardQuestID;
+ int32 UiWidgetSetID;
+
+ EnumFlag<VignetteFlags> GetFlags() const { return static_cast<VignetteFlags>(Flags); }
+ bool IsInfiniteAOI() const { return GetFlags().HasFlag(VignetteFlags::InfiniteAOI | VignetteFlags::ZoneInfiniteAOI); }
+};
+
struct WMOAreaTableEntry
{
LocalizedString AreaName;
diff --git a/src/server/game/DataStores/DBCEnums.h b/src/server/game/DataStores/DBCEnums.h
index 49d853218c0..92c71f389e1 100644
--- a/src/server/game/DataStores/DBCEnums.h
+++ b/src/server/game/DataStores/DBCEnums.h
@@ -2463,6 +2463,27 @@ enum VehicleSeatFlagsB
VEHICLE_SEAT_FLAG_B_VEHICLE_PLAYERFRAME_UI = 0x80000000 // Lua_UnitHasVehiclePlayerFrameUI - actually checked for flagsb &~ 0x80000000
};
+enum class VignetteFlags
+{
+ InfiniteAOI = 0x000001,
+ ShowOnMap = 0x000002,
+ PingMinimap = 0x000004,
+ TestVisibilityRules = 0x000008,
+ VerticalRangeIsAbsolute = 0x000010,
+ Unique = 0x000020,
+ ZoneInfiniteAOI = 0x000040,
+ PersistsThroughDeath = 0x000080,
+
+ DontShowOnMinimap = 0x000200,
+ HasTooltip = 0x000400,
+
+ AdditionalHeightReq = 0x008000, // Must be within 10 yards of vignette Z coord (hardcoded in client)
+ HideOnContinentMaps = 0x010000,
+ NoPaddingAboveUiWidgets = 0x020000
+};
+
+DEFINE_ENUM_FLAG(VignetteFlags);
+
enum WorldMapTransformsFlags
{
WORLD_MAP_TRANSFORMS_FLAG_DUNGEON = 0x04
diff --git a/src/server/game/DataStores/GameTables.cpp b/src/server/game/DataStores/GameTables.cpp
index 2f786ddb63b..6483b8fd63c 100644
--- a/src/server/game/DataStores/GameTables.cpp
+++ b/src/server/game/DataStores/GameTables.cpp
@@ -110,7 +110,11 @@ void LoadGameTables(std::string const& dataPath)
std::vector<std::string> bad_gt_files;
uint32 gameTableCount = 0, expectedGameTableCount = 0;
-#define LOAD_GT(store, file) gameTableCount += LoadGameTable(bad_gt_files, store, gtPath / file); ++expectedGameTableCount;
+ auto LOAD_GT = [&]<typename T>(GameTable<T>& gameTable, char const* file)
+ {
+ gameTableCount += LoadGameTable(bad_gt_files, gameTable, gtPath / file);
+ ++expectedGameTableCount;
+ };
LOAD_GT(sArtifactKnowledgeMultiplierGameTable, "ArtifactKnowledgeMultiplier.txt");
LOAD_GT(sArtifactLevelXPGameTable, "ArtifactLevelXP.txt");
diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp
index c122e51800a..2ceaa8ce7b1 100644
--- a/src/server/game/Entities/Creature/Creature.cpp
+++ b/src/server/game/Entities/Creature/Creature.cpp
@@ -1820,6 +1820,10 @@ bool Creature::CreateFromProto(ObjectGuid::LowType guidlow, uint32 entry, Creatu
if (CreateVehicleKit(vehId, entry, true))
UpdateDisplayPower();
+ if (!IsPet())
+ if (uint32 vignetteId = GetCreatureTemplate()->VignetteID)
+ SetVignette(vignetteId);
+
return true;
}
@@ -2284,6 +2288,9 @@ void Creature::setDeathState(DeathState s)
RemoveUnitFlag(UNIT_FLAG_IN_COMBAT);
SetMeleeDamageSchool(SpellSchools(cInfo->dmgschool));
+
+ if (uint32 vignetteId = cInfo->VignetteID)
+ SetVignette(vignetteId);
}
Motion_Initialize();
diff --git a/src/server/game/Entities/Creature/CreatureData.h b/src/server/game/Entities/Creature/CreatureData.h
index 1e386ff2da5..976244a8f85 100644
--- a/src/server/game/Entities/Creature/CreatureData.h
+++ b/src/server/game/Entities/Creature/CreatureData.h
@@ -488,7 +488,7 @@ struct TC_GAME_API CreatureTemplate
std::vector<uint32> GossipMenuIds;
std::unordered_map<Difficulty, CreatureDifficulty> difficultyStore;
uint32 RequiredExpansion;
- uint32 VignetteID; /// @todo Read Vignette.db2
+ uint32 VignetteID;
uint32 faction;
uint64 npcflag;
float speed_walk;
diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp
index ecc2ba48585..dacdb902d30 100644
--- a/src/server/game/Entities/GameObject/GameObject.cpp
+++ b/src/server/game/Entities/GameObject/GameObject.cpp
@@ -53,6 +53,7 @@
#include "SpellAuras.h"
#include "SpellMgr.h"
#include "Transport.h"
+#include "Vignette.h"
#include "World.h"
#include <G3D/Box.h>
#include <G3D/CoordinateFrame.h>
@@ -874,6 +875,8 @@ std::string const& GameObject::GetAIName() const
void GameObject::CleanupsBeforeDelete(bool finalCleanup)
{
+ SetVignette(0);
+
WorldObject::CleanupsBeforeDelete(finalCleanup);
RemoveFromOwner();
@@ -1152,6 +1155,9 @@ bool GameObject::Create(uint32 entry, Map* map, Position const& pos, QuaternionD
_animKitId = gameObjectAddon->AIAnimKitID;
}
+ if (uint32 vignetteId = GetGOInfo()->GetSpawnVignette())
+ SetVignette(vignetteId);
+
LastUsedScriptID = GetGOInfo()->ScriptId;
m_stringIds[0] = goInfo->StringId;
@@ -3425,6 +3431,24 @@ void GameObject::Use(Unit* user)
break;
}
+ if (m_vignette)
+ {
+ if (Player* player = user->ToPlayer())
+ {
+ if (Quest const* reward = sObjectMgr->GetQuestTemplate(m_vignette->Data->RewardQuestID))
+ if (!player->GetQuestRewardStatus(m_vignette->Data->RewardQuestID))
+ player->RewardQuest(reward, LootItemType::Item, 0, this, false);
+
+ if (m_vignette->Data->VisibleTrackingQuestID)
+ player->SetRewardedQuest(m_vignette->Data->VisibleTrackingQuestID);
+ }
+
+ // only unregister it from visibility (need to keep vignette for other gameobject users in case its usable by multiple players
+ // to flag their quest completion
+ if (GetGOInfo()->ClearObjectVignetteonOpening())
+ Vignettes::Remove(*m_vignette, this);
+ }
+
if (!spellId)
return;
@@ -4090,6 +4114,10 @@ void GameObject::AfterRelocation()
if (m_goTypeImpl)
m_goTypeImpl->OnRelocated();
+ // TODO: on heartbeat
+ if (m_vignette)
+ Vignettes::Update(*m_vignette, this);
+
UpdateObjectVisibility(false);
}
@@ -4167,6 +4195,21 @@ void GameObject::SetAnimKitId(uint16 animKitId, bool oneshot)
SendMessageToSet(activateAnimKit.Write(), true);
}
+void GameObject::SetVignette(uint32 vignetteId)
+{
+ if (m_vignette)
+ {
+ if (m_vignette->Data->ID == vignetteId)
+ return;
+
+ Vignettes::Remove(*m_vignette, this);
+ m_vignette = nullptr;
+ }
+
+ if (VignetteEntry const* vignette = sVignetteStore.LookupEntry(vignetteId))
+ m_vignette = Vignettes::Create(vignette, this);
+}
+
void GameObject::SetSpellVisualId(int32 spellVisualId, ObjectGuid activatorGuid)
{
SetUpdateFieldValue(m_values.ModifyValue(&GameObject::m_gameObjectData).ModifyValue(&UF::GameObjectData::SpellVisualID), spellVisualId);
diff --git a/src/server/game/Entities/GameObject/GameObject.h b/src/server/game/Entities/GameObject/GameObject.h
index 9926fd82cca..9b8651f1133 100644
--- a/src/server/game/Entities/GameObject/GameObject.h
+++ b/src/server/game/Entities/GameObject/GameObject.h
@@ -35,6 +35,11 @@ struct Loot;
struct TransportAnimation;
enum TriggerCastFlags : uint32;
+namespace Vignettes
+{
+struct VignetteData;
+}
+
// enum for GAMEOBJECT_TYPE_NEW_FLAG
// values taken from world state
enum class FlagState : uint8
@@ -419,6 +424,9 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject>
uint32 GetWorldEffectID() const { return _worldEffectID; }
void SetWorldEffectID(uint32 worldEffectID) { _worldEffectID = worldEffectID; }
+ Vignettes::VignetteData const* GetVignette() const { return m_vignette.get(); }
+ void SetVignette(uint32 vignetteId);
+
void SetSpellVisualId(int32 spellVisualId, ObjectGuid activatorGuid = ObjectGuid::Empty);
void AssaultCapturePoint(Player* player);
void UpdateCapturePoint();
@@ -502,6 +510,8 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject>
uint16 _animKitId;
uint32 _worldEffectID;
+ std::unique_ptr<Vignettes::VignetteData> m_vignette;
+
struct PerPlayerState
{
SystemTimePoint ValidUntil = SystemTimePoint::min();
diff --git a/src/server/game/Entities/GameObject/GameObjectData.h b/src/server/game/Entities/GameObject/GameObjectData.h
index deee786d1e6..1c05153c550 100644
--- a/src/server/game/Entities/GameObject/GameObjectData.h
+++ b/src/server/game/Entities/GameObject/GameObjectData.h
@@ -1277,6 +1277,29 @@ struct GameObjectTemplate
}
}
+ uint32 GetSpawnVignette() const
+ {
+ switch (type)
+ {
+ case GAMEOBJECT_TYPE_CHEST: return chest.SpawnVignette;
+ case GAMEOBJECT_TYPE_GOOBER: return goober.SpawnVignette;
+ case GAMEOBJECT_TYPE_NEW_FLAG: return newflag.SpawnVignette;
+ case GAMEOBJECT_TYPE_NEW_FLAG_DROP: return newflagdrop.SpawnVignette;
+ case GAMEOBJECT_TYPE_CAPTURE_POINT: return capturePoint.SpawnVignette;
+ case GAMEOBJECT_TYPE_GATHERING_NODE: return gatheringNode.SpawnVignette;
+ default: return 0;
+ }
+ }
+
+ bool ClearObjectVignetteonOpening() const
+ {
+ switch (type)
+ {
+ case GAMEOBJECT_TYPE_GATHERING_NODE: return gatheringNode.ClearObjectVignetteonOpening != 0;
+ default: return false;
+ }
+ }
+
uint32 GetSpellFocusType() const
{
switch (type)
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index 337055bf4ab..469bbee9e74 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -130,6 +130,8 @@
#include "Util.h"
#include "Vehicle.h"
#include "VehiclePackets.h"
+#include "Vignette.h"
+#include "VignettePackets.h"
#include "World.h"
#include "WorldPacket.h"
#include "WorldSession.h"
@@ -7623,6 +7625,24 @@ void Player::UpdateZone(uint32 newZone, uint32 newArea)
guild->UpdateMemberData(this, GUILD_MEMBER_DATA_ZONEID, newZone);
UpdateCriteria(CriteriaType::EnterTopLevelArea, newZone);
UpdateCriteria(CriteriaType::LeaveTopLevelArea, oldZone);
+
+ {
+ WorldPackets::Vignette::VignetteUpdate vignetteUpdate;
+
+ for (Vignettes::VignetteData const* vignette : GetMap()->GetInfiniteAOIVignettes())
+ {
+ if (!vignette->Data->GetFlags().HasFlag(VignetteFlags::ZoneInfiniteAOI))
+ continue;
+
+ if (vignette->ZoneID == newZone && Vignettes::CanSee(this, *vignette))
+ vignette->FillPacket(vignetteUpdate.Added);
+ else if (vignette->ZoneID == oldZone)
+ vignetteUpdate.Removed.push_back(vignette->Guid);
+ }
+
+ if (!vignetteUpdate.Added.IDs.empty() || !vignetteUpdate.Removed.empty())
+ SendDirectMessage(vignetteUpdate.Write());
+ }
}
}
@@ -23787,6 +23807,44 @@ inline void BeforeVisibilityDestroy<Creature>(Creature* t, Player* p)
{
if (p->GetPetGUID() == t->GetGUID() && t->IsPet())
t->ToPet()->Remove(PET_SAVE_NOT_IN_SLOT, true);
+
+ if (Vignettes::VignetteData const* vignette = t->GetVignette())
+ {
+ if (!vignette->Data->IsInfiniteAOI())
+ {
+ WorldPackets::Vignette::VignetteUpdate vignetteUpdate;
+ vignetteUpdate.Removed.push_back(vignette->Guid);
+ p->SendDirectMessage(vignetteUpdate.Write());
+ }
+ }
+}
+
+template<>
+inline void BeforeVisibilityDestroy<Player>(Player* t, Player* p)
+{
+ if (Vignettes::VignetteData const* vignette = t->GetVignette())
+ {
+ if (!vignette->Data->IsInfiniteAOI())
+ {
+ WorldPackets::Vignette::VignetteUpdate vignetteUpdate;
+ vignetteUpdate.Removed.push_back(vignette->Guid);
+ p->SendDirectMessage(vignetteUpdate.Write());
+ }
+ }
+}
+
+template<>
+inline void BeforeVisibilityDestroy<GameObject>(GameObject* t, Player* p)
+{
+ if (Vignettes::VignetteData const* vignette = t->GetVignette())
+ {
+ if (!vignette->Data->IsInfiniteAOI())
+ {
+ WorldPackets::Vignette::VignetteUpdate vignetteUpdate;
+ vignetteUpdate.Removed.push_back(vignette->Guid);
+ p->SendDirectMessage(vignetteUpdate.Write());
+ }
+ }
}
void Player::UpdateVisibilityOf(Trinity::IteratorPair<WorldObject**> targets)
@@ -23850,8 +23908,20 @@ void Player::UpdateVisibilityOf(WorldObject* target)
{
if (!CanSeeOrDetect(target, false, true))
{
- if (target->GetTypeId() == TYPEID_UNIT)
- BeforeVisibilityDestroy<Creature>(target->ToCreature(), this);
+ switch (target->GetTypeId())
+ {
+ case TYPEID_UNIT:
+ BeforeVisibilityDestroy<Creature>(target->ToCreature(), this);
+ break;
+ case TYPEID_PLAYER:
+ BeforeVisibilityDestroy<Player>(target->ToPlayer(), this);
+ break;
+ case TYPEID_GAMEOBJECT:
+ BeforeVisibilityDestroy<GameObject>(target->ToGameObject(), this);
+ break;
+ default:
+ break;
+ }
if (!target->IsDestroyedObject())
target->SendOutOfRangeForPlayer(this);
@@ -23927,6 +23997,16 @@ void Player::UpdateTriggerVisibility()
void Player::SendInitialVisiblePackets(WorldObject* target) const
{
+ auto sendVignette = [](Vignettes::VignetteData const& vignette, Player const* where)
+ {
+ if (!vignette.Data->IsInfiniteAOI() && Vignettes::CanSee(where, vignette))
+ {
+ WorldPackets::Vignette::VignetteUpdate vignetteUpdate;
+ vignette.FillPacket(vignetteUpdate.Added);
+ where->SendDirectMessage(vignetteUpdate.Write());
+ }
+ };
+
if (Unit* targetUnit = target->ToUnit())
{
SendAurasForTarget(targetUnit);
@@ -23935,6 +24015,14 @@ void Player::SendInitialVisiblePackets(WorldObject* target) const
if (targetUnit->HasUnitState(UNIT_STATE_MELEE_ATTACKING) && targetUnit->GetVictim())
targetUnit->SendMeleeAttackStart(targetUnit->GetVictim());
}
+
+ if (Vignettes::VignetteData const* vignette = targetUnit->GetVignette())
+ sendVignette(*vignette, this);
+ }
+ else if (GameObject* targetGo = target->ToGameObject())
+ {
+ if (Vignettes::VignetteData const* vignette = targetGo->GetVignette())
+ sendVignette(*vignette, this);
}
}
@@ -24233,6 +24321,19 @@ void Player::SendInitialPacketsAfterAddToMap()
{
UpdateVisibilityForPlayer();
+ // Send map wide vignettes before UpdateZone, that will send zone wide vignettes
+ // But first send on new map will wipe all vignettes on client
+ {
+ WorldPackets::Vignette::VignetteUpdate vignetteUpdate;
+ vignetteUpdate.ForceUpdate = true;
+
+ for (Vignettes::VignetteData const* vignette : GetMap()->GetInfiniteAOIVignettes())
+ if (!vignette->Data->GetFlags().HasFlag(VignetteFlags::ZoneInfiniteAOI) && Vignettes::CanSee(this, *vignette))
+ vignette->FillPacket(vignetteUpdate.Added);
+
+ SendDirectMessage(vignetteUpdate.Write());
+ }
+
// update zone
uint32 newzone, newarea;
GetZoneAndAreaId(newzone, newarea);
diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp
index fbcfd287752..bfdb97d3afb 100644
--- a/src/server/game/Entities/Unit/Unit.cpp
+++ b/src/server/game/Entities/Unit/Unit.cpp
@@ -85,6 +85,8 @@
#include "Util.h"
#include "Vehicle.h"
#include "VehiclePackets.h"
+#include "Vignette.h"
+#include "VignettePackets.h"
#include "World.h"
#include "WorldPacket.h"
#include "WorldSession.h"
@@ -8629,6 +8631,9 @@ void Unit::setDeathState(DeathState s)
SetEmoteState(EMOTE_ONESHOT_NONE);
SetStandState(UNIT_STAND_STATE_STAND);
+ if (m_vignette && !m_vignette->Data->GetFlags().HasFlag(VignetteFlags::PersistsThroughDeath))
+ SetVignette(0);
+
// players in instance don't have ZoneScript, but they have InstanceScript
if (ZoneScript* zoneScript = GetZoneScript() ? GetZoneScript() : GetInstanceScript())
zoneScript->OnUnitDeath(this);
@@ -9665,6 +9670,8 @@ void Unit::CleanupBeforeRemoveFromMap(bool finalCleanup)
// This needs to be before RemoveFromWorld to make GetCaster() return a valid pointer on aura removal
InterruptNonMeleeSpells(true);
+ SetVignette(0);
+
if (IsInWorld())
RemoveFromWorld();
else
@@ -10706,6 +10713,18 @@ void Unit::SetMeleeAnimKitId(uint16 animKitId)
}
}
+ if (Vignettes::VignetteData const* vignette = victim->GetVignette())
+ {
+ for (Player* tapper : tappers)
+ {
+ if (Quest const* reward = sObjectMgr->GetQuestTemplate(vignette->Data->RewardQuestID))
+ tapper->RewardQuest(reward, LootItemType::Item, 0, victim, false);
+
+ if (vignette->Data->VisibleTrackingQuestID)
+ tapper->SetRewardedQuest(vignette->Data->VisibleTrackingQuestID);
+ }
+ }
+
KillRewarder(Trinity::IteratorPair(tappers.data(), tappers.data() + tappers.size()), victim, false).Reward();
}
@@ -12412,6 +12431,10 @@ bool Unit::UpdatePosition(float x, float y, float z, float orientation, bool tel
if (isInWater)
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::Swimming);
+ // TODO: on heartbeat
+ if (m_vignette)
+ Vignettes::Update(*m_vignette, this);
+
return (relocated || turn);
}
@@ -13733,6 +13756,21 @@ float Unit::GetCollisionHeight() const
return collisionHeight == 0.0f ? DEFAULT_COLLISION_HEIGHT : collisionHeight;
}
+void Unit::SetVignette(uint32 vignetteId)
+{
+ if (m_vignette)
+ {
+ if (m_vignette->Data->ID == vignetteId)
+ return;
+
+ Vignettes::Remove(*m_vignette, this);
+ m_vignette = nullptr;
+ }
+
+ if (VignetteEntry const* vignette = sVignetteStore.LookupEntry(vignetteId))
+ m_vignette = Vignettes::Create(vignette, this);
+}
+
std::string Unit::GetDebugInfo() const
{
std::stringstream sstr;
diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h
index 0c17b38a2fe..e00fb1a44c9 100644
--- a/src/server/game/Entities/Unit/Unit.h
+++ b/src/server/game/Entities/Unit/Unit.h
@@ -111,6 +111,11 @@ namespace Movement
struct SpellEffectExtraData;
}
+namespace Vignettes
+{
+struct VignetteData;
+}
+
typedef std::list<Unit*> UnitList;
class TC_GAME_API DispelableAura
@@ -1799,6 +1804,9 @@ class TC_GAME_API Unit : public WorldObject
}
void ClearWorldEffects() { ClearDynamicUpdateFieldValues(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::WorldEffects)); }
+ Vignettes::VignetteData const* GetVignette() const { return m_vignette.get(); }
+ void SetVignette(uint32 vignetteId);
+
std::string GetDebugInfo() const override;
UF::UpdateField<UF::UnitData, 0, TYPEID_UNIT> m_unitData;
@@ -1903,6 +1911,8 @@ class TC_GAME_API Unit : public WorldObject
uint32 m_unitTypeMask;
LiquidTypeEntry const* _lastLiquid;
+ std::unique_ptr<Vignettes::VignetteData> m_vignette;
+
bool IsAlwaysVisibleFor(WorldObject const* seer) const override;
bool IsAlwaysDetectableFor(WorldObject const* seer) const override;
diff --git a/src/server/game/Entities/Unit/Vignette.cpp b/src/server/game/Entities/Unit/Vignette.cpp
new file mode 100644
index 00000000000..1f4aa074ea2
--- /dev/null
+++ b/src/server/game/Entities/Unit/Vignette.cpp
@@ -0,0 +1,128 @@
+/*
+ * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "Vignette.h"
+#include "CellImpl.h"
+#include "DB2Stores.h"
+#include "GridNotifiersImpl.h"
+#include "VignettePackets.h"
+
+namespace Vignettes
+{
+namespace
+{
+void UpdatePosition(VignetteData& vignette, WorldObject const* owner)
+{
+ vignette.Position = owner->GetPosition();
+ if (WmoLocation const* wmoLocation = owner->GetCurrentWmo())
+ {
+ vignette.WMOGroupID = wmoLocation->GroupId;
+ vignette.WMODoodadPlacementID = wmoLocation->UniqueId;
+ }
+}
+
+template<WorldPackets::Vignette::VignetteDataSet WorldPackets::Vignette::VignetteUpdate::* Field>
+void SendVignetteUpdate(VignetteData const& vignette, WorldObject const* owner)
+{
+ if (!owner->IsInWorld())
+ return;
+
+ WorldPackets::Vignette::VignetteUpdate vignetteUpdate;
+ vignette.FillPacket(vignetteUpdate.*Field);
+ vignetteUpdate.Write();
+
+ auto sender = [&](Player const* receiver)
+ {
+ if (CanSee(receiver, vignette))
+ receiver->SendDirectMessage(vignetteUpdate.GetRawPacket());
+ };
+
+ Trinity::MessageDistDeliverer notifier(owner, sender, owner->GetVisibilityRange());
+ Cell::VisitWorldObjects(owner, notifier, owner->GetVisibilityRange());
+}
+}
+
+void VignetteData::FillPacket(WorldPackets::Vignette::VignetteDataSet& dataSet) const
+{
+ dataSet.IDs.push_back(Guid);
+
+ WorldPackets::Vignette::VignetteData& data = dataSet.Data.emplace_back();
+ data.ObjGUID = Object;
+ data.Position = Position;
+ data.VignetteID = Data->ID;
+ data.ZoneID = ZoneID;
+ data.WMOGroupID = WMOGroupID;
+ data.WMODoodadPlacementID = WMODoodadPlacementID;
+}
+
+std::unique_ptr<VignetteData> Create(VignetteEntry const* vignetteData, WorldObject const* owner)
+{
+ std::unique_ptr<VignetteData> vignette = std::make_unique<VignetteData>();
+ vignette->Guid = ObjectGuid::Create<HighGuid::Vignette>(owner->GetMapId(), vignetteData->ID, owner->GetMap()->GenerateLowGuid<HighGuid::Vignette>());
+ vignette->Object = owner->GetGUID();
+ vignette->Position = owner->GetPosition();
+ vignette->Data = vignetteData;
+ vignette->ZoneID = owner->GetZoneId(); // not updateable
+ UpdatePosition(*vignette, owner);
+
+ if (vignetteData->IsInfiniteAOI())
+ owner->GetMap()->AddInfiniteAOIVignette(vignette.get());
+ else
+ SendVignetteUpdate<&WorldPackets::Vignette::VignetteUpdate::Added>(*vignette, owner);
+
+ return vignette;
+}
+
+void Update(VignetteData& vignette, WorldObject const* owner)
+{
+ UpdatePosition(vignette, owner);
+
+ if (vignette.Data->IsInfiniteAOI())
+ vignette.NeedUpdate = true;
+ else
+ SendVignetteUpdate<&WorldPackets::Vignette::VignetteUpdate::Updated>(vignette, owner);
+}
+
+void Remove(VignetteData& vignette, WorldObject const* owner)
+{
+ if (vignette.Data->IsInfiniteAOI())
+ owner->GetMap()->RemoveInfiniteAOIVignette(&vignette);
+ else
+ {
+ WorldPackets::Vignette::VignetteUpdate vignetteUpdate;
+ vignetteUpdate.Removed.push_back(vignette.Guid);
+ owner->SendMessageToSet(vignetteUpdate.Write(), true);
+ }
+}
+
+bool CanSee(Player const* player, VignetteData const& vignette)
+{
+ if (vignette.Data->GetFlags().HasFlag(VignetteFlags::ZoneInfiniteAOI))
+ if (vignette.ZoneID != player->GetZoneId())
+ return false;
+
+ if (vignette.Data->VisibleTrackingQuestID)
+ if (player->IsQuestRewarded(vignette.Data->VisibleTrackingQuestID))
+ return false;
+
+ if (PlayerConditionEntry const* playerCondition = sPlayerConditionStore.LookupEntry(vignette.Data->PlayerConditionID))
+ if (!ConditionMgr::IsPlayerMeetingCondition(player, playerCondition))
+ return false;
+
+ return true;
+}
+}
diff --git a/src/server/game/Entities/Unit/Vignette.h b/src/server/game/Entities/Unit/Vignette.h
new file mode 100644
index 00000000000..f318faff983
--- /dev/null
+++ b/src/server/game/Entities/Unit/Vignette.h
@@ -0,0 +1,57 @@
+/*
+ * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TRINITYCORE_VIGNETTE_H
+#define TRINITYCORE_VIGNETTE_H
+
+#include "ObjectGuid.h"
+#include "Position.h"
+#include <memory>
+
+struct VignetteEntry;
+class Player;
+class WorldObject;
+
+namespace WorldPackets::Vignette
+{
+struct VignetteDataSet;
+}
+
+namespace Vignettes
+{
+struct VignetteData
+{
+ ObjectGuid Guid;
+ ObjectGuid Object;
+ ::Position Position;
+ VignetteEntry const* Data;
+ uint32 ZoneID = 0;
+ uint32 WMOGroupID = 0;
+ uint32 WMODoodadPlacementID = 0;
+ bool NeedUpdate = false;
+
+ void FillPacket(WorldPackets::Vignette::VignetteDataSet& dataSet) const;
+};
+
+std::unique_ptr<VignetteData> Create(VignetteEntry const* vignetteData, WorldObject const* owner);
+void Update(VignetteData& vignette, WorldObject const* owner);
+void Remove(VignetteData& vignette, WorldObject const* owner);
+
+bool CanSee(Player const* player, VignetteData const& vignette);
+}
+
+#endif // TRINITYCORE_VIGNETTE_H
diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp
index 0f4c7112e8e..5987f183214 100644
--- a/src/server/game/Globals/ObjectMgr.cpp
+++ b/src/server/game/Globals/ObjectMgr.cpp
@@ -1152,6 +1152,12 @@ void ObjectMgr::CheckCreatureTemplate(CreatureTemplate const* cInfo)
TC_LOG_INFO("sql.sql", "Creature (Entry: {}) has assigned gossip menu, but npcflag does not include UNIT_NPC_FLAG_GOSSIP.", cInfo->Entry);
else if (cInfo->GossipMenuIds.empty() && cInfo->npcflag & UNIT_NPC_FLAG_GOSSIP)
TC_LOG_INFO("sql.sql", "Creature (Entry: {}) has npcflag UNIT_NPC_FLAG_GOSSIP, but gossip menu is unassigned.", cInfo->Entry);
+
+ if (cInfo->VignetteID && !sVignetteStore.HasRecord(cInfo->VignetteID))
+ {
+ TC_LOG_INFO("sql.sql", "Creature (Entry: {}) has non-existing Vignette {}, set to 0.", cInfo->Entry, cInfo->VignetteID);
+ const_cast<CreatureTemplate*>(cInfo)->VignetteID = 0;
+ }
}
void ObjectMgr::CheckCreatureMovement(char const* table, uint64 id, CreatureMovementData& creatureMovement)
diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp
index b722717a86a..9bb50c2d913 100644
--- a/src/server/game/Maps/Map.cpp
+++ b/src/server/game/Maps/Map.cpp
@@ -54,6 +54,8 @@
#include "Vehicle.h"
#include "VMapFactory.h"
#include "VMapManager2.h"
+#include "Vignette.h"
+#include "VignettePackets.h"
#include "Weather.h"
#include "WeatherMgr.h"
#include "World.h"
@@ -144,7 +146,7 @@ m_unloadTimer(0), m_VisibleDistance(DEFAULT_VISIBILITY_DISTANCE),
m_VisibilityNotifyPeriod(DEFAULT_VISIBILITY_NOTIFY_PERIOD),
m_activeNonPlayersIter(m_activeNonPlayers.end()), _transportsUpdateIter(_transports.end()),
i_gridExpiry(expiry), m_terrain(sTerrainMgr.LoadTerrain(id)), m_forceEnabledNavMeshFilterFlags(0), m_forceDisabledNavMeshFilterFlags(0),
-i_scriptLock(false), _respawnTimes(std::make_unique<RespawnListContainer>()), _respawnCheckTimer(0)
+i_scriptLock(false), _respawnTimes(std::make_unique<RespawnListContainer>()), _respawnCheckTimer(0), _vignetteUpdateTimer(5200, 5200)
{
for (uint32 x = 0; x < MAX_NUMBER_OF_GRIDS; ++x)
{
@@ -511,6 +513,38 @@ void Map::SetWorldStateValue(int32 worldStateId, int32 value, bool hidden)
}
}
+void Map::AddInfiniteAOIVignette(Vignettes::VignetteData* vignette)
+{
+ _infiniteAOIVignettes.push_back(vignette);
+
+ WorldPackets::Vignette::VignetteUpdate vignetteUpdate;
+ vignette->FillPacket(vignetteUpdate.Added);
+ vignetteUpdate.Write();
+
+ for (MapReference const& ref : m_mapRefManager)
+ if (Vignettes::CanSee(ref.GetSource(), *vignette))
+ ref.GetSource()->SendDirectMessage(vignetteUpdate.GetRawPacket());
+}
+
+void Map::RemoveInfiniteAOIVignette(Vignettes::VignetteData* vignette)
+{
+ if (!std::erase(_infiniteAOIVignettes, vignette))
+ return;
+
+ WorldPackets::Vignette::VignetteUpdate vignetteUpdate;
+ vignetteUpdate.Removed.push_back(vignette->Guid);
+ vignetteUpdate.Write();
+
+ if (vignette->Data->GetFlags().HasFlag(VignetteFlags::ZoneInfiniteAOI))
+ {
+ for (MapReference const& ref : m_mapRefManager)
+ if (ref.GetSource()->GetZoneId() == vignette->ZoneID)
+ ref.GetSource()->SendDirectMessage(vignetteUpdate.GetRawPacket());
+ }
+ else
+ SendToPlayers(vignetteUpdate.GetRawPacket());
+}
+
template<class T>
void Map::InitializeObject(T* /*obj*/) { }
@@ -774,6 +808,24 @@ void Map::Update(uint32 t_diff)
obj->Update(t_diff);
}
+ if (_vignetteUpdateTimer.Update(t_diff))
+ {
+ for (Vignettes::VignetteData* vignette : _infiniteAOIVignettes)
+ {
+ if (vignette->NeedUpdate)
+ {
+ WorldPackets::Vignette::VignetteUpdate vignetteUpdate;
+ vignette->FillPacket(vignetteUpdate.Updated);
+ vignetteUpdate.Write();
+ for (MapReference const& ref : m_mapRefManager)
+ if (Vignettes::CanSee(ref.GetSource(), *vignette))
+ ref.GetSource()->SendDirectMessage(vignetteUpdate.GetRawPacket());
+
+ vignette->NeedUpdate = false;
+ }
+ }
+ }
+
SendObjectUpdates();
///- Process necessary scripts
diff --git a/src/server/game/Maps/Map.h b/src/server/game/Maps/Map.h
index 5763fce777d..78a8cb3e647 100644
--- a/src/server/game/Maps/Map.h
+++ b/src/server/game/Maps/Map.h
@@ -78,6 +78,7 @@ enum WeatherState : uint32;
enum class ItemContext : uint8;
namespace Trinity { struct ObjectUpdater; }
+namespace Vignettes { struct VignetteData; }
namespace VMAP { enum class ModelIgnoreFlags : uint32; }
enum TransferAbortReason : uint32
@@ -821,6 +822,18 @@ class TC_GAME_API Map : public GridRefManager<NGridType>
private:
WorldStateValueContainer _worldStateValues;
+
+ /*********************************************************/
+ /*** Vignettes ***/
+ /*********************************************************/
+ public:
+ void AddInfiniteAOIVignette(Vignettes::VignetteData* vignette);
+ void RemoveInfiniteAOIVignette(Vignettes::VignetteData* vignette);
+ std::vector<Vignettes::VignetteData*> const& GetInfiniteAOIVignettes() const { return _infiniteAOIVignettes; }
+
+ private:
+ std::vector<Vignettes::VignetteData*> _infiniteAOIVignettes;
+ PeriodicTimer _vignetteUpdateTimer;
};
enum class InstanceResetMethod : uint8
diff --git a/src/server/game/Server/Packets/VignettePackets.cpp b/src/server/game/Server/Packets/VignettePackets.cpp
new file mode 100644
index 00000000000..10ca888ccbc
--- /dev/null
+++ b/src/server/game/Server/Packets/VignettePackets.cpp
@@ -0,0 +1,59 @@
+/*
+ * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "VignettePackets.h"
+
+namespace WorldPackets::Vignette
+{
+ByteBuffer& operator<<(ByteBuffer& data, VignetteData const& vignetteData)
+{
+ data << vignetteData.Position;
+ data << vignetteData.ObjGUID;
+ data << int32(vignetteData.VignetteID);
+ data << uint32(vignetteData.ZoneID);
+ data << uint32(vignetteData.WMOGroupID);
+ data << uint32(vignetteData.WMODoodadPlacementID);
+
+ return data;
+}
+
+ByteBuffer& operator<<(ByteBuffer& data, VignetteDataSet const& vignetteDataSet)
+{
+ data << uint32(vignetteDataSet.IDs.size());
+ data << uint32(vignetteDataSet.Data.size());
+ for (ObjectGuid const& id : vignetteDataSet.IDs)
+ data << id;
+
+ for (VignetteData const& vignetteData : vignetteDataSet.Data)
+ data << vignetteData;
+
+ return data;
+}
+
+WorldPacket const* VignetteUpdate::Write()
+{
+ _worldPacket.WriteBit(ForceUpdate);
+ _worldPacket.WriteBit(InFogOfWar);
+ _worldPacket << uint32(Removed.size());
+ _worldPacket << Added;
+ _worldPacket << Updated;
+ for (ObjectGuid const& removed : Removed)
+ _worldPacket << removed;
+
+ return &_worldPacket;
+}
+}
diff --git a/src/server/game/Server/Packets/VignettePackets.h b/src/server/game/Server/Packets/VignettePackets.h
new file mode 100644
index 00000000000..c96f68262df
--- /dev/null
+++ b/src/server/game/Server/Packets/VignettePackets.h
@@ -0,0 +1,58 @@
+/*
+ * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TRINITYCORE_VIGNETTE_PACKETS_H
+#define TRINITYCORE_VIGNETTE_PACKETS_H
+
+#include "ObjectGuid.h"
+#include "Packet.h"
+#include "Position.h"
+
+namespace WorldPackets::Vignette
+{
+struct VignetteData
+{
+ ObjectGuid ObjGUID;
+ TaggedPosition<Position::XYZ> Position;
+ int32 VignetteID = 0;
+ uint32 ZoneID = 0;
+ uint32 WMOGroupID = 0;
+ uint32 WMODoodadPlacementID = 0;
+};
+
+struct VignetteDataSet
+{
+ std::vector<ObjectGuid> IDs;
+ std::vector<VignetteData> Data;
+};
+
+class VignetteUpdate final : public ServerPacket
+{
+public:
+ VignetteUpdate() : ServerPacket(SMSG_VIGNETTE_UPDATE, 200) { }
+
+ WorldPacket const* Write() override;
+
+ VignetteDataSet Added;
+ VignetteDataSet Updated;
+ std::vector<ObjectGuid> Removed;
+ bool ForceUpdate = false;
+ bool InFogOfWar = false;
+};
+}
+
+#endif // TRINITYCORE_VIGNETTE_PACKETS_H
diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp
index 80a289eb9c4..613b4e1f970 100644
--- a/src/server/game/Server/Protocol/Opcodes.cpp
+++ b/src/server/game/Server/Protocol/Opcodes.cpp
@@ -2145,7 +2145,7 @@ void OpcodeTable::Initialize()
DEFINE_SERVER_OPCODE_HANDLER(SMSG_VAS_PURCHASE_COMPLETE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_VAS_PURCHASE_STATE_UPDATE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_VENDOR_INVENTORY, STATUS_NEVER, CONNECTION_TYPE_INSTANCE);
- DEFINE_SERVER_OPCODE_HANDLER(SMSG_VIGNETTE_UPDATE, STATUS_UNHANDLED, CONNECTION_TYPE_INSTANCE);
+ DEFINE_SERVER_OPCODE_HANDLER(SMSG_VIGNETTE_UPDATE, STATUS_NEVER, CONNECTION_TYPE_INSTANCE);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_VOICE_CHANNEL_INFO_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_INSTANCE);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_VOICE_CHANNEL_STT_TOKEN_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_INSTANCE);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_VOICE_LOGIN_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_INSTANCE);
diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp
index 65894bf3ec7..f21ebb30e61 100644
--- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp
+++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp
@@ -334,7 +334,7 @@ NonDefaultConstructible<pAuraEffectHandler> AuraEffectHandler[TOTAL_AURAS]=
&AuraEffect::HandleNoImmediateEffect, //263 SPELL_AURA_DISABLE_CASTING_EXCEPT_ABILITIES implemented in Spell::CheckCast
&AuraEffect::HandleNoImmediateEffect, //264 SPELL_AURA_DISABLE_ATTACKING_EXCEPT_ABILITIES implemented in Spell::CheckCast, Unit::AttackerStateUpdate
&AuraEffect::HandleUnused, //265 unused (4.3.4)
- &AuraEffect::HandleNULL, //266 SPELL_AURA_SET_VIGNETTE
+ &AuraEffect::HandleSetVignette, //266 SPELL_AURA_SET_VIGNETTE
&AuraEffect::HandleNoImmediateEffect, //267 SPELL_AURA_MOD_IMMUNE_AURA_APPLY_SCHOOL implemented in Unit::IsImmunedToSpellEffect
&AuraEffect::HandleModArmorPctFromStat, //268 SPELL_AURA_MOD_ARMOR_PCT_FROM_STAT also implemented in Player::UpdateArmor()
&AuraEffect::HandleNoImmediateEffect, //269 SPELL_AURA_MOD_IGNORE_TARGET_RESIST implemented in Unit::CalcAbsorbResist and CalcArmorReducedDamage
@@ -5322,6 +5322,14 @@ void AuraEffect::HandleAuraSetVehicle(AuraApplication const* aurApp, uint8 mode,
target->ToPlayer()->SendOnCancelExpectedVehicleRideAura();
}
+void AuraEffect::HandleSetVignette(AuraApplication const* aurApp, uint8 mode, bool apply) const
+{
+ if (!(mode & AURA_EFFECT_HANDLE_REAL))
+ return;
+
+ aurApp->GetTarget()->SetVignette(apply ? GetMiscValue() : 0);
+}
+
void AuraEffect::HandlePreventResurrection(AuraApplication const* aurApp, uint8 mode, bool apply) const
{
if (!(mode & AURA_EFFECT_HANDLE_REAL))
diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.h b/src/server/game/Spells/Auras/SpellAuraEffects.h
index 8260d96f621..810af0fc8a7 100644
--- a/src/server/game/Spells/Auras/SpellAuraEffects.h
+++ b/src/server/game/Spells/Auras/SpellAuraEffects.h
@@ -314,6 +314,7 @@ class TC_GAME_API AuraEffect
void HandleAuraModFakeInebriation(AuraApplication const* aurApp, uint8 mode, bool apply) const;
void HandleAuraOverrideSpells(AuraApplication const* aurApp, uint8 mode, bool apply) const;
void HandleAuraSetVehicle(AuraApplication const* aurApp, uint8 mode, bool apply) const;
+ void HandleSetVignette(AuraApplication const* aurApp, uint8 mode, bool apply) const;
void HandlePreventResurrection(AuraApplication const* aurApp, uint8 mode, bool apply) const;
void HandleMastery(AuraApplication const* aurApp, uint8 mode, bool apply) const;
void HandleAuraForceWeather(AuraApplication const* aurApp, uint8 mode, bool apply) const;