public static void ServerTrySpawnItemToCharacterOrGround( ICharacter character, IProtoItem protoItem, uint countToSpawn, ref IItemsContainer groundContainer) { var result = character.ProtoCharacter.ServerCreateItem(character, protoItem, countToSpawn); if (result.IsEverythingCreated) { // successfully spawned at character return; } countToSpawn -= result.TotalCreatedCount; // cannot spawn - try spawn to the ground if (groundContainer is null) { groundContainer = ObjectGroundItemsContainer.ServerTryGetOrCreateGroundContainerAtTileOrNeighbors( character, character.Tile); if (groundContainer is null) { return; } } ServerItemsService.CreateItem(protoItem, groundContainer, count: countToSpawn); }
public static CreateItemResult TryDropToGround( IReadOnlyDropItemsList dropItemsList, Vector2Ushort tilePosition, double probabilityMultiplier, DropItemContext context, [CanBeNull] out IItemsContainer groundContainer) { // obtain the ground container to drop the items into var tile = Api.Server.World.GetTile(tilePosition); groundContainer = ObjectGroundItemsContainer .ServerTryGetOrCreateGroundContainerAtTileOrNeighbors(tile); if (groundContainer == 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 (result.TotalCreatedCount == 0 && groundContainer.OccupiedSlotsCount == 0) { // nothing is spawned, the ground container should be destroyed Api.Server.World.DestroyObject(groundContainer.OwnerAsStaticObject); groundContainer = null; } return(result); }
public static void ServerSpawnEmptyBottle(ICharacter character, ushort count = 1) { var createItemResult = Server.Items.CreateItem <ItemBottleEmpty>(character, count); if (createItemResult.IsEverythingCreated) { // notify the owner about the spawned empty bottle NotificationSystem.ServerSendItemsNotification(character, createItemResult); return; } createItemResult.Rollback(); var groundContainer = ObjectGroundItemsContainer.ServerTryGetOrCreateGroundContainerAtTileOrNeighbors(character, character.Tile); if (groundContainer is null) { return; } createItemResult = Server.Items.CreateItem <ItemBottleEmpty>(groundContainer, count); if (createItemResult.IsEverythingCreated) { // notify the owner about the spawned empty bottle NotificationSystem.ServerSendNotificationNoSpaceInInventoryItemsDroppedToGround( character, createItemResult.ItemAmounts.FirstOrDefault().Key?.ProtoItem); return; } // BUG: cannot spawn an empty bottle either to player or to the ground. It's a rare case, but still possible. // It's better to return a false result and cancel the action such as drinking of the water. NotificationSystem.ServerSendNotificationNoSpaceInInventory(character); }
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 CreateGroundContainer() { var tile = Api.Server.World.GetTile(this.TilePosition); this.GroundContainer = ObjectGroundItemsContainer .ServerTryGetOrCreateGroundContainerAtTileOrNeighbors(this.Character, tile); // don't restrict the ground container space limit Api.Server.Items.SetSlotsCount(this.GroundContainer, byte.MaxValue); }
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 void ServerTrySpawnItemToCharacterOrGround( ICharacter character, IProtoItem protoItem, uint countToSpawn, ref IItemsContainer groundContainer) { var result = character.ProtoCharacter.ServerCreateItem(character, protoItem, countToSpawn); if (result.IsEverythingCreated) { // successfully spawned at character return; } countToSpawn -= result.TotalCreatedCount; // cannot spawn - try spawn to the ground if (groundContainer == null) { groundContainer = ObjectGroundItemsContainer.ServerTryGetOrCreateGroundContainerAtTileOrNeighbors( character.Tile); if (groundContainer == null) { // cannot drop on ground return; } ServerItemsService.SetContainerType <ItemsContainerOutputPublic>(groundContainer); } // estimate how many slots are required var groundSlotsCount = (int)groundContainer.OccupiedSlotsCount; groundSlotsCount += (int)Math.Ceiling(countToSpawn / (double)protoItem.MaxItemsPerStack); ServerItemsService.SetSlotsCount( groundContainer, (byte)Math.Min(byte.MaxValue, groundSlotsCount)); ServerItemsService.CreateItem(protoItem, groundContainer, count: countToSpawn); }
public static void ServerTrySpawnItemToCharacterOrGround( ICharacter character, IProtoItem protoItem, uint countToSpawn, ref IItemsContainer groundContainer) { var result = Server.Items.CreateItem(protoItem, character, countToSpawn); if (result.IsEverythingCreated) { // successfully spawned at character return; } countToSpawn -= result.TotalCreatedCount; // cannot spawn - try spawn to the ground if (groundContainer is null) { groundContainer = ObjectGroundItemsContainer.ServerTryGetOrCreateGroundContainerAtTileOrNeighbors( character, character.Tile); if (groundContainer is null) { return; } } ServerItemsService.CreateItem(protoItem, groundContainer, count: countToSpawn); if (groundContainer.OccupiedSlotsCount > 0) { return; } // nothing is spawned, the ground container should be destroyed Server.World.DestroyObject(groundContainer.OwnerAsStaticObject); groundContainer = null; }
/// <summary> /// Executed when a weapon must reload (after the reloading duration is completed). /// </summary> private static void SharedProcessWeaponReload( ICharacter character, WeaponState weaponState, out bool isAmmoTypeChanged) { var weaponReloadingState = weaponState.WeaponReloadingState; // remove weapon reloading state weaponState.WeaponReloadingState = null; var itemWeapon = weaponReloadingState.Item; var itemWeaponProto = (IProtoItemWeapon)itemWeapon.ProtoGameObject; var itemWeaponPrivateState = itemWeapon.GetPrivateState <WeaponPrivateState>(); var weaponAmmoCount = (int)itemWeaponPrivateState.AmmoCount; var weaponAmmoCapacity = itemWeaponProto.AmmoCapacity; isAmmoTypeChanged = false; var selectedProtoItemAmmo = weaponReloadingState.ProtoItemAmmo; var currentProtoItemAmmo = itemWeaponPrivateState.CurrentProtoItemAmmo; if (weaponAmmoCount > 0) { if (selectedProtoItemAmmo != currentProtoItemAmmo && weaponAmmoCount > 0) { // unload current ammo if (IsServer) { var targetContainers = SharedGetTargetContainersForCharacterAmmo(character, isForAmmoUnloading: true); var result = Server.Items.CreateItem( protoItem: currentProtoItemAmmo, new AggregatedItemsContainers(targetContainers), count: (ushort)weaponAmmoCount); if (!result.IsEverythingCreated) { // cannot unload current ammo - no space, try to unload to the ground result.Rollback(); var tile = Api.Server.World.GetTile(character.TilePosition); var groundContainer = ObjectGroundItemsContainer .ServerTryGetOrCreateGroundContainerAtTileOrNeighbors(character, tile); if (groundContainer is null) { // cannot unload current ammo to the ground - no free space around character NotificationSystem.ServerSendNotificationNoSpaceInInventory(character); return; } result = Server.Items.CreateItem( container: groundContainer, protoItem: currentProtoItemAmmo, count: (ushort)weaponAmmoCount); if (!result.IsEverythingCreated) { // cannot unload current ammo to the ground - no space in ground containers near the character result.Rollback(); NotificationSystem.ServerSendNotificationNoSpaceInInventory(character); return; } // notify player that there were not enough space in inventory so the items were dropped to the ground NotificationSystem.ServerSendNotificationNoSpaceInInventoryItemsDroppedToGround( character, result.ItemAmounts.First().Key?.ProtoItem); } } Logger.Info( $"Weapon ammo unloaded: {itemWeapon} -> {weaponAmmoCount} {currentProtoItemAmmo})", character); weaponAmmoCount = 0; itemWeaponPrivateState.SetAmmoCount(0); } else // if the same ammo type is loaded if (weaponAmmoCount == weaponAmmoCapacity) { // already completely loaded Logger.Info( $"Weapon reloading cancelled: {itemWeapon} - no reloading is required ({weaponAmmoCount}/{weaponAmmoCapacity} {selectedProtoItemAmmo})", character); return; } } else // if ammoCount == 0 if (selectedProtoItemAmmo is null && currentProtoItemAmmo is null) { Logger.Info( $"Weapon reloading cancelled: {itemWeapon} - already unloaded ({weaponAmmoCount}/{weaponAmmoCapacity})", character); return; } if (selectedProtoItemAmmo != null) { var selectedAmmoGroup = SharedGetCompatibleAmmoGroups(character, itemWeaponProto) .FirstOrDefault(g => g.Key == selectedProtoItemAmmo); if (selectedAmmoGroup is null) { Logger.Warning( $"Weapon reloading impossible: {itemWeapon} - no ammo of the required type ({selectedProtoItemAmmo})", character); return; } var ammoItems = SharedSelectAmmoItemsFromGroup(selectedAmmoGroup, ammoCountNeed: weaponAmmoCapacity - weaponAmmoCount); foreach (var request in ammoItems) { var itemAmmo = request.Item; Api.Assert(itemAmmo.ProtoItem == selectedProtoItemAmmo, "Sanity check"); int ammoToSubstract; var itemAmmoCount = itemAmmo.Count; if (itemAmmoCount == 0) { continue; } if (request.Count != itemAmmoCount) { if (request.Count < itemAmmoCount) { itemAmmoCount = request.Count; } else if (IsServer) { Logger.Warning( $"Trying to take more ammo to reload than player have: {itemAmmo} requested {request.Count}. Will reload as much as possible only.", character); } } if (weaponAmmoCount + itemAmmoCount >= weaponAmmoCapacity) { // there are more than enough ammo in that item stack to fully refill the weapon ammoToSubstract = weaponAmmoCapacity - weaponAmmoCount; weaponAmmoCount = weaponAmmoCapacity; } else { // consume full item stack ammoToSubstract = itemAmmoCount; weaponAmmoCount += itemAmmoCount; } // reduce ammo item count if (IsServer) { Server.Items.SetCount( itemAmmo, itemAmmo.Count - ammoToSubstract, byCharacter: character); } if (weaponAmmoCount == weaponAmmoCapacity) { // the weapon is fully reloaded, no need to subtract ammo from the next ammo items break; } } } if (currentProtoItemAmmo != selectedProtoItemAmmo) { // another ammo type selected itemWeaponPrivateState.CurrentProtoItemAmmo = selectedProtoItemAmmo; // reset weapon cache (it will be re-calculated on next fire processing) weaponState.WeaponCache = null; isAmmoTypeChanged = true; } if (weaponAmmoCount < 0 || weaponAmmoCount > weaponAmmoCapacity) { Logger.Error( "Something is completely wrong during reloading! Result ammo count is: " + weaponAmmoCount); weaponAmmoCount = 0; } itemWeaponPrivateState.SetAmmoCount((ushort)weaponAmmoCount); if (weaponAmmoCount == 0) { // weapon unloaded - and the log entry about this is already written (see above) return; } Logger.Info( $"Weapon reloaded: {itemWeapon} - ammo {weaponAmmoCount}/{weaponAmmoCapacity} {selectedProtoItemAmmo?.ToString() ?? "<no ammo>"}", character); if (IsServer) { ServerNotifyAboutReloading(character, weaponState, isFinished: true); } else { weaponState.ProtoWeapon.SoundPresetWeapon .PlaySound(WeaponSound.ReloadFinished, character, SoundConstants.VolumeWeapon); } }
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); }
private void ServerGatheringSystemGatherHandler(ICharacter character, IStaticWorldObject worldObject) { if (!(worldObject.ProtoStaticWorldObject is ObjectCorpse)) { return; } // corpse looted // find the device and vial var itemDevice = character.SharedGetPlayerContainerEquipment() .GetItemsOfProto(this) .FirstOrDefault(); if (itemDevice is null) { // don't have an equipped device return; } var protoItemVialEmpty = GetProtoEntity <ItemVialEmpty>(); // require at least one vial if (!character.ContainsItemsOfType(protoItemVialEmpty, requiredCount: 1)) { // don't have an empty vial this.CallClient(character, _ => _.ClientRemote_NotEnoughEmptyVials()); return; } var protoMob = worldObject.GetPublicState <ObjectCorpse.PublicState>() .ProtoCharacterMob; var healthMax = protoMob.StatDefaultHealthMax; var maxVialsToUse = (ushort)MathHelper.Clamp( Math.Floor(protoMob.BiomaterialValueMultiplier * healthMax / 100.0), min: 1, max: 10); // destroy empty vial Server.Items.DestroyItemsOfType( character, protoItemVialEmpty, maxVialsToUse, out var destroyedEmptyVialsCount); if (destroyedEmptyVialsCount == 0) { // cannot destroy any empty vial (should be impossible) return; } // spawn biomaterial vials var protoItemVialBiomaterial = Api.GetProtoEntity <ItemVialBiomaterial>(); var createItemResult = Server.Items.CreateItem(protoItemVialBiomaterial, character, count: destroyedEmptyVialsCount); if (!createItemResult.IsEverythingCreated) { createItemResult.Rollback(); var groundItemsContainer = ObjectGroundItemsContainer.ServerTryGetOrCreateGroundContainerAtTileOrNeighbors(character, character.Tile); if (groundItemsContainer is null) { Logger.Error("Not enough space around the player to drop a full vial"); // restore items Server.Items.CreateItem(protoItemVialEmpty, character, destroyedEmptyVialsCount); return; } createItemResult = Server.Items.CreateItem(protoItemVialBiomaterial, groundItemsContainer, count: destroyedEmptyVialsCount); if (!createItemResult.IsEverythingCreated) { createItemResult.Rollback(); Logger.Error("Not enough space around the player to drop a full vial"); // restore items Server.Items.CreateItem(protoItemVialEmpty, character, destroyedEmptyVialsCount); return; } NotificationSystem.ServerSendNotificationNoSpaceInInventoryItemsDroppedToGround(character, protoItemVialBiomaterial); } var itemChangedCount = NotificationSystem.SharedGetItemsChangedCount(createItemResult); itemChangedCount.Add(protoItemVialEmpty, -(int)destroyedEmptyVialsCount); NotificationSystem.ServerSendItemsNotification(character, itemChangedCount); ItemDurabilitySystem.ServerModifyDurability(itemDevice, -DurabilityDecreasePerUse); Logger.Info("Biomaterial collected successfully with Biomaterial collector", character); }
/// <summary> /// Executed when a weapon must reload (after the reloading duration is completed). /// </summary> private static void SharedProcessWeaponReload(ICharacter character, WeaponState weaponState) { var weaponReloadingState = weaponState.WeaponReloadingState; // remove weapon reloading state weaponState.WeaponReloadingState = null; var itemWeapon = weaponReloadingState.Item; var itemWeaponProto = (IProtoItemWeapon)itemWeapon.ProtoGameObject; var itemWeaponPrivateState = itemWeapon.GetPrivateState <WeaponPrivateState>(); var weaponAmmoCount = (int)itemWeaponPrivateState.AmmoCount; var weaponAmmoCapacity = itemWeaponProto.AmmoCapacity; var selectedProtoItemAmmo = weaponReloadingState.ProtoItemAmmo; var currentProtoItemAmmo = itemWeaponPrivateState.CurrentProtoItemAmmo; if (weaponAmmoCount > 0) { if (selectedProtoItemAmmo != currentProtoItemAmmo && weaponAmmoCount > 0) { // unload current ammo if (IsServer) { var result = Server.Items.CreateItem( toCharacter: character, protoItem: currentProtoItemAmmo, count: (ushort)weaponAmmoCount); if (!result.IsEverythingCreated) { // cannot unload current ammo - no space, try to unload to the ground result.Rollback(); var tile = Api.Server.World.GetTile(character.TilePosition); var groundContainer = ObjectGroundItemsContainer .ServerTryGetOrCreateGroundContainerAtTileOrNeighbors(tile); if (groundContainer == null) { // cannot unload current ammo to the ground - no free space around character Instance.CallClient(character, _ => _.ClientRemote_NoSpaceForUnloadedAmmo(currentProtoItemAmmo)); return; } result = Server.Items.CreateItem( container: groundContainer, protoItem: currentProtoItemAmmo, count: (ushort)weaponAmmoCount); if (!result.IsEverythingCreated) { // cannot unload current ammo to the ground - no space in ground containers near the character result.Rollback(); Instance.CallClient(character, _ => _.ClientRemote_NoSpaceForUnloadedAmmo( currentProtoItemAmmo)); return; } // notify player that there were not enough space in inventory so the items were dropped to the ground NotificationSystem.ServerSendNotificationNoSpaceInInventoryItemsDroppedToGround( character, result.ItemAmounts.First().Key?.ProtoItem); } } Logger.Info( $"Weapon ammo unloaded: {itemWeapon} -> {weaponAmmoCount} {currentProtoItemAmmo})", character); weaponAmmoCount = 0; itemWeaponPrivateState.AmmoCount = 0; } else // if the same ammo type is loaded if (weaponAmmoCount == weaponAmmoCapacity) { // already completely loaded Logger.Info( $"Weapon reloading cancelled: {itemWeapon} - no reloading is required ({weaponAmmoCount}/{weaponAmmoCapacity} {selectedProtoItemAmmo})", character); return; } } else // if ammoCount == 0 if (selectedProtoItemAmmo == null && currentProtoItemAmmo == null) { Logger.Info( $"Weapon reloading cancelled: {itemWeapon} - already unloaded ({weaponAmmoCount}/{weaponAmmoCapacity})", character); return; } if (selectedProtoItemAmmo != null) { var selectedAmmoGroup = SharedGetCompatibleAmmoGroups(character, itemWeaponProto) .FirstOrDefault(g => g.Key == selectedProtoItemAmmo); if (selectedAmmoGroup == null) { Logger.Warning( $"Weapon reloading impossible: {itemWeapon} - no ammo of the required type ({selectedProtoItemAmmo})", character); return; } var ammoItems = SharedSelectAmmoItemsFromGroup(selectedAmmoGroup, ammoCountNeed: weaponAmmoCapacity - weaponAmmoCount); foreach (var request in ammoItems) { var itemAmmo = request.Item; if (itemAmmo.ProtoItem != selectedProtoItemAmmo) { Logger.Error( "Trying to load multiple ammo types in one reloading: " + ammoItems.Select(a => a.Item.ProtoItem).GetJoinedString(), character); break; } int ammoToSubstract; var itemAmmoCount = itemAmmo.Count; if (itemAmmoCount == 0) { continue; } if (request.Count != itemAmmoCount) { if (request.Count < itemAmmoCount) { itemAmmoCount = request.Count; } else if (IsServer) { Logger.Warning( $"Trying to take more ammo to reload than player have: {itemAmmo} requested {request.Count}. Will reload as much as possible only.", character); } } if (weaponAmmoCount + itemAmmoCount >= weaponAmmoCapacity) { // there are more than enough ammo in that item stack to fully refill the weapon ammoToSubstract = weaponAmmoCapacity - weaponAmmoCount; weaponAmmoCount = weaponAmmoCapacity; } else { // consume full item stack ammoToSubstract = itemAmmoCount; weaponAmmoCount += itemAmmoCount; } // check if character owns this item if (itemAmmo.Container.OwnerAsCharacter != character) { Logger.Error("The character doesn't own " + itemAmmo + " - cannot use it to reload", character); continue; } // reduce ammo item count if (IsServer) { Server.Items.SetCount( itemAmmo, itemAmmo.Count - ammoToSubstract, byCharacter: character); } if (weaponAmmoCount == weaponAmmoCapacity) { // the weapon is fully reloaded, no need to subtract ammo from the next ammo items break; } } } if (currentProtoItemAmmo != selectedProtoItemAmmo) { // another ammo type selected itemWeaponPrivateState.CurrentProtoItemAmmo = selectedProtoItemAmmo; // reset weapon cache (it will be re-calculated on next fire processing) weaponState.WeaponCache = null; } if (weaponAmmoCount < 0 || weaponAmmoCount > weaponAmmoCapacity) { Logger.Error( "Something is completely wrong during reloading! Result ammo count is: " + weaponAmmoCount); weaponAmmoCount = 0; } itemWeaponPrivateState.AmmoCount = (ushort)weaponAmmoCount; Logger.Info( $"Weapon reloaded: {itemWeapon} - ammo {weaponAmmoCount}/{weaponAmmoCapacity} {selectedProtoItemAmmo?.ToString() ?? "<no ammo>"}", character); }