From 91f23fceef35849568c9645bc2f87d7e1dc29fea Mon Sep 17 00:00:00 2001 From: luis Date: Fri, 8 May 2026 22:14:33 -0300 Subject: [PATCH] Housing: wire blizzlike door click via SpellID 1271876 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sniff-decoded from C:\sniff\housing_stuff\{alliance,horde}_housing\ dump_12.0.5.67186_2026-04-28_*.pkt: when a player clicks a housing door GO the 12.0.5 retail client emits CMSG_CAST_SPELL with SpellID 1,271,876 (uint32 at body offset 0x15) targeting the door. Both faction sniffs are byte-identical in the spell-id field; only the target PackedGUID at offset 0x3D differs. CMSG_GAME_OBJ_REPORT_USE that follows is criteria-tracking, NOT the trigger — the cast is. The server side had four stacked gaps blocking this flow: A. SpellID 1271876 was missing from spell_name / spell_misc / spell_effect. Cast validation rejected it as unknown. Added a minimal row set with SPELL_EFFECT_DUMMY targeting TARGET_GAMEOBJECT_TARGET so the cast accepts and the SpellScript hook fires. B. gameobject_template.Data10 (goober.spell) on the housing front-door templates was either zero (575017, 602702) or pointing at the older 12.0.1 spell 1234192 (586576, 602705) / 1234193 (587318). Neither matches what the 12.0.5 retail client casts, so the goober mechanism wouldn't have routed correctly even if the spell existed. All five entries now point at 1271876. C. New SpellScript spell_housing_door_open, registered against 1271876 via spell_script_names, intercepts the cast and calls GameObject::Use() on the spell target. That routes to the existing go_housing_door::OnGossipHello which already handles edit-mode gating, visitor permissions, and the interior round trip. D. The exterior_component SQL hotfix table was on an older WoW build's schema (FileDataID/ConditionID/HookID/Slot/SortOrder/...) that doesn't line up with DB2LoadInfo's 12.0.5 layout (Name + 3 floats + ID + Size + ParentComponentID + ModelFileDataID + Flags + Field_7 + Type + Field_9 + GameObjectID + Field_11 + ItemID + HouseExteriorWmoDataID). Without a GameObjectID column the hotfix loader was filling that field with zeros, so HousingMap:: SpawnExtCompTree never spawned door GOs in the first place. Schema rebuilt to match the 14-field 12.0.5 record (LayoutHash 0x53EA0925). --- .../master/2026_05_08_00_hotfixes.sql | 98 +++++++++++++++++++ .../world/master/2026_05_08_01_world.sql | 39 ++++++++ src/server/scripts/Spells/spell_housing.cpp | 65 +++++++++++- 3 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 sql/updates/hotfixes/master/2026_05_08_00_hotfixes.sql create mode 100644 sql/updates/world/master/2026_05_08_01_world.sql diff --git a/sql/updates/hotfixes/master/2026_05_08_00_hotfixes.sql b/sql/updates/hotfixes/master/2026_05_08_00_hotfixes.sql new file mode 100644 index 000000000..34467313e --- /dev/null +++ b/sql/updates/hotfixes/master/2026_05_08_00_hotfixes.sql @@ -0,0 +1,98 @@ +-- +-- 2026-04-29: housing door click — spell 1271876 + exterior_component schema refresh +-- +-- Sniff-decoded from C:\sniff\housing_stuff\{alliance,horde}_housing\dump_12.0.5.67186_2026-04-28_*.pkt: +-- The retail client casts SpellID 1271876 (uint32 at CMSG_CAST_SPELL body offset 0x15) when a +-- player clicks a housing door GO. Bytes 0x15-0x18 are identical across both faction sniffs; +-- only the target PackedGUID at offset 0x3D differs. The cast (not CMSG_GAME_OBJ_REPORT_USE, +-- which is the criteria-tracking follow-up) is what triggers TRANSFER_PENDING + NEW_WORLD on +-- retail. Adding a minimal spell record so server-side cast validation accepts the spell — +-- the actual door action is wired via gameobject_template.data2 (goober.spell) in the world +-- update + the SpellScript in src/server/scripts/Spells/spell_housing.cpp. + +-- Task A: Spell 1271876 "Housing Door Open" +DELETE FROM `spell_name` WHERE `id` = 1271876; +INSERT INTO `spell_name` (`id`, `name`, `VerifiedBuild`) VALUES + (1271876, 'Housing Door Open', 67186); + +DELETE FROM `spell_misc` WHERE `SpellID` = 1271876; +INSERT INTO `spell_misc` ( + `Attributes1`, `Attributes2`, `Attributes3`, `Attributes4`, `Attributes5`, `Attributes6`, `Attributes7`, + `Attributes8`, `Attributes9`, `Attributes10`, `Attributes11`, `Attributes12`, `Attributes13`, `Attributes14`, + `Attributes15`, `Attributes16`, `Attributes17`, + `DifficultyID`, `CastingTimeIndex`, `DurationIndex`, `PvPDurationIndex`, `RangeIndex`, `SchoolMask`, + `Speed`, `LaunchDelay`, `MinDuration`, + `SpellIconFileDataID`, `ActiveIconFileDataID`, `ContentTuningID`, `ShowFutureSpellPlayerConditionID`, + `SpellVisualScript`, `ActiveSpellVisualScript`, + `SpellID`, `VerifiedBuild` +) VALUES + (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0, 1, 0, 0, 1, 0, + 0, 0, 0, + 0, 0, 0, 0, + 504329, 0, + 1271876, 67186); + +-- Effect 0: SPELL_EFFECT_DUMMY (3), targeting the GameObject (TARGET_GAMEOBJECT_TARGET = 50). +-- The spell needs at least one effect for SpellScript hooks to fire and for SpellInfo +-- validation to pass. The actual door action runs in spell_housing_door_open's +-- HandleDummy override (see src/server/scripts/Spells/spell_housing.cpp), which calls +-- GameObject::Use() on the target so go_housing_door::OnGossipHello executes. +DELETE FROM `spell_effect` WHERE `SpellID` = 1271876; +INSERT INTO `spell_effect` ( + `ID`, `EffectAura`, `DifficultyID`, `EffectIndex`, `Effect`, + `EffectAmplitude`, `EffectAttributes`, `EffectAuraPeriod`, + `EffectBonusCoefficient`, `EffectChainAmplitude`, `EffectChainTargets`, + `EffectItemType`, `EffectMechanic`, `EffectPointsPerResource`, + `EffectPosFacing`, `EffectRealPointsPerLevel`, `EffectTriggerSpell`, + `BonusCoefficientFromAP`, `PvpMultiplier`, `Coefficient`, + `Variance`, `ResourceCoefficient`, `GroupSizeBasePointsCoefficient`, + `EffectBasePoints`, `EffectMiscValue1`, `EffectMiscValue2`, + `EffectRadiusIndex1`, `EffectRadiusIndex2`, + `EffectSpellClassMask1`, `EffectSpellClassMask2`, + `EffectSpellClassMask3`, `EffectSpellClassMask4`, + `ImplicitTarget1`, `ImplicitTarget2`, + `SpellID`, `VerifiedBuild` +) VALUES + (1271876, 0, 0, 0, 3, -- ID, EffectAura, DifficultyID, EffectIndex, Effect=DUMMY + 0, 0, 0, -- EffectAmplitude, EffectAttributes, EffectAuraPeriod + 0, 0, 0, -- EffectBonusCoefficient, EffectChainAmplitude, EffectChainTargets + 0, 0, 0, -- EffectItemType, EffectMechanic, EffectPointsPerResource + 0, 0, 0, -- EffectPosFacing, EffectRealPointsPerLevel, EffectTriggerSpell + 0, 0, 0, -- BonusCoefficientFromAP, PvpMultiplier, Coefficient + 0, 0, 0, -- Variance, ResourceCoefficient, GroupSizeBasePointsCoefficient + 0, 0, 0, -- EffectBasePoints, EffectMiscValue1, EffectMiscValue2 + 0, 0, -- EffectRadiusIndex1, EffectRadiusIndex2 + 0, 0, 0, 0, -- EffectSpellClassMask1..4 + 23, 0, -- ImplicitTarget1=TARGET_GAMEOBJECT_TARGET, ImplicitTarget2=NONE + 1271876, 67186); + + +-- Task D: ExteriorComponent — schema refresh to match 12.0.5 DB2LoadInfo (build 67186 layout 0x53EA0925) +-- Old SQL columns (FileDataID/ConditionID/HookID/Slot/SortOrder/ComponentGroupID/UiTextureKitID/ +-- ExteriorComponentTypeID) were from an older WoW build and don't line up with the 14-field +-- 12.0.5 record (Name + 3 floats + ID + Size + ParentComponentID + ModelFileDataID + Flags + +-- Field_7 + Type + Field_9 + GameObjectID + Field_11 + ItemID + HouseExteriorWmoDataID). +-- HousingMap::SpawnExtCompTree reads `comp->GameObjectID` to spawn the door GO; with the old +-- schema that column doesn't exist and HotfixDatabase loads zeros, so doors never spawn. +DROP TABLE IF EXISTS `exterior_component`; +CREATE TABLE `exterior_component` ( + `Name` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `PositionX` float NOT NULL DEFAULT '0', + `PositionY` float NOT NULL DEFAULT '0', + `PositionZ` float NOT NULL DEFAULT '0', + `ID` int unsigned NOT NULL DEFAULT '0', + `Size` tinyint unsigned NOT NULL DEFAULT '0', + `ParentComponentID` int NOT NULL DEFAULT '0', + `ModelFileDataID` int NOT NULL DEFAULT '0', + `Flags` int NOT NULL DEFAULT '0', + `Field_7` tinyint unsigned NOT NULL DEFAULT '0', + `Type` tinyint unsigned NOT NULL DEFAULT '0', + `Field_9` int NOT NULL DEFAULT '0', + `GameObjectID` int NOT NULL DEFAULT '0', + `Field_11` int NOT NULL DEFAULT '0', + `ItemID` int NOT NULL DEFAULT '0', + `HouseExteriorWmoDataID` int unsigned NOT NULL DEFAULT '0', + `VerifiedBuild` int NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; \ No newline at end of file diff --git a/sql/updates/world/master/2026_05_08_01_world.sql b/sql/updates/world/master/2026_05_08_01_world.sql new file mode 100644 index 000000000..15a3ef431 --- /dev/null +++ b/sql/updates/world/master/2026_05_08_01_world.sql @@ -0,0 +1,39 @@ +-- +-- 2026-04-29: housing front-door GO templates — set goober.spell (Data10) to 1271876 +-- +-- Sniff-decoded from C:\sniff\housing_stuff\{alliance,horde}_housing\ +-- dump_12.0.5.67186_2026-04-28_*.pkt: when a housing door is clicked the 12.0.5 retail +-- client emits CMSG_CAST_SPELL with SpellID 1,271,876 targeting the door GO. That ID +-- is the new "Housing Door Open" spell (added in this commit's hotfix update). The +-- corresponding goober field on the door GO templates was either zero (575017, +-- 602702) or pointing at the older 12.0.1 spell 1234192 (586576, 602705) — neither +-- matches what the retail client casts, so server-side spell validation rejects the +-- cast (Spell::CheckSpellId or Spell::SendCastResult). Aligning Data10 with the +-- 12.0.5 spell makes the cast accept and run our SpellScript, which calls +-- GameObject::Use() on the target — invoking the existing go_housing_door:: +-- OnGossipHello handler that performs the interior teleport. +-- +-- Entries covered (all GAMEOBJECT_TYPE_GOOBER housing front doors): +-- 575017 Interior Front Door (Data10 was 0) +-- 586576 Front Door (Founder's Point) (Data10 was 1234192) +-- 587318 Razorwind Shores Front Door (Data10 was 1234193 — Razorwind variant) +-- 602702 Front Door (Data10 was 0) +-- 602705 Front Door (Data10 was 1234192) + +UPDATE `gameobject_template` SET `Data10` = 1271876 WHERE `entry` IN ( + 575017, + 586576, + 587318, + 602702, + 602705 +); + + +-- +-- Bind SpellID 1271876 to the spell_housing_door_open SpellScript so the dummy +-- effect actually invokes our handler. SpellScripts registered via +-- RegisterSpellScript() in C++ are matched to spells through this table. +-- +DELETE FROM `spell_script_names` WHERE `spell_id` = 1271876 AND `ScriptName` = 'spell_housing_door_open'; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES + (1271876, 'spell_housing_door_open'); \ No newline at end of file diff --git a/src/server/scripts/Spells/spell_housing.cpp b/src/server/scripts/Spells/spell_housing.cpp index 4e9546996..4ad0aefe6 100644 --- a/src/server/scripts/Spells/spell_housing.cpp +++ b/src/server/scripts/Spells/spell_housing.cpp @@ -23,7 +23,8 @@ enum HousingCornerstoneSpells { - SPELL_TRIGGER_CONVO_UNOWNED_PLOT = 1266097 + SPELL_TRIGGER_CONVO_UNOWNED_PLOT = 1266097, + SPELL_HOUSING_DOOR_OPEN = 1271876 // 12.0.5 retail housing-front-door open spell }; // 1266097 - [DNT] Trigger Convo for Unowned Plot @@ -54,7 +55,69 @@ class spell_housing_trigger_convo_unowned_plot : public SpellScript } }; +// 1271876 - Housing Door Open (12.0.5 retail) +// Cast by client when a player clicks a housing front door GO. Sniff-decoded from +// dump_12.0.5.67186_2026-04-28_*.pkt: bytes 0x15-0x18 of CMSG_CAST_SPELL body = +// 0x00136644 = 1271876, with the door GO as the spell target. Both faction sniffs +// agree on the spell ID (only the target PackedGUID differs). +// +// The spell row in spell_misc / spell_effect we ship for it (sql/updates/hotfixes/ +// master/2026_04_29_00_hotfixes.sql) is a minimal SPELL_EFFECT_DUMMY targeting +// TARGET_GAMEOBJECT_TARGET so server-side validation accepts the cast and the +// dummy hook fires. We intentionally do NOT use SPELL_EFFECT_OPEN_LOCK or +// SPELL_EFFECT_USE_GAMEOBJECT ? those would double-trigger Use() through the spell +// effect machinery in addition to the explicit Use() this script issues. Routing +// through one well-defined path keeps the door teleport semantics owned by the +// existing go_housing_door::OnGossipHello (which already handles edit-mode gating, +// visitor permissions, and the interior?exterior round trip). +class spell_housing_door_open : public SpellScript +{ + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return true; + } + + void HandleDummy(SpellEffIndex /*effIndex*/) const + { + Player* caster = GetCaster()->ToPlayer(); + if (!caster) + return; + + WorldObject* target = GetExplTargetWorldObject(); + if (!target) + target = GetHitGObj(); + if (!target) + { + TC_LOG_DEBUG("housing", "spell_housing_door_open: Spell {} cast by {} but no GO target ? ignored", + GetSpellInfo()->Id, caster->GetGUID().ToString()); + return; + } + + GameObject* doorGo = target->ToGameObject(); + if (!doorGo) + { + TC_LOG_DEBUG("housing", "spell_housing_door_open: Spell {} cast by {} on non-GO target {} ? ignored", + GetSpellInfo()->Id, caster->GetGUID().ToString(), target->GetGUID().ToString()); + return; + } + + TC_LOG_DEBUG("housing", "spell_housing_door_open: Player {} cast {} on door GO {} (entry={}) ? invoking Use()", + caster->GetGUID().ToString(), GetSpellInfo()->Id, doorGo->GetGUID().ToString(), doorGo->GetEntry()); + + // Route to the standard interaction path so go_housing_door::OnGossipHello + // performs the teleport. Use() handles edit-mode gating + animation; the + // gossip script then resolves the destination plot/interior. + doorGo->Use(caster); + } + + void Register() override + { + OnEffectHit += SpellEffectFn(spell_housing_door_open::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY); + } +}; + void AddSC_housing_spell_scripts() { RegisterSpellScript(spell_housing_trigger_convo_unowned_plot); + RegisterSpellScript(spell_housing_door_open); }