Skip to content

Instantly share code, notes, and snippets.

@Grayfox96
Last active December 10, 2023 20:42
Show Gist options
  • Save Grayfox96/08691179e2a6a95930d130c75dc103d7 to your computer and use it in GitHub Desktop.
Save Grayfox96/08691179e2a6a95930d130c75dc103d7 to your computer and use it in GitHub Desktop.
# 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