protected override void HandleMessage(EntityActorMessageContext messageContext, DefaultEntityActorStateContainer state, WorldTeleportPlayerEntityActorMessage message) { //Something just told player we are teleporting to another world //TODO: Kind of a potential exploit here. Since the process to transfer sessions //is async they could potentially log off and log back in quickly and avoid a trasnfer //So if this is a FORCED transfer, like for death or something or a kick, they could potentially avoid it?? ProjectVersionStage.AssertBeta(); //TODO: Find a way to make it so actors state is always valid. //If this throws then the entity dies. So no reason to check it. EntitySaveableConfiguration entity = PersistenceConfigurationMappable.RetrieveEntity(state.EntityGuid); entity.isWorldTeleporting = true; ZoneToSeverClient.TryWorldTeleportCharacter(new ZoneServerWorldTeleportCharacterRequest(state.EntityGuid, message.WorldTeleportGameObjectId)) .ContinueWith((task, o) => { //Whether this succeeds or not this continuation will occur //either way we should just disconnect the player because we //either have uncoverable issues or they are going to be transfered after //disconnection. //ConnectionServiceMappable.RetrieveEntity(args.TeleportingPlayer).Disconnect(); //Since this is async the entity might actually be gone, so we used the actor messaging system //To indicate that it should disconnect messageContext.Entity.Tell(new DisconnectNetworkPlayerEntityActorMessage()); }, TaskContinuationOptions.ExecuteSynchronously); }
[NoResponseCache] //we don't want to cache this, if they are removed from a guild then we want this reflected immediately or they may be taking in a guild chat they aren't apart of due to a race condition public async Task <IActionResult> GetCharacterMembershipGuildStatus([FromRoute(Name = "id")] int characterId, [NotNull][FromServices] IGuildCharacterMembershipRepository guildCharacterMembershipRepository) { if (guildCharacterMembershipRepository == null) { throw new ArgumentNullException(nameof(guildCharacterMembershipRepository)); } //If guild membership repo doesn't have the character id as an entry then it means there is no guild associated with them. if (!(await guildCharacterMembershipRepository.ContainsAsync(characterId).ConfigureAwaitFalse())) { return(BuildFailedResponseModel(CharacterGuildMembershipStatusResponseCode.NoGuild)); } //TODO: There is technically a race condition here. They could have just been kicked from a guild but the cached model may say they are in a guild //this could result in incredibly rare cases of a kicked member joining at the perfect moment who can talk in guild chat but no longer be in the guild ProjectVersionStage.AssertBeta(); //Otherwise, they are in a guild try { return(BuildSuccessfulResponseModel(new CharacterGuildMembershipStatusResponse((await guildCharacterMembershipRepository.RetrieveAsync(characterId).ConfigureAwaitFalse()).GuildId))); } catch (Exception e) { if (Logger.IsEnabled(LogLevel.Error)) { Logger.LogError($"Encountered error in expected guild membership status request. CharacterId: {characterId} Reason: {e.Message}\n\nStack: {e.StackTrace}"); } return(BuildFailedResponseModel(CharacterGuildMembershipStatusResponseCode.GeneralServerError)); } }
public async Task <IActionResult> GetCharacterActionBar([FromRoute(Name = "id")] int characterId, [FromServices] ICharacterActionBarRepository actionBarRepository, [FromServices] ICharacterDefaultActionBarRepository characterDefaultActionBarRepository, [FromServices] ITypeConverterProvider <ICharacterActionBarEntry, CharacterActionBarInstanceModel> converter) { ProjectVersionStage.AssertBeta(); //TODO: Check that they own the character. if (await actionBarRepository.ContainsAsync(characterId)) { CharacterActionBarEntry[] actionBarEntries = await actionBarRepository.RetrieveAllForCharacterAsync(characterId); CharacterActionBarInstanceModel[] barInstanceModels = actionBarEntries.Select(converter.Convert) .ToArrayTryAvoidCopy(); //Just send it as raw JSON. return(Json(barInstanceModels)); } else { //We need the default action bars //Right now we only have 1 class so let's use mage. CharacterDefaultActionBarEntry[] actionBarEntries = await characterDefaultActionBarRepository.RetrieveAllActionsAsync(EntityPlayerClassType.Mage); CharacterActionBarInstanceModel[] barInstanceModels = actionBarEntries.Select(converter.Convert) .ToArrayTryAvoidCopy(); //TODO: Return default bars. return(Json(barInstanceModels)); } }
public async Task<IActionResult> GetCharacterSessionDataByAccount([FromRoute(Name = "id")] int accountId) { if(!await CharacterSessionRepository.AccountHasActiveSession(accountId) .ConfigureAwaitFalse()) { return Ok(new CharacterSessionDataResponse(CharacterSessionDataResponseCode.NoSessionAvailable)); } //TODO: There is a dangerous race condition where the zoneserver can release a session inbetween the last databse call and the characte rsessin data call //This is unlikely to be exploitably but it is dangerous ProjectVersionStage.AssertBeta(); try { ClaimedSessionsModel claimedSessionsModel = await CharacterSessionRepository.RetrieveClaimedSessionByAccountId(accountId) .ConfigureAwaitFalse(); return Ok(new CharacterSessionDataResponse(claimedSessionsModel.Session.ZoneId, claimedSessionsModel.CharacterId)); } catch(Exception e) { if(Logger.IsEnabled(LogLevel.Error)) Logger.LogError($"Failed to query for character session data for active character session on AccountId: {accountId} Exception: {e.GetType().Name} - {e.Message}"); return Ok(new CharacterSessionDataResponse(CharacterSessionDataResponseCode.GeneralServerError)); } }
private async Task <string> GetSocialServiceAuthorizationToken([JetBrains.Annotations.NotNull] IAuthenticationService authService) { if (authService == null) { throw new ArgumentNullException(nameof(authService)); } //TODO: Don't hardcode the authentication details ProjectVersionStage.AssertBeta(); //TODO: Handle errors return((await authService.TryAuthenticate(new AuthenticationRequestModel(GladMMONetworkConstants.SOCIAL_SERVICE_NAME, "Test69!"))).AccessToken); }
public async Task <IActionResult> GetCharacterData([FromRoute(Name = "id")] int characterId, [FromServices][NotNull] ICharacterDataRepository characterDataRepository) { //TODO: We should only let the user themselves get their own character's data OR zoneservers who have a claimed session. ProjectVersionStage.AssertBeta(); if (!await characterDataRepository.ContainsAsync(characterId)) { return(BuildFailedResponseModel(CharacterDataQueryReponseCode.CharacterNotFound)); } CharacterDataModel characterData = await characterDataRepository.RetrieveAsync(characterId); return(BuildSuccessfulResponseModel(new CharacterDataInstance(characterData.ExperiencePoints))); }
//[AuthorizeJwt(GuardianApplicationRole.ZoneServer)] //TODO: Eventually we'll need to auth these zoneservers. public async Task <IActionResult> WorldTeleportCharacter([FromBody][NotNull] ZoneServerWorldTeleportCharacterRequest requestModel, [FromServices][NotNull] ICharacterLocationRepository characterLocationRepository, [FromServices][NotNull] IGameObjectBehaviourDataServiceClient <WorldTeleporterInstanceModel> worldTelporterDataClient, [FromServices][NotNull] IPlayerSpawnPointDataServiceClient playerSpawnDataClient) { if (requestModel == null) { throw new ArgumentNullException(nameof(requestModel)); } if (characterLocationRepository == null) { throw new ArgumentNullException(nameof(characterLocationRepository)); } if (worldTelporterDataClient == null) { throw new ArgumentNullException(nameof(worldTelporterDataClient)); } if (playerSpawnDataClient == null) { throw new ArgumentNullException(nameof(playerSpawnDataClient)); } //TODO: Right now there is no verification of WHO/WHAT is actually teleporting the player. //We need an authorization system with player-owned zone servers. So that we can determine //who is requesting to transfer the session and then verify that a player is even on //that zone server. ProjectVersionStage.AssertBeta(); //We don't await so that we can get rolling on this VERY async multi-part process. //TODO: Handle failure ResponseModel <WorldTeleporterInstanceModel, SceneContentQueryResponseCode> teleporterInstanceResponse = await worldTelporterDataClient.GetBehaviourInstance(requestModel.WorldTeleporterId); //TODO: Handle failure ResponseModel <PlayerSpawnPointInstanceModel, SceneContentQueryResponseCode> pointInstanceResponse = await playerSpawnDataClient.GetSpawnPointInstance(teleporterInstanceResponse.Result.RemoteSpawnPointId); //Remove current location and update the new location. await characterLocationRepository.TryDeleteAsync(requestModel.CharacterGuid.EntityId); await characterLocationRepository.TryCreateAsync(new CharacterLocationModel(requestModel.CharacterGuid.EntityId, pointInstanceResponse.Result.InitialPosition.x, pointInstanceResponse.Result.InitialPosition.y, pointInstanceResponse.Result.InitialPosition.z, pointInstanceResponse.Result.WorldId)); //TODO: Better indicate reason for failure. return(Ok()); }
/// <inheritdoc /> public string GetUserId(HubConnectionContext connection) { //TODO: This could fail if they don't put the header in ProjectVersionStage.AssertBeta(); //We trust the client to send us a header that contains the character id //You may be freaking out, but we aren't taking the client at face value here. //This is and MUST be verified in the Hub's OnConnected method to prevent //malicious uses from spoofing. int characterId = connection.GetHttpContext().Request.GetTypedHeaders().Get <int>(SocialNetworkConstants.CharacterIdRequestHeaderName); if (characterId <= 0) { if (Logger.IsEnabled(LogLevel.Warning)) { Logger.LogWarning($"Encountered client: {ClaimsReader.GetAccountName(connection.User)}:{ClaimsReader.GetAccountId(connection.User)} with invalid characterId {characterId}"); } connection.Abort(); return(String.Empty); } return(characterId.ToString()); }
protected override async Task OnMessageRecieved(IHubConnectionMessageContext <IRemoteSocialHubClient> context, GuildMemberInviteRequestModel payload) { var nameQueryResponseTask = NameQueryService.RetrievePlayerGuidAsync(payload.MemberToInvite); //First we need to check if they're in a guild. //We don't really need to handle the response for this, since it should never really happen. var guildStatus = await SocialService.GetCharacterMembershipGuildStatus(context.CallerGuid.EntityId); if (!guildStatus.isSuccessful) { if (Logger.IsEnabled(LogLevel.Warning)) { Logger.LogWarning($"User: {context.CallerGuid} attempted to Invite: {payload.MemberToInvite} to a guild but was not apart of a guild."); } return; } //Now we should know what guild the caller is in due to the query. //Now we need to do a reverse namequery to get the guid of who they're attempting to invite. var nameQueryResponse = await nameQueryResponseTask; //If it's not successful, assume the user doesn't exist. if (!nameQueryResponse.isSuccessful) { await SendGuildInviteResponse(context, GuildMemberInviteResponseCode.PlayerNotFound, NetworkEntityGuid.Empty); return; } //Now check if the user is already guilded //If they are we should indicate that to the client. if (await CheckIfGuilded(nameQueryResponse.Result)) { await SendGuildInviteResponse(context, GuildMemberInviteResponseCode.PlayerAlreadyInGuild, nameQueryResponse.Result); return; } //Ok, the reverse name query was successful. Check if there is a pending invite. //TODO: Right now we rely on local state to indicate if there is a pending invite. We need to NOT do that because it won't work when we scale out. ProjectVersionStage.AssertBeta(); //TODO: There is a race condition if multiple invites are sent at the same time, should we care?? //If they have a pending invite. if (PendingInviteData.ContainsKey(nameQueryResponse.Result)) { //If NOT expired then we need to say they're currently pending an invite if (!PendingInviteData[nameQueryResponse.Result].isInviteExpired()) { await SendGuildInviteResponse(context, GuildMemberInviteResponseCode.PlayerAlreadyHasPendingInvite, nameQueryResponse.Result); return; } else { //The invite is EXPIRED so let's added a new one. PendingInviteData.ReplaceObject(nameQueryResponse.Result, GeneratePendingInviteData(context.CallerGuid, guildStatus.Result.GuildId)); } } else { PendingInviteData.AddObject(nameQueryResponse.Result, GeneratePendingInviteData(context.CallerGuid, guildStatus.Result.GuildId)); } //TODO: There is currently no handling to indicate that they are online. //Now they have a valid pending invite, so let's address the client //that needs to recieve the guild invite. IRemoteSocialHubClient playerClient = context.Clients.RetrievePlayerClient(nameQueryResponse.Result); //Indicate the player has been invited. await SendGuildInviteResponse(context, GuildMemberInviteResponseCode.Success, nameQueryResponse.Result); //Now tell the remote/target player they're being invited to a guild. await playerClient.ReceiveGuildInviteEventAsync(new GuildMemberInviteEventModel(guildStatus.Result.GuildId, context.CallerGuid)); }
static GlobalEntityResourceLockingPolicy() { //TODO: There is kinda data race here, we need to a global for this collection too to prevent it being modidified before the lock occurs inbetween lookup ProjectVersionStage.AssertBeta(); }
protected override void OnGuildStatusChanged(GuildStatusChangedEventModel changeArgs) { //TODO: This needs to be authorative ProjectVersionStage.AssertBeta(); //If we're now guildless, we need to actually leave the guild chat channel //if we're in one. if (changeArgs.IsGuildless) { //TODO: Handle leaving guild channel return; } //We have a guild, let's join the guild channel now. UnityAsyncHelper.UnityMainThreadContext.PostAsync(async() => { try { ResponseModel <VivoxChannelJoinResponse, VivoxLoginResponseCode> channelJoinResponse = await VivoxAutheAuthorizationService.JoinGuildChatAsync(); //TODO: Better handle failure. if (!channelJoinResponse.isSuccessful) { if (Logger.IsErrorEnabled) { Logger.Error($"Failed to join Guild world channel. Reason: {channelJoinResponse.ResultCode}"); } return; } if (Logger.IsInfoEnabled) { Logger.Info($"Recieved Vivox Channel URI: {channelJoinResponse.Result.ChannelURI}"); } //TODO: Awaiting the ChatChannelSession may never complete. We should do a timeout. //TODO: We should share these inputs as shared constants. IChannelSession guildChannel = (await ChatChannelSession).GetChannelSession(new ChannelId(channelJoinResponse.Result.ChannelURI)); await guildChannel.ConnectionAsync(false, true, TransmitPolicy.Yes, channelJoinResponse.Result.AuthToken) .ConfigureAwait(true); if (Logger.IsInfoEnabled) { Logger.Info($"Joined Guild Chat"); } //Broadcast that we've joined proximity chat. ChannelJoinEventPublisher.PublishEvent(this, new ChatChannelJoinedEventArgs(ChatChannelType.Guild, new DefaultVivoxTextChannelSubscribableAdapter(guildChannel), new DefaultVivoxChatChannelSenderAdapter(guildChannel))); } catch (Exception e) { if (Logger.IsErrorEnabled) { Logger.Error($"Failed to Initialize Guild chat. Reason: {e.Message}\n\nStack: {e.StackTrace}"); } throw; } }); }
public async Task <IActionResult> CreateCharacter([FromRoute] string name, [FromServices][NotNull] IPlayfabCharacterClient playfabCharacterClient, [FromServices][NotNull] ICharacterAppearanceRepository characterAppearanceRepository, [FromServices][NotNull] ICharacterDataRepository characterDataRepository) { if (playfabCharacterClient == null) { throw new ArgumentNullException(nameof(playfabCharacterClient)); } if (characterAppearanceRepository == null) { throw new ArgumentNullException(nameof(characterAppearanceRepository)); } if (characterDataRepository == null) { throw new ArgumentNullException(nameof(characterDataRepository)); } if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); } int accountId = ClaimsReader.GetAccountIdInt(User); bool nameIsAvailable = await ValidateNameAvailability(name); if (!nameIsAvailable) { return(BadRequest(new CharacterCreationResponse(CharacterCreationResponseCode.NameUnavailableError))); } string playfabId = ClaimsReader.GetPlayfabId(User); //Now, we actually need to create the character on PlayFab first. It's better to have an orphaned character on PlayFab //than to have a character without a PlayFab equivalent. PlayFabResultModel <GladMMOPlayFabGrantCharacterToUserResult> playFabResultModel = await playfabCharacterClient.GrantCharacterToUser(new GladMMOPlayFabGrantCharacterToUserRequest(name, "test", playfabId)); //TODO: Better error handling if (playFabResultModel.ResultCode != HttpStatusCode.OK) { if (Logger.IsEnabled(LogLevel.Error)) { Logger.LogError($"PlayFab CharacterCreation Erorr: {playFabResultModel.ResultCode}:{playFabResultModel.ResultStatus}"); } return(BadRequest(new CharacterCreationResponse(CharacterCreationResponseCode.GeneralServerError))); } CharacterEntryModel characterEntryModel = new CharacterEntryModel(accountId, name, playfabId, playFabResultModel.Data.CharacterId); //TODO: We need a transition around the creation of the below entries. ProjectVersionStage.AssertBeta(); //TODO: Don't expose the database table model //Otherwise we should try to create. There is a race condition here that can cause it to still fail //since others could create a character with this name before we finish after checking bool result = await CharacterRepository.TryCreateAsync(characterEntryModel); //TODO: Also needs to be apart of the transaction if (result) { await characterDataRepository.TryCreateAsync(new CharacterDataModel(characterEntryModel.CharacterId, 0)); await characterAppearanceRepository.TryCreateAsync(new CharacterAppearanceModel(characterEntryModel.CharacterId, 9)); //Default is 9 right now. } return(Json(new CharacterCreationResponse(CharacterCreationResponseCode.Success))); }