diff --git a/Changelog.txt b/Changelog.txt index 7663831db..54b3fde0d 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -4056,3 +4056,6 @@ When setting a property like MORE to the a spell or skill defname, trying to rea 24-09-2025, Mulambo - Added: ARGN3 for trigger @PersonalSpace [R/W], allowing to bypass maximum stamina requirement (default 1 = require maximum stamina) when stepping over players. + +26-09-2025, Mulambo +- Changed: When the party leader gets disconnected (or deleted), new party leader is appointed, instead of disbanding the party. diff --git a/src/game/chars/CChar.cpp b/src/game/chars/CChar.cpp index d5e581f38..bf247ce6e 100644 --- a/src/game/chars/CChar.cpp +++ b/src/game/chars/CChar.cpp @@ -354,7 +354,7 @@ CChar::~CChar() if (m_pParty) { - m_pParty->RemoveMember( GetUID(), GetUID() ); + m_pParty->RemoveMember(GetUID(), GetUID(), false); m_pParty = nullptr; } //Guild_Resign(MEMORY_GUILD); Moved to the ClearPlayer method otherwise it will cause a server crash because the deleted player will still be found in the guild list. @@ -493,13 +493,6 @@ void CChar::ClientDetach() if ( !IsClientActive() ) return; - if ( m_pParty && m_pParty->IsPartyMaster( this )) - { - // Party must disband if the master is logged out. - m_pParty->Disband(GetUID()); - m_pParty = nullptr; - } - // If this char is on a IT_SHIP then we need to stop the ship ! if ( m_pArea && m_pArea->IsFlag( REGION_FLAG_SHIP )) { @@ -548,7 +541,7 @@ void CChar::SetDisconnected(CSector* pNewSector) if (m_pParty) { - m_pParty->RemoveMember( GetUID(), GetUID() ); + m_pParty->RemoveMember(GetUID(), GetUID(), false); m_pParty = nullptr; } diff --git a/src/game/clients/CParty.cpp b/src/game/clients/CParty.cpp index 76620ea92..c912c1cbf 100644 --- a/src/game/clients/CParty.cpp +++ b/src/game/clients/CParty.cpp @@ -293,43 +293,60 @@ void CPartyDef::AcceptMember( CChar *pChar ) SendAddList(nullptr); } -bool CPartyDef::RemoveMember( CUID uidRemove, CUID uidCommand ) +bool CPartyDef::RemoveMember(const CUID& uidRemove, const CUID &uidCommand, const bool fDisband) { ADDTOCALLSTACK("CPartyDef::RemoveMember"); - // ARGS: - // uidRemove = Who is being removed. - // uidCommand = who removed this person (only the master or self can remove) - // - // NOTE: remove of the master will cause the party to disband. - if ( m_Chars.GetCharCount() <= 0 ) + if (m_Chars.GetCharCount() <= 0) return false; - CUID uidMaster(GetMaster()); - if ( (uidRemove != uidCommand) && (uidCommand != uidMaster) ) + const CUID uidMaster(GetMaster()); + if (uidRemove != uidCommand && uidCommand != uidMaster) return false; CChar *pCharRemove(uidRemove.CharFind()); - if ( !pCharRemove ) + if (!pCharRemove) return false; - if ( !IsInParty(pCharRemove) ) + if (!IsInParty(pCharRemove)) return false; - if ( uidRemove == uidMaster ) + if (fDisband && uidRemove == uidMaster) return Disband(uidMaster); CChar *pSrc = uidCommand.CharFind(); - if ( pSrc && IsTrigUsed(TRIGGER_PARTYREMOVE) ) + if (pSrc && IsTrigUsed(TRIGGER_PARTYREMOVE)) { - if ( pCharRemove->OnTrigger(CTRIG_PartyRemove, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pSrc) == TRIGRET_RET_TRUE ) + if (pCharRemove->OnTrigger(CTRIG_PartyRemove, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pSrc) == TRIGRET_RET_TRUE) return false; } - if ( IsTrigUsed(TRIGGER_PARTYLEAVE) ) + if (IsTrigUsed(TRIGGER_PARTYLEAVE)) { - if ( pCharRemove->OnTrigger(CTRIG_PartyLeave, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pCharRemove) == TRIGRET_RET_TRUE ) + if (pCharRemove->OnTrigger(CTRIG_PartyLeave, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pCharRemove) == TRIGRET_RET_TRUE) return false; } - // Remove it from the party + // If the party leader left, try to promote new character to be the leader. + tchar *pszChangeLeader = Str_GetTemp(); + if (uidRemove == uidMaster) + { + // No valid character on second position in the party, disband it. + if (!m_Chars.IsValidIndex(1)) + return Disband(uidMaster); + + const CUID newMaster(m_Chars.GetChar(1)); + CChar *pCharNewMaster(newMaster.CharFind()); + + // Cannot find new leader's character, disband it. + if (!pCharNewMaster) + return Disband(uidMaster); + + // If new master wasn't appointed, disband the party. + if (!SetMaster(pCharNewMaster)) + return Disband(uidMaster); + + snprintf(pszChangeLeader, Str_TempLength(), g_Cfg.GetDefaultMsg(DEFMSG_PARTY_CHANGE_LEADER), pCharNewMaster->GetName()); + } + + // Remove the character from the party. SendRemoveList(pCharRemove, true); DetachChar(pCharRemove); pCharRemove->SysMessageDefault(DEFMSG_PARTY_LEAVE_2); @@ -338,13 +355,20 @@ bool CPartyDef::RemoveMember( CUID uidRemove, CUID uidCommand ) snprintf(pszMsg, Str_TempLength(), g_Cfg.GetDefaultMsg(DEFMSG_PARTY_LEAVE_1), pCharRemove->GetName()); SysMessageAll(pszMsg); - if ( m_Chars.GetCharCount() <= 1 ) + // Disband the party if I'm alone. + if (m_Chars.GetCharCount() <= 1) { - // Disband the party SysMessageAll(g_Cfg.GetDefaultMsg(DEFMSG_PARTY_LEAVE_LAST_PERSON)); return Disband(uidMaster); } + // Notify about change in party leadership (we need to do it here, so it doesn't get sent to the previous leader). + if (uidRemove == uidMaster) + SysMessageAll(pszChangeLeader); + + // We still have a party, notify other members about removal. + SendRemoveList(pCharRemove, false); + return true; } diff --git a/src/game/clients/CParty.h b/src/game/clients/CParty.h index 5182f0a4a..18aab6805 100644 --- a/src/game/clients/CParty.h +++ b/src/game/clients/CParty.h @@ -78,7 +78,20 @@ class CPartyDef : public CSObjListRec, public CScriptObj // Commands bool Disband( CUID uidMaster ); - bool RemoveMember( CUID uidRemove, CUID uidCommand ); + + /** + * Removes a member from party. If leader is removed, it tries to pass leader to another character. + * + * @note We need to switch characters at leader position before we remove him, because original client doesn't like removing leader and thinks, the party + * was disbanded (Classic UO doesn't care though). + * + * @param uidRemove Character being removed. + * @param uidCommand Character, who issued the removal (leader or self). + * @param fDisband If set to false, we try to change leader instead of disbanding the party. + * + * @return True if successfully removed, false otherwise. + */ + bool RemoveMember(const CUID& uidRemove, const CUID &uidCommand, bool fDisband = true); void AcceptMember( CChar * pChar ); void SetLootFlag( CChar * pChar, bool fSet ); bool GetLootFlag( const CChar * pChar ); diff --git a/src/network/receive.cpp b/src/network/receive.cpp index a5dcac1fe..1106e32a0 100644 --- a/src/network/receive.cpp +++ b/src/network/receive.cpp @@ -2655,6 +2655,7 @@ bool PacketPartyMessage::onReceive(CNetState* net) client->addTarget( CLIMODE_TARG_PARTY_ADD, g_Cfg.GetDefaultMsg(DEFMSG_PARTY_TARG_WHO), false, false); break; + // This doesn't happen, since disbanding the party is handled by client sending a packet to remove the party master. case PARTYMSG_Disband: if (character->m_pParty == nullptr) return false; diff --git a/src/tables/defmessages.tbl b/src/tables/defmessages.tbl index 4f80adebf..9838b04cc 100644 --- a/src/tables/defmessages.tbl +++ b/src/tables/defmessages.tbl @@ -732,6 +732,7 @@ MSG(PARTY_ADDED, "You have been added to the party.") MSG(PARTY_DECLINE_1, "%s: Does not wish to join the party.") MSG(PARTY_DECLINE_2, "You notify %s that you do not wish to join the party.") MSG(PARTY_DISBANDED, "Your party has disbanded.") +MSG(PARTY_CHANGE_LEADER, "%s was appointed the new party leader.") MSG(PARTY_INVITE, "You have invited %s to join the party.") MSG(PARTY_INVITE_TARG, "%s: You are invited to join the party. Type /accept to join or /decline to decline the offer.") MSG(PARTY_JOINED, "%s: joined the party.")