public void Spawn(Xml.SpawnGroup spawnGroup) { if (!Game.World.WorldData.TryGetValue(spawnGroup.MapId, out Map spawnmap)) { GameLog.SpawnWarning($"Map id {spawnGroup.MapId}: not found"); return; } // if (spawnGroup.Spawns.Count == 0) //GameLog.SpawnWarning($"Spawngroup {spawnGroup.Name}: no spawns?"); foreach (var spawn in spawnGroup.Spawns) { if (spawnmap.SpawnDebug) { GameLog.SpawnInfo($"Spawngroup {spawnGroup.Name}: {spawn.Name} processing"); } var monsters = spawnmap.Monsters; // If the map is disabled, or we don't have a spec for our spawning, or the individual spawn // previously had errors and was disabled - continue on if (spawnmap.SpawningDisabled || spawn.Disabled) { GameLog.SpawnWarning($"Spawngroup {spawnGroup.Name}, map {spawnmap.Name}: spawn disabled or map spawning disabled"); continue; } if (spawn.Spec is null) { GameLog.SpawnWarning($"Spawngroup {spawnGroup.Name}, map {spawnmap.Name}: no spec defined for spawning"); continue; } var formeval = new FormulaEvaluation() { Map = spawnmap, XmlSpawn = spawn, SpawnGroup = spawnGroup }; // Set some reasonable defaults. // // If there is no maximum specified, we consider an appropriate maximum // to be 1/10th of total number of map tiles for any given mob (maximum of 30) spawned // at a default interval of every 30 seconds, with (maxcount/5) spawned // per tick. int maxcount = Math.Min(20, spawnmap.X * spawnmap.Y / 30); int interval = 30; int maxPerInterval = maxcount / 5; int baseLevel = 0; try { if (!string.IsNullOrEmpty(spawn.Spec.MaxCount)) { maxcount = (int)FormulaParser.Eval(spawn.Spec.MaxCount, formeval); } if (!string.IsNullOrEmpty(spawn.Spec.Interval)) { interval = (int)FormulaParser.Eval(spawn.Spec.Interval, formeval); } if (!string.IsNullOrEmpty(spawn.Spec.MaxPerInterval)) { maxPerInterval = (int)FormulaParser.Eval(spawn.Spec.MaxPerInterval, formeval); } // If the spawn itself has a level defined, evaluate and use it; otherwise, // the spawn group (imported, or in the map itself) should define a base level if (string.IsNullOrEmpty(spawn.Base?.Level)) { baseLevel = (int)FormulaParser.Eval(spawnGroup.BaseLevel, formeval); } else { baseLevel = (int)FormulaParser.Eval(spawn.Base.Level, formeval); } } catch (Exception e) { spawn.Disabled = true; spawn.ErrorMessage = $"Spawn disabled due to formula evaluation exception: {e}"; GameLog.SpawnError("Spawn {spawn} on map {map} disabled due to exception: {ex}", spawn.Name, spawnmap.Name, e); continue; } var currentCount = monsters.Where(x => x.Name == spawn.Name).ToList().Count(); if (currentCount >= maxcount) { if (spawnmap.SpawnDebug) { GameLog.SpawnInfo($"Spawn: {spawnmap.Name}: not spawning {spawn.Name} - mob count is {currentCount}, maximum is {maxcount}"); } continue; } var since = (DateTime.Now - spawn.LastSpawn).TotalSeconds; if (since < interval) { if (spawnmap.SpawnDebug) { GameLog.SpawnInfo($"Spawn: {spawnmap.Name}: not spawning {spawn.Name} - last spawn was {since} ago, interval {interval}"); } continue; } // Now spawn stuff for (var x = 0; x <= Math.Min(maxcount - currentCount, maxPerInterval); x++) { if (Game.World.WorldData.TryGetValue(spawn.Name, out Xml.Creature creature)) { var newSpawnLoot = LootBox.CalculateLoot(spawn.Loot); newSpawnLoot += LootBox.CalculateLoot(creature.Loot); newSpawnLoot += LootBox.CalculateLoot(spawnGroup.Loot); var baseMob = new Monster(creature, spawn.Flags, (byte)baseLevel, newSpawnLoot); if (baseMob.LootableXP == 0) { // If no XP defined, prepopulate based on defaults. // TODO: another place a hardcoded formula should be elsewhere // This is most simply expressed as "amount between mob level and last level times .7%" baseMob.LootableXP = Convert.ToUInt32((Math.Pow(baseMob.Stats.Level, 3) * 250) - (Math.Pow(baseMob.Stats.Level - 1, 3) * 250) * 0.007); } // Is this a strong or weak mob? if (spawn.Base.StrongChance > 0 || spawn.Base.WeakChance > 0) { // TODO: potentially refactor with xml control. This defaults to 3-15% // modifications randomly var modifier = Math.Min(.03, Random.Shared.NextDouble() * .15); var mobtype = Random.Shared.NextDouble() * 100; if (mobtype <= spawn.Base.StrongChance + spawn.Base.WeakChance) { if (spawn.Base.StrongChance >= spawn.Base.WeakChance) { if (mobtype <= spawn.Base.WeakChance) { baseMob.ApplyModifier(modifier * -1); if (spawnmap.SpawnDebug) { GameLog.SpawnInfo($"Mob is weak: modifier {modifier}"); } } else { baseMob.ApplyModifier(modifier); if (spawnmap.SpawnDebug) { GameLog.SpawnInfo($"Mob is strong: modifier {modifier}"); } } } else { if (mobtype <= spawn.Base.StrongChance) { baseMob.ApplyModifier(modifier); if (spawnmap.SpawnDebug) { GameLog.SpawnInfo($"Mob is strong: modifier {modifier}"); } } else { baseMob.ApplyModifier(modifier * -1); if (spawnmap.SpawnDebug) { GameLog.SpawnInfo($"Mob is weak: modifier {modifier}"); } } } } } var mob = (Monster)baseMob.Clone(); var xcoord = 0; var ycoord = 0; if (spawn.Coordinates.Any()) { foreach (var coord in spawn.Coordinates) { if (spawnmap.EntityTree.GetObjects(new Rectangle(coord.X, coord.Y, 1, 1)) .Any(e => e is Creature)) { continue; } xcoord = coord.X; ycoord = coord.Y; } } else { do { xcoord = Random.Shared.Next(0, spawnmap.X); ycoord = Random.Shared.Next(0, spawnmap.Y); } while (spawnmap.IsWall[xcoord, ycoord]); } baseMob.X = (byte)xcoord; baseMob.Y = (byte)ycoord; if (spawn.Damage != null) { ushort minDmg = 0; ushort maxDmg = 0; try { minDmg = (ushort)FormulaParser.Eval(spawn.Damage.MinDmg, formeval); maxDmg = (ushort)FormulaParser.Eval(spawn.Damage.MaxDmg, formeval); if (minDmg > 0) { // They need some kind of weapon if (Game.World.WorldData.TryGetValueByIndex("monsterblade", out Xml.Item template)) { var newTemplate = template.Clone(); template.Properties.Damage.Small.Min = minDmg; template.Properties.Damage.Small.Max = maxDmg; template.Properties.Damage.Large.Min = minDmg; template.Properties.Damage.Large.Max = maxDmg; template.Properties.Physical.Durability = uint.MaxValue; baseMob.Stats.OffensiveElementOverride = spawn.Damage.Element switch { ElementType.RandomFour => (ElementType)Random.Shared.Next(1, 5), // earth/fire/wind/water ElementType.RandomEight => (ElementType)Random.Shared.Next(1, 9), // Above plus light/dark/metal/wood ElementType.Random => (ElementType)Random.Shared.Next(1, 10), // Above plus undead _ => spawn.Damage.Element }; var item = new ItemObject(newTemplate); baseMob.Equipment.Insert((byte)ItemSlots.Weapon, item); } } } catch (Exception e) { spawn.Disabled = true; spawn.ErrorMessage = $"Spawn disabled due to formula evaluation exception: {e}"; GameLog.SpawnError("Spawn {spawn} on map {map} disabled due to exception: {ex}", spawn.Name, spawnmap.Name, e); continue; } } if (spawn.Defense != null) { sbyte Ac = 0; sbyte Mr = 0; try { Ac = (sbyte)FormulaParser.Eval(spawn.Defense.Ac, formeval); Mr = (sbyte)FormulaParser.Eval(spawn.Defense.Mr, formeval); } catch (Exception e) { spawn.Disabled = true; spawn.ErrorMessage = $"Spawn disabled due to formula evaluation exception: {e}"; GameLog.SpawnError("Spawn {spawn} on map {map} disabled due to exception: {ex}", spawn.Name, spawnmap.Name, e); continue; } baseMob.Stats.BonusAc = Ac; baseMob.Stats.BonusMr = Mr; baseMob.Stats.DefensiveElementOverride = spawn.Defense.Element switch { ElementType.RandomFour => (ElementType)Random.Shared.Next(1, 5), // earth/fire/wind/water ElementType.RandomEight => (ElementType)Random.Shared.Next(1, 9), // Above plus light/dark/metal/wood ElementType.Random => (ElementType)Random.Shared.Next(1, 10), // Above plus undead _ => spawn.Damage.Element }; } foreach (var cookie in spawn.SetCookies) { baseMob.SetCookie(cookie.Name, cookie.Value); } SpawnMonster(baseMob, spawnmap); } else { GameLog.SpawnWarning("Map {map}: Spawn {spawn} not found", spawnmap.Name, spawn.Name); } } spawn.LastSpawn = DateTime.Now; } }
public override void Attack(Direction direction, Castable castObject = null, Creature target = null) { if (target != null) { var damage = castObject.Effects.Damage; if (damage.Formula == null) //will need to be expanded. also will need to account for damage scripts { var simple = damage.Simple; var damageType = EnumUtil.ParseEnum<Enums.DamageType>(damage.Type.ToString(), Enums.DamageType.Magical); Random rand = new Random(); var dmg = rand.Next(Convert.ToInt32(simple.Min), Convert.ToInt32(simple.Max)); //these need to be set to integers as attributes. note to fix. target.Damage(dmg, OffensiveElement, damageType, this); } else { var formula = damage.Formula; var damageType = EnumUtil.ParseEnum<Enums.DamageType>(damage.Type.ToString(), Enums.DamageType.Magical); FormulaParser parser = new FormulaParser(this, castObject, target); var dmg = parser.Eval(formula); target.Damage(dmg, OffensiveElement, damageType, this); } } }