Files
Leviathan/Server/GameServer/Game/Struct/StructEventItemManager.cpp
T
2026-06-01 12:46:52 +02:00

565 lines
17 KiB
C++

#include "stdafx.h"
#include <vector>
#include <mmo/ArTime.h>
#include "StructEventItemManager.h"
#include "GameDBUtil.h"
#include "DB_Commands.h"
struct dbEventItemDropInfo : public CADORecordBinding
{
BEGIN_ADO_BINDING(dbEventItemDropInfo)
ADO_VARIABLE_LENGTH_ENTRY4(1, adInteger, sid, sizeof(sid), FALSE)
ADO_VARIABLE_LENGTH_ENTRY4(2, adInteger, code, sizeof(code), FALSE)
ADO_VARIABLE_LENGTH_ENTRY4(3, adInteger, remain_time, sizeof(remain_time), FALSE)
ADO_VARIABLE_LENGTH_ENTRY4(4, adInteger, duration, sizeof(duration), FALSE)
ADO_VARIABLE_LENGTH_ENTRY4(5, adInteger, count, sizeof(count), FALSE)
ADO_VARIABLE_LENGTH_ENTRY4(6, adInteger, total_count, sizeof(total_count), FALSE)
END_ADO_BINDING()
int sid;
int code;
int remain_time;
int duration;
int count;
int total_count;
};
struct dbEventItemSupplyInfo : public CADORecordBinding
{
BEGIN_ADO_BINDING(dbEventItemSupplyInfo)
ADO_VARIABLE_LENGTH_ENTRY4(1, adInteger, sid, sizeof(sid), FALSE)
ADO_VARIABLE_LENGTH_ENTRY4(2, adInteger, code, sizeof(code), FALSE)
ADO_VARIABLE_LENGTH_ENTRY4(3, adInteger, min_count, sizeof(min_count), FALSE)
ADO_VARIABLE_LENGTH_ENTRY4(4, adInteger, max_count, sizeof(max_count), FALSE)
ADO_VARIABLE_LENGTH_ENTRY4(5, adInteger, flag, sizeof(flag), FALSE)
ADO_VARIABLE_LENGTH_ENTRY4(6, adDBTimeStamp, start_time, sizeof(start_time), FALSE)
ADO_VARIABLE_LENGTH_ENTRY4(7, adDBTimeStamp, end_time, sizeof(end_time), FALSE)
ADO_VARIABLE_LENGTH_ENTRY4(8, adInteger, left_count, sizeof(left_count), FALSE)
ADO_VARIABLE_LENGTH_ENTRY4(9, adInteger, total_count, sizeof(total_count), FALSE)
END_ADO_BINDING()
int sid;
int code;
int min_count;
int max_count;
int flag;
DBTIMESTAMP start_time;
DBTIMESTAMP end_time;
int left_count;
int total_count;
};
struct dbEventDungeonDroprateInfo : public CADORecordBinding
{
BEGIN_ADO_BINDING(dbEventDungeonDroprateInfo)
ADO_VARIABLE_LENGTH_ENTRY4(1, adInteger, dungeon_id, sizeof(dungeon_id), FALSE)
ADO_NUMERIC_ENTRY2(2, adDecimal, drop_rate, 10, 4, FALSE)
ADO_VARIABLE_LENGTH_ENTRY4(3, adDBTimeStamp, end_time, sizeof(end_time), FALSE)
END_ADO_BINDING()
int dungeon_id;
_decimal_variant drop_rate;
DBTIMESTAMP end_time;
};
StructEventItemManager & StructEventItemManager::GetInstance()
{
static StructEventItemManager _inst;
return _inst;
}
bool StructEventItemManager::Init()
{
LoadEventItemDropInfo();
LoadEventItemSupplyInfo();
LoadEventDungeonDroprateInfo();
// 불러들인 게 있으면 시작하자. (이벤트 시작도 안했는데 true로 체크되어 몹 죽을때마다 검사되는 거 방지.)
if( !m_vEventItemDropInfo.empty() )
m_bStartEventDrop = true;
if( !m_vEventItemSupplyInfo.empty() )
m_bStartEventSupply = true;
if( !m_vEventDungeonDroprateInfo.empty() )
m_bDungeonDropRateEvent = true;
return true;
}
bool StructEventItemManager::DeInit()
{
while( !m_lQueryList.empty() )
{
Sleep( 100 );
}
return true;
}
void StructEventItemManager::onEndQuery()
{
THREAD_SYNCRONIZE( m_QueryLock );
m_lQueryList.pop_front();
if( !m_lQueryList.empty() )
{
DB().Push( m_lQueryList.front() );
}
}
void StructEventItemManager::Push( GameDBManager::DBProc* pWork )
{
THREAD_SYNCRONIZE( m_QueryLock );
if( m_lQueryList.empty() )
{
DB().Push( pWork );
}
m_lQueryList.push_back( pWork );
}
float StructEventItemManager::GetEventDungeonDropRate( int nDungeonID )
{
THREAD_SYNCHRONIZE( m_DropRateLock );
std::vector< EVENT_DUNGEON_DROPRATE_INFO >::iterator it = m_vEventDungeonDroprateInfo.begin();
for( ; it != m_vEventDungeonDroprateInfo.end() ; ++it )
{
if( nDungeonID != (*it).m_nDungeonID )
continue;
if( (*it).m_tEndTime < time( NULL ) )
{
m_vEventDungeonDroprateInfo.erase( it );
break;
}
return (*it).m_fDropRate;
}
return 1.0f;
}
void StructEventItemManager::LoadEventDungeonDroprateInfo()
{
THREAD_SYNCHRONIZE( m_DropRateLock );
m_vEventDungeonDroprateInfo.clear();
_ConnectionPtr ConnPtr = NULL;
InitUserDbConnection( ConnPtr );
_RecordsetPtr pRS = NULL;
pRS.CreateInstance( __uuidof(Recordset) );
pRS->Open("SELECT * FROM dbo.EventDungeonDropRate ORDER BY end_time DESC",
_variant_t((IDispatch *)ConnPtr,true),
adOpenForwardOnly, adLockReadOnly, adCmdText);
dbEventDungeonDroprateInfo emprs;
IADORecordBinding *picRs = NULL; // Interface Pointer declared.
pRS->QueryInterface( __uuidof(IADORecordBinding),(LPVOID*)&picRs );
picRs->BindToRecordset( &emprs );
while( pRS->State != adStateClosed && !pRS->EndOfFile )
{
struct tm tmEndTime;
tmEndTime.tm_year = emprs.end_time.year - 1900;
tmEndTime.tm_mon = emprs.end_time.month - 1;
tmEndTime.tm_mday = emprs.end_time.day;
tmEndTime.tm_hour = emprs.end_time.hour;
tmEndTime.tm_min = emprs.end_time.minute;
tmEndTime.tm_sec = emprs.end_time.second;
tmEndTime.tm_isdst = -1;
time_t tEndTime = mktime( &tmEndTime );
StructEventItemManager::RegisterEventDungeonDroprateInfo( emprs.dungeon_id, emprs.drop_rate.getFloat(), tEndTime );
pRS->MoveNext();
}
}
void StructEventItemManager::LoadEventItemDropInfo()
{
AR_TIME t = GetArTime();
m_vEventItemDropInfo.clear();
_ConnectionPtr ConnPtr = NULL;
InitUserDbConnection( ConnPtr );
_RecordsetPtr pRS = NULL;
pRS.CreateInstance( __uuidof(Recordset) );
pRS->Open("SELECT * FROM dbo.EventItemDropInfo ORDER BY CAST( duration AS FLOAT ) / total_count DESC",
_variant_t((IDispatch *)ConnPtr,true),
adOpenForwardOnly, adLockReadOnly, adCmdText);
dbEventItemDropInfo emprs;
IADORecordBinding *picRs = NULL; // Interface Pointer declared.
pRS->QueryInterface( __uuidof(IADORecordBinding),(LPVOID*)&picRs );
picRs->BindToRecordset( &emprs );
while( pRS->State != adStateClosed && !pRS->EndOfFile )
{
StructEventItemManager::RegisterEventItemDropInfo( emprs.sid, emprs.code, emprs.remain_time * 100, emprs.duration * 100, emprs.count, emprs.total_count );
pRS->MoveNext();
}
// 드랍 개수 보관 벡터도 초기화.
m_vEventItemDropPicker.clear();
for( size_t i = 0 ; i < m_vEventItemDropInfo.size() ; ++i )
{
m_vEventItemDropPicker.push_back( 0 );
}
}
void StructEventItemManager::RegisterEventDungeonDroprateInfo( int dungeon_id, float drop_rate, time_t end_time )
{
THREAD_SYNCHRONIZE( m_DropRateLock );
if( time( NULL ) < end_time )
{
m_vEventDungeonDroprateInfo.push_back( EVENT_DUNGEON_DROPRATE_INFO( dungeon_id, drop_rate, end_time ) );
}
}
void StructEventItemManager::RegisterEventItemDropInfo( int sid, ItemBase::ItemCode code, AR_TIME remain_time, AR_TIME duration, int count, int total_count )
{
THREAD_SYNCRONIZE( &m_IntfLock );
AR_TIME t = GetArTime();
if( remain_time > duration )
remain_time = duration;
std::vector< EVENT_ITEM_DROP_INFO >::iterator it;
for( it = m_vEventItemDropInfo.begin(); it != m_vEventItemDropInfo.end(); ++it )
{
if( (*it).sid == sid )
{
(*it) = EVENT_ITEM_DROP_INFO( sid, code, 1, 1, -1, t + remain_time, duration, count, total_count );
return;
}
}
m_vEventItemDropInfo.push_back( EVENT_ITEM_DROP_INFO( sid, code, 1, 1, -1, t + remain_time, duration, count, total_count ) );
}
ItemBase::ItemCode StructEventItemManager::GetActiveDrop( AR_TIME t, float fDropRatePenalty )
{
if( !m_bStartEventDrop || m_vEventItemDropInfo.size() == 0 || fDropRatePenalty == 0.0f )
return 0;
THREAD_SYNCRONIZE( &m_IntfLock );
__int64 total_goal_count = 0;
__int64 total_remain_count = 0;
int i = 0;
for( std::vector< EVENT_ITEM_DROP_INFO >::iterator it = m_vEventItemDropInfo.begin() ; it != m_vEventItemDropInfo.end() ; ++it, ++i )
{
if( (*it).left_count < 1 || (*it).total_count < 1 ) continue;
int remain_time = 0;
float ratio = 0.0f;
if( (*it).end_time > t )
{
remain_time = (*it).end_time - t;
// 이벤트 기간에 대한 비율, 0에 가까울 수록 이벤트 초반 / 1에 가까울 수록 이벤트 후반
ratio = 1.0f - ( float( remain_time ) / float( (*it).duration ) );
}
// 현재 시간대에서 풀어야 할 수량
__int64 goal_count = ratio * (*it).total_count;
// 현재 떨어진 양을 감안하여 풀릴 수 있는 수량
__int64 remain_count = goal_count - ( (*it).total_count - (*it).left_count );
if( remain_count < 1 || goal_count < 1 )
continue;
total_remain_count += remain_count;
total_goal_count += goal_count;
m_vEventItemDropPicker[ i ] = remain_count;
}
if( total_remain_count < 1 || total_goal_count < 1 )
return 0;
// 드랍 확률은 [현재 시간 기준으로 남은 양] / [현재 시간 기준으로 풀려야 할 양]
__int64 probability = ( total_remain_count * fDropRatePenalty ) / total_goal_count * 1000000;
if( XRandom() % 1000000 < probability )
{
// 드랍 확률 통과. 이제는 무슨 아이템을 드랍시킬 지 결정.
int nPick = XRandom() % total_remain_count;
size_t i = 0;
for( ; i < m_vEventItemDropPicker.size() ; ++i )
{
nPick -= m_vEventItemDropPicker[ i ];
if( nPick < 0 )
break;
}
--m_vEventItemDropInfo[ i ].left_count;
Push( new DB_UpdateEventItemDropInfo(
m_vEventItemDropInfo[ i ].sid,
( m_vEventItemDropInfo[ i ].end_time > t ) ? ( ( m_vEventItemDropInfo[ i ].end_time - t ) / 100 ) : 0,
(int)(m_vEventItemDropInfo[ i ].duration / 100),
m_vEventItemDropInfo[ i ].left_count,
m_vEventItemDropInfo[ i ].total_count ) );
return m_vEventItemDropInfo[ i ].code;
}
return 0;
}
void StructEventItemManager::LoadEventItemSupplyInfo()
{
m_vEventItemSupplyInfo.clear();
_ConnectionPtr ConnPtr = NULL;
InitUserDbConnection( ConnPtr );
_RecordsetPtr pRS = NULL;
pRS.CreateInstance( __uuidof(Recordset) );
pRS->Open("EventItemSupplyInfo",
_variant_t((IDispatch *)ConnPtr,true),
adOpenForwardOnly, adLockReadOnly, adCmdTable);
dbEventItemSupplyInfo emprs;
IADORecordBinding *picRs = NULL; // Interface Pointer declared.
pRS->QueryInterface( __uuidof(IADORecordBinding),(LPVOID*)&picRs );
picRs->BindToRecordset( &emprs );
while( pRS->State != adStateClosed && !pRS->EndOfFile )
{
struct tm tmStartTime;
tmStartTime.tm_year = emprs.start_time.year - 1900;
tmStartTime.tm_mon = emprs.start_time.month - 1;
tmStartTime.tm_mday = emprs.start_time.day;
tmStartTime.tm_hour = emprs.start_time.hour;
tmStartTime.tm_min = emprs.start_time.minute;
tmStartTime.tm_sec = emprs.start_time.second;
tmStartTime.tm_isdst = -1;
time_t tStartTime = mktime( &tmStartTime );
struct tm tmEndTime;
tmEndTime.tm_year = emprs.end_time.year - 1900;
tmEndTime.tm_mon = emprs.end_time.month - 1;
tmEndTime.tm_mday = emprs.end_time.day;
tmEndTime.tm_hour = emprs.end_time.hour;
tmEndTime.tm_min = emprs.end_time.minute;
tmEndTime.tm_sec = emprs.end_time.second;
tmEndTime.tm_isdst = -1;
time_t tEndTime = mktime( &tmEndTime );
StructEventItemManager::RegisterEventItemSupplyInfo( emprs.sid, emprs.code, emprs.min_count, emprs.max_count, tStartTime, tStartTime, tEndTime, emprs.left_count, emprs.total_count );
pRS->MoveNext();
}
}
void StructEventItemManager::RegisterEventItemSupplyInfo( const int & sid, const ItemBase::ItemCode & code, const int & min_count, const int & max_count, const int & flag, const time_t & start_time, const time_t & end_time, const int & left_count, const int & total_count )
{
THREAD_SYNCRONIZE( &m_IntfLock );
std::vector< EVENT_ITEM_SUPPLY_INFO >::iterator it;
for( it = m_vEventItemSupplyInfo.begin() ; it != m_vEventItemSupplyInfo.end() ; ++it )
{
if( (*it).sid == sid )
{
(*it) = EVENT_ITEM_SUPPLY_INFO( sid, code, min_count, max_count, flag, start_time, end_time, left_count, total_count );
return;
}
}
m_vEventItemSupplyInfo.push_back( EVENT_ITEM_SUPPLY_INFO( sid, code, min_count, max_count, flag, start_time, end_time, left_count, total_count ) );
}
void StructEventItemManager::SetActiveSupply( const ItemBase::ItemCode & code, const int & min_count, const int & max_count, const int & flag )
{
THREAD_SYNCRONIZE( &m_IntfLock );
m_ReservedActiveSupply = EVENT_ITEM_INFO( code, min_count, max_count, flag );
}
const EVENT_ITEM_INFO StructEventItemManager::GetActiveSupply( const time_t & t )
{
EVENT_ITEM_INFO ActiveSupply( 0, 0, 0, 0 );
if( !m_bStartEventSupply )
return ActiveSupply;
THREAD_SYNCRONIZE( &m_IntfLock );
// 예약된 드랍이 있다면 드랍
if( m_ReservedActiveSupply.code )
{
ActiveSupply = m_ReservedActiveSupply;
m_ReservedActiveSupply.code = 0;
}
else
{
for( std::vector< EVENT_ITEM_SUPPLY_INFO >::iterator it = m_vEventItemSupplyInfo.begin() ; it != m_vEventItemSupplyInfo.end() ; ++it )
{
// 남은 수량이 없거나 아직 시작되지 않은 이벤트는 제낌
if( (*it).left_count < 1 || (*it).start_time > t ) continue;
bool bWinning = false;
if( (*it).end_time > t )
{
// 이벤트 종료 시점 이전: 당첨 체크
time_t tSecPerOneSupply = ( (*it).end_time - (*it).start_time ) / ( (*it).total_count + 1 );
time_t tThisSupplyTime = (*it).start_time + ( tSecPerOneSupply * ( (*it).total_count - (*it).left_count + 1 ) );
tThisSupplyTime += tSecPerOneSupply * ( ( XRandom( 50, 150 ) - 100 ) / 100 );
if( tThisSupplyTime <= t )
{
bWinning = true;
}
}
else
{
// 이벤트 종료 시점 이후: 100% 당첨
bWinning = true;
}
if( bWinning )
{
ActiveSupply = (*it);
--(*it).left_count;
Push( new DB_UpdateEventItemSupplyInfo( (*it).sid, (*it).left_count ) );
}
}
}
return ActiveSupply;
}
void StructEventItemManager::InsertEventDrop( StructPlayer *pPlayer, ItemBase::ItemCode code, int count, int minute )
{
THREAD_SYNCRONIZE( &m_IntfLock );
AR_TIME base_time = GetArTime();
int last_sid = 0; int remain_time, duration = 0;
std::vector< EVENT_ITEM_DROP_INFO >::iterator it;
for( it = m_vEventItemDropInfo.begin(); it != m_vEventItemDropInfo.end(); ++it )
{
if( (*it).sid > last_sid ) last_sid = (*it).sid;
}
int sid = last_sid + 1;
StructEventItemManager::RegisterEventItemDropInfo( sid, code, 6000 * minute, 6000 * minute, count, count );
for( it = m_vEventItemDropInfo.begin(); it != m_vEventItemDropInfo.end(); ++it )
{
if ( (*it).sid == sid )
{
duration = (*it).duration / 100;
remain_time = ( (*it).end_time - base_time) / 100;
if ( remain_time < 0 ) remain_time = 0;
Push( new DB_InsertEventItemDropInfo( (*it).sid, (*it).code, remain_time, duration, (*it).left_count, (*it).total_count ) );
}
}
m_vEventItemDropPicker.clear();
for( size_t i = 0 ; i < m_vEventItemDropInfo.size() ; ++i )
{
m_vEventItemDropPicker.push_back( 0 );
}
if ( pPlayer ) SendChatMessage( false, CHAT_NOTICE, "@SYSTEM", pPlayer, "Inserted." );
StructEventItemManager::ShowEventDrop( pPlayer );
}
void StructEventItemManager::ShowEventDrop( StructPlayer *pPlayer )
{
if ( !pPlayer ) return;
THREAD_SYNCRONIZE( &m_IntfLock );
char szBuf[255];
s_sprintf( szBuf, _countof( szBuf ), "<#FF00FF>Total:%d Start:%d", m_vEventItemDropInfo.size(), ( m_bStartEventDrop ? 1 : 0 ) );
SendChatMessage( false, CHAT_NOTICE, "@SYSTEM", pPlayer, szBuf );
std::vector< EVENT_ITEM_DROP_INFO >::iterator it;
int left_count, total_count, hour, day, hour_pr, rate;
time_t end_time, duration, time_diff;
signed int order = 0;
AR_TIME base_time = GetArTime();
if ( m_vEventItemDropInfo.begin() != m_vEventItemDropInfo.end() )
{
for( it = m_vEventItemDropInfo.begin(); it != m_vEventItemDropInfo.end(); ++it, ++order )
{
left_count = (*it).left_count;
total_count = (*it).total_count;
end_time = (*it).end_time;
duration = (*it).duration;
time_diff = (end_time - base_time) / 100;
hour = time_diff / 60 / 60;
day = hour / 24;
hour_pr = hour % 24;
if ( left_count <= 0
|| total_count <= 0
|| end_time < 0
|| duration <= 0
|| (rate = (total_count * (1.0 - ((end_time - base_time) / duration))),
rate <= 0) )
s_sprintf( szBuf, _countof( szBuf ), "<#FFFF00>%d: %d (%d/%d) %dd %dh %dm", order, (*it).sid, left_count, total_count, day, hour_pr, (time_diff / 60 % 60) );
else
s_sprintf( szBuf, _countof( szBuf ), "<#FFFF00>%d: %d (%d/%d) %dd %dh %dm (%d/%d) %.2f%%", order, (*it).sid, left_count, total_count, day, hour_pr, (time_diff / 60 % 60), (total_count - left_count), rate, (float)((1000000 * (rate - (total_count - left_count)) / rate) / 10000.0) );
SendChatMessage( false, CHAT_NOTICE, "@SYSTEM", pPlayer, szBuf );
}
}
}
void StructEventItemManager::UpdateEventDrop( StructPlayer *pPlayer, int no, int count, int minute )
{
THREAD_SYNCRONIZE( &m_IntfLock );
if ( m_vEventItemDropInfo.begin() == m_vEventItemDropInfo.end() && pPlayer ) SendChatMessage( false, CHAT_NOTICE, "@SYSTEM", pPlayer, "Can't find sid. type //show_event_drop() and check it." );
else
{
int duration, remain_time = 0;
std::vector< EVENT_ITEM_DROP_INFO >::iterator it;
for( it = m_vEventItemDropInfo.begin(); it != m_vEventItemDropInfo.end(); ++it )
{
if ( (*it).sid == no )
{
(*it).left_count += count;
(*it).total_count+= count;
(*it).end_time += (6000 * minute);
(*it).duration += (6000 * minute);
duration = (*it).duration / 100;
remain_time = ( (*it).end_time - GetArTime()) / 100;
if ( remain_time < 0 ) { remain_time = 0; }
Push( new DB_UpdateEventItemDropInfo( (*it).sid, remain_time, duration, (*it).left_count, (*it).total_count ) );
}
}
if ( pPlayer ) SendChatMessage( false, CHAT_NOTICE, "@SYSTEM", pPlayer, "Updated" );
StructEventItemManager::ShowEventDrop(pPlayer);
}
}