Release candidate

This commit is contained in:
Goppier
2024-03-27 00:57:15 +01:00
parent 79e5eba972
commit 0a98371737
7 changed files with 861 additions and 476 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 133 B

After

Width:  |  Height:  |  Size: 133 B

View File

Before

Width:  |  Height:  |  Size: 4.6 MiB

After

Width:  |  Height:  |  Size: 4.6 MiB

View File

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -1,133 +1,285 @@
"""
feebasCalcs.py
This file serves as the starting point for the Finding Feebas application.
It initialises the interface and has functions for all interactions, like moving the map,
pressing buttons and checking the input on the entry boxes.
"""
from tkinter import ttk
from tkinter import *
from trendyPhrase import group_conditions, group_lifestyles, group_hobbies, DewfordTrend
from feebasCoordinates import FEEBAS_COORDINATES
DEBUG_ENABLED = False
class FeebasCalculator:
def __init__(self, trainer_id, lottery_number, trendy_phrase_1, trendy_phrase_2, is_rs):
"""
This class calculates the exact spots of where Feebas is located based on the Trainer ID, the Lottery Number and the Trendy Phrase.
This class can only find Feebas if a new game has started without a working battery.
"""
def __init__(self, trainer_id, lottery_number, trendy_phrase_1, trendy_phrase_2, is_emerald):
"""
This function initialises AND calculates the Feebas spots based on the parameters given.
Args:
self: The class itself
trainer_id: The Trainer ID of the player
lottery_number: The Lottery Number found in Lilicove City
trendy_phrase_1: The first word of the Trendy Phrase found in Dewford Town
trendy_phrase_2: The second word of the Trendy Phrase found in Dewford Town
is_emerald: A boolean indicating if these values are from Ruby/Sapphire (False) or Emerald (True)
"""
self.is_feebas_found = False
self.feebas_seed = 0
# Checks to make sure the values given are correct
if not((trendy_phrase_1 in group_conditions) and ((trendy_phrase_2 in group_lifestyles) or (trendy_phrase_2 in group_hobbies))):
return
if(trainer_id == '' or lottery_number == ''):
return
# Initialises the values of the Feebas calculator
self.trainer_id = int(trainer_id)
self.secret_id = 0
self.secret_ids = []
self.lottery_number = int(lottery_number)
self.trendy_phrase_1 = trendy_phrase_1
self.trendy_phrase_2 = trendy_phrase_2
self.starting_seeds = []
self.calculated_feebas_spots = []
self.rng_counter = 0
self.vblank = 0xFFFFFFFF
if(is_rs):
# Initialise the RNG based on which game is used for the calculator
if(is_emerald == False):
self.seedRng(0x5A0)
else:
self.seedRng(self.trainer_id)
# Find the RNG starting point for the Feebas calculation
self.findFeebasStartingPoint()
for x in self.starting_seeds:
self.findFeebasSpotsEmerald(x)
# Calculate the Trendy Phrase
for seed in self.starting_seeds:
if(is_emerald == False):
self.findTrendyPhraseRubySapphire(seed)
else:
self.findTrendyPhraseEmerald(seed)
if(self.is_feebas_found == True):
break
# Return if no Feebas seed was found
if(self.is_feebas_found == False):
print("FUCK")
return
# Seed the RNG with the value found for the Trendy Phrase
self.seedRng(self.feebas_seed)
self.seedRng(self.dewford_trends[0].getRandomValue())
# Calculate the actual Feebas spots
x = 0
while(x != 6):
feebas_id = self.getFeebasRandomValue() % 447
feebas_id = self.getRandomValue() % 447
if(feebas_id == 0):
feebas_id = 447
if(feebas_id >= 4):
self.calculated_feebas_spots.append(FEEBAS_COORDINATES[feebas_id])
x += 1
return
def isFeebasFound(self):
"""
This function indicates if the class has found the Feebas spots or not
Args:
self: The class itself
Returns:
self.is_feebas_found: A boolean indicating if the class has found the Feebas spots
"""
return self.is_feebas_found
def getSecretIds(self):
return self.secret_ids
def getFeebasSpotCoordinates(self):
"""
This function returns the calculated Feebas spots
Args:
self: The class itself
Returns:
self.calculated_feebas_spots: An array containing the 6 Feebas spot coordinates
"""
return self.calculated_feebas_spots
#return FEEBAS_COORDINATES
def seedRng(self, seed):
"""
This function seeds the local RNG function with any 32 bit seed
Args:
self: The class itself
seed: A 32 bit value containing the seed for the RNG
"""
self.random_value = seed & 0xFFFFFFFF
def getFeebasRandomValue(self):
self.random_value = 0x41C64E6D * self.random_value + 0x00003039
self.random_value &= 0xFFFFFFFF
return (self.random_value >> 16)
def getRandomValue(self):
"""
This function progresses the RNG once and returns the newly generated value
Args:
self: The class itself
Returns:
self.random_value: The upper 16 bits of the randomly generated value.
"""
self.random_value = 0x41C64E6D * self.random_value + 0x00006073
self.random_value &= 0xFFFFFFFF
self.rng_counter += 1
if(self.vblank == self.rng_counter):
self.random_value = 0x41C64E6D * self.random_value + 0x00006073
self.random_value &= 0xFFFFFFFF
self.rng_counter += 1
print(self.vblank)
return (self.random_value >> 16)
def getPreviousRandomValue(self):
"""
This function calculates the previous RNG value and returns it
Args:
self: The class itself
Returns:
self.random_value: The upper 16 bits of the previous random value.
"""
self.random_value = 0xEEB9EB65 * self.random_value + 0x0A3561A1;
self.random_value &= 0xFFFFFFFF
return (self.random_value >> 16)
def findFeebasStartingPoint(self):
"""
This function finds the starting point for the Trendy Phrase calculation based on the Lottory Number. It does this by progressing the RNG
20000 frames (around 5,5 minutes) forward and saves all moments the RNG generated the given Lottory Number in an array.
Args:
self: The class itself
"""
self.starting_seeds = []
for x in range(20000):
random_value = self.getRandomValue()
if(random_value == self.lottery_number):
self.starting_seeds.append(self.random_value)
print(self.starting_seeds)
def findFeebasSpotsRubySapphire(self, lottery_seed):
def findTrendyPhraseRubySapphire(self, lottery_seed):
"""
This function generates the dewford phrases for Ruby and Sapphire based on the lottory seed that was found before. It first find the starting
point using the Trainer ID. Afterwards the Dewford Phrases are generated. If the last RNG call made ends with the Lottory Number and the Trendiest
Phrase matches, then Feebas is found successfully!
Args:
self: The class itself
lottery_seed: The seed of the RNG which generated the Lottory Number
"""
lottery_no = 0
final_trendy_prase = ["NO", "FEEBAS"]
# Seed the RNG and regress backwards a total of 50 RNG calls maximum or until the Trainer ID is found.
self.seedRng(lottery_seed)
self.rng_counter = 0
for x in range(50):
self.getPreviousRandomValue()
# if((self.random_value >> 16) == self.trainer_id)
def findFeebasSpotsEmerald(self, lottery_seed):
prev_rng_value = self.getPreviousRandomValue()
if(prev_rng_value == self.trainer_id):
break
# Nothing is found, return back
if(x == 50):
return
# Trainer ID is found! Time to calculate the rest of the values
# First we do one more step backwards for the Secret ID
temp_secret_id = self.getPreviousRandomValue()
# 3 RNG calls before the dewford phrases
self.getRandomValue()
self.getRandomValue()
self.getRandomValue()
# Generate the 5 dewford phrases
self.generateDewfordPhrases()
final_trendy_prase = self.dewford_trends[0].getPhrase()
# Generate the lottory number
lottery_no = self.getRandomValue()
# Check all the values. If it all matches, then Feebas is found!!
if((self.lottery_number == lottery_no) and (final_trendy_prase[0] == self.trendy_phrase_1 and final_trendy_prase[1] == self.trendy_phrase_2)):
if(DEBUG_ENABLED == True):
print("FOUND!!!!!")
print("Secret ID:" + str(temp_secret_id))
print("Feebas Seed:" + str(self.dewford_trends[0].getRandomValue()))
self.secret_ids.append(temp_secret_id)
self.is_feebas_found = True
self.feebas_seed = self.dewford_trends[0].getRandomValue()
def findTrendyPhraseEmerald(self, lottery_seed):
"""
This function generates the dewford phrases for Ruby and Sapphire based on the lottory seed that was found before. Emerald doesn't have a clear
starting point, so we need to regress a variable amount of time until we Trendy Phrase and the Lottory ID matches up. If this happens, then we have
found Feebas successfully! It also tries to find the Secret ID, but it is possible however to find more than one Secret ID...
Args:
self: The class itself
lottery_seed: The seed of the RNG which generated the Lottory Number
"""
reverse_steps = 50
lottery_no = 0
final_trendy_prase = ["NO", "FEEBAS"]
while((self.lottery_number != lottery_no) or (final_trendy_prase[0] != self.trendy_phrase_1 or final_trendy_prase[1] != self.trendy_phrase_2)):
for steps in range(20):
self.seedRng(lottery_seed)
#print(lottery_seed)
self.rng_counter = 0
for x in range(reverse_steps):
for x in range(reverse_steps - steps):
self.getPreviousRandomValue()
# First the Secret ID is generated
temp_secret_id = self.getRandomValue()
# 3 RNG calls before the dewford phrases
self.getRandomValue()
self.getRandomValue()
self.getRandomValue()
# Generate the 5 dewford phrases
self.generateDewfordPhrases()
final_trendy_prase = self.dewford_trends[0].getPhrase()
# Generate the lottory number
lottery_no = self.getRandomValue()
reverse_steps -= 1
if(reverse_steps < 30):
print("UGH")
return
print("FOUND!!!!!")
print(self.dewford_trends[0].getRandomValue())
self.is_feebas_found = True
# Check all the values. If it all matches, then Feebas is found!!
if((self.lottery_number == lottery_no) and (final_trendy_prase[0] == self.trendy_phrase_1 and final_trendy_prase[1] == self.trendy_phrase_2)):
if(DEBUG_ENABLED == True):
print("FOUND!!!!!")
print("Secret ID:" + str(temp_secret_id))
print("Feebas Seed:" + str(self.dewford_trends[0].getRandomValue()))
print("Reverse Steps:" + str(reverse_steps - steps))
self.secret_ids.append(temp_secret_id)
self.is_feebas_found = True
self.feebas_seed = self.dewford_trends[0].getRandomValue()
def generateDewfordPhrases(self):
"""
This function generates 5 Dewford Phrases and sorts them based on their Trendiness.
This is a direct copy of how the game does it as well.
Args:
self: The class itself
"""
self.dewford_trends = []
for x in range(5):
new_trend = DewfordTrend()
# Generate the phrase
phrase_1 = group_conditions[self.getRandomValue() % len(group_conditions)]
if(self.getRandomValue() & 1 == 1):
phrase_2 = group_lifestyles[self.getRandomValue() % len(group_lifestyles)]
else:
phrase_2 = group_hobbies[self.getRandomValue() % len(group_hobbies)]
new_trend.setPhrase(phrase_1, phrase_2)
#print(phrase_1)
# Generate the Trendiness values and the Random value
new_trend.setIsGainingTrendiness(self.getRandomValue() & 1)
rando = self.getRandomValue() % 98
@@ -141,10 +293,18 @@ class FeebasCalculator:
new_trend.setRandomValue(self.getRandomValue())
self.dewford_trends.append(new_trend)
# Sort the trends based on their Trendiness values
self.sortTrends()
def sortTrends(self):
"""
This function sorts the 5 Dewford Phrases based on their Trendiness values
This is a direct copy of how the game does it as well.
Args:
self: The class itself
"""
for x in range(5):
y = x + 1
while(y < 5):
@@ -153,6 +313,15 @@ class FeebasCalculator:
y += 1
def compareTrends(self, a, b):
"""
This function compares two trends in order to see which one has a higher Trendiness value.
This is a direct copy of how the game does it as well.
Args:
self: The class itself
a: A dewford trend index
b: A dewford trend index
"""
if(self.dewford_trends[a].getTrendiness() > self.dewford_trends[b].getTrendiness()):
return True
if(self.dewford_trends[a].getTrendiness() < self.dewford_trends[b].getTrendiness()):
@@ -164,6 +333,16 @@ class FeebasCalculator:
return (self.getRandomValue() & 1)
def SWAP(self, list, pos1, pos2):
list[pos1], list[pos2] = list[pos2], list[pos1]
return list
def SWAP(self, dewford_list, pos1, pos2):
"""
This function swaps two trends with each other in the dewford_list
This is a direct copy of how the game does it as well.
Args:
self: The class itself
dewford_list: The 5 dewford trends in a list
pos1: Index of a Dewford trend that is to be swapped with pos2
pos2: Index of a Dewford trend that is to be swapped with pos1
"""
dewford_list[pos1], dewford_list[pos2] = dewford_list[pos2], dewford_list[pos1]
return dewford_list

539
main.py
View File

@@ -1,239 +1,340 @@
"""
main.py
This file serves as the starting point for the Finding Feebas application.
It initialises the interface and has functions for all interactions, like moving the map,
pressing buttons and checking the input on the entry boxes.
"""
from feebasCalcs import FeebasCalculator
from trendyPhrase import group_conditions, group_lifestyles, group_hobbies
from tkinter import ttk
from tkinter import *
from ttkwidgets.autocomplete import AutocompleteCombobox
from feebasCalcs import FeebasCalculator
from trendyPhrase import group_conditions, group_lifestyles, group_hobbies
# pip install pillow
from PIL import Image, ImageTk
ROUTE119_PATH = "./Resources/Images/Hoenn_Route_119_E.png"
FINDING_FEEBAS_ART = "./Resources/Images/Finding_Feebas_BG.jpg"
FEEBAS_SPOT_INDICATOR = "./Resources/Images/Feebas_Spot_Indicator.png"
ROUTE119_INITAL_X_POS = 192
ROUTE119_INITAL_Y_POS = 960
HACKY_FEEBAS_BUTTON_INIT = [0, 1, 2, 3, 4, 5]
class Route119:
def __init__(self, root):
self.root = root
self.canvas = Canvas(self.root, width=320, height=560, bg="white")
image = Image.open('./Recources/Hoenn_Route_119_E.png')
self.render = ImageTk.PhotoImage(image)
img = self.canvas.create_image(192, 960, image=self.render)
self.canvas.place(relx=0.25,rely=0.5,anchor=CENTER)
"""
This class handles the entire interface and all possible inputs made by the user.
"""
def __init__(self, root):
"""
This function initialises the entire interface of the application.
Args:
self: The class itself
root: The root of the tkinter Finding Feebas Application
"""
self.root = root
# Initialise the map on the left
self.map_canvas = Canvas(self.root, width=320, height=560, bg="white")
image = Image.open(ROUTE119_PATH)
self.map_render = ImageTk.PhotoImage(image)
self.map_canvas.create_image(ROUTE119_INITAL_X_POS, ROUTE119_INITAL_Y_POS, image=self.map_render)
self.map_canvas.place(relx=0.25,rely=0.5,anchor=CENTER)
# Initialise the art on the right
self.art_canvas = Canvas(self.root, width=320, height=560, bg="white")
image = Image.open(FINDING_FEEBAS_ART)
resized_image= image.resize((325,565))
self.art_render = ImageTk.PhotoImage(resized_image)
self.art_canvas.create_image(160, 280, image=self.art_render)
self.art_canvas.place(relx=0.75,rely=0.5,anchor=CENTER)
# Initialise the current position of the map and mouse position
self.current_image_xpos = ROUTE119_INITAL_X_POS
self.current_image_ypos = ROUTE119_INITAL_Y_POS
self.current_mouse_xpos = 0
self.current_mouse_ypos = 0
self.map_canvas.bind('<Button-1>', self.mousePressCanvas)
self.map_canvas.bind("<B1-Motion>", self.mouseMove)
# Initialise the checkbox for Ruby/Sapphire and Emerald. It is coded so that a variable will be 1 if Emerald is selected.
self.is_emerald = IntVar()
self.is_emerald.set(0)
ruby_sapphire_checkbox = ttk.Checkbutton(self.root, text='Ruby/Sapphire',variable=self.is_emerald, onvalue=0, offvalue=1)
ruby_sapphire_checkbox.place(x=320*0.55/2+320, y=150, anchor="center")
emerald_checkbox = ttk.Checkbutton(self.root, text='Emerald',variable=self.is_emerald, onvalue=1, offvalue=0)
emerald_checkbox.place(x=320*1.45/2+320, y=150, anchor="center")
# Initialise the label and entry box for the Trainder ID and the Lottery ID
validate_command_function = (self.root.register(self.validateNumber), '%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W')
tid_label = ttk.Label(root, text='Trainer ID')
tid_label.place(x=320*1.5/7+320, y=200, anchor="w")
sv = StringVar()
sv.trace("w", lambda name, index, mode, sv=sv: self.maxNumberCallback(sv, 0xFFFF, 0))
self.tid_entry = ttk.Entry(root, validate="key", validatecommand=validate_command_function, textvariable=sv, width=10)
self.tid_entry.place(x=320*3.5/7+320, y=200, anchor="w")
lot_label = ttk.Label(root, text='Lottery No.')
lot_label.place(x=320*1.5/7+320, y=225, anchor="w")
sv = StringVar()
sv.trace("w", lambda name, index, mode, sv=sv: self.maxNumberCallback(sv, 0xFFFF, 1))
self.lot_enrty = ttk.Entry(root, validate="key", validatecommand=validate_command_function, textvariable=sv, width=10)
self.lot_enrty.place(x=320*3.5/7+320, y=225, anchor="w")
self.canvas2 = Canvas(self.root, width=320, height=560, bg="white")
image = Image.open('./Recources/Finding_Feebas_BG.jpg')
resized_image= image.resize((325,565))
self.render2 = ImageTk.PhotoImage(resized_image)
img = self.canvas2.create_image(160, 280, image=self.render2)
self.canvas2.place(relx=0.75,rely=0.5,anchor=CENTER)
self.current_image_xpos = 192
self.current_image_ypos = 960
# Initialise the lists for the trendhy phrase and place them in a dropdown menu
self.trendy_phrase_1 = group_conditions.copy()
self.trendy_phrase_1.sort()
self.trendy_phrase_2 = group_lifestyles + group_hobbies
self.trendy_phrase_2.sort()
self.drop1 = AutocompleteCombobox(root, completevalues=self.trendy_phrase_1)
self.drop1.place(x=320*0.55/2+320, y=360, anchor="center")
self.drop1.config(width = 17)
self.drop2 = AutocompleteCombobox(root, completevalues=self.trendy_phrase_2)
self.drop2.place(x=320*1.45/2+320, y=360, anchor="center")
self.drop2.config(width = 17)
# Initialise the buttons to calculate and clear the feebas spots
calculate_button = ttk.Button(root, text='Calculate', command=self.calculateFeebasSpots, state= NORMAL)
calculate_button.place(x=320*2/7+320, y=410, anchor="center")
clear_button = ttk.Button(root, text='Clear', command=self.clearFeebasSpots, state= NORMAL)
clear_button.place(x=320*5/7+320, y=410, anchor="center")
# Initialise label and unusable entry box for Secret ID
secret_id_label = ttk.Label(root, text='Secret ID:')
secret_id_label.place(x=320*1.2/7+320, y=455, anchor="center")
self.secret_id_entry = ttk.Label(root, text='')
self.secret_id_entry.place(x=320*4.15/7+320, y=455, anchor="center")
self.secret_id_entry.config(background='#c5cedb')
self.secret_id_entry.config(width = 33)
# Initialise the 6 buttons to find the feebas spots in the map.
self.feebas_spot_buttons = []
spot_button = ttk.Button(root, text='1', command= lambda: self.goToFeebasSpot(0), state= DISABLED, width=4)
spot_button.place(x=320/7+320, y=500, anchor="center")
self.feebas_spot_buttons.append(spot_button)
spot_button = ttk.Button(root, text='2', command= lambda: self.goToFeebasSpot(1), state= DISABLED, width=4)
spot_button.place(x=320*2/7+320, y=500, anchor="center")
self.feebas_spot_buttons.append(spot_button)
spot_button = ttk.Button(root, text='3', command= lambda: self.goToFeebasSpot(2), state= DISABLED, width=4)
spot_button.place(x=320*3/7+320, y=500, anchor="center")
self.feebas_spot_buttons.append(spot_button)
spot_button = ttk.Button(root, text='4', command= lambda: self.goToFeebasSpot(3), state= DISABLED, width=4)
spot_button.place(x=320*4/7+320, y=500, anchor="center")
self.feebas_spot_buttons.append(spot_button)
self.current_mouse_xpos = 0
self.current_mouse_ypos = 0
self.canvas.bind('<Button-1>', self.mousePressCanvas)
self.canvas.bind("<B1-Motion>", self.mouseMove)
spot_button = ttk.Button(root, text='5', command= lambda: self.goToFeebasSpot(4), state= DISABLED, width=4)
spot_button.place(x=320*5/7+320, y=500, anchor="center")
self.feebas_spot_buttons.append(spot_button)
spot_button = ttk.Button(root, text='6', command= lambda: self.goToFeebasSpot(5), state= DISABLED, width=4)
spot_button.place(x=320*6/7+320, y=500, anchor="center")
self.feebas_spot_buttons.append(spot_button)
self.trendy_1 = group_conditions.copy()
self.trendy_1.sort()
self.trendy_2 = group_lifestyles + group_hobbies
self.trendy_2.sort()
self.var = IntVar()
self.var.set(1)
self.ruby_sapphire_checkbox = ttk.Checkbutton(self.root, text='Ruby/Sapphire',variable=self.var, onvalue=1, offvalue=0, command=self.rs_selected)
self.ruby_sapphire_checkbox.place(x=320*0.55/2+320, y=150, anchor="center")
self.ruby_sapphire_checkbox = ttk.Checkbutton(self.root, text='Emerald',variable=self.var, onvalue=0, offvalue=1, command=self.rs_selected)
self.ruby_sapphire_checkbox.place(x=320*1.45/2+320, y=150, anchor="center")
# Create Dropdown menu
self.drop1 = AutocompleteCombobox(root, completevalues=self.trendy_1)
self.drop1.place(x=320*0.55/2+320, y=360, anchor="center")
self.drop1.config(width = 17)
self.drop2 = AutocompleteCombobox(root, completevalues=self.trendy_2)
self.drop2.place(x=320*1.45/2+320, y=360, anchor="center")
self.drop2.config(width = 17)
vcmd = (self.root.register(self.validateNumber), '%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W')
tid_label = ttk.Label(root, text='Trainer ID')
tid_label.place(x=320*1.5/7+320, y=200, anchor="w")
sv = StringVar()
sv.trace("w", lambda name, index, mode, sv=sv: self.maxNumberCallback(sv, 0xFFFF, 0))
self.tid_entry = ttk.Entry(root, validate="key", validatecommand=vcmd, textvariable=sv, width=10)
self.tid_entry.place(x=320*3.5/7+320, y=200, anchor="w")
lot_label = ttk.Label(root, text='Lottery No.')
lot_label.place(x=320*1.5/7+320, y=225, anchor="w")
sv = StringVar()
sv.trace("w", lambda name, index, mode, sv=sv: self.maxNumberCallback(sv, 0xFFFF, 1))
self.lot_enrty = ttk.Entry(root, validate="key", validatecommand=vcmd, textvariable=sv, width=10)
self.lot_enrty.place(x=320*3.5/7+320, y=225, anchor="w")
def goToFeebasSpot(self, spot_id):
"""
This function changes the position of the movable map to the desired Feebas Spot. The map is 640px wide and 2240px long.
The edges of the map on the canvas has the following coordinates:
- x(0) = 480
- x(640) = -160
- y(0) = 1400
- y(2240) = -840
sid_label = ttk.Label(root, text='Secret ID')
sid_label.place(x=320*1.5/7+320, y=250, anchor="w")
self.sid_entry = ttk.Label(root, text='')
self.sid_entry.place(x=320*3.5/7+320, y=250, anchor="w")
calc_button = ttk.Button(root, text='Calculate', command=self.calculateFeebasSpots, state= NORMAL)
calc_button.place(x=320*2/7+320, y=430, anchor="center")
clear_button = ttk.Button(root, text='Clear', command=self.clearFeebasSpots, state= NORMAL)
clear_button.place(x=320*5/7+320, y=430, anchor="center")
self.feebas_spot_buttons = []
spot_button = ttk.Button(root, text='1', command= lambda: self.goToFeebasSpot(0), state= DISABLED, width=4)
spot_button.place(x=320/7+320, y=500, anchor="center")
self.feebas_spot_buttons.append(spot_button)
spot_button = ttk.Button(root, text='2', command= lambda: self.goToFeebasSpot(1), state= DISABLED, width=4)
spot_button.place(x=320*2/7+320, y=500, anchor="center")
self.feebas_spot_buttons.append(spot_button)
spot_button = ttk.Button(root, text='3', command= lambda: self.goToFeebasSpot(2), state= DISABLED, width=4)
spot_button.place(x=320*3/7+320, y=500, anchor="center")
self.feebas_spot_buttons.append(spot_button)
spot_button = ttk.Button(root, text='4', command= lambda: self.goToFeebasSpot(3), state= DISABLED, width=4)
spot_button.place(x=320*4/7+320, y=500, anchor="center")
self.feebas_spot_buttons.append(spot_button)
spot_button = ttk.Button(root, text='5', command= lambda: self.goToFeebasSpot(4), state= DISABLED, width=4)
spot_button.place(x=320*5/7+320, y=500, anchor="center")
self.feebas_spot_buttons.append(spot_button)
spot_button = ttk.Button(root, text='6', command= lambda: self.goToFeebasSpot(5), state= DISABLED, width=4)
spot_button.place(x=320*6/7+320, y=500, anchor="center")
self.feebas_spot_buttons.append(spot_button)
Args:
self: The class itself
spot_id: The Feebas Spot ID ranging from 0 - 5
"""
calculated_spots = self.feebas_calcs.getFeebasSpotCoordinates()
# Calculate the new position of the map
self.current_image_xpos = (640 - calculated_spots[spot_id][0] - 160)
self.current_image_ypos = (2240 - calculated_spots[spot_id][1] - 840)
# Check if the image doesn't go past any of these set boundaries
if(self.current_image_xpos > 16*20):
self.current_image_xpos = 16*20
elif(self.current_image_xpos < 0):
self.current_image_xpos = 0
if(self.current_image_ypos > 16*70):
self.current_image_ypos = 16*70
elif(self.current_image_ypos < -16*12):
self.current_image_ypos = -16*12
def goToFeebasSpot(self, spot_id):
calculated_spots = self.feebas_calcs.getFeebasSpotCoordinates()
# x(0) = 480
# x(640) = -160
# y{0) = 1400
# y(2240) = -840
self.current_image_xpos = (640 - calculated_spots[spot_id][0] - 160)
self.current_image_ypos = (2240 - calculated_spots[spot_id][1] - 840)
if(self.current_image_xpos > 16*20):
self.current_image_xpos = 16*20
elif(self.current_image_xpos < 0):
self.current_image_xpos = 0
if(self.current_image_ypos > 16*60):
self.current_image_ypos = 16*60
elif(self.current_image_ypos < -16*12):
self.current_image_ypos = -16*12
# Draw the map on its new coordinates
self.map_canvas.create_image(self.current_image_xpos, self.current_image_ypos, image=self.map_render)
img = self.canvas.create_image(self.current_image_xpos, self.current_image_ypos, image=self.render)
def calculateFeebasSpots(self):
"""
This function is called when the user clicks on the "Calculate" button. This function will try to
calculate the correct Feebas Spots and draw them on the map if possible.
def calculateFeebasSpots(self):
self.feebas_calcs = FeebasCalculator(self.tid_entry.get(), self.lot_enrty.get(), self.drop1.get(), self.drop2.get(), self.var.get())
if(self.feebas_calcs.isFeebasFound() == False):
self.root.bell()
return
image = Image.open("./Recources/Hoenn_Route_119_E.png").convert('RGBA')
watermark = Image.open("./Recources/Feebas_Spot_Indicator.png").convert('RGBA')
layer = Image.new('RGBA', image.size, (0, 0, 0, 0))
#print(self.feebas_calcs.getSecretId())
#self.sid_entry.config(text=str(self.feebas_calcs.getSecretId()))
calculated_spots = self.feebas_calcs.getFeebasSpotCoordinates()
for xy in calculated_spots:
layer.paste(watermark, (xy[0], xy[1]))
xy = xy[:2]
layer2 = layer.copy()
layer2.putalpha(180)
layer.paste(layer2, layer)
result = Image.alpha_composite(image, layer)
#result.save("NewImage.png")
self.render = ImageTk.PhotoImage(result)
img = self.canvas.create_image(self.current_image_xpos, self.current_image_ypos, image=self.render)
for spot in self.feebas_spot_buttons:
spot['state'] = "normal"
Args:
self: The class itself
"""
# GIve the parameters to the FeebasCalculator and check if it could find a feebas. If not, then make a noise to let the user know about this.
self.feebas_calcs = FeebasCalculator(self.tid_entry.get(), self.lot_enrty.get(), self.drop1.get(), self.drop2.get(), self.is_emerald.get())
if(self.feebas_calcs.isFeebasFound() == False):
self.root.bell()
return
# Make a hacky string for the Secret IDs in case we got multiple results
secret_id_string = ""
for secret_id in self.feebas_calcs.getSecretIds():
if(secret_id_string != ""):
secret_id_string += ' / '
secret_id_string += str(secret_id)
self.secret_id_entry.config(text=str(secret_id_string))
# Prepare the map and spot indicator
map_image = Image.open(ROUTE119_PATH).convert('RGBA')
indicator_image = Image.open(FEEBAS_SPOT_INDICATOR).convert('RGBA')
map_layer = Image.new('RGBA', map_image.size, (0, 0, 0, 0))
def clearFeebasSpots(self):
image = Image.open('./Recources/Hoenn_Route_119_E.png')
self.render = ImageTk.PhotoImage(image)
img = self.canvas.create_image(self.current_image_xpos, self.current_image_ypos, image=self.render)
for spot in self.feebas_spot_buttons:
spot['state'] = "disabled"
def mousePressCanvas(self, e):
self.current_mouse_xpos = e.x
self.current_mouse_ypos = e.y
def mouseMove(self, e):
x_diff = self.current_mouse_xpos - e.x
y_diff = self.current_mouse_ypos - e.y
self.current_image_xpos -= x_diff
self.current_image_ypos -= y_diff
if(self.current_image_xpos > 16*20):
self.current_image_xpos = 16*20
elif(self.current_image_xpos < 0):
self.current_image_xpos = 0
if(self.current_image_ypos > 16*70):
self.current_image_ypos = 16*70
elif(self.current_image_ypos < -16*12):
self.current_image_ypos = -16*12
self.current_mouse_xpos = e.x
self.current_mouse_ypos = e.y
img = self.canvas.create_image(self.current_image_xpos, self.current_image_ypos, image=self.render)
def maxNumberCallback(self, sv, max_number, id):
current_number = 0
try:
current_number = int(sv.get())
except ValueError:
print("Nothing")
# Get the calculated spots and place the indicator in the map
calculated_spots = self.feebas_calcs.getFeebasSpotCoordinates()
for xy in calculated_spots:
map_layer.paste(indicator_image, (xy[0], xy[1]))
xy = xy[:2]
indicator_layer = map_layer.copy()
indicator_layer.putalpha(180)
map_layer.paste(indicator_layer, map_layer)
result = Image.alpha_composite(map_image, map_layer)
if current_number > max_number:
if(id == 0):
self.tid_entry.delete(0, END)
self.tid_entry.insert(0, "65535")
else:
self.lot_enrty.delete(0, END)
self.lot_enrty.insert(0, "65535")
current_number = max_number
self.root.bell()
def rs_selected(self):
print("SELECTED")
def validateNumber(self, d, i, P, s, S, v, V, W):
# %d = Type of action (1=insert, 0=delete, -1 for others)
# %i = index of char string to be inserted/deleted, or -1
# %P = value of the entry if the edit is allowed
# %s = value of entry prior to editing
# %S = the text string being inserted or deleted, if any
# %v = the type of validation that is currently set
# %V = the type of validation that triggered the callback
# (key, focusin, focusout, forced)
# %W = the tk name of the widget
# place the image with the indicators back in the tool for the user to see
self.map_render = ImageTk.PhotoImage(result)
self.map_canvas.create_image(self.current_image_xpos, self.current_image_ypos, image=self.map_render)
# Activate the Feebas spot buttons to directly see where the spots are
for spot in self.feebas_spot_buttons:
spot['state'] = "normal"
# Disallow anything that isn't a number
if S.isnumeric():
return True
else:
self.root.bell()
return False
root = Tk()
def clearFeebasSpots(self):
"""
This function is called when the user clicks on the "Clear" button. This function will reset the map and get rid of any Feebas Indicators placed.
The buttons for the Feebas spots will also be disabled.
root.wm_title("Finding Feebas")
root.geometry("640x560")
root.resizable(0,0)
Args:
self: The class itself
"""
self.secret_id_entry.config(text="")
image = Image.open(ROUTE119_PATH)
self.map_render = ImageTk.PhotoImage(image)
self.map_canvas.create_image(self.current_image_xpos, self.current_image_ypos, image=self.map_render)
for spot in self.feebas_spot_buttons:
spot['state'] = "disabled"
def mousePressCanvas(self, e):
"""
This function is called when the user does a leftbutton mouse press on the Route119 map. The current position of the mouse is stored in variables of this class.
app = Route119(root)
Args:
self: The class itself
e: Mouse events
"""
self.current_mouse_xpos = e.x
self.current_mouse_ypos = e.y
def mouseMove(self, e):
"""
This function is called when the user drags the canvas while having the leftbutton of the mouse pressed. The position of the map is updated based on which direction
the user drags the mouse. Some limits have been set in place to make sure the dragging stops at the edges of the map and stays in the relevant areas.
root.mainloop()
Args:
self: The class itself
e: Mouse events
"""
x_diff = self.current_mouse_xpos - e.x
y_diff = self.current_mouse_ypos - e.y
self.current_image_xpos -= x_diff
self.current_image_ypos -= y_diff
if(self.current_image_xpos > 16*20):
self.current_image_xpos = 16*20
elif(self.current_image_xpos < 0):
self.current_image_xpos = 0
if(self.current_image_ypos > 16*70):
self.current_image_ypos = 16*70
elif(self.current_image_ypos < -16*12):
self.current_image_ypos = -16*12
self.current_mouse_xpos = e.x
self.current_mouse_ypos = e.y
self.map_canvas.create_image(self.current_image_xpos, self.current_image_ypos, image=self.map_render)
def maxNumberCallback(self, sv, max_number, id):
"""
This function is called whenever the user gives an input for the lottery number or the trainer ID. It will check if the value inserted is within the bounds set for it.
Args:
self: The class itself
sv: Holds the input made by the user
max_number: Hold the max number set for the input field
id: Indicates which entry field is used. 0 for Trainer ID and 1 for Lottery Number
"""
current_number = 0
try:
current_number = int(sv.get())
except ValueError:
print("Nothing")
if current_number > max_number:
if(id == 0):
self.tid_entry.delete(0, END)
self.tid_entry.insert(0, "65535")
else:
self.lot_enrty.delete(0, END)
self.lot_enrty.insert(0, "65535")
current_number = max_number
self.root.bell()
def validateNumber(self, d, i, P, s, S, v, V, W):
"""
This function is called whenever the user gives an input for the lottery number or the trainer ID. It will check if the value inserted is a number.
It will dissalow any other characters.
Args:
self: The class itself
d: Type of action (1=insert, 0=delete, -1 for others)
i: Index of char string to be inserted/deleted, or -1
P: Value of the entry if the edit is allowed
s: Value of entry prior to editing
S: The text string being inserted or deleted, if any
v: The type of validation that is currently set
V: The type of validation that triggered the callback
(key, focusin, focusout, forced)
W: The tk name of the widget
"""
# Disallow anything that isn't a number
if S.isnumeric():
return True
else:
self.root.bell()
return False
if __name__ == "__main__":
root = Tk()
root.wm_title("Finding Feebas")
root.geometry("640x560")
root.resizable(0,0)
root.iconbitmap(default='Resources/Icon/LogoGoppier.ico')
app = Route119(root)
root.mainloop()

View File

@@ -1,216 +1,321 @@
"""
trendyPhrase.py
This file contains all the possible words used in the Trendy Phrase and also has a class that contains all the parameters of the Dewford Trend.
"""
class DewfordTrend:
def __init__(self):
self.trendiness = 0
self.max_trendiness = 0
self.is_gaining_trendiness = False
self.random_value = 0
self.easy_chat_words = ["", ""]
def setTrendiness(self, value):
self.trendiness = value
def getTrendiness(self):
return self.trendiness
def setMaxTrendiness(self, value):
self.max_trendiness = value
def getMaxTrendiness(self):
return self.max_trendiness
def setIsGainingTrendiness(self, value):
self.is_gaining_trendiness = value
def getIsGainingTrendiness(self):
return self.is_gaining_trendiness
def setRandomValue(self, value):
self.random_value = value
def getRandomValue(self):
return self.random_value
def setPhrase(self, phrase_1, phrase_2):
self.easy_chat_words[0] = phrase_1
self.easy_chat_words[1] = phrase_2
def getPhrase(self):
return self.easy_chat_words
"""
This class holds the parameters of each Trendy Phrase used in Dewford
"""
def __init__(self):
"""
This function initialises the Trendy Phrase parameters
Args:
self: The class itself
"""
self.trendiness = 0
self.max_trendiness = 0
self.is_gaining_trendiness = False
self.random_value = 0
self.easy_chat_words = ["", ""]
def setTrendiness(self, trendiness):
"""
This function sets the trendiness value for this Trendy Phrase
Args:
self: The class itself
trendiness: The value of the phrase's trendiness
"""
self.trendiness = trendiness
def getTrendiness(self):
"""
This function returns the trendiness value for this Trendy Phrase
Args:
self: The class itself
Returns:
self.trendiness: The trendiness value in the form on an integer
"""
return self.trendiness
def setMaxTrendiness(self, max_trendiness):
"""
This function sets the maximum trendiness value for this Trendy Phrase
Args:
self: The class itself
max_trendiness: The value of the phrase's maximum trendiness
"""
self.max_trendiness = max_trendiness
def getMaxTrendiness(self):
"""
This function returns the maximum trendiness value for this Trendy Phrase
Args:
self: The class itself
Returns:
self.max_trendiness: The maximum trendiness value in the form on an integer
"""
return self.max_trendiness
def setIsGainingTrendiness(self, is_gaining_trendiness):
"""
This function sets the is_gaining_trendiness value
Args:
self: The class itself
is_gaining_trendiness: A boolean value that decides if the phrase is gaining trendiness or not
"""
self.is_gaining_trendiness = is_gaining_trendiness
def getIsGainingTrendiness(self):
"""
This function returns the is_gaining_trendiness for this Trendy Phrase
Args:
self: The class itself
Returns:
self.is_gaining_trendiness: A boolean value that decides if the phrase is gaining trendiness or not
"""
return self.is_gaining_trendiness
def setRandomValue(self, random_value):
"""
This function sets the random value for this Trendy Phrase
Args:
self: The class itself
random_value: The random value of this Trendy Phrase
"""
self.random_value = random_value
def getRandomValue(self):
"""
This function returns the random value for this Trendy Phrase
Args:
self: The class itself
Returns:
self.random_value: The random value in the form on an integer
"""
return self.random_value
def setPhrase(self, easy_chat_word_1, easy_chat_word_2):
"""
This function sets the Trendy Phrase using two Easy Chat words
Args:
self: The class itself
easy_chat_word_1: The first word of the Trendy Phrase
easy_chat_word_2: The second word of the Trendy Phrase
"""
self.easy_chat_words[0] = easy_chat_word_1
self.easy_chat_words[1] = easy_chat_word_2
def getPhrase(self):
"""
This function returns the Easy Chat words for this Trendy Phrase
Args:
self: The class itself
Returns:
self.easy_chat_words: The Easy Chat words for this Trendy Phrase
"""
return self.easy_chat_words
"""
This list holds all the Easy Chat words for the category "Conditions"
"""
group_conditions=[
"HOT",
"EXISTS",
"EXCESS",
"APPROVED",
"HAS",
"GOOD",
"LESS",
"MOMENTUM",
"GOING",
"WEIRD",
"BUSY",
"TOGETHER",
"FULL",
"ABSENT",
"BEING",
"NEED",
"TASTY",
"SKILLED",
"NOISY",
"BIG",
"LATE",
"CLOSE",
"DOCILE",
"AMUSING",
"ENTERTAINING",
"PERFECTION",
"PRETTY",
"HEALTHY",
"EXCELLENT",
"UPSIDEDOWN",
"COLD",
"REFRESHING",
"UNAVOIDABLE",
"MUCH",
"OVERWHELMING",
"FABULOUS",
"ELSE",
"EXPENSIVE",
"CORRECT",
"IMPOSSIBLE",
"SMALL",
"DIFFERENT",
"TIRED",
"SKILL",
"TOP",
"NONSTOP",
"PREPOSTEROUS",
"NONE",
"NOTHING",
"NATURAL",
"BECOMES",
"LUKEWARM",
"FAST",
"LOW",
"AWFUL",
"ALONE",
"BORED",
"SECRET",
"MYSTERY",
"LACKS",
"BEST",
"LOUSY",
"MISTAKE",
"KIND",
"WELL",
"WEAKENED",
"SIMPLE",
"SEEMS",
"BADLY"
"HOT",
"EXISTS",
"EXCESS",
"APPROVED",
"HAS",
"GOOD",
"LESS",
"MOMENTUM",
"GOING",
"WEIRD",
"BUSY",
"TOGETHER",
"FULL",
"ABSENT",
"BEING",
"NEED",
"TASTY",
"SKILLED",
"NOISY",
"BIG",
"LATE",
"CLOSE",
"DOCILE",
"AMUSING",
"ENTERTAINING",
"PERFECTION",
"PRETTY",
"HEALTHY",
"EXCELLENT",
"UPSIDEDOWN",
"COLD",
"REFRESHING",
"UNAVOIDABLE",
"MUCH",
"OVERWHELMING",
"FABULOUS",
"ELSE",
"EXPENSIVE",
"CORRECT",
"IMPOSSIBLE",
"SMALL",
"DIFFERENT",
"TIRED",
"SKILL",
"TOP",
"NONSTOP",
"PREPOSTEROUS",
"NONE",
"NOTHING",
"NATURAL",
"BECOMES",
"LUKEWARM",
"FAST",
"LOW",
"AWFUL",
"ALONE",
"BORED",
"SECRET",
"MYSTERY",
"LACKS",
"BEST",
"LOUSY",
"MISTAKE",
"KIND",
"WELL",
"WEAKENED",
"SIMPLE",
"SEEMS",
"BADLY"
]
"""
This list holds all the Easy Chat words for the category "Lifestyles"
"""
group_lifestyles=[
"CHORES",
"HOME",
"MONEY",
"ALLOWANCE",
"BATH",
"CONVERSATION",
"SCHOOL",
"COMMEMORATE",
"HABIT",
"GROUP",
"WORD",
"STORE",
"SERVICE",
"WORK",
"SYSTEM",
"TRAIN",
"CLASS",
"LESSONS",
"INFORMATION",
"LIVING",
"TEACHER",
"TOURNAMENT",
"LETTER",
"EVENT",
"DIGITAL",
"TEST",
"DEPT_STORE",
"TELEVISION",
"PHONE",
"ITEM",
"NAME",
"NEWS",
"POPULAR",
"PARTY",
"STUDY",
"MACHINE",
"MAIL",
"MESSAGE",
"PROMISE",
"DREAM",
"KINDERGARTEN",
"LIFE",
"RADIO",
"RENTAL",
"WORLD"
"CHORES",
"HOME",
"MONEY",
"ALLOWANCE",
"BATH",
"CONVERSATION",
"SCHOOL",
"COMMEMORATE",
"HABIT",
"GROUP",
"WORD",
"STORE",
"SERVICE",
"WORK",
"SYSTEM",
"TRAIN",
"CLASS",
"LESSONS",
"INFORMATION",
"LIVING",
"TEACHER",
"TOURNAMENT",
"LETTER",
"EVENT",
"DIGITAL",
"TEST",
"DEPT_STORE",
"TELEVISION",
"PHONE",
"ITEM",
"NAME",
"NEWS",
"POPULAR",
"PARTY",
"STUDY",
"MACHINE",
"MAIL",
"MESSAGE",
"PROMISE",
"DREAM",
"KINDERGARTEN",
"LIFE",
"RADIO",
"RENTAL",
"WORLD"
]
"""
This list holds all the Easy Chat words for the category "Hobbies"
"""
group_hobbies=[
"IDOL",
"ANIME",
"SONG",
"MOVIE",
"SWEETS",
"CHAT",
"CHILD_S_PLAY",
"TOYS",
"MUSIC",
"CARDS",
"SHOPPING",
"CAMERA",
"VIEWING",
"SPECTATOR",
"GOURMET",
"GAME",
"RPG",
"COLLECTION",
"COMPLETE",
"MAGAZINE",
"WALK",
"BIKE",
"HOBBY",
"SPORTS",
"SOFTWARE",
"SONGS",
"DIET",
"TREASURE",
"TRAVEL",
"DANCE",
"CHANNEL",
"MAKING",
"FISHING",
"DATE",
"DESIGN",
"LOCOMOTIVE",
"PLUSH_DOLL",
"PC",
"FLOWERS",
"HERO",
"NAP",
"HEROINE",
"FASHION",
"ADVENTURE",
"BOARD",
"BALL",
"BOOK",
"FESTIVAL",
"COMICS",
"HOLIDAY",
"PLANS",
"TRENDY",
"VACATION",
"LOOK"
"IDOL",
"ANIME",
"SONG",
"MOVIE",
"SWEETS",
"CHAT",
"CHILD_S_PLAY",
"TOYS",
"MUSIC",
"CARDS",
"SHOPPING",
"CAMERA",
"VIEWING",
"SPECTATOR",
"GOURMET",
"GAME",
"RPG",
"COLLECTION",
"COMPLETE",
"MAGAZINE",
"WALK",
"BIKE",
"HOBBY",
"SPORTS",
"SOFTWARE",
"SONGS",
"DIET",
"TREASURE",
"TRAVEL",
"DANCE",
"CHANNEL",
"MAKING",
"FISHING",
"DATE",
"DESIGN",
"LOCOMOTIVE",
"PLUSH_DOLL",
"PC",
"FLOWERS",
"HERO",
"NAP",
"HEROINE",
"FASHION",
"ADVENTURE",
"BOARD",
"BALL",
"BOOK",
"FESTIVAL",
"COMICS",
"HOLIDAY",
"PLANS",
"TRENDY",
"VACATION",
"LOOK"
]