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); }
private IList <ITile> ReadSector(string fileName, string sectorFileContents, ushort xOffset, ushort yOffset, sbyte z) { var loadedTilesList = new List <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 = this.tileFactory.CreateTile(location); this.AddContent(newTile, CipFileParser.Parse(tileData)); loadedTilesList.Add(newTile); } this.logger.LogTrace($"Sector file {fileName}: {loadedTilesList.Count} tiles loaded."); return(loadedTilesList); }
/// <summary> /// Reads a <see cref="IMonsterTypeEntity"/> out of a monster file. /// </summary> /// <param name="monsterFileInfo">The information about the monster file.</param> /// <returns>The <see cref="IMonsterTypeEntity"/> instance.</returns> private IMonsterTypeEntity ReadMonsterFile(FileInfo monsterFileInfo) { monsterFileInfo.ThrowIfNull(nameof(monsterFileInfo)); if (!monsterFileInfo.Exists) { return(null); } var monsterType = new MonsterTypeEntity(); foreach ((string name, string value) in this.ReadInDataTuples(File.ReadLines(monsterFileInfo.FullName), monsterFileInfo.FullName)) { switch (name) { case "racenumber": monsterType.RaceId = Convert.ToUInt16(value); break; case "name": monsterType.Name = value; break; case "article": monsterType.Article = value; break; case "outfit": var(lookTypeId, headColor, bodyColor, legsColor, feetColor) = CipFileParser.ParseMonsterOutfit(value); monsterType.Outfit = new Outfit() { Id = lookTypeId, Head = headColor, Body = bodyColor, Legs = legsColor, Feet = feetColor, }; break; case "corpse": monsterType.Corpse = Convert.ToUInt16(value); break; case "blood": if (Enum.TryParse(value, out BloodType bloodType)) { monsterType.BloodType = bloodType; } break; case "experience": monsterType.BaseExperienceYield = Convert.ToUInt32(value); break; case "summoncost": monsterType.SummonCost = Convert.ToUInt16(value); break; case "fleethreshold": monsterType.HitpointFleeThreshold = Convert.ToUInt16(value); break; case "attack": monsterType.BaseAttack = Convert.ToUInt16(value); break; case "defend": monsterType.BaseDefense = Convert.ToUInt16(value); break; case "armor": monsterType.BaseArmorRating = Convert.ToUInt16(value); break; case "poison": monsterType.SetConditionInfect(ConditionFlag.Posion, Convert.ToUInt16(value)); break; case "losetarget": monsterType.LoseTargetDistance = Convert.ToByte(value); break; case "strategy": monsterType.Strategy = CipFileParser.ParseMonsterStrategy(value); break; case "flags": var parsedElements = CipFileParser.Parse(value); foreach (var element in parsedElements) { if (!element.IsFlag || element.Attributes == null || !element.Attributes.Any()) { continue; } if (Enum.TryParse(element.Attributes.First().Name, out CipCreatureFlag flagMatch)) { if (flagMatch.ToCreatureFlag() is CreatureFlag creatureFlag) { monsterType.SetCreatureFlag(creatureFlag); } } } break; case "skills": var skillParsed = CipFileParser.ParseMonsterSkills(value); foreach (var skill in skillParsed) { if (!Enum.TryParse(skill.Name, ignoreCase: true, out CipMonsterSkillType mSkill)) { continue; } switch (mSkill) { case CipMonsterSkillType.Hitpoints: monsterType.MaxHitpoints = skill.CurrentLevel < 0 ? ushort.MaxValue : (ushort)skill.DefaultLevel; break; case CipMonsterSkillType.GoStrength: monsterType.BaseSpeed = skill.CurrentLevel < 0 ? ushort.MinValue : (ushort)skill.DefaultLevel; break; case CipMonsterSkillType.CarryStrength: monsterType.Capacity = skill.CurrentLevel < 0 ? ushort.MinValue : (ushort)skill.DefaultLevel; break; case CipMonsterSkillType.FistFighting: if (skill.CurrentLevel > 0) { monsterType.SetSkill(SkillType.NoWeapon, skill.CurrentLevel, skill.DefaultLevel, skill.MaximumLevel, skill.TargetCount, skill.CountIncreaseFactor, skill.IncreaserPerLevel); } break; } } break; case "spells": monsterType.SetSpells(CipFileParser.ParseMonsterSpells(value)); break; case "inventory": monsterType.SetInventory(CipFileParser.ParseMonsterInventory(value)); break; case "talk": monsterType.SetPhrases(CipFileParser.ParsePhrases(value)); break; } } monsterType.Lock(); return(monsterType); }
/// <summary> /// Attempts to load the item catalog. /// </summary> /// <returns>The catalog, containing a mapping of loaded id to the item types.</returns> public IDictionary <ushort, IItemType> LoadTypes() { var itemDictionary = new Dictionary <ushort, IItemType>(); var objectsFilePath = Path.Combine(Environment.CurrentDirectory, this.LoaderOptions.FilePath); var current = new ItemType(); foreach (var readLine in File.ReadLines(objectsFilePath)) { if (readLine == null) { continue; } var inLine = readLine.Split(new[] { CommentSymbol }, 2).FirstOrDefault(); // ignore comments and empty lines. if (string.IsNullOrWhiteSpace(inLine)) { // wrap up the current ItemType and add it if it has enough properties set: if (current.TypeId == 0 || string.IsNullOrWhiteSpace(current.Name)) { continue; } current.LockChanges(); itemDictionary.Add(current.TypeId, current); current = new ItemType(); continue; } var data = inLine.Split(new[] { PropertyValueSeparator }, 2); if (data.Length != 2) { throw new InvalidDataException($"Malformed line [{inLine}] in objects file: [{objectsFilePath}]"); } var propName = data[0].ToLower().Trim(); var propData = data[1].Trim(); switch (propName) { case "typeid": current.SetId(Convert.ToUInt16(propData)); break; case "name": current.SetName(propData.Substring(Math.Min(1, propData.Length), Math.Max(0, propData.Length - 2))); break; case "description": current.SetDescription(propData); break; case "flags": foreach (var element in CipFileParser.Parse(propData)) { var flagName = element.Attributes.First().Name; if (Enum.TryParse(flagName, out ItemFlag flagMatch)) { current.SetFlag(flagMatch); continue; } this.Logger.Warning($"Unknown flag [{flagName}] found on item with TypeID [{current.TypeId}]."); } break; case "attributes": foreach (var attrStr in propData.Substring(Math.Min(1, propData.Length), Math.Max(0, propData.Length - 2)).Split(',')) { var attrPair = attrStr.Split('='); if (attrPair.Length != 2) { this.Logger.Error($"Invalid attribute {attrStr}."); continue; } current.SetAttribute(attrPair[0], Convert.ToInt32(attrPair[1])); } break; } } // wrap up the last ItemType and add it if it has enough properties set: if (current.TypeId != 0 && !string.IsNullOrWhiteSpace(current.Name)) { current.LockChanges(); itemDictionary.Add(current.TypeId, current); } return(itemDictionary); }
/// <summary> /// Reads a <see cref="IMonsterType"/> out of a monster file. /// </summary> /// <param name="monsterFileInfo">The information about the monster file.</param> /// <returns>The <see cref="IMonsterType"/> instance.</returns> private IMonsterType ReadMonsterFile(FileInfo monsterFileInfo) { monsterFileInfo.ThrowIfNull(nameof(monsterFileInfo)); if (!monsterFileInfo.Exists) { return(null); } var monsterType = new MonsterType(); foreach ((string name, string value) in this.ReadInDataTuples(File.ReadLines(monsterFileInfo.FullName), monsterFileInfo.FullName)) { switch (name) { case "racenumber": monsterType.SetId(Convert.ToUInt16(value)); break; case "name": monsterType.SetName(value.Trim('\"')); break; case "article": monsterType.SetArticle(value.Trim('\"')); break; case "outfit": monsterType.SetOutfit(value.Trim('(', ')')); break; case "corpse": monsterType.SetCorpse(Convert.ToUInt16(value)); break; case "blood": monsterType.SetBlood(value); break; case "experience": monsterType.SetExperience(Convert.ToUInt32(value)); break; case "summoncost": monsterType.SetSummonCost(Convert.ToUInt16(value)); break; case "fleethreshold": monsterType.SetFleeTreshold(Convert.ToUInt16(value)); break; case "attack": monsterType.SetAttack(Convert.ToUInt16(value)); break; case "defend": monsterType.SetDefense(Convert.ToUInt16(value)); break; case "armor": monsterType.SetArmor(Convert.ToUInt16(value)); break; case "poison": monsterType.SetConditionInfect(ConditionFlag.Posion, Convert.ToUInt16(value)); break; case "losetarget": monsterType.SetLoseTarget(Convert.ToByte(value)); break; case "strategy": monsterType.SetStrategy(CipFileParser.ParseMonsterStrategy(value)); break; case "flags": monsterType.SetFlags(CipFileParser.Parse(value)); break; case "skills": monsterType.SetSkills(CipFileParser.ParseMonsterSkills(value)); break; case "spells": monsterType.SetSpells(CipFileParser.ParseMonsterSpells(value)); break; case "inventory": monsterType.SetInventory(CipFileParser.ParseMonsterInventory(value)); break; case "talk": monsterType.SetPhrases(CipFileParser.ParsePhrases(value)); break; } } monsterType.Lock(); return(monsterType); }
/// <summary> /// Performs the conversion of .mon files to .json files. /// </summary> /// <param name="monDirPath">Optional. The path to the directory containing the .mon files. Defaults to the initialization <see cref="options"/> value if not supplied.</param> /// <param name="jsonDirPath">Optional. The path to the directory that will contain the .json files. Defaults to the initialization <see cref="options"/> value if not supplied.</param> /// <param name="overwriteFiles">Optional. A value indicating whether to overwrite files at the destination folder. Defaults to the initialization <see cref="options"/> value if not supplied.</param> public void Convert(string monDirPath = null, string jsonDirPath = null, bool?overwriteFiles = null) { this.options.MonsterFilesDirectory = monDirPath ?? this.options.MonsterFilesDirectory; this.options.OutputDirectory = jsonDirPath ?? this.options.OutputDirectory; this.options.OverwriteFiles = overwriteFiles ?? this.options.OverwriteFiles; DataAnnotationsValidator.ValidateObjectRecursive(this.options); var itemDictionary = this.itemTypesLoader.LoadTypes(); var monDirectoryInfo = new DirectoryInfo(monDirPath); var jsonDirectoryInfo = new DirectoryInfo(jsonDirPath); if (!monDirectoryInfo.Exists) { throw new ArgumentException($"The specified directory for .mon files: [{monDirectoryInfo.FullName}] does not exist."); } if (!jsonDirectoryInfo.Exists) { jsonDirectoryInfo.Create(); if (!jsonDirectoryInfo.Exists) { throw new ArgumentException($"The specified output directory: [{jsonDirectoryInfo.FullName}] does not exist and could not be created."); } } var existingFiles = monDirectoryInfo.GetFiles($"*{MonExtension}"); var modelMap = new Dictionary <string, MonsterModel>(); var raceMap = new Dictionary <uint, string>(); // Now that both directories exist, start processing files serially. foreach (var monFileInfo in existingFiles) { var parsedMonsterModel = CipFileParser.ParseMonsterFile(monFileInfo); if (parsedMonsterModel == null) { continue; } var targetModel = parsedMonsterModel.ToSerializableModel(); var fileNameWithoutExt = monFileInfo.Name.Replace(MonExtension, string.Empty); modelMap.Add(fileNameWithoutExt, targetModel); raceMap.Add(parsedMonsterModel.RaceId, fileNameWithoutExt); } // Ammend item names and summon references foreach (var convertedModel in modelMap.Values) { this.AmmendInventoryItemNames(itemDictionary, convertedModel); this.AmmendSummonReferences(raceMap, convertedModel); } // Output converted models foreach (var(fileNameWithoutExt, convertedModel) in modelMap) { var convertedFilePath = Path.Combine(jsonDirectoryInfo.FullName, Path.GetTempFileName()); var targetFilePath = $"{Path.Combine(jsonDirectoryInfo.FullName, fileNameWithoutExt)}{JsonExtension}"; var tempFileSream = File.Create(convertedFilePath); using (var fw = new StreamWriter(tempFileSream)) { var serializedMonster = JsonConvert.SerializeObject(convertedModel, Formatting.Indented); fw.Write(serializedMonster); fw.Flush(); fw.Close(); } File.Move(convertedFilePath, targetFilePath, overwriteFiles ?? false); } }
/// <summary> /// Reads a <see cref="MonsterTypeEntity"/> out of a monster file. /// </summary> /// <param name="monsterFileInfo">The information about the monster file.</param> /// <returns>The <see cref="MonsterTypeEntity"/> instance.</returns> private static MonsterTypeEntity ReadMonsterFile(FileInfo monsterFileInfo) { monsterFileInfo.ThrowIfNull(nameof(monsterFileInfo)); if (!monsterFileInfo.Exists) { return(null); } var parsedMonster = CipFileParser.ParseMonsterFile(monsterFileInfo); var monsterType = new MonsterTypeEntity() { RaceId = parsedMonster.RaceId.ToString(), Name = parsedMonster.Name, Article = parsedMonster.Article, OriginalOutfit = new Outfit() { Id = parsedMonster.Outfit.Id, Head = parsedMonster.Outfit.Head, Body = parsedMonster.Outfit.Body, Legs = parsedMonster.Outfit.Legs, Feet = parsedMonster.Outfit.Feet, }, Corpse = (ushort)parsedMonster.Corpse, BloodType = parsedMonster.BloodType, BaseExperienceYield = parsedMonster.Experience, SummonCost = parsedMonster.SummonCost, HitpointFleeThreshold = parsedMonster.FleeThreshold, BaseAttack = parsedMonster.Attack, BaseDefense = parsedMonster.Defense, BaseArmorRating = parsedMonster.Armor, LoseTargetDistance = parsedMonster.LoseTarget, Strategy = parsedMonster.Strategy, }; foreach (var flag in parsedMonster.Flags) { if (flag.ToCreatureFlag() is CreatureFlag creatureFlag) { monsterType.SetCreatureFlag(creatureFlag); } } foreach (var(skillType, defaultLevel, currentLevel, maximumLevel, targetCount, countIncreaseFactor, increaserPerLevel) in parsedMonster.Skills) { if (!Enum.TryParse(skillType, ignoreCase: true, out CipMonsterSkillType mSkill)) { continue; } switch (mSkill) { case CipMonsterSkillType.Hitpoints: monsterType.MaxHitpoints = currentLevel < 0 ? ushort.MaxValue : (ushort)defaultLevel; break; case CipMonsterSkillType.GoStrength: monsterType.BaseSpeed = currentLevel < 0 ? ushort.MinValue : (ushort)defaultLevel; break; case CipMonsterSkillType.CarryStrength: monsterType.Capacity = currentLevel < 0 ? ushort.MinValue : (ushort)defaultLevel; break; case CipMonsterSkillType.FistFighting: if (currentLevel > 0) { monsterType.SetSkill(SkillType.NoWeapon, Convert.ToInt32(currentLevel), Convert.ToInt32(defaultLevel), Convert.ToInt32(maximumLevel), targetCount, countIncreaseFactor, increaserPerLevel); } break; } } foreach (var spellRule in parsedMonster.Spells) { // Not implemented yet. } monsterType.SetInventory(parsedMonster.Inventory); monsterType.SetPhrases(parsedMonster.Phrases); return(monsterType); }