public sealed override void ServerItemHotbarSelectionChanged( IItem item, ICharacter character, bool isSelected) { if (!isSelected) { CharacterDroneControlSystem.ServerRecallAllDrones(character); } }
public override void Execute() { if (!(IsEnabled && CheckPrecondition())) { return; } if (!CharacterDroneControlSystem.SharedIsMaxDronesToControlNumberExceeded( CurrentCharacter, clientShowErrorNotification: false)) { TrySendDrone(); } }
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)); }
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); }
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);
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); }
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; } } } }
public override void ServerOnDestroy(IDynamicWorldObject gameObject) { base.ServerOnDestroy(gameObject); CharacterDroneControlSystem.ServerOnDroidDestroyed(gameObject); }