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 "party.h"
23#include "game.h"
24#include "configmanager.h"
25#include "events.h"
26
27extern Game g_game;
28extern ConfigManager g_config;
29extern Events* g_events;
30
31Party::Party(Player* leader) : leader(leader)
32{
33 leader->setParty(this);
34}
35
36void Party::disband()
37{
38 if (!g_events->eventPartyOnDisband(this)) {
39 return;
40 }
41
42 Player* currentLeader = leader;
43 leader = nullptr;
44
45 currentLeader->setParty(nullptr);
46 currentLeader->sendClosePrivate(CHANNEL_PARTY);
47 g_game.updatePlayerShield(currentLeader);
48 g_game.updatePlayerHelpers(*currentLeader);
49 currentLeader->sendCreatureSkull(currentLeader);
50 currentLeader->sendTextMessage(MESSAGE_INFO_DESCR, "Your party has been disbanded.");
51
52 for (Player* invitee : inviteList) {
53 invitee->removePartyInvitation(this);
54 currentLeader->sendCreatureShield(invitee);
55 }
56 inviteList.clear();
57
58 for (Player* member : memberList) {
59 member->setParty(nullptr);
60 member->sendClosePrivate(CHANNEL_PARTY);
61 member->sendTextMessage(MESSAGE_INFO_DESCR, "Your party has been disbanded.");
62 }
63
64 for (Player* member : memberList) {
65 g_game.updatePlayerShield(member);
66
67 for (Player* otherMember : memberList) {
68 otherMember->sendCreatureSkull(member);
69 }
70
71 member->sendCreatureSkull(currentLeader);
72 currentLeader->sendCreatureSkull(member);
73 g_game.updatePlayerHelpers(*member);
74 }
75 memberList.clear();
76 delete this;
77}
78
79bool Party::leaveParty(Player* player)
80{
81 if (!player) {
82 return false;
83 }
84
85 if (player->getParty() != this && leader != player) {
86 return false;
87 }
88
89 if (!g_events->eventPartyOnLeave(this, player)) {
90 return false;
91 }
92
93 bool missingLeader = false;
94 if (leader == player) {
95 if (!memberList.empty()) {
96 if (memberList.size() == 1 && inviteList.empty()) {
97 missingLeader = true;
98 } else {
99 passPartyLeadership(memberList.front());
100 }
101 } else {
102 missingLeader = true;
103 }
104 }
105
106 //since we already passed the leadership, we remove the player from the list
107 auto it = std::find(memberList.begin(), memberList.end(), player);
108 if (it != memberList.end()) {
109 memberList.erase(it);
110 }
111
112 player->setParty(nullptr);
113 player->sendClosePrivate(CHANNEL_PARTY);
114 g_game.updatePlayerShield(player);
115 g_game.updatePlayerHelpers(*player);
116
117 for (Player* member : memberList) {
118 member->sendCreatureSkull(player);
119 player->sendPlayerPartyIcons(member);
120 g_game.updatePlayerHelpers(*member);
121 }
122
123 leader->sendCreatureSkull(player);
124 player->sendCreatureSkull(player);
125 player->sendPlayerPartyIcons(leader);
126
127 player->sendTextMessage(MESSAGE_INFO_DESCR, "You have left the party.");
128
129 updateSharedExperience();
130
131 clearPlayerPoints(player);
132
133 std::ostringstream ss;
134 ss << player->getName() << " has left the party.";
135 broadcastPartyMessage(MESSAGE_INFO_DESCR, ss.str());
136
137 if (missingLeader || empty()) {
138 disband();
139 }
140
141 return true;
142}
143
144bool Party::passPartyLeadership(Player* player)
145{
146 if (!player || leader == player || player->getParty() != this) {
147 return false;
148 }
149
150 //Remove it before to broadcast the message correctly
151 auto it = std::find(memberList.begin(), memberList.end(), player);
152 if (it != memberList.end()) {
153 memberList.erase(it);
154 }
155
156 std::ostringstream ss;
157 ss << player->getName() << " is now the leader of the party.";
158 broadcastPartyMessage(MESSAGE_INFO_DESCR, ss.str(), true);
159
160 Player* oldLeader = leader;
161 leader = player;
162
163 memberList.insert(memberList.begin(), oldLeader);
164
165 updateSharedExperience();
166
167 for (Player* member : memberList) {
168 member->sendCreatureShield(oldLeader);
169 member->sendCreatureShield(leader);
170 }
171
172 for (Player* invitee : inviteList) {
173 invitee->sendCreatureShield(oldLeader);
174 invitee->sendCreatureShield(leader);
175 }
176
177 leader->sendCreatureShield(oldLeader);
178 leader->sendCreatureShield(leader);
179
180 player->sendTextMessage(MESSAGE_INFO_DESCR, "You are now the leader of the party.");
181 return true;
182}
183
184bool Party::joinParty(Player& player)
185{
186 if (!g_events->eventPartyOnJoin(this, &player)) {
187 return false;
188 }
189
190 auto it = std::find(inviteList.begin(), inviteList.end(), &player);
191 if (it == inviteList.end()) {
192 return false;
193 }
194
195 inviteList.erase(it);
196
197 std::ostringstream ss;
198 ss << player.getName() << " has joined the party.";
199 broadcastPartyMessage(MESSAGE_INFO_DESCR, ss.str());
200
201 player.setParty(this);
202
203 g_game.updatePlayerShield(&player);
204
205 for (Player* member : memberList) {
206 member->sendCreatureSkull(&player);
207 player.sendPlayerPartyIcons(member);
208 }
209
210 player.sendCreatureSkull(&player);
211 leader->sendCreatureSkull(&player);
212 player.sendPlayerPartyIcons(leader);
213
214 memberList.push_back(&player);
215
216 g_game.updatePlayerHelpers(player);
217
218 player.removePartyInvitation(this);
219 updateSharedExperience();
220
221 const std::string& leaderName = leader->getName();
222 ss.str(std::string());
223 ss << "You have joined " << leaderName << "'" << (leaderName.back() == 's' ? "" : "s") <<
224 " party. Open the party channel to communicate with your companions.";
225 player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str());
226 return true;
227}
228
229bool Party::removeInvite(Player& player, bool removeFromPlayer/* = true*/)
230{
231 auto it = std::find(inviteList.begin(), inviteList.end(), &player);
232 if (it == inviteList.end()) {
233 return false;
234 }
235
236 inviteList.erase(it);
237
238 leader->sendCreatureShield(&player);
239 player.sendCreatureShield(leader);
240
241 if (removeFromPlayer) {
242 player.removePartyInvitation(this);
243 }
244
245 if (empty()) {
246 disband();
247 } else {
248 for (Player* member : memberList) {
249 g_game.updatePlayerHelpers(*member);
250 }
251
252 g_game.updatePlayerHelpers(*leader);
253 }
254
255 return true;
256}
257
258void Party::revokeInvitation(Player& player)
259{
260 std::ostringstream ss;
261 ss << leader->getName() << " has revoked " << (leader->getSex() == PLAYERSEX_FEMALE ? "her" : "his") << " invitation.";
262 player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str());
263
264 ss.str(std::string());
265 ss << "Invitation for " << player.getName() << " has been revoked.";
266 leader->sendTextMessage(MESSAGE_INFO_DESCR, ss.str());
267
268 removeInvite(player);
269}
270
271bool Party::invitePlayer(Player& player)
272{
273 if (isPlayerInvited(&player)) {
274 return false;
275 }
276
277 std::ostringstream ss;
278 ss << player.getName() << " has been invited.";
279
280 if (empty()) {
281 ss << " Open the party channel to communicate with your members.";
282 g_game.updatePlayerShield(leader);
283 leader->sendCreatureSkull(leader);
284 }
285
286 leader->sendTextMessage(MESSAGE_INFO_DESCR, ss.str());
287
288 inviteList.push_back(&player);
289
290 for (Player* member : memberList) {
291 g_game.updatePlayerHelpers(*member);
292 }
293 g_game.updatePlayerHelpers(*leader);
294
295 leader->sendCreatureShield(&player);
296 player.sendCreatureShield(leader);
297
298 player.addPartyInvitation(this);
299
300 ss.str(std::string());
301 ss << leader->getName() << " has invited you to " << (leader->getSex() == PLAYERSEX_FEMALE ? "her" : "his") << " party.";
302 player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str());
303 return true;
304}
305
306bool Party::isPlayerInvited(const Player* player) const
307{
308 return std::find(inviteList.begin(), inviteList.end(), player) != inviteList.end();
309}
310
311void Party::updateAllPartyIcons()
312{
313 for (Player* member : memberList) {
314 for (Player* otherMember : memberList) {
315 member->sendCreatureShield(otherMember);
316 }
317
318 member->sendCreatureShield(leader);
319 leader->sendCreatureShield(member);
320 }
321 leader->sendCreatureShield(leader);
322}
323
324void Party::broadcastPartyMessage(MessageClasses msgClass, const std::string& msg, bool sendToInvitations /*= false*/)
325{
326 for (Player* member : memberList) {
327 member->sendTextMessage(msgClass, msg);
328 }
329
330 leader->sendTextMessage(msgClass, msg);
331
332 if (sendToInvitations) {
333 for (Player* invitee : inviteList) {
334 invitee->sendTextMessage(msgClass, msg);
335 }
336 }
337}
338
339void Party::updateSharedExperience()
340{
341 if (sharedExpActive) {
342 bool result = canEnableSharedExperience();
343 if (result != sharedExpEnabled) {
344 sharedExpEnabled = result;
345 updateAllPartyIcons();
346 }
347 }
348}
349
350bool Party::setSharedExperience(Player* player, bool sharedExpActive)
351{
352 if (!player || leader != player) {
353 return false;
354 }
355
356 if (this->sharedExpActive == sharedExpActive) {
357 return true;
358 }
359
360 this->sharedExpActive = sharedExpActive;
361
362 if (sharedExpActive) {
363 this->sharedExpEnabled = canEnableSharedExperience();
364
365 if (this->sharedExpEnabled) {
366 leader->sendTextMessage(MESSAGE_INFO_DESCR, "Shared Experience is now active.");
367 } else {
368 leader->sendTextMessage(MESSAGE_INFO_DESCR, "Shared Experience has been activated, but some members of your party are inactive.");
369 }
370 } else {
371 leader->sendTextMessage(MESSAGE_INFO_DESCR, "Shared Experience has been deactivated.");
372 }
373
374 updateAllPartyIcons();
375 return true;
376}
377
378void Party::shareExperience(uint64_t experience, Creature* source/* = nullptr*/)
379{
380 uint64_t shareExperience = experience;
381 g_events->eventPartyOnShareExperience(this, shareExperience);
382
383 for (Player* member : memberList) {
384 member->onGainSharedExperience(shareExperience, source);
385 }
386 leader->onGainSharedExperience(shareExperience, source);
387}
388
389bool Party::canUseSharedExperience(const Player* player) const
390{
391 if (memberList.empty()) {
392 return false;
393 }
394
395 uint32_t highestLevel = leader->getLevel();
396 for (Player* member : memberList) {
397 if (member->getLevel() > highestLevel) {
398 highestLevel = member->getLevel();
399 }
400 }
401
402 uint32_t minLevel = static_cast<uint32_t>(std::ceil((static_cast<float>(highestLevel) * 2) / 3));
403 if (player->getLevel() < minLevel) {
404 return false;
405 }
406
407 if (!Position::areInRange<30, 30, 1>(leader->getPosition(), player->getPosition())) {
408 return false;
409 }
410
411 if (!player->hasFlag(PlayerFlag_NotGainInFight)) {
412 //check if the player has healed/attacked anything recently
413 auto it = ticksMap.find(player->getID());
414 if (it == ticksMap.end()) {
415 return false;
416 }
417
418 uint64_t timeDiff = OTSYS_TIME() - it->second;
419 if (timeDiff > static_cast<uint64_t>(g_config.getNumber(ConfigManager::PZ_LOCKED))) {
420 return false;
421 }
422 }
423 return true;
424}
425
426bool Party::canEnableSharedExperience()
427{
428 if (!canUseSharedExperience(leader)) {
429 return false;
430 }
431
432 for (Player* member : memberList) {
433 if (!canUseSharedExperience(member)) {
434 return false;
435 }
436 }
437 return true;
438}
439
440void Party::updatePlayerTicks(Player* player, uint32_t points)
441{
442 if (points != 0 && !player->hasFlag(PlayerFlag_NotGainInFight)) {
443 ticksMap[player->getID()] = OTSYS_TIME();
444 updateSharedExperience();
445 }
446}
447
448void Party::clearPlayerPoints(Player* player)
449{
450 auto it = ticksMap.find(player->getID());
451 if (it != ticksMap.end()) {
452 ticksMap.erase(it);
453 updateSharedExperience();
454 }
455}
456
457bool Party::canOpenCorpse(uint32_t ownerId) const
458{
459 if (Player* player = g_game.getPlayerByID(ownerId)) {
460 return leader->getID() == ownerId || player->getParty() == this;
461 }
462 return false;
463}
464