knightonline/server/GameServer/KingSystem.cpp

1368 lines
36 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "stdafx.h"
#include "KingSystem.h"
#include "../shared/DateTime.h"
#include "Map.h"
CKingSystem::CKingSystem()
{
m_byNation = 0;
m_byType = ELECTION_TYPE_NO_TERM;
m_sYear = 0;
m_byMonth = m_byDay = m_byHour = m_byMinute = 0;
m_byImType = 0;
m_sImYear = 0;
m_byImMonth = m_byImDay = m_byImHour = m_byImMinute = 0;
m_byNoahEvent = m_byNoahEvent_Day = m_byNoahEvent_Hour = m_byNoahEvent_Minute = 0;
m_sNoahEvent_Duration = 0;
winPercent = 0;
m_byExpEvent = m_byExpEvent_Day = m_byExpEvent_Hour = m_byExpEvent_Minute;
m_sExpEvent_Duration = 0;
m_nTribute = 0;
m_nTerritoryTax = m_nNationalTreasury = 0;
m_bSentFirstMessage = false;
}
/**
* @brief Handles timed events related to the King system.
*/
void CKingSystem::CheckKingTimer()
{
// Get the current time.
uint8 bCurMonth = g_localTime.tm_mon + 1,
bCurDay = g_localTime.tm_mday,
bCurHour = g_localTime.tm_hour,
bCurMinute = g_localTime.tm_min;
// If there's an ongoing coin or XP event...
if (m_byNoahEvent || m_byExpEvent)
CheckSpecialEvent();
switch (m_byType)
{
case ELECTION_TYPE_NO_TERM:
{
DateTime dt(m_sYear, m_byMonth, m_byDay, m_byHour, m_byMinute);
// Nominations start a day before the election.
dt.AddDays(-1);
if (bCurMonth == dt.GetMonth()
&& bCurDay == dt.GetDay()
&& bCurHour == dt.GetHour()
&& bCurMinute == dt.GetMinute())
{
UpdateElectionStatus(ELECTION_TYPE_NOMINATION);
g_pMain->SendFormattedResource(IDS_KING_RECOMMEND_TIME, m_byNation, false);
SendUDP_ElectionStatus(m_byType);
ResetElectionLists();
LoadRecommendList();
}
}break;
case ELECTION_TYPE_NOMINATION://adaylık
{
DateTime dt(m_sYear, m_byMonth, m_byDay, m_byHour, m_byMinute);
// Nominations last until an hour before the scheduled election time.
dt.AddHours(-1);
dt.AddDays(2);
if (bCurMonth == dt.GetMonth()
&& bCurDay == dt.GetDay()
&& bCurHour == dt.GetHour()
&& bCurMinute == dt.GetMinute())
{
UpdateElectionStatus(ELECTION_TYPE_PRE_ELECTION);
g_pMain->SendFormattedResource(IDS_KING_RECOMMEND_FINISH_TIME, m_byNation, false);
LoadRecommendList();
SendUDP_ElectionStatus(m_byType);
}
if (!(bCurMinute % 30) && !m_bSentFirstMessage)
{
m_bSentFirstMessage = true;
g_pMain->SendFormattedResource(IDS_KING_PERIOD_OF_RECOMMEND_MESSAGE, m_byNation, true);
break; // awkward, but official behaviour.
}
m_bSentFirstMessage = false;
} break;
// This state seems like it could be completely removed.
// Leaving until the system's more complete, just in case.
case ELECTION_TYPE_PRE_ELECTION://1 saatlik ara
{
DateTime dt(m_sYear, m_byMonth, m_byDay, m_byHour, m_byMinute);
dt.AddDays(2);
if (bCurMonth == dt.GetMonth()
&& bCurDay == dt.GetDay()
&& bCurHour == dt.GetHour()
&& bCurMinute == dt.GetMinute())
{
UpdateElectionStatus(ELECTION_TYPE_ELECTION);
g_pMain->SendFormattedResource(IDS_KING_ELECTION_TIME, m_byNation, false);
SendUDP_ElectionStatus(m_byType);
}
} break;
case ELECTION_TYPE_ELECTION://secim zamanı
{
DateTime dt(m_sYear, m_byMonth, m_byDay, m_byHour, m_byMinute);
// Elections last for an hour.
dt.AddDays(5);
if (bCurMonth == dt.GetMonth()
&& bCurDay == dt.GetDay()
&& bCurHour == dt.GetHour()
&& bCurMinute == dt.GetMinute())
{
UpdateElectionStatus(ELECTION_TYPE_TERM_STARTED);
if (GetElectionResult(m_byNation, this))
{
g_pMain->SendFormattedResource(IDS_KING_ELECTION_RESULT_MESSAGE, m_byNation, true, winPercent, m_strKingName.c_str());
CUser * pOldKingUser = g_pMain->GetUserPtr(m_strOldKingName, TYPE_CHARACTER);
if (pOldKingUser != nullptr)
pOldKingUser->m_bRank = 0;
CUser * pNewKingUser = g_pMain->GetUserPtr(m_strKingName, TYPE_CHARACTER);
if (pNewKingUser != nullptr)
pNewKingUser->m_bRank = 1;
}
return;
}
if (!(bCurMinute % 30) && !m_bSentFirstMessage)
{
m_bSentFirstMessage = true;
g_pMain->SendFormattedResource(IDS_KING_PERIOD_OF_ELECTION_MESSAGE, m_byNation, true);
break; // awkward, but official behaviour.
}
m_bSentFirstMessage = false;
} break;
case ELECTION_TYPE_TERM_STARTED:
{
DateTime dt(m_sYear, m_byMonth, m_byDay, m_byHour, m_byMinute);
// Nominations start a day before the election.
dt.AddDays(35);
dt.AddMinutes(0);
if (bCurMonth == dt.GetMonth()
&& bCurDay == dt.GetDay()
&& bCurHour == dt.GetHour()
&& bCurMinute == dt.GetMinute())
{
UpdateElectionStatus(ELECTION_TYPE_TERM_ENDED);
}
} break;
case ELECTION_TYPE_TERM_ENDED:
{
DateTime dt(m_sYear, m_byMonth, m_byDay, m_byHour, m_byMinute);
// Nominations start a day before the election.
dt.AddDays(5);
dt.AddMinutes(1);
if (bCurMonth == dt.GetMonth()
&& bCurDay == dt.GetDay()
&& bCurHour == dt.GetHour()
&& bCurMinute == dt.GetMinute())
{
UpdateElectionStatus(ELECTION_TYPE_NOMINATION);
g_pMain->SendFormattedResource(IDS_KING_RECOMMEND_TIME, m_byNation, false);
SendUDP_ElectionStatus(m_byType);
ResetElectionLists();
LoadRecommendList();
}
} break;
}
switch (m_byImType)
{
case 1: // 47 hours after the impeachment time, call GetImpeachmentRequestResult()
{
DateTime dt(m_sImYear, m_byImMonth, m_byImDay, m_byImHour, m_byImMinute);
dt.AddHours(47);
if (bCurMonth == dt.GetMonth()
&& bCurDay == dt.GetDay()
&& bCurHour == dt.GetHour()
&& bCurMinute == dt.GetMinute())
{
// GetImpeachmentRequestResult();
}
} break;
case 2: // 2 days (48 hours) after the impeachment time, set the impeachment type to 3
// and send IDS_KING_IMPEACHMENT_ELECTION_MESSAGE as WAR_SYSTEM_CHAT
{
DateTime dt(m_sImYear, m_byImMonth, m_byImDay, m_byImHour, m_byImMinute);
dt.AddDays(2);
if (bCurMonth == dt.GetMonth()
&& bCurDay == dt.GetDay()
&& bCurHour == dt.GetHour()
&& bCurMinute == dt.GetMinute())
{
m_byImType = 3;
g_pMain->SendFormattedResource(IDS_KING_IMPEACHMENT_ELECTION_MESSAGE, m_byNation, false);
}
} break;
case 3: // 3 days (72 hours) after the impeachment time, set the impeachment type to 4
// and call GetImpeachmentElectionResult()
{
DateTime dt(m_sImYear, m_byImMonth, m_byImDay, m_byImHour, m_byImMinute);
dt.AddDays(3);
if (bCurMonth == dt.GetMonth()
&& bCurDay == dt.GetDay()
&& bCurHour == dt.GetHour()
&& bCurMinute == dt.GetMinute())
{
m_byImType = 4;
//GetImpeachmentElectionResult();
}
} break;
}
}
/**
* @brief Updates the election status.
*
* @param byElectionStatus The election status.
*/
void CKingSystem::UpdateElectionStatus(uint8 byElectionStatus)
{
Packet result(WIZ_KING, uint8(KING_ELECTION));
result << uint8(KING_ELECTION) << uint8(KING_ELECTION_UPDATE_STATUS)
<< m_byNation << byElectionStatus;
m_byType = byElectionStatus;
g_pMain->AddDatabaseRequest(result);
}
/**
* @brief Updates the election list.
*
* @param byElectionListType Which list are we referring to?
* 1 = ?
* 2 = ?
* 3 = senator list
* 4 = voted for king list
* @param bDeleteList true to delete the list.
* @param sClanID Identifier for the clan.
* @param strUserID Identifier for the user.
* @param pUser The user making the request, if applicable.
*/
void CKingSystem::UpdateElectionList(uint8 byElectionListType, bool bDeleteList, uint16 sClanID, std::string & strUserID, CUser * pUser /*= nullptr*/)
{
// byElectionListType:
// 3 = senator
Packet result(WIZ_KING, uint8(KING_ELECTION));
result << uint8(KING_ELECTION) // special, looks redundant but implies these special opcodes
<< uint8(KING_ELECTION_UPDATE_LIST) << m_byNation
<< byElectionListType << bDeleteList
<< sClanID << strUserID;
g_pMain->AddDatabaseRequest(result, pUser);
KingElectionList * pList = nullptr;
switch (byElectionListType)
{
case 3:
pList = &m_senatorList;
break;
case 4:
pList = &m_candidateList;
break;
}
if (pList == nullptr)
return;
KingElectionList::iterator itr = pList->find(strUserID);
if (bDeleteList)
{
if (itr != pList->end())
{
delete itr->second;
pList->erase(itr);
}
}
else
{
}
}
/**
* @brief Checks to see if a special (coin/XP) event should end.
*/
void CKingSystem::CheckSpecialEvent()
{
// Get the current time.
uint8 bCurDay = g_localTime.tm_mday,
bCurHour = g_localTime.tm_hour,
bCurMinute = g_localTime.tm_min;
int16 sEventExpiry;
// If there's an exp event ongoing...
if (m_byExpEvent)
{
if (bCurDay == m_byExpEvent_Day)
sEventExpiry = bCurMinute + 60 * (bCurHour - m_byExpEvent_Hour) - m_byExpEvent_Minute;
else
sEventExpiry = bCurMinute + 60 * (bCurHour - m_byExpEvent_Hour + 24) - m_byExpEvent_Minute;
if (sEventExpiry > m_sExpEvent_Duration)
{
m_byExpEvent = 0;
m_byExpEvent_Day = 0;
m_byExpEvent_Hour = 0;
m_byExpEvent_Minute = 0;
m_sExpEvent_Duration = 0;
// TODO: Tell other servers that the event expired (i.e. via UDP)
// TODO: Update the bonuses on the AI server's side (which we don't have implemented).
// TODO: Update the KING_SYSTEM table to reset the stored event data there too.
g_pMain->SendFormattedResource(IDS_KING_EXP_BONUS_EVENT_STOP, m_byNation, false);
// KingNotifyMessage(IDS_KING_EXP_BONUS_EVENT_STOP, m_byNation, WAR_SYSTEM_CHAT);
// 31 translates to a resource ID of 230, other args: 0, 0, 0, 0
}
}
// If there's a coin event ongoing...
if (m_byNoahEvent)
{
if (bCurDay == m_byNoahEvent_Day)
sEventExpiry = bCurMinute + 60 * (bCurHour - m_byNoahEvent_Hour) - m_byNoahEvent_Minute;
else
sEventExpiry = bCurMinute + 60 * (bCurHour - m_byNoahEvent_Hour + 24) - m_byNoahEvent_Minute;
if (sEventExpiry > m_sNoahEvent_Duration)
{
m_byNoahEvent = 0;
m_byNoahEvent_Day = 0;
m_byNoahEvent_Hour = 0;
m_byNoahEvent_Minute = 0;
m_sNoahEvent_Duration = 0;
// TODO: Tell other servers that the event expired (i.e. via UDP)
// TODO: Update the bonuses on the AI server's side (which we don't have implemented).
// TODO: Update the KING_SYSTEM table to reset the stored event data there too.
g_pMain->SendFormattedResource(IDS_KING_NOAH_BONUS_EVENT_STOP, m_byNation, false);
// KingNotifyMessage(IDS_KING_NOAH_BONUS_EVENT_STOP, m_byNation, WAR_SYSTEM_CHAT);
// 32 translates to a resource ID of 231, other args: 0, 0, 0, 0
}
}
}
/**
* @brief Generates a list of the top 10 clan leaders eligible to nominate a King.
* NOTE: These players are senators.
*/
void CKingSystem::LoadRecommendList()
{
m_top10ClanSet.clear();
for (int i = 1; i <= 1000; i++)
{
// Lookup the clan ranking #i.
_KNIGHTS_RATING * pRating = g_pMain->m_KnightsRatingArray[m_byNation - 1].GetData(i);
CKnights * pKnights = nullptr;
// Ignore this entry if no such clan is ranked #i
if (pRating == nullptr
// or for whatever reason the clan no longer exists...
|| (pKnights = g_pMain->GetClanPtr(pRating->sClanID)) == nullptr)
continue;
// Add user as senator.
UpdateElectionList(3, false, pRating->sClanID, pKnights->m_strChief);
// If the senator's online, we can send them an announcement
// to tell them they can nominate a King.
CUser * pUser = g_pMain->GetUserPtr(pKnights->m_strChief, TYPE_CHARACTER);
if (pUser != nullptr)
{
Packet result;
std::string notice;
// %s can nominate a King
g_pMain->GetServerResource(IDS_KING_RECOMMEND_REQUEST_MESSAGE, &notice, pKnights->m_strChief.c_str());
// Wrap it in a "#### NOTICE : ####" block.
g_pMain->GetServerResource(IDP_ANNOUNCEMENT, &notice, notice.c_str());
// Construct & send the chat/announcement packet
ChatPacket::Construct(&result, WAR_SYSTEM_CHAT, &notice);
pUser->Send(&result);
}
// add to our top 10 ranked clan set.
m_top10ClanSet.insert(pRating->sClanID);
}
}
/**
* @brief This sends the appropriate resource as a notice to the server (or to a particular
* user)
* Beyond initial reversing, this doesn't need to exist -- in fact, not even going to
* use it. It's just a temporary point of reference.
*
* @param nResourceID Identifier for the resource found in the SERVER_RESOURCE table.
* @param byNation The nation to send the notice/announcement to.
* @param chatType The chat type (notice/announcement).
*/
void CKingSystem::KingNotifyMessage(uint32 nResourceID, int byNation, ChatType chatType)
{
std::string result;
switch (nResourceID)
{
// Resource ID (SERVER_RESOURCE) // ID used internally (officially)
case IDS_KING_RECOMMEND_TIME: // 1 (none)
case IDS_KING_RECOMMEND_FINISH_TIME: // 2 (none)
case IDS_KING_ELECTION_TIME: // 3 (none)
case IDS_KING_IMPEACHMENT_REQUEST_TIME: // 4 (none)
case IDS_KING_IMPEACHMENT_ELECTION_TIME: // 5 (none)
case IDS_KING_REIGN_TIME: // 7 (none)
case IDS_KING_KARUS_PRIZE_EVENT_MESSAGE: // 11 (awarded %s %d coins)
case IDS_KING_ELMO_PRIZE_EVENT_MESSAGE: // 12 (awarded %s %d coins)
case IDS_KING_KARUS_FUGITIVE_EVENT_MESSAGE_1: // 13 (awarded %s %d coins -- probably inaccurate though, see below)
case IDS_KING_ELMO_FUGITIVE_EVENT_MESSAGE_1: // 14 (awarded %s %d coins -- probably inaccurate though, see below)
case IDS_KING_FUGITIVE_EVENT_MESSAGE_2: // 15 (%s killed %s and received %d coins as a reward)
case IDS_KING_KARUS_WEATHER_FINE_EVENT: // 16 (none)
case IDS_KING_KARUS_WEATHER_RAIN_EVENT: // 17 (none)
case IDS_KING_KARUS_WEATHER_SNOW_EVENT: // 18 (none)
case IDS_KING_ELMO_WEATHER_FINE_EVENT: // 19 (none)
case IDS_KING_ELMO_WEATHER_RAIN_EVENT: // 20 (none)
case IDS_KING_ELMO_WEATHER_SNOW_EVENT: // 21 (none)
case IDS_KING_KARUS_NOAH_BONUS_EVENT: // 22 (%d%% increased coin rate)
case IDS_KING_KARUS_EXP_BONUS_EVENT: // 23 (%d%% increased XP rate)
case IDS_KING_ELMO_NOAH_BONUS_EVENT: // 24 (%d%% increased coin rate)
case IDS_KING_ELMO_EXP_BONUS_EVENT: // 25 (%d%% increased XP rate)
case IDS_KING_RECOMMEND_REQUEST_MESSAGE: // 26 (%s can nominate a King)
case IDS_KING_CANDIDACY_RECOMMEND_MESSAGE: // 27 (%s has nominated %s as a King)
case IDS_KING_PERIOD_OF_RECOMMEND_MESSAGE: // 28 (none)
case IDS_KING_PERIOD_OF_ELECTION_MESSAGE: // 29 (none)
case IDS_KING_ELECTION_RESULT_MESSAGE: // 30 (%d%% of the vote was won by %s)
case IDS_KING_EXP_BONUS_EVENT_STOP: // 31 (none)
case IDS_KING_NOAH_BONUS_EVENT_STOP: // 32 (none)
case IDS_KING_IMPEACHMENT_REQUEST_MESSAGE: // 33 (none)
case IDS_KING_IMPEACHMENT_PASS_MESSAGE: // 34 (none)
case IDS_KING_IMPEACHMENT_REJECT_MESSAGE: // 35 (none)
case IDS_KING_IMPEACHMENT_ELECTION_MESSAGE: // 36 (none)
case IDS_KING_IMPEACHMENT_ELECTION_YES_RESULT_MESSAGE: // 37 (none)
case IDS_KING_IMPEACHMENT_ELECTION_NO_RESULT_MESSAGE: // 38 (none)
break;
}
}
/**
* @brief Wrapper for the King system's packet handler.
*
* @param pUser The user sending the packet.
* @param pkt The packet.
*/
void CKingSystem::PacketProcess(CUser * pUser, Packet & pkt)
{
if (pUser == nullptr)
return;
// ... onwards, to official-like code.
CKingSystem * pKingSystem = g_pMain->m_KingSystemArray.GetData(pUser->GetNation());
if (pKingSystem != nullptr)
pKingSystem->KingPacketProcess(pUser, pkt);
}
/**
* @brief The real packet handler for the King system.
*
* @param pUser The user sending the packet.
* @param pkt The packet.
*/
void CKingSystem::KingPacketProcess(CUser * pUser, Packet & pkt)
{
switch (pkt.read<uint8>())
{
case KING_ELECTION:
ElectionSystem(pUser, pkt);
break;
case KING_IMPEACHMENT:
ImpeachmentSystem(pUser, pkt);
break;
case KING_TAX:
KingTaxSystem(pUser, pkt);
break;
case KING_EVENT:
KingSpecialEvent(pUser, pkt);
break;
case KING_NATION_INTRO:
break;
}
}
/**
* @brief Election system.
*
* @param pUser The user sending the packet.
* @param pkt The packet.
*/
void CKingSystem::ElectionSystem(CUser * pUser, Packet & pkt)
{
switch (pkt.read<uint8>())
{
case KING_ELECTION_SCHEDULE:
ElectionScheduleConfirmation(pUser, pkt);
break;
case KING_ELECTION_NOMINATE:
CandidacyRecommend(pUser, pkt);
break;
case KING_ELECTION_NOTICE_BOARD:
CandidacyNoticeBoard(pUser, pkt);
break;
case KING_ELECTION_POLL:
ElectionPoll(pUser, pkt);
break;
case KING_ELECTION_RESIGN:
CandidacyResign(pUser, pkt);
break;
}
}
/**
* @brief "Check election day" button at the election NPC
*
* @param pUser The user sending the packet.
* @param pkt The packet.
*/
void CKingSystem::ElectionScheduleConfirmation(CUser * pUser, Packet & pkt)
{
Packet result(WIZ_KING, uint8(KING_ELECTION));
result << uint8(KING_ELECTION_SCHEDULE);
switch (m_byImType)
{
// No impeachment, send election date.
case 0:
{
// Client expects month as 1,12 (tm_mon is 0,11)
uint8 byElectionMonth = g_localTime.tm_mon + 1;
/* When's the next election? */
// If we've passed the election date, we need next month's election.
// (NOTE: this is official behaviour; it disregards the month set in the table)
if (g_localTime.tm_mday > m_byDay)
{
// Next month is January? Make it so.
++byElectionMonth;
while (byElectionMonth > 12)
byElectionMonth -= 12;
}
result << uint8(1) // election type
<< byElectionMonth
<< m_byDay << m_byHour << m_byMinute;
} break;
// Last scheduled impeachment?
case 1:
{
result << uint8(3)
<< m_byImMonth
<< m_byImDay << m_byImHour << m_byImMinute;
} break;
// Next impeachment?
case 3:
{
// This should not be necessary, but will leave.
uint8 byImpeachmentMonth = m_byImMonth;
while (byImpeachmentMonth > 12)
m_byImMonth -= 12;
result << uint8(2)
<< byImpeachmentMonth
<< m_byImDay << m_byImHour << m_byImMinute;
} break;
}
pUser->Send(&result);
}
/**
* @brief Handles King candidacy recommendations by
* leaders of top 10 clans.
*
* @param pUser The user sending the packet.
* @param pkt The packet.
*/
void CKingSystem::CandidacyRecommend(CUser * pUser, Packet & pkt)
{
Packet result(WIZ_KING, uint8(KING_ELECTION));
std::string strUserID;
pkt.SByte();
pkt >> strUserID;
if (strUserID.empty() || strUserID.length() > MAX_ID_SIZE)
return;
result << uint8(KING_ELECTION_NOMINATE);
// Make sure it's nomination time.
if (m_byType != ELECTION_TYPE_NOMINATION)
{
result << int16(-2);
pUser->Send(&result);
return;
}
LoadRecommendList();
// Make sure the user nominating a King is a clan leader
if (!pUser->isClanLeader()
// ... of a top 10 clan.
|| m_top10ClanSet.find(pUser->GetClanID()) == m_top10ClanSet.end()
// ... and they haven't resigned their candidacy.
|| m_resignedCandidateList.find(pUser->m_strUserID) != m_resignedCandidateList.end())
{
result << int16(-3);
pUser->Send(&result);
return;
}
// Send request to database.
result << strUserID;
g_pMain->AddDatabaseRequest(result, pUser);
}
/**
* @brief Inserts the nominated candidate to the election list.
*
* @param strNominee The nominee.
*/
void CKingSystem::InsertNominee(std::string & strNominee)
{
// All nominees must be senators.
// No need to create duplicate data, so just find & reuse the same data.
KingElectionList::iterator senatorItr = m_senatorList.find(strNominee);
if (senatorItr == m_senatorList.end())
return;
// Now see if the candidate already exists in the list.
// If they don't, no need to do anything.
KingElectionList::iterator candidateItr = m_candidateList.find(strNominee);
if (candidateItr != m_candidateList.end())
return;
// Copy the information we need from our senator list entry
// NOTE: This is fairly dumb, so we should work this out when the system's functional and
// we're open to straying from official table designs.
_KING_ELECTION_LIST * pEntry = new _KING_ELECTION_LIST;
memcpy(pEntry, senatorItr->second, sizeof(_KING_ELECTION_LIST));
m_candidateList.insert(make_pair(strNominee, pEntry));
}
/**
* @brief Candidacy notice board system.
*
* @param pUser The user sending the packet.
* @param pkt The packet.
*/
void CKingSystem::CandidacyNoticeBoard(CUser * pUser, Packet & pkt)
{
Packet result(WIZ_KING, uint8(KING_ELECTION));
uint8 opcode = pkt.read<uint8>();
bool bSuccess = false;
result << uint8(KING_ELECTION_NOTICE_BOARD) << opcode;
switch (opcode)
{
// Write to the candidacy noticeboard
case KING_CANDIDACY_BOARD_WRITE:
{
if (m_byType != ELECTION_TYPE_NOMINATION
&& m_byType != ELECTION_TYPE_PRE_ELECTION
&& m_byType != ELECTION_TYPE_ELECTION)
{
result << int16(-1);
pUser->Send(&result);
return;
}
std::string strNotice;
pkt >> strNotice;
// The official max length of invalid notices is 480 bytes.
// The size of the column is 1024 bytes. Possibly this limit was
// imposed purely for the sake of the shared memory queue?
if (strNotice.empty() || strNotice.length() > 480)
{
result << int16(-2);
pUser->Send(&result);
return;
}
// Check to make sure the user's in the candidacy list.
KingCandidacyNoticeBoardMap::iterator itr = m_noticeBoardMap.find(pUser->m_strUserID);
if (itr == m_noticeBoardMap.end())
{
result << int16(-3);
pUser->Send(&result);
return;
}
// Update the noticeboard.
itr->second = strNotice;
// Preserve the write position so that we can reuse the packet
// without requiring junk data.
size_t wpos = result.wpos();
// Update the user.
result << int16(1);
pUser->Send(&result);
// Now reuse the packet for the database request;
// overwrite the result sent to the client, so we don't need to send it.
result.put(wpos, strNotice.c_str(), strNotice.length());
// Update the database.
result << strNotice;
g_pMain->AddDatabaseRequest(result, pUser);
} return;
// Read from the candidacy noticeboard
case KING_CANDIDACY_BOARD_READ:
{
if (m_byType != ELECTION_TYPE_NOMINATION
&& m_byType != ELECTION_TYPE_PRE_ELECTION
&& m_byType != ELECTION_TYPE_ELECTION)
{
result << int16(-1);
pUser->Send(&result);
return;
}
pkt >> opcode;
result << opcode;
// List candidates
if (opcode == 1)
{
result << int16(1) // success
<< uint8(m_noticeBoardMap.size());
result.SByte();
foreach (itr, m_noticeBoardMap)
result << itr->first;
}
else if (opcode == 2)
{
std::string strCandidate;
pkt.SByte();
pkt >> strCandidate;
if (strCandidate.empty() || strCandidate.length() > MAX_ID_SIZE)
return;
// Does this candidate actually exist in the list?
KingCandidacyNoticeBoardMap::iterator itr = m_noticeBoardMap.find(strCandidate);
if (itr == m_noticeBoardMap.end()
// and is the message actually set?
|| itr->second.empty())
{
result << int16(-2);
/*
// Not implementing this oddity unless there's a really good
// client reason to explain this.
result.DByte();
result << strCandidate
<< <name of last user on notice board>;
*/
}
else
{
result.DByte();
result << int16(1) // success
<< strCandidate << itr->second;
}
}
pUser->Send(&result);
} return;
case 4:
if (m_byType == ELECTION_TYPE_NOMINATION
|| m_byType == ELECTION_TYPE_PRE_ELECTION
|| m_byType == ELECTION_TYPE_ELECTION)
{
// TODO: Find user in (candidate list?), if not found we can break out of here and error.
if (1 == 2)
break;
//
bSuccess = true;
}
break;
case 5:
if (m_byType == ELECTION_TYPE_NOMINATION
|| m_byType == ELECTION_TYPE_PRE_ELECTION
|| m_byType == ELECTION_TYPE_ELECTION)
bSuccess = true;
break;
default:
return;
}
result << int16(bSuccess ? 1 : -1);
if (opcode == 4)
result << bSuccess;
pUser->Send(&result);
}
/**
* @brief Election poll.
*
* @param pUser The user sending the packet.
* @param pkt The packet.
*/
void CKingSystem::ElectionPoll(CUser * pUser, Packet & pkt)
{
Packet result(WIZ_KING, uint8(KING_ELECTION));
uint8 opcode = pkt.read<uint8>();
result << uint8(KING_ELECTION_POLL) << opcode;
// Make sure player's trying to vote during the
// election stage.
if (m_byType != ELECTION_TYPE_ELECTION)
{
result << int16(-1);
pUser->Send(&result);
return;
}
switch (opcode)
{
// Show candidate list
case 1:
{
uint8 count = (uint8)m_candidateList.size();
result << uint16(1) << count;
result.SByte();
foreach (itr, m_candidateList)
{
CKnights * pKnights = g_pMain->GetClanPtr(itr->second->sKnights);
result << uint8(0) << itr->first; // candidate's name
if (pKnights != nullptr)
result << pKnights->m_strName; // clan name
else
result << uint8(0); // no clan name
}
pUser->Send(&result);
} break;
// Vote for candidate
case 2:
{
std::string strCandidate;
pkt.SByte();
pkt >> strCandidate;
if (strCandidate.empty() || strCandidate.length() > MAX_ID_SIZE)
return;
// Candidate voted for isn't in list...
KingElectionList::iterator itr = m_candidateList.find(strCandidate);
if (itr == m_candidateList.end())
{
result << int16(-2);
pUser->Send(&result);
return;
}
// User's level is too low to vote.
if (pUser->GetLevel() < 20)
{
result << int16(-4);
pUser->Send(&result);
return;
}
UpdateElectionList( 4, // voted for King
false, // registering our vote, not deleting.
itr->second->sKnights, // clan ID
strCandidate, // candidate's name
pUser); // us, glorious us (so that it can let us know what happened with the request)
} break;
}
}
/**
* @brief Handles candidate resignations.
*
* @param pUser The user sending the packet.
* @param pkt The packet.
*/
void CKingSystem::CandidacyResign(CUser * pUser, Packet & pkt)
{
Packet result(WIZ_KING, uint8(KING_ELECTION));
result << uint8(KING_ELECTION_RESIGN);
// We can only submit a resignation if we're in the nomination stage.
if (m_byType != ELECTION_TYPE_NOMINATION)
{
result << int16(-2);
pUser->Send(&result);
return;
}
KingElectionList::iterator itr = m_candidateList.find(pUser->m_strUserID);
// Do we even exist in the candidate list?
if (itr == m_candidateList.end())
{
result << int16(-3);
pUser->Send(&result);
return;
}
// Add user to our resigned candidates list so they can't be re-nominated.
m_resignedCandidateList.insert(pUser->m_strUserID);
// Remove them from our main candidates list.
m_candidateList.erase(itr);
UpdateElectionList(4, // elected King
true, // remove ourselves from the list
itr->second->sKnights,
pUser->m_strUserID,
pUser);
}
/**
* @brief Impeachment system.
*
* @param pUser The user sending the packet.
* @param pkt The packet.
*/
void CKingSystem::ImpeachmentSystem(CUser * pUser, Packet & pkt)
{
switch (pkt.read<uint8>())
{
case KING_IMPEACHMENT_REQUEST:
ImpeachmentRequest(pUser, pkt);
break;
case KING_IMPEACHMENT_REQUEST_ELECT:
ImpeachmentRequestElect(pUser, pkt);
break;
case KING_IMPEACHMENT_LIST:
ImpeachmentList(pUser, pkt);
break;
case KING_IMPEACHMENT_ELECT:
ImpeachmentElect(pUser, pkt);
break;
case KING_IMPEACHMENT_REQUEST_UI_OPEN:
ImpeachmentRequestUiOpen(pUser, pkt);
break;
case KING_IMPEACHMENT_ELECTION_UI_OPEN:
ImpeachmentElectionUiOpen(pUser, pkt);
break;
}
}
void CKingSystem::ImpeachmentRequest(CUser * pUser, Packet & pkt) {}
void CKingSystem::ImpeachmentRequestElect(CUser * pUser, Packet & pkt) {}
void CKingSystem::ImpeachmentList(CUser * pUser, Packet & pkt) {}
void CKingSystem::ImpeachmentElect(CUser * pUser, Packet & pkt) {}
/**
* @brief Attempt to open the impeachment request UI.
*
* @param pUser The user sending the packet.
* @param pkt The packet.
*/
void CKingSystem::ImpeachmentRequestUiOpen(CUser * pUser, Packet & pkt)
{
Packet result(WIZ_KING, uint8(KING_IMPEACHMENT));
result << uint8(KING_IMPEACHMENT_REQUEST_UI_OPEN);
// Not able to make an impeachment request right now.
if (m_byImType != 1)
result << int16(-1);
// If they're not an senator...
else if (pUser->m_bRank != 2)
result << int16(-2);
// Able to make an impeachment request.
else
result << int16(1);
pUser->Send(&result);
}
/**
* @brief Attempt to open the impeachment election UI.
*
* @param pUser The user sending the packet.
* @param pkt The packet.
*/
void CKingSystem::ImpeachmentElectionUiOpen(CUser * pUser, Packet & pkt)
{
Packet result(WIZ_KING, uint8(KING_IMPEACHMENT));
// If it's not the impeachment's election stage, send -1 as the error code
// otherwise, send 1 for success.
result << uint8(KING_IMPEACHMENT_ELECTION_UI_OPEN)
<< int16(m_byImType != 3 ? -1 : 1);
pUser->Send(&result);
}
/**
* @brief King tax system.
*
* @param pUser The user sending the packet.
* @param pkt The packet.
*/
void CKingSystem::KingTaxSystem(CUser * pUser, Packet & pkt)
{
CKingSystem *pKingSystem = g_pMain->m_KingSystemArray.GetData(m_byNation);
Packet result(WIZ_KING, uint8(KING_TAX));
uint8 bOpcode = pkt.read<uint8>();
result << bOpcode;
// If you're not a King, you shouldn't have access to this command.
if (!pUser->isKing())
{
result << int16(-1);
pUser->Send(&result);
return;
}
switch (bOpcode)
{
// Collect King's fund
case 2:
{
pUser->GoldGain(m_nTerritoryTax);
HandleDatabaseRequest_Tax(m_nTerritoryTariff, pUser->GetNation(), m_nTerritoryTax);
pKingSystem->m_nTerritoryTax -= m_nTerritoryTax;
}
break;
// Lookup the tariff
case 3:
{
C3DMap * pMap = g_pMain->GetZoneByID(m_byNation);
if (pMap == nullptr)
return;
result << int16(1) << (pMap->GetTariff() - 10);
pUser->Send(&result);
}
break;
// Update the tariff
case 4:
{
C3DMap * pMap = g_pMain->GetZoneByID(m_byNation);
uint8 byTerritoryTariff = pkt.read<uint8>();
// Map doesn't exist, or invalid tariff amount
if (pMap == nullptr
|| byTerritoryTariff > 10)
{
result << int16(-2);
pUser->Send(&result);
return;
}
// Update the tariff
pMap->SetTariff(10 + byTerritoryTariff);
// Let all users in your nation know.
result << int16(1) << byTerritoryTariff << m_byNation;
g_pMain->Send_All(&result, nullptr, m_byNation);
pKingSystem->m_nTerritoryTariff = byTerritoryTariff;
// Update the database (TODO: Implement the request)
g_pMain->AddDatabaseRequest(result, pUser);
}
break;
// King's scepter / "unnecessary translation"
case 7:
{
if (pUser->CheckExistItem(KING_SCEPTER))
{
result << int16(-1);
}
else if (pUser->FindSlotForItem(KING_SCEPTER) < 0)
{
result << int16(-2);
}
else
{
pUser->GiveItem(KING_SCEPTER);
pUser->GiveItem(KING_SCEPTER);
result << int16(1);
}
pUser->Send(&result);
}
break;
}
}
/**
* @brief Handles commands accessible to the King.
*
* @param pUser The user sending the packet.
* @param pkt The packet.
*/
void CKingSystem::KingSpecialEvent(CUser * pUser, Packet & pkt)
{
Packet result(WIZ_KING, uint8(KING_EVENT));
uint8 opcode = pkt.read<uint8>();
result << opcode;
if (!pUser->isKing())
{
result << int16(-1);
pUser->Send(&result);
return;
}
switch (opcode)
{
case KING_EVENT_NOAH: // Noah event
{
uint8 bAmount = pkt.read<uint8>();
if (bAmount < 1 || bAmount > 3)
return;
uint32 nCost = 50000000 * bAmount;
if (nCost > m_nNationalTreasury)
{
result << int16(-3);
pUser->Send(&result);
return;
}
m_nNationalTreasury -= nCost;
m_byNoahEvent = bAmount;
m_byNoahEvent_Day = g_localTime.tm_mday;
m_byNoahEvent_Hour = g_localTime.tm_hour;
m_byNoahEvent_Minute = g_localTime.tm_min;
m_sNoahEvent_Duration = 30; // event expires in 30 minutes
// %d%% increased coin rate
g_pMain->SendFormattedResource(m_byNation == KARUS ? IDS_KING_KARUS_NOAH_BONUS_EVENT : IDS_KING_ELMO_NOAH_BONUS_EVENT,
m_byNation, false, bAmount);
// TODO: Update other servers via UDP
// TODO: Update the AI server
// Update the database
result << m_byNation << bAmount << m_byNoahEvent_Day << m_byNoahEvent_Hour << m_byNoahEvent_Minute << m_sNoahEvent_Duration;
g_pMain->AddDatabaseRequest(result);
} break;
case KING_EVENT_EXP: // EXP event
{
uint8 bAmount = pkt.read<uint8>();
if (bAmount != 10 && bAmount != 30 && bAmount != 50)
return;
uint32 nCost = 30000000 * bAmount;
if (nCost > m_nNationalTreasury)
{
result << int16(-3);
pUser->Send(&result);
return;
}
m_nNationalTreasury -= nCost;
m_byExpEvent = bAmount;
m_byExpEvent_Day = g_localTime.tm_mday;
m_byExpEvent_Hour = g_localTime.tm_hour;
m_byExpEvent_Minute = g_localTime.tm_min;
m_sExpEvent_Duration = 30; // event expires in 30 minutes
// %d%% increased coin rate
g_pMain->SendFormattedResource(m_byNation == KARUS ? IDS_KING_KARUS_EXP_BONUS_EVENT : IDS_KING_ELMO_EXP_BONUS_EVENT,
m_byNation, false, bAmount);
// TODO: Update other servers via UDP
// TODO: Update the AI server
// Update the database
result << m_byNation << bAmount << m_byExpEvent_Day << m_byExpEvent_Hour << m_byExpEvent_Minute << m_sExpEvent_Duration;
g_pMain->AddDatabaseRequest(result);
} break;
case KING_EVENT_PRIZE:
{
uint32 nCoins;
std::string strUserID;
pkt.SByte();
pkt >> nCoins >> strUserID;
// If the user submitted invalid input, chances are
// the coins will end up 0. We can safely ignore it.
if (nCoins == 0)
return;
CUser * pTUser = g_pMain->GetUserPtr(strUserID, TYPE_CHARACTER);
if (pTUser == nullptr // this session check isn't official behaviour
// as they try to handle offline users -
// note the 'try' (it doesn't work properly)...
|| strUserID.empty() || strUserID.length() > MAX_ID_SIZE)
{
result << int16(-2);
pUser->Send(&result);
return;
}
if (nCoins > m_nNationalTreasury)
{
result << int16(-4);
pUser->Send(&result);
return;
}
m_nNationalTreasury -= nCoins;
pTUser->GoldGain(nCoins);
// (awarded %s %d coins)
g_pMain->SendFormattedResource(m_byNation == KARUS ? IDS_KING_KARUS_PRIZE_EVENT_MESSAGE : IDS_KING_ELMO_PRIZE_EVENT_MESSAGE,
m_byNation, false, pTUser->m_strUserID.c_str(), nCoins);
// TODO: Update other servers via UDP
// Update the database
result << m_byNation << nCoins << strUserID;
g_pMain->AddDatabaseRequest(result);
} break;
case KING_EVENT_FUGITIVE: // not sure what this is exactly, but it seems to work pretty much the same as the /prize command
break;
case KING_EVENT_WEATHER: // Weather
{
uint8 bType, bAmount;
pkt >> bType >> bAmount;
if (bAmount == 0 || bAmount > 100
|| bType == 0 || bType > WEATHER_SNOW)
return;
if (m_nNationalTreasury < 100000)
{
result << int16(-3);
pUser->Send(&result);
return;
}
m_nNationalTreasury -= 100000;
g_pMain->m_byKingWeatherEvent = 1;
g_pMain->m_byKingWeatherEvent_Day = g_localTime.tm_mday;
g_pMain->m_byKingWeatherEvent_Hour = g_localTime.tm_hour;
g_pMain->m_byKingWeatherEvent_Minute = g_localTime.tm_min;
g_pMain->m_byWeather = bType;
g_pMain->m_sWeatherAmount = bAmount;
g_pMain->UpdateWeather();
// TODO: Update other servers via UDP
// Get the resource ID, which differs per nation and weather type.
// This works because they're sequential.
uint32 nResourceID =
(m_byNation == KARUS
? IDS_KING_KARUS_WEATHER_FINE_EVENT + (bType-1)
: IDS_KING_ELMO_WEATHER_FINE_EVENT + (bType-1));
g_pMain->SendFormattedResource(nResourceID, m_byNation, false);
} break;
case KING_EVENT_NOTICE: // /royalorder command (King chat)
{
std::string strMessage;
pkt.SByte();
pkt >> strMessage;
if (strMessage.empty() || strMessage.length() > 256)
return;
Packet result(WIZ_KING);
result.SByte();
result << uint8(KING_EVENT) << uint8(KING_EVENT_NOTICE) << uint8(1) << short(1) << strMessage;
g_pMain->Send_All(&result,nullptr,m_byNation);
DateTime time;
g_pMain->WriteChatLogFile(string_format("[ KING - %d:%d:%d ] %s : %s ( Zone=%d, X=%d, Z=%d )\n",time.GetHour(),time.GetMinute(),time.GetSecond(),pUser->GetName().c_str(),strMessage.c_str(),pUser->GetZoneID(),uint16(pUser->GetX()),uint16(pUser->GetZ())));
} break;
}
}
/**
* @brief Resets the election lists.
*/
void CKingSystem::ResetElectionLists()
{
foreach (itr, m_senatorList)
delete itr->second;
m_senatorList.clear();
foreach (itr, m_candidateList)
delete itr->second;
m_candidateList.clear();
m_resignedCandidateList.clear();
}
CKingSystem::~CKingSystem()
{
ResetElectionLists();
}