private void AddMarker(Vector2Ushort position) { if (this.markers.ContainsKey(position)) { Api.Logger.Warning("Dropped items already has the map visualizer: " + position); return; } var mapControl = new WorldMapMarkDroppedItems(); var canvasPosition = this.worldMapController.WorldToCanvasPosition(position.ToVector2D()); Canvas.SetLeft(mapControl, canvasPosition.X); Canvas.SetTop(mapControl, canvasPosition.Y); Panel.SetZIndex(mapControl, 12); this.worldMapController.AddControl(mapControl); this.markers[position] = mapControl; }
/// <summary> /// Server spawn callback for mob. /// </summary> /// <param name="trigger">Trigger leading to this spawn.</param> /// <param name="zone">Server zone instance.</param> /// <param name="protoMob">Prototype of character mob object to spawn.</param> /// <param name="tilePosition">Position to try spawn at.</param> protected virtual IGameObjectWithProto ServerSpawnMob( IProtoTrigger trigger, IServerZone zone, IProtoCharacterMob protoMob, Vector2Ushort tilePosition) { var worldPosition = tilePosition.ToVector2D(); if (!ServerCharacterSpawnHelper.IsPositionValidForCharacterSpawn(worldPosition, isPlayer: false)) { // position is not valid for spawning return(null); } return(Server.Characters.SpawnCharacter( protoMob, worldPosition)); }
private void ClientRemote_OnStructurePlaced( IProtoStaticWorldObject protoStaticWorldObject, Vector2Ushort position, bool isByCurrentPlayer) { var soundPreset = protoStaticWorldObject.SharedGetObjectSoundPreset(); if (isByCurrentPlayer) { // play 2D sound soundPreset.PlaySound(ObjectSound.Place, limitOnePerFrame: false); } else { // play 3D sound (at the built object location) soundPreset.PlaySound(ObjectSound.Place, position.ToVector2D() + protoStaticWorldObject.Layout.Center); } }
private static bool ServerCheckCanSpawn(IProtoWorldObject protoObjectToSpawn, Vector2Ushort spawnPosition) { return(protoObjectToSpawn switch { IProtoCharacterMob => ServerCharacterSpawnHelper.IsPositionValidForCharacterSpawn( spawnPosition.ToVector2D(), isPlayer: false) && !LandClaimSystem.SharedIsLandClaimedByAnyone(spawnPosition), IProtoStaticWorldObject protoStaticWorldObject // Please note: land claim check must be integrated in the object tile requirements => protoStaticWorldObject.CheckTileRequirements( spawnPosition, character: null, logErrors: false), _ => throw new ArgumentOutOfRangeException("Unknown object type to spawn: " + protoObjectToSpawn) });
private static void SetupBoundsForLandClaimsInScope( IClientSceneObject sceneObject, Vector2D sceneObjectPosition, Vector2Ushort originTilePosition, RectangleInt originBounds, IProtoObjectLandClaim originProtoObjectLandClaim) { var landClaims = Api.Client.World.GetStaticWorldObjectsOfProto <IProtoObjectLandClaim>(); foreach (var landClaim in landClaims) { var protoObjectLandClaim = (IProtoObjectLandClaim)landClaim.ProtoGameObject; var landClaimCenterPosition = LandClaimSystem .SharedCalculateLandClaimObjectCenterTilePosition( landClaim.TilePosition, protoObjectLandClaim); var landClaimBounds = LandClaimSystem.SharedCalculateLandClaimAreaBounds( landClaimCenterPosition, protoObjectLandClaim.LandClaimWithGraceAreaSize); var intersectionDepth = CalculateIntersectionDepth(originBounds, landClaimBounds); if (intersectionDepth < 0) { // no intersection continue; } intersectionDepth = (intersectionDepth + 1) / 2; intersectionDepth = Math.Min(intersectionDepth, originProtoObjectLandClaim.LandClaimGraceAreaPaddingSizeOneDirection + 1); var exceptBounds = originBounds.Inflate(-intersectionDepth); using var tempList = Api.Shared.WrapObjectInTempList(exceptBounds); AddBoundLabels(sceneObject, sceneObjectPosition, exceptBounds: tempList.AsList(), protoObjectLandClaim, positionOffset: landClaimCenterPosition.ToVector2D() - originTilePosition.ToVector2D()); } }
private void ClientRemote_OnMiningSoundCue( IDynamicWorldObject objectDrone, uint ownerCharacterPartyId, Vector2Ushort fallbackPosition) { if (objectDrone is not null && !objectDrone.IsInitialized) { objectDrone = null; } var position = objectDrone?.Position ?? fallbackPosition.ToVector2D(); position += this.BeamOriginOffset; var isByPartyMember = ownerCharacterPartyId > 0 && ownerCharacterPartyId == PartySystem.ClientCurrentParty?.Id; ClientSoundCueManager.OnSoundEvent(position, isPartyMember: isByPartyMember); }
private static bool SharedIsWithinInteractionDistance(ICharacter character, Vector2Ushort tilePosition) { var interactionAreaShape = character.PhysicsBody.Shapes.FirstOrDefault( s => s.CollisionGroup == CollisionGroups.CharacterInteractionArea); if (interactionAreaShape == null) { // no interaction area shape (probably a spectator character) return(false); } var penetration = character.PhysicsBody.PhysicsSpace.TestShapeCollidesWithShape( sourceShape: interactionAreaShape, targetShape: new CircleShape( center: tilePosition.ToVector2D() + (0.5, 0.5), radius: ClickAreaRadius, collisionGroup: CollisionGroups.ClickArea), sourceShapeOffset: character.PhysicsBody.Position); return(penetration.HasValue); }
protected bool ServerCheckNoEventsNearby( Vector2Ushort position, double areaRadius, List <ILogicObject> allActiveEvents) { var position2D = position.ToVector2D(); foreach (var activeEvent in allActiveEvents) { var publicState = activeEvent.GetPublicState <EventWithAreaPublicState>(); var distance = (publicState.AreaCirclePosition.ToVector2D() - position2D).Length; distance -= publicState.AreaCircleRadius; distance -= areaRadius; if (distance <= 0) { // this event is too close return(false); } } return(true); }
protected bool ServerCheckNoEventsNearby(Vector2Ushort position, double areaRadius) { var position2D = position.ToVector2D(); using var tempEvents = Api.Shared.WrapInTempList( Server.World.GetGameObjectsOfProto <ILogicObject, IProtoEventWithArea>()); foreach (var activeEvent in tempEvents.AsList()) { var publicState = activeEvent.GetPublicState <EventWithAreaPublicState>(); var distance = (publicState.AreaCirclePosition.ToVector2D() - position2D).Length; distance -= publicState.AreaCircleRadius; distance -= areaRadius; if (distance <= 0) { // this event is too close return(false); } } return(true); }
public static void ServerSpawnBossMinionsOnDeath( Vector2Ushort epicenterPosition, double bossDifficultyCoef, IProtoCharacter minionProto, int minionsDefaultCount, double minionsRadius) { var countToSpawnRemains = minionsDefaultCount; // apply difficulty coefficient countToSpawnRemains = (int)Math.Ceiling(countToSpawnRemains * bossDifficultyCoef); var attemptsRemains = 3000; while (countToSpawnRemains > 0) { attemptsRemains--; if (attemptsRemains <= 0) { // attempts exceeded return; } // calculate random distance from the explosion epicenter var distance = RandomHelper.Range(2, minionsRadius); // ensure we spawn more objects closer to the epicenter var spawnProbability = 1 - (distance / minionsRadius); spawnProbability = Math.Pow(spawnProbability, 1.25); if (!RandomHelper.RollWithProbability(spawnProbability)) { // random skip continue; } var angle = RandomHelper.NextDouble() * MathConstants.DoublePI; var spawnPosition = new Vector2Ushort( (ushort)(epicenterPosition.X + distance * Math.Cos(angle)), (ushort)(epicenterPosition.Y + distance * Math.Sin(angle))); if (ServerTrySpawnMinion(spawnPosition)) { // spawned successfully! countToSpawnRemains--; } } bool ServerTrySpawnMinion(Vector2Ushort spawnPosition) { var worldPosition = spawnPosition.ToVector2D(); if (!ServerCharacterSpawnHelper.IsPositionValidForCharacterSpawn(worldPosition, isPlayer: false)) { // position is not valid for spawning return(false); } var spawnedCharacter = ServerCharacters.SpawnCharacter(minionProto, worldPosition); return(spawnedCharacter is not null); } }
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); }
private void ClientRemote_ButtonPressedByOtherCharacter(Vector2Ushort objectButtonTilePosition) { this.SoundPresetObject.PlaySound(ObjectSound.InteractSuccess, worldPosition: objectButtonTilePosition.ToVector2D() + this.Layout.Center); }
public static void ServerSpawnMinions( ICharacter characterBoss, Vector2D characterBossCenterPosition, IProtoCharacterMob protoMinion, List <ICharacter> minionsList, double spawnCheckDistanceSqr, ServerBossDamageTracker bossDamageTracker, double minionsPerPlayer, int minionsTotalMin, int minionsTotalMax, int?minionsSpawnPerIterationLimit, double baseMinionsNumber, double spawnNoObstaclesCircleRadius, double spawnDistanceMin, double spawnDistanceMax) { // calculate how many minions required var minionsRequired = baseMinionsNumber; using var tempListCharacters = Api.Shared.GetTempList <ICharacter>(); Api.Server.World.GetScopedByPlayers(characterBoss, tempListCharacters); foreach (var player in tempListCharacters.AsList()) { if (player.Position.DistanceSquaredTo(characterBossCenterPosition) <= spawnCheckDistanceSqr) { minionsRequired += minionsPerPlayer; } } minionsRequired = MathHelper.Clamp(minionsRequired, minionsTotalMin, minionsTotalMax); if (minionsSpawnPerIterationLimit.HasValue) { minionsRequired = Math.Min(minionsRequired, minionsSpawnPerIterationLimit.Value); } ServerProcessMinions(characterBoss, protoMinion, minionsList, out var spawnedMinionsCount, despawnDistanceSqr: spawnCheckDistanceSqr); //Logger.Dev($"Minions required: {minionsRequired} minions have: {minionsHave}"); minionsRequired -= spawnedMinionsCount; if (minionsRequired <= 0) { return; } // spawn minions var attemptsRemains = 300; var physicsSpace = characterBoss.PhysicsBody.PhysicsSpace; while (minionsRequired > 0) { attemptsRemains--; if (attemptsRemains <= 0) { // attempts exceeded return; } var spawnDistance = spawnDistanceMin + RandomHelper.NextDouble() * (spawnDistanceMax - spawnDistanceMin); var angle = RandomHelper.NextDouble() * MathConstants.DoublePI; var spawnPosition = new Vector2Ushort( (ushort)(characterBossCenterPosition.X + spawnDistance * Math.Cos(angle)), (ushort)(characterBossCenterPosition.Y + spawnDistance * Math.Sin(angle))); if (ServerTrySpawnMinion(spawnPosition) is { } spawnedMinion) { // spawned successfully! minionsRequired--; minionsList.Add(spawnedMinion); } } ICharacter ServerTrySpawnMinion(Vector2Ushort spawnPosition) { var worldPosition = spawnPosition.ToVector2D(); if (physicsSpace.TestCircle(worldPosition, spawnNoObstaclesCircleRadius, CollisionGroups.Default, sendDebugEvent: true).EnumerateAndDispose().Any()) { // obstacles nearby return(null); } var spawnedCharacter = Api.Server.Characters.SpawnCharacter(protoMinion, worldPosition); if (spawnedCharacter is null) { return(null); } // write this boss' damage tracker into the minion character // so any damage dealt to it will be counted in the winners ranking var privateState = spawnedCharacter.GetPrivateState <ICharacterPrivateStateWithBossDamageTracker>(); privateState.DamageTracker = bossDamageTracker; // start spawn animation if (spawnedCharacter.ProtoGameObject is IProtoCharacterMob protoCharacterMob) { protoCharacterMob.ServerSetSpawnState(spawnedCharacter, MobSpawnState.Spawning); } return(spawnedCharacter); } }
public void ServerTrySpawnMinions(ICharacter characterBoss) { var bossDamageTracker = GetPrivateState(characterBoss).DamageTracker; var bossPosition = characterBoss.Position + (0, 1.0); // calculate how many minions required var minionsRequired = 1; using var tempListCharacters = Api.Shared.GetTempList <ICharacter>(); Server.World.GetScopedByPlayers(characterBoss, tempListCharacters); foreach (var player in tempListCharacters.AsList()) { if (player.Position.DistanceSquaredTo(bossPosition) <= SpawnMinionsCheckDistance * SpawnMinionsCheckDistance) { minionsRequired += SpawnMinionsPerPlayer; } } if (minionsRequired < 3) { // ensure there are at least 3 minions minionsRequired = 3; } // calculate how many minions present tempListCharacters.Clear(); Server.World.GetScopedByCharacters(characterBoss, tempListCharacters.AsList(), onlyPlayerCharacters: false); var minionsHave = 0; var protoMobMinion = ProtoMinionObjectLazy.Value; foreach (var otherCharacter in tempListCharacters.AsList()) { if (otherCharacter.IsNpc && otherCharacter.ProtoGameObject == protoMobMinion && otherCharacter.Position.DistanceSquaredTo(bossPosition) <= SpawnMinionsCheckDistance * SpawnMinionsCheckDistance) { minionsHave++; } } //Logger.Dev($"Minions required: {minionsRequired} minions have: {minionsHave}"); minionsRequired -= minionsHave; if (minionsRequired <= 0) { return; } // spawn minions var attemptsRemains = 300; const double spawnDistanceMin = 2.0; const double spawnDistanceMax = 2.5; while (minionsRequired > 0) { attemptsRemains--; if (attemptsRemains <= 0) { // attempts exceeded return; } var spawnDistance = spawnDistanceMin + RandomHelper.NextDouble() * (spawnDistanceMax - spawnDistanceMin); var angle = RandomHelper.NextDouble() * MathConstants.DoublePI; var spawnPosition = new Vector2Ushort( (ushort)(bossPosition.X + spawnDistance * Math.Cos(angle)), (ushort)(bossPosition.Y + spawnDistance * Math.Sin(angle))); if (ServerTrySpawnMinion(spawnPosition)) { // spawned successfully! minionsRequired--; } } bool ServerTrySpawnMinion(Vector2Ushort spawnPosition) { var worldPosition = spawnPosition.ToVector2D(); var spawnedCharacter = Server.Characters.SpawnCharacter(protoMobMinion, worldPosition); if (spawnedCharacter is null) { return(false); } // write this boss' damage tracker into the minion character // so any damage dealt to it will be counted in the winners ranking var privateState = spawnedCharacter.GetPrivateState <ICharacterPrivateStateWithBossDamageTracker>(); privateState.DamageTracker = bossDamageTracker; return(true); } }
private Vector2Ushort GetAreasGroupCanvasPosition(Vector2Ushort worldPosition) { return(this.visualizer .WorldToCanvasPosition(worldPosition.ToVector2D()) .ToVector2Ushort()); }
public override bool ServerCanCatch(ICharacter character, Vector2Ushort fishingTilePosition) { return(character.SharedHasSkill <SkillFishing>(SkillFishingLevelRequired) && SharedEventHelper.SharedIsInsideEventArea <EventFishingBlueGlider>( fishingTilePosition.ToVector2D() + (0.5, 0.5))); }
public static void ClientOnWeaponHitOrTrace( ICharacter firingCharacter, IProtoItemWeapon protoWeapon, IProtoItemAmmo protoAmmo, IProtoCharacter protoCharacter, Vector2Ushort fallbackCharacterPosition, IReadOnlyList <WeaponHitData> hitObjects, Vector2D endPosition, bool endsWithHit) { if (firingCharacter is not null && !firingCharacter.IsInitialized) { firingCharacter = null; } var weaponTracePreset = protoWeapon.FireTracePreset ?? protoAmmo?.FireTracePreset; var worldPositionSource = SharedCalculateWeaponShotWorldPositon( firingCharacter, protoWeapon, protoCharacter, fallbackCharacterPosition.ToVector2D(), hasTrace: weaponTracePreset?.HasTrace ?? false); protoWeapon.ClientOnWeaponHitOrTrace(firingCharacter, worldPositionSource, protoWeapon, protoAmmo, protoCharacter, fallbackCharacterPosition, hitObjects, endPosition, endsWithHit); if (weaponTracePreset?.HasTrace ?? false) { ComponentWeaponTrace.Create(weaponTracePreset, worldPositionSource, endPosition, hasHit: endsWithHit, lastHitData: hitObjects.LastOrDefault(t => t.WorldObject is not null)); } foreach (var hitData in hitObjects) { var hitWorldObject = hitData.WorldObject; if (hitWorldObject is not null && !hitWorldObject.IsInitialized) { hitWorldObject = null; } var protoWorldObject = hitData.FallbackProtoWorldObject; double delay; { var worldObjectPosition = CalculateWorldObjectPosition(hitWorldObject, hitData); delay = weaponTracePreset?.HasTrace ?? false ? SharedCalculateTimeToHit(weaponTracePreset, worldPositionSource : worldPositionSource, endPosition : worldObjectPosition + hitData.HitPoint.ToVector2D()) : 0; } ClientTimersSystem.AddAction( delay, () => { // re-calculate the world object position var worldObjectPosition = CalculateWorldObjectPosition(hitWorldObject, hitData); var fireScatterPreset = protoAmmo?.OverrideFireScatterPreset ?? protoWeapon.FireScatterPreset; var projectilesCount = fireScatterPreset.ProjectileAngleOffets.Length; var objectMaterial = hitData.FallbackObjectMaterial; if (hitWorldObject is ICharacter hitCharacter && hitCharacter.IsInitialized) { objectMaterial = ((IProtoCharacterCore)hitCharacter.ProtoCharacter) .SharedGetObjectMaterialForCharacter(hitCharacter); } protoWeapon.ClientPlayWeaponHitSound(hitWorldObject, protoWorldObject, fireScatterPreset, objectMaterial, worldObjectPosition); if (weaponTracePreset is not null) { ClientAddHitSparks(weaponTracePreset.HitSparksPreset, hitData, hitWorldObject, protoWorldObject, worldObjectPosition, projectilesCount, objectMaterial, randomizeHitPointOffset: !weaponTracePreset.HasTrace, randomRotation: !weaponTracePreset.HasTrace, drawOrder: weaponTracePreset.DrawHitSparksAsLight ? DrawOrder.Light : DrawOrder.Default); } });
public void SharedValidatePlacement( ICharacter character, Vector2Ushort targetPosition, bool logErrors, out bool canPlace, out bool isTooFar, out object errorCodeOrMessage) { if (NewbieProtectionSystem.SharedIsNewbie(character)) { if (logErrors) { NewbieProtectionSystem.SharedNotifyNewbieCannotPerformAction(character, this); } canPlace = false; isTooFar = false; errorCodeOrMessage = null; return; } // check whether somebody nearby is already placing a bomb there var tempCharactersNearby = Api.Shared.GetTempList <ICharacter>(); if (IsServer) { Server.World.GetScopedByPlayers(character, tempCharactersNearby); } else { Client.Characters.GetKnownPlayerCharacters(tempCharactersNearby); } foreach (var otherCharacter in tempCharactersNearby.AsList()) { if (otherCharacter != character && otherCharacter.IsInitialized && PlayerCharacter.GetPublicState(otherCharacter).CurrentPublicActionState is ItemExplosiveActionPublicState explosiveActionState && explosiveActionState.TargetPosition == targetPosition) { // someone is already planting a bomb here canPlace = false; isTooFar = false; errorCodeOrMessage = null; 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 obstaclesInTheWay = physicsSpace.TestLine( characterCenter, toPosition, CollisionGroup.Default, sendDebugEvent: false); foreach (var test in obstaclesInTheWay.AsList()) { var testPhysicsBody = test.PhysicsBody; if (testPhysicsBody.AssociatedProtoTile is not null) { // obstacle tile on the way return(true); } var testWorldObject = testPhysicsBody.AssociatedWorldObject; if (testWorldObject is null) { // some barrier on the way return(true); } 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); } if (!this.ObjectExplosiveProto.CheckTileRequirements(targetPosition, character, out errorCodeOrMessage, logErrors)) { // explosive static object placement requirements failed canPlace = false; isTooFar = false; return; } // let's check whether there are any obstacles by casting rays // from character's center to the center of the planted bomb if (TestHasObstacle(worldObjectCenter)) { // has obstacle if (logErrors) { if (IsClient) { this.ClientShowCannotPlaceObstaclesInTheWayNotification(); } else { Logger.Warning($"{character} cannot place {this} - obstacles in the way"); this.CallClient(character, _ => _.ClientRemote_CannotPlaceObstacles()); } } canPlace = false; isTooFar = false; errorCodeOrMessage = CoreStrings.Notification_ObstaclesOnTheWay; return; } // validate distance to the character if (this.SharedIsTooFarToPlace(character, targetPosition, logErrors)) { canPlace = true; isTooFar = true; return; } canPlace = true; isTooFar = false; }
private void ClientRemote_OtherPlayerPickedUp(Vector2Ushort position) { Client.Audio.PlayOneShot(ItemsSoundPresets.SoundResourceOtherPlayerPickItem, position.ToVector2D() + this.Layout.Center); }
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; }