private static void Initialize() { if (isInitialized) { throw new Exception("Already initialized"); } isInitialized = true; defaultGroup = CollisionGroup.GetDefault(); defaultGroup.SetCollidesWith(defaultGroup); hitboxMelee = new CollisionGroup("Hitbox Melee", isSensor: true); //hitboxMelee.SetCollidesWith(defaultGroup); hitboxMelee.SetCollidesWith(HitboxMelee); hitboxRanged = new CollisionGroup("Hitbox Ranged", isSensor: true); //hitboxRanged.SetCollidesWith(defaultGroup); hitboxRanged.SetCollidesWith(HitboxRanged); characterInteractionArea = new CollisionGroup("Interaction Area", isSensor: true); clickArea = new CollisionGroup("Click Area", isSensor: true); clickArea.SetCollidesWith(clickArea); clickArea.SetCollidesWith(characterInteractionArea); }
private static bool AnyObstaclesBetween(ICharacter npc, ICharacter player) { var physicsSpace = npc.PhysicsBody.PhysicsSpace; var npcCharacterCenter = npc.Position + npc.PhysicsBody.CenterOffset; var playerCharacterCenter = player.Position + player.PhysicsBody.CenterOffset; using var obstaclesOnTheWay = physicsSpace.TestLine( npcCharacterCenter, playerCharacterCenter, CollisionGroup.GetDefault(), sendDebugEvent: false); foreach (var test in obstaclesOnTheWay.AsList()) { var testPhysicsBody = test.PhysicsBody; if (testPhysicsBody.AssociatedProtoTile is null) { continue; } var tile = ServerWorldService.GetTile(testPhysicsBody.Position.ToVector2Ushort()); if (!tile.IsSlope) { // cliff tile on the way return(true); } } return(false); }
private static bool AnyObstaclesBetween(ICharacter npc, ICharacter player) { var physicsSpace = npc.PhysicsBody.PhysicsSpace; var npcCharacterCenter = npc.Position + npc.PhysicsBody.CenterOffset; var playerCharacterCenter = player.Position + player.PhysicsBody.CenterOffset; using var obstaclesOnTheWay = physicsSpace.TestLine( npcCharacterCenter, playerCharacterCenter, CollisionGroup.GetDefault(), sendDebugEvent: false); 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 == npc || testWorldObject == player) { // not an obstacle - it's one of the characters continue; } // obstacle object on the way return(true); } return(false); }
private static bool SharedHasObstacle( ICharacter character, IStaticWorldObject forStructureRelocation, Vector2D position) { if (PveSystem.SharedIsPve(false)) { return(false); } var characterCenter = character.Position + character.PhysicsBody.CenterOffset; return(TestHasObstacle(position)); // local method for testing if there is an obstacle from current to the specified position bool TestHasObstacle(Vector2D toPosition) { using var obstaclesOnTheWay = character.PhysicsBody.PhysicsSpace.TestLine( characterCenter, toPosition, CollisionGroup.GetDefault(), sendDebugEvent: false); foreach (var test in obstaclesOnTheWay.AsList()) { var testPhysicsBody = test.PhysicsBody; if (testPhysicsBody.AssociatedProtoTile is not null) { // obstacle tile on the way return(true); } var testWorldObject = testPhysicsBody.AssociatedWorldObject; if (ReferenceEquals(testWorldObject, character) || ReferenceEquals(testWorldObject, forStructureRelocation)) { // not an obstacle - it's the character or world object itself continue; } switch (testWorldObject.ProtoWorldObject) { case IProtoObjectDeposit _: // allow deposits case ObjectWallDestroyed _: // allow destroyed walls continue; } // obstacle object on the way return(true); } // no obstacles return(false); } }
private static Vector2D FindClosestPosition(Vector2D position) { var physicsSpace = ServerWorldService.GetPhysicsSpace(); var collisionGroup = CollisionGroup.GetDefault(); if (IsValidPosition( // round position position.ToVector2Ushort().ToVector2D())) { // can teleport right there return(position); } // check nearby tiles var tile = ServerWorldService.GetTile(position.ToVector2Ushort()); foreach (var neighborTile in tile.EightNeighborTiles) { if (!neighborTile.IsValidTile) { continue; } var neighborTilePosition = neighborTile.Position.ToVector2D(); if (IsValidPosition(neighborTilePosition)) { return(neighborTilePosition); } } throw new Exception("No empty position available nearby."); // Local function for checking if the position is valid. bool IsValidPosition(Vector2D pos) { using var objectsNearby = physicsSpace.TestRectangle( // include some padding, otherwise the check will include border-objects position: pos + (0.01, 0.01), size: (0.98, 0.98), collisionGroup: collisionGroup, sendDebugEvent: false); if (objectsNearby.Count > 0) { return(false); } var posTile = Server.World.GetTile(pos.ToVector2Ushort()); return(ServerCharacterSpawnHelper.IsValidSpawnTile(posTile, checkNeighborTiles: false)); } }
private static void ServerTeleport(ICharacter player, Vector2D toPosition) { if (player.PhysicsBody.HasAnyShapeCollidingWithGroup(CollisionGroup.GetDefault())) { // perform position validity check toPosition = FindClosestValidPosition(toPosition); } else { // This is a character without the physical collider (probably a spectator). // It could be teleported anywhere. } ServerWorldService.SetPosition(player, toPosition); }
/// <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); }
public void SharedValidatePlacement( ICharacter character, Vector2Ushort targetPosition, bool logErrors, out bool canPlace, out bool isTooFar) { if (NewbieProtectionSystem.SharedIsNewbie(character)) { if (logErrors) { Logger.Warning("Newbie cannot plant bombs"); NewbieProtectionSystem.SharedNotifyNewbieCannotPerformAction(character, this); } canPlace = false; isTooFar = false; return; } // check if there is a direct line of sight // 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 = targetPosition.ToVector2D() + (0.5, 0.5); // 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: false); foreach (var test in obstaclesOnTheWay.AsList()) { var testPhysicsBody = test.PhysicsBody; if (testPhysicsBody.AssociatedProtoTile != null) { // obstacle tile on the way return(true); } var testWorldObject = testPhysicsBody.AssociatedWorldObject; if (testWorldObject == character) { // not an obstacle - it's the character or world object itself continue; } switch (testWorldObject.ProtoWorldObject) { case IProtoObjectDeposit _: // allow deposits case ObjectWallDestroyed _: // allow destroyed walls continue; } // obstacle object on the way return(true); } // no obstacles return(false); } // let's test by casting rays from character center to the center of the planted bomb if (TestHasObstacle(worldObjectCenter)) { // has obstacle if (logErrors) { if (IsClient) { this.ClientShowCannotPlaceObstaclesOnTheWayNotification(); } else { Logger.Warning($"{character} cannot place {this} - obstacles on the way"); this.CallClient(character, _ => _.ClientRemote_CannotPlaceObstacles()); } } canPlace = false; isTooFar = false; return; } if (!this.ObjectExplosiveProto.CheckTileRequirements(targetPosition, character, logErrors)) { // explosive static object placement requirements failed canPlace = false; isTooFar = false; return; } // validate distance to the character if (this.SharedIsTooFarToPlace(character, targetPosition, logErrors)) { canPlace = true; isTooFar = true; return; } canPlace = true; isTooFar = false; }
private static bool SharedHasObstaclesOnTheWay( ICharacter character, Vector2D characterCenter, IPhysicsSpace physicsSpace, [CanBeNull] IWorldObject worldObject, Vector2D worldObjectCenter, Vector2D worldObjectPointClosestToCharacter, bool sendDebugEvents) { // 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 return(true); } return(false); // 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: sendDebugEvents); foreach (var test in obstaclesOnTheWay) { var testPhysicsBody = test.PhysicsBody; if (!(testPhysicsBody.AssociatedProtoTile is null)) { // obstacle tile on the way return(true); } var testWorldObject = testPhysicsBody.AssociatedWorldObject; if (ReferenceEquals(testWorldObject, worldObject)) { // not an obstacle - it's the world object itself continue; } if (testWorldObject is ICharacter) { // characters are not assumed as an obstacle continue; } // no need for this check anymore as we're checking for general "is ICharacter" above //if (ReferenceEquals(testWorldObject, character)) //{ // // not an obstacle - it's the player's character itself // continue; //} if (!testWorldObject.ProtoWorldObject .SharedIsAllowedObjectToInteractThrough(testWorldObject)) { // obstacle object on the way return(true); } } // no obstacles return(false); } }
public bool SharedValidatePlacement(ICharacter character, Vector2Ushort targetPosition, bool logErrors) { // check if there is a direct line of sight // 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 = targetPosition.ToVector2D() + (0.5, 0.5); // 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: false)) { 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) { // not an obstacle - it's the character or world object itself continue; } // obstacle object on the way return(true); } // no obstacles return(false); } } // let's test by casting rays from character center to the center of the planted bomb if (TestHasObstacle(worldObjectCenter)) { // has obstacle if (logErrors) { if (IsClient) { this.ClientShowCannotPlaceObstaclesOnTheWayNotification(); } else { Logger.Warning($"{character} cannot place {this} - obstacles on the way"); this.CallClient(character, _ => _.ClientRemote_CannotPlaceObstacles()); } } return(false); } // validate distance to the character if (targetPosition.TileDistanceTo(character.TilePosition) > this.DeployDistanceMax) { // distance exceeded - too far if (logErrors) { if (IsClient) { this.ClientShowCannotPlaceTooFarNotification(); } else { Logger.Warning($"{character} cannot place {this} - too far"); this.CallClient(character, _ => _.ClientRemote_CannotPlaceTooFar()); } } return(false); } if (!this.ObjectExplosiveProto.CheckTileRequirements(targetPosition, character, logErrors)) { // explosive static object placement requirements failed return(false); } return(true); }