Game
1. char.cpp
Add:
#ifdef ENABLE_ITEMSHOP
#include "itemshop.h"
#endif
Search:
void CHARACTER::Initialize()
Add to the end of this function:
#ifdef ENABLE_ITEMSHOP
m_iItemshopCooldownTime = 0;
m_iPromotionCodeCooldownTime = 0;
#endif
Add to end of file:
#ifdef ENABLE_ITEMSHOP
void CHARACTER::SendPromotionRewardPacket(BYTE byAnswer, std::vector<TPromotionItemTable> items)
{
TEMP_BUFFER buf;
TPacketGCItemshopInfo itemshop_info;
itemshop_info.header = HEADER_GC_ITEMSHOP;
itemshop_info.subheader = SUBHEADER_PROMOTION_CODE_REWARDS;
buf.write(&itemshop_info, sizeof(TPacketGCItemshopInfo));
buf.write(&byAnswer, sizeof(BYTE));
size_t size = items.size();
buf.write(&size, sizeof(size_t));
buf.write(items.data(), sizeof(TPromotionItemTable) * size);
GetDesc()->Packet(buf.read_peek(), buf.size());
}
void CHARACTER::SendItemshopSingleItemRefreshPacket(TItemshopItemTable* item)
{
TEMP_BUFFER buf;
TPacketGCItemshopInfo itemshop_info;
itemshop_info.header = HEADER_GC_ITEMSHOP;
itemshop_info.subheader = SUBHEADER_ITEMSHOP_REFRESH_SINGLE_ITEM;
buf.write(&itemshop_info, sizeof(TPacketGCItemshopInfo));
buf.write(item, sizeof(TItemshopItemTable));
GetDesc()->Packet(buf.read_peek(), buf.size());
}
void CHARACTER::SendItemshopSingleItemRemovePacket(TItemshopItemTable* item)
{
TEMP_BUFFER buf;
TPacketGCItemshopInfo itemshop_info;
itemshop_info.header = HEADER_GC_ITEMSHOP;
itemshop_info.subheader = SUBHEADER_ITEMSHOP_REMOVE_SINGLE_ITEM;
buf.write(&itemshop_info, sizeof(TPacketGCItemshopInfo));
buf.write(item, sizeof(TItemshopItemTable));
GetDesc()->Packet(buf.read_peek(), buf.size());
}
void CHARACTER::SendItemshopSingleItemAddPacket(TItemshopItemTable* item, TItemshopCategoryInfo category)
{
TEMP_BUFFER buf;
TPacketGCItemshopInfo itemshop_info;
itemshop_info.header = HEADER_GC_ITEMSHOP;
itemshop_info.subheader = SUBHEADER_ITEMSHOP_ADD_SINGLE_ITEM;
buf.write(&itemshop_info, sizeof(TPacketGCItemshopInfo));
buf.write(&category, sizeof(TItemshopCategoryInfo));
buf.write(item, sizeof(TItemshopItemTable));
GetDesc()->Packet(buf.read_peek(), buf.size());
}
void CHARACTER::SendItemshopItemsPacket(const std::unordered_map <BYTE, std::vector<TItemshopItemTable>> items, const std::unordered_map <BYTE, TItemshopCategoryInfo> categories)
{
SendItemshopCoinPacket();
TEMP_BUFFER buf;
TPacketGCItemshopInfo itemshop_info;
itemshop_info.header = HEADER_GC_ITEMSHOP;
itemshop_info.subheader = SUBHEADER_ITEMSHOP_REFRESH_ITEMS;
buf.write(&itemshop_info, sizeof(TPacketGCItemshopInfo));
TPacketGCitemshopCategorySize itemshop_categorie_size;
itemshop_categorie_size.size = categories.size();
buf.write(&itemshop_categorie_size, sizeof(TPacketGCitemshopCategorySize));
for (const auto& [key, val] : categories)
{
buf.write(&key, sizeof(BYTE));
buf.write(&val, sizeof(TItemshopCategoryInfo));
}
TPacketGCItemshopItemSize itemshop_item_size;
itemshop_item_size.size = items.size();
buf.write(&itemshop_item_size, sizeof(TPacketGCItemshopItemSize));
size_t temp = 0;
for (const auto& [key, vec] : items) {
buf.write(&key, sizeof(BYTE));
temp = vec.size();
buf.write(&temp, sizeof(size_t));
buf.write(vec.data(), sizeof(TItemshopItemTable) * temp);
}
GetDesc()->Packet(buf.read_peek(), buf.size());
}
void CHARACTER::SendItemshopCoinPacket()
{
const TAccountTable& accTable = GetDesc()->GetAccountTable();
unsigned long long ulltotalCoins = 0;
std::unique_ptr<SQLMsg> pkMsg(DBManager::instance().DirectQuery("SELECT coins from account.account WHERE id = %ld", accTable.id));
SQLResult* pRes = pkMsg->Get();
if (pRes->uiNumRows > 0)
{
MYSQL_ROW data = mysql_fetch_row(pRes->pSQLResult);
str_to_number(ulltotalCoins, data[0]);
}
TEMP_BUFFER buf;
TPacketGCItemshopInfo itemshop_info;
itemshop_info.header = HEADER_GC_ITEMSHOP;
itemshop_info.subheader = SUBHEADER_ITEMSHOP_REFRESH_COINS;
buf.write(&itemshop_info, sizeof(TPacketGCItemshopInfo));
buf.write(&ulltotalCoins, sizeof(unsigned long long));
GetDesc()->Packet(buf.read_peek(), buf.size());
}
#endif
2. char.h
Add to end of class CHARACTER :
#ifdef ENABLE_ITEMSHOP
public:
void SendItemshopItemsPacket(
const std::unordered_map <BYTE, std::vector<TItemshopItemTable>> items,
const std::unordered_map <BYTE, TItemshopCategoryInfo> categories
);
void SendPromotionRewardPacket(BYTE byAnswer, std::vector<TPromotionItemTable>);
void SendItemshopSingleItemRefreshPacket(TItemshopItemTable* item);
void SendItemshopSingleItemRemovePacket(TItemshopItemTable* item);
void SendItemshopSingleItemAddPacket(TItemshopItemTable* item, TItemshopCategoryInfo category);
void SendItemshopCoinPacket();
bool CanUseItemshop() const { return m_iItemshopCooldownTime < thecore_pulse(); }
void SetItemshopCooldown(int pulse) { m_iItemshopCooldownTime = pulse; }
bool CanUsePromotionCode() const { return m_iPromotionCodeCooldownTime < thecore_pulse(); }
void SetPromotionCodedown(int pulse) { m_iPromotionCodeCooldownTime = pulse; }
protected:
int m_iItemshopCooldownTime;
int m_iPromotionCodeCooldownTime;
#endif
3. character_manager.cpp
Add:
#ifdef ENABLE_ITEMSHOP
#include "itemshop.h"
#endif
Add to end of file:
#ifdef ENABLE_ITEMSHOP
void CHARACTER_MANAGER::SendItemshopSingleRemoveItem(TItemshopItemTable* item)
{
for (const auto& [player_id, ch] : m_map_pkChrByPID)
{
if (!ch)
continue;
ch->SendItemshopSingleItemRemovePacket(item);
}
}
void CHARACTER_MANAGER::SendItemshopSingleAddItem(TItemshopItemTable* item)
{
const auto& categories = CItemshopManager::instance().GetItemshopCategories();
const auto& category_info = categories.find(item->byCategory);
if (category_info == categories.end())
{
// This could NEVER happen but it seems like it does
return;
}
for (const auto& [player_id, ch] : m_map_pkChrByPID)
{
if (!ch)
continue;
ch->SendItemshopSingleItemAddPacket(item, category_info->second);
}
}
void CHARACTER_MANAGER::SendItemshopSingleItemRefresh(TItemshopItemTable* item)
{
for (const auto& [player_id, ch] : m_map_pkChrByPID)
{
if (!ch)
continue;
ch->SendItemshopSingleItemRefreshPacket(item);
}
}
void CHARACTER_MANAGER::SendItemshopItems()
{
const auto& items = CItemshopManager::instance().GetItemshopItems();
const auto& categories = CItemshopManager::instance().GetItemshopCategories();
for (const auto& [player_id, ch] : m_map_pkChrByPID)
{
if (!ch)
continue;
ch->SendItemshopItemsPacket(items, categories);
}
}
#endif
4. character_manager.h
Add to end of class CHARACTER_MANAGER:
#ifdef ENABLE_ITEMSHOP
public:
void SendItemshopSingleRemoveItem(TItemshopItemTable* item);
void SendItemshopSingleAddItem(TItemshopItemTable* item);
void SendItemshopSingleItemRefresh(TItemshopItemTable* item);
void SendItemshopItems();
#endif
5. cmd_gm.cpp
Search:
case 'p':
ch->ChatPacket(CHAT_TYPE_INFO, "Reloading prototype tables,");
db_clientdesc->DBPacket(HEADER_GD_RELOAD_PROTO, 0, NULL, 0);
break;
Add:
#ifdef ENABLE_ITEMSHOP
case 'i':
ch->ChatPacket(CHAT_TYPE_INFO, "Reloading itemshop tables,");
db_clientdesc->DBPacket(HEADER_GD_RELOAD_ITEMSHOP, 0, NULL, 0);
break;
#endif
6. input.h
Search:
void Refine(LPCHARACTER ch, const char* c_pData);
Add:
#ifdef ENABLE_ITEMSHOP
void BuyItemshopItem(LPCHARACTER ch, const char* c_pData);
void RedeemPromotionCode(LPCHARACTER ch, const char* c_pData);
#endif
Search:
protected:
DWORD m_dwHandle;
Add:
#ifdef ENABLE_ITEMSHOP
protected:
void ReloadItemshop(const char* c_pData);
void ItemshopBuyAnswer(LPDESC d, const char* c_pData);
void RedeemPromotionCode(LPDESC d, const char* c_pData);
#endif
7. input_db.cpp
Add:
#ifdef ENABLE_ITEMSHOP
#include "itemshop.h"
#endif
Search:
if (decode_2bytes(data) != sizeof(TRefineTable))
{
sys_err("refine table size error");
thecore_shutdown();
return;
}
data += 2;
size = decode_2bytes(data);
data += 2;
sys_log(0, "BOOT: REFINE: %d", size);
if (size)
{
CRefineManager::instance().Initialize((TRefineTable*) data, size);
data += size * sizeof(TRefineTable);
}
Add:
#ifdef ENABLE_ITEMSHOP
if (decode_2bytes(data) != sizeof(TItemshopCategoryTable))
{
sys_err("itemshop table category size error");
thecore_shutdown();
return;
}
data += 2;
size = decode_2bytes(data);
data += 2;
sys_log(0, "BOOT: ITEMSHOP: %d", size);
if (size)
{
CItemshopManager::instance().InitializeCategories((TItemshopCategoryTable*)data, size);
data += size * sizeof(TItemshopCategoryTable);
}
if (decode_2bytes(data) != sizeof(TItemshopItemTable))
{
sys_err("itemshop item table size error");
thecore_shutdown();
return;
}
data += 2;
size = decode_2bytes(data);
data += 2;
sys_log(0, "BOOT: ITEMSHOP: %d", size);
if (size)
{
CItemshopManager::instance().InitializeItems((TItemshopItemTable*)data, size);
data += size * sizeof(TItemshopItemTable);
}
#endif
Search:
case HEADER_DG_RELOAD_PROTO:
ReloadProto(c_pData);
break;
Add:
#ifdef ENABLE_ITEMSHOP
case HEADER_DG_RELOAD_ITEMSHOP:
ReloadItemshop(c_pData);
break;
case HEADER_DG_REFRESH_ITEMSHOP_SINGLE_ITEM:
CItemshopManager::instance().RefreshSingleItem((TItemshopItemTable*)c_pData);
break;
case HEADER_DG_REMOVE_ITEMSHOP_SINGLE_ITEM:
CItemshopManager::instance().RemoveSingleItem((TItemshopItemTable*)c_pData);
break;
case HEADER_DG_ADD_ITEMSHOP_SINGLE_ITEM:
CItemshopManager::instance().AddSingleItem((TItemshopItemTable*)c_pData);
break;
case HEADER_DG_BUY_ITEMSHOP_ITEM:
ItemshopBuyAnswer(DESC_MANAGER::instance().FindByHandle(m_dwHandle), c_pData);
break;
case HEADER_DG_PROMOTION_CODE_REDEEM:
RedeemPromotionCode(DESC_MANAGER::instance().FindByHandle(m_dwHandle), c_pData);
break;
#endif
Add to end of file:
#ifdef ENABLE_ITEMSHOP
void CInputDB::ItemshopBuyAnswer(LPDESC d, const char* c_pData)
{
if (!d)
return;
if (!d->GetCharacter())
return;
TItemshopBuyAnswer* buy_answer = (TItemshopBuyAnswer*)c_pData;
LPCHARACTER ch = d->GetCharacter();
if (buy_answer->canBuy)
{
CItemshopManager::instance().BuyItem(ch, buy_answer->code, buy_answer->wCount);
}
else
{
ch->ChatPacket(CHAT_TYPE_INFO, "cannot buy");
}
}
void CInputDB::RedeemPromotionCode(LPDESC d, const char* c_pData)
{
if (!d)
return;
if (!d->GetCharacter())
return;
TPromotionRedeemAnswer* redeem_answer = (TPromotionRedeemAnswer*)c_pData;
LPCHARACTER ch = d->GetCharacter();
std::vector <TPromotionItemTable> itemsGiven;
if (redeem_answer->byRedeemAnswer == REDEEM_SUCCESS)
{
TPromotionItemTable* rewards = (TPromotionItemTable*)(c_pData + sizeof(TPromotionRedeemAnswer));
for (int i = 0; i < redeem_answer->reward_count; ++i, ++rewards)
{
const TItemTable* item_table = ITEM_MANAGER::instance().GetTable(rewards->dwVnum);
if (item_table)
{
// NOTE: Stupid admin i guess
if (rewards->wCount > 1 && ((IS_SET(item_table->dwAntiFlags, ITEM_ANTIFLAG_STACK)) || !(IS_SET(item_table->dwFlags, ITEM_FLAG_STACKABLE))))
rewards->wCount = 1;
}
LPITEM item = ITEM_MANAGER::instance().CreateItem(rewards->dwVnum, rewards->wCount);
if (!item)
{
ch->ChatPacket(CHAT_TYPE_INFO, "item does not exist, contact SA for free coins :)");
return;
}
bool forcedSockets = false;
if (rewards->alSockets[0])
{
for (int i = 0; i < ITEM_LIMIT_MAX_NUM; i++)
{
if (LIMIT_REAL_TIME == item->GetLimitType(i))
{
// NOTE: We set socket0 as time value, but only if socket is set so we still can use default times in createitem
item->SetSocket(0, time(0) + rewards->alSockets[0]);
// NOTE: We dont have to start realtime event because createitem does
forcedSockets = true;
}
else if (LIMIT_TIMER_BASED_ON_WEAR == item->GetLimitType(i))
{
// NOTE: We force socket0 if set
item->SetSocket(0, rewards->alSockets[0]);
forcedSockets = true;
}
}
}
// NOTE: I dont have systems where i need to set multible sockets in createitem but you can still do:
/*
for(const auto& socket : itemInfo.alSockets){
if (socket)
{
Funny socket method here
}
}
*/
if (!forcedSockets)
item->SetSockets(rewards->alSockets);
// NOTE: You can adjust this if you want to use bAlterToMagicItemPct
item->SetAttributes(rewards->aAttr);
ch->AutoGiveItem(item);
itemsGiven.push_back(*rewards);
}
}
ch->SendPromotionRewardPacket(redeem_answer->byRedeemAnswer, itemsGiven);
}
void CInputDB::ReloadItemshop(const char* c_pData)
{
WORD* category_size = (WORD*)c_pData;
c_pData += sizeof(WORD);
WORD* item_size = (WORD*)c_pData;
c_pData += sizeof(WORD);
CItemshopManager::instance().InitializeCategories((TItemshopCategoryTable*)c_pData, *category_size);
c_pData += sizeof(TItemshopCategoryTable) * (*category_size);
CItemshopManager::instance().InitializeItems((TItemshopItemTable*)c_pData, *item_size);
c_pData += sizeof(TItemshopItemTable) * (*item_size);
CHARACTER_MANAGER::instance().SendItemshopItems();
}
#endif
8. input_login.cpp
Add:
#ifdef ENABLE_ITEMSHOP
#include "itemshop.h"
#endif
Search:
void CInputLogin::Entergame(LPDESC d, const char * data)
Add to end of this function:
#ifdef ENABLE_ITEMSHOP
ch->SendItemshopItemsPacket(CItemshopManager::instance().GetItemshopItems(), CItemshopManager::instance().GetItemshopCategories());
#endif
9. input_main.cpp
Add:
#ifdef ENABLE_ITEMSHOP
#include "itemshop.h"
#endif
Search:
case HEADER_CG_ACCE:
{
if ((iExtraLen = AcceRefine(ch, c_pData, m_iBufferLeft)) < 0)
{
return -1;
}
}
break;
Add:
#ifdef ENABLE_ITEMSHOP
case HEADER_CG_BUY_ITEMSHOP_ITEM:
{
BuyItemshopItem(ch, c_pData);
}
break;
case HEADER_CG_PROMOTION:
{
RedeemPromotionCode(ch, c_pData);
}
break;
#endif
Add to end of file:
#ifdef ENABLE_ITEMSHOP
void CInputMain::BuyItemshopItem(LPCHARACTER ch, const char* c_pData)
{
const TPacketCGBuyItemshopItem* p = reinterpret_cast<const TPacketCGBuyItemshopItem*>(c_pData);
if (CItemshopManager::instance().CanBuyItem(ch, p->hash, p->wCount))
{
TItemshopCheckBuy p_gd;
strlcpy(p_gd.hash, p->hash, sizeof(p_gd.hash));
p_gd.wCount = p->wCount;
db_clientdesc->DBPacket(HEADER_GD_BUY_ITEMSHOP, ch->GetDesc()->GetHandle(), &p_gd, sizeof(p_gd));
}
}
void CInputMain::RedeemPromotionCode(LPCHARACTER ch, const char* c_pData)
{
if (!ch->CanUsePromotionCode())
{
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You have to wait 10 seconds after using promotion codes."));
return;
}
ch->SetPromotionCodedown(thecore_pulse() + PASSES_PER_SEC(10));
const TPacketCGRedeemPromotionCode* p = reinterpret_cast<const TPacketCGRedeemPromotionCode*>(c_pData);
// We dont have to check but atleast we dont send gd -> dg -> gc
if (!strcmp(p->promotion_code, ""))
return;
TPromotionRedeem gd_p;
strlcpy(gd_p.code, p->promotion_code, sizeof(gd_p.code));
gd_p.accID = ch->GetAID();
db_clientdesc->DBPacket(HEADER_GD_PROMOTION_REDEEM, ch->GetDesc()->GetHandle(), &gd_p, sizeof(gd_p));
}
#endif
10. main.cpp
Add:
#ifdef ENABLE_ITEMSHOP
#include "itemshop.h"
#endif
Search:
CRefineManager refine_manager;
Add:
#ifdef ENABLE_ITEMSHOP
CItemshopManager itemshop_manager;
#endif
11. packet.h
Search:
HEADER_GC_TIME_SYNC = 0xfc,
HEADER_GC_PHASE = 0xfd,
Add above:
#ifdef ENABLE_ITEMSHOP
HEADER_CG_BUY_ITEMSHOP_ITEM = 240,
HEADER_CG_PROMOTION = 241,
#endif
Search:
HEADER_GG_LOGIN = 1,
Add above:
#ifdef ENABLE_ITEMSHOP
HEADER_GC_ITEMSHOP = 240,
#endif
Add to the end of file before #pragma pack():
#ifdef ENABLE_ITEMSHOP
typedef struct SPacketGCitemshopCategorySize
{
DWORD size;
} TPacketGCitemshopCategorySize;
typedef struct SPacketGCItemshopItemSize
{
DWORD size;
} TPacketGCItemshopItemSize;
typedef struct SPacketGCItemshopInfo
{
BYTE header;
DWORD subheader;
} TPacketGCItemshopInfo;
enum
{
SUBHEADER_ITEMSHOP_REFRESH_ITEMS,
SUBHEADER_ITEMSHOP_REFRESH_COINS,
SUBHEADER_ITEMSHOP_REFRESH_SINGLE_ITEM,
SUBHEADER_ITEMSHOP_REMOVE_SINGLE_ITEM,
SUBHEADER_ITEMSHOP_ADD_SINGLE_ITEM,
SUBHEADER_PROMOTION_CODE_REWARDS,
};
typedef struct SPacketCGBuyItemshopItem
{
BYTE header;
char hash[ITEMSHOP_HASH_MAX_LEN + 1];
WORD wCount;
} TPacketCGBuyItemshopItem;
typedef struct SPacketCGRedeemPromotionCode
{
BYTE header;
char promotion_code[PROMOTION_CODE_MAX_LEN + 1];
} TPacketCGRedeemPromotionCode;
#endif
12. packet_info.cpp
Search:
CPacketInfoCG::CPacketInfoCG()
Add to the end of this function:
#ifdef ENABLE_ITEMSHOP
Set(HEADER_CG_BUY_ITEMSHOP_ITEM, sizeof(TPacketCGBuyItemshopItem), "BuyItemshopItem", false);
Set(HEADER_CG_PROMOTION, sizeof(TPacketCGRedeemPromotionCode), "RedeemPromotionCode", false);
#endif
13. MakeFile / Solution
Add:
itemshop.cpp\,
14. itemshop.h
Create: itemshop.h
Insert this:
/*
.oooooo. oooooo oooo ooooo ooo .oooo.
d8P' `Y8b `888. .8' `888b. `8' .dP""Y88b
888 `888. .8' 8 `88b. 8 ]8P'
888 `888.8' 8 `88b. 8 <88b.
888 `888' 8 `88b.8 `88b.
`88b ooo 888 8 `888 o. .88P
`Y8bood8P' o888o o8o `8 `8bd88P'
Version:
Release 1.0.0
Contacts:
Metin2Downloads: https://www.metin2downloads.to/cms/user/11018-cyn3/
Discord: Franky#6315
Support:
Discord: https://discord.gg/sura-head
Special thanks to:
- Rainer <- Design & Animations
- SamaLamaDingDong <- Code review (Server - Source)
- <> <- Mental support
*/
#include "../../common/singleton.h"
#include "../../common/service.h"
#include <unordered_map>
#include "constants.h"
#ifdef ENABLE_ITEMSHOP
class CItemshopManager : public singleton<CItemshopManager>
{
public:
CItemshopManager();
~CItemshopManager();
void InitializeItems(TItemshopItemTable* table, WORD size);
void InitializeCategories(TItemshopCategoryTable* table, WORD size);
void RefreshSingleItem(TItemshopItemTable* item);
void RemoveSingleItem(TItemshopItemTable* item);
void AddSingleItem(TItemshopItemTable* item);
bool IsValidHash(const char* hash);
bool HasEnoughCoins(DWORD accID, unsigned long long ullItemPrice);
bool IsValidPurchase(TItemshopItemTable itemInfo, WORD wCount, unsigned long long &ullPrice);
bool CanBuyItem(LPCHARACTER ch, const char* hash, WORD wCount);
bool BuyItem(LPCHARACTER ch, const char* hash, WORD wCount);
const std::unordered_map <BYTE, TItemshopCategoryInfo> GetItemshopCategories();
const std::unordered_map <BYTE, std::vector<TItemshopItemTable>> GetItemshopItems();
std::unordered_map <BYTE, TItemshopCategoryInfo> m_ItemshopCategories;
std::unordered_map <BYTE, std::vector<TItemshopItemTable>> m_ItemshopItems;
};
#endif
15. itemshop.cpp
Create: itemshop.cpp
Insert this:
#include "stdafx.h"
#include "itemshop.h"
#include "char.h"
#include "db.h"
#include "char_manager.h"
#include "sectree_manager.h"
#include "config.h"
#include "item_manager.h"
#include "item.h"
#include "desc.h"
#ifdef ENABLE_ITEMSHOP
CItemshopManager::CItemshopManager()
{
}
CItemshopManager::~CItemshopManager()
{
}
void CItemshopManager::InitializeItems(TItemshopItemTable * table, WORD size)
{
m_ItemshopItems.clear();
for (int i = 0; i < size; ++i, ++table)
{
const TItemTable* item_table = ITEM_MANAGER::instance().GetTable(table->dwVnum);
if (item_table)
{
if (table->wCount > 1 && ((IS_SET(item_table->dwAntiFlags, ITEM_ANTIFLAG_STACK)) || !(IS_SET(item_table->dwFlags, ITEM_FLAG_STACKABLE))))
{
sys_err("<Itemshop / Are you stupid?> Vnum %lld Count %d but item is not stackable. Adjust this shit!", table->dwVnum, table->wCount);
continue;
}
m_ItemshopItems[table->byCategory].push_back(*table);
}
}
}
void CItemshopManager::InitializeCategories(TItemshopCategoryTable* table, WORD size)
{
m_ItemshopCategories.clear();
for (int i = 0; i < size; ++i, ++table)
{
m_ItemshopCategories.insert(std::pair(table->index, table->info));
}
}
void CItemshopManager::RefreshSingleItem(TItemshopItemTable* item)
{
for (auto& category : m_ItemshopItems)
{
for (auto& cur_item : category.second)
{
if (!strcmp(cur_item.hash, item->hash))
{
thecore_memcpy(&cur_item, item, sizeof(TItemshopItemTable));
CHARACTER_MANAGER::instance().SendItemshopSingleItemRefresh(item);
return;
}
}
}
}
void CItemshopManager::RemoveSingleItem(TItemshopItemTable* item)
{
for (auto& category : m_ItemshopItems)
{
for(int i = 0; i < category.second.size(); i++)
{
if (!strcmp(category.second[i].hash, item->hash))
{
category.second.erase(category.second.begin() + i);
CHARACTER_MANAGER::instance().SendItemshopSingleRemoveItem(item);
return;
}
}
}
}
void CItemshopManager::AddSingleItem(TItemshopItemTable* item)
{
const TItemTable* item_table = ITEM_MANAGER::instance().GetTable(item->dwVnum);
if (item_table)
{
if (item->wCount > 1 && ((IS_SET(item_table->dwAntiFlags, ITEM_ANTIFLAG_STACK)) || !(IS_SET(item_table->dwFlags, ITEM_FLAG_STACKABLE))))
{
sys_err("<Itemshop / Are you stupid?> Vnum %ld Count %d but item is not stackable. Adjust this shit!", item->dwVnum, item->wCount);
return;
}
m_ItemshopItems[item->byCategory].push_back(*item);
CHARACTER_MANAGER::instance().SendItemshopSingleAddItem(item);
}
}
bool CItemshopManager::IsValidHash(const char* hash)
{
for (const auto& category : m_ItemshopItems)
{
for (const auto& item : category.second) {
if (!strcmp(hash, item.hash))
return true;
}
}
return false;
}
bool CItemshopManager::IsValidPurchase(TItemshopItemTable itemInfo, WORD wCount, unsigned long long &ullPrice)
{
const TItemTable* item_table = ITEM_MANAGER::instance().GetTable(itemInfo.dwVnum);
if (item_table)
{
// NOTE: Log user reason custom packet
if (wCount > 1 && ((IS_SET(item_table->dwAntiFlags, ITEM_ANTIFLAG_STACK)) || !(IS_SET(item_table->dwFlags, ITEM_FLAG_STACKABLE))))
return false;
}
if (wCount <= 0
|| check_mul_error(itemInfo.wCount, wCount)
|| itemInfo.wCount * wCount > MAX_ITEM_STACK)
{
return false;
}
BYTE discountPercent = MINMAX(0, itemInfo.byDiscountPercent, 100);
if (discountPercent > 0) {
ullPrice = ullPrice - (ullPrice * discountPercent / 100);
}
if (check_mul_error<unsigned long long>(ullPrice, wCount)) {
return false;
}
ullPrice *= wCount;
return true;
}
bool CItemshopManager::HasEnoughCoins(DWORD accID, unsigned long long ullItemPrice)
{
std::unique_ptr<SQLMsg> pkMsg(DBManager::instance().DirectQuery("SELECT coins from account.account WHERE id = %ld", accID));
SQLResult* pRes = pkMsg->Get();
if (pRes->uiNumRows == 0)
{
return false;
}
MYSQL_ROW data = mysql_fetch_row(pRes->pSQLResult);
unsigned long long ullTotalCoins;
str_to_number(ullTotalCoins, data[0]);
return ullTotalCoins >= ullItemPrice;
}
bool CItemshopManager::CanBuyItem(LPCHARACTER ch, const char* hash, WORD wCount)
{
if (!ch->CanUseItemshop())
{
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You have to wait 10 seconds after an invalid itemshop action."));
return false;
}
if (!IsValidHash(hash))
{
ch->SetItemshopCooldown(thecore_pulse() + PASSES_PER_SEC(10));
return false;
}
TItemshopItemTable itemInfo{ 0 };
for (const auto& category : m_ItemshopItems)
{
for (const auto& item : category.second)
{
if (!strcmp(hash, item.hash))
{
itemInfo = item;
break;
}
}
}
if (itemInfo.llLimitCount == 0)
return false;
unsigned long long ullPrice = itemInfo.ullPrice;
if (!IsValidPurchase(itemInfo, wCount, ullPrice))
{
sys_err("<Itemshop> OVERFLOW check failed. PID: %d - BuyCount %d - Hash %s - Vnum %lld - Count %d", ch->GetPlayerID(), wCount, itemInfo.hash, itemInfo.dwVnum, itemInfo.wCount);
return false;
}
const TAccountTable& accTable = ch->GetDesc()->GetAccountTable();
if (!HasEnoughCoins(accTable.id, ullPrice))
{
ch->SetItemshopCooldown(thecore_pulse() + PASSES_PER_SEC(10));
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You dont have enough coins."));
return false;
}
LPITEM item = ITEM_MANAGER::instance().CreateItem(itemInfo.dwVnum, itemInfo.wCount * wCount);
if (!item)
{
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Invalid item -> Write your Serveradmin to get some free coins :)"));
return false;
}
int iEmptyCell;
if (item->IsDragonSoul())
{
if ((iEmptyCell = ch->GetEmptyDragonSoulInventory(item)) == -1)
{
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You carry too many Items."));
M2_DESTROY_ITEM(item);
return false;
}
}
else
{
// NOTE: Adjust for special inventory by sanii
if ((iEmptyCell = ch->GetEmptyInventory(item->GetSize())) == -1)
{
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You carry too many Items."));
M2_DESTROY_ITEM(item);
return false;
}
}
M2_DESTROY_ITEM(item);
return true;
}
// NOTE: CALL THIS ONLY FROM DB_MANAGER
bool CItemshopManager::BuyItem(LPCHARACTER ch, const char* hash, WORD wCount)
{
if (!IsValidHash(hash))
{
ch->SendItemshopItemsPacket(GetItemshopItems(),GetItemshopCategories());
return false;
}
TItemshopItemTable itemInfo{0};
for (const auto& category : m_ItemshopItems)
{
for (const auto & item : category.second)
{
if (!strcmp(hash, item.hash))
{
itemInfo = item;
break;
}
}
}
unsigned long long ullPrice = itemInfo.ullPrice;
if (!IsValidPurchase(itemInfo, wCount, ullPrice))
{
sys_err("<Itemshop> OVERFLOW check failed. PID: %d - BuyCount %d - Hash %s - Vnum %lld - Count %d", ch->GetPlayerID(), wCount, itemInfo.hash, itemInfo.dwVnum, itemInfo.wCount);
return false;
}
const TAccountTable& accTable = ch->GetDesc()->GetAccountTable();
if (!HasEnoughCoins(accTable.id, ullPrice))
{
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You dont have enough coins."));
return false;
}
LPITEM item = ITEM_MANAGER::instance().CreateItem(itemInfo.dwVnum, itemInfo.wCount * wCount);
if (!item)
{
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Invalid item -> Write your Serveradmin to get some free coins :)"));
return false;
}
// NOTE: adjust as needed
bool forcedSockets = false;
if (itemInfo.alSockets[0])
{
for (int i = 0; i < ITEM_LIMIT_MAX_NUM; i++)
{
if (LIMIT_REAL_TIME == item->GetLimitType(i))
{
// NOTE: We set socket0 as time value, but only if socket is set so we still can use default times in createitem
item->SetSocket(0, time(0) + itemInfo.alSockets[0]);
// NOTE: We dont have to start realtime event because createitem already does
forcedSockets = true;
}
else if (LIMIT_TIMER_BASED_ON_WEAR == item->GetLimitType(i))
{
item->SetSocket(0, itemInfo.alSockets[0]);
forcedSockets = true;
}
}
}
// NOTE: I dont have systems where i need to set multiple sockets in createitem but you can still do:
// for(const auto& socket : itemInfo.alSockets)
if(!forcedSockets)
item->SetSockets(itemInfo.alSockets);
// NOTE: You can adjust this if you want to use bAlterToMagicItemPct
item->SetAttributes(itemInfo.aAttr);
int iEmptyCell;
if (item->IsDragonSoul())
{
if ((iEmptyCell = ch->GetEmptyDragonSoulInventory(item)) == -1)
{
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You carry too many Items."));
M2_DESTROY_ITEM(item);
return false;
}
}
else
{
// NOTE: Adjust for special inventory by sanii
if ((iEmptyCell = ch->GetEmptyInventory(item->GetSize())) == -1)
{
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You carry too many Items."));
M2_DESTROY_ITEM(item);
return false;
}
}
DBManager::instance().DirectQuery("UPDATE account.account SET coins = coins - %lld WHERE id = %ld", ullPrice, accTable.id);
if (item->IsDragonSoul())
item->AddToCharacter(ch, TItemPos(DRAGON_SOUL_INVENTORY, iEmptyCell));
else
item->AddToCharacter(ch, TItemPos(INVENTORY, iEmptyCell));
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("The purchase of the item was successful."));
ch->SendItemshopCoinPacket();
return true;
}
const std::unordered_map <BYTE, std::vector<TItemshopItemTable>> CItemshopManager::GetItemshopItems()
{
return m_ItemshopItems;
}
const std::unordered_map < BYTE, TItemshopCategoryInfo> CItemshopManager::GetItemshopCategories()
{
return m_ItemshopCategories;
}
#endif