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; public class GlobalPartyDetailInfoAction : EntityActionBase { private readonly PartyCacheRequest m_party_cache_request; private TaskCompletionSource m_vote_finish_task_source { get; set; } = new(); public GlobalPartyDetailInfoAction(GlobalPartyDetail owner) : base(owner) { m_party_cache_request = new PartyCacheRequest(owner.PartyGuid, GameServerApp.getServerLogic().getRedisConnector()); } public override async Task onInit() => await Task.FromResult(new Result()); public override void onClear() { return; } public async Task keep() { await m_party_cache_request.keepPartyCache(); } public async Task loadParty() { var result = new Result(); var party_attribute = getOwner().getEntityAttribute(); NullReferenceCheckHelper.throwIfNull(party_attribute, () => $"PartyAttribute is null !! - {getOwner().toBasicString()}"); result = await m_party_cache_request.fetchPartyCache(); if (result.isFail()) return result; result = await ServerBase.DataCopyHelper.copyEntityAttributeFromCaches(party_attribute, new List { m_party_cache_request.getPartyCache()! }); if (result.isFail()) { Log.getLogger().error(result.toBasicString()); } return result; } public async Task createParty(USER_GUID leader_guid, string leader_nickname) { var result = new Result(); var party_attribute = getOwner().getEntityAttribute(); NullReferenceCheckHelper.throwIfNull(party_attribute, () => $"PartyAttribute is null !! - {getOwner().toBasicString()}"); // 1. cache 생성 result = await m_party_cache_request.createPartyCache(leader_guid, leader_nickname); if (result.isFail()) return result; // 2. attribute 복사 result = await ServerBase.DataCopyHelper.copyEntityAttributeFromCaches(party_attribute, new List { m_party_cache_request.getPartyCache()! }); if (result.isFail()) { Log.getLogger().error(result.toBasicString()); } return result; } public async Task deleteParty() { // 1. cache 제거 var result = await m_party_cache_request.deletePartyCache(); if (result.isFail()) { Log.getLogger().error(result.toBasicString()); } // 2. attribute 제거 var party_attribute = getOwner().getEntityAttribute(); NullReferenceCheckHelper.throwIfNull(party_attribute, () => $"PartyAttribute is null !! - {getOwner().toBasicString()}"); party_attribute.onClear(); return result; } public async Task changePartyLeader(USER_GUID next_leader_guid, bool is_upsert_cache) { var result = new Result(); // 1. attribute 수정 var party_attribute = getOwner().getEntityAttribute(); NullReferenceCheckHelper.throwIfNull(party_attribute, () => $"PartyAttribute is null !! - {getOwner().toBasicString()}"); party_attribute.PartyLeaderCharGuid = next_leader_guid; // 2. cache 수정 var party_cache = m_party_cache_request.getPartyCache(); NullReferenceCheckHelper.throwIfNull(party_cache, () => $"Party Cache is null !! - party name:{party_attribute.PartyName}"); party_cache.PartyLeaderCharGuid = next_leader_guid; if(is_upsert_cache) result = await m_party_cache_request.upsertPartyCache(); return result; } public async Task changePartyName(string new_party_name, bool is_upsert_cache) { var result = new Result(); // 1. attribute 수정 var party_attribute = getOwner().getEntityAttribute(); NullReferenceCheckHelper.throwIfNull(party_attribute, () => $"PartyAttribute is null !! - {getOwner().toBasicString()}"); party_attribute.PartyName = new_party_name; // cache 수정 var party_cache = m_party_cache_request.getPartyCache(); NullReferenceCheckHelper.throwIfNull(party_cache, () => $"Party Cache is null !! - party name:{party_attribute.PartyName}"); party_cache.PartyName = new_party_name; if(is_upsert_cache) result = await m_party_cache_request.upsertPartyCache(); return result; } public string getPartyName() { var party_attribute = getOwner().getEntityAttribute(); NullReferenceCheckHelper.throwIfNull(party_attribute, () => $"PartyAttribute is null !! - {getOwner().toBasicString()}"); return party_attribute.PartyName; } public async Task VoteParty(VoteType vote, USER_GUID voter_guid) { var result = new Result(); string err_msg; var party = getOwner() as GlobalPartyDetail; NullReferenceCheckHelper.throwIfNull(party, () => $"global party detail is null !!"); var party_attribute = party.getEntityAttribute(); NullReferenceCheckHelper.throwIfNull(party_attribute, () => $"PartyAttribute is null !! - {getOwner().toBasicString()}"); // 1. 진행 중인 Vote 확인 if (null == party_attribute.PartyVote) { err_msg = $"Failed to reply party vote !!! - not start party vote - {party.PartyGuid}"; result.setFail(ServerErrorCode.NoStartPartyVote, err_msg); Log.getLogger().error(err_msg); return result; } // 2. 투표 허용 시간 확인 var allow_vote_time = DateTime.UtcNow.AddSeconds(-1 * MetaHelper.GameConfigMeta.VoteTimeLimitSec + 5 ); if (party_attribute.PartyVote.StartVoteTime < allow_vote_time.ToTimestamp()) { err_msg = $"Failed to reply party vote !!! - already passed party vote time - {party.PartyGuid}"; result.setFail(ServerErrorCode.AlreadyPassPartyVoteTime, err_msg); Log.getLogger().error(err_msg); return result; } // 3. 기 투표 여부 체크 if (party_attribute.PartyVote.Votes.TryGetValue(voter_guid, out var saved_vote) && saved_vote != VoteType.None) { err_msg = $"Failed to reply party vote !!! - already reply party vote - {voter_guid}"; result.setFail(ServerErrorCode.AlreadyReplyPartyVote, err_msg); Log.getLogger().error(err_msg); return result; } party_attribute.PartyVote.Votes[voter_guid] = vote; // 4. 투표 종료 체크 ( party leader 가 있는 서버만 ) var player_message = GameServerApp.getServerLogic().getPlayerManager(); if (player_message.tryGetUserByPrimaryKey(party_attribute.PartyLeaderCharGuid, out _)) { var is_finished = party_attribute.PartyVote.Votes.All(voted_type => voted_type.Value != VoteType.None); if (is_finished) m_vote_finish_task_source.TrySetResult(); return result; } var message = new ServerMessage(); message.ReplyPartyVoteNoti = new(); message.ReplyPartyVoteNoti.PartyGuid = party.PartyGuid; message.ReplyPartyVoteNoti.PartyVoterGuid = voter_guid; message.ReplyPartyVoteNoti.Vote = vote; await PartyHelper.sendToServerByTargetClient(party_attribute.PartyLeaderCharGuid, message); return result; } public PartyVoteInfo? getPartyVoteInfo() { var party = getOwner() as GlobalPartyDetail; NullReferenceCheckHelper.throwIfNull(party, () => $"global party detail is null !!"); var party_attribute = party.getEntityAttribute(); NullReferenceCheckHelper.throwIfNull(party_attribute, () => $"PartyAttribute is null !! - {getOwner().toBasicString()}"); return party_attribute.PartyVote; } public async Task<(Result result, PartyVoteInfo? vote)> registerPartyVote(string vote_title, Timestamp start_vote_time, bool is_start) { var result = new Result(); var party = getOwner() as GlobalPartyDetail; NullReferenceCheckHelper.throwIfNull(party, () => $"global party detail is null !!"); var party_attribute = party.getEntityAttribute(); NullReferenceCheckHelper.throwIfNull(party_attribute, () => $"PartyAttribute is null !! - {party.toBasicString()}"); var party_member_action = party.getEntityAction(); NullReferenceCheckHelper.throwIfNull(party_member_action, () => $"GlobalPartyDetailMemberAction is null !! - {party.toBasicString()}"); // 1. vote condition 체크 result = await checkVoteCondition(party, vote_title); if (result.isFail()) return (result, null); // 2. vote 정보 저장 : 시작시점의 유저 리스트 등록 - 추가(제외) / 감소(기권) var members = party_member_action.getMembers(); var vote = new PartyVoteInfo { VoteTitle = vote_title, StartVoteTime = start_vote_time }; foreach (var member in members) { vote.Votes.Add(member, VoteType.None); } party_attribute.PartyVote = vote; // 3. cache 저장 var cache = m_party_cache_request.getPartyCache(); NullReferenceCheckHelper.throwIfNull(cache, () => $"Party Cache is null !! - party name:{party_attribute.PartyName} / {party.toBasicString()}"); cache.LastVoteTime = start_vote_time; party_attribute.LastVoteTime = start_vote_time; if (is_start) { result = await m_party_cache_request.upsertPartyCache(); if (result.isFail()) { party_attribute.PartyVote = null; return (result, null); } // 4. Wait Vote Task 실행 ( fire and forget ) _ = Task.Run(waitPartyVoteFinish); } return (result, vote); } private async Task checkVoteCondition(GlobalPartyDetail party, string vote_title) { var result = new Result(); string err_msg; var party_attribute = party.getEntityAttribute(); NullReferenceCheckHelper.throwIfNull(party_attribute, () => $"PartyAttribute is null !! - {party.toBasicString()}"); // 1. vote title 길이 체크 if (vote_title.Length > MetaHelper.GameConfigMeta.MaxVoteAgendaInput) { err_msg = $"fail to start party vote !!! : invalid vote title length - {vote_title.Length}"; result.setFail(ServerErrorCode.InvalidPartyStringLength, err_msg); Log.getLogger().error(err_msg); return result; } // 2. 진행 중인 Vote 체크 if (null != party_attribute.PartyVote) { err_msg = $"Failed to start party vote !!! : exist party vote - {party_attribute.PartyVote.VoteTitle}"; result.setFail(ServerErrorCode.AlreadyStartPartyVote, err_msg); Log.getLogger().error(err_msg); return result; } // 3. Last Vote Time 체크 var last_vote_time = party_attribute.LastVoteTime ?? DateTimeHelper.MinTime.ToTimestamp(); if (last_vote_time > DateTime.UtcNow.AddSeconds(-1 * MetaHelper.GameConfigMeta.VoteCoolTimeSec).ToTimestamp()) { err_msg = $"Failed to start party vote !!! : invalid party vote time - {vote_title}"; result.setFail(ServerErrorCode.InvalidPartyVoteTime, err_msg); Log.getLogger().error(err_msg); return result; } return await Task.FromResult(result); } private async Task waitPartyVoteFinish() { var cts = new CancellationTokenSource(TimeSpan.FromSeconds(MetaHelper.GameConfigMeta.VoteTimeLimitSec + 1)); m_vote_finish_task_source = new(); cts.Token.Register(() => { m_vote_finish_task_source.TrySetCanceled(); }); try { await m_vote_finish_task_source.Task; } catch (OperationCanceledException) { // Ignore... } catch (Exception e) { Log.getLogger().error($"Failed to wait party vote !!! : exception finish task - {e}"); return; } // 1. 파티 정보 획득 var party = getOwner() as GlobalPartyDetail; NullReferenceCheckHelper.throwIfNull(party, () => $"global party detail is null !!"); // 2. 투표 결과 수집 var party_attribute = party.getEntityAttribute(); NullReferenceCheckHelper.throwIfNull(party_attribute, () => $"PartyAttribute is null !! - {party.toBasicString()}"); // 3. 결과 수집 var agreement = party_attribute.getVoteCount(VoteType.Agreement); var disAgreement = party_attribute.getVoteCount(VoteType.DisAgreement); var abstain = party_attribute.getVoteCount(VoteType.Abstain); abstain += party_attribute.getVoteCount(VoteType.None); // 4. 결과 통보 ( to server ) var server_message = new ServerMessage(); server_message.PartyVoteResultNoti = new(); server_message.PartyVoteResultNoti.PartyGuid = party.PartyGuid; server_message.PartyVoteResultNoti.VoteTitle = party_attribute.PartyVote?.VoteTitle ?? string.Empty; server_message.PartyVoteResultNoti.ResultTrue = agreement; server_message.PartyVoteResultNoti.ResultFalse = disAgreement; server_message.PartyVoteResultNoti.Abstain = abstain; PartyHelper.BroadcastToServers(party, server_message, true); // 4. 결과 통보 ( to client ) var client_message = new ClientToGame(); client_message.Message = new(); client_message.Message.PartyVoteResultNoti = new(); client_message.Message.PartyVoteResultNoti.VoteTitle = party_attribute.PartyVote?.VoteTitle ?? string.Empty; client_message.Message.PartyVoteResultNoti.ResultTrue = agreement; client_message.Message.PartyVoteResultNoti.ResultFalse = disAgreement; client_message.Message.PartyVoteResultNoti.Abstain = abstain; PartyHelper.BroadcastToClients(party, client_message, new List()); // 5. Business Log 기록 writeBusinessLog(party); // 6. 투표 데이터 삭제 party_attribute.PartyVote = null; } private void writeBusinessLog(GlobalPartyDetail party) { var log_invokers = new List(2); // 1. 파티정보 var party_log_data = PartyBusinessLogHelper.toPartyLogData(party.PartyGuid, false); var party_business_log = new PartyBusinessLog(party_log_data); log_invokers.Add(party_business_log); // 2. 파티 투표 정보 var party_vote_log = PartyBusinessLogHelper.toPartyVoteLogData(party.PartyGuid, false); var party_vote_business_log = new PartyVoteBusinessLog(party_vote_log); log_invokers.Add(party_vote_business_log); BusinessLogger.collectLogs(new LogActionEx(LogActionType.EndPartyVote), party, log_invokers); } }