[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)); } }
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); }
//[AuthorizeJwt(GuardianApplicationRole.ZoneServer)] //only zone servers should EVER be able to release the active session. They should also likely only be able to release an active session if it's on them. public async Task<IActionResult> ReleaseActiveSession([FromRoute(Name = "id")] int characterId) { //We NEED to AUTH for zoneserver JWT. ProjectVersionStage.AssertAlpha(); //If an active session does NOT exist we have a BIG problem. if(!await CharacterSessionRepository.CharacterHasActiveSession(characterId)) { if(Logger.IsEnabled(LogLevel.Error)) Logger.LogError($"ZoneServer requested ActiveSession for Player: {characterId} be removed. Session DOES NOT EXIST. This should NOT HAPPEN."); return NotFound(); } //We should try to remove the active sesison. //One this active session is revoked the character/account is free to claim any existing session //including the same one that was just freed. if(!await CharacterSessionRepository.TryDeleteClaimedSession(characterId)) { if(Logger.IsEnabled(LogLevel.Error)) Logger.LogError($"ZoneServer requested ActiveSession for Player: {characterId} be removed. Session DOES NOT EXIST. This should NOT HAPPEN."); return BadRequest(); } else { if(Logger.IsEnabled(LogLevel.Information)) Logger.LogInformation($"Removed ActiveSession for Player: {characterId}"); return Ok(); } }
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)); } }
/// <inheritdoc /> protected override Vector3 InternalUpdate(GameObject entity, long currentTime) { ProjectVersionStage.AssertAlpha(); for (int i = 0; i < MovementData.MovementPath.Count - 1; i++) { Debug.DrawLine(MovementData.MovementPath[i], MovementData.MovementPath[i + 1], Color.red, 0.1f); } //Knowing the two points is enough to linerally interpolate between them. long timeSinceSegementState = currentTime - State.TimePathStateCreated; float lerpRatio = (float)timeSinceSegementState / State.PathSegementDuration; entity.transform.position = Vector3.Lerp(MovementData.MovementPath[State.PathIndex], MovementData.MovementPath[State.PathIndex + 1], lerpRatio); if (lerpRatio >= 1.0f) { if (State.PathIndex + 2 >= MovementData.MovementPath.Count) { State = new PathState(MovementData.MovementPath.Count - 1, currentTime, 0); StopGenerator(); } else { State = new PathState(State.PathIndex + 1, currentTime, CalculateDistanceLengthInTicks((MovementData.MovementPath[State.PathIndex + 2] - MovementData.MovementPath[State.PathIndex + 1]).magnitude, 1.0f)); } } return(entity.transform.position); }
protected override void HandleEvent(ActionBarButtonStateChangedEventArgs args) { //TODO: Check actionbar index and handle multiple rows. if (ActionBarRow.ContainsKey(args.Index)) { IUIActionBarButton barButton = ActionBarRow[args.Index]; if (args.ActionType == ActionBarIndexType.Empty) { barButton.SetElementActive(false); } else { barButton.SetElementActive(true); //TODO: Refactor for spell/item if (args.ActionType == ActionBarIndexType.Spell) { //TODO: Abstract the icon content loading //TODO: Don't assume we have the content icon. Throw/log better exception SpellDefinitionDataModel definition = SpellDataCollection.GetSpellDefinition(args.ActionId); ContentIconInstanceModel icon = ContentIconCollection[definition.SpellIconId]; ProjectVersionStage.AssertAlpha(); //TODO: Load async Texture2D iconTexture = Resources.Load <Texture2D>(Path.Combine("Icon", Path.GetFileNameWithoutExtension(icon.IconPathName))); barButton.ActionBarImageIcon.SetSpriteTexture(iconTexture); } else { throw new InvalidOperationException($"TODO: Implement empty/item action bar support"); } } } }
protected override void OnThreadUnSafeEventFired(object source, CharacterSessionDataChangedEventArgs args) { if (Logger.IsInfoEnabled) { Logger.Info($"Starting process to download world."); } UnityAsyncHelper.UnityMainThreadContext.PostAsync(async() => { long worldId = 0; try { //TODO: Handle failure ProjectVersionStage.AssertAlpha(); //TODO: Handle throwing/error //We need to know the world the zone is it, so we can request a download URL for it. var worldConfig = await ZoneDataService.GetZoneWorldConfigurationAsync(args.ZoneIdentifier) .ConfigureAwaitFalse(); if (!worldConfig.isSuccessful) { throw new InvalidOperationException($"Failed to query World Configuration for ZoneId: {args.ZoneIdentifier}"); } worldId = worldConfig.Result.WorldId; //With the worldid we can get the download URL. ContentDownloadURLResponse urlDownloadResponse = await ContentService.RequestWorldDownloadUrl(worldId) .ConfigureAwaitFalse(); if (Logger.IsInfoEnabled) { Logger.Info($"World Download Url: {urlDownloadResponse.DownloadURL}"); } //Can't do web request not on the main thread, sadly. await new UnityYieldAwaitable(); WorldDownloader downloader = new WorldDownloader(Logger); await downloader.DownloadAsync(urlDownloadResponse.DownloadURL, urlDownloadResponse.Version, o => { OnWorldDownloadBegins?.Invoke(this, new WorldDownloadBeginEventArgs(o)); }); } catch (Exception e) { if (Logger.IsErrorEnabled) { Logger.Error($"Failed to query for Download URL for ZoneId: {args.ZoneIdentifier} WorldId: {worldId} (0 if never succeeded request). Error: {e.Message}"); } throw; } }); }
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); }
/// <inheritdoc /> protected override Task HandleMessage(IPeerSessionMessageContext <GameServerPacketPayload> context, PlayerModelChangeRequestPayload payload, NetworkEntityGuid guid) { //At this point, the player wants to change his model. //However we really can't be sure it's a valid model //so we validate it before setting the model id. //TODO: Validate the model id. ProjectVersionStage.AssertAlpha(); IEntityDataFieldContainer entityDataFieldContainer = EntityFieldMap.RetrieveEntity(guid); //This change will be broadcast to anyone interested. entityDataFieldContainer.SetFieldValue(BaseObjectField.UNIT_FIELD_DISPLAYID, payload.ModelId); return(Task.CompletedTask); }
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 DefaultLoadableContentResourceManager( [NotNull] ILog logger, UserContentType contentType) { if (!Enum.IsDefined(typeof(UserContentType), contentType)) { throw new InvalidEnumArgumentException(nameof(contentType), (int)contentType, typeof(UserContentType)); } //TODO: We haven't implemented the refcounted cleanup. We ref count, but don't yet dispose. ProjectVersionStage.AssertAlpha(); Logger = logger ?? throw new ArgumentNullException(nameof(logger)); ContentType = contentType; ResourceHandleCache = new Dictionary <long, ReferenceCountedPrefabContentResourceHandle>(); ReleaseUnmanagedResources(); }
public async Task<IActionResult> TryClaimSession([FromBody] ZoneServerTryClaimSessionRequest request) { //TODO: Renable auth for session claiming ProjectVersionStage.AssertAlpha(); if(!this.ModelState.IsValid) return BadRequest(); //TODO: Send JSON back too. //TODO: We should validate a lot things. One, that the character has a session on this zoneserver. //We should also validate that the account owns the character. We need a new auth process for entering users. //We have to do this validation, somehow. Or malicious players could spoof this. ProjectVersionStage.AssertAlpha(); //TODO: Verify that the zone id is correct. Right now we aren't providing it and the query doesn't enforce it. //We don't validate characterid/accountid association manually. It is implemented in the tryclaim SQL instead. //It additionally also checks the zone relation for the session so it will fail if it's invalid for the provided zone. //Therefore we don't need to make 3/4 database calls/queries to claim a session. Just one stored procedure call. //This is preferable. A result code will be used to indicate the exact error in the future. For now it just fails if it fails. bool sessionClaimed = await CharacterSessionRepository.TryClaimUnclaimedSession(request.PlayerAccountId, request.CharacterId); return Ok(new ZoneServerTryClaimSessionResponse(sessionClaimed ? ZoneServerTryClaimSessionResponseCode.Success : ZoneServerTryClaimSessionResponseCode.GeneralServerError)); //TODO }
/// <inheritdoc /> public async Task <HubOnConnectionState> OnConnected(Hub hubConnectedTo) { //TODO: Verify that the character they requested is owned by them. ProjectVersionStage.AssertAlpha(); NetworkEntityGuid guid = new NetworkEntityGuidBuilder() .WithId(int.Parse(hubConnectedTo.Context.UserIdentifier)) .WithType(EntityType.Player) .Build(); HubOnConnectionState state = await TryRequestCharacterGuildStatus(guid, hubConnectedTo.Context.UserIdentifier) .ConfigureAwaitFalse(); if (state == HubOnConnectionState.Success) { await RegisterGuildOnExistingResponse(guid, hubConnectedTo.Groups, hubConnectedTo.Context.ConnectionId) .ConfigureAwaitFalseVoid(); return(HubOnConnectionState.Success); } //Just error, we don't need to abort. Something didn't work right though. return(HubOnConnectionState.Error); }
/// <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()); }
void Start() { //TODO: Camera.main is SLOW. ProjectVersionStage.AssertAlpha(); LookTransform = Camera.main.transform; }
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(); }
/// <inheritdoc /> public override async Task HandleMessage(IPeerSessionMessageContext <GameServerPacketPayload> context, ClientSessionClaimRequestPayload payload) { //TODO: We need better validation/authorization for clients trying to claim a session. Right now it's open to malicious attack ZoneServerTryClaimSessionResponse zoneServerTryClaimSessionResponse = null; try { ProjectVersionStage.AssertAlpha(); zoneServerTryClaimSessionResponse = await GameServerClient.TryClaimSession(new ZoneServerTryClaimSessionRequest(await GameServerClient.GetAccountIdFromToken(payload.JWT), payload.CharacterId)) .ConfigureAwaitFalse(); } catch (Exception e) //we could get an unauthorized response { Logger.Error($"Failed to Query for AccountId: {e.Message}. AuthToken provided was: {payload.JWT}"); throw; } if (!zoneServerTryClaimSessionResponse.isSuccessful) { if (Logger.IsWarnEnabled) { Logger.Warn($"Client attempted to claim session for Character: {payload.CharacterId} but was denied."); } //TODO: Better error code await context.PayloadSendService.SendMessage(new ClientSessionClaimResponsePayload(ClientSessionClaimResponseCode.SessionUnavailable)) .ConfigureAwaitFalse(); return; } NetworkEntityGuid entityGuid = new NetworkEntityGuidBuilder() .WithId(payload.CharacterId) .WithType(EntityType.Player) .Build(); //TODO: We assume they are authenticated, we don't check at the moment but we WILL and SHOULD. Just load their location. ZoneServerCharacterLocationResponse locationResponse = await GameServerClient.GetCharacterLocation(payload.CharacterId) .ConfigureAwaitFalse(); Vector3 position = locationResponse.isSuccessful ? locationResponse.Position : SpawnPointProvider.GetSpawnPoint().WorldPosition; SpawnPointData pointData = new SpawnPointData(position, Quaternion.identity); if (Logger.IsDebugEnabled) { Logger.Debug($"Recieved player location: {pointData.WorldPosition} from {(locationResponse.isSuccessful ? "Database" : "Spawnpoint")}"); } //TODO: We need a cleaner/better way to load initial player data. ResponseModel <CharacterDataInstance, CharacterDataQueryReponseCode> characterData = await CharacterService.GetCharacterData(payload.CharacterId); //TODO: Check success. InitialCharacterDataMappable.AddObject(entityGuid, characterData.Result); //Just broadcast successful claim, let listeners figure out what to do with this information. OnSuccessfulSessionClaimed?.Invoke(this, new PlayerSessionClaimedEventArgs(entityGuid, pointData.WorldPosition, new PlayerEntitySessionContext(context.PayloadSendService, context.Details.ConnectionId, context.ConnectionService))); await context.PayloadSendService.SendMessage(new ClientSessionClaimResponsePayload(ClientSessionClaimResponseCode.Success)) .ConfigureAwaitFalse(); }
/// <inheritdoc /> public Task <bool> CanUserAccessWorldContet(int userId, long worldId) { //TODO: When this project leaves alpha, we should probably handle the download authorization validator. ProjectVersionStage.AssertAlpha(); return(Task.FromResult <bool>(true)); }
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))); }
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; } }); }