mirror of
https://github.com/ornfelt/azerothcore_lua_scripts
synced 2026-08-22 06:26:03 -04:00
403 lines
13 KiB
C++
403 lines
13 KiB
C++
#include "ScriptMgr.h"
|
|
#include "GameEventCallbacks.h"
|
|
#include "DatabaseEnv.h"
|
|
#include "Player.h"
|
|
#include "Random.h"
|
|
#include "ChannelMgr.h"
|
|
#include "Channel.h"
|
|
#include "ObjectExtension.cpp"
|
|
|
|
#define WaitMSUntilPickNewQuestion 60000
|
|
#define GiveNewHintEveryMS 15000
|
|
#define TriviaChatChannel "trivia"
|
|
#define TriviaPlayerGuid ((uint64)(0x00FFFFFF-5))
|
|
#define TriviaPlayerName "Trivia"
|
|
|
|
#ifndef toLower
|
|
#define toLower(c) (((c >= 'A') && (c <= 'Z')) ? (c - 'A' + 'a') : c)
|
|
#endif
|
|
|
|
bool IsSameStrTrivia(const char *str1, const char *str2)
|
|
{
|
|
int Index = 0;
|
|
while (str1[Index] == str2[Index])
|
|
Index++;
|
|
|
|
//not even first letter matched
|
|
if (Index == 0)
|
|
return false;
|
|
|
|
//full match ?
|
|
if (str1[Index-1] == 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void ReadLine(FILE *f, char *buff, uint32 MaxLen)
|
|
{
|
|
*buff = 0;
|
|
while (!feof(f))
|
|
{
|
|
size_t BytesRead = fread(buff, 1, 1, f);
|
|
if (*buff == '\n')
|
|
{
|
|
*buff = 0;
|
|
return;
|
|
}
|
|
buff++;
|
|
}
|
|
}
|
|
|
|
class QuestionStore
|
|
{
|
|
public:
|
|
bool LoadQuestionFromFile(FILE *f)
|
|
{
|
|
char ReadBuffer[3000];
|
|
//read points
|
|
ReadLine(f, ReadBuffer, (uint32)sizeof(ReadBuffer));
|
|
Points = atoi(ReadBuffer);
|
|
//read the question
|
|
ReadLine(f, ReadBuffer, (uint32)sizeof(ReadBuffer));
|
|
Question = strdup(ReadBuffer);
|
|
//read multiple answers
|
|
while (!feof(f))
|
|
{
|
|
ReadLine(f, ReadBuffer, (uint32)sizeof(ReadBuffer));
|
|
if (ReadBuffer[0] == 0)
|
|
break;
|
|
Answers.push_back(strdup(ReadBuffer));
|
|
HintLen = (uint32)strlen(ReadBuffer);
|
|
Hint = (char*)malloc(HintLen + 1);
|
|
Hint[HintLen] = 0;
|
|
}
|
|
//sanity checks
|
|
if (Points == 0)
|
|
return false;
|
|
if (strlen(Question) < 5)
|
|
return false;
|
|
//all good
|
|
return true;
|
|
}
|
|
const char *GetQuestion() { return Question; }
|
|
const char *GetFirstAnswer() { return Answers.at(0); }
|
|
uint32 GetPoints() { return Points; }
|
|
bool IsAcceptedAnswer(const char *msg)
|
|
{
|
|
//convert msg to lower
|
|
char lmsg[5000];
|
|
int32 LenMsg;
|
|
for (LenMsg = 0; msg[LenMsg] != 0 && LenMsg < sizeof(lmsg); LenMsg++)
|
|
lmsg[LenMsg] = tolower(msg[LenMsg]);
|
|
lmsg[LenMsg] = 0;
|
|
|
|
for (auto i = Answers.begin(); i != Answers.end(); i++)
|
|
{
|
|
const char *answ = *i;
|
|
//maybe there is a direct match
|
|
if (IsSameStrTrivia(lmsg, answ))
|
|
return true;
|
|
|
|
int32 LenAnsw = (int32)strlen(answ);
|
|
int32 LengthDifference = abs(LenAnsw - LenMsg);
|
|
if (LengthDifference >= 2)
|
|
return false;
|
|
|
|
// check letter by letter if it matches
|
|
uint32 MisMatchMatchingLetters = 0;
|
|
for (int32 j1 = 0, j2 = 0; j1 < LenAnsw && j2 < LenMsg; j1++, j2++)
|
|
{
|
|
if (answ[j1] == lmsg[j1])
|
|
continue;
|
|
//Answer has an extra letter here ?
|
|
if (answ[j1] == lmsg[j2 + 1] || answ[j1] == lmsg[j2 + 2])
|
|
continue;
|
|
//answer has a missing letter here ?
|
|
if (answ[j1 + 1] == lmsg[j2] || answ[j1 + 2] == lmsg[j2])
|
|
continue;
|
|
MisMatchMatchingLetters++;
|
|
}
|
|
float MatchPercent = 1.0f - (float)MisMatchMatchingLetters / LenAnsw;
|
|
if (MisMatchMatchingLetters > 2)
|
|
return false;
|
|
// the answer seem to have a few mistakes, but it's ok to accept it
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//hint will contain no letters at start. Will add more and more letters as time goes by
|
|
void ResetHint()
|
|
{
|
|
for (int i = 0; i < (int)HintLen; i++)
|
|
Hint[i] = '_';
|
|
Hint[HintLen] = 0; // make sure we did not accidentally overwrite the null terminator
|
|
}
|
|
|
|
//try to add a random letter to the empty hint string. If it fails in X tries, it will skip giving a hint
|
|
const char *GetHint()
|
|
{
|
|
//copy 1 letter from answer and "zero" out the rest
|
|
auto i = Answers.begin();
|
|
const char *Answer = *i;
|
|
//pick a random position and copy another char to it
|
|
for (int Try = 0; Try < 10; Try++)
|
|
{
|
|
uint32 RandLoc = rand32() % HintLen;
|
|
if (Hint[RandLoc] == '_')
|
|
{
|
|
Hint[RandLoc] = Answer[RandLoc];
|
|
return Hint;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
private:
|
|
char *Question;
|
|
std::vector<char *> Answers;
|
|
char *Hint;
|
|
uint32 HintLen;
|
|
uint32 Points;
|
|
};
|
|
|
|
class TriviaSytem
|
|
{
|
|
public:
|
|
TriviaSytem()
|
|
{
|
|
QuestionCount = 0;
|
|
CurrentQuestion = 0x07FFFFFF;
|
|
TimePassedSinceCurrentQuestion = 0;
|
|
}
|
|
|
|
//load questions from file
|
|
void LoadQuestions(const char *FileName)
|
|
{
|
|
FILE *f = fopen(FileName, "rt");
|
|
if (f == NULL)
|
|
{
|
|
printf("Could not open input file for trivia system. TriviaQuestions.txt missing ?\n");
|
|
return;
|
|
}
|
|
|
|
//load all questions from file
|
|
while (!feof(f))
|
|
{
|
|
QuestionStore *qs = new QuestionStore;
|
|
if (qs->LoadQuestionFromFile(f) == false)
|
|
{
|
|
delete qs;
|
|
break;
|
|
}
|
|
Qestions.push_back(qs);
|
|
QuestionCount++;
|
|
}
|
|
fclose(f);
|
|
|
|
//init the system
|
|
PickNewQuestion();
|
|
}
|
|
|
|
void PostChannelMessage(const char *what)
|
|
{
|
|
//post it on the "trivia" channel
|
|
ChannelMgr* cMgr = ChannelMgr::forTeam(TEAM_ALLIANCE);
|
|
if (cMgr == NULL)
|
|
return;
|
|
|
|
Channel *ch = cMgr->GetChannel(0, TriviaChatChannel, NULL, false);
|
|
if (ch == NULL)
|
|
return;
|
|
|
|
ch->SayOfflinePlayer(ObjectGuid(TriviaPlayerGuid), what);
|
|
}
|
|
|
|
//pick a random question
|
|
void PickNewQuestion(bool IsTimeOut=false)
|
|
{
|
|
//avoid division by 0 crash
|
|
if (QuestionCount == 0)
|
|
return;
|
|
|
|
//first give the answer to educate people
|
|
if (IsTimeOut)
|
|
{
|
|
std::string answer = "Question timeout. The answer was:";
|
|
answer += Qestions.at(CurrentQuestion)->GetFirstAnswer();
|
|
PostChannelMessage(answer.c_str());
|
|
}
|
|
|
|
//pick a random question
|
|
CurrentQuestion = rand32() % QuestionCount;
|
|
TimePassedSinceCurrentQuestion = 0;
|
|
TimePassedSincePreviousHint = 0;
|
|
|
|
//post the new question
|
|
std::string question = "Question:";
|
|
question += Qestions.at(CurrentQuestion)->GetQuestion();
|
|
PostChannelMessage(question.c_str());
|
|
|
|
//generate new hint
|
|
Qestions.at(CurrentQuestion)->ResetHint();
|
|
}
|
|
|
|
//update status if the current question has not been answered
|
|
void UpdateStatus(uint32 TimeDiff)
|
|
{
|
|
//avoid division by 0 crash
|
|
if (QuestionCount == 0)
|
|
return;
|
|
|
|
//update time spent on this question
|
|
TimePassedSinceCurrentQuestion += TimeDiff;
|
|
//enough time has passed, roll a new question
|
|
if (TimePassedSinceCurrentQuestion > WaitMSUntilPickNewQuestion)
|
|
PickNewQuestion(true);
|
|
GiveHint(TimeDiff);
|
|
}
|
|
|
|
void OnMessageReceived(const char *msg, Player *plr)
|
|
{
|
|
if (Qestions.at(CurrentQuestion)->IsAcceptedAnswer(msg))
|
|
{
|
|
uint32 PointsReceived = Qestions.at(CurrentQuestion)->GetPoints();
|
|
char itoastr[20];
|
|
itoa(PointsReceived, itoastr, 10);
|
|
int64 *PlayerPoints = plr->GetCreateIn64Extension(OE_PLAYER_TRIVIA_POINTS, false, 0);
|
|
*PlayerPoints += PointsReceived;
|
|
char itoastr2[20];
|
|
itoa((int)*PlayerPoints, itoastr2, 10);
|
|
std::string congratulation = "Player " + plr->GetName() + " guessed '" + Qestions.at(CurrentQuestion)->GetFirstAnswer() + "' and received " + itoastr + " points. Total " + itoastr2;
|
|
PostChannelMessage(congratulation.c_str());
|
|
PickNewQuestion();
|
|
}
|
|
}
|
|
|
|
void GiveHint(uint32 TimeDiff)
|
|
{
|
|
TimePassedSincePreviousHint += TimeDiff;
|
|
if (TimePassedSincePreviousHint > GiveNewHintEveryMS)
|
|
{
|
|
TimePassedSincePreviousHint = 0;
|
|
const char *MangledAnswer = Qestions.at(CurrentQuestion)->GetHint();
|
|
if(MangledAnswer)
|
|
PostChannelMessage(MangledAnswer);
|
|
}
|
|
}
|
|
private:
|
|
std::vector<QuestionStore*> Qestions;
|
|
uint32 QuestionCount;
|
|
uint32 CurrentQuestion;
|
|
uint32 TimePassedSinceCurrentQuestion;
|
|
uint32 TimePassedSincePreviousHint;
|
|
};
|
|
|
|
TriviaSytem ServerWideTrivia;
|
|
|
|
bool IsSameStrInsensitive2(const char *str1, const char *str2);
|
|
//every time an ingame player sends a message to the server, this function will trigger
|
|
void TriviaOnChatMessageReceived(void *p, void *)
|
|
{
|
|
CP_CHAT_RECEIVED *params = PointerCast(CP_CHAT_RECEIVED, p);
|
|
|
|
//skip GM messages
|
|
if (params->Msg->c_str()[0] == '.')
|
|
return;
|
|
|
|
//is this message directed to channel we want to link ?
|
|
if (IsSameStrInsensitive2(TriviaChatChannel, (*params->Channel).c_str()) == false)
|
|
return;
|
|
|
|
ServerWideTrivia.OnMessageReceived(params->Msg->c_str(), params->SenderPlayer);
|
|
|
|
#ifdef _DEBUG
|
|
if (params->Msg->c_str()[0] == '#' && params->Msg->c_str()[1] == 'r' && params->Msg->c_str()[2] == 't')
|
|
ServerWideTrivia.PickNewQuestion(true);
|
|
#endif
|
|
}
|
|
|
|
//this is a periodic tick function to update our trivia system
|
|
void TriviaPeriodUpdateQuestion(void *p, void *context)
|
|
{
|
|
CP_MAP_PERIODIC_UPDATE *params = PointerCast(CP_MAP_PERIODIC_UPDATE, p);
|
|
if (params->map == NULL || params->map->GetId() != 0)
|
|
return;
|
|
|
|
ServerWideTrivia.UpdateStatus(params->TimeDiff);
|
|
}
|
|
|
|
void SendFakeNameQuery(uint64 Guid, const char *Name, Player *Destination);
|
|
//if a player is curious about who is sending the trivia questions, we answer to him
|
|
void HandleNameQueryTriviaPlayer(void *p, void *)
|
|
{
|
|
CP_NAME_QUERY *params = PointerCast(CP_NAME_QUERY, p);
|
|
|
|
if (params->GUID != TriviaPlayerGuid)
|
|
return;
|
|
|
|
SendFakeNameQuery(params->GUID, TriviaPlayerName, params->Player);
|
|
|
|
params->DenyDefaultParsing = 1;
|
|
}
|
|
|
|
class TC_GAME_API TriviaPlayerLoaderRegisterScript : public PlayerScript
|
|
{
|
|
public:
|
|
TriviaPlayerLoaderRegisterScript() : PlayerScript("TriviaPlayerLoaderRegisterScript") {}
|
|
// load points for player when he logs in
|
|
void OnLogin(Player* player, bool firstLogin)
|
|
{
|
|
int64 *TP = player->GetCreateIn64Extension(OE_PLAYER_TRIVIA_POINTS);
|
|
char Query[5000];
|
|
sprintf_s(Query, sizeof(Query), "SELECT PointsAquired from character_TriviaPoints where GUID = %d", (uint32)player->GetGUID().GetRawValue());
|
|
QueryResult result = CharacterDatabase.Query(Query);
|
|
if (!result || result->GetRowCount() == 0)
|
|
return;
|
|
Field* fields = result->Fetch();
|
|
*TP = fields[0].GetUInt32();
|
|
}
|
|
//save points when he logs out
|
|
void OnLogout(Player* player)
|
|
{
|
|
int64 *TP = player->GetExtension<int64>(OE_PLAYER_TRIVIA_POINTS);
|
|
if (TP == NULL)
|
|
return;
|
|
char Query[5000];
|
|
sprintf_s(Query, sizeof(Query), "replace into character_TriviaPoints values(%d,%d)", (uint32)player->GetGUID().GetRawValue(),(uint32)*TP);
|
|
CharacterDatabase.Execute(Query);
|
|
}
|
|
//ditch points when he quits the game
|
|
void OnDelete(ObjectGuid guid, uint32 accountId)
|
|
{
|
|
char Query[5000];
|
|
sprintf_s(Query, sizeof(Query), "delete from character_TriviaPoints where GUID=%d", (uint32)guid.GetRawValue());
|
|
CharacterDatabase.Execute(Query);
|
|
}
|
|
};
|
|
|
|
void AddTriviaSystemScripts()
|
|
{
|
|
/*
|
|
CREATE TABLE IF NOT EXISTS `character_TriviaPoints` (
|
|
`GUID` int(11) NOT NULL,
|
|
`PointsAquired` int(11) DEFAULT NULL,
|
|
UNIQUE KEY `relation` (`GUID`),
|
|
KEY `RowUniqueId` (`GUID`) USING BTREE
|
|
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1;
|
|
*/
|
|
//cache the questions
|
|
ServerWideTrivia.LoadQuestions("TriviaQuestions.txt");
|
|
|
|
//we need something to periodically update the state of the system. Hook a map update
|
|
RegisterCallbackFunction(CALLBACK_TYPE_MAP_PERIODIC_UPDATE, TriviaPeriodUpdateQuestion, NULL);
|
|
|
|
//client might deny all posted messages unless comming form a real player
|
|
RegisterCallbackFunction(CALLBACK_TYPE_NAME_QUERY, HandleNameQueryTriviaPlayer, NULL);
|
|
|
|
//register listner that will check if message is an answer we are looking for
|
|
RegisterCallbackFunction(CALLBACK_TYPE_CHAT_RECEIVED, TriviaOnChatMessageReceived, NULL);
|
|
|
|
new TriviaPlayerLoaderRegisterScript();
|
|
}
|