protected override GatheringActionState SharedTryCreateState(WorldActionRequest request) { var worldObject = request.WorldObject; var character = request.Character; var staticWorldObject = (IStaticWorldObject)worldObject; if (!(worldObject.ProtoGameObject is IProtoObjectGatherable protoGatherable)) { throw new Exception("Not a gatherable resource: " + worldObject); } if (IsServer) { WorldObjectClaimSystem.ServerTryClaim(staticWorldObject, character, WorldObjectClaimDuration.RegularObjects); } var durationSeconds = protoGatherable.DurationGatheringSeconds; var multiplier = protoGatherable.GetGatheringSpeedMultiplier(staticWorldObject, character); durationSeconds /= multiplier; if (CreativeModeSystem.SharedIsInCreativeMode(character)) { durationSeconds = 0.1; } return(new GatheringActionState( character, staticWorldObject, durationSeconds)); }
private static bool ServerTrySpawnNode(Vector2Ushort spawnPosition, ICharacter pveTagForCharacter) { var protoNode = ProtoNodeLazy.Value; if (!protoNode.CheckTileRequirements(spawnPosition, character: null, logErrors: false)) { return(false); } var node = Server.World.CreateStaticWorldObject(protoNode, spawnPosition); if (node is null) { return(false); } // make this node to despawn automatically when there is no pragmium source nearby ObjectMineralPragmiumNode.ServerRestartDestroyTimer( ObjectMineralPragmiumNode.GetPrivateState(node)); WorldObjectClaimSystem.ServerTryClaim(node, pveTagForCharacter, WorldObjectClaimDuration.PragmiumSourceCluster); return(true); }
private void ServerCalculateDistanceSqrToTheClosestPragmiumSpires( ICharacter character, Vector2Ushort position, List <double> closestSqrDistances) { var isObjectClaimSystemEnabled = WorldObjectClaimSystem.SharedIsEnabled; using var tempList = Api.Shared.GetTempList <IStaticWorldObject>(); LazyPragmiumSource.Value.GetAllGameObjects(tempList.AsList()); foreach (var staticWorldObject in tempList.AsList()) { var distanceSqr = position.TileSqrDistanceTo(staticWorldObject.TilePosition); if (distanceSqr >= this.MaxRange * this.MaxRange) { // too far continue; } if (isObjectClaimSystemEnabled && !WorldObjectClaimSystem.SharedIsAllowInteraction(character, staticWorldObject, showClientNotification: false)) { // this pragmium source is claimed for another player continue; } closestSqrDistances.Add(distanceSqr); } closestSqrDistances.Sort(); }
public static void OnCharacterKilled( ICharacter attackerCharacter, ICharacter targetCharacter, IProtoSkill protoSkill) { if (attackerCharacter is null || targetCharacter is null) { return; } Api.Logger.Info("Killed " + targetCharacter, attackerCharacter); Api.SafeInvoke( () => CharacterKilled?.Invoke(attackerCharacter, targetCharacter)); if (attackerCharacter.IsNpc || !targetCharacter.IsNpc) { return; } var playerCharacterSkills = attackerCharacter.SharedGetSkills(); // give hunting skill experience for mob kill var huntXP = SkillHunting.ExperienceForKill; huntXP *= ((IProtoCharacterMob)targetCharacter.ProtoGameObject).MobKillExperienceMultiplier; if (huntXP > 0) { playerCharacterSkills.ServerAddSkillExperience <SkillHunting>(huntXP); } if (protoSkill is ProtoSkillWeapons protoSkillWeapon) { // give weapon experience for kill protoSkillWeapon.ServerOnKill(playerCharacterSkills, killedCharacter: targetCharacter); } if (!WorldObjectClaimSystem.SharedIsEnabled) { return; } // try claim the corpse for the attacker player using var tempListCorpses = Api.Shared.GetTempList <IStaticWorldObject>(); Api.GetProtoEntity <ObjectCorpse>() .GetAllGameObjects(tempListCorpses.AsList()); foreach (var worldObjectCorpse in tempListCorpses.AsList()) { if (targetCharacter.Id == ObjectCorpse.GetPublicState(worldObjectCorpse).DeadCharacterId) { WorldObjectClaimSystem.ServerTryClaim(worldObjectCorpse, attackerCharacter, WorldObjectClaimDuration.CreatureCorpse); return; } } }
protected override void ServerTryClaimObject(IStaticWorldObject targetObject, ICharacter character) { if (!ObjectMineralPragmiumSource.ServerTryClaimPragmiumClusterNearCharacter(character)) { WorldObjectClaimSystem.ServerTryClaim(targetObject, character, WorldObjectClaimDuration.PragmiumSourceCluster); } }
public override void ServerOnDestroy(IItem gameObject) { base.ServerOnDestroy(gameObject); ICharacter character = gameObject.Container.OwnerAsCharacter; if (character is null) { return; } var playerPrivateState = PlayerCharacter.GetPrivateState(character); IItemsContainer objectGroundContainer = null; objectGroundContainer = ObjectPlayerLootContainer.ServerTryCreateLootContainer(character); if (objectGroundContainer is not null) { int slotCount = this.GetGroundSlotCount(playerPrivateState); // set slots count matching the total occupied slots count Server.Items.SetSlotsCount(objectGroundContainer, (byte)Math.Min(byte.MaxValue, objectGroundContainer.OccupiedSlotsCount + slotCount)); } if (objectGroundContainer is null) { objectGroundContainer = ObjectGroundItemsContainer.ServerTryGetOrCreateGroundContainerAtTileOrNeighbors(character, character.Tile); if (objectGroundContainer is null) { return; } } for (byte i = PlayerConstants.InventorySlotsCount; i < playerPrivateState.ContainerInventory.SlotsCount; i++) { IItem itemToDrop = playerPrivateState.ContainerInventory.GetItemAtSlot(i); if (itemToDrop is not null) { Server.Items.MoveOrSwapItem(itemToDrop, objectGroundContainer, out _); } } if (playerPrivateState.ContainerInventory.SlotsCount != PlayerConstants.InventorySlotsCount) { Api.Server.Items.SetSlotsCount(playerPrivateState.ContainerInventory, PlayerConstants.InventorySlotsCount); } WorldObjectClaimSystem.ServerTryClaim(objectGroundContainer.OwnerAsStaticObject, character, durationSeconds: objectGroundContainer.OwnerAsStaticObject.ProtoStaticWorldObject is ObjectPlayerLootContainer ? ObjectPlayerLootContainer.AutoDestroyTimeoutSeconds + (10 * 60) : WorldObjectClaimDuration.DroppedGoods); }
private void SharedTryClaimObject() { if (Api.IsServer) { WorldObjectClaimSystem.ServerTryClaim( this.WorldObject, this.Character, durationSeconds: Math.Max(this.currentStageDurationSeconds + 5, WorldObjectClaimDuration.EventObjects)); } }
public override void ServerOnDestroy(IItem gameObject) { base.ServerOnDestroy(gameObject); if (gameObject.Container is null) { return; } ICharacter character = gameObject.Container.OwnerAsCharacter; if (character is null) { return; } var playerPrivateState = PlayerCharacter.GetPrivateState(character); IItemsContainer objectGroundContainer = null; objectGroundContainer = ObjectPlayerLootContainer.ServerTryCreateLootContainer(character); var privateState = GetPrivateState(gameObject); if (objectGroundContainer is not null) { int slotCount = privateState.ItemsContainer.OccupiedSlotsCount; // set slots count matching the total occupied slots count Server.Items.SetSlotsCount(objectGroundContainer, (byte)Math.Min(byte.MaxValue, objectGroundContainer.OccupiedSlotsCount + slotCount)); } if (objectGroundContainer is null) { objectGroundContainer = ObjectGroundItemsContainer.ServerTryGetOrCreateGroundContainerAtTileOrNeighbors(character, character.Tile); if (objectGroundContainer is null) { return; } } Api.Server.Items.TryMoveAllItems(privateState.ItemsContainer, objectGroundContainer); WorldObjectClaimSystem.ServerTryClaim(objectGroundContainer.OwnerAsStaticObject, character, durationSeconds: objectGroundContainer.OwnerAsStaticObject.ProtoStaticWorldObject is ObjectPlayerLootContainer ? ObjectPlayerLootContainer.AutoDestroyTimeoutSeconds + (10 * 60) : WorldObjectClaimDuration.DroppedGoods); }
public void ServerDropDroneToGround(IDynamicWorldObject objectDrone) { var privateState = objectDrone.GetPrivateState <DronePrivateState>(); var storageContainer = privateState.StorageItemsContainer; if (storageContainer.OccupiedSlotsCount == 0) { return; } // drop all items on the ground IItemsContainer groundContainer = null; if (privateState.CharacterOwner is not null) { groundContainer = ObjectPlayerLootContainer.ServerTryCreateLootContainer(privateState.CharacterOwner, objectDrone.Position); if (groundContainer is not null) { // set slots count matching the total occupied slots count Server.Items.SetSlotsCount( groundContainer, (byte)Math.Min(byte.MaxValue, groundContainer.OccupiedSlotsCount + storageContainer.OccupiedSlotsCount)); } } groundContainer ??= ObjectGroundItemsContainer.ServerTryGetOrCreateGroundContainerAtTileOrNeighbors( privateState.CharacterOwner, objectDrone.Tile); if (groundContainer is not null) { Server.Items.TryMoveAllItems(storageContainer, groundContainer); WorldObjectClaimSystem.ServerTryClaim( groundContainer.OwnerAsStaticObject, privateState.CharacterOwner, durationSeconds: ObjectPlayerLootContainer.AutoDestroyTimeoutSeconds + (10 * 60)); } else { // TODO: try to create a ground container in any other ground spot Logger.Error("Cannot find a place to drop the drone contents on the ground - drone lost!" + objectDrone); } }
public static CreateItemResult TryDropToCharacterOrGround( IReadOnlyDropItemsList dropItemsList, ICharacter toCharacter, Vector2Ushort tilePosition, bool sendNotificationWhenDropToGround, double probabilityMultiplier, DropItemContext context, out IItemsContainer groundContainer) { var containersProvider = new CharacterAndGroundContainersProvider(toCharacter, tilePosition); var result = dropItemsList.Execute( (protoItem, count) => Items.CreateItem(protoItem, containersProvider, count), context, probabilityMultiplier); groundContainer = containersProvider.GroundContainer; if (groundContainer is null) { return(result); } if (groundContainer.OccupiedSlotsCount == 0) { // nothing is spawned, the ground container should be destroyed World.DestroyObject(groundContainer.OwnerAsStaticObject); groundContainer = null; } else { if (sendNotificationWhenDropToGround && result.TotalCreatedCount > 0) { // notify player that there were not enough space in inventory so the items were dropped to the ground NotificationSystem.ServerSendNotificationNoSpaceInInventoryItemsDroppedToGround( toCharacter, protoItemForIcon: result.ItemAmounts .Keys .FirstOrDefault( k => k.Container == containersProvider.GroundContainer)? .ProtoItem); } WorldObjectClaimSystem.ServerTryClaim(groundContainer.OwnerAsStaticObject, toCharacter, WorldObjectClaimDuration.GroundItems); } return(result); }
/// <summary> /// Returns a ground container for the provided tile.<br /> /// Please note: the ground container will be automatically destroyed if (during the next update) it's empty! /// </summary> public static IItemsContainer ServerTryGetOrCreateGroundContainerAtTile( ICharacter forCharacter, Vector2Ushort tilePosition, bool writeWarningsToLog = true) { var tile = Server.World.GetTile(tilePosition); var objectGroundContainer = tile.StaticObjects.FirstOrDefault( so => so.ProtoGameObject is ObjectGroundItemsContainer); if (objectGroundContainer is null) { // No ground container found in the cell. Try to create a new ground container here. objectGroundContainer = TryCreateGroundContainer(); if (objectGroundContainer is null) { // cannot create return(null); } } else if (!WorldObjectClaimSystem.SharedIsAllowInteraction(forCharacter, objectGroundContainer, showClientNotification: false)) { // cannot interact with this ground container as it's claimed by another player return(null); } var result = GetPublicState(objectGroundContainer).ItemsContainer; ServerExpandContainerAndScheduleProcessing(result); return(result); IStaticWorldObject TryCreateGroundContainer() { if (instance.CheckTileRequirements(tilePosition, character: null, logErrors: false)) { Logger.Info("Creating ground container at " + tilePosition); return(Server.World.CreateStaticWorldObject(instance, tilePosition)); } if (writeWarningsToLog) { Logger.Warning( $"Cannot create ground container at {tilePosition} - tile contains something preventing it."); } return(null); } }
public static bool ServerTryClaimPragmiumClusterNearCharacter(ICharacter character) { if (!WorldObjectClaimSystem.SharedIsEnabled || character is null || character.IsNpc) { return(true); } using var objectsInScope = Api.Shared.GetTempList <IWorldObject>(); Server.World.GetWorldObjectsInView(character, objectsInScope.AsList(), sortByDistance: false); var hasSource = false; foreach (var worldObject in objectsInScope.AsList()) { if (!(worldObject.ProtoGameObject is ObjectMineralPragmiumSource)) { continue; } hasSource = true; WorldObjectClaimSystem.ServerTryClaim(worldObject, character, WorldObjectClaimDuration.PragmiumSourceCluster); break; } if (!hasSource) { return(false); } foreach (var worldObject in objectsInScope.AsList()) { if (!(worldObject.ProtoGameObject is ObjectMineralPragmiumNode)) { continue; } WorldObjectClaimSystem.ServerTryClaim(worldObject, character, WorldObjectClaimDuration.PragmiumSourceCluster); } return(true); }
public override bool SharedCanInteract(ICharacter character, IStaticWorldObject worldObject, bool writeToLog) { // don't use the base implementation as it will not work in PvE // (action forbidden if player doesn't have access to the land claim) if (character.GetPublicState <ICharacterPublicState>().IsDead || IsServer && !character.ServerIsOnline) { return(false); } if (!NewbieProtectionSystem.SharedValidateInteractionIsNotForbidden(character, worldObject, writeToLog) || !WorldObjectClaimSystem.SharedIsAllowInteraction(character, worldObject, writeToLog)) { return(false); } return(this.SharedIsInsideCharacterInteractionArea(character, worldObject, writeToLog)); }
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 static CreateItemResult TryDropToGround( IReadOnlyDropItemsList dropItemsList, Vector2Ushort tilePosition, double probabilityMultiplier, DropItemContext context, [CanBeNull] out IItemsContainer groundContainer) { var character = context.HasCharacter ? context.Character : null; // obtain the ground container to drop the items into var tile = World.GetTile(tilePosition); groundContainer = ObjectGroundItemsContainer .ServerTryGetOrCreateGroundContainerAtTileOrNeighbors(character, tile); if (groundContainer is null) { // cannot drop because there are no free space available on the ground return(new CreateItemResult() { IsEverythingCreated = false }); } var result = dropItemsList.TryDropToContainer(groundContainer, context, probabilityMultiplier); if (groundContainer.OccupiedSlotsCount == 0) { // nothing is spawned, the ground container should be destroyed World.DestroyObject(groundContainer.OwnerAsStaticObject); groundContainer = null; return(result); } WorldObjectClaimSystem.ServerTryClaim(groundContainer.OwnerAsStaticObject, character, WorldObjectClaimDuration.GroundItems); return(result); }
protected override void SharedValidateRequest(WorldActionRequest request) { var worldObject = (IStaticWorldObject)request.WorldObject; if (!(worldObject?.ProtoWorldObject is IProtoObjectGatherable protoGatherable)) { throw new Exception("The world object must be gatherable"); } if (!worldObject.ProtoWorldObject.SharedCanInteract(request.Character, worldObject, true)) { throw new Exception("Cannot interact with " + worldObject); } if (!WorldObjectClaimSystem.SharedIsAllowInteraction(request.Character, worldObject, showClientNotification: true)) { throw new Exception("Locked for another player: " + worldObject); } if (protoGatherable.SharedIsCanGather(worldObject)) { return; } Logger.Warning("Cannot gather now: " + worldObject, request.Character); if (Api.IsClient) { CannotInteractMessageDisplay.ShowOn(worldObject, NotificationNothingToHarvestYet); worldObject.ProtoWorldObject.SharedGetObjectSoundPreset() .PlaySound(ObjectSound.InteractFail); } throw new Exception("Cannot gather now: " + worldObject); }
protected virtual void ServerTryClaimObject(IStaticWorldObject targetObject, ICharacter character) { WorldObjectClaimSystem.ServerTryClaim(targetObject, character, WorldObjectClaimDuration.RegularObjects); }
private static void ServerTrySpawnNodesAround(IStaticWorldObject worldObject) { // calculate how many nodes are nearby var nodesAroundCount = 0; var neighborTiles = worldObject.OccupiedTiles .SelectMany(t => t.EightNeighborTiles) .Distinct() .ToList(); foreach (var neighborTile in neighborTiles) { foreach (var otherObject in neighborTile.StaticObjects) { if (!(otherObject.ProtoStaticWorldObject is ObjectMineralPragmiumNode)) { continue; } nodesAroundCount++; if (nodesAroundCount >= NodesCountLimit) { // there are already enough nodes around return; } } } // spawn node(s) nearby var countToSpawn = NodesCountLimit - nodesAroundCount; if (countToSpawn <= 0) { return; } var tag = GetPublicState(worldObject).WorldObjectClaim; var pveTagForCharacter = WorldObjectClaimSystem.ServerGetCharacterByClaim(tag); var attempts = neighborTiles.Count * 4; countToSpawn = Math.Min(countToSpawn, ServerSpawnNodesMaxCountPerIteration); while (attempts-- > 0) { var neighborTile = neighborTiles.TakeByRandom(); if (neighborTile.StaticObjects.Count > 0) { // cannot spawn there continue; } if (!ServerTrySpawnNode(neighborTile.Position, pveTagForCharacter)) { // cannot spawn there continue; } countToSpawn--; if (countToSpawn == 0) { return; } } }
public static void ServerCreateBossLoot( Vector2Ushort bossPosition, IProtoCharacterMob protoCharacterBoss, ServerBossDamageTracker damageTracker, double bossDifficultyCoef, IProtoStaticWorldObject lootObjectProto, int lootObjectsDefaultCount, double lootObjectsRadius, double learningPointsBonusPerLootObject, int maxLootWinners) { var approximatedTotalLootCountToSpawn = (int)Math.Ceiling(lootObjectsDefaultCount * bossDifficultyCoef); if (approximatedTotalLootCountToSpawn < 2) { approximatedTotalLootCountToSpawn = 2; } var allCharactersByDamage = damageTracker.GetDamageByCharacter(); var winners = ServerSelectWinnersByParticipation( ServerSelectWinnersByDamage(allCharactersByDamage, maxLootWinners)); var winnerEntries = winners .Select( c => new WinnerEntry( c.Character, damagePercent: (byte)Math.Max( 1, Math.Round(c.Score * 100, MidpointRounding.AwayFromZero)), lootCount: CalculateLootCountForScore(c.Score))) .ToList(); if (PveSystem.ServerIsPvE) { ServerNotifyCharactersNotEligibleForReward( protoCharacterBoss, allCharactersByDamage.Select(c => c.Character) .Except(winners.Select(c => c.Character)) .ToList()); ServerNotifyCharactersEligibleForReward( protoCharacterBoss, winnerEntries, totalLootCount: (ushort)winnerEntries.Sum(e => (int)e.LootCount)); } ServerSpawnLoot(); // send victory announcement notification var winnerNamesWithClanTags = winnerEntries .Select(w => (Name: w.Character.Name, ClanTag: FactionSystem.SharedGetClanTag(w.Character))) .ToArray(); Instance.CallClient( ServerCharacters.EnumerateAllPlayerCharacters(onlyOnline: true), _ => _.ClientRemote_VictoryAnnouncement(protoCharacterBoss, winnerNamesWithClanTags)); foreach (var entry in winnerEntries) { // provide bonus LP entry.Character.SharedGetTechnologies() .ServerAddLearningPoints( learningPointsBonusPerLootObject * entry.LootCount, allowModifyingByStatsAndRates: false); } Api.Logger.Important( protoCharacterBoss.ShortId + " boss defeated." + Environment.NewLine + "Damage by player:" + Environment.NewLine + allCharactersByDamage.Select(p => $" * {p.Character}: {p.Damage:F0}") .GetJoinedString(Environment.NewLine) + Environment.NewLine + "Player participation score: (only for selected winners)" + Environment.NewLine + winners.Select(p => $" * {p.Character}: {(p.Score * 100):F1}%") .GetJoinedString(Environment.NewLine)); Api.SafeInvoke( () => BossDefeated?.Invoke(protoCharacterBoss, bossPosition, winnerEntries)); byte CalculateLootCountForScore(double score) { var result = Math.Ceiling(approximatedTotalLootCountToSpawn * score); if (result < 1) { return(1); } return((byte)Math.Min(byte.MaxValue, result)); } void ServerSpawnLoot() { foreach (var winnerEntry in winnerEntries) { if (!ServerTrySpawnLootForWinner(winnerEntry.Character, winnerEntry.LootCount)) { // spawn attempts failure as logged inside the method, // abort further spawning return; } } } bool ServerTrySpawnLootForWinner(ICharacter forCharacter, double countToSpawnRemains) { var attemptsRemains = 2000; while (countToSpawnRemains > 0) { attemptsRemains--; if (attemptsRemains <= 0) { // attempts exceeded Api.Logger.Error( "Cannot spawn all the loot for boss - number of attempts exceeded. Cont to spawn for winner remains: " + countToSpawnRemains); return(false); } // calculate random distance from the explosion epicenter var distance = RandomHelper.Range(2, lootObjectsRadius); // ensure we spawn more objects closer to the epicenter var spawnProbability = 1 - (distance / lootObjectsRadius); spawnProbability = Math.Pow(spawnProbability, 1.25); if (!RandomHelper.RollWithProbability(spawnProbability)) { // random skip continue; } var angle = RandomHelper.NextDouble() * MathConstants.DoublePI; var spawnPosition = new Vector2Ushort( (ushort)(bossPosition.X + distance * Math.Cos(angle)), (ushort)(bossPosition.Y + distance * Math.Sin(angle))); if (ServerTrySpawnLootObject(spawnPosition, forCharacter)) { // spawned successfully! countToSpawnRemains--; } } return(true); } bool ServerTrySpawnLootObject(Vector2Ushort spawnPosition, ICharacter forCharacter) { if (!lootObjectProto.CheckTileRequirements(spawnPosition, character: null, logErrors: false)) { return(false); } var lootObject = ServerWorld.CreateStaticWorldObject(lootObjectProto, spawnPosition); if (lootObject is null) { return(false); } // mark the loot object for this player (works only in PvE) WorldObjectClaimSystem.ServerTryClaim(lootObject, forCharacter, WorldObjectClaimDuration.BossLoot, claimForPartyMembers: false); return(true); } }
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; } } } }