public async Task <IActionResult> GetServerEndpoint([FromRoute(Name = "id")] int zoneId) { if (!ModelState.IsValid) { return(Json(new ResolveServiceEndpointResponse(ResolveServiceEndpointResponseCode.GeneralRequestError))); } //We reuse the service discovery response model if (!await ZoneRepository.ContainsAsync(zoneId)) { return(Json(new ResolveServiceEndpointResponse(ResolveServiceEndpointResponseCode.ServiceUnlisted))); } //Small interval for race condition. So we try catch. try { ZoneInstanceEntryModel zone = await ZoneRepository.RetrieveAsync(zoneId); //Should be good, we just send them the endpoint if (zone != null) { return(Ok(new ResolveServiceEndpointResponse(new ResolvedEndpoint(zone.ZoneServerAddress, zone.ZoneServerPort)))); } } catch (Exception) { //TODO: Logging/event } return(Json(new ResolveServiceEndpointResponse(ResolveServiceEndpointResponseCode.GeneralRequestError))); }
public async Task <IActionResult> RegisteredZoneCheckin([FromBody] ZoneServerCheckinRequestModel zoneCheckinRequest) { //This is ok, it means the zone was unregistered before the checkin message was recieved. //It doesn't alone indicate a failure. if (!await ZoneRepository.ContainsAsync(ClaimsReader.GetAccountIdInt(User))) { if (Logger.IsEnabled(LogLevel.Warning)) { Logger.LogWarning($"UserId: {ClaimsReader.GetPlayerAccountId(User)} attempted to checkin ZoneId: {ClaimsReader.GetAccountId(User)} but the zone was not registered."); } return(Ok()); } ZoneInstanceEntryModel model = await ZoneRepository.RetrieveAsync(ClaimsReader.GetAccountIdInt(User)); //We don't REMOVE if expired. This should only update checkin time //something else should be responsible for removal if expired at some point. model.UpdateCheckinTime(); await ZoneRepository.UpdateAsync(model.ZoneId, model); //We don't really have anything to reply to, this shouldn't be called externally. //It's basically handling a proxied message queue message. return(Ok()); }
public async Task <IActionResult> GetDefaultServerEndpoint() { if (!ModelState.IsValid) { return(BuildFailedResponseModel(ResolveServiceEndpointResponseCode.GeneralRequestError)); } //Small interval for race condition. So we try catch. try { ZoneInstanceEntryModel zone = await ZoneRepository.AnyAsync(); //Should be good, we just send them the endpoint if (zone != null) { return(BuildSuccessfulResponseModel(new ZoneConnectionEndpointResponse(zone.ZoneId, new ResolvedEndpoint(zone.ZoneServerAddress, zone.ZoneServerPort)))); } else { return(BuildFailedResponseModel(ResolveServiceEndpointResponseCode.ServiceUnavailable)); } } catch (Exception) { //TODO: Logging/event return(BuildFailedResponseModel(ResolveServiceEndpointResponseCode.GeneralRequestError)); } }
/// <inheritdoc /> public async Task <bool> TryCreateAsync(ZoneInstanceEntryModel model) { #pragma warning disable AsyncFixer02 // Long running or blocking operations under an async method Context .ZoneEntries .Add(model); #pragma warning restore AsyncFixer02 // Long running or blocking operations under an async method return(0 != await Context.SaveChangesAsync()); }
public async Task <IActionResult> GetZoneWorld([FromRoute(Name = "id")] int zoneId) { if (!await ZoneRepository.ContainsAsync(zoneId).ConfigureAwaitFalse()) { Logger.LogError($"Failed to query for WorldId for Zone: {zoneId}"); return(NotFound()); } ZoneInstanceEntryModel entryModel = await ZoneRepository.RetrieveAsync(zoneId) .ConfigureAwaitFalse(); //We just return the world that this zone is for. return(Ok(entryModel.WorldId)); }
public async Task <IActionResult> GetZoneWorldConfiguration([FromRoute(Name = "id")] int zoneId) { if (!await ZoneRepository.ContainsAsync(zoneId).ConfigureAwaitFalse()) { if (Logger.IsEnabled(LogLevel.Error)) { Logger.LogError($"Failed to query for WorldId for Zone: {zoneId}"); } return(BuildFailedResponseModel(ZoneWorldConfigurationResponseCode.ZoneDoesntExist)); } ZoneInstanceEntryModel entryModel = await ZoneRepository.RetrieveAsync(zoneId) .ConfigureAwaitFalse(); //We just return the world that this zone is for. return(BuildSuccessfulResponseModel(new ZoneWorldConfigurationResponse((int)entryModel.WorldId))); }
public async Task <IActionResult> UpdateCharacterLocation( [FromRoute(Name = "id")] int characterId, [FromBody] ZoneServerCharacterLocationSaveRequest saveRequest, [NotNull][FromServices] ICharacterLocationRepository locationRepository, [NotNull][FromServices] IZoneServerRepository zoneRepository) { if (locationRepository == null) { throw new ArgumentNullException(nameof(locationRepository)); } if (zoneRepository == null) { throw new ArgumentNullException(nameof(zoneRepository)); } //TODO: For HTTP callers we should maybe include better information. Though with message queue we can't respond. if (!await CheckZoneAuthorizedToModifyCharacterData(characterId)) { return(Forbid()); } //TODO: This could fail if we unregistered. More gracefully handle that case. //Get world, we need it for location ZoneInstanceEntryModel zoneEntry = await zoneRepository.RetrieveAsync(ClaimsReader.GetAccountIdInt(User)); //If world was deleted then they won't have location. //TODO: Is this the best way to deal with this? if (await locationRepository.ContainsAsync(characterId).ConfigureAwaitFalse()) { await locationRepository.UpdateAsync(characterId, BuildCharacterLocationFromSave(characterId, saveRequest, zoneEntry.WorldId)) .ConfigureAwaitFalseVoid(); } else { await locationRepository.TryCreateAsync(BuildCharacterLocationFromSave(characterId, saveRequest, zoneEntry.WorldId)) .ConfigureAwaitFalse(); } return(Ok()); }
/// <inheritdoc /> public Task UpdateAsync(int key, ZoneInstanceEntryModel model) { GeneralGenericCrudRepositoryProvider <int, ZoneInstanceEntryModel> crudProvider = new GeneralGenericCrudRepositoryProvider <int, ZoneInstanceEntryModel>(Context.ZoneEntries, 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; } }