Browse Source

- Added most of the new stuff for home status, a better ui

master
Quildra 1 year ago
parent
commit
310e025dac
  1. 336
      DBEditor/DBEditor.py
  2. 49
      DataGatherers/DefaultForms.json
  3. 30
      DataGatherers/DetermineOriginGame.py
  4. 146
      DataGatherers/update_location_information.py
  5. 186
      DataGatherers/update_storable_in_home.py
  6. 132
      patches.json
  7. BIN
      pokemon_forms.db

336
DBEditor/DBEditor.py

@ -1,7 +1,7 @@
import sys import sys
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QListWidget, QLineEdit, from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QListWidget, QLineEdit,
QLabel, QCheckBox, QPushButton, QFormLayout, QListWidgetItem, QSplitter, QTreeWidget, QLabel, QCheckBox, QPushButton, QFormLayout, QListWidgetItem, QSplitter, QTreeWidget,
QTreeWidgetItem, QDialog, QDialogButtonBox, QComboBox, QMessageBox) QTreeWidgetItem, QDialog, QDialogButtonBox, QComboBox, QMessageBox, QSpinBox)
from PyQt6.QtCore import Qt, QSize from PyQt6.QtCore import Qt, QSize
from PyQt6.QtGui import QPixmap, QFontMetrics, QColor from PyQt6.QtGui import QPixmap, QFontMetrics, QColor
import sqlite3 import sqlite3
@ -54,6 +54,10 @@ class EvolutionEditDialog(QDialog):
def delete_evolution(self): def delete_evolution(self):
self.done(2) # Use a custom return code for delete action self.done(2) # Use a custom return code for delete action
def parse_pfic(pfic):
parts = pfic.split('-')
return tuple(int(part) if part.isdigit() else part for part in parts)
class DBEditor(QMainWindow): class DBEditor(QMainWindow):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@ -65,7 +69,9 @@ class DBEditor(QMainWindow):
self.init_database() self.init_database()
self.patches = self.load_and_apply_patches() self.patches = self.load_and_apply_patches()
self.encounter_cache = {} # Add this line
self.init_ui() self.init_ui()
#self.load_pokemon_list() # Make sure this is called after init_ui
def init_database(self): def init_database(self):
# Copy the original database to the in-memory database # Copy the original database to the in-memory database
@ -121,15 +127,28 @@ class DBEditor(QMainWindow):
# Left side: Search and List # Left side: Search and List
left_layout = QVBoxLayout() left_layout = QVBoxLayout()
search_layout = QHBoxLayout()
self.search_bar = QLineEdit() self.search_bar = QLineEdit()
self.search_bar.setPlaceholderText("Search Pokémon...") self.search_bar.setPlaceholderText("Search Pokémon...")
self.search_bar.textChanged.connect(self.filter_pokemon_list) self.search_bar.textChanged.connect(self.filter_pokemon_list)
left_layout.addWidget(self.search_bar) search_layout.addWidget(self.search_bar)
left_layout.addLayout(search_layout)
self.pokemon_list = QListWidget() self.pokemon_list = QListWidget()
self.pokemon_list.currentItemChanged.connect(self.load_pokemon_details) self.pokemon_list.currentItemChanged.connect(self.load_pokemon_details)
left_layout.addWidget(self.pokemon_list) left_layout.addWidget(self.pokemon_list)
# Move the checkbox here, after the pokemon_list
self.highlight_no_encounters = QCheckBox("Highlight Pokémon without encounters")
self.highlight_no_encounters.stateChanged.connect(self.toggle_highlight_mode)
left_layout.addWidget(self.highlight_no_encounters)
# Add the new checkbox for filtering Home-storable Pokémon
self.filter_home_storable = QCheckBox("Show only Home-storable Pokémon")
self.filter_home_storable.stateChanged.connect(self.filter_pokemon_list)
left_layout.addWidget(self.filter_home_storable)
# Right side: Edit panel # Right side: Edit panel
right_layout = QHBoxLayout() right_layout = QHBoxLayout()
@ -156,6 +175,19 @@ class DBEditor(QMainWindow):
self.evolution_tree.setColumnWidth(0, 200) self.evolution_tree.setColumnWidth(0, 200)
text_layout.addWidget(self.evolution_tree) text_layout.addWidget(self.evolution_tree)
# Add Locations tree
self.locations_tree = QTreeWidget()
self.locations_tree.setHeaderLabels(["Game/Location", "Details"])
self.locations_tree.setColumnWidth(0, 200)
self.locations_tree.itemDoubleClicked.connect(self.edit_encounter)
text_layout.addWidget(QLabel("Locations:"))
text_layout.addWidget(self.locations_tree)
# Add New Encounter button
self.add_encounter_button = QPushButton("Add New Encounter")
self.add_encounter_button.clicked.connect(self.add_new_encounter)
text_layout.addWidget(self.add_encounter_button)
# Move buttons to the bottom # Move buttons to the bottom
text_layout.addStretch(1) text_layout.addStretch(1)
@ -187,7 +219,6 @@ class DBEditor(QMainWindow):
main_layout.addLayout(right_layout, 1) main_layout.addLayout(right_layout, 1)
self.load_pokemon_list() self.load_pokemon_list()
self.adjust_list_width()
def adjust_list_width(self): def adjust_list_width(self):
max_width = 0 max_width = 0
@ -202,13 +233,16 @@ class DBEditor(QMainWindow):
self.search_bar.setFixedWidth(list_width) self.search_bar.setFixedWidth(list_width)
def load_pokemon_list(self): def load_pokemon_list(self):
self.pokemon_list.clear()
self.cursor.execute(''' self.cursor.execute('''
SELECT pf.PFIC, pf.name, pf.form_name, pf.national_dex SELECT pf.PFIC, pf.name, pf.form_name, pf.national_dex
FROM pokemon_forms pf FROM pokemon_forms pf
ORDER BY pf.national_dex, pf.form_name
''') ''')
pokemon_data = self.cursor.fetchall() pokemon_data = self.cursor.fetchall()
# Sort the pokemon_data based on PFIC
pokemon_data.sort(key=lambda x: parse_pfic(x[0]))
for pfic, name, form_name, national_dex in pokemon_data: for pfic, name, form_name, national_dex in pokemon_data:
display_name = f"{national_dex:04d} - {name}" display_name = f"{national_dex:04d} - {name}"
if form_name: if form_name:
@ -217,11 +251,33 @@ class DBEditor(QMainWindow):
item.setData(Qt.ItemDataRole.UserRole, pfic) item.setData(Qt.ItemDataRole.UserRole, pfic)
self.pokemon_list.addItem(item) self.pokemon_list.addItem(item)
self.update_encounter_cache()
self.update_pokemon_list_highlights()
self.adjust_list_width()
self.filter_pokemon_list()
def filter_pokemon_list(self): def filter_pokemon_list(self):
search_text = self.search_bar.text().lower() search_text = self.search_bar.text().lower()
show_only_home_storable = self.filter_home_storable.isChecked()
for i in range(self.pokemon_list.count()): for i in range(self.pokemon_list.count()):
item = self.pokemon_list.item(i) item = self.pokemon_list.item(i)
item.setHidden(search_text not in item.text().lower()) pfic = item.data(Qt.ItemDataRole.UserRole)
# Check if the item matches the search text
text_match = search_text in item.text().lower()
# Check if the item is storable in Home (if the filter is active)
home_storable = True
if show_only_home_storable:
self.cursor.execute('SELECT storable_in_home FROM pokemon_storage WHERE PFIC = ?', (pfic,))
result = self.cursor.fetchone()
home_storable = result[0] if result else False
# Show the item only if it matches both filters
item.setHidden(not (text_match and home_storable))
self.update_pokemon_list_highlights()
def load_pokemon_details(self, current, previous): def load_pokemon_details(self, current, previous):
if not current: if not current:
@ -243,6 +299,7 @@ class DBEditor(QMainWindow):
self.national_dex_label.setText(str(national_dex)) self.national_dex_label.setText(str(national_dex))
self.generation_label.setText(str(generation)) self.generation_label.setText(str(generation))
self.home_checkbox.setChecked(bool(storable_in_home)) self.home_checkbox.setChecked(bool(storable_in_home))
self.home_checkbox.stateChanged.connect(self.update_home_storable)
# Load and display the image # Load and display the image
image_path = f"images-new/{pfic}.png" image_path = f"images-new/{pfic}.png"
@ -255,9 +312,19 @@ class DBEditor(QMainWindow):
# Load and display evolution chain # Load and display evolution chain
self.load_evolution_chain(pfic) self.load_evolution_chain(pfic)
# Load and display encounter locations
self.load_encounter_locations(pfic)
self.current_pfic = pfic self.current_pfic = pfic
self.add_evolution_button.setEnabled(True) # Enable the button when a Pokémon is selected self.add_evolution_button.setEnabled(True) # Enable the button when a Pokémon is selected
def update_home_storable(self):
if hasattr(self, 'current_pfic'):
storable_in_home = self.home_checkbox.isChecked()
self.cursor.execute('UPDATE pokemon_storage SET storable_in_home = ? WHERE PFIC = ?', (storable_in_home, self.current_pfic))
self.conn.commit()
self.filter_pokemon_list() # Reapply the filter
def edit_evolution(self, item, column): def edit_evolution(self, item, column):
parent = item.parent() parent = item.parent()
if not parent: if not parent:
@ -443,6 +510,265 @@ class DBEditor(QMainWindow):
# Refresh the evolution chain display # Refresh the evolution chain display
self.load_evolution_chain(self.current_pfic) self.load_evolution_chain(self.current_pfic)
def load_encounter_locations(self, pfic):
self.locations_tree.clear()
self.cursor.execute('''
SELECT game, location, day, time, dual_slot, static_encounter_count, static_encounter, extra_text, stars, rods, fishing
FROM encounters
WHERE pfic = ?
ORDER BY game, location
''', (pfic,))
encounters = self.cursor.fetchall()
game_items = {}
for encounter in encounters:
game, location, day, time, dual_slot, static_encounter_count, static_encounter, extra_text, stars, rods, fishing = encounter
if game not in game_items:
game_item = QTreeWidgetItem([game])
game_items[game] = game_item
# Use generation for sorting, default to 0 if not found
game_item.setData(0, Qt.ItemDataRole.UserRole, self.game_generations.get(game, 0))
location_item = QTreeWidgetItem([location])
details = []
if day:
details.append(f"Day: {day}")
if time:
details.append(f"Time: {time}")
if dual_slot:
details.append(f"Dual Slot: {dual_slot}")
if static_encounter:
details.append(f"Static Encounter (Count: {static_encounter_count})")
if extra_text:
details.append(f"Extra: {extra_text}")
if stars:
details.append(f"Stars: {stars}")
if fishing:
details.append(f"Fishing")
if rods:
details.append(f"Rods: {rods}")
location_item.setText(1, ", ".join(details))
game_items[game].addChild(location_item)
# Sort game items by generation and add them to the tree
sorted_game_items = sorted(game_items.values(), key=lambda x: x.data(0, Qt.ItemDataRole.UserRole))
self.locations_tree.addTopLevelItems(sorted_game_items)
self.locations_tree.expandAll()
# Update the cache for this Pokémon
self.encounter_cache[pfic] = len(encounters) > 0
# After updating the locations tree
self.update_pokemon_list_highlights()
def edit_encounter(self, item, column):
if item.parent() is None: # This is a game item, not a location item
return
game = item.parent().text(0)
location = item.text(0)
dialog = QDialog(self)
dialog.setWindowTitle("Edit Encounter")
layout = QFormLayout(dialog)
game_edit = QLineEdit(game)
location_edit = QLineEdit(location)
day_edit = QLineEdit()
time_edit = QLineEdit()
dual_slot_edit = QLineEdit()
static_encounter_check = QCheckBox("Static Encounter")
static_encounter_count_edit = QSpinBox()
extra_text_edit = QLineEdit()
stars_edit = QLineEdit()
fishing_check = QCheckBox("Fishing")
rods_edit = QLineEdit()
layout.addRow("Game:", game_edit)
layout.addRow("Location:", location_edit)
layout.addRow("Day:", day_edit)
layout.addRow("Time:", time_edit)
layout.addRow("Dual Slot:", dual_slot_edit)
layout.addRow("Static Encounter:", static_encounter_check)
layout.addRow("Static Encounter Count:", static_encounter_count_edit)
layout.addRow("Extra Text:", extra_text_edit)
layout.addRow("Stars:", stars_edit)
layout.addRow("Fishing:", fishing_check)
layout.addRow("Rods:", rods_edit)
# Fetch current values
self.cursor.execute('''
SELECT day, time, dual_slot, static_encounter_count, static_encounter, extra_text, stars, fishing, rods
FROM encounters
WHERE pfic = ? AND game = ? AND location = ?
''', (self.current_pfic, game, location))
current_values = self.cursor.fetchone()
if current_values:
day, time, dual_slot, static_encounter_count, static_encounter, extra_text, stars, fishing, rods = current_values
day_edit.setText(day or "")
time_edit.setText(time or "")
dual_slot_edit.setText(dual_slot or "")
static_encounter_check.setChecked(bool(static_encounter))
static_encounter_count_edit.setValue(static_encounter_count or 0)
extra_text_edit.setText(extra_text or "")
stars_edit.setText(stars or "")
fishing_check.setChecked(bool(fishing))
rods_edit.setText(rods or "")
buttons = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel,
Qt.Orientation.Horizontal, dialog)
buttons.accepted.connect(dialog.accept)
buttons.rejected.connect(dialog.reject)
layout.addRow(buttons)
if dialog.exec() == QDialog.DialogCode.Accepted:
new_game = game_edit.text()
new_location = location_edit.text()
new_day = day_edit.text() or None
new_time = time_edit.text() or None
new_dual_slot = dual_slot_edit.text() or None
new_static_encounter = static_encounter_check.isChecked()
new_static_encounter_count = static_encounter_count_edit.value()
new_extra_text = extra_text_edit.text() or None
new_stars = stars_edit.text() or None
new_fishing = fishing_check.isChecked()
new_rods = rods_edit.text() or None
# Update the database
self.cursor.execute('''
UPDATE encounters
SET game = ?, location = ?, day = ?, time = ?, dual_slot = ?,
static_encounter = ?, static_encounter_count = ?, extra_text = ?,
stars = ?, fishing = ?, rods = ?
WHERE pfic = ? AND game = ? AND location = ?
''', (new_game, new_location, new_day, new_time, new_dual_slot,
new_static_encounter, new_static_encounter_count, new_extra_text,
new_stars, new_fishing, new_rods,
self.current_pfic, game, location))
self.conn.commit()
# Update the cache if all encounters for this Pokémon were deleted
if not self.check_pokemon_has_encounters(self.current_pfic):
self.encounter_cache[self.current_pfic] = False
# Refresh the locations tree
self.load_encounter_locations(self.current_pfic)
def add_new_encounter(self):
dialog = QDialog(self)
dialog.setWindowTitle("Add New Encounter")
layout = QFormLayout(dialog)
game_edit = QLineEdit()
location_edit = QLineEdit()
day_edit = QLineEdit()
time_edit = QLineEdit()
dual_slot_edit = QLineEdit()
static_encounter_check = QCheckBox("Static Encounter")
static_encounter_count_edit = QSpinBox()
extra_text_edit = QLineEdit()
stars_edit = QLineEdit()
fishing_check = QCheckBox("Fishing")
rods_edit = QLineEdit()
layout.addRow("Game:", game_edit)
layout.addRow("Location:", location_edit)
layout.addRow("Day:", day_edit)
layout.addRow("Time:", time_edit)
layout.addRow("Dual Slot:", dual_slot_edit)
layout.addRow("Static Encounter:", static_encounter_check)
layout.addRow("Static Encounter Count:", static_encounter_count_edit)
layout.addRow("Extra Text:", extra_text_edit)
layout.addRow("Stars:", stars_edit)
layout.addRow("Fishing:", fishing_check)
layout.addRow("Rods:", rods_edit)
buttons = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel,
Qt.Orientation.Horizontal, dialog)
buttons.accepted.connect(dialog.accept)
buttons.rejected.connect(dialog.reject)
layout.addRow(buttons)
if dialog.exec() == QDialog.DialogCode.Accepted:
game = game_edit.text()
location = location_edit.text()
day = day_edit.text() or None
time = time_edit.text() or None
dual_slot = dual_slot_edit.text() or None
static_encounter = static_encounter_check.isChecked()
static_encounter_count = static_encounter_count_edit.value()
extra_text = extra_text_edit.text() or None
stars = stars_edit.text() or None
fishing = fishing_check.isChecked()
rods = rods_edit.text() or None
# Insert new encounter into the database
self.cursor.execute('''
INSERT INTO encounters
(pfic, game, location, day, time, dual_slot, static_encounter, static_encounter_count, extra_text, stars, fishing, rods)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (self.current_pfic, game, location, day, time, dual_slot, static_encounter, static_encounter_count, extra_text, stars, fishing, rods))
self.conn.commit()
# Update the cache
self.encounter_cache[self.current_pfic] = True
# Refresh the locations tree
self.load_encounter_locations(self.current_pfic)
# Add this as a class attribute in the DBEditor class
game_generations = {
"Red": 1, "Blue": 1, "Yellow": 1,
"Gold": 2, "Silver": 2, "Crystal": 2,
"Ruby": 3, "Sapphire": 3, "Emerald": 3, "FireRed": 3, "LeafGreen": 3,
"Diamond": 4, "Pearl": 4, "Platinum": 4, "HeartGold": 4, "SoulSilver": 4,
"Black": 5, "White": 5, "Black 2": 5, "White 2": 5,
"X": 6, "Y": 6, "Omega Ruby": 6, "Alpha Sapphire": 6,
"Sun": 7, "Moon": 7, "Ultra Sun": 7, "Ultra Moon": 7,
"Sword": 8, "Shield": 8, "Brilliant Diamond": 8, "Shining Pearl": 8, "Expansion Pass": 8,
"Legends: Arceus": 8,
"Scarlet": 9, "Violet": 9, "The Teal Mask": 9, "The Hidden Treasure of Area Zero": 9, "The Hidden Treasure of Area Zero (Scarlet)": 9, "The Hidden Treasure of Area Zero (Violet)": 9, "The Teal Mask (Scarlet)": 9, "The Teal Mask (Violet)": 9,
"Pokémon Go": 0, "Pokémon Home": 0
}
def toggle_highlight_mode(self):
self.update_pokemon_list_highlights()
def update_pokemon_list_highlights(self):
highlight_mode = self.highlight_no_encounters.isChecked()
for i in range(self.pokemon_list.count()):
item = self.pokemon_list.item(i)
pfic = item.data(Qt.ItemDataRole.UserRole)
if highlight_mode:
has_encounters = self.encounter_cache.get(pfic, False)
if not has_encounters:
item.setData(Qt.ItemDataRole.BackgroundRole, QColor(255, 200, 200)) # Light red background
else:
item.setData(Qt.ItemDataRole.BackgroundRole, None) # White background
else:
item.setData(Qt.ItemDataRole.BackgroundRole, None) # White background
def update_encounter_cache(self):
self.cursor.execute('''
SELECT DISTINCT pfic
FROM encounters
''')
pokemon_with_encounters = set(row[0] for row in self.cursor.fetchall())
for i in range(self.pokemon_list.count()):
item = self.pokemon_list.item(i)
pfic = item.data(Qt.ItemDataRole.UserRole)
self.encounter_cache[pfic] = pfic in pokemon_with_encounters
def check_pokemon_has_encounters(self, pfic):
return self.encounter_cache.get(pfic, False)
if __name__ == '__main__': if __name__ == '__main__':
app = QApplication(sys.argv) app = QApplication(sys.argv)
editor = DBEditor() editor = DBEditor()

49
DataGatherers/DefaultForms.json

@ -0,0 +1,49 @@
[
"Male",
"Normal Forme",
"Hero of Many Battles",
"Altered Forme",
"Land Forme",
"Standard Mode",
"Galarian Standard Mode",
"Ordinary Forme",
"Aria Forme",
"Natural Form",
"Shield Forme",
"Neutral Mode",
"Hoopa Confined",
"Solo Form",
"Type: Normal",
"Red Core",
"Disguised Form",
"Ice Face",
"Full Belly Mode",
"Zero Form",
"Curly Form",
"Chest Form",
"Apex Build",
"Ultimate Mode",
"Teal Mask",
"Normal Form",
"Plant Cloak",
"Overcast Form",
"West Sea",
"Normal",
"Red-Striped Form",
"Spring Form",
"Incarnate Forme",
"Meadow Pattern",
"Red Flower",
"Average Size",
"50% Forme",
"Confined",
"Baile Style",
"Midday Form",
"Amped Form",
"Vanilla Cream Strawberry Sweet",
"Single Strike Style",
"Family of Three",
"Green Plumage",
"Two-Segment Form",
"Standard Form"
]

30
DataGatherers/DetermineOriginGame.py

@ -529,6 +529,24 @@ def get_intro_generation(pokemon_name, form, cache: CacheManager):
return None return None
def compare_forms(a, b):
if a == None or b == None:
return False
if a == b:
return True
temp_a = a.lower().replace("forme", "").replace("form", "").replace("é", "e").strip()
temp_b = b.lower().replace("forme", "").replace("form", "").replace("é", "e").strip()
temp_a = temp_a.replace("deputante", "debutante").replace("p'au", "pa'u").replace("blood moon", "bloodmoon")
temp_b = temp_b.replace("deputante", "debutante").replace("p'au", "pa'u").replace("blood moon", "bloodmoon")
if temp_a == temp_b:
return True
return False
def get_locations_from_bulbapedia(pokemon_name, form, cache: CacheManager): def get_locations_from_bulbapedia(pokemon_name, form, cache: CacheManager):
page_data = get_pokemon_data_bulbapedia(pokemon_name, cache) page_data = get_pokemon_data_bulbapedia(pokemon_name, cache)
if not page_data: if not page_data:
@ -631,11 +649,19 @@ def get_locations_from_bulbapedia(pokemon_name, form, cache: CacheManager):
if not main_form: if not main_form:
continue continue
if main_form == "Kantonian Form":
continue
if main_form == "All Forms": if main_form == "All Forms":
main_form = form main_form = form
main_form_match = fuzz.partial_ratio(form.lower(), main_form.lower()) >= 80 main_form_match = compare_forms(form, main_form)
sub_form_match = False if not sub_form else fuzz.partial_ratio(form.lower(), sub_form.lower()) >= 80 if not main_form_match:
main_form_match = fuzz.partial_ratio(form.lower(), main_form.lower()) >= 80
sub_form_match = compare_forms(form, sub_form)
if not sub_form_match:
sub_form_match = False if not sub_form else fuzz.partial_ratio(form.lower(), sub_form.lower()) >= 80
if main_form_match or sub_form_match: if main_form_match or sub_form_match:
raw_text = raw_location.get_text() raw_text = raw_location.get_text()

146
DataGatherers/update_location_information.py

@ -1,3 +1,4 @@
import json
import sqlite3 import sqlite3
from cache_manager import CacheManager from cache_manager import CacheManager
from DetermineOriginGame import get_locations_from_bulbapedia from DetermineOriginGame import get_locations_from_bulbapedia
@ -11,6 +12,7 @@ def create_encounters_table():
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(''' cursor.execute('''
CREATE TABLE IF NOT EXISTS encounters ( CREATE TABLE IF NOT EXISTS encounters (
id INTEGER PRIMARY KEY AUTOINCREMENT,
pfic TEXT, pfic TEXT,
game TEXT, game TEXT,
location TEXT, location TEXT,
@ -19,12 +21,11 @@ def create_encounters_table():
dual_slot TEXT, dual_slot TEXT,
static_encounter_count INTEGER, static_encounter_count INTEGER,
static_encounter BOOLEAN, static_encounter BOOLEAN,
only_two BOOLEAN, starter BOOLEAN,
extra_text TEXT, extra_text TEXT,
stars TEXT, stars TEXT,
fishing BOOLEAN, fishing BOOLEAN,
fishing_rod_needed TEXT, rods TEXT
PRIMARY KEY (pfic, game, location)
) )
''') ''')
conn.commit() conn.commit()
@ -112,6 +113,7 @@ def extract_additional_information(s):
details["dual_slot"] = None details["dual_slot"] = None
details["static_encounter_count"] = 0 details["static_encounter_count"] = 0
details["static_encounter"] = False details["static_encounter"] = False
details["starter"] = False
details["extra_text"] = [] details["extra_text"] = []
details["stars"] = [] details["stars"] = []
details["Fishing"] = False details["Fishing"] = False
@ -123,16 +125,21 @@ def extract_additional_information(s):
soup = BeautifulSoup(s, 'html.parser') soup = BeautifulSoup(s, 'html.parser')
full_text = soup.get_text() full_text = soup.get_text()
sup_tags = soup.find_all('sup') sup_tags = soup.find_all('sup')
sup_text = None sup_text = []
if "first partner" in full_text.lower():
details["starter"] = True
for sup_tag in sup_tags: for sup_tag in sup_tags:
sup_text = sup_tag.get_text(strip=True) text = sup_tag.get_text(strip=True)
if find_match(sup_text, days): if find_match(text, days):
details["days"].append(sup_text) details["days"].append(text)
sup_text.append(text)
if find_match(sup_text, times): if find_match(text, times):
details["times"].append(sup_text) details["times"].append(text)
sup_text.append(text)
bracket_text = extract_bracketed_text(full_text, 2) bracket_text = extract_bracketed_text(full_text, 2)
@ -141,59 +148,99 @@ def extract_additional_information(s):
text_lower = text.lower() text_lower = text.lower()
if text_lower in all_games: if text_lower in all_games:
details["dual_slot"] = text match = find_match(text_lower, all_games)
if match:
details["dual_slot"] = match
text = re.sub(match, '', text_lower, flags=re.IGNORECASE)
match = find_match(text_lower, days)
if match:
details["days"].append(match)
text = re.sub(match, '', text_lower, flags=re.IGNORECASE)
match = find_match(text_lower, times)
if match:
details["times"].append(match)
text = re.sub(match, '', text_lower, flags=re.IGNORECASE)
if "only one" in text_lower: if "only one" in text_lower:
details["static_encounter_count"] = 1 details["static_encounter_count"] = 1
details["static_encounter"] = True details["static_encounter"] = True
text = re.sub(r'only one', '', text_lower, flags=re.IGNORECASE).strip() text = re.sub(r'only one', '', text_lower, flags=re.IGNORECASE)
elif "only two" in text_lower: elif "only two" in text_lower:
details["static_encounter_count"] = 2 details["static_encounter_count"] = 2
details["static_encounter"] = True details["static_encounter"] = True
text = re.sub(r'only two', '', text_lower, flags=re.IGNORECASE).strip() text = re.sub(r'only two', '', text_lower, flags=re.IGNORECASE)
#elif "rod" in text_lower:
# details["static_encounter_count"] = 2 if "rod" in text_lower:
# details["static_encounter"] = True match = find_match(text_lower, rods)
# text = re.sub(r'only two', '', text_lower, flags=re.IGNORECASE).strip() if match:
details["Fishing"] = True
details["Rods"].append(match)
text = re.sub(match, '', text_lower, flags=re.IGNORECASE)
if "" in text: if "" in text:
star_parts = re.findall(r'\d★,*', text) star_parts = re.findall(r'\d★,*', text)
for part in star_parts: for part in star_parts:
details["stars"].append(part.replace(',', '').strip()) details["stars"].append(part.replace(',', '').strip())
text = re.sub(r'\d★,*', '', text).strip() text = re.sub(r'\d★,*', '', text)
if text.strip() != "":
details["extra_text"].append(text.strip())
sup_text.append(text.strip())
if text: if len(sup_text) > 0:
details["extra_text"].append(text) for text in sup_text:
full_text = full_text.replace(text, "")
if sup_text: if len(bracket_text) > 0:
return full_text.replace(sup_text, ""), details for text in bracket_text:
full_text = full_text.replace(text, "")
full_text = full_text.replace('(', "").replace(')', "")
return full_text.strip(), details
else: else:
return full_text, details return full_text, details
def save_encounter(conn, pfic, game, location, days, times, dual_slot,static_encounter, static_encounter_count, extra_text, stars): def save_encounter(conn, pfic, game, location, days, times, dual_slot, static_encounter, static_encounter_count, extra_text, stars, rods, fishing, starter):
cursor = conn.cursor() cursor = conn.cursor()
if len(days) > 0: if len(days) > 0:
for day in days: for day in days:
cursor.execute(''' cursor.execute('''
INSERT OR REPLACE INTO encounters INSERT OR REPLACE INTO encounters
(pfic, game, location, day, time, dual_slot, static_encounter_count, static_encounter, extra_text, stars) (pfic, game, location, day, time, dual_slot, static_encounter_count, static_encounter, extra_text, stars, rods, fishing, starter)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (pfic, game, location, day, None, dual_slot, static_encounter_count, static_encounter, ' '.join(extra_text), ','.join(stars))) ''', (pfic, game, location, day, None, dual_slot, static_encounter_count, static_encounter, ' '.join(extra_text), ','.join(stars), ','.join(rods), fishing, starter))
elif len(times) > 0: elif len(times) > 0:
for time in times: for time in times:
cursor.execute(''' cursor.execute('''
INSERT OR REPLACE INTO encounters INSERT OR REPLACE INTO encounters
(pfic, game, location, day, time, dual_slot, static_encounter_count, static_encounter, extra_text, stars) (pfic, game, location, day, time, dual_slot, static_encounter_count, static_encounter, extra_text, stars, rods, fishing, starter)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (pfic, game, location, None, time, dual_slot, static_encounter_count, static_encounter, ' '.join(extra_text), ','.join(stars))) ''', (pfic, game, location, None, time, dual_slot, static_encounter_count, static_encounter, ' '.join(extra_text), ','.join(stars), ','.join(rods), fishing, starter))
else: else:
cursor.execute(''' cursor.execute('''
INSERT OR REPLACE INTO encounters INSERT OR REPLACE INTO encounters
(pfic, game, location, day, time, dual_slot, static_encounter_count, static_encounter, extra_text, stars) (pfic, game, location, day, time, dual_slot, static_encounter_count, static_encounter, extra_text, stars, rods, fishing, starter)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (pfic, game, location, None, None, dual_slot, static_encounter_count, static_encounter, ' '.join(extra_text), ','.join(stars))) ''', (pfic, game, location, None, None, dual_slot, static_encounter_count, static_encounter, ' '.join(extra_text), ','.join(stars), ','.join(rods), fishing, starter))
conn.commit() conn.commit()
def compare_forms(a, b):
if a == b:
return True
temp_a = a.lower().replace("forme", "").replace("form", "").replace("é", "e").strip()
temp_b = b.lower().replace("forme", "").replace("form", "").replace("é", "e").strip()
temp_a = temp_a.replace("deputante", "debutante").replace("p'au", "pa'u").replace("blood moon", "bloodmoon")
temp_b = temp_b.replace("deputante", "debutante").replace("p'au", "pa'u").replace("blood moon", "bloodmoon")
if temp_a == temp_b:
return True
return False
if __name__ == "__main__": if __name__ == "__main__":
cache = CacheManager() cache = CacheManager()
@ -206,20 +253,41 @@ if __name__ == "__main__":
''') ''')
pokemon_forms = cursor.fetchall() pokemon_forms = cursor.fetchall()
try:
with open('./DataGatherers/DefaultForms.json', 'r') as f:
default_forms = json.load(f)
except FileNotFoundError:
default_forms = []
for pfic, name, form, national_dex in pokemon_forms: for pfic, name, form, national_dex in pokemon_forms:
print(f"Processing {name} {form if form else ''}") print(f"Processing {name} {form if form else ''}")
if form and name in form: if form and name in form:
form = form.replace(name, "").strip() form = form.replace(name, "").strip()
gender = None if form and form in default_forms:
if form and "male" in form.lower(): form = None
gender = form
if name == "Unown" and (form != "!" and form != "?"):
form = None
if name == "Tauros" and form == "Combat Breed":
form = "Paldean Form"
if name == "Alcremie":
form = None form = None
encounters_to_ignore = ["trade", "time capsule", "unobtainable", "evolve", "tradeversion", "poké transfer", "friend safari"] if form and form.lower() == "female":
form = None
search_form = form
# unrecognized_forms = ["Unown", "Zacian", "Zamazenta"]
# if name in unrecognized_forms:
# search_form = None
encounters_to_ignore = ["trade", "time capsule", "unobtainable", "evolve", "tradeversion", "poké transfer", "friend safari", "unavailable", "pokémon home"]
encounter_data = get_locations_from_bulbapedia(name, form, cache) encounter_data = get_locations_from_bulbapedia(name, search_form, cache)
if encounter_data == None: if encounter_data == None:
continue continue
@ -253,15 +321,15 @@ if __name__ == "__main__":
print(f"Remaining: {remaining.strip()}") print(f"Remaining: {remaining.strip()}")
print(f"Details: {details}") print(f"Details: {details}")
if len(details["days"]) > 0 and len(details["times"]) > 0: if len(details["times"]) > 0:
print("Stupid Data") print("Stupid Data")
for route in routes: for route in routes:
route_name = f"Route {route}" route_name = f"Route {route}"
save_encounter(conn, pfic, encounter, route_name, details["days"], details["times"], details["dual_slot"], details["static_encounter"], details["static_encounter_count"], details["extra_text"], details["stars"]) save_encounter(conn, pfic, encounter, route_name, details["days"], details["times"], details["dual_slot"], details["static_encounter"], details["static_encounter_count"], details["extra_text"], details["stars"], details["Rods"], details["Fishing"], details["starter"] )
if remaining != "": if remaining != "":
remaining_locations = remaining.replace(" and ", ",").split(",") remaining_locations = remaining.replace(" and ", ",").split(",")
for remaining_location in remaining_locations: for remaining_location in remaining_locations:
save_encounter(conn, pfic, encounter, remaining_location.strip(), details["days"], details["times"], details["dual_slot"], details["static_encounter"], details["static_encounter_count"], details["extra_text"], details["stars"]) save_encounter(conn, pfic, encounter, remaining_location.strip(), details["days"], details["times"], details["dual_slot"], details["static_encounter"], details["static_encounter_count"], details["extra_text"], details["stars"], details["Rods"], details["Fishing"], details["starter"] )
conn.close() conn.close()

186
DataGatherers/update_storable_in_home.py

@ -0,0 +1,186 @@
import json
import sqlite3
from cache_manager import CacheManager
from bs4 import BeautifulSoup, Tag
def create_pokemon_storage_db():
conn = sqlite3.connect('pokemon_forms.db')
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS pokemon_storage (
PFIC TEXT PRIMARY KEY,
storable_in_home BOOLEAN NOT NULL,
FOREIGN KEY (PFIC) REFERENCES pokemon_forms (PFIC)
)
''')
conn.commit()
return conn
def insert_pokemon_storage(conn, pfic: str, storable_in_home: bool):
cursor = conn.cursor()
cursor.execute('''
INSERT OR REPLACE INTO pokemon_storage
(PFIC, storable_in_home)
VALUES (?, ?)
''', (pfic, storable_in_home))
conn.commit()
def scrape_serebii_region_pokemon(url, cache):
response = cache.fetch_url(url)
if not response:
return []
soup = BeautifulSoup(response, 'html.parser')
pokemon_list = []
# Find the main table containing Pokémon data
table = soup.find('table', class_='dextable')
if table:
rows = table.find_all('tr')[2:] # Skip the header row and the game intro row
for row in rows:
cells = row.find_all('td')
if len(cells) <= 5: # Ensure we have enough cells to check depositability. if only 5 then its not depositable in any game.
continue
number = cells[0].text.strip().lstrip('#')
name = cells[2].text.strip()
# Get the image URL
img_url = cells[1].find('img')['src']
full_img_url = f"https://www.serebii.net{img_url}"
pokemon_list.append({
'number': number,
'name': name,
'image_url': full_img_url
})
return pokemon_list
def scrape_all_regions(cache):
base_url = "https://www.serebii.net/pokemonhome/"
regions = ["kanto", "johto", "hoenn", "sinnoh", "unova", "kalos", "alola", "galar", "paldea", "hisui", "unknown"]
all_pokemon = []
for region in regions:
url = f"{base_url}{region}pokemon.shtml"
region_pokemon = scrape_serebii_region_pokemon(url, cache)
all_pokemon.extend(region_pokemon)
print(f"Scraped {len(region_pokemon)} Pokémon from {region.capitalize()} region")
return all_pokemon
def get_objects_by_number(array, target_number):
return [obj for obj in array if obj['number'] == target_number]
def extract_bracketed_text(string):
results = []
stack = []
start_index = -1
for i, char in enumerate(string):
if char == '(':
if not stack:
start_index = i
stack.append(i)
elif char == ')':
if stack:
stack.pop()
if not stack:
results.append(string[start_index + 1:i])
start_index = -1
else:
print(f"Warning: Unmatched closing parenthesis at position {i}")
# Handle any remaining unclosed brackets
if stack:
print(f"Warning: {len(stack)} unmatched opening parentheses")
for unmatched_start in stack:
results.append(string[unmatched_start + 1:])
return results
def compare_forms(a, b):
if a == b:
return True
temp_a = a.lower().replace("forme", "").replace("form", "").replace("é", "e").strip()
temp_b = b.lower().replace("forme", "").replace("form", "").replace("é", "e").strip()
temp_a = temp_a.replace("deputante", "debutante").replace("p'au", "pa'u").replace("blood moon", "bloodmoon")
temp_b = temp_b.replace("deputante", "debutante").replace("p'au", "pa'u").replace("blood moon", "bloodmoon")
if temp_a == temp_b:
return True
return False
if __name__ == "__main__":
cache = CacheManager()
conn = create_pokemon_storage_db()
cursor = conn.cursor()
cursor.execute('''
SELECT pf.PFIC, pf.name, pf.form_name, pf.national_dex
FROM pokemon_forms pf
ORDER BY pf.national_dex, pf.form_name
''')
pokemon_forms = cursor.fetchall()
all_depositable_pokemon = scrape_all_regions(cache)
try:
with open('./DataGatherers/DefaultForms.json', 'r') as f:
default_forms = json.load(f)
except FileNotFoundError:
default_forms = []
for pfic, name, form, national_dex in pokemon_forms:
print(f"Processing {name} {form if form else ''}")
storable_in_home = False
if form and name in form:
form = form.replace(name, "").strip()
# serebii doesn't list gender in the table so we have to assume based on form name.
if form and ("male" in form.lower() or "female" in form.lower()):
form = None
if form and form in default_forms:
form = None
if name == "Unown" and (form != "!" and form != "?"):
form = None
if name == "Tauros" and form == "Combat Breed":
form = "Paldean Form"
if name == "Alcremie":
form = None
pokemon = get_objects_by_number(all_depositable_pokemon, f"{national_dex:04d}")
for p in pokemon:
if form == None and name.lower() in p['name'].lower():
storable_in_home = True
break
parts = p['name'].split(" ")
if len(parts) > 1 and parts[0] == form:
storable_in_home = True
brackets = extract_bracketed_text(p['name'])
if brackets:
for bracket in brackets:
if name in bracket:
bracket = bracket.replace(name, "").strip()
if compare_forms(form, bracket):
storable_in_home = True
break
print(f"{name} {form if form else ''} is storable in home: {storable_in_home}")
insert_pokemon_storage(conn, pfic, storable_in_home)

132
patches.json

@ -1,131 +1 @@
{ {"evolution_0019-01-000-2_0020-01-001-0": {"action": "delete"}, "evolution_0868-08-000-0_0869-08-001-0": {"action": "update", "new_from_pfic": "0868-08-000-0", "new_to_pfic": "0869-08-001-0", "new_method": "Spin clockwise for more than 5 seconds during the day while holding a Berry Sweet \u2192"}, "evolution_0868-08-000-0_0869-08-002-0": {"action": "add", "from_pfic": "0868-08-000-0", "to_pfic": "0869-08-002-0", "method": "Spin clockwise for more than 5 seconds during the day while holding a Clover Sweet \u2192"}, "evolution_0868-08-000-0_0869-08-003-0": {"action": "add", "from_pfic": "0868-08-000-0", "to_pfic": "0869-08-003-0", "method": "Spin clockwise for more than 5 seconds during the day while holding a Flower Sweet \u2192"}, "evolution_0868-08-000-0_0869-08-004-0": {"action": "add", "from_pfic": "0868-08-000-0", "to_pfic": "0869-08-004-0", "method": "Spin clockwise for more than 5 seconds during the day while holding a Love Sweet \u2192"}, "evolution_0868-08-000-0_0869-08-005-0": {"action": "add", "from_pfic": "0868-08-000-0", "to_pfic": "0869-08-005-0", "method": "Spin clockwise for more than 5 seconds during the day while holding a Ribbon Sweet \u2192"}, "evolution_0868-08-000-0_0869-08-006-0": {"action": "add", "from_pfic": "0868-08-000-0", "to_pfic": "0869-08-006-0", "method": "Spin clockwise for more than 5 seconds during the day while holding a Star Sweet \u2192"}, "evolution_0868-08-000-0_0869-08-007-0": {"action": "add", "from_pfic": "0868-08-000-0", "to_pfic": "0869-08-007-0", "method": "Spin clockwise for more than 5 seconds during the day while holding a Strawberry Sweet \u2192"}, "evolution_0868-08-000-0_0869-08-008-0": {"action": "update", "new_from_pfic": "0868-08-000-0", "new_to_pfic": "0869-08-008-0", "new_method": "Spin clockwise for more than 5 seconds at night while holding a Berry Sweet \u2192"}, "evolution_0868-08-000-0_0869-08-009-0": {"action": "add", "from_pfic": "0868-08-000-0", "to_pfic": "0869-08-009-0", "method": "Spin clockwise for more than 5 seconds at night while holding a Clover Sweet \u2192"}, "evolution_0868-08-000-0_0869-08-010-0": {"action": "add", "from_pfic": "0868-08-000-0", "to_pfic": "0869-08-010-0", "method": "Spin clockwise for more than 5 seconds at night while holding a Flower Sweet \u2192"}, "evolution_0868-08-000-0_0869-08-011-0": {"action": "add", "from_pfic": "0868-08-000-0", "to_pfic": "0869-08-011-0", "method": "Spin clockwise for more than 5 seconds at night while holding a Love Sweet \u2192"}, "evolution_0868-08-000-0_0869-08-012-0": {"action": "add", "from_pfic": "0868-08-000-0", "to_pfic": "0869-08-012-0", "method": "Spin clockwise for more than 5 seconds at night while holding a Ribbon Sweet \u2192"}, "evolution_0868-08-000-0_0869-08-013-0": {"action": "add", "from_pfic": "0868-08-000-0", "to_pfic": "0869-08-013-0", "method": "Spin clockwise for more than 5 seconds at night while holding a Star Sweet \u2192"}, "evolution_0868-08-000-0_0869-08-014-0": {"action": "add", "from_pfic": "0868-08-000-0", "to_pfic": "0869-08-014-0", "method": "Spin clockwise for more than 5 seconds at night while holding a Strawberry Sweet \u2192"}, "evolution_0868-08-000-0_0869-08-016-0": {"action": "update", "new_from_pfic": "0868-08-000-0", "new_to_pfic": "0869-08-016-0", "new_method": "Spin clockwise for less than 5 seconds at night while holding a Berry Sweet \u2192"}, "evolution_0868-08-000-0_0869-08-017-0": {"action": "add", "from_pfic": "0868-08-000-0", "to_pfic": "0869-08-017-0", "method": "Spin clockwise for less than 5 seconds at night while holding a Clover Sweet \u2192"}, "evolution_0868-08-000-0_0869-08-018-0": {"action": "add", "from_pfic": "0868-08-000-0", "to_pfic": "0869-08-018-0", "method": "Spin clockwise for less than 5 seconds at night while holding a Flower Sweet \u2192"}, "evolution_0868-08-000-0_0869-08-019-0": {"action": "add", "from_pfic": "0868-08-000-0", "to_pfic": "0869-08-019-0", "method": "Spin clockwise for less than 5 seconds at night while holding a Love Sweet \u2192"}, "evolution_0868-08-000-0_0869-08-020-0": {"action": "add", "from_pfic": "0868-08-000-0", "to_pfic": "0869-08-020-0", "method": "Spin clockwise for less than 5 seconds at night while holding a Ribbon Sweet \u2192"}, "evolution_0868-08-000-0_0869-08-021-0": {"action": "add", "from_pfic": "0868-08-000-0", "to_pfic": "0869-08-021-0", "method": "Spin clockwise for less than 5 seconds at night while holding a Star Sweet \u2192"}, "evolution_0868-08-000-0_0869-08-022-0": {"action": "add", "from_pfic": "0868-08-000-0", "to_pfic": "0869-08-022-0", "method": "Spin clockwise for less than 5 seconds at night while holding a Strawberry Sweet \u2192"}, "0869-08-064-0": {"storable_in_home": false}}
"evolution_0019-01-000-2_0020-01-001-0": {
"action": "delete"
},
"evolution_0868-08-000-0_0869-08-001-0": {
"action": "update",
"new_from_pfic": "0868-08-000-0",
"new_to_pfic": "0869-08-001-0",
"new_method": "Spin clockwise for more than 5 seconds during the day while holding a Berry Sweet \u2192"
},
"evolution_0868-08-000-0_0869-08-002-0": {
"action": "add",
"from_pfic": "0868-08-000-0",
"to_pfic": "0869-08-002-0",
"method": "Spin clockwise for more than 5 seconds during the day while holding a Clover Sweet \u2192"
},
"evolution_0868-08-000-0_0869-08-003-0": {
"action": "add",
"from_pfic": "0868-08-000-0",
"to_pfic": "0869-08-003-0",
"method": "Spin clockwise for more than 5 seconds during the day while holding a Flower Sweet \u2192"
},
"evolution_0868-08-000-0_0869-08-004-0": {
"action": "add",
"from_pfic": "0868-08-000-0",
"to_pfic": "0869-08-004-0",
"method": "Spin clockwise for more than 5 seconds during the day while holding a Love Sweet \u2192"
},
"evolution_0868-08-000-0_0869-08-005-0": {
"action": "add",
"from_pfic": "0868-08-000-0",
"to_pfic": "0869-08-005-0",
"method": "Spin clockwise for more than 5 seconds during the day while holding a Ribbon Sweet \u2192"
},
"evolution_0868-08-000-0_0869-08-006-0": {
"action": "add",
"from_pfic": "0868-08-000-0",
"to_pfic": "0869-08-006-0",
"method": "Spin clockwise for more than 5 seconds during the day while holding a Star Sweet \u2192"
},
"evolution_0868-08-000-0_0869-08-007-0": {
"action": "add",
"from_pfic": "0868-08-000-0",
"to_pfic": "0869-08-007-0",
"method": "Spin clockwise for more than 5 seconds during the day while holding a Strawberry Sweet \u2192"
},
"evolution_0868-08-000-0_0869-08-008-0": {
"action": "update",
"new_from_pfic": "0868-08-000-0",
"new_to_pfic": "0869-08-008-0",
"new_method": "Spin clockwise for more than 5 seconds at night while holding a Berry Sweet \u2192"
},
"evolution_0868-08-000-0_0869-08-009-0": {
"action": "add",
"from_pfic": "0868-08-000-0",
"to_pfic": "0869-08-009-0",
"method": "Spin clockwise for more than 5 seconds at night while holding a Clover Sweet \u2192"
},
"evolution_0868-08-000-0_0869-08-010-0": {
"action": "add",
"from_pfic": "0868-08-000-0",
"to_pfic": "0869-08-010-0",
"method": "Spin clockwise for more than 5 seconds at night while holding a Flower Sweet \u2192"
},
"evolution_0868-08-000-0_0869-08-011-0": {
"action": "add",
"from_pfic": "0868-08-000-0",
"to_pfic": "0869-08-011-0",
"method": "Spin clockwise for more than 5 seconds at night while holding a Love Sweet \u2192"
},
"evolution_0868-08-000-0_0869-08-012-0": {
"action": "add",
"from_pfic": "0868-08-000-0",
"to_pfic": "0869-08-012-0",
"method": "Spin clockwise for more than 5 seconds at night while holding a Ribbon Sweet \u2192"
},
"evolution_0868-08-000-0_0869-08-013-0": {
"action": "add",
"from_pfic": "0868-08-000-0",
"to_pfic": "0869-08-013-0",
"method": "Spin clockwise for more than 5 seconds at night while holding a Star Sweet \u2192"
},
"evolution_0868-08-000-0_0869-08-014-0": {
"action": "add",
"from_pfic": "0868-08-000-0",
"to_pfic": "0869-08-014-0",
"method": "Spin clockwise for more than 5 seconds at night while holding a Strawberry Sweet \u2192"
},
"evolution_0868-08-000-0_0869-08-016-0": {
"action": "update",
"new_from_pfic": "0868-08-000-0",
"new_to_pfic": "0869-08-016-0",
"new_method": "Spin clockwise for less than 5 seconds at night while holding a Berry Sweet \u2192"
},
"evolution_0868-08-000-0_0869-08-017-0": {
"action": "add",
"from_pfic": "0868-08-000-0",
"to_pfic": "0869-08-017-0",
"method": "Spin clockwise for less than 5 seconds at night while holding a Clover Sweet \u2192"
},
"evolution_0868-08-000-0_0869-08-018-0": {
"action": "add",
"from_pfic": "0868-08-000-0",
"to_pfic": "0869-08-018-0",
"method": "Spin clockwise for less than 5 seconds at night while holding a Flower Sweet \u2192"
},
"evolution_0868-08-000-0_0869-08-019-0": {
"action": "add",
"from_pfic": "0868-08-000-0",
"to_pfic": "0869-08-019-0",
"method": "Spin clockwise for less than 5 seconds at night while holding a Love Sweet \u2192"
},
"evolution_0868-08-000-0_0869-08-020-0": {
"action": "add",
"from_pfic": "0868-08-000-0",
"to_pfic": "0869-08-020-0",
"method": "Spin clockwise for less than 5 seconds at night while holding a Ribbon Sweet \u2192"
},
"evolution_0868-08-000-0_0869-08-021-0": {
"action": "add",
"from_pfic": "0868-08-000-0",
"to_pfic": "0869-08-021-0",
"method": "Spin clockwise for less than 5 seconds at night while holding a Star Sweet \u2192"
},
"evolution_0868-08-000-0_0869-08-022-0": {
"action": "add",
"from_pfic": "0868-08-000-0",
"to_pfic": "0869-08-022-0",
"method": "Spin clockwise for less than 5 seconds at night while holding a Strawberry Sweet \u2192"
}
}

BIN
pokemon_forms.db

Binary file not shown.
Loading…
Cancel
Save