/// <summary> /// Checks if the thing has the given condition. /// </summary> /// <param name="thing">The thing to check the conditions on.</param> /// <param name="conditionType">The type of condition.</param> /// <returns>True if the thing has such condition, false otherwise.</returns> public static bool HasCondition(this IThing thing, ConditionType conditionType) { thing.ThrowIfNull(nameof(thing)); conditionType.ThrowIfNull(nameof(conditionType)); return(thing.TrackedEvents.ContainsKey(conditionType.ToString())); }
/// <summary> /// Attempts to replace a thing from this cylinder with another. /// </summary> /// <param name="itemFactory">A reference to the item factory in use.</param> /// <param name="fromThing">The thing to remove from the cylinder.</param> /// <param name="toThing">The thing to add to the cylinder.</param> /// <param name="index">Optional. The index from which to replace the thing. Defaults to 0xFF, which instructs to replace the thing if found at any index.</param> /// <param name="amount">Optional. The amount of the <paramref name="fromThing"/> to replace.</param> /// <returns>A tuple with a value indicating whether the attempt was at least partially successful, and false otherwise. If the result was only partially successful, a remainder of the item may be returned.</returns> public (bool result, IThing remainderToChange) ReplaceContent(IItemFactory itemFactory, IThing fromThing, IThing toThing, byte index = 255, byte amount = 1) { itemFactory.ThrowIfNull(nameof(itemFactory)); fromThing.ThrowIfNull(nameof(fromThing)); toThing.ThrowIfNull(nameof(toThing)); IItem existingItem = null; if (index == 0xFF) { existingItem = this.Content.FirstOrDefault(i => i.ThingId == fromThing.ThingId); } else { // Attempt to get the item at that index. existingItem = index >= this.Content.Count ? null : this.Content[index]; } if (existingItem == null || fromThing.ThingId != existingItem.ThingId || existingItem.Amount < amount) { return(false, null); } this.Content.RemoveAt(index); this.Content.Insert(index, toThing as IItem); toThing.ParentCylinder = this; // We've changed an item at this index, so we notify observers. this.InvokeContentUpdated(index, toThing as IItem); return(true, null); }
/// <summary> /// Checks if the thing is exhausted. /// </summary> /// <param name="thing">The thing to check the exhaustion condition on.</param> /// <param name="type">The type of exhaustion to check for.</param> /// <returns>True if the thing has such condition, false otherwise.</returns> public static bool IsExhausted(this IThing thing, ExhaustionType type) { thing.ThrowIfNull(nameof(thing)); type.ThrowIfNull(nameof(type)); return(thing.TrackedEvents.TryGetValue(ConditionType.Exhausted.ToString(), out IEvent conditionEvent) && conditionEvent is IExhaustionCondition exhaustionCondition && exhaustionCondition.ExhaustionTimesPerType.ContainsKey(type)); }
/// <summary> /// Attempts to remove a thing from this container. /// </summary> /// <param name="thingFactory">A reference to the factory of things to use.</param> /// <param name="thing">The <see cref="IThing"/> to remove from the container.</param> /// <param name="index">Optional. The index from which to remove the <see cref="IThing"/>. Defaults to byte.MaxValue, which instructs to remove the <see cref="IThing"/> if found at any index.</param> /// <param name="amount">Optional. The amount of the <paramref name="thing"/> to remove.</param> /// <returns>A tuple with a value indicating whether the attempt was at least partially successful, and false otherwise. If the result was only partially successful, a remainder of the thing may be returned.</returns> public virtual (bool result, IThing remainder) RemoveContent(IThingFactory thingFactory, ref IThing thing, byte index = byte.MaxValue, byte amount = 1) { thingFactory.ThrowIfNull(nameof(thingFactory)); thing.ThrowIfNull(nameof(thing)); if (!(thingFactory is IItemFactory itemFactory)) { throw new ArgumentException($"The {nameof(thingFactory)} must be derived of type {nameof(IItemFactory)}."); } IItem existingItem = null; ushort thingId = thing.TypeId; if (index == byte.MaxValue) { existingItem = this.Content.FirstOrDefault(i => i.TypeId == thingId); } else { // Attempt to get the item at that index. existingItem = index >= this.Content.Count ? null : this.Content[index]; } if (existingItem == null || thing.TypeId != existingItem.TypeId || existingItem.Amount < amount) { return(false, null); } if (!existingItem.IsCumulative || existingItem.Amount == amount) { // Item has the exact amount we're looking for, just remove it. this.Content.RemoveAt(index); this.InvokeContentRemoved(index); return(true, null); } (bool success, IItem itemProduced) = existingItem.Split(itemFactory, amount); if (success) { if (itemProduced != null) { thing = itemProduced; } // We've changed an item at this index, so we notify observers. this.InvokeContentUpdated(index, existingItem); } return(success, existingItem); }
/// <summary> /// Attempts to get the position in the stack for the given <see cref="IThing"/>. /// </summary> /// <param name="thing">The thing to find.</param> /// <returns>The position in the stack for the <see cref="IThing"/>, or <see cref="byte.MaxValue"/> if its not found.</returns> public byte GetStackPositionOfThing(IThing thing) { thing.ThrowIfNull(nameof(thing)); byte n = 0; if (this.Ground != null && thing == this.Ground) { return(n); } foreach (var item in this.StayOnTopItems) { ++n; if (thing == item) { return(n); } } foreach (var item in this.StayOnBottomItems) { ++n; if (thing == item) { return(n); } } foreach (var creatureId in this.CreatureIds) { ++n; if (thing is ICreature creature && creature.Id == creatureId) { return(n); } } foreach (var item in this.Items) { ++n; if (thing == item) { return(n); } } return(byte.MaxValue); }
/// <summary> /// Calculates the remaining <see cref="TimeSpan"/> until the thing's exhaustion is over. /// </summary> /// <param name="thing">The thing to check the conditions on.</param> /// <param name="exhaustionType">The type of condition.</param> /// <param name="currentTime">The current time to calculate from.</param> /// <returns>The <see cref="TimeSpan"/> result.</returns> public static TimeSpan RemainingExhaustionTime(this IThing thing, ExhaustionType exhaustionType, DateTimeOffset currentTime) { thing.ThrowIfNull(nameof(thing)); if (!thing.IsExhausted(exhaustionType)) { return(TimeSpan.Zero); } var exhaustionCondition = thing.TrackedEvents[ConditionType.Exhausted.ToString()] as IExhaustionCondition; if (!exhaustionCondition.ExhaustionTimesPerType.TryGetValue(exhaustionType, out DateTimeOffset exhaustionEndTime)) { return(TimeSpan.Zero); } var timeLeft = exhaustionEndTime - currentTime; return(timeLeft < TimeSpan.Zero ? TimeSpan.Zero : timeLeft); }
/// <summary> /// Attempts to remove an item from this container. /// </summary> /// <param name="itemFactory">A reference to the item factory in use.</param> /// <param name="thing">The thing to remove from the cylinder, which must be an <see cref="IItem"/>.</param> /// <param name="index">Optional. The index from which to remove the thing. Defaults to 0xFF, which instructs to remove the thing if found at any index.</param> /// <param name="amount">Optional. The amount of the <paramref name="thing"/> to remove.</param> /// <returns>A tuple with a value indicating whether the attempt was at least partially successful, and false otherwise. If the result was only partially successful, a remainder of the item may be returned.</returns> public virtual (bool result, IThing remainder) RemoveContent(IItemFactory itemFactory, IThing thing, byte index = 0xFF, byte amount = 1) { itemFactory.ThrowIfNull(nameof(itemFactory)); thing.ThrowIfNull(nameof(thing)); IItem existingItem = null; if (index == 0xFF) { existingItem = this.Content.FirstOrDefault(i => i.ThingId == thing.ThingId); } else { // Attempt to get the item at that index. existingItem = index >= this.Content.Count ? null : this.Content[index]; } if (existingItem == null || thing.ThingId != existingItem.ThingId || existingItem.Amount < amount) { return(false, null); } if (!existingItem.IsCumulative || existingItem.Amount == amount) { // Item has the exact amount we're looking for, just remove it. this.Content.RemoveAt(index); this.InvokeContentRemoved(index); return(true, null); } (bool success, IItem remainderItem) = existingItem.SeparateFrom(itemFactory, amount); if (success) { // We've changed an item at this index, so we notify observers. this.InvokeContentUpdated(index, existingItem); } return(success, remainderItem); }
/// <summary> /// Attempts to replace a <see cref="IThing"/> from this container with another. /// </summary> /// <param name="thingFactory">A reference to the factory of things to use.</param> /// <param name="fromThing">The <see cref="IThing"/> to remove from the container.</param> /// <param name="toThing">The <see cref="IThing"/> to add to the container.</param> /// <param name="index">Optional. The index from which to replace the <see cref="IThing"/>. Defaults to byte.MaxValue, which instructs to replace the <see cref="IThing"/> if found at any index.</param> /// <param name="amount">Optional. The amount of the <paramref name="fromThing"/> to replace.</param> /// <returns>A tuple with a value indicating whether the attempt was at least partially successful, and false otherwise. If the result was only partially successful, a remainder of the thing may be returned.</returns> public (bool result, IThing remainderToChange) ReplaceContent(IThingFactory thingFactory, IThing fromThing, IThing toThing, byte index = byte.MaxValue, byte amount = 1) { thingFactory.ThrowIfNull(nameof(thingFactory)); fromThing.ThrowIfNull(nameof(fromThing)); toThing.ThrowIfNull(nameof(toThing)); if (!(thingFactory is IItemFactory itemFactory)) { throw new ArgumentException($"The {nameof(thingFactory)} must be derived of type {nameof(IItemFactory)}."); } IItem existingItem = null; if (index == byte.MaxValue) { existingItem = this.Content.FirstOrDefault(i => i.TypeId == fromThing.TypeId); } else { // Attempt to get the item at that index. existingItem = index >= this.Content.Count ? null : this.Content[index]; } if (existingItem == null || fromThing.TypeId != existingItem.TypeId || existingItem.Amount < amount) { return(false, null); } this.Content.RemoveAt(index); this.Content.Insert(index, toThing as IItem); toThing.ParentContainer = this; // We've changed an item at this index, so we notify observers. this.InvokeContentUpdated(index, toThing as IItem); return(true, null); }
/// <summary> /// Attempts to get the index for the given <see cref="IThing"/> within this container. /// </summary> /// <param name="thing">The thing to find.</param> /// <returns>The index for the <see cref="IThing"/> found, or <see cref="byte.MaxValue"/> if it was not found.</returns> public byte GetIndexOfThing(IThing thing) { thing.ThrowIfNull(nameof(thing)); byte i = (byte)(this.Ground != null ? 1 : 0); if (this.Ground != null && thing == this.Ground) { return(i); } foreach (var item in this.groundBorders) { if (thing == item) { return(i); } i++; } if (this.LiquidPool != null) { if (thing == this.LiquidPool) { return(i); } i++; } foreach (var item in this.stayOnTopItems) { if (thing == item) { return(i); } i++; } foreach (var item in this.stayOnBottomItems) { if (thing == item) { return(i); } i++; } foreach (var creatureOnTile in this.creaturesOnTile) { if (thing is ICreature creature && creature == creatureOnTile) { return(i); } i++; } foreach (var item in this.itemsOnTile) { if (thing == item) { return(i); } i++; } return(byte.MaxValue); }
/// <summary> /// Attempts to add an item to this container. /// </summary> /// <param name="itemFactory">A reference to the item factory in use.</param> /// <param name="thing">The thing to add to the cylinder, which must be an <see cref="IItem"/>.</param> /// <param name="index">Optional. The index at which to add the thing. Defaults to 0xFF, which instructs to add the thing at any free index.</param> /// <returns>A tuple with a value indicating whether the attempt was at least partially successful, and false otherwise. If the result was only partially successful, a remainder of the item may be returned.</returns> public override (bool result, IThing remainder) AddContent(IItemFactory itemFactory, IThing thing, byte index = 0xFF) { itemFactory.ThrowIfNull(nameof(itemFactory)); thing.ThrowIfNull(nameof(thing)); if (!(thing is IItem item)) { // Containers like this can only add items. return(false, null); } // Validate that the item being added is not a parent of this item. if (this.IsChildOf(item)) { // TODO: error message 'This is impossible'. return(false, thing); } // Find an index which falls in within the actual content boundaries. var targetIndex = index < this.Content.Count ? index : -1; // Then get an item if there is one, at that index. var existingItemAtIndex = targetIndex == -1 ? this.Content.FirstOrDefault() : this.Content[targetIndex]; (bool success, IThing remainderItem) = (false, item); if (existingItemAtIndex != null) { // We matched with an item, let's attempt to add or join with it first. if (existingItemAtIndex.IsContainer && existingItemAtIndex is IContainerItem existingContainer) { return(existingContainer.AddContent(itemFactory, remainderItem)); } else { (success, remainderItem) = existingItemAtIndex.JoinWith(itemFactory, remainderItem as IItem); if (success) { // Regardless if we're done, we've changed an item at this index, so we notify observers. this.InvokeContentUpdated((byte)targetIndex, remainderItem as IItem); } } } if (success) { // If we have partially succeeded, we need to return now. return(true, remainderItem); } if (existingItemAtIndex == null) { remainderItem.ParentCylinder = this; this.Content.Insert(0, remainderItem as IItem); this.InvokeContentAdded(remainderItem as IItem); remainderItem = null; } else { // Swap the items. this.Content.Clear(); remainderItem.ParentCylinder = this; this.Content.Insert(0, remainderItem as IItem); this.InvokeContentUpdated(0, remainderItem as IItem); remainderItem = existingItemAtIndex; } return(true, remainderItem); }
/// <summary> /// Attempts to add an item to this container. /// </summary> /// <param name="itemFactory">A reference to the item factory in use.</param> /// <param name="thing">The thing to add to the cylinder, which must be an <see cref="IItem"/>.</param> /// <param name="index">Optional. The index at which to add the thing. Defaults to 0xFF, which instructs to add the thing at any free index.</param> /// <returns>A tuple with a value indicating whether the attempt was at least partially successful, and false otherwise. If the result was only partially successful, a remainder of the item may be returned.</returns> public virtual (bool result, IThing remainder) AddContent(IItemFactory itemFactory, IThing thing, byte index = 0xFF) { itemFactory.ThrowIfNull(nameof(itemFactory)); thing.ThrowIfNull(nameof(thing)); if (!(thing is IItem item)) { // Containers like this can only add items. return(false, null); } // Validate that the item being added is not itself, or a parent of this item. if (thing == this || this.IsChildOf(item)) { // TODO: error message 'This is impossible'. return(false, thing); } // Find an index which falls in within the actual content boundaries. var targetIndex = index < this.Content.Count ? index : -1; var atCapacity = this.Capacity == this.Content.Count; // Then get an item if there is one, at that index. var existingItemAtIndex = targetIndex == -1 ? null : this.Content[targetIndex]; (bool success, IThing remainderToAdd) = (false, item); if (existingItemAtIndex != null) { // We matched with an item, let's attempt to add or join with it first. if (existingItemAtIndex.IsContainer && existingItemAtIndex is IContainerItem existingContainer) { (success, remainderToAdd) = existingContainer.AddContent(itemFactory, remainderToAdd); } else { (success, remainderToAdd) = existingItemAtIndex.JoinWith(itemFactory, remainderToAdd as IItem); if (success) { // Regardless if we're done, we've changed an item at this index, so we notify observers. if (remainderToAdd != null && !atCapacity) { targetIndex++; } this.InvokeContentUpdated((byte)targetIndex, existingItemAtIndex); } } } if (remainderToAdd == null) { // If there's nothing still waiting to be added, we're done. return(true, null); } // Now we need to add whatever is remaining to this container. if (atCapacity) { // This is full. return(success, remainderToAdd); } remainderToAdd.ParentCylinder = this; this.Content.Insert(0, remainderToAdd as IItem); this.InvokeContentAdded(remainderToAdd as IItem); return(true, null); }