private static void Randomize(int seed, string path, Flags flags, string encoded) { //Console.WriteLine("Seed: " + seed); string json = JsonConvert.SerializeObject(flags); if (string.IsNullOrWhiteSpace(encoded)) { encoded = flags.GetEncoded(); } else { flags.DecodeAndSet(encoded); json = JsonConvert.SerializeObject(flags); } Console.WriteLine("Flags JSON : " + json); Console.WriteLine("Flags Base64: " + encoded); StreamWriter spoilerWriter = new StreamWriter("spoiler.txt"); SpoilerLog spoilerLog = new SpoilerLog(spoilerWriter, flags.SpoilerLog); System.IO.File.AppendAllText(@"seed.txt", seed.ToString() + " " + encoded + Environment.NewLine); spoilerLog.WriteFlags(flags); var random = new Random(seed); var randomValues = new List <int>(); for (int i = 0; i < 50; i++) { randomValues.Add(random.Next()); } var ultimaData = new UltimaData(); IWorldMap worldMap = null; //WorldMapGenerateMap.PrintWorldMapInfo(path); if (flags.Overworld == 5) { worldMap = new WorldMapGenerateMap(spoilerLog); } else if (flags.Overworld == 1) { worldMap = new WorldMapUnchanged(spoilerLog); } else if (flags.Overworld == 2) { worldMap = new WorldMapShuffleLocations(spoilerLog); } worldMap.Load(path, randomValues[0], randomValues[1], randomValues[2], ultimaData); var avatar = new Avatar(spoilerLog); avatar.Load(path, ultimaData, worldMap, flags); var title = new Title(spoilerLog); title.Load(path, ultimaData); var talk = new Talk(spoilerLog); talk.Load(path); var dungeons = new Dungeons(spoilerLog); dungeons.Load(path, ultimaData, flags); var party = new Party(spoilerLog); party.Load(path, ultimaData); var towns = new Towns(spoilerLog); towns.Load(path, ultimaData); if (flags.Fixes) { spoilerLog.Add(SpoilerCategory.Fix, "Serpent Hold's Healer"); ultimaData.ShopLocations[ultimaData.LOC_SERPENT - 1][5] = 0x12; } worldMap.Randomize(ultimaData, new Random(randomValues[3]), new Random(randomValues[4])); dungeons.Randomize(new Random(randomValues[6]), flags); if (flags.ClothMap) { var clothMap = worldMap.ToClothMap(ultimaData, new Random(randomValues[5])); clothMap.SaveAsPng($"clothMap-{seed}.png"); new Process { StartInfo = new ProcessStartInfo($"clothMap-{seed}.png") { UseShellExecute = true } }.Start(); } if (flags.RandomizeSpells) { var recipes = new byte[2]; ultimaData.SpellsRecipes['r' - 'a'].Byte = 0; ultimaData.SpellsRecipes['g' - 'a'].Byte = 0; while (ultimaData.SpellsRecipes['r' - 'a'].Byte == 0) { random.NextBytes(recipes); ultimaData.SpellsRecipes['r' - 'a'].Byte = (byte)(recipes[0] | recipes[1]); } while (ultimaData.SpellsRecipes['g' - 'a'].Byte == 0) { random.NextBytes(recipes); ultimaData.SpellsRecipes['g' - 'a'].Byte = (byte)(recipes[0] | recipes[1]); } } if (!String.IsNullOrWhiteSpace(flags.SpellRemove)) { var arr = flags.SpellRemove.ToLower().ToArray(); for (int i = 0; i < arr.Length; i++) { if (arr[i] > 'z' || arr[i] < 'a') { throw new ArgumentException("spellRemove can only contain letters."); } ultimaData.SpellsRecipes[arr[i] - 'a'].Byte = 0; } } if (flags.StartingWeapons) { for (int charIdx = 0; charIdx < 8; charIdx++) { var selected = false; while (!selected) { // -1 so Mystic weapons and armors aren't included var weapon = random.Next(1, Party.AllowedWeaponsMask.Length - 1); //If weapon is allowed if ((Party.AllowedWeaponsMask[weapon] & (0x80 >> ultimaData.StartingCharacters[charIdx].Class)) != 0) { ultimaData.StartingCharacters[charIdx].Weapon = (ushort)weapon; selected = true; } } selected = false; while (!selected) { var armor = random.Next(1, Party.AllowedArmorMask.Length); //If weapon is allowed if ((Party.AllowedArmorMask[armor] & (0x80 >> ultimaData.StartingCharacters[charIdx].Class)) != 0) { ultimaData.StartingCharacters[charIdx].Armor = (ushort)armor; selected = true; } } } } //worldMap.TestAbyssEjection(); //Console.WriteLine(Talk.GetSextantText(ultimaData.LCB[0])); //for (int i = 0; i < 8; i++) //{ // ultimaData.StartingArmor[i] = Convert.ToUInt16(i + 10); //} //for (int i = 0; i < 16; i++) //{ // ultimaData.StartingWeapons[i] = Convert.ToUInt16(i + 10); //} //ultimaData.StartingFood = 2345 * 100 + 99; //ultimaData.StartingGold = 1337; //for (int i = 0; i < 4; i++) //{ // ultimaData.StartingEquipment[i] = Convert.ToUInt16(i + 10); //} //for (int i = 0; i < 8; i++) //{ // ultimaData.StartingReagents[i] = Convert.ToUInt16(i + 10); //} //for (int i = 0; i < 26; i++) //{ // ultimaData.StartingMixtures[i] = Convert.ToUInt16(i + 10); //} //ultimaData.StartingItems = 0XFFFF; if (flags.QuestItemPercentage > 0) { ushort ushortone = 1; ultimaData.StartingItems = 0; for (ushort i = 0; i < 16; i++) { if (random.Next(0, 100) < flags.QuestItemPercentage) { ultimaData.StartingItems |= (ushort)(ushortone << i); } } // Never have the skull destroyed ultimaData.StartingItems &= (ushort)(~(ushortone << 1)); // Don' pre-use bell, book and candle ultimaData.StartingItems &= (ushort)(~(ushortone << 10)); ultimaData.StartingItems &= (ushort)(~(ushortone << 11)); ultimaData.StartingItems &= (ushort)(~(ushortone << 12)); ultimaData.StartingRunes = 0; for (ushort i = 0; i < 8; i++) { if (random.Next(0, 100) < flags.QuestItemPercentage) { ultimaData.StartingRunes |= (byte)(1 << i); } } ultimaData.StartingStones = 0; for (ushort i = 0; i < 8; i++) { if (random.Next(0, 100) < flags.QuestItemPercentage) { ultimaData.StartingStones |= (byte)(1 << i); } } LogQuestItems(spoilerLog, ultimaData); } else { spoilerLog.Add(SpoilerCategory.Start, "No change to starting quest items."); } if (flags.Sextant) { ultimaData.StartingEquipment[3] = 0x01; } if (flags.KarmaSetPercentage > 0) { for (int virtue = 0; virtue < 8; virtue++) { if (random.Next(0, 100) < flags.KarmaSetPercentage) { ultimaData.StartingKarma[virtue] = (flags.KarmaValue.HasValue ? (byte)flags.KarmaValue.Value : (byte)random.Next(0, 100)); spoilerLog.Add(SpoilerCategory.Start, $"{ultimaData.ItemNames[virtue + 15]} karma at {ultimaData.StartingKarma[virtue]}"); } else { spoilerLog.Add(SpoilerCategory.Start, $"{ultimaData.ItemNames[virtue + 15]} karma unchanged."); } } } if (flags.Runes) { spoilerLog.Add(SpoilerCategory.Feature, $"Rune locations randomized"); var usedLocations = new List <byte>(); for (int i = UltimaData.ITEM_RUNE_HONESTY; i < 8 + UltimaData.ITEM_RUNE_HONESTY; i++) { var possibleOptions = ItemOptions.ItemToItemOptions[i].Where(x => !usedLocations.Contains(x.Item.Location)).ToList(); var selectedItemOption = possibleOptions[random.Next(0, possibleOptions.Count)]; ultimaData.Items[i].X = selectedItemOption.Item.X; ultimaData.Items[i].Y = selectedItemOption.Item.Y; ultimaData.Items[i].Location = selectedItemOption.Item.Location; ultimaData.ItemOptions.Add(i, selectedItemOption); usedLocations.Add(selectedItemOption.Item.Location); } } if (flags.Mantras) { int numberOfTwos = 3; int numberOfThrees = 4; int numberOfFours = 1; // Grab Sacrifice first since it is special var mantrasWithLimericks = talk.Mantras.Where(x => x.Limerick.Length > 0).ToList(); var sacrificeMantra = mantrasWithLimericks[random.Next(0, mantrasWithLimericks.Count)]; talk.Mantras.Remove(sacrificeMantra); if (sacrificeMantra.Text.Length == 2) { numberOfTwos--; } else if (sacrificeMantra.Text.Length == 3) { numberOfThrees--; } else if (sacrificeMantra.Text.Length == 4) { numberOfFours--; } var possibleTwos = talk.Mantras.Where(x => x.Text.Length == 2).ToList(); var possibleThrees = talk.Mantras.Where(x => x.Text.Length == 3).ToList(); var possibleFours = talk.Mantras.Where(x => x.Text.Length == 4).ToList(); var possibleMantras = new List <Mantra>(); for (int i = 0; i < numberOfTwos; i++) { var nextIdx = random.Next(0, possibleTwos.Count); possibleMantras.Add(possibleTwos[nextIdx]); possibleTwos.RemoveAt(nextIdx); } for (int i = 0; i < numberOfThrees; i++) { var nextIdx = random.Next(0, possibleThrees.Count); possibleMantras.Add(possibleThrees[nextIdx]); possibleThrees.RemoveAt(nextIdx); } for (int i = 0; i < numberOfFours; i++) { var nextIdx = random.Next(0, possibleFours.Count); possibleMantras.Add(possibleFours[nextIdx]); possibleFours.RemoveAt(nextIdx); } possibleMantras.Shuffle(random); possibleMantras.Insert(4, sacrificeMantra); talk.Mantras = possibleMantras; for (int i = 0; i < 8; i++) { ultimaData.Mantras[i] = talk.Mantras[i].Text.ToLower(); } } if (flags.WordOfPassage) { var selection = talk.WordsOfPassage[random.Next(0, talk.WordsOfPassage.Count)]; ultimaData.WordTruth = selection.Item1; ultimaData.WordLove = selection.Item2; ultimaData.WordCourage = selection.Item3; ultimaData.WordOfPassage = selection.Item1 + selection.Item2 + selection.Item3; } //ultimaData.StartingStones = 0XFF; //ultimaData.StartingRunes = 0XFF; title.Update(ultimaData, flags, encoded); talk.Update(ultimaData, avatar, flags, worldMap); avatar.Update(ultimaData, flags); dungeons.Update(ultimaData, flags); party.Update(ultimaData); towns.Update(ultimaData, flags); towns.Save(path); party.Save(path); dungeons.Save(path); title.Save(path); talk.Save(path); avatar.Save(path); worldMap.Save(path); if (flags.MiniMap) { var image = worldMap.ToImage(); image.SaveAsPng($"worldMap-{seed}.png"); //image = worldMap.ToHeightMapImage(); //if (image != null) //{ // image.SaveAsPng($"worldMap-hm-{seed}.png"); //} } spoilerWriter.Close(); //PrintWorldMapInfo(); }
static void Main(string[] args) { Console.WriteLine("Hello World!"); CommandLineApplication commandLineApplication = new CommandLineApplication(throwOnUnexpectedArg: false); CommandOption seedArg = commandLineApplication.Option( "-s |--s <seed>", "The seed for the randomizer. " + " Same seed will produce the same map. Defaults to random value.", CommandOptionType.SingleValue); CommandOption pathArg = commandLineApplication.Option( "-p |--p <path>", "Path to Ultima 4 installation. " + " Leaving blank will assume it is the working directory.", CommandOptionType.SingleValue); CommandOption restoreArg = commandLineApplication.Option( "-r |--r", "Restore original Ultima 4 files. ", CommandOptionType.NoValue); CommandOption encodedArg = commandLineApplication.Option( "-e |--encoded", "Encoded flags. Overrides all other flags. ", CommandOptionType.SingleValue); CommandOption minimapArg = commandLineApplication.Option( "--miniMap", "Output a minimap of the overworld. ", CommandOptionType.NoValue); CommandOption overworldArg = commandLineApplication.Option( "-o |--overworld", "Sets randomization level for Overworld map. 1 for no change. 2 for shuffle overworld locations. 5 for randomize the entire map. Defaults to 5.", CommandOptionType.SingleValue); CommandOption spellRemoveArg = commandLineApplication.Option( "--spellRemove", "Put in the letters of the spells you want removed. e.g. \"--spellRemove zed\" would remove zdown, energy field and dispel. ", CommandOptionType.SingleValue); CommandOption startingWeapons = commandLineApplication.Option( "--startingWeaponsArmor", "Randomize the weapons and armor player and companions start with.", CommandOptionType.NoValue); CommandOption minQuantityArg = commandLineApplication.Option( "--mixQuantity", "Lets you input how much of a spell you want to mix. ", CommandOptionType.NoValue); CommandOption dngStoneArg = commandLineApplication.Option( "--dngStone", "Randomize the location of stones in the dungeons ", CommandOptionType.NoValue); CommandOption fixesArg = commandLineApplication.Option( "--fixes", "Collection of non-gameplay fixes.", CommandOptionType.NoValue); CommandOption hythlothFixArg = commandLineApplication.Option( "--hythlothFix", "Fixes an issue with Hythloth dungeon room.", CommandOptionType.NoValue); CommandOption sleepLockAssistArg = commandLineApplication.Option( "--sleepLockAssist", "Helps prevent sleeplock in battles.", CommandOptionType.NoValue); CommandOption activePlayerArg = commandLineApplication.Option( "--activePlayer", "Allow selecting which characters are active in combat.", CommandOptionType.NoValue); CommandOption appleHitChanceArg = commandLineApplication.Option( "--appleHitChance", "Change hit chance to behave like the original Apple II version.", CommandOptionType.NoValue); CommandOption diagonalAttackArg = commandLineApplication.Option( "--diagonalAttack", "Allow diagonal attacks in combat.", CommandOptionType.NoValue); CommandOption sacrificeFixArg = commandLineApplication.Option( "--sacrificeFix", "Adds a way to gain sacrifice which the shrine says should work.", CommandOptionType.NoValue); CommandOption runesArg = commandLineApplication.Option( "--runes", "Randomize the location of the runes.", CommandOptionType.NoValue); CommandOption mantrasArg = commandLineApplication.Option( "--mantras", "Randomize the mantras.", CommandOptionType.NoValue); CommandOption wordOfPassageArg = commandLineApplication.Option( "--wordOfPassage", "Randomize the Word of Passage.", CommandOptionType.NoValue); CommandOption questItemsArg = commandLineApplication.Option( "--questItems <0-100>", "Percentage chance to start with a quest item.", CommandOptionType.SingleValue); CommandOption karmaValueArg = commandLineApplication.Option( "--karmaValue <value>", "Value to override starting karma value for a virtue. Leave blank for random.", CommandOptionType.SingleValue); CommandOption karmaPercentageArg = commandLineApplication.Option( "--karmaPercentage <0-100>", "Percentage chance to override a starting karma value for a virtue. Default 0 (no override).", CommandOptionType.SingleValue); CommandOption monsterDamageArg = commandLineApplication.Option( "--monsterDamage <0-3>", "Value to change how much damage monsters do. Allowed values 0-3. 0 is quad damage. 1 is more damge. 2 is default. 3 is less damage.", CommandOptionType.SingleValue); CommandOption weaponDamageArg = commandLineApplication.Option( "--weaponDamage <1-3>", "Value to change how much damage weapons do. Allowed values 1-3. 1 is more damge. 2 is default. 3 is less damage.", CommandOptionType.SingleValue); CommandOption earlierMonstersArg = commandLineApplication.Option( "--earlierMonsters", "Make more difficult monsters appear earlier.", CommandOptionType.NoValue); CommandOption monsterQtyArg = commandLineApplication.Option( "--monsterQty", "More monsters from the start.", CommandOptionType.NoValue); CommandOption noRequireFullPartyArg = commandLineApplication.Option( "--noRequireFullParty", "Don't require the full party.", CommandOptionType.NoValue); CommandOption randomizeSpellsArg = commandLineApplication.Option( "--randomizeSpells", "Randomizes the gate and resurrection spells that you learn in game.", CommandOptionType.NoValue); CommandOption sextantArg = commandLineApplication.Option( "--sextant", "Start with a sextant.", CommandOptionType.NoValue); CommandOption clothMapArg = commandLineApplication.Option( "--clothMap", "Cloth map of the world.", CommandOptionType.NoValue); CommandOption vgaPatchArg = commandLineApplication.Option( "--vgaPatch", "VGA patch compatibility.", CommandOptionType.NoValue); CommandOption spoilerLogArg = commandLineApplication.Option( "--spoilerLog", "Output a spoiler log.", CommandOptionType.NoValue); commandLineApplication.HelpOption("-? | -h | --help"); commandLineApplication.OnExecute(() => { var seed = Environment.TickCount; if (seedArg.HasValue()) { if (!int.TryParse(seedArg.Value(), out seed)) { throw new InvalidCastException("Seed must be a number"); } } var overworld = 5; if (overworldArg.HasValue()) { if (!int.TryParse(overworldArg.Value(), out overworld)) { throw new InvalidCastException("Overworld argument must be a number"); } } var questItems = 0; if (questItemsArg.HasValue()) { if (!int.TryParse(questItemsArg.Value(), out questItems) && questItems >= 0 && questItems <= 100) { throw new InvalidCastException("QuestItems argument must be a number between 0 and 100 inclusive"); } } var karmaPercentage = 0; if (karmaPercentageArg.HasValue()) { if (!int.TryParse(karmaPercentageArg.Value(), out karmaPercentage) && karmaPercentage >= 0 && karmaPercentage <= 100) { throw new InvalidCastException("KarmaPercentage argument must be a number between 0 and 100 inclusive"); } } int?karmaValue = null; var karmaValueTmp = 0; if (karmaValueArg.HasValue()) { if (!int.TryParse(karmaValueArg.Value(), out karmaValueTmp) && karmaValueTmp >= 1 && karmaValueTmp <= 100) { throw new InvalidCastException("KarmaValue argument must be a number between 1 and 100 inclusive"); } karmaValue = karmaValueTmp; } int monsterDamage = 2; var monsterDamageTmp = 2; if (monsterDamageArg.HasValue()) { if (!int.TryParse(monsterDamageArg.Value(), out monsterDamageTmp) && monsterDamageTmp >= 0 && monsterDamageTmp <= 3) { throw new InvalidCastException("MonsterDamage argument must be a number between 0 and 3 inclusive"); } monsterDamage = monsterDamageTmp; } int weaponDamage = 2; var weaponDamageTmp = 2; if (weaponDamageArg.HasValue()) { if (!int.TryParse(weaponDamageArg.Value(), out weaponDamageTmp) && weaponDamageTmp >= 1 && weaponDamageTmp <= 3) { throw new InvalidCastException("WeaponDamage argument must be a number between 1 and 3 inclusive"); } weaponDamage = weaponDamageTmp; } var path = Directory.GetCurrentDirectory(); if (pathArg.HasValue()) { if (!Directory.Exists(pathArg.Value())) { throw new ArgumentException("Path provided does not exist"); } else { path = pathArg.Value(); } } if (!File.Exists(Path.Combine(path, "WORLD.MAP"))) { Console.Write("Could not find WORLD.MAP please provide path: "); path = Console.ReadLine().Trim(); if (!Directory.Exists(path)) { throw new ArgumentException("Path provided does not exist"); } } if (restoreArg.HasValue()) { Restore(path); } else { Flags flags = new Flags(seed, 9); flags.Overworld = overworld; flags.MiniMap = minimapArg.HasValue(); flags.SpellRemove = spellRemoveArg.Value(); flags.StartingWeapons = startingWeapons.HasValue(); flags.DngStone = dngStoneArg.HasValue(); flags.MixQuantity = minQuantityArg.HasValue(); flags.Fixes = fixesArg.HasValue(); flags.FixHythloth = hythlothFixArg.HasValue(); flags.SleepLockAssist = sleepLockAssistArg.HasValue(); flags.ActivePlayer = activePlayerArg.HasValue(); flags.HitChance = appleHitChanceArg.HasValue(); flags.DiagonalAttack = diagonalAttackArg.HasValue(); flags.SacrificeFix = sacrificeFixArg.HasValue(); flags.Runes = runesArg.HasValue(); flags.Mantras = mantrasArg.HasValue(); flags.WordOfPassage = wordOfPassageArg.HasValue(); flags.QuestItemPercentage = questItems; flags.KarmaSetPercentage = karmaPercentage; flags.KarmaValue = karmaValue; flags.MonsterDamage = monsterDamage; flags.WeaponDamage = weaponDamage; flags.EarlierMonsters = earlierMonstersArg.HasValue(); flags.MonsterQty = monsterQtyArg.HasValue(); flags.NoRequireFullParty = noRequireFullPartyArg.HasValue(); flags.RandomizeSpells = randomizeSpellsArg.HasValue(); flags.Sextant = sextantArg.HasValue(); flags.ClothMap = clothMapArg.HasValue(); flags.SpoilerLog = spoilerLogArg.HasValue(); flags.VGAPatch = vgaPatchArg.HasValue(); Randomize(seed, path, flags, encodedArg.Value()); //Console.WriteLine("Seed: " + seed); //var random = new Random(seed); //var worldMap = new WorldMap(); //worldMap.SwampTest(random); //worldMap.Save(path); //var image = worldMap.ToImage(); //image.SaveAsPng("worldMap.png"); } return(0); }); commandLineApplication.Execute(args); }