From d633c9ef18faa640c736d6da4f08ce747ed7c7c4 Mon Sep 17 00:00:00 2001 From: FlagFlayer <76889547+FlagFlayer@users.noreply.github.com> Date: Fri, 26 Sep 2025 09:39:40 +0300 Subject: [PATCH 1/5] Cherrypick and finish up changes from old branch --- sql/migrations/20250925202020_world.sql | 276 ++++++++++++++++++++++++ src/game/Commands/CharacterCommands.cpp | 30 +-- src/game/Handlers/NPCHandler.cpp | 4 +- src/game/ObjectMgr.cpp | 71 +++--- src/game/Objects/Creature.cpp | 163 ++------------ src/game/Objects/Creature.h | 5 +- src/game/Objects/CreatureDefines.h | 14 -- src/game/Objects/Object.cpp | 2 +- src/game/Objects/Player.cpp | 8 +- 9 files changed, 354 insertions(+), 219 deletions(-) create mode 100644 sql/migrations/20250925202020_world.sql diff --git a/sql/migrations/20250925202020_world.sql b/sql/migrations/20250925202020_world.sql new file mode 100644 index 00000000000..0034b734258 --- /dev/null +++ b/sql/migrations/20250925202020_world.sql @@ -0,0 +1,276 @@ +DROP PROCEDURE IF EXISTS add_migration; +delimiter ?? +CREATE PROCEDURE `add_migration`() +BEGIN +DECLARE v INT DEFAULT 1; +SET v = (SELECT COUNT(*) FROM `migrations` WHERE `id`='20250925202020'); +IF v=0 THEN +INSERT INTO `migrations` VALUES ('20250925202020'); +-- Add your query below. + +INSERT INTO `conditions` (`condition_entry`, `type`, `value1`, `value2`, `value3`, `value4`, `flags`) VALUES +(10660, 17, 10660, 0, 0, 0, 0), -- Condition to check if the player has learnt Tribal Leatherworking +(10658, 17, 10658, 0, 0, 0, 0), -- Condition to check if the player has learnt Elemental Leatherworking +(10656, 17, 10656, 0, 0, 0, 0), -- Condition to check if the player has learnt Dragonscale Leatherworking +-- Note: Condition 1367 corresponds to a condition checking if the player has learnt Goblin Engineering +-- Note: Condition 1371 corresponds to a condition checking if the player has a Goblin Engineer Membership Card in their inventory +(20222, -1, 1367, 1371, 0, 0, 0), -- Condition for goblin engineering trainer gossip +-- Note: Condition 1368 corresponds to a condition checking if the player has learnt Gnomish Engineering +-- Note: Condition 1374 corresponds to a condition checking if the player has a Gnome Engineer Membership Card in their inventory +(20219, -1, 1368, 1374, 0, 0, 0), -- Condition for gnomish engineering trainer gossip +(20220, -1, 20219, 2, 0, 0, 0); -- Condition for gnomish engineering trainer gossip for horde (Oglethorpe is a neutral NPC) + +-- Add missing trainer gossip menu options +INSERT INTO `gossip_menu_option` (`menu_id`, `id`, `option_icon`, `option_text`, `option_broadcast_text`, `option_id`, `npc_option_npcflag`, `action_menu_id`, `action_poi_id`, `action_script_id`, `box_coded`, `box_money`, `box_text`, `box_broadcast_text`, `condition_id`) VALUES +(3068, 0, 3, 'I would like to train.', 2548, 5, 16, 0, 0, 0, 0, 0, NULL, 0, 10656), -- Trainer gossip menu option for Thorkaf Dragoneye (Dragonscale Leatherworking - Horde) +(3069, 0, 3, 'I would like to train.', 2548, 5, 16, 0, 0, 0, 0, 0, NULL, 0, 10658); -- Trainer gossip menu option for Brumn Winterhoof (Elemental Leatherworking - Horde) + + +-- Update condition for existing trainer gossip menu options +UPDATE `gossip_menu_option` SET `condition_id` = 1354 WHERE `option_id` = 5 AND `menu_id` IN ( +597, -- Grumnus Steelshaper (Armorsmithing - Alliance) +1043, -- Shayis Steelfury (Armorsmithing - Horde) +3203); -- Okothos Ironrage (Armorsmithing - Horde) + +UPDATE `gossip_menu_option` SET `condition_id` = 1352 WHERE `option_id` = 5 AND `menu_id` IN ( +1041, -- Borgus Steelhand (Weaponsmithing - Alliance) +1042, -- Kelgruk Blooadaxe (Weaponsmithing - Horde) +3201, -- Ironus Coldsteel (Weaponsmithing - Alliance) +3202); -- Borgosh Corebender (Weaponsmithing - Horde) +UPDATE `gossip_menu_option` SET `condition_id` = 10656 WHERE `menu_id` = 3067 AND `option_id` = 5; -- Peter Galen (Dragonscale Leatherworking - Alliance) +UPDATE `gossip_menu_option` SET `condition_id` = 10658 WHERE `menu_id` = 3070 AND `option_id` = 5; -- Sarah Tanner (Elemental Leatherworking - Alliance) +UPDATE `gossip_menu_option` SET `condition_id` = 10660 WHERE `option_id` = 5 AND `menu_id` IN ( +3072, -- Caryssia Moonhunter (Tribal Leatherworking - Alliance) +3073); -- Se'Jib (Tribal Leatherworking - Horde) +UPDATE `gossip_menu_option` SET `condition_id` = 20222, `option_broadcast_text` = 4551 WHERE `menu_id` = 1465 AND `option_id` = 5; -- Vazario Linkgrease (Goblin Engineering) + Correction to broadcast_text entry (currently uses the Gnomish entry 4553 instead of the Goblin one, 4551) +UPDATE `gossip_menu_option` SET `condition_id` = 20219 WHERE `menu_id` = 1468 AND `option_id` = 5; -- Tinkmaster Overspark (Gnomish Engineering - Alliance) +UPDATE `gossip_menu_option` SET `condition_id` = 20220 WHERE `menu_id` = 1467 AND `option_id` = 5; -- Oglethorpe Obnoticus (Gnomish Engineering - Horde) +UPDATE `gossip_menu_option` SET `condition_id` = 20222 WHERE `menu_id` = 1469 AND `option_id` = 5; -- Nixx Sprocketspring (Goblin Engineering) + +-- Add class conditions for all class trainer gossip options -- +-- Druid +UPDATE `gossip_menu_option` SET `condition_id` = 89 WHERE `option_id` = 5 AND menu_id IN ( +1403, -- Mathrengyl Bearwalker +3921, -- Turak Runetotem +3923, -- Kal +3924, -- Denatharion +3926, -- Gennia Runetotem +4507, -- Maldryn +4508, -- Sheldras Moontree +4571, -- Fylerian Nightwing +4606, -- Sheal Runetotem, Jannos Lighthoof +4607, -- Kym Wildmane +4644, -- Gart Mistrunner +4687, -- Loganaar +4688, -- Mardant Strongoak, Theridran +4689); -- Golhine the Hooded +-- Warlock +UPDATE `gossip_menu_option` SET `condition_id` = 67 WHERE `option_id` = 5 AND menu_id IN ( +1503, -- Drusilla La Salle +2381, -- Briarthorn +2383, -- Kaal Soulreaper +2384, -- Zevrost +4503, -- Demisette Cloyce +4504, -- Sandahl +4505, -- Ursula Deline +4566, -- Alexander Calder +4567, -- Thistleheart +4603, -- Grol'dar +4604, -- Mirket +4609, -- Luther Pickman +4610, -- Richard Kerwin +4641, -- Dhugru Gorelust +4642, -- Kartosh +4643, -- Nartok +4655, -- Maximillion +4656, -- Rupert Boch +4667, -- Maximillian Crowe +4681, -- Alamar Grimm +4682); -- Gimrizz Shadowcog +-- Demon trainers already use condition 67 +-- Mage (including Portal trainers) +UPDATE `gossip_menu_option` SET `condition_id` = 90 WHERE `option_id` = 5 AND menu_id IN ( +63, -- Cain Firesong +64, -- Un'Thuwa, Mai'ah +4484, -- Elsharin +4485, -- Jennea Cannon +4486, -- Maginor Dumas +4517, -- Uthel'nay +4518, -- Enyo +4519, -- Deino +4520, -- Pephredo +4534, -- Archmage Shymm +4535, -- Thurston Xane +4536, -- Ursyn Ghull +4537, -- Anastasia Hartwell +4538, -- Pierce Shackleton +4539, -- Kaelystia Hatebringer +4552, -- Bink, Nittlebur Sparkfizzle +4553, -- Dink +4554, -- Juli Stormkettle +4654, -- Isabella +4660, -- Khelden Bremen +4661, -- Zaldimar Wefhellt +4685, -- Magis Sparkmantle +4686, -- Marryk Nurribit +4821, -- Elissa Dumas +4822, -- Larimaine Purdue +4823, -- Milstaff Stormeye +4825, -- Birgitte Cranston +4826, -- Thuul +4827); -- Lexington Mortaim +-- Shaman +UPDATE `gossip_menu_option` SET `condition_id` = 92 WHERE `option_id` = 5 AND menu_id IN ( +4103, -- Meela Dawnstrider, Narm Skychaser +4104, -- Swart +4515, -- Sian'tsu +4516, -- Kardris Dreamseeker +4528, -- Siln Skychaser +4529, -- Beram Skychaser +4530, -- Tigor Skychaser +4652, -- Haromm, Shikrik +5123); -- Sagorne Creststrider +-- Priest +UPDATE `gossip_menu_option` SET `condition_id` = 94 WHERE `option_id` = 5 AND menu_id IN ( +3642, -- High Priest Rohan +3643, -- Nara Meideros +3644, -- Tai'jin, Ken'jai +3645, -- Dark Cleric Duesten, Dark Cleric Beryl +4466, -- High Priestess Laurena +4467, -- Brother Joshua +4468, -- Brother Benjamin +4521, -- Ur'kyo +4522, -- Zayus +4523, -- X'yera +4531, -- Malakai Cross +4532, -- Father Cobb +4533, -- Miles Welsh +4543, -- Father Lazarus +4544, -- Aelthalyste +4545, -- Father Lankester +4558, -- Braenna Flintcrag +4560, -- Toldren Deepiron +4572, -- Lariia +4573, -- Jandria +4665, -- Priestess Anetta +4666, -- Priestess Josetta +4679, -- Branstock Khalder +4680, -- Maxan Anvol, Theodrus Frostbeard +4691); -- Shanda, Laurna Morninglight, Astarii Starseeker, Priestess Alathea +-- Rogue +UPDATE `gossip_menu_option` SET `condition_id` = 100 WHERE `option_id` = 5 AND menu_id IN ( +85, -- David Trias, Marion Call +141, -- Rwag, Kaplak +381, -- Keryn Sylvius +410, -- Hogral Bakkan +411, -- Hulfdan Blackbeard +436, -- Jannok Breezesong +521, -- Shenthul +3984, -- Fahrad +4502, -- Osborne the Night Man +4512, -- Ormok +4513, -- Gest +4540, -- Miles Dexter +4541, -- Gregory Charles +4542, -- Carolyn Ward +4561, -- Fenthwick +4562, -- Ormyr Flinteye +4575, -- Anishar +4576, -- Syurna +4577, -- Erion Shadewhisper +4658, -- Ian Strom +4659, -- Jorik Kerridan +4676, -- Solm Hargrin +4690, -- Frahun Shadewhisper +4783, -- (Entry for all Pet trainers) +5061); -- Lord Tony Romano +-- Hunter (including Pet trainers) +UPDATE `gossip_menu_option` SET `condition_id` = 96 WHERE `option_id` = 5 AND menu_id IN ( +4007, -- Grif Wildheart +4008, -- Jocaste +4009, -- Dazalar +4010, -- Ormak Grimshot +4011, -- Kary Thunderhorn +4012, -- Yaw Sharpmane +4013, -- Sian'dur +4017, -- Thotar +4023, -- Holt Thunderhorn +4092, -- Danlaar Nightstride, Alenndaar Lapidaar +4101, -- Ogromm +4472, -- Thorfin Stoneshield +4473, -- Ulfir Ironbeard +4474, -- Einris Brightspear +4506, -- Xor'juul +4524, -- Urek Thunderhorn +4549, -- Daera Brightspear +4550, -- Olmin Burningbeard +4551, -- Regnus Thundergranite +4621, -- Jeen'ra Nightrunner +4647, -- Lanka Farshot +4648, -- Jen'shan +4657, -- Kragg +4674, -- Dargh Trueaim +4675, -- Thorgas Grimson +4693, -- Dorion +4694, -- Kaerbrus +4695); -- Ayanna Everstride +-- Paladin +UPDATE `gossip_menu_option` SET `condition_id` = 106 WHERE `option_id` = 5 AND menu_id IN ( +2304, -- Brandur Ironhammer +4469, -- Arthur the Faithful +4470, -- Katherine the Pure +4471, -- Lord Grayson Shadowbreaker +4556, -- Beldruk Doombrow +4557, -- Valgar Highforge +4662, -- Brother Karman +4663, -- Brother Sammuel +4664, -- Brother Wilhelm +4677, -- Azar Stronghammer +4678); -- Bromos Grummner +-- Warrior +UPDATE `gossip_menu_option` SET `condition_id` = 98 WHERE `option_id` = 5 AND menu_id IN ( +523, -- Malosh, Frang, Tarshaw Jaggedscar +655, -- Krang Stonehoof +656, -- Austil de Mon +4091, -- Captain Evencane +4475, -- Ander Germaine +4481, -- Ilsa Corbin +4482, -- Wu Shen +4509, -- Grezz Ragefist +4510, -- Zel'mak +4511, -- Sorek +4525, -- Sark Ragetotem +4526, -- Torm Ragetotem +4527, -- Ker Ragetotem +4546, -- Angela Curthas +4547, -- Baltus Fowler +4548, -- Christoph Walker +4568, -- Bilban Tosslespanner +4569, -- Kelstrum Stonebreaker +4570, -- Kelv Sternhammer +4578, -- Sildanair +4579, -- Darnath Bladesinger +4581, -- Arias'ta Bladesinger +4645, -- Harutt Thunderhorn +4649, -- Lyria Du Lac +4650, -- Llane Beshere +4653, -- Dannal Stern +4683, -- Granis Swiftaxe +4684, -- Thran Khorman +4696, -- Alyissia +4697); -- Kyra Windblade + +-- Remove the now deprecated columns +ALTER TABLE `creature_template` + DROP COLUMN `trainer_spell`, + DROP COLUMN `trainer_race`, + DROP COLUMN `trainer_class`, + DROP COLUMN `trainer_type`; + + +-- End of migration. +END IF; +END?? +delimiter ; +CALL add_migration(); +DROP PROCEDURE IF EXISTS add_migration; \ No newline at end of file diff --git a/src/game/Commands/CharacterCommands.cpp b/src/game/Commands/CharacterCommands.cpp index f09d79751e0..48bb0072893 100644 --- a/src/game/Commands/CharacterCommands.cpp +++ b/src/game/Commands/CharacterCommands.cpp @@ -34,6 +34,7 @@ #include "PlayerDump.h" #include "CharacterDatabaseCache.h" #include "Config/Config.h" +#include "Conditions.h" #include @@ -2817,22 +2818,25 @@ bool ChatHandler::HandleLearnAllTrainerCommand(char* args) if (!(cInfo->npc_flags & UNIT_NPC_FLAG_TRAINER)) continue; - switch (cInfo->trainer_type) + uint32 gossipMenuId = cInfo->gossip_menu_id; + if (!gossipMenuId) + continue; + + GossipMenuItemsMapBounds bounds = sObjectMgr.GetGossipMenuItemsMapBounds(gossipMenuId); + bool isTrainer = false; + for (auto itr = bounds.first; itr != bounds.second; ++itr) { - case TRAINER_TYPE_CLASS: - { - if (cInfo->trainer_class != pPlayer->GetClass()) - continue; - break; - } - case TRAINER_TYPE_PETS: - { - if (pPlayer->GetClass() != CLASS_HUNTER) - continue; - break; - } + GossipMenuItems const& gMenuItem = itr->second; + if (gMenuItem.option_id == GOSSIP_OPTION_TRAINER) + if (uint32 conditionId = gMenuItem.condition_id) + if (isTrainer = IsConditionSatisfied(conditionId, pPlayer, pPlayer->GetMap(), pPlayer, CONDITION_FROM_GOSSIP_OPTION)) + break; + } + if (!isTrainer) + continue; + if (TrainerSpellData const* cSpells = sObjectMgr.GetNpcTrainerSpells(itr.first)) HandleLearnTrainerHelper(pPlayer, cSpells); diff --git a/src/game/Handlers/NPCHandler.cpp b/src/game/Handlers/NPCHandler.cpp index 835be2d3f32..0428c7628c1 100644 --- a/src/game/Handlers/NPCHandler.cpp +++ b/src/game/Handlers/NPCHandler.cpp @@ -156,7 +156,7 @@ void WorldSession::SendTrainerList(ObjectGuid guid) } // trainer list loaded at check; - if (!unit->IsTrainerOf(_player, true)) + if (!unit->IsTrainerOf(_player)) return; CreatureInfo const* ci = unit->GetCreatureInfo(); @@ -277,7 +277,7 @@ void WorldSession::HandleTrainerBuySpellOpcode(WorldPacket& recv_data) Creature* unit = GetPlayer()->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_TRAINER); - if (!unit || !unit->IsTrainerOf(_player, true) || !unit->IsWithinLOSInMap(_player)) + if (!unit || !unit->IsTrainerOf(_player) || !unit->IsWithinLOSInMap(_player)) { SendTrainingFailure(guid, spellId, TRAIN_FAIL_UNAVAILABLE); sLog.Out(LOG_BASIC, LOG_LVL_DEBUG, "WORLD: HandleTrainerBuySpellOpcode - %s not found or you can't interact with him.", guid.GetString().c_str()); diff --git a/src/game/ObjectMgr.cpp b/src/game/ObjectMgr.cpp index 86c17973c6b..9a3d5c1a3a6 100644 --- a/src/game/ObjectMgr.cpp +++ b/src/game/ObjectMgr.cpp @@ -1187,8 +1187,8 @@ struct SQLCreatureLoader : public SQLStorageLoaderBase result(WorldDatabase.PQuery("SELECT `entry`, `name`, `subname`, `level_min`, `level_max`, `faction`, `npc_flags`, `gossip_menu_id`, `display_id1`, `display_id2`, `display_id3`, `display_id4`, `display_scale1`, `display_scale2`, `display_scale3`, `display_scale4`, `display_probability1`, `display_probability2`, `display_probability3`, `display_probability4`, `display_total_probability`, `mount_display_id`, `speed_walk`, `speed_run`, `detection_range`, `call_for_help_range`, `leash_range`, `type`, `pet_family`, `rank`, `unit_class`, `xp_multiplier`, `health_multiplier`, `mana_multiplier`, `armor_multiplier`, `damage_multiplier`, `damage_variance`, `damage_school`, `base_attack_time`, `ranged_attack_time`, `holy_res`, `fire_res`, `nature_res`, `frost_res`, `shadow_res`, `arcane_res`, `trainer_type`, `trainer_spell`, `trainer_class`, `trainer_race`, `loot_id`, `pickpocket_loot_id`, `skinning_loot_id`, `gold_min`, `gold_max`, `spell_id1`, `spell_id2`, `spell_id3`, `spell_id4`, `spell_list_id`, `pet_spell_list_id`, `spawn_spell_id`, `auras`, `ai_name`, `movement_type`, `inhabit_type`, `civilian`, `racial_leader`, `equipment_id`, `trainer_id`, `vendor_id`, `mechanic_immune_mask`, `school_immune_mask`, `immunity_flags`, `static_flags1`, `static_flags2`, `flags_extra`, `script_name` FROM `creature_template` t1 WHERE `patch`=(SELECT max(`patch`) FROM `creature_template` t2 WHERE t1.`entry`=t2.`entry` && `patch` <= %u)", sWorld.GetWowPatch())); + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 + std::unique_ptr result(WorldDatabase.PQuery("SELECT `entry`, `name`, `subname`, `level_min`, `level_max`, `faction`, `npc_flags`, `gossip_menu_id`, `display_id1`, `display_id2`, `display_id3`, `display_id4`, `display_scale1`, `display_scale2`, `display_scale3`, `display_scale4`, `display_probability1`, `display_probability2`, `display_probability3`, `display_probability4`, `display_total_probability`, `mount_display_id`, `speed_walk`, `speed_run`, `detection_range`, `call_for_help_range`, `leash_range`, `type`, `pet_family`, `rank`, `unit_class`, `xp_multiplier`, `health_multiplier`, `mana_multiplier`, `armor_multiplier`, `damage_multiplier`, `damage_variance`, `damage_school`, `base_attack_time`, `ranged_attack_time`, `holy_res`, `fire_res`, `nature_res`, `frost_res`, `shadow_res`, `arcane_res`, `loot_id`, `pickpocket_loot_id`, `skinning_loot_id`, `gold_min`, `gold_max`, `spell_id1`, `spell_id2`, `spell_id3`, `spell_id4`, `spell_list_id`, `pet_spell_list_id`, `spawn_spell_id`, `auras`, `ai_name`, `movement_type`, `inhabit_type`, `civilian`, `racial_leader`, `equipment_id`, `trainer_id`, `vendor_id`, `mechanic_immune_mask`, `school_immune_mask`, `immunity_flags`, `static_flags1`, `static_flags2`, `flags_extra`, `script_name` FROM `creature_template` t1 WHERE `patch`=(SELECT max(`patch`) FROM `creature_template` t2 WHERE t1.`entry`=t2.`entry` && `patch` <= %u)", sWorld.GetWowPatch())); if (!result) return; @@ -1206,8 +1206,8 @@ void ObjectMgr::LoadCreatureTemplates() void ObjectMgr::LoadCreatureTemplate(uint32 entry) { - // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 - std::unique_ptr result(WorldDatabase.PQuery("SELECT `entry`, `name`, `subname`, `level_min`, `level_max`, `faction`, `npc_flags`, `gossip_menu_id`, `display_id1`, `display_id2`, `display_id3`, `display_id4`, `display_scale1`, `display_scale2`, `display_scale3`, `display_scale4`, `display_probability1`, `display_probability2`, `display_probability3`, `display_probability4`, `display_total_probability`, `mount_display_id`, `speed_walk`, `speed_run`, `detection_range`, `call_for_help_range`, `leash_range`, `type`, `pet_family`, `rank`, `unit_class`, `xp_multiplier`, `health_multiplier`, `mana_multiplier`, `armor_multiplier`, `damage_multiplier`, `damage_variance`, `damage_school`, `base_attack_time`, `ranged_attack_time`, `holy_res`, `fire_res`, `nature_res`, `frost_res`, `shadow_res`, `arcane_res`, `trainer_type`, `trainer_spell`, `trainer_class`, `trainer_race`, `loot_id`, `pickpocket_loot_id`, `skinning_loot_id`, `gold_min`, `gold_max`, `spell_id1`, `spell_id2`, `spell_id3`, `spell_id4`, `spell_list_id`, `pet_spell_list_id`, `spawn_spell_id`, `auras`, `ai_name`, `movement_type`, `inhabit_type`, `civilian`, `racial_leader`, `equipment_id`, `trainer_id`, `vendor_id`, `mechanic_immune_mask`, `school_immune_mask`, `immunity_flags`, `static_flags1`, `static_flags2`, `flags_extra`, `script_name` FROM `creature_template` t1 WHERE `entry`=%u && `patch`=(SELECT max(`patch`) FROM `creature_template` t2 WHERE t1.`entry`=t2.`entry` && `patch` <= %u)", entry, sWorld.GetWowPatch())); + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 + std::unique_ptr result(WorldDatabase.PQuery("SELECT `entry`, `name`, `subname`, `level_min`, `level_max`, `faction`, `npc_flags`, `gossip_menu_id`, `display_id1`, `display_id2`, `display_id3`, `display_id4`, `display_scale1`, `display_scale2`, `display_scale3`, `display_scale4`, `display_probability1`, `display_probability2`, `display_probability3`, `display_probability4`, `display_total_probability`, `mount_display_id`, `speed_walk`, `speed_run`, `detection_range`, `call_for_help_range`, `leash_range`, `type`, `pet_family`, `rank`, `unit_class`, `xp_multiplier`, `health_multiplier`, `mana_multiplier`, `armor_multiplier`, `damage_multiplier`, `damage_variance`, `damage_school`, `base_attack_time`, `ranged_attack_time`, `holy_res`, `fire_res`, `nature_res`, `frost_res`, `shadow_res`, `arcane_res`, `loot_id`, `pickpocket_loot_id`, `skinning_loot_id`, `gold_min`, `gold_max`, `spell_id1`, `spell_id2`, `spell_id3`, `spell_id4`, `spell_list_id`, `pet_spell_list_id`, `spawn_spell_id`, `auras`, `ai_name`, `movement_type`, `inhabit_type`, `civilian`, `racial_leader`, `equipment_id`, `trainer_id`, `vendor_id`, `mechanic_immune_mask`, `school_immune_mask`, `immunity_flags`, `static_flags1`, `static_flags2`, `flags_extra`, `script_name` FROM `creature_template` t1 WHERE `entry`=%u && `patch`=(SELECT max(`patch`) FROM `creature_template` t2 WHERE t1.`entry`=t2.`entry` && `patch` <= %u)", entry, sWorld.GetWowPatch())); if (!result) return; @@ -1276,39 +1276,35 @@ void ObjectMgr::LoadCreatureInfo(Field* fields) pInfo->frost_res = fields[43].GetInt32(); pInfo->shadow_res = fields[44].GetInt32(); pInfo->arcane_res = fields[45].GetInt32(); - pInfo->trainer_type = fields[46].GetUInt32(); - pInfo->trainer_spell = fields[47].GetUInt32(); - pInfo->trainer_class = fields[48].GetUInt32(); - pInfo->trainer_race = fields[49].GetUInt32(); - pInfo->loot_id = fields[50].GetUInt32(); - pInfo->pickpocket_loot_id = fields[51].GetUInt32(); - pInfo->skinning_loot_id = fields[52].GetUInt32(); - pInfo->gold_min = fields[53].GetUInt32(); - pInfo->gold_max = fields[54].GetUInt32(); - pInfo->spells[0] = fields[55].GetUInt32(); - pInfo->spells[1] = fields[56].GetUInt32(); - pInfo->spells[2] = fields[57].GetUInt32(); - pInfo->spells[3] = fields[58].GetUInt32(); - pInfo->spell_list_id = fields[59].GetUInt32(); - pInfo->pet_spell_list_id = fields[60].GetUInt32(); - pInfo->spawn_spell_id = fields[61].GetUInt32(); + pInfo->loot_id = fields[46].GetUInt32(); + pInfo->pickpocket_loot_id = fields[47].GetUInt32(); + pInfo->skinning_loot_id = fields[48].GetUInt32(); + pInfo->gold_min = fields[49].GetUInt32(); + pInfo->gold_max = fields[50].GetUInt32(); + pInfo->spells[0] = fields[51].GetUInt32(); + pInfo->spells[1] = fields[52].GetUInt32(); + pInfo->spells[2] = fields[53].GetUInt32(); + pInfo->spells[3] = fields[54].GetUInt32(); + pInfo->spell_list_id = fields[55].GetUInt32(); + pInfo->pet_spell_list_id = fields[56].GetUInt32(); + pInfo->spawn_spell_id = fields[57].GetUInt32(); delete[] pInfo->auras; - pInfo->auras = (uint32*)(fields[62].GetString() ? mangos_strdup(fields[62].GetString()) : nullptr); - pInfo->ai_name = fields[63].GetCppString(); - pInfo->movement_type = fields[64].GetUInt32(); - pInfo->inhabit_type = fields[65].GetUInt32(); - pInfo->civilian = fields[66].GetBool(); - pInfo->racial_leader = fields[67].GetBool(); - pInfo->equipment_id = fields[68].GetUInt32(); - pInfo->trainer_id = fields[69].GetUInt32(); - pInfo->vendor_id = fields[70].GetUInt32(); - pInfo->mechanic_immune_mask = fields[71].GetUInt32(); - pInfo->school_immune_mask = fields[72].GetUInt32(); - pInfo->immunity_flags = fields[73].GetUInt32(); - pInfo->static_flags1 = fields[74].GetUInt32(); - pInfo->static_flags2 = fields[75].GetUInt32(); - pInfo->flags_extra = fields[76].GetUInt32(); - pInfo->script_id = sScriptMgr.GetScriptId(fields[77].GetString()); + pInfo->auras = (uint32*)(fields[58].GetString() ? mangos_strdup(fields[58].GetString()) : nullptr); + pInfo->ai_name = fields[59].GetCppString(); + pInfo->movement_type = fields[60].GetUInt32(); + pInfo->inhabit_type = fields[61].GetUInt32(); + pInfo->civilian = fields[62].GetBool(); + pInfo->racial_leader = fields[63].GetBool(); + pInfo->equipment_id = fields[64].GetUInt32(); + pInfo->trainer_id = fields[65].GetUInt32(); + pInfo->vendor_id = fields[66].GetUInt32(); + pInfo->mechanic_immune_mask = fields[67].GetUInt32(); + pInfo->school_immune_mask = fields[68].GetUInt32(); + pInfo->immunity_flags = fields[69].GetUInt32(); + pInfo->static_flags1 = fields[70].GetUInt32(); + pInfo->static_flags2 = fields[71].GetUInt32(); + pInfo->flags_extra = fields[72].GetUInt32(); + pInfo->script_id = sScriptMgr.GetScriptId(fields[73].GetString()); CheckCreatureTemplate(pInfo.get()); } @@ -1488,9 +1484,6 @@ void ObjectMgr::CheckCreatureTemplate(CreatureInfo* cInfo) cInfo->ranged_attack_time = BASE_ATTACK_TIME; } - if ((cInfo->npc_flags & UNIT_NPC_FLAG_TRAINER) && cInfo->trainer_type >= MAX_TRAINER_TYPE) - sLog.Out(LOG_DBERROR, LOG_LVL_MINIMAL, "Creature (Entry: %u) has wrong trainer type %u", cInfo->entry, cInfo->trainer_type); - if (cInfo->type && !sCreatureTypeStore.LookupEntry(cInfo->type)) { sLog.Out(LOG_DBERROR, LOG_LVL_MINIMAL, "Creature (Entry: %u) has invalid creature type (%u) in `type`", cInfo->entry, cInfo->type); diff --git a/src/game/Objects/Creature.cpp b/src/game/Objects/Creature.cpp index ea12b9b15e2..543a8d5b8c6 100644 --- a/src/game/Objects/Creature.cpp +++ b/src/game/Objects/Creature.cpp @@ -1285,7 +1285,7 @@ bool Creature::Create(uint32 guidlow, CreatureCreatePos& cPos, CreatureInfo cons return true; } -bool Creature::IsTrainerOf(Player* pPlayer, bool msg) const +bool Creature::IsTrainerOf(Player* pPlayer) const { if (!IsTrainer()) return false; @@ -1293,7 +1293,7 @@ bool Creature::IsTrainerOf(Player* pPlayer, bool msg) const TrainerSpellData const* cSpells = GetTrainerSpells(); TrainerSpellData const* tSpells = GetTrainerTemplateSpells(); - // for not pet trainer expected not empty trainer list always + // all trainers should have a non-empty trainer list if ((!cSpells || cSpells->spellList.empty()) && (!tSpells || tSpells->spellList.empty())) { sLog.Out(LOG_DBERROR, LOG_LVL_MINIMAL, "Creature %u (Entry: %u) have UNIT_NPC_FLAG_TRAINER but have empty trainer spell list.", @@ -1301,121 +1301,27 @@ bool Creature::IsTrainerOf(Player* pPlayer, bool msg) const return false; } - switch (GetCreatureInfo()->trainer_type) + uint32 gossipMenuId = GetCreatureInfo()->gossip_menu_id; + if (!gossipMenuId) { - case TRAINER_TYPE_CLASS: - if (pPlayer->GetClass() != GetCreatureInfo()->trainer_class) - { - if (msg) - { - pPlayer->PlayerTalkClass->ClearMenus(); - switch (GetCreatureInfo()->trainer_class) - { - case CLASS_DRUID: - pPlayer->PlayerTalkClass->SendGossipMenu(4913, GetObjectGuid()); - break; - case CLASS_HUNTER: - pPlayer->PlayerTalkClass->SendGossipMenu(10090, GetObjectGuid()); - break; - case CLASS_MAGE: - pPlayer->PlayerTalkClass->SendGossipMenu(328, GetObjectGuid()); - break; - case CLASS_PALADIN: - pPlayer->PlayerTalkClass->SendGossipMenu(1635, GetObjectGuid()); - break; - case CLASS_PRIEST: - pPlayer->PlayerTalkClass->SendGossipMenu(4436, GetObjectGuid()); - break; - case CLASS_ROGUE: - pPlayer->PlayerTalkClass->SendGossipMenu(4797, GetObjectGuid()); - break; - case CLASS_SHAMAN: - pPlayer->PlayerTalkClass->SendGossipMenu(5003, GetObjectGuid()); - break; - case CLASS_WARLOCK: - pPlayer->PlayerTalkClass->SendGossipMenu(5836, GetObjectGuid()); - break; - case CLASS_WARRIOR: - pPlayer->PlayerTalkClass->SendGossipMenu(4985, GetObjectGuid()); - break; - } - } - return false; - } - break; - case TRAINER_TYPE_PETS: - if (pPlayer->GetClass() != CLASS_HUNTER) - { - if (msg) - { - pPlayer->PlayerTalkClass->ClearMenus(); - pPlayer->PlayerTalkClass->SendGossipMenu(3620, GetObjectGuid()); - } - return false; - } - break; - case TRAINER_TYPE_MOUNTS: - if (GetCreatureInfo()->trainer_race && pPlayer->GetRace() != GetCreatureInfo()->trainer_race) - { - // Allowed to train if exalted - if (FactionTemplateEntry const* faction_template = GetFactionTemplateEntry()) - { - if (pPlayer->GetReputationRank(faction_template->faction) == REP_EXALTED) - return true; - } - - if (msg) - { - pPlayer->PlayerTalkClass->ClearMenus(); - switch (GetCreatureInfo()->trainer_class) - { - case RACE_DWARF: - pPlayer->PlayerTalkClass->SendGossipMenu(5865, GetObjectGuid()); - break; - case RACE_GNOME: - pPlayer->PlayerTalkClass->SendGossipMenu(4881, GetObjectGuid()); - break; - case RACE_HUMAN: - pPlayer->PlayerTalkClass->SendGossipMenu(5861, GetObjectGuid()); - break; - case RACE_NIGHTELF: - pPlayer->PlayerTalkClass->SendGossipMenu(5862, GetObjectGuid()); - break; - case RACE_ORC: - pPlayer->PlayerTalkClass->SendGossipMenu(5863, GetObjectGuid()); - break; - case RACE_TAUREN: - pPlayer->PlayerTalkClass->SendGossipMenu(5864, GetObjectGuid()); - break; - case RACE_TROLL: - pPlayer->PlayerTalkClass->SendGossipMenu(5816, GetObjectGuid()); - break; - case RACE_UNDEAD: - pPlayer->PlayerTalkClass->SendGossipMenu(624, GetObjectGuid()); - break; - } - } - return false; - } - break; - case TRAINER_TYPE_TRADESKILLS: - if (GetCreatureInfo()->trainer_spell && !pPlayer->HasSpell(GetCreatureInfo()->trainer_spell)) - { - if (msg) - { - pPlayer->PlayerTalkClass->ClearMenus(); - pPlayer->PlayerTalkClass->SendGossipMenu(11031, GetObjectGuid()); - } - return false; - } - break; - default: - return false; // checked and error output at creature_template loading + sLog.Out(LOG_DBERROR, LOG_LVL_MINIMAL, "Creature %u (Entry: %u) has npc_flag UNIT_NPC_FLAG_TRAINER but does not have a gossip_menu_id assigned to it."); + return false; } - return true; + + GossipMenuItemsMapBounds bounds = sObjectMgr.GetGossipMenuItemsMapBounds(gossipMenuId); + for (auto itr = bounds.first; itr != bounds.second; ++itr) + { + GossipMenuItems const& gMenuItem = itr->second; + if (gMenuItem.option_id == GOSSIP_OPTION_TRAINER) + { + uint32 conditionId = gMenuItem.condition_id; + return IsConditionSatisfied(conditionId, pPlayer, pPlayer->GetMap(), this, CONDITION_FROM_GOSSIP_OPTION); + } + } + return false; } -bool Creature::CanInteractWithBattleMaster(Player* pPlayer, bool msg) const +bool Creature::CanInteractWithBattleMaster(Player* pPlayer) const { if (!IsBattleMaster()) return false; @@ -1424,36 +1330,7 @@ bool Creature::CanInteractWithBattleMaster(Player* pPlayer, bool msg) const if (bgTypeId == BATTLEGROUND_TYPE_NONE) return false; - if (!msg) - return pPlayer->GetBGAccessByLevel(bgTypeId); - - if (!pPlayer->GetBGAccessByLevel(bgTypeId)) - { - pPlayer->PlayerTalkClass->ClearMenus(); - switch (bgTypeId) - { - case BATTLEGROUND_AV: - pPlayer->PlayerTalkClass->SendGossipMenu(7616, GetObjectGuid()); - break; - case BATTLEGROUND_WS: - pPlayer->PlayerTalkClass->SendGossipMenu(7599, GetObjectGuid()); - break; - case BATTLEGROUND_AB: - pPlayer->PlayerTalkClass->SendGossipMenu(7642, GetObjectGuid()); - break; - default: - break; - } - return false; - } - return true; -} - -bool Creature::CanTrainAndResetTalentsOf(Player const* pPlayer) const -{ - return pPlayer->GetLevel() >= 10 - && GetCreatureInfo()->trainer_type == TRAINER_TYPE_CLASS - && pPlayer->GetClass() == GetCreatureInfo()->trainer_class; + return pPlayer->GetBGAccessByLevel(bgTypeId); } /** diff --git a/src/game/Objects/Creature.h b/src/game/Objects/Creature.h index e2dd648ed7e..0d6cbe791c7 100644 --- a/src/game/Objects/Creature.h +++ b/src/game/Objects/Creature.h @@ -172,9 +172,8 @@ class Creature : public Unit bool HasCreatureReactState(ReactStates state) const { return (m_reactState == state); } void InitializeReactState(); - bool IsTrainerOf(Player* player, bool msg) const; - bool CanInteractWithBattleMaster(Player* player, bool msg) const; - bool CanTrainAndResetTalentsOf(Player const* pPlayer) const; + bool IsTrainerOf(Player* player) const; + bool CanInteractWithBattleMaster(Player* player) const; bool IsOutOfThreatArea(Unit const* pVictim) const; void FillGuidsListFromThreatList(std::vector& guids, uint32 maxamount = 0); diff --git a/src/game/Objects/CreatureDefines.h b/src/game/Objects/CreatureDefines.h index d889732d88f..171c900a211 100644 --- a/src/game/Objects/CreatureDefines.h +++ b/src/game/Objects/CreatureDefines.h @@ -27,16 +27,6 @@ #include -enum TrainerType // this is important type for npcs! -{ - TRAINER_TYPE_CLASS = 0, - TRAINER_TYPE_MOUNTS = 1, // on blizz it's 2 - TRAINER_TYPE_TRADESKILLS = 2, - TRAINER_TYPE_PETS = 3 -}; - -#define MAX_TRAINER_TYPE 4 - // CreatureType.dbc enum CreatureType { @@ -270,10 +260,6 @@ struct CreatureInfo int32 frost_res = 0; int32 shadow_res = 0; int32 arcane_res = 0; - uint32 trainer_type = 0; - uint32 trainer_spell = 0; - uint32 trainer_class = 0; - uint32 trainer_race = 0; uint32 loot_id = 0; uint32 pickpocket_loot_id = 0; uint32 skinning_loot_id = 0; diff --git a/src/game/Objects/Object.cpp b/src/game/Objects/Object.cpp index 554d9039534..c62ba8c542e 100644 --- a/src/game/Objects/Object.cpp +++ b/src/game/Objects/Object.cpp @@ -661,7 +661,7 @@ void Object::BuildValuesUpdate(uint8 updatetype, ByteBuffer* data, UpdateMask* u { if (appendValue & UNIT_NPC_FLAG_TRAINER) { - if (!((Creature*)this)->IsTrainerOf(target, false)) + if (!((Creature*)this)->IsTrainerOf(target)) appendValue &= ~UNIT_NPC_FLAG_TRAINER; } diff --git a/src/game/Objects/Player.cpp b/src/game/Objects/Player.cpp index 394c6e03e0f..0a268d1de88 100644 --- a/src/game/Objects/Player.cpp +++ b/src/game/Objects/Player.cpp @@ -12065,15 +12065,15 @@ void Player::PrepareGossipMenu(WorldObject* pSource, uint32 menuId) break; } case GOSSIP_OPTION_TRAINER: - if (!pCreature->IsTrainerOf(this, false)) + if (!pCreature->IsTrainerOf(this)) hasMenuItem = false; break; case GOSSIP_OPTION_UNLEARNTALENTS: - if (!pCreature->CanTrainAndResetTalentsOf(this)) + if (!pCreature->IsTrainerOf(this) || this->GetLevel() < 10) hasMenuItem = false; break; case GOSSIP_OPTION_UNLEARNPETSKILLS: - if (!GetPet() || GetPet()->getPetType() != HUNTER_PET || GetPet()->m_petSpells.size() <= 1 || pCreature->GetCreatureInfo()->trainer_type != TRAINER_TYPE_PETS || pCreature->GetCreatureInfo()->trainer_class != CLASS_HUNTER) + if (!GetPet() || GetPet()->getPetType() != HUNTER_PET || GetPet()->m_petSpells.size() <= 1 || !pCreature->IsTrainerOf(this)) hasMenuItem = false; break; case GOSSIP_OPTION_TAXIVENDOR: @@ -12081,7 +12081,7 @@ void Player::PrepareGossipMenu(WorldObject* pSource, uint32 menuId) pMenu->GetGossipMenu().SetDiscoveredNode(); break; case GOSSIP_OPTION_BATTLEFIELD: - if (!pCreature->CanInteractWithBattleMaster(this, false)) + if (!pCreature->CanInteractWithBattleMaster(this)) hasMenuItem = false; break; case GOSSIP_OPTION_STABLEPET: From d6b992eb0e62d1da2aedd3d2fcb0f09195ece81c Mon Sep 17 00:00:00 2001 From: FlagFlayer <76889547+FlagFlayer@users.noreply.github.com> Date: Sat, 4 Oct 2025 02:14:12 +0300 Subject: [PATCH 2/5] Update CharacterCommands.cpp trainer is valid if no condition required to train --- src/game/Commands/CharacterCommands.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/game/Commands/CharacterCommands.cpp b/src/game/Commands/CharacterCommands.cpp index 48bb0072893..6afc26af7a3 100644 --- a/src/game/Commands/CharacterCommands.cpp +++ b/src/game/Commands/CharacterCommands.cpp @@ -2823,18 +2823,24 @@ bool ChatHandler::HandleLearnAllTrainerCommand(char* args) continue; GossipMenuItemsMapBounds bounds = sObjectMgr.GetGossipMenuItemsMapBounds(gossipMenuId); - bool isTrainer = false; + bool validTrainer = false; for (auto itr = bounds.first; itr != bounds.second; ++itr) { GossipMenuItems const& gMenuItem = itr->second; if (gMenuItem.option_id == GOSSIP_OPTION_TRAINER) if (uint32 conditionId = gMenuItem.condition_id) - if (isTrainer = IsConditionSatisfied(conditionId, pPlayer, pPlayer->GetMap(), pPlayer, CONDITION_FROM_GOSSIP_OPTION)) - break; - + { + validTrainer = IsConditionSatisfied(conditionId, pPlayer, pPlayer->GetMap(), pPlayer, CONDITION_FROM_GOSSIP_OPTION); + break; + } + else + { + validTrainer = true; + break; + } } - if (!isTrainer) + if (!validTrainer) continue; if (TrainerSpellData const* cSpells = sObjectMgr.GetNpcTrainerSpells(itr.first)) From 3914651cb88373887f96418ec1e024ff70c744ab Mon Sep 17 00:00:00 2001 From: FlagFlayer <76889547+FlagFlayer@users.noreply.github.com> Date: Sat, 4 Oct 2025 02:56:32 +0300 Subject: [PATCH 3/5] Update CharacterCommands.cpp skip gossip_menu 0 entries (e.g. world trainers) --- src/game/Commands/CharacterCommands.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/Commands/CharacterCommands.cpp b/src/game/Commands/CharacterCommands.cpp index 6afc26af7a3..ec54a2c2679 100644 --- a/src/game/Commands/CharacterCommands.cpp +++ b/src/game/Commands/CharacterCommands.cpp @@ -2827,7 +2827,7 @@ bool ChatHandler::HandleLearnAllTrainerCommand(char* args) for (auto itr = bounds.first; itr != bounds.second; ++itr) { GossipMenuItems const& gMenuItem = itr->second; - if (gMenuItem.option_id == GOSSIP_OPTION_TRAINER) + if (gMenuItem.menu_id && gMenuItem.option_id == GOSSIP_OPTION_TRAINER) if (uint32 conditionId = gMenuItem.condition_id) { validTrainer = IsConditionSatisfied(conditionId, pPlayer, pPlayer->GetMap(), pPlayer, CONDITION_FROM_GOSSIP_OPTION); From 64adeb67c7070e00e401fce5cc7d0ef4a885e713 Mon Sep 17 00:00:00 2001 From: FlagFlayer <76889547+FlagFlayer@users.noreply.github.com> Date: Tue, 7 Oct 2025 21:14:06 +0300 Subject: [PATCH 4/5] Update Creature.cpp IsConditionSatisfied returns false on no condition. Added proper error logging. --- src/game/Objects/Creature.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/game/Objects/Creature.cpp b/src/game/Objects/Creature.cpp index 543a8d5b8c6..be821c9a8fd 100644 --- a/src/game/Objects/Creature.cpp +++ b/src/game/Objects/Creature.cpp @@ -1304,20 +1304,36 @@ bool Creature::IsTrainerOf(Player* pPlayer) const uint32 gossipMenuId = GetCreatureInfo()->gossip_menu_id; if (!gossipMenuId) { - sLog.Out(LOG_DBERROR, LOG_LVL_MINIMAL, "Creature %u (Entry: %u) has npc_flag UNIT_NPC_FLAG_TRAINER but does not have a gossip_menu_id assigned to it."); + sLog.Out(LOG_DBERROR, LOG_LVL_MINIMAL, "Creature %u (Entry: %u) has npc_flag UNIT_NPC_FLAG_TRAINER but does not have a gossip_menu_id assigned to it.", + GetGUIDLow(), GetEntry()); return false; } - + + bool found = false; + GossipMenuItemsMapBounds bounds = sObjectMgr.GetGossipMenuItemsMapBounds(gossipMenuId); for (auto itr = bounds.first; itr != bounds.second; ++itr) { GossipMenuItems const& gMenuItem = itr->second; if (gMenuItem.option_id == GOSSIP_OPTION_TRAINER) { + found = true; uint32 conditionId = gMenuItem.condition_id; - return IsConditionSatisfied(conditionId, pPlayer, pPlayer->GetMap(), this, CONDITION_FROM_GOSSIP_OPTION); + if (!conditionId) + { + return true; + } + else + { + return IsConditionSatisfied(conditionId, pPlayer, pPlayer->GetMap(), this, CONDITION_FROM_GOSSIP_OPTION); + } } } + + if (!found) + sLog.Out(LOG_DBERROR, LOG_LVL_MINIMAL, "Creature %u (Entry: %u) has npc_flag UNIT_NPC_FLAG_TRAINER but does not have a GOSSIP_OPTION_TRAINER entry assigend to its gossip_menu (Entry: %u) in gossip_menu_option.", + GetGUIDLow(), GetEntry(), gossipMenuId); + return false; } From 39e76a1125c54f3b842961f9c7f6ec9fe2d9e0f0 Mon Sep 17 00:00:00 2001 From: FlagFlayer <76889547+FlagFlayer@users.noreply.github.com> Date: Mon, 3 Nov 2025 18:04:12 +0200 Subject: [PATCH 5/5] fix bug where trainers with no gossip menu could not be interacted with no gossip menu means no condition to access the training menu --- src/game/Commands/CharacterCommands.cpp | 7 +++++++ src/game/Objects/Creature.cpp | 5 ++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/game/Commands/CharacterCommands.cpp b/src/game/Commands/CharacterCommands.cpp index ec54a2c2679..f924bd37eae 100644 --- a/src/game/Commands/CharacterCommands.cpp +++ b/src/game/Commands/CharacterCommands.cpp @@ -2828,6 +2828,7 @@ bool ChatHandler::HandleLearnAllTrainerCommand(char* args) { GossipMenuItems const& gMenuItem = itr->second; if (gMenuItem.menu_id && gMenuItem.option_id == GOSSIP_OPTION_TRAINER) + { if (uint32 conditionId = gMenuItem.condition_id) { validTrainer = IsConditionSatisfied(conditionId, pPlayer, pPlayer->GetMap(), pPlayer, CONDITION_FROM_GOSSIP_OPTION); @@ -2838,6 +2839,12 @@ bool ChatHandler::HandleLearnAllTrainerCommand(char* args) validTrainer = true; break; } + } + else + { + validTrainer = true; + break; + } } if (!validTrainer) diff --git a/src/game/Objects/Creature.cpp b/src/game/Objects/Creature.cpp index be821c9a8fd..8b0d6986e84 100644 --- a/src/game/Objects/Creature.cpp +++ b/src/game/Objects/Creature.cpp @@ -1304,9 +1304,8 @@ bool Creature::IsTrainerOf(Player* pPlayer) const uint32 gossipMenuId = GetCreatureInfo()->gossip_menu_id; if (!gossipMenuId) { - sLog.Out(LOG_DBERROR, LOG_LVL_MINIMAL, "Creature %u (Entry: %u) has npc_flag UNIT_NPC_FLAG_TRAINER but does not have a gossip_menu_id assigned to it.", - GetGUIDLow(), GetEntry()); - return false; + // no requirement to access training menu + return true; } bool found = false;