eluna-scripts-ornfelt/lua/WowEmulationScriptPack/patch/reforging_3.3.5.diff
2024-09-23 07:59:23 +02:00

951 lines
39 KiB
Diff

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 <vector>
+#include <string>
+
+static const bool send_cache_packets = true; // change player cache?
+
+std::vector<Item*> GetItemList(const Player* player)
+{
+ std::vector<Item*> 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<Item*> items = GetItemList(player);
+ for (std::vector<Item*>::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 <vector>
+
+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<Item*> 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<uint32, PlayerTalent*> PlayerTalentMap;
typedef std::unordered_map<uint32, PlayerSpell> PlayerSpellMap;
typedef std::unordered_set<SpellModifier*> SpellModContainer;
+struct ReforgeData
+{
+ uint32 increase, decrease;
+ int32 stat_value;
+};
+typedef std::unordered_map<uint32, ReforgeData> ReforgeMapType;
+
typedef std::unordered_map<uint32 /*instanceId*/, time_t/*releaseTime*/> InstanceTimeMap;
enum ActionButtonUpdateState
@@ -2180,6 +2187,8 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
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.<br />
+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 <sstream>
+#include <string>
+#include <vector>
+#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<Item*> items = GetItemList(player);
+ for (std::vector<Item*>::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();
}