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));
        }
Example #3
0
        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);
            }
        }
Example #4
0
        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());
            }
        }
Example #6
0
        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);
        }
Example #8
0
        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);
        }
Example #10
0
        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);
            }
        }
Example #11
0
        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);
        }
Example #12
0
 private void ClientRemote_ButtonPressedByOtherCharacter(Vector2Ushort objectButtonTilePosition)
 {
     this.SoundPresetObject.PlaySound(ObjectSound.InteractSuccess,
                                      worldPosition: objectButtonTilePosition.ToVector2D() + this.Layout.Center);
 }
Example #13
0
        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);
            }
        }
Example #14
0
        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);
            }
        }
Example #15
0
 private Vector2Ushort GetAreasGroupCanvasPosition(Vector2Ushort worldPosition)
 {
     return(this.visualizer
            .WorldToCanvasPosition(worldPosition.ToVector2D())
            .ToVector2Ushort());
 }
Example #16
0
 public override bool ServerCanCatch(ICharacter character, Vector2Ushort fishingTilePosition)
 {
     return(character.SharedHasSkill <SkillFishing>(SkillFishingLevelRequired) &&
            SharedEventHelper.SharedIsInsideEventArea <EventFishingBlueGlider>(
                fishingTilePosition.ToVector2D() + (0.5, 0.5)));
 }
Example #17
0
        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);
                    }
                });
Example #18
0
        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);
 }
Example #20
0
        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;
        }