protected virtual void FillInteractionQueue()
        {
            using var objectsInCharacterInteractionArea = InteractionCheckerSystem
                                                          .SharedGetTempObjectsInCharacterInteractionArea(this.CurrentCharacter);
            if (objectsInCharacterInteractionArea == null)
            {
                return;
            }
            var objectOfInterest = objectsInCharacterInteractionArea.AsList()
                                   .Where(t => this.EnabledEntityList.Contains(t.PhysicsBody?.AssociatedWorldObject?.ProtoGameObject))
                                   .ToList();

            if (!(objectOfInterest.Count > 0))
            {
                return;
            }
            foreach (var obj in objectOfInterest)
            {
                var testObject = obj.PhysicsBody.AssociatedWorldObject as IStaticWorldObject;
                if (this.TestObject(testObject))
                {
                    if (!this.interactionQueue.Contains(testObject))
                    {
                        this.interactionQueue.Add(testObject);
                    }
                }
            }
        }
예제 #2
0
        public static IStaticWorldObject SharedFindNearbyStationOfTypes(
            IReadOnlyStationsList stationTypes,
            ICharacter character)
        {
            using var objectsInCharacterInteractionArea =
                      InteractionCheckerSystem.SharedGetTempObjectsInCharacterInteractionArea(character);
            if (objectsInCharacterInteractionArea is null)
            {
                return(null);
            }

            foreach (var testResult in objectsInCharacterInteractionArea.AsList())
            {
                var worldObject = testResult.PhysicsBody.AssociatedWorldObject as IStaticWorldObject;
                if (worldObject is null ||
                    !stationTypes.Contains(worldObject.ProtoWorldObject))
                {
                    continue;
                }

                if (!worldObject.ProtoWorldObject.SharedCanInteract(character, worldObject, writeToLog: false))
                {
                    continue;
                }

                // found station with which player can interact
                return(worldObject);
            }

            return(null);
        }
예제 #3
0
        /// <summary>
        /// Check if the character's interaction area collides with the world object click area.
        /// The character also should not be dead.
        /// </summary>
        public virtual bool SharedIsInsideCharacterInteractionArea(
            ICharacter character,
            TWorldObject worldObject,
            bool writeToLog,
            CollisionGroup requiredCollisionGroup = null)
        {
            if (worldObject.IsDestroyed)
            {
                return(false);
            }

            try
            {
                this.VerifyGameObject(worldObject);
            }
            catch (Exception ex)
            {
                if (writeToLog)
                {
                    Logger.Exception(ex);
                }
                else
                {
                    Logger.Warning(ex.Message + " during " + nameof(SharedIsInsideCharacterInteractionArea));
                }

                return(false);
            }

            if (character.GetPublicState <ICharacterPublicState>().IsDead ||
                IsServer && !character.ServerIsOnline)
            {
                if (writeToLog)
                {
                    Logger.Warning(
                        $"Character cannot interact with {worldObject} - character is dead or offline.",
                        character);
                }

                return(false);
            }

            bool isInsideInteractionArea;

            if (worldObject.PhysicsBody.HasShapes &&
                worldObject.PhysicsBody.HasAnyShapeCollidingWithGroup(CollisionGroups.ClickArea))
            {
                // check that the world object is inside the interaction area of the character
                using var objectsInCharacterInteractionArea
                          = InteractionCheckerSystem.SharedGetTempObjectsInCharacterInteractionArea(
                                character,
                                writeToLog,
                                requiredCollisionGroup);

                isInsideInteractionArea = false;
                if (objectsInCharacterInteractionArea is not null)
                {
                    foreach (var t in objectsInCharacterInteractionArea.AsList())
                    {
                        if (!ReferenceEquals(worldObject, t.PhysicsBody.AssociatedWorldObject))
                        {
                            continue;
                        }

                        isInsideInteractionArea = true;
                        break;
                    }
                }
            }
            else if (worldObject.ProtoWorldObject is IProtoStaticWorldObject protoStaticWorldObject)
            {
                // the world object doesn't have click area collision shapes
                // check this object tile by tile
                // ensure at least one tile of this object is inside the character interaction area
                // ensure there is direct line of sight between player character and this tile

                var characterInteractionAreaShape = character.PhysicsBody.Shapes.FirstOrDefault(
                    s => s.CollisionGroup
                    == CollisionGroups.CharacterInteractionArea);
                isInsideInteractionArea = false;
                foreach (var tileOffset in protoStaticWorldObject.Layout.TileOffsets)
                {
                    var penetration = character.PhysicsBody.PhysicsSpace.TestShapeCollidesWithShape(
                        sourceShape: characterInteractionAreaShape,
                        targetShape: new RectangleShape(
                            position: (worldObject.TilePosition + tileOffset).ToVector2D()
                            + (0.01, 0.01),
                            size: (0.98, 0.98),
                            collisionGroup: CollisionGroups.ClickArea),
                        sourceShapeOffset: character.PhysicsBody.Position);

                    if (!penetration.HasValue)
                    {
                        // this tile is not inside the character interaction area
                        continue;
                    }

                    // the tile is inside the character interaction area
                    // check that there is a direct line between the character and the tile
                    isInsideInteractionArea = true;
                    break;
                }
            }
            else
            {
                isInsideInteractionArea = false;
            }

            if (!isInsideInteractionArea)
            {
                // the world object is outside the character interaction area
                if (writeToLog)
                {
                    Logger.Warning(
                        $"Character cannot interact with {worldObject} - outside the interaction area.",
                        character);

                    if (IsClient)
                    {
                        ClientOnCannotInteract(worldObject, CoreStrings.Notification_TooFar, isOutOfRange: true);
                    }
                }

                return(false);
            }

            if (character.ProtoCharacter is PlayerCharacterSpectator)
            {
                // don't test for obstacles for spectator character
                return(true);
            }

            // check that there are no other objects on the way between them (defined by default layer)
            var physicsSpace    = character.PhysicsBody.PhysicsSpace;
            var characterCenter = character.Position + character.PhysicsBody.CenterOffset;

            if (!ObstacleTestHelper.SharedHasObstaclesOnTheWay(characterCenter,
                                                               physicsSpace,
                                                               worldObject,
                                                               sendDebugEvents: writeToLog))
            {
                return(true);
            }

            if (writeToLog)
            {
                Logger.Warning(
                    $"Character cannot interact with {worldObject} - there are other objects on the way.",
                    character);

                if (IsClient)
                {
                    ClientOnCannotInteract(worldObject,
                                           CoreStrings.Notification_ObstaclesOnTheWay,
                                           isOutOfRange: true);
                }
            }

            return(false);
        }
예제 #4
0
        /// <summary>
        /// Check if the character's interaction area collides with the world object click area.
        /// The character also should not be dead.
        /// </summary>
        public bool SharedIsInsideCharacterInteractionArea(
            ICharacter character,
            TWorldObject worldObject,
            bool writeToLog,
            CollisionGroup requiredCollisionGroup = null)
        {
            if (worldObject.IsDestroyed)
            {
                return(false);
            }

            try
            {
                this.VerifyGameObject(worldObject);
            }
            catch (Exception ex)
            {
                if (writeToLog)
                {
                    Logger.Exception(ex);
                }
                else
                {
                    Logger.Warning(ex.Message + " during " + nameof(SharedIsInsideCharacterInteractionArea));
                }

                return(false);
            }

            bool isInsideInteractionArea;

            if (worldObject.PhysicsBody.HasShapes)
            {
                // check that the world object is inside the interaction area of the character
                using (var objectsInCharacterInteractionArea
                           = InteractionCheckerSystem.SharedGetTempObjectsInCharacterInteractionArea(
                                 character,
                                 writeToLog,
                                 requiredCollisionGroup))
                {
                    isInsideInteractionArea =
                        objectsInCharacterInteractionArea?.Any(t => t.PhysicsBody.AssociatedWorldObject == worldObject)
                        ?? false;
                }
            }
            else
            {
                // the world object doesn't have physics shapes
                // check this object tile by tile
                // ensure at least one tile of this object is inside the character interaction area
                // ensure there is direct line of sight between player character and this tile

                var characterInteractionAreaShape = character.PhysicsBody.Shapes.FirstOrDefault(
                    s => s.CollisionGroup
                    == CollisionGroups.CharacterInteractionArea);
                isInsideInteractionArea = false;
                foreach (var tileOffset in ((IProtoStaticWorldObject)worldObject.ProtoWorldObject).Layout.TileOffsets)
                {
                    var penetration = character.PhysicsBody.PhysicsSpace.TestShapeCollidesWithShape(
                        sourceShape: characterInteractionAreaShape,
                        targetShape: new RectangleShape(
                            position: (worldObject.TilePosition + tileOffset).ToVector2D()
                            + (0.01, 0.01),
                            size: (0.98, 0.98),
                            collisionGroup: CollisionGroups.ClickArea),
                        sourceShapeOffset: character.PhysicsBody.Position);

                    if (!penetration.HasValue)
                    {
                        // this tile is not inside the character interaction area
                        continue;
                    }

                    // the tile is inside the character interaction area
                    // check that there is a direct line between the character and the tile
                    isInsideInteractionArea = true;
                    break;
                }
            }

            if (!isInsideInteractionArea)
            {
                // the world object is outside the character interaction area
                if (writeToLog)
                {
                    Logger.Warning(
                        $"Character cannot interact with {worldObject} - outside the interaction area.",
                        character);

                    if (IsClient)
                    {
                        this.ClientOnCannotInteract(worldObject, CoreStrings.Notification_TooFar, isOutOfRange: true);
                    }
                }

                return(false);
            }

            // check that there are no other objects on the way between them (defined by default layer)
            var physicsSpace      = character.PhysicsBody.PhysicsSpace;
            var characterCenter   = character.Position + character.PhysicsBody.CenterOffset;
            var worldObjectCenter = worldObject.TilePosition.ToVector2D() + worldObject.PhysicsBody.CenterOffset;
            var worldObjectPointClosestToCharacter = worldObject.PhysicsBody.ClampPointInside(
                characterCenter,
                CollisionGroups.Default,
                out var isSuccess);

            if (!isSuccess)
            {
                // the physics body seems to not have the default collider, let's check for the click area instead
                worldObjectPointClosestToCharacter = worldObject.PhysicsBody.ClampPointInside(
                    characterCenter,
                    CollisionGroups.ClickArea,
                    out _);
            }

            // local method for testing if there is an obstacle from current to the specified position
            bool TestHasObstacle(Vector2D toPosition)
            {
                using (var obstaclesOnTheWay = physicsSpace.TestLine(
                           characterCenter,
                           toPosition,
                           CollisionGroup.GetDefault(),
                           sendDebugEvent: writeToLog))
                {
                    foreach (var test in obstaclesOnTheWay)
                    {
                        var testPhysicsBody = test.PhysicsBody;
                        if (testPhysicsBody.AssociatedProtoTile != null)
                        {
                            // obstacle tile on the way
                            return(true);
                        }

                        var testWorldObject = testPhysicsBody.AssociatedWorldObject;
                        if (testWorldObject == character ||
                            testWorldObject == worldObject)
                        {
                            // not an obstacle - it's the character or world object itself
                            continue;
                        }

                        if (!this.CommonIsAllowedObjectToInteractThrought(testWorldObject))
                        {
                            // obstacle object on the way
                            return(true);
                        }
                    }

                    // no obstacles
                    return(false);
                }
            }

            if (character.ProtoCharacter is PlayerCharacterSpectator)
            {
                // don't test for obstacles for spectator character
                return(true);
            }

            // let's test by casting rays from character center to:
            // 0) world object center
            // 1) world object point closest to the character
            // 2) combined - take X from center, take Y from closest
            // 3) combined - take X from closest, take Y from center
            if (TestHasObstacle(worldObjectCenter) &&
                TestHasObstacle(worldObjectPointClosestToCharacter) &&
                TestHasObstacle((worldObjectCenter.X,
                                 worldObjectPointClosestToCharacter.Y)) &&
                TestHasObstacle((worldObjectPointClosestToCharacter.X, worldObjectCenter.Y)))
            {
                // has obstacle
                if (writeToLog)
                {
                    Logger.Warning(
                        $"Character cannot interact with {worldObject} - there are other objects on the way.",
                        character);

                    if (IsClient)
                    {
                        this.ClientOnCannotInteract(worldObject,
                                                    CoreStrings.Notification_ObstaclesOnTheWay,
                                                    isOutOfRange: true);
                    }
                }

                return(false);
            }

            if (character.GetPublicState <ICharacterPublicState>().IsDead)
            {
                // character is dead
                if (writeToLog)
                {
                    Logger.Warning(
                        $"Character cannot interact with {worldObject} - character is dead.",
                        character);
                }

                return(false);
            }

            return(true);
        }