コード例 #1
0
ファイル: EntityCache.cs プロジェクト: kouweizhong/vita
        void Events_SavedChanges(object sender, EntitySessionEventArgs args)
        {
            var  session             = (EntitySession)args.Session;
            bool fullSetCacheUpdated = false;

            // important - use for-i loop here
            for (int i = 0; i < session.RecordsChanged.Count; i++)
            {
                var rec = session.RecordsChanged[i];
                switch (rec.EntityInfo.CacheType)
                {
                case CacheType.FullSet:
                    fullSetCacheUpdated = true;
                    break;

                case CacheType.Sparse:
                    switch (rec.StatusBeforeSave)
                    {
                    case EntityStatus.Deleting:
                        _sparseCache.Remove(rec); break;

                    default:
                        _sparseCache.Add(rec);
                        break;
                    }//switch
                    break;
                }
            }
            if (fullSetCacheUpdated)
            {
                _fullSetCache.Invalidate(); //it will be reloaded
                //_maxKnownVersion++; //tick up the version
            }
        }//method
コード例 #2
0
        protected override void OnBwoinkTextMessage(BwoinkTextMessage message, EntitySessionEventArgs eventArgs)
        {
            base.OnBwoinkTextMessage(message, eventArgs);
            var senderSession = (IPlayerSession)eventArgs.SenderSession;

            // TODO: Sanitize text?
            // Confirm that this person is actually allowed to send a message here.
            if ((senderSession.UserId != message.ChannelId) && (_adminManager.GetAdminData(senderSession) == null))
            {
                // Unauthorized bwoink (log?)
                return;
            }

            var msg = new BwoinkTextMessage(message.ChannelId, senderSession.UserId, $"{senderSession.Name}: {message.Text}");

            LogBwoink(msg);

            var targets = _adminManager.ActiveAdmins.Select(p => p.ConnectedClient);

            // Admins
            foreach (var channel in targets)
            {
                RaiseNetworkEvent(msg, channel);
            }

            // And involved player
            if (_playerManager.TryGetSessionById(message.ChannelId, out var session))
            {
                if (!targets.Contains(session.ConnectedClient))
                {
                    RaiseNetworkEvent(msg, session.ConnectedClient);
                }
            }
        }
コード例 #3
0
        /// <summary>
        ///     Move an entity which is dragged by the user, but check if they are allowed to do so and to these coordinates
        /// </summary>
        private void OnTabletopMove(TabletopMoveEvent msg, EntitySessionEventArgs args)
        {
            if (args.SenderSession as IPlayerSession is not {
                AttachedEntity : { } playerEntity
            } playerSession)
            {
                return;
            }

            if (!EntityManager.TryGetComponent(msg.TableUid, out TabletopGameComponent? tabletop) || tabletop.Session is not {
            } session)
            {
                return;
            }

            // Check if player is actually playing at this table
            if (!session.Players.ContainsKey(playerSession))
            {
                return;
            }

            if (!CanSeeTable(playerEntity, msg.TableUid) || !CanDrag(playerEntity, msg.MovedEntityUid, out _))
            {
                return;
            }

            // TODO: some permission system, disallow movement if you're not permitted to move the item

            // Move the entity and dirty it (we use the map ID from the entity so noone can try to be funny and move the item to another map)
            var transform         = EntityManager.GetComponent <TransformComponent>(msg.MovedEntityUid);
            var entityCoordinates = new EntityCoordinates(_mapManager.GetMapEntityId(transform.MapID), msg.Coordinates.Position);

            transform.Coordinates = entityCoordinates;
        }
コード例 #4
0
        private void HandleVerbRequest(RequestServerVerbsEvent args, EntitySessionEventArgs eventArgs)
        {
            var player = (IPlayerSession)eventArgs.SenderSession;

            if (!EntityManager.EntityExists(args.EntityUid))
            {
                Logger.Warning($"{nameof(HandleVerbRequest)} called on a non-existent entity with id {args.EntityUid} by player {player}.");
                return;
            }

            if (player.AttachedEntity is not {
            } attached)
            {
                Logger.Warning($"{nameof(HandleVerbRequest)} called by player {player} with no attached entity.");
                return;
            }

            // We do not verify that the user has access to the requested entity. The individual verbs should check
            // this, and some verbs (e.g. view variables) won't even care about whether an entity is accessible through
            // the entity menu or not.

            var force = args.AdminRequest && eventArgs.SenderSession is IPlayerSession playerSession &&
                        _adminMgr.HasAdminFlag(playerSession, AdminFlags.Admin);

            var response =
                new VerbsResponseEvent(args.EntityUid, GetLocalVerbs(args.EntityUid, attached, args.Type, force));

            RaiseNetworkEvent(response, player.ConnectedClient);
        }
コード例 #5
0
        private void HandleExecuteVerb(ExecuteVerbEvent args, EntitySessionEventArgs eventArgs)
        {
            var user = eventArgs.SenderSession.AttachedEntity;

            if (user == null)
            {
                return;
            }

            // It is possible that client-side prediction can cause this event to be raised after the target entity has
            // been deleted. So we need to check that the entity still exists.
            if (Deleted(args.Target) || Deleted(user))
            {
                return;
            }

            // Get the list of verbs. This effectively also checks that the requested verb is in fact a valid verb that
            // the user can perform.
            var verbs = GetLocalVerbs(args.Target, user.Value, args.RequestedVerb.GetType());

            // Note that GetLocalVerbs might waste time checking & preparing unrelated verbs even though we know
            // precisely which one we want to run. However, MOST entities will only have 1 or 2 verbs of a given type.
            // The one exception here is the "other" verb type, which has 3-4 verbs + all the debug verbs.

            // Find the requested verb.
            if (verbs.TryGetValue(args.RequestedVerb, out var verb))
            {
                ExecuteVerb(verb, user.Value, args.Target);
            }
        }
コード例 #6
0
 private void HandleActivateItemInHand(RequestActivateInHandEvent msg, EntitySessionEventArgs args)
 {
     if (args.SenderSession.AttachedEntity != null)
     {
         TryActivateItemInHand(args.SenderSession.AttachedEntity.Value);
     }
 }
コード例 #7
0
 private void HandleMoveItemFromHand(RequestMoveHandItemEvent msg, EntitySessionEventArgs args)
 {
     if (args.SenderSession.AttachedEntity != null)
     {
         TryMoveHeldEntityToActiveHand(args.SenderSession.AttachedEntity.Value, msg.HandName);
     }
 }
コード例 #8
0
 private void HandleActivateInHand(ActivateInHandMsg msg, EntitySessionEventArgs args)
 {
     if (TryComp(args.SenderSession.AttachedEntity, out SharedHandsComponent? hands))
     {
         hands.ActivateHeldEntity(msg.HandName);
     }
 }
コード例 #9
0
        private void OnDraggingPlayerChanged(TabletopDraggingPlayerChangedEvent msg, EntitySessionEventArgs args)
        {
            var dragged = msg.DraggedEntityUid;

            if (!EntityManager.TryGetComponent <TabletopDraggableComponent?>(dragged, out var draggableComponent))
            {
                return;
            }

            draggableComponent.DraggingPlayer = msg.IsDragging ? args.SenderSession.UserId : null;

            if (!EntityManager.TryGetComponent <AppearanceComponent?>(dragged, out var appearance))
            {
                return;
            }

            if (draggableComponent.DraggingPlayer != null)
            {
                appearance.SetData(TabletopItemVisuals.Scale, new Vector2(1.25f, 1.25f));
                appearance.SetData(TabletopItemVisuals.DrawDepth, (int)DrawDepth.Items + 1);
            }
            else
            {
                appearance.SetData(TabletopItemVisuals.Scale, Vector2.One);
                appearance.SetData(TabletopItemVisuals.DrawDepth, (int)DrawDepth.Items);
            }
        }
コード例 #10
0
 private void HandleInteractUsingInHand(ClientInteractUsingInHandMsg msg, EntitySessionEventArgs args)
 {
     if (TryComp(args.SenderSession.AttachedEntity, out SharedHandsComponent? hands))
     {
         hands.InteractHandWithActiveHand(msg.HandName);
     }
 }
コード例 #11
0
 private void HandleUseInHand(UseInHandMsg msg, EntitySessionEventArgs args)
 {
     if (TryComp(args.SenderSession.AttachedEntity, out SharedHandsComponent? hands))
     {
         hands.ActivateItem();
     }
 }
コード例 #12
0
 private void HandleMoveItemFromHand(MoveItemFromHandMsg msg, EntitySessionEventArgs args)
 {
     if (TryComp(args.SenderSession.AttachedEntity, out SharedHandsComponent? hands))
     {
         hands.TryMoveHeldEntityToActiveHand(msg.HandName);
     }
 }
コード例 #13
0
    private void OnFirePos(FirePosEvent msg, EntitySessionEventArgs args)
    {
        if (args.SenderSession.AttachedEntity is not {
            Valid : true
        } user)
        {
            return;
        }

        if (!msg.Coordinates.IsValid(EntityManager))
        {
            return;
        }

        if (!TryComp(user, out HandsComponent? handsComponent))
        {
            return;
        }

        // TODO: Not exactly robust
        var gun = handsComponent.GetActiveHand()?.HeldEntity;

        if (gun == null || !TryComp(gun, out ServerRangedWeaponComponent? weapon))
        {
            return;
        }

        // map pos
        TryFire(user, msg.Coordinates, weapon);
    }
コード例 #14
0
 private void HandleInteractUsingInHand(RequestHandInteractUsingEvent msg, EntitySessionEventArgs args)
 {
     if (args.SenderSession.AttachedEntity != null)
     {
         TryInteractHandWithActiveHand(args.SenderSession.AttachedEntity.Value, msg.HandName);
     }
 }
コード例 #15
0
        protected override void OnBwoinkTextMessage(BwoinkTextMessage message, EntitySessionEventArgs eventArgs)
        {
            base.OnBwoinkTextMessage(message, eventArgs);
            var senderSession = (IPlayerSession)eventArgs.SenderSession;

            // TODO: Sanitize text?
            // Confirm that this person is actually allowed to send a message here.
            var senderPersonalChannel = senderSession.UserId == message.ChannelId;
            var senderAdmin           = _adminManager.GetAdminData(senderSession);
            var authorized            = senderPersonalChannel || senderAdmin != null;

            if (!authorized)
            {
                // Unauthorized bwoink (log?)
                return;
            }

            var escapedText = Basic.EscapeText(message.Text);


            var bwoinkText = senderAdmin switch
            {
                var x when x is not null && x.Flags == AdminFlags.Adminhelp =>
                $"[color=purple]{senderSession.Name}[/color]: {escapedText}",
                var x when x is not null && x.HasFlag(AdminFlags.Adminhelp) =>
                $"[color=red]{senderSession.Name}[/color]: {escapedText}",
                _ => $"{senderSession.Name}: {escapedText}",
            };

            var msg = new BwoinkTextMessage(message.ChannelId, senderSession.UserId, bwoinkText);

            LogBwoink(msg);

            // Admins
            var targets = _adminManager.ActiveAdmins.Select(p => p.ConnectedClient).ToList();

            // And involved player
            if (_playerManager.TryGetSessionById(message.ChannelId, out var session))
            {
                if (!targets.Contains(session.ConnectedClient))
                {
                    targets.Add(session.ConnectedClient);
                }
            }

            foreach (var channel in targets)
            {
                RaiseNetworkEvent(msg, channel);
            }

            if (targets.Count == 1)
            {
                var systemText = senderPersonalChannel ?
                                 Loc.GetString("bwoink-system-starmute-message-no-other-users-primary") :
                                 Loc.GetString("bwoink-system-starmute-message-no-other-users-secondary");
                var starMuteMsg = new BwoinkTextMessage(message.ChannelId, SystemUserId, systemText);
                RaiseNetworkEvent(starMuteMsg, senderSession.ConnectedClient);
            }
        }
コード例 #16
0
        private void RequestVerbs(RequestVerbsMessage req, EntitySessionEventArgs eventArgs)
        {
            var player = (IPlayerSession)eventArgs.SenderSession;

            if (!_entityManager.TryGetEntity(req.EntityUid, out var entity))
            {
                Logger.Warning($"{nameof(RequestVerbs)} called on a nonexistant entity with id {req.EntityUid} by player {player}.");
                return;
            }

            var userEntity = player.AttachedEntity;

            if (userEntity == null)
            {
                Logger.Warning($"{nameof(UseVerb)} called by player {player} with no attached entity.");
                return;
            }

            var data = new List <VerbsResponseMessage.NetVerbData>();

            //Get verbs, component dependent.
            foreach (var(component, verb) in VerbUtility.GetVerbs(entity))
            {
                if (!VerbUtility.VerbAccessChecks(userEntity, entity, verb))
                {
                    continue;
                }

                var verbData = verb.GetData(userEntity, component);
                if (verbData.IsInvisible)
                {
                    continue;
                }

                // TODO: These keys being giant strings is inefficient as hell.
                data.Add(new VerbsResponseMessage.NetVerbData(verbData, $"{component.GetType()}:{verb.GetType()}"));
            }

            //Get global verbs. Visible for all entities regardless of their components.
            foreach (var globalVerb in VerbUtility.GetGlobalVerbs(Assembly.GetExecutingAssembly()))
            {
                if (!VerbUtility.VerbAccessChecks(userEntity, entity, globalVerb))
                {
                    continue;
                }

                var verbData = globalVerb.GetData(userEntity, entity);
                if (verbData.IsInvisible)
                {
                    continue;
                }

                data.Add(new VerbsResponseMessage.NetVerbData(verbData, globalVerb.GetType().ToString()));
            }

            var response = new VerbsResponseMessage(data.ToArray(), req.EntityUid);

            RaiseNetworkEvent(response, player.ConnectedClient);
        }
コード例 #17
0
    private void HandleSetHand(RequestSetHandEvent msg, EntitySessionEventArgs eventArgs)
    {
        if (eventArgs.SenderSession.AttachedEntity == null)
        {
            return;
        }

        TrySetActiveHand(eventArgs.SenderSession.AttachedEntity.Value, msg.HandName);
    }
コード例 #18
0
        private void HandleInteractUsingInHand(ClientInteractUsingInHandMsg msg, EntitySessionEventArgs args)
        {
            if (!TryGetHandsComp(args.SenderSession, out var hands))
            {
                return;
            }

            hands.InteractHandWithActiveHand(msg.HandName);
        }
コード例 #19
0
        private void HandleActivateInHand(ActivateInHandMsg msg, EntitySessionEventArgs args)
        {
            if (!TryGetHandsComp(args.SenderSession, out var hands))
            {
                return;
            }

            hands.ActivateHeldEntity(msg.HandName);
        }
コード例 #20
0
        private void HandleUseInHand(UseInHandMsg msg, EntitySessionEventArgs args)
        {
            if (!TryGetHandsComp(args.SenderSession, out var hands))
            {
                return;
            }

            hands.ActivateItem();
        }
コード例 #21
0
        private void HandleMoveItemFromHand(MoveItemFromHandMsg msg, EntitySessionEventArgs args)
        {
            if (!TryGetHandsComp(args.SenderSession, out var hands))
            {
                return;
            }

            hands.TryMoveHeldEntityToActiveHand(msg.HandName);
        }
コード例 #22
0
        private void HandleDrop(RequestDropHeldEntityEvent msg, EntitySessionEventArgs eventArgs)
        {
            var entity = eventArgs.SenderSession?.AttachedEntity;

            if (entity == null || !entity.TryGetComponent(out SharedHandsComponent? hands))
                return;

            hands.TryDropHand(msg.HandName, msg.DropTarget);
        }
コード例 #23
0
        private void HandleSetHand(RequestSetHandEvent msg, EntitySessionEventArgs eventArgs)
        {
            var entity = eventArgs.SenderSession?.AttachedEntity;

            if (entity == null || !entity.TryGetComponent(out SharedHandsComponent? hands))
                return;

            hands.ActiveHand = msg.HandName;
        }
コード例 #24
0
    /// <summary>
    ///     Will attempt to equip or unequip an item to/from the clicked slot. If the user clicked on an occupied slot
    ///     with some entity, will instead attempt to interact with this entity.
    /// </summary>
    private void OnUseSlot(UseSlotNetworkMessage ev, EntitySessionEventArgs eventArgs)
    {
        if (eventArgs.SenderSession.AttachedEntity is not EntityUid {
            Valid : true
        } actor)
        {
            return;
        }

        if (!TryComp(actor, out InventoryComponent? inventory) || !TryComp <SharedHandsComponent>(actor, out var hands))
        {
            return;
        }

        hands.TryGetActiveHeldEntity(out var held);
        TryGetSlotEntity(actor, ev.Slot, out var itemUid, inventory);

        // attempt to perform some interaction
        if (held != null && itemUid != null)
        {
            _interactionSystem.InteractUsing(actor, held.Value, itemUid.Value,
                                             new EntityCoordinates(), predicted: true);
            return;
        }

        // un-equip to hands
        if (itemUid != null)
        {
            if (hands.CanPickupEntityToActiveHand(itemUid.Value) && TryUnequip(actor, ev.Slot, inventory: inventory))
            {
                hands.PutInHand(itemUid.Value, false);
            }
            return;
        }

        // finally, just try to equip the held item.
        if (held == null)
        {
            return;
        }

        // before we drop the item, check that it can be equipped in the first place.
        if (!CanEquip(actor, held.Value, ev.Slot, out var reason))
        {
            if (_gameTiming.IsFirstTimePredicted)
            {
                _popup.PopupCursor(Loc.GetString(reason), Filter.Local());
            }
            return;
        }

        if (hands.TryDropNoInteraction())
        {
            TryEquip(actor, actor, held.Value, ev.Slot, predicted: true, inventory: inventory);
        }
    }
コード例 #25
0
        private void HandleDragDropRequestEvent(DragDropRequestEvent msg, EntitySessionEventArgs args)
        {
            if (!ValidateClientInput(args.SenderSession, msg.DropLocation, msg.Target, out var userEntity))
            {
                Logger.InfoS("system.interaction", $"DragDropRequestEvent input validation failed");
                return;
            }

            if (!_actionBlockerSystem.CanInteract(userEntity.Value))
            {
                return;
            }

            if (!EntityManager.EntityExists(msg.Dropped))
            {
                return;
            }
            if (!EntityManager.EntityExists(msg.Target))
            {
                return;
            }

            var interactionArgs = new DragDropEvent(userEntity.Value, msg.DropLocation, msg.Dropped, msg.Target);

            // must be in range of both the target and the object they are drag / dropping
            // Client also does this check but ya know we gotta validate it.
            if (!interactionArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true))
            {
                return;
            }

            // trigger dragdrops on the dropped entity
            RaiseLocalEvent(msg.Dropped, interactionArgs);
            foreach (var dragDrop in EntityManager.GetComponents <IDraggable>(msg.Dropped))
            {
                if (dragDrop.CanDrop(interactionArgs) &&
                    dragDrop.Drop(interactionArgs))
                {
                    return;
                }
            }

            // trigger dragdropons on the targeted entity
            RaiseLocalEvent(msg.Target, interactionArgs, false);
            foreach (var dragDropOn in EntityManager.GetComponents <IDragDropOn>(msg.Target))
            {
                if (dragDropOn.CanDragDropOn(interactionArgs) &&
                    dragDropOn.DragDropOn(interactionArgs))
                {
                    return;
                }
            }
        }
コード例 #26
0
        public static void RaisePredictiveEvent <T>(this IEntityManager entityManager, T msg)
            where T : EntitySystemMessage
        {
            var sequence = IoCManager.Resolve <IClientGameStateManager>().SystemMessageDispatched(msg);

            entityManager.EntityNetManager.SendSystemNetworkMessage(msg, sequence);

            var eventArgs = new EntitySessionEventArgs(IoCManager.Resolve <IPlayerManager>().LocalPlayer.Session);

            entityManager.EventBus.RaiseEvent(EventSource.Local, msg);
            entityManager.EventBus.RaiseEvent(EventSource.Local, new EntitySessionMessage <T>(eventArgs, msg));
        }
コード例 #27
0
        /// <summary>
        ///     Move an entity which is dragged by the user, but check if they are allowed to do so and to these coordinates
        /// </summary>
        private void OnTabletopMove(TabletopMoveEvent msg, EntitySessionEventArgs args)
        {
            if (args.SenderSession as IPlayerSession is not {
                AttachedEntity : { } playerEntity
            } playerSession)
            {
                return;
            }

            if (!EntityManager.TryGetComponent(msg.TableUid, out TabletopGameComponent? tabletop) || tabletop.Session is not {
            } session)
            {
                return;
            }

            // Check if player is actually playing at this table
            if (!session.Players.ContainsKey(playerSession))
            {
                return;
            }

            // Return if can not see table or stunned/no hands
            if (!EntityManager.TryGetEntity(msg.TableUid, out var table))
            {
                return;
            }

            if (!CanSeeTable(playerEntity, table) || StunnedOrNoHands(playerEntity))
            {
                return;
            }

            // Check if moved entity exists and has tabletop draggable component
            if (!EntityManager.TryGetEntity(msg.MovedEntityUid, out var movedEntity))
            {
                return;
            }

            if (!EntityManager.HasComponent <TabletopDraggableComponent>(movedEntity.Uid))
            {
                return;
            }

            // TODO: some permission system, disallow movement if you're not permitted to move the item

            // Move the entity and dirty it (we use the map ID from the entity so noone can try to be funny and move the item to another map)
            var transform         = EntityManager.GetComponent <ITransformComponent>(movedEntity.Uid);
            var entityCoordinates = new EntityCoordinates(_mapManager.GetMapEntityId(transform.MapID), msg.Coordinates.Position);

            transform.Coordinates = entityCoordinates;
            movedEntity.Dirty();
        }
コード例 #28
0
        private void RequestVerbs(RequestVerbsMessage req, EntitySessionEventArgs eventArgs)
        {
            var player = (IPlayerSession)eventArgs.SenderSession;

            if (!_entityManager.TryGetEntity(req.EntityUid, out var entity))
            {
                return;
            }

            var userEntity = player.AttachedEntity;

            var data = new List <VerbsResponseMessage.NetVerbData>();

            //Get verbs, component dependent.
            foreach (var(component, verb) in VerbUtility.GetVerbs(entity))
            {
                if (verb.RequireInteractionRange && !VerbUtility.InVerbUseRange(userEntity, entity))
                {
                    continue;
                }

                var verbData = verb.GetData(userEntity, component);
                if (verbData.IsInvisible)
                {
                    continue;
                }

                // TODO: These keys being giant strings is inefficient as hell.
                data.Add(new VerbsResponseMessage.NetVerbData(verbData, $"{component.GetType()}:{verb.GetType()}"));
            }

            //Get global verbs. Visible for all entities regardless of their components.
            foreach (var globalVerb in VerbUtility.GetGlobalVerbs(Assembly.GetExecutingAssembly()))
            {
                if (globalVerb.RequireInteractionRange && !VerbUtility.InVerbUseRange(userEntity, entity))
                {
                    continue;
                }

                var verbData = globalVerb.GetData(userEntity, entity);
                if (verbData.IsInvisible)
                {
                    continue;
                }

                data.Add(new VerbsResponseMessage.NetVerbData(verbData, globalVerb.GetType().ToString()));
            }

            var response = new VerbsResponseMessage(data.ToArray(), req.EntityUid);

            RaiseNetworkEvent(response, player.ConnectedClient);
        }
コード例 #29
0
        private void UseVerb(UseVerbMessage use, EntitySessionEventArgs eventArgs)
        {
            if (!_entityManager.TryGetEntity(use.EntityUid, out var entity))
            {
                return;
            }

            var session    = eventArgs.SenderSession;
            var userEntity = session.AttachedEntity;

            foreach (var(component, verb) in VerbUtility.GetVerbs(entity))
            {
                if ($"{component.GetType()}:{verb.GetType()}" != use.VerbKey)
                {
                    continue;
                }

                if (verb.RequireInteractionRange)
                {
                    var distanceSquared = (userEntity.Transform.WorldPosition - entity.Transform.WorldPosition)
                                          .LengthSquared;
                    if (distanceSquared > VerbUtility.InteractionRangeSquared)
                    {
                        break;
                    }
                }

                verb.Activate(userEntity, component);
                break;
            }

            foreach (var globalVerb in VerbUtility.GetGlobalVerbs(Assembly.GetExecutingAssembly()))
            {
                if (globalVerb.GetType().ToString() != use.VerbKey)
                {
                    continue;
                }

                if (globalVerb.RequireInteractionRange)
                {
                    var distanceSquared = (userEntity.Transform.WorldPosition - entity.Transform.WorldPosition)
                                          .LengthSquared;
                    if (distanceSquared > VerbUtility.InteractionRangeSquared)
                    {
                        break;
                    }
                }

                globalVerb.Activate(userEntity, entity);
                break;
            }
        }
コード例 #30
0
    private void OnRequestCharacterInfoEvent(RequestCharacterInfoEvent msg, EntitySessionEventArgs args)
    {
        if (!args.SenderSession.AttachedEntity.HasValue ||
            args.SenderSession.AttachedEntity != msg.EntityUid)
        {
            return;
        }

        var entity = args.SenderSession.AttachedEntity.Value;

        var conditions = new Dictionary <string, List <ConditionInfo> >();
        var jobTitle   = "No Profession";
        var briefing   = "!!ERROR: No Briefing!!"; //should never show on the UI unless there's a bug

        if (EntityManager.TryGetComponent(entity, out MindComponent? mindComponent) && mindComponent.Mind != null)
        {
            var mind = mindComponent.Mind;

            // Get objectives
            foreach (var objective in mind.AllObjectives)
            {
                if (!conditions.ContainsKey(objective.Prototype.Issuer))
                {
                    conditions[objective.Prototype.Issuer] = new List <ConditionInfo>();
                }
                foreach (var condition in objective.Conditions)
                {
                    conditions[objective.Prototype.Issuer].Add(new ConditionInfo(condition.Title,
                                                                                 condition.Description, condition.Icon, condition.Progress));
                }
            }

            // Get job title
            foreach (var role in mind.AllRoles)
            {
                if (role.GetType() != typeof(Job))
                {
                    continue;
                }

                jobTitle = role.Name;
                break;
            }

            // Get briefing
            briefing = mind.Briefing;
        }

        RaiseNetworkEvent(new CharacterInfoEvent(entity, jobTitle, conditions, briefing),
                          Filter.SinglePlayer(args.SenderSession));
    }