Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions data/sql/updates/pending_db_world/rev_8337814054007982.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
-- Create creature_multispawn table for multi-ID spawning
CREATE TABLE IF NOT EXISTS `creature_multispawn` (
Comment thread
sudlud marked this conversation as resolved.
`spawnId` int unsigned NOT NULL COMMENT 'creature.guid',
`entry` int unsigned NOT NULL COMMENT 'creature_template.entry',
PRIMARY KEY (`spawnId`, `entry`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Additional creature entries for multi-ID spawning';
Comment on lines +2 to +6

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Enforce parent-child cleanup for creature_multispawn.

Line 2 introduces a child table keyed by spawnId, but there is no referential cleanup to keep it in sync with creature deletions. That can leave orphan rows and make entry-based lookups return stale GUIDs.

🔧 Suggested fix
 CREATE TABLE IF NOT EXISTS `creature_multispawn` (
-  `spawnId` int unsigned NOT NULL COMMENT 'creature.guid',
-  `entry` int unsigned NOT NULL COMMENT 'creature_template.entry',
-  PRIMARY KEY (`spawnId`, `entry`)
+  `spawnId` int unsigned NOT NULL COMMENT 'creature.guid',
+  `entry` int unsigned NOT NULL COMMENT 'creature_template.entry',
+  PRIMARY KEY (`spawnId`, `entry`),
+  CONSTRAINT `fk_creature_multispawn_spawn`
+      FOREIGN KEY (`spawnId`) REFERENCES `creature` (`guid`)
+      ON DELETE CASCADE
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Additional creature entries for multi-ID spawning';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
CREATE TABLE IF NOT EXISTS `creature_multispawn` (
`spawnId` int unsigned NOT NULL COMMENT 'creature.guid',
`entry` int unsigned NOT NULL COMMENT 'creature_template.entry',
PRIMARY KEY (`spawnId`, `entry`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Additional creature entries for multi-ID spawning';
CREATE TABLE IF NOT EXISTS `creature_multispawn` (
`spawnId` int unsigned NOT NULL COMMENT 'creature.guid',
`entry` int unsigned NOT NULL COMMENT 'creature_template.entry',
PRIMARY KEY (`spawnId`, `entry`),
CONSTRAINT `fk_creature_multispawn_spawn`
FOREIGN KEY (`spawnId`) REFERENCES `creature` (`guid`)
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Additional creature entries for multi-ID spawning';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@data/sql/updates/pending_db_world/rev_8337814054007982.sql` around lines 2 -
6, The `creature_multispawn` table definition lacks a foreign key constraint to
enforce referential integrity with the parent `creature` table. Add a FOREIGN
KEY constraint on the `spawnId` column that references `creature(guid)` with `ON
DELETE CASCADE` to automatically remove orphaned rows from `creature_multispawn`
when a creature is deleted from the `creature` table. This constraint should be
added as part of the CREATE TABLE statement for `creature_multispawn` to ensure
data consistency and prevent stale GUID lookups.


-- Migrate id2 and id3 entries
DELETE FROM `creature_multispawn`;
INSERT IGNORE INTO `creature_multispawn` (`spawnId`, `entry`)
SELECT `guid`, `id2` FROM `creature` WHERE `id2` != 0
UNION ALL
SELECT `guid`, `id3` FROM `creature` WHERE `id3` != 0;

-- Drop old index before column rename
ALTER TABLE `creature` DROP INDEX `idx_id`;

-- Rename id1 -> id
ALTER TABLE `creature` CHANGE COLUMN `id1` `id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Creature Identifier';

-- Drop id2, id3
ALTER TABLE `creature` DROP COLUMN `id2`, DROP COLUMN `id3`;

-- Recreate index on renamed column
ALTER TABLE `creature` ADD INDEX `idx_id` (`id`);
8 changes: 4 additions & 4 deletions src/server/database/Database/Implementation/WorldDatabase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,15 @@ void WorldDatabaseConnection::DoPrepareStatements()
PrepareStatement(WORLD_SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, `rank`, dmgschool, DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, type, type_flags, lootid, pickpocketloot, skinloot, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, CreatureImmunitiesId, flags_extra, ScriptName FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_WAYPOINT_SCRIPT_BY_ID, "SELECT guid, delay, command, datalong, datalong2, dataint, x, y, z, o FROM waypoint_scripts WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_ITEM_TEMPLATE_BY_NAME, "SELECT entry FROM item_template WHERE name = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_CREATURE_BY_ID, "SELECT guid FROM creature WHERE id1 = ? OR id2 = ? OR id3 = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_CREATURE_BY_ID, "SELECT guid FROM creature WHERE id = ? UNION SELECT spawnId AS guid FROM creature_multispawn WHERE entry = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAMEOBJECT_NEAREST, "SELECT guid, id, position_x, position_y, position_z, map, (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) AS order_ FROM gameobject WHERE map = ? AND (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) <= ? AND (phaseMask & ?) <> 0 ORDER BY order_", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_CREATURE_NEAREST, "SELECT guid, id1, id2, id3, position_x, position_y, position_z, map, (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) AS order_ FROM creature WHERE map = ? AND (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) <= ? AND (phaseMask & ?) <> 0 ORDER BY order_", CONNECTION_SYNCH);
PrepareStatement(WORLD_INS_CREATURE, "INSERT INTO creature (guid, id1, id2, id3, map, spawnMask, phaseMask, equipment_id, position_x, position_y, position_z, orientation, spawntimesecs, wander_distance, currentwaypoint, curhealth, curmana, MovementType, npcflag, unit_flags, dynamicflags) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(WORLD_SEL_CREATURE_NEAREST, "SELECT guid, id, position_x, position_y, position_z, map, (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) AS order_ FROM creature WHERE map = ? AND (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) <= ? AND (phaseMask & ?) <> 0 ORDER BY order_", CONNECTION_SYNCH);
PrepareStatement(WORLD_INS_CREATURE, "INSERT INTO creature (guid, id, map, spawnMask, phaseMask, equipment_id, position_x, position_y, position_z, orientation, spawntimesecs, wander_distance, currentwaypoint, curhealth, curmana, MovementType, npcflag, unit_flags, dynamicflags) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(WORLD_SEL_GAME_EVENTS, "SELECT eventEntry, UNIX_TIMESTAMP(start_time), UNIX_TIMESTAMP(end_time), occurence, length, holiday, holidayStage, description, world_event, announce FROM game_event", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAME_EVENT_PREREQUISITE_DATA, "SELECT eventEntry, prerequisite_event FROM game_event_prerequisite", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAME_EVENT_CREATURE_DATA, "SELECT guid, eventEntry FROM game_event_creature", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAME_EVENT_GAMEOBJECT_DATA, "SELECT guid, eventEntry FROM game_event_gameobject", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAME_EVENT_MODEL_EQUIPMENT_DATA, "SELECT creature.guid, creature.id1, creature.id2, creature.id3, game_event_model_equip.eventEntry, game_event_model_equip.modelid, game_event_model_equip.equipment_id FROM creature JOIN game_event_model_equip ON creature.guid=game_event_model_equip.guid", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAME_EVENT_MODEL_EQUIPMENT_DATA, "SELECT creature.guid, creature.id, game_event_model_equip.eventEntry, game_event_model_equip.modelid, game_event_model_equip.equipment_id FROM creature JOIN game_event_model_equip ON creature.guid=game_event_model_equip.guid", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAME_EVENT_QUEST_DATA, "SELECT id, quest, eventEntry FROM game_event_creature_quest", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAME_EVENT_GAMEOBJECT_QUEST_DATA, "SELECT id, quest, eventEntry FROM game_event_gameobject_quest", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAME_EVENT_QUEST_CONDITION_DATA, "SELECT quest, eventEntry, condition_id, num FROM game_event_quest_condition", CONNECTION_SYNCH);
Expand Down
4 changes: 2 additions & 2 deletions src/server/game/AI/SmartScripts/SmartScriptMgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ void SmartAIMgr::CheckIfSmartAIInDatabaseExists()
// check GUID SAI
for (auto const& pair : sObjectMgr->GetAllCreatureData())
{
if (pair.second.id1 != creatureTemplate.Entry)
if (pair.second.id != creatureTemplate.Entry)
continue;

if (mEventMap[uint32(SmartScriptType::SMART_SCRIPT_TYPE_CREATURE)].find((-1) * pair.first) != mEventMap[uint32(SmartScriptType::SMART_SCRIPT_TYPE_CREATURE)].end())
Expand Down Expand Up @@ -2100,7 +2100,7 @@ bool SmartAIMgr::IsTextValid(SmartScriptHolder const& e, uint32 id)
return false;
}
else
entry = data->id1;
entry = data->id;
}
else
entry = uint32(e.entryOrGuid);
Expand Down
4 changes: 2 additions & 2 deletions src/server/game/Conditions/ConditionMgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1958,7 +1958,7 @@ bool ConditionMgr::isSourceTypeValid(Condition* cond)
return false;
}

if (data->id1 != uint32(cond->SourceEntry))
if (data->id != uint32(cond->SourceEntry))
{
LOG_ERROR("sql.sql", "CONDITION_SOURCE_TYPE_OBJECT_VISIBILITY has creature guid {} that does not match SourceEntry {}, skipped.", cond->SourceId, cond->SourceEntry);
return false;
Expand Down Expand Up @@ -2322,7 +2322,7 @@ bool ConditionMgr::isConditionTypeValid(Condition* cond)
{
if (CreatureData const* creatureData = sObjectMgr->GetCreatureData(cond->ConditionValue3))
{
if (cond->ConditionValue2 && creatureData->id1 != cond->ConditionValue2)
if (cond->ConditionValue2 && creatureData->id != cond->ConditionValue2)
{
LOG_ERROR("sql.sql", "ObjectEntryGuid condition has guid {} set but does not match creature entry ({}), skipped", cond->ConditionValue3, cond->ConditionValue2);
return false;
Expand Down
12 changes: 5 additions & 7 deletions src/server/game/Entities/Creature/Creature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1423,7 +1423,7 @@ void Creature::SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask)
dynamicflags = 0;
}

data.id1 = GetEntry();
data.id = GetEntry();
data.mapid = mapid;
data.phaseMask = phaseMask;
data.displayid = displayId;
Expand Down Expand Up @@ -1469,8 +1469,6 @@ void Creature::SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask)
stmt = WorldDatabase.GetPreparedStatement(WORLD_INS_CREATURE);
stmt->SetData(index++, m_spawnId);
stmt->SetData(index++, GetEntry());
stmt->SetData(index++, 0);
stmt->SetData(index++, 0);
stmt->SetData(index++, uint16(mapid));
stmt->SetData(index++, spawnMask);
stmt->SetData(index++, GetPhaseMask());
Expand Down Expand Up @@ -1725,7 +1723,7 @@ bool Creature::LoadCreatureFromDB(ObjectGuid::LowType spawnId, Map* map, bool ad
|| !groupData || (groupData->flags & SPAWNGROUP_FLAG_COMPATIBILITY_MODE);

// Add to world
uint32 entry = GetRandomId(data->id1, data->id2, data->id3);
uint32 entry = GetRandomId(data->id, data->id2, data->id3);

if (!Create(map->GenerateLowGuid<HighGuid::Unit>(), map, data->phaseMask, entry, 0, data->posX, data->posY, data->posZ, data->orientation, data))
return false;
Expand Down Expand Up @@ -2041,7 +2039,7 @@ void Creature::Respawn(bool force)
if (!allowed && !force) // Will be rechecked on next Update call
return;

ObjectGuid dbtableHighGuid = ObjectGuid::Create<HighGuid::Unit>(m_creatureData ? m_creatureData->id1 : GetEntry(), m_spawnId);
ObjectGuid dbtableHighGuid = ObjectGuid::Create<HighGuid::Unit>(m_creatureData ? m_creatureData->id : GetEntry(), m_spawnId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Wrap the linked-respawn GUID construction.

Line 2042 exceeds the project’s 80-column limit; split the ternary input before merge. As per coding guidelines, **/*.{cpp,h,hpp,cc,sql,json,yaml,yml,sh,ts,js} files must use “max 80 columns.”

Proposed formatting fix
-    ObjectGuid dbtableHighGuid = ObjectGuid::Create<HighGuid::Unit>(m_creatureData ? m_creatureData->id : GetEntry(), m_spawnId);
+    uint32 const dbEntry = m_creatureData ? m_creatureData->id : GetEntry();
+    ObjectGuid dbtableHighGuid =
+        ObjectGuid::Create<HighGuid::Unit>(dbEntry, m_spawnId);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ObjectGuid dbtableHighGuid = ObjectGuid::Create<HighGuid::Unit>(m_creatureData ? m_creatureData->id : GetEntry(), m_spawnId);
uint32 const dbEntry = m_creatureData ? m_creatureData->id : GetEntry();
ObjectGuid dbtableHighGuid =
ObjectGuid::Create<HighGuid::Unit>(dbEntry, m_spawnId);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/game/Entities/Creature/Creature.cpp` at line 2042, The
ObjectGuid::Create line for the dbtableHighGuid variable in the Creature.cpp
file exceeds the project's 80-column limit. Split this line by extracting the
ternary operator expression (m_creatureData ? m_creatureData->id : GetEntry())
to a separate variable or breaking the ObjectGuid::Create call across multiple
lines to ensure the total line length does not exceed 80 columns as required by
the coding guidelines.

Source: Coding guidelines

time_t linkedRespawntime = GetMap()->GetLinkedRespawnTime(dbtableHighGuid);

CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(GetEntry());
Expand All @@ -2061,7 +2059,7 @@ void Creature::Respawn(bool force)
// Respawn check if spawn has 2 entries
if (data->id2)
{
uint32 entry = GetRandomId(data->id1, data->id2, data->id3);
uint32 entry = GetRandomId(data->id, data->id2, data->id3);
UpdateEntry(entry, data, true); // Select Random Entry
m_defaultMovementType = MovementGeneratorType(data->movementType); // Reload Movement Type
LoadEquipment(data->equipmentId); // Reload Equipment
Expand Down Expand Up @@ -3169,7 +3167,7 @@ uint32 Creature::GetScriptId() const
if (CreatureData const* creatureData = GetCreatureData())
{
uint32 scriptId = creatureData->ScriptId;
if (scriptId && GetEntry() == creatureData->id1)
if (scriptId && GetEntry() == creatureData->id)
return scriptId;
}

Expand Down
6 changes: 3 additions & 3 deletions src/server/game/Entities/Creature/CreatureData.h
Original file line number Diff line number Diff line change
Expand Up @@ -369,9 +369,9 @@ typedef std::unordered_map<uint32, EquipmentInfoContainerInternal> EquipmentInfo
struct CreatureData : public SpawnData
{
CreatureData() : SpawnData(SPAWN_TYPE_CREATURE) {}
uint32 id1{0}; // entry in creature_template
uint32 id2{0}; // entry in creature_template
uint32 id3{0}; // entry in creature_template
uint32 id{0}; // entry in creature_template
uint32 id2{0}; // entry in creature_template (from creature_multispawn)
uint32 id3{0}; // entry in creature_template (from creature_multispawn)
uint32 displayid{0};
int8 equipmentId{0};
uint32 spawntimesecs{0};
Expand Down
12 changes: 5 additions & 7 deletions src/server/game/Events/GameEventMgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ void GameEventMgr::LoadEventVendors()
// Get creature entry
newEntry.Entry = 0;
if (CreatureData const* data = sObjectMgr->GetCreatureData(guid))
newEntry.Entry = data->id1;
newEntry.Entry = data->id;

// Validate vendor item
if (!sObjectMgr->IsVendorItemValid(newEntry.Entry, newEntry.Item, newEntry.MaxCount, newEntry.Incrtime, newEntry.ExtendedCost, nullptr, nullptr, event_npc_flag))
Expand Down Expand Up @@ -614,9 +614,7 @@ void GameEventMgr::LoadEventModelEquipmentChangeData()

ObjectGuid::LowType guid = fields[0].Get<uint32>();
uint32 entry = fields[1].Get<uint32>();
uint32 entry2 = fields[2].Get<uint32>();
uint32 entry3 = fields[3].Get<uint32>();
uint16 eventId = fields[4].Get<uint8>();
uint16 eventId = fields[2].Get<uint8>();

if (eventId >= _gameEventModelEquip.size())
{
Expand All @@ -626,15 +624,15 @@ void GameEventMgr::LoadEventModelEquipmentChangeData()

ModelEquipList& equiplist = _gameEventModelEquip[eventId];
ModelEquip newModelEquipSet;
newModelEquipSet.ModelId = fields[5].Get<uint32>();
newModelEquipSet.EquipmentId = fields[6].Get<uint8>();
newModelEquipSet.ModelId = fields[3].Get<uint32>();
newModelEquipSet.EquipmentId = fields[4].Get<uint8>();
newModelEquipSet.EquipementIdPrev = 0;
newModelEquipSet.ModelIdPrev = 0;

if (newModelEquipSet.EquipmentId > 0)
{
int8 equipId = static_cast<int8>(newModelEquipSet.EquipmentId);
if ((!sObjectMgr->GetEquipmentInfo(entry, equipId)) || (entry2 && !sObjectMgr->GetEquipmentInfo(entry2, equipId)) || (entry3 && !sObjectMgr->GetEquipmentInfo(entry3, equipId)))
if (!sObjectMgr->GetEquipmentInfo(entry, equipId))
{
LOG_ERROR("sql.sql", "Table `game_event_model_equip` have creature (Guid: {}) with equipment_id {} not found in table `creature_equip_template`, set to no equipment.",
guid, newModelEquipSet.EquipmentId);
Expand Down
Loading
Loading