Server upload

RD Core / Commit #2

Commit Server upload

Alex Alex committed 18/10/2026 23:38 main Patch
1,106 files +223,404 -0
A src/party.cpp +463-0 View file
@@ -0,0 +1,463 @@
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+
27+extern Game g_game;
28+extern ConfigManager g_config;
29+extern Events* g_events;
30+
31+Party::Party(Player* leader) : leader(leader)
32+{
33+ leader->setParty(this);
34+}
35+
36+void 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+
79+bool 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+
144+bool 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+
184+bool 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+
229+bool 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+
258+void 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+
271+bool 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+
306+bool Party::isPlayerInvited(const Player* player) const
307+{
308+ return std::find(inviteList.begin(), inviteList.end(), player) != inviteList.end();
309+}
310+
311+void 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+
324+void 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+
339+void Party::updateSharedExperience()
340+{
341+ if (sharedExpActive) {
342+ bool result = canEnableSharedExperience();
343+ if (result != sharedExpEnabled) {
344+ sharedExpEnabled = result;
345+ updateAllPartyIcons();
346+ }
347+ }
348+}
349+
350+bool 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+
378+void 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+
389+bool 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+
426+bool 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+
440+void 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+
448+void 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+
457+bool 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+}
A src/party.h +97-0 View file
@@ -0,0 +1,97 @@
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+#ifndef FS_PARTY_H_41D4D7CF417C4CC99FAE94D552255044
21+#define FS_PARTY_H_41D4D7CF417C4CC99FAE94D552255044
22+
23+#include "player.h"
24+#include "monsters.h"
25+
26+class Player;
27+class Party;
28+
29+using PlayerVector = std::vector<Player*>;
30+
31+class Party
32+{
33+ public:
34+ explicit Party(Player* leader);
35+
36+ Player* getLeader() const {
37+ return leader;
38+ }
39+ PlayerVector& getMembers() {
40+ return memberList;
41+ }
42+ const PlayerVector& getInvitees() const {
43+ return inviteList;
44+ }
45+ size_t getMemberCount() const {
46+ return memberList.size();
47+ }
48+ size_t getInvitationCount() const {
49+ return inviteList.size();
50+ }
51+
52+ void disband();
53+ bool invitePlayer(Player& player);
54+ bool joinParty(Player& player);
55+ void revokeInvitation(Player& player);
56+ bool passPartyLeadership(Player* player);
57+ bool leaveParty(Player* player);
58+
59+ bool removeInvite(Player& player, bool removeFromPlayer = true);
60+
61+ bool isPlayerInvited(const Player* player) const;
62+ void updateAllPartyIcons();
63+ void broadcastPartyMessage(MessageClasses msgClass, const std::string& msg, bool sendToInvitations = false);
64+ bool empty() const {
65+ return memberList.empty() && inviteList.empty();
66+ }
67+ bool canOpenCorpse(uint32_t ownerId) const;
68+
69+ void shareExperience(uint64_t experience, Creature* source = nullptr);
70+ bool setSharedExperience(Player* player, bool sharedExpActive);
71+ bool isSharedExperienceActive() const {
72+ return sharedExpActive;
73+ }
74+ bool isSharedExperienceEnabled() const {
75+ return sharedExpEnabled;
76+ }
77+ bool canUseSharedExperience(const Player* player) const;
78+ void updateSharedExperience();
79+
80+ void updatePlayerTicks(Player* player, uint32_t points);
81+ void clearPlayerPoints(Player* player);
82+
83+ private:
84+ bool canEnableSharedExperience();
85+
86+ std::map<uint32_t, int64_t> ticksMap;
87+
88+ PlayerVector memberList;
89+ PlayerVector inviteList;
90+
91+ Player* leader;
92+
93+ bool sharedExpActive = false;
94+ bool sharedExpEnabled = false;
95+};
96+
97+#endif
Top