Browse Source

Group fixes/support - requires characters table update

Fix #66

- Fixed crashing of the world server when a client in a group zones
- Tracking of players group_id in the characters table (for both sanity and tracking cross zone)
- Clients zoning now properly 'rejoin' their group
- Mutex locks added to group code, this makes groups more reliably work across different zones as they run on different threads (some behavior for example was some players would not see chat and others would)
image 3 years ago
parent
commit
41716c32fa

+ 1 - 0
DB/updates/characters_table_update_groupid_aug21_2020.sql

@@ -0,0 +1 @@
+alter table characters add column group_id int(10) unsigned not null default '0';

+ 28 - 0
EQ2/source/WorldServer/PlayerGroups.cpp

@@ -62,7 +62,10 @@ bool PlayerGroup::AddMember(Entity* member) {
 
 
 	member->SetGroupMemberInfo(gmi);
 	member->SetGroupMemberInfo(gmi);
 	member->UpdateGroupMemberInfo();
 	member->UpdateGroupMemberInfo();
+
+	MGroupMembers.writelock();
 	m_members.push_back(gmi);
 	m_members.push_back(gmi);
+	MGroupMembers.releasewritelock();
 
 
 	SendGroupUpdate();
 	SendGroupUpdate();
 	return true;
 	return true;
@@ -77,6 +80,7 @@ bool PlayerGroup::RemoveMember(Entity* member) {
 	bool ret = false;
 	bool ret = false;
 
 
 	member->SetGroupMemberInfo(0);
 	member->SetGroupMemberInfo(0);
+	MGroupMembers.writelock();
 	deque<GroupMemberInfo*>::iterator erase_itr = m_members.end();
 	deque<GroupMemberInfo*>::iterator erase_itr = m_members.end();
 	deque<GroupMemberInfo*>::iterator itr;
 	deque<GroupMemberInfo*>::iterator itr;
 	for (itr = m_members.begin(); itr != m_members.end(); itr++) {
 	for (itr = m_members.begin(); itr != m_members.end(); itr++) {
@@ -89,6 +93,7 @@ bool PlayerGroup::RemoveMember(Entity* member) {
 		ret = true;
 		ret = true;
 		m_members.erase(erase_itr);
 		m_members.erase(erase_itr);
 	}
 	}
+	MGroupMembers.releasewritelock();
 
 
 	safe_delete(gmi);
 	safe_delete(gmi);
 	if (member->IsBot())
 	if (member->IsBot())
@@ -99,6 +104,7 @@ bool PlayerGroup::RemoveMember(Entity* member) {
 
 
 void PlayerGroup::Disband() {
 void PlayerGroup::Disband() {
 	deque<GroupMemberInfo*>::iterator itr;
 	deque<GroupMemberInfo*>::iterator itr;
+	MGroupMembers.writelock();
 	for (itr = m_members.begin(); itr != m_members.end(); itr++) {
 	for (itr = m_members.begin(); itr != m_members.end(); itr++) {
 		if ((*itr)->member) {
 		if ((*itr)->member) {
 			(*itr)->member->SetGroupMemberInfo(0);
 			(*itr)->member->SetGroupMemberInfo(0);
@@ -112,37 +118,45 @@ void PlayerGroup::Disband() {
 	}
 	}
 
 
 	m_members.clear();
 	m_members.clear();
+	MGroupMembers.releasewritelock();
 }
 }
 
 
 void PlayerGroup::SendGroupUpdate(Client* exclude) {
 void PlayerGroup::SendGroupUpdate(Client* exclude) {
 	deque<GroupMemberInfo*>::iterator itr;
 	deque<GroupMemberInfo*>::iterator itr;
+	MGroupMembers.readlock();
 	for (itr = m_members.begin(); itr != m_members.end(); itr++) {
 	for (itr = m_members.begin(); itr != m_members.end(); itr++) {
 		GroupMemberInfo* gmi = *itr;
 		GroupMemberInfo* gmi = *itr;
 		if (gmi->client && gmi->client != exclude && !gmi->client->IsZoning())
 		if (gmi->client && gmi->client != exclude && !gmi->client->IsZoning())
 			gmi->client->GetPlayer()->SetCharSheetChanged(true);
 			gmi->client->GetPlayer()->SetCharSheetChanged(true);
 	}
 	}
+	MGroupMembers.releasereadlock();
 }
 }
 
 
 void PlayerGroup::SimpleGroupMessage(const char* message) {
 void PlayerGroup::SimpleGroupMessage(const char* message) {
 	deque<GroupMemberInfo*>::iterator itr;
 	deque<GroupMemberInfo*>::iterator itr;
+	MGroupMembers.readlock();
 	for(itr = m_members.begin(); itr != m_members.end(); itr++) {
 	for(itr = m_members.begin(); itr != m_members.end(); itr++) {
 		GroupMemberInfo* info = *itr;
 		GroupMemberInfo* info = *itr;
 		if(info->client)
 		if(info->client)
 			info->client->SimpleMessage(CHANNEL_GROUP, message);
 			info->client->SimpleMessage(CHANNEL_GROUP, message);
 	}
 	}
+	MGroupMembers.releasereadlock();
 }
 }
 
 
 void PlayerGroup::GroupChatMessage(Spawn* from, const char* message) {
 void PlayerGroup::GroupChatMessage(Spawn* from, const char* message) {
 	deque<GroupMemberInfo*>::iterator itr;
 	deque<GroupMemberInfo*>::iterator itr;
+	MGroupMembers.readlock();
 	for(itr = m_members.begin(); itr != m_members.end(); itr++) {
 	for(itr = m_members.begin(); itr != m_members.end(); itr++) {
 		GroupMemberInfo* info = *itr;
 		GroupMemberInfo* info = *itr;
 		if(info && info->client && info->client->GetCurrentZone())
 		if(info && info->client && info->client->GetCurrentZone())
 			info->client->GetCurrentZone()->HandleChatMessage(info->client, from, 0, CHANNEL_GROUP_SAY, message, 0);
 			info->client->GetCurrentZone()->HandleChatMessage(info->client, from, 0, CHANNEL_GROUP_SAY, message, 0);
 	}
 	}
+	MGroupMembers.releasereadlock();
 }
 }
 
 
 void PlayerGroup::MakeLeader(Entity* new_leader) {
 void PlayerGroup::MakeLeader(Entity* new_leader) {
 	deque<GroupMemberInfo*>::iterator itr;
 	deque<GroupMemberInfo*>::iterator itr;
+	MGroupMembers.readlock();
 	for (itr = m_members.begin(); itr != m_members.end(); itr++) {
 	for (itr = m_members.begin(); itr != m_members.end(); itr++) {
 		GroupMemberInfo* info = *itr;
 		GroupMemberInfo* info = *itr;
 		if (info->leader) {
 		if (info->leader) {
@@ -150,6 +164,7 @@ void PlayerGroup::MakeLeader(Entity* new_leader) {
 			break;
 			break;
 		}
 		}
 	}
 	}
+	MGroupMembers.releasereadlock();
 
 
 	new_leader->GetGroupMemberInfo()->leader = true;
 	new_leader->GetGroupMemberInfo()->leader = true;
 	SendGroupUpdate();
 	SendGroupUpdate();
@@ -388,6 +403,13 @@ void PlayerGroupManager::SendGroupUpdate(int32 group_id, Client* exclude) {
 	MGroups.releasewritelock(__FUNCTION__, __LINE__);
 	MGroups.releasewritelock(__FUNCTION__, __LINE__);
 }
 }
 
 
+PlayerGroup* PlayerGroupManager::GetGroup(int32 group_id) {
+	if (m_groups.count(group_id) > 0)
+		return m_groups[group_id];
+
+	return 0;
+}
+
 deque<GroupMemberInfo*>* PlayerGroupManager::GetGroupMembers(int32 group_id) {
 deque<GroupMemberInfo*>* PlayerGroupManager::GetGroupMembers(int32 group_id) {
 	if (m_groups.count(group_id) > 0)
 	if (m_groups.count(group_id) > 0)
 		return m_groups[group_id]->GetMembers();
 		return m_groups[group_id]->GetMembers();
@@ -479,6 +501,7 @@ void PlayerGroupManager::SendGroupQuests(int32 group_id, Client* client) {
 	GroupMemberInfo* info = 0;
 	GroupMemberInfo* info = 0;
 	MGroups.readlock(__FUNCTION__, __LINE__);
 	MGroups.readlock(__FUNCTION__, __LINE__);
 	if (m_groups.count(group_id) > 0) {
 	if (m_groups.count(group_id) > 0) {
+		m_groups[group_id]->MGroupMembers.readlock();
 		deque<GroupMemberInfo*>* members = m_groups[group_id]->GetMembers();
 		deque<GroupMemberInfo*>* members = m_groups[group_id]->GetMembers();
 		deque<GroupMemberInfo*>::iterator itr;
 		deque<GroupMemberInfo*>::iterator itr;
 		for (itr = members->begin(); itr != members->end(); itr++) {
 		for (itr = members->begin(); itr != members->end(); itr++) {
@@ -489,6 +512,7 @@ void PlayerGroupManager::SendGroupQuests(int32 group_id, Client* client) {
 				client->SendQuestJournal(false, info->client);
 				client->SendQuestJournal(false, info->client);
 			}
 			}
 		}
 		}
+		m_groups[group_id]->MGroupMembers.releasereadlock();
 	}
 	}
 	MGroups.releasereadlock(__FUNCTION__, __LINE__);
 	MGroups.releasereadlock(__FUNCTION__, __LINE__);
 }
 }
@@ -560,6 +584,7 @@ void PlayerGroupManager::UpdateGroupBuffs() {
 		/* loop through the group members and see if any of them have any maintained spells that are group buffs and friendly.
 		/* loop through the group members and see if any of them have any maintained spells that are group buffs and friendly.
 		if so, update the list of targets and apply/remove effects as needed */
 		if so, update the list of targets and apply/remove effects as needed */
 
 
+		group->MGroupMembers.readlock();
 		for (member_itr = group->GetMembers()->begin(); member_itr != group->GetMembers()->end(); member_itr++) {
 		for (member_itr = group->GetMembers()->begin(); member_itr != group->GetMembers()->end(); member_itr++) {
 			if ((*member_itr)->client)
 			if ((*member_itr)->client)
 				caster = (*member_itr)->client->GetPlayer();
 				caster = (*member_itr)->client->GetPlayer();
@@ -695,6 +720,7 @@ void PlayerGroupManager::UpdateGroupBuffs() {
 			}
 			}
 			caster->GetMaintainedMutex()->releasereadlock(__FUNCTION__, __LINE__);
 			caster->GetMaintainedMutex()->releasereadlock(__FUNCTION__, __LINE__);
 		}
 		}
+		group->MGroupMembers.releasereadlock();
 	}
 	}
 }
 }
 
 
@@ -704,6 +730,7 @@ bool PlayerGroupManager::IsInGroup(int32 group_id, Entity* member) {
 	MGroups.readlock(__FUNCTION__, __LINE__);
 	MGroups.readlock(__FUNCTION__, __LINE__);
 
 
 	if (m_groups.count(group_id) > 0) {
 	if (m_groups.count(group_id) > 0) {
+		m_groups[group_id]->MGroupMembers.readlock();
 		deque<GroupMemberInfo*>* members = m_groups[group_id]->GetMembers();
 		deque<GroupMemberInfo*>* members = m_groups[group_id]->GetMembers();
 		for (int8 i = 0; i < members->size(); i++) {
 		for (int8 i = 0; i < members->size(); i++) {
 			if (member == members->at(i)->member) {
 			if (member == members->at(i)->member) {
@@ -711,6 +738,7 @@ bool PlayerGroupManager::IsInGroup(int32 group_id, Entity* member) {
 				break;
 				break;
 			}
 			}
 		}
 		}
+		m_groups[group_id]->MGroupMembers.releasereadlock();
 	}
 	}
 
 
 	MGroups.releasereadlock(__FUNCTION__, __LINE__);
 	MGroups.releasereadlock(__FUNCTION__, __LINE__);

+ 4 - 0
EQ2/source/WorldServer/PlayerGroups.h

@@ -83,6 +83,7 @@ public:
 	void GroupChatMessage(Spawn* from, const char* message);
 	void GroupChatMessage(Spawn* from, const char* message);
 	void MakeLeader(Entity* new_leader);
 	void MakeLeader(Entity* new_leader);
 
 
+	Mutex MGroupMembers;				// Mutex for the group members
 private:
 private:
 	int32					m_id;		// ID of this group
 	int32					m_id;		// ID of this group
 	deque<GroupMemberInfo*>	m_members;	// List of members in this group
 	deque<GroupMemberInfo*>	m_members;	// List of members in this group
@@ -140,6 +141,9 @@ public:
 	/// <param name='exclude'>Client* to exclude from the update, usually the one that triggers the update</param>
 	/// <param name='exclude'>Client* to exclude from the update, usually the one that triggers the update</param>
 	void SendGroupUpdate(int32 group_id, Client* exclude = 0);
 	void SendGroupUpdate(int32 group_id, Client* exclude = 0);
 
 
+
+	PlayerGroup* GetGroup(int32 group_id);
+
 	/// <summary>
 	/// <summary>
 	/// Gets the group members for the given group, be sure to call GroupLock() before calling this and ReleaseGroupLock() after you 
 	/// Gets the group members for the given group, be sure to call GroupLock() before calling this and ReleaseGroupLock() after you 
 	/// are done with the list.  This is for reading purposes only, the list should never be altered using this function
 	/// are done with the list.  This is for reading purposes only, the list should never be altered using this function

+ 50 - 18
EQ2/source/WorldServer/World.cpp

@@ -1459,27 +1459,59 @@ void World::SendGroupQuests(PlayerGroup* group, Client* client){
 	}
 	}
 }*/
 }*/
 
 
-void World::RejoinGroup(Client* client){
-	/*map<GroupMemberInfo*, int32>::iterator itr;
-	GroupMemberInfo* found = 0;
+void World::RejoinGroup(Client* client, int32 group_id){
+	if (!group_id) // no need if no group id!
+		return;
+
+	world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__);
+	PlayerGroup* group = world.GetGroupManager()->GetGroup(group_id);
+	deque<GroupMemberInfo*>* members = 0;
+	if (group)
+		members = group->GetMembers();
+
 	string name = string(client->GetPlayer()->GetName());
 	string name = string(client->GetPlayer()->GetName());
-	MGroups.readlock(__FUNCTION__, __LINE__);
-	PlayerGroup* group = 0;
-	for(int i = player_groups.size()-1;!found && i >= 0;i--){
-		group = player_groups[i];
-		for(int x=group->members.size()-1;x>=0; x--){
-			if(group->members[x]->name == name){
-				found = group->members[x];
-				break;
-			}
+	if (!members)
+	{
+		// group does not exist!
+
+		Query query;
+		query.AddQueryAsync(client->GetCharacterID(), &database, Q_INSERT, "UPDATE characters set group_id = 0 where id = %u",
+			client->GetCharacterID());
+		LogWrite(PLAYER__ERROR, 0, "Player", "Group did not exist for player %s to group id %i, async query to group_id = 0.", name.c_str(), group_id);
+		world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
+		return;
+	}
+	deque<GroupMemberInfo*>::iterator itr;
+	GroupMemberInfo* info = 0;
+
+	bool match = false;
+	group->MGroupMembers.writelock();
+	for (itr = members->begin(); itr != members->end(); itr++) {
+
+		info = *itr;
+
+		if (info && info->name == name)
+		{
+			info->client = client;
+			info->member = client->GetPlayer();
+			client->GetPlayer()->SetGroup(group);
+			client->GetPlayer()->SetGroupMemberInfo(info);
+			client->GetPlayer()->UpdateGroupMemberInfo();
+			LogWrite(PLAYER__DEBUG, 0, "Player", "Identified group match for player %s to group id %u", name.c_str(), group_id);
+			match = true;
+			break;
 		}
 		}
 	}
 	}
-	MGroups.releasereadlock(__FUNCTION__, __LINE__);
-	if(found){
-		found->client = client;
-		client->GetPlayer()->SetGroup(found->group);
-		client->GetPlayer()->SetGroupMemberInfo(found);
-	}*/
+	group->MGroupMembers.releasewritelock();
+
+	// must be done after cause it needs a readlock
+	if (match)
+		group->SendGroupUpdate();
+
+	if (!match)
+		LogWrite(PLAYER__ERROR, 0, "Player", "Identified group match for player %s to group id %u, however the player name was not present in the group!  May be an old group id that has been re-used.", name.c_str(), group_id);
+	
+	world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
 }
 }
 
 
 
 

+ 1 - 1
EQ2/source/WorldServer/World.h

@@ -563,7 +563,7 @@ public:
 	//void GroupReadUnLock();
 	//void GroupReadUnLock();
 	//void CheckRemoveGroupedPlayer();
 	//void CheckRemoveGroupedPlayer();
 	//void SendGroupUpdate(PlayerGroup* group, Client* exclude = 0);
 	//void SendGroupUpdate(PlayerGroup* group, Client* exclude = 0);
-	void RejoinGroup(Client* client);
+	void RejoinGroup(Client* client, int32 group_id);
 	//bool MakeLeader(Client* leader, string new_leader);
 	//bool MakeLeader(Client* leader, string new_leader);
 	
 	
 	void AddBonuses(ItemStatsValues* values, int16 type, sint32 value, Entity* entity);
 	void AddBonuses(ItemStatsValues* values, int16 type, sint32 value, Entity* entity);

+ 9 - 5
EQ2/source/WorldServer/WorldDatabase.cpp

@@ -1530,7 +1530,7 @@ bool WorldDatabase::loadCharacter(const char* ch_name, int32 account_id, Client*
 	MYSQL_ROW row, row4;
 	MYSQL_ROW row, row4;
 	int32 id = 0;
 	int32 id = 0;
 	query.escaped_name = getEscapeString(ch_name);
 	query.escaped_name = getEscapeString(ch_name);
-	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, current_zone_id, x, y, z, heading, admin_status, race, model_type, class, deity, level, gender, tradeskill_class, tradeskill_level, wing_type, hair_type, chest_type, legs_type, soga_wing_type, soga_hair_type, soga_chest_type, soga_legs_type, 0xFFFFFFFF - crc32(name), facial_hair_type, soga_facial_hair_type,instance_id,last_saved, DATEDIFF(curdate(), created_date) as accage FROM characters where name='%s' and account_id=%i AND deleted = 0", query.escaped_name, account_id);
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, current_zone_id, x, y, z, heading, admin_status, race, model_type, class, deity, level, gender, tradeskill_class, tradeskill_level, wing_type, hair_type, chest_type, legs_type, soga_wing_type, soga_hair_type, soga_chest_type, soga_legs_type, 0xFFFFFFFF - crc32(name), facial_hair_type, soga_facial_hair_type, instance_id, group_id, last_saved, DATEDIFF(curdate(), created_date) as accage FROM characters where name='%s' and account_id=%i AND deleted = 0", query.escaped_name, account_id);
 	// no character found
 	// no character found
 	if ( result == NULL ) {
 	if ( result == NULL ) {
 		LogWrite(PLAYER__ERROR, 0, "Player", "Error loading character for '%s'", ch_name);
 		LogWrite(PLAYER__ERROR, 0, "Player", "Error loading character for '%s'", ch_name);
@@ -1578,6 +1578,10 @@ bool WorldDatabase::loadCharacter(const char* ch_name, int32 account_id, Client*
 		client->GetPlayer()->features.hair_face_type = atoi(row[24]);
 		client->GetPlayer()->features.hair_face_type = atoi(row[24]);
 		client->GetPlayer()->features.soga_hair_face_type = atoi(row[25]);
 		client->GetPlayer()->features.soga_hair_face_type = atoi(row[25]);
 		int32 instanceid = atoi(row[26]);
 		int32 instanceid = atoi(row[26]);
+
+		int32 groupid = atoi(row[27]);
+		client->SetRejoinGroupID(groupid);
+
 		int32 zoneid = atoul(row[1]);
 		int32 zoneid = atoul(row[1]);
 /*
 /*
 JA Notes on SOGA: I think there are many more settings to add than were commented out here,
 JA Notes on SOGA: I think there are many more settings to add than were commented out here,
@@ -1596,11 +1600,11 @@ SOGA chars looked ok in LoginServer screen tho... odd.
 
 
 
 
 
 
-		int32 lastsavedtime = atoi(row[27]);
+		int32 lastsavedtime = atoi(row[28]);
 		client->SetLastSavedTimeStamp(lastsavedtime);
 		client->SetLastSavedTimeStamp(lastsavedtime);
 
 
-		if (row[28])
-			client->GetPlayer()->GetPlayerInfo()->SetAccountAge(atoi(row[28]));
+		if (row[29])
+			client->GetPlayer()->GetPlayerInfo()->SetAccountAge(atoi(row[29]));
 
 
 		LoadCharacterFriendsIgnoreList(client->GetPlayer());
 		LoadCharacterFriendsIgnoreList(client->GetPlayer());
 		MYSQL_RES* result4 = query4.RunQuery2(Q_SELECT, "SELECT `guild_id` FROM `guild_members` WHERE `char_id`=%u", id);
 		MYSQL_RES* result4 = query4.RunQuery2(Q_SELECT, "SELECT `guild_id` FROM `guild_members` WHERE `char_id`=%u", id);
@@ -3697,7 +3701,7 @@ void WorldDatabase::Save(Client* client){
 	int32 zone_id = 0;
 	int32 zone_id = 0;
 	if(client->GetCurrentZone())
 	if(client->GetCurrentZone())
 		zone_id = client->GetCurrentZone()->GetZoneID();
 		zone_id = client->GetCurrentZone()->GetZoneID();
-	query.AddQueryAsync(client->GetCharacterID(), this, Q_UPDATE, "update characters set current_zone_id=%u, x=%f, y=%f, z=%f, heading=%f, level=%i,instance_id=%i,last_saved=%i, `class`=%i, `tradeskill_level`=%i, `tradeskill_class`=%i where id = %u", zone_id, player->GetX(), player->GetY(), player->GetZ(), player->GetHeading(), player->GetLevel(), instance_id, client->GetLastSavedTimeStamp(), client->GetPlayer()->GetAdventureClass(), client->GetPlayer()->GetTSLevel(), client->GetPlayer()->GetTradeskillClass(), client->GetCharacterID());
+	query.AddQueryAsync(client->GetCharacterID(), this, Q_UPDATE, "update characters set current_zone_id=%u, x=%f, y=%f, z=%f, heading=%f, level=%i,instance_id=%i,last_saved=%i, `class`=%i, `tradeskill_level`=%i, `tradeskill_class`=%i, `group_id`=%u where id = %u", zone_id, player->GetX(), player->GetY(), player->GetZ(), player->GetHeading(), player->GetLevel(), instance_id, client->GetLastSavedTimeStamp(), client->GetPlayer()->GetAdventureClass(), client->GetPlayer()->GetTSLevel(), client->GetPlayer()->GetTradeskillClass(), client->GetPlayer()->GetGroupMemberInfo() ? client->GetPlayer()->GetGroupMemberInfo()->group_id : 0, client->GetCharacterID());
 	query.AddQueryAsync(client->GetCharacterID(), this, Q_UPDATE, "update character_details set hp=%u, power=%u, str=%i, sta=%i, agi=%i, wis=%i, intel=%i, heat=%i, cold=%i, magic=%i, mental=%i, divine=%i, disease=%i, poison=%i, coin_copper=%u, coin_silver=%u, coin_gold=%u, coin_plat=%u, max_hp = %u, max_power=%u, xp = %u, xp_needed = %u, xp_debt = %u, xp_vitality = %f, tradeskill_xp = %u, tradeskill_xp_needed = %u, tradeskill_xp_vitality = %f, bank_copper = %u, bank_silver = %u, bank_gold = %u, bank_plat = %u, bind_zone_id=%u, bind_x = %f, bind_y = %f, bind_z = %f, bind_heading = %f, house_zone_id=%u, combat_voice = %i, emote_voice = %i, biography='%s', flags=%u, flags2=%u, last_name='%s' where char_id = %u",
 	query.AddQueryAsync(client->GetCharacterID(), this, Q_UPDATE, "update character_details set hp=%u, power=%u, str=%i, sta=%i, agi=%i, wis=%i, intel=%i, heat=%i, cold=%i, magic=%i, mental=%i, divine=%i, disease=%i, poison=%i, coin_copper=%u, coin_silver=%u, coin_gold=%u, coin_plat=%u, max_hp = %u, max_power=%u, xp = %u, xp_needed = %u, xp_debt = %u, xp_vitality = %f, tradeskill_xp = %u, tradeskill_xp_needed = %u, tradeskill_xp_vitality = %f, bank_copper = %u, bank_silver = %u, bank_gold = %u, bank_plat = %u, bind_zone_id=%u, bind_x = %f, bind_y = %f, bind_z = %f, bind_heading = %f, house_zone_id=%u, combat_voice = %i, emote_voice = %i, biography='%s', flags=%u, flags2=%u, last_name='%s' where char_id = %u",
 		player->GetHP(), player->GetPower(), player->GetStrBase(), player->GetStaBase(), player->GetAgiBase(), player->GetWisBase(), player->GetIntBase(), player->GetHeatResistanceBase(), player->GetColdResistanceBase(), player->GetMagicResistanceBase(),
 		player->GetHP(), player->GetPower(), player->GetStrBase(), player->GetStaBase(), player->GetAgiBase(), player->GetWisBase(), player->GetIntBase(), player->GetHeatResistanceBase(), player->GetColdResistanceBase(), player->GetMagicResistanceBase(),
 		player->GetMentalResistanceBase(), player->GetDivineResistanceBase(), player->GetDiseaseResistanceBase(), player->GetPoisonResistanceBase(), player->GetCoinsCopper(), player->GetCoinsSilver(), player->GetCoinsGold(), player->GetCoinsPlat(), player->GetTotalHPBase(), player->GetTotalPowerBase(), player->GetXP(), player->GetNeededXP(), player->GetXPDebt(), player->GetXPVitality(), player->GetTSXP(), player->GetNeededTSXP(), player->GetTSXPVitality(), player->GetBankCoinsCopper(),
 		player->GetMentalResistanceBase(), player->GetDivineResistanceBase(), player->GetDiseaseResistanceBase(), player->GetPoisonResistanceBase(), player->GetCoinsCopper(), player->GetCoinsSilver(), player->GetCoinsGold(), player->GetCoinsPlat(), player->GetTotalHPBase(), player->GetTotalPowerBase(), player->GetXP(), player->GetNeededXP(), player->GetXPDebt(), player->GetXPVitality(), player->GetTSXP(), player->GetNeededTSXP(), player->GetTSXPVitality(), player->GetBankCoinsCopper(),

+ 14 - 1
EQ2/source/WorldServer/client.cpp

@@ -194,6 +194,7 @@ Client::Client(EQStream* ieqs) : pos_update(125), quest_pos_timer(2000), lua_deb
 	placement_unique_item_id = 0;
 	placement_unique_item_id = 0;
 	SetHasOwnerOrEditAccess(false);
 	SetHasOwnerOrEditAccess(false);
 	temporary_transport_id = 0;
 	temporary_transport_id = 0;
+	rejoin_group_id = 0;
 }
 }
 
 
 Client::~Client() {
 Client::~Client() {
@@ -201,6 +202,7 @@ Client::~Client() {
 		if (player->GetGroupMemberInfo() && (player->GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD) > 0)
 		if (player->GetGroupMemberInfo() && (player->GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD) > 0)
 			world.GetGroupManager()->RemoveGroupMember(player->GetGroupMemberInfo()->group_id, player);
 			world.GetGroupManager()->RemoveGroupMember(player->GetGroupMemberInfo()->group_id, player);
 		world.GetGroupManager()->ClearPendingInvite(player);
 		world.GetGroupManager()->ClearPendingInvite(player);
+
 	}
 	}
 	if (lua_interface)
 	if (lua_interface)
 		lua_interface->RemoveDebugClients(this);
 		lua_interface->RemoveDebugClients(this);
@@ -3506,6 +3508,17 @@ void Client::Zone(ZoneServer* new_zone, bool set_coords) {
 		LogWrite(CCLIENT__DEBUG, 0, "Client", "Zone Request Denied! No 'new_zone' value");
 		LogWrite(CCLIENT__DEBUG, 0, "Client", "Zone Request Denied! No 'new_zone' value");
 		return;
 		return;
 	}
 	}
+
+	// block out the member info for the group
+	if (this->GetPlayer()->GetGroupMemberInfo())
+	{
+		PlayerGroup* group = world.GetGroupManager()->GetGroup(this->GetPlayer()->GetGroupMemberInfo()->group_id);
+		group->MGroupMembers.writelock();
+		this->GetPlayer()->GetGroupMemberInfo()->client = 0;
+		this->GetPlayer()->GetGroupMemberInfo()->member = 0;
+		group->MGroupMembers.releasewritelock();
+	}
+
 	client_zoning = true;
 	client_zoning = true;
 	LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Setting player Resurrecting to 'true'", __FUNCTION__);
 	LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Setting player Resurrecting to 'true'", __FUNCTION__);
 	player->SetResurrecting(true);
 	player->SetResurrecting(true);
@@ -8693,7 +8706,7 @@ bool Client::HandleNewLogin(int32 account_id, int32 access_code)
 				new_client_login = true;
 				new_client_login = true;
 				GetCurrentZone()->AddClient(this); //add to zones client list
 				GetCurrentZone()->AddClient(this); //add to zones client list
 
 
-				world.RejoinGroup(this);
+				world.RejoinGroup(this, rejoin_group_id);
 				zone_list.AddClientToMap(player->GetName(), this);
 				zone_list.AddClientToMap(player->GetName(), this);
 			}
 			}
 			else {
 			else {

+ 4 - 0
EQ2/source/WorldServer/client.h

@@ -433,6 +433,8 @@ public:
 
 
 	void SetTemporaryTransportID(int32 id) { temporary_transport_id = id; }
 	void SetTemporaryTransportID(int32 id) { temporary_transport_id = id; }
 	int32 GetTemporaryTransportID() { return temporary_transport_id; }
 	int32 GetTemporaryTransportID() { return temporary_transport_id; }
+
+	void SetRejoinGroupID(int32 id) { rejoin_group_id = id; }
 private:
 private:
 	void    SavePlayerImages();
 	void    SavePlayerImages();
 	void	SkillChanged(Skill* skill, int16 previous_value, int16 new_value);
 	void	SkillChanged(Skill* skill, int16 previous_value, int16 new_value);
@@ -534,6 +536,8 @@ private:
 	bool hasOwnerOrEditAccess;
 	bool hasOwnerOrEditAccess;
 
 
 	int32 temporary_transport_id;
 	int32 temporary_transport_id;
+
+	int32 rejoin_group_id;
 };
 };
 
 
 class ClientList {
 class ClientList {