Пример #1
0
        /// <summary>
        /// Calculate drops from a specific loot table.
        /// </summary>
        /// <param name="table">The table to be evaluated.</param>
        /// <returns>Loot structure containing Xp/Gold/List of items to be awarded.</returns>
        public static Loot CalculateTable(Xml.LootTable table)
        {
            var tableLoot = new Loot(0, 0);

            if (table.Gold != null)
            {
                if (table.Gold.Max != 0)
                {
                    tableLoot.Gold += RollBetween(table.Gold.Min, table.Gold.Max);
                }
                else
                {
                    tableLoot.Gold += table.Gold.Min;
                }
                GameLog.SpawnInfo("Processing loot: added {Gold} gp", tableLoot.Gold);
            }
            if (table.Xp != null)
            {
                if (table.Xp.Max != 0)
                {
                    tableLoot.Xp += RollBetween(table.Xp.Min, table.Xp.Max);
                }
                else
                {
                    tableLoot.Xp += table.Xp.Min;
                }
                GameLog.SpawnInfo("Processing loot: added {Xp} xp", tableLoot.Xp);
            }
            // Handle items now
            if (table.Items != null)
            {
                foreach (var itemlist in table.Items)
                {
                    tableLoot.Items.AddRange(CalculateItems(itemlist));
                }
            }
            else
            {
                GameLog.SpawnWarning("Loot table is null!");
            }
            return(tableLoot);
        }
Пример #2
0
        public void Spawn(Xml.SpawnGroup spawnGroup)
        {
            foreach (var map in spawnGroup.Maps)
            {
                if (map.Disabled)
                {
                    continue;
                }
                try
                {
                    var spawnMap = Game.World.WorldData.Get <Map>(map.Id);
                    GameLog.SpawnDebug("Spawn: calculating {0}", spawnMap.Name);
                    var monsterList  = spawnMap.Objects.OfType <Monster>().ToList();
                    var monsterCount = monsterList.Count;

                    // If there is no limit specified, we want a reasonable limit, which we consider to be 1/10th of total
                    // number of map tiles

                    var spawnLimit = map.Limit == 0 ? (spawnMap.X * spawnMap.Y) / 10 : map.Limit;

                    if (monsterCount > spawnLimit)
                    {
                        if (spawnMap.SpawnDebug)
                        {
                            GameLog.SpawnInfo($"Spawn: {map.Name}: not spawning, mob count is {monsterCount}, limit is {spawnLimit}");
                        }
                        continue;
                    }

                    var since = DateTime.Now - map.LastSpawn;
                    if (since.TotalSeconds < map.Interval)
                    {
                        if (spawnMap.SpawnDebug)
                        {
                            GameLog.SpawnInfo($"Spawn: {map.Name}: not spawning, last spawn was {since.TotalSeconds} ago, interval {map.Interval}");
                        }
                        continue;
                    }

                    map.LastSpawn = DateTime.Now;

                    var thisSpawn = _random.Next(map.MinSpawn, map.MaxSpawn + 1);

                    GameLog.SpawnInfo($"Spawn: {map.Name}: spawning {thisSpawn} mobs ");

                    for (var i = 0; i < thisSpawn; i++)
                    {
                        var spawn = spawnGroup.Spawns.PickRandom(true);

                        if (spawn == null)
                        {
                            GameLog.SpawnError("Spawngroup empty, skipping");
                            break;
                        }

                        var creature = _creatures.FirstOrDefault(x => x.Name == spawn.Base);

                        if (creature is default(Xml.Creature))
                        {
                            GameLog.SpawnError($"Base monster {spawn.Base} not found");
                            break;
                        }

                        var newSpawnLoot = LootBox.CalculateLoot(spawn);

                        if (spawnMap.SpawnDebug)
                        {
                            GameLog.SpawnInfo("Spawn {name}, map {map}: {Xp} xp, {Gold} gold, items {Items}", spawn.Base, map.Name, newSpawnLoot.Xp, newSpawnLoot.Gold,
                                              string.Join(',', newSpawnLoot.Items));
                        }

                        var baseMob = new Monster(creature, spawn, map.Id, newSpawnLoot);
                        var mob     = (Monster)baseMob.Clone();
                        var xcoord  = 0;
                        var ycoord  = 0;

                        if (map.Coordinates.Count > 0)
                        {
                            // TODO: optimize / improve
                            foreach (var coord in map.Coordinates)
                            {
                                if (spawnMap.EntityTree.GetObjects(new System.Drawing.Rectangle(coord.X, coord.Y, 1, 1)).Where(e => e is Creature).Count() == 0)
                                {
                                    xcoord = coord.X;
                                    ycoord = coord.Y;
                                    break;
                                }
                            }
                        }
                        else
                        {
                            do
                            {
                                xcoord = _random.Next(0, spawnMap.X);
                                ycoord = _random.Next(0, spawnMap.Y);
                            } while (spawnMap.IsWall[xcoord, ycoord]);
                        }
                        mob.X = (byte)xcoord;
                        mob.Y = (byte)ycoord;
                        if (spawnMap.SpawnDebug)
                        {
                            GameLog.SpawnInfo($"Spawn: spawning {mob.Name} on {spawnMap.Name}");
                        }
                        SpawnMonster(mob, spawnMap);
                    }
                }
                catch (Exception e)
                {
                    Game.ReportException(e);
                    GameLog.SpawnError(e, "Spawngroup {Filename}: disabled map {Name} due to error", spawnGroup.Filename, map.Name);
                    map.Disabled = true;
                    continue;
                }
            }
        }
Пример #3
0
        /// <summary>
        /// Given a list of items in a loot table, return the items awarded.
        /// </summary>
        /// <param name="list">LootTableItemList containing items</param>
        /// <returns>List of items</returns>
        public static List <string> CalculateItems(Xml.LootTableItemList list)
        {
            // Ordinarily, return one item from the list.
            var rolls    = CalculateSuccessfulRolls(list.Rolls, list.Chance);
            var loot     = new List <Xml.LootItem>();
            var itemList = new List <ItemObject>();

            // First, process any "always" items, which always drop when the container fires
            foreach (var item in list.Item.Where(i => i.Always))
            {
                GameLog.SpawnInfo("Processing loot: added always item {item}", item.Value);
                loot.Add(item);
            }
            var totalRolls = 0;

            // Process the rest of the rolls now
            do
            {
                // Get a random item from the list
                var item = list.Item.Where(i => !i.Always).PickRandom();
                // As soon as we get an item from our table, we've "rolled"; we'll add another roll below if needed
                rolls--;

                // Check uniqueness. If something has already dropped, don't drop it again, and reroll
                if (item.Unique && loot.Contains(item))
                {
                    rolls++;
                    GameLog.SpawnInfo("Processing loot: added duplicate unique item {item}. Rerolling", item.Value);
                    continue;
                }

                // Check max quantity. If it is exceeded, reroll
                if (item.Max > 0 && loot.Where(i => i.Value == item.Value).Count() >= item.Max)
                {
                    rolls++;
                    GameLog.SpawnInfo("Processing loot: added over max quantity for {item}. Rerolling", item.Value);
                    continue;
                }

                // If quantity and uniqueness are good, add the item
                loot.Add(item);
                GameLog.SpawnInfo("Processing loot: added {item}", item.Value);
                totalRolls++;
                // As a check against something incredibly stupid in XML, we only allow a maximum of
                // 100 rolls
                if (totalRolls > 100)
                {
                    GameLog.SpawnInfo("Processing loot: maximum number of rolls exceeded..?");
                    throw new LootRecursionError("Maximum number of rolls (100) exceeded!");
                }
            }while (rolls > 0);

            // Now we have the canonical droplist, which needs resolving into Items

            foreach (var lootitem in loot)
            {
                // Does the base item exist?
                var xmlItemList = Game.World.WorldData.FindItem(lootitem.Value);
                // Don't handle the edge case of multiple genders .... yet
                if (xmlItemList.Count != 0)
                {
                    var xmlItem = xmlItemList.First();
                    // Handle variants.
                    // If multiple variants are specified, we pick one at random
                    if (lootitem.Variants.Count() > 0)
                    {
                        var lootedVariant = lootitem.Variants.PickRandom();
                        if (xmlItem.Variants.TryGetValue(lootedVariant, out List <Xml.Item> variantItems))
                        {
                            itemList.Add(Game.World.CreateItem(variantItems.PickRandom().Id));
                        }
                        else
                        {
                            GameLog.SpawnError("Spawn loot calculation: variant group {name} not found", lootedVariant);
                        }
                    }
                    else
                    {
                        itemList.Add(Game.World.CreateItem(xmlItem.Id));
                    }
                }
                else
                {
                    GameLog.SpawnError("Spawn loot calculation: item {name} not found!", lootitem.Value);
                }
            }
            // We store loot as strings inside mobs to avoid having tens or hundreds of thousands of ItemObjects or
            // Items lying around - they're made into real objects at the time of mob death
            if (itemList.Count > 0)
            {
                return(itemList.Where(x => x != null).Select(y => y.Name).ToList());
            }
            return(new List <String>());
        }
Пример #4
0
        /// <summary>
        /// Calculate loot for a given spawn.
        /// </summary>
        /// <param name="spawn">The spawn we will use to calculate Loot.</param>
        /// <returns>A Loot struct with XP, gold and items, if any</returns>
        public static Loot CalculateLoot(Xml.Spawn spawn)
        {
            // Loot calculations are not particularly complex but have a lot of components:
            // Spawns can have loot sets, loot tables, or both.
            // We resolve sets first, then tables. We keep a running tab of gold drops.
            // Lastly, we return a Loot struct with our calculations.

            var loot   = new Loot(0, 0);
            var tables = new List <Xml.LootTable>();

            // Assign base XP
            loot.Xp = spawn.Loot.Xp;
            // Sets
            foreach (var set in spawn.Loot?.Set ?? new List <Xml.LootImport>())
            {
                // Is the set present?
                GameLog.SpawnInfo("Processing loot set {Name}", set.Name);
                if (Game.World.WorldData.TryGetValueByIndex(set.Name, out Xml.LootSet lootset))
                {
                    // Set is present, does it fire?
                    // Chance is implemented as a decimalized percentage, e.g. 0.08 = 8% chance

                    // If rolls == 0, all tables fire
                    if (set.Rolls == 0)
                    {
                        tables.AddRange(lootset.Table);
                        GameLog.SpawnInfo("Processing loot set {Name}: set rolls == 0, looting", set.Name);
                        continue;
                    }

                    for (var x = 0; x < set.Rolls; x++)
                    {
                        if (Roll() <= set.Chance)
                        {
                            GameLog.SpawnInfo("Processing loot set {Name}: set hit, looting", set.Name);

                            // Ok, the set fired. Check the subtables, which can have independent chances.
                            // If no chance is present, we simply award something from each table in the set.
                            // Note that we do table processing last! We just find the tables that fire here.
                            foreach (var setTable in lootset.Table)
                            {
                                // Rolls == 0 (default) means the table is automatically used.
                                if (setTable.Rolls == 0)
                                {
                                    tables.Add(setTable);
                                    GameLog.SpawnInfo("Processing loot set {Name}: setTable rolls == 0, looting", set.Name);
                                    continue;
                                }
                                // Did the subtable hit?
                                for (var y = 0; y < setTable.Rolls; y++)
                                {
                                    if (Roll() <= setTable.Chance)
                                    {
                                        tables.Add(setTable);
                                        GameLog.SpawnInfo("Processing loot set {Name}: set subtable hit, looting ", set.Name);
                                    }
                                }
                            }
                        }
                        else
                        {
                            GameLog.SpawnInfo("Processing loot set {Name}: Set subtable missed", set.Name);
                        }
                    }
                }
                else
                {
                    GameLog.Warning("Spawn {name}: Loot set {name} missing", spawn.Base, set.Name);
                }
            }

            // Now, calculate loot for any tables attached to the spawn
            foreach (var table in spawn.Loot?.Table ?? new List <Xml.LootTable>())
            {
                if (table.Rolls == 0)
                {
                    tables.Add(table);
                    GameLog.SpawnInfo("Processing loot: spawn table for {Name}, rolls == 0, looting", spawn.Base);
                    continue;
                }
                for (var z = 0; z <= table.Rolls; z++)
                {
                    if (Roll() <= table.Chance)
                    {
                        GameLog.SpawnInfo("Processing loot: spawn table for {Name} hit, looting", spawn.Base);
                        tables.Add(table);
                    }
                    else
                    {
                        GameLog.SpawnInfo("Processing loot set {Name}: Spawn subtable missed", spawn.Base);
                    }
                }
            }

            // Now that we have all tables that fired, we need to calculate actual loot

            GameLog.SpawnInfo("Loot for {Name}: tables: {Count}", spawn.Base, tables.Count());
            foreach (var table in tables)
            {
                loot += CalculateTable(table);
            }

            GameLog.SpawnInfo("Final loot for {Name}: {Xp} xp, {Gold} gold, items [{items}]", spawn.Base, loot.Xp, loot.Gold, string.Join(",", loot.Items));
            return(loot);
        }
Пример #5
0
        public void Spawn(Xml.SpawnGroup spawnGroup)
        {
            foreach (var map in spawnGroup.Maps)
            {
                if (map.Disabled)
                {
                    continue;
                }
                try
                {
                    var spawnMap = Game.World.WorldData.Get <Map>(map.Id);
                    GameLog.SpawnDebug("Spawn: calculating {0}", spawnMap.Name);
                    var monsterList  = spawnMap.Objects.OfType <Monster>().ToList();
                    var monsterCount = monsterList.Count;

                    // If there is no limit specified, we want a reasonable limit, which we consider to be 1/10th of total
                    // number of map tiles

                    var spawnLimit = map.Limit == 0 ? (spawnMap.X * spawnMap.Y) / 10 : map.Limit;

                    if (monsterCount > spawnLimit)
                    {
                        if (spawnMap.SpawnDebug)
                        {
                            GameLog.SpawnInfo($"Spawn: {map.Name}: not spawning, mob count is {monsterCount}, limit is {spawnLimit}");
                        }
                        continue;
                    }

                    var since = DateTime.Now - map.LastSpawn;
                    if (since.TotalSeconds < map.Interval)
                    {
                        if (spawnMap.SpawnDebug)
                        {
                            GameLog.SpawnInfo($"Spawn: {map.Name}: not spawning, last spawn was {since.TotalSeconds} ago, interval {map.Interval}");
                        }
                        continue;
                    }

                    map.LastSpawn = DateTime.Now;

                    var thisSpawn = _random.Next(map.MinSpawn, map.MaxSpawn);

                    GameLog.SpawnInfo($"Spawn: {map.Name}: spawning {thisSpawn} mobs ");

                    for (var i = 0; i < thisSpawn; i++)
                    {
                        var idx          = _random.Next(0, spawnGroup.Spawns.Count - 1);
                        var spawn        = spawnGroup.Spawns[idx];
                        var creature     = _creatures.Single(x => x.Name == spawn.Base);
                        var newSpawnLoot = LootBox.CalculateLoot(spawn);

                        if (spawnMap.SpawnDebug)
                        {
                            GameLog.SpawnInfo("Spawn {name}, map {map}: {Xp} xp, {Gold} gold, items {Items}", spawn.Base, map.Name, newSpawnLoot.Xp, newSpawnLoot.Gold,
                                              string.Join(',', newSpawnLoot.Items));
                        }
                        var baseMob = new Monster(creature, spawn, map.Id, newSpawnLoot);
                        var mob     = (Monster)baseMob.Clone();

                        var xcoord = 0;
                        var ycoord = 0;
                        do
                        {
                            xcoord = _random.Next(0, spawnMap.X - 1);
                            ycoord = _random.Next(0, spawnMap.Y - 1);
                        } while (spawnMap.IsWall[xcoord, ycoord]);
                        mob.X = (byte)xcoord;
                        mob.Y = (byte)ycoord;

                        if (spawnMap.SpawnDebug)
                        {
                            GameLog.SpawnInfo($"Spawn: spawning {mob.Name} on {spawnMap.Name}");
                        }
                        SpawnMonster(mob, spawnMap);
                    }
                }
                catch (Exception e)
                {
                    GameLog.SpawnError(e, "Spawngroup {Filename}: disabled map {Name} due to error", spawnGroup.Filename, map.Name);
                    map.Disabled = true;
                    continue;
                }
            }
        }