Compare commits
12 commits
v0.11.6+ch
...
master
Author | SHA1 | Date | |
---|---|---|---|
2a73e487a5 | |||
1523860c9a | |||
a36c9a95f0 | |||
fe6fa75972 | |||
672cf3ca23 | |||
01dee434bd | |||
367fdd75ec | |||
0c17440bde | |||
dc597e2924 | |||
49daaf1da8 | |||
0c9d1a472f | |||
b2bcbb3027 |
9 changed files with 1371 additions and 453 deletions
673
Fusion_Palette_Shuffle.py
Normal file
673
Fusion_Palette_Shuffle.py
Normal file
|
@ -0,0 +1,673 @@
|
|||
# Source Generated with Decompyle++
|
||||
# File: Fusion_Palette_Shuffle.pyc (Python 3.8)
|
||||
|
||||
import struct
|
||||
import random
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from MFOR import file_hash
|
||||
|
||||
tileset_fixes = {
|
||||
4640526: 575,
|
||||
4640528: 735,
|
||||
4640530: 863,
|
||||
4640558: 575,
|
||||
4640560: 735,
|
||||
4640562: 863,
|
||||
4640592: 575,
|
||||
4640594: 735,
|
||||
4640626: 575
|
||||
}
|
||||
|
||||
static_tilesets = [
|
||||
[4219100, 448],
|
||||
[4223388, 448],
|
||||
[4224028, 448],
|
||||
[4225244, 448],
|
||||
[4226268, 448],
|
||||
[4226716, 448],
|
||||
[4632980, 448],
|
||||
[4640084, 448],
|
||||
[4640788, 448],
|
||||
[4641236, 448],
|
||||
[4641684, 448],
|
||||
[4642132, 448],
|
||||
[4643124, 448],
|
||||
[4643572, 448],
|
||||
[4644020, 448],
|
||||
[4644468, 448],
|
||||
[4645172, 192],
|
||||
[4645620, 448],
|
||||
[4646068, 448],
|
||||
[4646644, 448],
|
||||
[4648820, 448],
|
||||
[4649268, 448],
|
||||
[4818056, 448],
|
||||
[4818504, 448],
|
||||
[4818952, 448],
|
||||
[4819656, 448],
|
||||
[4820104, 128],
|
||||
[5031360, 448],
|
||||
[5031808, 448],
|
||||
[5032256, 448],
|
||||
[5032704, 448],
|
||||
[5033152, 448],
|
||||
[5033600, 448],
|
||||
[5034048, 448],
|
||||
[5186040, 448],
|
||||
[5186488, 448],
|
||||
[5186936, 448],
|
||||
[5187512, 448],
|
||||
[5188184, 448],
|
||||
[5310568, 448],
|
||||
[5311016, 448],
|
||||
[5311464, 352],
|
||||
[5460880, 288],
|
||||
[5461456, 448],
|
||||
[5461904, 448],
|
||||
[5462800, 448],
|
||||
[5558492, 448],
|
||||
[5559164, 448],
|
||||
[5559612, 448]
|
||||
]
|
||||
|
||||
tileset_single_pass = {
|
||||
4641812: 32,
|
||||
4647188: 32,
|
||||
4819000: 8,
|
||||
4819016: 8,
|
||||
4819048: 8,
|
||||
4819704: 8,
|
||||
4819720: 8,
|
||||
4819752: 8,
|
||||
5186568: 5,
|
||||
5186584: 7,
|
||||
5186604: 6,
|
||||
5186640: 4,
|
||||
5186704: 4,
|
||||
5186732: 6,
|
||||
5186860: 6,
|
||||
5311144: 32,
|
||||
5460752: 32,
|
||||
5460784: 32,
|
||||
5460816: 32,
|
||||
5460848: 32,
|
||||
5462576: 32,
|
||||
5462864: 32,
|
||||
5462960: 16,
|
||||
5463088: 16
|
||||
}
|
||||
|
||||
animated_tilesets = [
|
||||
[4223836, 192],
|
||||
[4224476, 192],
|
||||
[4633428, 320],
|
||||
[4636308, 256],
|
||||
[4640532, 256],
|
||||
[4645364, 64],
|
||||
[5187960, 224],
|
||||
[5188632, 256],
|
||||
[4819400, 256],
|
||||
[4820232, 64],
|
||||
[5558940, 224],
|
||||
[5461168, 288],
|
||||
[4644916, 256],
|
||||
[5187384, 128],
|
||||
[5185272, 160],
|
||||
[5185880, 160],
|
||||
[4646516, 128],
|
||||
[4635092, 320],
|
||||
[4642580, 256],
|
||||
[4642836, 288],
|
||||
[5311816, 64]
|
||||
]
|
||||
|
||||
paired_tilesets = [
|
||||
[
|
||||
[3930000, 192], [4633748, 448]
|
||||
],
|
||||
[
|
||||
[3280920, 96], [3561816, 32], [4218524, 448], [4218972, 128]
|
||||
],
|
||||
[
|
||||
[3109872, 96], [4222236, 448], [4222684, 128]
|
||||
],
|
||||
[
|
||||
[3297776, 96], [4222812, 448], [4223260, 128]
|
||||
],
|
||||
[
|
||||
[3267440, 96], [4224668, 448], [4225116, 128]
|
||||
],
|
||||
[
|
||||
[3084920, 96], [4225692, 448], [4226140, 128]
|
||||
],
|
||||
[
|
||||
[3456412, 64], [4632340, 448], [4632788, 192], [4647540, 448], [4647988, 128], [4648116, 448], [4648564, 256]
|
||||
],
|
||||
[
|
||||
[4647092, 448], [3451140, 32]
|
||||
],
|
||||
[
|
||||
[4634644, 448], [4635412, 448]
|
||||
],
|
||||
[
|
||||
[4219548, 448], [4635860, 448], [4636308, 256]
|
||||
],
|
||||
[
|
||||
[4219996, 448], [4636564, 448], [4637012, 256]
|
||||
],
|
||||
[
|
||||
[4220444, 448], [4637268, 448], [4637716, 256]
|
||||
],
|
||||
[
|
||||
[4220892, 448], [4637972, 448], [4638420, 256]
|
||||
],
|
||||
[
|
||||
[4221340, 448], [4638676, 448], [4639124, 256]
|
||||
],
|
||||
[
|
||||
[4221788, 448], [4639380, 448], [4639828, 256]
|
||||
],
|
||||
[
|
||||
[5184824, 448], [5185432, 448]
|
||||
],
|
||||
[
|
||||
[3447972, 64], [5462352, 448]
|
||||
]
|
||||
]
|
||||
sprite_fixes = {
|
||||
3468542: 8266,
|
||||
3468544: 7178,
|
||||
3499380: 142,
|
||||
3499406: 523,
|
||||
3558744: 3264,
|
||||
3558758: 14
|
||||
}
|
||||
|
||||
sprites = [
|
||||
[2835304, 128],
|
||||
[3042180, 32],
|
||||
[3058284, 96],
|
||||
[3066148, 64],
|
||||
[3070696, 32],
|
||||
[3076896, 96],
|
||||
[3103328, 64],
|
||||
[3113676, 32],
|
||||
[3117204, 32],
|
||||
[3124184, 96],
|
||||
[3130896, 64],
|
||||
[3150104, 256],
|
||||
[3169028, 160],
|
||||
[3181768, 160],
|
||||
[3192168, 160],
|
||||
[3202568, 160],
|
||||
[3212968, 160],
|
||||
[3223368, 160],
|
||||
[3231720, 128],
|
||||
[3241064, 64],
|
||||
[3245764, 64],
|
||||
[3252504, 64],
|
||||
[3254656, 32],
|
||||
[3257752, 32],
|
||||
[3260912, 32],
|
||||
[3273680, 64],
|
||||
[3288044, 64],
|
||||
[3305456, 96],
|
||||
[3310352, 32],
|
||||
[3321092, 160],
|
||||
[3332584, 160],
|
||||
[3342984, 160],
|
||||
[3353384, 160],
|
||||
[3368040, 64],
|
||||
[3373196, 64],
|
||||
[3377084, 32],
|
||||
[3384092, 64],
|
||||
[3390996, 32],
|
||||
[3391028, 32],
|
||||
[3398460, 96],
|
||||
[3403768, 32],
|
||||
[3406232, 32],
|
||||
[3411720, 64],
|
||||
[3436688, 256],
|
||||
[3443020, 32],
|
||||
[3462012, 64],
|
||||
[3468532, 64],
|
||||
[3473740, 64],
|
||||
[3479228, 64],
|
||||
[3485468, 96],
|
||||
[3493060, 96],
|
||||
[3499376, 64],
|
||||
[3503052, 32],
|
||||
[3505876, 32],
|
||||
[3508068, 32],
|
||||
[3512272, 32],
|
||||
[3519012, 96],
|
||||
[3529712, 96],
|
||||
[3534488, 64],
|
||||
[3537920, 32],
|
||||
[3540236, 32],
|
||||
[3544716, 64],
|
||||
[3547808, 32],
|
||||
[3552044, 64],
|
||||
[3554544, 32],
|
||||
[3558732, 64],
|
||||
[3578836, 256],
|
||||
[3581056, 32],
|
||||
[3581088, 32],
|
||||
[3581120, 96],
|
||||
[3611928, 256],
|
||||
[3641296, 32],
|
||||
[3643992, 32],
|
||||
[3646200, 32],
|
||||
[3652548, 64],
|
||||
[3657176, 32],
|
||||
[3659632, 32],
|
||||
[3662052, 32],
|
||||
[3664548, 32],
|
||||
[3667884, 32],
|
||||
[3672824, 64],
|
||||
[3684520, 128],
|
||||
[3690908, 32],
|
||||
[3693436, 32],
|
||||
[3695788, 32],
|
||||
[3700504, 32],
|
||||
[3755348, 224],
|
||||
[3772744, 160],
|
||||
[3798160, 32],
|
||||
[3822584, 256],
|
||||
[3829592, 64],
|
||||
[3837700, 32],
|
||||
[3887568, 256],
|
||||
[3917260, 256]
|
||||
]
|
||||
|
||||
paired_sprites = [
|
||||
[
|
||||
[5865648, 64], [5865776, 32], [6368840, 32], [6369096, 64], [6369224, 32]
|
||||
],
|
||||
[
|
||||
[3098012, 192], [3794264, 224], [6515960, 96], [6516536, 32]
|
||||
],
|
||||
[
|
||||
[3634936, 256], [3639120, 128]
|
||||
],
|
||||
[
|
||||
[3803008, 64], [3858444, 256], [3868728, 64]
|
||||
],
|
||||
[
|
||||
[2835432, 32], [3046752, 64], [3051992, 64], [3723188, 256]
|
||||
],
|
||||
[
|
||||
[3363784, 160], [3835340, 32]
|
||||
],
|
||||
[
|
||||
[4634196, 448], [6516792, 512]
|
||||
]
|
||||
]
|
||||
|
||||
beam_fixes = {
|
||||
5813384: 8031,
|
||||
5813388: 8472,
|
||||
5813480: 24480
|
||||
}
|
||||
|
||||
beams = [
|
||||
[5813348, 32],
|
||||
[5813380, 32],
|
||||
[5813412, 32],
|
||||
[5813444, 32],
|
||||
[5813476, 32]
|
||||
]
|
||||
|
||||
paired_beams = [
|
||||
[
|
||||
[5813508, 32], [4079900, 10], [4079996, 32]
|
||||
]
|
||||
]
|
||||
|
||||
suit_fixes = {
|
||||
2678144: 32585,
|
||||
2678150: 16643,
|
||||
2678154: 799,
|
||||
2678164: 501,
|
||||
2678180: 6730,
|
||||
2678182: 328,
|
||||
2678186: 23070,
|
||||
2678202: 10406,
|
||||
2678208: 28311,
|
||||
2678228: 648,
|
||||
2678234: 10406,
|
||||
2678246: 201
|
||||
}
|
||||
|
||||
suit_misc = [
|
||||
[2678268, 32],
|
||||
[2678332, 32],
|
||||
[2678364, 32],
|
||||
[2678396, 32],
|
||||
[2678428, 32],
|
||||
[2678460, 32],
|
||||
[2679036, 32],
|
||||
[2679068, 32],
|
||||
[2679868, 32],
|
||||
[2679900, 32],
|
||||
[2679932, 32],
|
||||
[2679964, 32],
|
||||
[2679996, 32],
|
||||
[2680028, 32],
|
||||
[2680060, 32],
|
||||
[2680092, 32],
|
||||
[2680124, 32],
|
||||
[2680156, 32]
|
||||
]
|
||||
|
||||
suits = [
|
||||
[
|
||||
[2678140, 14], [2678300, 14], [2678492, 14], [2679100, 14], [2679132, 14], [2679164, 14], [2679196, 14], [2679228, 14], [2679260, 14], [2679292, 14], [2679324, 14], [2679356, 14], [2679388, 14], [2679420, 14], [2679452, 14], [2679484, 14], [2679516, 14], [2679548, 14], [2679580, 14], [2679612, 14], [2679644, 14], [2679676, 14], [2679708, 14], [2679740, 14], [2679772, 14], [2679804, 14], [2679836, 14], [2680188, 14], [2680220, 14], [2680252, 14], [2680284, 14], [2680316, 14], [2681596, 14], [2681628, 14], [2681660, 14], [2681692, 14], [2682108, 14], [2682140, 14], [2682172, 14], [2682204, 14], [2682620, 14], [2682652, 14], [2682684, 14], [2682716, 14], [2683132, 14], [2683164, 14], [2683196, 14], [2683228, 14], [2683644, 14], [2683676, 14], [2683708, 14], [2683740, 14], [2684156, 14], [2684188, 14], [2684220, 14], [2684252, 14], [2684668, 14], [2684700, 14], [2684732, 14], [2684764, 14]
|
||||
],
|
||||
[
|
||||
[2678172, 14], [2680348, 14], [2680380, 14], [2680412, 14], [2680444, 14], [2680476, 14], [2681724, 14], [2681756, 14], [2681788, 14], [2681820, 14], [2682236, 14], [2682268, 14], [2682300, 14], [2682332, 14], [2682748, 14], [2682780, 14], [2682812, 14], [2682844, 14], [2683260, 14], [2683292, 14], [2683324, 14], [2683356, 14], [2683772, 14], [2683804, 14], [2683836, 14], [2683868, 14], [2684284, 14], [2684316, 14], [2684348, 14], [2684380, 14], [2684796, 14], [2684828, 14], [2684860, 14], [2684892, 14]
|
||||
],
|
||||
[
|
||||
[2678204, 14], [2680508, 14], [2680540, 14], [2680572, 14], [2680604, 14], [2680636, 14], [2681852, 14], [2681884, 14], [2681916, 14], [2681948, 14], [2682364, 14], [2682396, 14], [2682428, 14], [2682460, 14], [2682876, 14], [2682908, 14], [2682940, 14], [2682972, 14], [2683388, 14], [2683420, 14], [2683452, 14], [2683484, 14], [2683900, 14], [2683932, 14], [2683964, 14], [2683996, 14], [2684412, 14], [2684444, 14], [2684476, 14], [2684508, 14], [2684924, 14], [2684956, 14], [2684988, 14], [2685020, 14]
|
||||
],
|
||||
[
|
||||
[2678236, 14], [2680988, 14], [2681020, 14], [2681052, 14], [2681084, 14], [2681116, 14], [2681980, 14], [2682012, 14], [2682044, 14], [2682076, 14], [2682492, 14], [2682524, 14], [2682556, 14], [2682588, 14], [2683004, 14], [2683036, 14], [2683068, 14], [2683100, 14], [2683516, 14], [2683548, 14], [2683580, 14], [2683612, 14], [2684028, 14], [2684060, 14], [2684092, 14], [2684124, 14], [2684540, 14], [2684572, 14], [2684604, 14], [2684636, 14], [2685052, 14], [2685084, 14], [2685116, 14], [2685148, 14]
|
||||
]
|
||||
]
|
||||
|
||||
skip_colors = {
|
||||
4218620: 16,
|
||||
4218716: 16,
|
||||
4640148: 8,
|
||||
4647764: 16,
|
||||
4649428: 8,
|
||||
5031520: 16,
|
||||
5186680: 12,
|
||||
5187704: 11,
|
||||
5310686: 5,
|
||||
5310696: 8,
|
||||
5310760: 16,
|
||||
5311112: 11,
|
||||
5311176: 16,
|
||||
5311528: 16,
|
||||
5461634: 1
|
||||
}
|
||||
|
||||
base_color_list = [
|
||||
['blue', 'blue', 'green'],
|
||||
['blue', 'blue', 'red'],
|
||||
['blue', 'blue', 'None'],
|
||||
['blue', 'green', 'blue'],
|
||||
['blue', 'green', 'green'],
|
||||
['blue', 'green', 'None'],
|
||||
['blue', 'red', 'blue'],
|
||||
['blue', 'red', 'green'],
|
||||
['blue', 'red', 'red'],
|
||||
['blue', 'red', 'None'],
|
||||
['blue', 'None', 'blue'],
|
||||
['blue', 'None', 'green'],
|
||||
['blue', 'None', 'red'],
|
||||
['green', 'blue', 'blue'],
|
||||
['green', 'blue', 'green'],
|
||||
['green', 'blue', 'red'],
|
||||
['green', 'blue', 'None'],
|
||||
['green', 'green', 'blue'],
|
||||
['green', 'green', 'red'],
|
||||
['green', 'green', 'None'],
|
||||
['green', 'red', 'blue'],
|
||||
['green', 'red', 'green'],
|
||||
['green', 'red', 'red'],
|
||||
['green', 'red', 'None'],
|
||||
['green', 'None', 'blue'],
|
||||
['green', 'None', 'green'],
|
||||
['green', 'None', 'red'],
|
||||
['red', 'blue', 'blue'],
|
||||
['red', 'blue', 'green'],
|
||||
['red', 'blue', 'red'],
|
||||
['red', 'blue', 'None'],
|
||||
['red', 'green', 'blue'],
|
||||
['red', 'green', 'green'],
|
||||
['red', 'green', 'red'],
|
||||
['red', 'green', 'None'],
|
||||
['red', 'red', 'blue'],
|
||||
['red', 'red', 'green'],
|
||||
['red', 'red', 'None'],
|
||||
['red', 'None', 'blue'],
|
||||
['red', 'None', 'green'],
|
||||
['red', 'None', 'red'],
|
||||
['None', 'blue', 'blue'],
|
||||
['None', 'blue', 'green'],
|
||||
['None', 'blue', 'red'],
|
||||
['None', 'green', 'blue'],
|
||||
['None', 'green', 'green'],
|
||||
['None', 'green', 'red'],
|
||||
['None', 'red', 'blue'],
|
||||
['None', 'red', 'green'],
|
||||
['None', 'red', 'red'],
|
||||
['green', 'green', 'green']
|
||||
]
|
||||
|
||||
def modify_color(color, arrangement, tileset=False):
|
||||
if color == 0 or color == 32767:
|
||||
return color
|
||||
blue = (color & 31744) >> 10
|
||||
green = (color & 992) >> 5
|
||||
red = color & 31
|
||||
if arrangement[0] == "blue":
|
||||
high = blue << 10
|
||||
elif arrangement[0] == "green":
|
||||
high = green << 10
|
||||
elif arrangement[0] == "red":
|
||||
high = red << 10
|
||||
else:
|
||||
high = 0
|
||||
if arrangement[1] == "blue":
|
||||
mid = blue << 5
|
||||
elif arrangement[1] == "green":
|
||||
mid = green << 5
|
||||
elif arrangement[1] == "red":
|
||||
mid = red << 5
|
||||
else:
|
||||
mid = 0
|
||||
if arrangement[2] == "blue":
|
||||
low = blue
|
||||
elif arrangement[2] == "green":
|
||||
low = green
|
||||
elif arrangement[2] == "red":
|
||||
low = red
|
||||
else:
|
||||
low = 0
|
||||
return high | mid | low
|
||||
|
||||
|
||||
def get_color_slot():
|
||||
color_list = base_color_list.copy()
|
||||
old_color = None
|
||||
while True:
|
||||
if len(color_list) < 1:
|
||||
color_list = base_color_list.copy()
|
||||
new_color = color_list.pop(random.randrange(len(color_list)))
|
||||
if new_color == old_color:
|
||||
print("Repeat color")
|
||||
new_color = color_list.pop(random.randrange(len(color_list)))
|
||||
old_color = new_color
|
||||
yield new_color
|
||||
|
||||
|
||||
def randomize_palettes(data, seed=None, shuffle_tilesets=True, shuffle_sprites=True, shuffle_suits=True, shuffle_beams=True):
|
||||
if seed is None:
|
||||
random.seed()
|
||||
else:
|
||||
random.seed(seed)
|
||||
if shuffle_tilesets:
|
||||
for (offset, length) in static_tilesets:
|
||||
skip = 0
|
||||
color_slot = next(get_color_slot())
|
||||
fixed_color_count = 0
|
||||
fixed_color_slot = next(get_color_slot())
|
||||
for x in range(0, length, 2):
|
||||
current_offset = offset + x
|
||||
if x % 32 == 0:
|
||||
color_slot = next(get_color_slot())
|
||||
if current_offset in skip_colors.keys():
|
||||
skip = skip_colors.get(current_offset)
|
||||
if skip > 0:
|
||||
skip = skip - 1
|
||||
else:
|
||||
if current_offset in tileset_fixes.keys():
|
||||
half_word = tileset_fixes.get(current_offset)
|
||||
else:
|
||||
half_word = struct.unpack_from("<H", data, current_offset)[0]
|
||||
if current_offset in tileset_single_pass.keys():
|
||||
fixed_color_count = tileset_single_pass.get(current_offset)
|
||||
if fixed_color_count > 0:
|
||||
half_word = modify_color(half_word, fixed_color_slot)
|
||||
fixed_color_count = fixed_color_count - 1
|
||||
else:
|
||||
half_word = modify_color(half_word, color_slot, True)
|
||||
struct.pack_into("<H", data, current_offset, half_word)
|
||||
|
||||
for (offset, length) in animated_tilesets:
|
||||
skip = 0
|
||||
color_slot = next(get_color_slot())
|
||||
for x in range(0, length, 2):
|
||||
current_offset = offset + x
|
||||
if current_offset in skip_colors.keys():
|
||||
skip = skip_colors.get(current_offset)
|
||||
if skip > 0:
|
||||
skip = skip - 1
|
||||
else:
|
||||
if current_offset in tileset_fixes.keys():
|
||||
half_word = tileset_fixes.get(current_offset)
|
||||
else:
|
||||
half_word = struct.unpack_from("<H", data, current_offset)[0]
|
||||
half_word = modify_color(half_word, color_slot)
|
||||
struct.pack_into("<H", data, current_offset, half_word)
|
||||
|
||||
for group in paired_tilesets:
|
||||
color_slot = next(get_color_slot())
|
||||
for (offset, length) in group:
|
||||
skip = 0
|
||||
for x in range(0, length, 2):
|
||||
current_offset = offset + x
|
||||
if current_offset in skip_colors.keys():
|
||||
skip = skip_colors.get(current_offset)
|
||||
if skip > 0:
|
||||
skip = skip - 1
|
||||
else:
|
||||
if current_offset in tileset_fixes.keys():
|
||||
half_word = tileset_fixes.get(current_offset)
|
||||
else:
|
||||
half_word = struct.unpack_from("<H", data, current_offset)[0]
|
||||
half_word = modify_color(half_word, color_slot, True)
|
||||
struct.pack_into("<H", data, current_offset, half_word)
|
||||
|
||||
if shuffle_sprites:
|
||||
for (offset, length) in sprites:
|
||||
skip = 0
|
||||
color_slot = next(get_color_slot())
|
||||
for x in range(0, length, 2):
|
||||
current_offset = offset + x
|
||||
if current_offset in skip_colors.keys():
|
||||
skip = skip_colors.get(current_offset)
|
||||
if skip > 0:
|
||||
skip = skip - 1
|
||||
else:
|
||||
if current_offset in sprite_fixes.keys():
|
||||
half_word = sprite_fixes.get(current_offset)
|
||||
else:
|
||||
half_word = struct.unpack_from("<H", data, current_offset)[0]
|
||||
half_word = modify_color(half_word, color_slot)
|
||||
struct.pack_into("<H", data, current_offset, half_word)
|
||||
|
||||
for group in paired_sprites:
|
||||
skip = 0
|
||||
color_slot = next(get_color_slot())
|
||||
for (offset, length) in group:
|
||||
for x in range(0, length, 2):
|
||||
current_offset = offset + x
|
||||
if current_offset in skip_colors.keys():
|
||||
skip = skip_colors.get(current_offset)
|
||||
if skip > 0:
|
||||
skip = skip - 1
|
||||
else:
|
||||
if current_offset in sprite_fixes.keys():
|
||||
half_word = sprite_fixes.get(current_offset)
|
||||
else:
|
||||
half_word = struct.unpack_from("<H", data, current_offset)[0]
|
||||
half_word = modify_color(half_word, color_slot)
|
||||
struct.pack_into("<H", data, current_offset, half_word)
|
||||
|
||||
if shuffle_suits:
|
||||
for (offset, length) in suit_misc:
|
||||
color_slot = next(get_color_slot())
|
||||
for x in range(0, length, 2):
|
||||
current_offset = offset + x
|
||||
if current_offset in suit_fixes.keys():
|
||||
half_word = suit_fixes.get(current_offset)
|
||||
else:
|
||||
half_word = struct.unpack_from("<H", data, current_offset)[0]
|
||||
half_word = modify_color(half_word, color_slot)
|
||||
struct.pack_into("<H", data, current_offset, half_word)
|
||||
|
||||
for group in suits:
|
||||
color_slot = next(get_color_slot())
|
||||
for (offset, length) in group:
|
||||
for x in range(0, length, 2):
|
||||
current_offset = offset + x
|
||||
if current_offset in suit_fixes.keys():
|
||||
half_word = suit_fixes.get(current_offset)
|
||||
else:
|
||||
half_word = struct.unpack_from("<H", data, current_offset)[0]
|
||||
half_word = modify_color(half_word, color_slot)
|
||||
struct.pack_into("<H", data, current_offset, half_word)
|
||||
|
||||
if shuffle_beams:
|
||||
for (offset, length) in beams:
|
||||
color_slot = next(get_color_slot())
|
||||
for x in range(0, length, 2):
|
||||
current_offset = offset + x
|
||||
if current_offset in beam_fixes.keys():
|
||||
half_word = beam_fixes.get(current_offset)
|
||||
else:
|
||||
half_word = struct.unpack_from("<H", data, current_offset)[0]
|
||||
half_word = modify_color(half_word, color_slot)
|
||||
struct.pack_into("<H", data, current_offset, half_word)
|
||||
|
||||
for group in paired_beams:
|
||||
color_slot = next(get_color_slot())
|
||||
for (offset, length) in group:
|
||||
for x in range(0, length, 2):
|
||||
current_offset = offset + x
|
||||
if current_offset in beam_fixes.keys():
|
||||
half_word = beam_fixes.get(current_offset)
|
||||
else:
|
||||
half_word = struct.unpack_from("<H", data, current_offset)[0]
|
||||
half_word = modify_color(half_word, color_slot)
|
||||
struct.pack_into("<H", data, current_offset, half_word)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1:
|
||||
filename = sys.argv[1]
|
||||
if len(sys.argv) > 2:
|
||||
seed_arg = sys.argv[2]
|
||||
else:
|
||||
seed_arg = None
|
||||
try:
|
||||
checksum = file_hash(filename)
|
||||
if checksum != 1819625372:
|
||||
sys.exit("Only Metroid Fusion (U) is supported. Check the CRC32 value: it should be 6C75479C")
|
||||
game = bytearray(Path(filename).read_bytes())
|
||||
randomize_palettes(game, seed_arg)
|
||||
filename = filename.replace(".gba", " Colorized.gba")
|
||||
Path(filename).write_bytes(game)
|
||||
except FileNotFoundError:
|
||||
print(f"Could not open file {filename}")
|
||||
except PermissionError:
|
||||
print(f"Could not write file {filename}, it may be write protected.")
|
||||
|
||||
else:
|
||||
exit("Usage: 'Metroid Fusion(U).gba' <optional seed>")
|
||||
|
413
GUI.py
413
GUI.py
|
@ -10,299 +10,276 @@
|
|||
# Source Generated with Decompyle++
|
||||
# File: GUI.pyc (Python 3.8)
|
||||
|
||||
import PySimpleGUI as ui
|
||||
import zlib
|
||||
import time
|
||||
import PySimpleGUI as sg
|
||||
import threading
|
||||
import os
|
||||
|
||||
ui.theme('SystemDefault')
|
||||
from pathlib import Path
|
||||
from MFOR import file_hash
|
||||
sg.theme('SystemDefault')
|
||||
tt_race = ' Generating a race seed will not generate a spoiler log. '
|
||||
tt_createpatch = ' Automatically creates a .bps patch after generating a randomized game, so you can easily share with others. '
|
||||
tt_difficulty = ' Higher difficulty means harder tricks. Difficulty 0 means no tricks are expected outside of vanilla strategies. '
|
||||
tt_itempool = ' Limit where important items can be placed. If checked, major upgrades will only be in Data rooms, \n at bosses, or at vanilla E-tank locations. If unchecked, any item can be anywhere within logic. '
|
||||
tt_missileswithupgrades = ' If checked, collecting any major Missile item will enable Missiles. Logic will account for this setting. '
|
||||
tt_pbswithoutbombs = ' If checked, Power Bombs can be used without having regular Bombs. Logic will account for this setting. '
|
||||
tt_damageruns = ' Allow logic to require Samus to run through damaging environments to acquire important items. \n Examples include heated rooms, electrified water, and lava. '
|
||||
tt_splitsecurity = ' If checked, each door color must be unlocked independently. \n If unchecked, unlocking higher security levels will unlock all security levels below it. \n E.G.: unlocking Level 2 (Green) doors will also unlock Level 1 (Blue) doors. '
|
||||
tt_SectorShuffle = ' Randomly shuffle the arrangement of the Sectors. '
|
||||
tt_HideItems = ' Make all items appear as ? tanks. '
|
||||
tt_patch = ' Create a .bps patch after generating a game, so you can easily share with others. '
|
||||
tt_difficulty = ' Higher difficulty means harder tricks can be required.\n Difficulty 0 means no tricks are expected outside of vanilla strategies. '
|
||||
tt_major_minor = ' Limit where important items can be placed. If checked, major items will only be in Data rooms, \n at bosses, or at vanilla E-tank locations. If unchecked, any item can be anywhere within logic. '
|
||||
tt_missile_data = ' If checked, collecting any Missile data will enable Missiles. Logic will account for this setting. '
|
||||
tt_pb_data = ' If checked, Power Bombs can be used without having regular Bombs. Logic will account for this setting. '
|
||||
tt_hazard_runs = ' Allow logic to require Samus to run through hazardous environments to acquire important items. \n Examples include heated rooms, electrified water, and lava. '
|
||||
tt_split_security = ' If checked, each security level must be unlocked independently. \n If unchecked, unlocking higher security levels will unlock all lesser security levels. \n E.G.: unlocking Level 2 (Green) doors will also unlock Level 1 (Blue) doors. '
|
||||
tt_shuffle_sectors = ' Randomly shuffle the arrangement of the Sectors. '
|
||||
tt_shuffle_tubes = ' Randomly shuffle the tube connections between the Sectors. '
|
||||
tt_hide_items = ' Make all items appear as ? tanks. '
|
||||
generating = False
|
||||
|
||||
def fileHash(fileName):
|
||||
with open(fileName, 'rb') as fh:
|
||||
hash = 0
|
||||
while True:
|
||||
s = fh.read(65536)
|
||||
if not s:
|
||||
break
|
||||
hash = zlib.crc32(s, hash)
|
||||
return hash & 0xFFFFFFFF
|
||||
|
||||
def rando_thread(window = None, values = None):
|
||||
global fileName, basegame, failedgen, randoerror, generating, finishGenerating
|
||||
global failed_gen, rando_error, generating, file_name, failed_gen, generating, finish_generating
|
||||
values.update({
|
||||
'Difficulty': int(values.get('Difficulty')) })
|
||||
if values['Num']:
|
||||
'-DIFFICULTY-': int(values.get('-DIFFICULTY-')) })
|
||||
if values['-NUM-']:
|
||||
values.update({
|
||||
'Num': int(values.get('Num')) })
|
||||
'-NUM-': int(values.get('-NUM-')) })
|
||||
else:
|
||||
values.update({
|
||||
'Num': 1 })
|
||||
if values['Num'] < 1:
|
||||
'-NUM-': 1 })
|
||||
if values['-NUM-'] < 1:
|
||||
values.update({
|
||||
'Num': 1 })
|
||||
'-NUM-': 1 })
|
||||
values.pop(0)
|
||||
failedgen = False
|
||||
randoerror = False
|
||||
# FIXME: whatever this is
|
||||
# values = (lambda .0 = None: pass# WARNING: Decompyle incomplete
|
||||
#)(sorted(values.keys()))
|
||||
failed_gen = False
|
||||
rando_error = False
|
||||
generating = True
|
||||
|
||||
from Randomizer import start_randomizer
|
||||
if values['Debug']:
|
||||
fileName = start_randomizer(basegame, values)
|
||||
file_name = start_randomizer(base_game, values)
|
||||
else:
|
||||
# FIXME: let it explode
|
||||
#try:
|
||||
fileName = start_randomizer(basegame, values)
|
||||
# finally:
|
||||
# pass
|
||||
#except SystemExit:
|
||||
# failedgen = True
|
||||
#except:
|
||||
# print('An error occurred, no game was generated.')
|
||||
# randoerror = True
|
||||
|
||||
try:
|
||||
file_name = start_randomizer(base_game, values)
|
||||
except SystemExit:
|
||||
failed_gen = True
|
||||
|
||||
generating = False
|
||||
finishGenerating = True
|
||||
finish_generating = True
|
||||
|
||||
|
||||
def main_window(debug):
|
||||
global fileName, basegame, generating, finishGenerating
|
||||
itemPool = [
|
||||
global file_name, finish_generating, base_game, finish_generating
|
||||
item_pool = [
|
||||
[
|
||||
ui.Text('Charge Beam'),
|
||||
ui.Radio('Shuffled', 'radioChargeBeam', default=True),
|
||||
ui.Radio('Starting', 'radioChargeBeam'),
|
||||
ui.Radio('Disabled', 'radioChargeBeam')],
|
||||
sg.Text('Charge Beam'),
|
||||
sg.Radio('Shuffled', 'radioChargeBeam', default=True),
|
||||
sg.Radio('Starting', 'radioChargeBeam'),
|
||||
sg.Radio('Disabled', 'radioChargeBeam')],
|
||||
[
|
||||
ui.Text('Wide Beam'),
|
||||
ui.Radio('Shuffled', 'radioWideBeam', default=True),
|
||||
ui.Radio('Starting', 'radioWideBeam'),
|
||||
ui.Radio('Disabled', 'radioWideBeam')],
|
||||
sg.Text('Wide Beam'),
|
||||
sg.Radio('Shuffled', 'radioWideBeam', default=True),
|
||||
sg.Radio('Starting', 'radioWideBeam'),
|
||||
sg.Radio('Disabled', 'radioWideBeam')],
|
||||
[
|
||||
ui.Text('Plasma Beam'),
|
||||
ui.Radio('Shuffled', 'radioPlasmaBeam', default=True),
|
||||
ui.Radio('Starting', 'radioPlasmaBeam'),
|
||||
ui.Radio('Disabled', 'radioPlasmaBeam')],
|
||||
sg.Text('Plasma Beam'),
|
||||
sg.Radio('Shuffled', 'radioPlasmaBeam', default=True),
|
||||
sg.Radio('Starting', 'radioPlasmaBeam'),
|
||||
sg.Radio('Disabled', 'radioPlasmaBeam')],
|
||||
[
|
||||
ui.Text('Wave Beam'),
|
||||
ui.Radio('Shuffled', 'radioWaveBeam', default=True),
|
||||
ui.Radio('Starting', 'radioWaveBeam'),
|
||||
ui.Radio('Disabled', 'radioWaveBeam')],
|
||||
sg.Text('Wave Beam'),
|
||||
sg.Radio('Shuffled', 'radioWaveBeam', default=True),
|
||||
sg.Radio('Starting', 'radioWaveBeam'),
|
||||
sg.Radio('Disabled', 'radioWaveBeam')],
|
||||
[
|
||||
ui.Text('Ice Beam'),
|
||||
ui.Radio('Shuffled', 'radioIceBeam', default=True),
|
||||
ui.Radio('Starting', 'radioIceBeam'),
|
||||
ui.Radio('Disabled', 'radioIceBeam')],
|
||||
sg.Text('Ice Beam'),
|
||||
sg.Radio('Shuffled', 'radioIceBeam', default=True),
|
||||
sg.Radio('Starting', 'radioIceBeam'),
|
||||
sg.Radio('Disabled', 'radioIceBeam')],
|
||||
[
|
||||
ui.Text('Missile Data'),
|
||||
ui.Radio('Shuffled', 'radioMissileData', default=True),
|
||||
ui.Radio('Starting', 'radioMissileData'),
|
||||
ui.Radio('Disabled', 'radioMissileData')],
|
||||
sg.Text('Missile Data'),
|
||||
sg.Radio('Shuffled', 'radioMissileData', default=True),
|
||||
sg.Radio('Starting', 'radioMissileData'),
|
||||
sg.Radio('Disabled', 'radioMissileData')],
|
||||
[
|
||||
ui.Text('Super Missile Data'),
|
||||
ui.Radio('Shuffled', 'radioSuperMissile', default=True),
|
||||
ui.Radio('Starting', 'radioSuperMissile'),
|
||||
ui.Radio('Disabled', 'radioSuperMissile')],
|
||||
sg.Text('Super Missile Data'),
|
||||
sg.Radio('Shuffled', 'radioSuperMissile', default=True),
|
||||
sg.Radio('Starting', 'radioSuperMissile'),
|
||||
sg.Radio('Disabled', 'radioSuperMissile')],
|
||||
[
|
||||
ui.Text('Ice Missile Data'),
|
||||
ui.Radio('Shuffled', 'radioIceMissile', default=True),
|
||||
ui.Radio('Starting', 'radioIceMissile'),
|
||||
ui.Radio('Disabled', 'radioIceMissile')],
|
||||
sg.Text('Ice Missile Data'),
|
||||
sg.Radio('Shuffled', 'radioIceMissile', default=True),
|
||||
sg.Radio('Starting', 'radioIceMissile'),
|
||||
sg.Radio('Disabled', 'radioIceMissile')],
|
||||
[
|
||||
ui.Text('Diffusion Data'),
|
||||
ui.Radio('Shuffled', 'radioDiffusion', default=True),
|
||||
ui.Radio('Starting', 'radioDiffusion'),
|
||||
ui.Radio('Disabled', 'radioDiffusion')],
|
||||
sg.Text('Diffusion Data'),
|
||||
sg.Radio('Shuffled', 'radioDiffusion', default=True),
|
||||
sg.Radio('Starting', 'radioDiffusion'),
|
||||
sg.Radio('Disabled', 'radioDiffusion')],
|
||||
[
|
||||
ui.Text('Bombs'),
|
||||
ui.Radio('Shuffled', 'radioBombs', default=True),
|
||||
ui.Radio('Starting', 'radioBombs'),
|
||||
ui.Radio('Disabled', 'radioBombs')],
|
||||
sg.Text('Bombs'),
|
||||
sg.Radio('Shuffled', 'radioBombs', default=True),
|
||||
sg.Radio('Starting', 'radioBombs'),
|
||||
sg.Radio('Disabled', 'radioBombs')],
|
||||
[
|
||||
ui.Text('Power Bomb Data'),
|
||||
ui.Radio('Shuffled', 'radioPowerBombData', default=True),
|
||||
ui.Radio('Starting', 'radioPowerBombData'),
|
||||
ui.Radio('Disabled', 'radioPowerBombData')],
|
||||
sg.Text('Power Bomb Data'),
|
||||
sg.Radio('Shuffled', 'radioPowerBombData', default=True),
|
||||
sg.Radio('Starting', 'radioPowerBombData'),
|
||||
sg.Radio('Disabled', 'radioPowerBombData')],
|
||||
[
|
||||
ui.Text('Hi-Jump Boots'),
|
||||
ui.Radio('Shuffled', 'radioHiJump', default=True),
|
||||
ui.Radio('Starting', 'radioHiJump'),
|
||||
ui.Radio('Disabled', 'radioHiJump')],
|
||||
sg.Text('Hi-Jump Boots'),
|
||||
sg.Radio('Shuffled', 'radioHiJump', default=True),
|
||||
sg.Radio('Starting', 'radioHiJump'),
|
||||
sg.Radio('Disabled', 'radioHiJump')],
|
||||
[
|
||||
ui.Text('Speed Booster'),
|
||||
ui.Radio('Shuffled', 'radioSpeedBooster', default=True),
|
||||
ui.Radio('Starting', 'radioSpeedBooster'),
|
||||
ui.Radio('Disabled', 'radioSpeedBooster')],
|
||||
sg.Text('Speed Booster'),
|
||||
sg.Radio('Shuffled', 'radioSpeedBooster', default=True),
|
||||
sg.Radio('Starting', 'radioSpeedBooster'),
|
||||
sg.Radio('Disabled', 'radioSpeedBooster')],
|
||||
[
|
||||
ui.Text('Space Jump'),
|
||||
ui.Radio('Shuffled', 'radioSpaceJump', default=True),
|
||||
ui.Radio('Starting', 'radioSpaceJump'),
|
||||
ui.Radio('Disabled', 'radioSpaceJump')],
|
||||
sg.Text('Space Jump'),
|
||||
sg.Radio('Shuffled', 'radioSpaceJump', default=True),
|
||||
sg.Radio('Starting', 'radioSpaceJump'),
|
||||
sg.Radio('Disabled', 'radioSpaceJump')],
|
||||
[
|
||||
ui.Text('Screw Attack'),
|
||||
ui.Radio('Shuffled', 'radioScrewAttack', default=True),
|
||||
ui.Radio('Starting', 'radioScrewAttack'),
|
||||
ui.Radio('Disabled', 'radioScrewAttack')],
|
||||
sg.Text('Screw Attack'),
|
||||
sg.Radio('Shuffled', 'radioScrewAttack', default=True),
|
||||
sg.Radio('Starting', 'radioScrewAttack'),
|
||||
sg.Radio('Disabled', 'radioScrewAttack')],
|
||||
[
|
||||
ui.Text('Varia Suit'),
|
||||
ui.Radio('Shuffled', 'radioVariaSuit', default=True),
|
||||
ui.Radio('Starting', 'radioVariaSuit'),
|
||||
ui.Radio('Disabled', 'radioVariaSuit')],
|
||||
sg.Text('Varia Suit'),
|
||||
sg.Radio('Shuffled', 'radioVariaSuit', default=True),
|
||||
sg.Radio('Starting', 'radioVariaSuit'),
|
||||
sg.Radio('Disabled', 'radioVariaSuit')],
|
||||
[
|
||||
ui.Text('Gravity Suit'),
|
||||
ui.Radio('Shuffled', 'radioGravitySuit', default=True),
|
||||
ui.Radio('Starting', 'radioGravitySuit'),
|
||||
ui.Radio('Disabled', 'radioGravitySuit')],
|
||||
sg.Text('Gravity Suit'),
|
||||
sg.Radio('Shuffled', 'radioGravitySuit', default=True),
|
||||
sg.Radio('Starting', 'radioGravitySuit'),
|
||||
sg.Radio('Disabled', 'radioGravitySuit')],
|
||||
[
|
||||
ui.Text('Morph Ball'),
|
||||
ui.Radio('Shuffled', 'radioMorphBall', default=True),
|
||||
ui.Radio('Starting', 'radioMorphBall'),
|
||||
ui.Radio('Disabled', 'radioMorphBall')],
|
||||
sg.Text('Morph Ball'),
|
||||
sg.Radio('Shuffled', 'radioMorphBall', default=True),
|
||||
sg.Radio('Starting', 'radioMorphBall'),
|
||||
sg.Radio('Disabled', 'radioMorphBall')],
|
||||
[
|
||||
ui.Text('Blue Doors (Level 1)'),
|
||||
ui.Radio('Standard', 'radioBlueDoors', default=True),
|
||||
ui.Radio('Starting', 'radioBlueDoors'),
|
||||
ui.Radio('Shuffled', 'radioBlueDoors')],
|
||||
sg.Text('Blue Doors (Level 1)'),
|
||||
sg.Radio('Standard', 'radioBlueDoors', default=True),
|
||||
sg.Radio('Starting', 'radioBlueDoors'),
|
||||
sg.Radio('Shuffled', 'radioBlueDoors')],
|
||||
[
|
||||
ui.Text('Green Doors (Level 2)'),
|
||||
ui.Radio('Standard', 'radioGreenDoors', default=True),
|
||||
ui.Radio('Starting', 'radioGreenDoors'),
|
||||
ui.Radio('Shuffled', 'radioGreenDoors')],
|
||||
sg.Text('Green Doors (Level 2)'),
|
||||
sg.Radio('Standard', 'radioGreenDoors', default=True),
|
||||
sg.Radio('Starting', 'radioGreenDoors'),
|
||||
sg.Radio('Shuffled', 'radioGreenDoors')],
|
||||
[
|
||||
ui.Text('Yellow Doors (Level 3)'),
|
||||
ui.Radio('Standard', 'radioYellowDoors', default=True),
|
||||
ui.Radio('Starting', 'radioYellowDoors'),
|
||||
ui.Radio('Shuffled', 'radioYellowDoors')],
|
||||
sg.Text('Yellow Doors (Level 3)'),
|
||||
sg.Radio('Standard', 'radioYellowDoors', default=True),
|
||||
sg.Radio('Starting', 'radioYellowDoors'),
|
||||
sg.Radio('Shuffled', 'radioYellowDoors')],
|
||||
[
|
||||
ui.Text('Red Doors (Level 4)'),
|
||||
ui.Radio('Standard', 'radioRedDoors', default=True),
|
||||
ui.Radio('Starting', 'radioRedDoors'),
|
||||
ui.Radio('Shuffled', 'radioRedDoors')]]
|
||||
sg.Text('Red Doors (Level 4)'),
|
||||
sg.Radio('Standard', 'radioRedDoors', default=True),
|
||||
sg.Radio('Starting', 'radioRedDoors'),
|
||||
sg.Radio('Shuffled', 'radioRedDoors')]]
|
||||
settings = [
|
||||
[
|
||||
ui.Text('Difficulty'),
|
||||
ui.Slider(range=(0, 5), orientation='h', tooltip=tt_difficulty, key='Difficulty')],
|
||||
sg.Text('Difficulty'),
|
||||
sg.Slider(range=(0, 5), orientation='h', tooltip=tt_difficulty, key='-DIFFICULTY-')],
|
||||
[
|
||||
ui.Checkbox(text='Major/minor item split', key='MajorMinor', tooltip=tt_itempool)],
|
||||
sg.Checkbox(text='Generate race seed', key='-RACE-', tooltip=tt_race)],
|
||||
[
|
||||
ui.Checkbox(text='Missile upgrades enable Missiles', key='MissilesWithoutMainData', tooltip=tt_missileswithupgrades)],
|
||||
sg.Checkbox(text='Hide item graphics', key='-HIDEITEMS-', tooltip=tt_hide_items)],
|
||||
[
|
||||
ui.Checkbox(text='Enable Power Bombs without Bombs', key='PowerBombsWithoutBombs', tooltip=tt_pbswithoutbombs)],
|
||||
sg.Checkbox(text='Major/minor item split', key='-MAJORMINOR-', tooltip=tt_major_minor)],
|
||||
[
|
||||
ui.Checkbox(text='Damage runs', key='DamageRuns', tooltip=tt_damageruns)],
|
||||
sg.Checkbox(text='Missile upgrades enable Missiles', key='-MISSILEDATA-', tooltip=tt_missile_data)],
|
||||
[
|
||||
ui.Checkbox(text='Split security levels', key='SplitSecurity', tooltip=tt_splitsecurity)],
|
||||
sg.Checkbox(text='Enable Power Bombs without Bombs', key='-PBDATA-', tooltip=tt_pb_data)],
|
||||
[
|
||||
ui.Checkbox(text='Sector Shuffle', key='SectorShuffle', tooltip=tt_SectorShuffle)],
|
||||
sg.Checkbox(text='Hazard runs', key='-HAZARDRUNS-', tooltip=tt_hazard_runs)],
|
||||
[
|
||||
ui.Checkbox(text='Hide item graphics', key='HideItems', tooltip=tt_HideItems)],
|
||||
sg.Checkbox(text='Split security levels', key='-SPLITSECURITY-', tooltip=tt_split_security)],
|
||||
[
|
||||
ui.Checkbox(text='Race seed', key='RaceSeed', tooltip=tt_race)]]
|
||||
tabLayout = [
|
||||
sg.Text(text='Sector Shuffle:')],
|
||||
[
|
||||
ui.TabGroup([
|
||||
sg.Checkbox(text='Shuffle Sectors', key='-SHUFFLESECTORS-', tooltip=tt_shuffle_sectors),
|
||||
sg.Checkbox(text='Shuffle Tubes', key='-SHUFFLETUBES-', tooltip=tt_shuffle_tubes)]]
|
||||
cosmetic = [
|
||||
[
|
||||
sg.Text('Palette Shuffle')],
|
||||
[
|
||||
sg.Checkbox(text='Tilesets', key='-PALTILESETS-')],
|
||||
[
|
||||
sg.Checkbox(text='Sprites', key='-PALSPRITES-')],
|
||||
[
|
||||
sg.Checkbox(text='Suits', key='-PALSUITS-')],
|
||||
[
|
||||
sg.Checkbox(text='Beams', key='-PALBEAMS-')]]
|
||||
tab_layout = [
|
||||
[
|
||||
sg.TabGroup([
|
||||
[
|
||||
ui.Tab('Logic', settings)]], tab_location='topleft')]]
|
||||
sg.Tab('Logic', settings)],
|
||||
[
|
||||
sg.Tab('Cosmetic', cosmetic)]], tab_location='topleft', expand_x=True)]]
|
||||
layout = [
|
||||
[
|
||||
ui.Text('Seed value'),
|
||||
ui.Input(key='Seed')],
|
||||
sg.Text('Seed value'),
|
||||
sg.Input(key='-SEED-')],
|
||||
[
|
||||
tabLayout],
|
||||
tab_layout],
|
||||
[
|
||||
ui.Text('Number of Seeds to generate:'),
|
||||
ui.Input(key='Num', size=5, justification='center', enable_events=True),
|
||||
ui.Push(),
|
||||
ui.Checkbox(text='Create patch', key='Patch', tooltip=tt_createpatch),
|
||||
ui.Button(button_text='Generate', bind_return_key=True)]]
|
||||
window = ui.Window('Open Metroid Fusion Open Randomizer', layout)
|
||||
fileName = str()
|
||||
finishGenerating = False
|
||||
oldNum = str()
|
||||
sg.Text('Number of Seeds to generate:'),
|
||||
sg.Input(key='-NUM-', size=5, justification='center', enable_events=True),
|
||||
sg.Push(),
|
||||
sg.Checkbox(text='Create patch', key='-PATCH-', tooltip=tt_patch),
|
||||
sg.Button(button_text='Generate', bind_return_key=True)]]
|
||||
window = sg.Window('Open Metroid Fusion Open Randomizer', layout, icon=str(Path.cwd() / 'data' / 'MFOR.ico'))
|
||||
file_name = str()
|
||||
finish_generating = False
|
||||
old_num = str()
|
||||
disable_exceptions = (0, 'Logic', 'Cosmetic')
|
||||
|
||||
while True:
|
||||
(event, values) = window.read()
|
||||
if event == ui.WINDOW_CLOSED:
|
||||
|
||||
if event == sg.WINDOW_CLOSED:
|
||||
break
|
||||
|
||||
if event == 'Num':
|
||||
if len(values['Num']) > 3:
|
||||
|
||||
if event == '-NUM-':
|
||||
if len(values['-NUM-']) > 3:
|
||||
window['Num'].update(oldNum)
|
||||
for x in range(4):
|
||||
if x < len(values['Num']):
|
||||
if values['Num'][x] not in '0123456789':
|
||||
if x < len(values['-NUM-']):
|
||||
if values['-NUM-'][x] not in '0123456789':
|
||||
window['Num'].update(oldNum)
|
||||
break
|
||||
else:
|
||||
oldNum = values['Num']
|
||||
oldNum = values['-NUM-']
|
||||
|
||||
if event == 'Generate':
|
||||
basegame = ui.popup_get_file('Choose base game', no_titlebar=True, file_types=[('GBA File', '*.gba')], history=True, history_setting_filename=os.path.join('.', 'settings.json'))
|
||||
if basegame == '':
|
||||
ui.popup('Please select a Metroid Fusion (U) rom.', title='No source rom selected')
|
||||
if basegame != None and basegame != '':
|
||||
checksum = fileHash(basegame)
|
||||
base_game = sg.popup_get_file('Choose base game', default_path=str(Path.cwd()), no_titlebar=True, file_types=[('GBA File', '*.gba')], history=True, history_setting_filename=str(Path.cwd() / 'settings.json'))
|
||||
if base_game == '':
|
||||
sg.popup('Please select a Metroid Fusion (U) rom.', title='No source rom selected')
|
||||
if base_game != None and base_game != '':
|
||||
checksum = file_hash(base_game)
|
||||
if checksum != 1819625372:
|
||||
ui.popup('Only Metroid Fusion (U) is supported.\nCheck the CRC32 value: it should be 6C75479C', title='Bad Checksum')
|
||||
sg.popup('Only Metroid Fusion (U) is supported.\nCheck the CRC32 value: it should be 6C75479C', title='Bad Checksum')
|
||||
else:
|
||||
values.update({
|
||||
'Debug': debug })
|
||||
threading.Thread(target=rando_thread, args=[window, values], daemon=True).start()
|
||||
window['Difficulty'].update(disabled=True)
|
||||
window['MajorMinor'].update(disabled=True)
|
||||
window['MissilesWithoutMainData'].update(disabled=True)
|
||||
window['PowerBombsWithoutBombs'].update(disabled=True)
|
||||
window['DamageRuns'].update(disabled=True)
|
||||
window['SplitSecurity'].update(disabled=True)
|
||||
window['SectorShuffle'].update(disabled=True)
|
||||
window['HideItems'].update(disabled=True)
|
||||
window['Seed'].update(disabled=True)
|
||||
window['RaceSeed'].update(disabled=True)
|
||||
window['Num'].update(disabled=True)
|
||||
window['Patch'].update(disabled=True)
|
||||
window['Generate'].update(disabled=True)
|
||||
|
||||
|
||||
while generating:
|
||||
ui.popup_animated(ui.DEFAULT_BASE64_LOADING_GIF, 'Generating game, please wait...', time_between_frames=20)
|
||||
sg.popup_animated(sg.DEFAULT_BASE64_LOADING_GIF, 'Generating game, please wait...', time_between_frames=20)
|
||||
window.Refresh()
|
||||
|
||||
if finishGenerating:
|
||||
ui.popup_animated(None)
|
||||
if failedgen:
|
||||
ui.popup('Could not generate a game with the current settings. Try changing your settings.', title='Metroid Fusion Open Randomizer')
|
||||
elif randoerror:
|
||||
ui.popup('An error occurred, no game was generated.', title='Error')
|
||||
elif ui.Input.get(window['Num']) != '':
|
||||
if int(ui.Input.get(window['Num'])) > 1:
|
||||
ui.popup('Multiple games have been added to the seeds folder.', title='Success!')
|
||||
|
||||
if finish_generating:
|
||||
sg.popup_animated(None)
|
||||
if failed_gen:
|
||||
sg.popup('Could not generate a game with the current settings. Try changing your settings.', title='Metroid Fusion Open Randomizer')
|
||||
elif rando_error:
|
||||
sg.popup('An error occurred, no game was generated.', title='Error')
|
||||
elif sg.Input.get(window['-NUM-']) != '':
|
||||
if int(sg.Input.get(window['-NUM-'])) > 1:
|
||||
sg.popup('Multiple games have been added to the seeds folder.', title='Success!')
|
||||
else:
|
||||
ui.popup('{}\nhas been added to the seeds folder.'.format(fileName), title='Success!')
|
||||
sg.popup(f'''{file_name}\nhas been added to the seeds folder.''', title='Success!')
|
||||
else:
|
||||
ui.popup('{}\nhas been added to the seeds folder.'.format(fileName), title='Success!')
|
||||
window['Difficulty'].update(disabled=False)
|
||||
window['MajorMinor'].update(disabled=False)
|
||||
window['MissilesWithoutMainData'].update(disabled=False)
|
||||
window['PowerBombsWithoutBombs'].update(disabled=False)
|
||||
window['DamageRuns'].update(disabled=False)
|
||||
window['SplitSecurity'].update(disabled=False)
|
||||
window['SectorShuffle'].update(disabled=False)
|
||||
window['HideItems'].update(disabled=False)
|
||||
window['Seed'].update(disabled=False)
|
||||
window['RaceSeed'].update(disabled=False)
|
||||
window['Num'].update(disabled=False)
|
||||
window['Patch'].update(disabled=False)
|
||||
window['Generate'].update(disabled=False)
|
||||
finishGenerating = False
|
||||
window.close()
|
||||
sg.popup(f'''{file_name}\nhas been added to the seeds folder.''', title='Success!')
|
||||
finish_generating = False
|
||||
|
||||
window.close()
|
||||
|
|
242
MFOR.py
242
MFOR.py
|
@ -7,79 +7,179 @@
|
|||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
|
||||
# Source Generated with Decompyle++
|
||||
# File: MFOR.pyc (Python 3.8)
|
||||
|
||||
import argparse
|
||||
import os
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-seed', help='Seed value, optional')
|
||||
parser.add_argument('-race', default=False, action='store_true', help='Generate race rom (no spoiler log)')
|
||||
parser.add_argument('-patch', default=False, action='store_true', help='Generate BPS patch with seed (for easy sharing)')
|
||||
parser.add_argument('-diff', help='Trick difficulty level (0-5, default 0)')
|
||||
parser.add_argument('-limit', default=False, action='store_true', help='Major/minor item pool')
|
||||
parser.add_argument('-missiles', default=False, action='store_true', help='Missile upgrades enable the missile launcher (default off)')
|
||||
parser.add_argument('-pbs', default=False, action='store_true', help='Enable Power Bombs without normal Bombs (default off)')
|
||||
parser.add_argument('-damage-runs', default=False, action='store_true', help='Allow logical damage runs (default off)')
|
||||
parser.add_argument('-split-security', default=False, action='store_true', help='Separated Security levels (default off)')
|
||||
parser.add_argument('-sector-shuffle', default=False, action='store_true', help='Randomly shuffle the arrangement of the Sectors')
|
||||
parser.add_argument('-rom', help='Base rom (Metroid Fusion (U).gba)')
|
||||
parser.add_argument('-nogui', default=False, action='store_true', help='Termnial based seed generation (this)')
|
||||
parser.add_argument('-num', help='Number of seeds to generate (default 1)')
|
||||
parser.add_argument('-hideitems', default=False, action='store_true', help='All items use ? tank graphics')
|
||||
parser.add_argument('-debug', default=False, action='store_true', help='Debugging, for my own use')
|
||||
args = parser.parse_args()
|
||||
import random
|
||||
import sys
|
||||
import zlib
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
if not os.path.exists(os.path.join('.', 'data')):
|
||||
os.mkdir('data')
|
||||
if not os.path.exists(os.path.join('.', 'seeds')):
|
||||
os.mkdir('seeds')
|
||||
if not os.path.exists(os.path.join('.', 'spoilers')):
|
||||
os.mkdir('spoilers')
|
||||
if not args.debug:
|
||||
args.debug = False
|
||||
if args.nogui:
|
||||
settings = dict()
|
||||
settings.update({
|
||||
'Seed': None })
|
||||
if args.seed:
|
||||
settings.update({
|
||||
'Seed': args.seed })
|
||||
settings.update({
|
||||
'Debug': args.debug })
|
||||
settings.update({
|
||||
'Patch': args.patch })
|
||||
if not args.diff:
|
||||
args.diff = 0
|
||||
if int(args.diff) <= 0:
|
||||
args.diff = 0
|
||||
elif int(args.diff) >= 5:
|
||||
args.diff = 5
|
||||
settings.update({
|
||||
'Difficulty': args.diff })
|
||||
settings.update({
|
||||
'MajorMinor': args.limit })
|
||||
settings.update({
|
||||
'MissilesWithoutMainData': args.missiles })
|
||||
settings.update({
|
||||
'PowerBombsWithoutBombs': args.pbs })
|
||||
settings.update({
|
||||
'DamageRuns': args.damage_runs })
|
||||
settings.update({
|
||||
'SplitSecurity': args.split_security })
|
||||
settings.update({
|
||||
'SectorShuffle': args.sector_shuffle })
|
||||
settings.update({
|
||||
'HideItems': args.hideitems })
|
||||
settings.update({
|
||||
'RaceSeed': args.race })
|
||||
settings.update({
|
||||
'Num': None })
|
||||
if args.num:
|
||||
settings.update({
|
||||
'Num': int(args.num) })
|
||||
from Randomizer import start_randomizer
|
||||
start_randomizer(args.rom, settings)
|
||||
else:
|
||||
import GUI as gui
|
||||
gui.main_window(args.debug)
|
||||
version = '2024.02.10+chozo1'
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=f'''Metroid Fusion Open Randomizer v{version}''')
|
||||
parser.add_argument('-file', help='Path to unmodified game (Metroid Fusion (U).gba)')
|
||||
parser.add_argument('-seed', default=None, help='Optional seed value')
|
||||
parser.add_argument('-difficulty', choices=['0','1','2','3','4','5','r'], default='0', help='Trick difficulty level 0-5 (default 0)')
|
||||
parser.add_argument('-race', choices=['t','f','r'], default='f', help='Generate race seed (no spoiler log)')
|
||||
parser.add_argument('-hidden', choices=['t','f','r'], default='f', help='All items use ? tank graphics')
|
||||
parser.add_argument('-limit', choices=['t','f','r'], default='f', help='Major/minor item pool')
|
||||
parser.add_argument('-missiles', choices=['t','f','r'], default='f', help='Missile upgrades enable the Missile launcher (default f)')
|
||||
parser.add_argument('-bombs', choices=['t','f','r'], default='f', help='Enable Power Bombs without normal Bombs (default f)')
|
||||
parser.add_argument('-hazards', choices=['t','f','r'], default='f', help='Logic allows hazard runs (default f)')
|
||||
parser.add_argument('-security', choices=['t','f','r'], default='f', help='Split security: if split, security stations only unlock their specific security level. By default, higher level security stations will unlock all lesser security levels.')
|
||||
parser.add_argument('-sectors', choices=['t','f','r'], default='f', help='Choose whether to shuffle the arrangement of the Sectors')
|
||||
parser.add_argument('-tubes', choices=['t','f','r'], default='f', help='Choose whether to shuffle the tube connections between the Sectors')
|
||||
parser.add_argument('-palettes', help='Enable palette shuffle: [t]ilesets, [b]eams, [s]uits, s[p]rites')
|
||||
parser.add_argument('-num', default=1, help='Number of seeds to generate')
|
||||
parser.add_argument('-patch', default=False, action='store_true', help='Generate BPS patch for easy sharing')
|
||||
args = parser.parse_args()
|
||||
|
||||
if not hasattr(args, "debug"):
|
||||
args.debug = False
|
||||
if args.file:
|
||||
from Randomizer import start_randomizer
|
||||
settings = dict()
|
||||
logic = list()
|
||||
settings.update({"-SEED-": (args.seed)})
|
||||
if args.difficulty == "r":
|
||||
args.difficulty = random.randrange(6)
|
||||
logic.append(f"Difficulty: {args.difficulty}")
|
||||
else:
|
||||
args.difficulty = int(args.difficulty)
|
||||
if args.race == "r":
|
||||
args.race = random.choice([True, False])
|
||||
logic.append(f"Race: {args.race}")
|
||||
elif args.race == "t":
|
||||
args.race = True
|
||||
else:
|
||||
args.race = False
|
||||
if args.hidden == "r":
|
||||
args.hidden = random.choice([True, False])
|
||||
logic.append(f"Hide items: {args.hidden}")
|
||||
elif args.hidden == "t":
|
||||
args.hidden = True
|
||||
else:
|
||||
args.hidden = False
|
||||
if args.limit == "r":
|
||||
args.limit = random.choice([True, False])
|
||||
logic.append(f"Major/minor: {args.limit}")
|
||||
elif args.limit == "t":
|
||||
args.limit = True
|
||||
else:
|
||||
args.limit = False
|
||||
if args.missiles == "r":
|
||||
args.missiles = random.choice([True, False])
|
||||
logic.append(f'Missile launcher: {"any" if args.missiles else "main"}')
|
||||
elif args.missiles == "t":
|
||||
args.missiles = True
|
||||
else:
|
||||
args.missiles = False
|
||||
if args.bombs == "r":
|
||||
args.bombs = random.choice([True, False])
|
||||
logic.append(f'Power Bombs require: {"data" if args.bombs else "bombs"}')
|
||||
elif args.bombs == "t":
|
||||
args.bombs = True
|
||||
else:
|
||||
args.bombs = False
|
||||
if args.hazards == "r":
|
||||
args.hazards = random.choice([True, False])
|
||||
logic.append(f"Hazard runs: {args.hazards}")
|
||||
elif args.hazards == "t":
|
||||
args.hazards = True
|
||||
else:
|
||||
args.hazards = False
|
||||
if args.security == "r":
|
||||
args.security = random.choice([True, False])
|
||||
logic.append(f"Split security: {args.security}")
|
||||
elif args.security == "t":
|
||||
args.security = True
|
||||
else:
|
||||
args.security = False
|
||||
if args.sectors == "r":
|
||||
args.sectors = random.choice([True, False])
|
||||
logic.append(f"Shuffle Sectors: {args.sectors}")
|
||||
elif args.sectors == "t":
|
||||
args.sectors = True
|
||||
else:
|
||||
args.sectors = False
|
||||
if args.tubes == "r":
|
||||
args.tubes = random.choice([True, False])
|
||||
logic.append(f"Shuffle tubes: {args.tubes}")
|
||||
elif args.tubes == "t":
|
||||
args.tubes = True
|
||||
else:
|
||||
args.tubes = False
|
||||
settings.update({"-DIFFICULTY-": (args.difficulty)})
|
||||
settings.update({"-RACE-": (args.race)})
|
||||
settings.update({"-HIDEITEMS-": (args.hidden)})
|
||||
settings.update({"-MAJORMINOR-": (args.limit)})
|
||||
settings.update({"-MISSILEDATA-": (args.missiles)})
|
||||
settings.update({"-PBDATA-": (args.bombs)})
|
||||
settings.update({"-HAZARDRUNS-": (args.hazards)})
|
||||
settings.update({"-SPLITSECURITY-": (args.security)})
|
||||
settings.update({"-SHUFFLESECTORS-": (args.sectors)})
|
||||
settings.update({"-SHUFFLETUBES-": (args.tubes)})
|
||||
if args.num:
|
||||
args.num = int(args.num)
|
||||
if args.num < 1:
|
||||
args.num = 1
|
||||
settings.update({"-NUM-": (args.num)})
|
||||
else:
|
||||
settings.update({"-NUM-": 1})
|
||||
settings.update({"-PATCH-": (args.patch)})
|
||||
settings.update({"Debug": (args.debug)})
|
||||
settings.update({"-PALTILESETS-": False})
|
||||
settings.update({"-PALSPRITES-": False})
|
||||
settings.update({"-PALSUITS-": False})
|
||||
settings.update({"-PALBEAMS-": False})
|
||||
if args.palettes:
|
||||
if "t" in args.palettes:
|
||||
settings.update({"-PALTILESETS-": True})
|
||||
if "b" in args.palettes:
|
||||
settings.update({"-PALBEAMS-": True})
|
||||
if "s" in args.palettes:
|
||||
settings.update({"-PALSUITS-": True})
|
||||
if "p" in args.palettes:
|
||||
settings.update({"-PALSPRITES-": True})
|
||||
|
||||
settings = {key: settings[key] for key in sorted(settings.keys())}
|
||||
start_randomizer(args.file, settings)
|
||||
if len(logic) > 0:
|
||||
print("\nRandom settings:")
|
||||
for item in logic:
|
||||
print(item)
|
||||
|
||||
else:
|
||||
import GUI
|
||||
GUI.main_window(args.debug)
|
||||
|
||||
|
||||
def _init():
|
||||
checksum = file_hash(Path.cwd() / 'data' / 'MFOR.bps')
|
||||
if checksum != 558161692:
|
||||
sys.exit('Error: Core files could not be read. Please go to https://metroidconstruction.com/ and re-download MFOR.')
|
||||
Path(Path.cwd() / 'seeds').mkdir(exist_ok=True)
|
||||
Path(Path.cwd() / 'spoilers').mkdir(exist_ok=True)
|
||||
|
||||
|
||||
def file_hash(file):
|
||||
checksum = 0
|
||||
try:
|
||||
with open(file, 'rb') as source:
|
||||
while True:
|
||||
s = source.read(65536)
|
||||
if not s:
|
||||
break
|
||||
checksum = zlib.crc32(s, checksum)
|
||||
except FileNotFoundError:
|
||||
sys.exit('Error:', file, 'could not be opened.')
|
||||
return checksum & 0xFFFFFFFF
|
||||
|
||||
if __name__ == '__main__':
|
||||
_init()
|
||||
main()
|
||||
|
|
24
README.md
24
README.md
|
@ -25,11 +25,26 @@ Caveat emptor: the project is already diverging from the original decompilation
|
|||
- [x] Reconstructing missing parts of code
|
||||
- [x] Patching
|
||||
- [x] Generating BPS patches (python-bps-continued used to apply base patch as it's platform independent, but FLIPS to generate BPS files)
|
||||
- [ ] Potentially merging/reimplementing the new features from v2024.02.10
|
||||
- [x] Potentially merging/reimplementing the new features from v2024.02.10
|
||||
- [ ] Merging the logic edits from v2024.02.10
|
||||
- [ ] Solver refactoring
|
||||
- [ ] CI/CD for Windows frozen builds
|
||||
- [x] CI/CD for Windows frozen builds
|
||||
|
||||
## Install
|
||||
## Quick start (Windows)
|
||||
|
||||
- fetch the latest [OpenMFOR release](https://git.inabaudonge.reisen/OpenMFOR/OpenMFOR/releases/latest)
|
||||
- fetch the latest [FLIPS for Windows release](https://github.com/Alcaro/Flips/releases/latest)
|
||||
- extract OpenMFOR
|
||||
- put `flips.exe` extracted from the second archive into the `flips` folder in OpenMFOR
|
||||
- double click `OpenMFOR.exe`
|
||||
- ????
|
||||
- PROFIT
|
||||
|
||||
Prebuilt release builds might be available on the [releases](https://git.inabaudonge.reisen/OpenMFOR/OpenMFOR/releases) tab.
|
||||
|
||||
If you feel more inclined testing the tip of the development branches or playing with the logic, part of the following guide can be used on Windows too to run the randomizer from sources rather than PyInstaller packed executables.
|
||||
|
||||
## Install (any OS)
|
||||
|
||||
On *NIX OSes, the only strict prerequisites are Python 3.x and pip. For the GUI, sourcing `python3-tk` from your package manager is highly recommended.
|
||||
|
||||
|
@ -61,7 +76,6 @@ The randomizer currently works **only** with the USA (and, by proxy, the Austral
|
|||
## Help
|
||||
|
||||
* [OpenMFOR Discord guild](https://discord.gg/QV3p8MWKab) since we've been told to refrain to bring this up on the wider community server over ongoing diplomacy issues.
|
||||
|
||||
* soon™ we might have an IRC channel or a Matrix group bridged to the Discord guild.
|
||||
|
||||
## Special Thanks
|
||||
|
@ -70,6 +84,8 @@ The randomizer currently works **only** with the USA (and, by proxy, the Austral
|
|||
- [Decompyle++](https://github.com/zrax/pycdc) has been used for the initial disassembly. [R. Bernstein](https://github.com/rocky) because uncompyle6, decompile3, xdis and python-control-flow kickstarted this project.
|
||||
- [tcprescott's python-bps-continued](https://github.com/tcprescott/python-bps-continued) since this makes at least rolling the seeds completely platform independent.
|
||||
- [Alcaro's Floating IPS](https://github.com/Alcaro/Flips)
|
||||
- Fedor Batogonov's [docker-pyinstaller](https://gitlab.com/batonogov/docker-pyinstaller) used by the CI/CD to make Windows builds on AppVeyor
|
||||
- The testers in the various Discord guilds that are spotting the massive scams before I do! (SkullAdult, Mr. Fox, baria, Fpiz_, jakoliath)
|
||||
|
||||
[Kazuto88](https://github.com/Kazuto88) being the original randomizer developer. The following lines are his special thanks to the people that contributed to making this a reality:
|
||||
|
||||
|
|
468
Randomizer.py
468
Randomizer.py
|
@ -20,11 +20,14 @@ import json
|
|||
import os
|
||||
import zlib
|
||||
import math
|
||||
from pathlib import Path
|
||||
import Fusion_Graph as Graph
|
||||
import Fusion_Palette_Shuffle
|
||||
from Fusion_Items import *
|
||||
from MFOR import file_hash
|
||||
from bps.apply import apply_to_files
|
||||
|
||||
version = '0.11.6+chozo1'
|
||||
version = '2024.02.10+chozo1'
|
||||
|
||||
# FIXME: Debug should not be set here
|
||||
# Debug = False
|
||||
|
@ -32,20 +35,6 @@ version = '0.11.6+chozo1'
|
|||
def ceiling(num, denom):
|
||||
return -(num // -denom)
|
||||
|
||||
def fileHash(file):
|
||||
checksum = 0
|
||||
try:
|
||||
with open(file, 'rb') as source:
|
||||
while True:
|
||||
s = source.read(65536)
|
||||
if not s:
|
||||
break
|
||||
checksum = zlib.crc32(s, checksum)
|
||||
except FileNotFoundError:
|
||||
sys.exit('Error:', file, 'could not be opened.')
|
||||
return checksum & 0xFFFFFFFF
|
||||
|
||||
|
||||
def enable_item(graph, item):
|
||||
globals()[item] = True
|
||||
update_items(graph)
|
||||
|
@ -72,7 +61,7 @@ def update_items(graph):
|
|||
global SuperMissiles
|
||||
global UseBombs
|
||||
global WallJump
|
||||
if SeedSettings['MissilesWithoutMainData'] == True:
|
||||
if SeedSettings['-MISSILEDATA-'] == True:
|
||||
Missiles = MainMissiles or SuperMissileItem or IceMissileItem or DiffusionItem
|
||||
else:
|
||||
Missiles = MainMissiles
|
||||
|
@ -83,7 +72,7 @@ def update_items(graph):
|
|||
HiJump = HiJumpBoots or SpaceJump
|
||||
SpringBall = HiJumpBoots
|
||||
WallJump = SpaceJump or WallJumpSetting
|
||||
if SeedSettings['PowerBombsWithoutBombs'] == True:
|
||||
if SeedSettings['-PBDATA-'] == True:
|
||||
PowerBombs = MorphBall and MainPowerBombs
|
||||
else:
|
||||
PowerBombs = MorphBall and Bombs and MainPowerBombs
|
||||
|
@ -1769,7 +1758,7 @@ def find_available_areas(graph):
|
|||
path = graph.get_path('Security-Level-2', StartLocation, depth=250)
|
||||
if path == None:
|
||||
disable_item(graph, 'GreenDoors')
|
||||
elif SeedSettings['SplitSecurity'] == False:
|
||||
elif SeedSettings['-SPLITSECURITY-'] == False:
|
||||
enable_item(graph, 'BlueDoors')
|
||||
if YellowDoors == False:
|
||||
path = graph.get_path(StartLocation, 'Security-Level-3', depth=250)
|
||||
|
@ -1778,7 +1767,7 @@ def find_available_areas(graph):
|
|||
path = graph.get_path('Security-Level-3', StartLocation, depth=250)
|
||||
if path == None:
|
||||
disable_item(graph, 'YellowDoors')
|
||||
elif SeedSettings['SplitSecurity'] == False:
|
||||
elif SeedSettings['-SPLITSECURITY-'] == False:
|
||||
enable_item(graph, 'GreenDoors')
|
||||
enable_item(graph, 'BlueDoors')
|
||||
if RedDoors == False:
|
||||
|
@ -1788,7 +1777,7 @@ def find_available_areas(graph):
|
|||
path = graph.get_path('Security-Level-4', StartLocation, depth=250)
|
||||
if path == None:
|
||||
disable_item(graph, 'RedDoors')
|
||||
elif SeedSettings['SplitSecurity'] == False:
|
||||
elif SeedSettings['-SPLITSECURITY-'] == False:
|
||||
enable_item(graph, 'YellowDoors')
|
||||
enable_item(graph, 'GreenDoors')
|
||||
enable_item(graph, 'BlueDoors')
|
||||
|
@ -1811,6 +1800,8 @@ def randomize_game(graph):
|
|||
global PlacedMissiles
|
||||
global PlacedPowerBombs
|
||||
global areaLayout
|
||||
global tubeLayout
|
||||
|
||||
update_requirements(graph)
|
||||
PlacedETanks = PlacedMissiles = PlacedPowerBombs = 0
|
||||
gameBeatable = False
|
||||
|
@ -1834,68 +1825,76 @@ def randomize_game(graph):
|
|||
FirstItem = None
|
||||
AddETank = False
|
||||
attemptedLayouts = [[5,3,1,2,4,6]]
|
||||
WeightedMajors = { 'MainMissiles': 16,
|
||||
'MorphBall': 16,
|
||||
'ChargeBeam': 8,
|
||||
'Bombs': 14,
|
||||
'HiJumpBoots': 14,
|
||||
'SpeedBooster': 8,
|
||||
'SuperMissileItem': 8,
|
||||
'VariaSuit': 12,
|
||||
'IceMissileItem': 6,
|
||||
'WideBeam': 4,
|
||||
'MainPowerBombs': 6,
|
||||
'SpaceJump': 4,
|
||||
'PlasmaBeam': 2,
|
||||
'GravitySuit': 4,
|
||||
'DiffusionItem': 6,
|
||||
'WaveBeam': 4,
|
||||
'ScrewAttack': 2,
|
||||
'IceBeam': 2}
|
||||
WeightedMajors = {
|
||||
'MainMissiles': 6,
|
||||
'MorphBall': 6,
|
||||
'ChargeBeam': 3,
|
||||
'Bombs': 5,
|
||||
'HiJumpBoots': 5,
|
||||
'SpeedBooster': 4,
|
||||
'SuperMissileItem': 4,
|
||||
'VariaSuit': 3,
|
||||
'IceMissileItem': 3,
|
||||
'WideBeam': 4,
|
||||
'MainPowerBombs': 2,
|
||||
'SpaceJump': 2,
|
||||
'PlasmaBeam': 2,
|
||||
'GravitySuit': 1,
|
||||
'DiffusionItem': 3,
|
||||
'WaveBeam': 1,
|
||||
'ScrewAttack': 1,
|
||||
'IceBeam': 1 }
|
||||
BossHealths = {
|
||||
'Arachnus': 0,
|
||||
'Yakuza': 1000,
|
||||
'Ridley': 4500,
|
||||
'Charge Core-X': 0,
|
||||
'Zazabi': 100,
|
||||
'Nettori': 2000,
|
||||
'Data S3': 300,
|
||||
'Wide Core-X': 0,
|
||||
'Serris': 50,
|
||||
'Nightmare': 1800,
|
||||
'Mega Core-X': 0,
|
||||
'Box-2': 500}
|
||||
'Arachnus': 0,
|
||||
'Charge Core-X': 0,
|
||||
'Zazabi': 100,
|
||||
'Serris': 50,
|
||||
'Data S3': 300,
|
||||
'Mega Core-X': 0,
|
||||
'Wide Core-X': 0,
|
||||
'Yakuza': 1000,
|
||||
'Nettori': 2000,
|
||||
'Nightmare': 1800,
|
||||
'Ridley': 4500,
|
||||
'BOX-2': 500 }
|
||||
print('Generating, please wait...')
|
||||
while gameBeatable == False:
|
||||
if init == False:
|
||||
if SeedSettings['SectorShuffle'] == True:
|
||||
if SeedSettings['-SHUFFLESECTORS-'] == True or SeedSettings['-SHUFFLETUBES-'] == True:
|
||||
areaDoors = {1:['S1-6B', 'S1-68'],
|
||||
2:['S2-7F', 'S2-82'],
|
||||
3:['S3-59', 'S3-56'],
|
||||
4:['S4-6C', 'S4-6A'],
|
||||
5:['S5-02', 'S5-53'],
|
||||
6:['S6-51', 'S6-54']}
|
||||
areaLayout = random.sample(range(1, 7), 6)
|
||||
if SeedSettings['MajorMinor'] == True:
|
||||
while areaLayout[2] != 1 and areaLayout[2] != 2 and areaLayout[3] != 1 and areaLayout[3] != 2:
|
||||
attemptedLayouts.append(areaLayout)
|
||||
|
||||
if SeedSettings['-SHUFFLESECTORS-'] == True:
|
||||
areaLayout = random.sample(range(1, 7), 6)
|
||||
if SeedSettings['-MAJORMINOR-'] == True:
|
||||
while areaLayout[2] != 1 and areaLayout[2] != 2 and areaLayout[3] != 1 and areaLayout[3] != 2:
|
||||
attemptedLayouts.append(areaLayout)
|
||||
while areaLayout in attemptedLayouts:
|
||||
areaLayout = random.sample(range(1, 7), 6)
|
||||
else:
|
||||
while areaLayout in attemptedLayouts:
|
||||
areaLayout = random.sample(range(1, 7), 6)
|
||||
else:
|
||||
while areaLayout in attemptedLayouts:
|
||||
areaLayout = random.sample(range(1, 7), 6)
|
||||
attemptedLayouts.append(areaLayout)
|
||||
attemptedLayouts.append(areaLayout)
|
||||
|
||||
tubeLayout = areaLayout
|
||||
if SeedSettings['-SHUFFLETUBES-'] == True:
|
||||
tubeLayout = random.sample(range(1, 7), 6)
|
||||
|
||||
for area in areaLayout:
|
||||
elevator = areaLayout.index(area)
|
||||
if elevator == 0:
|
||||
leftArea = areaLayout[5]
|
||||
if tubeLayout.index(area) == 0:
|
||||
leftArea = tubeLayout[5]
|
||||
else:
|
||||
leftArea = areaLayout[areaLayout.index(area) - 1]
|
||||
if elevator == 5:
|
||||
rightArea = areaLayout[0]
|
||||
leftArea = tubeLayout[tubeLayout.index(area) - 1]
|
||||
if tubeLayout.index(area) == 5:
|
||||
rightArea = tubeLayout[0]
|
||||
else:
|
||||
rightArea = areaLayout[areaLayout.index(area) + 1]
|
||||
rightArea = tubeLayout[tubeLayout.index(area) + 1]
|
||||
|
||||
if area == 1:
|
||||
topTile = 19
|
||||
bottomTile = 35
|
||||
|
@ -2145,7 +2144,7 @@ def randomize_game(graph):
|
|||
graph.ConnectAllNodes()
|
||||
|
||||
PlacedETanks = PlacedMissiles = PlacedPowerBombs = 0
|
||||
if SeedSettings['DamageRuns']:
|
||||
if SeedSettings['-HAZARDRUNS-']:
|
||||
Energy = PlacedETanks * 100 + 99
|
||||
MissileCount = 10
|
||||
locationWeights.clear()
|
||||
|
@ -2165,7 +2164,7 @@ def randomize_game(graph):
|
|||
oldLocations.clear()
|
||||
oldWeights.clear()
|
||||
MajorLocations.extend(graph.majorItemLocations)
|
||||
if SeedSettings['MajorMinor'] == False:
|
||||
if SeedSettings['-MAJORMINOR-'] == False:
|
||||
MajorLocations.extend(graph.minorItemLocations)
|
||||
MinorLocations.extend(graph.minorItemLocations)
|
||||
AllItemLocations.extend(graph.majorItemLocations)
|
||||
|
@ -2188,22 +2187,22 @@ def randomize_game(graph):
|
|||
weightValue.append(1)
|
||||
|
||||
MajorList = list(WeightedMajors.keys())
|
||||
if SeedSettings['MissilesWithoutMainData']:
|
||||
WeightedMajors.update({'MainMissiles': 8})
|
||||
WeightedMajors.update({'SuperMissileItem': 8})
|
||||
WeightedMajors.update({'IceMissileItem': 8})
|
||||
WeightedMajors.update({'DiffusionItem': 8})
|
||||
if SeedSettings['PowerBombsWithoutBombs']:
|
||||
WeightedMajors.update({'Bombs': 10})
|
||||
WeightedMajors.update({'MainPowerBombs': 10})
|
||||
if SeedSettings['DamageRuns']:
|
||||
WeightedMajors.update({'VariaSuit': 4})
|
||||
if SeedSettings['-MISSILEDATA-']:
|
||||
WeightedMajors.update({'MainMissiles': 4})
|
||||
WeightedMajors.update({'SuperMissileItem': 4})
|
||||
WeightedMajors.update({'IceMissileItem': 4})
|
||||
WeightedMajors.update({'DiffusionItem': 4})
|
||||
if SeedSettings['-PBDATA-']:
|
||||
WeightedMajors.update({'Bombs': 3})
|
||||
WeightedMajors.update({'MainPowerBombs': 3})
|
||||
if SeedSettings['-HAZARDRUNS-']:
|
||||
WeightedMajors.update({'VariaSuit': 3})
|
||||
MajorWeights = list(WeightedMajors.values())
|
||||
MajorWeights.pop(MajorList.index('Bombs'))
|
||||
MajorList.remove('Bombs')
|
||||
MajorWeights.pop(MajorList.index('MainPowerBombs'))
|
||||
MajorList.remove('MainPowerBombs')
|
||||
if SeedSettings['MissilesWithoutMainData'] == False:
|
||||
if SeedSettings['-MISSILEDATA-'] == False:
|
||||
MajorWeights.pop(MajorList.index('IceMissileItem'))
|
||||
MajorList.remove('IceMissileItem')
|
||||
MajorWeights.pop(MajorList.index('SuperMissileItem'))
|
||||
|
@ -2273,7 +2272,7 @@ def randomize_game(graph):
|
|||
MajorList.remove(FirstItem)
|
||||
enable_item(graph, FirstItem)
|
||||
path = None
|
||||
if SeedSettings['DamageRuns']:
|
||||
if SeedSettings['-HAZARDRUNS-']:
|
||||
Energy = Energy / 2
|
||||
while path == None:
|
||||
location = random.choice(AccessibleLocations)
|
||||
|
@ -2288,7 +2287,7 @@ def randomize_game(graph):
|
|||
weightValue[area] += 1
|
||||
|
||||
if FirstItem == 'MainMissiles':
|
||||
if SeedSettings['MissilesWithoutMainData'] == False:
|
||||
if SeedSettings['-MISSILEDATA-'] == False:
|
||||
MajorList.append('IceMissileItem')
|
||||
MajorWeights.append(WeightedMajors.get('IceMissileItem'))
|
||||
MajorList.append('SuperMissileItem')
|
||||
|
@ -2298,17 +2297,17 @@ def randomize_game(graph):
|
|||
elif FirstItem == 'MorphBall':
|
||||
MajorList.append('Bombs')
|
||||
MajorWeights.append(WeightedMajors.get('Bombs'))
|
||||
if SeedSettings['PowerBombsWithoutBombs'] == True:
|
||||
if SeedSettings['-PBDATA-'] == True:
|
||||
MajorList.append('MainPowerBombs')
|
||||
MajorWeights.append(WeightedMajors.get('MainPowerBombs'))
|
||||
elif FirstItem == 'Bombs':
|
||||
if SeedSettings['PowerBombsWithoutBombs'] == False:
|
||||
if SeedSettings['-PBDATA-'] == False:
|
||||
MajorList.append('MainPowerBombs')
|
||||
MajorWeights.append(WeightedMajors.get('MainPowerBombs'))
|
||||
|
||||
init = True
|
||||
|
||||
if SeedSettings['DamageRuns']:
|
||||
if SeedSettings['-HAZARDRUNS-']:
|
||||
Energy = PlacedETanks * 100 + 99
|
||||
|
||||
find_available_areas(graph)
|
||||
|
@ -2344,7 +2343,7 @@ def randomize_game(graph):
|
|||
|
||||
for area in range(0, 7):
|
||||
if len(locationsPerArea[area]) > 0:
|
||||
if SeedSettings['MajorMinor']:
|
||||
if SeedSettings['-MAJORMINOR-']:
|
||||
areaWeights[area] = weightValue[area] / len(locationsPerArea[area])
|
||||
else:
|
||||
areaWeights[area] = weightValue[area] / len(locationsPerArea[area]) + area
|
||||
|
@ -2354,7 +2353,7 @@ def randomize_game(graph):
|
|||
if location in AreaItemLocations[area]:
|
||||
if location in newLocations:
|
||||
locationWeights.append(areaWeights[area] * 1.8)
|
||||
elif location in BossLocations and SeedSettings['MajorMinor'] == False:
|
||||
elif location in BossLocations and SeedSettings['-MAJORMINOR-'] == False:
|
||||
locationWeights.append(areaWeights[area] * 1.5)
|
||||
else:
|
||||
locationWeights.append(areaWeights[area])
|
||||
|
@ -2367,7 +2366,7 @@ def randomize_game(graph):
|
|||
MajorList.remove(item)
|
||||
enable_item(graph, item)
|
||||
|
||||
if SeedSettings['DamageRuns']:
|
||||
if SeedSettings['-HAZARDRUNS-']:
|
||||
Energy = Energy / 2
|
||||
|
||||
while path == None and len(AccessibleLocations) > 0:
|
||||
|
@ -2385,11 +2384,11 @@ def randomize_game(graph):
|
|||
if itemLocation in BossLocations:
|
||||
if path != None:
|
||||
disable_item(graph, item)
|
||||
if ChargeBeam == False and SeedSettings['Difficulty'] > 0:
|
||||
if ChargeBeam == False and SeedSettings['-DIFFICULTY-'] > 0:
|
||||
healthCheck = BossHealths.get(itemLocation)
|
||||
if SeedSettings['Difficulty'] > 4:
|
||||
if SeedSettings['-DIFFICULTY-'] > 4:
|
||||
healthCheck = healthCheck * 1.02
|
||||
elif SeedSettings['Difficulty'] > 2:
|
||||
elif SeedSettings['-DIFFICULTY-'] > 2:
|
||||
healthCheck = healthCheck * 1.05
|
||||
else:
|
||||
healthCheck = healthCheck * 1.1
|
||||
|
@ -2398,7 +2397,7 @@ def randomize_game(graph):
|
|||
if MissileDamage > 0:
|
||||
tankCheck = math.ceil(healthCheck / MissileDamage / 5)
|
||||
PossibleMissileTanks.clear()
|
||||
if SeedSettings['MajorMinor']:
|
||||
if SeedSettings['-MAJORMINOR-']:
|
||||
for missileLocation in MinorLocations:
|
||||
if missileLocation not in UsedLocations:
|
||||
missilePath = None
|
||||
|
@ -2435,7 +2434,7 @@ def randomize_game(graph):
|
|||
for x in range(0, int(tankCheck)):
|
||||
missileLocation = random.choice(PossibleMissileTanks)
|
||||
PossibleMissileTanks.remove(missileLocation)
|
||||
if SeedSettings['MajorMinor'] == False:
|
||||
if SeedSettings['-MAJORMINOR-'] == False:
|
||||
locationWeights.pop(AccessibleLocations.index(missileLocation))
|
||||
AccessibleLocations.remove(missileLocation)
|
||||
UsedLocations.append(missileLocation)
|
||||
|
@ -2487,7 +2486,7 @@ def randomize_game(graph):
|
|||
weightValue[area] += 1
|
||||
|
||||
if item == 'MainMissiles':
|
||||
if SeedSettings['MissilesWithoutMainData'] == False:
|
||||
if SeedSettings['-MISSILEDATA-'] == False:
|
||||
MajorList.append('IceMissileItem')
|
||||
MajorWeights.append(WeightedMajors.get('IceMissileItem'))
|
||||
MajorList.append('SuperMissileItem')
|
||||
|
@ -2497,15 +2496,15 @@ def randomize_game(graph):
|
|||
elif item == 'MorphBall':
|
||||
MajorList.append('Bombs')
|
||||
MajorWeights.append(WeightedMajors.get('Bombs'))
|
||||
if SeedSettings['PowerBombsWithoutBombs'] == True:
|
||||
if SeedSettings['-PBDATA-'] == True:
|
||||
MajorList.append('MainPowerBombs')
|
||||
MajorWeights.append(WeightedMajors.get('MainPowerBombs'))
|
||||
elif item == 'Bombs':
|
||||
if SeedSettings['PowerBombsWithoutBombs'] == False:
|
||||
if SeedSettings['-PBDATA-'] == False:
|
||||
MajorList.append('MainPowerBombs')
|
||||
MajorWeights.append(WeightedMajors.get('MainPowerBombs'))
|
||||
|
||||
if SeedSettings['MajorMinor'] == False:
|
||||
if SeedSettings['-MAJORMINOR-'] == False:
|
||||
if len(AccessibleLocations) > 3:
|
||||
if PlacedETanks < MaxETanks:
|
||||
itemLocation = random.choice(AccessibleLocations)
|
||||
|
@ -2539,7 +2538,7 @@ def randomize_game(graph):
|
|||
UsedLocations.append(location)
|
||||
PlacedItems.append(item)
|
||||
|
||||
if SeedSettings['MajorMinor']:
|
||||
if SeedSettings['-MAJORMINOR-']:
|
||||
for location in MajorLocations:
|
||||
if PlacedETanks < MaxETanks:
|
||||
if location not in UsedLocations:
|
||||
|
@ -2600,6 +2599,107 @@ def patch_game():
|
|||
'BlueDoors': 7657669,
|
||||
'YellowDoors': 7657777,
|
||||
'RedDoors': 7657885 }
|
||||
CreditsNames = {
|
||||
'Item S0-05-08': 'Main Elevator',
|
||||
'Item S0-05-16': 'Restricted Lab',
|
||||
'Item S0-08-0B': 'Main Deck: Spitter Hallway',
|
||||
'Item S0-09-04': 'Habitation Deck',
|
||||
'Item S0-0C-09': 'Quarantine Bay Morph Tunnel',
|
||||
'Item S0-0E-07': 'Main Deck: Power Bomb Storage',
|
||||
'Item S0-0E-0B': 'Main Deck: Maintenance Tunnel',
|
||||
'Item S0-13-07': 'Vent Shaft Hidden Item',
|
||||
'Item S0-14-07': 'Main Deck: Vent Shaft',
|
||||
'Item S0-15-10': 'Reactor Silo Hallway',
|
||||
'Item S0-16-12': 'Lower Reactor Silo',
|
||||
'Item S0-18-06': 'Arachnus Room',
|
||||
'Item S0-19-06': 'Arachnus Alcove',
|
||||
'Item S1-03-0A': 'Ridley Area E-Tank',
|
||||
'Item S1-05-03': 'Sector 1: Across Lava Lake',
|
||||
'Item S1-06-03': 'Sector 1: Lava Lake Center',
|
||||
'Item S1-07-00': 'Sector 1: Connector to S2',
|
||||
'Item S1-07-04': 'Sector 1: Lava Dive',
|
||||
'Item S1-08-0B': 'Ridley Area Golden Pirate',
|
||||
'Item S1-09-04': 'Sector 1: Speed Ceiling',
|
||||
'Item S1-0A-04': 'Charge Core-X Missile Alcove',
|
||||
'Item S1-0C-07': 'Sector 1: Gravity Well',
|
||||
'Item S1-0D-02': 'Sector 1: Entry Hallway',
|
||||
'Item S1-0D-08': 'Sector 1: Crab Pond',
|
||||
'Item S1-11-02': 'Sector 1: Wall Jump Climb',
|
||||
'Item S2-00-05': 'Sector 2: Data Room Escape',
|
||||
'Item S2-02-0E': 'Sector 2: Blue Zoro Room',
|
||||
'Item S2-03-0C': 'Sector 2: Wonderwall',
|
||||
'Item S2-04-03': 'Sector 2: Kago Hallway',
|
||||
'Item S2-04-04': 'Sector 2: Data Room Access',
|
||||
'Item S2-04-0B': 'Sector 2: Oasis',
|
||||
'Item S2-05-00': 'Crumble City Upper Item',
|
||||
'Item S2-05-01': 'Crumble City Lower Item',
|
||||
'Item S2-05-08': 'Ripper Tower Upper Item',
|
||||
'Item S2-05-0A': 'Ripper Tower Lower Item',
|
||||
'Item S2-08-08': 'Sector 2: Puyo Tank',
|
||||
'Item S2-09-03': 'Sector 2: Blue Door Owtches',
|
||||
'Item S2-09-05': 'Sector 2: Hub Morph Tunnel',
|
||||
'Item S2-0C-04': 'Sector 2: Nettori Owtches',
|
||||
'Item S2-0C-0B': 'Sector 2: Zazabi Access',
|
||||
'Item S2-10-0C': 'Zazabi Speedway Upper Item',
|
||||
'Item S2-10-0E': 'Zazabi Speedway Lower Item',
|
||||
'Item S3-00-05': 'Sector 3: Connector to S5',
|
||||
'Item S3-01-02': 'Sector 3: Shinespark Puzzle',
|
||||
'Item S3-03-04': 'Sector 3: Lava Hall Access',
|
||||
'Item S3-06-06': 'Sector 3: Security Access',
|
||||
'Item S3-07-0B': 'Sector 3: Magma Dive',
|
||||
'Item S3-0A-01': 'Sector 3: Hidden PB Alcove',
|
||||
'Item S3-0B-02': 'Sector 3: Sidehopper Hallway',
|
||||
'Item S3-0B-04': 'Sector 3: Bob Tank',
|
||||
'Item S3-0B-06': 'Sector 3: Power Bomb Fune',
|
||||
'Item S3-0E-0A': 'Sector 3: Lava Pool Access',
|
||||
'Item S3-0F-00': 'Sector 3: BOX Attic',
|
||||
'Item S3-11-04': 'Sector 3: BOX Basement',
|
||||
'Item S3-11-0A': 'Sector 3: Lower Nova Stairway',
|
||||
'Item S3-12-09': 'Sector 3: Upper Nova Stairway',
|
||||
'Item S3-14-03': 'Sector 3: Upper Trash Chute',
|
||||
'Item S3-14-09': 'Sector 3: Lower Trash Chute',
|
||||
'Item S4-00-06': 'Sector 4: Pump Control',
|
||||
'Item S4-05-03': 'Sector 4: Owtch Room',
|
||||
'Item S4-06-0E': 'Sector 4: Crab Battle',
|
||||
'Item S4-07-08': 'Lower S4: Screw Block Alcove',
|
||||
'Item S4-07-0A': 'Lower S4: Security Access',
|
||||
'Item S4-09-02': 'Sector 4: Collapsed Ceiling',
|
||||
'Item S4-09-06': 'S4: Speed Booster Aquarium',
|
||||
'Item S4-0A-0C': 'Sector 4: Pirate Fish Tank',
|
||||
'Item S4-0B-08': 'Sector 4: Snail Hallway',
|
||||
'Item S4-0C-06': 'Sector 4: Electric PB Tank',
|
||||
'Item S4-0D-01': 'Serris Escape, Upper Item',
|
||||
'Item S4-0E-02': 'Serris Escape, Lower Item',
|
||||
'Item S4-0F-06': 'Sector 4: Underwater Gate',
|
||||
'Item S4-12-07': 'Sector 4: Powamp Path',
|
||||
'Item S4-13-07': 'Sector 4: Coral Speed Boost',
|
||||
'Item S5-03-04': 'Sector 5: Connector to S6',
|
||||
'Item S5-04-01': 'Sector 5: Speed Wall',
|
||||
'Item S5-05-01': 'Sector 5: Choot Climb',
|
||||
'Item S5-05-04': 'Sector 5: Gerubus Room',
|
||||
'Item S5-06-05': 'Sector 5: Ripper Hallway',
|
||||
'Item S5-07-0B': 'Sector 5: Security Access',
|
||||
'Item S5-08-07': 'Sector 5: Ripper Climb',
|
||||
'Item S5-0B-01': "Sector 5: Crow's Nest",
|
||||
'Item S5-0C-07': 'Sector 5: E-Tank Mimic',
|
||||
'Item S5-0E-08': 'Sector 5: Ripper Spawn Puzzle',
|
||||
'Item S5-0F-07': 'Sector 5: Minifridge',
|
||||
'Item S5-11-05': 'Sector 5: Crab Room',
|
||||
'Item S5-12-04': 'Sector 5: Nightmare Recharge',
|
||||
'Item S5-14-07': 'Sector 5: Sector 4 Access',
|
||||
'Item S5-16-04': 'Sector 5: Nightmare Drop',
|
||||
'Item S6-01-06': 'Sector 6: Shinespark Lower',
|
||||
'Item S6-03-04': 'Sector 6: Shinespark Upper',
|
||||
'Item S6-05-03': 'Sector 6: Entrance Tunnel',
|
||||
'Item S6-05-0B': 'Sector 6: Data Access Alcove',
|
||||
'Item S6-06-08': 'Sector 6: Power Bomb Wall',
|
||||
'Item S6-08-03': 'Sector 6: Bomb Chain Alcove',
|
||||
'Item S6-09-05': 'Sector 6: Owtch Puzzle',
|
||||
'Item S6-0A-06': 'Sector 6: Wave Gate',
|
||||
'Item S6-0B-09': 'Sector 6: Mega Core-X Attic',
|
||||
'Item S6-0C-08': 'Sector 6: Blue X Climb',
|
||||
'Item S6-0E-03': 'Sector 6: Missile Mimic',
|
||||
'Item S6-0E-04': 'Sector 6: Pillar Highway' }
|
||||
print('Patching game, please wait')
|
||||
HashValue = random.sample(HashList, 4)
|
||||
FileName = 'OpenMFOR -'
|
||||
|
@ -2626,11 +2726,12 @@ def patch_game():
|
|||
print('Error: failed to patch game with base patch!')
|
||||
sys.exit(1)
|
||||
|
||||
checksum = fileHash(os.path.join('.', 'seeds', '{}.gba'.format(FileName)))
|
||||
if checksum != 2455114263:
|
||||
os.remove(os.path.join('.', 'seeds', '{}.gba'.format(FileName)))
|
||||
print('Error: Base patch file has been modified. Please go to the repository you picked this from and re-download OpenMFOR.')
|
||||
sys.exit(1)
|
||||
# FIXME: no gatekeeping my scams
|
||||
# checksum = file_hash(os.path.join('.', 'seeds', '{}.gba'.format(FileName)))
|
||||
# if checksum != 2455114263:
|
||||
# os.remove(os.path.join('.', 'seeds', '{}.gba'.format(FileName)))
|
||||
# print('Error: Base patch file has been modified. Please go to the repository you picked this from and re-download OpenMFOR.')
|
||||
# sys.exit(1)
|
||||
|
||||
with open(os.path.join('.', 'seeds', '{}.gba'.format(FileName)), 'rb+') as patchedGame:
|
||||
if Debug:
|
||||
|
@ -2645,15 +2746,24 @@ def patch_game():
|
|||
roomEventOffset = int(sym.get('t_bossanddownloadevents'), 16)
|
||||
itemEventOffset = int(sym.get('t_obtainitemevents'), 16)
|
||||
securityOffset = int(sym.get('b_unlocklowerlevels'), 16)
|
||||
print('roomEventOffset: 0x{:06X}'.format(roomEventOffset))
|
||||
print('itemEventOffset: 0x{:06X}'.format(itemEventOffset))
|
||||
print('securityOffset: 0x{:06X}'.format(securityOffset))
|
||||
saxAnyOffset = int(sym.get('@t_saxany'), 16)
|
||||
spawnBox = int(sym.get('@spawnbox'), 16)
|
||||
spawnMegaCoreX = int(sym.get('@spawnmegacorex'), 16)
|
||||
print('Debug values')
|
||||
print(f'''roomEventOffset={roomEventOffset:06X}''')
|
||||
print(f'''itemEventOffset={itemEventOffset:06X}''')
|
||||
print(f'''securityOffset={securityOffset:06X}''')
|
||||
print(f'''saxAnyOffset={saxAnyOffset:06X}''')
|
||||
print(f'''spawnBox={spawnBox:06X}''')
|
||||
print(f'''spawnMegaCoreX={spawnMegaCoreX:06X}''')
|
||||
else:
|
||||
roomEventOffset = 8326320
|
||||
roomEventOffset = 8326304
|
||||
itemEventOffset = 5726112
|
||||
securityOffset = 479192
|
||||
saxAnyOffset = 395796
|
||||
spawnBox = 8326096
|
||||
spawnMegaCoreX = 8326260
|
||||
for area in RoomNodes:
|
||||
areaIndex = list(RoomNodes.keys()).index(area)
|
||||
for node in RoomNodes[area]:
|
||||
name = node.get('Name')
|
||||
nodeType = node.get('Type')
|
||||
|
@ -2664,7 +2774,7 @@ def patch_game():
|
|||
bg1 = int(node.get('BG1'), 16)
|
||||
clipdata = int(node.get('Clipdata'), 16)
|
||||
tileset = int(node.get('Tileset'), 16)
|
||||
if SeedSettings['HideItems']:
|
||||
if SeedSettings['-HIDEITEMS-']:
|
||||
if tileset == 33:
|
||||
blockValue = 7
|
||||
elif tileset == 34:
|
||||
|
@ -2677,6 +2787,7 @@ def patch_game():
|
|||
blockValue = 3
|
||||
else:
|
||||
blockValue = ItemList.index(itemName)
|
||||
|
||||
if blockValue < 2:
|
||||
blockValue = blockValue ^ 1
|
||||
elif blockValue > 2:
|
||||
|
@ -2690,6 +2801,7 @@ def patch_game():
|
|||
blockValue = 7
|
||||
else:
|
||||
blockValue = 3
|
||||
|
||||
blockValue += 70
|
||||
if tileset == 9:
|
||||
blockValue += 1
|
||||
|
@ -2721,6 +2833,7 @@ def patch_game():
|
|||
blockValue += 1
|
||||
elif tileset == 72:
|
||||
blockValue += 1
|
||||
|
||||
clipValue = ItemList.index(itemName)
|
||||
if clipValue < 2:
|
||||
# Energy Tank or Missile Tank
|
||||
|
@ -2741,18 +2854,21 @@ def patch_game():
|
|||
clipValue += 1
|
||||
elif 'Underwater' in nodeType:
|
||||
clipValue += 2
|
||||
|
||||
if 'Hidden' not in nodeType:
|
||||
patchedGame.seek(bg1)
|
||||
patchedGame.write(blockValue.to_bytes(1, 'little'))
|
||||
|
||||
patchedGame.seek(clipdata)
|
||||
patchedGame.write(clipValue.to_bytes(1, 'little'))
|
||||
elif 'Boss' in nodeType or 'Data' in nodeType:
|
||||
|
||||
if 'Boss' in nodeType or 'Data' in nodeType:
|
||||
itemName = PlacedItems[UsedLocations.index(name)]
|
||||
itemValue = ItemList.index(itemName)
|
||||
slot = BossDataList.index(name)
|
||||
roomEvent = roomEventOffset
|
||||
if roomEvent != None:
|
||||
roomEvent = roomEvent + 3 + slot*4
|
||||
roomEvent = roomEvent + 3 + slot * 4
|
||||
if itemValue < 3:
|
||||
itemValue += 1
|
||||
else:
|
||||
|
@ -2774,22 +2890,28 @@ def patch_game():
|
|||
offset = CreditsOffsets.get(itemName)
|
||||
if 'Boss' in nodeType:
|
||||
location = name
|
||||
elif name in CreditsNames:
|
||||
location = CreditsNames.get(name)
|
||||
elif 'S0' in name:
|
||||
location = 'Main Deck : '
|
||||
else:
|
||||
if 'S0' in name:
|
||||
location = 'Main Deck : '
|
||||
else:
|
||||
location = 'Sector {} : '.format(name[6:7])
|
||||
if 'Tank' in nodeType:
|
||||
location = location + name[8:]
|
||||
else:
|
||||
location = location + nodeType + ' Room'
|
||||
location = 'Sector {} : '.format(name[6:7])
|
||||
|
||||
spaces = ceiling(30 - len(location), 2)
|
||||
location = ' '*spaces + location
|
||||
location = ' ' * spaces + location
|
||||
patchedGame.seek(offset)
|
||||
patchedGame.write(location.encode('ascii'))
|
||||
for x in range(len(location), 35):
|
||||
patchedGame.write((0).to_bytes(1, 'little'))
|
||||
if SeedSettings['HideItems']:
|
||||
|
||||
patchedGame.seek(5726109)
|
||||
patchedGame.write(PlacedETanks.to_bytes(1, 'little'))
|
||||
patchedGame.seek(5726110)
|
||||
patchedGame.write(PlacedMissiles.to_bytes(1, 'little'))
|
||||
patchedGame.seek(5726111)
|
||||
patchedGame.write(PlacedPowerBombs.to_bytes(1, 'little'))
|
||||
|
||||
if SeedSettings['-HIDEITEMS-']:
|
||||
patchedGame.seek(3926048)
|
||||
patchedGame.write((76).to_bytes(2, 'little'))
|
||||
patchedGame.write((77).to_bytes(2, 'little'))
|
||||
|
@ -2803,23 +2925,34 @@ def patch_game():
|
|||
patchedGame.write((77).to_bytes(2, 'little'))
|
||||
patchedGame.write((78).to_bytes(2, 'little'))
|
||||
patchedGame.write((79).to_bytes(2, 'little'))
|
||||
if SeedSettings['SplitSecurity'] == True:
|
||||
security = securityOffset
|
||||
patchedGame.seek(security)
|
||||
if SeedSettings['-SPLITSECURITY-'] == True:
|
||||
patchedGame.seek(securityOffset)
|
||||
patchedGame.write((0).to_bytes(2, 'little'))
|
||||
if SeedSettings['MissilesWithoutMainData']:
|
||||
if SeedSettings['-MISSILEDATA-']:
|
||||
patchedGame.seek(24828)
|
||||
patchedGame.write((15).to_bytes(1, 'little'))
|
||||
patchedGame.seek(395742)
|
||||
patchedGame.seek(saxAnyOffset + 1)
|
||||
patchedGame.write((15).to_bytes(1, 'little'))
|
||||
patchedGame.seek(465582)
|
||||
patchedGame.write((15).to_bytes(1, 'little'))
|
||||
if SeedSettings['PowerBombsWithoutBombs']:
|
||||
patchedGame.seek(spawnBox + 13)
|
||||
patchedGame.write((2).to_bytes(1, 'little'))
|
||||
patchedGame.seek(spawnBox + 21)
|
||||
patchedGame.write((4).to_bytes(1, 'little'))
|
||||
patchedGame.seek(spawnBox + 29)
|
||||
patchedGame.write((8).to_bytes(1, 'little'))
|
||||
patchedGame.seek(spawnMegaCoreX + 13)
|
||||
patchedGame.write((2).to_bytes(1, 'little'))
|
||||
patchedGame.seek(spawnMegaCoreX + 21)
|
||||
patchedGame.write((4).to_bytes(1, 'little'))
|
||||
patchedGame.seek(spawnMegaCoreX + 29)
|
||||
patchedGame.write((8).to_bytes(1, 'little'))
|
||||
if SeedSettings['-PBDATA-']:
|
||||
patchedGame.seek(24756)
|
||||
patchedGame.write((32).to_bytes(1, 'little'))
|
||||
patchedGame.seek(465672)
|
||||
patchedGame.write((32).to_bytes(1, 'little'))
|
||||
if SeedSettings['SectorShuffle'] == True:
|
||||
if SeedSettings['-SHUFFLESECTORS-'] == True or SeedSettings['-SHUFFLETUBES-'] == True:
|
||||
for currentArea in range(7):
|
||||
patchedGame.seek(7977108 + currentArea * 4, 0)
|
||||
data = patchedGame.read(4)
|
||||
|
@ -2857,6 +2990,10 @@ def patch_game():
|
|||
value = World.patcher.get(target)
|
||||
patchedGame.seek(target)
|
||||
patchedGame.write(value.to_bytes(1, 'little'))
|
||||
|
||||
game = bytearray((Path.cwd() / 'seeds' / f'''{FileName}.gba''').read_bytes())
|
||||
Fusion_Palette_Shuffle.randomize_palettes(game, SeedValue + str(SeedSettings), palette_tilesets, palette_sprites, palette_suits, palette_beams)
|
||||
(Path.cwd() / 'seeds' / f'''{FileName}.gba''').write_bytes(game)
|
||||
|
||||
if Patch:
|
||||
if os.path.exists(os.path.join('.', 'flips', 'flips.exe')):
|
||||
|
@ -2887,27 +3024,33 @@ def patch_game():
|
|||
'Wave Beam',
|
||||
'Screw Attack',
|
||||
'Ice Beam']
|
||||
if SeedSettings['RaceSeed'] == False:
|
||||
if SeedSettings['-RACE-'] == False:
|
||||
spoilerLog = dict()
|
||||
spoilerLog.update({ 'OpenMFOR Version': version })
|
||||
spoilerLog.update({ 'Seed': SeedValue })
|
||||
settingsDict = dict()
|
||||
settingsDict.update({ 'Difficulty': SeedSettings['Difficulty'] })
|
||||
if SeedSettings['MajorMinor'] == False:
|
||||
settingsDict.update({ 'Difficulty': SeedSettings['-DIFFICULTY-'] })
|
||||
if SeedSettings['-MAJORMINOR-'] == False:
|
||||
settingsDict.update({ 'Item pool': 'Major items anywhere' })
|
||||
else:
|
||||
settingsDict.update({ 'Item pool': 'Limited major item locations' })
|
||||
settingsDict.update({ 'Missile upgrades enable Missiles': SeedSettings['MissilesWithoutMainData'] })
|
||||
settingsDict.update({ 'Power Bombs without normal Bombs': SeedSettings['PowerBombsWithoutBombs'] })
|
||||
settingsDict.update({ 'Allow logical damage runs': SeedSettings['DamageRuns'] })
|
||||
settingsDict.update({ 'Separated security levels': SeedSettings['SplitSecurity'] })
|
||||
settingsDict.update({ 'Sector shuffle': SeedSettings['SectorShuffle'] })
|
||||
if SeedSettings['SectorShuffle'] == True:
|
||||
settingsDict.update({ 'Missile upgrades enable Missiles': SeedSettings['-MISSILEDATA-'] })
|
||||
settingsDict.update({ 'Power Bombs without normal Bombs': SeedSettings['-PBDATA-'] })
|
||||
settingsDict.update({ 'Allow logical damage runs': SeedSettings['-HAZARDRUNS-'] })
|
||||
settingsDict.update({ 'Separated security levels': SeedSettings['-SPLITSECURITY-'] })
|
||||
settingsDict.update({ 'Sector shuffle': SeedSettings['-SHUFFLESECTORS-'] })
|
||||
if SeedSettings['-SHUFFLESECTORS-'] == True:
|
||||
sectorLayout = str()
|
||||
for x in areaLayout:
|
||||
sectorLayout = sectorLayout.strip() + ' {}'.format(x)
|
||||
settingsDict.update({ 'Sector layout:': sectorLayout })
|
||||
settingsDict.update({ 'Hide item graphics': SeedSettings['HideItems'] })
|
||||
settingsDict.update({ 'Tube shuffle': SeedSettings['-SHUFFLETUBES-'] })
|
||||
if SeedSettings['-SHUFFLETUBES-'] == True:
|
||||
tubeStr = str()
|
||||
for x in tubeLayout:
|
||||
tubeStr = tubeStr.strip() + f''' {x}'''
|
||||
settingsDict.update({ 'Tube layout:': tubeStr })
|
||||
settingsDict.update({ 'Hide item graphics': SeedSettings['-HIDEITEMS-'] })
|
||||
settingsDict.update({ 'E-Tanks': PlacedETanks })
|
||||
settingsDict.update({ 'Missile Tanks': PlacedMissiles })
|
||||
settingsDict.update({ 'Power Bomb Tanks': PlacedPowerBombs })
|
||||
|
@ -3008,7 +3151,7 @@ def initialize():
|
|||
'Nettori',
|
||||
'Nightmare',
|
||||
'Data S4',
|
||||
'Box-2',
|
||||
'BOX-2',
|
||||
'Ridley',
|
||||
'Omega Metroid']
|
||||
ItemList = MinorItems + MajorItems
|
||||
|
@ -3029,37 +3172,45 @@ def start_randomizer(rom, settings):
|
|||
global SeedValue
|
||||
global StartLocation
|
||||
global World
|
||||
global palette_tilesets
|
||||
global palette_sprites
|
||||
global palette_beams
|
||||
global palette_suits
|
||||
|
||||
BaseGame = rom
|
||||
if BaseGame == None or BaseGame == '':
|
||||
print('Error: no base game provided.')
|
||||
sys.exit(1)
|
||||
checksum = fileHash(BaseGame)
|
||||
checksum = file_hash(BaseGame)
|
||||
if checksum != 1819625372:
|
||||
print('Only Metroid Fusion (U) is supported. Check the CRC32 value: it should be 6C75479C')
|
||||
sys.exit(1)
|
||||
Debug = settings['Debug']
|
||||
settings.pop('Debug')
|
||||
Debug = settings.pop('Debug')
|
||||
palette_tilesets = settings.pop('-PALTILESETS-')
|
||||
palette_sprites = settings.pop('-PALSPRITES-')
|
||||
palette_beams = settings.pop('-PALBEAMS-')
|
||||
palette_suits = settings.pop('-PALSUITS-')
|
||||
if Debug == False:
|
||||
checksum = fileHash(os.path.join('.', 'data', 'MFOR.bps'))
|
||||
checksum = file_hash(os.path.join('.', 'data', 'MFOR.bps'))
|
||||
if checksum != 558161692:
|
||||
print('Error: Base patch file has been modified. Please go to the repository you picked this from and re-download OpenMFOR.')
|
||||
sys.exit(1)
|
||||
totalRandoTime = time.time()
|
||||
if settings['Seed']:
|
||||
SeedValue = str(settings['Seed']).strip(' \n')
|
||||
totalRandoTime = time.perf_counter()
|
||||
if settings['-SEED-']:
|
||||
SeedValue = str(settings['-SEED-']).strip(' \n')
|
||||
else:
|
||||
SeedValue = str(random.randrange(sys.maxsize))
|
||||
settings.pop('Seed')
|
||||
settings.pop('-SEED-')
|
||||
repeat = 1
|
||||
if settings['Num']:
|
||||
if settings['Num'] > repeat:
|
||||
repeat = settings['Num']
|
||||
settings.pop('Num')
|
||||
if settings['Patch']:
|
||||
if settings['-NUM-']:
|
||||
if settings['-NUM-'] > repeat:
|
||||
repeat = settings['-NUM-']
|
||||
settings.pop('-NUM-')
|
||||
if settings['-PATCH-']:
|
||||
Patch = True
|
||||
else:
|
||||
Patch = False
|
||||
settings.pop('Patch')
|
||||
settings.pop('-PATCH-')
|
||||
|
||||
for loop in range(repeat):
|
||||
initialize()
|
||||
|
@ -3069,8 +3220,8 @@ def start_randomizer(rom, settings):
|
|||
if loop > 0:
|
||||
SeedValue = str(random.randrange(sys.maxsize))
|
||||
|
||||
Difficulty = SeedSettings['Difficulty']
|
||||
DamageRuns = SeedSettings['DamageRuns']
|
||||
Difficulty = SeedSettings['-DIFFICULTY-']
|
||||
DamageRuns = SeedSettings['-HAZARDRUNS-']
|
||||
random.seed(SeedValue + str(SeedSettings))
|
||||
World = Graph.Game(BaseGame)
|
||||
World.RemoveNodeFromRoom('Room-S2-07', 'S2-10')
|
||||
|
@ -3123,13 +3274,14 @@ def start_randomizer(rom, settings):
|
|||
|
||||
World.ConnectAllNodes()
|
||||
StartLocation = 'S0-00'
|
||||
startTime = time.time()
|
||||
startTime = time.perf_counter()
|
||||
randomize_game(World)
|
||||
seedTime = time.time() - startTime
|
||||
seedTime = time.perf_counter() - startTime
|
||||
print(str(FileName))
|
||||
print('Randomized in:', seedTime)
|
||||
print(f'''Randomized in: {round(seedTime, 3)} seconds''')
|
||||
|
||||
totalRandoTime = time.time() - totalRandoTime
|
||||
print('All seeds took:', totalRandoTime)
|
||||
totalRandoTime = round(time.perf_counter() - totalRandoTime, 3)
|
||||
if repeat > 1:
|
||||
print(f'''All seeds took: {totalRandoTime} seconds''')
|
||||
return FileName
|
||||
|
||||
|
|
BIN
data/MFOR.bps
BIN
data/MFOR.bps
Binary file not shown.
BIN
data/MFOR.ico
Normal file
BIN
data/MFOR.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 728 KiB |
|
@ -1017,7 +1017,7 @@
|
|||
"Type": "Boss"
|
||||
},
|
||||
{
|
||||
"Name": "Box-2",
|
||||
"Name": "BOX-2",
|
||||
"Room": "0x10",
|
||||
"Type": "Boss"
|
||||
}
|
||||
|
|
2
flips
2
flips
|
@ -1 +1 @@
|
|||
Subproject commit fdd5c6e34285beef5b9be759c9b91390df486c66
|
||||
Subproject commit e12ef189900b2c720c6dcd55036a8bb43925ea53
|
Loading…
Add table
Add a link
Reference in a new issue