chat.cpp

main 644 lines · 16 KB Raw
Alex Alex Commit Server upload 18/10/2026 23:38
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 "chat.h"
23#include "game.h"
24#include "pugicast.h"
25#include "scheduler.h"
26
27extern Chat* g_chat;
28extern Game g_game;
29
30bool PrivateChatChannel::isInvited(uint32_t guid) const
31{
32 if (guid == getOwner()) {
33 return true;
34 }
35 return invites.find(guid) != invites.end();
36}
37
38bool PrivateChatChannel::removeInvite(uint32_t guid)
39{
40 return invites.erase(guid) != 0;
41}
42
43void PrivateChatChannel::invitePlayer(const Player& player, Player& invitePlayer)
44{
45 auto result = invites.emplace(invitePlayer.getGUID(), &invitePlayer);
46 if (!result.second) {
47 return;
48 }
49
50 std::ostringstream ss;
51 ss << player.getName() << " invites you to " << (player.getSex() == PLAYERSEX_FEMALE ? "her" : "his") << " private chat channel.";
52 invitePlayer.sendTextMessage(MESSAGE_INFO_DESCR, ss.str());
53
54 ss.str(std::string());
55 ss << invitePlayer.getName() << " has been invited.";
56 player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str());
57
58 for (const auto& it : users) {
59 it.second->sendChannelEvent(id, invitePlayer.getName(), CHANNELEVENT_INVITE);
60 }
61}
62
63void PrivateChatChannel::excludePlayer(const Player& player, Player& excludePlayer)
64{
65 if (!removeInvite(excludePlayer.getGUID())) {
66 return;
67 }
68
69 removeUser(excludePlayer);
70
71 std::ostringstream ss;
72 ss << excludePlayer.getName() << " has been excluded.";
73 player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str());
74
75 excludePlayer.sendClosePrivate(id);
76
77 for (const auto& it : users) {
78 it.second->sendChannelEvent(id, excludePlayer.getName(), CHANNELEVENT_EXCLUDE);
79 }
80}
81
82void PrivateChatChannel::closeChannel() const
83{
84 for (const auto& it : users) {
85 it.second->sendClosePrivate(id);
86 }
87}
88
89bool ChatChannel::addUser(Player& player)
90{
91 if (users.find(player.getID()) != users.end()) {
92 return false;
93 }
94
95 if (!executeOnJoinEvent(player)) {
96 return false;
97 }
98
99 // TODO: Move to script when guild channels can be scripted
100 if (id == CHANNEL_GUILD) {
101 Guild* guild = player.getGuild();
102 if (guild && !guild->getMotd().empty()) {
103 g_scheduler.addEvent(createSchedulerTask(150, std::bind(&Game::sendGuildMotd, &g_game, player.getID())));
104 }
105 }
106
107 if (!publicChannel) {
108 for (const auto& it : users) {
109 it.second->sendChannelEvent(id, player.getName(), CHANNELEVENT_JOIN);
110 }
111 }
112
113 users[player.getID()] = &player;
114 return true;
115}
116
117bool ChatChannel::removeUser(const Player& player)
118{
119 auto iter = users.find(player.getID());
120 if (iter == users.end()) {
121 return false;
122 }
123
124 users.erase(iter);
125
126 if (!publicChannel) {
127 for (const auto& it : users) {
128 it.second->sendChannelEvent(id, player.getName(), CHANNELEVENT_LEAVE);
129 }
130 }
131
132 executeOnLeaveEvent(player);
133 return true;
134}
135
136bool ChatChannel::hasUser(const Player& player) {
137 return users.find(player.getID()) != users.end();
138}
139
140void ChatChannel::sendToAll(const std::string& message, SpeakClasses type) const
141{
142 for (const auto& it : users) {
143 it.second->sendChannelMessage("", message, type, id);
144 }
145}
146
147bool ChatChannel::talk(const Player& fromPlayer, SpeakClasses type, const std::string& text)
148{
149 if (users.find(fromPlayer.getID()) == users.end()) {
150 return false;
151 }
152
153 for (const auto& it : users) {
154 it.second->sendToChannel(&fromPlayer, type, text, id);
155 }
156 return true;
157}
158
159bool ChatChannel::executeCanJoinEvent(const Player& player)
160{
161 if (canJoinEvent == -1) {
162 return true;
163 }
164
165 //canJoin(player)
166 LuaScriptInterface* scriptInterface = g_chat->getScriptInterface();
167 if (!scriptInterface->reserveScriptEnv()) {
168 std::cout << "[Error - CanJoinChannelEvent::execute] Call stack overflow" << std::endl;
169 return false;
170 }
171
172 ScriptEnvironment* env = scriptInterface->getScriptEnv();
173 env->setScriptId(canJoinEvent, scriptInterface);
174
175 lua_State* L = scriptInterface->getLuaState();
176
177 scriptInterface->pushFunction(canJoinEvent);
178 LuaScriptInterface::pushUserdata(L, &player);
179 LuaScriptInterface::setMetatable(L, -1, "Player");
180
181 return scriptInterface->callFunction(1);
182}
183
184bool ChatChannel::executeOnJoinEvent(const Player& player)
185{
186 if (onJoinEvent == -1) {
187 return true;
188 }
189
190 //onJoin(player)
191 LuaScriptInterface* scriptInterface = g_chat->getScriptInterface();
192 if (!scriptInterface->reserveScriptEnv()) {
193 std::cout << "[Error - OnJoinChannelEvent::execute] Call stack overflow" << std::endl;
194 return false;
195 }
196
197 ScriptEnvironment* env = scriptInterface->getScriptEnv();
198 env->setScriptId(onJoinEvent, scriptInterface);
199
200 lua_State* L = scriptInterface->getLuaState();
201
202 scriptInterface->pushFunction(onJoinEvent);
203 LuaScriptInterface::pushUserdata(L, &player);
204 LuaScriptInterface::setMetatable(L, -1, "Player");
205
206 return scriptInterface->callFunction(1);
207}
208
209bool ChatChannel::executeOnLeaveEvent(const Player& player)
210{
211 if (onLeaveEvent == -1) {
212 return true;
213 }
214
215 //onLeave(player)
216 LuaScriptInterface* scriptInterface = g_chat->getScriptInterface();
217 if (!scriptInterface->reserveScriptEnv()) {
218 std::cout << "[Error - OnLeaveChannelEvent::execute] Call stack overflow" << std::endl;
219 return false;
220 }
221
222 ScriptEnvironment* env = scriptInterface->getScriptEnv();
223 env->setScriptId(onLeaveEvent, scriptInterface);
224
225 lua_State* L = scriptInterface->getLuaState();
226
227 scriptInterface->pushFunction(onLeaveEvent);
228 LuaScriptInterface::pushUserdata(L, &player);
229 LuaScriptInterface::setMetatable(L, -1, "Player");
230
231 return scriptInterface->callFunction(1);
232}
233
234bool ChatChannel::executeOnSpeakEvent(const Player& player, SpeakClasses& type, const std::string& message)
235{
236 if (onSpeakEvent == -1) {
237 return true;
238 }
239
240 //onSpeak(player, type, message)
241 LuaScriptInterface* scriptInterface = g_chat->getScriptInterface();
242 if (!scriptInterface->reserveScriptEnv()) {
243 std::cout << "[Error - OnSpeakChannelEvent::execute] Call stack overflow" << std::endl;
244 return false;
245 }
246
247 ScriptEnvironment* env = scriptInterface->getScriptEnv();
248 env->setScriptId(onSpeakEvent, scriptInterface);
249
250 lua_State* L = scriptInterface->getLuaState();
251
252 scriptInterface->pushFunction(onSpeakEvent);
253 LuaScriptInterface::pushUserdata(L, &player);
254 LuaScriptInterface::setMetatable(L, -1, "Player");
255
256 lua_pushnumber(L, type);
257 LuaScriptInterface::pushString(L, message);
258
259 bool result = false;
260 int size0 = lua_gettop(L);
261 int ret = scriptInterface->protectedCall(L, 3, 1);
262 if (ret != 0) {
263 LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L));
264 } else if (lua_gettop(L) > 0) {
265 if (lua_isboolean(L, -1)) {
266 result = LuaScriptInterface::getBoolean(L, -1);
267 } else if (lua_isnumber(L, -1)) {
268 result = true;
269 type = LuaScriptInterface::getNumber<SpeakClasses>(L, -1);
270 }
271 lua_pop(L, 1);
272 }
273
274 if ((lua_gettop(L) + 4) != size0) {
275 LuaScriptInterface::reportError(nullptr, "Stack size changed!");
276 }
277 scriptInterface->resetScriptEnv();
278 return result;
279}
280
281Chat::Chat():
282 scriptInterface("Chat Interface"),
283 dummyPrivate(CHANNEL_PRIVATE, "Private Chat Channel")
284{
285 scriptInterface.initState();
286}
287
288bool Chat::load()
289{
290 pugi::xml_document doc;
291 pugi::xml_parse_result result = doc.load_file("data/chatchannels/chatchannels.xml");
292 if (!result) {
293 printXMLError("Error - Chat::load", "data/chatchannels/chatchannels.xml", result);
294 return false;
295 }
296
297 for (auto channelNode : doc.child("channels").children()) {
298 uint16_t channelId = pugi::cast<uint16_t>(channelNode.attribute("id").value());
299 std::string channelName = channelNode.attribute("name").as_string();
300 bool isPublic = channelNode.attribute("public").as_bool();
301 pugi::xml_attribute scriptAttribute = channelNode.attribute("script");
302
303 auto it = normalChannels.find(channelId);
304 if (it != normalChannels.end()) {
305 ChatChannel& channel = it->second;
306 channel.publicChannel = isPublic;
307 channel.name = channelName;
308
309 if (scriptAttribute) {
310 if (scriptInterface.loadFile("data/chatchannels/scripts/" + std::string(scriptAttribute.as_string())) == 0) {
311 channel.onSpeakEvent = scriptInterface.getEvent("onSpeak");
312 channel.canJoinEvent = scriptInterface.getEvent("canJoin");
313 channel.onJoinEvent = scriptInterface.getEvent("onJoin");
314 channel.onLeaveEvent = scriptInterface.getEvent("onLeave");
315 } else {
316 std::cout << "[Warning - Chat::load] Can not load script: " << scriptAttribute.as_string() << std::endl;
317 }
318 }
319
320 UsersMap tempUserMap = std::move(channel.users);
321 for (const auto& pair : tempUserMap) {
322 channel.addUser(*pair.second);
323 }
324 continue;
325 }
326
327 ChatChannel channel(channelId, channelName);
328 channel.publicChannel = isPublic;
329
330 if (scriptAttribute) {
331 if (scriptInterface.loadFile("data/chatchannels/scripts/" + std::string(scriptAttribute.as_string())) == 0) {
332 channel.onSpeakEvent = scriptInterface.getEvent("onSpeak");
333 channel.canJoinEvent = scriptInterface.getEvent("canJoin");
334 channel.onJoinEvent = scriptInterface.getEvent("onJoin");
335 channel.onLeaveEvent = scriptInterface.getEvent("onLeave");
336 } else {
337 std::cout << "[Warning - Chat::load] Can not load script: " << scriptAttribute.as_string() << std::endl;
338 }
339 }
340
341 normalChannels[channel.id] = channel;
342 }
343 return true;
344}
345
346ChatChannel* Chat::createChannel(const Player& player, uint16_t channelId)
347{
348 if (getChannel(player, channelId)) {
349 return nullptr;
350 }
351
352 switch (channelId) {
353 case CHANNEL_GUILD: {
354 Guild* guild = player.getGuild();
355 if (guild) {
356 auto ret = guildChannels.emplace(std::make_pair(guild->getId(), ChatChannel(channelId, guild->getName())));
357 return &ret.first->second;
358 }
359 break;
360 }
361
362 case CHANNEL_PARTY: {
363 Party* party = player.getParty();
364 if (party) {
365 auto ret = partyChannels.emplace(std::make_pair(party, ChatChannel(channelId, "Party")));
366 return &ret.first->second;
367 }
368 break;
369 }
370
371 case CHANNEL_PRIVATE: {
372 //only 1 private channel for each premium player
373 if (!player.isPremium() || getPrivateChannel(player)) {
374 return nullptr;
375 }
376
377 //find a free private channel slot
378 for (uint16_t i = 100; i < 10000; ++i) {
379 auto ret = privateChannels.emplace(std::make_pair(i, PrivateChatChannel(i, player.getName() + "'s Channel")));
380 if (ret.second) { //second is a bool that indicates that a new channel has been placed in the map
381 auto& newChannel = (*ret.first).second;
382 newChannel.setOwner(player.getGUID());
383 return &newChannel;
384 }
385 }
386 break;
387 }
388
389 default:
390 break;
391 }
392 return nullptr;
393}
394
395bool Chat::deleteChannel(const Player& player, uint16_t channelId)
396{
397 switch (channelId) {
398 case CHANNEL_GUILD: {
399 Guild* guild = player.getGuild();
400 if (!guild) {
401 return false;
402 }
403
404 auto it = guildChannels.find(guild->getId());
405 if (it == guildChannels.end()) {
406 return false;
407 }
408
409 guildChannels.erase(it);
410 break;
411 }
412
413 case CHANNEL_PARTY: {
414 Party* party = player.getParty();
415 if (!party) {
416 return false;
417 }
418
419 auto it = partyChannels.find(party);
420 if (it == partyChannels.end()) {
421 return false;
422 }
423
424 partyChannels.erase(it);
425 break;
426 }
427
428 default: {
429 auto it = privateChannels.find(channelId);
430 if (it == privateChannels.end()) {
431 return false;
432 }
433
434 it->second.closeChannel();
435
436 privateChannels.erase(it);
437 break;
438 }
439 }
440 return true;
441}
442
443ChatChannel* Chat::addUserToChannel(Player& player, uint16_t channelId)
444{
445 ChatChannel* channel = getChannel(player, channelId);
446 if (channel && channel->addUser(player)) {
447 return channel;
448 }
449 return nullptr;
450}
451
452bool Chat::removeUserFromChannel(const Player& player, uint16_t channelId)
453{
454 ChatChannel* channel = getChannel(player, channelId);
455 if (!channel || !channel->removeUser(player)) {
456 return false;
457 }
458
459 if (channel->getOwner() == player.getGUID()) {
460 deleteChannel(player, channelId);
461 }
462 return true;
463}
464
465void Chat::removeUserFromAllChannels(const Player& player)
466{
467 for (auto& it : normalChannels) {
468 it.second.removeUser(player);
469 }
470
471 for (auto& it : partyChannels) {
472 it.second.removeUser(player);
473 }
474
475 for (auto& it : guildChannels) {
476 it.second.removeUser(player);
477 }
478
479 auto it = privateChannels.begin();
480 while (it != privateChannels.end()) {
481 PrivateChatChannel* channel = &it->second;
482 channel->removeInvite(player.getGUID());
483 channel->removeUser(player);
484 if (channel->getOwner() == player.getGUID()) {
485 channel->closeChannel();
486 it = privateChannels.erase(it);
487 } else {
488 ++it;
489 }
490 }
491}
492
493bool Chat::talkToChannel(const Player& player, SpeakClasses type, const std::string& text, uint16_t channelId)
494{
495 ChatChannel* channel = getChannel(player, channelId);
496 if (!channel) {
497 return false;
498 }
499
500 if (channelId == CHANNEL_GUILD) {
501 GuildRank_ptr rank = player.getGuildRank();
502 if (rank && rank->level > 1) {
503 type = TALKTYPE_CHANNEL_O;
504 } else if (type != TALKTYPE_CHANNEL_Y) {
505 type = TALKTYPE_CHANNEL_Y;
506 }
507 } else if (type != TALKTYPE_CHANNEL_Y && (channelId == CHANNEL_PRIVATE || channelId == CHANNEL_PARTY)) {
508 type = TALKTYPE_CHANNEL_Y;
509 }
510
511 if (!channel->executeOnSpeakEvent(player, type, text)) {
512 return false;
513 }
514
515 return channel->talk(player, type, text);
516}
517
518ChannelList Chat::getChannelList(const Player& player)
519{
520 ChannelList list;
521 if (player.getGuild()) {
522 ChatChannel* channel = getChannel(player, CHANNEL_GUILD);
523 if (channel) {
524 list.push_back(channel);
525 } else {
526 channel = createChannel(player, CHANNEL_GUILD);
527 if (channel) {
528 list.push_back(channel);
529 }
530 }
531 }
532
533 if (player.getParty()) {
534 ChatChannel* channel = getChannel(player, CHANNEL_PARTY);
535 if (channel) {
536 list.push_back(channel);
537 } else {
538 channel = createChannel(player, CHANNEL_PARTY);
539 if (channel) {
540 list.push_back(channel);
541 }
542 }
543 }
544
545 for (const auto& it : normalChannels) {
546 ChatChannel* channel = getChannel(player, it.first);
547 if (channel) {
548 list.push_back(channel);
549 }
550 }
551
552 bool hasPrivate = false;
553 for (auto& it : privateChannels) {
554 if (PrivateChatChannel* channel = &it.second) {
555 uint32_t guid = player.getGUID();
556 if (channel->isInvited(guid)) {
557 list.push_back(channel);
558 }
559
560 if (channel->getOwner() == guid) {
561 hasPrivate = true;
562 }
563 }
564 }
565
566 if (!hasPrivate && player.isPremium()) {
567 list.push_front(&dummyPrivate);
568 }
569 return list;
570}
571
572ChatChannel* Chat::getChannel(const Player& player, uint16_t channelId)
573{
574 switch (channelId) {
575 case CHANNEL_GUILD: {
576 Guild* guild = player.getGuild();
577 if (guild) {
578 auto it = guildChannels.find(guild->getId());
579 if (it != guildChannels.end()) {
580 return &it->second;
581 }
582 }
583 break;
584 }
585
586 case CHANNEL_PARTY: {
587 Party* party = player.getParty();
588 if (party) {
589 auto it = partyChannels.find(party);
590 if (it != partyChannels.end()) {
591 return &it->second;
592 }
593 }
594 break;
595 }
596
597 default: {
598 auto it = normalChannels.find(channelId);
599 if (it != normalChannels.end()) {
600 ChatChannel& channel = it->second;
601 if (!channel.executeCanJoinEvent(player)) {
602 return nullptr;
603 }
604 return &channel;
605 } else {
606 auto it2 = privateChannels.find(channelId);
607 if (it2 != privateChannels.end() && it2->second.isInvited(player.getGUID())) {
608 return &it2->second;
609 }
610 }
611 break;
612 }
613 }
614 return nullptr;
615}
616
617ChatChannel* Chat::getGuildChannelById(uint32_t guildId)
618{
619 auto it = guildChannels.find(guildId);
620 if (it == guildChannels.end()) {
621 return nullptr;
622 }
623 return &it->second;
624}
625
626ChatChannel* Chat::getChannelById(uint16_t channelId)
627{
628 auto it = normalChannels.find(channelId);
629 if (it == normalChannels.end()) {
630 return nullptr;
631 }
632 return &it->second;
633}
634
635PrivateChatChannel* Chat::getPrivateChannel(const Player& player)
636{
637 for (auto& it : privateChannels) {
638 if (it.second.getOwner() == player.getGUID()) {
639 return &it.second;
640 }
641 }
642 return nullptr;
643}
644
Top