using Google.Protobuf; using Google.Protobuf.WellKnownTypes; using ServerCore; using ServerBase; using ServerCommon; using ServerCommon.BusinessLogDomain; using MetaAssets; using USER_GUID = System.String; namespace GameServer.PacketHandler; [PacketHandler(typeof(ClientToGameReq), typeof(ClientToGameReq.Types.SummonPartyMemberReq), typeof(SummonPartyMemberPacketHandler), typeof(GameLoginListener))] public class SummonPartyMemberPacketHandler : PacketRecvHandler { private static void send_S2C_ACK_SUMMON_PARTY_MEMBER(Player owner, Result result, Item? spentItem) { var ack_packet = new ClientToGame(); ack_packet.Response = new(); ack_packet.Response.ErrorCode = result.ErrorCode; ack_packet.Response.SummonPartyMemberRes = new(); if (result.isSuccess()) { NullReferenceCheckHelper.throwIfNull(spentItem, () => $"spentItem is null !!! - {owner.toBasicString()}"); var delete = spentItem.toItemData4Client(); var item = new ItemGuidCount(); item.ItemGuid = delete.ItemGuid; item.ItemCount = delete.Count; ack_packet.Response.SummonPartyMemberRes.Items.Add(item); } GameServerApp.getServerLogic().onSendPacket(owner, ack_packet); } public override async Task onProcessPacket(ISession entityWithSession, IMessage recvMessage) { var player = entityWithSession as Player; ArgumentNullReferenceCheckHelper.throwIfNull(player, () => $"player is null !!!"); var game_msg = recvMessage as ClientToGame; ArgumentNullReferenceCheckHelper.throwIfNull(game_msg, () => $"game_msg is null !!! - {player.toBasicString()}"); var request = game_msg.Request.SummonPartyMemberReq; ArgumentNullReferenceCheckHelper.throwIfNull(request, () => $"request is null !!! - {player.toBasicString()}"); var result = new Result(); string err_msg; var player_action = player.getEntityAction(); NullReferenceCheckHelper.throwIfNull(player_action, () => $"player_action is null !!! - {player.toBasicString()}"); // 1. 캐릭터 선택 상태 체크 var selected_character = player_action.getSelectedCharacter(); if (null == selected_character) { err_msg = $"Not selected Character !!! - {player.toBasicString()}"; result.setFail(ServerErrorCode.CharacterNotSelected, err_msg); Log.getLogger().error(result.toBasicString()); send_S2C_ACK_SUMMON_PARTY_MEMBER(player, result, null); return result; } // 2. 소속 파티 Guid 조회 var personal_party_action = player.getEntityAction(); NullReferenceCheckHelper.throwIfNull(personal_party_action, () => $"personal_party_action is null !!! - {player.toBasicString()}"); var personal_party = personal_party_action.getPersonalParty(); if (null == personal_party) { err_msg = $"Failed to get party info !!! - not party member - {player.getUserGuid()}"; result.setFail(ServerErrorCode.NotParty, err_msg); Log.getLogger().error(err_msg); send_S2C_ACK_SUMMON_PARTY_MEMBER(player, result, null); return result; } var global_party = GameServerApp.getServerLogic().findGlobalEntity(); NullReferenceCheckHelper.throwIfNull(global_party, () => $"global_party is null !!! - {player.toBasicString()}"); var party = global_party.getParty(personal_party.getPartyGuid()); NullReferenceCheckHelper.throwIfNull(party, () => $"party is null !!! - {player.toBasicString()}"); // 3. 파티장 체크 var party_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_action, () => $"party_action is null !!! - {player.toBasicString()}"); var is_leader = party_action.isLeader(player.getUserGuid()); if (false == is_leader) { err_msg = $"Failed to summon party member !!! : not party leader - {player.getUserGuid()}"; result.setFail(ServerErrorCode.NotPartyLeader, err_msg); Log.getLogger().error(err_msg); send_S2C_ACK_SUMMON_PARTY_MEMBER(player, result, null); return result; } // 4. 파티원 체크 var party_member_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_member_action, () => $"party_member_action is null !!! - {player.toBasicString()}"); var is_member = party_member_action.isPartyMember(request.PartyMemberUserGuid); if (false == is_member) { err_msg = $"Failed to summon party member !!! : not party member - {request.PartyMemberUserGuid}"; result.setFail(ServerErrorCode.NotPartyMember, err_msg); Log.getLogger().error(err_msg); send_S2C_ACK_SUMMON_PARTY_MEMBER(player, result, null); return result; } // 5. 소환 조건 체크 result = await checkSummonConditionAsync(player, request.PartyMemberUserGuid, party); if (result.isFail()) { send_S2C_ACK_SUMMON_PARTY_MEMBER(player, result, null); return result; } // 6. 소환 처리 result = await player.runTransactionRunnerSafely( TransactionIdType.PrivateContents, "SummonPartyMember" , summonDelegate ); if (result.isFail()) { _ = await party_member_action.clearSummonMemberAsync(request.PartyMemberUserGuid, false); send_S2C_ACK_SUMMON_PARTY_MEMBER(player, result, null); return result; } return result; async Task summonDelegate() => await summonAsync(player, request.PartyMemberUserGuid, party); } private async Task checkSummonConditionAsync(Player player, USER_GUID summonUserGuid, GlobalPartyDetail party) { var result = new Result(); string err_msg; var server_logic = GameServerApp.getServerLogic(); var party_member_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_member_action, () => $"party_member_action is null !!! - {player.toBasicString()}"); var summon_members = party_member_action.getSummonMembers(); var proud_net_listener = server_logic.getProudNetListener(); // 1. base 체크 var curr_user_count = proud_net_listener.getEntityWithSessions().Count + server_logic.getReservationManager().getReservedUserCount() + server_logic.getReturnManager().getReturnUserCount() + UgcNpcCountManager.Instance.calculateNpcCount(); if (proud_net_listener.getMaxConnectionCount() <= curr_user_count) { err_msg = $"Failed to summon party member !!! : not channel server - {curr_user_count}"; result.setFail(ServerErrorCode.PartyLeaderServerIsFull, err_msg); Log.getLogger().error(err_msg); return result; } // 2. leader condition 체크 result = await checkLeaderConditions(player, summon_members.Count()); if (result.isFail()) return result; // 3. summon user condition 체크 result = await checkSummonUserConditions(player, summon_members.ToList(), summonUserGuid); if (result.isFail()) return result; return result; } private async Task checkLeaderConditions(Player player, int summonMemberCount) { await Task.CompletedTask; var server_logic = GameServerApp.getServerLogic(); var result = new Result(); string err_msg; // 1. leader 가 channel 인지 체크 if (server_logic.getServerType().toServerType() != ServerType.Channel) { err_msg = $"Failed to summon party member !!! : not channel server - {GameServerApp.getServerLogic().getServerType().toServerType()}"; result.setFail(ServerErrorCode.InvalidSummonServerType, err_msg); Log.getLogger().error(err_msg); return result; } // 2. 소환 불가 위치 체크 // todo (sangyeob.kim) : client 에서 체크 중 -> 서버체크는 필요시 추가 ( 현재는 notifyReply 로 한다. ) // 3. 소환석 아이템 수량 체크 var inventory_action = player.getEntityAction(); NullReferenceCheckHelper.throwIfNull(inventory_action, () => $"inventory_action is null !!! - {player.toBasicString()}"); var summon_item_count = inventory_action.getItemCountAllByMetaId((uint)MetaHelper.GameConfigMeta.SummonStoneItemMetaId); if (summon_item_count < summonMemberCount + 1) { err_msg = $"Failed to summon party member !!! : insufficient summon item - summon_item_count[{summon_item_count}] / summon_count[{summonMemberCount}]"; result.setFail(ServerErrorCode.ItemCountNotEnough, err_msg); Log.getLogger().error(err_msg); } return result; } private async Task checkSummonUserConditions(Player player, IReadOnlyList summonUsers, USER_GUID summonUserGuid) { var server_logic = GameServerApp.getServerLogic(); var result = new Result(); string err_msg; // 1. 소환 중인 유저 체크 if (summonUsers.Contains(summonUserGuid)) { err_msg = $"Failed to summon party member !!! : Already summon member - {summonUserGuid}"; result.setFail(ServerErrorCode.AlreadySummon, err_msg); Log.getLogger().error(err_msg); return result; } // 2. 소환 유저 로그인 체크 var login_cache = await PartyHelper.getOtherUserLoginCache(summonUserGuid); if (null == login_cache) { err_msg = $"Failed to summon party member !!! : summon user logged out - {summonUserGuid}"; result.setFail(ServerErrorCode.LogOffTarget, err_msg); Log.getLogger().error(err_msg); return result; } // 3. 동일 월드 체크 (result, var summon_user_server_info) = await server_logic.getServerInfoByServerName(login_cache.CurrentServer); NullReferenceCheckHelper.throwIfNull(summon_user_server_info, () => $"summon_user_server_info is null !!! - {player.toBasicString()}"); if (summon_user_server_info.WorldId != server_logic.getWorldId()) { err_msg = $"Failed to summon party member !!! : invalid world id - leader[{server_logic.getWorldId()}] summon[{summon_user_server_info.WorldId}]"; result.setFail(ServerErrorCode.InvalidSummonWorldServer, err_msg); Log.getLogger().error(err_msg); return result; } // 4. 소환 파티원 위치 체크 var player_manager = server_logic.getPlayerManager(); if (player_manager.tryGetUserByPrimaryKey(summonUserGuid, out var summon_user)) { if(null == summon_user) { err_msg = $"Failed to summon party member !!! : summon user logged out - {summonUserGuid}"; result.setFail(ServerErrorCode.LogOffTarget, err_msg); Log.getLogger().error(err_msg); return result; } var leader_location_action = player.getEntityAction(); NullReferenceCheckHelper.throwIfNull(leader_location_action, () => $"leader_location_action is null !!! - {player.toBasicString()}"); var leader_curr_pos = leader_location_action.getCurrentPos(); var summon_user_location_action = summon_user.getEntityAction(); NullReferenceCheckHelper.throwIfNull(summon_user_location_action, () => $"summon_user_location_action is null !!! : {summon_user.toBasicString()} - {player.toBasicString()}"); var summon_user_location = summon_user_location_action.getCurrentPos(); var distance = PartyHelper.calculateDistance(leader_curr_pos, summon_user_location); if (distance <= ServerCommon.Constant.PARTY_SUMMON_LIMIT_DISTANCE) { err_msg = $"Failed to summon party member !!! : summon user is short distance - {distance}"; result.setFail(ServerErrorCode.SummonUserLimitDistance, err_msg); Log.getLogger().error(err_msg); return result; } } return result; } private async Task summonAsync(Player leader, string summon_user_guid, GlobalPartyDetail party) { var leader_location = leader.getEntityAction()?.getCurrentPos(); NullReferenceCheckHelper.throwIfNull(leader_location, () => $"leader_location is null !!! - {leader.toBasicString()}"); // 0. 소환 데이터 생성 var summon = new SummonInfo(); summon.IsAlreadySummon = true; summon.SummonServer = GameServerApp.getServerLogic().getServerName(); summon.SummonPos = leader_location; var party_member_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_member_action, () => $"party_member_action is null !!! - {leader.toBasicString()}"); // 1. 소환 처리 var summon_result = await party_member_action.summonMemberAsync(summon_user_guid, summon); if (summon_result.result.isFail()) { return summon_result.result; } var batch = new QueryBatchEx( leader, LogActionType.SummonParty , GameServerApp.getServerLogic().getDynamoDbClient()); { batch.addQuery(new DBQWriteToAttributeAllWithTransactionRunner()); batch.addQuery(new QueryFinal()); } // 2. Business Log 기록 writeBusinessLog(batch, party.PartyGuid, summon_user_guid); var result = await QueryHelper.sendQueryAndBusinessLog(batch); if (result.isFail()) return result; send_S2C_ACK_SUMMON_PARTY_MEMBER(leader, result, summon_result.delete_item); return result; } private void writeBusinessLog(QueryBatchBase queryBatch, string partyGuid, string userGuid) { // 1. 파티 정보 var party_log_data = PartyBusinessLogHelper.toPartyLogData(partyGuid, false); var party_business_log = new PartyBusinessLog(party_log_data); queryBatch.appendBusinessLog(party_business_log); // 2. 파티 멤버 정보 var party_member_log_data = PartyBusinessLogHelper.toPartyMemberLogData(partyGuid, userGuid, PartyMemberActionType.Summon); var party_member_business_log = new PartyMemberBusinessLog(party_member_log_data); queryBatch.appendBusinessLog(party_member_business_log); } }