Initialize repo

This commit is contained in:
silviu20092
2024-04-07 20:33:57 +03:00
commit 8ce4fed2e2
36 changed files with 1834 additions and 0 deletions

42
README.md Normal file
View File

@@ -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

View File

@@ -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

105
mod-reforging/.gitattributes vendored Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 }}

View File

@@ -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

48
mod-reforging/.gitignore vendored Normal file
View File

@@ -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

21
mod-reforging/LICENSE Normal file
View File

@@ -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.

View File

View File

View File

@@ -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"

View File

View File

@@ -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

View File

View File

@@ -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;

View File

@@ -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');

0
mod-reforging/include.sh Normal file
View File

View File

@@ -0,0 +1,25 @@
<!-- First of all, THANK YOU for your contribution. -->
## Changes Proposed:
-
-
## Issues Addressed:
<!-- If your fix has a relating issue, link it below -->
- Closes
## SOURCE:
<!-- If you can, include a source that can strengthen your claim -->
## Tests Performed:
<!-- Does it build without errors? Did you test in-game? What did you test? On which OS did you test? Describe any other tests performed -->
-
-
## How to Test the Changes:
<!-- Describe in a detailed step-by-step order how to test the changes -->
1.
2.
3.

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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<bool>("Reforging.Enable", true));
sItemReforge->SetReforgeableStats(sConfigMgr->GetOption<std::string>("Reforging.ReforgeableStats", ItemReforge::DefaultReforgeableStats));
sItemReforge->SetPercentage(sConfigMgr->GetOption<float>("Reforging.Percentage", ItemReforge::PERCENTAGE_DEFAULT));
if (reload)
sItemReforge->HandleReload(true);
}
void OnBeforeWorldInitialized() override
{
sItemReforge->LoadFromDB();
}
};
void AddSC_mod_reforging_worldscript()
{
new mod_reforging_worldscript();
}

View File

@@ -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<uint32, ObjectGuid> itemMap;
bool CloseGossip(Player* player, bool retVal = true)
{
CloseGossipMenuFor(player);
return retVal;
}
bool AddEquipmentSlotMenu(Player* player, Creature* creature)
{
ClearGossipMenuFor(player);
const std::vector<uint32>& 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<uint32>& 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();
}

BIN
pics/pic1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 KiB

BIN
pics/pic2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 KiB

BIN
pics/pic3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 KiB

BIN
pics/pic4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 724 KiB

BIN
pics/pic5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 KiB

908
reforging.patch Normal file
View File

@@ -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<std::string_view> 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<uint32>(str); });
+ }
+}
+
+bool ItemReforge::IsReforgeableStat(uint32 stat) const
+{
+ std::vector<uint32>::const_iterator citer = std::find_if(reforgeableStats.begin(), reforgeableStats.end(), [&](const uint32& s) { return s == stat; });
+ return citer != reforgeableStats.end();
+}
+
+const std::vector<uint32>& 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<uint32>();
+ reforgingData.item_guid = fields[1].Get<uint32>();
+ reforgingData.stat_decrease = fields[2].Get<uint32>();
+ reforgingData.stat_increase = fields[3].Get<uint32>();
+ reforgingData.stat_value = fields[4].Get<uint32>();
+ 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<uint32, std::string> 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<Item*> ItemReforge::GetPlayerItems(const Player* player, bool inBankAlso) const
+{
+ std::vector<Item*> 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<Item*> items = GetPlayerItems(player, true);
+ std::vector<Item*>::const_iterator itr = items.begin();
+ for (itr; itr != items.end(); ++itr)
+ SendItemPacket(player, *itr);
+}
+
+void ItemReforge::HandleReload(Player* player, bool apply) const
+{
+ std::vector<Item*> playerItems = GetPlayerItems(player, true);
+ std::vector<Item*>::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<char const*, 16> 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<uint32> reforgeableStats;
+ float percentage;
+
+ ItemReforge();
+ ~ItemReforge();
+
+ typedef std::unordered_map<uint32, ReforgingData> 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<uint32>& 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<Item*> 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)