From 634606b5ba447addaddb7b25ee18bccf67afd0db Mon Sep 17 00:00:00 2001 From: Nico Date: Wed, 7 Oct 2026 19:49:32 +0200 Subject: [PATCH] Added item to summon DungeonBots --- src/server/scripts/World/item_scripts.cpp | 554 +++++++++++++++++- ...pp.bak-vcore-dungeon-horns-20261007-184440 | 490 ++++++++++++++++ 2 files changed, 1026 insertions(+), 18 deletions(-) create mode 100644 src/server/scripts/World/item_scripts.cpp.bak-vcore-dungeon-horns-20261007-184440 diff --git a/src/server/scripts/World/item_scripts.cpp b/src/server/scripts/World/item_scripts.cpp index 896de8b8f7..d29192665f 100644 --- a/src/server/scripts/World/item_scripts.cpp +++ b/src/server/scripts/World/item_scripts.cpp @@ -31,6 +31,7 @@ EndContentData */ #include "ScriptMgr.h" #include "GameObject.h" +#include "Group.h" #include "Item.h" #include "Player.h" #include "ScriptedCreature.h" @@ -38,6 +39,9 @@ EndContentData */ #include "SpellMgr.h" #include "TemporarySummon.h" #include "Log.h" +#include +#include +#include namespace ItemScripts { @@ -414,26 +418,298 @@ class item_generic_limit_chance_above_60 : public ItemScript } }; /*##### -# item_horde_adventurer_horn +# Vcore dungeon horns #####*/ -class item_horde_adventurer_horn : public ItemScript +enum VcoreDungeonHornSpells +{ + SPELL_VCORE_HORN_TANK = 120000, + SPELL_VCORE_HORN_HEALER = 120001, + SPELL_VCORE_HORN_DAMAGE = 120002, + SPELL_VCORE_HORN_DAMAGE_3 = 120003 +}; + +enum VcoreDungeonHornNpcs +{ + NPC_HORDE_ADVENTURER_DAMAGE = 900001, + NPC_HORDE_ADVENTURER_TANK = 900002, + NPC_HORDE_ADVENTURER_HEALER = 900003, + + // TODO: replace once Alliance creature templates exist. + NPC_ALLIANCE_ADVENTURER_DAMAGE = 0, + NPC_ALLIANCE_ADVENTURER_TANK = 0, + NPC_ALLIANCE_ADVENTURER_HEALER = 0 +}; + +static constexpr uint32 VCORE_DUNGEON_GROUP_LIMIT = 5; + +struct VcoreHornPlan +{ + uint32 occupancy = 0; + uint32 freeSlots = 0; + uint32 ownBots = 0; + uint32 spawnCount = 0; + uint32 replaceCount = 0; +}; + +static uint32 GetVcoreHornSpellForItem(uint32 itemId) +{ + switch (itemId) + { + case 80000: + case 80004: + return SPELL_VCORE_HORN_TANK; + + case 80001: + case 80005: + return SPELL_VCORE_HORN_HEALER; + + case 80002: + case 80006: + return SPELL_VCORE_HORN_DAMAGE; + + case 80003: + case 80007: + return SPELL_VCORE_HORN_DAMAGE_3; + + default: + return 0; + } +} + +static uint32 GetVcoreHornRequestedCount(uint32 spellId) +{ + return spellId == SPELL_VCORE_HORN_DAMAGE_3 ? 3u : 1u; +} + +static bool IsVcoreHordeHorn(uint32 itemId) +{ + return itemId >= 80000 && itemId <= 80003; +} + +static bool IsVcoreAllianceHorn(uint32 itemId) +{ + return itemId >= 80004 && itemId <= 80007; +} + +static uint32 GetVcoreHornNpcEntry(Player* player, uint32 spellId) +{ + if (!player) + return 0; + + if (player->GetTeam() == HORDE) + { + switch (spellId) + { + case SPELL_VCORE_HORN_TANK: + return NPC_HORDE_ADVENTURER_TANK; + + case SPELL_VCORE_HORN_HEALER: + return NPC_HORDE_ADVENTURER_HEALER; + + case SPELL_VCORE_HORN_DAMAGE: + case SPELL_VCORE_HORN_DAMAGE_3: + return NPC_HORDE_ADVENTURER_DAMAGE; + + default: + return 0; + } + } + + if (player->GetTeam() == ALLIANCE) + { + switch (spellId) + { + case SPELL_VCORE_HORN_TANK: + return NPC_ALLIANCE_ADVENTURER_TANK; + + case SPELL_VCORE_HORN_HEALER: + return NPC_ALLIANCE_ADVENTURER_HEALER; + + case SPELL_VCORE_HORN_DAMAGE: + case SPELL_VCORE_HORN_DAMAGE_3: + return NPC_ALLIANCE_ADVENTURER_DAMAGE; + + default: + return 0; + } + } + + return 0; +} + +static void CollectVcoreDungeonBots( + Player* player, + std::vector& allBots, + std::vector& ownBots) +{ + allBots.clear(); + ownBots.clear(); + + if (!player) + return; + + std::list units; + player->GetPartyMembers(units); + + for (Unit* unit : units) + { + Creature* creature = unit ? unit->ToCreature() : nullptr; + + if (!creature) + continue; + + if (!creature->IsAlive()) + continue; + + if (!creature->IsVianoAdventurer()) + continue; + + if (!creature->IsInMap(player)) + continue; + + if (std::find(allBots.begin(), allBots.end(), creature) == allBots.end()) + allBots.push_back(creature); + + Unit* owner = creature->GetOwner(); + + if (owner && owner->GetGUID() == player->GetGUID()) + ownBots.push_back(creature); + } + + // Current Viano summons receive increasing Creature GUIDs. + // The Viano formation code already uses the same ordering. + // Therefore this gives oldest -> newest. + std::sort( + ownBots.begin(), + ownBots.end(), + [](Creature const* left, Creature const* right) + { + return left->GetGUID().GetRawValue() + < right->GetGUID().GetRawValue(); + }); +} + +static uint32 GetVcoreRealPlayerCount(Player* player) +{ + if (!player) + return 0; + + if (Group* group = player->GetGroup()) + return group->GetMembersCount(); + + return 1; +} + +static VcoreHornPlan BuildVcoreHornPlan( + Player* player, + uint32 requestedCount, + std::vector& ownBots) +{ + VcoreHornPlan plan; + + std::vector allBots; + + CollectVcoreDungeonBots( + player, + allBots, + ownBots); + + uint32 const realPlayers = + GetVcoreRealPlayerCount(player); + + uint32 const botCount = + uint32(allBots.size()); + + plan.occupancy = + realPlayers + botCount; + + plan.ownBots = + uint32(ownBots.size()); + + // Do not mutate an already invalid >5 state. + if (plan.occupancy > VCORE_DUNGEON_GROUP_LIMIT) + return plan; + + plan.freeSlots = + VCORE_DUNGEON_GROUP_LIMIT - plan.occupancy; + + plan.spawnCount = + std::min( + requestedCount, + plan.freeSlots + plan.ownBots); + + plan.replaceCount = + plan.spawnCount > plan.freeSlots + ? plan.spawnCount - plan.freeSlots + : 0; + + return plan; +} + +class item_vcore_dungeon_horn : public ItemScript { public: - item_horde_adventurer_horn() : ItemScript("item_horde_adventurer_horn") { } - - bool OnUse(Player* player, Item* item, SpellCastTargets const& /*targets*/) override + item_vcore_dungeon_horn() + : ItemScript("item_vcore_dungeon_horn") { + } + + bool OnUse( + Player* player, + Item* item, + SpellCastTargets const& /*targets*/) override + { + if (!player || !item) + return true; + + uint32 const itemId = + item->GetEntry(); + + uint32 const spellId = + GetVcoreHornSpellForItem(itemId); + TC_LOG_INFO( "scripts", - "[VianoHorn] OnUse player='%s' item=%u", + "[VianoHorn] OnUse player='%s' item=%u spell=%u", player->GetName().c_str(), - item->GetEntry()); + itemId, + spellId); + + if (!spellId) + { + TC_LOG_ERROR( + "scripts", + "[VianoHorn] unknown horn item=%u", + itemId); + + return true; + } + + if ((IsVcoreHordeHorn(itemId) + && player->GetTeam() != HORDE) + || + (IsVcoreAllianceHorn(itemId) + && player->GetTeam() != ALLIANCE)) + { + TC_LOG_INFO( + "scripts", + "[VianoHorn] rejected: wrong faction player='%s' item=%u", + player->GetName().c_str(), + itemId); + + player->SendEquipError( + EQUIP_ERR_CLIENT_LOCKED_OUT, + item, + nullptr); + + return true; + } - // Horns may not be used during combat. if (player->IsInCombat()) { - TC_LOG_INFO("scripts", + TC_LOG_INFO( + "scripts", "[VianoHorn] rejected: player is in combat"); player->SendEquipError( @@ -444,10 +720,10 @@ public: return true; } - // Horns may only be used inside dungeon instances. if (!player->GetMap()->IsDungeon()) { - TC_LOG_INFO("scripts", + TC_LOG_INFO( + "scripts", "[VianoHorn] rejected: player is not inside a dungeon"); player->SendEquipError( @@ -458,17 +734,258 @@ public: return true; } + // Alliance NPC templates are not configured yet. + // Reject before the normal cast so no cooldown is consumed. + uint32 const npcEntry = + GetVcoreHornNpcEntry( + player, + spellId); + + if (!npcEntry) + { + TC_LOG_INFO( + "scripts", + "[VianoHorn] rejected: no creature entry configured " + "for team=%u spell=%u", + uint32(player->GetTeam()), + spellId); + + player->SendEquipError( + EQUIP_ERR_CLIENT_LOCKED_OUT, + item, + nullptr); + + return true; + } + + uint32 const requestedCount = + GetVcoreHornRequestedCount(spellId); + + std::vector ownBots; + + VcoreHornPlan const plan = + BuildVcoreHornPlan( + player, + requestedCount, + ownBots); + TC_LOG_INFO( "scripts", - "[VianoHorn] prechecks passed; allowing normal item spell processing"); + "[VianoHorn] precheck occupancy=%u free=%u own=%u " + "requested=%u spawn=%u replace=%u", + plan.occupancy, + plan.freeSlots, + plan.ownBots, + requestedCount, + plan.spawnCount, + plan.replaceCount); - // IMPORTANT: - // false means the ItemScript does not consume/suppress the normal item use. - // The item's SpellID1 (120000-120003) may now run through the regular - // Trinity spell lifecycle. + // Full group and nothing belonging to this player + // can be replaced. Do not start the spell/cooldown. + if (!plan.spawnCount) + { + player->SendEquipError( + EQUIP_ERR_CLIENT_LOCKED_OUT, + item, + nullptr); + + return true; + } + + // Preserve the normal item -> spell lifecycle. return false; } -}; +}; + +class spell_vcore_dungeon_horn : public SpellScriptLoader +{ +public: + spell_vcore_dungeon_horn() + : SpellScriptLoader("spell_vcore_dungeon_horn") + { + } + + class spell_vcore_dungeon_horn_SpellScript + : public SpellScript + { + void HandleAfterCast() + { + Player* player = + GetCaster() + ? GetCaster()->ToPlayer() + : nullptr; + + if (!player) + return; + + uint32 const spellId = + GetSpellInfo()->Id; + + if (spellId < SPELL_VCORE_HORN_TANK + || spellId > SPELL_VCORE_HORN_DAMAGE_3) + { + return; + } + + // Re-check immediately before changing world state. + if (player->IsInCombat()) + { + TC_LOG_INFO( + "scripts", + "[VianoHorn] AfterCast aborted: player entered combat"); + + return; + } + + if (!player->GetMap()->IsDungeon()) + { + TC_LOG_INFO( + "scripts", + "[VianoHorn] AfterCast aborted: player left dungeon"); + + return; + } + + uint32 const npcEntry = + GetVcoreHornNpcEntry( + player, + spellId); + + if (!npcEntry) + { + TC_LOG_ERROR( + "scripts", + "[VianoHorn] AfterCast aborted: no NPC entry " + "team=%u spell=%u", + uint32(player->GetTeam()), + spellId); + + return; + } + + uint32 const requestedCount = + GetVcoreHornRequestedCount(spellId); + + std::vector ownBots; + + VcoreHornPlan const plan = + BuildVcoreHornPlan( + player, + requestedCount, + ownBots); + + TC_LOG_INFO( + "scripts", + "[VianoHorn] AfterCast player='%s' spell=%u npc=%u " + "occupancy=%u free=%u own=%u requested=%u " + "spawn=%u replace=%u", + player->GetName().c_str(), + spellId, + npcEntry, + plan.occupancy, + plan.freeSlots, + plan.ownBots, + requestedCount, + plan.spawnCount, + plan.replaceCount); + + if (!plan.spawnCount) + return; + + // Replace only this player's oldest active bots. + for (uint32 i = 0; + i < plan.replaceCount + && i < ownBots.size(); + ++i) + { + Creature* oldBot = + ownBots[i]; + + if (!oldBot) + continue; + + TC_LOG_INFO( + "scripts", + "[VianoHorn] replacing own bot " + "entry=%u guid=%s", + oldBot->GetEntry(), + oldBot->GetGUID().ToString().c_str()); + + oldBot->DespawnOrUnsummon(); + } + + uint32 spawned = 0; + + for (uint32 i = 0; + i < plan.spawnCount; + ++i) + { + // Prevent 3-DD summons from stacking exactly + // on the same coordinates. + float const yOffset = + (float(i) + - (float(plan.spawnCount) - 1.0f) / 2.0f) + * 1.5f; + + Creature* summon = + player->SummonCreature( + npcEntry, + player->GetPositionX() + 2.0f, + player->GetPositionY() + yOffset, + player->GetPositionZ(), + player->GetOrientation(), + TEMPSUMMON_MANUAL_DESPAWN, + 0); + + if (!summon) + { + TC_LOG_ERROR( + "scripts", + "[VianoHorn] summon failed " + "player='%s' spell=%u npc=%u index=%u", + player->GetName().c_str(), + spellId, + npcEntry, + i); + + continue; + } + + ++spawned; + + TC_LOG_INFO( + "scripts", + "[VianoHorn] summoned " + "entry=%u guid=%s owner='%s'", + summon->GetEntry(), + summon->GetGUID().ToString().c_str(), + player->GetName().c_str()); + } + + TC_LOG_INFO( + "scripts", + "[VianoHorn] completed " + "player='%s' spell=%u spawned=%u " + "requested=%u replaced=%u", + player->GetName().c_str(), + spellId, + spawned, + requestedCount, + plan.replaceCount); + } + + void Register() override + { + AfterCast.Register( + &spell_vcore_dungeon_horn_SpellScript::HandleAfterCast); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_vcore_dungeon_horn_SpellScript(); + } +}; }// namespace ItemScripts @@ -486,5 +1003,6 @@ void AddSC_item_scripts() new item_dehta_trap_smasher(); new item_captured_frog(); new item_generic_limit_chance_above_60(); - new item_horde_adventurer_horn(); + new item_vcore_dungeon_horn(); + new spell_vcore_dungeon_horn(); } diff --git a/src/server/scripts/World/item_scripts.cpp.bak-vcore-dungeon-horns-20261007-184440 b/src/server/scripts/World/item_scripts.cpp.bak-vcore-dungeon-horns-20261007-184440 new file mode 100644 index 0000000000..896de8b8f7 --- /dev/null +++ b/src/server/scripts/World/item_scripts.cpp.bak-vcore-dungeon-horns-20261007-184440 @@ -0,0 +1,490 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +/* ScriptData +SDName: Item_Scripts +SD%Complete: 100 +SDComment: Items for a range of different items. See content below (in script) +SDCategory: Items +EndScriptData */ + +/* ContentData +item_nether_wraith_beacon(i31742) Summons creatures for quest Becoming a Spellfire Tailor (q10832) +item_flying_machine(i34060, i34061) Engineering crafted flying machines +item_gor_dreks_ointment(i30175) Protecting Our Own(q10488) +item_only_for_flight Items which should only useable while flying +EndContentData */ + +#include "ScriptMgr.h" +#include "GameObject.h" +#include "Item.h" +#include "Player.h" +#include "ScriptedCreature.h" +#include "Spell.h" +#include "SpellMgr.h" +#include "TemporarySummon.h" +#include "Log.h" + +namespace ItemScripts +{ + +/*##### +# item_only_for_flight +#####*/ + +enum OnlyForFlight +{ + SPELL_ARCANE_CHARGES = 45072 +}; + +class item_only_for_flight : public ItemScript +{ +public: + item_only_for_flight() : ItemScript("item_only_for_flight") { } + + bool OnUse(Player* player, Item* item, SpellCastTargets const& /*targets*/) override + { + uint32 itemId = item->GetEntry(); + bool disabled = false; + + //for special scripts + switch (itemId) + { + case 24538: + if (player->GetAreaId() != 3628) + disabled = true; + break; + case 34489: + if (player->GetZoneId() != 4080) + disabled = true; + break; + case 34475: + if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(SPELL_ARCANE_CHARGES)) + Spell::SendCastResult(player, spellInfo, 1, SPELL_FAILED_NOT_ON_GROUND); + break; + } + + // allow use in flight only + if (player->IsInFlight() && !disabled) + return false; + + // error + player->SendEquipError(EQUIP_ERR_CLIENT_LOCKED_OUT, item, nullptr); + return true; + } +}; + +/*##### +# item_nether_wraith_beacon +#####*/ + +class item_nether_wraith_beacon : public ItemScript +{ +public: + item_nether_wraith_beacon() : ItemScript("item_nether_wraith_beacon") { } + + bool OnUse(Player* player, Item* /*item*/, SpellCastTargets const& /*targets*/) override + { + if (player->GetQuestStatus(10832) == QUEST_STATUS_INCOMPLETE) + { + if (Creature* nether = player->SummonCreature(22408, player->GetPositionX(), player->GetPositionY()+20, player->GetPositionZ(), 0, TEMPSUMMON_TIMED_DESPAWN, 180000)) + nether->AI()->AttackStart(player); + + if (Creature* nether = player->SummonCreature(22408, player->GetPositionX(), player->GetPositionY()-20, player->GetPositionZ(), 0, TEMPSUMMON_TIMED_DESPAWN, 180000)) + nether->AI()->AttackStart(player); + } + return false; + } +}; + +/*##### +# item_gor_dreks_ointment +#####*/ + +class item_gor_dreks_ointment : public ItemScript +{ +public: + item_gor_dreks_ointment() : ItemScript("item_gor_dreks_ointment") { } + + bool OnUse(Player* player, Item* item, SpellCastTargets const& targets) override + { + if (targets.GetUnitTarget() && targets.GetUnitTarget()->GetTypeId() == TYPEID_UNIT && + targets.GetUnitTarget()->GetEntry() == 20748 && !targets.GetUnitTarget()->HasAura(32578)) + return false; + + player->SendEquipError(EQUIP_ERR_CLIENT_LOCKED_OUT, item, nullptr); + return true; + } +}; + +/*##### +# item_incendiary_explosives +#####*/ + +class item_incendiary_explosives : public ItemScript +{ +public: + item_incendiary_explosives() : ItemScript("item_incendiary_explosives") { } + + bool OnUse(Player* player, Item* item, SpellCastTargets const & /*targets*/) override + { + if (player->FindNearestCreature(26248, 15) || player->FindNearestCreature(26249, 15)) + return false; + else + { + player->SendEquipError(EQUIP_ERR_OUT_OF_RANGE, item, nullptr); + return true; + } + } +}; + +/*##### +# item_mysterious_egg +#####*/ + +class item_mysterious_egg : public ItemScript +{ +public: + item_mysterious_egg() : ItemScript("item_mysterious_egg") { } + + bool OnExpire(Player* player, ItemTemplate const* /*pItemProto*/) override + { + ItemPosCountVec dest; + uint8 msg = player->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, 39883, 1); // Cracked Egg + if (msg == EQUIP_ERR_OK) + player->StoreNewItem(dest, 39883, true, GenerateItemRandomPropertyId(39883)); + + return true; + } +}; + +/*##### +# item_disgusting_jar +#####*/ + +class item_disgusting_jar : public ItemScript +{ +public: + item_disgusting_jar() : ItemScript("item_disgusting_jar") { } + + bool OnExpire(Player* player, ItemTemplate const* /*pItemProto*/) override + { + ItemPosCountVec dest; + uint8 msg = player->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, 44718, 1); // Ripe Disgusting Jar + if (msg == EQUIP_ERR_OK) + player->StoreNewItem(dest, 44718, true, GenerateItemRandomPropertyId(44718)); + + return true; + } +}; + +/*##### +# item_pile_fake_furs +#####*/ + +enum PileFakeFur +{ + GO_CARIBOU_TRAP_1 = 187982, + GO_CARIBOU_TRAP_2 = 187995, + GO_CARIBOU_TRAP_3 = 187996, + GO_CARIBOU_TRAP_4 = 187997, + GO_CARIBOU_TRAP_5 = 187998, + GO_CARIBOU_TRAP_6 = 187999, + GO_CARIBOU_TRAP_7 = 188000, + GO_CARIBOU_TRAP_8 = 188001, + GO_CARIBOU_TRAP_9 = 188002, + GO_CARIBOU_TRAP_10 = 188003, + GO_CARIBOU_TRAP_11 = 188004, + GO_CARIBOU_TRAP_12 = 188005, + GO_CARIBOU_TRAP_13 = 188006, + GO_CARIBOU_TRAP_14 = 188007, + GO_CARIBOU_TRAP_15 = 188008, + GO_HIGH_QUALITY_FUR = 187983, + NPC_NESINGWARY_TRAPPER = 25835 +}; + +#define CaribouTrapsNum 15 +const uint32 CaribouTraps[CaribouTrapsNum] = +{ + GO_CARIBOU_TRAP_1, GO_CARIBOU_TRAP_2, GO_CARIBOU_TRAP_3, GO_CARIBOU_TRAP_4, GO_CARIBOU_TRAP_5, + GO_CARIBOU_TRAP_6, GO_CARIBOU_TRAP_7, GO_CARIBOU_TRAP_8, GO_CARIBOU_TRAP_9, GO_CARIBOU_TRAP_10, + GO_CARIBOU_TRAP_11, GO_CARIBOU_TRAP_12, GO_CARIBOU_TRAP_13, GO_CARIBOU_TRAP_14, GO_CARIBOU_TRAP_15, +}; + +class item_pile_fake_furs : public ItemScript +{ +public: + item_pile_fake_furs() : ItemScript("item_pile_fake_furs") { } + + bool OnUse(Player* player, Item* /*item*/, SpellCastTargets const & /*targets*/) override + { + GameObject* go = nullptr; + for (uint8 i = 0; i < CaribouTrapsNum; ++i) + { + go = player->FindNearestGameObject(CaribouTraps[i], 5.0f); + if (go) + break; + } + + if (!go) + return false; + + if (go->FindNearestCreature(NPC_NESINGWARY_TRAPPER, 10.0f, true) || go->FindNearestCreature(NPC_NESINGWARY_TRAPPER, 10.0f, false) || go->FindNearestGameObject(GO_HIGH_QUALITY_FUR, 2.0f)) + return true; + + float x, y, z; + go->GetClosePoint(x, y, z, go->GetCombatReach() / 3, 7.0f); + go->SummonGameObject(GO_HIGH_QUALITY_FUR, *go, QuaternionData(), 1); + if (TempSummon* summon = player->SummonCreature(NPC_NESINGWARY_TRAPPER, x, y, z, go->GetOrientation(), TEMPSUMMON_DEAD_DESPAWN, 1000)) + { + summon->SetVisible(false); + summon->SetReactState(REACT_PASSIVE); + summon->SetImmuneToPC(true); + } + return false; + } +}; + +/*##### +# item_petrov_cluster_bombs +#####*/ + +enum PetrovClusterBombs +{ + SPELL_PETROV_BOMB = 42406, + AREA_ID_SHATTERED_STRAITS = 4064, + ZONE_ID_HOWLING = 495 +}; + +class item_petrov_cluster_bombs : public ItemScript +{ +public: + item_petrov_cluster_bombs() : ItemScript("item_petrov_cluster_bombs") { } + + bool OnUse(Player* player, Item* /*item*/, const SpellCastTargets & /*targets*/) override + { + if (player->GetZoneId() != ZONE_ID_HOWLING) + return false; + + if (!player->GetTransport() || player->GetAreaId() != AREA_ID_SHATTERED_STRAITS) + { + if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(SPELL_PETROV_BOMB)) + Spell::SendCastResult(player, spellInfo, 1, SPELL_FAILED_NOT_HERE); + + return true; + } + + return false; + } +}; + +/*###### +# item_dehta_trap_smasher +# For quest 11876, Help Those That Cannot Help Themselves +######*/ +enum HelpThemselves +{ + QUEST_CANNOT_HELP_THEMSELVES = 11876, + NPC_TRAPPED_MAMMOTH_CALF = 25850, + GO_MAMMOTH_TRAP_1 = 188022, + GO_MAMMOTH_TRAP_2 = 188024, + GO_MAMMOTH_TRAP_3 = 188025, + GO_MAMMOTH_TRAP_4 = 188026, + GO_MAMMOTH_TRAP_5 = 188027, + GO_MAMMOTH_TRAP_6 = 188028, + GO_MAMMOTH_TRAP_7 = 188029, + GO_MAMMOTH_TRAP_8 = 188030, + GO_MAMMOTH_TRAP_9 = 188031, + GO_MAMMOTH_TRAP_10 = 188032, + GO_MAMMOTH_TRAP_11 = 188033, + GO_MAMMOTH_TRAP_12 = 188034, + GO_MAMMOTH_TRAP_13 = 188035, + GO_MAMMOTH_TRAP_14 = 188036, + GO_MAMMOTH_TRAP_15 = 188037, + GO_MAMMOTH_TRAP_16 = 188038, + GO_MAMMOTH_TRAP_17 = 188039, + GO_MAMMOTH_TRAP_18 = 188040, + GO_MAMMOTH_TRAP_19 = 188041, + GO_MAMMOTH_TRAP_20 = 188042, + GO_MAMMOTH_TRAP_21 = 188043, + GO_MAMMOTH_TRAP_22 = 188044, +}; + +#define MammothTrapsNum 22 +const uint32 MammothTraps[MammothTrapsNum] = +{ + GO_MAMMOTH_TRAP_1, GO_MAMMOTH_TRAP_2, GO_MAMMOTH_TRAP_3, GO_MAMMOTH_TRAP_4, GO_MAMMOTH_TRAP_5, + GO_MAMMOTH_TRAP_6, GO_MAMMOTH_TRAP_7, GO_MAMMOTH_TRAP_8, GO_MAMMOTH_TRAP_9, GO_MAMMOTH_TRAP_10, + GO_MAMMOTH_TRAP_11, GO_MAMMOTH_TRAP_12, GO_MAMMOTH_TRAP_13, GO_MAMMOTH_TRAP_14, GO_MAMMOTH_TRAP_15, + GO_MAMMOTH_TRAP_16, GO_MAMMOTH_TRAP_17, GO_MAMMOTH_TRAP_18, GO_MAMMOTH_TRAP_19, GO_MAMMOTH_TRAP_20, + GO_MAMMOTH_TRAP_21, GO_MAMMOTH_TRAP_22 +}; + +class item_dehta_trap_smasher : public ItemScript +{ +public: + item_dehta_trap_smasher() : ItemScript("item_dehta_trap_smasher") { } + + bool OnUse(Player* player, Item* /*item*/, const SpellCastTargets & /*targets*/) override + { + if (player->GetQuestStatus(QUEST_CANNOT_HELP_THEMSELVES) != QUEST_STATUS_INCOMPLETE) + return false; + + Creature* pMammoth = player->FindNearestCreature(NPC_TRAPPED_MAMMOTH_CALF, 5.0f); + if (!pMammoth) + return false; + + GameObject* pTrap = nullptr; + for (uint8 i = 0; i < MammothTrapsNum; ++i) + { + pTrap = player->FindNearestGameObject(MammothTraps[i], 11.0f); + if (pTrap) + { + pMammoth->AI()->DoAction(1); + pTrap->SetGoState(GO_STATE_READY); + player->KilledMonsterCredit(NPC_TRAPPED_MAMMOTH_CALF); + return true; + } + } + return false; + } +}; + +enum CapturedFrog +{ + QUEST_THE_PERFECT_SPIES = 25444, + NPC_VANIRAS_SENTRY_TOTEM = 40187 +}; + +class item_captured_frog : public ItemScript +{ +public: + item_captured_frog() : ItemScript("item_captured_frog") { } + + bool OnUse(Player* player, Item* item, SpellCastTargets const& /*targets*/) override + { + if (player->GetQuestStatus(QUEST_THE_PERFECT_SPIES) == QUEST_STATUS_INCOMPLETE) + { + if (player->FindNearestCreature(NPC_VANIRAS_SENTRY_TOTEM, 10.0f)) + return false; + else + player->SendEquipError(EQUIP_ERR_OUT_OF_RANGE, item, nullptr); + } + else + player->SendEquipError(EQUIP_ERR_CLIENT_LOCKED_OUT, item, nullptr); + return true; + } +}; + +// Only used currently for +// 19169: Nightfall +class item_generic_limit_chance_above_60 : public ItemScript +{ + public: + item_generic_limit_chance_above_60() : ItemScript("item_generic_limit_chance_above_60") { } + + bool OnCastItemCombatSpell(Player* /*player*/, Unit* victim, SpellInfo const* /*spellInfo*/, Item* /*item*/) override + { + // spell proc chance gets severely reduced on victims > 60 (formula unknown) + if (victim->getLevel() > 60) + { + // gives ~0.1% proc chance at lvl 70 + float const lvlPenaltyFactor = 9.93f; + float const failureChance = (victim->getLevel() - 60) * lvlPenaltyFactor; + + // base ppm chance was already rolled, only roll success chance + return !roll_chance_f(failureChance); + } + + return true; + } +}; +/*##### +# item_horde_adventurer_horn +#####*/ + +class item_horde_adventurer_horn : public ItemScript +{ +public: + item_horde_adventurer_horn() : ItemScript("item_horde_adventurer_horn") { } + + bool OnUse(Player* player, Item* item, SpellCastTargets const& /*targets*/) override + { + TC_LOG_INFO( + "scripts", + "[VianoHorn] OnUse player='%s' item=%u", + player->GetName().c_str(), + item->GetEntry()); + + // Horns may not be used during combat. + if (player->IsInCombat()) + { + TC_LOG_INFO("scripts", + "[VianoHorn] rejected: player is in combat"); + + player->SendEquipError( + EQUIP_ERR_CLIENT_LOCKED_OUT, + item, + nullptr); + + return true; + } + + // Horns may only be used inside dungeon instances. + if (!player->GetMap()->IsDungeon()) + { + TC_LOG_INFO("scripts", + "[VianoHorn] rejected: player is not inside a dungeon"); + + player->SendEquipError( + EQUIP_ERR_CLIENT_LOCKED_OUT, + item, + nullptr); + + return true; + } + + TC_LOG_INFO( + "scripts", + "[VianoHorn] prechecks passed; allowing normal item spell processing"); + + // IMPORTANT: + // false means the ItemScript does not consume/suppress the normal item use. + // The item's SpellID1 (120000-120003) may now run through the regular + // Trinity spell lifecycle. + return false; + } +}; +}// namespace ItemScripts + + +void AddSC_item_scripts() +{ + using namespace ItemScripts; + new item_only_for_flight(); + new item_nether_wraith_beacon(); + new item_gor_dreks_ointment(); + new item_incendiary_explosives(); + new item_mysterious_egg(); + new item_disgusting_jar(); + new item_pile_fake_furs(); + new item_petrov_cluster_bombs(); + new item_dehta_trap_smasher(); + new item_captured_frog(); + new item_generic_limit_chance_above_60(); + new item_horde_adventurer_horn(); +}