public void PartyRandomize(MT19337 rng, int numberForced) { // Always randomize all 4 default members (but don't force if not needed) Data[0x3A0AE] = (byte)rng.Between(0, 5); Data[0x3A0BE] = (byte)rng.Between(0, 5); Data[0x3A0CE] = (byte)rng.Between(0, 5); Data[0x3A0DE] = (byte)rng.Between(0, 5); if (numberForced <= 0) { return; } Data[0x39D35] = 0xE0; Data[0x39D36] = (byte)(numberForced * 0x10); Put(0x39D37, Blob.FromHex("30DFFE0003BD0003E906D0039D0003A9018537")); /* Starting at 0x39D35 (which is just after LDX char_index) * CPX ____(numberForced * 0x10)____ * BMI @MainLoop * INC ptygen_class, X * LDA ptygen_class, X * SBC #$06 * BNE :+ * STA ptygen_class, X * : LDA #$01 * STA menustall */ }
private void KillShops(ShopType shopType, ShopKillMode mode, ShopKillFactor factor, bool excludeConeria) { if (mode == ShopKillMode.Random) { mode = (ShopKillMode)Math.Max(rng.Between(-3, 4), 0); } if (factor == ShopKillFactor.KillRandom) { factor = (ShopKillFactor)rng.Between(0, 4); } switch (mode) { case ShopKillMode.WholeShops: KillWholeShops(shopType, factor, excludeConeria); break; case ShopKillMode.AllItemsOfAKind: KillAllItemsOfAKind(shopType, factor, excludeConeria); break; case ShopKillMode.Spread: KillSpreads(shopType, factor, excludeConeria); break; case ShopKillMode.Chaos: KillChaos(shopType, factor, excludeConeria); break; default: break; } }
public void GenerateRooms(MT19337 rng) { // this function generates all the rooms and hallways for this BSPMapNode and all of its children. if (LeftChild == null && RightChild == null) { if (WalkableSpace != null) { return; } // the room can be between 3 x 3 tiles to the size of the leaf - 2. var roomWidth = rng.Between(BSPTreeEngine.MIN_ROOM_WIDTH, Width - 2); var roomHeight = rng.Between(BSPTreeEngine.MIN_ROOM_HEIGHT, Height - 2); // place the room within the BSPMapNode, but don't put it right // against the side of the BSPMapNode (that would merge rooms together) var roomStartX = rng.Between(1, Width - roomWidth - 1); var roomStartY = rng.Between(1, Height - roomHeight - 1); WalkableSpace = new Rectangle(X + roomStartX, Y + roomStartY, roomWidth, roomHeight); return; } // subleafs: LeftChild?.GenerateRooms(rng); RightChild?.GenerateRooms(rng); // both leafs exist. we know their GetRandomRoom shouldn't return null, because we just called GenerateRooms. if (LeftChild != null && RightChild != null) { MakeHallway(rng, (Rectangle)LeftChild.GetRandomRoom(rng), (Rectangle)RightChild.GetRandomRoom(rng)); } }
public static void Work(IItemPlacementFlags flags, List <Item> treasurePool, MT19337 rng) { int count = treasurePool.Count; var consumableChestSet = ConsumableChestSets[flags.MoreConsumableChests]; var extConsumableChestSet = flags.ExtConsumableSet != ExtConsumableSet.None ? ExtConsumableChestSets[flags.ExtConsumableChests] : ExtConsumableChestSets[ExtConsumableChestSet.None]; if (flags.MoreConsumableChests == ConsumableChestSet.Random || flags.MoreConsumableChests == ConsumableChestSet.RandomLow) { consumableChestSet = ( Tents : rng.Between(0, consumableChestSet.Tents), Cabins : rng.Between(0, consumableChestSet.Cabins), Houses : rng.Between(0, consumableChestSet.Houses), Heals : rng.Between(0, consumableChestSet.Heals), Pures : rng.Between(0, consumableChestSet.Pures), Softs : rng.Between(0, consumableChestSet.Softs) ); } if (flags.ExtConsumableSet != ExtConsumableSet.None && (flags.ExtConsumableChests == ExtConsumableChestSet.Random || flags.ExtConsumableChests == ExtConsumableChestSet.RandomLow)) { extConsumableChestSet = ( WoodenNunchucks : rng.Between(0, extConsumableChestSet.WoodenNunchucks), SmallKnives : rng.Between(0, extConsumableChestSet.SmallKnives), WoodenRods : rng.Between(0, extConsumableChestSet.WoodenRods), Rapiers : rng.Between(0, extConsumableChestSet.Rapiers) ); } int requestedchests = consumableChestSet.Tents + consumableChestSet.Cabins + consumableChestSet.Houses + consumableChestSet.Heals + consumableChestSet.Pures + consumableChestSet.Softs + extConsumableChestSet.WoodenNunchucks + extConsumableChestSet.SmallKnives + extConsumableChestSet.WoodenRods + extConsumableChestSet.Rapiers; int removedchests = 0; RemoveConsumableChests(flags, treasurePool, ref removedchests); RemoveGoldChests(treasurePool, requestedchests, ref removedchests); if (flags.ExtConsumableSet != ExtConsumableSet.None) { AddConsumableChests(treasurePool, extConsumableChestSet.WoodenNunchucks, Item.WoodenNunchucks, ref removedchests); AddConsumableChests(treasurePool, extConsumableChestSet.SmallKnives, Item.SmallKnife, ref removedchests); AddConsumableChests(treasurePool, extConsumableChestSet.WoodenRods, Item.WoodenRod, ref removedchests); AddConsumableChests(treasurePool, extConsumableChestSet.Rapiers, Item.Rapier, ref removedchests); } AddConsumableChests(treasurePool, consumableChestSet.Tents, Item.Tent, ref removedchests); AddConsumableChests(treasurePool, consumableChestSet.Cabins, Item.Cabin, ref removedchests); AddConsumableChests(treasurePool, consumableChestSet.Houses, Item.House, ref removedchests); AddConsumableChests(treasurePool, consumableChestSet.Heals, Item.Heal, ref removedchests); AddConsumableChests(treasurePool, consumableChestSet.Pures, Item.Pure, ref removedchests); AddConsumableChests(treasurePool, consumableChestSet.Softs, Item.Soft, ref removedchests); AddGoldChests(treasurePool, removedchests); if (treasurePool.Count != count) { throw new Exception("Sorry, I f****d it up!"); } }
public void UpdateMagicAutohitThreshold(MT19337 rng, AutohitThreshold threshold) { short limit = 300; switch (threshold) { case AutohitThreshold.Vanilla: limit = 300; break; case AutohitThreshold.Autohit600: limit = 600; break; case AutohitThreshold.Autohit900: limit = 900; break; case AutohitThreshold.Autohit1200: limit = 1200; break; case AutohitThreshold.Autohit65535: limit = short.MaxValue; break; case AutohitThreshold.Autohit300to600: limit = (short)(rng.Between(1, 2) * 300); break; case AutohitThreshold.Autohit300to900: limit = (short)(rng.Between(1, 3) * 300); break; case AutohitThreshold.Autohit300to1200: limit = (short)(rng.Between(1, 4) * 300); break; case AutohitThreshold.Any: { short[] any = { 300, 600, 900, 1200, short.MaxValue }; limit = any.PickRandom(rng); break; } } // Set the low and high bytes of the limit which are then loaded and compared to the targets hp. Data[0x33AE0] = (byte)(limit & 0x00ff); Data[0x33AE5] = (byte)((limit >> 8) & 0x00ff); }
public bool Split(MT19337 rng) { // begin splitting the leaf into two children if (LeftChild != null || RightChild != null) { return(false); // we're already split! Abort! } // determine direction of split // if the width is >25% larger than height, we split vertically // if the height is >25% larger than the width, we split horizontally // otherwise we split randomly bool split_horizontally; if (Width > Height && Width / Height >= 1.25) { split_horizontally = false; } else if (Height > Width && Height / Width >= 1.25) { split_horizontally = true; } else { split_horizontally = rng.Between(0, 1) == 0; } int max = (split_horizontally ? Height : Width) - BSPTreeEngine.MIN_LEAF_SIZE; // determine the maximum height or width if (max <= BSPTreeEngine.MIN_LEAF_SIZE) { return(false); // the area is too small to split any more... } int split = rng.Between(BSPTreeEngine.MIN_LEAF_SIZE, max); // determine where we're going to split // create our left and right children based on the direction of the split if (split_horizontally) { LeftChild = new BSPMapNode(X, Y, Width, split); RightChild = new BSPMapNode(X, Y + split, Width, Height - split); } else { LeftChild = new BSPMapNode(X, Y, split, Height); RightChild = new BSPMapNode(X + split, Y, Width - split, Height); } return(true); // split successful! }
//offset lookups //0 - hit //1 - dmg //2 - crit //3 - weapon spell index //4 - elemental weaknesses //5 - type weaknesses //6 - weapon type sprite //7 - weapon sprite palette color public void RandomWeaponBonus(MT19337 rng, int min, int max, bool excludeMasa) { //get base stats Weapon currentWeapon; for (int i = 0; i < WeaponCount; i++) { if (i != 39 || !excludeMasa) { currentWeapon = new Weapon(i, this); int bonus = rng.Between(min, max); if (bonus != 0) { //adjust stats //clamp to 1 dmg min, 0 hit min, 50 hit maximum currentWeapon.HitBonus = (byte)Math.Max(0, (int)(currentWeapon.HitBonus + (3 * bonus))); currentWeapon.HitBonus = (byte)Math.Min(50, (int)(currentWeapon.HitBonus)); currentWeapon.Damage = (byte)Math.Max(1, (int)currentWeapon.Damage + (2 * bonus)); currentWeapon.Crit = (byte)Math.Max(1, (int)currentWeapon.Crit + (3 * bonus)); //change last two non icon characters to -/+bonus string bonusString = string.Format((bonus > 0) ? "+{0}" : "{0}", bonus.ToString()); byte[] bonusBytes = FF1Text.TextToBytes(bonusString); int iconIndex = currentWeapon.NameBytes[6] > 200 && currentWeapon.NameBytes[6] != 255 ? 5 : 6; for (int j = 0; j < bonusBytes.Length - 1; j++) { currentWeapon.NameBytes[iconIndex - j] = bonusBytes[bonusBytes.Length - 2 - j]; } currentWeapon.writeWeaponMemory(this); } } } }
private static bool ConvertTriState(bool?tristate, MT19337 rng) { int rngval = rng.Between(0, 1); bool rval = tristate ?? (rngval == 0); return(rval); }
public Rectangle?GetRandomRoom(MT19337 rng) { if (WalkableSpace != null) { return(WalkableSpace); } var leftRoom = LeftChild.GetRandomRoom(rng); var rightRoom = RightChild.GetRandomRoom(rng); if (leftRoom == null) { if (rightRoom == null) { return(null); } return(rightRoom); } if (rightRoom == null) { return(leftRoom); } // got both return((rng.Between(0, 1) == 0) ? leftRoom : rightRoom); }
public void RandomArmorBonus(MT19337 rng, int min, int max, bool cleanNames) { //get base stats Armor currentArmor; for (int i = 0; i < ArmorCount; i++) { currentArmor = new Armor(i, this); int bonus = rng.Between(min, max); if (bonus != 0) { //body armor(indexes 0 - 15) +2/-2 +2/-2 weight //shield(indexes 16 - 24) +1/-1 +1/-1 weight //helm(indexes 25 - 31) +1/-1 +1/-1 weight //hand(indexes 32 - 39) +1/-1 +1/-1 weight int armorTypeAbsorbBonus = i < 16 ? 2 : 1; int armorTypeWeightBonus = i < 16 ? 2 : 1; //adjust stats //clamp minimums to 0 weight, and 1 absorb currentArmor.Weight = (byte)Math.Max(0, currentArmor.Weight - (armorTypeWeightBonus * bonus)); currentArmor.Absorb = (byte)Math.Max(1, currentArmor.Absorb + (armorTypeAbsorbBonus * bonus)); //change last two non icon characters to -/+bonus string bonusString = string.Format((bonus > 0) ? "+{0}" : "{0}", bonus.ToString()); byte[] bonusBytes = FF1Text.TextToBytes(bonusString); // Adjusts blursed armor names to be more understandable if (cleanNames) { if (currentArmor.Name[0..6] == "Copper")
private List <List <byte> > Bucketize(List <byte> bytes, int bucketCount, int bucketSize, MT19337 rng) { var buckets = new List <List <byte> >(); for (int i = 0; i < bucketCount; i++) { buckets.Add(new List <byte>()); } int index = 0; while (index < bucketCount) { buckets[index].Add(bytes[index++]); } while (index < bytes.Count) { var bucket = rng.Between(0, buckets.Count - 1); if (buckets[bucket].Count < bucketSize) { buckets[bucket].Add(bytes[index++]); } } return(buckets); }
public void ShuffleLeader(MT19337 rng) { byte leader = (byte)(rng.Between(0, 3) << 6); Data[0x7D8BC] = leader; Data[0x7E933] = leader; }
//offset lookups //0 - hit //1 - dmg //2 - crit //3 - weapon spell index //4 - elemental weaknesses //5 - type weaknesses //6 - weapon type sprite //7 - weapon sprite palette color public List <int> RandomWeaponBonus(MT19337 rng, int min, int max, bool excludeMasa, bool cleanNames) { //get base stats Weapon currentWeapon; List <int> blurseValues = new(); for (int i = 0; i < WeaponCount; i++) { if (i != 39 || !excludeMasa) { currentWeapon = new Weapon(i, this); int bonus = rng.Between(min, max); blurseValues.Add(bonus); if (bonus != 0) { //adjust stats //clamp to 1 dmg min, 0 hit min, 50 hit maximum currentWeapon.HitBonus = (byte)Math.Max(0, (int)(currentWeapon.HitBonus + (3 * bonus))); currentWeapon.HitBonus = (byte)Math.Min(50, (int)(currentWeapon.HitBonus)); currentWeapon.Damage = (byte)Math.Max(1, (int)currentWeapon.Damage + (2 * bonus)); currentWeapon.Crit = (byte)Math.Max(1, (int)currentWeapon.Crit + (3 * bonus)); // Shortens names to make more sense when there are only 4 letters available // Most of these are only used with Weaponizer, but some are vanilla if (cleanNames) { // I didn't use a switch+case here because of the mix of name lengths being checked, but I'm sure this could be more elegant and anyone can feel free to clean it up if (currentWeapon.Name[0..5] == "Shock")
public void RandomEnemyStatusAttacks(MT19337 rng, bool AllowUnsafePirates, bool DisableStunTouch) { var enemies = Get(EnemyOffset, EnemySize * EnemyCount).Chunk(EnemySize); List <(byte touch, byte element)> statusElements = new List <(byte touch, byte element)>() { (0x04, 0x02), //Poison Touch = Poison (0x08, 0x01), //Dark Touch = Status (0x10, 0x01), //Stun Touch = Status (0x20, 0x01), //Sleep Touch = Status (0x40, 0x01), //Mute Touch = Status }; if (DisableStunTouch) { statusElements.Remove((0x10, 0x01)); } (byte touch, byte element)deathElement = (0x01, 0x08); //Death Touch = Death Element (byte touch, byte element)stoneElement = (0x02, 0x02); //Stone Touch = Poison for (int i = 0; i < EnemyCount; i++) { if (!AllowUnsafePirates) { if (i == 15) //pirates { continue; } } int roll = rng.Between(0, 128); if (roll < 1) //1 vanilla death toucher { //Death Touch var(touch, element) = deathElement; } else if (roll < 2) //1 vanilla stone toucher { //Stone Touch var(touch, element) = stoneElement; } else if (roll < 37) //35 enemies with other assorted status touches { var(touch, element) = statusElements.PickRandom(rng); enemies[i][15] = touch; enemies[i][14] = element; } else { //Otherwise, the enemy has no touch or associated element. enemies[i][14] = 0x00; enemies[i][15] = 0x00; } } Put(EnemyOffset, enemies.SelectMany(enemy => enemy.ToBytes()).ToArray()); }
private void SwapMirage3F() { if (rng.Between(0, 1) == 0) { return; } var map = maps[(int)MapId.MirageTower3F]; SwapTiles(map, 0x08, 0x01, 0x0E, 0x08); SwapTele(MapId.MirageTower3F, 0x08, 0x01, 0x0E, 0x08); DuplicateTile(MapId.MirageTower3F, 0x03, 0x07, 0x02, 0x07); DuplicateTile(MapId.MirageTower3F, 0x03, 0x08, 0x02, 0x08); DuplicateTile(MapId.MirageTower3F, 0x03, 0x09, 0x02, 0x09); DuplicateTile(MapId.MirageTower3F, 0x03, 0x0A, 0x02, 0x0A); SwapTiles(map, 0x08, 0x0B, 0x03, 0x08); SwapTiles(map, 0x08, 0x0C, 0x03, 0x09); SwapTiles(map, 0x08, 0x0D, 0x03, 0x0A); SwapTiles(map, 0x08, 0x0E, 0x03, 0x0B); DuplicateTile(MapId.MirageTower3F, 0x08, 0x03, 0x03, 0x07); DuplicateTile(MapId.MirageTower3F, 0x07, 0x0C, 0x08, 0x0C); DuplicateTile(MapId.MirageTower3F, 0x07, 0x0B, 0x08, 0x0B); }
public void EnableOrbHunt(MT19337 rng) { // Replace unused CANOE string and EarthOrb pointer with whatever we're calling the scavenged item. Put(0x2B981, FF1Text.TextToBytes("SHARD ", false, FF1Text.Delimiter.Null)); Data[0x2B72A] = 0x81; // Now actually print shards when printing items, and print them with quantity Data[0x7EF49] = 0x15; Data[0x7EF91] = 0x15; // Replace the upper two tiles of the unlit orb with an empty and found shard. // These are at tile address $76 and $77 respectively. Put(0x37760, Blob.FromHex("001C22414141221CFFE3DDBEBEBEDDE3001C3E7F7F7F3E1CFFFFE3CFDFDFFFFF")); // Hard code the total number of orbs and where we start depending on how many are needed int goal = rng.Between(16, 24); String hexCount = goal.ToString("X2"); String ppuLowByte = goal <= 24 ? "63" : "43"; // Fancy orb drawing code, see 0E_B8D7_DrawOrbBox.asm Put(0x3B87D, Blob.FromHex($"A9{ppuLowByte}8511A977A00048AD0220A9208D0620A51118692085118D0620900DAD0220A9218D0620A9038D062068A200CC3560D002A976C0{hexCount}D001608D0720C8E8E006D0EB1890C3")); // Black Orb Override to jump to the final floor. This allows us to give out some last minute loot and // and make the repeated attempts the final battle strategy take a little longer due to some walking. Put(0x39502, Blob.FromHex($"AD3560C9{hexCount}300CA0CA209690E67DE67DA51160A51260")); Put(0x7CDB3, Blob.FromHex("08CE")); Data[0x00D80] = 0x80; // Map edits Data[0x02D01] = 0x0F; Data[0x02D41] = 0x03; Data[0x02D81] = 0x3B; // ToFR Map Hacks Put(0x1A899, Blob.FromHex("0C00810502BF38000462615D5E5F0402BF3334B0020004638405600402B00235BF2D3430B10303840905B1033035BF2A34B1033330068409083032B10335BF2834B10233B00231300684021004118402083031B00232B10235BF2732313330B104300687050830B10430323133BF2634B10233B10538B00336B00338B10532B10235BF2532313330B1053831B8023AB802")); // A little narrative overhaul. Put(0x289B2, FF1Text.TextToBytes("The SHARDS coalesce to\nrestore the Black ORB.\n\nBrave Light Warriors....\nDestroy the Evil within!")); // Black Orb Text Put(0x28CF8, FF1Text.TextToBytes("Ah, the Light Warriors!\n\nSo you have collected\nthe SHARDS and restored\nthe BLACK ORB.")); Put(0x28D57, FF1Text.TextToBytes("Thus you've travelled\n2000 years into the past\nto try to stop me?\n\nStep forward then,\nto your peril!")); Put(0x28DAF, FF1Text.TextToBytes("Oh, Light Warriors!\nSuch arrogant bravery.\n\nLet us see whom history\nremembers. En Garde!")); // This ugliness picks a random FinalFormation transformFinalFormation((FinalFormation)rng.Between(0, Enum.GetValues(typeof(FinalFormation)).Length - 1)); }
public void RandomEnemyStatusAttacks(MT19337 rng, bool AllowUnsafePirates) { var enemies = Get(EnemyOffset, EnemySize * EnemyCount).Chunk(EnemySize); byte[] statusElements = new byte[8]; statusElements[0] = 0x08; //Death Touch = Death Element statusElements[1] = 0x02; //Stone Touch = Poison statusElements[2] = 0x02; //Poison Touch = Poison statusElements[3] = 0x01; //Dark Touch = Status statusElements[4] = 0x01; //Stun Touch = Status statusElements[5] = 0x01; //Sleep Touch = Status statusElements[6] = 0x01; //Mute Touch = Status statusElements[7] = 0x01; //Conf Touch = Status for (int i = 0; i < EnemyCount; i++) { if (!AllowUnsafePirates) { if (i == 15) //pirates { continue; } } //We don't want EVERYTHING to have a status, so let's add a bias amount - say, 25%? if (rng.Between(0, 5) == 0) { int status = rng.Between(0, 7); byte element = statusElements[status]; enemies[i][14] = (byte)(0x01 << status); enemies[i][15] = element; } else { enemies[i][14] = 0x00; enemies[i][15] = 0x00; } } Put(EnemyOffset, enemies.SelectMany(enemy => enemy.ToBytes()).ToArray()); }
void updateCharacterFromOptions(int slotNumber, bool forced, IList <FF1Class> options, MT19337 rng) { const int lut_PtyGenBuf = 0x784AA; // offset for party generation buffer LUT const int lut_AllowedClasses = 0x78110; // offset for allowed classes per slot LUT var i = slotNumber - 1; if (forced) // if forced { FF1Class forcedclass; if (options.Any()) { forcedclass = options.PickRandom(rng); } else { forcedclass = (FF1Class)(Enum.GetValues(typeof(FF1Class))). GetValue(rng.Between(0, slotNumber == 1 ? 5 : 6)); } options.Clear(); options.Add(forcedclass); } // don't make any changes if there's nothing to do if (!options.Any()) { return; } byte allowedFlags = 0b0000_0000; foreach (FF1Class option in options) { allowedFlags |= AllowedClassBitmasks[(int)option]; } // set default member var defaultclass = (forced || !DefaultChoices.SequenceEqual(options)) ? (int)options.PickRandom(rng) : slotNumber - 1; Data[lut_PtyGenBuf + i * 0x10] = defaultclass == 6 ? (byte)0xFF : (byte)defaultclass; // set allowed classes Data[lut_AllowedClasses + i] = allowedFlags; options.Clear(); }
public void RandomEnemyStatusAttacks(MT19337 rng, bool AllowUnsafePirates) { var enemies = Get(EnemyOffset, EnemySize * EnemyCount).Chunk(EnemySize); List <(byte touch, byte element)> statusElements = new List <(byte touch, byte element)>() { (0x01, 0x08), //Death Touch = Death Element (0x02, 0x02), //Stone Touch = Poison (0x04, 0x02), //Poison Touch = Poison (0x08, 0x01), //Dark Touch = Status (0x10, 0x01), //Stun Touch = Status (0x20, 0x01), //Sleep Touch = Status (0x40, 0x01), //Mute Touch = Status }; for (int i = 0; i < EnemyCount; i++) { if (!AllowUnsafePirates) { if (i == 15) //pirates { continue; } } //Vanilla ratio is 37/128, hence the magic numbers if (rng.Between(0, 128) < 37) { var(touch, element) = statusElements.PickRandom(rng); enemies[i][15] = touch; enemies[i][14] = element; } else { //Otherwise, the enemy has no touch or associated element. enemies[i][14] = 0x00; enemies[i][15] = 0x00; } } Put(EnemyOffset, enemies.SelectMany(enemy => enemy.ToBytes()).ToArray()); }
public static void Work(IItemPlacementFlags flags, List <Item> treasurePool, MT19337 rng) { //var x = treasurePool.GroupBy(i => i).Select(g => (g.Key, g.Count())).OrderBy(g => (int)g.Key).ToList(); if (flags.MoreConsumableChests == ConsumableChestSet.Vanilla) { return; } int count = treasurePool.Count; var consumableChestSet = ConsumableChestSets[flags.MoreConsumableChests]; if (flags.MoreConsumableChests == ConsumableChestSet.Random || flags.MoreConsumableChests == ConsumableChestSet.RandomLow) { consumableChestSet = ( Tents : rng.Between(0, consumableChestSet.Tents), Cabins : rng.Between(0, consumableChestSet.Cabins), Houses : rng.Between(0, consumableChestSet.Houses), Heals : rng.Between(0, consumableChestSet.Heals), Pures : rng.Between(0, consumableChestSet.Pures), Softs : rng.Between(0, consumableChestSet.Softs) ); } int requestedchests = consumableChestSet.Tents + consumableChestSet.Cabins + consumableChestSet.Houses + consumableChestSet.Heals + consumableChestSet.Pures + consumableChestSet.Softs; int removedchests = 0; RemoveConsumableChests(treasurePool, ref removedchests); RemoveGoldChests(treasurePool, requestedchests, ref removedchests); AddConsumableChests(treasurePool, consumableChestSet.Tents, Item.Tent, ref removedchests); AddConsumableChests(treasurePool, consumableChestSet.Cabins, Item.Cabin, ref removedchests); AddConsumableChests(treasurePool, consumableChestSet.Houses, Item.House, ref removedchests); AddConsumableChests(treasurePool, consumableChestSet.Heals, Item.Heal, ref removedchests); AddConsumableChests(treasurePool, consumableChestSet.Pures, Item.Pure, ref removedchests); AddConsumableChests(treasurePool, consumableChestSet.Softs, Item.Soft, ref removedchests); AddGoldChests(treasurePool, removedchests); if (treasurePool.Count != count) { throw new Exception("Sorry, I f****d it up!"); } }
public void PubReplaceClinic(MT19337 rng) { List <byte> pub_lut = new List <byte> { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5 }; pub_lut.Shuffle(rng); pub_lut.Insert(3, (byte)0xFF); // Will break if Melmond ever gets a clinic, Nones will need to be hired dead, this results in them being alive. int lefein_class = rng.Between(0, 5); pub_lut.Add((byte)lefein_class); Put(0x38066, Blob.FromHex("9D8A9F8E9B97")); // Replaces "CLINIC" with "TAVERN" // EnterClinic PutInBank(0x0E, 0xA5A1, Blob.FromHex("60A90085248525205BAAA0FFC8B91DA7991003D0F7A902991003C8A648BD0A9D69F0991003C8A905991003C8A9C5991003C8A900991003A910853EA903853F2032AA20D7A6A5628D0C03B0B4209BAA20C2A8B0ADA562D0A92089A6AD0C03186D0C036D0C03AABD10036A6A6A29C04818201C9D68B08BAAA562D0458A690A8510A96185118A488512A9638513A000A90091109112C8C00A30F7C040D0F59D266120E99CA448B90A9D9D00612071C2A00E20799068AA900918BD006169069D0061A9019D0A61A9009D016120349DEAEAEAEA200CE92078A7A921205BAA2043A7A5240525F0F74CA2A5")); PutInBank(0x0E, 0x9D0A, pub_lut.ToArray()); // ClinicBuildNameString PutInBank(0x0E, 0xA6DD, Blob.FromHex("EDA6A910853EA903853F2032AA4C07A9A000A20086638A2A2A2A29036910991003A900991103A90199120398186903A8E6638A186940AAD0DDA900991003386091ACB5A8FFA40500")); // Moved routine PutInBank(0x0E, 0xA3F8, Blob.FromHex("7D9C")); PutInBank(0x0E, 0x9C7D, Blob.FromHex("A5626A6A6A29C08D0A03A666D01EA018AE0A03BD1861F032C8BD1961F02CC8BD1A61F026C8BD1B61F0203860A01CAE0A03BD1C61F025C8BD1D61F01FC8BD1E61F019C8BD1F61F013386098186D0A03AAAD0C0338E91B9D0061186098186D0A03AAAD0C0338E9439D00611860")); // New routine that unequips all gear and sets all substats to zero PutInBank(0x0E, 0x9CE9, Blob.FromHex("188A69188510A9618511A007B110297F91108810F7A900A0089110C8C00ED0F960")); PutInBank(0x1F, 0xC271, CreateLongJumpTableEntry(0x00, 0xC783)); //ReviveorBuy routine PutInBank(0x0E, 0x9D1C, Blob.FromHex("A903203BAAA912853EA99D853F2032AAA9028D62004C07A9")); // Buy Revive text options PutInBank(0x0E, 0x9D12, Blob.FromHex("8BB8BC019BA8B9AC3200")); // New routine to level up replaced character and zero some stuff, needs new level up stuff in bank 1B PutInBank(0x0E, 0x9D34, Blob.FromHex("A99D48A94B48A98748A9A9488A182A2A2A8510A91B4C03FEA9008D24008D25008D012060")); Data[0x101A] = 0x13; Data[0x109A] = 0x13; Data[0x111A] = 0x76; Data[0x119A] = 0x77; }
public void RandomArmorBonus(MT19337 rng, int min, int max) { //get base stats Armor currentArmor; for (int i = 0; i < ArmorCount; i++) { currentArmor = new Armor(i, this); int bonus = rng.Between(min, max); if (bonus != 0) { //body armor(indexes 0 - 15) +2/-2 +2/-2 weight //shield(indexes 16 - 24) +1/-1 +1/-1 weight //helm(indexes 25 - 31) +1/-1 +1/-1 weight //hand(indexes 32 - 39) +1/-1 +1/-1 weight int armorTypeAbsorbBonus = i < 16 ? 2 : 1; int armorTypeWeightBonus = i < 16 ? 2 : 1; //adjust stats //clamp minimums to 0 weight, and 1 absorb currentArmor.Weight = (byte)Math.Max(0, currentArmor.Weight - (armorTypeWeightBonus * bonus)); currentArmor.Absorb = (byte)Math.Max(1, currentArmor.Absorb + (armorTypeAbsorbBonus * bonus)); //change last two non icon characters to -/+bonus string bonusString = string.Format((bonus > 0) ? "+{0}" : "{0}", bonus.ToString()); byte[] bonusBytes = FF1Text.TextToBytes(bonusString); int iconIndex = currentArmor.NameBytes[6] > 200 && currentArmor.NameBytes[6] != 255 ? 5 : 6; for (int j = 0; j < bonusBytes.Length - 1; j++) { currentArmor.NameBytes[iconIndex - j] = bonusBytes[bonusBytes.Length - 2 - j]; } currentArmor.writeArmorMemory(this); } } }
public void BuildExpChests() { rom.PutInBank(0x1F, 0xDDD0, Blob.FromHex("A9B648A9FF48A9114C03FE8A20DA8760")); int expChestCountPercent = rng.Between(flags.ExpChestConversionMin, flags.ExpChestConversionMax); int expChestCount = treasureData.Data.Where(g => g >= Item.Gold10).Count() * expChestCountPercent / 100; double lowScale = (double)flags.ExpChestMinReward / (double)BaseExp; double highScale = (double)flags.ExpChestMaxReward / (double)BaseExp; if (expChestCount == 0) { return; } LoadData(); var unusedGoldDic = new HashSet <int>(rom.UnusedGoldItems.Cast <int>()); // construct a dictionary and get a shuffled index into it. var goldItems = ItemLists.AllGoldTreasure.Select(g => (item: g, price: itemPrices[g], name: itemNames[(int)g])).ToList(); goldItems.Shuffle(rng); var goldItemsDic = goldItems.Select((g, i) => (shuffleindex: i, item: g.item, price: g.price, name: g.name)).ToDictionary(g => g.item); var expItems = new HashSet <Item>(treasureData.Data .Where(g => goldItemsDic.ContainsKey(g) && !unusedGoldDic.Contains((int)g)) .Select(g => (item: g, shuffleindex: goldItemsDic[g].shuffleindex)) .OrderBy(g => g.shuffleindex) .Take(expChestCount) .Select(g => g.item) .Distinct()); var firstExpItem = RepackGoldExpItems(goldItemsDic, expItems, unusedGoldDic); for (int i = (int)firstExpItem; i < 176; i++) { if (unusedGoldDic.Contains(i)) { continue; } var e = (Item)i; var exp = (ushort)Math.Min(Math.Max(FF1Rom.RangeScale(BaseExp, lowScale, highScale, 1.0, rng), 0), 65535); itemPrices[e] = exp; itemNames[(int)e] = exp.ToString() + " EXP"; } FirstExpItem = firstExpItem; if (flags.Archipelago) { rom.PutInBank(0x11, 0xB446, new byte[] { (byte)firstExpItem }); } else { rom.PutInBank(0x11, 0xB447, new byte[] { (byte)firstExpItem }); } var result = treasureData.Data.Where(x => x > Item.Gold10).OrderBy(x => x).Select(x => itemNames[(int)x]).ToList(); StoreData(); }
public void ShuffleMusic(MusicShuffle mode, MT19337 rng) { switch (mode) { case MusicShuffle.Standard: List <byte> overworldTracks = new List <byte> { 0x41, 0x42, 0x44, 0x45, 0x46, 0x47, 0x4A, 0x4F }; List <byte> townTracks = new List <byte> { 0x41, 0x42, 0x45, 0x46, 0x47, 0x48, 0x4A, 0x4F, 0x51 }; List <byte> dungeonTracks = new List <byte> { 0x41, 0x42, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x52, 0x53 }; overworldTracks.Shuffle(rng); townTracks.Shuffle(rng); dungeonTracks.Shuffle(rng); //Overworld Data[0x7C649] = overworldTracks[0]; Data[0x7C6F9] = overworldTracks[0]; Data[0x7C75A] = overworldTracks[0]; Data[0x7C75B] = overworldTracks[0]; //Ship Data[0x7C62D] = overworldTracks[1]; Data[0x7C75D] = overworldTracks[1]; //Airship Data[0x7C235] = overworldTracks[2]; Data[0x7C761] = overworldTracks[2]; //Remove used songs from other pools var usedTracks = overworldTracks.Take(3).ToList(); townTracks = townTracks.Except(usedTracks).ToList(); //Town Data[0x7CFC3] = townTracks[0]; //Castle Data[0x7CFC4] = townTracks[1]; //Shop Data[0x3A351] = townTracks[2]; Data[0x3A56E] = townTracks[2]; Data[0x3A597] = townTracks[2]; //Menu Data[0x3ADB4] = townTracks[3]; Data[0x3B677] = townTracks[3]; Data[0x3997F] = townTracks[3]; //Lineup menu //Remove used songs from other pools usedTracks.AddRange(townTracks.Take(4)); dungeonTracks = dungeonTracks.Except(usedTracks).ToList(); //Dungeons Data[0x7CFC5] = dungeonTracks[0]; Data[0x7CFC6] = dungeonTracks[1]; Data[0x7CFC7] = dungeonTracks[2]; Data[0x7CFC8] = dungeonTracks[3]; Data[0x7CFC9] = dungeonTracks[4]; Data[0x7CFCA] = dungeonTracks[5]; break; case MusicShuffle.Nonsensical: //They asked for it... List <byte> tracks = new List <byte> { 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x51, 0x52, 0x53, 0x55 }; tracks.Shuffle(rng); //Overworld Data[0x7C649] = tracks[0]; Data[0x7C6F9] = tracks[0]; Data[0x7C75A] = tracks[0]; Data[0x7C75B] = tracks[0]; //Ship Data[0x7C62D] = tracks[1]; Data[0x7C75D] = tracks[1]; //Airship Data[0x7C235] = tracks[2]; Data[0x7C761] = tracks[2]; //Tilesets 1-8 Data[0x7CFC3] = tracks[3]; //Town Data[0x7CFC4] = tracks[4]; //Castle Data[0x7CFC5] = tracks[5]; Data[0x7CFC6] = tracks[6]; Data[0x7CFC7] = tracks[7]; Data[0x7CFC8] = tracks[8]; Data[0x7CFC9] = tracks[9]; Data[0x7CFCA] = tracks[10]; //Title Data[0x3A226] = tracks[11]; //Shop Data[0x3A351] = tracks[12]; Data[0x3A56E] = tracks[12]; Data[0x3A597] = tracks[12]; //Menu Data[0x3ADB4] = tracks[13]; Data[0x3B677] = tracks[13]; Data[0x3997F] = tracks[13]; //Lineup menu //Ending Data[0x37804] = tracks[14]; //Bridge Cutscene Data[0x3784E] = tracks[15]; //Battle Fanfare Data[0x31E44] = tracks[16]; //Gameover Data[0x2DAF6] = tracks[17]; //Battle //Data[0x2D9C1] = Songs[rng.Between(0, Songs.Count - 1)]; //Mini Things Data[0x36E86] = tracks[rng.Between(0, tracks.Count - 1)]; //minigame Data[0x27C0D] = tracks[rng.Between(0, tracks.Count - 1)]; //minimap break; case MusicShuffle.MusicDisabled: //Set Sq1, Sq2, and Tri channels for crystal theme all point to the same music data Put(0x34000, Blob.FromHex("C080C080C080")); //Overwrite beginning of crystal theme with a song that initializes properly but plays no notes Put(0x340C0, Blob.FromHex("FDF805E0D8C7D0C480")); List <int> AllSongs = new List <int> { 0x7C649, 0x7C6F9, 0x7C75A, 0x7C75B, 0x7C62D, 0x7C75D, 0x7C235, 0x7C761, 0x7CFC3, 0x7CFC4, 0x7CFC5, 0x7CFC6, 0x7CFC7, 0x7CFC8, 0x7CFC9, 0x7CFCA, 0x3A226, 0x3A351, 0x3A56E, 0x3A597, 0x3ADB4, 0x3B677, 0x3997F, 0x37804, 0x3784E, 0x31E44, 0x2DAF6, 0x2D9C1, 0x36E86, 0x27C0D }; //Set all music playback calls to play the new empty song foreach (int address in AllSongs) { Data[address] = 0x41; } break; } }
private void ShuffleShopType(ShopType shopType, ushort[] pointers, MT19337 rng) { var shops = GetShops(shopType, pointers); var allEntries = shops.SelectMany(list => list).ToList(); allEntries.Shuffle(rng); var newShops = new List<byte>[ShopSectionSize]; var entry = 0; for (int i = 0; i < ShopSectionSize; i++) { newShops[i] = new List<byte>(); if (pointers[(int)shopType + i] != ShopNullPointer) { newShops[i].Add(allEntries[entry++]); } } while (entry < allEntries.Count) { var tryShop = newShops[rng.Between(0, ShopSectionSize - 1)]; if (tryShop.Count > 0 && tryShop.Count < 5 && !tryShop.Contains(allEntries[entry])) { tryShop.Add(allEntries[entry++]); } } foreach (var newShop in newShops) { newShop.Add(0); } var pointer = pointers[(int)shopType]; for (int i = 0; i < ShopSectionSize; i++) { if (newShops[i].Count > 1) { Put(ShopPointerBase + pointer, newShops[i].ToArray()); pointers[(int)shopType + i] = pointer; pointer += (ushort)newShops[i].Count; } } }
private List<List<byte>> Bucketize(List<byte> bytes, int bucketCount, int bucketSize, MT19337 rng) { var buckets = new List<List<byte>>(); for (int i = 0; i < bucketCount; i++) { buckets.Add(new List<byte>()); } int index = 0; while (index < bucketCount) { buckets[index].Add(bytes[index++]); } while (index < bytes.Count) { var bucket = rng.Between(0, buckets.Count - 1); if (buckets[bucket].Count < bucketSize) { buckets[bucket].Add(bytes[index++]); } } return buckets; }
private void GenerateTree(MT19337 rng) { root = new BSPMapNode(0, 0, MapRequirements.Width, MapRequirements.Height); all_nodes = new List <BSPMapNode> { root }; leaf_nodes = new HashSet <BSPMapNode> { root }; while (true) { var new_leafs = new List <BSPMapNode>(); foreach (var split_leaf in leaf_nodes.ToList(). Where(l => l.Width > MAX_LEAF_SIZE || l.Height > MAX_LEAF_SIZE || rng.Between(0, 3) < 3). // always split if too big, or on a 3/4 chance. Where(l => l.Split(rng))) // only select leafs that actually successfully split { // if we did split, add the new leafs to the list new_leafs.Add(split_leaf.LeftChild); new_leafs.Add(split_leaf.RightChild); leaf_nodes.Remove(split_leaf); } if (new_leafs.Count == 0) { break; } all_nodes.AddRange(new_leafs); leaf_nodes.UnionWith(new_leafs); } }
private void MakeHallway(MT19337 rng, Rectangle l, Rectangle r) { // now we connect these two rooms together with hallways. // this looks pretty complicated, but it's just trying to figure out which point is where and then either draw a straight line, or a pair of lines to make a right-angle to connect them. // you could do some extra logic to make your halls more bendy, or do some more advanced things if you wanted. Hallways = new List <Rectangle>(); int hallway_width = rng.Between(0, 4) == 4 ? rng.Between(1, BSPTreeEngine.MAX_HALLWAY_WIDTH) : BSPTreeEngine.MAX_HALLWAY_WIDTH; var point1 = new Point(rng.Between(l.Left, l.Right - hallway_width), rng.Between(l.Top, l.Bottom - hallway_width)); var point2 = new Point(rng.Between(r.Left, r.Right - hallway_width), rng.Between(r.Top, r.Bottom - hallway_width)); int x_difference = point2.X - point1.X; int y_difference = point2.Y - point1.Y; if (x_difference < 0) { if (y_difference < 0) { if (rng.Between(0, 1) == 0) { Hallways.Add(new Rectangle(point2.X, point1.Y, Math.Abs(x_difference), hallway_width)); Hallways.Add(new Rectangle(point2.X, point2.Y, hallway_width, Math.Abs(y_difference))); } else { Hallways.Add(new Rectangle(point2.X, point2.Y, Math.Abs(x_difference), hallway_width)); Hallways.Add(new Rectangle(point1.X, point2.Y, hallway_width, Math.Abs(y_difference))); } } else if (y_difference > 0) { if (rng.Between(0, 1) == 0) { Hallways.Add(new Rectangle(point2.X, point1.Y, Math.Abs(x_difference), hallway_width)); Hallways.Add(new Rectangle(point2.X, point1.Y, hallway_width, Math.Abs(y_difference))); } else { Hallways.Add(new Rectangle(point2.X, point2.Y, Math.Abs(x_difference), hallway_width)); Hallways.Add(new Rectangle(point1.X, point1.Y, hallway_width, Math.Abs(y_difference))); } } else // if (h == 0) { Hallways.Add(new Rectangle(point2.X, point2.Y, Math.Abs(x_difference), hallway_width)); } } else if (x_difference > 0) { if (y_difference < 0) { if (rng.Between(0, 1) == 0) { Hallways.Add(new Rectangle(point1.X, point2.Y, Math.Abs(x_difference), hallway_width)); Hallways.Add(new Rectangle(point1.X, point2.Y, hallway_width, Math.Abs(y_difference))); } else { Hallways.Add(new Rectangle(point1.X, point1.Y, Math.Abs(x_difference), hallway_width)); Hallways.Add(new Rectangle(point2.X, point2.Y, hallway_width, Math.Abs(y_difference))); } } else if (y_difference > 0) { if (rng.Between(0, 1) == 0) { Hallways.Add(new Rectangle(point1.X, point1.Y, Math.Abs(x_difference), hallway_width)); Hallways.Add(new Rectangle(point2.X, point1.Y, hallway_width, Math.Abs(y_difference))); } else { Hallways.Add(new Rectangle(point1.X, point2.Y, Math.Abs(x_difference), hallway_width)); Hallways.Add(new Rectangle(point1.X, point1.Y, hallway_width, Math.Abs(y_difference))); } } else // if (y_difference == 0) { Hallways.Add(new Rectangle(point1.X, point1.Y, Math.Abs(x_difference), hallway_width)); } } else // if (x_difference == 0) { if (y_difference < 0) { Hallways.Add(new Rectangle(point2.X, point2.Y, hallway_width, Math.Abs(y_difference))); } else if (y_difference > 0) { Hallways.Add(new Rectangle(point1.X, point1.Y, hallway_width, Math.Abs(y_difference))); } } }
private Point PointInAnyRandomRoom(MT19337 rng) { var some_room = (Rectangle)root.GetRandomRoom(rng); return(new Point(some_room.X + rng.Between(1, some_room.Width - 1), some_room.Y + rng.Between(1, some_room.Height - 1))); }
public void Randomize(Blob seed, Flags flags, Preferences preferences) { MT19337 rng; using (SHA256 hasher = SHA256.Create()) { Blob FlagsBlob = Encoding.UTF8.GetBytes(Flags.EncodeFlagsText(flags)); Blob SeedAndFlags = Blob.Concat(new Blob[] { FlagsBlob, seed }); Blob hash = hasher.ComputeHash(SeedAndFlags); rng = new MT19337(BitConverter.ToUInt32(hash, 0)); } if (flags.TournamentSafe) { AssureSafe(); } UpgradeToMMC3(); MakeSpace(); Bank1E(); Bank1B(); EasterEggs(); DynamicWindowColor(preferences.MenuColor); PermanentCaravan(); ShiftEarthOrbDown(); CastableItemTargeting(); FixEnemyPalettes(); // fixes a bug in the original game's programming that causes third enemy slot's palette to render incorrectly FixWarpBug(); // The warp bug must be fixed for magic level shuffle and spellcrafter SeparateUnrunnables(); var talkroutines = new TalkRoutines(); var npcdata = new NPCdata(this); UpdateDialogs(npcdata); if (flags.TournamentSafe) { Put(0x3FFE3, Blob.FromHex("66696E616C2066616E74617379")); } flags = Flags.ConvertAllTriState(flags, rng); TeleportShuffle teleporters = new TeleportShuffle(); var palettes = OverworldMap.GeneratePalettes(Get(OverworldMap.MapPaletteOffset, MapCount * OverworldMap.MapPaletteSize).Chunk(OverworldMap.MapPaletteSize)); var overworldMap = new OverworldMap(this, flags, palettes, teleporters); var maps = ReadMaps(); var shopItemLocation = ItemLocations.CaravanItemShop1; var oldItemNames = ReadText(ItemTextPointerOffset, ItemTextPointerBase, ItemTextPointerCount); if (flags.EFGWaterfall || flags.EFGEarth1 || flags.EFGEarth2) { MapRequirements reqs; MapGeneratorStrategy strategy; MapGenerator generator = new MapGenerator(); if (flags.EFGWaterfall) { reqs = new MapRequirements { MapId = MapId.Waterfall, Rom = this, }; strategy = MapGeneratorStrategy.WaterfallClone; CompleteMap waterfall = generator.Generate(rng, strategy, reqs); // Should add more into the reqs so that this can be done inside the generator. teleporters.Waterfall.SetEntrance(waterfall.Entrance); overworldMap.PutOverworldTeleport(OverworldTeleportIndex.Waterfall, teleporters.Waterfall); maps[(int)MapId.Waterfall] = waterfall.Map; } if (flags.EFGEarth1) { reqs = new MapRequirements { MapId = MapId.EarthCaveB1, Rom = this, }; strategy = MapGeneratorStrategy.Square; var earthB1 = generator.Generate(rng, strategy, reqs); // Should add more into the reqs so that this can be done inside the generator. teleporters.EarthCave1.SetEntrance(earthB1.Entrance); overworldMap.PutOverworldTeleport(OverworldTeleportIndex.EarthCave1, teleporters.EarthCave1); maps[(int)MapId.EarthCaveB1] = earthB1.Map; } if (flags.EFGEarth2) { reqs = new MapRequirements { MapId = MapId.EarthCaveB2, Rom = this, }; strategy = MapGeneratorStrategy.Square; var earthB2 = generator.Generate(rng, strategy, reqs); // Should add more into the reqs so that this can be done inside the generator. teleporters.EarthCave2.SetEntrance(earthB2.Entrance); overworldMap.PutStandardTeleport(TeleportIndex.EarthCave2, teleporters.EarthCave2, OverworldTeleportIndex.EarthCave1); maps[(int)MapId.EarthCaveB2] = earthB2.Map; } } var flippedMaps = new List <MapId>(); if ((bool)flags.FlipDungeons) { flippedMaps = HorizontalFlipDungeons(rng, maps, teleporters, overworldMap); } if ((bool)flags.RandomizeFormationEnemizer) { DoEnemizer(rng, (bool)flags.RandomizeEnemizer, (bool)flags.RandomizeFormationEnemizer, flags.EnemizerDontMakeNewScripts); } if (preferences.ModernBattlefield) { EnableModernBattlefield(); } if ((bool)flags.TitansTrove) { EnableTitansTrove(maps); } if ((bool)flags.LefeinShops) { EnableLefeinShops(maps); } // This has to be done before we shuffle spell levels. if (flags.SpellBugs) { FixSpellBugs(); } //must be done before spells get shuffled around otherwise we'd be changing a spell that isnt lock if (flags.LockMode != LockHitMode.Vanilla) { ChangeLockMode(flags.LockMode); } if (flags.EnemySpellsTargetingAllies) { FixEnemyAOESpells(); } if (flags.AllSpellLevelsForKnightNinja) { KnightNinjaChargesForAllLevels(); } if (flags.BuffHealingSpells) { BuffHealingSpells(); } UpdateMagicAutohitThreshold(rng, flags.MagicAutohitThreshold); if ((bool)flags.GenerateNewSpellbook) { CraftNewSpellbook(rng, (bool)flags.SpellcrafterMixSpells, flags.LockMode, (bool)flags.MagicLevels, (bool)flags.SpellcrafterRetainPermissions); } if ((bool)flags.MagisizeWeapons) { MagisizeWeapons(rng, (bool)flags.MagisizeWeaponsBalanced); } if ((bool)flags.ItemMagic) { ShuffleItemMagic(rng, (bool)flags.BalancedItemMagicShuffle); } if ((bool)flags.GuaranteedRuseItem) { CraftRuseItem(); } if ((bool)flags.ShortToFR) { ShortenToFR(maps, (bool)flags.PreserveFiendRefights, (bool)flags.PreserveAllFiendRefights, rng); } if (((bool)flags.Treasures) && flags.ShardHunt && !flags.FreeOrbs) { EnableShardHunt(rng, talkroutines, flags.ShardCount); } if ((bool)flags.TransformFinalFormation && !flags.SpookyFlag) { TransformFinalFormation((FinalFormation)rng.Between(0, Enum.GetValues(typeof(FinalFormation)).Length - 1), flags.EvadeCap); } var maxRetries = 8; for (var i = 0; i < maxRetries; i++) { try { overworldMap = new OverworldMap(this, flags, palettes, teleporters); if (((bool)flags.Entrances || (bool)flags.Floors || (bool)flags.Towns) && ((bool)flags.Treasures) && ((bool)flags.NPCItems)) { overworldMap.ShuffleEntrancesAndFloors(rng, flags); // Disable the Princess Warp back to Castle Coneria if ((bool)flags.Entrances || (bool)flags.Floors) { talkroutines.ReplaceChunk(newTalkRoutines.Talk_Princess1, Blob.FromHex("20CC90"), Blob.FromHex("EAEAEA")); } } if ((bool)flags.Treasures && (bool)flags.ShuffleObjectiveNPCs) { overworldMap.ShuffleObjectiveNPCs(rng); } IncentiveData incentivesData = new IncentiveData(rng, flags, overworldMap, shopItemLocation); if (((bool)flags.Shops)) { var excludeItemsFromRandomShops = new List <Item>(); if ((bool)flags.Treasures) { excludeItemsFromRandomShops = incentivesData.ForcedItemPlacements.Select(x => x.Item).Concat(incentivesData.IncentiveItems).ToList(); } if (!((bool)flags.RandomWaresIncludesSpecialGear)) { excludeItemsFromRandomShops.AddRange(ItemLists.SpecialGear); if ((bool)flags.GuaranteedRuseItem) { excludeItemsFromRandomShops.Add(Item.PowerRod); } } if ((bool)flags.NoMasamune) { excludeItemsFromRandomShops.Add(Item.Masamune); } shopItemLocation = ShuffleShops(rng, (bool)flags.ImmediatePureAndSoftRequired, ((bool)flags.RandomWares), excludeItemsFromRandomShops, flags.WorldWealth); incentivesData = new IncentiveData(rng, flags, overworldMap, shopItemLocation); } if ((bool)flags.Treasures) { generatedPlacement = ShuffleTreasures(rng, flags, incentivesData, shopItemLocation, overworldMap, teleporters); } break; } catch (InsaneException e) { Console.WriteLine(e.Message); if (maxRetries > (i + 1)) { continue; } throw new InvalidOperationException(e.Message); } } // Change Astos routine so item isn't lost in wall of text if ((bool)flags.NPCItems || (bool)flags.NPCFetchItems || (bool)flags.ShuffleAstos) { talkroutines.Replace(newTalkRoutines.Talk_Astos, Blob.FromHex("A674F005BD2060F027A5738561202096B020A572203D96A575200096A476207F90207392A5611820109F201896A9F060A57060")); } npcdata.UpdateItemPlacement(generatedPlacement); if ((bool)flags.MagicShopLocs) { ShuffleMagicLocations(rng); } if (((bool)flags.MagicShops)) { ShuffleMagicShops(rng); } if (((bool)flags.MagicLevels)) { ShuffleMagicLevels(rng, ((bool)flags.MagicPermissions), (bool)flags.MagicLevelsTiered, (bool)flags.MagicLevelsMixed, (bool)!flags.GenerateNewSpellbook); } new StartingInventory(rng, flags, this).SetStartingInventory(); /* * if (flags.WeaponPermissions) * { * ShuffleWeaponPermissions(rng); * } * * if (flags.ArmorPermissions) * { * ShuffleArmorPermissions(rng); * } */ if (flags.SaveGameWhenGameOver) { EnableSaveOnDeath(flags); } // Ordered before RNG shuffle. In the event that both flags are on, RNG shuffle depends on this. if (((bool)flags.FixMissingBattleRngEntry)) { FixMissingBattleRngEntry(); } if (((bool)flags.Rng)) { ShuffleRng(rng); } if (((bool)flags.EnemyScripts)) { ShuffleEnemyScripts(rng, (bool)flags.AllowUnsafePirates, (bool)!flags.BossScriptsOnly, ((bool)flags.EnemySkillsSpellsTiered || (bool)flags.ScaryImps), (bool)flags.ScaryImps); } if (((bool)flags.EnemySkillsSpells)) { if ((bool)flags.EnemySkillsSpellsTiered && (bool)!flags.BossSkillsOnly) { GenerateBalancedEnemyScripts(rng, (bool)flags.SwolePirates); ShuffleEnemySkillsSpells(rng, false); } else { ShuffleEnemySkillsSpells(rng, (bool)!flags.BossSkillsOnly); } } if (((bool)flags.EnemyStatusAttacks)) { if (((bool)flags.RandomStatusAttacks)) { RandomEnemyStatusAttacks(rng, (bool)flags.AllowUnsafePirates, (bool)flags.DisableStunTouch); } else { ShuffleEnemyStatusAttacks(rng, (bool)flags.AllowUnsafePirates); } } if (flags.Runnability == Runnability.Random) { flags.Runnability = (Runnability)Rng.Between(rng, 0, 3); } if (flags.Runnability == Runnability.AllRunnable) { CompletelyRunnable(); } else if (flags.Runnability == Runnability.AllUnrunnable) { CompletelyUnrunnable(); } else if (flags.Runnability == Runnability.Shuffle) { ShuffleUnrunnable(rng); } // Always on to supply the correct changes for WaitWhenUnrunnable AllowStrikeFirstAndSurprise(flags.WaitWhenUnrunnable, (bool)flags.UnrunnablesStrikeFirstAndSurprise); if (((bool)flags.EnemyFormationsSurprise)) { ShuffleSurpriseBonus(rng); } // Put this before other encounter / trap tile edits. if ((bool)flags.AllowUnsafeMelmond) { EnableMelmondGhetto(flags.EnemizerEnabled); } // After unrunnable shuffle and before formation shuffle. Perfect! if (flags.WarMECHMode != WarMECHMode.Vanilla) { WarMECHNpc(flags.WarMECHMode, npcdata, rng, maps); } if (flags.WarMECHMode == WarMECHMode.Unleashed) { UnleashWarMECH(); } if ((bool)flags.ClassAsNpcFiends || (bool)flags.ClassAsNpcKeyNPC) { ClassAsNPC(flags, talkroutines, npcdata, flippedMaps, rng); } if ((bool)flags.FiendShuffle) { FiendShuffle(rng); } if (flags.FormationShuffleMode != FormationShuffleMode.None && !flags.EnemizerEnabled) { ShuffleEnemyFormations(rng, flags.FormationShuffleMode); } if ((bool)flags.RemoveTrapTiles) { RemoveTrapTiles(flags.EnemizerEnabled); } if (((bool)flags.EnemyTrapTiles) && !flags.EnemizerEnabled) { ShuffleTrapTiles(rng, ((bool)flags.RandomTrapFormations)); } if ((bool)flags.OrdealsPillars) { ShuffleOrdeals(rng, maps); } if (flags.SkyCastle4FMazeMode == SkyCastle4FMazeMode.Maze) { DoSkyCastle4FMaze(rng, maps); } else if (flags.SkyCastle4FMazeMode == SkyCastle4FMazeMode.Teleporters) { ShuffleSkyCastle4F(rng, maps); } if ((bool)flags.ConfusedOldMen) { EnableConfusedOldMen(rng); } if ((bool)flags.EarlyOrdeals) { EnableEarlyOrdeals(); } if (flags.ChaosRush) { EnableChaosRush(); } if ((bool)flags.EarlyKing) { EnableEarlyKing(npcdata); } if ((bool)flags.EarlySarda) { EnableEarlySarda(npcdata); } if ((bool)flags.EarlySage) { EnableEarlySage(npcdata); } if ((bool)flags.FreeBridge) { EnableFreeBridge(); } if ((bool)flags.FreeAirship) { EnableFreeAirship(); } if ((bool)flags.FreeShip) { EnableFreeShip(); } if (flags.FreeOrbs) { EnableFreeOrbs(); } if ((bool)flags.FreeCanal) { EnableFreeCanal((bool)flags.NPCItems, npcdata); } if ((bool)flags.FreeCanoe) { EnableFreeCanoe(); } if ((bool)flags.FreeLute) { EnableFreeLute(); } if ((bool)flags.FreeTail && !(bool)flags.NoTail) { EnableFreeTail(); } if (flags.NoPartyShuffle) { DisablePartyShuffle(); } if (flags.SpeedHacks) { EnableSpeedHacks(); } if (flags.IdentifyTreasures) { EnableIdentifyTreasures(); } if (flags.Dash) { EnableDash(); } if (flags.BuyTen) { EnableBuyQuantity(); } else if (flags.BuyTenOld) { EnableBuyTen(); } if (flags.WaitWhenUnrunnable) { ChangeUnrunnableRunToWait(); } if (flags.SpeedHacks && flags.EnableCritNumberDisplay) { EnableCritNumberDisplay(); } if (flags.BattleMagicMenuWrapAround) { BattleMagicMenuWrapAround(); } if (flags.NPCSwatter) { EnableNPCSwatter(npcdata); } if (flags.EasyMode) { EnableEasyMode(); } if ((bool)flags.TrappedChests || (bool)flags.TCMasaGuardian || (bool)flags.TrappedShards) { MonsterInABox(rng, flags); } if (flags.HouseMPRestoration || flags.HousesFillHp) { FixHouse(flags.HouseMPRestoration, flags.HousesFillHp); } if (flags.BBCritRate) { DontDoubleBBCritRates(); } if (flags.WeaponCritRate) { DoubleWeaponCritRates(); } //needs to go after item magic, moved after double weapon crit to have more control over the actual number of crit gained. if ((bool)flags.RandomWeaponBonus) { RandomWeaponBonus(rng, flags.RandomWeaponBonusLow, flags.RandomWeaponBonusHigh, (bool)flags.RandomWeaponBonusExcludeMasa); } if ((bool)flags.RandomArmorBonus) { RandomArmorBonus(rng, flags.RandomArmorBonusLow, flags.RandomArmorBonusHigh); } if (flags.WeaponBonuses) { IncreaseWeaponBonus(flags.WeaponTypeBonusValue); } if (flags.WeaponStats) { FixWeaponStats(); } if (flags.ChanceToRun) { FixChanceToRun(); } if (flags.EnemyStatusAttackBug) { FixEnemyStatusAttackBug(); } if (flags.BlackBeltAbsorb) { FixBBAbsorbBug(); } if (flags.MDefMode != MDEFGrowthMode.None) { MDefChanges(flags.MDefMode); } if (flags.ThiefHitRate) { ThiefHitRate(); } if (flags.ImproveTurnOrderRandomization) { ImproveTurnOrderRandomization(rng); } if (flags.FixHitChanceCap) { FixHitChanceCap(); } if (flags.EnemyElementalResistancesBug) { FixEnemyElementalResistances(); } if (preferences.FunEnemyNames && !flags.EnemizerEnabled) { FunEnemyNames(preferences.TeamSteak); } var itemText = ReadText(ItemTextPointerOffset, ItemTextPointerBase, ItemTextPointerCount); itemText[(int)Item.Ribbon] = itemText[(int)Item.Ribbon].Remove(7); if ((bool)flags.HintsVillage || (bool)flags.HintsDungeon) { if ((bool)flags.HintsDungeon) { SetDungeonNPC(flippedMaps, rng); } NPCHints(rng, npcdata, flags, overworldMap); } if (flags.Etherizer && !flags.HouseMPRestoration && !flags.HousesFillHp) { Etherizer(); itemText[(int)Item.Tent] = "ETHR@p"; itemText[(int)Item.Cabin] = "DRY@p "; itemText[(int)Item.House] = "XETH@p"; } ExpGoldBoost(flags.ExpBonus, flags.ExpMultiplier); ScalePrices(flags, itemText, rng, ((bool)flags.ClampMinimumPriceScale), shopItemLocation); ScaleEncounterRate(flags.EncounterRate / 30.0, flags.DungeonEncounterRate / 30.0); overworldMap.ApplyMapEdits(); WriteMaps(maps); WriteText(itemText, ItemTextPointerOffset, ItemTextPointerBase, ItemTextOffset, UnusedGoldItems); if ((bool)flags.SwolePirates) { EnableSwolePirates(); } if (flags.EnemyScaleStatsHigh != 100 || flags.EnemyScaleStatsLow != 100 || ((bool)flags.SeparateEnemyHPScaling && (flags.EnemyScaleHpLow != 100 || flags.EnemyScaleHpHigh != 100))) { ScaleEnemyStats(rng, flags); } if (flags.BossScaleStatsHigh != 100 || flags.BossScaleStatsLow != 100 || ((bool)flags.SeparateBossHPScaling && (flags.BossScaleHpLow != 100 || flags.BossScaleHpHigh != 100))) { ScaleBossStats(rng, flags); } PartyComposition(rng, flags, preferences); if (((bool)flags.RecruitmentMode)) { PubReplaceClinic(rng, flags); } if ((bool)flags.ChangeMaxMP) { SetMPMax(flags.RedMageMaxMP, flags.WhiteMageMaxMP, flags.BlackMageMaxMP, flags.KnightMaxMP, flags.NinjaMaxMP); } if ((bool)flags.ShuffleAstos) { ShuffleAstos(flags, npcdata, talkroutines, rng); } if ((bool)flags.EnablePoolParty) { EnablePoolParty(flags, rng); } if ((bool)flags.MapCanalBridge) { EnableCanalBridge(); } if (flags.NoDanMode) { NoDanMode(); } SetProgressiveScaleMode(flags); if ((bool)flags.RandomizeClass) { RandomizeClass(rng, flags, oldItemNames); } if ((bool)flags.EnableRandomPromotions) { EnableRandomPromotions(flags, rng); } if (flags.DisableTentSaving) { CannotSaveOnOverworld(); } if (flags.DisableInnSaving) { CannotSaveAtInns(); } if (flags.PacifistMode && !flags.SpookyFlag) { PacifistEnd(talkroutines, npcdata, (bool)flags.EnemyTrapTiles || flags.EnemizerEnabled); } if (flags.ShopInfo) { ShopUpgrade(); } if (flags.SpookyFlag) { Spooky(talkroutines, npcdata, rng, flags); } if (flags.InventoryAutosort && !(preferences.RenounceAutosort)) { EnableInventoryAutosort(); } // We have to do "fun" stuff last because it alters the RNG state. // Back up Rng so that fun flags are uniform when different ones are selected uint funRngSeed = rng.Next(); RollCredits(rng); if (preferences.DisableDamageTileFlicker) { DisableDamageTileFlicker(); } if (preferences.ThirdBattlePalette) { UseVariablePaletteForCursorAndStone(); } if (preferences.PaletteSwap && !flags.EnemizerEnabled) { rng = new MT19337(funRngSeed); PaletteSwap(rng); } if (preferences.TeamSteak && !(bool)flags.RandomizeEnemizer) { TeamSteak(); } if (preferences.ChangeLute) { rng = new MT19337(funRngSeed); ChangeLute(rng); } rng = new MT19337(funRngSeed); HurrayDwarfFate(preferences.HurrayDwarfFate, npcdata, rng); if (preferences.Music != MusicShuffle.None) { rng = new MT19337(funRngSeed); ShuffleMusic(preferences.Music, rng); } if (preferences.DisableSpellCastFlash) { DisableSpellCastScreenFlash(); } npcdata.WriteNPCdata(this); talkroutines.WriteRoutines(this); talkroutines.UpdateNPCRoutines(this, npcdata); WriteSeedAndFlags(seed.ToHex(), Flags.EncodeFlagsText(flags)); ExtraTrackingAndInitCode(flags); }
public IncentiveData(MT19337 rng, IIncentiveFlags flags, OverworldMap map) { Dictionary <MapLocation, Tuple <List <MapChange>, AccessRequirement> > fullLocationRequirements = map.FullLocationRequirements; var forcedItemPlacements = ItemLocations.AllOtherItemLocations.ToList(); if (!flags.NPCItems) { forcedItemPlacements.AddRange(ItemLocations.AllNPCFreeItemLocations); } if (!flags.NPCFetchItems) { forcedItemPlacements.AddRange(ItemLocations.AllNPCFetchItemLocations); } if (!flags.Treasures) { forcedItemPlacements.AddRange(ItemLocations.AllTreasures); } var incentivePool = new List <Item>(); if (flags.IncentivizeBridge) { incentivePool.Add(Item.Bridge); } if (flags.IncentivizeShip) { incentivePool.Add(Item.Ship); } if (flags.IncentivizeCanal) { incentivePool.Add(Item.Canal); } if (flags.IncentivizeLute) { incentivePool.Add(Item.Lute); } if (flags.IncentivizeCrown) { incentivePool.Add(Item.Crown); } if (flags.IncentivizeCrystal) { incentivePool.Add(Item.Crystal); } if (flags.IncentivizeHerb) { incentivePool.Add(Item.Herb); } if (flags.IncentivizeKey) { incentivePool.Add(Item.Key); } if (flags.IncentivizeTnt) { incentivePool.Add(Item.Tnt); } if (flags.IncentivizeAdamant) { incentivePool.Add(Item.Adamant); } if (flags.IncentivizeSlab) { incentivePool.Add(Item.Slab); } if (flags.IncentivizeRuby) { incentivePool.Add(Item.Ruby); } if (flags.IncentivizeRod) { incentivePool.Add(Item.Rod); } if (flags.IncentivizeFloater) { incentivePool.Add(Item.Floater); } if (flags.IncentivizeChime) { incentivePool.Add(Item.Chime); } if (flags.IncentivizeTail) { incentivePool.Add(Item.Tail); } if (flags.IncentivizeCube) { incentivePool.Add(Item.Cube); } if (flags.IncentivizeBottle) { incentivePool.Add(Item.Bottle); } if (flags.IncentivizeOxyale) { incentivePool.Add(Item.Oxyale); } if (flags.IncentivizeCanoe) { incentivePool.Add(Item.Canoe); } if (flags.IncentivizeXcalber) { incentivePool.Add(Item.Xcalber); } if (flags.IncentivizeMasamune) { incentivePool.Add(Item.Masamune); } if (flags.IncentivizeRibbon) { incentivePool.Add(Item.Ribbon); } if (flags.IncentivizeRibbon2) { incentivePool.Add(Item.Ribbon); } if (flags.IncentivizeOpal) { incentivePool.Add(Item.Opal); } if (flags.Incentivize65K) { incentivePool.Add(Item.Gold65000); } if (flags.IncentivizeBad) { incentivePool.Add(Item.Cloth); } if (flags.IncentivizeDefCastArmor) { incentivePool.Add(Item.WhiteShirt); } if (flags.IncentivizeOffCastArmor) { incentivePool.Add(Item.BlackShirt); } if (flags.IncentivizeOtherCastArmor) { incentivePool.Add(Item.PowerGauntlets); } if (flags.IncentivizeDefCastWeapon) { incentivePool.Add(Item.Defense); } if (flags.IncentivizeOffCastWeapon) { incentivePool.Add(Item.ThorHammer); } if (flags.IncentivizeOtherCastWeapon) { incentivePool.Add(Item.BaneSword); } var incentiveLocationPool = new List <IRewardSource>(); if (flags.IncentivizeKingConeria) { incentiveLocationPool.Add(ItemLocations.KingConeria); } if (flags.IncentivizePrincess) { incentiveLocationPool.Add(ItemLocations.Princess); } if (flags.IncentivizeMatoya) { incentiveLocationPool.Add(ItemLocations.Matoya); } if (flags.IncentivizeBikke) { incentiveLocationPool.Add(ItemLocations.Bikke); } if (flags.IncentivizeElfPrince) { incentiveLocationPool.Add(ItemLocations.ElfPrince); } if (flags.IncentivizeAstos) { incentiveLocationPool.Add(ItemLocations.Astos); } if (flags.IncentivizeNerrick) { incentiveLocationPool.Add(ItemLocations.Nerrick); } if (flags.IncentivizeSmith) { incentiveLocationPool.Add(ItemLocations.Smith); } if (flags.IncentivizeSarda) { incentiveLocationPool.Add(ItemLocations.Sarda); } if (flags.IncentivizeCanoeSage) { incentiveLocationPool.Add(ItemLocations.CanoeSage); } if (flags.IncentivizeCubeBot) { incentiveLocationPool.Add(ItemLocations.CubeBot); } if (flags.IncentivizeFairy) { incentiveLocationPool.Add(ItemLocations.Fairy); } if (flags.IncentivizeLefein) { incentiveLocationPool.Add(ItemLocations.Lefein); } if (flags.IncentivizeVolcano) { incentiveLocationPool.Add(ItemLocations.VolcanoMajor); } if (flags.IncentivizeEarth) { incentiveLocationPool.Add(ItemLocations.EarthCaveMajor); } if (flags.IncentivizeMarsh) { incentiveLocationPool.Add(ItemLocations.MarshCaveMajor); } if (flags.IncentivizeMarshKeyLocked) { incentiveLocationPool.Add(ItemLocations.MarshCave13); } if (flags.IncentivizeSkyPalace) { incentiveLocationPool.Add(ItemLocations.SkyPalaceMajor); } if (flags.IncentivizeSeaShrine) { incentiveLocationPool.Add(ItemLocations.SeaShrineMajor); } if (flags.IncentivizeConeria) { incentiveLocationPool.Add(ItemLocations.ConeriaMajor); } if (flags.IncentivizeIceCave) { incentiveLocationPool.Add(ItemLocations.IceCaveMajor); } if (flags.IncentivizeOrdeals) { incentiveLocationPool.Add(ItemLocations.OrdealsMajor); } if (flags.IncentivizeCaravan) { incentiveLocationPool.Add(ItemLocations.CaravanItemShop1); } var itemLocationPool = ItemLocations.AllTreasures.Concat(ItemLocations.AllNPCItemLocations) .Where(x => !x.IsUnused && !forcedItemPlacements.Any(y => y.Address == x.Address)) .ToList(); if (flags.CrownlessOrdeals) { forcedItemPlacements = forcedItemPlacements .Select(x => ((x as TreasureChest)?.AccessRequirement.HasFlag(AccessRequirement.Crown) ?? false) ? new TreasureChest(x, x.Item, x.AccessRequirement & ~AccessRequirement.Crown) : x).ToList(); itemLocationPool = itemLocationPool .Select(x => ((x as TreasureChest)?.AccessRequirement.HasFlag(AccessRequirement.Crown) ?? false) ? new TreasureChest(x, x.Item, x.AccessRequirement & ~AccessRequirement.Crown) : x).ToList(); incentiveLocationPool = incentiveLocationPool .Select(x => ((x as TreasureChest)?.AccessRequirement.HasFlag(AccessRequirement.Crown) ?? false) ? new TreasureChest(x, x.Item, x.AccessRequirement & ~AccessRequirement.Crown) : x).ToList(); } if (flags.EarlySage) { forcedItemPlacements = forcedItemPlacements .Select(x => x.Address == ItemLocations.CanoeSage.Address ? new MapObject(ObjectId.CanoeSage, MapLocation.CrescentLake, x.Item) : x).ToList(); itemLocationPool = itemLocationPool .Select(x => x.Address == ItemLocations.CanoeSage.Address ? new MapObject(ObjectId.CanoeSage, MapLocation.CrescentLake, x.Item) : x).ToList(); incentiveLocationPool = incentiveLocationPool .Select(x => x.Address == ItemLocations.CanoeSage.Address ? new MapObject(ObjectId.CanoeSage, MapLocation.CrescentLake, x.Item) : x).ToList(); } if (flags.EarlySarda) { forcedItemPlacements = forcedItemPlacements .Select(x => x.Address == ItemLocations.Sarda.Address ? new MapObject(ObjectId.Sarda, MapLocation.SardasCave, x.Item) : x).ToList(); itemLocationPool = itemLocationPool .Select(x => x.Address == ItemLocations.Sarda.Address ? new MapObject(ObjectId.Sarda, MapLocation.SardasCave, x.Item) : x).ToList(); incentiveLocationPool = incentiveLocationPool .Select(x => x.Address == ItemLocations.Sarda.Address ? new MapObject(ObjectId.Sarda, MapLocation.SardasCave, x.Item) : x).ToList(); } MapLocation elfDoctorLocation = map.ObjectiveNPCs[ObjectId.ElfDoc]; if (elfDoctorLocation != MapLocation.ElflandCastle) { forcedItemPlacements = forcedItemPlacements .Select(x => x.Address == ItemLocations.ElfPrince.Address ? new MapObject(ObjectId.ElfPrince, MapLocation.ElflandCastle, x.Item, AccessRequirement.Herb, ObjectId.ElfDoc, requiredSecondLocation: elfDoctorLocation) : x).ToList(); itemLocationPool = itemLocationPool .Select(x => x.Address == ItemLocations.ElfPrince.Address ? new MapObject(ObjectId.ElfPrince, MapLocation.ElflandCastle, x.Item, AccessRequirement.Herb, ObjectId.ElfDoc, requiredSecondLocation: elfDoctorLocation) : x).ToList(); incentiveLocationPool = incentiveLocationPool .Select(x => x.Address == ItemLocations.ElfPrince.Address ? new MapObject(ObjectId.ElfPrince, MapLocation.ElflandCastle, x.Item, AccessRequirement.Herb, ObjectId.ElfDoc, requiredSecondLocation: elfDoctorLocation) : x).ToList(); } MapLocation unneLocation = map.ObjectiveNPCs[ObjectId.Unne]; if (unneLocation != MapLocation.Melmond) { forcedItemPlacements = forcedItemPlacements .Select(x => x.Address == ItemLocations.Lefein.Address ? new MapObject(ObjectId.Lefein, MapLocation.Lefein, x.Item, AccessRequirement.Slab, ObjectId.Unne, requiredSecondLocation: unneLocation) : x).ToList(); itemLocationPool = itemLocationPool .Select(x => x.Address == ItemLocations.Lefein.Address ? new MapObject(ObjectId.Lefein, MapLocation.Lefein, x.Item, AccessRequirement.Slab, ObjectId.Unne, requiredSecondLocation: unneLocation) : x).ToList(); incentiveLocationPool = incentiveLocationPool .Select(x => x.Address == ItemLocations.Lefein.Address ? new MapObject(ObjectId.Lefein, MapLocation.Lefein, x.Item, AccessRequirement.Slab, ObjectId.Unne, requiredSecondLocation: unneLocation) : x).ToList(); } foreach (var item in forcedItemPlacements.Select(x => x.Item)) { incentivePool.Remove(item); } var validKeyLocations = new List <IRewardSource> { ItemLocations.ElfPrince }; var validBridgeLocations = new List <IRewardSource> { ItemLocations.KingConeria }; var validShipLocations = new List <IRewardSource> { ItemLocations.Bikke }; var validCanoeLocations = new List <IRewardSource> { ItemLocations.CanoeSage }; if (flags.NPCFetchItems) { var validKeyMapLocations = ItemPlacement.AccessibleMapLocations(~(AccessRequirement.BlackOrb | AccessRequirement.Key), MapChange.All, fullLocationRequirements); validKeyLocations = itemLocationPool.Where(x => validKeyMapLocations.Contains(x.MapLocation) && validKeyMapLocations.Contains((x as MapObject)?.SecondLocation ?? MapLocation.StartingLocation)).ToList(); var keyPlacementRank = rng.Between(1, incentivePool.Count); if (incentivePool.Contains(Item.Key) && incentiveLocationPool.Any(x => validKeyLocations.Any(y => y.Address == x.Address)) && keyPlacementRank <= incentiveLocationPool.Count) { validKeyLocations = validKeyLocations.Where(x => incentiveLocationPool.Any(y => y.Address == x.Address)).ToList(); } else if (!flags.IncentivizeKey && incentivePool.Count >= incentiveLocationPool.Count) { validKeyLocations = validKeyLocations.Where(x => !incentiveLocationPool.Any(y => y.Address == x.Address)).ToList(); } } if (flags.NPCItems) { var everythingButCanoe = ~MapChange.Canoe; var everythingButOrbs = ~AccessRequirement.BlackOrb; var startingPotentialAccess = map.StartingPotentialAccess; var startingMapLocations = ItemPlacement.AccessibleMapLocations(startingPotentialAccess, MapChange.None, fullLocationRequirements); var validShipMapLocations = ItemPlacement.AccessibleMapLocations(startingPotentialAccess | AccessRequirement.Crystal, MapChange.Bridge, fullLocationRequirements); var validCanoeMapLocations = ItemPlacement.AccessibleMapLocations(everythingButOrbs, everythingButCanoe, fullLocationRequirements); validBridgeLocations = itemLocationPool.Where(x => startingMapLocations.Contains(x.MapLocation) && startingMapLocations.Contains((x as MapObject)?.SecondLocation ?? MapLocation.StartingLocation)).ToList(); validShipLocations = itemLocationPool.Where(x => validShipMapLocations.Contains(x.MapLocation) && validShipMapLocations.Contains((x as MapObject)?.SecondLocation ?? MapLocation.StartingLocation)).ToList(); validCanoeLocations = itemLocationPool.Where(x => validCanoeMapLocations.Contains(x.MapLocation) && validCanoeMapLocations.Contains((x as MapObject)?.SecondLocation ?? MapLocation.StartingLocation)).ToList(); var canoePlacementRank = rng.Between(1, incentivePool.Count); var validCanoeIncentives = validCanoeLocations.Where(x => incentiveLocationPool.Any(y => y.Address == x.Address)).ToList(); if (incentivePool.Contains(Item.Canoe) && canoePlacementRank <= incentiveLocationPool.Count && validKeyLocations.Union(validCanoeIncentives).Count() > 1) // The Key can be placed in at least one place more than than the Canoe { validCanoeLocations = validCanoeIncentives; } else if (!flags.IncentivizeBridge && incentivePool.Count >= incentiveLocationPool.Count) { validCanoeLocations = validCanoeLocations.Where(x => !incentiveLocationPool.Any(y => y.Address == x.Address)).ToList(); } } ForcedItemPlacements = forcedItemPlacements.ToList(); IncentiveItems = incentivePool.ToList(); BridgeLocations = validBridgeLocations .Where(x => !forcedItemPlacements.Any(y => y.Address == x.Address)) .ToList(); ShipLocations = validShipLocations .Where(x => !forcedItemPlacements.Any(y => y.Address == x.Address)) .ToList(); KeyLocations = validKeyLocations .Where(x => !forcedItemPlacements.Any(y => y.Address == x.Address)) .ToList(); CanoeLocations = validCanoeLocations .Where(x => !forcedItemPlacements.Any(y => y.Address == x.Address)) .ToList(); IncentiveLocations = incentiveLocationPool .Where(x => !forcedItemPlacements.Any(y => y.Address == x.Address)) .ToList(); AllValidItemLocations = itemLocationPool.ToList(); }
public CompleteMap Generate(MT19337 rng, MapRequirements reqs) { //Constants! Get yer constants here! int iteration_count = 15; //((List<RoomSpec>)reqs.Rooms)[0].Tiledata = ((List<RoomSpec>)reqs.Rooms)[0].Tiledata; CompleteMap complete = new CompleteMap { Map = new Map((byte)Tile.WaterfallInside) }; //(57,56) var startLoc = (x : 0x39, y : 0x38); var startingX = rng.Between(-3, 0) + startLoc.x; var startingY = rng.Between(-4, -1) + startLoc.y; var startRegion = new Region(startingX, startingY, 4, 5, Tile.WaterfallRandomEncounters); List <Region> regionList = new List <Region>(); regionList.Add(startRegion); List <Region> endingRegions = new List <Region>(); for (var i = 0; i < iteration_count; i++) { var startPoint = regionList[rng.Between(0, regionList.Count - 1)]; var newRegions = RegionChain(rng, startPoint, regionList, 30 - i); regionList.AddRange(newRegions); if (newRegions.Count > 0) { endingRegions.Add(newRegions[newRegions.Count - 1]); } } var foundRoomPlace = false; var room = ((List <RoomSpec>)reqs.Rooms)[0]; var room_x = -1; var room_y = -1; while (!foundRoomPlace && endingRegions.Count > 0) { var borderRegion = endingRegions.PickRandom(rng); var base_x = (borderRegion.x - (room.Width - 1) + 64) % 64; room_y = (borderRegion.y - room.Height + 64) % 64; var x_offset = 1; List <int> valid_offsets = new List <int>(); while (x_offset < room.Width) { room_x = (base_x + x_offset) % 64; var validRoomPlace = true; foreach (Region r in regionList) { var testVal = validRoomPlace && !r.IntersectsRoom(room, room_x, room_y); if (!testVal && validRoomPlace) { //Console.WriteLine(room_x); } validRoomPlace = testVal; } if (validRoomPlace) { valid_offsets.Add(x_offset); } x_offset++; } if (valid_offsets.Count != 0) { foundRoomPlace = true; room_x = (base_x + valid_offsets[rng.Between(0, valid_offsets.Count - 1)]); } endingRegions.Remove(borderRegion); } if (!foundRoomPlace) { List <int> idxs = Enumerable.Range(0, regionList.Count).ToList(); while (!foundRoomPlace && idxs.Count > 0) { int regionIdx = idxs.PickRandom(rng); var borderRegion = regionList[regionIdx]; var base_x = (borderRegion.x - (room.Width - 1) + 64) % 64; room_y = (borderRegion.y - (room.Height - 1) + 64) % 64; var x_offset = 1; List <int> valid_offsets = new List <int>(); while (x_offset < room.Width) { room_x = (base_x + x_offset) % 64; var validRoomPlace = true; foreach (Region r in regionList) { var testVal = validRoomPlace && !r.IntersectsRoom(room, room_x, room_y); if (!testVal && validRoomPlace) { //Console.WriteLine(room_x); } validRoomPlace = testVal; } if (validRoomPlace) { valid_offsets.Add(x_offset); } x_offset++; } if (valid_offsets.Count != 0) { foundRoomPlace = true; room_x = (base_x + valid_offsets[rng.Between(0, valid_offsets.Count - 1)]); } idxs.Remove(regionIdx); } } if (!foundRoomPlace) { Console.WriteLine("No room found :o"); return(null); } //Draw every room in regionList to complete foreach (Region r in regionList) { r.DrawRegion(complete); } Region waterfallRoom = new Region(room_x, room_y, room); waterfallRoom.DrawRegion(complete); int doorYPos = (room_y + room.Height) % 64; List <int> possibleDoors = new List <int>(); for (var i = 0; i < room.Width; i++) { if (complete.Map[((room_x + i) % 64, doorYPos)].Tile == Tile.WaterfallRandomEncounters)