public sealed override void ServerItemHotbarSelectionChanged(
     IItem item,
     ICharacter character,
     bool isSelected)
 {
     if (!isSelected)
     {
         CharacterDroneControlSystem.ServerRecallAllDrones(character);
     }
 }
예제 #2
0
 public override void Execute()
 {
     if (!(IsEnabled && CheckPrecondition()))
     {
         return;
     }
     if (!CharacterDroneControlSystem.SharedIsMaxDronesToControlNumberExceeded(
             CurrentCharacter,
             clientShowErrorNotification: false))
     {
         TrySendDrone();
     }
 }
예제 #3
0
 private bool IsValidObject(IStaticWorldObject staticWorldObject)
 {
     return((EnabledMineralList.Contains(staticWorldObject.ProtoStaticWorldObject) ||
             EnabledTreeList.Contains(staticWorldObject.ProtoStaticWorldObject)) &&
            CharacterDroneControlSystem.SharedIsValidStartLocation(
                CurrentCharacter,
                staticWorldObject.TilePosition,
                out bool hasObstacle) &&
            WorldObjectClaimSystem.SharedIsAllowInteraction(
                CurrentCharacter,
                staticWorldObject,
                showClientNotification: false) &&
            !(staticWorldObject.ProtoStaticWorldObject is IProtoObjectTree tree &&
              tree.SharedGetGrowthProgress(staticWorldObject) < AllowedTreeGrowthFractionLevel) &&
            !CharacterDroneControlSystem.SharedIsTargetAlreadyScheduledForAnyActiveDrone(
                CurrentCharacter,
                staticWorldObject.TilePosition,
                logError: false));
 }
예제 #4
0
        public void ServerSetDroneTarget(
            IDynamicWorldObject objectDrone,
            IStaticWorldObject targetWorldObject,
            Vector2D fromStartPosition)
        {
            var targetObjectPosition = targetWorldObject.TilePosition;
            var publicState          = GetPublicState(objectDrone);

            var targetDronePosition = targetObjectPosition.ToVector2D()
                                      + DroneTargetPositionHelper.GetTargetPosition(targetWorldObject);

            // ensure the drone will select a position on the side of the object
            var directionX = fromStartPosition.X - targetDronePosition.X;

            if (Math.Abs(directionX) < 0.5)
            {
                directionX = 0.5 * Math.Sign(directionX);
            }
            else if (Math.Abs(directionX) > 1.0)
            {
                directionX = 1.0 * Math.Sign(directionX);
            }

            if (targetWorldObject.ProtoGameObject is IProtoObjectTree)
            {
                // ensure the drone will always fly somewhere above below the tree
                // (a beam behind the tree doesn't look good)
                targetDronePosition -= (0, 0.5);
                directionX           = Math.Sign(directionX) * 1.5;
            }
            else
            {
                // ensure the drone will always fly somewhere above the object
                targetDronePosition += (0, 0.5);
            }

            targetDronePosition += new Vector2D(directionX * this.BeamMaxLength, 0);

            publicState.SetTargetPosition(targetObjectPosition, targetDronePosition);

            CharacterDroneControlSystem.ServerUnregisterCurrentMining(objectDrone);
        }
예제 #5
0
        private void TrySendDrone()
        {
            var targetsList =
                Api.Client.World.GetStaticWorldObjectsOfProto <IProtoStaticWorldObject>()
                .Where(IsValidObject)
                .OrderBy(o => CurrentCharacter.Position.DistanceTo(o.TilePosition.ToVector2D()))
                .ToList();

            if (targetsList.Count == 0)
            {
                return;
            }
            int targetN           = 0;
            int droneControlLimit = ((IProtoItemDroneControl)SelectedItem.ProtoGameObject).MaxDronesToControl;
            int droneNumberToSend = Math.Min(
                droneControlLimit - CurrentCharacter.SharedGetCurrentControlledDronesNumber(),
                targetsList.Count);

            using var tempExceptDrones = Api.Shared.GetTempList <IItem>();
            for (var index = 0; index < droneNumberToSend; index++)
            {
                IItem itemDrone;
                do
                {
                    itemDrone = CharacterDroneControlSystem.ClientSelectNextDrone(tempExceptDrones.AsList());
                    if (itemDrone is null)
                    {
                        return;
                    }
                    tempExceptDrones.Add(itemDrone);
                } while (ItemDurabilitySystem.SharedGetDurabilityFraction(itemDrone) < DroneDurabilityThreshold);

                if (!CharacterDroneControlSystem.ClientTryStartDrone(itemDrone,
                                                                     targetsList[targetN].TilePosition,
                                                                     showErrorNotification: false))
                {
                    return;
                }
                targetN++;
            }
        }
        protected override bool ClientItemUseFinish(ClientItemData data)
        {
            var character             = ClientCurrentCharacterHelper.Character;
            var characterTilePosition = character.TilePosition;
            var mouseTilePosition     = Client.Input.MousePointedTilePosition;
            var dronesNumberToLaunch  = Api.Client.Input.IsKeyHeld(InputKey.Shift, evenIfHandled: true)
                                           ? this.MaxDronesToControl
                                           : 1;

            using var tempExceptDrones  = Api.Shared.GetTempList <IItem>();
            using var tempExceptTargets = Api.Shared.GetTempList <Vector2Ushort>();

            for (var index = 0; index < dronesNumberToLaunch; index++)
            {
                var showErrorNotification = index == 0;
                var itemDrone             = CharacterDroneControlSystem.ClientSelectNextDrone(tempExceptDrones.AsList());
                if (itemDrone is null)
                {
                    if (CharacterDroneControlSystem.SharedIsMaxDronesToControlNumberExceeded(
                            character,
                            clientShowErrorNotification: showErrorNotification))
                    {
                        break;
                    }

                    if (showErrorNotification)
                    {
                        CannotInteractMessageDisplay.ClientOnCannotInteract(
                            character,
                            CharacterDroneControlSystem.Notification_ErrorNoDrones_Title,
                            isOutOfRange: false);
                    }

                    break;
                }

                tempExceptDrones.Add(itemDrone);
                Vector2Ushort targetPosition;

                if (index == 0)
                {
                    targetPosition = mouseTilePosition;
                    var targetObject = CharacterDroneControlSystem
                                       .SharedGetCompatibleTarget(character,
                                                                  mouseTilePosition,
                                                                  out var hasIncompatibleTarget,
                                                                  out var isPveActionForbidden);
                    if (targetObject is null)
                    {
                        if (showErrorNotification)
                        {
                            if (isPveActionForbidden)
                            {
                                PveSystem.ClientShowNotificationActionForbidden();
                            }

                            CannotInteractMessageDisplay.ClientOnCannotInteract(
                                character,
                                hasIncompatibleTarget
                                    ? CharacterDroneControlSystem.Notification_CannotMineThat
                                    : CharacterDroneControlSystem.Notification_NothingToMineThere,
                                isOutOfRange: false);
                        }

                        return(false);
                    }

                    if (!WorldObjectClaimSystem.SharedIsAllowInteraction(character,
                                                                         targetObject,
                                                                         showClientNotification: showErrorNotification))
                    {
                        return(false);
                    }

                    if (CharacterDroneControlSystem.SharedIsTargetAlreadyScheduledForAnyActiveDrone(
                            character,
                            mouseTilePosition,
                            logError: false))
                    {
                        // already scheduled a drone mining there...try find another target of the same type
                        targetPosition = TryGetNextTargetPosition();
                        if (targetPosition == default)
                        {
                            // no further targets
                            CannotInteractMessageDisplay.ClientOnCannotInteract(
                                character,
                                CharacterDroneControlSystem.Notification_DroneAlreadySent,
                                isOutOfRange: false);
                            return(false);
                        }
                    }
                }
                else
                {
                    targetPosition = TryGetNextTargetPosition();
                    if (targetPosition == default)
                    {
                        // no further targets
                        break;
                    }
                }

                if (!CharacterDroneControlSystem.ClientTryStartDrone(itemDrone,
                                                                     targetPosition,
                                                                     showErrorNotification: showErrorNotification))
                {
                    break;
                }

                tempExceptTargets.Add(targetPosition);
            }

            // always return false as we don't want to play any device sounds
            return(false);

            Vector2Ushort TryGetNextTargetPosition()
            {
                var targetObjectProto = CharacterDroneControlSystem
                                        .SharedGetCompatibleTarget(character,
                                                                   mouseTilePosition,
                                                                   out _,
                                                                   out _)?
                                        .ProtoWorldObject;

                if (targetObjectProto is null)
                {
                    return(default);
예제 #7
0
        private static void ServerOnDroneReturnedToPlayer(IDynamicWorldObject worldObject)
        {
            var privateState = GetPrivateState(worldObject);
            var character    = privateState.CharacterOwner;

            CharacterDroneControlSystem.ServerDespawnDrone(worldObject, isReturnedToPlayer: true);

            var storageContainer = privateState.StorageItemsContainer;

            if (storageContainer.OccupiedSlotsCount == 0)
            {
                return;
            }

            // drop storage container contents to player
            // but first, move the drone item to its original slot (if possible)
            var characterContainerInventory = character.SharedGetPlayerContainerInventory();
            var characterContainerHotbar    = character.SharedGetPlayerContainerHotbar();

            var itemInFirstSlot = storageContainer.GetItemAtSlot(0);

            if (itemInFirstSlot is not null)
            {
                // item in the first slot is the drone's associated item
                // it could be destroyed in case the drone's HP dropped <= 1
                Server.Items.MoveOrSwapItem(itemInFirstSlot,
                                            privateState.IsStartedFromHotbarContainer
                                                ? characterContainerHotbar
                                                : characterContainerInventory,
                                            slotId: privateState.StartedFromSlotIndex,
                                            movedCount: out _);
            }

            var result = Server.Items.TryMoveAllItems(storageContainer, characterContainerInventory);

            try
            {
                if (storageContainer.OccupiedSlotsCount == 0)
                {
                    // all items moved from drone to player
                    return;
                }

                // try move remaining items to hotbar
                var resultToHotbar = Server.Items.TryMoveAllItems(storageContainer, characterContainerHotbar);
                result.MergeWith(resultToHotbar,
                                 areAllItemsMoved: resultToHotbar.AreAllItemMoved);

                if (storageContainer.OccupiedSlotsCount == 0)
                {
                    // all items moved from drone to player
                    return;
                }
            }
            finally
            {
                if (result.MovedItems.Count > 0)
                {
                    // notify player about the received items
                    NotificationSystem.ServerSendItemsNotification(
                        character,
                        result.MovedItems
                        .GroupBy(p => p.Key.ProtoItem)
                        .Where(p => !(p.Key is TItemDrone))
                        .ToDictionary(p => p.Key, p => p.Sum(v => v.Value)));
                }
            }

            // try to drop the remaining items on the ground
            var groundContainer = ObjectGroundItemsContainer
                                  .ServerTryGetOrCreateGroundContainerAtTileOrNeighbors(character, character.Tile);

            if (groundContainer is not null)
            {
                var result2 = Server.Items.TryMoveAllItems(storageContainer, groundContainer);
                if (result2.MovedItems.Count > 0)
                {
                    var protoItemForIcon = result2.MovedItems.First().Key.ProtoItem;

                    NotificationSystem.ServerSendNotificationNoSpaceInInventoryItemsDroppedToGround(
                        character,
                        protoItemForIcon);

                    // ensure that these items could be lifted only by their owner in PvE
                    WorldObjectClaimSystem.ServerTryClaim(groundContainer.OwnerAsStaticObject,
                                                          character,
                                                          WorldObjectClaimDuration.DroppedGoods);
                }
            }

            if (storageContainer.OccupiedSlotsCount == 0)
            {
                return;
            }

            Logger.Error("Not all items dropped on the ground from the drone storage: "
                         + worldObject
                         + " slots occupied: "
                         + storageContainer.OccupiedSlotsCount);
        }
예제 #8
0
        protected override void ServerUpdate(ServerUpdateData data)
        {
            base.ServerUpdate(data);

            var objectDrone  = data.GameObject;
            var privateState = data.PrivateState;
            var publicState  = data.PublicState;

            if (privateState.AssociatedItem is null)
            {
                // incorrectly configured drone
                Server.World.DestroyObject(objectDrone);
                return;
            }

            if (privateState.IsDespawned)
            {
                this.ServerSetUpdateRate(objectDrone, isRare: true);
                return;
            }

            UpdateWeaponCooldown();

            Vector2D destinationCoordinate;
            var      characterOwner = privateState.CharacterOwner;

            var hasMiningTargets = publicState.TargetObjectPosition.HasValue;

            if (hasMiningTargets &&
                !(CharacterDroneControlSystem.SharedIsValidDroneOperationDistance(objectDrone.TilePosition,
                                                                                  characterOwner.TilePosition) &&
                  objectDrone.Tile.Height == characterOwner.Tile.Height))
            {
                Logger.Info("The drone is beyond operation distance or has different tile height and will be recalled: "
                            + objectDrone);
                CharacterDroneControlSystem.ServerRecallDrone(objectDrone);
                hasMiningTargets = false;
            }

            if (hasMiningTargets)
            {
                // go to the next waypoint
                destinationCoordinate = publicState.TargetDronePosition
                                        ?? publicState.TargetObjectPosition.Value.ToVector2D();

                if (!CharacterDroneControlSystem.ServerIsMiningAllowed(
                        publicState.TargetObjectPosition.Value,
                        objectDrone))
                {
                    // cannot mine as it's already mined by another drone
                    publicState.ResetTargetPosition();
                    return;
                }
            }
            else
            {
                // should return to the player to despawn
                if (characterOwner is null ||
                    characterOwner.GetPublicState <ICharacterPublicState>().IsDead ||
                    CharacterDroneControlSystem.SharedIsBeyondDroneAbandonDistance(
                        objectDrone.TilePosition,
                        characterOwner.TilePosition))
                {
                    CharacterDroneControlSystem.ServerDeactivateDrone(objectDrone);
                    return;
                }

                destinationCoordinate = characterOwner.Position;
            }

            RefreshMovement(isToMineral: hasMiningTargets,
                            destinationCoordinate,
                            out var isDestinationReached);
            if (!isDestinationReached)
            {
                return;
            }

            if (hasMiningTargets)
            {
                PerformMining();
            }
            else
            {
                // we were going to the player and reached its location, despawn
                ServerOnDroneReturnedToPlayer(objectDrone);
            }

            void RefreshMovement(
                bool isToMineral,
                Vector2D toPosition,
                out bool isDestinationReached)
            {
                var positionDelta       = toPosition - objectDrone.Position;
                var positionDeltaLength = positionDelta.Length;

                double targetVelocity;

                if (positionDeltaLength
                    > (isToMineral
                           ? DistanceThresholdToMineral
                           : DistanceThresholdToPlayer))
                {
                    // fly towards that object
                    var moveSpeed = this.StatMoveSpeed;

                    targetVelocity       = moveSpeed;
                    isDestinationReached = false;

                    if (isToMineral)
                    {
                        // reduce speed when too close to the mineral
                        var distanceCoef = positionDeltaLength / (0.333 * targetVelocity);
                        if (distanceCoef < 1)
                        {
                            targetVelocity *= Math.Pow(distanceCoef, 0.5);
                            // ensure it cannot drop lower than 5% of the original move speed
                            targetVelocity = Math.Max(0.05 * moveSpeed, targetVelocity);
                        }
                    }
                }
                else
                {
                    isDestinationReached = true;

                    // stop
                    if (Server.World.GetDynamicObjectMoveSpeed(objectDrone) == 0)
                    {
                        // already stopped
                        return;
                    }

                    targetVelocity = 0;
                }

                var movementDirectionNormalized = positionDelta.Normalized;
                var moveAcceleration            = movementDirectionNormalized * this.PhysicsBodyAccelerationCoef * targetVelocity;

                Server.World.SetDynamicObjectMoveSpeed(objectDrone, targetVelocity);

                Server.World.SetDynamicObjectPhysicsMovement(objectDrone,
                                                             moveAcceleration,
                                                             targetVelocity: targetVelocity);
                objectDrone.PhysicsBody.Friction = this.PhysicsBodyFriction;
            }

            void PerformMining()
            {
                var targetObject = CharacterDroneControlSystem.SharedGetCompatibleTarget(
                    characterOwner,
                    publicState.TargetObjectPosition.Value,
                    out _,
                    out _);

                if (targetObject is null ||
                    !WorldObjectClaimSystem.SharedIsAllowInteraction(characterOwner,
                                                                     targetObject,
                                                                     showClientNotification: false))
                {
                    // nothing to mine there, or finished mining, or cannot mine
                    CharacterDroneControlSystem.ServerUnregisterCurrentMining(
                        publicState.TargetObjectPosition.Value,
                        objectDrone);

                    publicState.ResetTargetPosition();
                    return;
                }

                if (privateState.WeaponCooldownSecondsRemains > 0)
                {
                    return;
                }

                if (!CharacterDroneControlSystem.ServerTryRegisterCurrentMining(
                        publicState.TargetObjectPosition.Value,
                        objectDrone))
                {
                    // cannot mine as it's already mined by another drone
                    publicState.ResetTargetPosition();
                    return;
                }

                publicState.IsMining = true;

                var protoMiningTool = this.ProtoItemMiningTool;

                privateState.WeaponCooldownSecondsRemains +=
                    Api.Shared.RoundDurationByServerFrameDuration(protoMiningTool.FireInterval);

                var characterFinalStatsCache = characterOwner.SharedGetFinalStatsCache();

                var weaponFinalCache = privateState.WeaponFinalCache;

                if (weaponFinalCache is null ||
                    !privateState.LastCharacterOwnerFinalStatsCache.CustomEquals(characterFinalStatsCache))
                {
                    weaponFinalCache = ServerCreateWeaponFinalCacheForDrone(characterOwner,
                                                                            protoMiningTool,
                                                                            objectDrone);

                    privateState.WeaponFinalCache = weaponFinalCache;
                    privateState.LastCharacterOwnerFinalStatsCache = characterFinalStatsCache;
                }

                var protoWorldObject = (IDamageableProtoWorldObject)targetObject.ProtoGameObject;

                protoWorldObject.SharedOnDamage(
                    weaponFinalCache,
                    targetObject,
                    damagePreMultiplier: 1,
                    damagePostMultiplier: 1,
                    obstacleBlockDamageCoef: out _,
                    damageApplied: out var damageApplied);

                // reduce drone durability on 1 unit (reflected as HP when it's a world object)
                // but ensure the new HP cannot drop to exact 0 (to prevent destruction while mining)
                var newHP = publicState.StructurePointsCurrent
                            - 1 * LazyProtoItemDrone.Value.DurabilityToStructurePointsConversionCoefficient;

                publicState.StructurePointsCurrent = Math.Max(float.Epsilon, (float)newHP);

                if (damageApplied <= 0)
                {
                    // cannot mine there for whatever reason, recall the drone
                    publicState.ResetTargetPosition();
                }

                this.ServerSendMiningSoundCue(objectDrone, characterOwner);
            }

            void UpdateWeaponCooldown()
            {
                if (privateState.WeaponCooldownSecondsRemains <= 0)
                {
                    return;
                }

                // process weapon cooldown
                var deltaTime = data.DeltaTime;

                if (deltaTime > 0.4)
                {
                    // too large delta time probably due to a frame skip
                    deltaTime = 0.4;
                }

                if (privateState.WeaponCooldownSecondsRemains > 0)
                {
                    privateState.WeaponCooldownSecondsRemains -= deltaTime;
                    if (privateState.WeaponCooldownSecondsRemains < -0.2)
                    {
                        // clamp the remaining cooldown in case of a frame skip
                        privateState.WeaponCooldownSecondsRemains = -0.2;
                    }
                }
            }
        }
예제 #9
0
 public override void ServerOnDestroy(IDynamicWorldObject gameObject)
 {
     base.ServerOnDestroy(gameObject);
     CharacterDroneControlSystem.ServerOnDroidDestroyed(gameObject);
 }