Last active
December 10, 2023 20:42
-
-
Save Grayfox96/08691179e2a6a95930d130c75dc103d7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# https://github.com/Grayfox96/FFX-RNG-tracker required | |
# download the code as a zip file, unzip and | |
# run 'py -m pip install .' in the main directory to install | |
from datetime import datetime, timedelta | |
from ffx_rng_tracker.data.constants import EncounterCondition | |
from ffx_rng_tracker.data.seeds import datetime_to_seed | |
from ffx_rng_tracker.events.parsing_functions import (parse_encounter, | |
parse_encounter_checks, | |
parse_roll) | |
from ffx_rng_tracker.gamestate import GameState | |
from ffx_rng_tracker.tracker import FFXRNGTracker | |
def get_frame(label: str) -> int: | |
while True: | |
try: | |
frame = int(input(f'Type the {label} frame: ')) | |
except ValueError: | |
print('Frame must be an integer.') | |
continue | |
if frame < 0: | |
print('Frame must be a positive integer.') | |
continue | |
return frame | |
def predict_n_of_encounters(gs: GameState, | |
zone: str, | |
steps: int, | |
continue_zone: str, | |
) -> int: | |
encounter_checks = parse_encounter_checks( | |
gs, zone, str(steps), continue_zone) | |
return sum([ec.encounter for ec in encounter_checks.checks]) | |
def predict_n_of_ambushes(gs: GameState, | |
zone: str, | |
steps: int, | |
continue_zone: str = 'false', | |
) -> tuple[int, int]: | |
"""returns # of encounters and # of ambushes""" | |
n_of_ambushes = 0 | |
n_of_encounters = predict_n_of_encounters(gs, zone, steps, continue_zone) | |
for _ in range(n_of_encounters): | |
if parse_encounter(gs, zone).condition is EncounterCondition.AMBUSH: | |
n_of_ambushes += 1 | |
return n_of_encounters, n_of_ambushes | |
def predict_encounters_ambushes(gs: GameState) -> list[tuple[int, int]]: | |
"""Predicts number of encounters and ambushes between start | |
of the game and Thunder Plains | |
""" | |
data = [] | |
# sinscales ammes tanker sahagins geosgaeno klikk_1 klikk_2 | |
parse_roll(gs, rng_index='1', times='7') | |
data.append(predict_n_of_ambushes(gs, 'underwater_ruins', 64)) | |
# piranhas tros | |
parse_roll(gs, rng_index='1', times='2') | |
data.append(predict_n_of_ambushes(gs, 'besaid_lagoon', 143)) | |
# wakka_tutorial elements_tutorial kimahri garuda_1 garuda_2 | |
# besaid_forced_encounter | |
parse_roll(gs, rng_index='1', times='6') | |
data.append(predict_n_of_ambushes(gs, 'besaid_road', 39)) | |
# sin_fin echuilles lancet_tutorial | |
parse_roll(gs, rng_index='1', times='3') | |
data.append(predict_n_of_ambushes(gs, 'kilika_woods', 140)) | |
# geneaux | |
parse_roll(gs, rng_index='1', times='1') | |
data.append(predict_n_of_ambushes(gs, 'kilika_woods', 213)) | |
# machina_1 machina_2 machina_3 oblitzerator sahagin_chiefs vouivre garuda | |
# pierce_tutorial | |
parse_roll(gs, rng_index='1', times='8') | |
data.append(predict_n_of_ambushes(gs, 'miihen_screen_1', 131)) | |
data.append(predict_n_of_ambushes(gs, 'miihen_screen_2_3', 97)) | |
data.append(predict_n_of_ambushes(gs, 'miihen_screen_2_3', 252)) | |
# chocobo_eater | |
parse_roll(gs, rng_index='1', times='1') | |
data.append(predict_n_of_ambushes(gs, 'old_road', 122)) | |
data.append(predict_n_of_ambushes(gs, 'old_road', 73)) | |
# no encounters in clasko_skip_screen on ps2 na | |
data.append(predict_n_of_ambushes(gs, 'djose_highroad_front_half', 78)) | |
data.append(predict_n_of_ambushes(gs, 'djose_highroad_back_half', 108, 'true')) | |
data.append(predict_n_of_ambushes(gs, 'djose_highroad_back_half', 26)) | |
data.append(predict_n_of_ambushes(gs, 'moonflow_south', 312)) | |
# extractor rikku_tutorial | |
parse_roll(gs, rng_index='1', times='2') | |
data.append(predict_n_of_ambushes(gs, 'moonflow_north', 86)) | |
data.append(predict_n_of_ambushes(gs, 'thunder_plains_south', 221)) | |
data.append(predict_n_of_ambushes(gs, 'thunder_plains_north', 217)) | |
data.append(predict_n_of_ambushes(gs, 'macalania_woods', 59)) | |
data.append(predict_n_of_ambushes(gs, 'macalania_woods', 150)) | |
data.append(predict_n_of_ambushes(gs, 'macalania_woods', 122)) | |
# spherimorph | |
parse_roll(gs, rng_index='1', times='1') | |
data.append(predict_n_of_ambushes(gs, 'lake_macalania', 46)) | |
# crawler, seymour, guado encounters | |
parse_roll(gs, rng_index='1', times='4') | |
data.append(predict_n_of_ambushes(gs, 'crevasse', 121)) | |
# wendigo, zu | |
parse_roll(gs, rng_index='1', times='2') | |
data.append(predict_n_of_ambushes(gs, 'sanubia_desert_east', 140)) | |
# machina steal tutorial | |
parse_roll(gs, rng_index='1', times='1') | |
data.append(predict_n_of_ambushes(gs, 'sanubia_desert_east', 91)) | |
data.append(predict_n_of_ambushes(gs, 'sanubia_desert_central', 158)) | |
data.append(predict_n_of_ambushes(gs, 'sanubia_desert_ruins', 52, 'true')) | |
data.append(predict_n_of_ambushes(gs, 'sanubia_desert_west', 66)) | |
# tp sandragora | |
parse_roll(gs, rng_index='1', times='1') | |
data.append(predict_n_of_ambushes(gs, 'sanubia_desert_west', 46)) | |
# bombs, dual horns | |
parse_roll(gs, rng_index='1', times='2') | |
data.append(predict_n_of_ambushes(gs, 'home', 16)) | |
# chimeras | |
parse_roll(gs, rng_index='1', times='1') | |
data.append(predict_n_of_ambushes(gs, 'airship', 23)) | |
# evrae, 5 bevelle guards fights | |
parse_roll(gs, rng_index='1', times='6') | |
data.append(predict_n_of_ambushes(gs, 'via_purifico_maze', 57)) | |
data.append(predict_n_of_ambushes(gs, 'via_purifico_corridor', 44, 'true')) | |
# isaaru fights | |
parse_roll(gs, rng_index='1', times='3') | |
data.append(predict_n_of_ambushes(gs, 'via_purifico_underwater', 64)) | |
# altana | |
parse_roll(gs, rng_index='1', times='1') | |
data.append(predict_n_of_ambushes(gs, 'highbridge', 103)) | |
# natus | |
parse_roll(gs, rng_index='1', times='1') | |
data.append(predict_n_of_ambushes(gs, 'calm_lands_south_before_defender_x', 186)) | |
data.append(predict_n_of_ambushes(gs, 'calm_lands_central_north_east', 293)) | |
return data | |
def predict_ghosts(gs: GameState) -> tuple[int, int]: | |
"""returns the random index of the next ghost | |
in the white and green zones | |
""" | |
ghost_in_white = 0 | |
ghost_in_green = 0 | |
while not ghost_in_white or not ghost_in_green: | |
encounters = parse_encounter(gs, 'multizone', 'cave_white_zone', 'cave_green_zone') | |
white, green = encounters.encounters | |
if not ghost_in_white and 'Ghost' in str(white): | |
ghost_in_white = white.random_index | |
if not ghost_in_green and 'Ghost' in str(green): | |
ghost_in_green = green.random_index | |
return ghost_in_white, ghost_in_green | |
def main(target_datetime_string: str = None, | |
starting_frame: int = None, | |
ending_frame: int = None, | |
) -> None: | |
while True: | |
if target_datetime_string is None: | |
target_datetime_string = input( | |
'Type the target time and date (DD/MM/YYYY hh:mm:ss): ') | |
try: | |
dt = datetime.strptime( | |
target_datetime_string, r'%d/%m/%Y %H:%M:%S') | |
except ValueError: | |
print('The correct format is "DD/MM/YYYY hh:mm:ss".') | |
target_datetime_string = None | |
continue | |
break | |
if starting_frame is None: | |
starting_frame = get_frame('starting') | |
if ending_frame is None: | |
ending_frame = get_frame('ending') | |
print(f'Date: {dt} | Frame window {starting_frame}-{ending_frame}') | |
# there is a ~0.9s delay between pressing new game and the | |
# seed creation function being called | |
dt = dt + ONE_SECOND | |
# treat every component as base 16 numbers | |
# November > 11 > 0x11 > 17 | |
# only the least significant byte is used for calculations but year | |
# is the only component that can be greater than 255 | |
xored_datetime = (int(f'0x0{dt.day}'[-2:], 16) | |
^ int(f'0x0{dt.month}'[-2:], 16) | |
^ int(f'0x0{dt.year % 256}'[-2:], 16) | |
^ int(f'0x0{dt.hour}'[-2:], 16) | |
^ int(f'0x0{dt.minute}'[-2:], 16) | |
^ int(f'0x0{dt.second}'[-2:], 16) | |
) | |
dt = dt - ONE_SECOND | |
dt_string = (str(dt) | |
.replace(' ', '_') | |
.replace('/', '_') | |
.replace(':', '_') | |
.replace('-', '_') | |
) | |
file_name = (f'ffx_ps2_time_to_seed_output_{dt_string}_' | |
f'{starting_frame}_{ending_frame}.txt') | |
data = [ | |
f'Datetime: {dt}', | |
' frame | seed|encs|ambush|ghost w/g|chain|lagoo|besai|kilin|kiout|miih1|miih2|miih3|oldr1|oldr2|djofr|djoba|djote|moons|moonn|tpsou|tpnor|macw1|macw2|macw3|lakem|creva|bprem|postm|centr|ruins|bpres|posts| home|airsh|vpmaz|vpcor|vpwat|highb|clsou|clnor|', | |
] | |
for frame in range(starting_frame, ending_frame + 1): | |
seed = datetime_to_seed(xored_datetime, frame) | |
GAMESTATE.seed = seed | |
GAMESTATE.reset() | |
encs_ambush = predict_encounters_ambushes(GAMESTATE) | |
white, green = predict_ghosts(GAMESTATE) | |
encs = sum(e for e, _ in encs_ambush) | |
ambush = sum(a for _, a in encs_ambush) | |
encs_ambush_str = '|'.join([f'{e:2}-{a:2}' for e, a in encs_ambush]) | |
data.append(f'{frame:>10} | {seed:>10}|{encs:4}|{ambush:6}|{white:3} / {green:3}|{encs_ambush_str}|') | |
data = '\n'.join(data) | |
with open(file_name, 'w') as file: | |
file.write(data) | |
print(f'Output saved as "{file_name}"!') | |
ONE_SECOND = timedelta(seconds=1) | |
GAMESTATE = GameState(FFXRNGTracker(0)) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment