Initial commit.

This commit is contained in:
郑佩茹
2022-03-11 14:36:23 -07:00
commit 13cb5945f0
16 changed files with 615 additions and 0 deletions

8
.editorconfig Normal file
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
.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

49
.github/workflows/core-build.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
name: core-build
on:
push:
pull_request:
jobs:
build:
strategy:
fail-fast: false
matrix:
compiler: [clang]
runs-on: ubuntu-latest
name: ${{ matrix.compiler }}
env:
COMPILER: ${{ matrix.compiler }}
steps:
- uses: actions/checkout@v2
with:
repository: 'azerothcore/azerothcore-wotlk'
ref: 'master'
submodules: 'recursive'
- uses: actions/checkout@v2
with:
submodules: 'recursive'
path: 'modules/skeleton-module'
- name: Cache
uses: actions/cache@v2
with:
path: /home/runner/.ccache
key: ccache:${{ matrix.compiler }}:${{ github.ref }}:${{ github.sha }}
restore-keys: |
ccache:${{ matrix.compiler }}:${{ github.ref }}
ccache:${{ matrix.compiler }}
- name: Configure OS
run: source ./acore.sh install-deps
env:
CONTINUOUS_INTEGRATION: true
- name: Create conf/config.sh
run: source ./apps/ci/ci-conf.sh
- name: Import db
run: source ./apps/ci/ci-import-db.sh
- name: Build
run: source ./apps/ci/ci-compile.sh
- name: Dry run
run: source ./apps/ci/ci-worldserver-dry-run.sh
- name: Check startup errors
run: source ./apps/ci/ci-error-check.sh
- name: Run unit tests
run: source ./apps/ci/ci-run-unit-tests.sh

48
.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
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 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.

32
conf/conf.sh.dist Normal file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env bash
## CUSTOM SQL - Important file used by the db_assembler.sh
## Keep only the required variables (base sql files or updates, depending on the DB)
## BASE SQL
DB_AUTH_CUSTOM_PATHS+=(
"$MOD_SKELETON_ROOT/sql/auth/base/"
)
DB_CHARACTERS_CUSTOM_PATHS+=(
"$MOD_SKELETON_ROOT/sql/characters/base/"
)
DB_WORLD_CUSTOM_PATHS+=(
"$MOD_SKELETON_ROOT/sql/world/base/"
)
## UPDATES
DB_AUTH_UPDATES_PATHS+=(
"$MOD_SKELETON_ROOT/sql/auth/updates/"
)
DB_CHARACTERS_UPDATES_PATHS+=(
"$MOD_SKELETON_ROOT/sql/characters/updates/"
)
DB_WORLD_UPDATES_PATHS+=(
"$MOD_SKELETON_ROOT/sql/world/updates/"
)

View File

@@ -0,0 +1,17 @@
#
# Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3
#
[worldserver]
########################################
# Reagent Bank Config
########################################
#
# ReagentBank.Enable
# Description: Enable the reagent bank
# Default: 0 - Disabled
# 1 - Enabled
#
ReagentBank.Enable = 1

10
include.sh Normal file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env bash
## GETS THE CURRENT MODULE ROOT DIRECTORY
MOD_SKELETON_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )/" && pwd )"
source $MOD_SKELETON_ROOT"/conf/conf.sh.dist"
if [ -f $MOD_SKELETON_ROOT"/conf/conf.sh" ]; then
source $MOD_SKELETON_ROOT"/conf/conf.sh"
fi

View File

View File

@@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS `custom_reagent_bank` (
`character_id` int(11) NOT NULL,
`item_entry` int(11) NOT NULL,
`item_subclass` int(11) NOT NULL,
`amount` int(11) NOT NULL,
PRIMARY KEY (`character_id`,`item_entry`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

View File

View File

@@ -0,0 +1,7 @@
SET
@Entry = 190011,
@Name = "Ling";
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, 15965, 0, @Name, 'Reagent Banker', NULL, 0, 6, 6, 0, 35, 1, 1, 0, 0, 2000, 0, 1, 0, 7, 138412032, 0, 0, 0, '', 0, 1, 0, 0, 1, 0, 2, 'npc_reagent_banker');

View File

271
src/ReagentBank.cpp Normal file
View File

@@ -0,0 +1,271 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3
*/
#include "ReagentBank.h"
// Add player scripts
class npc_reagent_banker : public CreatureScript
{
private:
std::string GetItemLink(uint32 entry, WorldSession* session) const
{
int loc_idx = session->GetSessionDbLocaleIndex();
const ItemTemplate *temp = sObjectMgr->GetItemTemplate(entry);
std::string name = temp->Name1;
if (ItemLocale const* il = sObjectMgr->GetItemLocale(temp->ItemId))
ObjectMgr::GetLocaleString(il->Name, loc_idx, name);
std::ostringstream oss;
oss << "|c" << std::hex << ItemQualityColors[temp->Quality] << std::dec <<
"|Hitem:" << temp->ItemId << ":" <<
(uint32)0 << "|h[" << name << "]|h|r";
return oss.str();
}
std::string GetItemIcon(uint32 entry, uint32 width, uint32 height, int x, int y) const
{
std::ostringstream ss;
ss << "|TInterface";
const ItemTemplate *temp = sObjectMgr->GetItemTemplate(entry);
const ItemDisplayInfoEntry *dispInfo = NULL;
if (temp)
{
dispInfo = sItemDisplayInfoStore.LookupEntry(temp->DisplayInfoID);
if (dispInfo)
ss << "/ICONS/" << dispInfo->inventoryIcon;
}
if (!dispInfo)
ss << "/InventoryItems/WoWUnknownItem01";
ss << ":" << width << ":" << height << ":" << x << ":" << y << "|t";
return ss.str();
}
void WithdrawItem(Player* player, uint32 entry)
{
// This query can be changed to async to improve performance, but there will be some visual bugs because the query will not be done executing when the menu refreshes
WorldSession *session = player->GetSession();
std::string query = "SELECT amount FROM custom_reagent_bank WHERE character_id = " + std::to_string(player->GetSession()->GetAccountId()) + " AND item_entry = " + std::to_string(entry);
QueryResult result = CharacterDatabase.Query("SELECT amount FROM custom_reagent_bank WHERE character_id = " + std::to_string(player->GetSession()->GetAccountId()) + " AND item_entry = " + std::to_string(entry));
if (result)
{
uint32 storedAmount = (*result)[0].Get<uint32>();
const ItemTemplate *temp = sObjectMgr->GetItemTemplate(entry);
uint32 stackSize = temp->GetMaxStackSize();
if (storedAmount <= stackSize)
{
// Give the player all of the item and remove it from the DB
ItemPosCountVec dest;
InventoryResult msg = player->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, entry, storedAmount);
if (msg == EQUIP_ERR_OK)
{
CharacterDatabase.Execute("DELETE FROM custom_reagent_bank WHERE character_id = {} AND item_entry = {}", player->GetGUID().GetCounter(), entry);
Item* item = player->StoreNewItem(dest, entry, true);
player->SendNewItem(item, storedAmount, true, false);
}
else
{
player->SendEquipError(msg, nullptr, nullptr, entry);
return;
}
}
else
{
// Give the player a single stack
ItemPosCountVec dest;
InventoryResult msg = player->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, entry, stackSize);
if (msg == EQUIP_ERR_OK)
{
CharacterDatabase.Execute("UPDATE custom_reagent_bank SET amount = {} WHERE character_id = {} AND item_entry = {}", storedAmount - stackSize, player->GetGUID().GetCounter(), entry);
Item* item = player->StoreNewItem(dest, entry, true);
player->SendNewItem(item, stackSize, true, false);
}
else
{
player->SendEquipError(msg, nullptr, nullptr, entry);
return;
}
}
}
}
void UpdateItemCount(std::map<uint32, uint32> &entryToAmountMap, std::map<uint32, uint32> &entryToSubclassMap, Item* pItem, Player* player, uint32 bagSlot, uint32 itemSlot)
{
uint32 count = pItem->GetCount();
ItemTemplate const *itemTemplate = pItem->GetTemplate();
if (itemTemplate->Class != ITEM_CLASS_TRADE_GOODS || itemTemplate->GetMaxStackSize() == 1)
return;
uint32 itemEntry = itemTemplate->ItemId;
uint32 itemSubclass = itemTemplate->SubClass;
if (!entryToAmountMap.count(itemEntry))
{
// Item does not exist yet in storage
entryToAmountMap[itemEntry] = count;
entryToSubclassMap[itemEntry] = itemSubclass;
}
else
{
entryToAmountMap[itemEntry] = entryToAmountMap.find(itemEntry)->second + count;
}
// The item counts have been updated, remove the original items from the player
player->DestroyItem(bagSlot, itemSlot, true);
}
void DepositAllReagents(Player* player) {
WorldSession *session = player->GetSession();
std::string query = "SELECT item_entry, item_subclass, amount FROM custom_reagent_bank WHERE character_id = " + std::to_string(player->GetGUID().GetCounter());
session->GetQueryProcessor().AddCallback( CharacterDatabase.AsyncQuery(query).WithCallback([=](QueryResult result) {
std::map<uint32, uint32> entryToAmountMap;
std::map<uint32, uint32> entryToSubclassMap;
if (result)
{
do {
uint32 itemEntry = (*result)[0].Get<uint32>();
uint32 itemSubclass = (*result)[1].Get<uint32>();
uint32 itemAmount = (*result)[2].Get<uint32>();
entryToAmountMap[itemEntry] = itemAmount;
entryToSubclassMap[itemEntry] = itemSubclass;
} while (result->NextRow());
}
// Inventory Items
for (uint8 i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i)
{
if (Item* pItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
UpdateItemCount(entryToAmountMap, entryToSubclassMap, pItem, player, INVENTORY_SLOT_BAG_0, i);
}
}
// Bag Items
for (uint32 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; i++)
{
Bag* bag = player->GetBagByPos(i);
if (!bag)
continue;
for (uint32 j = 0; j < bag->GetBagSize(); j++) {
if (Item * pItem = player->GetItemByPos(i, j))
{
UpdateItemCount(entryToAmountMap, entryToSubclassMap, pItem, player, i, j);
}
}
}
if (entryToAmountMap.size() != 0)
{
auto trans = CharacterDatabase.BeginTransaction();
for (std::pair<uint32, uint32> mapEntry : entryToAmountMap)
{
uint32 itemEntry = mapEntry.first;
uint32 itemAmount = mapEntry.second;
uint32 itemSubclass = entryToSubclassMap.find(itemEntry)->second;
trans->Append("REPLACE INTO custom_reagent_bank (character_id, item_entry, item_subclass, amount) VALUES ({}, {}, {}, {})", player->GetGUID().GetCounter(), itemEntry, itemSubclass, itemAmount);
}
CharacterDatabase.CommitTransaction(trans);
}
}));
ChatHandler(player->GetSession()).PSendSysMessage("All reagents deposited successfully.");
CloseGossipMenuFor(player);
}
public:
npc_reagent_banker() : CreatureScript("npc_reagent_banker") { }
bool OnGossipHello(Player* player, Creature* creature) override
{
AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, GetItemIcon(4359, 30, 30, -18, 0) + "Parts", ITEM_SUBCLASS_PARTS, 0);
AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, GetItemIcon(4358, 30, 30, -18, 0) + "Explosives", ITEM_SUBCLASS_EXPLOSIVES, 0);
AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, GetItemIcon(4388, 30, 30, -18, 0) + "Devices", ITEM_SUBCLASS_DEVICES, 0);
AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, GetItemIcon(1206, 30, 30, -18, 0) + "Jewelcrafting", ITEM_SUBCLASS_JEWELCRAFTING, 0);
AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, GetItemIcon(2589, 30, 30, -18, 0) + "Cloth", ITEM_SUBCLASS_CLOTH, 0);
AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, GetItemIcon(2318, 30, 30, -18, 0) + "Leather", ITEM_SUBCLASS_LEATHER, 0);
AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, GetItemIcon(2772, 30, 30, -18, 0) + "Metal & Stone", ITEM_SUBCLASS_METAL_STONE, 0);
AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, GetItemIcon(12208, 30, 30, -18, 0) + "Meat", ITEM_SUBCLASS_MEAT, 0);
AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, GetItemIcon(2453, 30, 30, -18, 0) + "Herb", ITEM_SUBCLASS_HERB, 0);
AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, GetItemIcon(7068, 30, 30, -18, 0) + "Elemental", ITEM_SUBCLASS_ELEMENTAL, 0);
AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, GetItemIcon(10940, 30, 30, -18, 0) + "Enchanting", ITEM_SUBCLASS_ENCHANTING, 0);
AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, GetItemIcon(23572, 30, 30, -18, 0) + "Nether Material", ITEM_SUBCLASS_MATERIAL, 0);
AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, GetItemIcon(2604, 30, 30, -18, 0) + "Other Trade Goods", ITEM_SUBCLASS_TRADE_GOODS_OTHER, 0);
AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, GetItemIcon(38682, 30, 30, -18, 0) + "Armor Vellum", ITEM_SUBCLASS_ARMOR_ENCHANTMENT, 0);
AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, GetItemIcon(39349, 30, 30, -18, 0) + "Weapon Vellum", ITEM_SUBCLASS_WEAPON_ENCHANTMENT, 0);
AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, "Deposit All Reagents", DEPOSIT_ALL_REAGENTS, 0);
SendGossipMenuFor(player, NPC_TEXT_ID, creature->GetGUID());
return true;
}
bool OnGossipSelect(Player* player, Creature* creature, uint32 item_subclass, uint32 gossipPageNumber) override
{
player->PlayerTalkClass->ClearMenus();
if (item_subclass > MAX_PAGE_NUMBER)
{
// item_subclass is actually an item ID to withdraw
// Get the actual item subclass from the template
const ItemTemplate *temp = sObjectMgr->GetItemTemplate(item_subclass);
WithdrawItem(player, item_subclass);
ShowReagentItems(player, creature, temp->SubClass, gossipPageNumber);
return true;
}
if (item_subclass == DEPOSIT_ALL_REAGENTS)
{
DepositAllReagents(player);
return true;
}
else if (item_subclass == MAIN_MENU)
{
OnGossipHello(player, creature);
return true;
}
else
{
ShowReagentItems(player, creature, item_subclass, gossipPageNumber);
return true;
}
}
void ShowReagentItems(Player* player, Creature* creature, uint32 item_subclass, uint16 gossipPageNumber)
{
WorldSession* session = player->GetSession();
std::string query = "SELECT item_entry, amount FROM custom_reagent_bank WHERE character_id = " + std::to_string(player->GetSession()->GetAccountId()) + " AND item_subclass = " +
std::to_string(item_subclass) + " ORDER BY item_entry";
session->GetQueryProcessor().AddCallback(CharacterDatabase.AsyncQuery(query).WithCallback([=](QueryResult result)
{
uint32 startValue = (gossipPageNumber * (MAX_OPTIONS));
uint32 endValue = (gossipPageNumber + 1) * (MAX_OPTIONS) - 1;
std::map<uint32, uint32> entryToAmountMap;
std::vector<uint32> itemEntries;
if (result) {
do {
uint32 itemEntry = (*result)[0].Get<uint32>();
uint32 itemAmount = (*result)[1].Get<uint32>();
entryToAmountMap[itemEntry] = itemAmount;
itemEntries.push_back(itemEntry);
} while (result->NextRow());
}
for (uint32 i = startValue; i <= endValue; i++)
{
if (itemEntries.empty() || i > itemEntries.size() - 1)
{
break;
}
uint32 itemEntry = itemEntries.at(i);
AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, GetItemIcon(itemEntry, 30, 30, -18, 0) + GetItemLink(itemEntry, session) + " (" + std::to_string(entryToAmountMap.find(itemEntry)->second) + ")", itemEntry, gossipPageNumber);
}
if (gossipPageNumber > 0)
{
AddGossipItemFor(player, GOSSIP_ICON_CHAT, "Previous Page", item_subclass, gossipPageNumber - 1);
}
if (endValue < entryToAmountMap.size())
{
AddGossipItemFor(player, GOSSIP_ICON_CHAT, "Next Page", item_subclass, gossipPageNumber + 1);
}
AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, "|TInterface/ICONS/Ability_Spy:30:30:-18:0|tBack...", MAIN_MENU, 0);
SendGossipMenuFor(player, NPC_TEXT_ID, creature->GetGUID());
}));
}
};
// Add all scripts in one
void AddSC_mod_reagent_bank()
{
new npc_reagent_banker();
}

25
src/ReagentBank.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef AZEROTHCORE_REAGENTBANK_H
#define AZEROTHCORE_REAGENTBANK_H
#include "ScriptMgr.h"
#include "Player.h"
#include "Config.h"
#include "Chat.h"
#include "ScriptedCreature.h"
#include "ScriptedGossip.h"
#include "Item.h"
#include "ItemTemplate.h"
#include <map>
#define MAX_OPTIONS 23
#define MAX_PAGE_NUMBER 700 // Values higher than this are considered Item IDs
#define NPC_TEXT_ID 4259 // Pre-existing NPC text
enum GossipItemType : uint8
{
DEPOSIT_ALL_REAGENTS = 16,
MAIN_MENU = 17
};
#endif //AZEROTHCORE_REAGENTBANK_H

View File

@@ -0,0 +1,15 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3
*/
// From SC
void AddSC_mod_reagent_bank();
// Add all
// cf. the naming convention https://github.com/azerothcore/azerothcore-wotlk/blob/master/doc/changelog/master.md#how-to-upgrade-4
// additionally replace all '-' in the module folder name with '_' here
void Addmod_reagent_bankScripts()
{
AddSC_mod_reagent_bank();
}