using Google.Protobuf.WellKnownTypes; using Nettention.Proud ; using ServerCore; using ServerBase; using ServerCommon; using ServerCommon.BusinessLogDomain; using MetaAssets; using USER_GUID = System.String; namespace GameServer; public class GlobalPartyDetailAction : EntityActionBase { public GlobalPartyDetailAction(GlobalPartyDetail owner) : base(owner) { } public override async Task onInit() { await Task.CompletedTask; var result = new Result(); return result; } public override void onClear() { return; } public async Task keepParty() { // 1. party keep var party_action = getOwner().getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_action, () => $"GlobalPartyDetailInfoAction is null !!! - {getOwner().toBasicString()}"); await party_action.keep(); // 2. party member keep var party_member_action = getOwner().getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_member_action, () => $"GlobalPartyDetailMemberAction is null !!! - {getOwner().toBasicString()}"); await party_member_action.keep(); // 3. party server keep var party_server_action = getOwner().getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_server_action, () => $"GlobalPartyDetailServerAction is null !!! - {getOwner().toBasicString()}"); await party_server_action.keep(); // 4. party invite send keep var party_invite_send_action = getOwner().getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_invite_send_action, () => $"GlobalPartyInvitePartySendAction is null !!! - {getOwner().toBasicString()}"); await party_invite_send_action.keep(); // 5. party instance keep var party_instance_action = getOwner().getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_instance_action, () => $"GlobalPartyDetailInstanceAction is null !!! - {getOwner().toBasicString()}"); await party_instance_action.keep(); return new Result(); } public bool isLeader(USER_GUID user_guid) { var detail = getOwner() as GlobalPartyDetail; NullReferenceCheckHelper.throwIfNull(detail, () => $"global party detail is null !!! - {getOwner().toBasicString()}"); var party_attribute = detail.getEntityAttribute(); NullReferenceCheckHelper.throwIfNull(party_attribute, () => $"PartyAttribute is null !!! - user guid:{user_guid}, {detail.toBasicString()}"); return party_attribute.PartyLeaderCharGuid == user_guid; } public USER_GUID getLeaderGuid() { var detail = getOwner() as GlobalPartyDetail; NullReferenceCheckHelper.throwIfNull(detail, () => $"global party detail is null !!! - {getOwner().toBasicString()}"); var party_attribute = detail.getEntityAttribute(); NullReferenceCheckHelper.throwIfNull(party_attribute, () => $"PartyAttribute is null !!! - {detail.toBasicString()}"); return party_attribute.PartyLeaderCharGuid; } public string getLeaderNickname() { var detail = getOwner() as GlobalPartyDetail; NullReferenceCheckHelper.throwIfNull(detail, () => $"global party detail is null !!! - {getOwner().toBasicString()}"); var party_attribute = detail.getEntityAttribute(); NullReferenceCheckHelper.throwIfNull(party_attribute, () => $"PartyAttribute is null !!! - {detail.toBasicString()}"); return party_attribute.PartyLeaderNickname; } public async Task<(Result result, string change_leader_guid)> changePartyLeader() { var result = new Result(); var party = getOwner() as GlobalPartyDetail; NullReferenceCheckHelper.throwIfNull(party, () => $"global party detail is null !!! - {getOwner().toBasicString()}"); var party_member_attribute = party.getEntityAttribute(); NullReferenceCheckHelper.throwIfNull(party_member_attribute, () => $"PartyMemberAttribute is null !! - party_guid:{party.PartyGuid}"); // 1. 파티장 자동 변경 ( 조건 1. jointime 이 가장 오래된 유저 ) var origin_leader_guid = getLeaderGuid(); var next_leader_guid = origin_leader_guid; var join_time = DateTime.UtcNow.ToTimestamp(); foreach (var member in party_member_attribute.getPartyMembers()) { if (origin_leader_guid == member.Value.UserGuid) continue; if (join_time < member.Value.JoinTime) continue; next_leader_guid = member.Value.UserGuid; join_time = member.Value.JoinTime; } // 2. 파티장 변경 처리 var party_detail_info_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_detail_info_action, () => $"GlobalPartyDetailInfoAction is null !! - party_guid:{party.PartyGuid}"); result = await party_detail_info_action.changePartyLeader(next_leader_guid, true); // 3. 파티장 변경 알림 var message = new ServerMessage { ChangePartyLeaderNoti = new ServerMessage.Types.ChangePartyLeaderNoti() { PartyGuid = party.PartyGuid, NewPartyLeaderGuid = next_leader_guid } }; PartyHelper.BroadcastToServers(party, message, false); return (result, next_leader_guid); } public HostID getP2PHostId() { var party_attribute = getOwner().getEntityAttribute(); ArgumentNullException.ThrowIfNull(party_attribute); return party_attribute.P2PGroup; } public void sendPartyInstance(USER_GUID user_guid) { var result = new Result(); var party_instance_attribute = getOwner().getEntityAttribute(); NullReferenceCheckHelper.throwIfNull(party_instance_attribute, () => $"PartyInstanceAttribute is null !! - {getOwner().toBasicString()}"); if (party_instance_attribute.InstanceId <= 0) { return; } var client_message = new ClientToGame(); client_message.Message = new(); client_message.Message.PartyInstanceInfoNoti = new(); client_message.Message.PartyInstanceInfoNoti.InstanceId = party_instance_attribute.InstanceId; client_message.Message.PartyInstanceInfoNoti.StartTime = party_instance_attribute.StartTime; client_message.Message.PartyInstanceInfoNoti.EndTime = party_instance_attribute.EndTime; client_message.Message.PartyInstanceInfoNoti.JoinMemberCount = party_instance_attribute.JoinMemberCount; client_message.Message.PartyInstanceInfoNoti.IsEnd = party_instance_attribute.InstanceId == 0 ? BoolType.True : BoolType.False; var player_manager = GameServerApp.getServerLogic().getPlayerManager(); if (! player_manager.tryGetUserByPrimaryKey(user_guid, out var user_entity) || null == user_entity) { var err_msg = $"Failed to send party instance !!! : not logged in server - {user_guid}"; result.setFail(ServerErrorCode.UserNotLogin,err_msg); Log.getLogger().error(err_msg); return; } PartyHelper.sendToClient(client_message, user_entity.getHostId()); } public async Task sendPartyInfo(IReadOnlyList sendUsers, USER_GUID leaderGuid, bool exceptAnotherServer) { var party = getOwner() as GlobalPartyDetail; NullReferenceCheckHelper.throwIfNull(party, () => $"party is null !!"); var party_info_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_info_action, () => $"GlobalPartyDetailInfoAction is null !! - {party.toBasicString()}"); var party_member_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_member_action, () => $"GlobalPartyDetailMemberAction is null !! - {party.toBasicString()}"); var player_manager = GameServerApp.getServerLogic().getPlayerManager(); foreach (var send in sendUsers) { if (player_manager.tryGetUserByPrimaryKey(send, out var player)) { var client_message = new ClientToGame(); client_message.Message = new(); client_message.Message.JoinPartyInfoNoti = new(); client_message.Message.JoinPartyInfoNoti.PartyName = party_info_action.getPartyName(); client_message.Message.JoinPartyInfoNoti.PartyLeaderGuid = getLeaderGuid(); client_message.Message.JoinPartyInfoNoti.PartyLeaderNickname = getLeaderNickname(); client_message.Message.JoinPartyInfoNoti.PartyMemberList.AddRange(party_member_action.getMembers4PartyInfo().ToList()); if (leaderGuid != send) { client_message.Message.JoinPartyInfoNoti.ServerConnectInfo = await tryMoveToLeaderServer(leaderGuid, send); } if (null == player) continue; PartyHelper.sendToClient(client_message, player.getHostId()); await QuestManager.It.QuestCheck(player, new QuestParty(EQuestEventTargetType.PARTY, EQuestEventNameType.ENTERED)); continue; } if (exceptAnotherServer) continue; var server_message = new ServerMessage(); server_message.NtfPartyInfo = new(); server_message.NtfPartyInfo.PartyGuid = party.PartyGuid; server_message.NtfPartyInfo.PartyMemberGuids.Add(send); await PartyHelper.sendToServerByTargetClient(send, server_message); } return await Task.FromResult(new Result()); } public async Task joinPartyP2PGroup(USER_GUID user_guid) { var result = new Result(); var player_manager = GameServerApp.getServerLogic().getPlayerManager(); if (! player_manager.tryGetUserByPrimaryKey(user_guid, out var user_entity)) { var err_msg = $"Failed to join party p2p group !!! : not logged in server - {user_guid}"; result.setFail(ServerErrorCode.UserNotLogin,err_msg); Log.getLogger().error(err_msg); return result; } var p2p_group = getP2PHostId(); GameServerApp.getServerLogic().getProudNetListener().getNetServer().JoinP2PGroup(user_entity.getHostId(), p2p_group); var client_message = new ClientToGame(); client_message.Message = new(); client_message.Message.PartyP2PGroupHostIdNoti = new(); client_message.Message.PartyP2PGroupHostIdNoti.PartyP2PGroupHostId = (int)p2p_group; PartyHelper.sendToClient(client_message, user_entity.getHostId()); return await Task.FromResult(new Result()); } public async Task checkPartyP2PState(USER_GUID userGuid, bool isJoin, bool includeSendingMe) { // 1. 타 유저에게 내 정보 전송 _ = await sendPartyP2PState(userGuid, isJoin); if (!includeSendingMe) return await Task.FromResult(new Result()); // 2. 타 유저들의 정보를 내게 전송 var party = getOwner() as GlobalPartyDetail; NullReferenceCheckHelper.throwIfNull(party, () => "GlobalPartyDetail is null"); var party_member_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_member_action, () => $"GlobalPartyDetailMemberAction is null !! - {party.toBasicString()}"); var player_manager = GameServerApp.getServerLogic().getPlayerManager(); var client_message = new ClientToGame(); client_message.Message = new(); client_message.Message.PartyMemberP2PStateNoti = new(); var members = party_member_action.getMembers(); foreach (var member in members) { if (member == userGuid) continue; client_message.Message.PartyMemberP2PStateNoti.MemberGuid = member; client_message.Message.PartyMemberP2PStateNoti.IsP2P = player_manager.tryGetUserByPrimaryKey(member, out _) ? BoolType.True : BoolType.False; PartyHelper.sendToClient(client_message, userGuid); } return await Task.FromResult(new Result()); } private async Task sendPartyP2PState(USER_GUID userGuid, bool isJoin) { var client_message = new ClientToGame(); client_message.Message = new(); client_message.Message.PartyMemberP2PStateNoti = new(); client_message.Message.PartyMemberP2PStateNoti.MemberGuid = userGuid; client_message.Message.PartyMemberP2PStateNoti.IsP2P = isJoin ? BoolType.True : BoolType.False; var party = getOwner() as GlobalPartyDetail; NullReferenceCheckHelper.throwIfNull(party, () => "GlobalPartyDetail is null"); PartyHelper.BroadcastToClients(party, client_message, new List { userGuid }); return await Task.FromResult(new Result()); } public bool leavePartyP2PGroup(USER_GUID user_guid) { var player_manager = GameServerApp.getServerLogic().getPlayerManager(); if (player_manager.tryGetUserByPrimaryKey(user_guid, out var user)) { return GameServerApp.getServerLogic().getProudNetListener().getNetServer().LeaveP2PGroup(user.getHostId(), getP2PHostId()); } return false; } public bool destroyPartyP2PGroup() { var p2p_group = getP2PHostId(); return GameServerApp.getServerLogic().getProudNetListener().getNetServer().DestroyP2PGroup(p2p_group); } public async Task createPartyDetailInfo(string leader_guid, string leader_nickname) { var result = new Result(); var party = getOwner() as GlobalPartyDetail; if (null == party) { var err_msg = $"Fail to get entity base : {nameof(GlobalPartyDetail)}"; result.setFail(ServerErrorCode.EntityBaseNotFound, err_msg); Log.getLogger().error(err_msg); return result; } // 1. party 정보 create var party_info_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_info_action, () => $"GlobalPartyDetailInfoAction is null !! - {party.toBasicString()}"); result = await party_info_action.createParty(leader_guid, leader_nickname); if (result.isFail()) return result; // 2. leader member 정보 설정 var party_member_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_member_action, () => $"GlobalPartyDetailMemberAction is null !! - {party.toBasicString()}"); var change = await party_member_action.changeMember(PartyHelper.makePartyMember(leader_guid, leader_nickname)); if (change.result.isFail()) return result; // 3. party server 정보 설정 var party_server_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_server_action, () => $"GlobalPartyDetailServerAction is null !! - {party.toBasicString()}"); result = await party_server_action.addPartyServer(GameServerApp.getServerLogic().getServerName()); if (result.isFail()) return result; return result; } public async Task loadAllFromCache() { var result = new Result(); var party = getOwner() as GlobalPartyDetail; ArgumentNullException.ThrowIfNull(party); // 1. party 정보 Load result = await loadPartyCache(); if (result.isFail()) return result; // 2. party member 정보 Load result = await loadPartyMemberCache(); if (result.isFail()) return result; // 3. party server 정보 Load result = await loadPartyServerCache(); if (result.isFail()) return result; // 4. party invite send 정보 load result = await loadPartyInvitePartySendCache(); if (result.isFail()) return result; // 5. party instance 정보 load ( 없을 수 있으므로 result 무시 ) _ = await loadPartyInstanceCache(); return result; } public async Task loadPartyCache() { var party = getOwner() as GlobalPartyDetail; NullReferenceCheckHelper.throwIfNull(party, () => "GlobalPartyDetail is null"); var party_info_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_info_action, () => $"GlobalPartyDetailInfoAction is null !! - {party.toBasicString()}"); var result = await party_info_action.loadParty(); return result; } public async Task loadPartyMemberCache() { var party = getOwner() as GlobalPartyDetail; NullReferenceCheckHelper.throwIfNull(party, () => "GlobalPartyDetail is null"); var party_member_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_member_action, () => $"GlobalPartyDetailMemberAction is null !! - {party.toBasicString()}"); var result = await party_member_action.loadPartyMember(); return result; } private async Task loadPartyServerCache() { var party = getOwner() as GlobalPartyDetail; NullReferenceCheckHelper.throwIfNull(party, () => "GlobalPartyDetail is null"); var party_server_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_server_action, () => $"GlobalPartyDetailServerAction is null !! - {party.toBasicString()}"); var result = await party_server_action.loadPartyServer(); return result; } private async Task loadPartyInvitePartySendCache() { var party = getOwner() as GlobalPartyDetail; NullReferenceCheckHelper.throwIfNull(party, () => "GlobalPartyDetail is null"); var party_invite_party_send_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_invite_party_send_action, () => $"GlobalPartyInvitePartySendAction is null !! - {party.toBasicString()}"); var result = await party_invite_party_send_action.loadPartyInvitePartySend(party.PartyGuid); return result; } private async Task loadPartyInstanceCache() { var party = getOwner() as GlobalPartyDetail; NullReferenceCheckHelper.throwIfNull(party, () => "GlobalPartyDetail is null"); var party_instance_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_instance_action, () => $"GlobalPartyDetailInstanceAction is null !! - {party.toBasicString()}"); var result = await party_instance_action.loadPartyInstance(); return result; } private async Task tryMoveToLeaderServer(USER_GUID leaderGuid, USER_GUID userGuid) { var server_logic = GameServerApp.getServerLogic(); var result = new Result(); string err_msg; // 1. party leader 세션 체크 var leader_login_cache = await PartyHelper.getOtherUserLoginCache(leaderGuid); if (null == leader_login_cache) { err_msg = $"Failed to get party leader session !!! - {leaderGuid}"; result.setFail(ServerErrorCode.NotFoundParty, err_msg); Log.getLogger().error(err_msg); return null; } // 2. 조건 체크 if (false == await checkConditionLeaderServer(leader_login_cache.CurrentServer)) return null; // 3. 서버 정보 조회 (result, var server_info) = await server_logic.getServerInfoByServerName(leader_login_cache.CurrentServer); if (null == server_info) return null; if (server_info.Sessions + server_info.Reservation + server_info.ReturnCount >= server_info.Capacity) { return null; } // 4. 예약 요청 var message = new ServerMessage.Types.GS2GS_REQ_RESERVATION_ENTER_TO_SERVER(); message.MoveType = ServerMoveType.Force; message.RequestServerName = server_logic.getServerName(); message.RequestUserGuid = userGuid; var reserved = await server_logic.getReservationManager().registerReservationEnterToServer(message, server_info.Name); // 5. 예약 실패 체크 if (null == reserved) { err_msg = $"Failed to reservation enter to game server!!! - {server_info.Name}"; Log.getLogger().error(err_msg); result.setFail(ServerErrorCode.FailedToReservationEnter, err_msg); return null; } // 6. 이동 처리 var player_manager = server_logic.getPlayerManager(); if (!player_manager.tryGetUserByPrimaryKey(userGuid, out var player)) return null; result = await player.runTransactionRunnerSafely(TransactionIdType.PrivateContents, "JoinParty", moveDelegate); if (result.isFail()) { Log.getLogger().error(result.toBasicString()); return null; } // 7. 이동 정보 체크 var account_attribute = player.getEntityAttribute(); ArgumentNullException.ThrowIfNull(account_attribute); var res = new ServerConnectInfo(); res.ServerAddr = account_attribute.ToConnectGameServerAddress.IP; res.ServerPort = account_attribute.ToConnectGameServerAddress.Port; res.Otp = account_attribute.OtpForServerConnect; return res; async Task moveDelegate() => await moveAsync(player, server_info); } private async Task checkConditionLeaderServer(string leaderServerName) { var server_logic = GameServerApp.getServerLogic(); (var result, var leader_server) = await server_logic.getServerInfoByServerName(leaderServerName); if (null == leader_server) return false; // 1. join user 가 channel 에 있는지 확인 if (server_logic.getServerType().toServerType() != ServerType.Channel) return false; // 2. leader 와 동일한 channel 서버에 있는지 확인 if (leaderServerName == server_logic.getServerName()) return false; // 3. leader 가 channel 에 있는지 확인 if (leaderServerName.toServerType() != ServerType.Channel) return false; // 4. leader 와 동일한 world id 에 있는지 확인 if (server_logic.getWorldId() != leader_server.WorldId) return false; return true; } private async Task moveAsync(Player player, ServerInfo serverInfo) { // 1. 파티 리더 위치로 이동 ( to Channel ) var result = await GameZoneMoveHelper.moveToAnotherChannel(player, serverInfo, null); if (result.isFail()) return result; var batch = new QueryBatchEx(player, LogActionType.None, GameServerApp.getServerLogic().getDynamoDbClient()); { batch.addQuery(new DBQWriteToAttributeAllWithTransactionRunner()); batch.addQuery(new QueryFinal()); } return await QueryHelper.sendQueryAndBusinessLog(batch); } }