1/**
2 * The Forgotten Server - a free and open-source MMORPG server emulator
3 * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#include "otpch.h"
21
22#include "configmanager.h"
23#include "globalevent.h"
24#include "tools.h"
25#include "scheduler.h"
26#include "pugicast.h"
27
28extern ConfigManager g_config;
29
30GlobalEvents::GlobalEvents() :
31 scriptInterface("GlobalEvent Interface")
32{
33 scriptInterface.initState();
34}
35
36GlobalEvents::~GlobalEvents()
37{
38 clear(false);
39}
40
41void GlobalEvents::clearMap(GlobalEventMap& map, bool fromLua)
42{
43 for (auto it = map.begin(); it != map.end(); ) {
44 if (fromLua == it->second.fromLua) {
45 it = map.erase(it);
46 } else {
47 ++it;
48 }
49 }
50}
51
52void GlobalEvents::clear(bool fromLua)
53{
54 g_scheduler.stopEvent(thinkEventId);
55 thinkEventId = 0;
56 g_scheduler.stopEvent(timerEventId);
57 timerEventId = 0;
58
59 clearMap(thinkMap, fromLua);
60 clearMap(serverMap, fromLua);
61 clearMap(timerMap, fromLua);
62
63 reInitState(fromLua);
64}
65
66Event_ptr GlobalEvents::getEvent(const std::string& nodeName)
67{
68 if (strcasecmp(nodeName.c_str(), "globalevent") != 0) {
69 return nullptr;
70 }
71 return Event_ptr(new GlobalEvent(&scriptInterface));
72}
73
74bool GlobalEvents::registerEvent(Event_ptr event, const pugi::xml_node&)
75{
76 GlobalEvent_ptr globalEvent{static_cast<GlobalEvent*>(event.release())}; //event is guaranteed to be a GlobalEvent
77 if (globalEvent->getEventType() == GLOBALEVENT_TIMER) {
78 auto result = timerMap.emplace(globalEvent->getName(), std::move(*globalEvent));
79 if (result.second) {
80 if (timerEventId == 0) {
81 timerEventId = g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, std::bind(&GlobalEvents::timer, this)));
82 }
83 return true;
84 }
85 } else if (globalEvent->getEventType() != GLOBALEVENT_NONE) {
86 auto result = serverMap.emplace(globalEvent->getName(), std::move(*globalEvent));
87 if (result.second) {
88 return true;
89 }
90 } else { // think event
91 auto result = thinkMap.emplace(globalEvent->getName(), std::move(*globalEvent));
92 if (result.second) {
93 if (thinkEventId == 0) {
94 thinkEventId = g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, std::bind(&GlobalEvents::think, this)));
95 }
96 return true;
97 }
98 }
99
100 std::cout << "[Warning - GlobalEvents::configureEvent] Duplicate registered globalevent with name: " << globalEvent->getName() << std::endl;
101 return false;
102}
103
104bool GlobalEvents::registerLuaEvent(GlobalEvent* event)
105{
106 GlobalEvent_ptr globalEvent{ event };
107 if (globalEvent->getEventType() == GLOBALEVENT_TIMER) {
108 auto result = timerMap.emplace(globalEvent->getName(), std::move(*globalEvent));
109 if (result.second) {
110 if (timerEventId == 0) {
111 timerEventId = g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, std::bind(&GlobalEvents::timer, this)));
112 }
113 return true;
114 }
115 } else if (globalEvent->getEventType() != GLOBALEVENT_NONE) {
116 auto result = serverMap.emplace(globalEvent->getName(), std::move(*globalEvent));
117 if (result.second) {
118 return true;
119 }
120 } else { // think event
121 auto result = thinkMap.emplace(globalEvent->getName(), std::move(*globalEvent));
122 if (result.second) {
123 if (thinkEventId == 0) {
124 thinkEventId = g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, std::bind(&GlobalEvents::think, this)));
125 }
126 return true;
127 }
128 }
129
130 std::cout << "[Warning - GlobalEvents::configureEvent] Duplicate registered globalevent with name: " << globalEvent->getName() << std::endl;
131 return false;
132}
133
134void GlobalEvents::startup() const
135{
136 execute(GLOBALEVENT_STARTUP);
137}
138
139void GlobalEvents::timer()
140{
141 time_t now = time(nullptr);
142
143 int64_t nextScheduledTime = std::numeric_limits<int64_t>::max();
144
145 auto it = timerMap.begin();
146 while (it != timerMap.end()) {
147 GlobalEvent& globalEvent = it->second;
148
149 int64_t nextExecutionTime = globalEvent.getNextExecution() - now;
150 if (nextExecutionTime > 0) {
151 if (nextExecutionTime < nextScheduledTime) {
152 nextScheduledTime = nextExecutionTime;
153 }
154
155 ++it;
156 continue;
157 }
158
159 if (!globalEvent.executeEvent()) {
160 it = timerMap.erase(it);
161 continue;
162 }
163
164 nextExecutionTime = 86400;
165 if (nextExecutionTime < nextScheduledTime) {
166 nextScheduledTime = nextExecutionTime;
167 }
168
169 globalEvent.setNextExecution(globalEvent.getNextExecution() + nextExecutionTime);
170
171 ++it;
172 }
173
174 if (nextScheduledTime != std::numeric_limits<int64_t>::max()) {
175 timerEventId = g_scheduler.addEvent(createSchedulerTask(std::max<int64_t>(1000, nextScheduledTime * 1000),
176 std::bind(&GlobalEvents::timer, this)));
177 }
178}
179
180void GlobalEvents::think()
181{
182 int64_t now = OTSYS_TIME();
183
184 int64_t nextScheduledTime = std::numeric_limits<int64_t>::max();
185 for (auto& it : thinkMap) {
186 GlobalEvent& globalEvent = it.second;
187
188 int64_t nextExecutionTime = globalEvent.getNextExecution() - now;
189 if (nextExecutionTime > 0) {
190 if (nextExecutionTime < nextScheduledTime) {
191 nextScheduledTime = nextExecutionTime;
192 }
193 continue;
194 }
195
196 if (!globalEvent.executeEvent()) {
197 std::cout << "[Error - GlobalEvents::think] Failed to execute event: " << globalEvent.getName() << std::endl;
198 }
199
200 nextExecutionTime = globalEvent.getInterval();
201 if (nextExecutionTime < nextScheduledTime) {
202 nextScheduledTime = nextExecutionTime;
203 }
204
205 globalEvent.setNextExecution(globalEvent.getNextExecution() + nextExecutionTime);
206 }
207
208 if (nextScheduledTime != std::numeric_limits<int64_t>::max()) {
209 thinkEventId = g_scheduler.addEvent(createSchedulerTask(nextScheduledTime, std::bind(&GlobalEvents::think, this)));
210 }
211}
212
213void GlobalEvents::execute(GlobalEvent_t type) const
214{
215 for (const auto& it : serverMap) {
216 const GlobalEvent& globalEvent = it.second;
217 if (globalEvent.getEventType() == type) {
218 globalEvent.executeEvent();
219 }
220 }
221}
222
223GlobalEventMap GlobalEvents::getEventMap(GlobalEvent_t type)
224{
225 // TODO: This should be better implemented. Maybe have a map for every type.
226 switch (type) {
227 case GLOBALEVENT_NONE: return thinkMap;
228 case GLOBALEVENT_TIMER: return timerMap;
229 case GLOBALEVENT_STARTUP:
230 case GLOBALEVENT_SHUTDOWN:
231 case GLOBALEVENT_RECORD: {
232 GlobalEventMap retMap;
233 for (const auto& it : serverMap) {
234 if (it.second.getEventType() == type) {
235 retMap.emplace(it.first, it.second);
236 }
237 }
238 return retMap;
239 }
240 default: return GlobalEventMap();
241 }
242}
243
244GlobalEvent::GlobalEvent(LuaScriptInterface* interface) : Event(interface) {}
245
246bool GlobalEvent::configureEvent(const pugi::xml_node& node)
247{
248 pugi::xml_attribute nameAttribute = node.attribute("name");
249 if (!nameAttribute) {
250 std::cout << "[Error - GlobalEvent::configureEvent] Missing name for a globalevent" << std::endl;
251 return false;
252 }
253
254 name = nameAttribute.as_string();
255 eventType = GLOBALEVENT_NONE;
256
257 pugi::xml_attribute attr;
258 if ((attr = node.attribute("time"))) {
259 std::vector<int32_t> params = vectorAtoi(explodeString(attr.as_string(), ":"));
260
261 int32_t hour = params.front();
262 if (hour < 0 || hour > 23) {
263 std::cout << "[Error - GlobalEvent::configureEvent] Invalid hour \"" << attr.as_string() << "\" for globalevent with name: " << name << std::endl;
264 return false;
265 }
266
267 interval |= hour << 16;
268
269 int32_t min = 0;
270 int32_t sec = 0;
271 if (params.size() > 1) {
272 min = params[1];
273 if (min < 0 || min > 59) {
274 std::cout << "[Error - GlobalEvent::configureEvent] Invalid minute \"" << attr.as_string() << "\" for globalevent with name: " << name << std::endl;
275 return false;
276 }
277
278 if (params.size() > 2) {
279 sec = params[2];
280 if (sec < 0 || sec > 59) {
281 std::cout << "[Error - GlobalEvent::configureEvent] Invalid second \"" << attr.as_string() << "\" for globalevent with name: " << name << std::endl;
282 return false;
283 }
284 }
285 }
286
287 time_t current_time = time(nullptr);
288 tm* timeinfo = localtime(¤t_time);
289 timeinfo->tm_hour = hour;
290 timeinfo->tm_min = min;
291 timeinfo->tm_sec = sec;
292
293 time_t difference = static_cast<time_t>(difftime(mktime(timeinfo), current_time));
294 if (difference < 0) {
295 difference += 86400;
296 }
297
298 nextExecution = current_time + difference;
299 eventType = GLOBALEVENT_TIMER;
300 } else if ((attr = node.attribute("type"))) {
301 const char* value = attr.value();
302 if (strcasecmp(value, "startup") == 0) {
303 eventType = GLOBALEVENT_STARTUP;
304 } else if (strcasecmp(value, "shutdown") == 0) {
305 eventType = GLOBALEVENT_SHUTDOWN;
306 } else if (strcasecmp(value, "record") == 0) {
307 eventType = GLOBALEVENT_RECORD;
308 } else {
309 std::cout << "[Error - GlobalEvent::configureEvent] No valid type \"" << attr.as_string() << "\" for globalevent with name " << name << std::endl;
310 return false;
311 }
312 } else if ((attr = node.attribute("interval"))) {
313 interval = std::max<int32_t>(SCHEDULER_MINTICKS, pugi::cast<int32_t>(attr.value()));
314 nextExecution = OTSYS_TIME() + interval;
315 } else {
316 std::cout << "[Error - GlobalEvent::configureEvent] No interval for globalevent with name " << name << std::endl;
317 return false;
318 }
319 return true;
320}
321
322std::string GlobalEvent::getScriptEventName() const
323{
324 switch (eventType) {
325 case GLOBALEVENT_STARTUP: return "onStartup";
326 case GLOBALEVENT_SHUTDOWN: return "onShutdown";
327 case GLOBALEVENT_RECORD: return "onRecord";
328 case GLOBALEVENT_TIMER: return "onTime";
329 default: return "onThink";
330 }
331}
332
333bool GlobalEvent::executeRecord(uint32_t current, uint32_t old)
334{
335 //onRecord(current, old)
336 if (!scriptInterface->reserveScriptEnv()) {
337 std::cout << "[Error - GlobalEvent::executeRecord] Call stack overflow" << std::endl;
338 return false;
339 }
340
341 ScriptEnvironment* env = scriptInterface->getScriptEnv();
342 env->setScriptId(scriptId, scriptInterface);
343
344 lua_State* L = scriptInterface->getLuaState();
345 scriptInterface->pushFunction(scriptId);
346
347 lua_pushnumber(L, current);
348 lua_pushnumber(L, old);
349 return scriptInterface->callFunction(2);
350}
351
352bool GlobalEvent::executeEvent() const
353{
354 if (!scriptInterface->reserveScriptEnv()) {
355 std::cout << "[Error - GlobalEvent::executeEvent] Call stack overflow" << std::endl;
356 return false;
357 }
358
359 ScriptEnvironment* env = scriptInterface->getScriptEnv();
360 env->setScriptId(scriptId, scriptInterface);
361 lua_State* L = scriptInterface->getLuaState();
362 scriptInterface->pushFunction(scriptId);
363
364 int32_t params = 0;
365 if (eventType == GLOBALEVENT_NONE || eventType == GLOBALEVENT_TIMER) {
366 lua_pushnumber(L, interval);
367 params = 1;
368 }
369
370 return scriptInterface->callFunction(params);
371}
372