Files
Sun1602/Server/CommunityServer/Services/Lotto/Lotto.cpp
T
2022-10-26 12:25:11 +08:00

463 lines
13 KiB
C++

#include "WorldServerPch.hxx"
#include "./Lotto.h"
#include "misc.h"
#include "LottoStates/InitialState.h"
#include "LottoStates/ActiveState.h"
#include "LottoStates/DrawingState.h"
#include "LottoStates/InactiveState.h"
#include "LottoStates/FinalState.h"
// implemented by __NA_001359_20090619_HEIM_LOTTO_SYSTEM
static int compareByte(const void* arg1, const void* arg2);
Lotto Lotto::instance_;
Lotto::Lotto()
: numResults_(0)
, lastDrawNum_(0)
, numWinners_(NULL)
, prizes_(NULL)
{
srand((unsigned)time(NULL));
// 하임 로또 설정 로딩
config_.Init();
// 당첨 보상 자료 공간 할당
numWinners_ = new int[config_.GetNumPrizes()];
prizes_ = new INT64[config_.GetNumPrizes()];
// 각 상태 객체 초기화
states_[Lotto::INITIAL_STATE] = new LottoInitialState();
states_[Lotto::ACTIVE_STATE] = new LottoActiveState();
states_[Lotto::DRAWING_STATE] = new LottoDrawingState();
states_[Lotto::INACTIVE_STATE] = new LottoInactiveState();
states_[Lotto::FINAL_STATE] = new LottoFinalState();
// 초기 전이
currentState_ = NULL;
TransitState(Lotto::INITIAL_STATE);
}
Lotto::~Lotto()
{
if (currentState_ != NULL) {
currentState_->OnExit();
currentState_ = NULL;
}
// 상태 객체 해제
for (int i = 0; i < Lotto::NUM_STATES; ++i) {
if (states_[i] != NULL) { delete states_[i]; }
}
// 당첨 보상 자료 공간 해제
if (prizes_ != NULL) { delete[] prizes_; }
if (numWinners_ != NULL) { delete[] numWinners_; }
}
INT64 Lotto::AddCurrentTotalAccum(INT64 delta)
{
if (numResults_ <= 0) {
return 0;
}
INT64& totalAccum = results_[0].totalAccum;
totalAccum += delta;
if (totalAccum < 0) {
totalAccum = 0;
}
return totalAccum;
}
LottoResult* Lotto::GetResult(int index)
{
ASSERT(0 <= index && index < numResults_);
if (index < 0 || numResults_ <= index) { return NULL; }
return &results_[index];
}
bool Lotto::ProcessTicket(CHARGUID charGuid, int drawNum, bool isAuto, BYTE* pickedNum,
int* status, int* errorCode)
{
*errorCode = -1;
if (drawNum != GetCurrentDrawNum()) {
*errorCode = 2;
return false;
}
LottoEntry* entry = FindEntry(charGuid);
if (entry == NULL) {
*errorCode = 0;
return false;
}
if (entry->numRemainingTickets <= 0) {
*errorCode = 1;
return false;
}
if (isAuto) {
PickRandomNumbers(pickedNum);
}
// 지금 구현은 응모와 동시에 당첨 여부 판단
// [TODO] 당첨 판단 논리를 당첨 조건으로 옮기는 것 검토
int numMatches = CheckMatches(pickedNum);
switch (numMatches)
{
case 4:
*status = 1;
break;
case 3:
*status = 2;
break;
case 2:
*status = 3;
break;
default:
*status = 0;
}
return true;
}
void Lotto::EnterTicket(CHARGUID charGuid, int drawNum, int status, const BYTE* pickedNum)
{
LottoEntry* entry = FindEntry(charGuid);
if (entry == NULL) { return; }
LottoTicket ticket;
ticket.drawNum = drawNum;
ticket.status = status;
ticket.pickedNum1 = pickedNum[0];
ticket.pickedNum2 = pickedNum[1];
ticket.pickedNum3 = pickedNum[2];
ticket.pickedNum4 = pickedNum[3];
entry->tickets.push_front(ticket);
#ifdef _NA_0_20100115_HEIM_LOTTO_TICKET_PURCHASE
// 복권 판매 대금 적립
results_[0].totalAccum += config_.GetTicketPrice();
#endif // _NA_0_20100115_HEIM_LOTTO_TICKET_PURCHASE
--entry->numRemainingTickets;
++entry->numUsedTickets;
}
LottoEntry* Lotto::FindEntry(CHARGUID charGuid)
{
LottoEntryMap::iterator it = entries_.find(charGuid);
if (it == entries_.end()) { return NULL; }
return &(it->second);
}
LottoEntry* Lotto::NewEntry(CHARGUID charGuid)
{
LottoEntryMap::iterator it = entries_.find(charGuid);
if (it == entries_.end()) {
LottoEntry entry;
entries_.insert(std::pair<CHARGUID, LottoEntry>(charGuid, entry));
it = entries_.find(charGuid);
ASSERT(it != entries_.end());
}
return &(it->second);
}
void Lotto::ReleaseEntry(CHARGUID charGuid)
{
entries_.erase(charGuid);
}
int Lotto::OnHeimConsumption(CHARGUID charGuid, eHEIM_CONSUMPTION_TYPE type, INT64 amount, int percentage)
{
if (config_.IsEffectiveConsumption(type) == false) { return -1; }
// 적립율 적용
float rate;
if (percentage > -1) {
// 적립율이 지정되어 있으면 해당 적립율 사용
rate = (float)percentage * 0.01f;
}
else {
// 아니면 글로벌 적립율 사용
rate = config_.GetAccumulationRate();
}
// 총 적립액 갱신
results_[0].totalAccum += (INT64)(amount * rate);
#ifdef _NA_20101011_HEIM_LOTTO_CASH_TICKET
// 응모권 캐시 판매 기능이 활성화되면 하임 적립에 따른 자동 지급은 사용되지 않는다.
return -1;
#else
// 캐릭터 별로 지출에 따라 복권 용지 자동 지급
LottoEntry* entry = FindEntry(charGuid);
if (entry == NULL) { return -1; }
int numTotalTickets = entry->numRemainingTickets + entry->numUsedTickets;
if (numTotalTickets >= LottoConfig::MAX_TICKETS) { return -1; }
INT64 payment = entry->payment;
payment += amount;
if (payment < config_.GetTicketPrice()) {
entry->payment = (DWORD)payment;
return -1;
}
int numAffordableTickets = (int)(payment / config_.GetTicketPrice());
entry->payment = (DWORD)(payment % config_.GetTicketPrice());
int numNewTickets = min(numAffordableTickets, (LottoConfig::MAX_TICKETS - numTotalTickets));
int numTickets = entry->numRemainingTickets + numNewTickets;
numTotalTickets = numTickets + entry->numUsedTickets;
if (numTotalTickets >= LottoConfig::MAX_TICKETS) { entry->payment = 0; }
return numTickets;
#endif // _NA_20101011_HEIM_LOTTO_CASH_TICKET
}
void Lotto::RollOver(BYTE* nextWinningNum)
{
// 결과 목록
for (int i = 0; i < min(numResults_, LottoConfig::NUM_RECENT_DRAWS - 1); ++i) {
results_[i + 1] = results_[i];
}
LottoResult& currentDrawResult = results_[0];
currentDrawResult.Reset();
if (numResults_ > 0) {
currentDrawResult.drawNum = results_[1].drawNum + 1;
// 전회 1등 당첨자가 없으면 1등 당첨금 이월 처리
if (results_[1].numWinners == 0) {
currentDrawResult.totalAccum = results_[1].jackpot;
results_[1].jackpot = 0;
}
}
else {
currentDrawResult.drawNum = 1;
}
// 차회 당첨 번호 저장
for (int i = 0; i < LottoConfig::NUM_PICKS; ++i) {
currentDrawResult.winningNum[i] = nextWinningNum[i];
}
numResults_ = min(numResults_ + 1, LottoConfig::NUM_RECENT_DRAWS);
ASSERT(numResults_ > 0);
if (numResults_ <= 0) { return; }
// 과거 응모 내역 삭제 및 새로운 현재 회차 응모 내역 초기화
int minDrawNum = results_[numResults_ - 1].drawNum;
LottoEntryMap::iterator it(entries_.begin());
LottoEntryMap::iterator end(entries_.end());
for ( ; it != end; ++it) {
LottoEntry& entry = it->second;
entry.payment = 0;
// 보유 중인 복권 용지는 유지
entry.numUsedTickets = 0;
LottoTicketList& tickets = entry.tickets;
while (!tickets.empty() && (int)tickets.back().drawNum < minDrawNum) {
tickets.pop_back();
}
}
}
int Lotto::CheckMatches(BYTE* numbers) const
{
// 현재 진행 중인 회차가 없으면 -1을 리턴
if (numResults_ < 1) { return -1; }
// [TODO] 개선 필요
int n = 0;
for (int i = 0; i < LottoConfig::NUM_PICKS; ++i) {
for (int j = 0; j < LottoConfig::NUM_PICKS; ++j) {
register BYTE refNum = results_[0].winningNum[j];
if (numbers[i] == refNum) {
++n;
break;
}
// 오름차순으로 정렬되어 있기 때문에
else if (numbers[i] < refNum) {
break;
}
}
}
return n;
}
void Lotto::PickRandomNumbers(BYTE* numbers)
{
// [TODO] 개선 필요 : 일단은 순진하게 단순한 구현
for (int i = 0; i < LottoConfig::NUM_PICKS; ++i) {
int pick;
bool isUnique = false;
do {
pick = random(LottoConfig::MIN_NUMBER, LottoConfig::MAX_NUMBER);
int j = 0;
for (; j < i; ++j) {
if (pick == numbers[j]) { break; }
}
if (j == i) { isUnique = true; }
} while (!isUnique);
numbers[i] = pick;
}
Sort(numbers);
}
void Lotto::Sort(BYTE* numbers)
{
qsort(numbers, LottoConfig::NUM_PICKS, sizeof(BYTE), compareByte);
}
bool Lotto::AreValidPicks(BYTE* numbers)
{
DWORD32 marks = 0;
for (int i = 0; i < LottoConfig::NUM_PICKS; ++i) {
BYTE n = numbers[i];
if ( n < LottoConfig::MIN_NUMBER || LottoConfig::MAX_NUMBER < n ) { return false; }
if ( (marks & (1 << n)) != 0 ) { return false; }
marks |= (1 << n);
}
return true;
}
void Lotto::CalculatePrizes()
{
INT64 funds = GetCurrentTotalAccum();
// 고정 금액 먼저 처리
for (int i = config_.GetNumPrizes(); i > 0; --i)
{
const LottoPrize* prizeSetting = config_.GetPrize(i);
ASSERT(prizeSetting != NULL);
if (prizeSetting == NULL) { continue; }
if (prizeSetting->type == LottoPrize::TYPE_AMOUNT)
{
int numWinners = numWinners_[i - 1];
INT64 rankTotalPrize = prizeSetting->value.amount * numWinners;
prizes_[i - 1] = prizeSetting->value.amount;
// 기금보다 고정 금액 당첨금이 많으면 적자
funds = max(funds - rankTotalPrize, 0);
SUNLOG(eFULL_LOG, "[Lotto] %d Prize %I64d x %d", i, prizes_[i - 1], numWinners);
}
}
if (funds > 0)
{
// 기금이 남았으면 비율에 의한 가변 금액 처리
double bonusRate = 0.0f;
for (int i = config_.GetNumPrizes(); i > 0; --i)
{
const LottoPrize* prizeSetting = config_.GetPrize(i);
ASSERT(prizeSetting != NULL);
if (prizeSetting == NULL) { continue; }
if (prizeSetting->type == LottoPrize::TYPE_RATIO)
{
int numWinners = numWinners_[i - 1];
INT64 rankTotalPrize;
if (i > 1) { // 1등 미만
rankTotalPrize = (INT64)(funds * prizeSetting->value.ratio);
if (numWinners > 0) {
prizes_[i - 1] = (INT64)(rankTotalPrize / numWinners);
}
else {
prizes_[i - 1] = rankTotalPrize;
bonusRate += prizeSetting->value.ratio; // 1등 당첨금에 합산
}
}
else { // 1등
rankTotalPrize = (INT64)(funds * (prizeSetting->value.ratio + bonusRate));
if (numWinners > 0) {
prizes_[i - 1] = (INT64)(rankTotalPrize / numWinners);
}
else {
prizes_[i - 1] = rankTotalPrize;
}
// 현재 회차 결과 레코드에 저장
ASSERT(numResults_ > 0);
if (numResults_ > 0) {
results_[0].numWinners = numWinners;
results_[0].jackpot = prizes_[i - 1];
}
}
SUNLOG(eFULL_LOG, "[Lotto] %d Prize %I64d x %d", i, prizes_[i - 1], numWinners);
}
}
}
}
void Lotto::TransitState(Lotto::State newState)
{
ASSERT(newState < Lotto::NUM_STATES);
if (newState >= Lotto::NUM_STATES) { return; }
if (currentState_ != NULL) { currentState_->OnExit(); } // 기존 상태 탈출 액션
LottoState* state = states_[newState];
state->OnEnter(); // 새로운 상태 진입 액션
currentState_ = state; // 상태 전이
}
void Lotto::GetCurrentWinningNum(BYTE* numbers) const
{
for (int i = 0; i < LottoConfig::NUM_PICKS; ++i) {
numbers[i] = 0;
}
if (numResults_ < 1) { return; }
for (int i = 0; i < LottoConfig::NUM_PICKS; ++i) {
numbers[i] = results_[0].winningNum[i];
}
}
void Lotto::ForceRollOver()
{
// 강제로 추첨 단계로 진행
TransitState(Lotto::DRAWING_STATE);
}
static int compareByte(const void* arg1, const void* arg2)
{
return (*(BYTE*)arg1 - *(BYTE*)arg2);
}
#ifdef _NA_20101011_HEIM_LOTTO_CASH_TICKET
void TicketActivationXactions::Add(CHARGUID char_guid, SLOTIDX index, POSTYPE pos)
{
if (Find(char_guid) != NULL) {
return;
}
TicketActivationXactionInfo info;
info.index = index;
info.pos = pos;
map_.insert(std::pair<CHARGUID, TicketActivationXactionInfo>(char_guid, info));
}
void TicketActivationXactions::Remove(CHARGUID char_guid)
{
map_.erase(char_guid);
}
void TicketActivationXactions::Clear()
{
map_.clear();
}
const TicketActivationXactionInfo* TicketActivationXactions::Find(CHARGUID char_guid) const
{
XactionInfoMap::const_iterator it = map_.find(char_guid);
if (it == map_.end()) {
return NULL;
}
return &(it->second);
}
#endif // _NA_20101011_HEIM_LOTTO_CASH_TICKET