FacetedFox

4-Esper Challenge generator

Dec 24th, 2018 (edited)
301
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.49 KB | None | 0 0
  1. #!/usr/bin/env python3
  2. #
  3. # Copyright (C) 2019-2022 Síle Ekaterin Liszka
  4. #
  5. # Permission to use, copy, modify, and/or distribute this software for
  6. # any purpose with or without fee is hereby granted, provided that the
  7. # above copyright notice and this permission notice appear in all
  8. # copies.
  9. #
  10. # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
  11. # WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
  12. # WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
  13. # AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
  14. # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
  15. # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  16. # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
  17. # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  18. #
  19. # This project was made possible by:
  20. # * Lenophis, who told me where the 'give this esper' bytes are
  21. # * ff6hacking.com, which hosts the data tables I used to pick from.
  22. # * CanOfWorms, for figuring out the GBA offsets.
  23. #
  24. # ChangeLog:
  25. # 0.3:
  26. # - GBA support! Purportedly works for J, U, *and* E versions.
  27. # 0.2:
  28. # - Preliminary work toward supporting GBA ROMs. This needs data I
  29. #   don't have to complete, however, but the cleanup it required was
  30. #   useful.
  31. # - Reworked learning rates. Rates should be overall lower now.
  32. # - Majority of output moved to a spoilers file.
  33.  
  34. import sys
  35. import io
  36.  
  37. from random import randint, seed, shuffle, random, choice, sample
  38. from enum import IntEnum
  39. from copy import copy
  40.  
  41. class Bonus(IntEnum):
  42.     HP_10 = 0x00
  43.     HP_30 = 0x01
  44.     HP_50 = 0x02
  45.     MP_10 = 0x03
  46.     MP_30 = 0x04
  47.     MP_50 = 0x05
  48.     HP100 = 0x06
  49.     LV_30 = 0x07
  50.     LV_50 = 0x08
  51.     Str_1 = 0x09
  52.     Str_2 = 0x0A
  53.     Spd_1 = 0x0B
  54.     Spd_2 = 0x0C
  55.     Vit_1 = 0x0D
  56.     Vit_2 = 0x0E
  57.     Mag_1 = 0x0F
  58.     Mag_2 = 0x10
  59.     Nil   = 0xFF
  60.  
  61. bonuses = {
  62.     Bonus.HP_10: 'HP +10%',
  63.     Bonus.HP_30: 'HP +30%',
  64.     Bonus.HP_50: 'HP +50%',
  65.     Bonus.MP_10: 'MP +10%',
  66.     Bonus.MP_30: 'MP +30%',
  67.     Bonus.MP_50: 'MP +50%',
  68.     Bonus.HP100: 'HP +100%',
  69.     Bonus.Str_1: 'Strength +1',
  70.     Bonus.Str_2: 'Strength +2',
  71.     Bonus.Spd_1: 'Speed +1',
  72.     Bonus.Spd_2: 'Speed +2',
  73.     Bonus.Mag_1: 'Magic +1',
  74.     Bonus.Mag_2: 'Magic +2',
  75. }
  76.  
  77. class Spell(IntEnum):
  78.     Fire       = 0x00
  79.     Blizzard   = 0x01
  80.     Thunder    = 0x02
  81.     Poison     = 0x03
  82.     Drain      = 0x04
  83.     Firera     = 0x05
  84.     Blizzardra = 0x06
  85.     Thunderra  = 0x07
  86.     Bio        = 0x08
  87.     Firega     = 0x09
  88.     Blizzardga = 0x0A
  89.     Thunderga  = 0x0B
  90.     Break      = 0x0C
  91.     Death      = 0x0D
  92.     Holy       = 0x0E
  93.     Flare      = 0x0F
  94.     Gravity    = 0x10
  95.     Gravityga  = 0x11
  96.     Dezone     = 0x12
  97.     Meteor     = 0x13
  98.     Ultima     = 0x14
  99.     Quake      = 0x15
  100.     Tornado    = 0x16
  101.     Meltdown   = 0x17
  102.     Library    = 0x18
  103.     Slow       = 0x19
  104.     Rasp       = 0x1A
  105.     Silence    = 0x1B
  106.     Protect    = 0x1C
  107.     Sleep      = 0x1D
  108.     Confuse    = 0x1E
  109.     Haste      = 0x1F
  110.     Stop       = 0x20
  111.     Berserk    = 0x21
  112.     Float      = 0x22
  113.     Kappa      = 0x23
  114.     Reflect    = 0x24
  115.     Shell      = 0x25
  116.     Vanish     = 0x26
  117.     Hastega    = 0x27
  118.     Slowga     = 0x28
  119.     Osmose     = 0x29
  120.     Warp       = 0x2A
  121.     Quick      = 0x2B
  122.     Dispel     = 0x2C
  123.     Cure       = 0x2D
  124.     Curera     = 0x2E
  125.     Curega     = 0x2F
  126.     Raise      = 0x30
  127.     Arise      = 0x31
  128.     NullPoison = 0x32
  129.     NullStatus = 0x33
  130.     Regen      = 0x34
  131.     Reraise    = 0x35
  132.  
  133.     Flood   = 54
  134.     Gravija = 55
  135.     Valor   = 56
  136.  
  137.     SNES_Count = 54
  138.     GBA_Count  = 57
  139.  
  140. class Esper(IntEnum):
  141.     Ramuh         =  0
  142.     Ifrit         =  1
  143.     Shiva         =  2
  144.     Siren         =  3
  145.     Jormungand    =  4
  146.     Catoblepas    =  5
  147.     Maduin        =  6
  148.     Bismarck      =  7
  149.     CatSidhe      =  8
  150.     Quetzali      =  9
  151.     Valigarmander = 10
  152.     Odin          = 11
  153.     Raiden        = 12
  154.     Bahamut       = 13
  155.     Alexander     = 14
  156.     Jihad         = 15
  157.     Ragnarok      = 16
  158.     QiLin         = 17
  159.     ZonaSeeker    = 18
  160.     Carbunkle     = 19
  161.     Phantom       = 20
  162.     Seraphim      = 21
  163.     Golem         = 22
  164.     Unicorn       = 23
  165.     Fenrir        = 24
  166.     Lakshmi       = 25
  167.     Phoenix       = 26
  168.  
  169.     Leviathan = 27
  170.     Cactender = 28
  171.     Diabolus  = 29
  172.     Gilgamesh = 30
  173.  
  174.     SNES_Count = 27
  175.     GBA_Count = 31
  176.  
  177. def roll_espers(max):
  178.     return [Esper(i) for i in sample(list(range(max)), 4)]
  179.  
  180. spell_rate = [1, 1, 1, 2, 2, 3, 4, 3, 2, 2, 1, 1, 1]
  181.  
  182. def roll_spells(count, max, forbidden=[]):
  183.     spells = []
  184.     spell_base = []
  185.  
  186.     def add_spell(spell):
  187.         spell_base.append(spell)
  188.         spells.append([spell, choice(spell_rate) * 5])
  189.  
  190.     if randint(1, 100) < 50: add_spell(Spell.Curera)
  191.     else:                    add_spell(Spell.Curega)
  192.  
  193.     num = randint(1, 100)
  194.     if num < 34:
  195.         add_spell(Spell.Fire)
  196.         num2 = randint(1, 100)
  197.         if num2 < 50: add_spell(Spell.Blizzardra)
  198.         else:         add_spell(Spell.Thunderra)
  199.     elif num < 67:
  200.         add_spell(Spell.Blizzard)
  201.         num2 = randint(1, 100)
  202.         if num2 < 50: add_spell(Spell.Firera)
  203.         else:         add_spell(Spell.Thunderra)
  204.     else:
  205.         add_spell(Spell.Thunder)
  206.         num2 = randint(1, 100)
  207.         if num2 < 50: add_spell(Spell.Firera)
  208.         else:         add_spell(Spell.Blizzardra)
  209.  
  210.     add_spell(Spell.Protect)
  211.     add_spell(Spell.Shell)
  212.     add_spell(Spell.Haste)
  213.  
  214.     num = randint(1, 100)
  215.     if num < 34:   add_spell(Spell.Raise)
  216.     elif num < 67: add_spell(Spell.Arise)
  217.  
  218.     n = count - len(spells)
  219.     while True:
  220.         if n < 0:
  221.             break
  222.  
  223.         spell = Spell(randint(0, max))
  224.  
  225.         if spell not in spell_base and spell not in forbidden:
  226.             add_spell(spell)
  227.             n -= 1
  228.  
  229.     return spells
  230.  
  231. def generate(rng_seed=None, mode='snes'):
  232.     seed(rng_seed)
  233.  
  234.     if mode == 'snes':
  235.         esper_max = Esper.SNES_Count - 1
  236.         spell_max = Spell.SNES_Count - 1
  237.     elif mode == 'gba':
  238.         esper_max = Esper.GBA_Count - 1
  239.         spell_max = Spell.GBA_Count - 1
  240.  
  241.     espers = roll_espers(esper_max)
  242.     statup = [Bonus(i) for i in sample(list(bonuses.keys()), 4)]
  243.  
  244.     spells = roll_spells(20, spell_max)
  245.     shuffle(spells)
  246.  
  247.     return {
  248.         'espers': espers,
  249.         'level+': statup,
  250.         'spells': spells,
  251.         'seed':   rng_seed
  252.     }
  253.  
  254. remove = [
  255.     0xA55DE, 0xB4DE3, 0xB52C9, 0xB5452,
  256.     0xA5A2C, 0xB5B77, 0xB9A7C, 0xC1ED1,
  257.     0xC1F85, 0xC2044, 0xC316C, 0xC37A8,
  258.     0xC4ADA, 0xC5E08, 0xC5E08, 0xC79D9,
  259.     0xC79E9, 0xCD78F
  260. ]
  261. def write_rom(inname, outname, mode, data):
  262.     print('Writing data for seed {} to {}...'.format(data['seed'], realname))
  263.  
  264.     offset = 0
  265.     if mode == 'gba':
  266.         offset = 0x70000
  267.         spello = 0x62A480
  268.         remove.extend((0xD2B52, 0xD2BD4, 0xD11ED, 0xD27A5))
  269.     else:
  270.         spello = 0x186e00
  271.  
  272.     rom = io.BytesIO()
  273.  
  274.     with open(inname, mode='rb') as f:
  275.         rom.write(f.read())
  276.  
  277.     # erase all the slots we're removing
  278.     # Bahamut
  279.     rom.seek(0xA00DE + offset, 0)
  280.     rom.write(b'\xFD' * 5)
  281.     # sorcerous research facility
  282.     rom.seek(0xC7C6D + offset, 0)
  283.     rom.write(b'\xFD' * 11)
  284.     # everybody else
  285.     for i in remove:
  286.         rom.seek(i + offset, 0)
  287.         rom.write(b'\xFD' * 2)
  288.  
  289.     # these four are the espers given when you reach Tina in Zozo
  290.     rom.seek(0xAA824 + offset, 0)
  291.     rom.write(bytes([data['espers'][0] + 0x36]))
  292.     rom.seek(0xAAC9C + offset, 0)
  293.     rom.write(bytes([data['espers'][1] + 0x36]))
  294.     rom.seek(0xAACAB + offset, 0)
  295.     rom.write(bytes([data['espers'][2] + 0x36]))
  296.     rom.seek(0xAACBA + offset, 0)
  297.     rom.write(bytes([data['espers'][3] + 0x36]))
  298.  
  299.     # write esper data for the espers we're giving out
  300.     for i in range(4):
  301.         rom.seek(spello + (data['espers'][i] * 11), 0)
  302.         for j in range(5):
  303.             spell = data['spells'].pop(0)
  304.             rom.write(bytes([spell[1], spell[0]]))
  305.         rom.write(bytes([data['level+'][i]]))
  306.  
  307.     rom.seek(0, 0)
  308.  
  309.     with open(outname, mode='wb') as f:
  310.         f.write(rom.read())
  311.  
  312. if __name__ == '__main__':
  313.     from sys import argv
  314.     from time import time
  315.  
  316.     now = str(time()).split('.')[0]
  317.  
  318.     if len(argv) == 3:
  319.         rng_seed = argv[2]
  320.     elif len(argv) < 2 or len(argv) > 3:
  321.         print('Usage: {} <filename> [seed]'.format(argv[0]))
  322.     else:
  323.         rng_seed = now
  324.  
  325.     filename = argv[1]
  326.  
  327.     if filename.endswith('.gba'):
  328.         mode = 'gba'
  329.     else:
  330.         mode = 'snes'
  331.     print('Mode: {}'.format(mode))
  332.  
  333.     print('Seed: {}'.format(rng_seed))
  334.     data = generate(rng_seed, mode)
  335.  
  336.     filefrags = filename.split('.')
  337.     ext = filefrags[-1]
  338.     filefrags[-1] = str(data['seed'])
  339.     filefrags.append(ext)
  340.     realname = '.'.join(filefrags)
  341.     filefrags[-1] = 'txt'
  342.     spoilers = '.'.join(filefrags)
  343.  
  344.     espers = []
  345.     for i in range(4):
  346.         esper = str(data['espers'][i]).split('.')[1]
  347.         bonus = bonuses[data['level+'][i]]
  348.         espers.append(esper + '(' + bonus + ')')
  349.    
  350.     spells = data['spells']
  351.  
  352.     with open(spoilers, mode='wt', encoding='utf8') as f:
  353.         f.write('Seed: {}\n'.format(rng_seed))
  354.         for i in range(4):
  355.             f.write('{}\n'.format(espers[i]))
  356.  
  357.             for j in range(5):
  358.                 spell = data['spells'][(i * 5) + j]
  359.                 f.write('  {} ×{}\n'.format(str(spell[0]).split('.')[1], spell[1]))
  360.  
  361.     write_rom(filename, realname, mode, data)
Add Comment
Please, Sign In to add comment