/// <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(); //We may already be able to register. if (await TryRegisterGuildStatus(guid, hubConnectedTo.Groups, hubConnectedTo.Context.ConnectionId).ConfigureAwait(false) == HubOnConnectionState.Success) { return(HubOnConnectionState.Success); } HubOnConnectionState state = await TryRequestCharacterGuildStatus(guid, hubConnectedTo.Context.UserIdentifier) .ConfigureAwait(false); if (state == HubOnConnectionState.Success) { return(await TryRegisterGuildStatus(guid, hubConnectedTo.Groups, hubConnectedTo.Context.ConnectionId) .ConfigureAwait(false)); } //Just error, we don't need to abort. Something didn't work right though. return(HubOnConnectionState.Error); }
//TODO: Race condition here because it's possible the subsciber hasn't subscribed just yet. /// <inheritdoc /> public async Task OnGameInitialized() { CharacterListResponse listResponse = await CharacterQueryable.GetCharacters(AuthTokenRepository.RetrieveWithType()) .ConfigureAwait(true); if (!listResponse.isSuccessful || listResponse.CharacterIds.Count == 0) { if (Logger.IsErrorEnabled) { Logger.Error($"Failed to query character list. Recieved ResultCode: {listResponse.ResultCode}"); } //We don't have character creation... Soooo, nothing we can do right now. return; } //TODO: Should we make this API spit out network guids? foreach (var characterId in listResponse.CharacterIds) { NetworkEntityGuid entityGuid = new NetworkEntityGuidBuilder() .WithType(EntityType.Player) .WithId(characterId) .Build(); OnCharacterSelectionEntryChanged?.Invoke(this, new CharacterSelectionEntryDataChangeEventArgs(entityGuid)); } }
//TODO: Create a converter type private static ZoneServerNpcEntryModel BuildDatabaseNPCEntryToTransportNPC(NPCEntryModel npc) { NetworkEntityGuidBuilder guidBuilder = new NetworkEntityGuidBuilder(); NetworkEntityGuid guid = guidBuilder.WithId(npc.EntryId) .WithType(EntityType.Npc) .Build(); //TODO: Create a Vector3 converter return(new ZoneServerNpcEntryModel(guid, npc.NpcTemplateId, new Vector3(npc.SpawnPosition.X, npc.SpawnPosition.Y, npc.SpawnPosition.Z), npc.MovementType, npc.MovementData)); }
/// <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)) .ConfigureAwait(false); } 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) { //TODO: Better error code await context.PayloadSendService.SendMessage(new ClientSessionClaimResponsePayload(ClientSessionClaimResponseCode.SessionUnavailable)) .ConfigureAwait(false); return; } NetworkEntityGuidBuilder builder = new NetworkEntityGuidBuilder(); builder .WithId(payload.CharacterId) .WithType(EntityType.Player); //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) .ConfigureAwait(false); Vector3 position = locationResponse.isSuccessful ? locationResponse.Position : Vector3.zero; if (Logger.IsDebugEnabled) { Logger.Debug($"Recieved player location: {position}"); } //Just broadcast successful claim, let listeners figure out what to do with this information. OnSuccessfulSessionClaimed?.Invoke(this, new PlayerSessionClaimedEventArgs(builder.Build(), position, new PlayerEntitySessionContext(context.PayloadSendService, context.Details.ConnectionId))); await context.PayloadSendService.SendMessage(new ClientSessionClaimResponsePayload(ClientSessionClaimResponseCode.Success)) .ConfigureAwait(false); //TODO: We shouldn't hardcode this, we should send the correct scene specified by the gameserver this zone/instance connects to to service. await context.PayloadSendService.SendMessage(new LoadNewSceneEventPayload(PlayableGameScene.LobbyType1)) .ConfigureAwait(false); }
/// <inheritdoc /> public override async Task OnDisconnectedAsync(Exception exception) { NetworkEntityGuid guid = new NetworkEntityGuidBuilder() .WithId(int.Parse(Context.UserIdentifier)) .WithType(EntityType.Player) .Build(); //Right now this doesn't depend on entity, so we just do it. if (ZoneLookupService.Contains(Context.ConnectionId)) { ZoneLookupService.Unregister(Context.ConnectionId); } if (Logger.IsEnabled(LogLevel.Information)) { Logger.LogInformation($"About to attempt final cleanup for Entity: {guid}"); } //If the entity is no longer contained we should clear up FinalEntityLockResult entityLockResult = await EntityLockService.TryAquireFinalEntityLockAsync(guid); if (entityLockResult.Result) { if (Logger.IsEnabled(LogLevel.Information)) { Logger.LogInformation($"Clearing Entity data for Entity: {guid}. Last connection related to the Entity."); } using (entityLockResult.LockObject) { foreach (var c in EntityRemovable) { c.RemoveEntityEntry(guid); } } } else { if (Logger.IsEnabled(LogLevel.Information)) { Logger.LogInformation($"Entity: {guid} still has active connections/sessions claiming interest. Won't cleanup entity data."); } //We still need to release interest //so that the ref count goes down, otherwise it'll never be cleaned up. await this.EntityLockService.ReleaseEntityInterestAsync(guid) .ConfigureAwait(false); } await base.OnDisconnectedAsync(exception); }
/// <inheritdoc /> public override async Task OnConnectedAsync() { await base.OnConnectedAsync() .ConfigureAwait(false); if (Logger.IsEnabled(LogLevel.Information)) { Logger.LogInformation($"Account Connected: {ClaimsReader.GetUserName(Context.User)}:{ClaimsReader.GetUserId(Context.User)}"); } NetworkEntityGuid guid = new NetworkEntityGuidBuilder() .WithId(int.Parse(Context.UserIdentifier)) .WithType(EntityType.Player) .Build(); //Register interest and then lock //We need to lock on the entity so only 1 connection for the entity can go through this process at a time. await EntityLockService.RegisterEntityInterestAsync(guid) .ConfigureAwait(false); using (await EntityLockService.AquireEntityLockAsync(guid).ConfigureAwait(false)) { try { foreach (var listener in OnConnectionHubListeners) { HubOnConnectionState connectionState = await listener.OnConnected(this).ConfigureAwait(false); //if the listener indicated we need to abort for whatever reason we //should believe it and just abort. if (connectionState == HubOnConnectionState.Abort) { Context.Abort(); break; } } } catch (Exception e) { if (Logger.IsEnabled(LogLevel.Error)) { Logger.LogInformation($"Account: {ClaimsReader.GetUserName(Context.User)}:{ClaimsReader.GetUserId(Context.User)} failed to properly connect to hub. Error: {e.Message}\n\nStack: {e.StackTrace}"); } Context.Abort(); } } }
protected EntityAssociatedData <T> BuildForwardableAssociatedData <T>([JetBrains.Annotations.NotNull] IHubConnectionMessageContext context, [JetBrains.Annotations.NotNull] T envolpeContents) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (envolpeContents == null) { throw new ArgumentNullException(nameof(envolpeContents)); } //TODO: We should cache somehow the identifier's int value, parsing it each time I think can be costly. NetworkEntityGuid guid = new NetworkEntityGuidBuilder() .WithId(int.Parse(context.HubConntext.UserIdentifier)) .WithType(EntityType.Player) .Build(); return(new EntityAssociatedData <T>(guid, envolpeContents)); }