using System.Collections.Concurrent; using Google.Protobuf; using Google.Protobuf.WellKnownTypes; using ServerCore; using ServerBase; using ServerCommon; using ServerCommon.BusinessLogDomain; using MetaAssets; using PARTY_GUID = System.String; using USER_GUID = System.String; namespace GameServer; public class GlobalPartyAction : EntityActionBase { private ConcurrentDictionary m_parties { get; set; } = new(); public GlobalPartyAction(GlobalParty owner) : base(owner) { } public override async Task onInit() { await Task.CompletedTask; var result = new Result(); return result; } public override void onClear() { return; } public GlobalPartyDetail? getGlobalPartyDetail(PARTY_GUID party_guid) { if (string.IsNullOrEmpty(party_guid)) return null; return m_parties.GetValueOrDefault(party_guid); } private void setGlobalPartyDetail(GlobalPartyDetail detail) => m_parties.TryAdd(detail.PartyGuid, detail); public async Task createParty(PARTY_GUID party_guid, string leader_guid, string leader_nickname) { var result = new Result(); var owner = getOwner() as GlobalParty; NullReferenceCheckHelper.throwIfNull(owner, () => $"GlobalParty is null !! - party_guid:{party_guid}"); var detail = new GlobalPartyDetail(owner, party_guid); await detail.onInit(); // redis 등록 var detail_action = detail.getEntityAction(); NullReferenceCheckHelper.throwIfNull(detail_action, () => $"GlobalPartyDetailAction is null !! - party_guid:{party_guid}"); result = await detail_action.createPartyDetailInfo(leader_guid, leader_nickname); // 메모리 등록 setGlobalPartyDetail(detail); return result; } private async Task loadParty(PARTY_GUID party_guid) { var owner = getOwner() as GlobalParty; NullReferenceCheckHelper.throwIfNull(owner, () => $"global party is null !!! - party guid: {party_guid}"); var detail = new GlobalPartyDetail(owner, party_guid); await detail.onInit(); var detail_action = detail.getEntityAction(); NullReferenceCheckHelper.throwIfNull(detail_action, () => $"global party detail action is null !!! - {owner.toBasicString()}"); var result = await detail_action.loadAllFromCache(); if (result.isFail()) return result; setGlobalPartyDetail(detail); // 1-1. party server 정보에 server 추가 var global_party_server_action = detail.getEntityAction(); NullReferenceCheckHelper.throwIfNull(global_party_server_action, () => $"global party detail server action is null !!! - {owner.toBasicString()}"); result = await global_party_server_action.addPartyServer(GameServerApp.getServerLogic().getServerName()); if (result.isFail()) return result; // 1-2. 서버 변경 알림 result = await global_party_server_action.notifyChangePartyServerToServers(BoolType.True, GameServerApp.getServerLogic().getServerName()); return result; } public async Task<(string? leader_guid, string? leader_nickname, int? member_count)> getPartyLeaderGuidAndMemberCount(PARTY_GUID party_guid) { var owner = getOwner() as GlobalParty; NullReferenceCheckHelper.throwIfNull(owner, () => $"global party is null !!! - party guid: {party_guid}"); var detail = getGlobalPartyDetail(party_guid); // 메모리에 파티정보가 있으면, 활용 if (null != detail) { var origin_detail_action = detail.getEntityAction(); NullReferenceCheckHelper.throwIfNull(origin_detail_action, () => $"global party detail action is null !!! - {owner.toBasicString()}"); var origin_detail_member_action = detail.getEntityAction(); NullReferenceCheckHelper.throwIfNull(origin_detail_member_action, () => $"global party detail member action is null !!! - {owner.toBasicString()}"); return (origin_detail_action.getLeaderGuid(), origin_detail_action.getLeaderNickname(), origin_detail_member_action.getMemberCount()); } // 메모리에 파티 정보가 없으면, load 하여 활용 ( 단, 메모리 저장은 하지 않음 - 서버내 없는 파티 정보 조회 ) detail = new GlobalPartyDetail(owner, party_guid); await detail.onInit(); var detail_action = detail.getEntityAction(); NullReferenceCheckHelper.throwIfNull(detail_action, () => $"global party detail action is null !!! - {owner.toBasicString()}"); var detail_member_action = detail.getEntityAction(); NullReferenceCheckHelper.throwIfNull(detail_member_action, () => $"global party detail member action is null !!! - {owner.toBasicString()}"); var result = await detail_action.loadPartyCache(); if (result.isFail()) return (null, null, null); result = await detail_action.loadPartyMemberCache(); if (result.isFail()) return (null, null, null); return (detail_action.getLeaderGuid(), detail_action.getLeaderNickname(), detail_member_action.getMemberCount()); } public async Task joinParty(PARTY_GUID party_guid, PartyMemberInfo user) { var result = new Result(); var owner = getOwner() as GlobalParty; if (null == owner) { var err_msg = $"Fail to find global entity !!! : {nameof(GlobalParty)}"; result.setFail(ServerErrorCode.EntityBaseNotFound, err_msg ); Log.getLogger().error(err_msg); return result; } var party = getGlobalPartyDetail(party_guid); // 1. 없으면, Redis 에서 Load 하여 채워 넣음 if (null == party) { result = await loadParty(party_guid); if (result.isFail()) return result; party = getGlobalPartyDetail(party_guid); } NullReferenceCheckHelper.throwIfNull(party, () => $"GlobalDetailParty is null !! - party_guid:{party_guid}"); // 2. member 추가 var global_party_member_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(global_party_member_action, () => $"GlobalPartyDetailMemberAction is null !! - party_guid:{party_guid}"); var add_member = await global_party_member_action.addJoinMember(user); if (add_member.result.isFail()) return add_member.result; // 3. Party p2p group host 전달 ( to client ) var party_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_action, () => $"global party detail action is null !!! - {party.toBasicString()}"); _ = await party_action.joinPartyP2PGroup(user.UserGuid); _ = await party_action.checkPartyP2PState(user.UserGuid, true, true); if (add_member.isAready == true) return add_member.result; // 4. 파티 정보 전달 var send_party_members = new List(); var member_count = global_party_member_action.getMemberCount(); if (member_count == 2) { send_party_members.Add(party_action.getLeaderGuid()); } send_party_members.Add(user.UserGuid); await party_action.sendPartyInfo(send_party_members, party_action.getLeaderGuid(), false); // 5. Party Instance 정보 전달 var party_instance_attribute = party.getEntityAttribute(); NullReferenceCheckHelper.throwIfNull(party_instance_attribute, () => $"party instance attribute is null !!! - {party.toBasicString()}"); if (party_instance_attribute.InstanceId > 0) { party_action.sendPartyInstance(user.UserGuid); } return add_member.result; } public async Task leaveParty(PARTY_GUID party_guid, USER_GUID leave_user_guid, BoolType is_ban) { var result = new Result(); var global_party = getOwner() as GlobalParty; NullReferenceCheckHelper.throwIfNull(global_party, () => $"global party is null !!! - party guid: {party_guid}"); var party = global_party.getParty(party_guid); ArgumentNullException.ThrowIfNull(party); // 1. 파티 멤버에서 삭제 var party_member_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_member_action, () => $"GlobalPartyDetailMemberAction is null !! - party_guid:{party_guid}, {party.toBasicString()}"); var delete_party_member = await party_member_action.deleteJoinMember(leave_user_guid, true); if (delete_party_member.result.isFail()) return delete_party_member.result; // 2. p2p 그룹에서 제외 var party_action = party.getEntityAction(); ArgumentNullException.ThrowIfNull(party_action); _ = party_action.leavePartyP2PGroup(leave_user_guid); _ = party_action.checkPartyP2PState(leave_user_guid, false, false); // 3. 파티 탈퇴 알림 notifyLeavePartyMember(party, leave_user_guid, is_ban); // 4. 초대 메시지 발송 체크 : 초대 메시지에 응답하여 Party 가 결정될 수 있기 때문에 파티원이 1명이어도 Party 를 파괴시키지 않음 var invite_send_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(invite_send_action, () => $"global party invite party send action is null !!! - {party.toBasicString()}"); var sends = await invite_send_action.getInviteSendsCount(); if (sends > 0 && delete_party_member.party_member_count >= 1) { Log.getLogger().debug($"Not Destory Party: Send Invite Count - {sends}"); return result; } // 5. 파티원이 1명으로 파티 파괴 if (delete_party_member.party_member_count <= 1) { result = await destroyParty(party_guid, true); } // 6. party server 체크 else { result = await checkPartyServer(party, leave_user_guid); } return result; } public async Task destroyParty(PARTY_GUID party_guid, bool is_notify_to_servers) { var global_party = getOwner() as GlobalParty; ArgumentNullException.ThrowIfNull(global_party); var party = global_party.getParty(party_guid); if (null == party) return new Result(); // 0. business log 준비 var party_log_data = PartyBusinessLogHelper.toPartyLogData(party_guid, false); var party_business_log = new PartyBusinessLog(new LogActionEx(LogActionType.DestroyParty), party_log_data); // 1. party info 제거 var party_info_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_info_action, () => $"global party detail info action is null !!! - {party.toBasicString()}"); _ = await party_info_action.deleteParty(); // 2. party member 제거 var party_member_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_member_action, () => $"global party detail member action is null !!! - {party.toBasicString()}"); var members = party_member_action.getMembers(); _ = await party_member_action.deletePartyMembers(); // 3. party server 제거 var party_server_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_server_action, () => $"global party detail server action is null !!! - {party.toBasicString()}"); var servers = party_server_action.getServers(); _ = await party_server_action.deleteAllPartyServers(); // 4. invite party send 제거 var party_invite_party_send_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_invite_party_send_action, () => $"global party invite party send action is null !!! - {party.toBasicString()}"); _ = await party_invite_party_send_action.deleteInvitePartySends(); // 5. instance 제거 var party_instance_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_instance_action, () => $"global party detail instance action is null !!! - {party.toBasicString()}"); _ = await party_instance_action.deletePartyInstance(); // 5. p2pgroup 제거 var party_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_action, () => $"global party detail action is null !!! - {party.toBasicString()}"); _ = party_action.destroyPartyP2PGroup(); // 6. member 들의 Party 정보 제거 await clearPersonalPartyWithMembers(members.ToList()); // 7. party 제거 m_parties.Remove(party_guid, out _); // 8. Server 에 알림 if (is_notify_to_servers) { var server_message = new ServerMessage { NtfDestroyParty = new() { DestroyPartyGuid = party_guid } }; PartyHelper.BroadcastToServers(servers.ToList(), server_message, true); } // 9. Client 에게 알림 var client_message = new ClientToGame { Message = new() { DestroyPartyNoti = new() } }; PartyHelper.BroadcastToClients(members.ToList(), client_message, new List()); BusinessLogger.collectLog(party, party_business_log); return new Result(); } private void notifyLeavePartyMember(GlobalPartyDetail party, USER_GUID leave_user_guid, BoolType is_ban) { // 1. 파티원 탈퇴에 따른 알림 ( to server ) var server_message = new ServerMessage { LeavePartyMemberNoti = new() { IsBan = is_ban, PartyGuid = party.PartyGuid, LeavePartyUserGuid = leave_user_guid } }; PartyHelper.BroadcastToServers(party, server_message, true); // 2. 파티원 탈퇴에 따른 알림 ( to client ) var client_message = new ClientToGame { Message = new() { LeavePartyMemberNoti = new() { IsBan = is_ban, LeavePartyUserGuid = leave_user_guid } } }; PartyHelper.BroadcastToClients(party, client_message, new List()); } public async Task checkPartyServer(GlobalPartyDetail party, USER_GUID leave_user_guid) { var result = new Result(); // 1. 서버 내 파티원 체크 var party_member_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_member_action, () => $"GlobalPartyDetailMemberAction is null !!! - {party.toBasicString()}"); var exist_members_without_me = party_member_action.checkExistPartyMembers(leave_user_guid); if (exist_members_without_me) return result; // 2. 서버 정보 제거 var party_server_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_server_action, () => $"GlobalPartyDetailServerAction is null !!! - {party.toBasicString()}"); result = await party_server_action.deletePartyServer(GameServerApp.getServerLogic().getServerName()); if (result.isFail()) return result; // 3. 서버 정보 제거 알림 result = await party_server_action.notifyChangePartyServerToServers(BoolType.False, GameServerApp.getServerLogic().getServerName()); // 4. 파티 정보 제거 m_parties.Remove(party.PartyGuid, out _); return result; } private async Task clearPersonalPartyWithMembers(IReadOnlyList members) { var player_manager = GameServerApp.getServerLogic().getPlayerManager(); foreach (var user in members) { if (!player_manager.tryGetUserByPrimaryKey(user, out var member)) continue; if (null == member) continue; var personal_party_action = member.getEntityAction(); if (null == personal_party_action) continue; await personal_party_action.clearPersonalParty(); } } }