mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 16:51:29 +00:00
[Scheduler] Event scheduler implementation (#1257)
* Event scheduler implementation * Create 2021_02_17_server_scheduled_events.sql * Tweak * Remove unused event [skip ci] * Cleanup [skip ci] * PR adjustments * Database manifest
This commit is contained in:
parent
f51bc4daaf
commit
7aa5308f9c
@ -69,6 +69,7 @@ SET(common_sources
|
||||
rulesys.cpp
|
||||
say_link.cpp
|
||||
serialize_buffer.cpp
|
||||
server_event_scheduler.cpp
|
||||
serverinfo.cpp
|
||||
shareddb.cpp
|
||||
skills.cpp
|
||||
@ -252,6 +253,7 @@ SET(repositories
|
||||
repositories/base/base_rule_sets_repository.h
|
||||
repositories/base/base_rule_values_repository.h
|
||||
repositories/base/base_saylink_repository.h
|
||||
repositories/base/base_server_scheduled_events_repository.h
|
||||
repositories/base/base_skill_caps_repository.h
|
||||
repositories/base/base_spawn2_repository.h
|
||||
repositories/base/base_spawnentry_repository.h
|
||||
@ -415,6 +417,7 @@ SET(repositories
|
||||
repositories/rule_sets_repository.h
|
||||
repositories/rule_values_repository.h
|
||||
repositories/saylink_repository.h
|
||||
repositories/server_scheduled_events_repository.h
|
||||
repositories/skill_caps_repository.h
|
||||
repositories/spawn2_repository.h
|
||||
repositories/spawnentry_repository.h
|
||||
@ -461,6 +464,7 @@ SET(common_headers
|
||||
cli/argh.h
|
||||
cli/eqemu_command_handler.h
|
||||
cli/terminal_color.hpp
|
||||
cron/croncpp.h
|
||||
database/database_dump_service.h
|
||||
data_verification.h
|
||||
database.h
|
||||
@ -543,6 +547,7 @@ SET(common_headers
|
||||
say_link.h
|
||||
seperator.h
|
||||
serialize_buffer.h
|
||||
server_event_scheduler.h
|
||||
serverinfo.h
|
||||
servertalk.h
|
||||
shareddb.h
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
#include "../database.h"
|
||||
#include "../rulesys.h"
|
||||
#include "../eqemu_logsys.h"
|
||||
#include "../repositories/content_flags_repository.h"
|
||||
|
||||
|
||||
WorldContentService::WorldContentService()
|
||||
@ -99,3 +100,21 @@ bool WorldContentService::IsContentFlagEnabled(const std::string& content_flag)
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void WorldContentService::ReloadContentFlags(Database &db)
|
||||
{
|
||||
std::vector<std::string> set_content_flags;
|
||||
auto content_flags = ContentFlagsRepository::GetWhere(db, "enabled = 1");
|
||||
|
||||
set_content_flags.reserve(content_flags.size());
|
||||
for (auto &flags: content_flags) {
|
||||
set_content_flags.push_back(flags.flag_name);
|
||||
}
|
||||
|
||||
LogInfo(
|
||||
"Enabled content flags [{}]",
|
||||
implode(", ", set_content_flags)
|
||||
);
|
||||
|
||||
SetContentFlags(set_content_flags);
|
||||
}
|
||||
|
||||
@ -24,6 +24,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class Database;
|
||||
|
||||
namespace Expansion {
|
||||
static const int EXPANSION_ALL = -1;
|
||||
static const int EXPANSION_FILTER_MAX = 99;
|
||||
@ -165,6 +167,7 @@ public:
|
||||
const std::vector<std::string> &GetContentFlags() const;
|
||||
bool IsContentFlagEnabled(const std::string& content_flag);
|
||||
void SetContentFlags(std::vector<std::string> content_flags);
|
||||
void ReloadContentFlags(Database &db);
|
||||
void SetExpansionContext();
|
||||
};
|
||||
|
||||
|
||||
876
common/cron/croncpp.h
Normal file
876
common/cron/croncpp.h
Normal file
@ -0,0 +1,876 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <bitset>
|
||||
#include <cctype>
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
#include <algorithm>
|
||||
|
||||
#if __cplusplus > 201402L
|
||||
#include <string_view>
|
||||
#define CRONCPP_IS_CPP17
|
||||
#endif
|
||||
|
||||
namespace cron
|
||||
{
|
||||
#ifdef CRONCPP_IS_CPP17
|
||||
#define HAS_STRING_VIEW
|
||||
#define STRING_VIEW std::string_view
|
||||
#define STRING_VIEW_NPOS std::string_view::npos
|
||||
#define CONSTEXPTR constexpr
|
||||
#else
|
||||
#define STRING_VIEW std::string const &
|
||||
#define STRING_VIEW_NPOS std::string::npos
|
||||
#define CONSTEXPTR
|
||||
#endif
|
||||
|
||||
using cron_int = uint8_t;
|
||||
|
||||
constexpr std::time_t INVALID_TIME = static_cast<std::time_t>(-1);
|
||||
|
||||
constexpr size_t INVALID_CRON_INDEX = static_cast<size_t>(-1);
|
||||
|
||||
class cronexpr;
|
||||
|
||||
namespace detail
|
||||
{
|
||||
enum class cron_field
|
||||
{
|
||||
second,
|
||||
minute,
|
||||
hour_of_day,
|
||||
day_of_week,
|
||||
day_of_month,
|
||||
month,
|
||||
year
|
||||
};
|
||||
|
||||
template <typename Traits>
|
||||
static bool find_next(cronexpr const & cex,
|
||||
std::tm& date,
|
||||
size_t const dot);
|
||||
}
|
||||
|
||||
struct bad_cronexpr : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
explicit bad_cronexpr(STRING_VIEW message) :
|
||||
std::runtime_error(message.data())
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
struct cron_standard_traits
|
||||
{
|
||||
static const cron_int CRON_MIN_SECONDS = 0;
|
||||
static const cron_int CRON_MAX_SECONDS = 59;
|
||||
|
||||
static const cron_int CRON_MIN_MINUTES = 0;
|
||||
static const cron_int CRON_MAX_MINUTES = 59;
|
||||
|
||||
static const cron_int CRON_MIN_HOURS = 0;
|
||||
static const cron_int CRON_MAX_HOURS = 23;
|
||||
|
||||
static const cron_int CRON_MIN_DAYS_OF_WEEK = 0;
|
||||
static const cron_int CRON_MAX_DAYS_OF_WEEK = 6;
|
||||
|
||||
static const cron_int CRON_MIN_DAYS_OF_MONTH = 1;
|
||||
static const cron_int CRON_MAX_DAYS_OF_MONTH = 31;
|
||||
|
||||
static const cron_int CRON_MIN_MONTHS = 1;
|
||||
static const cron_int CRON_MAX_MONTHS = 12;
|
||||
|
||||
static const cron_int CRON_MAX_YEARS_DIFF = 4;
|
||||
|
||||
#ifdef CRONCPP_IS_CPP17
|
||||
static const inline std::vector<std::string> DAYS = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" };
|
||||
static const inline std::vector<std::string> MONTHS = { "NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };
|
||||
#else
|
||||
static std::vector<std::string>& DAYS()
|
||||
{
|
||||
static std::vector<std::string> days = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" };
|
||||
return days;
|
||||
}
|
||||
|
||||
static std::vector<std::string>& MONTHS()
|
||||
{
|
||||
static std::vector<std::string> months = { "NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };
|
||||
return months;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
struct cron_oracle_traits
|
||||
{
|
||||
static const cron_int CRON_MIN_SECONDS = 0;
|
||||
static const cron_int CRON_MAX_SECONDS = 59;
|
||||
|
||||
static const cron_int CRON_MIN_MINUTES = 0;
|
||||
static const cron_int CRON_MAX_MINUTES = 59;
|
||||
|
||||
static const cron_int CRON_MIN_HOURS = 0;
|
||||
static const cron_int CRON_MAX_HOURS = 23;
|
||||
|
||||
static const cron_int CRON_MIN_DAYS_OF_WEEK = 1;
|
||||
static const cron_int CRON_MAX_DAYS_OF_WEEK = 7;
|
||||
|
||||
static const cron_int CRON_MIN_DAYS_OF_MONTH = 1;
|
||||
static const cron_int CRON_MAX_DAYS_OF_MONTH = 31;
|
||||
|
||||
static const cron_int CRON_MIN_MONTHS = 0;
|
||||
static const cron_int CRON_MAX_MONTHS = 11;
|
||||
|
||||
static const cron_int CRON_MAX_YEARS_DIFF = 4;
|
||||
|
||||
#ifdef CRONCPP_IS_CPP17
|
||||
static const inline std::vector<std::string> DAYS = { "NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" };
|
||||
static const inline std::vector<std::string> MONTHS = { "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };
|
||||
#else
|
||||
|
||||
static std::vector<std::string>& DAYS()
|
||||
{
|
||||
static std::vector<std::string> days = { "NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" };
|
||||
return days;
|
||||
}
|
||||
|
||||
static std::vector<std::string>& MONTHS()
|
||||
{
|
||||
static std::vector<std::string> months = { "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };
|
||||
return months;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
struct cron_quartz_traits
|
||||
{
|
||||
static const cron_int CRON_MIN_SECONDS = 0;
|
||||
static const cron_int CRON_MAX_SECONDS = 59;
|
||||
|
||||
static const cron_int CRON_MIN_MINUTES = 0;
|
||||
static const cron_int CRON_MAX_MINUTES = 59;
|
||||
|
||||
static const cron_int CRON_MIN_HOURS = 0;
|
||||
static const cron_int CRON_MAX_HOURS = 23;
|
||||
|
||||
static const cron_int CRON_MIN_DAYS_OF_WEEK = 1;
|
||||
static const cron_int CRON_MAX_DAYS_OF_WEEK = 7;
|
||||
|
||||
static const cron_int CRON_MIN_DAYS_OF_MONTH = 1;
|
||||
static const cron_int CRON_MAX_DAYS_OF_MONTH = 31;
|
||||
|
||||
static const cron_int CRON_MIN_MONTHS = 1;
|
||||
static const cron_int CRON_MAX_MONTHS = 12;
|
||||
|
||||
static const cron_int CRON_MAX_YEARS_DIFF = 4;
|
||||
|
||||
#ifdef CRONCPP_IS_CPP17
|
||||
static const inline std::vector<std::string> DAYS = { "NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" };
|
||||
static const inline std::vector<std::string> MONTHS = { "NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };
|
||||
#else
|
||||
static std::vector<std::string>& DAYS()
|
||||
{
|
||||
static std::vector<std::string> days = { "NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" };
|
||||
return days;
|
||||
}
|
||||
|
||||
static std::vector<std::string>& MONTHS()
|
||||
{
|
||||
static std::vector<std::string> months = { "NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };
|
||||
return months;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
class cronexpr;
|
||||
|
||||
template <typename Traits = cron_standard_traits>
|
||||
static cronexpr make_cron(STRING_VIEW expr);
|
||||
|
||||
class cronexpr
|
||||
{
|
||||
std::bitset<60> seconds;
|
||||
std::bitset<60> minutes;
|
||||
std::bitset<24> hours;
|
||||
std::bitset<7> days_of_week;
|
||||
std::bitset<31> days_of_month;
|
||||
std::bitset<12> months;
|
||||
|
||||
friend bool operator==(cronexpr const & e1, cronexpr const & e2);
|
||||
friend bool operator!=(cronexpr const & e1, cronexpr const & e2);
|
||||
|
||||
template <typename Traits>
|
||||
friend bool detail::find_next(cronexpr const & cex,
|
||||
std::tm& date,
|
||||
size_t const dot);
|
||||
|
||||
friend std::string to_string(cronexpr const & cex);
|
||||
|
||||
template <typename Traits>
|
||||
friend cronexpr make_cron(STRING_VIEW expr);
|
||||
};
|
||||
|
||||
inline bool operator==(cronexpr const & e1, cronexpr const & e2)
|
||||
{
|
||||
return
|
||||
e1.seconds == e2.seconds &&
|
||||
e1.minutes == e2.minutes &&
|
||||
e1.hours == e2.hours &&
|
||||
e1.days_of_week == e2.days_of_week &&
|
||||
e1.days_of_month == e2.days_of_month &&
|
||||
e1.months == e2.months;
|
||||
}
|
||||
|
||||
inline bool operator!=(cronexpr const & e1, cronexpr const & e2)
|
||||
{
|
||||
return !(e1 == e2);
|
||||
}
|
||||
|
||||
inline std::string to_string(cronexpr const & cex)
|
||||
{
|
||||
return
|
||||
cex.seconds.to_string() + " " +
|
||||
cex.minutes.to_string() + " " +
|
||||
cex.hours.to_string() + " " +
|
||||
cex.days_of_month.to_string() + " " +
|
||||
cex.months.to_string() + " " +
|
||||
cex.days_of_week.to_string();
|
||||
}
|
||||
|
||||
namespace utils
|
||||
{
|
||||
inline std::time_t tm_to_time(std::tm& date)
|
||||
{
|
||||
return std::mktime(&date);
|
||||
}
|
||||
|
||||
inline std::tm* time_to_tm(std::time_t const * date, std::tm* const out)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
errno_t err = localtime_s(out, date);
|
||||
return 0 == err ? out : nullptr;
|
||||
#else
|
||||
return localtime_r(date, out);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline std::tm to_tm(STRING_VIEW time)
|
||||
{
|
||||
std::istringstream str(time.data());
|
||||
str.imbue(std::locale(setlocale(LC_ALL, nullptr)));
|
||||
|
||||
std::tm result;
|
||||
str >> std::get_time(&result, "%Y-%m-%d %H:%M:%S");
|
||||
if (str.fail()) throw std::runtime_error("Parsing date failed!");
|
||||
|
||||
result.tm_isdst = -1; // DST info not available
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string to_string(std::tm const & tm)
|
||||
{
|
||||
std::ostringstream str;
|
||||
str.imbue(std::locale(setlocale(LC_ALL, nullptr)));
|
||||
str << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
|
||||
if (str.fail()) throw std::runtime_error("Writing date failed!");
|
||||
|
||||
return str.str();
|
||||
}
|
||||
|
||||
inline std::string to_upper(std::string text)
|
||||
{
|
||||
std::transform(std::begin(text), std::end(text),
|
||||
std::begin(text), static_cast<int(*)(int)>(std::toupper));
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
static std::vector<std::string> split(STRING_VIEW text, char const delimiter)
|
||||
{
|
||||
std::vector<std::string> tokens;
|
||||
std::string token;
|
||||
std::istringstream tokenStream(text.data());
|
||||
while (std::getline(tokenStream, token, delimiter))
|
||||
{
|
||||
tokens.push_back(token);
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
CONSTEXPTR inline bool contains(STRING_VIEW text, char const ch) noexcept
|
||||
{
|
||||
return STRING_VIEW_NPOS != text.find_first_of(ch);
|
||||
}
|
||||
}
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
inline cron_int to_cron_int(STRING_VIEW text)
|
||||
{
|
||||
try
|
||||
{
|
||||
return static_cast<cron_int>(std::stoul(text.data()));
|
||||
}
|
||||
catch (std::exception const & ex)
|
||||
{
|
||||
throw bad_cronexpr(ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
static std::string replace_ordinals(
|
||||
std::string text,
|
||||
std::vector<std::string> const & replacement)
|
||||
{
|
||||
for (size_t i = 0; i < replacement.size(); ++i)
|
||||
{
|
||||
auto pos = text.find(replacement[i]);
|
||||
if (std::string::npos != pos)
|
||||
text.replace(pos, 3 ,std::to_string(i));
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
static std::pair<cron_int, cron_int> make_range(
|
||||
STRING_VIEW field,
|
||||
cron_int const minval,
|
||||
cron_int const maxval)
|
||||
{
|
||||
cron_int first = 0;
|
||||
cron_int last = 0;
|
||||
if (field.size() == 1 && field[0] == '*')
|
||||
{
|
||||
first = minval;
|
||||
last = maxval;
|
||||
}
|
||||
else if (!utils::contains(field, '-'))
|
||||
{
|
||||
first = to_cron_int(field);
|
||||
last = first;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto parts = utils::split(field, '-');
|
||||
if (parts.size() != 2)
|
||||
throw bad_cronexpr("Specified range requires two fields");
|
||||
|
||||
first = to_cron_int(parts[0]);
|
||||
last = to_cron_int(parts[1]);
|
||||
}
|
||||
|
||||
if (first > maxval || last > maxval)
|
||||
{
|
||||
throw bad_cronexpr("Specified range exceeds maximum");
|
||||
}
|
||||
if (first < minval || last < minval)
|
||||
{
|
||||
throw bad_cronexpr("Specified range is less than minimum");
|
||||
}
|
||||
if (first > last)
|
||||
{
|
||||
throw bad_cronexpr("Specified range start exceeds range end");
|
||||
}
|
||||
|
||||
return { first, last };
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
static void set_cron_field(
|
||||
STRING_VIEW value,
|
||||
std::bitset<N>& target,
|
||||
cron_int const minval,
|
||||
cron_int const maxval)
|
||||
{
|
||||
if(value.length() > 0 && value[value.length()-1] == ',')
|
||||
throw bad_cronexpr("Value cannot end with comma");
|
||||
|
||||
auto fields = utils::split(value, ',');
|
||||
if (fields.empty())
|
||||
throw bad_cronexpr("Expression parsing error");
|
||||
|
||||
for (auto const & field : fields)
|
||||
{
|
||||
if (!utils::contains(field, '/'))
|
||||
{
|
||||
#ifdef CRONCPP_IS_CPP17
|
||||
auto[first, last] = detail::make_range(field, minval, maxval);
|
||||
#else
|
||||
auto range = detail::make_range(field, minval, maxval);
|
||||
auto first = range.first;
|
||||
auto last = range.second;
|
||||
#endif
|
||||
for (cron_int i = first - minval; i <= last - minval; ++i)
|
||||
{
|
||||
target.set(i);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto parts = utils::split(field, '/');
|
||||
if (parts.size() != 2)
|
||||
throw bad_cronexpr("Incrementer must have two fields");
|
||||
|
||||
#ifdef CRONCPP_IS_CPP17
|
||||
auto[first, last] = detail::make_range(parts[0], minval, maxval);
|
||||
#else
|
||||
auto range = detail::make_range(parts[0], minval, maxval);
|
||||
auto first = range.first;
|
||||
auto last = range.second;
|
||||
#endif
|
||||
|
||||
if (!utils::contains(parts[0], '-'))
|
||||
{
|
||||
last = maxval;
|
||||
}
|
||||
|
||||
auto delta = detail::to_cron_int(parts[1]);
|
||||
if(delta <= 0)
|
||||
throw bad_cronexpr("Incrementer must be a positive value");
|
||||
|
||||
for (cron_int i = first - minval; i <= last - minval; i += delta)
|
||||
{
|
||||
target.set(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Traits>
|
||||
static void set_cron_days_of_week(
|
||||
std::string value,
|
||||
std::bitset<7>& target)
|
||||
{
|
||||
auto days = utils::to_upper(value);
|
||||
auto days_replaced = detail::replace_ordinals(
|
||||
days,
|
||||
#ifdef CRONCPP_IS_CPP17
|
||||
Traits::DAYS
|
||||
#else
|
||||
Traits::DAYS()
|
||||
#endif
|
||||
);
|
||||
|
||||
if (days_replaced.size() == 1 && days_replaced[0] == '?')
|
||||
days_replaced[0] = '*';
|
||||
|
||||
set_cron_field(
|
||||
days_replaced,
|
||||
target,
|
||||
Traits::CRON_MIN_DAYS_OF_WEEK,
|
||||
Traits::CRON_MAX_DAYS_OF_WEEK);
|
||||
}
|
||||
|
||||
template <typename Traits>
|
||||
static void set_cron_days_of_month(
|
||||
std::string value,
|
||||
std::bitset<31>& target)
|
||||
{
|
||||
if (value.size() == 1 && value[0] == '?')
|
||||
value[0] = '*';
|
||||
|
||||
set_cron_field(
|
||||
value,
|
||||
target,
|
||||
Traits::CRON_MIN_DAYS_OF_MONTH,
|
||||
Traits::CRON_MAX_DAYS_OF_MONTH);
|
||||
}
|
||||
|
||||
template <typename Traits>
|
||||
static void set_cron_month(
|
||||
std::string value,
|
||||
std::bitset<12>& target)
|
||||
{
|
||||
auto month = utils::to_upper(value);
|
||||
auto month_replaced = replace_ordinals(
|
||||
month,
|
||||
#ifdef CRONCPP_IS_CPP17
|
||||
Traits::MONTHS
|
||||
#else
|
||||
Traits::MONTHS()
|
||||
#endif
|
||||
);
|
||||
|
||||
set_cron_field(
|
||||
month_replaced,
|
||||
target,
|
||||
Traits::CRON_MIN_MONTHS,
|
||||
Traits::CRON_MAX_MONTHS);
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
inline size_t next_set_bit(
|
||||
std::bitset<N> const & target,
|
||||
size_t /*minimum*/,
|
||||
size_t /*maximum*/,
|
||||
size_t offset)
|
||||
{
|
||||
for (auto i = offset; i < N; ++i)
|
||||
{
|
||||
if (target.test(i)) return i;
|
||||
}
|
||||
|
||||
return INVALID_CRON_INDEX;
|
||||
}
|
||||
|
||||
inline void add_to_field(
|
||||
std::tm& date,
|
||||
cron_field const field,
|
||||
int const val)
|
||||
{
|
||||
switch (field)
|
||||
{
|
||||
case cron_field::second:
|
||||
date.tm_sec += val;
|
||||
break;
|
||||
case cron_field::minute:
|
||||
date.tm_min += val;
|
||||
break;
|
||||
case cron_field::hour_of_day:
|
||||
date.tm_hour += val;
|
||||
break;
|
||||
case cron_field::day_of_week:
|
||||
case cron_field::day_of_month:
|
||||
date.tm_mday += val;
|
||||
break;
|
||||
case cron_field::month:
|
||||
date.tm_mon += val;
|
||||
break;
|
||||
case cron_field::year:
|
||||
date.tm_year += val;
|
||||
break;
|
||||
}
|
||||
|
||||
if (INVALID_TIME == utils::tm_to_time(date))
|
||||
throw bad_cronexpr("Invalid time expression");
|
||||
}
|
||||
|
||||
inline void set_field(
|
||||
std::tm& date,
|
||||
cron_field const field,
|
||||
int const val)
|
||||
{
|
||||
switch (field)
|
||||
{
|
||||
case cron_field::second:
|
||||
date.tm_sec = val;
|
||||
break;
|
||||
case cron_field::minute:
|
||||
date.tm_min = val;
|
||||
break;
|
||||
case cron_field::hour_of_day:
|
||||
date.tm_hour = val;
|
||||
break;
|
||||
case cron_field::day_of_week:
|
||||
date.tm_wday = val;
|
||||
break;
|
||||
case cron_field::day_of_month:
|
||||
date.tm_mday = val;
|
||||
break;
|
||||
case cron_field::month:
|
||||
date.tm_mon = val;
|
||||
break;
|
||||
case cron_field::year:
|
||||
date.tm_year = val;
|
||||
break;
|
||||
}
|
||||
|
||||
if (INVALID_TIME == utils::tm_to_time(date))
|
||||
throw bad_cronexpr("Invalid time expression");
|
||||
}
|
||||
|
||||
inline void reset_field(
|
||||
std::tm& date,
|
||||
cron_field const field)
|
||||
{
|
||||
switch (field)
|
||||
{
|
||||
case cron_field::second:
|
||||
date.tm_sec = 0;
|
||||
break;
|
||||
case cron_field::minute:
|
||||
date.tm_min = 0;
|
||||
break;
|
||||
case cron_field::hour_of_day:
|
||||
date.tm_hour = 0;
|
||||
break;
|
||||
case cron_field::day_of_week:
|
||||
date.tm_wday = 0;
|
||||
break;
|
||||
case cron_field::day_of_month:
|
||||
date.tm_mday = 1;
|
||||
break;
|
||||
case cron_field::month:
|
||||
date.tm_mon = 0;
|
||||
break;
|
||||
case cron_field::year:
|
||||
date.tm_year = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (INVALID_TIME == utils::tm_to_time(date))
|
||||
throw bad_cronexpr("Invalid time expression");
|
||||
}
|
||||
|
||||
inline void reset_all_fields(
|
||||
std::tm& date,
|
||||
std::bitset<7> const & marked_fields)
|
||||
{
|
||||
for (size_t i = 0; i < marked_fields.size(); ++i)
|
||||
{
|
||||
if (marked_fields.test(i))
|
||||
reset_field(date, static_cast<cron_field>(i));
|
||||
}
|
||||
}
|
||||
|
||||
inline void mark_field(
|
||||
std::bitset<7> & orders,
|
||||
cron_field const field)
|
||||
{
|
||||
if (!orders.test(static_cast<size_t>(field)))
|
||||
orders.set(static_cast<size_t>(field));
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
static size_t find_next(
|
||||
std::bitset<N> const & target,
|
||||
std::tm& date,
|
||||
unsigned int const minimum,
|
||||
unsigned int const maximum,
|
||||
unsigned int const value,
|
||||
cron_field const field,
|
||||
cron_field const next_field,
|
||||
std::bitset<7> const & marked_fields)
|
||||
{
|
||||
auto next_value = next_set_bit(target, minimum, maximum, value);
|
||||
if (INVALID_CRON_INDEX == next_value)
|
||||
{
|
||||
add_to_field(date, next_field, 1);
|
||||
reset_field(date, field);
|
||||
next_value = next_set_bit(target, minimum, maximum, 0);
|
||||
}
|
||||
|
||||
if (INVALID_CRON_INDEX == next_value || next_value != value)
|
||||
{
|
||||
set_field(date, field, static_cast<int>(next_value));
|
||||
reset_all_fields(date, marked_fields);
|
||||
}
|
||||
|
||||
return next_value;
|
||||
}
|
||||
|
||||
template <typename Traits>
|
||||
static size_t find_next_day(
|
||||
std::tm& date,
|
||||
std::bitset<31> const & days_of_month,
|
||||
size_t day_of_month,
|
||||
std::bitset<7> const & days_of_week,
|
||||
size_t day_of_week,
|
||||
std::bitset<7> const & marked_fields)
|
||||
{
|
||||
unsigned int count = 0;
|
||||
unsigned int maximum = 366;
|
||||
while (
|
||||
(!days_of_month.test(day_of_month - Traits::CRON_MIN_DAYS_OF_MONTH) ||
|
||||
!days_of_week.test(day_of_week - Traits::CRON_MIN_DAYS_OF_WEEK))
|
||||
&& count++ < maximum)
|
||||
{
|
||||
add_to_field(date, cron_field::day_of_month, 1);
|
||||
|
||||
day_of_month = date.tm_mday;
|
||||
day_of_week = date.tm_wday;
|
||||
|
||||
reset_all_fields(date, marked_fields);
|
||||
}
|
||||
|
||||
return day_of_month;
|
||||
}
|
||||
|
||||
template <typename Traits>
|
||||
static bool find_next(cronexpr const & cex,
|
||||
std::tm& date,
|
||||
size_t const dot)
|
||||
{
|
||||
bool res = true;
|
||||
|
||||
std::bitset<7> marked_fields{ 0 };
|
||||
std::bitset<7> empty_list{ 0 };
|
||||
|
||||
unsigned int second = date.tm_sec;
|
||||
auto updated_second = find_next(
|
||||
cex.seconds,
|
||||
date,
|
||||
Traits::CRON_MIN_SECONDS,
|
||||
Traits::CRON_MAX_SECONDS,
|
||||
second,
|
||||
cron_field::second,
|
||||
cron_field::minute,
|
||||
empty_list);
|
||||
|
||||
if (second == updated_second)
|
||||
{
|
||||
mark_field(marked_fields, cron_field::second);
|
||||
}
|
||||
|
||||
unsigned int minute = date.tm_min;
|
||||
auto update_minute = find_next(
|
||||
cex.minutes,
|
||||
date,
|
||||
Traits::CRON_MIN_MINUTES,
|
||||
Traits::CRON_MAX_MINUTES,
|
||||
minute,
|
||||
cron_field::minute,
|
||||
cron_field::hour_of_day,
|
||||
marked_fields);
|
||||
if (minute == update_minute)
|
||||
{
|
||||
mark_field(marked_fields, cron_field::minute);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = find_next<Traits>(cex, date, dot);
|
||||
if (!res) return res;
|
||||
}
|
||||
|
||||
unsigned int hour = date.tm_hour;
|
||||
auto updated_hour = find_next(
|
||||
cex.hours,
|
||||
date,
|
||||
Traits::CRON_MIN_HOURS,
|
||||
Traits::CRON_MAX_HOURS,
|
||||
hour,
|
||||
cron_field::hour_of_day,
|
||||
cron_field::day_of_week,
|
||||
marked_fields);
|
||||
if (hour == updated_hour)
|
||||
{
|
||||
mark_field(marked_fields, cron_field::hour_of_day);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = find_next<Traits>(cex, date, dot);
|
||||
if (!res) return res;
|
||||
}
|
||||
|
||||
unsigned int day_of_week = date.tm_wday;
|
||||
unsigned int day_of_month = date.tm_mday;
|
||||
auto updated_day_of_month = find_next_day<Traits>(
|
||||
date,
|
||||
cex.days_of_month,
|
||||
day_of_month,
|
||||
cex.days_of_week,
|
||||
day_of_week,
|
||||
marked_fields);
|
||||
if (day_of_month == updated_day_of_month)
|
||||
{
|
||||
mark_field(marked_fields, cron_field::day_of_month);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = find_next<Traits>(cex, date, dot);
|
||||
if (!res) return res;
|
||||
}
|
||||
|
||||
unsigned int month = date.tm_mon;
|
||||
auto updated_month = find_next(
|
||||
cex.months,
|
||||
date,
|
||||
Traits::CRON_MIN_MONTHS,
|
||||
Traits::CRON_MAX_MONTHS,
|
||||
month,
|
||||
cron_field::month,
|
||||
cron_field::year,
|
||||
marked_fields);
|
||||
if (month != updated_month)
|
||||
{
|
||||
if (date.tm_year - dot > Traits::CRON_MAX_YEARS_DIFF)
|
||||
return false;
|
||||
|
||||
res = find_next<Traits>(cex, date, dot);
|
||||
if (!res) return res;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Traits>
|
||||
static cronexpr make_cron(STRING_VIEW expr)
|
||||
{
|
||||
cronexpr cex;
|
||||
|
||||
if (expr.empty())
|
||||
throw bad_cronexpr("Invalid empty cron expression");
|
||||
|
||||
auto fields = utils::split(expr, ' ');
|
||||
fields.erase(
|
||||
std::remove_if(std::begin(fields), std::end(fields),
|
||||
[](STRING_VIEW s) {return s.empty(); }),
|
||||
std::end(fields));
|
||||
if (fields.size() != 6)
|
||||
throw bad_cronexpr("cron expression must have six fields");
|
||||
|
||||
detail::set_cron_field(fields[0], cex.seconds, Traits::CRON_MIN_SECONDS, Traits::CRON_MAX_SECONDS);
|
||||
detail::set_cron_field(fields[1], cex.minutes, Traits::CRON_MIN_MINUTES, Traits::CRON_MAX_MINUTES);
|
||||
detail::set_cron_field(fields[2], cex.hours, Traits::CRON_MIN_HOURS, Traits::CRON_MAX_HOURS);
|
||||
|
||||
detail::set_cron_days_of_week<Traits>(fields[5], cex.days_of_week);
|
||||
|
||||
detail::set_cron_days_of_month<Traits>(fields[3], cex.days_of_month);
|
||||
|
||||
detail::set_cron_month<Traits>(fields[4], cex.months);
|
||||
|
||||
return cex;
|
||||
}
|
||||
|
||||
template <typename Traits = cron_standard_traits>
|
||||
static std::tm cron_next(cronexpr const & cex, std::tm date)
|
||||
{
|
||||
time_t original = utils::tm_to_time(date);
|
||||
if (INVALID_TIME == original) return {};
|
||||
|
||||
if (!detail::find_next<Traits>(cex, date, date.tm_year))
|
||||
return {};
|
||||
|
||||
time_t calculated = utils::tm_to_time(date);
|
||||
if (INVALID_TIME == calculated) return {};
|
||||
|
||||
if (calculated == original)
|
||||
{
|
||||
add_to_field(date, detail::cron_field::second, 1);
|
||||
if (!detail::find_next<Traits>(cex, date, date.tm_year))
|
||||
return {};
|
||||
}
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
template <typename Traits = cron_standard_traits>
|
||||
static std::time_t cron_next(cronexpr const & cex, std::time_t const & date)
|
||||
{
|
||||
std::tm val;
|
||||
std::tm* dt = utils::time_to_tm(&date, &val);
|
||||
if (dt == nullptr) return INVALID_TIME;
|
||||
|
||||
time_t original = utils::tm_to_time(*dt);
|
||||
if (INVALID_TIME == original) return INVALID_TIME;
|
||||
|
||||
if(!detail::find_next<Traits>(cex, *dt, dt->tm_year))
|
||||
return INVALID_TIME;
|
||||
|
||||
time_t calculated = utils::tm_to_time(*dt);
|
||||
if (INVALID_TIME == calculated) return calculated;
|
||||
|
||||
if (calculated == original)
|
||||
{
|
||||
add_to_field(*dt, detail::cron_field::second, 1);
|
||||
if(!detail::find_next<Traits>(cex, *dt, dt->tm_year))
|
||||
return INVALID_TIME;
|
||||
}
|
||||
|
||||
return utils::tm_to_time(*dt);
|
||||
}
|
||||
}
|
||||
@ -328,7 +328,7 @@ namespace DatabaseSchema {
|
||||
"reports",
|
||||
"respawn_times",
|
||||
"saylink",
|
||||
|
||||
"server_scheduled_events",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -127,6 +127,7 @@ void EQEmuLogSys::LoadLogSettingsDefaults()
|
||||
log_settings[Logs::HotReload].log_to_gmsay = static_cast<uint8>(Logs::General);
|
||||
log_settings[Logs::HotReload].log_to_console = static_cast<uint8>(Logs::General);
|
||||
log_settings[Logs::Loot].log_to_gmsay = static_cast<uint8>(Logs::General);
|
||||
log_settings[Logs::Scheduler].log_to_console = static_cast<uint8>(Logs::General);
|
||||
|
||||
/**
|
||||
* RFC 5424
|
||||
@ -206,11 +207,7 @@ std::string EQEmuLogSys::FormatOutMessageString(
|
||||
const std::string &in_message
|
||||
)
|
||||
{
|
||||
std::string return_string;
|
||||
|
||||
if (IsRfc5424LogCategory(log_category)) {
|
||||
return_string = "[" + GetPlatformName() + "] ";
|
||||
}
|
||||
std::string return_string = "[" + GetPlatformName() + "] ";
|
||||
|
||||
return return_string + "[" + Logs::LogCategoryName[log_category] + "] " + in_message;
|
||||
}
|
||||
|
||||
@ -120,6 +120,7 @@ namespace Logs {
|
||||
Loot,
|
||||
Expeditions,
|
||||
DynamicZones,
|
||||
Scheduler,
|
||||
MaxCategoryID /* Don't Remove this */
|
||||
};
|
||||
|
||||
@ -199,6 +200,7 @@ namespace Logs {
|
||||
"Loot",
|
||||
"Expeditions",
|
||||
"DynamicZones",
|
||||
"Scheduler",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -636,6 +636,16 @@
|
||||
OutF(LogSys, Logs::Detail, Logs::DynamicZones, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
} while (0)
|
||||
|
||||
#define LogScheduler(message, ...) do {\
|
||||
if (LogSys.log_settings[Logs::Scheduler].is_category_enabled == 1)\
|
||||
OutF(LogSys, Logs::General, Logs::Scheduler, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
} while (0)
|
||||
|
||||
#define LogSchedulerDetail(message, ...) do {\
|
||||
if (LogSys.log_settings[Logs::Scheduler].is_category_enabled == 1)\
|
||||
OutF(LogSys, Logs::Detail, Logs::Scheduler, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
} while (0)
|
||||
|
||||
#define Log(debug_level, log_category, message, ...) do {\
|
||||
if (LogSys.log_settings[log_category].is_category_enabled == 1)\
|
||||
LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
|
||||
@ -46,15 +46,15 @@ std::string GetPlatformName()
|
||||
{
|
||||
switch (GetExecutablePlatformInt()) {
|
||||
case EQEmuExePlatform::ExePlatformWorld:
|
||||
return "WorldServer";
|
||||
return "World";
|
||||
case EQEmuExePlatform::ExePlatformQueryServ:
|
||||
return "QueryServer";
|
||||
return "QS";
|
||||
case EQEmuExePlatform::ExePlatformZone:
|
||||
return "ZoneServer";
|
||||
return "Zone";
|
||||
case EQEmuExePlatform::ExePlatformUCS:
|
||||
return "UCS";
|
||||
case EQEmuExePlatform::ExePlatformLogin:
|
||||
return "LoginServer";
|
||||
return "Login";
|
||||
case EQEmuExePlatform::ExePlatformSocket_Server:
|
||||
return "SocketServer";
|
||||
case EQEmuExePlatform::ExePlatformSharedMemory:
|
||||
@ -70,4 +70,4 @@ std::string GetPlatformName()
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,427 @@
|
||||
/**
|
||||
* DO NOT MODIFY THIS FILE
|
||||
*
|
||||
* This repository was automatically generated and is NOT to be modified directly.
|
||||
* Any repository modifications are meant to be made to the repository extending the base.
|
||||
* Any modifications to base repositories are to be made by the generator only
|
||||
*
|
||||
* @generator ./utils/scripts/generators/repository-generator.pl
|
||||
* @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories
|
||||
*/
|
||||
|
||||
#ifndef EQEMU_BASE_SERVER_SCHEDULED_EVENTS_REPOSITORY_H
|
||||
#define EQEMU_BASE_SERVER_SCHEDULED_EVENTS_REPOSITORY_H
|
||||
|
||||
#include "../../database.h"
|
||||
#include "../../string_util.h"
|
||||
|
||||
class BaseServerScheduledEventsRepository {
|
||||
public:
|
||||
struct ServerScheduledEvents {
|
||||
int id;
|
||||
std::string description;
|
||||
std::string event_type;
|
||||
std::string event_data;
|
||||
int minute_start;
|
||||
int hour_start;
|
||||
int day_start;
|
||||
int month_start;
|
||||
int year_start;
|
||||
int minute_end;
|
||||
int hour_end;
|
||||
int day_end;
|
||||
int month_end;
|
||||
int year_end;
|
||||
std::string cron_expression;
|
||||
std::string created_at;
|
||||
std::string deleted_at;
|
||||
};
|
||||
|
||||
static std::string PrimaryKey()
|
||||
{
|
||||
return std::string("id");
|
||||
}
|
||||
|
||||
static std::vector<std::string> Columns()
|
||||
{
|
||||
return {
|
||||
"id",
|
||||
"description",
|
||||
"event_type",
|
||||
"event_data",
|
||||
"minute_start",
|
||||
"hour_start",
|
||||
"day_start",
|
||||
"month_start",
|
||||
"year_start",
|
||||
"minute_end",
|
||||
"hour_end",
|
||||
"day_end",
|
||||
"month_end",
|
||||
"year_end",
|
||||
"cron_expression",
|
||||
"created_at",
|
||||
"deleted_at",
|
||||
};
|
||||
}
|
||||
|
||||
static std::string ColumnsRaw()
|
||||
{
|
||||
return std::string(implode(", ", Columns()));
|
||||
}
|
||||
|
||||
static std::string TableName()
|
||||
{
|
||||
return std::string("server_scheduled_events");
|
||||
}
|
||||
|
||||
static std::string BaseSelect()
|
||||
{
|
||||
return fmt::format(
|
||||
"SELECT {} FROM {}",
|
||||
ColumnsRaw(),
|
||||
TableName()
|
||||
);
|
||||
}
|
||||
|
||||
static std::string BaseInsert()
|
||||
{
|
||||
return fmt::format(
|
||||
"INSERT INTO {} ({}) ",
|
||||
TableName(),
|
||||
ColumnsRaw()
|
||||
);
|
||||
}
|
||||
|
||||
static ServerScheduledEvents NewEntity()
|
||||
{
|
||||
ServerScheduledEvents entry{};
|
||||
|
||||
entry.id = 0;
|
||||
entry.description = "";
|
||||
entry.event_type = "";
|
||||
entry.event_data = "";
|
||||
entry.minute_start = 0;
|
||||
entry.hour_start = 0;
|
||||
entry.day_start = 0;
|
||||
entry.month_start = 0;
|
||||
entry.year_start = 0;
|
||||
entry.minute_end = 0;
|
||||
entry.hour_end = 0;
|
||||
entry.day_end = 0;
|
||||
entry.month_end = 0;
|
||||
entry.year_end = 0;
|
||||
entry.cron_expression = "";
|
||||
entry.created_at = "";
|
||||
entry.deleted_at = "";
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
static ServerScheduledEvents GetServerScheduledEventsEntry(
|
||||
const std::vector<ServerScheduledEvents> &server_scheduled_eventss,
|
||||
int server_scheduled_events_id
|
||||
)
|
||||
{
|
||||
for (auto &server_scheduled_events : server_scheduled_eventss) {
|
||||
if (server_scheduled_events.id == server_scheduled_events_id) {
|
||||
return server_scheduled_events;
|
||||
}
|
||||
}
|
||||
|
||||
return NewEntity();
|
||||
}
|
||||
|
||||
static ServerScheduledEvents FindOne(
|
||||
Database& db,
|
||||
int server_scheduled_events_id
|
||||
)
|
||||
{
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{} WHERE id = {} LIMIT 1",
|
||||
BaseSelect(),
|
||||
server_scheduled_events_id
|
||||
)
|
||||
);
|
||||
|
||||
auto row = results.begin();
|
||||
if (results.RowCount() == 1) {
|
||||
ServerScheduledEvents entry{};
|
||||
|
||||
entry.id = atoi(row[0]);
|
||||
entry.description = row[1] ? row[1] : "";
|
||||
entry.event_type = row[2] ? row[2] : "";
|
||||
entry.event_data = row[3] ? row[3] : "";
|
||||
entry.minute_start = atoi(row[4]);
|
||||
entry.hour_start = atoi(row[5]);
|
||||
entry.day_start = atoi(row[6]);
|
||||
entry.month_start = atoi(row[7]);
|
||||
entry.year_start = atoi(row[8]);
|
||||
entry.minute_end = atoi(row[9]);
|
||||
entry.hour_end = atoi(row[10]);
|
||||
entry.day_end = atoi(row[11]);
|
||||
entry.month_end = atoi(row[12]);
|
||||
entry.year_end = atoi(row[13]);
|
||||
entry.cron_expression = row[14] ? row[14] : "";
|
||||
entry.created_at = row[15] ? row[15] : "";
|
||||
entry.deleted_at = row[16] ? row[16] : "";
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
return NewEntity();
|
||||
}
|
||||
|
||||
static int DeleteOne(
|
||||
Database& db,
|
||||
int server_scheduled_events_id
|
||||
)
|
||||
{
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"DELETE FROM {} WHERE {} = {}",
|
||||
TableName(),
|
||||
PrimaryKey(),
|
||||
server_scheduled_events_id
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() ? results.RowsAffected() : 0);
|
||||
}
|
||||
|
||||
static int UpdateOne(
|
||||
Database& db,
|
||||
ServerScheduledEvents server_scheduled_events_entry
|
||||
)
|
||||
{
|
||||
std::vector<std::string> update_values;
|
||||
|
||||
auto columns = Columns();
|
||||
|
||||
update_values.push_back(columns[1] + " = '" + EscapeString(server_scheduled_events_entry.description) + "'");
|
||||
update_values.push_back(columns[2] + " = '" + EscapeString(server_scheduled_events_entry.event_type) + "'");
|
||||
update_values.push_back(columns[3] + " = '" + EscapeString(server_scheduled_events_entry.event_data) + "'");
|
||||
update_values.push_back(columns[4] + " = " + std::to_string(server_scheduled_events_entry.minute_start));
|
||||
update_values.push_back(columns[5] + " = " + std::to_string(server_scheduled_events_entry.hour_start));
|
||||
update_values.push_back(columns[6] + " = " + std::to_string(server_scheduled_events_entry.day_start));
|
||||
update_values.push_back(columns[7] + " = " + std::to_string(server_scheduled_events_entry.month_start));
|
||||
update_values.push_back(columns[8] + " = " + std::to_string(server_scheduled_events_entry.year_start));
|
||||
update_values.push_back(columns[9] + " = " + std::to_string(server_scheduled_events_entry.minute_end));
|
||||
update_values.push_back(columns[10] + " = " + std::to_string(server_scheduled_events_entry.hour_end));
|
||||
update_values.push_back(columns[11] + " = " + std::to_string(server_scheduled_events_entry.day_end));
|
||||
update_values.push_back(columns[12] + " = " + std::to_string(server_scheduled_events_entry.month_end));
|
||||
update_values.push_back(columns[13] + " = " + std::to_string(server_scheduled_events_entry.year_end));
|
||||
update_values.push_back(columns[14] + " = '" + EscapeString(server_scheduled_events_entry.cron_expression) + "'");
|
||||
update_values.push_back(columns[15] + " = '" + EscapeString(server_scheduled_events_entry.created_at) + "'");
|
||||
update_values.push_back(columns[16] + " = '" + EscapeString(server_scheduled_events_entry.deleted_at) + "'");
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"UPDATE {} SET {} WHERE {} = {}",
|
||||
TableName(),
|
||||
implode(", ", update_values),
|
||||
PrimaryKey(),
|
||||
server_scheduled_events_entry.id
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() ? results.RowsAffected() : 0);
|
||||
}
|
||||
|
||||
static ServerScheduledEvents InsertOne(
|
||||
Database& db,
|
||||
ServerScheduledEvents server_scheduled_events_entry
|
||||
)
|
||||
{
|
||||
std::vector<std::string> insert_values;
|
||||
|
||||
insert_values.push_back(std::to_string(server_scheduled_events_entry.id));
|
||||
insert_values.push_back("'" + EscapeString(server_scheduled_events_entry.description) + "'");
|
||||
insert_values.push_back("'" + EscapeString(server_scheduled_events_entry.event_type) + "'");
|
||||
insert_values.push_back("'" + EscapeString(server_scheduled_events_entry.event_data) + "'");
|
||||
insert_values.push_back(std::to_string(server_scheduled_events_entry.minute_start));
|
||||
insert_values.push_back(std::to_string(server_scheduled_events_entry.hour_start));
|
||||
insert_values.push_back(std::to_string(server_scheduled_events_entry.day_start));
|
||||
insert_values.push_back(std::to_string(server_scheduled_events_entry.month_start));
|
||||
insert_values.push_back(std::to_string(server_scheduled_events_entry.year_start));
|
||||
insert_values.push_back(std::to_string(server_scheduled_events_entry.minute_end));
|
||||
insert_values.push_back(std::to_string(server_scheduled_events_entry.hour_end));
|
||||
insert_values.push_back(std::to_string(server_scheduled_events_entry.day_end));
|
||||
insert_values.push_back(std::to_string(server_scheduled_events_entry.month_end));
|
||||
insert_values.push_back(std::to_string(server_scheduled_events_entry.year_end));
|
||||
insert_values.push_back("'" + EscapeString(server_scheduled_events_entry.cron_expression) + "'");
|
||||
insert_values.push_back("'" + EscapeString(server_scheduled_events_entry.created_at) + "'");
|
||||
insert_values.push_back("'" + EscapeString(server_scheduled_events_entry.deleted_at) + "'");
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{} VALUES ({})",
|
||||
BaseInsert(),
|
||||
implode(",", insert_values)
|
||||
)
|
||||
);
|
||||
|
||||
if (results.Success()) {
|
||||
server_scheduled_events_entry.id = results.LastInsertedID();
|
||||
return server_scheduled_events_entry;
|
||||
}
|
||||
|
||||
server_scheduled_events_entry = NewEntity();
|
||||
|
||||
return server_scheduled_events_entry;
|
||||
}
|
||||
|
||||
static int InsertMany(
|
||||
Database& db,
|
||||
std::vector<ServerScheduledEvents> server_scheduled_events_entries
|
||||
)
|
||||
{
|
||||
std::vector<std::string> insert_chunks;
|
||||
|
||||
for (auto &server_scheduled_events_entry: server_scheduled_events_entries) {
|
||||
std::vector<std::string> insert_values;
|
||||
|
||||
insert_values.push_back(std::to_string(server_scheduled_events_entry.id));
|
||||
insert_values.push_back("'" + EscapeString(server_scheduled_events_entry.description) + "'");
|
||||
insert_values.push_back("'" + EscapeString(server_scheduled_events_entry.event_type) + "'");
|
||||
insert_values.push_back("'" + EscapeString(server_scheduled_events_entry.event_data) + "'");
|
||||
insert_values.push_back(std::to_string(server_scheduled_events_entry.minute_start));
|
||||
insert_values.push_back(std::to_string(server_scheduled_events_entry.hour_start));
|
||||
insert_values.push_back(std::to_string(server_scheduled_events_entry.day_start));
|
||||
insert_values.push_back(std::to_string(server_scheduled_events_entry.month_start));
|
||||
insert_values.push_back(std::to_string(server_scheduled_events_entry.year_start));
|
||||
insert_values.push_back(std::to_string(server_scheduled_events_entry.minute_end));
|
||||
insert_values.push_back(std::to_string(server_scheduled_events_entry.hour_end));
|
||||
insert_values.push_back(std::to_string(server_scheduled_events_entry.day_end));
|
||||
insert_values.push_back(std::to_string(server_scheduled_events_entry.month_end));
|
||||
insert_values.push_back(std::to_string(server_scheduled_events_entry.year_end));
|
||||
insert_values.push_back("'" + EscapeString(server_scheduled_events_entry.cron_expression) + "'");
|
||||
insert_values.push_back("'" + EscapeString(server_scheduled_events_entry.created_at) + "'");
|
||||
insert_values.push_back("'" + EscapeString(server_scheduled_events_entry.deleted_at) + "'");
|
||||
|
||||
insert_chunks.push_back("(" + implode(",", insert_values) + ")");
|
||||
}
|
||||
|
||||
std::vector<std::string> insert_values;
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{} VALUES {}",
|
||||
BaseInsert(),
|
||||
implode(",", insert_chunks)
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() ? results.RowsAffected() : 0);
|
||||
}
|
||||
|
||||
static std::vector<ServerScheduledEvents> All(Database& db)
|
||||
{
|
||||
std::vector<ServerScheduledEvents> all_entries;
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{}",
|
||||
BaseSelect()
|
||||
)
|
||||
);
|
||||
|
||||
all_entries.reserve(results.RowCount());
|
||||
|
||||
for (auto row = results.begin(); row != results.end(); ++row) {
|
||||
ServerScheduledEvents entry{};
|
||||
|
||||
entry.id = atoi(row[0]);
|
||||
entry.description = row[1] ? row[1] : "";
|
||||
entry.event_type = row[2] ? row[2] : "";
|
||||
entry.event_data = row[3] ? row[3] : "";
|
||||
entry.minute_start = atoi(row[4]);
|
||||
entry.hour_start = atoi(row[5]);
|
||||
entry.day_start = atoi(row[6]);
|
||||
entry.month_start = atoi(row[7]);
|
||||
entry.year_start = atoi(row[8]);
|
||||
entry.minute_end = atoi(row[9]);
|
||||
entry.hour_end = atoi(row[10]);
|
||||
entry.day_end = atoi(row[11]);
|
||||
entry.month_end = atoi(row[12]);
|
||||
entry.year_end = atoi(row[13]);
|
||||
entry.cron_expression = row[14] ? row[14] : "";
|
||||
entry.created_at = row[15] ? row[15] : "";
|
||||
entry.deleted_at = row[16] ? row[16] : "";
|
||||
|
||||
all_entries.push_back(entry);
|
||||
}
|
||||
|
||||
return all_entries;
|
||||
}
|
||||
|
||||
static std::vector<ServerScheduledEvents> GetWhere(Database& db, std::string where_filter)
|
||||
{
|
||||
std::vector<ServerScheduledEvents> all_entries;
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{} WHERE {}",
|
||||
BaseSelect(),
|
||||
where_filter
|
||||
)
|
||||
);
|
||||
|
||||
all_entries.reserve(results.RowCount());
|
||||
|
||||
for (auto row = results.begin(); row != results.end(); ++row) {
|
||||
ServerScheduledEvents entry{};
|
||||
|
||||
entry.id = atoi(row[0]);
|
||||
entry.description = row[1] ? row[1] : "";
|
||||
entry.event_type = row[2] ? row[2] : "";
|
||||
entry.event_data = row[3] ? row[3] : "";
|
||||
entry.minute_start = atoi(row[4]);
|
||||
entry.hour_start = atoi(row[5]);
|
||||
entry.day_start = atoi(row[6]);
|
||||
entry.month_start = atoi(row[7]);
|
||||
entry.year_start = atoi(row[8]);
|
||||
entry.minute_end = atoi(row[9]);
|
||||
entry.hour_end = atoi(row[10]);
|
||||
entry.day_end = atoi(row[11]);
|
||||
entry.month_end = atoi(row[12]);
|
||||
entry.year_end = atoi(row[13]);
|
||||
entry.cron_expression = row[14] ? row[14] : "";
|
||||
entry.created_at = row[15] ? row[15] : "";
|
||||
entry.deleted_at = row[16] ? row[16] : "";
|
||||
|
||||
all_entries.push_back(entry);
|
||||
}
|
||||
|
||||
return all_entries;
|
||||
}
|
||||
|
||||
static int DeleteWhere(Database& db, std::string where_filter)
|
||||
{
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"DELETE FROM {} WHERE {}",
|
||||
TableName(),
|
||||
where_filter
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() ? results.RowsAffected() : 0);
|
||||
}
|
||||
|
||||
static int Truncate(Database& db)
|
||||
{
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"TRUNCATE TABLE {}",
|
||||
TableName()
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() ? results.RowsAffected() : 0);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif //EQEMU_BASE_SERVER_SCHEDULED_EVENTS_REPOSITORY_H
|
||||
70
common/repositories/server_scheduled_events_repository.h
Normal file
70
common/repositories/server_scheduled_events_repository.h
Normal file
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* EQEmulator: Everquest Server Emulator
|
||||
* Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY except by those people which sell it, which
|
||||
* are required to give you total support for your newly bought product;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef EQEMU_SERVER_SCHEDULED_EVENTS_REPOSITORY_H
|
||||
#define EQEMU_SERVER_SCHEDULED_EVENTS_REPOSITORY_H
|
||||
|
||||
#include "../database.h"
|
||||
#include "../string_util.h"
|
||||
#include "base/base_server_scheduled_events_repository.h"
|
||||
|
||||
class ServerScheduledEventsRepository: public BaseServerScheduledEventsRepository {
|
||||
public:
|
||||
|
||||
/**
|
||||
* This file was auto generated and can be modified and extended upon
|
||||
*
|
||||
* Base repository methods are automatically
|
||||
* generated in the "base" version of this repository. The base repository
|
||||
* is immutable and to be left untouched, while methods in this class
|
||||
* are used as extension methods for more specific persistence-layer
|
||||
* accessors or mutators.
|
||||
*
|
||||
* Base Methods (Subject to be expanded upon in time)
|
||||
*
|
||||
* Note: Not all tables are designed appropriately to fit functionality with all base methods
|
||||
*
|
||||
* InsertOne
|
||||
* UpdateOne
|
||||
* DeleteOne
|
||||
* FindOne
|
||||
* GetWhere(std::string where_filter)
|
||||
* DeleteWhere(std::string where_filter)
|
||||
* InsertMany
|
||||
* All
|
||||
*
|
||||
* Example custom methods in a repository
|
||||
*
|
||||
* ServerScheduledEventsRepository::GetByZoneAndVersion(int zone_id, int zone_version)
|
||||
* ServerScheduledEventsRepository::GetWhereNeverExpires()
|
||||
* ServerScheduledEventsRepository::GetWhereXAndY()
|
||||
* ServerScheduledEventsRepository::DeleteWhereXAndY()
|
||||
*
|
||||
* Most of the above could be covered by base methods, but if you as a developer
|
||||
* find yourself re-using logic for other parts of the code, its best to just make a
|
||||
* method that can be re-used easily elsewhere especially if it can use a base repository
|
||||
* method and encapsulate filters there
|
||||
*/
|
||||
|
||||
// Custom extended repository methods here
|
||||
|
||||
};
|
||||
|
||||
#endif //EQEMU_SERVER_SCHEDULED_EVENTS_REPOSITORY_H
|
||||
247
common/server_event_scheduler.cpp
Normal file
247
common/server_event_scheduler.cpp
Normal file
@ -0,0 +1,247 @@
|
||||
#include "../common/database.h"
|
||||
#include "../common/string_util.h"
|
||||
#include "server_event_scheduler.h"
|
||||
#include "../common/cron/croncpp.h"
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <time.h>
|
||||
|
||||
ServerEventScheduler::ServerEventScheduler()
|
||||
{
|
||||
m_last_polled_minute = -1;
|
||||
m_events = {};
|
||||
m_active_events = {};
|
||||
}
|
||||
|
||||
ServerEventScheduler::~ServerEventScheduler() = default;
|
||||
|
||||
void ServerEventScheduler::LoadScheduledEvents()
|
||||
{
|
||||
if (!ValidateDatabaseConnection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::time_t time = std::time(nullptr);
|
||||
std::tm *now = std::localtime(&time);
|
||||
|
||||
m_events = ServerScheduledEventsRepository::GetWhere(*m_database, "deleted_at is null");
|
||||
for (auto &e: m_events) {
|
||||
|
||||
auto start = BuildStartTimeFromEvent(e, now);
|
||||
auto end = BuildEndTimeFromEvent(e, now);
|
||||
|
||||
// data excluded from output because it can be very large
|
||||
|
||||
LogScheduler(
|
||||
"Loaded Event ({}) [{}] type [{}] start [{}/{}/{} {:02}:{:02}:00] end [{}/{}/{} {:02}:{:02}:00] cron [{}] created [{}]",
|
||||
e.id,
|
||||
e.description,
|
||||
e.event_type,
|
||||
start.tm_mon + 1,
|
||||
start.tm_mday,
|
||||
start.tm_year + 1900,
|
||||
start.tm_hour,
|
||||
start.tm_min,
|
||||
end.tm_mon + 1,
|
||||
end.tm_mday,
|
||||
end.tm_year + 1900,
|
||||
end.tm_hour,
|
||||
end.tm_min,
|
||||
e.cron_expression,
|
||||
e.created_at
|
||||
);
|
||||
}
|
||||
|
||||
LogScheduler("Loaded scheduled events [{}]", m_events.size());
|
||||
}
|
||||
|
||||
// checks to see if event is ready to be activated
|
||||
bool ServerEventScheduler::ValidateEventReadyToActivate(
|
||||
ServerScheduledEventsRepository::ServerScheduledEvents &e
|
||||
)
|
||||
{
|
||||
|
||||
// if there is a cron expression, it will try to parse it first before falling back to
|
||||
// alternative time logic
|
||||
if (!e.cron_expression.empty()) {
|
||||
try {
|
||||
auto cron = cron::make_cron<cron::cron_standard_traits>(e.cron_expression);
|
||||
std::time_t cron_now = std::time(nullptr);
|
||||
std::time_t cron_next = cron::cron_next(cron, cron_now);
|
||||
|
||||
// we have to pad our now window just a tad so we don't miss the cron window
|
||||
if ((cron_now + 10) >= cron_next) {
|
||||
LogScheduler("Cron time has been met! Event scheduling ({}) [{}]", e.id, e.description);
|
||||
return true;
|
||||
}
|
||||
|
||||
LogSchedulerDetail("Cron now [{}] cron next [{}]\n", cron_now, cron_next);
|
||||
}
|
||||
catch (cron::bad_cronexpr const &ex) {
|
||||
LogScheduler(
|
||||
"Error: Cron expression error [{}] see [https://github.com/mariusbancila/croncpp#cron-expressions]",
|
||||
ex.what()
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::time_t time = std::time(nullptr);
|
||||
std::tm *now = std::localtime(&time);
|
||||
time_t now_time_unix = mktime(now);
|
||||
auto start = BuildStartTimeFromEvent(e, now);
|
||||
auto end = BuildEndTimeFromEvent(e, now);
|
||||
time_t start_time_unix = mktime(&start);
|
||||
|
||||
bool doesnt_end = (
|
||||
e.year_end == 0 &&
|
||||
e.month_end == 0 &&
|
||||
e.day_end == 0 &&
|
||||
e.hour_end == 0 &&
|
||||
e.minute_end == 0
|
||||
);
|
||||
|
||||
time_t end_time_unix;
|
||||
if (!doesnt_end) {
|
||||
end_time_unix = mktime(&end);
|
||||
}
|
||||
|
||||
if (now_time_unix >= start_time_unix && (doesnt_end || now_time_unix < end_time_unix)) {
|
||||
LogSchedulerDetail(
|
||||
"[ValidateEventReadyToActivate] now_time [{}] start_time [{}] doesnt_end [{}] end_time [{}]",
|
||||
now_time_unix,
|
||||
start_time_unix,
|
||||
doesnt_end ? "true" : "false",
|
||||
end_time_unix
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ServerEventScheduler *ServerEventScheduler::SetDatabase(Database *db)
|
||||
{
|
||||
m_database = db;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
bool ServerEventScheduler::ValidateDatabaseConnection()
|
||||
{
|
||||
if (!m_database) {
|
||||
LogError("[ServerEventScheduler::LoadScheduledEvents] No database connection");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// in this function we simply look at events we have internally and events
|
||||
// in the database and determine if any edits have been made
|
||||
// this helps inform decisions to tell all zones to reload their events
|
||||
bool ServerEventScheduler::CheckIfEventsChanged()
|
||||
{
|
||||
auto events = ServerScheduledEventsRepository::GetWhere(*m_database, "deleted_at is null");
|
||||
|
||||
// first check if the size changed, if it did this is the easiest step
|
||||
if (m_events.size() != events.size()) {
|
||||
LogSchedulerDetail("[CheckIfEventsChanged] Event size has changed");
|
||||
m_events = events;
|
||||
return true;
|
||||
}
|
||||
|
||||
// compare fields of database fields to internal events to see if any fields changed
|
||||
for (auto &e: m_events) {
|
||||
for (auto &dbe: events) {
|
||||
if (dbe.id == e.id) {
|
||||
if (
|
||||
dbe.description != e.description ||
|
||||
dbe.event_type != e.event_type ||
|
||||
dbe.event_data != e.event_data ||
|
||||
dbe.minute_start != e.minute_start ||
|
||||
dbe.hour_start != e.hour_start ||
|
||||
dbe.day_start != e.day_start ||
|
||||
dbe.month_start != e.month_start ||
|
||||
dbe.year_start != e.year_start ||
|
||||
dbe.minute_end != e.minute_end ||
|
||||
dbe.hour_end != e.hour_end ||
|
||||
dbe.day_end != e.day_end ||
|
||||
dbe.month_end != e.month_end ||
|
||||
dbe.year_end != e.year_end ||
|
||||
dbe.cron_expression != e.cron_expression ||
|
||||
dbe.created_at != e.created_at ||
|
||||
dbe.deleted_at != e.deleted_at
|
||||
) {
|
||||
LogSchedulerDetail("[CheckIfEventsChanged] Field change detected");
|
||||
m_events = events;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// checks if event is active
|
||||
bool ServerEventScheduler::IsEventActive(ServerScheduledEventsRepository::ServerScheduledEvents &e)
|
||||
{
|
||||
for (auto &a: m_active_events) {
|
||||
if (a.id == e.id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ServerEventScheduler::RemoveActiveEvent(ServerScheduledEventsRepository::ServerScheduledEvents &e)
|
||||
{
|
||||
m_active_events.erase(
|
||||
std::remove_if(
|
||||
m_active_events.begin(),
|
||||
m_active_events.end(),
|
||||
[&](ServerScheduledEventsRepository::ServerScheduledEvents const &active_event) {
|
||||
return active_event.id == e.id;
|
||||
}
|
||||
),
|
||||
m_active_events.end());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::tm ServerEventScheduler::BuildStartTimeFromEvent(
|
||||
ServerScheduledEventsRepository::ServerScheduledEvents &e,
|
||||
std::tm *now
|
||||
)
|
||||
{
|
||||
struct tm time{};
|
||||
time.tm_year = ((e.year_start > 0) ? e.year_start - 1900 : now->tm_year);
|
||||
time.tm_mon = ((e.month_start > 0) ? e.month_start - 1 : now->tm_mon);
|
||||
time.tm_mday = ((e.day_start > 0) ? e.day_start : now->tm_mday);
|
||||
time.tm_hour = ((e.hour_start > 0) ? e.hour_start : now->tm_hour);
|
||||
time.tm_min = ((e.minute_start > 0) ? e.minute_start : now->tm_min);
|
||||
time.tm_sec = 0;
|
||||
time.tm_isdst = now->tm_isdst;
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
std::tm ServerEventScheduler::BuildEndTimeFromEvent(
|
||||
ServerScheduledEventsRepository::ServerScheduledEvents &e,
|
||||
std::tm *now
|
||||
)
|
||||
{
|
||||
struct tm time{};
|
||||
time.tm_year = ((e.year_end > 0) ? e.year_end - 1900 : now->tm_year);
|
||||
time.tm_mon = ((e.month_end > 0) ? e.month_end - 1 : now->tm_mon);
|
||||
time.tm_mday = ((e.day_end > 0) ? e.day_end : now->tm_mday);
|
||||
time.tm_hour = ((e.hour_end > 0) ? e.hour_end : now->tm_hour);
|
||||
time.tm_min = ((e.minute_end > 0) ? e.minute_end : now->tm_min);
|
||||
time.tm_sec = 0;
|
||||
time.tm_isdst = now->tm_isdst;
|
||||
|
||||
return time;
|
||||
}
|
||||
57
common/server_event_scheduler.h
Normal file
57
common/server_event_scheduler.h
Normal file
@ -0,0 +1,57 @@
|
||||
#ifndef EQEMU_SERVER_EVENT_SCHEDULER_H
|
||||
#define EQEMU_SERVER_EVENT_SCHEDULER_H
|
||||
|
||||
#include "../common/repositories/server_scheduled_events_repository.h"
|
||||
#include <time.h>
|
||||
#include <chrono>
|
||||
|
||||
namespace ServerEvents {
|
||||
static const std::string EVENT_TYPE_HOT_ZONE_ACTIVE = "hot_zone_activate";
|
||||
static const std::string EVENT_TYPE_BROADCAST = "broadcast";
|
||||
static const std::string EVENT_TYPE_RELOAD_WORLD = "reload_world";
|
||||
static const std::string EVENT_TYPE_RULE_CHANGE = "rule_change";
|
||||
static const std::string EVENT_TYPE_CONTENT_FLAG_CHANGE = "content_flag_change";
|
||||
}
|
||||
|
||||
class ServerEventScheduler {
|
||||
public:
|
||||
virtual ~ServerEventScheduler();
|
||||
ServerEventScheduler();
|
||||
ServerEventScheduler *SetDatabase(Database *db);
|
||||
void LoadScheduledEvents();
|
||||
bool CheckIfEventsChanged();
|
||||
|
||||
protected:
|
||||
|
||||
// events directly from the database
|
||||
std::vector<ServerScheduledEventsRepository::ServerScheduledEvents> m_events;
|
||||
|
||||
// used to track only when it is convenient to undo an action from an active event
|
||||
// typically there should be two separate events to turn something on / off
|
||||
// hotzones use this right now simply to keep us from toggling off the hotzone
|
||||
// every minute we trigger and then immediately turning it right back on
|
||||
std::vector<ServerScheduledEventsRepository::ServerScheduledEvents> m_active_events;
|
||||
|
||||
// simple ticker used to determine when the last polled minute was so that when the minute
|
||||
// changes we fire checking the scheduler
|
||||
int m_last_polled_minute;
|
||||
|
||||
// validates an event is currently active or not
|
||||
bool ValidateEventReadyToActivate(ServerScheduledEventsRepository::ServerScheduledEvents &e);
|
||||
|
||||
// is event active
|
||||
bool IsEventActive(ServerScheduledEventsRepository::ServerScheduledEvents &e);
|
||||
|
||||
// remove active event
|
||||
bool RemoveActiveEvent(ServerScheduledEventsRepository::ServerScheduledEvents &e);
|
||||
|
||||
// build time object from event
|
||||
std::tm BuildStartTimeFromEvent(ServerScheduledEventsRepository::ServerScheduledEvents &e, tm *now);
|
||||
std::tm BuildEndTimeFromEvent(ServerScheduledEventsRepository::ServerScheduledEvents &e, tm *now);
|
||||
|
||||
// reference to database
|
||||
Database *m_database;
|
||||
bool ValidateDatabaseConnection();
|
||||
};
|
||||
|
||||
#endif //EQEMU_SERVER_EVENT_SCHEDULER_H
|
||||
@ -222,6 +222,7 @@
|
||||
#define ServerOP_UCSServerStatusRequest 0x4009
|
||||
#define ServerOP_UCSServerStatusReply 0x4010
|
||||
#define ServerOP_HotReloadQuests 0x4011
|
||||
#define ServerOP_UpdateSchedulerEvents 0x4012
|
||||
|
||||
#define ServerOP_CZCastSpellPlayer 0x4500
|
||||
#define ServerOP_CZCastSpellGroup 0x4501
|
||||
@ -320,7 +321,7 @@
|
||||
#define ServerOP_QSPlayerDropItem 0x5007
|
||||
|
||||
/* Query Serv Generic Packet Flag/Type Enumeration */
|
||||
enum { QSG_LFGuild = 0 };
|
||||
enum { QSG_LFGuild = 0 };
|
||||
enum { QSG_LFGuild_PlayerMatches = 0, QSG_LFGuild_UpdatePlayerInfo, QSG_LFGuild_RequestPlayerInfo, QSG_LFGuild_UpdateGuildInfo, QSG_LFGuild_GuildMatches,
|
||||
QSG_LFGuild_RequestGuildInfo };
|
||||
|
||||
@ -1933,7 +1934,7 @@ struct WWRemoveTask_Struct {
|
||||
uint32 task_id;
|
||||
uint8 min_status;
|
||||
uint8 max_status;
|
||||
|
||||
|
||||
};
|
||||
|
||||
struct WWResetActivity_Struct {
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
|
||||
*/
|
||||
|
||||
#define CURRENT_BINARY_DATABASE_VERSION 9161
|
||||
#define CURRENT_BINARY_DATABASE_VERSION 9162
|
||||
|
||||
#ifdef BOTS
|
||||
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9027
|
||||
|
||||
@ -237,7 +237,7 @@ foreach my $table_to_generate (@tables) {
|
||||
elsif ($column_default eq "''") {
|
||||
$default_value = '""';
|
||||
}
|
||||
elsif ((trim($column_default) eq "" || $column_default eq "NULL") && $column_type =~ /text|varchar/i) {
|
||||
elsif ((trim($column_default) eq "" || $column_default eq "NULL") && $column_type =~ /text|varchar|datetime/i) {
|
||||
$default_value = '""';
|
||||
}
|
||||
|
||||
|
||||
@ -415,6 +415,7 @@
|
||||
9159|2020_12_22_expedition_system.sql|SELECT * FROM db_version WHERE version >= 9159|empty|
|
||||
9160|2021_02_14_npc_exp_mod.sql|SHOW COLUMNS from `npc_types` LIKE 'exp_mod'|empty|
|
||||
9161|2021_02_15_npc_spell_entries_unsigned.sql|SELECT * FROM db_version WHERE version >= 9161|empty|
|
||||
9162|2021_02_17_server_scheduled_events|SELECT * FROM db_version WHERE version >= 9162|empty|
|
||||
|
||||
# Upgrade conditions:
|
||||
# This won't be needed after this system is implemented, but it is used database that are not
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
CREATE TABLE `server_scheduled_events`
|
||||
(
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`description` varchar(255) DEFAULT NULL,
|
||||
`event_type` varchar(100) DEFAULT NULL,
|
||||
`event_data` text DEFAULT NULL,
|
||||
`minute_start` int(11) DEFAULT 0,
|
||||
`hour_start` int(11) DEFAULT 0,
|
||||
`day_start` int(11) DEFAULT 0,
|
||||
`month_start` int(11) DEFAULT 0,
|
||||
`year_start` int(11) DEFAULT 0,
|
||||
`minute_end` int(11) DEFAULT 0,
|
||||
`hour_end` int(11) DEFAULT 0,
|
||||
`day_end` int(11) DEFAULT 0,
|
||||
`month_end` int(11) DEFAULT 0,
|
||||
`year_end` int(11) DEFAULT 0,
|
||||
`cron_expression` varchar(100) DEFAULT NULL,
|
||||
`created_at` datetime DEFAULT NULL,
|
||||
`deleted_at` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
|
||||
@ -25,6 +25,7 @@ SET(world_sources
|
||||
web_interface.cpp
|
||||
web_interface_eqw.cpp
|
||||
wguild_mgr.cpp
|
||||
world_event_scheduler.cpp
|
||||
world_config.cpp
|
||||
world_console_connection.cpp
|
||||
world_server_command_handler.cpp
|
||||
@ -65,6 +66,7 @@ SET(world_headers
|
||||
world_tcp_connection.h
|
||||
world_server_command_handler.h
|
||||
worlddb.h
|
||||
world_event_scheduler.h
|
||||
world_store.h
|
||||
zonelist.h
|
||||
zoneserver.h
|
||||
|
||||
@ -97,6 +97,7 @@ union semun {
|
||||
#include "../common/content/world_content_service.h"
|
||||
#include "../common/repositories/merchantlist_temp_repository.h"
|
||||
#include "world_store.h"
|
||||
#include "world_event_scheduler.h"
|
||||
|
||||
WorldStore world_store;
|
||||
ClientList client_list;
|
||||
@ -107,6 +108,7 @@ UCSConnection UCSLink;
|
||||
QueryServConnection QSLink;
|
||||
LauncherList launcher_list;
|
||||
AdventureManager adventure_manager;
|
||||
WorldEventScheduler event_scheduler;
|
||||
EQ::Random emu_random;
|
||||
volatile bool RunLoops = true;
|
||||
uint32 numclients = 0;
|
||||
@ -442,6 +444,8 @@ int main(int argc, char** argv) {
|
||||
content_db.LoadCharacterCreateAllocations();
|
||||
content_db.LoadCharacterCreateCombos();
|
||||
|
||||
event_scheduler.SetDatabase(&database)->LoadScheduledEvents();
|
||||
|
||||
std::unique_ptr<EQ::Net::ConsoleServer> console;
|
||||
if (Config->TelnetEnabled) {
|
||||
LogInfo("Console (TCP) listener started");
|
||||
@ -603,6 +607,8 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
|
||||
event_scheduler.Process(&zoneserver_list);
|
||||
|
||||
client_list.Process();
|
||||
|
||||
if (PurgeInstanceTimer.Check()) {
|
||||
|
||||
64
world/world_event_scheduler.cpp
Normal file
64
world/world_event_scheduler.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
#include "world_event_scheduler.h"
|
||||
#include "../common/servertalk.h"
|
||||
|
||||
void WorldEventScheduler::Process(ZSList *zs_list)
|
||||
{
|
||||
std::time_t time = std::time(nullptr);
|
||||
std::tm *now = std::localtime(&time);
|
||||
|
||||
// once a minute polling
|
||||
if (m_last_polled_minute != now->tm_min) {
|
||||
|
||||
// refresh; world polls and tells zones if they should update if there is a change
|
||||
if (CheckIfEventsChanged()) {
|
||||
LogSchedulerDetail("Event changes detected, forcing zones to refresh their schedules...");
|
||||
auto pack = new ServerPacket(ServerOP_UpdateSchedulerEvents, 0);
|
||||
zs_list->SendPacket(pack);
|
||||
safe_delete(pack);
|
||||
}
|
||||
|
||||
int month = (now->tm_mon + 1);
|
||||
int year = (now->tm_year + 1900);
|
||||
|
||||
LogSchedulerDetail(
|
||||
"Polling year [{}] month [{}] day [{}] hour [{}] minute [{}]",
|
||||
year,
|
||||
month,
|
||||
now->tm_mday,
|
||||
now->tm_hour,
|
||||
now->tm_min
|
||||
);
|
||||
|
||||
for (auto &e: m_events) {
|
||||
|
||||
// discard uninteresting events as its less work to calculate time on events we don't care about
|
||||
// different processes are interested in different events
|
||||
if (
|
||||
e.event_type != ServerEvents::EVENT_TYPE_BROADCAST &&
|
||||
e.event_type != ServerEvents::EVENT_TYPE_RELOAD_WORLD
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// validate event is ready to activate and run it
|
||||
if (ValidateEventReadyToActivate(e)) {
|
||||
if (e.event_type == ServerEvents::EVENT_TYPE_BROADCAST) {
|
||||
LogScheduler("Sending broadcast [{}]", e.event_data.c_str());
|
||||
zs_list->SendEmoteMessage(nullptr, 0, 0, 15, e.event_data.c_str());
|
||||
}
|
||||
|
||||
if (e.event_type == ServerEvents::EVENT_TYPE_RELOAD_WORLD) {
|
||||
LogScheduler("Sending reload world event [{}]", e.event_data.c_str());
|
||||
|
||||
auto pack = new ServerPacket(ServerOP_ReloadWorld, sizeof(ReloadWorld_Struct));
|
||||
auto *reload_world = (ReloadWorld_Struct *) pack->pBuffer;
|
||||
reload_world->Option = 1;
|
||||
zs_list->SendPacket(pack);
|
||||
safe_delete(pack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_last_polled_minute = now->tm_min;
|
||||
}
|
||||
}
|
||||
12
world/world_event_scheduler.h
Normal file
12
world/world_event_scheduler.h
Normal file
@ -0,0 +1,12 @@
|
||||
#ifndef EQEMU_EVENT_SCHEDULER_H
|
||||
#define EQEMU_EVENT_SCHEDULER_H
|
||||
|
||||
#include "../common/server_event_scheduler.h"
|
||||
#include "zonelist.h"
|
||||
|
||||
class WorldEventScheduler : public ServerEventScheduler {
|
||||
public:
|
||||
void Process(ZSList *zs_list);
|
||||
};
|
||||
|
||||
#endif //EQEMU_EVENT_SCHEDULER_H
|
||||
@ -152,6 +152,7 @@ SET(zone_sources
|
||||
zone.cpp
|
||||
zone_config.cpp
|
||||
zonedb.cpp
|
||||
zone_event_scheduler.cpp
|
||||
zone_reload.cpp
|
||||
zone_store.cpp
|
||||
zoning.cpp)
|
||||
@ -265,6 +266,7 @@ SET(zone_headers
|
||||
worldserver.h
|
||||
xtargetautohaters.h
|
||||
zone.h
|
||||
zone_event_scheduler.h
|
||||
zone_config.h
|
||||
zonedb.h
|
||||
zonedump.h
|
||||
|
||||
@ -78,6 +78,7 @@
|
||||
#include <pthread.h>
|
||||
#include "../common/unix.h"
|
||||
#include "zone_store.h"
|
||||
#include "zone_event_scheduler.h"
|
||||
|
||||
#endif
|
||||
|
||||
@ -100,8 +101,9 @@ QueryServ *QServ = 0;
|
||||
TaskManager *task_manager = 0;
|
||||
NpcScaleManager *npc_scale_manager;
|
||||
QuestParserCollection *parse = 0;
|
||||
EQEmuLogSys LogSys;
|
||||
WorldContentService content_service;
|
||||
EQEmuLogSys LogSys;
|
||||
ZoneEventScheduler event_scheduler;
|
||||
WorldContentService content_service;
|
||||
const SPDat_Spell_Struct* spells;
|
||||
int32 SPDAT_RECORDS = -1;
|
||||
const ZoneConfig *Config;
|
||||
@ -387,6 +389,8 @@ int main(int argc, char** argv) {
|
||||
|
||||
ZoneStore::LoadContentFlags();
|
||||
|
||||
event_scheduler.SetDatabase(&database)->LoadScheduledEvents();
|
||||
|
||||
#ifdef BOTS
|
||||
LogInfo("Loading bot commands");
|
||||
int botretval = bot_command_init();
|
||||
@ -430,6 +434,7 @@ int main(int argc, char** argv) {
|
||||
parse->ReloadQuests();
|
||||
|
||||
worldserver.Connect();
|
||||
worldserver.SetScheduler(&event_scheduler);
|
||||
|
||||
Timer InterserverTimer(INTERSERVER_TIMER); // does MySQL pings and auto-reconnect
|
||||
#ifdef EQPROFILE
|
||||
@ -541,11 +546,11 @@ int main(int argc, char** argv) {
|
||||
entity_list.CorpseProcess();
|
||||
entity_list.TrapProcess();
|
||||
entity_list.RaidProcess();
|
||||
|
||||
entity_list.Process();
|
||||
entity_list.MobProcess();
|
||||
entity_list.BeaconProcess();
|
||||
entity_list.EncounterProcess();
|
||||
event_scheduler.Process(zone, &content_service);
|
||||
|
||||
if (zone) {
|
||||
if (!zone->Process()) {
|
||||
|
||||
@ -55,7 +55,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "zone_config.h"
|
||||
#include "zone_reload.h"
|
||||
|
||||
|
||||
extern EntityList entity_list;
|
||||
extern Zone* zone;
|
||||
extern volatile bool is_zone_loaded;
|
||||
@ -2815,6 +2814,15 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
|
||||
break;
|
||||
}
|
||||
|
||||
case ServerOP_UpdateSchedulerEvents: {
|
||||
LogScheduler("Received signal from world to update");
|
||||
if (m_zone_scheduler) {
|
||||
m_zone_scheduler->LoadScheduledEvents();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ServerOP_HotReloadQuests:
|
||||
{
|
||||
if (!zone) {
|
||||
@ -3301,3 +3309,13 @@ void WorldServer::OnKeepAlive(EQ::Timer *t)
|
||||
ServerPacket pack(ServerOP_KeepAlive, 0);
|
||||
SendPacket(&pack);
|
||||
}
|
||||
|
||||
ZoneEventScheduler *WorldServer::GetScheduler() const
|
||||
{
|
||||
return m_zone_scheduler;
|
||||
}
|
||||
|
||||
void WorldServer::SetScheduler(ZoneEventScheduler *scheduler)
|
||||
{
|
||||
WorldServer::m_zone_scheduler = scheduler;
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
|
||||
#include "../common/eq_packet_structs.h"
|
||||
#include "../common/net/servertalk_client_connection.h"
|
||||
#include "zone_event_scheduler.h"
|
||||
|
||||
class ServerPacket;
|
||||
class EQApplicationPacket;
|
||||
@ -76,6 +77,11 @@ private:
|
||||
|
||||
std::unique_ptr<EQ::Net::ServertalkClient> m_connection;
|
||||
std::unique_ptr<EQ::Timer> m_keepalive;
|
||||
|
||||
ZoneEventScheduler *m_zone_scheduler;
|
||||
public:
|
||||
ZoneEventScheduler *GetScheduler() const;
|
||||
void SetScheduler(ZoneEventScheduler *scheduler);
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
@ -941,7 +941,6 @@ Zone::Zone(uint32 in_zoneid, uint32 in_instanceid, const char* in_short_name)
|
||||
spawn2_timer(1000),
|
||||
hot_reload_timer(1000),
|
||||
qglobal_purge_timer(30000),
|
||||
hotzone_timer(120000),
|
||||
m_SafePoint(0.0f,0.0f,0.0f),
|
||||
m_Graveyard(0.0f,0.0f,0.0f,0.0f)
|
||||
{
|
||||
@ -1582,8 +1581,6 @@ bool Zone::Process() {
|
||||
}
|
||||
}
|
||||
|
||||
if(hotzone_timer.Check()) { UpdateHotzone(); }
|
||||
|
||||
mMovementManager->Process();
|
||||
|
||||
return true;
|
||||
@ -2602,19 +2599,9 @@ uint32 Zone::GetSpawnKillCount(uint32 in_spawnid) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Zone::UpdateHotzone()
|
||||
void Zone::SetIsHotzone(bool is_hotzone)
|
||||
{
|
||||
std::string query = StringFormat("SELECT hotzone FROM zone WHERE short_name = '%s'", GetShortName());
|
||||
auto results = content_db.QueryDatabase(query);
|
||||
if (!results.Success())
|
||||
return;
|
||||
|
||||
if (results.RowCount() == 0)
|
||||
return;
|
||||
|
||||
auto row = results.begin();
|
||||
|
||||
is_hotzone = atoi(row[0]) == 0 ? false: true;
|
||||
Zone::is_hotzone = is_hotzone;
|
||||
}
|
||||
|
||||
void Zone::RequestUCSServerStatus() {
|
||||
|
||||
@ -289,7 +289,6 @@ public:
|
||||
void SpawnConditionChanged(const SpawnCondition &c, int16 old_value);
|
||||
void SpawnStatus(Mob *client);
|
||||
void StartShutdownTimer(uint32 set_time = (RuleI(Zone, AutoShutdownDelay)));
|
||||
void UpdateHotzone();
|
||||
void UpdateQGlobal(uint32 qid, QGlobal newGlobal);
|
||||
void weatherSend(Client *client = nullptr);
|
||||
|
||||
@ -356,6 +355,7 @@ public:
|
||||
*/
|
||||
void mod_init();
|
||||
void mod_repop();
|
||||
void SetIsHotzone(bool is_hotzone);
|
||||
|
||||
private:
|
||||
bool allow_mercs;
|
||||
@ -401,7 +401,6 @@ private:
|
||||
Timer *Weather_Timer;
|
||||
Timer autoshutdown_timer;
|
||||
Timer clientauth_timer;
|
||||
Timer hotzone_timer;
|
||||
Timer initgrids_timer;
|
||||
Timer qglobal_purge_timer;
|
||||
ZoneSpellsBlocked *blocked_spells;
|
||||
|
||||
165
zone/zone_event_scheduler.cpp
Normal file
165
zone/zone_event_scheduler.cpp
Normal file
@ -0,0 +1,165 @@
|
||||
#include "zone_event_scheduler.h"
|
||||
#include "../common/rulesys.h"
|
||||
|
||||
void ZoneEventScheduler::Process(Zone *zone, WorldContentService *content_service)
|
||||
{
|
||||
std::time_t time = std::time(nullptr);
|
||||
std::tm *now = std::localtime(&time);
|
||||
|
||||
// once a minute polling
|
||||
if (m_last_polled_minute != now->tm_min) {
|
||||
int month = (now->tm_mon + 1);
|
||||
int year = (now->tm_year + 1900);
|
||||
|
||||
LogSchedulerDetail(
|
||||
"Polling year [{}] month [{}] day [{}] hour [{}] minute [{}]",
|
||||
year,
|
||||
month,
|
||||
now->tm_mday,
|
||||
now->tm_hour,
|
||||
now->tm_min
|
||||
);
|
||||
|
||||
// because stored active events could have a reference of time that has been changed since
|
||||
// the time has been updated, we need to make sure we update internal fields so that
|
||||
// the scheduler can properly end events if we set a new end date
|
||||
SyncEventDataWithActiveEvents();
|
||||
|
||||
// active events
|
||||
for (auto &e: m_active_events) {
|
||||
LogSchedulerDetail("Looping active event [{}]", e.description);
|
||||
|
||||
// if event becomes no longer active
|
||||
if (!ValidateEventReadyToActivate(e)) {
|
||||
LogSchedulerDetail("Looping active event validated [{}]", e.event_type);
|
||||
if (e.event_type == ServerEvents::EVENT_TYPE_HOT_ZONE_ACTIVE) {
|
||||
LogScheduler("Deactivating event [{}] disabling hotzone status", e.description);
|
||||
for (auto &short_name: split(e.event_data, ',')) {
|
||||
if (zone->GetShortName() == short_name) {
|
||||
zone->SetIsHotzone(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
RemoveActiveEvent(e);
|
||||
}
|
||||
|
||||
if (e.event_type == ServerEvents::EVENT_TYPE_RULE_CHANGE) {
|
||||
LogScheduler("Deactivating event [{}] resetting rules to normal", e.description);
|
||||
RuleManager::Instance()->LoadRules(m_database, RuleManager::Instance()->GetActiveRuleset(), true);
|
||||
|
||||
// force active events clear and reapply all active events because we reset the entire state
|
||||
// ideally if we could revert only the state of which was originally set we would only remove one active event
|
||||
m_active_events.clear();
|
||||
}
|
||||
|
||||
if (e.event_type == ServerEvents::EVENT_TYPE_CONTENT_FLAG_CHANGE) {
|
||||
auto flag_name = e.event_data;
|
||||
if (!flag_name.empty()) {
|
||||
LogScheduler("Deactivating event [{}] resetting content flags", e.description);
|
||||
content_service->ReloadContentFlags(*m_database);
|
||||
}
|
||||
|
||||
// force active events clear and reapply all active events because we reset the entire state
|
||||
// ideally if we could revert only the state of which was originally set we would only remove one active event
|
||||
m_active_events.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for active
|
||||
for (auto &e: m_events) {
|
||||
|
||||
// discard uninteresting events as its less work to calculate time on events we don't care about
|
||||
// different processes are interested in different events
|
||||
if (
|
||||
e.event_type != ServerEvents::EVENT_TYPE_HOT_ZONE_ACTIVE &&
|
||||
e.event_type != ServerEvents::EVENT_TYPE_CONTENT_FLAG_CHANGE &&
|
||||
e.event_type != ServerEvents::EVENT_TYPE_RULE_CHANGE
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// the scheduler as of today manipulates events in memory and is preferred to be that way
|
||||
// the scheduler changes temporary "state" in the server for a period of time for things such as
|
||||
// hotzone activation, content flag activation, rule value activation
|
||||
// when these events expire, the events become untoggled in memory
|
||||
// there can be support for one-time events that are more suitable to run from worlds scheduler
|
||||
// such as broadcasts, reloads
|
||||
if (ValidateEventReadyToActivate(e) && !IsEventActive(e)) {
|
||||
if (e.event_type == ServerEvents::EVENT_TYPE_HOT_ZONE_ACTIVE) {
|
||||
for (auto &short_name: split(e.event_data, ',')) {
|
||||
if (zone->GetShortName() == short_name) {
|
||||
zone->SetIsHotzone(true);
|
||||
LogScheduler("Activating Event [{}] Enabling zone as hotzone", e.description);
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_active_events.push_back(e);
|
||||
}
|
||||
|
||||
if (e.event_type == ServerEvents::EVENT_TYPE_RULE_CHANGE) {
|
||||
auto params = split(e.event_data, '=');
|
||||
auto rule_key = params[0];
|
||||
auto rule_value = params[1];
|
||||
if (!rule_key.empty() && !rule_value.empty()) {
|
||||
LogScheduler(
|
||||
"Activating Event [{}] scheduled rule change, setting rule [{}] to [{}]",
|
||||
e.description,
|
||||
rule_key,
|
||||
rule_value
|
||||
);
|
||||
RuleManager::Instance()->SetRule(rule_key.c_str(), rule_value.c_str(), nullptr, false, true);
|
||||
}
|
||||
m_active_events.push_back(e);
|
||||
}
|
||||
|
||||
if (e.event_type == ServerEvents::EVENT_TYPE_CONTENT_FLAG_CHANGE) {
|
||||
auto flag_name = e.event_data;
|
||||
if (!flag_name.empty()) {
|
||||
LogScheduler(
|
||||
"Activating Event [{}] scheduled content flag change, setting flag [{}] to enabled",
|
||||
e.description,
|
||||
flag_name
|
||||
);
|
||||
|
||||
auto flags = content_service->GetContentFlags();
|
||||
flags.push_back(flag_name);
|
||||
content_service->SetContentFlags(flags);
|
||||
m_active_events.push_back(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_last_polled_minute = now->tm_min;
|
||||
}
|
||||
}
|
||||
|
||||
// because stored active events could have a reference of time that has been changed since
|
||||
// the time has been updated, we need to make sure we update internal fields so that
|
||||
// the scheduler can properly end events if we set a new end date
|
||||
void ZoneEventScheduler::SyncEventDataWithActiveEvents()
|
||||
{
|
||||
for (auto &a: m_active_events) {
|
||||
for (auto &e: m_events) {
|
||||
if (e.id == a.id) {
|
||||
a.description = e.description;
|
||||
a.event_type = e.event_type;
|
||||
a.event_data = e.event_data;
|
||||
a.minute_start = e.minute_start;
|
||||
a.hour_start = e.hour_start;
|
||||
a.day_start = e.day_start;
|
||||
a.month_start = e.month_start;
|
||||
a.year_start = e.year_start;
|
||||
a.minute_end = e.minute_end;
|
||||
a.hour_end = e.hour_end;
|
||||
a.day_end = e.day_end;
|
||||
a.month_end = e.month_end;
|
||||
a.year_end = e.year_end;
|
||||
a.cron_expression = e.cron_expression;
|
||||
a.created_at = e.created_at;
|
||||
a.deleted_at = e.deleted_at;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
zone/zone_event_scheduler.h
Normal file
14
zone/zone_event_scheduler.h
Normal file
@ -0,0 +1,14 @@
|
||||
#ifndef EQEMU_ZONE_EVENT_SCHEDULER_H
|
||||
#define EQEMU_ZONE_EVENT_SCHEDULER_H
|
||||
|
||||
#include "../common/server_event_scheduler.h"
|
||||
#include "zone.h"
|
||||
#include "../common/content/world_content_service.h"
|
||||
|
||||
class ZoneEventScheduler : public ServerEventScheduler {
|
||||
public:
|
||||
void Process(Zone *zone, WorldContentService *content_service);
|
||||
void SyncEventDataWithActiveEvents();
|
||||
};
|
||||
|
||||
#endif //EQEMU_ZONE_EVENT_SCHEDULER_H
|
||||
@ -146,20 +146,7 @@ ZoneRepository::Zone ZoneStore::GetZone(const char *in_zone_name)
|
||||
*/
|
||||
void ZoneStore::LoadContentFlags()
|
||||
{
|
||||
std::vector<std::string> set_content_flags;
|
||||
auto content_flags = ContentFlagsRepository::GetWhere(database, "enabled = 1");
|
||||
|
||||
set_content_flags.reserve(content_flags.size());
|
||||
for (auto &flags: content_flags) {
|
||||
set_content_flags.push_back(flags.flag_name);
|
||||
}
|
||||
|
||||
LogInfo(
|
||||
"Enabled content flags [{}]",
|
||||
implode(", ", set_content_flags)
|
||||
);
|
||||
|
||||
content_service.SetContentFlags(set_content_flags);
|
||||
content_service.ReloadContentFlags(database);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user