From 92d5bd2800e70bbbb55b7baeadf72853326d134d Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 7 Oct 2024 15:57:11 +0100 Subject: [PATCH] - Improvements to the db editor as well as come changes for Alcreamie --- DBEditor/DBEditor.py | 450 +++++++++++++++++++++++++++++++++++++++++++ patches.json | 131 +++++++++++++ pokemon_forms.db | Bin 278528 -> 282624 bytes 3 files changed, 581 insertions(+) create mode 100644 DBEditor/DBEditor.py create mode 100644 patches.json diff --git a/DBEditor/DBEditor.py b/DBEditor/DBEditor.py new file mode 100644 index 0000000..de7beac --- /dev/null +++ b/DBEditor/DBEditor.py @@ -0,0 +1,450 @@ +import sys +from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QListWidget, QLineEdit, + QLabel, QCheckBox, QPushButton, QFormLayout, QListWidgetItem, QSplitter, QTreeWidget, + QTreeWidgetItem, QDialog, QDialogButtonBox, QComboBox, QMessageBox) +from PyQt6.QtCore import Qt, QSize +from PyQt6.QtGui import QPixmap, QFontMetrics, QColor +import sqlite3 +import json +import os + +class EvolutionEditDialog(QDialog): + def __init__(self, parent=None, from_pfic=None, to_pfic=None, method=None): + super().__init__(parent) + self.setWindowTitle("Edit Evolution") + self.setModal(True) + + layout = QVBoxLayout(self) + + self.from_combo = QComboBox() + self.to_combo = QComboBox() + self.method_edit = QLineEdit(method) + + layout.addWidget(QLabel("From Pokémon:")) + layout.addWidget(self.from_combo) + layout.addWidget(QLabel("To Pokémon:")) + layout.addWidget(self.to_combo) + layout.addWidget(QLabel("Evolution Method:")) + layout.addWidget(self.method_edit) + + self.button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) + self.button_box.accepted.connect(self.accept) + self.button_box.rejected.connect(self.reject) + layout.addWidget(self.button_box) + + self.delete_button = QPushButton("Delete Evolution") + self.delete_button.clicked.connect(self.delete_evolution) + layout.addWidget(self.delete_button) + + self.populate_combos(from_pfic, to_pfic) + + def populate_combos(self, from_pfic, to_pfic): + cursor = self.parent().cursor + cursor.execute('SELECT PFIC, name, form_name FROM pokemon_forms ORDER BY name, form_name') + for pfic, name, form_name in cursor.fetchall(): + display_name = f"{name} ({form_name})" if form_name else name + self.from_combo.addItem(display_name, pfic) + self.to_combo.addItem(display_name, pfic) + + if from_pfic: + self.from_combo.setCurrentIndex(self.from_combo.findData(from_pfic)) + if to_pfic: + self.to_combo.setCurrentIndex(self.to_combo.findData(to_pfic)) + + def delete_evolution(self): + self.done(2) # Use a custom return code for delete action + +class DBEditor(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("Pokémon Database Editor") + self.setGeometry(100, 100, 1000, 600) + + self.conn = sqlite3.connect(':memory:') # Use in-memory database for runtime + self.cursor = self.conn.cursor() + self.init_database() + self.patches = self.load_and_apply_patches() + + self.init_ui() + + def init_database(self): + # Copy the original database to the in-memory database + disk_conn = sqlite3.connect('pokemon_forms.db') + disk_conn.backup(self.conn) + disk_conn.close() + + def load_and_apply_patches(self): + try: + with open('patches.json', 'r') as f: + patches = json.load(f) + except FileNotFoundError: + patches = {} + + # Apply patches to the in-memory database + for patch_key, patch in patches.items(): + if patch_key.startswith('evolution_'): + from_pfic, to_pfic = patch_key.split('_')[1:] + if patch['action'] == 'delete': + self.cursor.execute(''' + DELETE FROM evolution_chains + WHERE from_pfic = ? AND to_pfic = ? + ''', (from_pfic, to_pfic)) + elif patch['action'] == 'update': + self.cursor.execute(''' + UPDATE evolution_chains + SET from_pfic = ?, to_pfic = ?, method = ? + WHERE from_pfic = ? AND to_pfic = ? + ''', (patch['new_from_pfic'], patch['new_to_pfic'], patch['new_method'], from_pfic, to_pfic)) + elif patch['action'] == 'add': + self.cursor.execute(''' + INSERT OR REPLACE INTO evolution_chains (from_pfic, to_pfic, method) + VALUES (?, ?, ?) + ''', (patch['from_pfic'], patch['to_pfic'], patch['method'])) + else: # pokemon_storage patches + self.cursor.execute(''' + UPDATE pokemon_storage + SET storable_in_home = ? + WHERE PFIC = ? + ''', (patch['storable_in_home'], patch_key)) + + self.conn.commit() + return patches + + def save_patches(self): + with open('patches.json', 'w') as f: + json.dump(self.patches, f) + + def init_ui(self): + central_widget = QWidget() + self.setCentralWidget(central_widget) + main_layout = QHBoxLayout(central_widget) + + # Left side: Search and List + left_layout = QVBoxLayout() + self.search_bar = QLineEdit() + self.search_bar.setPlaceholderText("Search Pokémon...") + self.search_bar.textChanged.connect(self.filter_pokemon_list) + left_layout.addWidget(self.search_bar) + + self.pokemon_list = QListWidget() + self.pokemon_list.currentItemChanged.connect(self.load_pokemon_details) + left_layout.addWidget(self.pokemon_list) + + # Right side: Edit panel + right_layout = QHBoxLayout() + + # Left side of right panel: Text information + text_layout = QVBoxLayout() + self.edit_form = QFormLayout() + self.name_label = QLabel() + self.form_name_label = QLabel() + self.national_dex_label = QLabel() + self.generation_label = QLabel() + self.home_checkbox = QCheckBox("Available in Home") + + self.edit_form.addRow("Name:", self.name_label) + self.edit_form.addRow("Form:", self.form_name_label) + self.edit_form.addRow("National Dex:", self.national_dex_label) + self.edit_form.addRow("Generation:", self.generation_label) + self.edit_form.addRow(self.home_checkbox) + + text_layout.addLayout(self.edit_form) + + # Evolution chain tree + self.evolution_tree = QTreeWidget() + self.evolution_tree.setHeaderLabels(["Pokémon", "Evolution Method"]) + self.evolution_tree.setColumnWidth(0, 200) + text_layout.addWidget(self.evolution_tree) + + # Move buttons to the bottom + text_layout.addStretch(1) + + self.save_button = QPushButton("Save Changes") + self.save_button.clicked.connect(self.save_changes) + text_layout.addWidget(self.save_button) + + self.export_button = QPushButton("Export Database") + self.export_button.clicked.connect(self.export_database) + text_layout.addWidget(self.export_button) + + # Add New Evolution button + self.add_evolution_button = QPushButton("Add New Evolution") + self.add_evolution_button.clicked.connect(self.add_new_evolution) + text_layout.addWidget(self.add_evolution_button) + + # Right side of right panel: Image + image_layout = QVBoxLayout() + self.image_label = QLabel() + self.image_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.image_label.setFixedSize(200, 200) + image_layout.addWidget(self.image_label) + image_layout.addStretch(1) + + right_layout.addLayout(text_layout) + right_layout.addLayout(image_layout) + + main_layout.addLayout(left_layout, 1) + main_layout.addLayout(right_layout, 1) + + self.load_pokemon_list() + self.adjust_list_width() + + def adjust_list_width(self): + max_width = 0 + font_metrics = QFontMetrics(self.pokemon_list.font()) + for i in range(self.pokemon_list.count()): + item_width = font_metrics.horizontalAdvance(self.pokemon_list.item(i).text()) + max_width = max(max_width, item_width) + + # Add some padding to the width + list_width = max_width + 50 + self.pokemon_list.setFixedWidth(list_width) + self.search_bar.setFixedWidth(list_width) + + def load_pokemon_list(self): + self.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_data = self.cursor.fetchall() + + for pfic, name, form_name, national_dex in pokemon_data: + display_name = f"{national_dex:04d} - {name}" + if form_name: + display_name += f" ({form_name})" + item = QListWidgetItem(display_name) + item.setData(Qt.ItemDataRole.UserRole, pfic) + self.pokemon_list.addItem(item) + + def filter_pokemon_list(self): + search_text = self.search_bar.text().lower() + for i in range(self.pokemon_list.count()): + item = self.pokemon_list.item(i) + item.setHidden(search_text not in item.text().lower()) + + def load_pokemon_details(self, current, previous): + if not current: + return + + pfic = current.data(Qt.ItemDataRole.UserRole) + self.cursor.execute(''' + SELECT pf.name, pf.form_name, pf.national_dex, pf.generation, ps.storable_in_home + FROM pokemon_forms pf + LEFT JOIN pokemon_storage ps ON pf.PFIC = ps.PFIC + WHERE pf.PFIC = ? + ''', (pfic,)) + pokemon_data = self.cursor.fetchone() + + if pokemon_data: + name, form_name, national_dex, generation, storable_in_home = pokemon_data + self.name_label.setText(name) + self.form_name_label.setText(form_name if form_name else "") + self.national_dex_label.setText(str(national_dex)) + self.generation_label.setText(str(generation)) + self.home_checkbox.setChecked(bool(storable_in_home)) + + # Load and display the image + image_path = f"images-new/{pfic}.png" + if os.path.exists(image_path): + pixmap = QPixmap(image_path) + self.image_label.setPixmap(pixmap.scaled(200, 200, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)) + else: + self.image_label.setText("Image not found") + + # Load and display evolution chain + self.load_evolution_chain(pfic) + + self.current_pfic = pfic + self.add_evolution_button.setEnabled(True) # Enable the button when a Pokémon is selected + + def edit_evolution(self, item, column): + parent = item.parent() + if not parent: + return # Don't edit the root item + + from_pfic = parent.data(0, Qt.ItemDataRole.UserRole) + to_pfic = item.data(0, Qt.ItemDataRole.UserRole) + method = item.text(1) + + dialog = EvolutionEditDialog(self, from_pfic, to_pfic, method) + result = dialog.exec() + + if result == QDialog.DialogCode.Accepted: + new_from_pfic = dialog.from_combo.currentData() + new_to_pfic = dialog.to_combo.currentData() + new_method = dialog.method_edit.text() + + # Update the in-memory database + self.cursor.execute(''' + UPDATE evolution_chains + SET from_pfic = ?, to_pfic = ?, method = ? + WHERE from_pfic = ? AND to_pfic = ? + ''', (new_from_pfic, new_to_pfic, new_method, from_pfic, to_pfic)) + + # Create or update the patch + patch_key = f"evolution_{from_pfic}_{to_pfic}" + self.patches[patch_key] = { + 'action': 'update', + 'new_from_pfic': new_from_pfic, + 'new_to_pfic': new_to_pfic, + 'new_method': new_method + } + self.save_patches() + + # Refresh the evolution chain display + self.load_evolution_chain(self.current_pfic) + + elif result == 2: # Delete action + confirm = QMessageBox.question(self, "Confirm Deletion", "Are you sure you want to delete this evolution?", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No) + if confirm == QMessageBox.StandardButton.Yes: + # Delete from the in-memory database + self.cursor.execute(''' + DELETE FROM evolution_chains + WHERE from_pfic = ? AND to_pfic = ? + ''', (from_pfic, to_pfic)) + + # Create a delete patch + patch_key = f"evolution_{from_pfic}_{to_pfic}" + self.patches[patch_key] = {'action': 'delete'} + self.save_patches() + + # Refresh the evolution chain display + self.load_evolution_chain(self.current_pfic) + + def get_evolution_chain(self, pfic): + def follow_chain(start_pfic, visited=None): + if visited is None: + visited = set() + + chain = [] + stack = [(start_pfic, None)] + + while stack: + current_pfic, method = stack.pop() + if current_pfic in visited: + continue + + visited.add(current_pfic) + self.cursor.execute('SELECT name, form_name FROM pokemon_forms WHERE PFIC = ?', (current_pfic,)) + name, form_name = self.cursor.fetchone() + + chain.append((current_pfic, name, form_name, method)) + + # Get previous evolutions + self.cursor.execute('SELECT from_pfic, method FROM evolution_chains WHERE to_pfic = ?', (current_pfic,)) + prev_evolutions = self.cursor.fetchall() + for prev_pfic, prev_method in prev_evolutions: + if prev_pfic not in visited: + stack.append((prev_pfic, prev_method)) + + # Get next evolutions + self.cursor.execute('SELECT to_pfic, method FROM evolution_chains WHERE from_pfic = ?', (current_pfic,)) + next_evolutions = self.cursor.fetchall() + for next_pfic, next_method in next_evolutions: + if next_pfic not in visited: + stack.append((next_pfic, next_method)) + + return chain + + return follow_chain(pfic) + + def load_evolution_chain(self, pfic): + self.evolution_tree.clear() + evolution_chain = self.get_evolution_chain(pfic) + + # Create a dictionary to store tree items + tree_items = {} + + # First pass: create all tree items + for current_pfic, name, form_name, method in evolution_chain: + display_name = f"{name} ({form_name})" if form_name else name + item = QTreeWidgetItem([display_name, method if method else ""]) + item.setData(0, Qt.ItemDataRole.UserRole, current_pfic) + tree_items[current_pfic] = item + + if current_pfic == pfic: + item.setBackground(0, QColor(255, 255, 0, 100)) # Highlight selected Pokémon + + # Second pass: build the tree structure + root = None + for current_pfic, name, form_name, method in evolution_chain: + item = tree_items[current_pfic] + + # Find the parent of this item + self.cursor.execute('SELECT from_pfic FROM evolution_chains WHERE to_pfic = ?', (current_pfic,)) + parent_pfic = self.cursor.fetchone() + + if parent_pfic: + parent_item = tree_items.get(parent_pfic[0]) + if parent_item: + parent_item.addChild(item) + elif not root: + root = item + self.evolution_tree.addTopLevelItem(root) + + # Expand the entire tree + self.evolution_tree.expandAll() + + # Scroll to and select the current Pokémon + current_item = tree_items[pfic] + self.evolution_tree.scrollToItem(current_item) + self.evolution_tree.setCurrentItem(current_item) + + # Connect double-click signal + self.evolution_tree.itemDoubleClicked.connect(self.edit_evolution) + + def save_changes(self): + if hasattr(self, 'current_pfic'): + storable_in_home = self.home_checkbox.isChecked() + self.patches[self.current_pfic] = {'storable_in_home': storable_in_home} + + with open('patches.json', 'w') as f: + json.dump(self.patches, f) + + def export_database(self): + export_conn = sqlite3.connect('pokemon_forms_production.db') + self.conn.backup(export_conn) + export_conn.close() + + def closeEvent(self, event): + self.conn.close() + event.accept() + + def add_new_evolution(self): + if not hasattr(self, 'current_pfic'): + return + + dialog = EvolutionEditDialog(self, self.current_pfic, None, "") + result = dialog.exec() + + if result == QDialog.DialogCode.Accepted: + new_from_pfic = dialog.from_combo.currentData() + new_to_pfic = dialog.to_combo.currentData() + new_method = dialog.method_edit.text() + + # Update the in-memory database + self.cursor.execute(''' + INSERT INTO evolution_chains (from_pfic, to_pfic, method) + VALUES (?, ?, ?) + ''', (new_from_pfic, new_to_pfic, new_method)) + + # Create a new patch + patch_key = f"evolution_{new_from_pfic}_{new_to_pfic}" + self.patches[patch_key] = { + 'action': 'add', + 'from_pfic': new_from_pfic, + 'to_pfic': new_to_pfic, + 'method': new_method + } + self.save_patches() + + # Refresh the evolution chain display + self.load_evolution_chain(self.current_pfic) + +if __name__ == '__main__': + app = QApplication(sys.argv) + editor = DBEditor() + editor.show() + sys.exit(app.exec()) \ No newline at end of file diff --git a/patches.json b/patches.json new file mode 100644 index 0000000..548e30a --- /dev/null +++ b/patches.json @@ -0,0 +1,131 @@ +{ + "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" + } +} \ No newline at end of file diff --git a/pokemon_forms.db b/pokemon_forms.db index f69daf3163a9691f30b2602493bd7f69e8195a92..6e6cc386b1bf49c25f2a5646d78c4cb6205b7ac8 100644 GIT binary patch delta 2509 zcmZvdYfKbZ6vyY@o!PsyyR+kiS8b66krk1L%g*dB%XURT!50d?waTJvLPW8MmD;MP ziHVgejrHEvv?+e^gN;$FN}Jf&584>orcG1pCtC#pQG7J5rb%tmv$u%sEc;=yxxe#2 z=iL9CJGZrUd~3(}_E)6_K@iG6aE9PdUe)f-jU6f)cT5`RluoJV)vI4jAGb@*lG-3B zncCnfwiUs160M5LSOq+jj_1HLpG`Q>oz~TzhKgpHYVj#XYSB|s5YXvMVTAJcqAsCK z_ywWw@C|W^m@hh{lhQsZEZ56H`8CUP^_F^0J)&+_=d06H;^=c+BQEohxzpTY4w^;j znlwo{Ls~59Xb(PS`bxaxIBfmFaut=~HKupO)5;DqXgPxHc&aI6UF%4(e{Xl$hU{Tm zP~5H%(rHrf^sq?I7^vg^=jqO+zs+%&R> zLK@)c*1b5SQ4gR1TMwNWR34fO$j^oz;EY#d6Plh0HfS-rZ??1!eJGBc zY~}!-nC+*<(Y4X$fI(xJ!^25{Y7jn`Wk4sxDM%5`gSB#Y4q6yqOM$nQ;!3R?l~yl| zre%-AC_+563>1ELvmd7?IcG4`w(Zo*ojWFc>W!*X7JfZrvfTrX8^^GpH4HmZh-3+ zG`Ffo3jpIvEabXD zt~2=Vr9prpR(}s?8t2GE=L7Sz6>vz5%u8o-of+mY!T2(5?+QBmv>&@+v`QhYk|_ft zqb1r;2-{f20B#^YS^~6$9RM1)Bc09y7-U}qOp0~JuG86^%?CCi_NXf%!Ob?^!+DTB z#Irx;*$w;FXgRwveF;55ve(hvQgAB6rj>g8b<`7jv5behe(xDLY*mjiAXH!FU? z`*@xAkq;Ip8%t?)0WaVLkK>@1761&f5SUDcm0gz~;WBiv16}ICXjZoLk5FnlgrxUO zhsbqNAj`z<=05WobE8=^U6Z~Mi=;(TqWG)$IkU8*jqFG}+5mSPY3+L2jy}Zj9b5tx zN|^$)@#5(F=xW}ty=RPmy|jk6^hs~@CWtd+Ex;fvPy9_IaucenR=ZT`1b%L=` zaufzFyBE6?ymVz!6*3C6Vu|o3asmvRzdw;J*)0h*Qv#7isNs`O~(btaYwVG+TnKClpf`Z@|n`A z)G1|3mO|`(_KWs|_D%M3dzPKp`fV3%2W(4hlWl_aH|u5V32TdWxmC9&$bZQ{$!Fy@ ed9yrE&Xz?>kL8NxBour3>uf`ou0PsQEBOzb(9w+m delta 1857 zcmZuxZA@Eb6z+T5-h2B&FAE4doX`&jgHcMqONV@wFWHn2r`trg;Tl~=Mn)D;LPCv^ zWEmMS)^mWagvDgWm>H)NF!6_r8qH>$Ka79O5_EJBV+=Fd5)+eodRef&-JdtT=Xsv< zK6%c08(G*EiENuJ(4J7KRH3P&TKZuhJbU%ng2be0ZFZ6N8|hPN`LnX@VQHsfd>^s2 zXX((4SJPPuN9f#+t#sx^%jwLA{C95@F5M_()phDV)xRhlVfQsVAfI16%2=g9ls8rP zR9Lpc9kYlXVv+E-a7(x%oDq%-twOQD^9lZI{(XLg@8%tRHlADIUSEo|Y9hkYsda8b z7+YYA3mmH(rABS4WtDVW`b0V>osin4Qb{l+Oy8QuOR|H=Z$e z8w(5x!-s}b2EYEH{(Jp|{(!zfm(YEz8`3rC4B{_hOzag)g};PFVMGX#ji2~gzL&4i zKG0s*j%hozPicPFT+s|_>NULjM|E_&x<_5i{mFgKy~WjXT6U97vxCg6x}#d2dgmGZ zXq77$f^e8G+0lZxS2-Kri?STS1Fumfh)*XtTWzUZp;5Yn)LO zz3`$;?^)xlc?xe{=$0`dN&jMvlPH-kss!;Cu@xTu&`D4|PTt|nxNDtj)wtmW9w%m5 zNlt1^PI#Vp9=t!x%;_x33rb$BMzbhE5lMrKyd)(uTO8e+^T ziY^Ed&4d!>g2>Mfof|1U023;H<*m z=2)IQ>RdOtlg(0LhYY9B-3uN!Q&rs zX2