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); } } } }
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); }
/// <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); }
/// <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); }