예제 #1
0
        /// <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()));
        }
예제 #2
0
        /// <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);
        }
예제 #3
0
        /// <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));
        }
예제 #4
0
        /// <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);
        }
예제 #5
0
        /// <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);
        }
예제 #6
0
        /// <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);
        }
예제 #7
0
        /// <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);
        }
예제 #8
0
        /// <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);
        }
예제 #9
0
        /// <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);
        }
예제 #10
0
        /// <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);
        }
예제 #11
0
        /// <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);
        }