commit 8ce4fed2e2814a650943bd0c5b7e440d93141658 Author: silviu20092 Date: Sun Apr 7 20:33:57 2024 +0300 Initialize repo diff --git a/README.md b/README.md new file mode 100644 index 0000000..a1757be --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# ![logo](https://raw.githubusercontent.com/azerothcore/azerothcore.github.io/master/images/logo-github.png) AzerothCore + +# Reforging System for AzerothCore + +## Overview + +First, thank you Rochet2 for inspiration. His reforging repo for TrinityCore can be found here: https://github.com/Rochet2/TrinityCore/tree/reforging_3.3.5/src/server/scripts/Custom/Reforging + +Adds the possiblity to **reforge** items on 3.3.5a client. Reforging allows players to **decrease** a stat by a certain percentage (default is 40%) and **add** a new stat based on the difference. The allowed stats and the percentage is **configurable** via the mod .conf file. + +## Limitations + +1. Due to the nature of **WOTLK** client, the new **STATS** will only be visible to the owner. **This is only visual, stats will be there nonetheless!** +2. Upgrades will be lost (of course) when trading, sending mail, depositing to guild bank, deposit to auction. +3. Heirlooms can't be reforged. +4. Items with random properties (like "of the Bear", "of Intellect") can't be reforged. + +## How to install + +1. Clone this repository somewhere on your device. +2. Copy mod-reforging to your AzerothCore repo modules folder. +3. Copy reforging.patch to your AzerothCore repo (root level). +4. Open a git command prompt in your AzerothCore repo root and use "patch -p1 < reforging.patch" (no quotes). Ignore any warnings about whitespace if any. +5. Re-run cmake to generate the solution. +6. Re-build your project. +7. You should have mod_reforging.conf.dist copied in configs/modules after building, copy this to your server's binaries folder. +8. Start the server, .sql files should automatically be imported in DB, if not, apply them manually. + +## Ingame usage + +Use .npc add 200004 to spawn the Master Reforger NPC. The rest is self explanatory. + +## Some photos + +![pic1](https://github.com/silviu20092/azerothcore-reforging/blob/master/pics/pic1.jpg?raw=true) +![pic2](https://github.com/silviu20092/azerothcore-reforging/blob/master/pics/pic2.jpg?raw=true) +![pic3](https://github.com/silviu20092/azerothcore-reforging/blob/master/pics/pic3.jpg?raw=true) +![pic4](https://github.com/silviu20092/azerothcore-reforging/blob/master/pics/pic4.jpg?raw=true) +![pic5](https://github.com/silviu20092/azerothcore-reforging/blob/master/pics/pic5.jpg?raw=true) + +## Credits +- silviu20092 \ No newline at end of file diff --git a/mod-reforging/.editorconfig b/mod-reforging/.editorconfig new file mode 100644 index 0000000..eb64e2f --- /dev/null +++ b/mod-reforging/.editorconfig @@ -0,0 +1,8 @@ +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +tab_width = 4 +insert_final_newline = true +trim_trailing_whitespace = true +max_line_length = 80 diff --git a/mod-reforging/.gitattributes b/mod-reforging/.gitattributes new file mode 100644 index 0000000..7ef9001 --- /dev/null +++ b/mod-reforging/.gitattributes @@ -0,0 +1,105 @@ +## AUTO-DETECT +## Handle line endings automatically for files detected as +## text and leave all files detected as binary untouched. +## This will handle all files NOT defined below. +* text=auto eol=lf + +# Text +*.conf text +*.conf.dist text +*.cmake text + +## Scripts +*.sh text +*.fish text +*.lua text + +## SQL +*.sql text + +## C++ +*.c text +*.cc text +*.cxx text +*.cpp text +*.c++ text +*.hpp text +*.h text +*.h++ text +*.hh text + + +## For documentation + +# Documents +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain + +## DOCUMENTATION +*.markdown text +*.md text +*.mdwn text +*.mdown text +*.mkd text +*.mkdn text +*.mdtxt text +*.mdtext text +*.txt text +AUTHORS text +CHANGELOG text +CHANGES text +CONTRIBUTING text +COPYING text +copyright text +*COPYRIGHT* text +INSTALL text +license text +LICENSE text +NEWS text +readme text +*README* text +TODO text + +## GRAPHICS +*.ai binary +*.bmp binary +*.eps binary +*.gif binary +*.ico binary +*.jng binary +*.jp2 binary +*.jpg binary +*.jpeg binary +*.jpx binary +*.jxr binary +*.pdf binary +*.png binary +*.psb binary +*.psd binary +*.svg text +*.svgz binary +*.tif binary +*.tiff binary +*.wbmp binary +*.webp binary + + +## ARCHIVES +*.7z binary +*.gz binary +*.jar binary +*.rar binary +*.tar binary +*.zip binary + +## EXECUTABLES +*.exe binary +*.pyc binary diff --git a/mod-reforging/.github/ISSUE_TEMPLATE/bug_report.yml b/mod-reforging/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..5610d2b --- /dev/null +++ b/mod-reforging/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,72 @@ +name: Bug report +description: Create a bug report to help us improve. +title: "Bug: " +body: + - type: textarea + id: current + attributes: + label: Current Behaviour + description: | + Description of the problem or issue here. + Include entries of affected creatures / items / quests / spells etc. + If this is a crash, post the crashlog (upload to https://gist.github.com/) and include the link here. + Never upload files! Use GIST for text and YouTube for videos! + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected Behaviour + description: | + Tell us what should happen instead. + validations: + required: true + - type: textarea + id: reproduce + attributes: + label: Steps to reproduce the problem + description: | + What does someone else need to do to encounter the same bug? + placeholder: | + 1. Step 1 + 2. Step 2 + 3. Step 3 + validations: + required: true + - type: textarea + id: extra + attributes: + label: Extra Notes + description: | + Do you have any extra notes that can help solve the issue that does not fit any other field? + placeholder: | + None + validations: + required: false + - type: textarea + id: commit + attributes: + label: AC rev. hash/commit + description: | + Copy the result of the `.server debug` command (if you need to run it from the client get a prat addon) + validations: + required: true + - type: input + id: os + attributes: + label: Operating system + description: | + The Operating System the Server is running on. + i.e. Windows 11 x64, Debian 10 x64, macOS 12, Ubuntu 20.04 + validations: + required: true + - type: textarea + id: custom + attributes: + label: Custom changes or Modules + description: | + List which custom changes or modules you have applied, i.e. Eluna module, etc. + placeholder: | + None + validations: + required: false diff --git a/mod-reforging/.github/ISSUE_TEMPLATE/feature_request.yml b/mod-reforging/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..58f79dd --- /dev/null +++ b/mod-reforging/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,33 @@ +name: Feature request +description: Suggest an idea for this project +title: "Feature: " +body: + - type: markdown + attributes: + value: | + Thank you for taking your time to fill out a feature request. Remember to fill out all fields including the title above. + An issue that is not properly filled out will be closed. + - type: textarea + id: description + attributes: + label: Describe your feature request or suggestion in detail + description: | + A clear and concise description of what you want to happen. + validations: + required: true + - type: textarea + id: solution + attributes: + label: Describe a possible solution to your feature or suggestion in detail + description: | + A clear and concise description of any alternative solutions or features you've considered. + validations: + required: false + - type: textarea + id: additional + attributes: + label: Additional context + description: | + Add any other context or screenshots about the feature request here. + validations: + required: false diff --git a/mod-reforging/.github/workflows/core-build.yml b/mod-reforging/.github/workflows/core-build.yml new file mode 100644 index 0000000..a178aed --- /dev/null +++ b/mod-reforging/.github/workflows/core-build.yml @@ -0,0 +1,11 @@ +name: core-build +on: + push: + pull_request: + workflow_dispatch: + +jobs: + build: + uses: azerothcore/reusable-workflows/.github/workflows/core_build_modules.yml@main + with: + module_repo: ${{ github.event.repository.name }} diff --git a/mod-reforging/.github/workflows/core_codestyle.yml b/mod-reforging/.github/workflows/core_codestyle.yml new file mode 100644 index 0000000..3f9a73e --- /dev/null +++ b/mod-reforging/.github/workflows/core_codestyle.yml @@ -0,0 +1,19 @@ +name: Codestyle Checks +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + check-codestyle: + strategy: + fail-fast: false + + runs-on: ubuntu-latest + name: Check Codestyling + steps: + - uses: actions/checkout@v2 + + - name: Check Codestyling + run: source ./apps/ci/ci-codestyle.sh diff --git a/mod-reforging/.gitignore b/mod-reforging/.gitignore new file mode 100644 index 0000000..c6e1299 --- /dev/null +++ b/mod-reforging/.gitignore @@ -0,0 +1,48 @@ +!.gitignore + +# +#Generic +# + +.directory +.mailmap +*.orig +*.rej +*.*~ +.hg/ +*.kdev* +.DS_Store +CMakeLists.txt.user +*.bak +*.patch +*.diff +*.REMOTE.* +*.BACKUP.* +*.BASE.* +*.LOCAL.* + +# +# IDE & other softwares +# +/.settings/ +/.externalToolBuilders/* +# exclude in all levels +nbproject/ +.sync.ffs_db +*.kate-swp + +# +# Eclipse +# +*.pydevproject +.metadata +.gradle +tmp/ +*.tmp +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.project +.cproject diff --git a/mod-reforging/LICENSE b/mod-reforging/LICENSE new file mode 100644 index 0000000..d0882e0 --- /dev/null +++ b/mod-reforging/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 AzerothCore + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/mod-reforging/apps/.gitkeep b/mod-reforging/apps/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/mod-reforging/apps/ci/.gitkeep b/mod-reforging/apps/ci/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/mod-reforging/apps/ci/ci-codestyle.sh b/mod-reforging/apps/ci/ci-codestyle.sh new file mode 100644 index 0000000..c96a31d --- /dev/null +++ b/mod-reforging/apps/ci/ci-codestyle.sh @@ -0,0 +1,40 @@ +#!/bin/bash +set -e + +echo "Codestyle check script:" +echo + +declare -A singleLineRegexChecks=( + ["LOG_.+GetCounter"]="Use ObjectGuid::ToString().c_str() method instead of ObjectGuid::GetCounter() when logging. Check the lines above" + ["[[:blank:]]$"]="Remove whitespace at the end of the lines above" + ["\t"]="Replace tabs with 4 spaces in the lines above" +) + +for check in ${!singleLineRegexChecks[@]}; do + echo " Checking RegEx: '${check}'" + + if grep -P -r -I -n ${check} src; then + echo + echo "${singleLineRegexChecks[$check]}" + exit 1 + fi +done + +declare -A multiLineRegexChecks=( + ["LOG_[^;]+GetCounter"]="Use ObjectGuid::ToString().c_str() method instead of ObjectGuid::GetCounter() when logging. Check the lines above" + ["\n\n\n"]="Multiple blank lines detected, keep only one. Check the files above" +) + +for check in ${!multiLineRegexChecks[@]}; do + echo " Checking RegEx: '${check}'" + + if grep -Pzo -r -I ${check} src; then + echo + echo + echo "${multiLineRegexChecks[$check]}" + exit 1 + fi +done + +echo +echo "Everything looks good" diff --git a/mod-reforging/conf/.gitkeep b/mod-reforging/conf/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/mod-reforging/conf/mod_reforging.conf.dist b/mod-reforging/conf/mod_reforging.conf.dist new file mode 100644 index 0000000..4c8fe73 --- /dev/null +++ b/mod-reforging/conf/mod_reforging.conf.dist @@ -0,0 +1,35 @@ +# +# Credits: silviu20092 +# + +[worldserver] + +######################################## +# Mod Reforging configuration +######################################## +# +# Reforging.Enable +# Description: Enable Reforging module +# Default: 0 - Disabled +# 1 - Enabled +# + +Reforging.Enable = 1 + +# +# Reforging.ReforgeableStats +# Description: Stats that can be reforged. These are usually secondary stats like spirit, hit rating, etc. These correspond +# to ItemModType enum in ItemTemplate.h. Choose a MAXIMUM of 15 stats. +# Default: 6,13,14,31,32,36,37 (Spirit, Dodge Rating, Parry Rating, Hit Rating, Crit Rating, Haste Rating, Expertise Rating) +# + +Reforging.ReforgeableStats = 6,13,14,31,32,36,37 + +# +# Reforging.Percentage +# Description: A number between 10 and 90 which calculates how much from the reforged stat goes into another stat. +# Example: if you reforge 130 Spirit and this percentage is 40, then the new stat will be 52 (40% of 130) +# Default: 40 - 40% of reforged stat goes to another (that's what Blizzard used back when reforging was a thing) +# + +Reforging.Percentage = 40 \ No newline at end of file diff --git a/mod-reforging/data/.gitkeep b/mod-reforging/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/mod-reforging/data/sql/db-auth/base/.gitkeep b/mod-reforging/data/sql/db-auth/base/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/mod-reforging/data/sql/db-auth/updates/.gitkeep b/mod-reforging/data/sql/db-auth/updates/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/mod-reforging/data/sql/db-characters/base/.gitkeep b/mod-reforging/data/sql/db-characters/base/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/mod-reforging/data/sql/db-characters/base/character_reforging.sql b/mod-reforging/data/sql/db-characters/base/character_reforging.sql new file mode 100644 index 0000000..09baf40 --- /dev/null +++ b/mod-reforging/data/sql/db-characters/base/character_reforging.sql @@ -0,0 +1,9 @@ +DROP TABLE IF EXISTS `character_reforging`; +CREATE TABLE `character_reforging`( + `guid` int unsigned not null, + `item_guid` int unsigned not null, + `stat_decrease` int unsigned not null, + `stat_increase` int unsigned not null, + `stat_value` int unsigned not null, + PRIMARY KEY (`item_guid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; \ No newline at end of file diff --git a/mod-reforging/data/sql/db-characters/updates/.gitkeep b/mod-reforging/data/sql/db-characters/updates/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/mod-reforging/data/sql/db-world/base/.gitkeep b/mod-reforging/data/sql/db-world/base/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/mod-reforging/data/sql/db-world/updates/.gitkeep b/mod-reforging/data/sql/db-world/updates/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/mod-reforging/data/sql/db-world/updates/MR_2024_04_07_00.sql b/mod-reforging/data/sql/db-world/updates/MR_2024_04_07_00.sql new file mode 100644 index 0000000..cbc5bc6 --- /dev/null +++ b/mod-reforging/data/sql/db-world/updates/MR_2024_04_07_00.sql @@ -0,0 +1,7 @@ +SET @Entry = 200004; +SET @Name = "Master"; +SET @Subname = "Reforger"; +DELETE FROM `creature_template` WHERE `entry` = @Entry; + +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, 19646, 0, @Name, @Subname, null, 0, 80, 80, 2, 35, 1, 1, 0, 0, 2000, 0, 1, 2147483648, 7, 138936390, 0, 0, 0, '', 0, 1, 0, 0, 1, 0, 0, 'npc_reforger'); \ No newline at end of file diff --git a/mod-reforging/include.sh b/mod-reforging/include.sh new file mode 100644 index 0000000..e69de29 diff --git a/mod-reforging/pull_request_template.md b/mod-reforging/pull_request_template.md new file mode 100644 index 0000000..21c9245 --- /dev/null +++ b/mod-reforging/pull_request_template.md @@ -0,0 +1,25 @@ + + +## Changes Proposed: +- +- + +## Issues Addressed: + +- Closes + +## SOURCE: + + +## Tests Performed: + +- +- + + +## How to Test the Changes: + + +1. +2. +3. diff --git a/mod-reforging/src/mod_reforging_itemscript.cpp b/mod-reforging/src/mod_reforging_itemscript.cpp new file mode 100644 index 0000000..1646660 --- /dev/null +++ b/mod-reforging/src/mod_reforging_itemscript.cpp @@ -0,0 +1,23 @@ +/* + * Credits: silviu20092 + */ + +#include "ScriptMgr.h" +#include "item_reforge.h" + +class mod_reforging_itemscript : public AllItemScript +{ +public: + mod_reforging_itemscript() : AllItemScript("mod_reforging_itemscript") {} + + bool CanItemRemove(Player* player, Item* item) override + { + sItemReforge->RemoveReforge(player, item, true); + return true; + } +}; + +void AddSC_mod_reforging_itemscript() +{ + new mod_reforging_itemscript(); +} diff --git a/mod-reforging/src/mod_reforging_loader.cpp b/mod-reforging/src/mod_reforging_loader.cpp new file mode 100644 index 0000000..98ef1d1 --- /dev/null +++ b/mod-reforging/src/mod_reforging_loader.cpp @@ -0,0 +1,17 @@ +/* + * Credits: silviu20092 + */ + +void AddSC_mod_reforging_worldscript(); +void AddSC_npc_reforger(); +void AddSC_mod_reforging_playerscript(); +void AddSC_mod_reforging_itemscript(); + +void Addmod_reforgingScripts() +{ + AddSC_mod_reforging_worldscript(); + AddSC_npc_reforger(); + AddSC_mod_reforging_playerscript(); + AddSC_mod_reforging_itemscript(); +} + diff --git a/mod-reforging/src/mod_reforging_playerscript.cpp b/mod-reforging/src/mod_reforging_playerscript.cpp new file mode 100644 index 0000000..a883bfa --- /dev/null +++ b/mod-reforging/src/mod_reforging_playerscript.cpp @@ -0,0 +1,54 @@ +/* + * Credits: silviu20092 + */ + +#include "ScriptMgr.h" +#include "DatabaseEnv.h" +#include "Player.h" +#include "item_reforge.h" + +class mod_reforging_playerscript : public PlayerScript +{ +private: + class SendReforgePackets : public BasicEvent + { + public: + SendReforgePackets(Player* player) : player(player) + { + player->m_Events.AddEvent(this, player->m_Events.CalculateTime(DELAY_MS)); + } + + bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) + { + sItemReforge->SendItemPackets(player); + return true; + } + private: + static constexpr uint64 DELAY_MS = 3000; + + Player* player; + }; +public: + mod_reforging_playerscript() : PlayerScript("mod_reforging_playerscript") {} + + void OnAfterMoveItemFromInventory(Player* player, Item* it, uint8 /*bag*/, uint8 /*slot*/, bool /*update*/) override + { + sItemReforge->RemoveReforge(player, it, true); + } + + void OnDeleteFromDB(CharacterDatabaseTransaction trans, uint32 guid) override + { + trans->Append("DELETE FROM character_reforging WHERE guid = {}", guid); + sItemReforge->HandleCharacterRemove(guid); + } + + void OnLogin(Player* player) override + { + new SendReforgePackets(player); + } +}; + +void AddSC_mod_reforging_playerscript() +{ + new mod_reforging_playerscript(); +} diff --git a/mod-reforging/src/mod_reforging_worldscript.cpp b/mod-reforging/src/mod_reforging_worldscript.cpp new file mode 100644 index 0000000..4463fc5 --- /dev/null +++ b/mod-reforging/src/mod_reforging_worldscript.cpp @@ -0,0 +1,36 @@ +/* + * Credits: silviu20092 + */ + +#include "ScriptMgr.h" +#include "Config.h" +#include "item_reforge.h" + +class mod_reforging_worldscript : public WorldScript +{ +public: + mod_reforging_worldscript() : WorldScript("mod_reforging_worldscript") {} + + void OnAfterConfigLoad(bool reload) override + { + if (reload) + sItemReforge->HandleReload(false); + + sItemReforge->SetEnabled(sConfigMgr->GetOption("Reforging.Enable", true)); + sItemReforge->SetReforgeableStats(sConfigMgr->GetOption("Reforging.ReforgeableStats", ItemReforge::DefaultReforgeableStats)); + sItemReforge->SetPercentage(sConfigMgr->GetOption("Reforging.Percentage", ItemReforge::PERCENTAGE_DEFAULT)); + + if (reload) + sItemReforge->HandleReload(true); + } + + void OnBeforeWorldInitialized() override + { + sItemReforge->LoadFromDB(); + } +}; + +void AddSC_mod_reforging_worldscript() +{ + new mod_reforging_worldscript(); +} diff --git a/mod-reforging/src/npc_reforger.cpp b/mod-reforging/src/npc_reforger.cpp new file mode 100644 index 0000000..98df113 --- /dev/null +++ b/mod-reforging/src/npc_reforger.cpp @@ -0,0 +1,321 @@ +/* + * Credits: silviu20092 + */ + +#include "ScriptMgr.h" +#include "ScriptedGossip.h" +#include "Creature.h" +#include "Player.h" +#include "StringConvert.h" +#include "item_reforge.h" + +class npc_reforger : public CreatureScript +{ +private: + std::unordered_map itemMap; + + bool CloseGossip(Player* player, bool retVal = true) + { + CloseGossipMenuFor(player); + return retVal; + } + + bool AddEquipmentSlotMenu(Player* player, Creature* creature) + { + ClearGossipMenuFor(player); + + const std::vector& reforgeableStats = sItemReforge->GetReforgeableStats(); + std::ostringstream oss; + oss << "Reforgeable stats: "; + bool hasStats = false; + for (uint32 i = 0; i < reforgeableStats.size(); i++) + { + hasStats = true; + oss << sItemReforge->StatTypeToString(reforgeableStats[i]); + if (i < reforgeableStats.size() - 1) + oss << ", "; + } + if (!hasStats) + oss << ItemReforge::TextRed("NONE"); + + AddGossipItemFor(player, GOSSIP_ICON_INTERACT_1, oss.str(), GOSSIP_SENDER_MAIN + 1, EQUIPMENT_SLOT_END); + + for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; slot++) + { + Item* item = sItemReforge->GetItemInSlot(player, slot); + std::ostringstream oss; + oss << sItemReforge->GetSlotIcon(slot); + oss << sItemReforge->GetSlotName(slot); + + if (item == nullptr) + oss << " [" << ItemReforge::TextRed("NO ITEM") << "]"; + else + { + if (sItemReforge->IsAlreadyReforged(item)) + oss << " [" << ItemReforge::TextRed("ALREADY REFORGED") << "]"; + else if (!sItemReforge->IsReforgeable(player, item)) + oss << " [" << ItemReforge::TextRed("NOT REFORGEABLE") << "]"; + else + oss << " [" << ItemReforge::TextGreen("REFORGEABLE") << "]"; + } + + AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, oss.str(), GOSSIP_SENDER_MAIN + 1, slot); + } + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, "Go Back", GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF); + + SendGossipMenuFor(player, DEFAULT_GOSSIP_MESSAGE, creature->GetGUID()); + return true; + } + + bool CanAdvanceWithReforging(Player* player, const Item* item) const + { + if (item == nullptr) + { + ItemReforge::SendMessageA(player, "There is no equipped item in that slot."); + return false; + } + else if (sItemReforge->IsAlreadyReforged(item)) + { + ItemReforge::SendMessageA(player, "This item was already reforged."); + return false; + } + else if (!sItemReforge->IsReforgeable(player, item)) + { + ItemReforge::SendMessageA(player, "This item is not reforgeable."); + return false; + } + + return true; + } + + bool AddReforgingMenu(Player* player, Creature* creature) + { + ClearGossipMenuFor(player); + + ObjectGuid itemGuid = itemMap[player->GetGUID().GetCounter()]; + Item* item = player->GetItemByGuid(itemGuid); + if (!CanAdvanceWithReforging(player, item)) + return CloseGossip(player, false); + + AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, ItemReforge::ItemLinkForUI(item, player), GOSSIP_SENDER_MAIN + 2, GOSSIP_ACTION_INFO_DEF + 100); + + std::vector<_ItemStat> itemStats = sItemReforge->LoadItemStatInfo(item, true); + for (const _ItemStat& stat : itemStats) + AddGossipItemFor(player, GOSSIP_ICON_INTERACT_1, "Reforge " + sItemReforge->StatTypeToString(stat.ItemStatType), GOSSIP_SENDER_MAIN + 2, stat.ItemStatType); + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, "Go Back", GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); + + SendGossipMenuFor(player, DEFAULT_GOSSIP_MESSAGE, creature->GetGUID()); + return true; + } + + bool AddReforgingStatsMenu(Player* player, Creature* creature, uint32 stat) + { + ClearGossipMenuFor(player); + + ObjectGuid itemGuid = itemMap[player->GetGUID().GetCounter()]; + Item* item = player->GetItemByGuid(itemGuid); + if (!CanAdvanceWithReforging(player, item)) + return CloseGossip(player, false); + + AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, ItemReforge::ItemLinkForUI(item, player), GOSSIP_SENDER_MAIN + 2, stat); + + const std::vector& reforgeableStats = sItemReforge->GetReforgeableStats(); + std::vector<_ItemStat> itemStats = sItemReforge->LoadItemStatInfo(item); + const _ItemStat* toReforgeStat = sItemReforge->FindItemStat(itemStats, stat); + if (toReforgeStat == nullptr) + return CloseGossip(player, false); + + uint32 taken = sItemReforge->CalculateReforgePct(toReforgeStat->ItemStatValue); + uint32 newVal = toReforgeStat->ItemStatValue - taken; + std::ostringstream oss; + oss << "Will take " << ItemReforge::TextRed(Acore::ToString((uint32)sItemReforge->GetPercentage()) + "%") << " from " << sItemReforge->StatTypeToString(stat); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, oss.str(), GOSSIP_SENDER_MAIN + 2, stat); + + oss.str(""); + oss << sItemReforge->StatTypeToString(stat) << " value after reforge: "; + oss << ItemReforge::TextRed(Acore::ToString(newVal)) << " (-" << Acore::ToString(taken) << ")"; + AddGossipItemFor(player, GOSSIP_ICON_CHAT, oss.str(), GOSSIP_SENDER_MAIN + 2, stat); + + for (const uint32& rstat : reforgeableStats) + { + if (sItemReforge->FindItemStat(itemStats, rstat) != nullptr) + continue; + + AddGossipItemFor(player, GOSSIP_ICON_INTERACT_1, ItemReforge::TextGreen("+" + Acore::ToString(taken) + " " + sItemReforge->StatTypeToString(rstat)), GOSSIP_SENDER_MAIN + 10 + stat, rstat, "Are you sure you want to reforge this item?", 0, false); + } + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, "Go Back", GOSSIP_SENDER_MAIN + 2, GOSSIP_ACTION_INFO_DEF + 100); + + SendGossipMenuFor(player, DEFAULT_GOSSIP_MESSAGE, creature->GetGUID()); + return true; + } + + bool AddRemoveReforgeMenu(Player* player, Creature* creature) + { + ClearGossipMenuFor(player); + + for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; slot++) + { + Item* item = sItemReforge->GetItemInSlot(player, slot); + std::ostringstream oss; + oss << sItemReforge->GetSlotIcon(slot); + oss << sItemReforge->GetSlotName(slot); + + if (item == nullptr) + oss << " [" << ItemReforge::TextRed("NO ITEM") << "]"; + else + { + if (sItemReforge->IsAlreadyReforged(item)) + oss << " [" << ItemReforge::TextGreen("REFORGED") << "]"; + else + oss << " [" << ItemReforge::TextRed("NOT REFORGED") << "]"; + } + + AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, oss.str(), GOSSIP_SENDER_MAIN + 3, slot); + } + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, "Go Back", GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF); + + SendGossipMenuFor(player, DEFAULT_GOSSIP_MESSAGE, creature->GetGUID()); + return true; + } + + bool AddRemoveReforgeStatsMenu(Player* player, Creature* creature) + { + ClearGossipMenuFor(player); + + ObjectGuid itemGuid = itemMap[player->GetGUID().GetCounter()]; + Item* item = player->GetItemByGuid(itemGuid); + if (!sItemReforge->CanRemoveReforge(item)) + return CloseGossip(player, false); + + AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, ItemReforge::ItemLinkForUI(item, player), GOSSIP_SENDER_MAIN + 4, GOSSIP_ACTION_INFO_DEF); + + const ItemReforge::ReforgingData* reforging = sItemReforge->GetReforgingData(item); + if (reforging == nullptr) + return CloseGossip(player, false); + + std::vector<_ItemStat> itemStats = sItemReforge->LoadItemStatInfo(item); + const _ItemStat* decreasedStat = sItemReforge->FindItemStat(itemStats, reforging->stat_decrease); + if (decreasedStat == nullptr) + return CloseGossip(player, false); + + std::ostringstream oss; + oss << "Will restore " << sItemReforge->StatTypeToString(decreasedStat->ItemStatType) << " to " << ItemReforge::TextGreen(Acore::ToString(decreasedStat->ItemStatValue)); + AddGossipItemFor(player, GOSSIP_ICON_INTERACT_1, oss.str(), GOSSIP_SENDER_MAIN + 4, GOSSIP_ACTION_INFO_DEF); + + oss.str(""); + oss << ItemReforge::TextRed("-" + Acore::ToString(reforging->stat_value) + " " + sItemReforge->StatTypeToString(reforging->stat_increase)); + AddGossipItemFor(player, GOSSIP_ICON_INTERACT_1, oss.str(), GOSSIP_SENDER_MAIN + 4, GOSSIP_ACTION_INFO_DEF); + + AddGossipItemFor(player, GOSSIP_ICON_BATTLE, ItemReforge::TextRed("[RESTORE]"), GOSSIP_SENDER_MAIN + 4, GOSSIP_ACTION_INFO_DEF + 1, "Are you sure?", 0, false); + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, "Go Back", GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 3); + + SendGossipMenuFor(player, DEFAULT_GOSSIP_MESSAGE, creature->GetGUID()); + return true; + } +public: + npc_reforger() : CreatureScript("npc_reforger") {} + + bool OnGossipHello(Player* player, Creature* creature) override + { + if (!sItemReforge->GetEnabled()) + AddGossipItemFor(player, GOSSIP_ICON_CHAT, "|cffb50505NOT AVAILABLE|r", GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 2); + else + { + AddGossipItemFor(player, GOSSIP_ICON_BATTLE, "Select equipment slot to reforge", GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); + AddGossipItemFor(player, GOSSIP_ICON_BATTLE, "Remove reforge from items", GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 3); + } + AddGossipItemFor(player, GOSSIP_ICON_CHAT, "Nevermind", GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 2); + SendGossipMenuFor(player, DEFAULT_GOSSIP_MESSAGE, creature->GetGUID()); + return true; + } + + bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) override + { + if (!sItemReforge->GetEnabled()) + return CloseGossip(player); + + if (sender == GOSSIP_SENDER_MAIN) + { + if (action == GOSSIP_ACTION_INFO_DEF) + { + ClearGossipMenuFor(player); + return OnGossipHello(player, creature); + } + else if (action == GOSSIP_ACTION_INFO_DEF + 1) + return AddEquipmentSlotMenu(player, creature); + else if (action == GOSSIP_ACTION_INFO_DEF + 2) + return CloseGossip(player); + else if (action == GOSSIP_ACTION_INFO_DEF + 3) + return AddRemoveReforgeMenu(player, creature); + } + else if (sender == GOSSIP_SENDER_MAIN + 1) + { + uint8 slot = (uint8)action; + if (slot == EQUIPMENT_SLOT_END) + return AddEquipmentSlotMenu(player, creature); + + Item* item = sItemReforge->GetItemInSlot(player, slot); + if (!CanAdvanceWithReforging(player, item)) + return AddEquipmentSlotMenu(player, creature); + else + { + itemMap[player->GetGUID().GetCounter()] = item->GetGUID(); + return AddReforgingMenu(player, creature); + } + } + else if (sender == GOSSIP_SENDER_MAIN + 2) + { + if (action == GOSSIP_ACTION_INFO_DEF + 100) + return AddReforgingMenu(player, creature); + else + return AddReforgingStatsMenu(player, creature, action); + } + else if (sender == GOSSIP_SENDER_MAIN + 3) + { + uint8 slot = (uint8)action; + Item* item = sItemReforge->GetItemInSlot(player, slot); + if (!sItemReforge->CanRemoveReforge(item)) + return AddRemoveReforgeMenu(player, creature); + else + { + itemMap[player->GetGUID().GetCounter()] = item->GetGUID(); + return AddRemoveReforgeStatsMenu(player, creature); + } + } + else if (sender == GOSSIP_SENDER_MAIN + 4) + { + if (action == GOSSIP_ACTION_INFO_DEF) + return AddRemoveReforgeStatsMenu(player, creature); + else + { + if (sItemReforge->RemoveReforge(player, itemMap[player->GetGUID().GetCounter()])) + sItemReforge->VisualFeedback(player); + + return CloseGossip(player); + } + } + else if (sender >= GOSSIP_SENDER_MAIN + 10) + { + uint32 decreaseStat = sender - (GOSSIP_SENDER_MAIN + 10); + uint32 increaseStat = action; + if (!sItemReforge->Reforge(player, itemMap[player->GetGUID().GetCounter()], decreaseStat, increaseStat)) + ItemReforge::SendMessageA(player, "Could not reforge item, try again."); + else + sItemReforge->VisualFeedback(player); + + return CloseGossip(player); + } + + return CloseGossip(player, false); + } +}; + +void AddSC_npc_reforger() +{ + new npc_reforger(); +} diff --git a/pics/pic1.jpg b/pics/pic1.jpg new file mode 100644 index 0000000..882d880 Binary files /dev/null and b/pics/pic1.jpg differ diff --git a/pics/pic2.jpg b/pics/pic2.jpg new file mode 100644 index 0000000..118cf3a Binary files /dev/null and b/pics/pic2.jpg differ diff --git a/pics/pic3.jpg b/pics/pic3.jpg new file mode 100644 index 0000000..996c6ce Binary files /dev/null and b/pics/pic3.jpg differ diff --git a/pics/pic4.jpg b/pics/pic4.jpg new file mode 100644 index 0000000..09fbec8 Binary files /dev/null and b/pics/pic4.jpg differ diff --git a/pics/pic5.jpg b/pics/pic5.jpg new file mode 100644 index 0000000..4128b17 Binary files /dev/null and b/pics/pic5.jpg differ diff --git a/reforging.patch b/reforging.patch new file mode 100644 index 0000000..60a7e83 --- /dev/null +++ b/reforging.patch @@ -0,0 +1,908 @@ +diff --git a/src/server/game/Entities/Item/item_reforge.cpp b/src/server/game/Entities/Item/item_reforge.cpp +new file mode 100644 +index 000000000..cdbb5bd3d +--- /dev/null ++++ b/src/server/game/Entities/Item/item_reforge.cpp +@@ -0,0 +1,739 @@ ++/* ++ * Credits: silviu20092 ++ */ ++ ++#include "DatabaseEnv.h" ++#include "Player.h" ++#include "Chat.h" ++#include "Tokenize.h" ++#include "StringConvert.h" ++#include "item_reforge.h" ++ ++ItemReforge::ItemReforge() ++{ ++ enabled = true; ++ percentage = PERCENTAGE_DEFAULT; ++} ++ ++ItemReforge::~ItemReforge() {} ++ ++/*static*/ ItemReforge* ItemReforge::instance() ++{ ++ static ItemReforge instance; ++ return &instance; ++} ++ ++void ItemReforge::SetEnabled(bool value) ++{ ++ enabled = value; ++} ++ ++bool ItemReforge::GetEnabled() const ++{ ++ return enabled; ++} ++ ++void ItemReforge::SetReforgeableStats(const std::string& stats) ++{ ++ reforgeableStats.clear(); ++ std::vector tokenized = Acore::Tokenize(stats, ',', false); ++ if (tokenized.size() <= MAX_REFORGEABLE_STATS) ++ { ++ std::transform(tokenized.begin(), tokenized.end(), std::back_inserter(reforgeableStats), ++ [](const std::string_view& str) { return *Acore::StringTo(str); }); ++ } ++} ++ ++bool ItemReforge::IsReforgeableStat(uint32 stat) const ++{ ++ std::vector::const_iterator citer = std::find_if(reforgeableStats.begin(), reforgeableStats.end(), [&](const uint32& s) { return s == stat; }); ++ return citer != reforgeableStats.end(); ++} ++ ++const std::vector& ItemReforge::GetReforgeableStats() const ++{ ++ return reforgeableStats; ++} ++ ++void ItemReforge::SetPercentage(float value) ++{ ++ if (value < PERCENTAGE_MIN || value > PERCENTAGE_MAX) ++ percentage = PERCENTAGE_DEFAULT; ++ else ++ percentage = value; ++} ++ ++float ItemReforge::GetPercentage() const ++{ ++ return percentage; ++} ++ ++void ItemReforge::CleanupDB() const ++{ ++ CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); ++ CharacterDatabase.DirectExecute("DELETE FROM character_reforging WHERE guid NOT IN (SELECT guid FROM characters)"); ++ CharacterDatabase.DirectExecute("DELETE FROM character_reforging WHERE item_guid NOT IN (SELECT guid FROM item_instance)"); ++ CharacterDatabase.DirectCommitTransaction(trans); ++} ++ ++void ItemReforge::LoadFromDB() ++{ ++ reforgingDataMap.clear(); ++ ++ CleanupDB(); ++ ++ QueryResult result = CharacterDatabase.Query("SELECT guid, item_guid, stat_decrease, stat_increase, stat_value FROM character_reforging"); ++ if (!result) ++ return; ++ ++ do ++ { ++ Field* fields = result->Fetch(); ++ ++ ReforgingData reforgingData; ++ reforgingData.guid = fields[0].Get(); ++ reforgingData.item_guid = fields[1].Get(); ++ reforgingData.stat_decrease = fields[2].Get(); ++ reforgingData.stat_increase = fields[3].Get(); ++ reforgingData.stat_value = fields[4].Get(); ++ reforgingDataMap[reforgingData.item_guid] = reforgingData; ++ } while (result->NextRow()); ++} ++ ++std::string ItemReforge::GetSlotIcon(uint8 slot, uint32 width, uint32 height, int x, int y) const ++{ ++ std::ostringstream ss; ++ ss << "|TInterface/PaperDoll/"; ++ switch (slot) ++ { ++ case EQUIPMENT_SLOT_HEAD: ++ ss << "UI-PaperDoll-Slot-Head"; ++ break; ++ case EQUIPMENT_SLOT_NECK: ++ ss << "UI-PaperDoll-Slot-Neck"; ++ break; ++ case EQUIPMENT_SLOT_SHOULDERS: ++ ss << "UI-PaperDoll-Slot-Shoulder"; ++ break; ++ case EQUIPMENT_SLOT_BODY: ++ ss << "UI-PaperDoll-Slot-Shirt"; ++ break; ++ case EQUIPMENT_SLOT_CHEST: ++ ss << "UI-PaperDoll-Slot-Chest"; ++ break; ++ case EQUIPMENT_SLOT_WAIST: ++ ss << "UI-PaperDoll-Slot-Waist"; ++ break; ++ case EQUIPMENT_SLOT_LEGS: ++ ss << "UI-PaperDoll-Slot-Legs"; ++ break; ++ case EQUIPMENT_SLOT_FEET: ++ ss << "UI-PaperDoll-Slot-Feet"; ++ break; ++ case EQUIPMENT_SLOT_WRISTS: ++ ss << "UI-PaperDoll-Slot-Wrists"; ++ break; ++ case EQUIPMENT_SLOT_HANDS: ++ ss << "UI-PaperDoll-Slot-Hands"; ++ break; ++ case EQUIPMENT_SLOT_FINGER1: ++ case EQUIPMENT_SLOT_FINGER2: ++ ss << "UI-PaperDoll-Slot-Finger"; ++ break; ++ case EQUIPMENT_SLOT_TRINKET1: ++ case EQUIPMENT_SLOT_TRINKET2: ++ ss << "UI-PaperDoll-Slot-Trinket"; ++ break; ++ case EQUIPMENT_SLOT_BACK: ++ ss << "UI-PaperDoll-Slot-Chest"; ++ break; ++ case EQUIPMENT_SLOT_MAINHAND: ++ ss << "UI-PaperDoll-Slot-MainHand"; ++ break; ++ case EQUIPMENT_SLOT_OFFHAND: ++ ss << "UI-PaperDoll-Slot-SecondaryHand"; ++ break; ++ case EQUIPMENT_SLOT_RANGED: ++ ss << "UI-PaperDoll-Slot-Ranged"; ++ break; ++ case EQUIPMENT_SLOT_TABARD: ++ ss << "UI-PaperDoll-Slot-Tabard"; ++ break; ++ default: ++ ss << "UI-Backpack-EmptySlot"; ++ break; ++ } ++ ss << ":" << width << ":" << height << ":" << x << ":" << y << "|t"; ++ return ss.str(); ++} ++ ++std::string ItemReforge::GetSlotName(uint8 slot) const ++{ ++ 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 "Finger 1"; ++ case EQUIPMENT_SLOT_FINGER2: ++ return "Finger 2"; ++ case EQUIPMENT_SLOT_TRINKET1: ++ return "Trinket 1"; ++ case EQUIPMENT_SLOT_TRINKET2: ++ return "Trinket 2"; ++ case EQUIPMENT_SLOT_BACK: ++ return "Back"; ++ case EQUIPMENT_SLOT_MAINHAND: ++ return "Main Hand"; ++ case EQUIPMENT_SLOT_OFFHAND: ++ return "Off Hand"; ++ case EQUIPMENT_SLOT_RANGED: ++ return "Ranged"; ++ case EQUIPMENT_SLOT_TABARD: ++ return "Tabard"; ++ default: ++ return "Unknown"; ++ } ++} ++ ++std::string ItemReforge::StatTypeToString(uint32 statType) const ++{ ++ static std::unordered_map statTypeToStrMap = { ++ {ITEM_MOD_MANA, "Mana"}, {ITEM_MOD_HEALTH, "Health"}, {ITEM_MOD_AGILITY, "Agility"}, ++ {ITEM_MOD_STRENGTH, "Strength"}, {ITEM_MOD_INTELLECT, "Intellect"}, {ITEM_MOD_SPIRIT, "Spirit"}, ++ {ITEM_MOD_STAMINA, "Stamina"}, {ITEM_MOD_DEFENSE_SKILL_RATING, "Defense Rating"}, {ITEM_MOD_DODGE_RATING, "Dodge Rating"}, ++ {ITEM_MOD_PARRY_RATING, "Parry Rating"}, {ITEM_MOD_BLOCK_RATING, "Block Rating"}, {ITEM_MOD_HIT_MELEE_RATING, "Melee Hit Rating"}, ++ {ITEM_MOD_HIT_RANGED_RATING, "Ranged Hit Rating"}, {ITEM_MOD_HIT_SPELL_RATING, "Spell Hit Rating"}, {ITEM_MOD_CRIT_MELEE_RATING, "Melee Crit Rating"}, ++ {ITEM_MOD_CRIT_RANGED_RATING, "Ranged Crit Rating"}, {ITEM_MOD_CRIT_SPELL_RATING, "Spell Crit Rating"}, {ITEM_MOD_HIT_TAKEN_MELEE_RATING, "Melee Hit Taken Rating"}, ++ {ITEM_MOD_HIT_TAKEN_RANGED_RATING, "Ranged Hit Taken Rating"}, {ITEM_MOD_HIT_TAKEN_SPELL_RATING, "Spell Hit Taken Rating"}, {ITEM_MOD_CRIT_TAKEN_MELEE_RATING, "Melee Crit Taken Rating"}, ++ {ITEM_MOD_CRIT_TAKEN_RANGED_RATING, "Ranged Crit Taken Rating"}, {ITEM_MOD_CRIT_TAKEN_SPELL_RATING, "Spell Crit Taken Rating"}, {ITEM_MOD_HASTE_MELEE_RATING, "Melee Haste Rating"}, ++ {ITEM_MOD_HASTE_RANGED_RATING, "Ranged Haste Rating"}, {ITEM_MOD_HASTE_SPELL_RATING, "Spell Haste Rating"}, {ITEM_MOD_HIT_RATING, "Hit Rating"}, ++ {ITEM_MOD_CRIT_RATING, "Crit Rating"}, {ITEM_MOD_HIT_TAKEN_RATING, "Hit Taken Rating"}, {ITEM_MOD_CRIT_TAKEN_RATING, "Crit Taken Rating"}, ++ {ITEM_MOD_RESILIENCE_RATING, "Resilience Rating"}, {ITEM_MOD_HASTE_RATING, "Haste Rating"}, {ITEM_MOD_EXPERTISE_RATING, "Expertise"}, ++ {ITEM_MOD_ATTACK_POWER, "Attack Power"}, {ITEM_MOD_RANGED_ATTACK_POWER, "Ranged Attack Power"}, {ITEM_MOD_MANA_REGENERATION, "Mana Regen"}, ++ {ITEM_MOD_ARMOR_PENETRATION_RATING, "Armor Penetration"}, {ITEM_MOD_SPELL_POWER, "Spell Power"}, {ITEM_MOD_HEALTH_REGEN, "HP Regen"}, ++ {ITEM_MOD_SPELL_PENETRATION, "Spell Penetration"}, {ITEM_MOD_BLOCK_VALUE, "Block Value"} ++ }; ++ ++ if (statTypeToStrMap.find(statType) != statTypeToStrMap.end()) ++ return statTypeToStrMap.at(statType); ++ ++ return "unknown"; ++} ++ ++bool ItemReforge::IsReforgeable(const Player* player, const Item* item) const ++{ ++ if (!item || !item->IsEquipped()) ++ return false; ++ ++ if (item->GetOwnerGUID() != player->GetGUID()) ++ return false; ++ ++ if (reforgeableStats.empty()) ++ return false; ++ ++ const ItemTemplate* proto = item->GetTemplate(); ++ if (!proto->StatsCount || proto->StatsCount >= MAX_ITEM_PROTO_STATS) ++ return false; ++ ++ if (proto->Quality > ITEM_QUALITY_LEGENDARY) ++ return false; ++ ++ if (IsAlreadyReforged(item)) ++ return false; ++ ++ for (uint32 i = 0; i < proto->StatsCount; i++) ++ { ++ if (!IsReforgeableStat(proto->ItemStat[i].ItemStatType)) ++ continue; ++ if (CalculateReforgePct(proto->ItemStat[i].ItemStatValue) >= 1) ++ return true; ++ } ++ ++ return false; ++} ++ ++bool ItemReforge::IsAlreadyReforged(const Item* item) const ++{ ++ return reforgingDataMap.find(item->GetGUID().GetCounter()) != reforgingDataMap.end(); ++} ++ ++Item* ItemReforge::GetItemInSlot(const Player* player, uint8 slot) const ++{ ++ return player->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); ++} ++ ++uint32 ItemReforge::CalculateReforgePct(int32 value) const ++{ ++ if (value <= 0) ++ return 0; ++ ++ return (uint32)(std::floorf((float)value * (GetPercentage() / 100.0f))); ++} ++ ++std::vector<_ItemStat> ItemReforge::LoadItemStatInfo(const Item* item, bool onlyReforgeable) const ++{ ++ std::vector<_ItemStat> statInfo; ++ ItemTemplate const* proto = item->GetTemplate(); ++ ++ for (uint8 i = 0; i < MAX_ITEM_PROTO_STATS; ++i) ++ { ++ if (i >= proto->StatsCount) ++ continue; ++ ++ uint32 statType = proto->ItemStat[i].ItemStatType; ++ int32 statValue = proto->ItemStat[i].ItemStatValue; ++ if (statValue <= 0) ++ continue; ++ ++ if (onlyReforgeable && !IsReforgeableStat(statType)) ++ continue; ++ ++ _ItemStat stat; ++ stat.ItemStatType = statType; ++ stat.ItemStatValue = statValue; ++ statInfo.push_back(stat); ++ } ++ ++ return statInfo; ++} ++ ++const _ItemStat* ItemReforge::FindItemStat(const std::vector<_ItemStat>& stats, uint32 statType) const ++{ ++ std::vector<_ItemStat>::const_iterator citer = std::find_if(stats.begin(), stats.end(), [&](const _ItemStat& stat) { return stat.ItemStatType == statType; }); ++ if (citer != stats.end()) ++ return &*citer; ++ ++ return nullptr; ++} ++ ++bool ItemReforge::Reforge(Player* player, ObjectGuid itemGuid, uint32 statDecrease, uint32 statIncrease) ++{ ++ Item* item = player->GetItemByGuid(itemGuid); ++ if (!IsReforgeable(player, item)) ++ return false; ++ ++ std::vector<_ItemStat> itemStats = LoadItemStatInfo(item); ++ const _ItemStat* decreasedStat = FindItemStat(itemStats, statDecrease); ++ if (decreasedStat == nullptr) ++ return false; ++ ++ if (FindItemStat(itemStats, statIncrease) != nullptr) ++ return false; ++ ++ player->_ApplyItemMods(item, item->GetSlot(), false); ++ ++ uint32 value = CalculateReforgePct(decreasedStat->ItemStatValue); ++ ReforgingData reforgingData; ++ reforgingData.guid = player->GetGUID().GetCounter(); ++ reforgingData.item_guid = item->GetGUID().GetCounter(); ++ reforgingData.stat_decrease = statDecrease; ++ reforgingData.stat_increase = statIncrease; ++ reforgingData.stat_value = value; ++ reforgingDataMap[reforgingData.item_guid] = reforgingData; ++ ++ player->_ApplyItemMods(item, item->GetSlot(), true); ++ ++ CharacterDatabase.Execute("INSERT INTO character_reforging (guid, item_guid, stat_decrease, stat_increase, stat_value) VALUES ({}, {}, {}, {}, {})", ++ reforgingData.guid, reforgingData.item_guid, statDecrease, statIncrease, value); ++ ++ SendItemPacket(player, item); ++ ++ return true; ++} ++ ++const ItemReforge::ReforgingData* ItemReforge::GetReforgingData(const Item* item) const ++{ ++ if (!GetEnabled()) ++ return nullptr; ++ ++ if (IsAlreadyReforged(item)) ++ return &reforgingDataMap.at(item->GetGUID().GetCounter()); ++ ++ return nullptr; ++} ++ ++std::vector ItemReforge::GetPlayerItems(const Player* player, bool inBankAlso) const ++{ ++ std::vector items; ++ for (uint8 i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; i++) ++ if (Item* item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) ++ items.push_back(item); ++ ++ for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; i++) ++ if (Bag* bag = player->GetBagByPos(i)) ++ for (uint32 j = 0; j < bag->GetBagSize(); j++) ++ if (Item* item = player->GetItemByPos(i, j)) ++ items.push_back(item); ++ ++ for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; i++) ++ if (Item* item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) ++ items.push_back(item); ++ ++ if (inBankAlso) ++ { ++ for (uint8 i = BANK_SLOT_ITEM_START; i < BANK_SLOT_ITEM_END; i++) ++ if (Item* item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) ++ items.push_back(item); ++ ++ for (uint8 i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; i++) ++ if (Bag* bag = player->GetBagByPos(i)) ++ for (uint32 j = 0; j < bag->GetBagSize(); j++) ++ if (Item* item = player->GetItemByPos(i, j)) ++ items.push_back(item); ++ } ++ ++ return items; ++} ++ ++bool ItemReforge::CanRemoveReforge(const Item* item) const ++{ ++ if (!item || !item->IsEquipped()) ++ return false; ++ ++ return IsAlreadyReforged(item); ++} ++ ++bool ItemReforge::RemoveReforge(Player* player, ObjectGuid itemGuid) ++{ ++ return RemoveReforge(player, player->GetItemByGuid(itemGuid)); ++} ++ ++bool ItemReforge::RemoveReforge(Player* player, Item* item, bool force) ++{ ++ if (!force && !CanRemoveReforge(item)) ++ return false; ++ ++ player->_ApplyItemMods(item, item->GetSlot(), false); ++ reforgingDataMap.erase(item->GetGUID().GetCounter()); ++ player->_ApplyItemMods(item, item->GetSlot(), true); ++ ++ CharacterDatabase.Execute("DELETE FROM character_reforging WHERE item_guid = {}", item->GetGUID().GetCounter()); ++ ++ SendItemPacket(player, item); ++ ++ return true; ++} ++ ++void ItemReforge::VisualFeedback(Player* player) ++{ ++ player->CastSpell(player, VISUAL_FEEDBACK_SPELL_ID, true); ++} ++ ++void ItemReforge::HandleCharacterRemove(uint32 guid) ++{ ++ for (auto it = reforgingDataMap.begin(); it != reforgingDataMap.end(); ) ++ { ++ if (it->second.guid == guid) ++ it = reforgingDataMap.erase(it); ++ else ++ ++it; ++ } ++} ++ ++void ItemReforge::SendItemPacket(Player* player, const Item* item) const ++{ ++ ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(item->GetEntry()); ++ std::string Name = pProto->Name1; ++ std::string Description = pProto->Description; ++ ++ int loc_idx = player->GetSession()->GetSessionDbLocaleIndex(); ++ if (loc_idx >= 0) ++ { ++ if (ItemLocale const* il = sObjectMgr->GetItemLocale(pProto->ItemId)) ++ { ++ ObjectMgr::GetLocaleString(il->Name, loc_idx, Name); ++ ObjectMgr::GetLocaleString(il->Description, loc_idx, Description); ++ } ++ } ++ // guess size ++ WorldPacket queryData(SMSG_ITEM_QUERY_SINGLE_RESPONSE, 600); ++ queryData << pProto->ItemId; ++ queryData << pProto->Class; ++ queryData << pProto->SubClass; ++ queryData << pProto->SoundOverrideSubclass; ++ queryData << Name; ++ queryData << uint8(0x00); //pProto->Name2; // blizz not send name there, just uint8(0x00); <-- \0 = empty string = empty name... ++ queryData << uint8(0x00); //pProto->Name3; // blizz not send name there, just uint8(0x00); ++ queryData << uint8(0x00); //pProto->Name4; // blizz not send name there, just uint8(0x00); ++ queryData << pProto->DisplayInfoID; ++ queryData << pProto->Quality; ++ queryData << pProto->Flags; ++ queryData << pProto->Flags2; ++ queryData << pProto->BuyPrice; ++ queryData << pProto->SellPrice; ++ queryData << pProto->InventoryType; ++ queryData << pProto->AllowableClass; ++ queryData << pProto->AllowableRace; ++ queryData << pProto->ItemLevel; ++ queryData << pProto->RequiredLevel; ++ queryData << pProto->RequiredSkill; ++ queryData << pProto->RequiredSkillRank; ++ queryData << pProto->RequiredSpell; ++ queryData << pProto->RequiredHonorRank; ++ queryData << pProto->RequiredCityRank; ++ queryData << pProto->RequiredReputationFaction; ++ queryData << pProto->RequiredReputationRank; ++ queryData << int32(pProto->MaxCount); ++ queryData << int32(pProto->Stackable); ++ queryData << pProto->ContainerSlots; ++ const ReforgingData* reforgingData = GetReforgingData(item); ++ if (reforgingData == nullptr) ++ { ++ queryData << pProto->StatsCount; ++ for (uint32 i = 0; i < pProto->StatsCount; ++i) ++ { ++ queryData << pProto->ItemStat[i].ItemStatType; ++ queryData << pProto->ItemStat[i].ItemStatValue; ++ } ++ } ++ else ++ { ++ queryData << pProto->StatsCount + 1; ++ for (uint32 i = 0; i < pProto->StatsCount; ++i) ++ { ++ uint32 statType = pProto->ItemStat[i].ItemStatType; ++ queryData << statType; ++ if (reforgingData->stat_decrease == statType) ++ queryData << pProto->ItemStat[i].ItemStatValue - reforgingData->stat_value; ++ else ++ queryData << pProto->ItemStat[i].ItemStatValue; ++ } ++ ++ queryData << reforgingData->stat_increase; ++ queryData << (int32)reforgingData->stat_value; ++ } ++ ++ queryData << pProto->ScalingStatDistribution; // scaling stats distribution ++ queryData << pProto->ScalingStatValue; // some kind of flags used to determine stat values column ++ for (int i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i) ++ { ++ queryData << pProto->Damage[i].DamageMin; ++ queryData << pProto->Damage[i].DamageMax; ++ queryData << pProto->Damage[i].DamageType; ++ } ++ ++ // resistances (7) ++ queryData << pProto->Armor; ++ queryData << pProto->HolyRes; ++ queryData << pProto->FireRes; ++ queryData << pProto->NatureRes; ++ queryData << pProto->FrostRes; ++ queryData << pProto->ShadowRes; ++ queryData << pProto->ArcaneRes; ++ ++ queryData << pProto->Delay; ++ queryData << pProto->AmmoType; ++ queryData << 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; ++ ++ queryData << pProto->Spells[s].SpellId; ++ queryData << pProto->Spells[s].SpellTrigger; ++ queryData << int32(pProto->Spells[s].SpellCharges); ++ ++ if (db_data) ++ { ++ queryData << uint32(pProto->Spells[s].SpellCooldown); ++ queryData << uint32(pProto->Spells[s].SpellCategory); ++ queryData << uint32(pProto->Spells[s].SpellCategoryCooldown); ++ } ++ else ++ { ++ queryData << uint32(spell->RecoveryTime); ++ queryData << uint32(spell->GetCategory()); ++ queryData << uint32(spell->CategoryRecoveryTime); ++ } ++ } ++ else ++ { ++ queryData << uint32(0); ++ queryData << uint32(0); ++ queryData << uint32(0); ++ queryData << uint32(-1); ++ queryData << uint32(0); ++ queryData << uint32(-1); ++ } ++ } ++ queryData << pProto->Bonding; ++ queryData << Description; ++ queryData << pProto->PageText; ++ queryData << pProto->LanguageID; ++ queryData << pProto->PageMaterial; ++ queryData << pProto->StartQuest; ++ queryData << pProto->LockID; ++ queryData << int32(pProto->Material); ++ queryData << pProto->Sheath; ++ queryData << pProto->RandomProperty; ++ queryData << pProto->RandomSuffix; ++ queryData << pProto->Block; ++ queryData << pProto->ItemSet; ++ queryData << pProto->MaxDurability; ++ queryData << pProto->Area; ++ queryData << pProto->Map; // Added in 1.12.x & 2.0.1 client branch ++ queryData << pProto->BagFamily; ++ queryData << pProto->TotemCategory; ++ for (int s = 0; s < MAX_ITEM_PROTO_SOCKETS; ++s) ++ { ++ queryData << pProto->Socket[s].Color; ++ queryData << pProto->Socket[s].Content; ++ } ++ queryData << pProto->socketBonus; ++ queryData << pProto->GemProperties; ++ queryData << pProto->RequiredDisenchantSkill; ++ queryData << pProto->ArmorDamageModifier; ++ queryData << pProto->Duration; // added in 2.4.2.8209, duration (seconds) ++ queryData << pProto->ItemLimitCategory; // WotLK, ItemLimitCategory ++ queryData << pProto->HolidayId; // Holiday.dbc? ++ player->GetSession()->SendPacket(&queryData); ++} ++ ++void ItemReforge::SendItemPackets(Player* player) const ++{ ++ std::vector items = GetPlayerItems(player, true); ++ std::vector::const_iterator itr = items.begin(); ++ for (itr; itr != items.end(); ++itr) ++ SendItemPacket(player, *itr); ++} ++ ++void ItemReforge::HandleReload(Player* player, bool apply) const ++{ ++ std::vector playerItems = GetPlayerItems(player, true); ++ std::vector::iterator iter = playerItems.begin(); ++ for (iter; iter != playerItems.end(); ++iter) ++ { ++ Item* item = *iter; ++ if (apply) ++ SendItemPacket(player, item); ++ ++ if (!item->IsEquipped()) ++ continue; ++ ++ player->_ApplyItemMods(item, item->GetSlot(), apply); ++ } ++} ++ ++void ItemReforge::HandleReload(bool apply) const ++{ ++ const SessionMap& sessions = sWorld->GetAllSessions(); ++ SessionMap::const_iterator itr; ++ for (itr = sessions.begin(); itr != sessions.end(); ++itr) ++ if (itr->second && itr->second->GetPlayer() && itr->second->GetPlayer()->IsInWorld()) ++ HandleReload(itr->second->GetPlayer(), apply); ++} ++ ++/*static*/ void ItemReforge::SendMessage(Player* player, const std::string& message) ++{ ++ ChatHandler(player->GetSession()).SendSysMessage(message); ++} ++ ++/*static*/ std::string ItemReforge::TextWithColor(const std::string& text, const std::string& color) ++{ ++ return "|cff" + color + text + "|r"; ++} ++ ++/*static*/ std::string ItemReforge::TextRed(const std::string& text) ++{ ++ return TextWithColor(text, RED_COLOR); ++} ++ ++/*static*/ std::string ItemReforge::TextGreen(const std::string& text) ++{ ++ return TextWithColor(text, GREEN_COLOR); ++} ++ ++/*static*/ std::string ItemReforge::ItemIcon(const ItemTemplate* proto, uint32 width, uint32 height, int x, int y) ++{ ++ std::ostringstream ss; ++ ss << "|TInterface"; ++ const ItemDisplayInfoEntry* dispInfo = nullptr; ++ if (proto) ++ { ++ dispInfo = sItemDisplayInfoStore.LookupEntry(proto->DisplayInfoID); ++ if (dispInfo) ++ ss << "/ICONS/" << dispInfo->inventoryIcon; ++ } ++ if (!dispInfo) ++ ss << "/InventoryItems/WoWUnknownItem01"; ++ ss << ":" << width << ":" << height << ":" << x << ":" << y << "|t"; ++ return ss.str(); ++} ++ ++/*static*/ std::string ItemReforge::ItemNameWithLocale(const Player* player, const ItemTemplate* itemTemplate, int32 randomPropertyId) ++{ ++ LocaleConstant loc_idx = player->GetSession()->GetSessionDbLocaleIndex(); ++ std::string name = itemTemplate->Name1; ++ if (ItemLocale const* il = sObjectMgr->GetItemLocale(itemTemplate->ItemId)) ++ ObjectMgr::GetLocaleString(il->Name, loc_idx, name); ++ ++ std::array const* suffix = nullptr; ++ if (randomPropertyId < 0) ++ { ++ if (const ItemRandomSuffixEntry* itemRandEntry = sItemRandomSuffixStore.LookupEntry(-randomPropertyId)) ++ suffix = &itemRandEntry->Name; ++ } ++ else ++ { ++ if (const ItemRandomPropertiesEntry* itemRandEntry = sItemRandomPropertiesStore.LookupEntry(randomPropertyId)) ++ suffix = &itemRandEntry->Name; ++ } ++ if (suffix) ++ { ++ std::string_view test((*suffix)[(name != itemTemplate->Name1) ? loc_idx : DEFAULT_LOCALE]); ++ if (!test.empty()) ++ { ++ name += ' '; ++ name += test; ++ } ++ } ++ ++ return name; ++} ++ ++/*static*/ std::string ItemReforge::ItemLink(const Player* player, const ItemTemplate* itemTemplate, int32 randomPropertyId) ++{ ++ std::stringstream oss; ++ oss << "|c"; ++ oss << std::hex << ItemQualityColors[itemTemplate->Quality] << std::dec; ++ oss << "|Hitem:"; ++ oss << itemTemplate->ItemId; ++ oss << ":0:0:0:0:0:0:0:0:0|h["; ++ oss << ItemNameWithLocale(player, itemTemplate, randomPropertyId); ++ oss << "]|h|r"; ++ ++ return oss.str(); ++} ++ ++/*static*/ std::string ItemReforge::ItemLinkForUI(const Item* item, const Player* player) ++{ ++ const ItemTemplate* proto = item->GetTemplate(); ++ std::ostringstream oss; ++ oss << ItemIcon(proto); ++ oss << ItemLink(player, proto, item->GetItemRandomPropertyId()); ++ return oss.str(); ++} +diff --git a/src/server/game/Entities/Item/item_reforge.h b/src/server/game/Entities/Item/item_reforge.h +new file mode 100644 +index 000000000..609fb090f +--- /dev/null ++++ b/src/server/game/Entities/Item/item_reforge.h +@@ -0,0 +1,94 @@ ++/* ++ * Credits: silviu20092 ++ */ ++ ++#ifndef _ITEM_REFORGE_H_ ++#define _ITEM_REFORGE_H_ ++ ++#include "Define.h" ++ ++class ItemReforge ++{ ++public: ++ struct ReforgingData ++ { ++ uint32 guid; ++ uint32 item_guid; ++ uint32 stat_decrease; ++ uint32 stat_increase; ++ uint32 stat_value; ++ }; ++private: ++ static constexpr float PERCENTAGE_MIN = 10.0f; ++ static constexpr float PERCENTAGE_MAX = 90.0f; ++ static constexpr const char* RED_COLOR = "b50505"; ++ static constexpr const char* GREEN_COLOR = "056e3a"; ++ static constexpr uint32 MAX_REFORGEABLE_STATS = 15; ++ ++ bool enabled; ++ std::vector reforgeableStats; ++ float percentage; ++ ++ ItemReforge(); ++ ~ItemReforge(); ++ ++ typedef std::unordered_map ReforgingDataContainer; ++ ++ ReforgingDataContainer reforgingDataMap; ++ ++ void CleanupDB() const; ++ ++ static std::string TextWithColor(const std::string& text, const std::string& color); ++public: ++ static constexpr const char* DefaultReforgeableStats = "6,13,14,31,32,36,37"; ++ static constexpr float PERCENTAGE_DEFAULT = 40.0f; ++ static constexpr int VISUAL_FEEDBACK_SPELL_ID = 46331; ++ ++ static ItemReforge* instance(); ++ ++ void SetEnabled(bool value); ++ bool GetEnabled() const; ++ void SetReforgeableStats(const std::string& stats); ++ bool IsReforgeableStat(uint32 stat) const; ++ const std::vector& GetReforgeableStats() const; ++ void SetPercentage(float value); ++ float GetPercentage() const; ++ ++ void LoadFromDB(); ++ ++ std::string GetSlotIcon(uint8 slot, uint32 width = 30, uint32 height = 30, int x = 0, int y = 0) const; ++ std::string GetSlotName(uint8 slot) const; ++ std::string StatTypeToString(uint32 statType) const; ++ ++ bool IsReforgeable(const Player* player, const Item* item) const; ++ bool IsAlreadyReforged(const Item* item) const; ++ Item* GetItemInSlot(const Player* player, uint8 slot) const; ++ uint32 CalculateReforgePct(int32 value) const; ++ std::vector<_ItemStat> LoadItemStatInfo(const Item* item, bool onlyReforgeable = false) const; ++ const _ItemStat* FindItemStat(const std::vector<_ItemStat>& stats, uint32 statType) const; ++ ++ bool Reforge(Player* player, ObjectGuid itemGuid, uint32 statDecrease, uint32 statIncrease); ++ void SendItemPacket(Player* player, const Item* item) const; ++ void SendItemPackets(Player* player) const; ++ void HandleReload(Player* player, bool apply) const; ++ void HandleReload(bool apply) const; ++ const ReforgingData* GetReforgingData(const Item* item) const; ++ std::vector GetPlayerItems(const Player* player, bool inBankAlso) const; ++ bool CanRemoveReforge(const Item* item) const; ++ bool RemoveReforge(Player* player, ObjectGuid itemGuid); ++ bool RemoveReforge(Player* player, Item* item, bool force = false); ++ void VisualFeedback(Player* player); ++ void HandleCharacterRemove(uint32 guid); ++ ++ static void SendMessage(Player* player, const std::string& message); ++ static std::string TextRed(const std::string& text); ++ static std::string TextGreen(const std::string& text); ++ static std::string ItemIcon(const ItemTemplate* proto, uint32 width = 30, uint32 height = 30, int x = 0, int y = 0); ++ static std::string ItemNameWithLocale(const Player* player, const ItemTemplate* itemTemplate, int32 randomPropertyId); ++ static std::string ItemLink(const Player* player, const ItemTemplate* itemTemplate, int32 randomPropertyId); ++ static std::string ItemLinkForUI(const Item* item, const Player* player); ++}; ++ ++#define sItemReforge ItemReforge::instance() ++ ++#endif +diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp +index 10140ff94..a5c3f34c1 100644 +--- a/src/server/game/Entities/Player/Player.cpp ++++ b/src/server/game/Entities/Player/Player.cpp +@@ -91,6 +91,8 @@ + // see: https://github.com/azerothcore/azerothcore-wotlk/issues/9766 + #include "GridNotifiersImpl.h" + ++#include "item_reforge.h" ++ + enum CharacterFlags + { + CHARACTER_FLAG_NONE = 0x00000000, +@@ -6553,6 +6555,15 @@ void Player::_ApplyItemBonuses(ItemTemplate const* proto, uint8 slot, bool apply + if (only_level_scale && !ssv) + return; + ++ uint32 statCount = proto->StatsCount; ++ const ItemReforge::ReforgingData* reforging = nullptr; ++ if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) ++ { ++ reforging = sItemReforge->GetReforgingData(item); ++ if (reforging != nullptr) ++ statCount++; ++ } ++ + for (uint8 i = 0; i < MAX_ITEM_PROTO_STATS; ++i) + { + uint32 statType = 0; +@@ -6579,11 +6590,30 @@ 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 (reforging == nullptr) ++ { ++ statType = proto->ItemStat[i].ItemStatType; ++ val = proto->ItemStat[i].ItemStatValue; ++ } ++ else ++ { ++ if (i == statCount - 1) ++ { ++ statType = reforging->stat_increase; ++ val = reforging->stat_value; ++ } ++ else ++ { ++ statType = proto->ItemStat[i].ItemStatType; ++ val = proto->ItemStat[i].ItemStatValue; ++ ++ if (statType == reforging->stat_decrease) ++ val -= reforging->stat_value; ++ } ++ } + } + + if (val == 0)