diff --git a/sql/custom/characters/2017_05_03_00_characters_reforging.sql b/sql/custom/characters/2017_05_03_00_characters_reforging.sql new file mode 100644 index 000000000000..8b51859c1e2a --- /dev/null +++ b/sql/custom/characters/2017_05_03_00_characters_reforging.sql @@ -0,0 +1,10 @@ +CREATE TABLE IF NOT EXISTS `custom_reforging` ( + `GUID` INT(10) UNSIGNED NOT NULL COMMENT 'item guid low', + `increase` INT(10) UNSIGNED NOT NULL COMMENT 'stat_type', + `decrease` INT(10) UNSIGNED NOT NULL COMMENT 'stat_type', + `stat_value` INT(10) NOT NULL DEFAULT '0' COMMENT 'stat change', + `Owner` INT(10) UNSIGNED NULL DEFAULT NULL COMMENT 'player guid', + PRIMARY KEY (`GUID`) +) +COLLATE='utf8_general_ci' +ENGINE=InnoDB; diff --git a/src/server/game/Entities/Item/Reforging.cpp b/src/server/game/Entities/Item/Reforging.cpp new file mode 100644 index 000000000000..2b05cb07a06a --- /dev/null +++ b/src/server/game/Entities/Item/Reforging.cpp @@ -0,0 +1,244 @@ +#include "Reforging.h" + +#include "Define.h" +#include "Player.h" +#include "WorldSession.h" +#include "Item.h" +#include "ItemTemplate.h" +#include "Bag.h" +#include "ObjectGuid.h" +#include "WorldPacket.h" +#include "Opcodes.h" +#include "ObjectMgr.h" +#include "SpellInfo.h" +#include "SpellMgr.h" +#include +#include + +static const bool send_cache_packets = true; // change player cache? + +std::vector GetItemList(const Player* player) +{ + std::vector itemlist; + + // Copy paste from Player::GetItemByGuid(guid) + + for (uint8 i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i) + if (Item* pItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + itemlist.push_back(pItem); + + for (uint8 i = KEYRING_SLOT_START; i < CURRENCYTOKEN_SLOT_END; ++i) + if (Item* pItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + itemlist.push_back(pItem); + + for (int i = BANK_SLOT_ITEM_START; i < BANK_SLOT_BAG_END; ++i) + if (Item* pItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + itemlist.push_back(pItem); + + for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + if (Bag* pBag = player->GetBagByPos(i)) + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) + if (Item* pItem = pBag->GetItemByPos(j)) + itemlist.push_back(pItem); + + for (uint8 i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i) + if (Bag* pBag = player->GetBagByPos(i)) + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) + if (Item* pItem = pBag->GetItemByPos(j)) + itemlist.push_back(pItem); + + return itemlist; +} + +void RemoveReforge(Player* player, uint32 itemguid, bool update) +{ + if (!itemguid || player->reforgeMap.empty() || + player->reforgeMap.find(itemguid) == player->reforgeMap.end()) + return; + + Item* invItem = update ? player->GetItemByGuid(ObjectGuid(HighGuid::Item, 0, itemguid)) : NULL; + if (invItem && invItem->IsEquipped()) + player->_ApplyItemMods(invItem, invItem->GetSlot(), false); + player->reforgeMap.erase(itemguid); + if (invItem && invItem->IsEquipped()) + player->_ApplyItemMods(invItem, invItem->GetSlot(), true); + if (invItem) + SendReforgePacket(player, invItem->GetEntry()); +} + +void SendReforgePackets(Player* player) +{ + if (!send_cache_packets) + return; + + std::vector items = GetItemList(player); + for (std::vector::const_iterator it = items.begin(); it != items.end(); ++it) + SendReforgePacket(player, (*it)->GetEntry(), (*it)->GetGUID().GetCounter()); +} + +// Supply lowguid or reforge! (or both) +// Warning, this function may modify player->reforgeMap when lowguid is supplied +void SendReforgePacket(Player* player, uint32 entry, uint32 lowguid, const ReforgeData* reforge) +{ + ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(entry); + if (!pProto) + return; + + if (lowguid) + { + if (!player->reforgeMap.empty() && player->reforgeMap.find(lowguid) != player->reforgeMap.end()) + reforge = &player->reforgeMap[lowguid]; + else + RemoveReforge(player, lowguid, true); + } + + // Update player cache (self only) pure visual. + // HandleItemQuerySingleOpcode copy paste + std::string Name = pProto->Name1; + std::string Description = pProto->Description; + LocaleConstant loc_idx = player->GetSession()->GetSessionDbLocaleIndex(); + if (ItemLocale const* il = sObjectMgr->GetItemLocale(pProto->ItemId)) + { + ObjectMgr::GetLocaleString(il->Name, loc_idx, Name); + ObjectMgr::GetLocaleString(il->Description, loc_idx, Description); + } + WorldPacket data(SMSG_ITEM_QUERY_SINGLE_RESPONSE, 600); + data << pProto->ItemId; + data << pProto->Class; + data << pProto->SubClass; + data << pProto->SoundOverrideSubclass; + data << Name; + data << uint8(0x00); //pProto->Name2; // blizz not send name there, just uint8(0x00); <-- \0 = empty string = empty name... + data << uint8(0x00); //pProto->Name3; // blizz not send name there, just uint8(0x00); + data << uint8(0x00); //pProto->Name4; // blizz not send name there, just uint8(0x00); + data << pProto->DisplayInfoID; + data << pProto->Quality; + data << pProto->Flags; + data << pProto->Flags2; + data << pProto->BuyPrice; + data << pProto->SellPrice; + data << pProto->InventoryType; + data << pProto->AllowableClass; + data << pProto->AllowableRace; + data << pProto->ItemLevel; + data << pProto->RequiredLevel; + data << pProto->RequiredSkill; + data << pProto->RequiredSkillRank; + data << pProto->RequiredSpell; + data << pProto->RequiredHonorRank; + data << pProto->RequiredCityRank; + data << pProto->RequiredReputationFaction; + data << pProto->RequiredReputationRank; + data << int32(pProto->MaxCount); + data << int32(pProto->Stackable); + data << pProto->ContainerSlots; + data << pProto->StatsCount + (reforge ? 1 : 0); // increase stat count by 1 + bool decreased = false; + for (uint32 i = 0; i < pProto->StatsCount; ++i) + { + data << pProto->ItemStat[i].ItemStatType; + if (reforge && !decreased && pProto->ItemStat[i].ItemStatType == reforge->decrease) + { + data << pProto->ItemStat[i].ItemStatValue - reforge->stat_value; + decreased = true; + } + else + { + data << pProto->ItemStat[i].ItemStatValue; + } + } + if (reforge) + { + data << reforge->increase; // set new stat type + data << reforge->stat_value; // and value + } + data << pProto->ScalingStatDistribution; // scaling stats distribution + data << pProto->ScalingStatValue; // some kind of flags used to determine stat values column + for (int i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i) + { + data << pProto->Damage[i].DamageMin; + data << pProto->Damage[i].DamageMax; + data << pProto->Damage[i].DamageType; + } + + // resistances (7) + data << pProto->Armor; + data << pProto->HolyRes; + data << pProto->FireRes; + data << pProto->NatureRes; + data << pProto->FrostRes; + data << pProto->ShadowRes; + data << pProto->ArcaneRes; + + data << pProto->Delay; + data << pProto->AmmoType; + data << pProto->RangedModRange; + + for (int s = 0; s < MAX_ITEM_PROTO_SPELLS; ++s) + { + // send DBC data for cooldowns in same way as it used in Spell::SendSpellCooldown + // use `item_template` or if not set then only use spell cooldowns + SpellInfo const* spell = sSpellMgr->GetSpellInfo(pProto->Spells[s].SpellId); + if (spell) + { + bool db_data = pProto->Spells[s].SpellCooldown >= 0 || pProto->Spells[s].SpellCategoryCooldown >= 0; + + data << pProto->Spells[s].SpellId; + data << pProto->Spells[s].SpellTrigger; + data << uint32(-abs(pProto->Spells[s].SpellCharges)); + + if (db_data) + { + data << uint32(pProto->Spells[s].SpellCooldown); + data << uint32(pProto->Spells[s].SpellCategory); + data << uint32(pProto->Spells[s].SpellCategoryCooldown); + } + else + { + data << uint32(spell->RecoveryTime); + data << uint32(spell->GetCategory()); + data << uint32(spell->CategoryRecoveryTime); + } + } + else + { + data << uint32(0); + data << uint32(0); + data << uint32(0); + data << uint32(-1); + data << uint32(0); + data << uint32(-1); + } + } + data << pProto->Bonding; + data << Description; + data << pProto->PageText; + data << pProto->LanguageID; + data << pProto->PageMaterial; + data << pProto->StartQuest; + data << pProto->LockID; + data << int32(pProto->Material); + data << pProto->Sheath; + data << pProto->RandomProperty; + data << pProto->RandomSuffix; + data << pProto->Block; + data << pProto->ItemSet; + data << pProto->MaxDurability; + data << pProto->Area; + data << pProto->Map; // Added in 1.12.x & 2.0.1 client branch + data << pProto->BagFamily; + data << pProto->TotemCategory; + for (int s = 0; s < MAX_ITEM_PROTO_SOCKETS; ++s) + { + data << pProto->Socket[s].Color; + data << pProto->Socket[s].Content; + } + data << pProto->socketBonus; + data << pProto->GemProperties; + data << pProto->RequiredDisenchantSkill; + data << pProto->ArmorDamageModifier; + data << pProto->Duration; // added in 2.4.2.8209, duration (seconds) + data << pProto->ItemLimitCategory; // WotLK, ItemLimitCategory + data << pProto->HolidayId; // Holiday.dbc? + player->GetSession()->SendPacket(&data); +} diff --git a/src/server/game/Entities/Item/Reforging.h b/src/server/game/Entities/Item/Reforging.h new file mode 100644 index 000000000000..7fbec60084ce --- /dev/null +++ b/src/server/game/Entities/Item/Reforging.h @@ -0,0 +1,18 @@ +#ifndef REFORGING_H +#define REFORGING_H + +#include "Define.h" +#include + +class Player; +class Item; +struct ReforgeData; + +// Supply lowguid or reforge! (or both) +// Warning, this function may modify player->reforgeMap when lowguid is supplied +void TC_GAME_API SendReforgePacket(Player* player, uint32 entry, uint32 lowguid = 0, const ReforgeData* reforge = NULL); +void TC_GAME_API SendReforgePackets(Player* player); +void TC_GAME_API RemoveReforge(Player* player, uint32 itemguid, bool update); +std::vector TC_GAME_API GetItemList(const Player* player); + +#endif diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 8c3fcafaf83a..5893e8364f9f 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -7346,6 +7346,20 @@ void Player::_ApplyItemBonuses(ItemTemplate const* proto, uint8 slot, bool apply if (only_level_scale && (!ssd || !ssv)) return; + uint32 statcount = proto->StatsCount; + ReforgeData* reforgeData = NULL; + bool decreased = false; + if (statcount < MAX_ITEM_PROTO_STATS) + { + if (Item* invItem = GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) + { + if (reforgeMap.find(invItem->GetGUID().GetCounter()) != reforgeMap.end()) + { + reforgeData = &reforgeMap[invItem->GetGUID().GetCounter()]; + ++statcount; + } + } + } for (uint8 i = 0; i < MAX_ITEM_PROTO_STATS; ++i) { uint32 statType = 0; @@ -7360,10 +7374,24 @@ void Player::_ApplyItemBonuses(ItemTemplate const* proto, uint8 slot, bool apply } else { - if (i >= proto->StatsCount) + if (i >= statcount) continue; statType = proto->ItemStat[i].ItemStatType; val = proto->ItemStat[i].ItemStatValue; + + if (reforgeData) + { + if(i == statcount-1) + { + statType = reforgeData->increase; + val = reforgeData->stat_value; + } + else if (!decreased && reforgeData->decrease == statType) + { + val -= reforgeData->stat_value; + decreased = true; + } + } } if (val == 0) @@ -12369,12 +12397,14 @@ void Player::RemoveItem(uint8 bag, uint8 slot, bool update) } } +extern void RemoveReforge(Player* player, uint32 itemguid, bool update); // Common operation need to remove item from inventory without delete in trade, auction, guild bank, mail.... void Player::MoveItemFromInventory(uint8 bag, uint8 slot, bool update) { if (Item* it = GetItemByPos(bag, slot)) { RemoveItem(bag, slot, update); + RemoveReforge(this, it->GetGUID().GetCounter(), true); ItemRemovedQuestCheck(it->GetEntry(), it->GetCount()); it->SetNotRefundable(this, false); RemoveItemFromUpdateQueueOf(it, this); diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index b5ddaca836c5..47bd6c444dcb 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -155,6 +155,13 @@ typedef std::unordered_map PlayerTalentMap; typedef std::unordered_map PlayerSpellMap; typedef std::unordered_set SpellModContainer; +struct ReforgeData +{ + uint32 increase, decrease; + int32 stat_value; +}; +typedef std::unordered_map ReforgeMapType; + typedef std::unordered_map InstanceTimeMap; enum ActionButtonUpdateState @@ -2180,6 +2187,8 @@ class TC_GAME_API Player : public Unit, public GridObject std::string GetMapAreaAndZoneString() const; std::string GetCoordsMapAreaAndZoneString() const; + ReforgeMapType reforgeMap; // reforgeMap[iGUID] = ReforgeData + std::string GetDebugInfo() const override; protected: diff --git a/src/server/scripts/Custom/Reforging/README.md b/src/server/scripts/Custom/Reforging/README.md new file mode 100644 index 000000000000..819e962cda15 --- /dev/null +++ b/src/server/scripts/Custom/Reforging/README.md @@ -0,0 +1,40 @@ +# Reforging [![Build Status](https://travis-ci.org/Rochet2/TrinityCore.svg?branch=reforging_3.3.5)](https://travis-ci.org/Rochet2/TrinityCore) + +#### About +Blizz*like* reforging +Change 40% of your item stats to something else +Sends item packets so you can see the changes on item tooltips +Simple and easy to use interface +Made for 3.3.5a.
+Source: http://rochet2.github.io/Reforging.html + +Video: https://youtu.be/84EtvKTfqQM + +#### Installation + +Available as: +- Direct merge: https://github.com/Rochet2/TrinityCore/tree/reforging_3.3.5 +- Diff: https://github.com/Rochet2/TrinityCore/compare/TrinityCore:3.3.5...reforging_3.3.5.diff +- Diff in github view: https://github.com/Rochet2/TrinityCore/compare/TrinityCore:3.3.5...reforging_3.3.5 + +Using direct merge: +- open git bash to source location +- do `git remote add rochet2 https://github.com/Rochet2/TrinityCore.git` +- do `git pull rochet2 reforging_3.3.5` +- use cmake and compile + +Using diff: +- DO NOT COPY THE DIFF DIRECTLY! It causes apply to fail. +- download the diff by __right clicking__ the link and select __Save link as__ +- place the downloaded `reforging_3.3.5.diff` to the source root folder +- open git bash to source location +- do `git apply reforging_3.3.5.diff` +- use cmake and compile + +After compiling: +- TrinityCore auto updater should run needed SQLs automatically. +- If you do not use the auto updater then run files named `*_reforging.sql` from `\sql\custom` to your databases. +- optionally you can also insert a reforger NPC to your database by running `\src\server\scripts\Custom\Reforging\sql\world_npc.sql` to your world database. + +#### Bugs and Contact +Report issues and similar to https://rochet2.github.io/ diff --git a/src/server/scripts/Custom/Reforging/Reforger.cpp b/src/server/scripts/Custom/Reforging/Reforger.cpp new file mode 100644 index 000000000000..eea92ea4574d --- /dev/null +++ b/src/server/scripts/Custom/Reforging/Reforger.cpp @@ -0,0 +1,488 @@ +#include "Reforging.h" +#include +#include +#include +#include "Creature.h" +#include "Define.h" +#include "EventProcessor.h" +#include "GossipDef.h" +#include "Item.h" +#include "ItemTemplate.h" +#include "ObjectGuid.h" +#include "ObjectMgr.h" +#include "Player.h" +#include "ScriptedCreature.h" +#include "ScriptedGossip.h" +#include "ScriptMgr.h" +#include "SharedDefines.h" +#include "Spell.h" +#include "Transaction.h" +#include "WorldPacket.h" +#include "WorldSession.h" +#include "DatabaseEnv.h" + +/* +Reforging by Rochet2 +http://rochet2.github.io/ + +Rules of thumb: +Item can be reforged once. +Item reforge wont show to anyone but you in tooltips. Stats will be there nevertheless. +You will see the increased stats on all tooltips of the same item you reforged. +You can disable the stat changes to tooltips by setting send_cache_packets to false in Reforging.cpp. +Reforges are stripped when you mail, ah, guildbank the item etc. Only YOU can have the reforge. +Only item base stats are reforgable. Enchants and random stats are not. + +This script is made blizzlike. This means that the reforgable stats etc are from CATACLYSM! +I have been informed that some stats were removed etc that would be important to be reforgable. +However I do not know what those stats are currently. Do look through the statTypes to add whatever you want. +Edit IsReforgable is you want to tweak requirements + +*/ + +// Remember to add to GetStatName too +static const ItemModType statTypes[] = { ITEM_MOD_SPIRIT, ITEM_MOD_DODGE_RATING, ITEM_MOD_PARRY_RATING, ITEM_MOD_HIT_RATING, ITEM_MOD_CRIT_RATING, ITEM_MOD_HASTE_RATING, ITEM_MOD_EXPERTISE_RATING }; +static const uint8 stat_type_max = sizeof(statTypes) / sizeof(*statTypes); + +static const char* GetStatName(uint32 ItemStatType) +{ + switch (ItemStatType) + { + case ITEM_MOD_SPIRIT: return "Spirit"; break; + case ITEM_MOD_DODGE_RATING: return "Dodge rating"; break; + case ITEM_MOD_PARRY_RATING: return "Parry rating"; break; + case ITEM_MOD_HIT_RATING: return "Hit rating"; break; + case ITEM_MOD_CRIT_RATING: return "Crit rating"; break; + case ITEM_MOD_HASTE_RATING: return "Haste rating"; break; + case ITEM_MOD_EXPERTISE_RATING: return "Expertise rating"; break; + default: return NULL; + } +} + +static const char* GetSlotName(uint8 slot, WorldSession* /*session*/) +{ + switch (slot) + { + case EQUIPMENT_SLOT_HEAD: return "Head"; + case EQUIPMENT_SLOT_NECK: return "Neck"; + case EQUIPMENT_SLOT_SHOULDERS: return "Shoulders"; + case EQUIPMENT_SLOT_BODY: return "Shirt"; + case EQUIPMENT_SLOT_CHEST: return "Chest"; + case EQUIPMENT_SLOT_WAIST: return "Waist"; + case EQUIPMENT_SLOT_LEGS: return "Legs"; + case EQUIPMENT_SLOT_FEET: return "Feet"; + case EQUIPMENT_SLOT_WRISTS: return "Wrists"; + case EQUIPMENT_SLOT_HANDS: return "Hands"; + case EQUIPMENT_SLOT_FINGER1: return "Right finger"; + case EQUIPMENT_SLOT_FINGER2: return "Left finger"; + case EQUIPMENT_SLOT_TRINKET1: return "Right trinket"; + case EQUIPMENT_SLOT_TRINKET2: return "Left trinket"; + case EQUIPMENT_SLOT_BACK: return "Back"; + case EQUIPMENT_SLOT_MAINHAND: return "Main hand"; + case EQUIPMENT_SLOT_OFFHAND: return "Off hand"; + case EQUIPMENT_SLOT_TABARD: return "Tabard"; + case EQUIPMENT_SLOT_RANGED: return "Ranged"; + default: return NULL; + } +} + +static uint32 Melt(uint8 i, uint8 j) +{ + return (i << 8) + j; +} + +static void Unmelt(uint32 melt, uint8& i, uint8& j) +{ + i = melt >> 8; + j = melt & 0xFF; +} + +static Item* GetEquippedItem(Player* player, uint32 guidlow) +{ + for (uint8 i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i) + if (Item* pItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + if (pItem->GetGUID().GetCounter() == guidlow) + return pItem; + return NULL; +} + +static bool IsReforgable(Item* invItem, Player* player) +{ + //if (!invItem->IsEquipped()) + // return false; + if (invItem->GetOwnerGUID() != player->GetGUID()) + return false; + const ItemTemplate* pProto = invItem->GetTemplate(); + //if (pProto->ItemLevel < 200) + // return false; + //if (pProto->Quality == ITEM_QUALITY_HEIRLOOM) // block heirlooms necessary? + // return false; + if (!pProto->StatsCount || pProto->StatsCount >= MAX_ITEM_PROTO_STATS) // Mandatory! Do NOT remove or edit + return false; + if (!player->reforgeMap.empty() && player->reforgeMap.find(invItem->GetGUID().GetCounter()) != player->reforgeMap.end()) // Mandatory! Do NOT remove or edit + return false; + for (uint32 i = 0; i < pProto->StatsCount; ++i) + { + if (!GetStatName(pProto->ItemStat[i].ItemStatType)) + continue; + if (((int32)floorf((float)pProto->ItemStat[i].ItemStatValue * 0.4f)) > 1) + return true; + } + return false; +} + +static void UpdatePlayerReforgeStats(Item* invItem, Player* player, uint32 decrease, uint32 increase) // stat types +{ + const ItemTemplate* pProto = invItem->GetTemplate(); + + int32 stat_diff = 0; + for (uint32 i = 0; i < pProto->StatsCount; ++i) + { + if (pProto->ItemStat[i].ItemStatType == increase) + return; // Should not have the increased stat already + if (pProto->ItemStat[i].ItemStatType == decrease) + stat_diff = (int32)floorf((float)pProto->ItemStat[i].ItemStatValue * 0.4f); + } + if (stat_diff <= 0) + return; // Should have some kind of diff + + // Update player stats + if (invItem->IsEquipped()) + player->_ApplyItemMods(invItem, invItem->GetSlot(), false); + uint32 guidlow = invItem->GetGUID().GetCounter(); + ReforgeData& data = player->reforgeMap[guidlow]; + data.increase = increase; + data.decrease = decrease; + data.stat_value = stat_diff; + if (invItem->IsEquipped()) + player->_ApplyItemMods(invItem, invItem->GetSlot(), true); + // CharacterDatabase.PExecute("REPLACE INTO `custom_reforging` (`GUID`, `increase`, `decrease`, `stat_value`) VALUES (%u, %u, %u, %i)", guidlow, increase, decrease, stat_diff); + player->ModifyMoney(pProto->SellPrice < (10 * GOLD) ? (-10 * GOLD) : -(int32)pProto->SellPrice); + SendReforgePacket(player, invItem->GetEntry(), 0, &data); + // player->SaveToDB(); +} + +class REFORGE_PLAYER : public PlayerScript +{ +public: + REFORGE_PLAYER() : PlayerScript("REFORGE_PLAYER") + { + CharacterDatabase.DirectExecute("DELETE FROM `custom_reforging` WHERE NOT EXISTS (SELECT 1 FROM `item_instance` WHERE `item_instance`.`guid` = `custom_reforging`.`GUID`)"); + } + + class SendRefPackLogin : public BasicEvent + { + public: + SendRefPackLogin(Player* _player) : player(_player) + { + _player->m_Events.AddEvent(this, _player->m_Events.CalculateTime(Milliseconds(1000))); + } + + bool Execute(uint64, uint32) override + { + SendReforgePackets(player); + return true; + } + Player* player; + }; + + void OnLogin(Player* player, bool /*firstLogin*/) override + { + uint32 playerGUID = player->GetGUID().GetCounter(); + QueryResult result = CharacterDatabase.PQuery("SELECT `GUID`, `increase`, `decrease`, `stat_value` FROM `custom_reforging` WHERE `Owner` = %u", playerGUID); + if (result) + { + do + { + uint32 lowGUID = (*result)[0].GetUInt32(); + Item* invItem = player->GetItemByGuid(ObjectGuid(HighGuid::Item, 0, lowGUID)); + if (invItem && invItem->IsEquipped()) + player->_ApplyItemMods(invItem, invItem->GetSlot(), false); + ReforgeData& data = player->reforgeMap[lowGUID]; + data.increase = (*result)[1].GetUInt32(); + data.decrease = (*result)[2].GetUInt32(); + data.stat_value = (*result)[3].GetInt32(); + if (invItem && invItem->IsEquipped()) + player->_ApplyItemMods(invItem, invItem->GetSlot(), true); + // SendReforgePacket(player, entry, lowGUID); + } while (result->NextRow()); + + // SendReforgePackets(player); + new SendRefPackLogin(player); + } + } + + //void OnLogout(Player* player) override + //{ + // if (player->reforgeMap.empty()) + // return; + // for (ReforgeMapType::const_iterator it = player->reforgeMap.begin(); it != player->reforgeMap.end();) + // { + // ReforgeMapType::const_iterator old_it = it++; + // RemoveReforge(player, old_it->first, false); + // } + //} + + void OnSave(Player* player) override + { + uint32 lowguid = player->GetGUID().GetCounter(); + auto trans = CharacterDatabase.BeginTransaction(); + trans->PAppend("DELETE FROM `custom_reforging` WHERE `Owner` = %u", lowguid); + if (!player->reforgeMap.empty()) + { + // Only save items that are in inventory / bank / etc + std::vector items = GetItemList(player); + for (std::vector::const_iterator it = items.begin(); it != items.end(); ++it) + { + ReforgeMapType::const_iterator it2 = player->reforgeMap.find((*it)->GetGUID().GetCounter()); + if (it2 == player->reforgeMap.end()) + continue; + + const ReforgeData& data = it2->second; + trans->PAppend("REPLACE INTO `custom_reforging` (`GUID`, `increase`, `decrease`, `stat_value`, `Owner`) VALUES (%u, %u, %u, %i, %u)", it2->first, data.increase, data.decrease, data.stat_value, lowguid); + } + } + + if (trans->GetSize()) // basically never false + CharacterDatabase.CommitTransaction(trans); + } +}; + +class REFORGER_NPC : public CreatureScript +{ +public: + REFORGER_NPC() : CreatureScript("REFORGER_NPC") { } + + class ReforgeAI : public ScriptedAI + { + public: + ReforgeAI(Creature* creature) : ScriptedAI(creature) + { + } + + struct Timed : public BasicEvent + { + // This timed event tries to fix modify money breaking gossip + // This event closes the gossip menu and on the second player update tries to open the next menu + Timed(Player* player, Creature* creature) : guid(creature->GetGUID()), player(player), triggered(false) + { + CloseGossipMenuFor(player); + player->m_Events.AddEvent(this, player->m_Events.CalculateTime(Milliseconds(1))); + } + + bool Execute(uint64, uint32) override + { + if (!triggered) + { + triggered = true; + player->m_Events.AddEvent(this, player->m_Events.CalculateTime(Milliseconds(1))); + return false; + } + if (Creature* creature = player->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_GOSSIP)) + OnGossipHello(player, creature); + return true; + } + + ObjectGuid guid; + Player* player; + bool triggered; + }; + + bool OnGossipHello(Player* player) override + { + return OnGossipHello(player, me); + } + + static bool OnGossipHello(Player* player, Creature* creature) + { + AddGossipItemFor(player, GOSSIP_ICON_BATTLE, "Select slot of the item to reforge:", 0, Melt(MAIN_MENU, 0)); + for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot) + { + if (Item* invItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) + if (IsReforgable(invItem, player)) + if (const char* slotname = GetSlotName(slot, player->GetSession())) + AddGossipItemFor(player, GOSSIP_ICON_TRAINER, slotname, 0, Melt(SELECT_STAT_REDUCE, slot)); + } + AddGossipItemFor(player, GOSSIP_ICON_TRAINER, "Remove reforges", 0, Melt(SELECT_RESTORE, 0)); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, "Update menu", 0, Melt(MAIN_MENU, 0)); + SendGossipMenuFor(player, DEFAULT_GOSSIP_MESSAGE, creature->GetGUID()); + return true; + } + + bool OnGossipSelect(Player* player, uint32 /*menu_id*/, uint32 gossipListId) override + { + uint32 sender = player->PlayerTalkClass->GetGossipOptionSender(gossipListId); + uint32 action = player->PlayerTalkClass->GetGossipOptionAction(gossipListId); + return OnGossipSelect(player, me, sender, action); + } + + static bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 melt) + { + ClearGossipMenuFor(player); + + uint8 menu, action; + Unmelt(melt, menu, action); + + switch (menu) + { + case MAIN_MENU: OnGossipHello(player, creature); break; + case SELECT_STAT_REDUCE: + // action = slot + if (Item* invItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, action)) + { + if (IsReforgable(invItem, player)) + { + uint32 guidlow = invItem->GetGUID().GetCounter(); + const ItemTemplate* pProto = invItem->GetTemplate(); + AddGossipItemFor(player, GOSSIP_ICON_BATTLE, "Stat to decrease:", sender, melt); + for (uint32 i = 0; i < pProto->StatsCount; ++i) + { + int32 stat_diff = ((int32)floorf((float)pProto->ItemStat[i].ItemStatValue * 0.4f)); + if (stat_diff > 1) + if (const char* stat_name = GetStatName(pProto->ItemStat[i].ItemStatType)) + { + std::ostringstream oss; + oss << stat_name << " (" << pProto->ItemStat[i].ItemStatValue << " |cFFDB2222-" << stat_diff << "|r)"; + AddGossipItemFor(player, GOSSIP_ICON_TRAINER, oss.str(), guidlow, Melt(SELECT_STAT_INCREASE, i)); + } + } + AddGossipItemFor(player, GOSSIP_ICON_TALK, "Back..", 0, Melt(MAIN_MENU, 0)); + SendGossipMenuFor(player, DEFAULT_GOSSIP_MESSAGE, creature->GetGUID()); + } + else + { + player->GetSession()->SendNotification("Invalid item selected"); + OnGossipHello(player, creature); + } + } + else + { + player->GetSession()->SendNotification("Invalid item selected"); + OnGossipHello(player, creature); + } + break; + case SELECT_STAT_INCREASE: + // sender = item guidlow + // action = StatsCount id + { + Item* invItem = GetEquippedItem(player, sender); + if (invItem) + { + const ItemTemplate* pProto = invItem->GetTemplate(); + int32 stat_diff = ((int32)floorf((float)pProto->ItemStat[action].ItemStatValue * 0.4f)); + + AddGossipItemFor(player, GOSSIP_ICON_BATTLE, "Stat to increase:", sender, melt); + for (uint8 i = 0; i < stat_type_max; ++i) + { + bool cont = false; + for (uint32 j = 0; j < pProto->StatsCount; ++j) + { + if (statTypes[i] == pProto->ItemStat[j].ItemStatType) // skip existing stats on item + { + cont = true; + break; + } + } + if (cont) + continue; + if (const char* stat_name = GetStatName(statTypes[i])) + { + std::ostringstream oss; + oss << stat_name << " |cFF3ECB3C+" << stat_diff << "|r"; + AddGossipItemFor(player, GOSSIP_ICON_INTERACT_1, oss.str(), sender, Melt(i, (uint8)pProto->ItemStat[action].ItemStatType), "Are you sure you want to reforge\n\n" + pProto->Name1, (pProto->SellPrice < (10 * GOLD) ? (10 * GOLD) : pProto->SellPrice), false); + } + } + AddGossipItemFor(player, GOSSIP_ICON_TALK, "Back..", 0, Melt(SELECT_STAT_REDUCE, invItem->GetSlot())); + SendGossipMenuFor(player, DEFAULT_GOSSIP_MESSAGE, creature->GetGUID()); + } + else + { + player->GetSession()->SendNotification("Invalid item selected"); + OnGossipHello(player, creature); + } + } + break; + case SELECT_RESTORE: + { + AddGossipItemFor(player, GOSSIP_ICON_BATTLE, "Select slot to remove reforge from:", sender, melt); + if (!player->reforgeMap.empty()) + { + for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot) + { + if (Item* invItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) + if (player->reforgeMap.find(invItem->GetGUID().GetCounter()) != player->reforgeMap.end()) + if (const char* slotname = GetSlotName(slot, player->GetSession())) + AddGossipItemFor(player, GOSSIP_ICON_INTERACT_1, slotname, invItem->GetGUID().GetCounter(), Melt(RESTORE, 0), "Remove reforge from\n\n" + invItem->GetTemplate()->Name1, 0, false); + } + } + AddGossipItemFor(player, GOSSIP_ICON_CHAT, "Update menu", sender, melt); + AddGossipItemFor(player, GOSSIP_ICON_TALK, "Back..", 0, Melt(MAIN_MENU, 0)); + SendGossipMenuFor(player, DEFAULT_GOSSIP_MESSAGE, creature->GetGUID()); + } + break; + case RESTORE: + // sender = item guidlow + { + if (player->GetItemByGuid(ObjectGuid(HighGuid::Item, 0, sender))) + { + if (!player->reforgeMap.empty() && player->reforgeMap.find(sender) != player->reforgeMap.end()) + RemoveReforge(player, sender, true); + } + OnGossipHello(player, creature); + } + break; + default: // Reforge + // sender = item guidlow + // menu = stat type to increase index to statTypes[] + // action = stat type to decrease + { + if (menu < stat_type_max) + { + Item* invItem = GetEquippedItem(player, sender); + if (invItem && IsReforgable(invItem, player)) + { + if (player->HasEnoughMoney(invItem->GetTemplate()->SellPrice < (10 * GOLD) ? (10 * GOLD) : invItem->GetTemplate()->SellPrice)) + { + // int32 stat_diff = ((int32)floorf((float)invItem->GetTemplate()->ItemStat[action].ItemStatValue * 0.4f)); + UpdatePlayerReforgeStats(invItem, player, action, statTypes[menu]); // rewrite this function + } + else + { + player->GetSession()->SendNotification("Not enough money"); + } + } + else + { + player->GetSession()->SendNotification("Invalid item selected"); + } + } + // OnGossipHello(player, creature); + new Timed(player, creature); + } + } + return true; + } + + enum Menus + { + MAIN_MENU = 200, // stat_type_max + SELECT_ITEM, + SELECT_STAT_REDUCE, + SELECT_STAT_INCREASE, + SELECT_RESTORE, + RESTORE, + REFORGE, + }; + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return new ReforgeAI(creature); + } +}; + +void AddSC_REFORGER_NPC() +{ + new REFORGER_NPC; + new REFORGE_PLAYER; +} diff --git a/src/server/scripts/Custom/Reforging/sql/world_npc.sql b/src/server/scripts/Custom/Reforging/sql/world_npc.sql new file mode 100644 index 000000000000..d4a62fc11d73 --- /dev/null +++ b/src/server/scripts/Custom/Reforging/sql/world_npc.sql @@ -0,0 +1,6 @@ +SET +@Entry = 190011, +@Name = "Thaumaturge Vashreen"; + +INSERT INTO `creature_template` (`entry`, `modelid1`, `modelid2`, `name`, `subname`, `IconName`, `gossip_menu_id`, `minlevel`, `maxlevel`, `exp`, `faction`, `npcflag`, `scale`, `rank`, `dmgschool`, `baseattacktime`, `rangeattacktime`, `unit_class`, `unit_flags`, `type`, `type_flags`, `lootid`, `pickpocketloot`, `skinloot`, `AIName`, `MovementType`, `HoverHeight`, `RacialLeader`, `movementId`, `RegenHealth`, `mechanic_immune_mask`, `flags_extra`, `ScriptName`) VALUES +(@Entry, 20988, 0, @Name, "Arcane Reforger", NULL, 0, 80, 80, 2, 35, 1, 1, 0, 0, 2000, 0, 1, 0, 7, 138936390, 0, 0, 0, '', 0, 1, 0, 0, 1, 0, 0, 'REFORGER_NPC'); diff --git a/src/server/scripts/Custom/custom_script_loader.cpp b/src/server/scripts/Custom/custom_script_loader.cpp index 9e5e9ba2bfd6..74be1bfa6f98 100644 --- a/src/server/scripts/Custom/custom_script_loader.cpp +++ b/src/server/scripts/Custom/custom_script_loader.cpp @@ -16,9 +16,11 @@ */ // This is where scripts' loading functions should be declared: +void AddSC_REFORGER_NPC(); // The name of this function should match: // void Add${NameOfDirectory}Scripts() void AddCustomScripts() { + AddSC_REFORGER_NPC(); }