zaela-Server/zone/tasks.cpp

3538 lines
110 KiB
C++
Raw Permalink Normal View History

2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
/* EQEMu: Everquest Server Emulator
2013-02-16 16:14:39 -08:00
Copyright (C) 2001-2008 EQEMu Development Team (http://eqemulator.net)
2013-05-09 10:44:08 -04:00
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY except by those people which sell it, which
2013-02-16 16:14:39 -08:00
are required to give you total support for your newly bought product;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
2013-05-09 10:44:08 -04:00
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
2013-02-16 16:14:39 -08:00
*/
#include "../common/global_define.h"
2013-02-16 16:14:39 -08:00
#include "tasks.h"
#include <string.h>
#ifdef _WINDOWS
#define strcasecmp _stricmp
#endif
2014-08-21 19:33:02 -07:00
#include "../common/misc_functions.h"
2013-02-16 16:14:39 -08:00
#include "../common/rulesys.h"
2014-12-15 18:54:23 -06:00
#include "../common/string_util.h"
#include "../common/say_link.h"
2014-12-15 18:54:23 -06:00
#include "client.h"
#include "entity.h"
#include "mob.h"
2018-06-16 22:42:26 -04:00
#include "string_ids.h"
2014-12-15 18:54:23 -06:00
Changed zone process window title format, example: 'crushbone :: clients: 6 inst_id: 1 inst_ver: 0 :: port: 7015' Most of the following changes are QueryServ related, fully implemented its original functionality to be able to offload intensive or metric based logging to a remote server process that could exist on another server entirely Implemented Player Event Logging Types (Go to table `qs_player_events`): 1 = Player_Log_Quest, 2 = Player_Log_Zoning, 3 = Player_Log_Deaths, 4 = Player_Log_Connect_State, 5 = Player_Log_Levels, 6 = Player_Log_Keyring_Addition, 7 = Player_Log_QGlobal_Update, 8 = Player_Log_Task_Updates, 9 = Player_Log_AA_Purchases, 10 = Player_Log_Trade_Skill_Events, 11 = Player_Log_Issued_Commands, 12 = Player_Log_Money_Transactions, 13 = Player_Log_Alternate_Currency_Transactions, - All QueryServ logging will be implemented with a front end in EoC 2.0 very soon Changed all QS Error related logging to 'QUERYSERV__ERROR' (Natedog) (Crash Fix) Legacy MySQL bug revert for loading AA's COALESCE( from COALESCE ( Implemented Perl Quest objects (LUA still needed to be exported): - quest::qs_send_query("MySQL query") - Will send a raw query to the QueryServ process, useful for custom logging - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` Added MySQL Tables: - `qs_player_aa_rate_hourly` - `qs_player_events` - Source table structures from: - utils\sql\git\queryserv\required\08_23_2014_player_events_and_player_aa_rate_hourly To get the complete QueryServ schema, source from here: - utils\sql\git\queryserv\required\Complete_QueryServ_Table_Structures.sql Added rules for each logging type, source rules here with them enabled by default: - utils\sql\git\queryserv\required\Complete_QueryServ_Rules_Enabled.sql Spawn related logging cleanup General code cleanup Added queryserv.cpp and queryserv.h with QueryServ class
2014-08-23 23:59:20 -05:00
#include "queryserv.h"
2014-12-15 18:54:23 -06:00
#include "quest_parser_collection.h"
2013-02-16 16:14:39 -08:00
Changed zone process window title format, example: 'crushbone :: clients: 6 inst_id: 1 inst_ver: 0 :: port: 7015' Most of the following changes are QueryServ related, fully implemented its original functionality to be able to offload intensive or metric based logging to a remote server process that could exist on another server entirely Implemented Player Event Logging Types (Go to table `qs_player_events`): 1 = Player_Log_Quest, 2 = Player_Log_Zoning, 3 = Player_Log_Deaths, 4 = Player_Log_Connect_State, 5 = Player_Log_Levels, 6 = Player_Log_Keyring_Addition, 7 = Player_Log_QGlobal_Update, 8 = Player_Log_Task_Updates, 9 = Player_Log_AA_Purchases, 10 = Player_Log_Trade_Skill_Events, 11 = Player_Log_Issued_Commands, 12 = Player_Log_Money_Transactions, 13 = Player_Log_Alternate_Currency_Transactions, - All QueryServ logging will be implemented with a front end in EoC 2.0 very soon Changed all QS Error related logging to 'QUERYSERV__ERROR' (Natedog) (Crash Fix) Legacy MySQL bug revert for loading AA's COALESCE( from COALESCE ( Implemented Perl Quest objects (LUA still needed to be exported): - quest::qs_send_query("MySQL query") - Will send a raw query to the QueryServ process, useful for custom logging - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` Added MySQL Tables: - `qs_player_aa_rate_hourly` - `qs_player_events` - Source table structures from: - utils\sql\git\queryserv\required\08_23_2014_player_events_and_player_aa_rate_hourly To get the complete QueryServ schema, source from here: - utils\sql\git\queryserv\required\Complete_QueryServ_Table_Structures.sql Added rules for each logging type, source rules here with them enabled by default: - utils\sql\git\queryserv\required\Complete_QueryServ_Rules_Enabled.sql Spawn related logging cleanup General code cleanup Added queryserv.cpp and queryserv.h with QueryServ class
2014-08-23 23:59:20 -05:00
extern QueryServ* QServ;
2013-02-16 16:14:39 -08:00
TaskManager::TaskManager() {
for(int i=0; i<MAXTASKS; i++)
2013-05-04 18:06:58 -07:00
Tasks[i] = nullptr;
2013-02-16 16:14:39 -08:00
}
TaskManager::~TaskManager() {
for(int i=0; i<MAXTASKS; i++) {
2013-05-04 18:06:58 -07:00
if(Tasks[i] != nullptr) {
2013-02-16 16:14:39 -08:00
safe_delete(Tasks[i]);
}
}
}
2019-09-01 22:05:44 -05:00
bool TaskManager::LoadTaskSets()
{
2013-02-16 16:14:39 -08:00
// Clear all task sets in memory. Done so we can reload them on the fly if required by just calling
// this method again.
2019-09-01 22:05:44 -05:00
for (int i = 0; i < MAXTASKSETS; i++)
2013-02-16 16:14:39 -08:00
TaskSets[i].clear();
2019-09-01 22:05:44 -05:00
std::string query = StringFormat(
"SELECT `id`, `taskid` from `tasksets` "
"WHERE `id` > 0 AND `id` < %i "
"AND `taskid` >= 0 AND `taskid` < %i "
"ORDER BY `id`, `taskid` ASC",
MAXTASKSETS, MAXTASKS
);
auto results = database.QueryDatabase(query);
if (!results.Success()) {
LogError("Error in TaskManager::LoadTaskSets: [{}]", results.ErrorMessage().c_str());
return false;
2019-09-01 22:05:44 -05:00
}
2013-02-16 16:14:39 -08:00
for (auto row = results.begin(); row != results.end(); ++row) {
2019-09-01 22:05:44 -05:00
int taskSet = atoi(row[0]);
int taskID = atoi(row[1]);
2013-02-16 16:14:39 -08:00
2019-09-01 22:05:44 -05:00
TaskSets[taskSet].push_back(taskID);
Log(Logs::General, Logs::Tasks, "[GLOBALLOAD] Adding TaskID %4i to TaskSet %4i", taskID, taskSet);
}
2013-02-16 16:14:39 -08:00
return true;
}
bool TaskManager::LoadSingleTask(int TaskID) {
Log(Logs::General, Logs::Tasks, "[GLOBALLOAD] TaskManager::LoadSingleTask(%i)", TaskID);
2013-02-16 16:14:39 -08:00
if((TaskID <= 0) || (TaskID >= MAXTASKS)) return false;
// If this task already exists in memory, free all the dynamically allocated strings.
if(Tasks[TaskID]) {
safe_delete(Tasks[TaskID]);
}
return LoadTasks(TaskID);
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
void TaskManager::ReloadGoalLists() {
2013-05-06 13:07:41 -04:00
if(!GoalListManager.LoadLists())
Log(Logs::Detail, Logs::Tasks,"TaskManager::LoadTasks LoadLists failed");
2013-02-16 16:14:39 -08:00
}
bool TaskManager::LoadTasks(int singleTask)
{
2013-02-16 16:14:39 -08:00
// If TaskID !=0, then just load the task specified.
Log(Logs::General, Logs::Tasks, "[GLOBALLOAD] TaskManager::LoadTasks Called");
2013-02-16 16:14:39 -08:00
std::string query;
if (singleTask == 0) {
if (!GoalListManager.LoadLists())
Log(Logs::Detail, Logs::Tasks, "TaskManager::LoadTasks LoadLists failed");
2013-02-16 16:14:39 -08:00
if (!LoadTaskSets())
Log(Logs::Detail, Logs::Tasks, "TaskManager::LoadTasks LoadTaskSets failed");
2013-02-16 16:14:39 -08:00
2018-06-13 17:08:21 -04:00
query = StringFormat("SELECT `id`, `type`, `duration`, `duration_code`, `title`, `description`, "
"`reward`, `rewardid`, `cashreward`, `xpreward`, `rewardmethod`, `faction_reward`,"
2018-07-20 14:06:07 -04:00
"`minlevel`, `maxlevel`, `repeatable`, `completion_emote` FROM `tasks` WHERE `id` < %i",
MAXTASKS);
} else
2018-06-13 17:08:21 -04:00
query = StringFormat("SELECT `id`, `type`, `duration`, `duration_code`, `title`, `description`, "
"`reward`, `rewardid`, `cashreward`, `xpreward`, `rewardmethod`, `faction_reward`,"
2018-07-20 14:06:07 -04:00
"`minlevel`, `maxlevel`, `repeatable`, `completion_emote` FROM `tasks` WHERE `id` = %i",
singleTask);
2013-02-16 16:14:39 -08:00
const char *ERR_MYSQLERROR = "[TASKS]Error in TaskManager::LoadTasks: %s";
2013-02-16 16:14:39 -08:00
auto results = database.QueryDatabase(query);
if (!results.Success()) {
2019-09-01 22:05:44 -05:00
LogError(ERR_MYSQLERROR, results.ErrorMessage().c_str());
2013-02-16 16:14:39 -08:00
return false;
}
2013-02-16 16:14:39 -08:00
for (auto row = results.begin(); row != results.end(); ++row) {
int taskID = atoi(row[0]);
2014-10-07 11:56:38 -07:00
if ((taskID <= 0) || (taskID >= MAXTASKS)) {
// This shouldn't happen, as the SELECT is bounded by MAXTASKS
LogError("[TASKS]Task ID [{}] out of range while loading tasks from database", taskID);
2014-10-07 11:56:38 -07:00
continue;
}
2014-10-07 11:56:38 -07:00
Tasks[taskID] = new TaskInformation;
2018-06-13 17:08:21 -04:00
Tasks[taskID]->type = static_cast<TaskType>(atoi(row[1]));
Tasks[taskID]->Duration = atoi(row[2]);
Tasks[taskID]->dur_code = static_cast<DurationCode>(atoi(row[3]));
Tasks[taskID]->Title = row[4];
Tasks[taskID]->Description = row[5];
Tasks[taskID]->Reward = row[6];
Tasks[taskID]->RewardID = atoi(row[7]);
Tasks[taskID]->CashReward = atoi(row[8]);
Tasks[taskID]->XPReward = atoi(row[9]);
Tasks[taskID]->RewardMethod = (TaskMethodType)atoi(row[10]);
Tasks[taskID]->faction_reward = atoi(row[11]);
Tasks[taskID]->MinLevel = atoi(row[12]);
Tasks[taskID]->MaxLevel = atoi(row[13]);
Tasks[taskID]->Repeatable = atoi(row[14]);
2018-07-20 14:06:07 -04:00
Tasks[taskID]->completion_emote = row[15];
2014-10-07 11:56:38 -07:00
Tasks[taskID]->ActivityCount = 0;
Tasks[taskID]->SequenceMode = ActivitiesSequential;
Tasks[taskID]->LastStep = 0;
Log(Logs::General, Logs::Tasks,
"[GLOBALLOAD] TaskID: %5i, Duration: %8i, Reward: %s MinLevel %i MaxLevel %i "
"Repeatable: %s",
taskID, Tasks[taskID]->Duration, Tasks[taskID]->Reward.c_str(),
Tasks[taskID]->MinLevel, Tasks[taskID]->MaxLevel, Tasks[taskID]->Repeatable ? "Yes" : "No");
Log(Logs::General, Logs::Tasks, "[GLOBALLOAD] Title: %s", Tasks[taskID]->Title.c_str());
}
2013-02-16 16:14:39 -08:00
if (singleTask == 0)
query =
StringFormat("SELECT `taskid`, `step`, `activityid`, `activitytype`, `target_name`, `item_list`, "
"`skill_list`, `spell_list`, `description_override`, `goalid`, `goalmethod`, "
"`goalcount`, `delivertonpc`, `zones`, `optional` FROM `task_activities` WHERE `taskid` < "
"%i AND `activityid` < %i ORDER BY taskid, activityid ASC",
MAXTASKS, MAXACTIVITIESPERTASK);
2014-10-07 11:56:38 -07:00
else
query =
StringFormat("SELECT `taskid`, `step`, `activityid`, `activitytype`, `target_name`, `item_list`, "
"`skill_list`, `spell_list`, `description_override`, `goalid`, `goalmethod`, "
"`goalcount`, `delivertonpc`, `zones`, `optional` FROM `task_activities` WHERE `taskid` = "
"%i AND `activityid` < %i ORDER BY taskid, activityid ASC",
singleTask, MAXACTIVITIESPERTASK);
results = database.QueryDatabase(query);
if (!results.Success()) {
2019-09-01 22:05:44 -05:00
LogError(ERR_MYSQLERROR, results.ErrorMessage().c_str());
2013-02-16 16:14:39 -08:00
return false;
}
2014-10-07 11:56:38 -07:00
for (auto row = results.begin(); row != results.end(); ++row) {
int taskID = atoi(row[0]);
int step = atoi(row[1]);
2014-10-07 11:56:38 -07:00
int activityID = atoi(row[2]);
2014-10-07 11:56:38 -07:00
if ((taskID <= 0) || (taskID >= MAXTASKS) || (activityID < 0) || (activityID >= MAXACTIVITIESPERTASK)) {
// This shouldn't happen, as the SELECT is bounded by MAXTASKS
LogError("[TASKS]Task or Activity ID ([{}], [{}]) out of range while loading activities from database", taskID, activityID);
continue;
}
2014-10-07 11:56:38 -07:00
if (Tasks[taskID] == nullptr) {
LogError("[TASKS]Activity for non-existent task ([{}], [{}]) while loading activities from database",
taskID, activityID);
continue;
}
2014-10-07 11:56:38 -07:00
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].StepNumber = step;
2014-10-07 11:56:38 -07:00
if (step != 0)
Tasks[taskID]->SequenceMode = ActivitiesStepped;
2014-10-07 11:56:38 -07:00
if (step > Tasks[taskID]->LastStep)
Tasks[taskID]->LastStep = step;
2014-10-07 11:56:38 -07:00
// Task Activities MUST be numbered sequentially from 0. If not, log an error
// and set the task to nullptr. Subsequent activities for this task will raise
// ERR_NOTASK errors.
// Change to (activityID != (Tasks[taskID]->ActivityCount + 1)) to index from 1
if (activityID != Tasks[taskID]->ActivityCount) {
LogError("[TASKS]Activities for Task [{}] are not sequential starting at 0. Not loading task", taskID,
activityID);
Tasks[taskID] = nullptr;
continue;
}
2014-10-07 11:56:38 -07:00
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].Type = atoi(row[3]);
2014-10-07 11:56:38 -07:00
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].target_name = row[4];
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].item_list = row[5];
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].skill_list = row[6];
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].skill_id = atoi(row[6]); // for older clients
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].spell_list = row[7];
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].spell_id = atoi(row[7]); // for older clients
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].desc_override = row[8];
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].GoalID = atoi(row[9]);
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].GoalMethod = (TaskMethodType)atoi(row[10]);
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].GoalCount = atoi(row[11]);
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].DeliverToNPC = atoi(row[12]);
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].zones = row[13];
auto zones = SplitString(Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].zones, ';');
for (auto && e : zones)
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].ZoneIDs.push_back(std::stoi(e));
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].Optional = atoi(row[14]);
2014-10-07 11:56:38 -07:00
Log(Logs::General, Logs::Tasks,
"[GLOBALLOAD] Activity Slot %2i: ID %i for Task %5i. Type: %3i, GoalID: %8i, "
"GoalMethod: %i, GoalCount: %3i, Zones:%s",
Tasks[taskID]->ActivityCount, activityID, taskID,
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].Type,
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].GoalID,
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].GoalMethod,
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].GoalCount,
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].zones.c_str());
Log(Logs::General, Logs::Tasks, "[GLOBALLOAD] target_name: %s",
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].target_name.c_str());
Log(Logs::General, Logs::Tasks, "[GLOBALLOAD] item_list: %s",
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].item_list.c_str());
Log(Logs::General, Logs::Tasks, "[GLOBALLOAD] skill_list: %s",
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].skill_list.c_str());
Log(Logs::General, Logs::Tasks, "[GLOBALLOAD] spell_list: %s",
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].spell_list.c_str());
Log(Logs::General, Logs::Tasks, "[GLOBALLOAD] description_override: %s",
Tasks[taskID]->Activity[Tasks[taskID]->ActivityCount].desc_override.c_str());
2014-10-07 11:56:38 -07:00
Tasks[taskID]->ActivityCount++;
2013-02-16 16:14:39 -08:00
}
2014-10-07 11:56:38 -07:00
2013-02-16 16:14:39 -08:00
return true;
}
bool TaskManager::SaveClientState(Client *c, ClientTaskState *state)
{
// I am saving the slot in the ActiveTasks table, because unless a Task is cancelled/completed, the client
// doesn't seem to like tasks moving slots between zoning and you can end up with 'bogus' activities if the task
// previously in that slot had more activities than the one now occupying it. Hopefully retaining the slot
// number for the duration of a session will overcome this.
if (!c || !state)
return false;
2013-02-16 16:14:39 -08:00
const char *ERR_MYSQLERROR = "[TASKS]Error in TaskManager::SaveClientState %s";
int characterID = c->CharacterID();
2013-02-16 16:14:39 -08:00
Log(Logs::Detail, Logs::Tasks, "TaskManager::SaveClientState for character ID %d", characterID);
2013-02-16 16:14:39 -08:00
2018-07-08 18:07:08 -04:00
if (state->ActiveTaskCount > 0 || state->ActiveTask.TaskID != TASKSLOTEMPTY) { // TODO: tasks
for (int task = 0; task < MAXACTIVEQUESTS + 1; task++) {
int taskID = state->ActiveTasks[task].TaskID;
if (taskID == TASKSLOTEMPTY)
continue;
2018-07-09 14:01:52 -04:00
int slot = state->ActiveTasks[task].slot;
2018-07-08 18:07:08 -04:00
if (state->ActiveTasks[task].Updated) {
Log(Logs::General, Logs::Tasks,
"[CLIENTSAVE] TaskManager::SaveClientState for character ID %d, Updating TaskIndex "
"%i TaskID %i",
2018-07-09 14:01:52 -04:00
characterID, slot, taskID);
std::string query = StringFormat(
"REPLACE INTO character_tasks (charid, taskid, slot, type, acceptedtime) "
"VALUES (%i, %i, %i, %i, %i)",
2018-07-09 14:01:52 -04:00
characterID, taskID, slot, static_cast<int>(Tasks[taskID]->type),
2018-07-08 18:07:08 -04:00
state->ActiveTasks[task].AcceptedTime);
auto results = database.QueryDatabase(query);
if (!results.Success()) {
2019-09-01 22:05:44 -05:00
LogError(ERR_MYSQLERROR, results.ErrorMessage().c_str());
} else {
2018-07-08 18:07:08 -04:00
state->ActiveTasks[task].Updated = false;
}
2013-02-16 16:14:39 -08:00
}
std::string query =
"REPLACE INTO character_activities (charid, taskid, activityid, donecount, completed) "
"VALUES ";
int updatedActivityCount = 0;
for (int activityIndex = 0; activityIndex < Tasks[taskID]->ActivityCount; ++activityIndex) {
2018-07-08 18:07:08 -04:00
if (!state->ActiveTasks[task].Activity[activityIndex].Updated)
continue;
Log(Logs::General, Logs::Tasks,
"[CLIENTSAVE] TaskManager::SaveClientSate for character ID %d, Updating Activity "
"%i, %i",
2018-07-09 14:01:52 -04:00
characterID, slot, activityIndex);
if (updatedActivityCount == 0)
query +=
StringFormat("(%i, %i, %i, %i, %i)", characterID, taskID, activityIndex,
2018-07-08 18:07:08 -04:00
state->ActiveTasks[task].Activity[activityIndex].DoneCount,
state->ActiveTasks[task].Activity[activityIndex].State ==
ActivityCompleted);
else
query +=
StringFormat(", (%i, %i, %i, %i, %i)", characterID, taskID, activityIndex,
2018-07-08 18:07:08 -04:00
state->ActiveTasks[task].Activity[activityIndex].DoneCount,
state->ActiveTasks[task].Activity[activityIndex].State ==
ActivityCompleted);
updatedActivityCount++;
2013-02-16 16:14:39 -08:00
}
if (updatedActivityCount == 0)
continue;
2013-05-06 13:07:41 -04:00
Log(Logs::General, Logs::Tasks, "[CLIENTSAVE] Executing query %s", query.c_str());
auto results = database.QueryDatabase(query);
2013-02-16 16:14:39 -08:00
if (!results.Success()) {
2019-09-01 22:05:44 -05:00
LogError(ERR_MYSQLERROR, results.ErrorMessage().c_str());
continue;
}
2013-02-16 16:14:39 -08:00
2018-07-08 18:07:08 -04:00
state->ActiveTasks[task].Updated = false;
for (int activityIndex = 0; activityIndex < Tasks[taskID]->ActivityCount; ++activityIndex)
2018-07-08 18:07:08 -04:00
state->ActiveTasks[task].Activity[activityIndex].Updated = false;
2013-02-16 16:14:39 -08:00
}
}
if (!RuleB(TaskSystem, RecordCompletedTasks) ||
(state->CompletedTasks.size() <= (unsigned int)state->LastCompletedTaskLoaded)) {
state->LastCompletedTaskLoaded = state->CompletedTasks.size();
return true;
}
const char *completedTaskQuery = "REPLACE INTO completed_tasks (charid, completedtime, taskid, activityid) "
"VALUES (%i, %i, %i, %i)";
for (unsigned int i = state->LastCompletedTaskLoaded; i < state->CompletedTasks.size(); i++) {
Log(Logs::General, Logs::Tasks,
"[CLIENTSAVE] TaskManager::SaveClientState Saving Completed Task at slot %i", i);
int taskID = state->CompletedTasks[i].TaskID;
if ((taskID <= 0) || (taskID >= MAXTASKS) || (Tasks[taskID] == nullptr))
continue;
// First we save a record with an ActivityID of -1.
// This indicates this task was completed at the given time. We infer that all
// none optional activities were completed.
//
std::string query =
StringFormat(completedTaskQuery, characterID, state->CompletedTasks[i].CompletedTime, taskID, -1);
auto results = database.QueryDatabase(query);
if (!results.Success()) {
2019-09-01 22:05:44 -05:00
LogError(ERR_MYSQLERROR, results.ErrorMessage().c_str());
continue;
}
// If the Rule to record non-optional task completion is not enabled, don't save it
if (!RuleB(TaskSystem, RecordCompletedOptionalActivities))
continue;
// Insert one record for each completed optional task.
for (int j = 0; j < Tasks[taskID]->ActivityCount; j++) {
if (!Tasks[taskID]->Activity[j].Optional || !state->CompletedTasks[i].ActivityDone[j])
continue;
query = StringFormat(completedTaskQuery, characterID, state->CompletedTasks[i].CompletedTime,
taskID, j);
results = database.QueryDatabase(query);
if (!results.Success())
2019-09-01 22:05:44 -05:00
LogError(ERR_MYSQLERROR, results.ErrorMessage().c_str());
}
}
state->LastCompletedTaskLoaded = state->CompletedTasks.size();
2013-02-16 16:14:39 -08:00
return true;
}
void Client::LoadClientTaskState() {
if(RuleB(TaskSystem, EnableTaskSystem) && taskmanager) {
if(taskstate)
safe_delete(taskstate);
taskstate = new ClientTaskState;
if(!taskmanager->LoadClientState(this, taskstate)) {
safe_delete(taskstate);
}
else {
taskmanager->SendActiveTasksToClient(this);
taskmanager->SendCompletedTasksToClient(this, taskstate);
}
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
}
void Client::RemoveClientTaskState() {
if(taskstate) {
taskstate->CancelAllTasks(this);
safe_delete(taskstate);
}
}
2013-05-06 13:07:41 -04:00
bool TaskManager::LoadClientState(Client *c, ClientTaskState *state)
{
if (!c || !state)
return false;
2013-02-16 16:14:39 -08:00
int characterID = c->CharacterID();
2013-02-16 16:14:39 -08:00
state->ActiveTaskCount = 0;
Log(Logs::General, Logs::Tasks, "[CLIENTLOAD] TaskManager::LoadClientState for character ID %d", characterID);
2013-02-16 16:14:39 -08:00
2018-07-08 18:01:14 -04:00
std::string query = StringFormat("SELECT `taskid`, `slot`,`type`, `acceptedtime` "
"FROM `character_tasks` "
"WHERE `charid` = %i ORDER BY acceptedtime",
characterID);
auto results = database.QueryDatabase(query);
if (!results.Success()) {
LogError("[TASKS]Error in TaskManager::LoadClientState load Tasks: [{}]",
results.ErrorMessage().c_str());
return false;
}
2013-02-16 16:14:39 -08:00
for (auto row = results.begin(); row != results.end(); ++row) {
int taskID = atoi(row[0]);
int slot = atoi(row[1]);
2018-07-08 18:01:14 -04:00
TaskType type = static_cast<TaskType>(atoi(row[2]));
2013-02-16 16:14:39 -08:00
if ((taskID < 0) || (taskID >= MAXTASKS)) {
LogError("[TASKS]Task ID [{}] out of range while loading character tasks from database", taskID);
continue;
}
2013-02-16 16:14:39 -08:00
2018-07-08 18:01:14 -04:00
auto task_info = state->GetClientTaskInfo(type, slot);
if (task_info == nullptr) {
LogError("[TASKS] Slot [{}] out of range while loading character tasks from database", slot);
continue;
}
2013-02-16 16:14:39 -08:00
2018-07-08 18:01:14 -04:00
if (task_info->TaskID != TASKSLOTEMPTY) {
LogError("[TASKS] Slot [{}] for Task [{}]s is already occupied", slot,
taskID);
continue;
}
2013-02-16 16:14:39 -08:00
2018-07-08 18:01:14 -04:00
int acceptedtime = atoi(row[3]);
2013-02-16 16:14:39 -08:00
2018-07-08 18:01:14 -04:00
task_info->TaskID = taskID;
task_info->CurrentStep = -1;
task_info->AcceptedTime = acceptedtime;
task_info->Updated = false;
2013-02-16 16:14:39 -08:00
for (int i = 0; i < MAXACTIVITIESPERTASK; i++)
2018-07-08 18:01:14 -04:00
task_info->Activity[i].ActivityID = -1;
2013-02-16 16:14:39 -08:00
2018-07-08 18:01:14 -04:00
if (type == TaskType::Quest)
++state->ActiveTaskCount;
2013-02-16 16:14:39 -08:00
Log(Logs::General, Logs::Tasks,
"[CLIENTLOAD] TaskManager::LoadClientState. Char: %i Task ID %i, Accepted Time: %8X", characterID,
taskID, acceptedtime);
2013-02-16 16:14:39 -08:00
}
// Load Activities
Log(Logs::General, Logs::Tasks, "[CLIENTLOAD] LoadClientState. Loading activities for character ID %d",
characterID);
query = StringFormat("SELECT `taskid`, `activityid`, `donecount`, `completed` "
"FROM `character_activities` "
"WHERE `charid` = %i "
"ORDER BY `taskid` ASC, `activityid` ASC",
characterID);
results = database.QueryDatabase(query);
if (!results.Success()) {
LogError("[TASKS]Error in TaskManager::LoadClientState load Activities: [{}]",
results.ErrorMessage().c_str());
return false;
}
2013-02-16 16:14:39 -08:00
for (auto row = results.begin(); row != results.end(); ++row) {
int taskID = atoi(row[0]);
if ((taskID < 0) || (taskID >= MAXTASKS)) {
LogError("[TASKS]Task ID [{}] out of range while loading character activities from database", taskID);
continue;
}
2013-02-16 16:14:39 -08:00
int activityID = atoi(row[1]);
if ((activityID < 0) || (activityID >= MAXACTIVITIESPERTASK)) {
LogError("[TASKS]Activity ID [{}] out of range while loading character activities from database",
activityID);
continue;
}
2013-02-16 16:14:39 -08:00
2018-07-08 18:01:14 -04:00
ClientTaskInformation *task_info = nullptr;
2013-02-16 16:14:39 -08:00
2018-07-08 18:01:14 -04:00
if (state->ActiveTask.TaskID == taskID)
task_info = &state->ActiveTask;
2018-07-08 18:07:08 -04:00
// wasn't task
2018-07-08 18:01:14 -04:00
if (task_info == nullptr)
for (int i = 0; i < MAXACTIVEQUESTS; i++)
if (state->ActiveQuests[i].TaskID == taskID)
task_info = &state->ActiveQuests[i];
2013-02-16 16:14:39 -08:00
2018-07-08 18:01:14 -04:00
if (task_info == nullptr) {
LogError("[TASKS]Activity [{}] found for task [{}] which client does not have", activityID, taskID);
continue;
}
2013-02-16 16:14:39 -08:00
int doneCount = atoi(row[2]);
bool completed = atoi(row[3]);
2018-07-08 18:01:14 -04:00
task_info->Activity[activityID].ActivityID = activityID;
task_info->Activity[activityID].DoneCount = doneCount;
if (completed)
2018-07-08 18:01:14 -04:00
task_info->Activity[activityID].State = ActivityCompleted;
else
2018-07-08 18:01:14 -04:00
task_info->Activity[activityID].State = ActivityHidden;
2013-02-16 16:14:39 -08:00
2018-07-08 18:01:14 -04:00
task_info->Activity[activityID].Updated = false;
2013-02-16 16:14:39 -08:00
Log(Logs::General, Logs::Tasks,
"[CLIENTLOAD] TaskManager::LoadClientState. Char: %i Task ID %i, ActivityID: %i, DoneCount: %i, "
"Completed: %i",
characterID, taskID, activityID, doneCount, completed);
}
if (RuleB(TaskSystem, RecordCompletedTasks)) {
query = StringFormat("SELECT `taskid`, `activityid`, `completedtime` "
"FROM `completed_tasks` "
"WHERE `charid` = %i ORDER BY completedtime, taskid, activityid",
characterID);
results = database.QueryDatabase(query);
if (!results.Success()) {
LogError("[TASKS]Error in TaskManager::LoadClientState load completed tasks: [{}]",
results.ErrorMessage().c_str());
return false;
}
2013-02-16 16:14:39 -08:00
CompletedTaskInformation cti;
2013-02-16 16:14:39 -08:00
for (int i = 0; i < MAXACTIVITIESPERTASK; i++)
cti.ActivityDone[i] = false;
int previousTaskID = -1;
int previousCompletedTime = -1;
for (auto row = results.begin(); row != results.end(); ++row) {
int taskID = atoi(row[0]);
if ((taskID <= 0) || (taskID >= MAXTASKS)) {
LogError("[TASKS]Task ID [{}] out of range while loading completed tasks from database",
taskID);
continue;
}
2013-02-16 16:14:39 -08:00
// An ActivityID of -1 means mark all the none optional activities in the
// task as complete. If the Rule to record optional activities is enabled,
// subsequent records for this task will flag any optional tasks that were
// completed.
int activityID = atoi(row[1]);
if ((activityID < -1) || (activityID >= MAXACTIVITIESPERTASK)) {
LogError("[TASKS]Activity ID [{}] out of range while loading completed tasks from database",
activityID);
continue;
}
2013-02-16 16:14:39 -08:00
int completedTime = atoi(row[2]);
if ((previousTaskID != -1) &&
((taskID != previousTaskID) || (completedTime != previousCompletedTime))) {
state->CompletedTasks.push_back(cti);
for (int i = 0; i < MAXACTIVITIESPERTASK; i++)
cti.ActivityDone[i] = false;
}
2013-05-06 13:07:41 -04:00
cti.TaskID = previousTaskID = taskID;
cti.CompletedTime = previousCompletedTime = completedTime;
2013-02-16 16:14:39 -08:00
// If ActivityID is -1, Mark all the non-optional tasks as completed.
if (activityID < 0) {
TaskInformation *task = Tasks[taskID];
if (task == nullptr)
continue;
2013-02-16 16:14:39 -08:00
for (int i = 0; i < task->ActivityCount; i++)
if (!task->Activity[i].Optional)
cti.ActivityDone[i] = true;
} else
cti.ActivityDone[activityID] = true;
}
2013-02-16 16:14:39 -08:00
if (previousTaskID != -1)
state->CompletedTasks.push_back(cti);
2013-02-16 16:14:39 -08:00
state->LastCompletedTaskLoaded = state->CompletedTasks.size();
2013-02-16 16:14:39 -08:00
}
query = StringFormat("SELECT `taskid` FROM character_enabledtasks "
"WHERE `charid` = %i AND `taskid` >0 AND `taskid` < %i "
"ORDER BY `taskid` ASC",
characterID, MAXTASKS);
results = database.QueryDatabase(query);
if (!results.Success()) {
LogError("[TASKS]Error in TaskManager::LoadClientState load enabled tasks: [{}]",
results.ErrorMessage().c_str());
} else {
for (auto row = results.begin(); row != results.end(); ++row) {
int taskID = atoi(row[0]);
state->EnabledTasks.push_back(taskID);
Log(Logs::General, Logs::Tasks, "[CLIENTLOAD] Adding TaskID %i to enabled tasks", taskID);
2013-02-16 16:14:39 -08:00
}
}
2013-02-16 16:14:39 -08:00
// Check that there is an entry in the client task state for every activity in each task
// This should only break if a ServerOP adds or deletes activites for a task that players already
// have active, or due to a bug.
2018-07-08 18:01:14 -04:00
for (int i = 0; i < MAXACTIVEQUESTS + 1; i++) {
int taskID = state->ActiveTasks[i].TaskID;
if (taskID == TASKSLOTEMPTY)
continue;
if (!Tasks[taskID]) {
2019-08-11 00:00:55 -05:00
c->Message(Chat::Red,
"Active Task Slot %i, references a task (%i), that does not exist. "
"Removing from memory. Contact a GM to resolve this.",
i, taskID);
LogError("[TASKS]Character [{}] has task [{}] which does not exist",
characterID, taskID);
2018-07-08 18:01:14 -04:00
state->ActiveTasks[i].TaskID = TASKSLOTEMPTY;
2013-02-16 16:14:39 -08:00
continue;
}
for (int j = 0; j < Tasks[taskID]->ActivityCount; j++) {
2018-07-08 18:01:14 -04:00
if (state->ActiveTasks[i].Activity[j].ActivityID != j) {
2019-08-11 00:00:55 -05:00
c->Message(Chat::Red,
"Active Task %i, %s. Activity count does not match expected value."
"Removing from memory. Contact a GM to resolve this.",
taskID, Tasks[taskID]->Title.c_str());
LogError("[TASKS]Fatal error in character [{}] task state. Activity [{}] for Task [{}] either missing from client state or from task",
characterID, j, taskID);
2018-07-08 18:01:14 -04:00
state->ActiveTasks[i].TaskID = TASKSLOTEMPTY;
2013-02-16 16:14:39 -08:00
break;
}
}
}
2018-06-24 23:21:35 -04:00
if (state->ActiveTask.TaskID != TASKSLOTEMPTY)
state->UnlockActivities(characterID, state->ActiveTask);
// TODO: shared
for (int i = 0; i < MAXACTIVEQUESTS; i++)
if (state->ActiveQuests[i].TaskID != TASKSLOTEMPTY)
state->UnlockActivities(characterID, state->ActiveQuests[i]);
2013-02-16 16:14:39 -08:00
Log(Logs::General, Logs::Tasks, "[CLIENTLOAD] LoadClientState for Character ID %d DONE!", characterID);
2013-02-16 16:14:39 -08:00
return true;
}
2014-10-08 13:07:03 -07:00
void ClientTaskState::EnableTask(int characterID, int taskCount, int *tasks) {
2013-02-16 16:14:39 -08:00
// Check if the Task is already enabled for this client
//
2014-10-08 13:07:03 -07:00
std::vector<int> tasksEnabled;
2013-02-16 16:14:39 -08:00
2014-10-08 13:07:03 -07:00
for(int i=0; i<taskCount; i++) {
auto iterator = EnabledTasks.begin();
bool addTask = true;
2013-02-16 16:14:39 -08:00
2014-10-08 13:07:03 -07:00
while(iterator != EnabledTasks.end()) {
2013-02-16 16:14:39 -08:00
// If this task is already enabled, stop looking
2014-10-08 13:07:03 -07:00
if((*iterator) == tasks[i]) {
addTask = false;
2013-02-16 16:14:39 -08:00
break;
}
// Our list of enabled tasks is sorted, so we can quit if we find a taskid higher than
// the one we are looking for.
2014-10-08 13:07:03 -07:00
if((*iterator) > tasks[i])
break;
++iterator;
2013-02-16 16:14:39 -08:00
}
2014-10-08 13:07:03 -07:00
if(addTask) {
EnabledTasks.insert(iterator, tasks[i]);
2013-02-16 16:14:39 -08:00
// Make a note of the task we enabled, for later SQL generation
2014-10-08 13:07:03 -07:00
tasksEnabled.push_back(tasks[i]);
2013-02-16 16:14:39 -08:00
}
}
Log(Logs::General, Logs::Tasks, "[UPDATE] New enabled task list ");
2013-02-16 16:14:39 -08:00
for(unsigned int i=0; i<EnabledTasks.size(); i++)
Log(Logs::General, Logs::Tasks, "[UPDATE] %i", EnabledTasks[i]);
2013-02-16 16:14:39 -08:00
if(tasksEnabled.empty() )
2014-10-08 13:07:03 -07:00
return;
2013-02-16 16:14:39 -08:00
std::stringstream queryStream;
queryStream << "REPLACE INTO character_enabledtasks (charid, taskid) VALUES ";
2014-10-08 13:07:03 -07:00
for(unsigned int i=0; i<tasksEnabled.size(); i++)
queryStream << (i ? ", " : "") << StringFormat("(%i, %i)", characterID, tasksEnabled[i]);
2013-02-16 16:14:39 -08:00
2014-10-08 13:07:03 -07:00
std::string query = queryStream.str();
if (tasksEnabled.size()) {
Log(Logs::General, Logs::Tasks, "[UPDATE] Executing query %s", query.c_str());
database.QueryDatabase(query);
}
else {
Log(Logs::General, Logs::Tasks, "[UPDATE] EnableTask called for characterID: %u .. but, no tasks exist", characterID);
}
2013-02-16 16:14:39 -08:00
}
2014-10-08 13:17:03 -07:00
void ClientTaskState::DisableTask(int charID, int taskCount, int *taskList) {
2013-02-16 16:14:39 -08:00
// Check if the Task is enabled for this client
//
2014-10-08 13:17:03 -07:00
std::vector<int> tasksDisabled;
2013-05-06 13:07:41 -04:00
2014-10-08 13:17:03 -07:00
for(int i=0; i<taskCount; i++) {
auto iterator = EnabledTasks.begin();
bool removeTask = false;
2013-02-16 16:14:39 -08:00
2014-10-08 13:17:03 -07:00
while(iterator != EnabledTasks.end()) {
if((*iterator) == taskList[i]) {
removeTask = true;
2013-02-16 16:14:39 -08:00
break;
}
2014-10-08 13:17:03 -07:00
if((*iterator) > taskList[i])
break;
++iterator;
2013-02-16 16:14:39 -08:00
}
2014-10-08 13:17:03 -07:00
if(removeTask) {
EnabledTasks.erase(iterator);
tasksDisabled.push_back(taskList[i]);
2013-02-16 16:14:39 -08:00
}
}
Log(Logs::General, Logs::Tasks, "[UPDATE] New enabled task list ");
2013-02-16 16:14:39 -08:00
for(unsigned int i=0; i<EnabledTasks.size(); i++)
Log(Logs::General, Logs::Tasks, "[UPDATE] %i", EnabledTasks[i]);
2013-02-16 16:14:39 -08:00
if(tasksDisabled.empty())
2014-10-08 13:17:03 -07:00
return;
2013-02-16 16:14:39 -08:00
std::stringstream queryStream;
queryStream << StringFormat("DELETE FROM character_enabledtasks WHERE charid = %i AND (", charID);
2013-02-16 16:14:39 -08:00
2014-10-08 13:17:03 -07:00
for(unsigned int i=0; i<tasksDisabled.size(); i++)
queryStream << (i ? StringFormat("taskid = %i ", tasksDisabled[i]) : StringFormat("OR taskid = %i ", tasksDisabled[i]));
2013-02-16 16:14:39 -08:00
2014-10-08 13:17:03 -07:00
queryStream << ")";
2014-10-08 13:17:03 -07:00
std::string query = queryStream.str();
if (tasksDisabled.size()) {
Log(Logs::General, Logs::Tasks, "[UPDATE] Executing query %s", query.c_str());
database.QueryDatabase(query);
}
else {
Log(Logs::General, Logs::Tasks, "[UPDATE] DisableTask called for characterID: %u .. but, no tasks exist", charID);
}
2013-02-16 16:14:39 -08:00
}
bool ClientTaskState::IsTaskEnabled(int TaskID) {
std::vector<int>::iterator Iterator;
2013-02-16 16:14:39 -08:00
Iterator = EnabledTasks.begin();
while(Iterator != EnabledTasks.end()) {
if((*Iterator) == TaskID) return true;
if((*Iterator) > TaskID) break;
++Iterator;
2013-02-16 16:14:39 -08:00
}
return false;
}
int ClientTaskState::EnabledTaskCount(int TaskSetID) {
// Return the number of tasks in TaskSet that this character is enabled for.
unsigned int EnabledTaskIndex = 0;
unsigned int TaskSetIndex = 0;
int EnabledTaskCount = 0;
if((TaskSetID<=0) || (TaskSetID>=MAXTASKSETS)) return -1;
while((EnabledTaskIndex < EnabledTasks.size()) && (TaskSetIndex < taskmanager->TaskSets[TaskSetID].size())) {
if(EnabledTasks[EnabledTaskIndex] == taskmanager->TaskSets[TaskSetID][TaskSetIndex]) {
EnabledTaskCount++;
EnabledTaskIndex++;
TaskSetIndex++;
continue;
}
if(EnabledTasks[EnabledTaskIndex] < taskmanager->TaskSets[TaskSetID][TaskSetIndex])
EnabledTaskIndex++;
else
TaskSetIndex++;
}
return EnabledTaskCount;
}
int ClientTaskState::ActiveTasksInSet(int TaskSetID) {
if((TaskSetID<=0) || (TaskSetID>=MAXTASKSETS)) return -1;
int Count = 0;
2013-05-06 13:07:41 -04:00
for(unsigned int i=0; i<taskmanager->TaskSets[TaskSetID].size(); i++)
2013-02-16 16:14:39 -08:00
if(IsTaskActive(taskmanager->TaskSets[TaskSetID][i]))
Count++;
return Count;
}
int ClientTaskState::CompletedTasksInSet(int TaskSetID) {
if((TaskSetID<=0) || (TaskSetID>=MAXTASKSETS)) return -1;
int Count = 0;
2013-05-06 13:07:41 -04:00
for(unsigned int i=0; i<taskmanager->TaskSets[TaskSetID].size(); i++)
2013-02-16 16:14:39 -08:00
if(IsTaskCompleted(taskmanager->TaskSets[TaskSetID][i]))
Count++;
return Count;
}
2018-06-16 22:42:26 -04:00
bool ClientTaskState::HasSlotForTask(TaskInformation *task)
{
if (task == nullptr)
return false;
switch (task->type) {
case TaskType::Task:
return ActiveTask.TaskID == TASKSLOTEMPTY;
case TaskType::Shared:
return false; // todo
case TaskType::Quest:
for (int i = 0; i < MAXACTIVEQUESTS; ++i)
if (ActiveQuests[i].TaskID == TASKSLOTEMPTY)
return true;
case TaskType::E:
return false; // removed on live
}
return false;
}
2013-02-16 16:14:39 -08:00
int TaskManager::FirstTaskInSet(int TaskSetID) {
if((TaskSetID<=0) || (TaskSetID>=MAXTASKSETS)) return 0;
if(TaskSets[TaskSetID].empty()) return 0;
2013-02-16 16:14:39 -08:00
2016-05-25 16:10:28 -04:00
auto Iterator = TaskSets[TaskSetID].begin();
2013-02-16 16:14:39 -08:00
while(Iterator != TaskSets[TaskSetID].end()) {
if((*Iterator) > 0)
return (*Iterator);
++Iterator;
2013-02-16 16:14:39 -08:00
}
return 0;
}
int TaskManager::LastTaskInSet(int TaskSetID) {
if((TaskSetID<=0) || (TaskSetID>=MAXTASKSETS)) return 0;
if(TaskSets[TaskSetID].empty()) return 0;
2013-02-16 16:14:39 -08:00
return TaskSets[TaskSetID][TaskSets[TaskSetID].size()-1];
}
int TaskManager::NextTaskInSet(int TaskSetID, int TaskID) {
if((TaskSetID<=0) || (TaskSetID>=MAXTASKSETS)) return 0;
if(TaskSets[TaskSetID].empty()) return 0;
2013-02-16 16:14:39 -08:00
for(unsigned int i=0; i<TaskSets[TaskSetID].size(); i++) {
if(TaskSets[TaskSetID][i] > TaskID) return TaskSets[TaskSetID][i];
}
return 0;
}
bool TaskManager::AppropriateLevel(int TaskID, int PlayerLevel) {
2013-05-04 18:06:58 -07:00
if(Tasks[TaskID] == nullptr) return false;
2013-02-16 16:14:39 -08:00
if(Tasks[TaskID]->MinLevel && (PlayerLevel < Tasks[TaskID]->MinLevel)) return false;
if(Tasks[TaskID]->MaxLevel && (PlayerLevel > Tasks[TaskID]->MaxLevel)) return false;
return true;
}
2020-03-31 22:24:09 -04:00
std::string TaskManager::GetTaskName(uint32 task_id)
{
if (task_id > 0 && task_id < MAXTASKS) {
if (Tasks[task_id] != nullptr) {
return Tasks[task_id]->Title;
}
}
return std::string();
}
int TaskManager::GetTaskMinLevel(int TaskID)
{
if (Tasks[TaskID]->MinLevel)
{
return Tasks[TaskID]->MinLevel;
}
return -1;
}
int TaskManager::GetTaskMaxLevel(int TaskID)
{
if (Tasks[TaskID]->MaxLevel)
{
return Tasks[TaskID]->MaxLevel;
}
return -1;
}
2018-06-16 22:42:26 -04:00
void TaskManager::TaskSetSelector(Client *c, ClientTaskState *state, Mob *mob, int TaskSetID)
{
2013-02-16 16:14:39 -08:00
int TaskList[MAXCHOOSERENTRIES];
int TaskListIndex = 0;
int PlayerLevel = c->GetLevel();
2018-06-16 22:42:26 -04:00
Log(Logs::General, Logs::Tasks, "[UPDATE] TaskSetSelector called for taskset %i. EnableTaskSize is %i",
TaskSetID, state->EnabledTasks.size());
2013-02-16 16:14:39 -08:00
2018-06-16 22:42:26 -04:00
if (TaskSetID <= 0 || TaskSetID >= MAXTASKSETS)
return;
2013-02-16 16:14:39 -08:00
2018-06-16 22:42:26 -04:00
if (TaskSets[TaskSetID].empty()) {
2019-08-11 01:09:11 -05:00
mob->SayString(c, Chat::Yellow, MAX_ACTIVE_TASKS, c->GetName()); // I think this is suppose to be yellow
2018-06-16 22:42:26 -04:00
return;
2013-02-16 16:14:39 -08:00
}
2018-06-16 22:42:26 -04:00
bool all_enabled = false;
2013-02-16 16:14:39 -08:00
2018-06-16 22:42:26 -04:00
// A TaskID of 0 in a TaskSet indicates that all Tasks in the set are enabled for all players.
if (TaskSets[TaskSetID][0] == 0) {
Log(Logs::General, Logs::Tasks, "[UPDATE] TaskSets[%i][0] == 0. All Tasks in Set enabled.", TaskSetID);
all_enabled = true;
}
2013-02-16 16:14:39 -08:00
2018-06-16 22:42:26 -04:00
auto Iterator = TaskSets[TaskSetID].begin();
2013-02-16 16:14:39 -08:00
2018-06-16 22:42:26 -04:00
if (all_enabled)
++Iterator; // skip first when all enabled since it's useless data
2013-02-16 16:14:39 -08:00
2018-06-16 22:42:26 -04:00
while (Iterator != TaskSets[TaskSetID].end() && TaskListIndex < MAXCHOOSERENTRIES) {
auto task = *Iterator;
// verify level, we're not currently on it, repeatable status, if it's a (shared) task
// we aren't currently on another, and if it's enabled if not all_enabled
if ((all_enabled || state->IsTaskEnabled(task)) && AppropriateLevel(task, PlayerLevel) &&
!state->IsTaskActive(task) && state->HasSlotForTask(Tasks[task]) && // this slot checking is a bit silly, but we allow mixing of task types ...
(IsTaskRepeatable(task) || !state->IsTaskCompleted(task)))
TaskList[TaskListIndex++] = task;
2013-02-16 16:14:39 -08:00
2018-06-16 22:42:26 -04:00
++Iterator;
2013-02-16 16:14:39 -08:00
}
2018-06-16 22:42:26 -04:00
if (TaskListIndex > 0) {
SendTaskSelector(c, mob, TaskListIndex, TaskList);
} else {
2019-08-11 01:09:11 -05:00
mob->SayString(c, Chat::Yellow, MAX_ACTIVE_TASKS, c->GetName()); // check color, I think this might be only for (Shared) Tasks, w/e -- think should be yellow
2018-06-16 22:42:26 -04:00
}
2013-02-16 16:14:39 -08:00
2018-06-16 22:42:26 -04:00
return;
2013-02-16 16:14:39 -08:00
}
// unlike the non-Quest version of this function, it does not check enabled, that is assumed the responsibility of the quest to handle
// we do however still want it to check the other stuff like level, active, room, etc
void TaskManager::TaskQuestSetSelector(Client *c, ClientTaskState *state, Mob *mob, int count, int *tasks)
{
int TaskList[MAXCHOOSERENTRIES];
int TaskListIndex = 0;
int PlayerLevel = c->GetLevel();
Log(Logs::General, Logs::Tasks, "[UPDATE] TaskQuestSetSelector called for array size %d", count);
if (count <= 0)
return;
for (int i = 0; i < count; ++i) {
auto task = tasks[i];
// verify level, we're not currently on it, repeatable status, if it's a (shared) task
// we aren't currently on another, and if it's enabled if not all_enabled
if (AppropriateLevel(task, PlayerLevel) &&
!state->IsTaskActive(task) && state->HasSlotForTask(Tasks[task]) && // this slot checking is a bit silly, but we allow mixing of task types ...
(IsTaskRepeatable(task) || !state->IsTaskCompleted(task)))
TaskList[TaskListIndex++] = task;
}
if (TaskListIndex > 0) {
SendTaskSelector(c, mob, TaskListIndex, TaskList);
} else {
2019-08-11 01:09:11 -05:00
mob->SayString(c, Chat::Yellow, MAX_ACTIVE_TASKS, c->GetName()); // check color, I think this might be only for (Shared) Tasks, w/e -- think should be yellow
}
return;
}
2013-02-16 16:14:39 -08:00
void TaskManager::SendTaskSelector(Client *c, Mob *mob, int TaskCount, int *TaskList) {
if (c->ClientVersion() >= EQ::versions::ClientVersion::RoF)
2013-02-16 16:14:39 -08:00
{
SendTaskSelectorNew(c, mob, TaskCount, TaskList);
return;
}
// Titanium OpCode: 0x5e7c
Log(Logs::General, Logs::Tasks, "[UPDATE] TaskSelector for %i Tasks", TaskCount);
2013-02-16 16:14:39 -08:00
int PlayerLevel = c->GetLevel();
2013-05-09 10:44:08 -04:00
// Check if any of the tasks exist
for (int i = 0; i < TaskCount; i++) {
if (Tasks[TaskList[i]] != nullptr)
break;
2013-02-16 16:14:39 -08:00
}
int ValidTasks = 0;
for (int i = 0; i < TaskCount; i++) {
if (!AppropriateLevel(TaskList[i], PlayerLevel))
continue;
if (c->IsTaskActive(TaskList[i]))
continue;
if (!IsTaskRepeatable(TaskList[i]) && c->IsTaskCompleted(TaskList[i]))
continue;
2013-02-16 16:14:39 -08:00
ValidTasks++;
}
if (ValidTasks == 0)
return;
2013-05-06 13:07:41 -04:00
SerializeBuffer buf(50 * ValidTasks);
2013-02-16 16:14:39 -08:00
buf.WriteUInt32(ValidTasks);
buf.WriteUInt32(2); // task type, live doesn't let you send more than one type, but we do?
buf.WriteUInt32(mob->GetID());
2013-02-16 16:14:39 -08:00
for (int i = 0; i < TaskCount; i++) {
if (!AppropriateLevel(TaskList[i], PlayerLevel))
continue;
if (c->IsTaskActive(TaskList[i]))
continue;
if (!IsTaskRepeatable(TaskList[i]) && c->IsTaskCompleted(TaskList[i]))
continue;
2013-02-16 16:14:39 -08:00
buf.WriteUInt32(TaskList[i]); // TaskID
if (c->ClientVersion() != EQ::versions::ClientVersion::Titanium)
buf.WriteFloat(1.0f); // affects color, difficulty?
buf.WriteUInt32(Tasks[TaskList[i]]->Duration);
buf.WriteUInt32(static_cast<int>(Tasks[TaskList[i]]->dur_code));
2013-02-16 16:14:39 -08:00
buf.WriteString(Tasks[TaskList[i]]->Title); // max 64 with null
buf.WriteString(Tasks[TaskList[i]]->Description); // max 4000 with null
2013-02-16 16:14:39 -08:00
if (c->ClientVersion() != EQ::versions::ClientVersion::Titanium)
buf.WriteUInt8(0); // Has reward set flag
2013-02-16 16:14:39 -08:00
buf.WriteUInt32(Tasks[TaskList[i]]->ActivityCount);
2013-02-16 16:14:39 -08:00
for (int j = 0; j < Tasks[TaskList[i]]->ActivityCount; ++j) {
buf.WriteUInt32(j); // ActivityNumber
auto &activity = Tasks[TaskList[i]]->Activity[j];
buf.WriteUInt32(activity.Type);
buf.WriteUInt32(0); // solo, group, raid?
buf.WriteString(activity.target_name); // max length 64, "target name" so like loot x foo from bar (this is bar)
2013-02-16 16:14:39 -08:00
buf.WriteString(activity.item_list); // max length 64 in these clients
2013-02-16 16:14:39 -08:00
buf.WriteUInt32(activity.GoalCount);
2013-05-06 13:07:41 -04:00
buf.WriteInt32(activity.skill_id);
buf.WriteInt32(activity.spell_id);
buf.WriteInt32(activity.ZoneIDs.empty() ? 0 : activity.ZoneIDs.front());
buf.WriteString(activity.desc_override);
}
2013-02-16 16:14:39 -08:00
}
2018-07-08 23:03:53 -04:00
auto outapp = new EQApplicationPacket(OP_OpenNewTasksWindow, buf);
2013-02-16 16:14:39 -08:00
c->QueuePacket(outapp);
safe_delete(outapp);
}
void TaskManager::SendTaskSelectorNew(Client *c, Mob *mob, int TaskCount, int *TaskList)
{
Log(Logs::General, Logs::Tasks, "[UPDATE] TaskSelector for %i Tasks", TaskCount);
2013-02-16 16:14:39 -08:00
int PlayerLevel = c->GetLevel();
2013-05-09 10:44:08 -04:00
// Check if any of the tasks exist
for (int i = 0; i < TaskCount; i++)
if (Tasks[TaskList[i]] != nullptr)
break;
2013-02-16 16:14:39 -08:00
int ValidTasks = 0;
for (int i = 0; i < TaskCount; i++) {
if (!AppropriateLevel(TaskList[i], PlayerLevel))
continue;
if (c->IsTaskActive(TaskList[i]))
continue;
if (!IsTaskRepeatable(TaskList[i]) && c->IsTaskCompleted(TaskList[i]))
continue;
2013-02-16 16:14:39 -08:00
ValidTasks++;
}
if (ValidTasks == 0)
return;
2013-02-16 16:14:39 -08:00
SerializeBuffer buf(50 * ValidTasks);
2013-05-06 13:07:41 -04:00
buf.WriteUInt32(ValidTasks); // TaskCount
buf.WriteUInt32(2); // Type, valid values: 0-3. 0 = Task, 1 = Shared Task, 2 = Quest, 3 = ??? -- should fix maybe some day, but we let more than 1 type through :P
2018-06-07 18:14:09 -04:00
// so I guess an NPC can only offer one type of quests or we can only open a selection with one type :P (so quest call can tell us I guess)
// this is also sent in OP_TaskDescription
buf.WriteUInt32(mob->GetID()); // TaskGiver
2013-02-16 16:14:39 -08:00
for (int i = 0; i < TaskCount; i++) { // max 40
if (!AppropriateLevel(TaskList[i], PlayerLevel))
continue;
if (c->IsTaskActive(TaskList[i]))
continue;
if (!IsTaskRepeatable(TaskList[i]) && c->IsTaskCompleted(TaskList[i]))
continue;
2013-02-16 16:14:39 -08:00
buf.WriteUInt32(TaskList[i]); // TaskID
buf.WriteFloat(1.0f); // affects color, difficulty?
buf.WriteUInt32(Tasks[TaskList[i]]->Duration);
buf.WriteUInt32(static_cast<int>(Tasks[TaskList[i]]->dur_code)); // 1 = Short, 2 = Medium, 3 = Long, anything else Unlimited
2013-05-06 13:07:41 -04:00
buf.WriteString(Tasks[TaskList[i]]->Title); // max 64 with null
buf.WriteString(Tasks[TaskList[i]]->Description); // max 4000 with null
2013-02-16 16:14:39 -08:00
buf.WriteUInt8(0); // Has reward set flag
buf.WriteUInt32(Tasks[TaskList[i]]->ActivityCount); // ActivityCount
2013-02-16 16:14:39 -08:00
for (int j = 0; j < Tasks[TaskList[i]]->ActivityCount; ++j) {
buf.WriteUInt32(j); // ActivityNumber
auto &activity = Tasks[TaskList[i]]->Activity[j];
buf.WriteUInt32(activity.Type); // ActivityType
buf.WriteUInt32(0); // solo, group, raid?
buf.WriteString(activity.target_name); // max length 64, "target name" so like loot x foo from bar (this is bar)
// this string is item names
buf.WriteLengthString(activity.item_list);
buf.WriteUInt32(activity.GoalCount); // GoalCount
// this string is skill IDs? probably one of the "use on" tasks
buf.WriteLengthString(activity.skill_list);
// this string is spell IDs? probably one of the "use on" tasks
buf.WriteLengthString(activity.spell_list);
//buf.WriteString(itoa(Tasks[TaskList[i]]->Activity[ActivityID].ZoneID));
buf.WriteString(activity.zones); // Zone number in ascii max length 64, can be multiple with separated by ;
buf.WriteString(activity.desc_override); // max length 128 -- overrides the automatic descriptions
// this doesn't appear to be shown to the client at all and isn't the same as zones ... defaults to '0' though
buf.WriteString(activity.zones); // Zone number in ascii max length 64, probably can be separated by ; too, haven't found it used
}
2013-02-16 16:14:39 -08:00
}
2018-07-08 23:03:53 -04:00
auto outapp = new EQApplicationPacket(OP_OpenNewTasksWindow, buf);
2013-02-16 16:14:39 -08:00
c->QueuePacket(outapp);
safe_delete(outapp);
}
int TaskManager::GetActivityCount(int TaskID) {
// Return the total number of activities in a particular task.
2013-05-06 13:07:41 -04:00
if((TaskID>0) && (TaskID<MAXTASKS))
2013-02-16 16:14:39 -08:00
if(Tasks[TaskID]) return Tasks[TaskID]->ActivityCount;
return 0;
}
void TaskManager::ExplainTask(Client*c, int TaskID) {
// TODO: This method is not finished (hardly started). It was intended to
// explain in English, what each activity did, conditions for step unlocking, etc.
//
return;
if(!c) return;
if((TaskID<=0) || (TaskID>=MAXTASKS)) {
c->Message(Chat::White, "TaskID out-of-range.");
2013-02-16 16:14:39 -08:00
return;
}
2013-05-04 18:06:58 -07:00
if(Tasks[TaskID] == nullptr) {
c->Message(Chat::White, "Task does not exist.");
2013-02-16 16:14:39 -08:00
return;
}
char Explanation[1000], *ptr;
c->Message(Chat::White, "Task %4i: Title: %s", TaskID, Tasks[TaskID]->Description.c_str());
c->Message(Chat::White, "%3i Activities", Tasks[TaskID]->ActivityCount);
2013-02-16 16:14:39 -08:00
ptr = Explanation;
for(int i=0; i<Tasks[TaskID]->ActivityCount; i++) {
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
sprintf(ptr, "Act: %3i: ", i); ptr = ptr + strlen(ptr);
switch(Tasks[TaskID]->Activity[i].Type) {
case ActivityDeliver:
sprintf(ptr, "Deliver");
break;
}
}
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
ClientTaskState::ClientTaskState() {
ActiveTaskCount = 0;
LastCompletedTaskLoaded = 0;
CheckedTouchActivities = false;
2018-06-24 23:21:35 -04:00
for (int i = 0; i < MAXACTIVEQUESTS; i++) {
ActiveQuests[i].slot = i;
ActiveQuests[i].TaskID = TASKSLOTEMPTY;
2018-06-24 23:21:35 -04:00
}
2018-06-16 23:52:25 -04:00
2018-06-24 23:21:35 -04:00
ActiveTask.slot = 0;
2018-06-16 23:52:25 -04:00
ActiveTask.TaskID = TASKSLOTEMPTY;
2018-06-24 23:21:35 -04:00
// TODO: shared task
2013-02-16 16:14:39 -08:00
}
ClientTaskState::~ClientTaskState() {
}
int ClientTaskState::GetActiveTaskID(int index) {
// Return the TaskID from the client's specified Active Task slot.
if((index<0) || (index>=MAXACTIVEQUESTS)) return 0;
2013-02-16 16:14:39 -08:00
return ActiveQuests[index].TaskID;
2013-02-16 16:14:39 -08:00
}
2014-10-08 13:22:10 -07:00
static void DeleteCompletedTaskFromDatabase(int charID, int taskID) {
2013-02-16 16:14:39 -08:00
Log(Logs::General, Logs::Tasks, "[UPDATE] DeleteCompletedTasksFromDatabase. CharID = %i, TaskID = %i", charID, taskID);
2013-02-16 16:14:39 -08:00
2014-10-08 13:22:10 -07:00
const std::string query = StringFormat("DELETE FROM completed_tasks WHERE charid=%i AND taskid = %i", charID, taskID);
auto results = database.QueryDatabase(query);
if(!results.Success()) {
2013-02-16 16:14:39 -08:00
return;
}
2014-10-08 13:22:10 -07:00
Log(Logs::General, Logs::Tasks, "[UPDATE] Delete query %s", query.c_str());
2013-02-16 16:14:39 -08:00
}
2018-06-24 23:21:35 -04:00
bool ClientTaskState::UnlockActivities(int CharID, ClientTaskInformation &task_info)
{
2013-02-16 16:14:39 -08:00
bool AllActivitiesComplete = true;
2018-06-24 23:21:35 -04:00
TaskInformation* Task = taskmanager->Tasks[task_info.TaskID];
2013-02-16 16:14:39 -08:00
2018-06-24 23:21:35 -04:00
if (Task == nullptr)
return true;
2013-02-16 16:14:39 -08:00
// On loading the client state, all activities that are not completed, are
2013-05-09 10:44:08 -04:00
// marked as hidden. For Sequential (non-stepped) mode, we mark the first
2013-02-16 16:14:39 -08:00
// activity as active if not complete.
Log(Logs::General, Logs::Tasks, "[UPDATE] CharID: %i Task: %i Sequence mode is %i",
2018-06-24 23:21:35 -04:00
CharID, task_info.TaskID, Task->SequenceMode);
2013-02-16 16:14:39 -08:00
2018-06-24 23:21:35 -04:00
if (Task->SequenceMode == ActivitiesSequential) {
if (task_info.Activity[0].State != ActivityCompleted)
task_info.Activity[0].State = ActivityActive;
2013-02-16 16:14:39 -08:00
// Enable the next Hidden task.
2018-06-24 23:21:35 -04:00
for (int i = 0; i < Task->ActivityCount; i++) {
if ((task_info.Activity[i].State == ActivityActive) &&
(!Task->Activity[i].Optional)) {
2013-02-16 16:14:39 -08:00
AllActivitiesComplete = false;
break;
}
2018-06-24 23:21:35 -04:00
if (task_info.Activity[i].State == ActivityHidden) {
task_info.Activity[i].State = ActivityActive;
2013-02-16 16:14:39 -08:00
AllActivitiesComplete = false;
break;
}
}
2018-06-24 23:21:35 -04:00
if (AllActivitiesComplete && RuleB(TaskSystem, RecordCompletedTasks)) {
if (RuleB(TasksSystem, KeepOneRecordPerCompletedTask)) {
Log(Logs::General, Logs::Tasks, "[UPDATE] KeepOneRecord enabled");
2016-05-25 16:10:28 -04:00
auto Iterator = CompletedTasks.begin();
2013-02-16 16:14:39 -08:00
int ErasedElements = 0;
2018-06-24 23:21:35 -04:00
while (Iterator != CompletedTasks.end()) {
2013-02-16 16:14:39 -08:00
int TaskID = (*Iterator).TaskID;
2018-06-24 23:21:35 -04:00
if (TaskID == task_info.TaskID) {
2013-02-16 16:14:39 -08:00
Iterator = CompletedTasks.erase(Iterator);
ErasedElements++;
2018-06-24 23:21:35 -04:00
} else
++Iterator;
2013-02-16 16:14:39 -08:00
}
2018-06-24 23:21:35 -04:00
Log(Logs::General, Logs::Tasks, "[UPDATE] Erased Element count is %i", ErasedElements);
2018-06-24 23:21:35 -04:00
if (ErasedElements) {
2013-02-16 16:14:39 -08:00
LastCompletedTaskLoaded -= ErasedElements;
2018-06-24 23:21:35 -04:00
DeleteCompletedTaskFromDatabase(CharID, task_info.TaskID);
2013-02-16 16:14:39 -08:00
}
}
CompletedTaskInformation cti;
2018-06-24 23:21:35 -04:00
cti.TaskID = task_info.TaskID;
2013-05-04 18:06:58 -07:00
cti.CompletedTime = time(nullptr);
2013-02-16 16:14:39 -08:00
2018-06-24 23:21:35 -04:00
for (int i = 0; i < Task->ActivityCount; i++)
cti.ActivityDone[i] = (task_info.Activity[i].State == ActivityCompleted);
2013-02-16 16:14:39 -08:00
CompletedTasks.push_back(cti);
}
2018-06-24 23:21:35 -04:00
Log(Logs::General, Logs::Tasks, "[UPDATE] Returning sequential task, AllActivitiesComplete is %i",
AllActivitiesComplete);
2013-02-16 16:14:39 -08:00
return AllActivitiesComplete;
}
// Stepped Mode
// TODO: This code is probably more complex than it needs to be
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
bool CurrentStepComplete = true;
2018-06-24 23:21:35 -04:00
Log(Logs::General, Logs::Tasks, "[UPDATE] Current Step is %i, Last Step is %i", task_info.CurrentStep,
Task->LastStep);
2013-02-16 16:14:39 -08:00
// If CurrentStep is -1, this is the first call to this method since loading the
// client state. Unlock all activities with a step number of 0
2013-05-06 13:07:41 -04:00
2018-06-24 23:21:35 -04:00
if (task_info.CurrentStep == -1) {
for (int i = 0; i < Task->ActivityCount; i++) {
if (Task->Activity[i].StepNumber == 0 && task_info.Activity[i].State == ActivityHidden) {
task_info.Activity[i].State = ActivityActive;
// task_info.Activity[i].Updated=true;
2013-02-16 16:14:39 -08:00
}
}
2018-06-24 23:21:35 -04:00
task_info.CurrentStep = 0;
2013-02-16 16:14:39 -08:00
}
2018-06-24 23:21:35 -04:00
for (int Step = task_info.CurrentStep; Step <= Task->LastStep; Step++) {
for (int Activity = 0; Activity < Task->ActivityCount; Activity++) {
if (Task->Activity[Activity].StepNumber == (int)task_info.CurrentStep) {
if ((task_info.Activity[Activity].State != ActivityCompleted) &&
(!Task->Activity[Activity].Optional)) {
2013-02-16 16:14:39 -08:00
CurrentStepComplete = false;
AllActivitiesComplete = false;
break;
}
}
}
2018-06-24 23:21:35 -04:00
if (!CurrentStepComplete)
break;
task_info.CurrentStep++;
2013-02-16 16:14:39 -08:00
}
2018-06-24 23:21:35 -04:00
if (AllActivitiesComplete) {
if (RuleB(TaskSystem, RecordCompletedTasks)) {
2013-02-16 16:14:39 -08:00
// If we are only keeping one completed record per task, and the player has done
// the same task again, erase the previous completed entry for this task.
2018-06-24 23:21:35 -04:00
if (RuleB(TasksSystem, KeepOneRecordPerCompletedTask)) {
Log(Logs::General, Logs::Tasks, "[UPDATE] KeepOneRecord enabled");
2016-05-25 16:10:28 -04:00
auto Iterator = CompletedTasks.begin();
2013-02-16 16:14:39 -08:00
int ErasedElements = 0;
2018-06-24 23:21:35 -04:00
while (Iterator != CompletedTasks.end()) {
2013-02-16 16:14:39 -08:00
int TaskID = (*Iterator).TaskID;
2018-06-24 23:21:35 -04:00
if (TaskID == task_info.TaskID) {
2013-02-16 16:14:39 -08:00
Iterator = CompletedTasks.erase(Iterator);
ErasedElements++;
2018-06-24 23:21:35 -04:00
} else
++Iterator;
2013-02-16 16:14:39 -08:00
}
2018-06-24 23:21:35 -04:00
Log(Logs::General, Logs::Tasks, "[UPDATE] Erased Element count is %i", ErasedElements);
2018-06-24 23:21:35 -04:00
if (ErasedElements) {
2013-02-16 16:14:39 -08:00
LastCompletedTaskLoaded -= ErasedElements;
2018-06-24 23:21:35 -04:00
DeleteCompletedTaskFromDatabase(CharID, task_info.TaskID);
2013-02-16 16:14:39 -08:00
}
}
2018-06-24 23:21:35 -04:00
2013-02-16 16:14:39 -08:00
CompletedTaskInformation cti;
2018-06-24 23:21:35 -04:00
cti.TaskID = task_info.TaskID;
2013-05-04 18:06:58 -07:00
cti.CompletedTime = time(nullptr);
2013-02-16 16:14:39 -08:00
2018-06-24 23:21:35 -04:00
for (int i = 0; i < Task->ActivityCount; i++)
cti.ActivityDone[i] = (task_info.Activity[i].State == ActivityCompleted);
2013-02-16 16:14:39 -08:00
CompletedTasks.push_back(cti);
}
return true;
}
// Mark all non-completed tasks in the current step as active
//
2018-06-24 23:21:35 -04:00
for (int Activity = 0; Activity < Task->ActivityCount; Activity++) {
if ((Task->Activity[Activity].StepNumber == (int)task_info.CurrentStep) &&
(task_info.Activity[Activity].State == ActivityHidden)) {
task_info.Activity[Activity].State = ActivityActive;
task_info.Activity[Activity].Updated = true;
2013-02-16 16:14:39 -08:00
}
}
return false;
}
void ClientTaskState::UpdateTasksOnKill(Client *c, int NPCTypeID) {
UpdateTasksByNPC(c, ActivityKill, NPCTypeID);
}
bool ClientTaskState::UpdateTasksOnSpeakWith(Client *c, int NPCTypeID) {
return UpdateTasksByNPC(c, ActivitySpeakWith, NPCTypeID);
}
bool ClientTaskState::UpdateTasksByNPC(Client *c, int ActivityType, int NPCTypeID) {
int Ret = false;
Log(Logs::General, Logs::Tasks, "[UPDATE] ClientTaskState::UpdateTasks for NPCTypeID: %d", NPCTypeID);
2013-02-16 16:14:39 -08:00
// If the client has no tasks, there is nothing further to check.
2013-05-06 13:07:41 -04:00
if (!taskmanager || (ActiveTaskCount == 0 && ActiveTask.TaskID == TASKSLOTEMPTY)) // could be better ...
return false;
2013-02-16 16:14:39 -08:00
// loop over the union of tasks and quests
for (int i = 0; i < MAXACTIVEQUESTS + 1; i++) {
auto cur_task = &ActiveTasks[i];
if (cur_task->TaskID == TASKSLOTEMPTY)
continue;
2013-02-16 16:14:39 -08:00
// Check if there are any active kill activities for this task
auto Task = taskmanager->Tasks[cur_task->TaskID];
2013-02-16 16:14:39 -08:00
if (Task == nullptr)
return false;
2013-02-16 16:14:39 -08:00
for (int j = 0; j < Task->ActivityCount; j++) {
2013-02-16 16:14:39 -08:00
// We are not interested in completed or hidden activities
if (cur_task->Activity[j].State != ActivityActive)
continue;
2013-02-16 16:14:39 -08:00
// We are only interested in Kill activities
if (Task->Activity[j].Type != ActivityType)
continue;
2013-02-16 16:14:39 -08:00
// Is there a zone restriction on the activity ?
if (!Task->Activity[j].CheckZone(zone->GetZoneID())) {
Log(Logs::General, Logs::Tasks,
"[UPDATE] Char: %s Task: %i, Activity %i, Activity type %i for NPC %i failed zone "
"check",
c->GetName(), cur_task->TaskID, j, ActivityType, NPCTypeID);
2013-02-16 16:14:39 -08:00
continue;
}
// Is the activity to kill this type of NPC ?
switch (Task->Activity[j].GoalMethod) {
2013-02-16 16:14:39 -08:00
case METHODSINGLEID:
if (Task->Activity[j].GoalID != NPCTypeID)
continue;
break;
2013-02-16 16:14:39 -08:00
case METHODLIST:
if (!taskmanager->GoalListManager.IsInList(Task->Activity[j].GoalID, NPCTypeID))
2013-02-16 16:14:39 -08:00
continue;
break;
default:
// If METHODQUEST, don't update the activity here
continue;
2013-02-16 16:14:39 -08:00
}
// We found an active task to kill this type of NPC, so increment the done count
Log(Logs::General, Logs::Tasks, "[UPDATE] Calling increment done count ByNPC");
2018-07-09 14:01:52 -04:00
IncrementDoneCount(c, Task, cur_task->slot, j);
2013-02-16 16:14:39 -08:00
Ret = true;
}
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
return Ret;
}
int ClientTaskState::ActiveSpeakTask(int NPCTypeID) {
// This method is to be used from Perl quests only and returns the TaskID of the first
// active task found which has an active SpeakWith activity for this NPC.
if (!taskmanager || (ActiveTaskCount == 0 && ActiveTask.TaskID == TASKSLOTEMPTY)) // could be better ...
return 0;
2013-02-16 16:14:39 -08:00
// loop over the union of tasks and quests
for (int i = 0; i < MAXACTIVEQUESTS + 1; i++) {
auto cur_task = &ActiveTasks[i];
if (cur_task->TaskID == TASKSLOTEMPTY)
continue;
2013-02-16 16:14:39 -08:00
TaskInformation* Task = taskmanager->Tasks[cur_task->TaskID];
2013-02-16 16:14:39 -08:00
if (Task == nullptr)
continue;
2013-02-16 16:14:39 -08:00
for (int j = 0; j < Task->ActivityCount; j++) {
2013-02-16 16:14:39 -08:00
// We are not interested in completed or hidden activities
if (cur_task->Activity[j].State != ActivityActive)
continue;
if (Task->Activity[j].Type != ActivitySpeakWith)
continue;
2013-02-16 16:14:39 -08:00
// Is there a zone restriction on the activity ?
if (!Task->Activity[j].CheckZone(zone->GetZoneID()))
2013-02-16 16:14:39 -08:00
continue;
// Is the activity to speak with this type of NPC ?
if (Task->Activity[j].GoalMethod == METHODQUEST && Task->Activity[j].GoalID == NPCTypeID)
return cur_task->TaskID;
2013-02-16 16:14:39 -08:00
}
}
return 0;
}
int ClientTaskState::ActiveSpeakActivity(int NPCTypeID, int TaskID) {
// This method is to be used from Perl quests only and returns the ActivityID of the first
// active activity found in the specified task which is to SpeakWith this NPC.
if (!taskmanager || (ActiveTaskCount == 0 && ActiveTask.TaskID == TASKSLOTEMPTY)) // could be better ...
return -1;
if (TaskID <= 0 || TaskID >= MAXTASKS)
return -1;
2013-02-16 16:14:39 -08:00
// loop over the union of tasks and quests
for (int i = 0; i < MAXACTIVEQUESTS + 1; i++) {
auto cur_task = &ActiveTasks[i];
if (cur_task->TaskID != TaskID)
continue;
2013-02-16 16:14:39 -08:00
TaskInformation* Task = taskmanager->Tasks[cur_task->TaskID];
2013-02-16 16:14:39 -08:00
if (Task == nullptr)
continue;
2013-02-16 16:14:39 -08:00
for (int j = 0; j < Task->ActivityCount; j++) {
2013-02-16 16:14:39 -08:00
// We are not interested in completed or hidden activities
if (cur_task->Activity[j].State != ActivityActive)
continue;
if (Task->Activity[j].Type != ActivitySpeakWith)
continue;
2013-02-16 16:14:39 -08:00
// Is there a zone restriction on the activity ?
if (!Task->Activity[j].CheckZone(zone->GetZoneID()))
2013-02-16 16:14:39 -08:00
continue;
// Is the activity to speak with this type of NPC ?
if (Task->Activity[j].GoalMethod == METHODQUEST && Task->Activity[j].GoalID == NPCTypeID)
return j;
2013-02-16 16:14:39 -08:00
}
return 0;
}
return 0;
}
2018-07-09 14:01:52 -04:00
2013-02-16 16:14:39 -08:00
void ClientTaskState::UpdateTasksForItem(Client *c, ActivityType Type, int ItemID, int Count) {
// This method updates the client's task activities of the specified type which relate
// to the specified item.
//
// Type should be one of ActivityLoot, ActivityTradeSkill, ActivityFish or ActivityForage
// If the client has no tasks, there is nothing further to check.
2013-05-06 13:07:41 -04:00
Log(Logs::General, Logs::Tasks, "[UPDATE] ClientTaskState::UpdateTasksForItem(%d,%d)", Type, ItemID);
2013-02-16 16:14:39 -08:00
if (!taskmanager || (ActiveTaskCount == 0 && ActiveTask.TaskID == TASKSLOTEMPTY)) // could be better ...
return;
2013-02-16 16:14:39 -08:00
// loop over the union of tasks and quests
for (int i = 0; i < MAXACTIVEQUESTS + 1; i++) {
auto cur_task = &ActiveTasks[i];
if (cur_task->TaskID == TASKSLOTEMPTY)
continue;
2013-02-16 16:14:39 -08:00
// Check if there are any active loot activities for this task
TaskInformation* Task = taskmanager->Tasks[cur_task->TaskID];
2013-02-16 16:14:39 -08:00
if (Task == nullptr)
return;
2013-02-16 16:14:39 -08:00
for (int j = 0; j < Task->ActivityCount; j++) {
2013-02-16 16:14:39 -08:00
// We are not interested in completed or hidden activities
if (cur_task->Activity[j].State != ActivityActive)
continue;
2013-02-16 16:14:39 -08:00
// We are only interested in the ActivityType we were called with
if (Task->Activity[j].Type != (int)Type)
continue;
2013-02-16 16:14:39 -08:00
// Is there a zone restriction on the activity ?
if (!Task->Activity[j].CheckZone(zone->GetZoneID())) {
Log(Logs::General, Logs::Tasks, "[UPDATE] Char: %s Activity type %i for Item %i failed zone check",
2013-05-09 10:44:08 -04:00
c->GetName(), Type, ItemID);
2013-02-16 16:14:39 -08:00
continue;
}
// Is the activity related to this item ?
//
switch(Task->Activity[j].GoalMethod) {
case METHODSINGLEID:
if(Task->Activity[j].GoalID != ItemID) continue;
break;
case METHODLIST:
if(!taskmanager->GoalListManager.IsInList(Task->Activity[j].GoalID, ItemID)) continue;
break;
default:
// If METHODQUEST, don't update the activity here
continue;
}
// We found an active task related to this item, so increment the done count
Log(Logs::General, Logs::Tasks, "[UPDATE] Calling increment done count ForItem");
2018-07-09 14:01:52 -04:00
IncrementDoneCount(c, Task, cur_task->slot, j, Count);
2013-02-16 16:14:39 -08:00
}
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
return;
}
void ClientTaskState::UpdateTasksOnExplore(Client *c, int ExploreID)
{
2013-02-16 16:14:39 -08:00
// If the client has no tasks, there is nothing further to check.
2013-05-06 13:07:41 -04:00
Log(Logs::General, Logs::Tasks, "[UPDATE] ClientTaskState::UpdateTasksOnExplore(%i)", ExploreID);
if (!taskmanager || (ActiveTaskCount == 0 && ActiveTask.TaskID == TASKSLOTEMPTY)) // could be better ...
return;
2013-02-16 16:14:39 -08:00
// loop over the union of tasks and quests
for (int i = 0; i < MAXACTIVEQUESTS + 1; i++) {
auto cur_task = &ActiveTasks[i];
if (cur_task->TaskID == TASKSLOTEMPTY)
continue;
2013-02-16 16:14:39 -08:00
// Check if there are any active explore activities for this task
TaskInformation *Task = taskmanager->Tasks[cur_task->TaskID];
2013-02-16 16:14:39 -08:00
if (Task == nullptr)
return;
2013-02-16 16:14:39 -08:00
for (int j = 0; j < Task->ActivityCount; j++) {
2013-02-16 16:14:39 -08:00
// We are not interested in completed or hidden activities
if (cur_task->Activity[j].State != ActivityActive)
continue;
2013-02-16 16:14:39 -08:00
// We are only interested in explore activities
if (Task->Activity[j].Type != ActivityExplore)
continue;
if (!Task->Activity[j].CheckZone(zone->GetZoneID())) {
Log(Logs::General, Logs::Tasks,
"[UPDATE] Char: %s Explore exploreid %i failed zone check", c->GetName(),
ExploreID);
2013-02-16 16:14:39 -08:00
continue;
}
// Is the activity to explore this area id ?
switch (Task->Activity[j].GoalMethod) {
2013-02-16 16:14:39 -08:00
case METHODSINGLEID:
if (Task->Activity[j].GoalID != ExploreID)
continue;
break;
2013-02-16 16:14:39 -08:00
case METHODLIST:
if (!taskmanager->GoalListManager.IsInList(Task->Activity[j].GoalID, ExploreID))
2013-02-16 16:14:39 -08:00
continue;
break;
default:
// If METHODQUEST, don't update the activity here
continue;
2013-02-16 16:14:39 -08:00
}
// We found an active task to explore this area, so set done count to goal count
// (Only a goal count of 1 makes sense for explore activities?)
Log(Logs::General, Logs::Tasks, "[UPDATE] Increment on explore");
2018-07-09 14:01:52 -04:00
IncrementDoneCount(c, Task, cur_task->slot, j,
Task->Activity[j].GoalCount - cur_task->Activity[j].DoneCount);
2013-02-16 16:14:39 -08:00
}
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
return;
}
bool ClientTaskState::UpdateTasksOnDeliver(Client *c, std::list<EQ::ItemInstance *> &Items, int Cash, int NPCTypeID)
{
2013-02-16 16:14:39 -08:00
bool Ret = false;
Log(Logs::General, Logs::Tasks, "[UPDATE] ClientTaskState::UpdateTasksForOnDeliver(%d)", NPCTypeID);
2013-02-16 16:14:39 -08:00
if (!taskmanager || (ActiveTaskCount == 0 && ActiveTask.TaskID == TASKSLOTEMPTY)) // could be better ...
return false;
2013-02-16 16:14:39 -08:00
// loop over the union of tasks and quests
for (int i = 0; i < MAXACTIVEQUESTS + 1; i++) {
auto cur_task = &ActiveTasks[i];
if (cur_task->TaskID == TASKSLOTEMPTY)
continue;
2013-02-16 16:14:39 -08:00
// Check if there are any active deliver activities for this task
TaskInformation *Task = taskmanager->Tasks[cur_task->TaskID];
2013-02-16 16:14:39 -08:00
if (Task == nullptr)
return false;
2013-02-16 16:14:39 -08:00
for (int j = 0; j < Task->ActivityCount; j++) {
2013-02-16 16:14:39 -08:00
// We are not interested in completed or hidden activities
if (cur_task->Activity[j].State != ActivityActive)
continue;
2013-02-16 16:14:39 -08:00
// We are only interested in Deliver activities
if (Task->Activity[j].Type != ActivityDeliver && Task->Activity[j].Type != ActivityGiveCash)
continue;
2013-02-16 16:14:39 -08:00
// Is there a zone restriction on the activity ?
if (!Task->Activity[j].CheckZone(zone->GetZoneID())) {
Log(Logs::General, Logs::Tasks,
"[UPDATE] Char: %s Deliver activity failed zone check (current zone %i, need zone "
"%s",
c->GetName(), zone->GetZoneID(), Task->Activity[j].zones.c_str());
2013-02-16 16:14:39 -08:00
continue;
}
// Is the activity to deliver to this NPCTypeID ?
if (Task->Activity[j].DeliverToNPC != NPCTypeID)
continue;
2013-02-16 16:14:39 -08:00
// Is the activity related to these items ?
//
if ((Task->Activity[j].Type == ActivityGiveCash) && Cash) {
Log(Logs::General, Logs::Tasks, "[UPDATE] Increment on GiveCash");
2013-02-16 16:14:39 -08:00
IncrementDoneCount(c, Task, i, j, Cash);
Ret = true;
} else {
for (auto &k : Items) {
switch (Task->Activity[j].GoalMethod) {
2013-02-16 16:14:39 -08:00
case METHODSINGLEID:
if (Task->Activity[j].GoalID != k->GetID())
continue;
break;
2013-05-06 13:07:41 -04:00
case METHODLIST:
if (!taskmanager->GoalListManager.IsInList(Task->Activity[j].GoalID,
k->GetID()))
2013-02-16 16:14:39 -08:00
continue;
break;
default:
// If METHODQUEST, don't update the activity here
continue;
2013-02-16 16:14:39 -08:00
}
// We found an active task related to this item, so increment the done count
Log(Logs::General, Logs::Tasks, "[UPDATE] Increment on GiveItem");
2018-07-09 14:01:52 -04:00
IncrementDoneCount(c, Task, cur_task->slot, j, k->GetCharges() <= 0 ? 1 : k->GetCharges());
2013-02-16 16:14:39 -08:00
Ret = true;
}
}
}
2013-05-06 13:07:41 -04:00
}
2013-02-16 16:14:39 -08:00
return Ret;
}
void ClientTaskState::UpdateTasksOnTouch(Client *c, int ZoneID)
{
2013-02-16 16:14:39 -08:00
// If the client has no tasks, there is nothing further to check.
2013-05-06 13:07:41 -04:00
Log(Logs::General, Logs::Tasks, "[UPDATE] ClientTaskState::UpdateTasksOnTouch(%i)", ZoneID);
if (!taskmanager || (ActiveTaskCount == 0 && ActiveTask.TaskID == TASKSLOTEMPTY)) // could be better ...
return;
2013-02-16 16:14:39 -08:00
// loop over the union of tasks and quests
for (int i = 0; i < MAXACTIVEQUESTS + 1; i++) {
auto cur_task = &ActiveTasks[i];
if (cur_task->TaskID == TASKSLOTEMPTY)
continue;
2013-02-16 16:14:39 -08:00
// Check if there are any active explore activities for this task
TaskInformation *Task = taskmanager->Tasks[cur_task->TaskID];
2013-02-16 16:14:39 -08:00
if (Task == nullptr)
return;
2013-02-16 16:14:39 -08:00
for (int j = 0; j < Task->ActivityCount; j++) {
2013-02-16 16:14:39 -08:00
// We are not interested in completed or hidden activities
if (cur_task->Activity[j].State != ActivityActive)
continue;
2013-02-16 16:14:39 -08:00
// We are only interested in touch activities
if (Task->Activity[j].Type != ActivityTouch)
continue;
if (Task->Activity[j].GoalMethod != METHODSINGLEID)
continue;
if (!Task->Activity[j].CheckZone(ZoneID)) {
Log(Logs::General, Logs::Tasks, "[UPDATE] Char: %s Touch activity failed zone check",
c->GetName());
2013-02-16 16:14:39 -08:00
continue;
}
// We found an active task to zone into this zone, so set done count to goal count
// (Only a goal count of 1 makes sense for touch activities?)
Log(Logs::General, Logs::Tasks, "[UPDATE] Increment on Touch");
2018-07-09 14:01:52 -04:00
IncrementDoneCount(c, Task, cur_task->slot, j,
Task->Activity[j].GoalCount - cur_task->Activity[j].DoneCount);
2013-02-16 16:14:39 -08:00
}
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
return;
}
void ClientTaskState::IncrementDoneCount(Client *c, TaskInformation *Task, int TaskIndex, int ActivityID, int Count,
bool ignore_quest_update)
{
Log(Logs::General, Logs::Tasks, "[UPDATE] IncrementDoneCount");
2013-05-06 13:07:41 -04:00
auto info = GetClientTaskInfo(Task->type, TaskIndex);
if (info == nullptr)
return;
info->Activity[ActivityID].DoneCount += Count;
2013-02-16 16:14:39 -08:00
if(info->Activity[ActivityID].DoneCount > Task->Activity[ActivityID].GoalCount)
info->Activity[ActivityID].DoneCount = Task->Activity[ActivityID].GoalCount;
2013-02-16 16:14:39 -08:00
if (!ignore_quest_update){
char buf[24];
snprintf(buf, 23, "%d %d %d", info->Activity[ActivityID].DoneCount, info->Activity[ActivityID].ActivityID, info->TaskID);
buf[23] = '\0';
parse->EventPlayer(EVENT_TASK_UPDATE, c, buf, 0);
}
2013-02-16 16:14:39 -08:00
info->Activity[ActivityID].Updated=true;
2013-02-16 16:14:39 -08:00
// Have we reached the goal count for this activity ?
if(info->Activity[ActivityID].DoneCount >= Task->Activity[ActivityID].GoalCount) {
Log(Logs::General, Logs::Tasks, "[UPDATE] Done (%i) = Goal (%i) for Activity %i",
info->Activity[ActivityID].DoneCount,
2013-05-09 10:44:08 -04:00
Task->Activity[ActivityID].GoalCount,
ActivityID);
2013-02-16 16:14:39 -08:00
// Flag the activity as complete
info->Activity[ActivityID].State = ActivityCompleted;
2013-02-16 16:14:39 -08:00
// Unlock subsequent activities for this task
bool TaskComplete = UnlockActivities(c->CharacterID(), *info);
Log(Logs::General, Logs::Tasks, "[UPDATE] TaskCompleted is %i", TaskComplete);
2013-02-16 16:14:39 -08:00
// and by the 'Task Stage Completed' message
c->SendTaskActivityComplete(info->TaskID, ActivityID, TaskIndex, Task->type);
2013-02-16 16:14:39 -08:00
// Send the updated task/activity list to the client
2018-07-08 00:29:46 -04:00
taskmanager->SendSingleActiveTaskToClient(c, *info, TaskComplete, false);
2013-02-16 16:14:39 -08:00
// Inform the client the task has been updated, both by a chat message
c->Message(Chat::White, "Your task '%s' has been updated.", Task->Title.c_str());
Changed zone process window title format, example: 'crushbone :: clients: 6 inst_id: 1 inst_ver: 0 :: port: 7015' Most of the following changes are QueryServ related, fully implemented its original functionality to be able to offload intensive or metric based logging to a remote server process that could exist on another server entirely Implemented Player Event Logging Types (Go to table `qs_player_events`): 1 = Player_Log_Quest, 2 = Player_Log_Zoning, 3 = Player_Log_Deaths, 4 = Player_Log_Connect_State, 5 = Player_Log_Levels, 6 = Player_Log_Keyring_Addition, 7 = Player_Log_QGlobal_Update, 8 = Player_Log_Task_Updates, 9 = Player_Log_AA_Purchases, 10 = Player_Log_Trade_Skill_Events, 11 = Player_Log_Issued_Commands, 12 = Player_Log_Money_Transactions, 13 = Player_Log_Alternate_Currency_Transactions, - All QueryServ logging will be implemented with a front end in EoC 2.0 very soon Changed all QS Error related logging to 'QUERYSERV__ERROR' (Natedog) (Crash Fix) Legacy MySQL bug revert for loading AA's COALESCE( from COALESCE ( Implemented Perl Quest objects (LUA still needed to be exported): - quest::qs_send_query("MySQL query") - Will send a raw query to the QueryServ process, useful for custom logging - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` Added MySQL Tables: - `qs_player_aa_rate_hourly` - `qs_player_events` - Source table structures from: - utils\sql\git\queryserv\required\08_23_2014_player_events_and_player_aa_rate_hourly To get the complete QueryServ schema, source from here: - utils\sql\git\queryserv\required\Complete_QueryServ_Table_Structures.sql Added rules for each logging type, source rules here with them enabled by default: - utils\sql\git\queryserv\required\Complete_QueryServ_Rules_Enabled.sql Spawn related logging cleanup General code cleanup Added queryserv.cpp and queryserv.h with QueryServ class
2014-08-23 23:59:20 -05:00
if(Task->Activity[ActivityID].GoalMethod != METHODQUEST) {
if (!ignore_quest_update){
char buf[24];
snprintf(buf, 23, "%d %d", info->TaskID, info->Activity[ActivityID].ActivityID);
buf[23] = '\0';
parse->EventPlayer(EVENT_TASK_STAGE_COMPLETE, c, buf, 0);
}
Changed zone process window title format, example: 'crushbone :: clients: 6 inst_id: 1 inst_ver: 0 :: port: 7015' Most of the following changes are QueryServ related, fully implemented its original functionality to be able to offload intensive or metric based logging to a remote server process that could exist on another server entirely Implemented Player Event Logging Types (Go to table `qs_player_events`): 1 = Player_Log_Quest, 2 = Player_Log_Zoning, 3 = Player_Log_Deaths, 4 = Player_Log_Connect_State, 5 = Player_Log_Levels, 6 = Player_Log_Keyring_Addition, 7 = Player_Log_QGlobal_Update, 8 = Player_Log_Task_Updates, 9 = Player_Log_AA_Purchases, 10 = Player_Log_Trade_Skill_Events, 11 = Player_Log_Issued_Commands, 12 = Player_Log_Money_Transactions, 13 = Player_Log_Alternate_Currency_Transactions, - All QueryServ logging will be implemented with a front end in EoC 2.0 very soon Changed all QS Error related logging to 'QUERYSERV__ERROR' (Natedog) (Crash Fix) Legacy MySQL bug revert for loading AA's COALESCE( from COALESCE ( Implemented Perl Quest objects (LUA still needed to be exported): - quest::qs_send_query("MySQL query") - Will send a raw query to the QueryServ process, useful for custom logging - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` Added MySQL Tables: - `qs_player_aa_rate_hourly` - `qs_player_events` - Source table structures from: - utils\sql\git\queryserv\required\08_23_2014_player_events_and_player_aa_rate_hourly To get the complete QueryServ schema, source from here: - utils\sql\git\queryserv\required\Complete_QueryServ_Table_Structures.sql Added rules for each logging type, source rules here with them enabled by default: - utils\sql\git\queryserv\required\Complete_QueryServ_Rules_Enabled.sql Spawn related logging cleanup General code cleanup Added queryserv.cpp and queryserv.h with QueryServ class
2014-08-23 23:59:20 -05:00
/* QS: PlayerLogTaskUpdates :: Update */
if (RuleB(QueryServ, PlayerLogTaskUpdates)){
std::string event_desc = StringFormat("Task Stage Complete :: taskid:%i activityid:%i donecount:%i in zoneid:%i instid:%i", info->TaskID, info->Activity[ActivityID].ActivityID, info->Activity[ActivityID].DoneCount, c->GetZoneID(), c->GetInstanceID());
Changed zone process window title format, example: 'crushbone :: clients: 6 inst_id: 1 inst_ver: 0 :: port: 7015' Most of the following changes are QueryServ related, fully implemented its original functionality to be able to offload intensive or metric based logging to a remote server process that could exist on another server entirely Implemented Player Event Logging Types (Go to table `qs_player_events`): 1 = Player_Log_Quest, 2 = Player_Log_Zoning, 3 = Player_Log_Deaths, 4 = Player_Log_Connect_State, 5 = Player_Log_Levels, 6 = Player_Log_Keyring_Addition, 7 = Player_Log_QGlobal_Update, 8 = Player_Log_Task_Updates, 9 = Player_Log_AA_Purchases, 10 = Player_Log_Trade_Skill_Events, 11 = Player_Log_Issued_Commands, 12 = Player_Log_Money_Transactions, 13 = Player_Log_Alternate_Currency_Transactions, - All QueryServ logging will be implemented with a front end in EoC 2.0 very soon Changed all QS Error related logging to 'QUERYSERV__ERROR' (Natedog) (Crash Fix) Legacy MySQL bug revert for loading AA's COALESCE( from COALESCE ( Implemented Perl Quest objects (LUA still needed to be exported): - quest::qs_send_query("MySQL query") - Will send a raw query to the QueryServ process, useful for custom logging - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` Added MySQL Tables: - `qs_player_aa_rate_hourly` - `qs_player_events` - Source table structures from: - utils\sql\git\queryserv\required\08_23_2014_player_events_and_player_aa_rate_hourly To get the complete QueryServ schema, source from here: - utils\sql\git\queryserv\required\Complete_QueryServ_Table_Structures.sql Added rules for each logging type, source rules here with them enabled by default: - utils\sql\git\queryserv\required\Complete_QueryServ_Rules_Enabled.sql Spawn related logging cleanup General code cleanup Added queryserv.cpp and queryserv.h with QueryServ class
2014-08-23 23:59:20 -05:00
QServ->PlayerLogEvent(Player_Log_Task_Updates, c->CharacterID(), event_desc);
}
}
2013-02-16 16:14:39 -08:00
// If this task is now complete, the Completed tasks will have been
// updated in UnlockActivities. Send the completed task list to the
// client. This is the same sequence the packets are sent on live.
if(TaskComplete) {
char buf[24];
snprintf(buf, 23, "%d %d %d", info->Activity[ActivityID].DoneCount, info->Activity[ActivityID].ActivityID, info->TaskID);
2013-02-16 16:14:39 -08:00
buf[23] = '\0';
parse->EventPlayer(EVENT_TASK_COMPLETE, c, buf, 0);
Changed zone process window title format, example: 'crushbone :: clients: 6 inst_id: 1 inst_ver: 0 :: port: 7015' Most of the following changes are QueryServ related, fully implemented its original functionality to be able to offload intensive or metric based logging to a remote server process that could exist on another server entirely Implemented Player Event Logging Types (Go to table `qs_player_events`): 1 = Player_Log_Quest, 2 = Player_Log_Zoning, 3 = Player_Log_Deaths, 4 = Player_Log_Connect_State, 5 = Player_Log_Levels, 6 = Player_Log_Keyring_Addition, 7 = Player_Log_QGlobal_Update, 8 = Player_Log_Task_Updates, 9 = Player_Log_AA_Purchases, 10 = Player_Log_Trade_Skill_Events, 11 = Player_Log_Issued_Commands, 12 = Player_Log_Money_Transactions, 13 = Player_Log_Alternate_Currency_Transactions, - All QueryServ logging will be implemented with a front end in EoC 2.0 very soon Changed all QS Error related logging to 'QUERYSERV__ERROR' (Natedog) (Crash Fix) Legacy MySQL bug revert for loading AA's COALESCE( from COALESCE ( Implemented Perl Quest objects (LUA still needed to be exported): - quest::qs_send_query("MySQL query") - Will send a raw query to the QueryServ process, useful for custom logging - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` Added MySQL Tables: - `qs_player_aa_rate_hourly` - `qs_player_events` - Source table structures from: - utils\sql\git\queryserv\required\08_23_2014_player_events_and_player_aa_rate_hourly To get the complete QueryServ schema, source from here: - utils\sql\git\queryserv\required\Complete_QueryServ_Table_Structures.sql Added rules for each logging type, source rules here with them enabled by default: - utils\sql\git\queryserv\required\Complete_QueryServ_Rules_Enabled.sql Spawn related logging cleanup General code cleanup Added queryserv.cpp and queryserv.h with QueryServ class
2014-08-23 23:59:20 -05:00
/* QS: PlayerLogTaskUpdates :: Complete */
if (RuleB(QueryServ, PlayerLogTaskUpdates)){
std::string event_desc = StringFormat("Task Complete :: taskid:%i activityid:%i donecount:%i in zoneid:%i instid:%i", info->TaskID, info->Activity[ActivityID].ActivityID, info->Activity[ActivityID].DoneCount, c->GetZoneID(), c->GetInstanceID());
Changed zone process window title format, example: 'crushbone :: clients: 6 inst_id: 1 inst_ver: 0 :: port: 7015' Most of the following changes are QueryServ related, fully implemented its original functionality to be able to offload intensive or metric based logging to a remote server process that could exist on another server entirely Implemented Player Event Logging Types (Go to table `qs_player_events`): 1 = Player_Log_Quest, 2 = Player_Log_Zoning, 3 = Player_Log_Deaths, 4 = Player_Log_Connect_State, 5 = Player_Log_Levels, 6 = Player_Log_Keyring_Addition, 7 = Player_Log_QGlobal_Update, 8 = Player_Log_Task_Updates, 9 = Player_Log_AA_Purchases, 10 = Player_Log_Trade_Skill_Events, 11 = Player_Log_Issued_Commands, 12 = Player_Log_Money_Transactions, 13 = Player_Log_Alternate_Currency_Transactions, - All QueryServ logging will be implemented with a front end in EoC 2.0 very soon Changed all QS Error related logging to 'QUERYSERV__ERROR' (Natedog) (Crash Fix) Legacy MySQL bug revert for loading AA's COALESCE( from COALESCE ( Implemented Perl Quest objects (LUA still needed to be exported): - quest::qs_send_query("MySQL query") - Will send a raw query to the QueryServ process, useful for custom logging - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` Added MySQL Tables: - `qs_player_aa_rate_hourly` - `qs_player_events` - Source table structures from: - utils\sql\git\queryserv\required\08_23_2014_player_events_and_player_aa_rate_hourly To get the complete QueryServ schema, source from here: - utils\sql\git\queryserv\required\Complete_QueryServ_Table_Structures.sql Added rules for each logging type, source rules here with them enabled by default: - utils\sql\git\queryserv\required\Complete_QueryServ_Rules_Enabled.sql Spawn related logging cleanup General code cleanup Added queryserv.cpp and queryserv.h with QueryServ class
2014-08-23 23:59:20 -05:00
QServ->PlayerLogEvent(Player_Log_Task_Updates, c->CharacterID(), event_desc);
}
2013-02-16 16:14:39 -08:00
taskmanager->SendCompletedTasksToClient(c, this);
c->SendTaskActivityComplete(info->TaskID, 0, TaskIndex, Task->type, 0);
2013-02-16 16:14:39 -08:00
taskmanager->SaveClientState(c, this);
//c->SendTaskComplete(TaskIndex);
c->CancelTask(TaskIndex, Task->type);
2013-02-16 16:14:39 -08:00
//if(Task->RewardMethod != METHODQUEST) RewardTask(c, Task);
// If Experience and/or cash rewards are set, reward them from the task even if RewardMethod is METHODQUEST
RewardTask(c, Task);
//RemoveTask(c, TaskIndex);
Changed zone process window title format, example: 'crushbone :: clients: 6 inst_id: 1 inst_ver: 0 :: port: 7015' Most of the following changes are QueryServ related, fully implemented its original functionality to be able to offload intensive or metric based logging to a remote server process that could exist on another server entirely Implemented Player Event Logging Types (Go to table `qs_player_events`): 1 = Player_Log_Quest, 2 = Player_Log_Zoning, 3 = Player_Log_Deaths, 4 = Player_Log_Connect_State, 5 = Player_Log_Levels, 6 = Player_Log_Keyring_Addition, 7 = Player_Log_QGlobal_Update, 8 = Player_Log_Task_Updates, 9 = Player_Log_AA_Purchases, 10 = Player_Log_Trade_Skill_Events, 11 = Player_Log_Issued_Commands, 12 = Player_Log_Money_Transactions, 13 = Player_Log_Alternate_Currency_Transactions, - All QueryServ logging will be implemented with a front end in EoC 2.0 very soon Changed all QS Error related logging to 'QUERYSERV__ERROR' (Natedog) (Crash Fix) Legacy MySQL bug revert for loading AA's COALESCE( from COALESCE ( Implemented Perl Quest objects (LUA still needed to be exported): - quest::qs_send_query("MySQL query") - Will send a raw query to the QueryServ process, useful for custom logging - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` Added MySQL Tables: - `qs_player_aa_rate_hourly` - `qs_player_events` - Source table structures from: - utils\sql\git\queryserv\required\08_23_2014_player_events_and_player_aa_rate_hourly To get the complete QueryServ schema, source from here: - utils\sql\git\queryserv\required\Complete_QueryServ_Table_Structures.sql Added rules for each logging type, source rules here with them enabled by default: - utils\sql\git\queryserv\required\Complete_QueryServ_Rules_Enabled.sql Spawn related logging cleanup General code cleanup Added queryserv.cpp and queryserv.h with QueryServ class
2014-08-23 23:59:20 -05:00
2013-02-16 16:14:39 -08:00
}
2018-07-08 00:29:46 -04:00
} else {
2013-02-16 16:14:39 -08:00
// Send an update packet for this single activity
2018-07-08 00:29:46 -04:00
taskmanager->SendTaskActivityLong(c, info->TaskID, ActivityID, TaskIndex,
Task->Activity[ActivityID].Optional);
}
2013-02-16 16:14:39 -08:00
}
void ClientTaskState::RewardTask(Client *c, TaskInformation *Task) {
if(!Task || !c) return;
const EQ::ItemData* Item;
std::vector<int> RewardList;
2013-02-16 16:14:39 -08:00
switch(Task->RewardMethod) {
case METHODSINGLEID:
{
if(Task->RewardID) {
c->SummonItem(Task->RewardID);
Item = database.GetItem(Task->RewardID);
if(Item)
2019-08-11 00:00:55 -05:00
c->Message(Chat::Yellow, "You receive %s as a reward.", Item->Name);
2013-02-16 16:14:39 -08:00
}
break;
}
2013-05-06 13:07:41 -04:00
case METHODLIST:
2013-02-16 16:14:39 -08:00
{
RewardList = taskmanager->GoalListManager.GetListContents(Task->RewardID);
for(unsigned int i=0; i<RewardList.size(); i++) {
c->SummonItem(RewardList[i]);
Item = database.GetItem(RewardList[i]);
if(Item)
2019-08-11 00:00:55 -05:00
c->Message(Chat::Yellow, "You receive %s as a reward.", Item->Name);
2013-02-16 16:14:39 -08:00
}
break;
}
default:
{
// Nothing special done for METHODQUEST
break;
}
}
2018-07-20 14:06:07 -04:00
if (!Task->completion_emote.empty())
2019-08-11 00:00:55 -05:00
c->SendColoredText(Chat::Yellow, Task->completion_emote); // unsure if they use this packet or color, should work
2018-07-20 14:06:07 -04:00
// just use normal NPC faction ID stuff
if (Task->faction_reward)
c->SetFactionLevel(c->CharacterID(), Task->faction_reward, c->GetBaseClass(), c->GetBaseRace(), c->GetDeity());
2013-02-16 16:14:39 -08:00
if(Task->CashReward) {
int Plat, Gold, Silver, Copper;
Copper = Task->CashReward;
c->AddMoneyToPP(Copper, true);
Plat = Copper / 1000;
Copper = Copper - (Plat * 1000);
Gold = Copper / 100;
Copper = Copper - (Gold * 100);
Silver = Copper / 10;
Copper = Copper - (Silver * 10);
std::string CashMessage;
2013-02-16 16:14:39 -08:00
if (Plat>0){
CashMessage = "You receive ";
CashMessage += itoa(Plat);
CashMessage += " platinum";
}
if (Gold>0){
if (CashMessage.length()==0){
CashMessage = "You receive ";
}
else{
CashMessage += ",";
}
CashMessage += itoa(Gold);
CashMessage += " gold";
}
if(Silver>0){
if (CashMessage.length()==0){
CashMessage = "You receive ";
}
else{
CashMessage += ",";
}
CashMessage += itoa(Silver);
CashMessage += " silver";
}
if(Copper>0){
if (CashMessage.length()==0){
CashMessage = "You receive ";
}
else{
CashMessage += ",";
}
CashMessage += itoa(Copper);
CashMessage += " copper";
}
CashMessage += " pieces.";
2019-08-11 00:00:55 -05:00
c->Message(Chat::Yellow,CashMessage.c_str());
2013-02-16 16:14:39 -08:00
}
int32 EXPReward = Task->XPReward;
if(EXPReward > 0) {
c->AddEXP(EXPReward);
}
if(EXPReward < 0) {
uint32 PosReward = EXPReward * -1;
// Minimal Level Based Exp Reward Setting is 101 (1% exp at level 1)
if (PosReward > 100 && PosReward < 25700) {
uint8 MaxLevel = PosReward / 100;
uint8 ExpPercent = PosReward - (MaxLevel * 100);
c->AddLevelBasedExp(ExpPercent, MaxLevel);
}
}
c->SendSound();
}
2018-06-16 22:42:26 -04:00
bool ClientTaskState::IsTaskActive(int TaskID)
{
2018-07-08 00:29:46 -04:00
if (ActiveTask.TaskID == TaskID)
return true;
2018-06-16 22:42:26 -04:00
if (ActiveTaskCount == 0 || TaskID == 0)
return false;
2013-02-16 16:14:39 -08:00
2018-06-16 22:42:26 -04:00
for (int i = 0; i < MAXACTIVEQUESTS; i++) {
if (ActiveQuests[i].TaskID == TaskID)
return true;
2013-02-16 16:14:39 -08:00
}
return false;
}
void ClientTaskState::FailTask(Client *c, int TaskID)
{
Log(Logs::General, Logs::Tasks, "[UPDATE] FailTask %i, ActiveTaskCount is %i", TaskID, ActiveTaskCount);
2013-02-16 16:14:39 -08:00
if (ActiveTask.TaskID == TaskID) {
c->SendTaskFailed(TaskID, 0, TaskType::Task);
// Remove the task from the client
c->CancelTask(0, TaskType::Task);
return;
}
2013-02-16 16:14:39 -08:00
// TODO: shared tasks
if (ActiveTaskCount == 0)
return;
for (int i = 0; i < MAXACTIVEQUESTS; i++) {
if (ActiveQuests[i].TaskID == TaskID) {
c->SendTaskFailed(ActiveQuests[i].TaskID, i, TaskType::Quest);
2013-02-16 16:14:39 -08:00
// Remove the task from the client
c->CancelTask(i, TaskType::Quest);
2013-02-16 16:14:39 -08:00
return;
}
}
}
2018-07-08 00:29:46 -04:00
// TODO: Shared tasks
bool ClientTaskState::IsTaskActivityActive(int TaskID, int ActivityID)
{
2013-02-16 16:14:39 -08:00
Log(Logs::General, Logs::Tasks, "[UPDATE] ClientTaskState IsTaskActivityActive(%i, %i).", TaskID, ActivityID);
2013-02-16 16:14:39 -08:00
// Quick sanity check
2018-07-08 00:29:46 -04:00
if (ActivityID < 0)
return false;
if (ActiveTaskCount == 0 && ActiveTask.TaskID == TASKSLOTEMPTY)
return false;
2013-02-16 16:14:39 -08:00
int ActiveTaskIndex = -1;
2018-07-08 00:29:46 -04:00
auto type = TaskType::Task;
2013-02-16 16:14:39 -08:00
2018-07-08 00:29:46 -04:00
if (ActiveTask.TaskID == TaskID)
ActiveTaskIndex = 0;
if (ActiveTaskIndex == -1) {
for (int i = 0; i < MAXACTIVEQUESTS; i++) {
if (ActiveQuests[i].TaskID == TaskID) {
ActiveTaskIndex = i;
type = TaskType::Quest;
break;
}
2013-02-16 16:14:39 -08:00
}
}
// The client does not have this task
2018-07-08 00:29:46 -04:00
if (ActiveTaskIndex == -1)
return false;
2013-02-16 16:14:39 -08:00
2018-07-08 00:29:46 -04:00
auto info = GetClientTaskInfo(type, ActiveTaskIndex);
2013-02-16 16:14:39 -08:00
2018-07-08 00:29:46 -04:00
if (info == nullptr)
return false;
2013-02-16 16:14:39 -08:00
2018-07-08 00:29:46 -04:00
TaskInformation *Task = taskmanager->Tasks[info->TaskID];
2013-02-16 16:14:39 -08:00
2018-07-08 00:29:46 -04:00
// The task is invalid
if (Task == nullptr)
return false;
2013-02-16 16:14:39 -08:00
2018-07-08 00:29:46 -04:00
// The ActivityID is out of range
if (ActivityID >= Task->ActivityCount)
return false;
2013-02-16 16:14:39 -08:00
2018-07-08 00:29:46 -04:00
Log(Logs::General, Logs::Tasks, "[UPDATE] ClientTaskState IsTaskActivityActive(%i, %i). State is %i ", TaskID,
ActivityID, info->Activity[ActivityID].State);
2013-02-16 16:14:39 -08:00
2018-07-08 00:29:46 -04:00
return (info->Activity[ActivityID].State == ActivityActive);
2013-02-16 16:14:39 -08:00
}
void ClientTaskState::UpdateTaskActivity(Client *c, int TaskID, int ActivityID, int Count, bool ignore_quest_update /*= false*/)
{
2013-02-16 16:14:39 -08:00
2018-07-08 00:29:46 -04:00
Log(Logs::General, Logs::Tasks, "[UPDATE] ClientTaskState UpdateTaskActivity(%i, %i, %i).", TaskID, ActivityID,
Count);
2013-02-16 16:14:39 -08:00
// Quick sanity check
2018-07-08 00:29:46 -04:00
if (ActivityID < 0 || (ActiveTaskCount == 0 && ActiveTask.TaskID == TASKSLOTEMPTY))
return;
2013-02-16 16:14:39 -08:00
int ActiveTaskIndex = -1;
2018-07-08 00:29:46 -04:00
auto type = TaskType::Task;
2013-02-16 16:14:39 -08:00
2018-07-08 00:29:46 -04:00
if (ActiveTask.TaskID == TaskID)
ActiveTaskIndex = 0;
if (ActiveTaskIndex == -1) {
for (int i = 0; i < MAXACTIVEQUESTS; i++) {
if (ActiveQuests[i].TaskID == TaskID) {
ActiveTaskIndex = i;
type = TaskType::Quest;
break;
}
2013-02-16 16:14:39 -08:00
}
}
// The client does not have this task
2018-07-08 00:29:46 -04:00
if (ActiveTaskIndex == -1)
return;
auto info = GetClientTaskInfo(type, ActiveTaskIndex);
if (info == nullptr)
return;
2013-02-16 16:14:39 -08:00
2018-07-08 00:29:46 -04:00
TaskInformation *Task = taskmanager->Tasks[info->TaskID];
2013-02-16 16:14:39 -08:00
// The task is invalid
2018-07-08 00:29:46 -04:00
if (Task == nullptr)
return;
2013-02-16 16:14:39 -08:00
// The ActivityID is out of range
2018-07-08 00:29:46 -04:00
if (ActivityID >= Task->ActivityCount)
return;
2013-02-16 16:14:39 -08:00
// The Activity is not currently active
2018-07-08 00:29:46 -04:00
if (info->Activity[ActivityID].State != ActivityActive)
return;
Log(Logs::General, Logs::Tasks, "[UPDATE] Increment done count on UpdateTaskActivity");
IncrementDoneCount(c, Task, ActiveTaskIndex, ActivityID, Count, ignore_quest_update);
2013-02-16 16:14:39 -08:00
}
2018-07-08 00:29:46 -04:00
void ClientTaskState::ResetTaskActivity(Client *c, int TaskID, int ActivityID)
{
Log(Logs::General, Logs::Tasks, "[UPDATE] ClientTaskState UpdateTaskActivity(%i, %i, 0).", TaskID, ActivityID);
2013-02-16 16:14:39 -08:00
// Quick sanity check
2018-07-08 00:29:46 -04:00
if (ActivityID < 0 || (ActiveTaskCount == 0 && ActiveTask.TaskID == TASKSLOTEMPTY))
return;
2013-02-16 16:14:39 -08:00
int ActiveTaskIndex = -1;
2018-07-08 00:29:46 -04:00
auto type = TaskType::Task;
2013-02-16 16:14:39 -08:00
2018-07-08 00:29:46 -04:00
if (ActiveTask.TaskID == TaskID)
ActiveTaskIndex = 0;
if (ActiveTaskIndex == -1) {
for (int i = 0; i < MAXACTIVEQUESTS; i++) {
if (ActiveQuests[i].TaskID == TaskID) {
ActiveTaskIndex = i;
type = TaskType::Quest;
break;
}
2013-02-16 16:14:39 -08:00
}
}
// The client does not have this task
2018-07-08 00:29:46 -04:00
if (ActiveTaskIndex == -1)
return;
2013-02-16 16:14:39 -08:00
2018-07-08 00:29:46 -04:00
auto info = GetClientTaskInfo(type, ActiveTaskIndex);
if (info == nullptr)
return;
TaskInformation *Task = taskmanager->Tasks[info->TaskID];
2013-02-16 16:14:39 -08:00
// The task is invalid
2018-07-08 00:29:46 -04:00
if (Task == nullptr)
return;
2013-02-16 16:14:39 -08:00
// The ActivityID is out of range
2018-07-08 00:29:46 -04:00
if (ActivityID >= Task->ActivityCount)
return;
2013-02-16 16:14:39 -08:00
// The Activity is not currently active
2018-07-08 00:29:46 -04:00
if (info->Activity[ActivityID].State != ActivityActive)
return;
2013-02-16 16:14:39 -08:00
Log(Logs::General, Logs::Tasks, "[UPDATE] ResetTaskActivityCount");
2013-02-16 16:14:39 -08:00
2018-07-08 00:29:46 -04:00
info->Activity[ActivityID].DoneCount = 0;
2013-02-16 16:14:39 -08:00
2018-07-08 00:29:46 -04:00
info->Activity[ActivityID].Updated = true;
2013-02-16 16:14:39 -08:00
// Send an update packet for this single activity
2018-07-08 00:29:46 -04:00
taskmanager->SendTaskActivityLong(c, info->TaskID, ActivityID, ActiveTaskIndex,
Task->Activity[ActivityID].Optional);
2013-02-16 16:14:39 -08:00
}
void ClientTaskState::ShowClientTasks(Client *c)
{
c->Message(Chat::White, "Task Information:");
if (ActiveTask.TaskID != TASKSLOTEMPTY) {
c->Message(Chat::White, "Task: %i %s", ActiveTask.TaskID, taskmanager->Tasks[ActiveTask.TaskID]->Title.c_str());
c->Message(Chat::White, " Description: [%s]\n", taskmanager->Tasks[ActiveTask.TaskID]->Description.c_str());
for (int j = 0; j < taskmanager->GetActivityCount(ActiveTask.TaskID); j++) {
c->Message(Chat::White, " Activity: %2d, DoneCount: %2d, Status: %d (0=Hidden, 1=Active, 2=Complete)",
ActiveTask.Activity[j].ActivityID, ActiveTask.Activity[j].DoneCount,
ActiveTask.Activity[j].State);
}
}
for (int i = 0; i < MAXACTIVEQUESTS; i++) {
if (ActiveQuests[i].TaskID == TASKSLOTEMPTY)
2013-02-16 16:14:39 -08:00
continue;
c->Message(Chat::White, "Quest: %i %s", ActiveQuests[i].TaskID,
taskmanager->Tasks[ActiveQuests[i].TaskID]->Title.c_str());
c->Message(Chat::White, " Description: [%s]\n", taskmanager->Tasks[ActiveQuests[i].TaskID]->Description.c_str());
for (int j = 0; j < taskmanager->GetActivityCount(ActiveQuests[i].TaskID); j++) {
c->Message(Chat::White, " Activity: %2d, DoneCount: %2d, Status: %d (0=Hidden, 1=Active, 2=Complete)",
ActiveQuests[i].Activity[j].ActivityID, ActiveQuests[i].Activity[j].DoneCount,
ActiveQuests[i].Activity[j].State);
2013-02-16 16:14:39 -08:00
}
}
}
2018-07-08 16:36:00 -04:00
// TODO: Shared Task
int ClientTaskState::TaskTimeLeft(int TaskID)
{
if (ActiveTask.TaskID == TaskID) {
int Now = time(nullptr);
2013-02-16 16:14:39 -08:00
2018-07-08 16:36:00 -04:00
TaskInformation *Task = taskmanager->Tasks[TaskID];
2013-02-16 16:14:39 -08:00
2018-07-08 16:36:00 -04:00
if (Task == nullptr)
return -1;
if (!Task->Duration)
return -1;
int TimeLeft = (ActiveTask.AcceptedTime + Task->Duration - Now);
return (TimeLeft > 0 ? TimeLeft : 0);
}
if (ActiveTaskCount == 0)
return -1;
2013-02-16 16:14:39 -08:00
2018-07-08 16:36:00 -04:00
for (int i = 0; i < MAXACTIVEQUESTS; i++) {
if (ActiveQuests[i].TaskID != TaskID)
continue;
2013-02-16 16:14:39 -08:00
2013-05-04 18:06:58 -07:00
int Now = time(nullptr);
2013-02-16 16:14:39 -08:00
2018-07-08 16:36:00 -04:00
TaskInformation *Task = taskmanager->Tasks[ActiveQuests[i].TaskID];
2013-02-16 16:14:39 -08:00
2018-07-08 16:36:00 -04:00
if (Task == nullptr)
return -1;
2013-02-16 16:14:39 -08:00
2018-07-08 16:36:00 -04:00
if (!Task->Duration)
return -1;
2013-02-16 16:14:39 -08:00
int TimeLeft = (ActiveQuests[i].AcceptedTime + Task->Duration - Now);
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
// If Timeleft is negative, return 0, else return the number of seconds left
2013-05-06 13:07:41 -04:00
2018-07-08 16:36:00 -04:00
return (TimeLeft > 0 ? TimeLeft : 0);
2013-02-16 16:14:39 -08:00
}
return -1;
}
int ClientTaskState::IsTaskCompleted(int TaskID) {
// Returns: -1 if RecordCompletedTasks is not true
2013-05-09 10:44:08 -04:00
// +1 if the task has been completed
// 0 if the task has not been completed
2013-02-16 16:14:39 -08:00
if(!(RuleB(TaskSystem, RecordCompletedTasks))) return -1;
for(unsigned int i=0; i<CompletedTasks.size(); i++) {
Log(Logs::General, Logs::Tasks, "[UPDATE] Comparing completed task %i with %i", CompletedTasks[i].TaskID, TaskID);
2013-02-16 16:14:39 -08:00
if(CompletedTasks[i].TaskID == TaskID) return 1;
}
return 0;
}
2013-05-09 10:44:08 -04:00
bool TaskManager::IsTaskRepeatable(int TaskID) {
2013-02-16 16:14:39 -08:00
if((TaskID <= 0) || (TaskID >= MAXTASKS)) return false;
TaskInformation* Task = taskmanager->Tasks[TaskID];
2013-05-04 18:06:58 -07:00
if(Task == nullptr) return false;
2013-02-16 16:14:39 -08:00
return Task->Repeatable;
}
bool ClientTaskState::TaskOutOfTime(TaskType type, int Index)
{
2013-02-16 16:14:39 -08:00
// Returns true if the Task in the specified slot has a time limit that has been exceeded.
auto info = GetClientTaskInfo(type, Index);
2013-02-16 16:14:39 -08:00
if (info == nullptr)
return false;
2013-02-16 16:14:39 -08:00
// make sure the TaskID is at least maybe in our array
if (info->TaskID <= 0 || info->TaskID >= MAXTASKS)
return false;
2013-02-16 16:14:39 -08:00
2013-05-04 18:06:58 -07:00
int Now = time(nullptr);
2013-02-16 16:14:39 -08:00
TaskInformation *Task = taskmanager->Tasks[info->TaskID];
2013-02-16 16:14:39 -08:00
if (Task == nullptr)
return false;
2013-02-16 16:14:39 -08:00
return (Task->Duration && (info->AcceptedTime + Task->Duration <= Now));
2013-02-16 16:14:39 -08:00
}
void ClientTaskState::TaskPeriodicChecks(Client *c)
{
2018-07-08 16:36:00 -04:00
if (ActiveTask.TaskID != TASKSLOTEMPTY) {
if (TaskOutOfTime(TaskType::Task, 0)) {
// Send Red Task Failed Message
c->SendTaskFailed(ActiveTask.TaskID, 0, TaskType::Task);
// Remove the task from the client
c->CancelTask(0, TaskType::Task);
// It is a conscious decision to only fail one task per call to this method,
// otherwise the player will not see all the failed messages where multiple
// tasks fail at the same time.
return;
}
}
// TODO: shared tasks -- although that will probably be manager in world checking and telling zones to fail us
2013-02-16 16:14:39 -08:00
if (ActiveTaskCount == 0)
return;
2013-02-16 16:14:39 -08:00
// Check for tasks that have failed because they have not been completed in the specified time
//
for (int i = 0; i < MAXACTIVEQUESTS; i++) {
if (ActiveQuests[i].TaskID == TASKSLOTEMPTY)
continue;
2013-02-16 16:14:39 -08:00
if (TaskOutOfTime(TaskType::Quest, i)) {
2013-02-16 16:14:39 -08:00
// Send Red Task Failed Message
c->SendTaskFailed(ActiveQuests[i].TaskID, i, TaskType::Quest);
2013-02-16 16:14:39 -08:00
// Remove the task from the client
c->CancelTask(i, TaskType::Quest);
2013-02-16 16:14:39 -08:00
// It is a conscious decision to only fail one task per call to this method,
// otherwise the player will not see all the failed messages where multiple
2013-05-06 13:07:41 -04:00
// tasks fail at the same time.
2013-02-16 16:14:39 -08:00
break;
}
}
// Check for activities that require zoning into a specific zone.
// This is done in this method because it gives an extra few seconds for the client screen to display
// the zone before we send the 'Task Activity Completed' message.
//
if (!CheckedTouchActivities) {
2013-02-16 16:14:39 -08:00
UpdateTasksOnTouch(c, zone->GetZoneID());
CheckedTouchActivities = true;
}
}
#if 0
void Client::SendTaskComplete(int TaskIndex) {
// 0x4c8c
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
TaskComplete_Struct* tcs;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
EQApplicationPacket* outapp = new EQApplicationPacket(OP_TaskComplete, sizeof(TaskComplete_Struct));
tcs = (TaskComplete_Struct*)outapp->pBuffer;
// I have seen unknown0 as non-zero. It always seems to match the value in the first word of the
// Task Activity Complete packet sent immediately prior to it.
2013-05-06 13:07:41 -04:00
//tcs->unknown00 = 0x00000000;
2013-02-16 16:14:39 -08:00
tcs->unknown00 = TaskIndex;
// I have only seen 0x00000002 in the next field. This is a common 'unknown' value in the task packets.
// I suspect this is the type field to indicate this is a quest task, as opposed to other types.
2013-05-06 13:07:41 -04:00
tcs->unknown04 = 0x00000002;
2013-02-16 16:14:39 -08:00
Log.LogDebugType(Logs::Detail, Logs::Tasks, "SendTasksComplete");
2013-02-16 16:14:39 -08:00
DumpPacket(outapp); fflush(stdout);
QueuePacket(outapp);
safe_delete(outapp);
}
#endif
void ClientTaskState::SendTaskHistory(Client *c, int TaskIndex) {
Log(Logs::General, Logs::Tasks, "[UPDATE] Task History Requested for Completed Task Index %i", TaskIndex);
2013-02-16 16:14:39 -08:00
// We only sent the most recent 50 completed tasks, so we need to offset the Index the client sent to us.
int AdjustedTaskIndex = TaskIndex;
if(CompletedTasks.size() > 50)
AdjustedTaskIndex += (CompletedTasks.size() - 50);
if((AdjustedTaskIndex < 0) || (AdjustedTaskIndex >= (int)CompletedTasks.size())) return;
int TaskID = CompletedTasks[AdjustedTaskIndex].TaskID;
if((TaskID < 0) || (TaskID > MAXTASKS)) return;
TaskInformation* Task = taskmanager->Tasks[TaskID];
2013-05-04 18:06:58 -07:00
if(Task == nullptr) return;
2013-02-16 16:14:39 -08:00
TaskHistoryReplyHeader_Struct* ths;
TaskHistoryReplyData1_Struct* thd1;
TaskHistoryReplyData2_Struct* thd2;
char *Ptr;
int CompletedActivityCount = 0;;
int PacketLength = sizeof(TaskHistoryReplyHeader_Struct);
for(int i=0; i<Task->ActivityCount; i++) {
if(CompletedTasks[AdjustedTaskIndex].ActivityDone[i]) {
CompletedActivityCount++;
PacketLength = PacketLength + sizeof(TaskHistoryReplyData1_Struct) +
2018-07-08 16:55:08 -04:00
Task->Activity[i].target_name.size() + 1 +
Task->Activity[i].item_list.size() + 1 +
2013-05-09 10:44:08 -04:00
sizeof(TaskHistoryReplyData2_Struct) +
2018-07-08 16:55:08 -04:00
Task->Activity[i].desc_override.size() + 1;
2013-02-16 16:14:39 -08:00
}
}
2013-05-06 13:07:41 -04:00
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_TaskHistoryReply, PacketLength);
2013-02-16 16:14:39 -08:00
ths = (TaskHistoryReplyHeader_Struct*)outapp->pBuffer;
// We use the TaskIndex the client sent in the request
ths->TaskID = TaskIndex;
ths->ActivityCount = CompletedActivityCount;
Ptr = (char *) ths + sizeof(TaskHistoryReplyHeader_Struct);
for(int i=0; i<Task->ActivityCount; i++) {
if(CompletedTasks[AdjustedTaskIndex].ActivityDone[i]) {
thd1 = (TaskHistoryReplyData1_Struct*)Ptr;
thd1->ActivityType = Task->Activity[i].Type;
Ptr = (char *)thd1 + sizeof(TaskHistoryReplyData1_Struct);
2018-07-08 16:55:08 -04:00
VARSTRUCT_ENCODE_STRING(Ptr, Task->Activity[i].target_name.c_str());
VARSTRUCT_ENCODE_STRING(Ptr, Task->Activity[i].item_list.c_str());
2013-02-16 16:14:39 -08:00
thd2 = (TaskHistoryReplyData2_Struct*)Ptr;
thd2->GoalCount = Task->Activity[i].GoalCount;
thd2->unknown04 = 0xffffffff;
thd2->unknown08 = 0xffffffff;
thd2->ZoneID = Task->Activity[i].ZoneIDs.empty() ? 0 : Task->Activity[i].ZoneIDs.front();
2013-02-16 16:14:39 -08:00
thd2->unknown16 = 0x00000000;
Ptr = (char *)thd2 + sizeof(TaskHistoryReplyData2_Struct);
2018-07-08 16:55:08 -04:00
VARSTRUCT_ENCODE_STRING(Ptr, Task->Activity[i].desc_override.c_str());
2013-02-16 16:14:39 -08:00
}
}
c->QueuePacket(outapp);
safe_delete(outapp);
}
void Client::SendTaskActivityComplete(int TaskID, int ActivityID, int TaskIndex, TaskType type, int TaskIncomplete)
{
2013-02-16 16:14:39 -08:00
// 0x54eb
TaskActivityComplete_Struct* tac;
2013-05-06 13:07:41 -04:00
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_TaskActivityComplete, sizeof(TaskActivityComplete_Struct));
2013-02-16 16:14:39 -08:00
tac = (TaskActivityComplete_Struct*)outapp->pBuffer;
tac->TaskIndex = TaskIndex;
tac->TaskType = static_cast<uint32>(type);
tac->TaskID = TaskID;
2013-02-16 16:14:39 -08:00
tac->ActivityID = ActivityID;
tac->task_completed = 0x00000001;
tac->stage_complete = TaskIncomplete;
2013-02-16 16:14:39 -08:00
QueuePacket(outapp);
safe_delete(outapp);
}
void Client::SendTaskFailed(int TaskID, int TaskIndex, TaskType type)
{
2013-02-16 16:14:39 -08:00
// 0x54eb
char buf[24];
snprintf(buf, 23, "%d", TaskID);
buf[23] = '\0';
parse->EventPlayer(EVENT_TASK_FAIL, this, buf, 0);
TaskActivityComplete_Struct* tac;
2013-05-06 13:07:41 -04:00
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_TaskActivityComplete, sizeof(TaskActivityComplete_Struct));
2013-02-16 16:14:39 -08:00
tac = (TaskActivityComplete_Struct*)outapp->pBuffer;
tac->TaskIndex = TaskIndex;
tac->TaskType = static_cast<uint32>(type);
tac->TaskID = TaskID;
2013-02-16 16:14:39 -08:00
tac->ActivityID = 0;
tac->task_completed = 0; //Fail
tac->stage_complete = 0; // 0 for task complete or failed.
2013-02-16 16:14:39 -08:00
Log(Logs::General, Logs::Tasks, "[UPDATE] TaskFailed");
2013-02-16 16:14:39 -08:00
QueuePacket(outapp);
safe_delete(outapp);
}
void TaskManager::SendCompletedTasksToClient(Client *c, ClientTaskState *State) {
int PacketLength = 4;
//vector<CompletedTaskInformation>::const_iterator iterator;
// The client only display the first 50 Completed Tasks send, so send the 50 most recent
int FirstTaskToSend = 0;
int LastTaskToSend = State->CompletedTasks.size();
2013-05-06 13:07:41 -04:00
if(State->CompletedTasks.size() > 50)
2013-02-16 16:14:39 -08:00
FirstTaskToSend = State->CompletedTasks.size() - 50;
Log(Logs::General, Logs::Tasks, "[UPDATE] Completed Task Count: %i, First Task to send is %i, Last is %i",
2013-05-09 10:44:08 -04:00
State->CompletedTasks.size(), FirstTaskToSend, LastTaskToSend);
2013-02-16 16:14:39 -08:00
/*
for(iterator=State->CompletedTasks.begin(); iterator!=State->CompletedTasks.end(); iterator++) {
int TaskID = (*iterator).TaskID;
2013-05-04 18:06:58 -07:00
if(Tasks[TaskID] == nullptr) continue;
2013-02-16 16:14:39 -08:00
PacketLength = PacketLength + 8 + strlen(Tasks[TaskID]->Title) + 1;
}
*/
for(int i = FirstTaskToSend; i<LastTaskToSend; i++) {
int TaskID = State->CompletedTasks[i].TaskID;
2013-05-04 18:06:58 -07:00
if(Tasks[TaskID] == nullptr) continue;
PacketLength = PacketLength + 8 + Tasks[TaskID]->Title.size() + 1;
2013-02-16 16:14:39 -08:00
}
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_CompletedTasks, PacketLength);
2013-02-16 16:14:39 -08:00
char *buf = (char*)outapp->pBuffer;
//*(uint32 *)buf = State->CompletedTasks.size();
*(uint32 *)buf = LastTaskToSend - FirstTaskToSend;
buf = buf + 4;
//for(iterator=State->CompletedTasks.begin(); iterator!=State->CompletedTasks.end(); iterator++) {
// int TaskID = (*iterator).TaskID;
for(int i = FirstTaskToSend; i<LastTaskToSend; i++) {
int TaskID = State->CompletedTasks[i].TaskID;
2013-05-04 18:06:58 -07:00
if(Tasks[TaskID] == nullptr) continue;
2013-02-16 16:14:39 -08:00
*(uint32 *)buf = TaskID;
buf = buf + 4;
sprintf(buf, "%s", Tasks[TaskID]->Title.c_str());
2013-02-16 16:14:39 -08:00
buf = buf + strlen(buf) + 1;
//*(uint32 *)buf = (*iterator).CompletedTime;
*(uint32 *)buf = State->CompletedTasks[i].CompletedTime;
buf = buf + 4;
}
c->QueuePacket(outapp);
safe_delete(outapp);
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
void TaskManager::SendTaskActivityShort(Client *c, int TaskID, int ActivityID, int ClientTaskIndex)
{
// This Activity Packet is sent for activities that have not yet been unlocked and appear as ???
// in the client.
TaskActivityShort_Struct* tass;
if (c->ClientVersionBit() & EQ::versions::maskRoFAndLater)
2013-02-16 16:14:39 -08:00
{
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_TaskActivity, 25);
2013-02-16 16:14:39 -08:00
outapp->WriteUInt32(ClientTaskIndex);
2018-06-24 23:21:35 -04:00
outapp->WriteUInt32(static_cast<uint32>(Tasks[TaskID]->type));
2013-02-16 16:14:39 -08:00
outapp->WriteUInt32(TaskID);
outapp->WriteUInt32(ActivityID);
outapp->WriteUInt32(0);
outapp->WriteUInt32(0xffffffff);
outapp->WriteUInt8(0);
c->FastQueuePacket(&outapp);
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
return;
}
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_TaskActivity, sizeof(TaskActivityShort_Struct));
2013-02-16 16:14:39 -08:00
tass = (TaskActivityShort_Struct*)outapp->pBuffer;
tass->TaskSequenceNumber = ClientTaskIndex;
2018-06-24 23:21:35 -04:00
tass->unknown2 = static_cast<uint32>(Tasks[TaskID]->type);
2013-02-16 16:14:39 -08:00
tass->TaskID = TaskID;
tass->ActivityID = ActivityID;
tass->unknown3 = 0x000000;
tass->ActivityType = 0xffffffff;
tass->unknown4 = 0x00000000;
c->QueuePacket(outapp);
safe_delete(outapp);
}
void TaskManager::SendTaskActivityLong(Client *c, int TaskID, int ActivityID, int ClientTaskIndex, bool Optional, bool TaskComplete) {
if (c->ClientVersion() >= EQ::versions::ClientVersion::RoF)
2013-02-16 16:14:39 -08:00
{
SendTaskActivityNew(c, TaskID, ActivityID, ClientTaskIndex, Optional, TaskComplete);
return;
}
2018-07-04 13:30:24 -04:00
SerializeBuffer buf(100);
2013-02-16 16:14:39 -08:00
2018-07-04 13:30:24 -04:00
buf.WriteUInt32(ClientTaskIndex);
buf.WriteUInt32(static_cast<uint32>(Tasks[TaskID]->type));
buf.WriteUInt32(TaskID);
buf.WriteUInt32(ActivityID);
buf.WriteUInt32(0); // unknown3
2013-02-16 16:14:39 -08:00
// We send our 'internal' types as ActivityCastOn. text3 should be set to the activity description, so it makes
2013-02-16 16:14:39 -08:00
// no difference to the client. All activity updates will be done based on our interal activity types.
2013-05-09 10:44:08 -04:00
if((Tasks[TaskID]->Activity[ActivityID].Type > 0) && Tasks[TaskID]->Activity[ActivityID].Type < 100)
2018-07-04 13:30:24 -04:00
buf.WriteUInt32(Tasks[TaskID]->Activity[ActivityID].Type);
2013-02-16 16:14:39 -08:00
else
2018-07-04 13:30:24 -04:00
buf.WriteUInt32(ActivityCastOn); // w/e!
2013-02-16 16:14:39 -08:00
2018-07-04 14:36:18 -04:00
buf.WriteUInt32(Optional);
2018-07-04 13:30:24 -04:00
buf.WriteUInt32(0); // solo, group, raid
2013-02-16 16:14:39 -08:00
2018-07-04 13:30:24 -04:00
buf.WriteString(Tasks[TaskID]->Activity[ActivityID].target_name); // target name string
buf.WriteString(Tasks[TaskID]->Activity[ActivityID].item_list); // item name list
2013-02-16 16:14:39 -08:00
if(Tasks[TaskID]->Activity[ActivityID].Type != ActivityGiveCash)
2018-07-04 13:30:24 -04:00
buf.WriteUInt32(Tasks[TaskID]->Activity[ActivityID].GoalCount);
2013-02-16 16:14:39 -08:00
else
// For our internal type GiveCash, where the goal count has the amount of cash that must be given,
// we don't want the donecount and goalcount fields cluttered up with potentially large numbers, so we just
// send a goalcount of 1, and a bit further down, a donecount of 1 if the activity is complete, 0 otherwise.
// The text3 field should decribe the exact activity goal, e.g. give 3500gp to Hasten Bootstrutter.
2018-07-04 13:30:24 -04:00
buf.WriteUInt32(1);
2013-02-16 16:14:39 -08:00
2018-07-04 13:30:24 -04:00
buf.WriteUInt32(Tasks[TaskID]->Activity[ActivityID].skill_id);
buf.WriteUInt32(Tasks[TaskID]->Activity[ActivityID].spell_id);
buf.WriteUInt32(Tasks[TaskID]->Activity[ActivityID].ZoneIDs.empty() ? 0 : Tasks[TaskID]->Activity[ActivityID].ZoneIDs.front());
2018-07-04 13:30:24 -04:00
buf.WriteUInt32(0);
2013-02-16 16:14:39 -08:00
2018-07-04 13:30:24 -04:00
buf.WriteString(Tasks[TaskID]->Activity[ActivityID].desc_override);
2013-02-16 16:14:39 -08:00
if(Tasks[TaskID]->Activity[ActivityID].Type != ActivityGiveCash)
buf.WriteUInt32(c->GetTaskActivityDoneCount(Tasks[TaskID]->type, ClientTaskIndex, ActivityID));
2013-02-16 16:14:39 -08:00
else
// For internal activity types, DoneCount is either 1 if the activity is complete, 0 otherwise.
buf.WriteUInt32((c->GetTaskActivityDoneCount(Tasks[TaskID]->type, ClientTaskIndex, ActivityID) >= Tasks[TaskID]->Activity[ActivityID].GoalCount));
2013-02-16 16:14:39 -08:00
2018-07-04 13:30:24 -04:00
buf.WriteUInt32(1); // unknown
2013-02-16 16:14:39 -08:00
2018-07-08 23:03:53 -04:00
auto outapp = new EQApplicationPacket(OP_TaskActivity, buf);
2013-02-16 16:14:39 -08:00
c->QueuePacket(outapp);
safe_delete(outapp);
}
// Used only by RoF+ Clients
void TaskManager::SendTaskActivityNew(Client *c, int TaskID, int ActivityID, int ClientTaskIndex, bool Optional, bool TaskComplete)
{
SerializeBuffer buf(100);
2013-02-16 16:14:39 -08:00
buf.WriteUInt32(ClientTaskIndex); // TaskSequenceNumber
buf.WriteUInt32(static_cast<uint32>(Tasks[TaskID]->type)); // task type
buf.WriteUInt32(TaskID);
buf.WriteUInt32(ActivityID);
buf.WriteUInt32(0); // unknown3
2013-02-16 16:14:39 -08:00
// We send our 'internal' types as ActivityCastOn. text3 should be set to the activity description, so it makes
2013-02-16 16:14:39 -08:00
// no difference to the client. All activity updates will be done based on our interal activity types.
2013-05-09 10:44:08 -04:00
if((Tasks[TaskID]->Activity[ActivityID].Type > 0) && Tasks[TaskID]->Activity[ActivityID].Type < 100)
buf.WriteUInt32(Tasks[TaskID]->Activity[ActivityID].Type);
2013-02-16 16:14:39 -08:00
else
buf.WriteUInt32(ActivityCastOn); // w/e!
2013-02-16 16:14:39 -08:00
buf.WriteUInt8(Optional);
buf.WriteUInt32(0); // solo, group, raid
2013-02-16 16:14:39 -08:00
// One of these unknown fields maybe related to the 'Use On' activity types
buf.WriteString(Tasks[TaskID]->Activity[ActivityID].target_name); // target name string
2013-02-16 16:14:39 -08:00
buf.WriteLengthString(Tasks[TaskID]->Activity[ActivityID].item_list); // item name list
2013-02-16 16:14:39 -08:00
// Goal Count
if(Tasks[TaskID]->Activity[ActivityID].Type != ActivityGiveCash)
buf.WriteUInt32(Tasks[TaskID]->Activity[ActivityID].GoalCount);
2013-02-16 16:14:39 -08:00
else
buf.WriteUInt32(1); // GoalCount
2013-05-06 13:07:41 -04:00
// skill ID list ; separated
buf.WriteLengthString(Tasks[TaskID]->Activity[ActivityID].skill_list);
2013-02-16 16:14:39 -08:00
// spelll ID list ; separated -- unsure wtf we're doing here
buf.WriteLengthString(Tasks[TaskID]->Activity[ActivityID].spell_list);
2013-05-06 13:07:41 -04:00
buf.WriteString(Tasks[TaskID]->Activity[ActivityID].zones);
buf.WriteUInt32(0); // unknown7
2013-02-16 16:14:39 -08:00
buf.WriteString(Tasks[TaskID]->Activity[ActivityID].desc_override); // description override
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
if(Tasks[TaskID]->Activity[ActivityID].Type != ActivityGiveCash)
buf.WriteUInt32(c->GetTaskActivityDoneCount(Tasks[TaskID]->type, ClientTaskIndex, ActivityID)); // DoneCount
2013-02-16 16:14:39 -08:00
else
// For internal activity types, DoneCount is either 1 if the activity is complete, 0 otherwise.
buf.WriteUInt32((c->GetTaskActivityDoneCount(Tasks[TaskID]->type, ClientTaskIndex, ActivityID) >= Tasks[TaskID]->Activity[ActivityID].GoalCount));
2013-02-16 16:14:39 -08:00
buf.WriteUInt8(1); // unknown9
2013-05-06 13:07:41 -04:00
buf.WriteString(Tasks[TaskID]->Activity[ActivityID].zones);
2013-02-16 16:14:39 -08:00
2018-07-08 23:03:53 -04:00
auto outapp = new EQApplicationPacket(OP_TaskActivity, buf);
2013-02-16 16:14:39 -08:00
c->QueuePacket(outapp);
safe_delete(outapp);
}
2018-06-24 23:21:35 -04:00
void TaskManager::SendActiveTasksToClient(Client *c, bool TaskComplete)
{
auto state = c->GetTaskState();
if (!state)
return;
2013-02-16 16:14:39 -08:00
2018-07-08 16:55:08 -04:00
for (int TaskIndex = 0; TaskIndex < MAXACTIVEQUESTS + 1; TaskIndex++) {
int TaskID = state->ActiveTasks[TaskIndex].TaskID;
if ((TaskID == 0) || (Tasks[TaskID] == 0))
continue;
int StartTime = state->ActiveTasks[TaskIndex].AcceptedTime;
2013-05-06 13:07:41 -04:00
2018-07-08 16:55:08 -04:00
SendActiveTaskDescription(c, TaskID, state->ActiveTasks[TaskIndex], StartTime, Tasks[TaskID]->Duration,
false);
Log(Logs::General, Logs::Tasks, "[UPDATE] SendActiveTasksToClient: Task %i, Activities: %i", TaskID,
GetActivityCount(TaskID));
2013-02-16 16:14:39 -08:00
int Sequence = 0;
2018-07-09 14:01:52 -04:00
int fixed_index = Tasks[TaskID]->type == TaskType::Task ? 0 : TaskIndex - 1; // hmmm fuck
2018-07-08 16:55:08 -04:00
for (int Activity = 0; Activity < GetActivityCount(TaskID); Activity++) {
2018-07-09 14:01:52 -04:00
if (c->GetTaskActivityState(Tasks[TaskID]->type, fixed_index, Activity) != ActivityHidden) {
2018-07-08 16:55:08 -04:00
Log(Logs::General, Logs::Tasks, "[UPDATE] Long: %i, %i, %i Complete=%i", TaskID,
2018-07-09 14:01:52 -04:00
Activity, fixed_index, TaskComplete);
2018-07-08 16:55:08 -04:00
if (Activity == GetActivityCount(TaskID) - 1)
2018-07-09 14:01:52 -04:00
SendTaskActivityLong(c, TaskID, Activity, fixed_index,
2018-07-08 16:55:08 -04:00
Tasks[TaskID]->Activity[Activity].Optional, TaskComplete);
2013-02-16 16:14:39 -08:00
else
2018-07-09 14:01:52 -04:00
SendTaskActivityLong(c, TaskID, Activity, fixed_index,
2018-07-08 16:55:08 -04:00
Tasks[TaskID]->Activity[Activity].Optional, 0);
} else {
Log(Logs::General, Logs::Tasks, "[UPDATE] Short: %i, %i, %i", TaskID, Activity,
2018-07-09 14:01:52 -04:00
fixed_index);
SendTaskActivityShort(c, TaskID, Activity, fixed_index);
2013-02-16 16:14:39 -08:00
}
Sequence++;
}
}
}
2018-06-24 23:21:35 -04:00
void TaskManager::SendSingleActiveTaskToClient(Client *c, ClientTaskInformation &task_info, bool TaskComplete,
bool BringUpTaskJournal)
{
int TaskID = task_info.TaskID;
2013-02-16 16:14:39 -08:00
2018-06-24 23:21:35 -04:00
if (TaskID == 0 || Tasks[TaskID] == nullptr)
return;
2013-02-16 16:14:39 -08:00
2018-06-24 23:21:35 -04:00
int StartTime = task_info.AcceptedTime;
SendActiveTaskDescription(c, TaskID, task_info, StartTime, Tasks[TaskID]->Duration, BringUpTaskJournal);
Log(Logs::General, Logs::Tasks, "[UPDATE] SendSingleActiveTasksToClient: Task %i, Activities: %i", TaskID, GetActivityCount(TaskID));
2013-02-16 16:14:39 -08:00
2018-06-24 23:21:35 -04:00
for (int Activity = 0; Activity < GetActivityCount(TaskID); Activity++) {
if(task_info.Activity[Activity].State != ActivityHidden) {
Log(Logs::General, Logs::Tasks, "[UPDATE] Long: %i, %i Complete=%i", TaskID, Activity, TaskComplete);
if (Activity == GetActivityCount(TaskID) - 1)
SendTaskActivityLong(c, TaskID, Activity, task_info.slot,
2013-05-09 10:44:08 -04:00
Tasks[TaskID]->Activity[Activity].Optional, TaskComplete);
2013-02-16 16:14:39 -08:00
else
2018-06-24 23:21:35 -04:00
SendTaskActivityLong(c, TaskID, Activity, task_info.slot,
2013-05-09 10:44:08 -04:00
Tasks[TaskID]->Activity[Activity].Optional, 0);
2018-06-24 23:21:35 -04:00
} else {
Log(Logs::General, Logs::Tasks, "[UPDATE] Short: %i, %i", TaskID, Activity);
SendTaskActivityShort(c, TaskID, Activity, task_info.slot);
2013-02-16 16:14:39 -08:00
}
}
}
2018-06-24 23:21:35 -04:00
void TaskManager::SendActiveTaskDescription(Client *c, int TaskID, ClientTaskInformation &task_info, int StartTime, int Duration, bool BringUpTaskJournal)
{
if ((TaskID < 1) || (TaskID >= MAXTASKS) || !Tasks[TaskID])
return;
2013-02-16 16:14:39 -08:00
2018-06-24 23:21:35 -04:00
int PacketLength = sizeof(TaskDescriptionHeader_Struct) + Tasks[TaskID]->Title.length() + 1
+ sizeof(TaskDescriptionData1_Struct) + Tasks[TaskID]->Description.length() + 1
2013-05-09 10:44:08 -04:00
+ sizeof(TaskDescriptionData2_Struct) + 1 + sizeof(TaskDescriptionTrailer_Struct);
2013-02-16 16:14:39 -08:00
// If there is an item make the Reward text into a link to the item (only the first item if a list
// is specified). I have been unable to get multiple item links to work.
//
2018-06-24 23:21:35 -04:00
if(Tasks[TaskID]->RewardID && Tasks[TaskID]->item_link.empty()) {
int ItemID = 0;
2013-02-16 16:14:39 -08:00
// If the reward is a list of items, and the first entry on the list is valid
2018-06-24 23:21:35 -04:00
if (Tasks[TaskID]->RewardMethod == METHODSINGLEID) {
2013-02-16 16:14:39 -08:00
ItemID = Tasks[TaskID]->RewardID;
2018-06-24 23:21:35 -04:00
} else if (Tasks[TaskID]->RewardMethod == METHODLIST) {
2013-02-16 16:14:39 -08:00
ItemID = GoalListManager.GetFirstEntry(Tasks[TaskID]->RewardID);
2018-06-24 23:21:35 -04:00
if (ItemID < 0)
2013-02-16 16:14:39 -08:00
ItemID = 0;
}
2013-02-16 16:14:39 -08:00
if(ItemID) {
const EQ::ItemData* reward_item = database.GetItem(ItemID);
2014-12-28 12:49:14 -05:00
EQ::SayLinkEngine linker;
linker.SetLinkType(EQ::saylink::SayLinkItemData);
2014-12-28 12:49:14 -05:00
linker.SetItemData(reward_item);
linker.SetTaskUse();
2018-06-24 23:21:35 -04:00
Tasks[TaskID]->item_link = linker.GenerateLink();
2013-02-16 16:14:39 -08:00
}
}
2018-06-24 23:21:35 -04:00
PacketLength += Tasks[TaskID]->Reward.length() + 1 + Tasks[TaskID]->item_link.length() + 1;
2013-02-16 16:14:39 -08:00
char *Ptr;
TaskDescriptionHeader_Struct* tdh;
TaskDescriptionData1_Struct* tdd1;
TaskDescriptionData2_Struct* tdd2;
TaskDescriptionTrailer_Struct* tdt;
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_TaskDescription, PacketLength);
2013-02-16 16:14:39 -08:00
tdh = (TaskDescriptionHeader_Struct*)outapp->pBuffer;
2018-06-24 23:21:35 -04:00
tdh->SequenceNumber = task_info.slot;
2013-02-16 16:14:39 -08:00
tdh->TaskID = TaskID;
2018-06-24 23:21:35 -04:00
tdh->open_window = BringUpTaskJournal;
tdh->task_type = static_cast<uint32>(Tasks[TaskID]->type);
tdh->reward_type = 0; // TODO: 4 says Radiant Crystals else Ebon Crystals when shared task
2013-02-16 16:14:39 -08:00
Ptr = (char *) tdh + sizeof(TaskDescriptionHeader_Struct);
sprintf(Ptr, "%s", Tasks[TaskID]->Title.c_str());
2018-06-24 23:21:35 -04:00
Ptr += Tasks[TaskID]->Title.length() + 1;
2013-02-16 16:14:39 -08:00
tdd1 = (TaskDescriptionData1_Struct*)Ptr;
tdd1->Duration = Duration;
2018-06-24 23:21:35 -04:00
tdd1->dur_code = static_cast<uint32>(Tasks[TaskID]->dur_code);
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
tdd1->StartTime = StartTime;
Ptr = (char *) tdd1 + sizeof(TaskDescriptionData1_Struct);
sprintf(Ptr, "%s", Tasks[TaskID]->Description.c_str());
2018-06-24 23:21:35 -04:00
Ptr += Tasks[TaskID]->Description.length() + 1;
2013-02-16 16:14:39 -08:00
tdd2 = (TaskDescriptionData2_Struct*)Ptr;
2018-06-24 23:21:35 -04:00
// we have this reward stuff!
// if we ever don't hardcode this, TaskDescriptionTrailer_Struct will need to be fixed since
// "has_reward_selection" is after this bool! Smaller packet when this is 0
tdd2->has_rewards = 1;
2013-05-06 13:07:41 -04:00
2018-06-24 23:21:35 -04:00
tdd2->coin_reward = Tasks[TaskID]->CashReward;
tdd2->xp_reward = Tasks[TaskID]->XPReward ? 1 : 0; // just booled
tdd2->faction_reward = Tasks[TaskID]->faction_reward ? 1 : 0; // faction booled
2013-02-16 16:14:39 -08:00
Ptr = (char *) tdd2 + sizeof(TaskDescriptionData2_Struct);
2018-06-24 23:21:35 -04:00
// we actually have 2 strings here. One is max length 96 and not parsed for item links
// We actually skipped past that string incorrectly before, so TODO: fix item link string
sprintf(Ptr, "%s", Tasks[TaskID]->Reward.c_str());
Ptr += Tasks[TaskID]->Reward.length() + 1;
2013-02-16 16:14:39 -08:00
2018-06-24 23:21:35 -04:00
// second string is parsed for item links
sprintf(Ptr, "%s", Tasks[TaskID]->item_link.c_str());
Ptr += Tasks[TaskID]->item_link.length() + 1;
2013-02-16 16:14:39 -08:00
2018-06-24 23:21:35 -04:00
tdt = (TaskDescriptionTrailer_Struct*)Ptr;
tdt->Points = 0x00000000; // Points Count TODO: this does have a visible affect on the client ...
tdt->has_reward_selection = 0; // TODO: new rewards window
2013-02-16 16:14:39 -08:00
c->QueuePacket(outapp);
safe_delete(outapp);
}
bool ClientTaskState::IsTaskActivityCompleted(TaskType type, int index, int ActivityID)
{
switch (type) {
case TaskType::Task:
if (index != 0)
return false;
return ActiveTask.Activity[ActivityID].State == ActivityCompleted;
case TaskType::Shared:
return false; // TODO: shared tasks
case TaskType::Quest:
if (index < MAXACTIVEQUESTS)
return ActiveQuests[index].Activity[ActivityID].State == ActivityCompleted;
default:
return false;
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
}
// should we be defaulting to hidden?
ActivityState ClientTaskState::GetTaskActivityState(TaskType type, int index, int ActivityID)
{
switch (type) {
case TaskType::Task:
if (index != 0)
return ActivityHidden;
return ActiveTask.Activity[ActivityID].State;
case TaskType::Shared:
return ActivityHidden; // TODO: shared tasks
case TaskType::Quest:
if (index < MAXACTIVEQUESTS)
return ActiveQuests[index].Activity[ActivityID].State;
default:
return ActivityHidden;
}
2013-02-16 16:14:39 -08:00
}
int ClientTaskState::GetTaskActivityDoneCount(TaskType type, int index, int ActivityID)
{
switch (type) {
case TaskType::Task:
if (index != 0)
return 0;
return ActiveTask.Activity[ActivityID].DoneCount;
case TaskType::Shared:
return 0; // TODO: shared tasks
case TaskType::Quest:
if (index < MAXACTIVEQUESTS)
return ActiveQuests[index].Activity[ActivityID].DoneCount;
default:
return 0;
}
2013-02-16 16:14:39 -08:00
}
int ClientTaskState::GetTaskActivityDoneCountFromTaskID(int TaskID, int ActivityID)
{
if (ActiveTask.TaskID == TaskID)
return ActiveTask.Activity[ActivityID].DoneCount;
// TODO: shared tasks
2013-02-16 16:14:39 -08:00
int ActiveTaskIndex = -1;
for(int i=0; i<MAXACTIVEQUESTS; i++) {
if(ActiveQuests[i].TaskID==TaskID) {
2013-02-16 16:14:39 -08:00
ActiveTaskIndex = i;
break;
}
}
if (ActiveTaskIndex == -1)
return 0;
if (ActiveQuests[ActiveTaskIndex].Activity[ActivityID].DoneCount){
return ActiveQuests[ActiveTaskIndex].Activity[ActivityID].DoneCount;
}
else{
return 0;
}
2013-02-16 16:14:39 -08:00
}
int ClientTaskState::GetTaskStartTime(TaskType type, int index)
{
switch (type) {
case TaskType::Task:
return ActiveTask.AcceptedTime;
case TaskType::Quest:
return ActiveQuests[index].AcceptedTime;
case TaskType::Shared: // TODO
default:
return -1;
}
2013-02-16 16:14:39 -08:00
}
void ClientTaskState::CancelAllTasks(Client *c) {
// This method exists solely to be called during #task reloadall
2013-05-06 13:07:41 -04:00
// It removes tasks from the in-game client state ready for them to be
2013-02-16 16:14:39 -08:00
// resent to the client, in case an updated task fails to load
2018-06-24 23:21:35 -04:00
CancelTask(c, 0, TaskType::Task, false);
ActiveTask.TaskID = TASKSLOTEMPTY;
for(int i=0; i<MAXACTIVEQUESTS; i++)
if(ActiveQuests[i].TaskID != TASKSLOTEMPTY) {
2018-06-24 23:21:35 -04:00
CancelTask(c, i, TaskType::Quest, false);
ActiveQuests[i].TaskID = TASKSLOTEMPTY;
2013-02-16 16:14:39 -08:00
}
2013-05-06 13:07:41 -04:00
2018-06-24 23:21:35 -04:00
// TODO: shared
2013-02-16 16:14:39 -08:00
}
2018-06-24 23:21:35 -04:00
void ClientTaskState::CancelTask(Client *c, int SequenceNumber, TaskType type, bool RemoveFromDB)
{
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_CancelTask, sizeof(CancelTask_Struct));
2013-02-16 16:14:39 -08:00
CancelTask_Struct* cts = (CancelTask_Struct*)outapp->pBuffer;
cts->SequenceNumber = SequenceNumber;
2018-06-24 23:21:35 -04:00
cts->type = static_cast<uint32>(type);
2013-02-16 16:14:39 -08:00
Log(Logs::General, Logs::Tasks, "[UPDATE] CancelTask");
2013-02-16 16:14:39 -08:00
c->QueuePacket(outapp);
safe_delete(outapp);
if(RemoveFromDB)
2018-06-24 23:21:35 -04:00
RemoveTask(c, SequenceNumber, type);
2013-02-16 16:14:39 -08:00
}
2018-06-24 23:21:35 -04:00
void ClientTaskState::RemoveTask(Client *c, int sequenceNumber, TaskType type)
{
2014-10-08 13:33:22 -07:00
int characterID = c->CharacterID();
2018-07-08 16:55:08 -04:00
Log(Logs::General, Logs::Tasks, "[UPDATE] ClientTaskState Cancel Task %i ", sequenceNumber);
2013-02-16 16:14:39 -08:00
2018-07-08 16:55:08 -04:00
int task_id = -1;
switch (type) {
case TaskType::Task:
if (sequenceNumber == 0)
task_id = ActiveTask.TaskID;
break;
case TaskType::Quest:
if (sequenceNumber < MAXACTIVEQUESTS)
task_id = ActiveQuests[sequenceNumber].TaskID;
break;
case TaskType::Shared: // TODO:
default:
break;
}
std::string query = StringFormat("DELETE FROM character_activities WHERE charid=%i AND taskid = %i",
characterID, task_id);
auto results = database.QueryDatabase(query);
if (!results.Success()) {
LogError("[TASKS] Error in CientTaskState::CancelTask [{}]",
2018-07-08 16:55:08 -04:00
results.ErrorMessage().c_str());
2013-02-16 16:14:39 -08:00
return;
}
2018-07-08 16:55:08 -04:00
Log(Logs::General, Logs::Tasks, "[UPDATE] CancelTask: %s", query.c_str());
2013-02-16 16:14:39 -08:00
2018-07-08 16:55:08 -04:00
query = StringFormat("DELETE FROM character_tasks WHERE charid=%i AND taskid = %i AND type=%i", characterID,
task_id, static_cast<int>(type));
2014-10-08 13:33:22 -07:00
results = database.QueryDatabase(query);
2018-07-08 16:55:08 -04:00
if (!results.Success())
LogError("[TASKS] Error in CientTaskState::CancelTask [{}]",
2018-07-08 16:55:08 -04:00
results.ErrorMessage().c_str());
2013-02-16 16:14:39 -08:00
Log(Logs::General, Logs::Tasks, "[UPDATE] CancelTask: %s", query.c_str());
2013-02-16 16:14:39 -08:00
2018-06-24 23:21:35 -04:00
switch (type) {
case TaskType::Task:
ActiveTask.TaskID = TASKSLOTEMPTY;
break;
case TaskType::Shared:
break; // TODO: shared tasks
case TaskType::Quest:
ActiveQuests[sequenceNumber].TaskID = TASKSLOTEMPTY;
ActiveTaskCount--;
break;
default:
break;
}
2013-02-16 16:14:39 -08:00
}
2018-06-24 23:21:35 -04:00
void ClientTaskState::AcceptNewTask(Client *c, int TaskID, int NPCID, bool enforce_level_requirement)
{
if (!taskmanager || TaskID < 0 || TaskID >= MAXTASKS) {
2019-08-11 00:00:55 -05:00
c->Message(Chat::Red, "Task system not functioning, or TaskID %i out of range.", TaskID);
2013-02-16 16:14:39 -08:00
return;
}
2018-06-24 23:21:35 -04:00
auto task = taskmanager->Tasks[TaskID];
if (task == nullptr) {
2019-08-11 00:00:55 -05:00
c->Message(Chat::Red, "Invalid TaskID %i", TaskID);
2013-02-16 16:14:39 -08:00
return;
}
2018-06-24 23:21:35 -04:00
bool max_tasks = false;
switch (task->type) {
case TaskType::Task:
if (ActiveTask.TaskID != TASKSLOTEMPTY)
max_tasks = true;
break;
case TaskType::Shared: // TODO: shared tasks
// if (something)
max_tasks = true;
break;
case TaskType::Quest:
if (ActiveTaskCount == MAXACTIVEQUESTS)
max_tasks = true;
break;
default:
break;
}
if (max_tasks) {
2019-08-11 00:00:55 -05:00
c->Message(Chat::Red, "You already have the maximum allowable number of active tasks (%i)", MAXACTIVEQUESTS);
2013-02-16 16:14:39 -08:00
return;
}
2018-06-24 23:21:35 -04:00
// only Quests can have more than one, so don't need to check others
if (task->type == TaskType::Quest) {
for (int i = 0; i < MAXACTIVEQUESTS; i++) {
if (ActiveQuests[i].TaskID == TaskID) {
2019-08-11 00:00:55 -05:00
c->Message(Chat::Red, "You have already been assigned this task.");
2018-06-24 23:21:35 -04:00
return;
}
2013-02-16 16:14:39 -08:00
}
}
2018-06-24 23:21:35 -04:00
if (enforce_level_requirement && !taskmanager->AppropriateLevel(TaskID, c->GetLevel())) {
2019-08-11 00:00:55 -05:00
c->Message(Chat::Red, "You are outside the level range of this task.");
return;
}
2018-06-24 23:21:35 -04:00
if (!taskmanager->IsTaskRepeatable(TaskID) && IsTaskCompleted(TaskID))
return;
2013-02-16 16:14:39 -08:00
// We do it this way, because when the Client cancels a task, it retains the sequence number of the remaining
// tasks in it's window, until something causes the TaskDescription packets to be sent again. We could just
// resend all the active task data to the client when it cancels a task, but that could be construed as a
// waste of bandwidth.
//
2018-06-24 23:21:35 -04:00
ClientTaskInformation *active_slot = nullptr;
switch (task->type) {
case TaskType::Task:
active_slot = &ActiveTask;
break;
case TaskType::Shared: // TODO: shared
active_slot = nullptr;
break;
case TaskType::Quest:
for (int i = 0; i < MAXACTIVEQUESTS; i++) {
Log(Logs::General, Logs::Tasks,
"[UPDATE] ClientTaskState Looking for free slot in slot %i, found TaskID of %i", i,
ActiveQuests[i].TaskID);
if (ActiveQuests[i].TaskID == 0) {
active_slot = &ActiveQuests[i];
break;
}
2013-02-16 16:14:39 -08:00
}
2018-06-24 23:21:35 -04:00
break;
default:
break;
2013-02-16 16:14:39 -08:00
}
// This shouldn't happen unless there is a bug in the handling of ActiveTaskCount somewhere
2018-06-24 23:21:35 -04:00
if (active_slot == nullptr) {
2019-08-11 00:00:55 -05:00
c->Message(Chat::Red, "You already have the maximum allowable number of active tasks (%i)", MAXACTIVEQUESTS);
2013-02-16 16:14:39 -08:00
return;
}
2013-05-06 13:07:41 -04:00
2018-06-24 23:21:35 -04:00
active_slot->TaskID = TaskID;
active_slot->AcceptedTime = time(nullptr);
active_slot->Updated = true;
active_slot->CurrentStep = -1;
2013-02-16 16:14:39 -08:00
2018-06-24 23:21:35 -04:00
for (int i = 0; i < taskmanager->Tasks[TaskID]->ActivityCount; i++) {
active_slot->Activity[i].ActivityID = i;
active_slot->Activity[i].DoneCount = 0;
active_slot->Activity[i].State = ActivityHidden;
active_slot->Activity[i].Updated = true;
2013-02-16 16:14:39 -08:00
}
2018-06-24 23:21:35 -04:00
UnlockActivities(c->CharacterID(), *active_slot);
if (task->type == TaskType::Quest)
ActiveTaskCount++;
taskmanager->SendSingleActiveTaskToClient(c, *active_slot, false, true);
c->Message(Chat::White, "You have been assigned the task '%s'.", taskmanager->Tasks[TaskID]->Title.c_str());
2013-02-16 16:14:39 -08:00
2018-06-24 23:21:35 -04:00
std::string buf = std::to_string(TaskID);
2013-02-16 16:14:39 -08:00
NPC *npc = entity_list.GetID(NPCID)->CastToNPC();
if(!npc) {
2019-08-11 00:00:55 -05:00
c->Message(Chat::Yellow, "Task Giver ID is %i", NPCID);
c->Message(Chat::Red, "Unable to find NPC to send EVENT_TASKACCEPTED to. Report this bug.");
2013-02-16 16:14:39 -08:00
return;
}
2018-06-24 23:21:35 -04:00
taskmanager->SaveClientState(c, this);
parse->EventNPC(EVENT_TASK_ACCEPTED, npc, c, buf.c_str(), 0);
2013-02-16 16:14:39 -08:00
}
void ClientTaskState::ProcessTaskProximities(Client *c, float X, float Y, float Z) {
float LastX = c->ProximityX();
float LastY = c->ProximityY();
float LastZ = c->ProximityZ();
if((LastX==X) && (LastY==Y) && (LastZ==Z)) return;
Log(Logs::General, Logs::Tasks, "[PROXIMITY] Checking proximities for Position %8.3f, %8.3f, %8.3f", X, Y, Z);
2013-02-16 16:14:39 -08:00
int ExploreID = taskmanager->ProximityManager.CheckProximities(X, Y, Z);
2015-01-20 03:46:27 -06:00
if(ExploreID > 0) {
Log(Logs::General, Logs::Tasks, "[PROXIMITY] Position %8.3f, %8.3f, %8.3f is within proximity %i", X, Y, Z, ExploreID);
2013-02-16 16:14:39 -08:00
UpdateTasksOnExplore(c, ExploreID);
}
}
TaskGoalListManager::TaskGoalListManager()
{
2013-02-16 16:14:39 -08:00
NumberOfLists = 0;
}
TaskGoalListManager::~TaskGoalListManager() {}
2013-02-16 16:14:39 -08:00
bool TaskGoalListManager::LoadLists()
{
2013-02-16 16:14:39 -08:00
Log(Logs::General, Logs::Tasks, "[GLOBALLOAD] TaskGoalListManager::LoadLists Called");
2013-02-16 16:14:39 -08:00
TaskGoalLists.clear();
2013-02-16 16:14:39 -08:00
const char *ERR_MYSQLERROR = "Error in TaskGoalListManager::LoadLists: %s %s";
2013-02-16 16:14:39 -08:00
2014-10-08 14:11:35 -07:00
NumberOfLists = 0;
2013-02-16 16:14:39 -08:00
std::string query = "SELECT `listid`, COUNT(`entry`) "
"FROM `goallists` GROUP by `listid` "
"ORDER BY `listid`";
auto results = database.QueryDatabase(query);
if (!results.Success()) {
2014-10-08 14:11:35 -07:00
return false;
}
2013-02-16 16:14:39 -08:00
NumberOfLists = results.RowCount();
Log(Logs::General, Logs::Tasks, "[GLOBALLOAD] Database returned a count of %i lists", NumberOfLists);
2013-05-06 13:07:41 -04:00
TaskGoalLists.reserve(NumberOfLists);
2013-02-16 16:14:39 -08:00
int listIndex = 0;
2013-02-16 16:14:39 -08:00
for (auto row = results.begin(); row != results.end(); ++row) {
int listID = atoi(row[0]);
int listSize = atoi(row[1]);
TaskGoalLists.push_back({listID, 0, 0});
2013-02-16 16:14:39 -08:00
TaskGoalLists[listIndex].GoalItemEntries.reserve(listSize);
2013-02-16 16:14:39 -08:00
listIndex++;
}
2013-02-16 16:14:39 -08:00
for (int listIndex = 0; listIndex < NumberOfLists; listIndex++) {
2013-02-16 16:14:39 -08:00
2014-10-08 14:11:35 -07:00
int listID = TaskGoalLists[listIndex].ListID;
auto size = TaskGoalLists[listIndex].GoalItemEntries.capacity(); // this was only done for manual memory management, shouldn't need to do this
query = StringFormat("SELECT `entry` from `goallists` "
"WHERE `listid` = %i "
"ORDER BY `entry` ASC LIMIT %i",
listID, size);
results = database.QueryDatabase(query);
if (!results.Success()) {
2014-10-08 14:11:35 -07:00
continue;
}
2013-02-16 16:14:39 -08:00
for (auto row = results.begin(); row != results.end(); ++row) {
2013-02-16 16:14:39 -08:00
int entry = atoi(row[0]);
2013-02-16 16:14:39 -08:00
if (entry < TaskGoalLists[listIndex].Min)
TaskGoalLists[listIndex].Min = entry;
2013-02-16 16:14:39 -08:00
if (entry > TaskGoalLists[listIndex].Max)
TaskGoalLists[listIndex].Max = entry;
2013-02-16 16:14:39 -08:00
TaskGoalLists[listIndex].GoalItemEntries.push_back(entry);
}
2013-02-16 16:14:39 -08:00
}
2014-10-08 14:11:35 -07:00
2013-02-16 16:14:39 -08:00
return true;
}
int TaskGoalListManager::GetListByID(int ListID) {
// Find the list with the specified ListID and return the index
auto it = std::find_if(TaskGoalLists.begin(), TaskGoalLists.end(),
[ListID](const TaskGoalList_Struct &t) { return t.ListID == ListID; });
2013-05-06 13:07:41 -04:00
if (it == TaskGoalLists.end())
return -1;
2013-02-16 16:14:39 -08:00
return std::distance(TaskGoalLists.begin(), it);
2013-02-16 16:14:39 -08:00
}
int TaskGoalListManager::GetFirstEntry(int ListID) {
int ListIndex = GetListByID(ListID);
if((ListIndex < 0) || (ListIndex >= NumberOfLists)) return -1;
if(TaskGoalLists[ListIndex].GoalItemEntries.empty()) return -1;
2013-02-16 16:14:39 -08:00
return TaskGoalLists[ListIndex].GoalItemEntries[0];
}
std::vector<int> TaskGoalListManager::GetListContents(int ListID) {
2013-02-16 16:14:39 -08:00
std::vector<int> ListContents;
2013-02-16 16:14:39 -08:00
int ListIndex = GetListByID(ListID);
if((ListIndex < 0) || (ListIndex >= NumberOfLists)) return ListContents;
ListContents = TaskGoalLists[ListIndex].GoalItemEntries;
2013-02-16 16:14:39 -08:00
return ListContents;
}
bool TaskGoalListManager::IsInList(int ListID, int Entry)
{
Log(Logs::General, Logs::Tasks, "[UPDATE] TaskGoalListManager::IsInList(%i, %i)", ListID, Entry);
2013-02-16 16:14:39 -08:00
int ListIndex = GetListByID(ListID);
if ((ListIndex < 0) || (ListIndex >= NumberOfLists))
return false;
2013-02-16 16:14:39 -08:00
if ((Entry < TaskGoalLists[ListIndex].Min) || (Entry > TaskGoalLists[ListIndex].Max))
2013-05-09 10:44:08 -04:00
return false;
2013-02-16 16:14:39 -08:00
int FirstEntry = 0;
auto &task = TaskGoalLists[ListIndex];
2013-02-16 16:14:39 -08:00
auto it = std::find(task.GoalItemEntries.begin(), task.GoalItemEntries.end(), Entry);
2013-02-16 16:14:39 -08:00
if (it == task.GoalItemEntries.end())
return false;
2013-02-16 16:14:39 -08:00
Log(Logs::General, Logs::Tasks, "[UPDATE] TaskGoalListManager::IsInList(%i, %i) returning true", ListIndex,
Entry);
return true;
2013-02-16 16:14:39 -08:00
}
TaskProximityManager::TaskProximityManager() {
}
TaskProximityManager::~TaskProximityManager() {
}
bool TaskProximityManager::LoadProximities(int zoneID) {
TaskProximity proximity;
2013-02-16 16:14:39 -08:00
Log(Logs::General, Logs::Tasks, "[GLOBALLOAD] TaskProximityManager::LoadProximities Called for zone %i", zoneID);
2013-02-16 16:14:39 -08:00
TaskProximities.clear();
std::string query = StringFormat("SELECT `exploreid`, `minx`, `maxx`, "
"`miny`, `maxy`, `minz`, `maxz` "
"FROM `proximities` WHERE `zoneid` = %i "
"ORDER BY `zoneid` ASC", zoneID);
auto results = database.QueryDatabase(query);
if (!results.Success()) {
return false;
}
2013-02-16 16:14:39 -08:00
for( auto row = results.begin(); row != results.end(); ++row) {
proximity.ExploreID = atoi(row[0]);
proximity.MinX = atof(row[1]);
proximity.MaxX = atof(row[2]);
proximity.MinY = atof(row[3]);
proximity.MaxY = atof(row[4]);
proximity.MinZ = atof(row[5]);
proximity.MaxZ = atof(row[6]);
2013-02-16 16:14:39 -08:00
TaskProximities.push_back(proximity);
}
2013-02-16 16:14:39 -08:00
return true;
}
int TaskProximityManager::CheckProximities(float X, float Y, float Z) {
for(unsigned int i=0; i<TaskProximities.size(); i++) {
TaskProximity* P = &TaskProximities[i];
Log(Logs::General, Logs::Tasks, "[PROXIMITY] Checking %8.3f, %8.3f, %8.3f against %8.3f, %8.3f, %8.3f, %8.3f, %8.3f, %8.3f",
2013-05-09 10:44:08 -04:00
X, Y, Z, P->MinX, P->MaxX, P->MinY, P->MaxY, P->MinZ, P->MaxZ);
2013-02-16 16:14:39 -08:00
if(X < P->MinX || X > P->MaxX || Y < P->MinY || Y > P->MaxY ||
2013-05-09 10:44:08 -04:00
Z < P->MinZ || Z > P->MaxZ) continue;
2013-02-16 16:14:39 -08:00
return P->ExploreID;
}
return 0;
}