#include "stdafx.h"
#ifdef ENABLE_BOSS_DAMAGE_RANKING
// #include "buffer_manager.h"
#include "config.h"
#include "char_manager.h"
#include "desc.h"
#include "char.h"
#include "BossDamageRanking.h"
#include "mob_manager.h"
#include "sectree_manager.h"
// #define USING_JSON_FOR_INFOS
#ifdef USING_JSON_FOR_INFOS
#include "locale_service.h"
#include <fstream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
#else
#include "db.h"
#endif
// #define ENABLE_RANKING_NOTICE // -> when spawn boss sending notice
// #if (__cplusplus >= 202002L) // -> check c++ version
// #include <ranges>
// #endif
#ifdef ENABLE_DAMAGE_RANKING_SAFE_ZONE
// constexpr BYTE GUVENLI_ALAN_SURE = 1;
constexpr BYTE CHECK_HOUR = 59;
#endif
// bossdamageclass
EVENTINFO(ranking_boss_regen_info)
{
CBossDamageRanking *pClass;
ranking_boss_regen_info() : pClass(nullptr)
{
}
};
EVENTFUNC(ranking_regen_event)
{
auto* info = dynamic_cast<ranking_boss_regen_info*>(event->info);
if (info == NULL)
{
sys_err(">ranking_regen_event> <Factor> Null pointer");
return 0;
}
auto* pClass = info->pClass;
if (!pClass) { return 0;}
time_t cur_Time = time(NULL);
struct tm vKey = *localtime(&cur_Time);
int hour = vKey.tm_hour;
int minute = vKey.tm_min;
pClass->CheckBoss(hour, minute);
return PASSES_PER_SEC(60);
}
BossDamageInfo::BossDamageInfo(const TBossInfo & info) {
SetVnum(info.dwVnum);
SetHP(info.dwMaxHP);
SetMapIDX(info.dwMapIDX);
SetPosX(info.posX);
SetPosY(info.posY);
SetReturnTime(info.wReturnMin);
m_Info.gift_info = info.gift_info;
SpawnMob(); // -> when open game spawn boss
}
BossDamageInfo::~BossDamageInfo() {
Destroy();
sys_err("Bossdamageinfo has been destroyed!");
}
BossDamageInfo* CBossDamageRanking::FindBossClass(const DWORD dwVnum) const
{
const auto& it = boss_Map.find(dwVnum);
return it != boss_Map.cend() ? it->second.get() : nullptr;
}
auto BossDamageInfo::AddPlayer(LPCHARACTER ch, const DWORD dam) -> void {
if (!ch || !ch->GetDesc()) { return; }
const auto& it = std::find_if(std::begin(m_DmgVec), std::end(m_DmgVec),
[&](const TPlayerInfo& a) { return ch->GetPlayerID() == a.dwID; }
);
if (it != std::end(m_DmgVec)) { return; }
TPlayerInfo info = {};
info.dwID = ch->GetPlayerID();
info.bRace = ch->GetJob();
info.cName = ch->GetName();
info.bLevel = ch->GetLevel();
info.bEmpire = ch->GetEmpire();
info.dwDamage = dam;
m_DmgVec.emplace_back(info);
ch->ChatPacket(CHAT_TYPE_COMMAND, "ClearItems");
for (const auto& it : m_Info.gift_info) {
if (it.first == 0 || it.second == 0) { break; }
ch->ChatPacket(CHAT_TYPE_COMMAND, "BossDamageRankingItem %u %u", it.first, it.second);
}
ch->ChatPacket(CHAT_TYPE_COMMAND, "OpenBossDamageRanking");
}
auto BossDamageInfo::PlayerAction(LPCHARACTER ch, const DWORD dam) -> void {
if (!ch || !ch->GetDesc()) { return; }
const auto& it = std::find_if(std::begin(m_DmgVec), std::end(m_DmgVec),
[&](const TPlayerInfo& a) { return ch->GetPlayerID() == a.dwID; }
);
if (it != std::end(m_DmgVec)) {
it->dwDamage += dam;
}
else {
AddPlayer(ch, dam);
}
SendRankings();
}
auto BossDamageInfo::SendRankings(const bool isDead) -> void {
if (m_DmgVec.empty()) { return; }
std::sort(std::begin(m_DmgVec), std::end(m_DmgVec));
const size_t size = m_DmgVec.size() > 5 ? 5 : m_DmgVec.size();
std::vector<TPlayerInfo> vec = std::vector<TPlayerInfo>(std::begin(m_DmgVec), std::next(std::begin(m_DmgVec), size));
if (vec.size() >= 5) {
vec.resize(5);
vec.shrink_to_fit();
}
for (auto&& it : m_DmgVec)
{
const LPCHARACTER ch = CHARACTER_MANAGER::instance().FindByPID(it.dwID);
if (!ch || !ch->GetDesc()) { continue; }
BYTE idx = 0;
ch->ChatPacket(CHAT_TYPE_COMMAND, "ClearRanks");
for (const auto& snd : vec)
{
BYTE percentDmg = std::clamp(static_cast<BYTE>((snd.dwDamage * 100) / (GetHP())), static_cast<BYTE>(0), static_cast<BYTE>(100));
TBossDamageGCRankingData pack = {};
pack.bHeader = HEADER_GC_BOSS_RANKING_INFO;
pack.bRank = idx++;
pack.bRaceNum = snd.bRace;
std::strcpy(pack.cName, snd.cName);
pack.bLevel = snd.bLevel;
pack.bEmpire = snd.bEmpire;
pack.bDamage = percentDmg;
ch->GetDesc()->Packet(&pack, sizeof(pack));
}
if (isDead) {
ch->ChatPacket(CHAT_TYPE_COMMAND, "CloseBossDamageRanking");
}
else {
ch->ChatPacket(CHAT_TYPE_COMMAND, "LoadRanks");
}
}
if (isDead) { // -> give gifts
BYTE rank = 0;
for (const auto& it :vec)
{
const LPCHARACTER ch = CHARACTER_MANAGER::instance().FindByPID(it.dwID);
if (!ch || !ch->GetDesc()) { continue; }
#if (__cplusplus >= 201703L)
std::pair<DWORD, DWORD> p = GetGiftWithIDX(rank++);
if (p.first && p.second) {
ch->AutoGiveItem(p.first, p.second);
}
#else
const auto& [vnum, count] = GetGiftWithIDX(rank++);
if (vnum && count)
ch->AutoGiveItem(vnum, count);
#endif
}
SetIsSpawn(false);
#ifdef ENABLE_DAMAGE_RANKING_SAFE_ZONE
SetIsSafeZone(false);
#endif
Destroy();
// CreateTimer();
}
vec.clear();
}
auto BossDamageInfo::SpawnMob() -> void {
if (GetIsSpawn()) { return; }
CHARACTER_MANAGER::instance().SpawnMob(GetVnum(), GetMapIDX(), GetPosX() * 100, GetPosY() * 100, 0, false, 360);
SetIsSpawn(true);
#ifdef ENABLE_RANKING_NOTICE
char szNotice[52] = {};
snprintf(szNotice, sizeof(szNotice), "Spawn Notice");
BroadcastNotice(szNotice);
#endif
}
auto BossDamageInfo::ErasePlayer(const uint32_t pid) -> void {
m_DmgVec.erase(
std::remove_if(m_DmgVec.begin(), m_DmgVec.end(), [&](TPlayerInfo const & elm) {
return elm.dwID == pid;
}),m_DmgVec.end());
SendRankings();
}
#ifdef ENABLE_DAMAGE_RANKING_SAFE_ZONE
auto BossDamageInfo::SetIsSafeZone(const bool isSafe) -> void {
auto pMap = SECTREE_MANAGER::instance().GetMap(GetMapIDX());
if (pMap) {
char szNotice[52] = {};
const auto& ExitAllFunc = [&](LPENTITY ent) -> void {
if (ent->IsType(ENTITY_CHARACTER))
{
auto ch = (LPCHARACTER)ent;
if(!ch || !ch->IsPC()) { return; }
snprintf(szNotice, sizeof(szNotice), "Bu bolgede korumali alan %s!", isSafe ? "aktif edildi" : "deaktif edildi");
ch->ChatPacket(CHAT_TYPE_COMMAND, "BossDamageRankingSafeZone %d", isSafe);
ch->ChatPacket(CHAT_TYPE_BIG_NOTICE, szNotice);
}
};
pMap->for_each(ExitAllFunc);
}
m_Info.bIsSafeZone = isSafe;
}
#endif
auto BossDamageInfo::GetGiftWithIDX(const BYTE idx) -> std::pair<DWORD, DWORD> {
if (idx > m_Info.gift_info.size()) {
return std::make_pair(0,0);
}
return std::make_pair(m_Info.gift_info[idx].first, m_Info.gift_info[idx].second);
}
auto BossDamageInfo::Destroy() -> void {
m_DmgVec.clear();
}
// bossdamageclass end
auto CBossDamageRanking::CheckBoss(const int hour, const int min) const -> void {
for (const auto& elm : boss_Map)
{
const auto it = FindBossClass(elm.first);
if (!it) { continue; }
const bool isHour = it->GetReturnTime() % 60 == 1;
if(isHour)
{
// if (min % )
const bool mintoHour = it->GetReturnTime() / 60 == 0;
#ifdef ENABLE_DAMAGE_RANKING_SAFE_ZONE
if (mintoHour && min % it->GetReturnTime() == 1) {
it->SetIsSafeZone(true);
}
#endif
if (mintoHour && min == 0){
it->SpawnMob();
}
}
else {
#ifdef ENABLE_DAMAGE_RANKING_SAFE_ZONE
if (min % it->GetReturnTime() == 1) {
it->SetIsSafeZone(true);
}
#endif
if (min % it->GetReturnTime() == 0) {
it->SpawnMob();
}
}
}
}
auto CBossDamageRanking::CreateTimer() -> void {
CloseTimer();
auto* info = AllocEventInfo<ranking_boss_regen_info>();
info->pClass = this;
spawnEvent = event_create(ranking_regen_event, info, PASSES_PER_SEC(30));
}
auto CBossDamageRanking::CloseTimer() -> void {
if (spawnEvent) {
event_cancel(&spawnEvent);
spawnEvent = nullptr;
}
}
auto CBossDamageRanking::Initialize() -> bool {
#ifdef USING_JSON_FOR_INFOS
char file_name[256+1];
snprintf(file_name, sizeof(file_name), "%s/bossdamage_info.json", LocaleService_GetBasePath().c_str());
std::ifstream ifs(file_name);
if (!ifs.is_open()) { return false; }
try {
json jf = json::parse(ifs);
json& arr = jf["boss_info"];
for (const auto& i : arr) {
TBossInfo info = {};
i.at("boss_vnum").get_to(info.dwVnum);
//mobtable control
const auto* pMob = CMobManager::instance().Get(info.dwVnum);
if (!pMob) {
sys_err("Bossdamage_info.json have wrong boss! vnum : %u pls check it!", info.dwVnum);
continue;
}
i.at("map_index").get_to(info.dwMapIDX);
i.at("spawn_x").get_to(info.posX);
i.at("spawn_y").get_to(info.posY);
i.at("return_time").get_to(info.wReturnMin);
info.dwMaxHP = pMob->m_table.dwMaxHP;
for(const auto& [vnum, count] : i["gift_items"].items()) {
const DWORD vnm = std::stoi(vnum);
const DWORD cnt = count.get<DWORD>();
info.gift_info.emplace_back(vnm, cnt);
}
boss_Map.emplace(info.dwVnum, std::make_unique<BossDamageInfo>(info));
}
ifs.close();
}
catch (const std::exception& e) {
sys_err("ReadBossDamageError error : %s - file : %s", e.what(), file_name);
return false;
}
#else
const std::unique_ptr<SQLMsg> pkMsg(DBManager::instance().DirectQuery("SELECT * FROM boss_damage_ranking%s", get_table_postfix()));
if (pkMsg->Get()->uiNumRows == 0) {
sys_err("boss_damage_ranking table is empty");
return false;
}
boss_Map.clear();
MYSQL_ROW row;
while ((row = mysql_fetch_row(pkMsg->Get()->pSQLResult)) != nullptr)
{
DWORD col = 1;
TBossInfo info = {};
str_to_number(info.dwVnum, row[col++]);
const auto* pMob = CMobManager::instance().Get(info.dwVnum);
if (!pMob) {
sys_err("Bossdamage_info.json have wrong boss! vnum : %u pls check it!", info.dwVnum);
continue;
}
info.dwMaxHP = pMob->m_table.dwMaxHP;
str_to_number(info.dwMapIDX, row[col++]);
str_to_number(info.posX, row[col++]);
str_to_number(info.posY, row[col++]);
str_to_number(info.wReturnMin, row[col++]);
for (uint8_t i(0); i < 5; ++i) {
DWORD vnum, count = 0;
str_to_number(vnum, row[col++]);
str_to_number(count, row[col++]);
info.gift_info.emplace_back(vnum, count);
}
auto data = std::unique_ptr<BossDamageInfo>(new BossDamageInfo(info));
boss_Map.emplace(info.dwVnum, std::move(data));
}
#endif
CreateTimer();
return true;
}
auto CBossDamageRanking::SendGUIData(LPCHARACTER ch) -> void {
if (!ch || !ch->GetDesc()) { return; }
for (const auto& elm : boss_Map) {
const auto it = FindBossClass(elm.first);
if (!it) { continue; }
ch->ChatPacket(CHAT_TYPE_COMMAND, "BossRankingUI %u %u %u %u %u", elm.first, it->GetMapIDX(), it->GetPosX(), it->GetPosY(), it->GetReturnTime());
}
}
auto CBossDamageRanking::IsRankingBoss(const DWORD dwVnum) const -> bool {
#if (__cplusplus >= 202002L)
return boss_Map.contains(dwVnum); // -> c++20 is have contains func
#endif
const auto& it = boss_Map.find(dwVnum);
return it != boss_Map.cend() ? true : false;
}
#ifdef ENABLE_DAMAGE_RANKING_SAFE_ZONE
auto CBossDamageRanking::CheckSafeZone(LPCHARACTER ch) -> void {
if (!ch) { return; }
DWORD dwVnum = 0;
for (const auto& it : boss_Map){
if (it.second->GetMapIDX() == ch->GetMapIndex()) {
dwVnum = it.first;
break;
}
}
const auto it = FindBossClass(dwVnum);
if (!it) { return; }
if(it->GetIsSafeZone()) {
ch->ChatPacket(CHAT_TYPE_COMMAND, "BossDamageRankingSafeZone %d", 1);
}
}
#endif
auto CBossDamageRanking::CheckPlayerID(const uint32_t pid) -> void {
for (const auto& it : boss_Map) {
it.second->ErasePlayer(pid);
}
}
auto CBossDamageRanking::Destroy() -> void {
CloseTimer();
boss_Map.clear();
}
#endif