-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathfeebasCalcs.py
More file actions
373 lines (307 loc) · 14.9 KB
/
feebasCalcs.py
File metadata and controls
373 lines (307 loc) · 14.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
#!/usr/bin/python
"""
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_BRIDGE_TILES, FEEBAS_COORDINATES_ORIGINAL, FEEBAS_COORDINATES_NEW
DEBUG_ENABLED = False
class FeebasCalculator:
"""
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, is_fixed_game):
"""
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_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 = []
# 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()
# 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):
return
# Seed the RNG with the value found for the Trendy Phrase
self.seedRng(self.feebas_seed)
# Calculate the actual Feebas spots
x = 0
add_bridge_tiles = False
while(x != 6):
feebas_id = self.getFeebasRandomValue() % 447
if(feebas_id == 0):
feebas_id = 447
if((is_fixed_game == False and feebas_id >= 4)):
self.calculated_feebas_spots.append(FEEBAS_COORDINATES_ORIGINAL[feebas_id])
x += 1
if(feebas_id == 132):
add_bridge_tiles = True
elif(is_fixed_game == TRUE):
self.calculated_feebas_spots.append(FEEBAS_COORDINATES_NEW[feebas_id])
x += 1
if(add_bridge_tiles == True):
for tile in FEEBAS_BRIDGE_TILES:
self.calculated_feebas_spots.append(tile)
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
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):
"""
This function progresses the RNG once and returns the newly generated value for the Feebas RNG
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 + 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 for the Regular RNG
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
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 Lottery
Number. It does this by progressing the RNG 20000 frames (around 5,5 minutes) forward and saves
all moments the RNG generated the given Lottery 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)
def findTrendyPhraseRubySapphire(self, lottery_seed):
"""
This function generates the dewford phrases for Ruby and Sapphire based on the lottery 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 Lottery 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 Lottery 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)
for x in range(50):
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 lottery 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 lottery 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 Lottery 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 Lottery Number
"""
reverse_steps = 50
lottery_no = 0
final_trendy_prase = ["NO", "FEEBAS"]
for steps in range(20):
self.seedRng(lottery_seed)
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 lottery 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()))
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)
# Generate the Trendiness values and the Random value
new_trend.setIsGainingTrendiness(self.getRandomValue() & 1)
rando = self.getRandomValue() % 98
if (rando > 50):
rando = self.getRandomValue() % 98
if (rando > 80):
rando = self.getRandomValue() % 98
new_trend.setMaxTrendiness(rando + 30)
new_trend.setTrendiness((self.getRandomValue() % (rando + 1)) + 30)
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):
if(self.compareTrends(y, x)):
self.SWAP(self.dewford_trends, y, x)
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()):
return False
if(self.dewford_trends[a].getMaxTrendiness() > self.dewford_trends[b].getMaxTrendiness()):
return True
if(self.dewford_trends[a].getMaxTrendiness() < self.dewford_trends[b].getMaxTrendiness()):
return False
return (self.getRandomValue() & 1)
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