public async Task <IActionResult> JoinZoneProximityChat([FromServices] ICharacterSessionRepository characterSessionRepository, [FromServices] IFactoryCreatable <VivoxTokenClaims, VivoxTokenClaimsCreationContext> claimsFactory, [FromServices] IVivoxTokenSignService signService) { int accountId = this.ClaimsReader.GetAccountIdInt(User); //If the user doesn't actually have a claimed session in the game //then we shouldn't log them into Vivox. if (!await characterSessionRepository.AccountHasActiveSession(accountId)) { return(BuildFailedResponseModel(VivoxLoginResponseCode.NoActiveCharacterSession)); } int characterId = await RetrieveSessionCharacterIdAsync(characterSessionRepository, accountId); CharacterSessionModel session = await characterSessionRepository.RetrieveAsync(characterId); //Players in the same zone will all join the same proximity channel such as Prox-1. //They can use this for proximity text and voice chat. //TODO: Use a factory for channel name generation maybe? VivoxTokenClaims claims = claimsFactory.Create(new VivoxTokenClaimsCreationContext(characterId, VivoxAction.JoinChannel, new VivoxChannelData(true, $"Prox-{session.ZoneId}"))); //We don't send it back in a JSON form even though it's technically a JSON object //because the client just needs it as a raw string anyway to put through the Vivox client API. return(BuildSuccessfulResponseModel(new VivoxChannelJoinResponse(signService.CreateSignature(claims), claims.DestinationSIPURI))); }
private async Task <bool> CheckZoneAuthorizedToModifyCharacterData(int characterId) { if (!await CharacterSessionRepository.CharacterHasActiveSession(characterId)) { return(false); } CharacterSessionModel model = await CharacterSessionRepository.RetrieveAsync(characterId); //If they aren't in this zone we shouldn't be allowed to save it. return(ClaimsReader.GetAccountIdInt(User) == model.ZoneId); }
/// <inheritdoc /> public async Task <bool> TryCreateAsync(CharacterSessionModel model) { if (model == null) { throw new ArgumentNullException(nameof(model)); } //TODO: look into transactions/locks await Context .CharacterSessions .AddAsync(model); //TODO: This can throw due to race condition int rowChangedCount = await Context.SaveChangesAsync(); return(rowChangedCount != 0); }
/// <inheritdoc /> public Task UpdateAsync(int key, CharacterSessionModel model) { GeneralGenericCrudRepositoryProvider <int, CharacterSessionModel> crudProvider = new GeneralGenericCrudRepositoryProvider <int, CharacterSessionModel>(Context.CharacterSessions, Context); return(crudProvider.UpdateAsync(key, model)); }
public async Task<CharacterSessionEnterResponse> EnterSession([FromRoute(Name = "id")] int characterId, [FromServices] ICharacterLocationRepository characterLocationRepository, [FromServices] IZoneServerRepository zoneServerRepository) { if(!await IsCharacterIdValidForUser(characterId, CharacterRepository)) return new CharacterSessionEnterResponse(CharacterSessionEnterResponseCode.InvalidCharacterIdError); int accountId = ClaimsReader.GetAccountIdInt(User); //This checks to see if the account, not just the character, has an active session. //We do this before we check anything to reject quick even though the query behind this //may be abit more expensive //As a note, this checks (or should) CLAIMED SESSIONS. So, it won't prevent multiple session entries for an account //This is good because we actually use the left over session data to re-enter the instances on disconnect. if(await CharacterSessionRepository.AccountHasActiveSession(accountId)) return new CharacterSessionEnterResponse(CharacterSessionEnterResponseCode.AccountAlreadyHasCharacterSession); //They may have a session entry already, which is ok. So long as they don't have an active claimed session //which the above query checks for. bool hasSession = await CharacterSessionRepository.ContainsAsync(characterId); //We need to check active or not if (hasSession) { //It's possible that the session no longer matches the character's //persisted location. We should check their location and put them in the correct zone. //If it's active we can just retrieve the data and send them off on their way CharacterSessionModel sessionModel = await CharacterSessionRepository.RetrieveAsync(characterId, true); if (await characterLocationRepository.ContainsAsync(characterId)) { CharacterLocationModel locationModel = await characterLocationRepository.RetrieveAsync(characterId); //They have a location, verify it matches the session if (locationModel.WorldId != sessionModel.ZoneEntry.WorldId) { //The location world and the session's world do not match, so remove the session. await CharacterSessionRepository.TryDeleteAsync(sessionModel.CharacterId); } else return new CharacterSessionEnterResponse(sessionModel.ZoneId); } else //TODO: Handle case when we have an inactive session that can be claimed return new CharacterSessionEnterResponse(sessionModel.ZoneId); } //If we didn't return above then we should be in a state where the below can handle this now. try { int targetSessionZoneId = 0; //TO know what zone we should connect to we need to check potential //character location. if (await characterLocationRepository.ContainsAsync(characterId)) { CharacterLocationModel locationModel = await characterLocationRepository.RetrieveAsync(characterId); //We have no session so we need to find a zone that matches this server. ZoneInstanceEntryModel firstWithWorldId = await zoneServerRepository.FindFirstWithWorldId(locationModel.WorldId); //TODO: Should we request one be created for the user?? //There is NO instance available for this world. if (firstWithWorldId == null) { //Location is basically invalid since there is no running world await characterLocationRepository.TryDeleteAsync(locationModel.CharacterId); } else { targetSessionZoneId = firstWithWorldId.ZoneId; } } //Try to get into any zone if (targetSessionZoneId == 0) { ZoneInstanceEntryModel entryModel = await zoneServerRepository.AnyAsync(); if (entryModel != null) targetSessionZoneId = entryModel.ZoneId; } //Still zero means literally no zone servers are available. if(targetSessionZoneId == 0) return new CharacterSessionEnterResponse(CharacterSessionEnterResponseCode.GeneralServerError); if(await CharacterSessionRepository.TryCreateAsync(new CharacterSessionModel(characterId, targetSessionZoneId))) return new CharacterSessionEnterResponse(targetSessionZoneId); else return new CharacterSessionEnterResponse(CharacterSessionEnterResponseCode.GeneralServerError); } catch (Exception e) { if(Logger.IsEnabled(LogLevel.Error)) Logger.LogError($"Character with ID: {characterId} failed to create session. Potentially no default world assigned or World with session was deleted and orphaned. Reason: {e.Message}"); throw; } }