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 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); }
public static CreateItemResult TryDropToCharacterOrGround( IReadOnlyDropItemsList dropItemsList, ICharacter toCharacter, Vector2Ushort tilePosition, bool sendNotificationWhenDropToGround, double probabilityMultiplier, DropItemContext context, out IItemsContainer groundContainer) { CreateItemResult result; if (toCharacter != null) { result = TryDropToCharacter( dropItemsList, toCharacter, sendNoFreeSpaceNotification: false, probabilityMultiplier, context); if (result.IsEverythingCreated) { groundContainer = null; return(result); } // cannot add to character - rollback and drop on ground instead result.Rollback(); } result = TryDropToGround(dropItemsList, tilePosition, probabilityMultiplier, context, out groundContainer); 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, result.ItemAmounts.FirstOrDefault().Key?.ProtoItem); } return(result); }
/// <summary> /// Cancel crafting queue item. /// </summary> public static void ServerCancelCraftingQueueItem( ICharacter character, CraftingQueueItem item) { var craftingQueue = character.GetPrivateState <PlayerCharacterPrivateState>() .CraftingQueue; IItemsContainer groundContainer = null; ServerCancelCraftingQueueItem(character, item, craftingQueue, ref groundContainer); if (groundContainer != null) { NotificationSystem.ServerSendNotificationNoSpaceInInventoryItemsDroppedToGround(character); } }
/// <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); }