Skip to content

refactor(Core/DB): normalize creature table by extracting multi-ID spawns - #25197

Merged
sudlud merged 7 commits into
azerothcore:masterfrom
Nyeriah:multispawn
Jun 16, 2026
Merged

refactor(Core/DB): normalize creature table by extracting multi-ID spawns#25197
sudlud merged 7 commits into
azerothcore:masterfrom
Nyeriah:multispawn

Conversation

@Nyeriah

@Nyeriah Nyeriah commented Mar 23, 2026

Copy link
Copy Markdown
Member

Changes Proposed:

This PR normalizes the creature table by extracting the rarely-used multi-ID spawning system into a dedicated table.

  • Core (units, players, creatures, game systems).
  • Scripts (bosses, spell scripts, creature scripts).
  • Database (SAI, creatures, etc).

Database Normalization

The creature table currently stores three creature template entry columns: id1, id2, and id3. This was introduced in PR #10115 to support spawn points that randomly pick between multiple creature templates on spawn/respawn.

However, only ~737 out of 5.3M+ creature spawns (~0.014%) actually use id2 or id3. The remaining 99.99% of rows carry two permanently-zero columns, violating database normalization principles (specifically 1NF — the columns represent a repeating group of the same attribute type).

This PR:

  • Creates a new creature_multispawn table with a composite primary key (spawnId, entry) to store alternate creature template entries for the rare spawns that need them
  • Renames id1 back to id in the creature table, restoring the original column name
  • Drops id2 and id3 from the creature table
  • Migrates existing data — all non-zero id2/id3 values are inserted into creature_multispawn before the columns are dropped

The C++ CreatureData struct retains id2/id3 fields internally, populated from the new table during loading, so all downstream spawn logic (GetRandomId, respawn entry selection, etc.) remains unchanged.

AI-assisted Pull Requests

Important

While the use of AI tools when preparing pull requests is not prohibited, contributors must clearly disclose when such tools have been used and specify the model involved.

Contributors are also expected to fully understand the changes they are submitting and must be able to explain and justify those changes when requested by maintainers.

  • AI tools (e.g. ChatGPT, Claude, or similar) were used entirely or partially in preparing this pull request. Claude Opus 4.6 (Claude Code) was used.

Issues Addressed:

N/A — this is a schema improvement.

SOURCE:

The changes have been validated through:

  • Live research (checked on live servers, e.g Classic WotLK, Retail, etc.)
  • Sniffs (remember to share them with the open source community!)
  • Video evidence, knowledge databases or other public sources (e.g forums, Wowhead, etc.)
  • The changes promoted by this pull request come partially or entirely from another project (cherry-pick).

Tests Performed:

This PR has been:

  • Tested in-game by the author.
  • Tested in-game by other community members/someone else other than the author/has been live on production servers.
  • This pull request requires further testing and may have edge cases to be tested.

How to Test the Changes:

  • This pull request can be tested by following the reproduction steps provided in the linked issue
  • This pull request requires further testing. Provide steps to test your changes. If it requires any specific setup e.g multiple players please specify it as well.
  1. Apply the pending SQL migration
  2. Verify creatures with single entries spawn normally
  3. Verify creatures that previously had id2/id3 values still randomly pick between entries on spawn and respawn
  4. Test .npc info on both single-entry and multi-entry spawns
  5. Test .npc add to verify new creature spawns work correctly

Known Issues and TODO List:

  • Modules with custom SQL referencing id1, id2, id3 columns will need updates

How to Test AzerothCore PRs

When a PR is ready to be tested, it will be marked as [WAITING TO BE TESTED].

You can help by testing PRs and writing your feedback here on the PR's page on GitHub. Follow the instructions here:

http://www.azerothcore.org/wiki/How-to-test-a-PR

REMEMBER: when testing a PR that changes something generic (i.e. a part of code that handles more than one specific thing), the tester should not only check that the PR does its job (e.g. fixing spell XXX) but especially check that the PR does not cause any regression (i.e. introducing new bugs).

For example: if a PR fixes spell X by changing a part of code that handles spells X, Y, and Z, we should not only test X, but we should test Y and Z as well.

Summary by CodeRabbit

  • Refactor
    • Updated internal creature spawn data storage and management system.

…awns

Move the rarely-used id2/id3 columns from the creature table into a
new creature_multispawn table, and rename id1 back to id. Only ~737
out of 5.3M+ spawns use multiple entries, so this eliminates two
wasted columns on 99.99% of rows while preserving full functionality.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 23, 2026 04:51

Copilot AI left a comment

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.

Pull request overview

Normalizes creature spawns by renaming creature.id1 back to id, moving multi-entry spawn variants into a new creature_multispawn table, and updating server code to load/consume the new schema while keeping existing random-entry behavior.

Changes:

  • Renamed CreatureData::id1id and updated references across scripts/commands/core.
  • Updated creature loading to read base entry from creature.id and load variant entries from creature_multispawn into id2/id3.
  • Added SQL migration creating creature_multispawn, migrating id2/id3 data, and dropping old columns from creature.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp Switch spawn entry lookup from id1 to id.
src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.cpp Update script logic to use CreatureData::id.
src/server/scripts/Northrend/FrozenHalls/PitOfSaron/instance_pit_of_saron.cpp Switch spawn entry lookup from id1 to id.
src/server/scripts/Kalimdor/TempleOfAhnQiraj/temple_of_ahnqiraj.cpp Use CreatureData::id when building HighGuid.
src/server/scripts/Commands/cs_wp.cpp Update prepared statement argument count for new query.
src/server/scripts/Commands/cs_tele.cpp Use id in in-memory filtering and SQL query joins.
src/server/scripts/Commands/cs_npc.cpp Spawn creation uses data.id; adjust DB query field indices after schema change.
src/server/scripts/Commands/cs_go.cpp Use CreatureData::id for entry matching.
src/server/game/OutdoorPvP/OutdoorPvP.cpp Use CreatureData::id when resolving capture point spawns.
src/server/game/Maps/ZoneScript.h Default entry resolver now returns data->id.
src/server/game/Handlers/AuctionHouseHandler.cpp Auctioneer template lookup uses CreatureData::id.
src/server/game/Globals/ObjectMgr.cpp Load creatures from new schema; add loading of variants from creature_multispawn.
src/server/game/Events/GameEventMgr.cpp Update event vendor/model equip loading for new creature schema.
src/server/game/Entities/Creature/CreatureData.h Rename id1 to id; annotate id2/id3 as multispawn variants.
src/server/game/Entities/Creature/Creature.cpp Save/load/respawn logic updated to use data->id as primary template ID.
src/server/game/Conditions/ConditionMgr.cpp Condition validation now compares against CreatureData::id.
src/server/game/AI/SmartScripts/SmartScriptMgr.cpp SAI validation uses CreatureData::id.
src/server/database/Database/Implementation/WorldDatabase.cpp Update prepared statements to match new creature schema + multispawn lookup.
data/sql/updates/pending_db_world/rev_8337814054007982.sql Introduce creature_multispawn and migrate/drop old repeating columns.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +9 to +13
INSERT INTO `creature_multispawn` (`spawnId`, `entry`)
SELECT `guid`, `id2` FROM `creature` WHERE `id2` != 0;

-- Migrate id3 entries
INSERT INTO `creature_multispawn` (`spawnId`, `entry`)

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

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

These inserts can fail and abort the migration if duplicates exist (e.g., id2 == id3 for a spawn, or repeated values across runs), because (spawnId, entry) is the primary key. Use INSERT IGNORE / ON DUPLICATE KEY handling, or filter duplicates in the SELECT (e.g., exclude id3 rows where id3 = id2) to make the migration idempotent and robust.

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

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed — using INSERT IGNORE now to handle potential id2 == id3 duplicates safely.

Comment on lines +2607 to +2624
// Load alternate entries from creature_multispawn
QueryResult variantResult = WorldDatabase.Query("SELECT entry FROM creature_multispawn WHERE spawnId = {}", spawnId);
if (variantResult)
{
do
{
uint32 variantEntry = variantResult->Fetch()[0].Get<uint32>();
if (!GetCreatureTemplate(variantEntry))
{
LOG_ERROR("sql.sql", "Table `creature_multispawn` has creature (SpawnId: {}) with non-existing entry {}, skipped.", spawnId, variantEntry);
continue;
}
if (!creatureData.id2)
creatureData.id2 = variantEntry;
else if (!creatureData.id3)
creatureData.id3 = variantEntry;
} while (variantResult->NextRow());
}

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

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

The lazy-load path (LoadCreatureDataFromDB) diverges from the main bulk load (LoadCreatures) validation/behavior: it does not check difficulty-entry constraints, does not validate equipment compatibility, and does not detect/log >2 variants (it silently ignores extras after filling id2/id3). This can lead to inconsistent runtime behavior depending on whether a spawn was loaded via bulk load or on-demand. Consider reusing the same validation logic as LoadCreatures (including logging when >2 variants exist) and making the variant row order deterministic (e.g., ORDER BY entry) so id2/id3 assignment is stable.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed — LoadCreatureDataFromDB now has the same validation as the bulk loader: difficulty entry checks, equipment validation, overflow logging, and ORDER BY entry for deterministic assignment.

Comment thread src/server/game/Globals/ObjectMgr.cpp Outdated
Comment on lines +2501 to +2507
CreatureData* data = const_cast<CreatureData*>(GetCreatureData(spawnId));
if (!data)
{
LOG_ERROR("sql.sql", "Table `creature_multispawn` has entry for non-existing creature spawn (SpawnId: {}), skipped.", spawnId);
continue;
}

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

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

Avoid const_cast here. Since variants are being applied during the load phase, prefer retrieving a mutable reference/pointer directly from _creatureDataStore (e.g., via find(spawnId) and modifying the stored CreatureData). This removes const-correctness hazards and makes it clearer that the data is intentionally mutable in this context.

Suggested change
CreatureData* data = const_cast<CreatureData*>(GetCreatureData(spawnId));
if (!data)
{
LOG_ERROR("sql.sql", "Table `creature_multispawn` has entry for non-existing creature spawn (SpawnId: {}), skipped.", spawnId);
continue;
}
auto creatureDataIt = _creatureDataStore.find(spawnId);
if (creatureDataIt == _creatureDataStore.end())
{
LOG_ERROR("sql.sql", "Table `creature_multispawn` has entry for non-existing creature spawn (SpawnId: {}), skipped.", spawnId);
continue;
}
CreatureData* data = &creatureDataIt->second;

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed — using _creatureDataStore.find() directly now, no more const_cast.

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 FROM creature_multispawn WHERE entry = ?", CONNECTION_SYNCH);

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

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

In the UNION, the second SELECT returns spawnId without aliasing it to match the first column name. While most consumers fetch by index, aliasing improves clarity and avoids subtle issues in any code path that relies on column names. Consider: ... UNION SELECT spawnId AS guid ....

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed — aliased as spawnId AS guid.

Comment thread src/server/game/Globals/ObjectMgr.cpp Outdated
Comment on lines +2537 to +2544
if (!data->id2)
data->id2 = entry;
else if (!data->id3)
data->id3 = entry;
else
LOG_ERROR("sql.sql", "Table `creature_multispawn` has more than 2 variant entries for creature (SpawnId: {}), extra entry {} skipped.", spawnId, entry);

++variantCount;

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

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

variantCount is incremented even when the variant entry is not actually stored (e.g., when a spawn already has both id2 and id3 and the entry is skipped). Increment only when an entry is successfully assigned to id2/id3 to keep the load log accurate.

Suggested change
if (!data->id2)
data->id2 = entry;
else if (!data->id3)
data->id3 = entry;
else
LOG_ERROR("sql.sql", "Table `creature_multispawn` has more than 2 variant entries for creature (SpawnId: {}), extra entry {} skipped.", spawnId, entry);
++variantCount;
bool stored = false;
if (!data->id2)
{
data->id2 = entry;
stored = true;
}
else if (!data->id3)
{
data->id3 = entry;
stored = true;
}
else
{
LOG_ERROR("sql.sql", "Table `creature_multispawn` has more than 2 variant entries for creature (SpawnId: {}), extra entry {} skipped.", spawnId, entry);
}
if (stored)
++variantCount;

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed — variantCount now only increments when an entry is actually stored in id2/id3.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use INSERT IGNORE for migration to handle id2==id3 duplicates
- Remove const_cast, use _creatureDataStore.find() directly
- Only increment variantCount when entry is actually stored
- Add validation parity to LoadCreatureDataFromDB (difficulty, equipment, overflow)
- Add ORDER BY entry for deterministic id2/id3 assignment
- Alias spawnId AS guid in UNION query

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added DB related to the SQL database CORE Related to the core Script Refers to C++ Scripts for the Core file-cpp Used to trigger the matrix build labels Mar 23, 2026
Comment thread data/sql/updates/pending_db_world/rev_8337814054007982.sql
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sudlud

sudlud commented Mar 23, 2026

Copy link
Copy Markdown
Member

Will Claude provide fixes for the 100 broken modules 🫣

@Nyeriah

Nyeriah commented Mar 23, 2026

Copy link
Copy Markdown
Member Author

The change is a simple rename (id1id) — modules just need to replace data->id1 with data->id (or .id1 with .id). Most modules don't even reference id1/id2/id3 at all. For the few that do, it's a one-line find-and-replace each.

@Nyeriah

Nyeriah commented Mar 23, 2026

Copy link
Copy Markdown
Member Author

Only one module in the CI suite is affected — mod-guildhouse (2 occurrences of id1). Fix PR submitted: azerothcore/mod-guildhouse#77

@sudlud

sudlud commented Mar 23, 2026

Copy link
Copy Markdown
Member

Alright, currently module ci is failing on build already, so it's not showing the failing sql files yet

@Nyeriah

Nyeriah commented Mar 23, 2026

Copy link
Copy Markdown
Member Author

Resolve conflicts from the creature id1->id rename and creature_multispawn extraction against current master:

- Creature::Respawn: keep master's compat/non-compat split; multi-ID random
  re-selection stays in compat mode (id1->id), non-compat recreates via
  ProcessRespawns/LoadCreatureFromDB which re-rolls the entry.
- ObjectMgr::LoadCreatures: keep new-schema field indices, add master's
  data.spawnId assignment.
- Update id1 references added to master after the PR was opened
  (cs_pool, cs_misc, cs_list, ConditionMgr) to the renamed field.
- Fix .list creature SQL to query creature.id plus creature_multispawn
  instead of the dropped id1/id2/id3 columns.
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Introduces a new creature_multispawn table to hold alternate spawn entries, migrates existing id2/id3 data into it via a SQL update, renames creature.id1 to creature.id, and drops the old multi-id columns. All C++ code referencing CreatureData::id1 is updated to use id across loading, saving, respawning, conditions, events, GM commands, and instance scripts.

Changes

creature_multispawn schema migration and id1→id rename

Layer / File(s) Summary
DB schema: creature_multispawn creation and creature column rename
data/sql/updates/pending_db_world/rev_8337814054007982.sql
Creates creature_multispawn (spawnId, entry), migrates non-zero id2/id3 rows from creature into it, renames creature.id1 to creature.id, drops id2/id3, and rebuilds idx_id.
CreatureData struct rename and prepared statement updates
src/server/game/Entities/Creature/CreatureData.h, src/server/database/Database/Implementation/WorldDatabase.cpp
Replaces id1 with id in struct CreatureData, and updates WORLD_SEL_CREATURE_BY_ID (now unions creature_multispawn), WORLD_SEL_CREATURE_NEAREST, WORLD_INS_CREATURE, and WORLD_SEL_GAME_EVENT_MODEL_EQUIPMENT_DATA to use creature.id.
ObjectMgr creature loading and linked-respawn refactor
src/server/game/Globals/ObjectMgr.cpp
Refactors LoadCreatures, LoadCreatureDataFromDB, and AddCreData to use creature.id as the primary entry and load alternates from creature_multispawn into data.id2/data.id3. Updates linked-respawn GUID construction and movement-override SQL joins.
Creature entity methods
src/server/game/Entities/Creature/Creature.cpp
Updates SaveToDB, LoadCreatureFromDB, Respawn, and GetScriptId to use data.id/data->id; removes two zero-bound parameters from WORLD_INS_CREATURE binding sequence.
Game systems
src/server/game/Events/GameEventMgr.cpp, src/server/game/Conditions/ConditionMgr.cpp, src/server/game/AI/SmartScripts/SmartScriptMgr.cpp, src/server/game/Handlers/AuctionHouseHandler.cpp, src/server/game/Maps/ZoneScript.h, src/server/game/OutdoorPvP/OutdoorPvP.cpp
Updates vendor loading, model/equipment change parsing, condition entry validation, SmartAI GUID checks, auctioneer template lookup, ZoneScript::GetCreatureEntry, and capture-point entry fallback to use id instead of id1.
GM commands
src/server/scripts/Commands/cs_go.cpp, cs_list.cpp, cs_misc.cpp, cs_npc.cpp, cs_pool.cpp, cs_tele.cpp, cs_wp.cpp
Updates all command handlers to filter, display, and construct creature spawn records using data->id; rewrites HandleListCreatureCommand SQL to union creature_multispawn; adjusts field-index offsets in HandleNpcNearCommand and HandleWpShowCommand.
Instance scripts
src/server/scripts/Kalimdor/TempleOfAhnQiraj/temple_of_ahnqiraj.cpp, src/server/scripts/Northrend/FrozenHalls/PitOfSaron/instance_pit_of_saron.cpp, src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.cpp, src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp
Updates linked-respawn GUID construction, GetCreatureEntry initializers, and captain/undead-state logic to read data->id instead of data->id1.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 A field called id1 once roamed the land,
But one brave rabbit said, "This must be unmanned!"
creature_multispawn now holds the rest,
While plain old id passed every test.
The warren is cleaner, the burrows aligned—
No extra id2/id3 left behind! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main database normalization change: extracting multi-ID spawns from the creature table into a dedicated table.
Description check ✅ Passed The description covers all required template sections with comprehensive detail: changes proposed, database normalization explanation, AI disclosure, testing requirements, and known issues. All critical information is present and well-documented.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/server/game/Events/GameEventMgr.cpp (1)

615-643: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Collapse multispawn variants before storing model/equipment changes.

The updated query can return multiple rows for the same (eventId, guid) through creature_multispawn, but _gameEventModelEquip stores only guid-level changes. Pushing one row per valid variant makes ChangeEquipOrModel() apply the same guid multiple times, which can overwrite EquipementIdPrev/ModelIdPrev and restore the wrong equipment/model when the event ends.

Proposed localized guard
+        std::unordered_set<uint64> loadedModelEquipKeys;
         uint32 count = 0;
         do
         {
             Field* fields = result->Fetch();

             ObjectGuid::LowType guid = fields[0].Get<uint32>();
             uint32 entry = fields[1].Get<uint32>();
             uint16 eventId = fields[2].Get<uint8>();
@@
                 }
             }
 
+            uint64 const key = (uint64(eventId) << 32) | guid;
+            if (!loadedModelEquipKeys.insert(key).second)
+                continue;
+
             equiplist.push_back(std::pair<ObjectGuid::LowType, ModelEquip>(guid, newModelEquipSet));
🤖 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/Events/GameEventMgr.cpp` around lines 615 - 643, The query
can now return multiple rows for the same (eventId, guid) pair due to
creature_multispawn joins, but the current code pushes all variants into the
equiplist within _gameEventModelEquip, causing ChangeEquipOrModel() to apply the
same guid multiple times and overwrite EquipementIdPrev/ModelIdPrev. Deduplicate
the results by tracking which (eventId, guid) pairs have already been processed
in the current load iteration and skip pushing subsequent variants, ensuring
only the first valid variant per guid is stored in the equiplist.
🧹 Nitpick comments (1)
data/sql/updates/pending_db_world/rev_8337814054007982.sql (1)

2-6: ⚡ Quick win

Add a secondary index for entry lookups.

The primary key (spawnId, entry) does not efficiently support WHERE entry = ? lookups. Since that predicate is now used in prepared statements, a dedicated entry index avoids unnecessary scans.

🔧 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`)
+  PRIMARY KEY (`spawnId`, `entry`),
+  KEY `idx_entry` (`entry`)
 ) 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 primary key on (spawnId, entry) does not efficiently support lookups
filtering by entry alone. Add a secondary index on the entry column to the
creature_multispawn table to optimize WHERE entry = ? queries. This should be
added after the PRIMARY KEY definition to provide efficient index coverage for
entry-based lookups in prepared statements.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@data/sql/updates/pending_db_world/rev_8337814054007982.sql`:
- Around line 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.

In `@src/server/game/Entities/Creature/Creature.cpp`:
- 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.

In `@src/server/game/Globals/ObjectMgr.cpp`:
- Line 2369: In src/server/game/Globals/ObjectMgr.cpp at line 2369 and line
2599, after assigning the primary creatureId to data.id, also reset the
alternate entry fields data.id2 and data.id3 to clear any stale values. This is
necessary because these alternate fields are no longer selected from the
creature table, so reused CreatureData entries can retain stale alternates when
multispawn rows change. Add the reset of id2 and id3 alongside the data.id
assignment at both locations to ensure consistency across both load paths.
- Line 2501: The startup variant loading query in ObjectMgr.cpp at the
WorldDatabase.Query call for creature_multispawn needs to match the
deterministic ordering used by the lazy path. Currently, the query only orders
by spawnId, but it should order by both spawnId and entry to ensure consistent
variant assignment and prevent different "extra" variants from being selected
when handling duplicate or malformed data. Add entry to the ORDER BY clause in
the same query that selects spawnId and entry from creature_multispawn.
- Around line 2111-2112: The code calls GetCreatureData(guidLow) which can
return nullptr, but then immediately dereferences the master pointer without
validation on the next line when building the GUID via
ObjectGuid::Create<HighGuid::Unit>(master->id, guidLow). Add a null check for
the master pointer immediately after the GetCreatureData call and before the
ObjectGuid::Create dereference to handle the case where GetCreatureData returns
nullptr, preventing a potential server crash from invalid input.

In `@src/server/scripts/Commands/cs_list.cpp`:
- Around line 80-92: The three WorldDatabase.Query calls at lines 80–92 are
using raw SQL strings with inline parameters instead of prepared statements.
Convert each of these queries to use PreparedStatement objects from
WorldDatabase, binding the parameters (creatureId, count, player position
values) via the SetData method. This applies to the COUNT query, the
distance-ordered query with player position calculations (using POW and
player->GetPositionX/Y/Z), and the simpler query without position ordering.

In `@src/server/scripts/Commands/cs_npc.cpp`:
- Around line 733-735: The PSendSysMessage call in the NPC info command is
missing the id1 argument for the LANG_NPCINFO_CHAR format string, causing all
subsequent field values to shift and produce incorrect output. Add the missing
cData->id1 argument to the handler->PSendSysMessage call at the correct position
so that id1, id2, id3 are passed in the proper order after the initial
ObjectGuid argument, ensuring the format string placeholders align correctly
with their corresponding values.

In `@src/server/scripts/Commands/cs_tele.cpp`:
- Line 355: Replace the raw SQL query in the WorldDatabase.Query() call with a
prepared statement approach. Define a new prepared statement enum
WORLD_SEL_CREATURE_POS_BY_TEMPLATE_NAME in the WorldDatabase implementation to
encapsulate the creature position query (selecting position_x, position_y,
position_z, orientation, map, and creature template name), then use
PreparedStatement with the WorldDatabase instead of passing the raw query string
with inline parameter substitution. This ensures the query follows the server
database contract and protects against SQL injection through proper
parameterized query handling.

---

Outside diff comments:
In `@src/server/game/Events/GameEventMgr.cpp`:
- Around line 615-643: The query can now return multiple rows for the same
(eventId, guid) pair due to creature_multispawn joins, but the current code
pushes all variants into the equiplist within _gameEventModelEquip, causing
ChangeEquipOrModel() to apply the same guid multiple times and overwrite
EquipementIdPrev/ModelIdPrev. Deduplicate the results by tracking which
(eventId, guid) pairs have already been processed in the current load iteration
and skip pushing subsequent variants, ensuring only the first valid variant per
guid is stored in the equiplist.

---

Nitpick comments:
In `@data/sql/updates/pending_db_world/rev_8337814054007982.sql`:
- Around line 2-6: The primary key on (spawnId, entry) does not efficiently
support lookups filtering by entry alone. Add a secondary index on the entry
column to the creature_multispawn table to optimize WHERE entry = ? queries.
This should be added after the PRIMARY KEY definition to provide efficient index
coverage for entry-based lookups in prepared statements.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8e6d76b7-73c4-4a83-addd-a20506b7fe95

📥 Commits

Reviewing files that changed from the base of the PR and between eb3cdd1 and 97f701f.

📒 Files selected for processing (22)
  • data/sql/updates/pending_db_world/rev_8337814054007982.sql
  • src/server/database/Database/Implementation/WorldDatabase.cpp
  • src/server/game/AI/SmartScripts/SmartScriptMgr.cpp
  • src/server/game/Conditions/ConditionMgr.cpp
  • src/server/game/Entities/Creature/Creature.cpp
  • src/server/game/Entities/Creature/CreatureData.h
  • src/server/game/Events/GameEventMgr.cpp
  • src/server/game/Globals/ObjectMgr.cpp
  • src/server/game/Handlers/AuctionHouseHandler.cpp
  • src/server/game/Maps/ZoneScript.h
  • src/server/game/OutdoorPvP/OutdoorPvP.cpp
  • src/server/scripts/Commands/cs_go.cpp
  • src/server/scripts/Commands/cs_list.cpp
  • src/server/scripts/Commands/cs_misc.cpp
  • src/server/scripts/Commands/cs_npc.cpp
  • src/server/scripts/Commands/cs_pool.cpp
  • src/server/scripts/Commands/cs_tele.cpp
  • src/server/scripts/Commands/cs_wp.cpp
  • src/server/scripts/Kalimdor/TempleOfAhnQiraj/temple_of_ahnqiraj.cpp
  • src/server/scripts/Northrend/FrozenHalls/PitOfSaron/instance_pit_of_saron.cpp
  • src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.cpp
  • src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp

Comment on lines +2 to +6
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';

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.

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

Comment on lines 2111 to +2112
CreatureData const* master = GetCreatureData(guidLow);
ObjectGuid guid = ObjectGuid::Create<HighGuid::Unit>(master->id1, guidLow);
ObjectGuid guid = ObjectGuid::Create<HighGuid::Unit>(master->id, guidLow);

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 | 🔴 Critical | ⚡ Quick win

Validate master before building the GUID.

GetCreatureData(guidLow) can return nullptr, but Line 2112 dereferences master before any check. Invalid nonzero input can crash the server.

Proposed fix
     CreatureData const* master = GetCreatureData(guidLow);
+    if (!master)
+    {
+        LOG_ERROR("sql.sql", "Creature '{}' does not exist.", guidLow);
+        return false;
+    }
+
     ObjectGuid guid = ObjectGuid::Create<HighGuid::Unit>(master->id, guidLow);
📝 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
CreatureData const* master = GetCreatureData(guidLow);
ObjectGuid guid = ObjectGuid::Create<HighGuid::Unit>(master->id1, guidLow);
ObjectGuid guid = ObjectGuid::Create<HighGuid::Unit>(master->id, guidLow);
CreatureData const* master = GetCreatureData(guidLow);
if (!master)
{
LOG_ERROR("sql.sql", "Creature '{}' does not exist.", guidLow);
return false;
}
ObjectGuid guid = ObjectGuid::Create<HighGuid::Unit>(master->id, guidLow);
🤖 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/Globals/ObjectMgr.cpp` around lines 2111 - 2112, The code
calls GetCreatureData(guidLow) which can return nullptr, but then immediately
dereferences the master pointer without validation on the next line when
building the GUID via ObjectGuid::Create<HighGuid::Unit>(master->id, guidLow).
Add a null check for the master pointer immediately after the GetCreatureData
call and before the ObjectGuid::Create dereference to handle the case where
GetCreatureData returns nullptr, preventing a potential server crash from
invalid input.

data.unit_flags = fields[21].Get<uint32>();
data.dynamicflags = fields[22].Get<uint32>();
data.ScriptId = GetScriptId(fields[23].Get<std::string>());
data.id = creatureId;

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

Reset alternate entries before loading creature_multispawn.

id2 and id3 are no longer selected from creature, so reused CreatureData entries can retain stale alternates when the multispawn rows change. Clear them with the primary id assignment in both load paths.

Proposed fix
         data.id                 = creatureId;
+        data.id2                = 0;
+        data.id3                = 0;
         data.mapid              = fields[2].Get<uint16>();
     creatureData.id               = creatureId;
+    creatureData.id2              = 0;
+    creatureData.id3              = 0;
     creatureData.mapid            = fields[2].Get<uint16>();

Also applies to: 2599-2599

🤖 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/Globals/ObjectMgr.cpp` at line 2369, In
src/server/game/Globals/ObjectMgr.cpp at line 2369 and line 2599, after
assigning the primary creatureId to data.id, also reset the alternate entry
fields data.id2 and data.id3 to clear any stale values. This is necessary
because these alternate fields are no longer selected from the creature table,
so reused CreatureData entries can retain stale alternates when multispawn rows
change. Add the reset of id2 and id3 alongside the data.id assignment at both
locations to ensure consistency across both load paths.

} while (result->NextRow());

// Load alternate entries from creature_multispawn
QueryResult variantResult = WorldDatabase.Query("SELECT spawnId, entry FROM creature_multispawn ORDER BY 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

Use deterministic ordering for startup variant loading.

The lazy path orders a spawn’s variants by entry, but startup only orders by spawnId; ties can assign id2/id3 differently and choose a different “extra” variant when bad data has more than two rows.

Proposed fix
-    QueryResult variantResult = WorldDatabase.Query("SELECT spawnId, entry FROM creature_multispawn ORDER BY spawnId");
+    QueryResult variantResult = WorldDatabase.Query(
+        "SELECT spawnId, entry FROM creature_multispawn "
+        "ORDER BY spawnId, entry");
🤖 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/Globals/ObjectMgr.cpp` at line 2501, The startup variant
loading query in ObjectMgr.cpp at the WorldDatabase.Query call for
creature_multispawn needs to match the deterministic ordering used by the lazy
path. Currently, the query only orders by spawnId, but it should order by both
spawnId and entry to ensure consistent variant assignment and prevent different
"extra" variants from being selected when handling duplicate or malformed data.
Add entry to the ORDER BY clause in the same query that selects spawnId and
entry from creature_multispawn.

Comment on lines +80 to +92
result = WorldDatabase.Query("SELECT COUNT(guid) FROM creature WHERE id = '{}' OR guid IN (SELECT spawnId FROM creature_multispawn WHERE entry = '{}')", uint32(creatureId), uint32(creatureId));
if (result)
creatureCount = (*result)[0].Get<uint64>();

if (handler->GetSession())
{
Player* player = handler->GetSession()->GetPlayer();
result = WorldDatabase.Query("SELECT guid, position_x, position_y, position_z, map, (POW(position_x - '{}', 2) + POW(position_y - '{}', 2) + POW(position_z - '{}', 2)) AS order_ FROM creature WHERE id1='{}' OR id2='{}' OR id3='{}' ORDER BY order_ ASC LIMIT {}",
player->GetPositionX(), player->GetPositionY(), player->GetPositionZ(), uint32(creatureId), uint32(creatureId), uint32(creatureId), count);
result = WorldDatabase.Query("SELECT guid, position_x, position_y, position_z, map, (POW(position_x - '{}', 2) + POW(position_y - '{}', 2) + POW(position_z - '{}', 2)) AS order_ FROM creature WHERE id = '{}' OR guid IN (SELECT spawnId FROM creature_multispawn WHERE entry = '{}') ORDER BY order_ ASC LIMIT {}",
player->GetPositionX(), player->GetPositionY(), player->GetPositionZ(), uint32(creatureId), uint32(creatureId), count);
}
else
result = WorldDatabase.Query("SELECT guid, position_x, position_y, position_z, map FROM creature WHERE id1='{}' OR id2='{}' OR id3='{}' LIMIT {}",
uint32(creatureId), uint32(creatureId), uint32(creatureId), count);
result = WorldDatabase.Query("SELECT guid, position_x, position_y, position_z, map FROM creature WHERE id = '{}' OR guid IN (SELECT spawnId FROM creature_multispawn WHERE entry = '{}') LIMIT {}",
uint32(creatureId), uint32(creatureId), count);

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.

🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Use prepared statements for the new creature listing queries.

Lines 80–92 introduce/modify raw SQL string queries in server C++ command code. Please move these to WorldDatabase prepared statements and bind parameters (creatureId, count, position values) via SetData to stay compliant and keep query contracts centralized.

As per coding guidelines, use PreparedStatement (via WorldDatabase, CharacterDatabase, or LoginDatabase) instead of raw query strings for database queries in C++.

🤖 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/scripts/Commands/cs_list.cpp` around lines 80 - 92, The three
WorldDatabase.Query calls at lines 80–92 are using raw SQL strings with inline
parameters instead of prepared statements. Convert each of these queries to use
PreparedStatement objects from WorldDatabase, binding the parameters
(creatureId, count, player position values) via the SetData method. This applies
to the COUNT query, the distance-ordered query with player position calculations
(using POW and player->GetPositionX/Y/Z), and the simpler query without position
ordering.

Source: Coding guidelines

Comment on lines +733 to 735
handler->PSendSysMessage(LANG_NPCINFO_CHAR, lowGuid, ObjectGuid::Create<HighGuid::Unit>(cData->id, lowGuid).ToString(), cData->id,
cData->id2, cData->id3, cData->displayid, cData->displayid, cInfo->faction,
cData->npcflag);

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

LANG_NPCINFO_CHAR arguments are shifted in the DB-only NPC info path.

Line 733 drops the id1 slot value, so the remaining fields shift (id2 is shown as id1, etc.). This can produce incorrect output and format-argument mismatch behavior.

Proposed fix
-        handler->PSendSysMessage(LANG_NPCINFO_CHAR, lowGuid, ObjectGuid::Create<HighGuid::Unit>(cData->id, lowGuid).ToString(), cData->id,
-            cData->id2, cData->id3, cData->displayid, cData->displayid, cInfo->faction,
+        handler->PSendSysMessage(LANG_NPCINFO_CHAR, lowGuid, ObjectGuid::Create<HighGuid::Unit>(cData->id, lowGuid).ToString(), cData->id,
+            cData->id, cData->id2, cData->id3, cData->displayid, cData->displayid, cInfo->faction,
             cData->npcflag);
🤖 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/scripts/Commands/cs_npc.cpp` around lines 733 - 735, The
PSendSysMessage call in the NPC info command is missing the id1 argument for the
LANG_NPCINFO_CHAR format string, causing all subsequent field values to shift
and produce incorrect output. Add the missing cData->id1 argument to the
handler->PSendSysMessage call at the correct position so that id1, id2, id3 are
passed in the proper order after the initial ObjectGuid argument, ensuring the
format string placeholders align correctly with their corresponding values.


// May need work //PussyWizardEliteMalcrom
QueryResult result = WorldDatabase.Query("SELECT c.position_x, c.position_y, c.position_z, c.orientation, c.map, ct.name FROM creature c INNER JOIN creature_template ct ON c.id1 = ct.entry WHERE ct.name LIKE '{}'", normalizedName);
QueryResult result = WorldDatabase.Query("SELECT c.position_x, c.position_y, c.position_z, c.orientation, c.map, ct.name FROM creature c INNER JOIN creature_template ct ON c.id = ct.entry WHERE ct.name LIKE '{}'", normalizedName);

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

Use a prepared statement instead of an inline SQL string.

Line 355 still executes a raw query built from command input. Even with escaping, this should follow the server DB contract and move to a PreparedStatement path.

As per coding guidelines, "Use PreparedStatement (via WorldDatabase, CharacterDatabase, or LoginDatabase and prepared-statement enums) instead of raw query strings for database queries in C++."

Suggested direction
- QueryResult result = WorldDatabase.Query("SELECT c.position_x, c.position_y, c.position_z, c.orientation, c.map, ct.name FROM creature c INNER JOIN creature_template ct ON c.id = ct.entry WHERE ct.name LIKE '{}'", normalizedName);
+ WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_CREATURE_POS_BY_TEMPLATE_NAME);
+ stmt->SetData(0, normalizedName);
+ QueryResult result = WorldDatabase.Query(stmt);

Also add WORLD_SEL_CREATURE_POS_BY_TEMPLATE_NAME in src/server/database/Database/Implementation/WorldDatabase.cpp.

🤖 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/scripts/Commands/cs_tele.cpp` at line 355, Replace the raw SQL
query in the WorldDatabase.Query() call with a prepared statement approach.
Define a new prepared statement enum WORLD_SEL_CREATURE_POS_BY_TEMPLATE_NAME in
the WorldDatabase implementation to encapsulate the creature position query
(selecting position_x, position_y, position_z, orientation, map, and creature
template name), then use PreparedStatement with the WorldDatabase instead of
passing the raw query string with inline parameter substitution. This ensures
the query follows the server database contract and protects against SQL
injection through proper parameterized query handling.

Source: Coding guidelines

@sudlud sudlud left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Merged current master into the branch and resolved the two conflicts:

  • Creature::Respawn: kept master's compat / non-compat split. The multi-ID re-roll stays in compat mode; non-compat destroys and recreates via ProcessRespawns -> LoadCreatureFromDB, which re-rolls the entry anyway.
  • ObjectMgr::LoadCreatures: kept the new-schema field indices and master's data.spawnId assignment.

Also updated id1 references that master added after this PR was opened and that the rename would otherwise break to compile (cs_pool, cs_misc, cs_list, ConditionMgr), and fixed the .list creature query to read creature.id plus creature_multispawn instead of the dropped id1/id2/id3 columns.

Verified the net diff against master is exactly the rename plus the creature_multispawn extraction, with no remaining id1 references. LGTM.

@sudlud
sudlud merged commit cf0d86a into azerothcore:master Jun 16, 2026
22 of 23 checks passed
Deokishisu added a commit to Deokishisu/mod-dungeon-master that referenced this pull request Jun 18, 2026
In the `creature` table, `id1` was renamed to `id` and `id2` and `id3` were moved to a `creature_multispawn` table upstream in AzerothCore in this PR: azerothcore/azerothcore-wotlk#25197 .

This commit updates the module's SQL files, a .h file, and a .cpp file to work with those changes.
@Nyeriah
Nyeriah deleted the multispawn branch June 21, 2026 11:42
sudlud added a commit to mpfans/azerothcore-wotlk that referenced this pull request Jun 27, 2026
…::id

PR azerothcore#25197 renamed CreatureData::id1 to id, so the despawner loop no longer
compiled. Update the field access and clean up the loop per review:

- Snapshot matching spawnIds first, then re-time outside the iteration to
  avoid mutating GetCreatureRespawnTimes() while iterating it.
- Drop the redundant per-map mapid guard (the store is already per-map).
- Trim the comments.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tntdruid added a commit to Tntdruid/mod-changeablespawnrates that referenced this pull request Jul 19, 2026
The id1, id2 and id3 got removed it azerothcore/azerothcore-wotlk#25197 its only id now.
jspahrsummers added a commit to felworld/azerothcore that referenced this pull request Aug 2, 2026
The pending update was generated against the base-dump creature schema
(id1/id2/id3), but 2026_06_16_00.sql (creature_multispawn, azerothcore#25197)
renamed id1 to id and dropped id2/id3, so db-import failed with
"Unknown column 'id1' in 'field list'". Rename the column in the
INSERT; no other table in the update was altered post-base-dump.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
trauntrow added a commit to InstanceForge/mod-dungeon-master that referenced this pull request Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CORE Related to the core DB related to the SQL database file-cpp Used to trigger the matrix build Script Refers to C++ Scripts for the Core To Be Merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants