public async Task <IActionResult> UpdateCharacterLocation( [FromBody] ZoneServerCharacterLocationSaveRequest saveRequest, [NotNull][FromServices] ICharacterLocationRepository locationRepository) { if (locationRepository == null) { throw new ArgumentNullException(nameof(locationRepository)); } int characterId = saveRequest.CharacterId; if (characterId <= 0 || !await CharacterRepository.ContainsAsync(characterId) .ConfigureAwait(false)) { return(NotFound()); } //TODO: Is this the best way to deal with this? if (await locationRepository.ContainsAsync(characterId).ConfigureAwait(false)) { await locationRepository.UpdateAsync(characterId, BuildCharacterLocationFromSave(characterId, saveRequest)) .ConfigureAwait(false); } else { await locationRepository.TryCreateAsync(BuildCharacterLocationFromSave(characterId, saveRequest)) .ConfigureAwait(false); } return(Ok()); }
public static async Task Test_Controller_Produces_AlreadyHasActiveSession_When_Session_Has() { //arrange IServiceProvider serviceProvider = ControllerTestsHelpers.BuildServiceProvider <CharacterSessionController>("Test", 1); CharacterSessionController controller = serviceProvider.GetService <CharacterSessionController>(); ICharacterRepository characterRepo = serviceProvider.GetService <ICharacterRepository>(); ICharacterSessionRepository sessionRepo = serviceProvider.GetService <ICharacterSessionRepository>(); ICharacterLocationRepository characterLocationRepo = serviceProvider.GetService <ICharacterLocationRepository>(); IZoneServerRepository zoneRepository = serviceProvider.GetService <IZoneServerRepository>(); await characterRepo.TryCreateAsync(new CharacterEntryModel(1, "Testing")); await sessionRepo.TryCreateAsync(new CharacterSessionModel(1, 0)); //We can't create the claimed session through this interface because it's a stored procedure. //Raw SQL can't execute. So we must interact directly with the DbSet //await sessionRepo.TryClaimUnclaimedSession(1, 1); CharacterDatabaseContext context = serviceProvider.GetService <CharacterDatabaseContext>(); await context.ClaimedSession.AddAsync(new ClaimedSessionsModel(1)); await context.SaveChangesAsync(); //act CharacterSessionEnterResponse response = await controller.EnterSession(1, characterLocationRepo, zoneRepository); //assert Assert.False(response.isSuccessful, $"Characters that already have "); Assert.AreEqual(CharacterSessionEnterResponseCode.AccountAlreadyHasCharacterSession, response.ResultCode); }
public SpawnCharacterCommandHandler( ICharacterLocationRepository characterLocationRepository, ISingleLocationPerCharacterChecker singleLocationPerCharacterChecker, IUnitOfWork unitOfWork) { _characterLocationRepository = characterLocationRepository; _singleLocationPerCharacterChecker = singleLocationPerCharacterChecker; _unitOfWork = unitOfWork; }
public static async Task Test_Controller_Produces_InvalidId_When_Empty() { //arrange IServiceProvider serviceProvider = ControllerTestsHelpers.BuildServiceProvider <CharacterSessionController>("Test", 1); CharacterSessionController controller = serviceProvider.GetService <CharacterSessionController>(); ICharacterLocationRepository characterLocationRepo = serviceProvider.GetService <ICharacterLocationRepository>(); IZoneServerRepository zoneRepository = serviceProvider.GetService <IZoneServerRepository>(); //act CharacterSessionEnterResponse response = await controller.EnterSession(5, characterLocationRepo, zoneRepository); //assert Assert.False(response.isSuccessful); Assert.AreEqual(CharacterSessionEnterResponseCode.InvalidCharacterIdError, response.ResultCode); }
//[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()); }
public static async Task Test_Controller_Creates_UnclaimedSession_On_OnEnterSession(int accountId) { //arrange IServiceProvider serviceProvider = ControllerTestsHelpers.BuildServiceProvider <CharacterSessionController>("Test", accountId); CharacterSessionController controller = serviceProvider.GetService <CharacterSessionController>(); ICharacterRepository characterRepo = serviceProvider.GetService <ICharacterRepository>(); ICharacterLocationRepository characterLocationRepo = serviceProvider.GetService <ICharacterLocationRepository>(); IZoneServerRepository zoneRepository = serviceProvider.GetService <IZoneServerRepository>(); await characterRepo.TryCreateAsync(new CharacterEntryModel(accountId, "Testing")); //act: We also test that we can do it multiple times CharacterSessionEnterResponse response = await controller.EnterSession(1, characterLocationRepo, zoneRepository); //assert Assert.True(response.isSuccessful); Assert.AreEqual(CharacterSessionEnterResponseCode.Success, response.ResultCode); }
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()); }
public static async Task Test_Controller_Produces_InvalidId_When_Wrong_AccountId() { //arrange IServiceProvider serviceProvider = ControllerTestsHelpers.BuildServiceProvider <CharacterSessionController>("Test", 2); CharacterSessionController controller = serviceProvider.GetService <CharacterSessionController>(); ICharacterRepository characterRepo = serviceProvider.GetService <ICharacterRepository>(); ICharacterSessionRepository sessionRepo = serviceProvider.GetService <ICharacterSessionRepository>(); ICharacterLocationRepository characterLocationRepo = serviceProvider.GetService <ICharacterLocationRepository>(); IZoneServerRepository zoneRepository = serviceProvider.GetService <IZoneServerRepository>(); await characterRepo.TryCreateAsync(new CharacterEntryModel(1, "Testing")); await sessionRepo.TryCreateAsync(new CharacterSessionModel(1, 0)); //act CharacterSessionEnterResponse response = await controller.EnterSession(1, characterLocationRepo, zoneRepository); //assert Assert.False(response.isSuccessful, $"Characters should not be able to create sessions when the accountid doesn't match."); Assert.AreEqual(response.ResultCode, CharacterSessionEnterResponseCode.InvalidCharacterIdError); }
public async Task <IActionResult> SaveFullCharacterDataAsync([FromRoute(Name = "id")] int characterId, [FromBody][NotNull] FullCharacterDataSaveRequest request, [NotNull][FromServices] ICharacterLocationRepository locationRepository, [NotNull][FromServices] IZoneServerRepository zoneRepository, [FromServices][NotNull] ICharacterDataRepository characterDataRepository) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (characterId <= 0) { throw new ArgumentOutOfRangeException(nameof(characterId)); } //TODO: Is it safe to not do this as a transaction?? //TODO: For HTTP callers we should maybe include better information. Though with message queue we can't respond. if (!await CheckZoneAuthorizedToModifyCharacterData(characterId)) { return(Forbid()); } //Don't always want to save the position of the user. if (request.isPositionSaved) { await UpdateCharacterLocation(characterId, request.CharacterLocationData, locationRepository, zoneRepository); } //TODO: Probably need to handle this abit better and more data than just experience. //Entity data can now be saved. await UpdatePlayerData(characterId, new CharacterDataInstance(request.PlayerDataSnapshot.GetFieldValue <int>(PlayerObjectField.PLAYER_TOTAL_EXPERIENCE)), characterDataRepository); if (request.ShouldReleaseCharacterSession) { await CharacterSessionRepository.TryDeleteClaimedSession(characterId); } return(Ok()); }
public static async Task Test_Controller_Produces_SessionGranted_With_Zone_Id_If_UnclaimedSession_Exists(int accountId, int zoneid) { //arrange IServiceProvider serviceProvider = ControllerTestsHelpers.BuildServiceProvider <CharacterSessionController>("Test", accountId); CharacterSessionController controller = serviceProvider.GetService <CharacterSessionController>(); ICharacterRepository characterRepo = serviceProvider.GetService <ICharacterRepository>(); ICharacterSessionRepository sessionRepo = serviceProvider.GetService <ICharacterSessionRepository>(); ICharacterLocationRepository characterLocationRepo = serviceProvider.GetService <ICharacterLocationRepository>(); IZoneServerRepository zoneRepository = serviceProvider.GetService <IZoneServerRepository>(); await characterRepo.TryCreateAsync(new CharacterEntryModel(accountId, "Testing")); await sessionRepo.TryCreateAsync(new CharacterSessionModel(1, zoneid)); //act CharacterSessionEnterResponse response = await controller.EnterSession(1, characterLocationRepo, zoneRepository); //assert Assert.True(response.isSuccessful, $"Created sessions should be granted if no active account session or character session is claimed."); Assert.AreEqual(CharacterSessionEnterResponseCode.Success, response.ResultCode); Assert.AreEqual(zoneid, response.ZoneId, $"Provided zone id was not the same as the session."); }
public SingleLocationPerCharacterChecker(ICharacterLocationRepository characterLocationRepository) { _characterLocationRepository = characterLocationRepository; }
//TODO: Renable ZoneServer authorization eventually. //[AuthorizeJwt(GuardianApplicationRole.ZoneServer)] public async Task <IActionResult> GetCharacterLocation([FromRoute(Name = "id")] int characterId, [NotNull][FromServices] ICharacterLocationRepository locationRepository) { if (locationRepository == null) { throw new ArgumentNullException(nameof(locationRepository)); } if (characterId <= 0 || !await CharacterRepository.ContainsAsync(characterId) .ConfigureAwaitFalse()) { return(Json(new ZoneServerCharacterLocationResponse(ZoneServerCharacterLocationResponseCode.CharacterDoesntExist))); } //So, the character exists and we now need to check if we can find a location for it. It may not have one, for whatever reason. //so we need to handle the case where it has none (maybe new character, or was manaully wiped). if (!await locationRepository.ContainsAsync(characterId).ConfigureAwaitFalse()) { return(Json(new ZoneServerCharacterLocationResponse(ZoneServerCharacterLocationResponseCode.NoLocationDefined))); } //Otherwise, let's load and send the result CharacterLocationModel locationModel = await locationRepository.RetrieveAsync(characterId) .ConfigureAwaitFalse(); //TODO: Integrate Map Id design into Schema, and implement it here. return(Json(new ZoneServerCharacterLocationResponse(new Vector3(locationModel.XPosition, locationModel.YPosition, locationModel.ZPosition), 1))); }
public GetCharacterLocationQueryHandler(ICharacterLocationRepository characterLocationRepository) { _characterLocationRepository = characterLocationRepository; }
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; } }