Exemple #1
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);
        }
Exemple #2
0
        /// <summary>
        /// Adds parsed content elements to this container.
        /// </summary>
        /// <param name="logger">A reference to the logger in use.</param>
        /// <param name="itemFactory">A reference to the item factory in use.</param>
        /// <param name="contentElements">The content elements to add.</param>
        public void AddContent(ILogger logger, IItemFactory itemFactory, IEnumerable <IParsedElement> contentElements)
        {
            logger.ThrowIfNull(nameof(logger));
            itemFactory.ThrowIfNull(nameof(itemFactory));
            contentElements.ThrowIfNull(nameof(contentElements));

            foreach (var element in contentElements)
            {
                if (element.IsFlag)
                {
                    // A flag is unexpected in this context.
                    logger.Warning($"Unexpected flag {element.Attributes?.First()?.Name}, ignoring.");

                    continue;
                }

                IItem item = itemFactory.Create((ushort)element.Id);

                if (item == null)
                {
                    logger.Warning($"Item with id {element.Id} not found in the catalog, skipping.");

                    continue;
                }

                item.SetAttributes(logger.ForContext <IItem>(), itemFactory, element.Attributes);

                // TODO: we should be able to go over capacity here.
                this.AddContent(itemFactory, item, 0xFF);
            }
        }
Exemple #3
0
        /// <summary>
        /// Initializes a new instance of the <see cref="SectorMapLoader"/> class.
        /// </summary>
        /// <param name="logger">A reference to the logger instance in use.</param>
        /// <param name="itemFactory">A reference to the item factory.</param>
        /// <param name="tileFactory">A reference to the tile factory.</param>
        /// <param name="sectorMapLoaderOptions">The options for this map loader.</param>
        public SectorMapLoader(
            ILogger <SectorMapLoader> logger,
            IItemFactory itemFactory,
            ITileFactory tileFactory,
            IOptions <SectorMapLoaderOptions> sectorMapLoaderOptions)
        {
            logger.ThrowIfNull(nameof(logger));
            itemFactory.ThrowIfNull(nameof(itemFactory));
            tileFactory.ThrowIfNull(nameof(tileFactory));
            sectorMapLoaderOptions.ThrowIfNull(nameof(sectorMapLoaderOptions));

            DataAnnotationsValidator.ValidateObjectRecursive(sectorMapLoaderOptions.Value);

            this.mapDirInfo = new DirectoryInfo(sectorMapLoaderOptions.Value.LiveMapDirectory);

            if (!this.mapDirInfo.Exists)
            {
                throw new ApplicationException($"The map directory '{sectorMapLoaderOptions.Value.LiveMapDirectory}' could not be found.");
            }

            this.logger      = logger;
            this.itemFactory = itemFactory;
            this.tileFactory = tileFactory;

            this.totalTileCount   = 1;
            this.totalLoadedCount = default;

            this.loadLock = new object();

            this.sectorsLengthX = 1 + SectorXMax - SectorXMin;
            this.sectorsLengthY = 1 + SectorYMax - SectorYMin;
            this.sectorsLengthZ = 1 + SectorZMax - SectorZMin;

            this.sectorsLoaded = new bool[this.sectorsLengthX, this.sectorsLengthY, this.sectorsLengthZ];
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="PredefinedItemSet_v772"/> class.
        /// </summary>
        /// <param name="itemFactory">A reference to the item factory in use.</param>
        public PredefinedItemSet_v772(IItemFactory itemFactory)
        {
            itemFactory.ThrowIfNull(nameof(itemFactory));

            this.itemFactory = itemFactory;

            this.InitializeItemSet();
        }
Exemple #5
0
        /// <summary>
        /// Initializes a new instance of the <see cref="CreatureFactory"/> class.
        /// </summary>
        /// <param name="applicationContext">A reference to the application context.</param>
        /// <param name="itemFactory">A reference to the item factory in use.</param>
        public CreatureFactory(IApplicationContext applicationContext, IItemFactory itemFactory)
        {
            applicationContext.ThrowIfNull(nameof(applicationContext));
            itemFactory.ThrowIfNull(nameof(itemFactory));

            this.ApplicationContext = applicationContext;
            this.ItemFactory        = itemFactory;
        }
Exemple #6
0
        /// <summary>
        /// Initializes a new instance of the <see cref="GrassOnlyDummyMapLoader"/> class.
        /// </summary>
        /// <param name="itemFactory">A reference to the item factory.</param>
        /// <param name="tileFactory">A reference to the tile factory.</param>
        public GrassOnlyDummyMapLoader(IItemFactory itemFactory, ITileFactory tileFactory)
        {
            itemFactory.ThrowIfNull(nameof(itemFactory));
            tileFactory.ThrowIfNull(nameof(tileFactory));

            this.itemFactory = itemFactory;
            this.tileFactory = tileFactory;
        }
Exemple #7
0
        /// <summary>
        /// Initializes a new instance of the <see cref="CreatureFactory"/> class.
        /// </summary>
        /// <param name="monsterLoader">A reference to the monster type loader in use.</param>
        /// <param name="itemFactory">A reference to the item factory in use.</param>
        public CreatureFactory(IMonsterTypeLoader monsterLoader, IItemFactory itemFactory)
        {
            monsterLoader.ThrowIfNull(nameof(monsterLoader));
            itemFactory.ThrowIfNull(nameof(itemFactory));

            this.monsterTypeCatalog = monsterLoader.LoadTypes();

            this.ItemFactory = itemFactory;
        }
Exemple #8
0
        /// <summary>
        /// Initializes a new instance of the <see cref="GrassOnlyDummyMapLoader"/> class.
        /// </summary>
        /// <param name="itemFactory">A reference to the item factory.</param>
        /// <param name="tileFactory">A reference to the tile factory.</param>
        public GrassOnlyDummyMapLoader(IItemFactory itemFactory, ITileFactory tileFactory)
        {
            itemFactory.ThrowIfNull(nameof(itemFactory));
            tileFactory.ThrowIfNull(nameof(tileFactory));

            this.ItemFactory = itemFactory;
            this.TileFactory = tileFactory;

            this.tilesAndLocations = new ConcurrentDictionary <Location, ITile>();
        }
Exemple #9
0
        /// <summary>
        /// Initializes a new instance of the <see cref="Game"/> class.
        /// </summary>
        /// <param name="logger">A reference to the logger in use.</param>
        /// <param name="applicationContext">A reference to the application context.</param>
        /// <param name="mapDescriptor">A reference to the map descriptor in use.</param>
        /// <param name="map">A reference to the map.</param>
        /// <param name="creatureManager">A reference to the creature manager in use.</param>
        /// <param name="itemFactory">A reference to the item factory in use.</param>
        /// <param name="creatureFactory">A reference to the creature factory in use.</param>
        /// <param name="containerManager">A reference to the container manager in use.</param>
        /// <param name="pathFinderAlgo">A reference to the path finding algorithm in use.</param>
        /// <param name="predefinedItemSet">A reference to the predefined item set declared.</param>
        /// <param name="monsterSpawnsLoader">A reference to the monster spawns loader.</param>
        /// <param name="scheduler">A reference to the global scheduler instance.</param>
        public Game(
            ILogger logger,
            IApplicationContext applicationContext,
            IMapDescriptor mapDescriptor,
            IMap map,
            ICreatureManager creatureManager,
            IItemFactory itemFactory,
            ICreatureFactory creatureFactory,
            IContainerManager containerManager,
            IPathFinder pathFinderAlgo,
            IPredefinedItemSet predefinedItemSet,
            IMonsterSpawnLoader monsterSpawnsLoader,
            IScheduler scheduler)
        {
            logger.ThrowIfNull(nameof(logger));
            applicationContext.ThrowIfNull(nameof(applicationContext));
            mapDescriptor.ThrowIfNull(nameof(mapDescriptor));
            map.ThrowIfNull(nameof(map));
            creatureManager.ThrowIfNull(nameof(creatureManager));
            itemFactory.ThrowIfNull(nameof(itemFactory));
            creatureFactory.ThrowIfNull(nameof(creatureFactory));
            containerManager.ThrowIfNull(nameof(containerManager));
            pathFinderAlgo.ThrowIfNull(nameof(pathFinderAlgo));
            predefinedItemSet.ThrowIfNull(nameof(predefinedItemSet));
            monsterSpawnsLoader.ThrowIfNull(nameof(monsterSpawnsLoader));
            scheduler.ThrowIfNull(nameof(scheduler));

            this.logger             = logger.ForContext <Game>();
            this.applicationContext = applicationContext;
            this.mapDescriptor      = mapDescriptor;
            this.map               = map;
            this.creatureManager   = creatureManager;
            this.itemFactory       = itemFactory;
            this.creatureFactory   = creatureFactory;
            this.containerManager  = containerManager;
            this.pathFinder        = pathFinderAlgo;
            this.predefinedItemSet = predefinedItemSet;
            this.scheduler         = scheduler;

            // Initialize game vars.
            this.worldInfo = new WorldInformation()
            {
                Status     = WorldState.Loading,
                LightColor = (byte)LightColors.White,
                LightLevel = (byte)LightLevels.World,
            };

            // Load the spawns
            this.monsterSpawns = monsterSpawnsLoader.LoadSpawns();

            // Hook some event handlers.
            this.scheduler.EventFired += this.ProcessFiredEvent;
            this.map.WindowLoaded     += this.OnMapWindowLoaded;
        }
Exemple #10
0
        public static IList <(Location, ITile)> ReadSector(ILogger logger, IItemFactory itemFactory, string fileName, string sectorFileContents, ushort xOffset, ushort yOffset, sbyte z)
        {
            itemFactory.ThrowIfNull(nameof(itemFactory));

            var loadedTilesList = new List <(Location, ITile)>();

            var lines = sectorFileContents.Split("\r\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);

            foreach (var readLine in lines)
            {
                var inLine = readLine?.Split(new[] { CommentSymbol }, 2).FirstOrDefault();

                // ignore comments and empty lines.
                if (string.IsNullOrWhiteSpace(inLine))
                {
                    continue;
                }

                var data = inLine.Split(new[] { SectorSeparator }, 2);

                if (data.Length != 2)
                {
                    throw new InvalidDataException($"Malformed line [{inLine}] in sector file: [{fileName}]");
                }

                var tileInfo = data[0].Split(new[] { PositionSeparator }, 2);
                var tileData = data[1];

                var location = new Location
                {
                    X = (ushort)(xOffset + Convert.ToUInt16(tileInfo[0])),
                    Y = (ushort)(yOffset + Convert.ToUInt16(tileInfo[1])),
                    Z = z,
                };

                // start off with a tile that has no ground in it.
                ITile newTile = new Tile(location, null);

                newTile.AddContent(logger, itemFactory, CipFileParser.Parse(tileData));

                loadedTilesList.Add((location, newTile));
            }

            // TODO: proper logging.
            // Console.WriteLine($"Sector file {sectorFileContents.Name}: {loadedTilesList.Count} tiles loaded.");
            return(loadedTilesList);
        }
Exemple #11
0
        /// <summary>
        /// Attempts to split this item into two based on the amount provided.
        /// </summary>
        /// <param name="itemFactory">A reference to the item factory in use.</param>
        /// <param name="amount">The amount of the item to split.</param>
        /// <returns>True if the operation was successful, false otherwise, along with the item produced, if any.</returns>
        public (bool success, IItem itemProduced) Split(IItemFactory itemFactory, byte amount)
        {
            itemFactory.ThrowIfNull(nameof(itemFactory));

            if (!this.IsCumulative || this.Amount <= amount)
            {
                return(false, null);
            }

            this.Amount -= amount;

            var remainder = this.Clone() as Item;

            remainder.Amount = amount;

            return(true, remainder);
        }
Exemple #12
0
        /// <summary>
        /// Attempts to split this item into two based on the amount provided.
        /// </summary>
        /// <param name="itemFactory">A reference to the item factory in use.</param>
        /// <param name="amount">The amount of the item to split.</param>
        /// <returns>True if the operation was successful, false otherwise, along with the item produced, if any.</returns>
        public (bool success, IItem itemProduced) Split(IItemFactory itemFactory, byte amount)
        {
            itemFactory.ThrowIfNull(nameof(itemFactory));

            if (!this.IsCumulative || this.Amount <= amount)
            {
                return(false, null);
            }

            this.Amount -= amount;

            var remainder = itemFactory.CreateItem(new ItemCreationArguments()
            {
                TypeId = this.Type.TypeId
            });

            remainder.Amount = amount;

            return(true, remainder);
        }
Exemple #13
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);
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="ConditionContext"/> class.
        /// </summary>
        /// <param name="logger">A reference to the logger in use.</param>
        /// <param name="mapDescriptor">A reference to the map descriptor in use.</param>
        /// <param name="map">A reference to the map in use.</param>
        /// <param name="creatureFinder">A reference to the creature finder in use.</param>
        /// <param name="itemFactory">A reference to the item factory in use.</param>
        /// <param name="scheduler">A reference to the scheduler instance.</param>
        public ConditionContext(
            ILogger logger,
            IMapDescriptor mapDescriptor,
            IMap map,
            ICreatureFinder creatureFinder,
            IItemFactory itemFactory,
            IScheduler scheduler)
            : base(logger, () => scheduler.CurrentTime)
        {
            mapDescriptor.ThrowIfNull(nameof(mapDescriptor));
            map.ThrowIfNull(nameof(map));
            creatureFinder.ThrowIfNull(nameof(creatureFinder));
            itemFactory.ThrowIfNull(nameof(itemFactory));
            scheduler.ThrowIfNull(nameof(scheduler));

            this.MapDescriptor  = mapDescriptor;
            this.Map            = map;
            this.CreatureFinder = creatureFinder;
            this.ItemFactory    = itemFactory;
            this.Scheduler      = scheduler;
        }
Exemple #15
0
        /// <summary>
        /// Initializes a new instance of the <see cref="ConditionContext"/> class.
        /// </summary>
        /// <param name="logger">A reference to the logger in use.</param>
        /// <param name="map">A reference to the map in use.</param>
        /// <param name="creatureFinder">A reference to the creature finder in use.</param>
        /// <param name="gameOperationsApi">A reference to the game operations api.</param>
        /// <param name="itemFactory">A reference to the item factory in use.</param>
        /// <param name="scheduler">A reference to the scheduler instance.</param>
        public ConditionContext(
            ILogger logger,
            IMap map,
            IGameOperationsApi gameOperationsApi,
            ICreatureFinder creatureFinder,
            IItemFactory itemFactory,
            IScheduler scheduler)
            : base(logger, () => scheduler.CurrentTime)
        {
            map.ThrowIfNull(nameof(map));
            gameOperationsApi.ThrowIfNull(nameof(gameOperationsApi));
            creatureFinder.ThrowIfNull(nameof(creatureFinder));
            itemFactory.ThrowIfNull(nameof(itemFactory));
            scheduler.ThrowIfNull(nameof(scheduler));

            this.Map            = map;
            this.GameApi        = gameOperationsApi;
            this.CreatureFinder = creatureFinder;
            this.ItemFactory    = itemFactory;
            this.Scheduler      = scheduler;
        }
Exemple #16
0
        /// <summary>
        /// Initializes a new instance of the <see cref="SectorMapLoader"/> class.
        /// </summary>
        /// <param name="logger">A reference to the logger instance in use.</param>
        /// <param name="creatureFinder">A reference to the creature finder.</param>
        /// <param name="itemFactory">A reference to the item factory.</param>
        /// <param name="sectorMapLoaderOptions">The options for this map loader.</param>
        public SectorMapLoader(ILogger logger, ICreatureFinder creatureFinder, IItemFactory itemFactory, IOptions <SectorMapLoaderOptions> sectorMapLoaderOptions)
        {
            logger.ThrowIfNull(nameof(logger));
            creatureFinder.ThrowIfNull(nameof(creatureFinder));
            itemFactory.ThrowIfNull(nameof(itemFactory));
            sectorMapLoaderOptions.ThrowIfNull(nameof(sectorMapLoaderOptions));

            this.mapDirInfo = new DirectoryInfo(sectorMapLoaderOptions.Value.LiveMapDirectory);

            this.Logger         = logger.ForContext <SectorMapLoader>();
            this.CreatureFinder = creatureFinder;
            this.ItemFactory    = itemFactory;

            this.totalTileCount   = 1;
            this.totalLoadedCount = default;

            this.sectorsLengthX = 1 + SectorXMax - SectorXMin;
            this.sectorsLengthY = 1 + SectorYMax - SectorYMin;
            this.sectorsLengthZ = 1 + SectorZMax - SectorZMin;

            this.sectorsLoaded = new bool[this.sectorsLengthX, this.sectorsLengthY, this.sectorsLengthZ];
        }
Exemple #17
0
        /// <summary>
        /// Initializes a new instance of the <see cref="OperationContext"/> class.
        /// </summary>
        /// <param name="logger">A reference to the logger in use.</param>
        /// <param name="mapDescriptor">A reference to the map descriptor in use.</param>
        /// <param name="map">A reference to the map in use.</param>
        /// <param name="creatureFinder">A reference to the creature finder in use.</param>
        /// <param name="itemFactory">A reference to the item factory in use.</param>
        /// <param name="creatureFactory">A reference to the creature factory in use.</param>
        /// <param name="containerManager">A reference to the container manager in use.</param>
        /// <param name="gameOperationsApi">A reference to the game operations api.</param>
        /// <param name="combatOperationsApi">A reference to the combat operations api.</param>
        /// <param name="pathFinderAlgo">A reference to the path finding algorithm in use.</param>
        /// <param name="predefinedItemSet">A reference to the predefined item set declared.</param>
        /// <param name="scheduler">A reference to the scheduler instance.</param>
        public OperationContext(
            ILogger logger,
            IMapDescriptor mapDescriptor,
            IMap map,
            ICreatureFinder creatureFinder,
            IItemFactory itemFactory,
            ICreatureFactory creatureFactory,
            IContainerManager containerManager,
            IGameOperationsApi gameOperationsApi,
            ICombatOperationsApi combatOperationsApi,
            IPathFinder pathFinderAlgo,
            IPredefinedItemSet predefinedItemSet,
            IScheduler scheduler)
            : base(logger, () => scheduler.CurrentTime)
        {
            mapDescriptor.ThrowIfNull(nameof(mapDescriptor));
            map.ThrowIfNull(nameof(map));
            creatureFinder.ThrowIfNull(nameof(creatureFinder));
            itemFactory.ThrowIfNull(nameof(itemFactory));
            creatureFactory.ThrowIfNull(nameof(creatureFactory));
            containerManager.ThrowIfNull(nameof(containerManager));
            gameOperationsApi.ThrowIfNull(nameof(gameOperationsApi));
            combatOperationsApi.ThrowIfNull(nameof(combatOperationsApi));
            pathFinderAlgo.ThrowIfNull(nameof(pathFinderAlgo));
            predefinedItemSet.ThrowIfNull(nameof(predefinedItemSet));
            scheduler.ThrowIfNull(nameof(scheduler));

            this.MapDescriptor     = mapDescriptor;
            this.Map               = map;
            this.CreatureFinder    = creatureFinder;
            this.ItemFactory       = itemFactory;
            this.CreatureFactory   = creatureFactory;
            this.ContainerManager  = containerManager;
            this.GameApi           = gameOperationsApi;
            this.CombatApi         = combatOperationsApi;
            this.PathFinder        = pathFinderAlgo;
            this.PredefinedItemSet = predefinedItemSet;
            this.Scheduler         = scheduler;
        }
Exemple #18
0
        /// <summary>
        /// Attempts to add content to the first possible parent container that accepts it, on a chain of parent containers.
        /// </summary>
        /// <param name="thingContainer">The first thing container to add to.</param>
        /// <param name="itemFactory">The factory of items to use in case of splitting.</param>
        /// <param name="remainderItem">The remainder content to add, which overflows to the next container in the chain.</param>
        /// <param name="addIndex">The index at which to attempt to add, only for the first attempted container.</param>
        /// <param name="includeTileAsFallback">Optional. A value for whether to include tiles in the fallback chain.</param>
        /// <returns>True if the content was successfully added, false otherwise.</returns>
        public static bool AddItemToContainerRecursively(
            this IItemsContainer thingContainer,
            IItemFactory itemFactory,
            ref IItem remainderItem,
            byte addIndex = byte.MaxValue,
            bool includeTileAsFallback = true)
        {
            thingContainer.ThrowIfNull(nameof(thingContainer));
            itemFactory.ThrowIfNull(nameof(itemFactory));

            const byte FallbackIndex = byte.MaxValue;

            bool success      = false;
            bool firstAttempt = true;

            foreach (var targetContainer in thingContainer.GetParentContainerHierarchy(includeTileAsFallback))
            {
                IThing lastAddedThing = remainderItem;

                if (!success)
                {
                    (success, remainderItem) = targetContainer.AddItem(itemFactory, remainderItem, firstAttempt ? addIndex : FallbackIndex);
                }
                else if (remainderItem != null)
                {
                    (success, remainderItem) = targetContainer.AddItem(itemFactory, remainderItem);
                }

                firstAttempt = false;

                if (success && remainderItem == null)
                {
                    break;
                }
            }

            return(success);
        }
Exemple #19
0
        /// <summary>
        /// Adds parsed content elements to this tile.
        /// </summary>
        /// <param name="logger">A reference to the logger in use.</param>
        /// <param name="itemFactory">A reference to the item factory in use.</param>
        /// <param name="contentElements">The content elements to add.</param>
        public void AddContent(ILogger logger, IItemFactory itemFactory, IEnumerable <IParsedElement> contentElements)
        {
            logger.ThrowIfNull(nameof(logger));
            itemFactory.ThrowIfNull(nameof(itemFactory));
            contentElements.ThrowIfNull(nameof(contentElements));

            // load and add tile flags and contents.
            foreach (var e in contentElements)
            {
                foreach (var attribute in e.Attributes)
                {
                    if (attribute.Name.Equals("Content"))
                    {
                        if (attribute.Value is IEnumerable <IParsedElement> elements)
                        {
                            var thingStack = new Stack <IThing>();

                            foreach (var element in elements)
                            {
                                if (element.IsFlag)
                                {
                                    // A flag is unexpected in this context.
                                    logger.Warning($"Unexpected flag {element.Attributes?.First()?.Name}, ignoring.");

                                    continue;
                                }

                                IItem item = itemFactory.Create((ushort)element.Id);

                                if (item == null)
                                {
                                    logger.Warning($"Item with id {element.Id} not found in the catalog, skipping.");

                                    continue;
                                }

                                item.SetAttributes(logger.ForContext <IItem>(), itemFactory, element.Attributes);

                                thingStack.Push(item);
                            }

                            // Add them in reversed order.
                            while (thingStack.Count > 0)
                            {
                                var thing = thingStack.Pop();

                                this.AddContent(itemFactory, thing);

                                thing.ParentCylinder = this;
                            }
                        }
                    }
                    else
                    {
                        // it's a flag
                        if (Enum.TryParse(attribute.Name, out TileFlag flagMatch))
                        {
                            this.SetFlag(flagMatch);
                        }
                        else
                        {
                            logger.Warning($"Unknown flag [{attribute.Name}] found on tile at location {this.Location}.");
                        }
                    }
                }
            }
        }
Exemple #20
0
        /// <summary>
        /// Attempts to add an item to this tile.
        /// </summary>
        /// <param name="itemFactory">A reference to the item factory in use.</param>
        /// <param name="thing">The thing to add to the tile.</param>
        /// <param name="index">Optional. The index at which to add the thing. Defaults to 0xFF, which instructs to add the thing at any index.</param>
        /// <returns>A tuple with a value indicating whether the attempt was successful, and false otherwise. The remainder part of the result is not in use for this implementation, as any cummulative remainder is recursively added to the tile.</returns>
        public (bool result, IThing remainder) AddContent(IItemFactory itemFactory, IThing thing, byte index = 0xFF)
        {
            itemFactory.ThrowIfNull(nameof(itemFactory));

            if (thing is ICreature creature)
            {
                lock (this.creatureIdsOnTile)
                {
                    this.creatureIdsOnTile.Push(creature.Id);
                }
            }
            else if (thing is IItem item)
            {
                if (item.IsGround)
                {
                    this.Ground = item;
                }
                else if (item.StaysOnTop)
                {
                    lock (this.stayOnTopItems)
                    {
                        this.stayOnTopItems.Push(item);
                    }
                }
                else if (item.StaysOnBottom)
                {
                    lock (this.stayOnBottomItems)
                    {
                        this.stayOnBottomItems.Push(item);
                    }
                }
                else
                {
                    lock (this.itemsOnTile)
                    {
                        var remainingAmountToAdd = item.Amount;

                        while (remainingAmountToAdd > 0)
                        {
                            if (!item.IsCumulative)
                            {
                                this.itemsOnTile.Push(item);
                                break;
                            }

                            var existingItem = this.itemsOnTile.Count > 0 ? this.itemsOnTile.Peek() as IItem : null;

                            // Check if there is an existing top item and if it is of the same type.
                            if (existingItem == null || existingItem.Type != item.Type || existingItem.Amount >= IItem.MaximumAmountOfCummulativeItems)
                            {
                                this.itemsOnTile.Push(item);
                                break;
                            }

                            remainingAmountToAdd += existingItem.Amount;

                            // Modify the existing item with the new amount, or the maximum permitted.
                            var newExistingAmount = Math.Min(remainingAmountToAdd, IItem.MaximumAmountOfCummulativeItems);

                            existingItem.SetAmount(newExistingAmount);

                            remainingAmountToAdd -= newExistingAmount;

                            if (remainingAmountToAdd == 0)
                            {
                                break;
                            }

                            item = itemFactory.Create(item.Type.TypeId);

                            item.SetAmount(remainingAmountToAdd);

                            item.ParentCylinder = this;
                        }
                    }
                }

                // Update the tile's version so that it invalidates the cache.
                // TOOD: if we start caching creatures, move to outer scope.
                this.LastModified = DateTimeOffset.UtcNow;
            }

            if (thing != null)
            {
                thing.ParentCylinder = this;
            }

            return(true, null);
        }
Exemple #21
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);
        }
Exemple #22
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);
        }
Exemple #23
0
        /// <summary>
        /// Attempts to add an <see cref="IItem"/> to this container.
        /// </summary>
        /// <param name="itemFactory">A reference to a factory of items. Used in case the item can only partially be added.</param>
        /// <param name="itemToAdd">The item to add to the container.</param>
        /// <param name="atIndex">Optional. The index at which to add the item. Defaults to a value of <see cref="byte.MaxValue"/>, which means to try adding 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 (bool result, IItem remainder) AddItem(IItemFactory itemFactory, IItem itemToAdd, byte atIndex = byte.MaxValue)
        {
            itemFactory.ThrowIfNull(nameof(itemFactory));

            lock (this.tileLock)
            {
                if (itemToAdd.IsGround)
                {
                    this.Ground = itemToAdd;
                }
                else if (itemToAdd.IsGroundFix)
                {
                    this.groundBorders.Push(itemToAdd);
                }
                else if (itemToAdd.IsLiquidPool)
                {
                    this.LiquidPool = itemToAdd;
                }
                else if (itemToAdd.StaysOnTop)
                {
                    this.stayOnTopItems.Push(itemToAdd);
                }
                else if (itemToAdd.StaysOnBottom)
                {
                    this.stayOnBottomItems.Push(itemToAdd);
                }
                else
                {
                    var remainingAmountToAdd = itemToAdd.Amount;

                    while (remainingAmountToAdd > 0)
                    {
                        if (!itemToAdd.IsCumulative)
                        {
                            this.itemsOnTile.Push(itemToAdd);
                            break;
                        }

                        var existingItem = this.itemsOnTile.Count > 0 ? this.itemsOnTile.Peek() : null;

                        // Check if there is an existing top itemToAdd and if it is of the same type.
                        if (existingItem == null || existingItem.Type != itemToAdd.Type || existingItem.Amount >= ItemConstants.MaximumAmountOfCummulativeItems)
                        {
                            this.itemsOnTile.Push(itemToAdd);
                            break;
                        }

                        remainingAmountToAdd += existingItem.Amount;

                        // Modify the existing itemToAdd with the new amount, or the maximum permitted.
                        var newExistingAmount = Math.Min(remainingAmountToAdd, ItemConstants.MaximumAmountOfCummulativeItems);

                        existingItem.Amount = newExistingAmount;

                        remainingAmountToAdd -= newExistingAmount;

                        if (remainingAmountToAdd == 0)
                        {
                            break;
                        }

                        itemToAdd = itemToAdd.Clone();

                        itemToAdd.Amount = remainingAmountToAdd;

                        itemToAdd.ParentContainer = this;
                    }
                }

                // Update the tile's version so that it invalidates the cache.
                this.LastModified = DateTimeOffset.UtcNow;
            }

            this.ItemAdded?.Invoke(this, itemToAdd);

            return(true, null);
        }