Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions conf/mod-double-xp-weekend.conf.dist
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ XPWeekend.IsDKStartZoneRequired = 0

# XPWeekend.IsJoyousJourneysActive = 0
# Promotional event, adds yet another layer of bonus on top of the current bonus (yay!)
# Ignored while a window is set with the `.weekendxp joyousjourneys schedule`
# command; the scheduled window decides whether the event is active instead.
# Default: 0 (Disabled)

XPWeekend.IsJoyousJourneysActive = 0
Expand Down
64 changes: 64 additions & 0 deletions data/sql/db-world/mod-weekend-xp-texts.sql
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,74 @@ INSERT INTO `module_string_locale` (`module`, `id`, `locale`, `string`) VALUES
('mod-weekend-xp', 1, 'ruRU', 'Указано неверное значение. Пожалуйста, укажите значение от 0 до {}'),
('mod-weekend-xp', 2, 'ruRU', 'Применяемый к вам множитель: {}.\nТекущая конфигурация weekendxp:\nAnnounce {}\nAlwaysEnabled {}\nQuestOnly {}\nMaxLevel {}\nxpAmount {}\nIndividualXPEnabled {}\nEnabled {}\nMaxAllowedRate {}');

DELETE FROM `module_string` WHERE `id` IN (3, 4, 5, 6, 7, 8) AND `module` = "mod-weekend-xp";
INSERT INTO `module_string` (`module`, `id`, `string`) VALUES
("mod-weekend-xp", 3, 'No Joyous Journeys schedule is set. The event follows the XPWeekend.IsJoyousJourneysActive config (currently: {}).'),
("mod-weekend-xp", 4, 'Joyous Journeys is scheduled from {} to {} (inclusive). Currently active: {}.'),
("mod-weekend-xp", 5, 'Joyous Journeys schedule cleared. The event now follows the XPWeekend.IsJoyousJourneysActive config.'),
("mod-weekend-xp", 6, 'Invalid date ''{}''. Use the YYYY-MM-DD format.'),
("mod-weekend-xp", 7, 'The end date must not be before the start date.'),
("mod-weekend-xp", 8, '|cff00ccffThe Joyous Journeys event is active until {}! Experience gains have been increased. Type .weekendxp j off to disable it.|r');

DELETE FROM `module_string_locale` WHERE `module` = 'mod-weekend-xp' AND `id` IN (3, 4, 5, 6, 7, 8);
INSERT INTO `module_string_locale` (`module`, `id`, `locale`, `string`) VALUES
('mod-weekend-xp', 3, 'koKR', '설정된 기쁨의 여정 일정이 없습니다. 이벤트는 XPWeekend.IsJoyousJourneysActive 설정을 따릅니다 (현재: {}).'),
('mod-weekend-xp', 4, 'koKR', '기쁨의 여정이 {}부터 {}까지 (양일 포함) 예정되어 있습니다. 현재 활성화 여부: {}.'),
('mod-weekend-xp', 5, 'koKR', '기쁨의 여정 일정이 삭제되었습니다. 이벤트는 이제 XPWeekend.IsJoyousJourneysActive 설정을 따릅니다.'),
('mod-weekend-xp', 6, 'koKR', '잘못된 날짜 ''{}''입니다. YYYY-MM-DD 형식을 사용하세요.'),
('mod-weekend-xp', 7, 'koKR', '종료 날짜는 시작 날짜보다 빠를 수 없습니다.'),
('mod-weekend-xp', 8, 'koKR', '|cff00ccff기쁨의 여정 이벤트가 {}까지 진행됩니다! 경험치 획득량이 증가했습니다. 비활성화하려면 .weekendxp j off 를 입력하세요.|r'),
('mod-weekend-xp', 3, 'frFR', 'Aucune période n''est définie pour Joyeux Voyages. L''événement suit la configuration XPWeekend.IsJoyousJourneysActive (actuellement : {}).'),
('mod-weekend-xp', 4, 'frFR', 'Joyeux Voyages est programmé du {} au {} (inclus). Actuellement actif : {}.'),
('mod-weekend-xp', 5, 'frFR', 'La période de Joyeux Voyages a été supprimée. L''événement suit désormais la configuration XPWeekend.IsJoyousJourneysActive.'),
('mod-weekend-xp', 6, 'frFR', 'Date invalide ''{}''. Utilisez le format YYYY-MM-DD.'),
('mod-weekend-xp', 7, 'frFR', 'La date de fin ne doit pas être antérieure à la date de début.'),
('mod-weekend-xp', 8, 'frFR', '|cff00ccffL''événement Joyeux Voyages est actif jusqu''au {} ! Les gains d''expérience ont été augmentés. Tapez .weekendxp j off pour le désactiver.|r'),
('mod-weekend-xp', 3, 'deDE', 'Es ist kein Zeitraum für Freudige Reisen festgelegt. Das Event folgt der Einstellung XPWeekend.IsJoyousJourneysActive (aktuell: {}).'),
('mod-weekend-xp', 4, 'deDE', 'Freudige Reisen ist vom {} bis zum {} (einschließlich) geplant. Aktuell aktiv: {}.'),
('mod-weekend-xp', 5, 'deDE', 'Der Zeitraum für Freudige Reisen wurde entfernt. Das Event folgt nun wieder der Einstellung XPWeekend.IsJoyousJourneysActive.'),
('mod-weekend-xp', 6, 'deDE', 'Ungültiges Datum ''{}''. Bitte das Format YYYY-MM-DD verwenden.'),
('mod-weekend-xp', 7, 'deDE', 'Das Enddatum darf nicht vor dem Startdatum liegen.'),
('mod-weekend-xp', 8, 'deDE', '|cff00ccffDas Event Freudige Reisen ist bis zum {} aktiv! Die Erfahrungsgewinne wurden erhöht. Gib .weekendxp j off ein, um es zu deaktivieren.|r'),
('mod-weekend-xp', 3, 'zhCN', '未设置欢乐之旅日程。活动遵循 XPWeekend.IsJoyousJourneysActive 配置(当前:{})。'),
('mod-weekend-xp', 4, 'zhCN', '欢乐之旅已安排在 {} 至 {}(含)期间。当前是否激活:{}。'),
('mod-weekend-xp', 5, 'zhCN', '欢乐之旅日程已清除。活动现在遵循 XPWeekend.IsJoyousJourneysActive 配置。'),
('mod-weekend-xp', 6, 'zhCN', '无效日期 ''{}''。请使用 YYYY-MM-DD 格式。'),
('mod-weekend-xp', 7, 'zhCN', '结束日期不能早于开始日期。'),
('mod-weekend-xp', 8, 'zhCN', '|cff00ccff欢乐之旅活动将持续到 {}!经验值获取已提升。输入 .weekendxp j off 可将其关闭。|r'),
('mod-weekend-xp', 3, 'zhTW', '未設定歡樂之旅日程。活動遵循 XPWeekend.IsJoyousJourneysActive 設定(目前:{})。'),
('mod-weekend-xp', 4, 'zhTW', '歡樂之旅已安排在 {} 至 {}(含)期間。目前是否啟用:{}。'),
('mod-weekend-xp', 5, 'zhTW', '歡樂之旅日程已清除。活動現在遵循 XPWeekend.IsJoyousJourneysActive 設定。'),
('mod-weekend-xp', 6, 'zhTW', '無效日期 ''{}''。請使用 YYYY-MM-DD 格式。'),
('mod-weekend-xp', 7, 'zhTW', '結束日期不能早於開始日期。'),
('mod-weekend-xp', 8, 'zhTW', '|cff00ccff歡樂之旅活動將持續到 {}!經驗值獲得已提升。輸入 .weekendxp j off 可將其停用。|r'),
('mod-weekend-xp', 3, 'esES', 'No hay un periodo establecido para Viajes Alegres. El evento sigue la configuración XPWeekend.IsJoyousJourneysActive (actualmente: {}).'),
('mod-weekend-xp', 4, 'esES', 'Viajes Alegres está programado del {} al {} (inclusive). Actualmente activo: {}.'),
('mod-weekend-xp', 5, 'esES', 'Se ha eliminado el periodo de Viajes Alegres. El evento vuelve a seguir la configuración XPWeekend.IsJoyousJourneysActive.'),
('mod-weekend-xp', 6, 'esES', 'Fecha no válida ''{}''. Usa el formato YYYY-MM-DD.'),
('mod-weekend-xp', 7, 'esES', 'La fecha de fin no debe ser anterior a la fecha de inicio.'),
('mod-weekend-xp', 8, 'esES', '|cff00ccff¡El evento Viajes Alegres está activo hasta el {}! La experiencia obtenida ha aumentado. Escribe .weekendxp j off para desactivarlo.|r'),
('mod-weekend-xp', 3, 'esMX', 'No hay un periodo establecido para Viajes Alegres. El evento sigue la configuración XPWeekend.IsJoyousJourneysActive (actualmente: {}).'),
('mod-weekend-xp', 4, 'esMX', 'Viajes Alegres está programado del {} al {} (inclusive). Actualmente activo: {}.'),
('mod-weekend-xp', 5, 'esMX', 'Se eliminó el periodo de Viajes Alegres. El evento vuelve a seguir la configuración XPWeekend.IsJoyousJourneysActive.'),
('mod-weekend-xp', 6, 'esMX', 'Fecha no válida ''{}''. Usa el formato YYYY-MM-DD.'),
('mod-weekend-xp', 7, 'esMX', 'La fecha de fin no debe ser anterior a la fecha de inicio.'),
('mod-weekend-xp', 8, 'esMX', '|cff00ccff¡El evento Viajes Alegres está activo hasta el {}! La experiencia obtenida aumentó. Escribe .weekendxp j off para desactivarlo.|r'),
('mod-weekend-xp', 3, 'ruRU', 'Расписание события «Радостные путешествия» не задано. Событие следует настройке XPWeekend.IsJoyousJourneysActive (сейчас: {}).'),
('mod-weekend-xp', 4, 'ruRU', 'Событие «Радостные путешествия» запланировано с {} по {} (включительно). Сейчас активно: {}.'),
('mod-weekend-xp', 5, 'ruRU', 'Расписание события «Радостные путешествия» удалено. Событие снова следует настройке XPWeekend.IsJoyousJourneysActive.'),
('mod-weekend-xp', 6, 'ruRU', 'Неверная дата ''{}''. Используйте формат YYYY-MM-DD.'),
('mod-weekend-xp', 7, 'ruRU', 'Дата окончания не должна быть раньше даты начала.'),
('mod-weekend-xp', 8, 'ruRU', '|cff00ccffСобытие «Радостные путешествия» активно до {}! Получаемый опыт увеличен. Введите .weekendxp j off, чтобы отключить его.|r');

DELETE FROM `command` WHERE `name` IN ('weekendxp rate');
INSERT INTO `command` (`name`, `security`, `help`) VALUES
('weekendxp rate', 0, 'Syntax: weekendxp rate $value \nSet your experience rate up to the allowed value.');

DELETE FROM `command` WHERE `name` IN ('weekendxp config');
INSERT INTO `command` (`name`, `security`, `help`) VALUES
('weekendxp config', 0, 'Syntax: weekendxp config\nDisplays the current configuration for the weekendxp mod.');

DELETE FROM `command` WHERE `name` IN ('weekendxp joyousjourneys schedule');
INSERT INTO `command` (`name`, `security`, `help`) VALUES
('weekendxp joyousjourneys schedule', 3, 'Syntax: weekendxp joyousjourneys schedule [$start $end || clear]\nSchedules the Joyous Journeys event. Dates use the YYYY-MM-DD format and are inclusive; the scheduled window overrides the XPWeekend.IsJoyousJourneysActive config.\nWithout arguments the current schedule is shown, "clear" removes it.');
145 changes: 142 additions & 3 deletions src/mod-double-xp-weekend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
#include "ScriptMgr.h"
#include "Player.h"
#include "Chat.h"
#include "Timer.h"
#include "WorldState.h"
#include <cstdio>
#include <time.h>

using namespace Acore::ChatCommands;
Expand All @@ -18,6 +21,14 @@ enum WeekendXP
WD_SUNDAY = 0,
};

// Indices into the characters DB `worldstates` table. Must be unique across the
// core (see WorldStateDefines.h, custom entries start at 20001) and other modules.
enum WeekendXPWorldStates
{
WS_JOYOUS_JOURNEYS_START = 97301, // unix timestamp, first second of the event
WS_JOYOUS_JOURNEYS_END = 97302, // unix timestamp, first second AFTER the event
};

class DoubleXpWeekend
{

Expand Down Expand Up @@ -138,7 +149,87 @@ class DoubleXpWeekend
return true;
}

bool IsJoyousJourneysActive() const { return sConfigMgr->GetOption<bool>("XPWeekend.IsJoyousJourneysActive", false); }
// When a window was set via `.weekendxp joyousjourneys schedule`, the
// scheduled window decides whether the event is active. Otherwise the
// static config flag is used.
bool IsJoyousJourneysActive() const
{
if (HasJoyousJourneysSchedule())
{
time_t now = time(nullptr);
return now >= JoyousJourneysStart() && now < JoyousJourneysEnd();
}

return sConfigMgr->GetOption<bool>("XPWeekend.IsJoyousJourneysActive", false);
}

bool HasJoyousJourneysSchedule() const { return JoyousJourneysEnd() != 0; }

std::string JoyousJourneysEndDateString() const
{
// The stored end is the first second after the event, so the last
// (inclusive) event day is one second earlier.
return FormatDate(JoyousJourneysEnd() - 1);
}

bool HandleJoyousJourneysScheduleCommand(ChatHandler* handler, Optional<std::string> startDate, Optional<std::string> endDate) const
{
// PSendModuleSysMessage drops the message when there is no session, and this
// command is available from the console, so parse and send separately.
if (!startDate)
{
if (!HasJoyousJourneysSchedule())
{
handler->SendSysMessage(handler->PGetParseModuleString("mod-weekend-xp", 3,
sConfigMgr->GetOption<bool>("XPWeekend.IsJoyousJourneysActive", false)));
}
else
{
handler->SendSysMessage(handler->PGetParseModuleString("mod-weekend-xp", 4,
FormatDate(JoyousJourneysStart()), JoyousJourneysEndDateString(), IsJoyousJourneysActive()));
}

return true;
}

if (StringEqualI(*startDate, "clear"))
{
sWorldState->setWorldState(WS_JOYOUS_JOURNEYS_START, 0);
sWorldState->setWorldState(WS_JOYOUS_JOURNEYS_END, 0);
handler->SendSysMessage(handler->PGetParseModuleString("mod-weekend-xp", 5));
return true;
}

if (!endDate)
{
return false; // show command usage
}

Optional<time_t> start = ParseDate(*startDate);
Optional<time_t> endStart = ParseDate(*endDate);

if (!start || !endStart)
{
handler->SendSysMessage(handler->PGetParseModuleString("mod-weekend-xp", 6, !start ? *startDate : *endDate));
handler->SetSentErrorMessage(true);
return true;
}

if (*endStart < *start)
{
handler->SendSysMessage(handler->PGetParseModuleString("mod-weekend-xp", 7));
handler->SetSentErrorMessage(true);
return true;
}

sWorldState->setWorldState(WS_JOYOUS_JOURNEYS_START, uint64(*start));
sWorldState->setWorldState(WS_JOYOUS_JOURNEYS_END, uint64(*endStart + DAY));
handler->SendSysMessage(handler->PGetParseModuleString("mod-weekend-xp", 4,
FormatDate(JoyousJourneysStart()), JoyousJourneysEndDateString(), IsJoyousJourneysActive()));

return true;
}

float ConfigJoyousJourneysXPRate() const { return sConfigMgr->GetOption<float>("XPWeekend.JoyousJourneysXPRate", 1.0f); }
float ConfigJoyousJourneysRepRate() const { return sConfigMgr->GetOption<float>("XPWeekend.JoyousJourneysRepRate", 1.10f); }
bool ExcludeInsaneReps() const { return sConfigMgr->GetOption<bool>("XPWeekend.ExcludeInsaneReps", true); }
Expand All @@ -161,6 +252,42 @@ class DoubleXpWeekend
return player->IsQuestRewarded(13188) || player->IsQuestRewarded(13189);
}

time_t JoyousJourneysStart() const { return time_t(sWorldState->getWorldState(WS_JOYOUS_JOURNEYS_START)); }
time_t JoyousJourneysEnd() const { return time_t(sWorldState->getWorldState(WS_JOYOUS_JOURNEYS_END)); }

// Parses YYYY-MM-DD into the first second of that day in server local time.
static Optional<time_t> ParseDate(std::string const& text)
{
unsigned int year, month, day;
char extra;
if (sscanf(text.c_str(), "%4u-%2u-%2u%c", &year, &month, &day, &extra) != 3)
{
return std::nullopt;
}

tm date{};
date.tm_year = int(year) - 1900;
date.tm_mon = int(month) - 1;
date.tm_mday = int(day);
date.tm_isdst = -1;

time_t stamp = mktime(&date);

// mktime normalizes out-of-range fields (e.g. 2026-02-30 becomes March 2nd),
// so a changed day/month means the input was not a real calendar date.
if (stamp == time_t(-1) || date.tm_mday != int(day) || date.tm_mon != int(month) - 1)
{
return std::nullopt;
}

return stamp;
}

static std::string FormatDate(time_t when)
{
return Acore::Time::TimeToTimestampStr(Seconds(when), "%Y-%m-%d");
}

void PlayerSettingSetRate(Player* player, float rate) const
{
// HACK PlayerSetting seems to store uint32 only, so save our `float` as if it was a `uint32`
Expand Down Expand Up @@ -285,7 +412,8 @@ class weekendxp_commandscript : public CommandScript
{
{ "weekendxp rate", HandleSetXPBonusCommand, SEC_PLAYER, Console::No },
{ "weekendxp config", HandleGetCurrentConfigCommand, SEC_PLAYER, Console::No },
{ "weekendxp joyousjourneys", HandleJoyousJourneysCommand, SEC_PLAYER, Console::No }
{ "weekendxp joyousjourneys", HandleJoyousJourneysCommand, SEC_PLAYER, Console::No },
{ "weekendxp joyousjourneys schedule", HandleJoyousJourneysScheduleCommand, SEC_ADMINISTRATOR, Console::Yes }
};

return commandTable;
Expand Down Expand Up @@ -317,6 +445,12 @@ class weekendxp_commandscript : public CommandScript
handler->PSendSysMessage("Joyous Journeys experience boost {}.", !enable ? "disabled" : "enabled");
return true;
}

static bool HandleJoyousJourneysScheduleCommand(ChatHandler* handler, Optional<std::string> startDate, Optional<std::string> endDate)
{
DoubleXpWeekend* mod = DoubleXpWeekend::instance();
return mod->HandleJoyousJourneysScheduleCommand(handler, startDate, endDate);
}
};

class DoubleXpWeekendPlayerScript : public PlayerScript
Expand All @@ -335,7 +469,12 @@ class DoubleXpWeekendPlayerScript : public PlayerScript
mod->OnPlayerLogin(player, &handler);

if (mod->IsJoyousJourneysActive() && mod->ConfigJoyousJourneysXPRate())
handler.PSendSysMessage("|cff00ccffThe Joyous Journeys event is active! Experience gains have been increased. Type .weekendxp j off to disable it.|r");
{
if (mod->HasJoyousJourneysSchedule())
handler.PSendModuleSysMessage("mod-weekend-xp", 8, mod->JoyousJourneysEndDateString());
else
handler.PSendSysMessage("|cff00ccffThe Joyous Journeys event is active! Experience gains have been increased. Type .weekendxp j off to disable it.|r");
}
}

void OnPlayerGiveXP(Player* player, uint32& amount, Unit* /*victim*/, uint8 xpSource) override
Expand Down
Loading