using System; using System.Collections.Generic; using System.Linq; namespace PKHeX.Core { /// /// /// encounter area /// public sealed class EncounterArea2 : EncounterAreaGB { /// /// Gets the encounter areas with information from Generation 2 Grass/Water data. /// /// Input raw data. /// Array of encounter areas. public static EncounterArea2[] GetArray2GrassWater(byte[] data) { int ofs = 0; var areas = new List(); areas.AddRange(GetAreas2(data, ref ofs, SlotType.Grass, 3, 7)); // Johto Grass areas.AddRange(GetAreas2(data, ref ofs, SlotType.Surf, 1, 3)); // Johto Water areas.AddRange(GetAreas2(data, ref ofs, SlotType.Grass, 3, 7)); // Kanto Grass areas.AddRange(GetAreas2(data, ref ofs, SlotType.Surf, 1, 3)); // Kanto Water areas.AddRange(GetAreas2(data, ref ofs, SlotType.Swarm, 3, 7)); // Swarm areas.AddRange(GetAreas2(data, ref ofs, SlotType.Special, 1, 3)); // Union Cave return areas.ToArray(); } // Fishing Tables are not associated to a single map; a map picks a table to use. // For all maps that use a table, create a new EncounterArea with reference to the table's slots. private static readonly sbyte[] convMapIDtoFishLocationID = { -1, 1, -1, 0, 3, 3, 3, -1, 10, 3, 2, -1, -1, 2, 3, 0, -1, -1, 3, -1, -1, -1, 3, -1, -1, -1, -1, 0, -1, -1, 0, 9, 1, 0, 2, 2, -1, 3, 7, 3, -1, 3, 4, 8, 2, -1, 2, 1, -1, 3, -1, -1, -1, -1, -1, 0, 2, 2, -1, -1, 3, 1, -1, -1, -1, 2, -1, 2, -1, -1, -1, -1, -1, -1, 10, 10, -1, -1, -1, -1, -1, 7, 0, 1, -1, 1, 1, 3, -1, -1, -1, 1, 1, 2, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, }; /// /// Gets the encounter areas with information from Generation 2 Grass/Water data. /// /// Input raw data. /// Array of encounter areas. public static EncounterArea2[] GetArray2Fishing(byte[] data) { int ofs = 0; var f = GetAreas2Fishing(data, ref ofs); var areas = new List(); for (int i = 0; i < convMapIDtoFishLocationID.Length; i++) { var loc = convMapIDtoFishLocationID[i]; if (loc == -1) // no table for map continue; areas.Add(new EncounterArea2 { Location = i, Slots = f[loc].Slots }); } // Some maps have two tables. Fortunately, there's only two. Add the second table. areas.Add(new EncounterArea2 { Location = 0x1B, Slots = f[1].Slots }); // Olivine City (0: Harbor, 1: City) areas.Add(new EncounterArea2 { Location = 0x2E, Slots = f[3].Slots }); // Silver Cave (2: Inside, 3: Outside) return areas.ToArray(); } public static EncounterArea2[] GetArray2Headbutt(byte[] data) { int ofs = 0; return GetAreas2Headbutt(data, ref ofs).ToArray(); } private static EncounterSlot1[] GetSlots2GrassWater(byte[] data, ref int ofs, SlotType t, int slotSets, int slotCount) { byte[] rates = new byte[slotSets]; for (int i = 0; i < rates.Length; i++) rates[i] = data[ofs++]; var slots = ReadSlots1(data, ref ofs, slotSets * slotCount, t, rates[0]); if (slotSets <= 1) return slots; for (int i = 0; i < slotCount; i++) { slots[i].Time = EncounterTime.Morning; } for (int r = 1; r < slotSets; r++) { for (int i = 0; i < slotCount; i++) { int index = i + (r * slotCount); slots[index].Rate = rates[r]; slots[index].SlotNumber = i; slots[index].Time = r == 1 ? EncounterTime.Day : EncounterTime.Night; } } return slots; } private static EncounterSlot1[] GetSlots2Fishing(byte[] data, ref int ofs, SlotType t) { // slot set ends with final slot having 0xFF 0x** 0x** const int size = 3; int end = ofs; // scan for count while (data[end] != 0xFF) end += size; var count = ((end - ofs) / size) + 1; var slots = new EncounterSlot1[count]; for (int i = 0; i < slots.Length; i++) { int rate = data[ofs++]; int species = data[ofs++]; int level = data[ofs++]; slots[i] = new EncounterSlot1 { Rate = rate, Species = species, LevelMin = level, LevelMax = level, SlotNumber = i, Type = species == 0 ? SlotType.Special : t // day/night specific }; } return slots; } private static EncounterSlot1[] GetSlots2Headbutt(byte[] data, ref int ofs, SlotType t) { // slot set ends in 0xFF var slots = new List(); int tableCount = t == SlotType.Headbutt ? 2 : 1; SlotType slottype = t; while (tableCount != 0) { if (t == SlotType.Headbutt) slottype = tableCount == 2 ? SlotType.Headbutt_Special : SlotType.Headbutt; int rate = data[ofs++]; if (rate == 0xFF) // end of table { tableCount--; continue; } int species = data[ofs++]; int level = data[ofs++]; slots.Add(new EncounterSlot1 { Rate = rate, Species = species, LevelMin = level, LevelMax = level, Type = slottype }); } return slots.ToArray(); } private static IEnumerable GetAreas2(byte[] data, ref int ofs, SlotType t, int slotSets, int slotCount) { var areas = new List(); while (data[ofs] != 0xFF) // end { var location = data[ofs++] << 8 | data[ofs++]; var slots = GetSlots2GrassWater(data, ref ofs, t, slotSets, slotCount); var area = new EncounterArea2 { Location = location, Slots = slots, }; foreach (var slot in slots) slot.Area = area; areas.Add(area); } ofs++; return areas; } private static List GetAreas2Fishing(byte[] data, ref int ofs) { var areas = new List(); var types = new[] { SlotType.Old_Rod, SlotType.Good_Rod, SlotType.Super_Rod }; while (ofs != 0x18C) { areas.Add(new EncounterArea2 { Slots = GetSlots2Fishing(data, ref ofs, types[0]) .Concat(GetSlots2Fishing(data, ref ofs, types[1])) .Concat(GetSlots2Fishing(data, ref ofs, types[2])).ToArray() }); } // Read TimeFishGroups var dl = new List(); while (ofs < data.Length) dl.Add(new SlotTemplate(data[ofs++], data[ofs++])); // Add TimeSlots foreach (var area in areas) { var slots = area.Slots; for (int i = 0; i < slots.Length; i++) { var slot = slots[i]; if (slot.Type != SlotType.Special) continue; Array.Resize(ref slots, slots.Length + 1); Array.Copy(slots, i, slots, i + 1, slots.Length - i - 1); // shift slots down slots[i + 1] = slot.Clone(); // differentiate copied slot int index = slot.LevelMin * 2; for (int j = 0; j < 2; j++) // load special slot info { var s = (EncounterSlot1)slots[i + j]; s.Species = dl[index + j].Species; s.LevelMin = s.LevelMax = dl[index + j].Level; s.Type = slots[i - 1].Type; // special slots are never first in a set, so copy previous type s.Time = j == 0 ? EncounterTime.Morning | EncounterTime.Day : EncounterTime.Night; } } area.Slots = slots; } return areas; } private readonly struct SlotTemplate { public readonly byte Species; public readonly byte Level; public SlotTemplate(byte species, byte level) { Species = species; Level = level; } } private static IEnumerable GetAreas2Headbutt(byte[] data, ref int ofs) { // Read Location Table var head = new List(); var headID = new List(); while (data[ofs] != 0xFF) { head.Add(new EncounterArea2 { Location = (data[ofs++] << 8) | data[ofs++], //Slots = null, // later }); headID.Add(data[ofs++]); } ofs++; var rock = new List(); var rockID = new List(); while (data[ofs] != 0xFF) { rock.Add(new EncounterArea2 { Location = (data[ofs++] << 8) | data[ofs++], //Slots = null, // later }); rockID.Add(data[ofs++]); } ofs++; ofs += 0x16; // jump over GetTreeMons // Read ptr table int[] ptr = new int[data.Length == 0x109 ? 6 : 9]; // GS : C for (int i = 0; i < ptr.Length; i++) ptr[i] = data[ofs++] | (data[ofs++] << 8); int baseOffset = ptr.Min() - ofs; // Read Tables for (int i = 0; i < head.Count; i++) { int o = ptr[headID[i]] - baseOffset; head[i].Slots = GetSlots2Headbutt(data, ref o, SlotType.Headbutt); } for (int i = 0; i < rock.Count; i++) { int o = ptr[rockID[i]] - baseOffset; rock[i].Slots = GetSlots2Headbutt(data, ref o, SlotType.Rock_Smash); } return head.Concat(rock); } } }