-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrandomizer.py
More file actions
2447 lines (2062 loc) · 102 KB
/
randomizer.py
File metadata and controls
2447 lines (2062 loc) · 102 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python3
import configparser
from time import time, sleep, gmtime
import re
import sys
from sys import argv
from shutil import copyfile
import os
from hashlib import md5
#added for formation management
from typing import BinaryIO, Callable, Dict, List, Set, Tuple
#added for musicrandomizer
import music.musicrandomizer
#^ added for musicrandomizer
#added for command management
import character
from character import get_characters, get_character, equip_offsets
#^ added for command management
#added for enemy sprite swap
import multiprocessing
from remonsterate.remonsterate import remonsterate
#^ added for enemy sprite swap
from formationrandomizer import (REPLACE_FORMATIONS, KEFKA_EXTRA_FORMATION,
NOREPLACE_FORMATIONS, get_formations,
get_fsets, get_formation, Formation,
FormationSet)
from itemrandomizer import (reset_equippable, get_ranked_items, get_item,
reset_special_relics, reset_rage_blizzard,
reset_cursed_shield, unhardcode_tintinabar,
ItemBlock)
from locationrandomizer import (get_locations, get_location, get_zones, get_npcs, randomize_forest)
from menufeatures import (improve_item_display, improve_gogo_status_menu, improve_rage_menu,
show_original_names, improve_dance_menu, y_equip_relics, fix_gogo_portrait)
from monsterrandomizer import (MonsterBlock, REPLACE_ENEMIES, MonsterGraphicBlock, get_monsters,
get_metamorphs, get_ranked_monsters,
shuffle_monsters, get_monster, read_ai_table,
change_enemy_name, randomize_enemy_name,
get_collapsing_house_help_skill)
from musicinterface import randomize_music, manage_opera, get_music_spoiler, music_init#, get_opera_log
from skillrandomizer import (SpellBlock, CommandBlock, SpellSub, ComboSpellSub,
RandomSpellSub, MultipleSpellSub, ChainSpellSub,
get_ranked_spells, get_spell)
from options import ALL_MODES, ALL_FLAGS, Options_
from patches import (allergic_dog, banon_life3, vanish_doom, evade_mblock,
death_abuse, no_kutan_skip, show_coliseum_rewards,
cycle_statuses, no_dance_stumbles, fewer_flashes)
from utils import (COMMAND_TABLE, LOCATION_TABLE, LOCATION_PALETTE_TABLE,
FINAL_BOSS_AI_TABLE, SKIP_EVENTS_TABLE, DANCE_NAMES_TABLE,
DIVERGENT_TABLE,
get_long_battle_text_pointer,
Substitution, shorttexttable, name_to_bytes,
hex2int, int2bytes, read_multi, write_multi,
generate_swapfunc, shift_middle, get_palette_transformer,
battlebg_palettes, set_randomness_multiplier,
mutate_index, utilrandom as random, open_mei_fallback,
AutoLearnRageSub)
#added for musicrandomizer
from musicinterface import randomize_music, manage_opera, get_music_spoiler, music_init
#^ added for musicrandomizer
VERSION = "4"
BETA = False
VERSION_ROMAN = "IV"
if BETA:
VERSION_ROMAN += " BETA"
TEST_ON = False
TEST_SEED = "44.abcefghijklmnopqrstuvwxyz-partyparty.42069"
TEST_FILE = "program.rom"
seed, flags = None, None
seedcounter = 1
sourcefile, outfile = None, None
fout = None
NEVER_REPLACE = ["fight", "item", "magic", "row", "def", "magitek", "lore",
"jump", "mimic", "xmagic", "summon", "morph", "revert"]
RESTRICTED_REPLACE = ["throw", "steal"]
ALWAYS_REPLACE = ["leap", "possess", "health", "shock"]
FORBIDDEN_COMMANDS = ["leap", "possess"]
MD5HASH = "e986575b98300f721ce27c180264d890"
TEK_SKILLS = (
# [0x18, 0x6E, 0x70, 0x7D, 0x7E] +
list(range(0x86, 0x8B)) +
[0xA7, 0xB1] +
list(range(0xB4, 0xBA)) +
[0xBF, 0xCD, 0xD1, 0xD4, 0xD7, 0xDD, 0xE3])
namelocdict = {}
changed_commands = set([])
randlog = {}
####################WC - global variables for options
#itemstats1 = "n"
#itemstats2 = "n"
#itembreakproc = "n"
#itemteacher = "n"
#itemelemental = "n"
#itemspecial = "n"
#itemfeature = "n"
#itemheavy = "n"
#item_wild_breaks = "n"
#item_extra_effects = "n"
#monsterstats1 = "n"
#monsterstats2 = "n"
#monstermisc = "n"
#monsterautostatus = "n"
#monsterelemental = "n"
#monsterspecials = "n"
#monsters_darkworld = "n"
#monsterscripts = "n"
#monstercontrol = "n"
#monsterdrops = "n"
#monstersteals = "n"
#monstermorphs = "n"
#command_no_guarantee_itemmagic = "n"
#command_more_duplicates = "n"
####################WC - global variables for options
def log(text, section):
global randlog
if section not in randlog:
randlog[section] = []
if "\n" in text:
text = text.split("\n")
text = "\n".join([line.rstrip() for line in text])
text = text.strip()
randlog[section].append(text)
def get_logstring(ordering: List = None) -> str:
global randlog
s = ""
if ordering is None:
ordering = sorted([o for o in randlog if o is not None])
ordering = [o for o in ordering if o is not None]
for d in randlog[None]:
s += d + "\n"
s += "\n"
sections_with_content = []
for section in ordering:
if section in randlog:
sections_with_content.append(section)
s += "-{0:02d}- {1}\n".format(len(sections_with_content), " ".join([word.capitalize()
for word in section.split()]))
for sectnum, section in enumerate(sections_with_content):
datas = sorted(randlog[section])
s += "\n" + "=" * 60 + "\n"
s += "-{0:02d}- {1}\n".format(sectnum + 1, section.upper())
s += "-" * 60 + "\n"
newlines = False
if any("\n" in d for d in datas):
s += "\n"
newlines = True
for d in datas:
s += d.strip() + "\n"
if newlines:
s += "\n"
return s.strip()
def reseed():
global seedcounter
random.seed(seed + seedcounter)
seedcounter += (seedcounter * 2) + 1
def rewrite_checksum(filename=None):
if filename is None:
filename = outfile
MEGABIT = 0x20000
f = open(filename, 'r+b')
subsums = [sum(f.read(MEGABIT)) for _ in range(32)]
checksum = sum(subsums) & 0xFFFF
f.seek(0xFFDE)
write_multi(f, checksum, length=2)
f.seek(0xFFDC)
write_multi(f, checksum ^ 0xFFFF, length=2)
f.close()
class FreeBlock:
def __init__(self, start, end):
self.start = start
self.end = end
@property
def size(self):
return self.end - self.start
def unfree(self, start, length):
end = start + length
if start < self.start:
raise Exception("Used space out of bounds (left)")
if end > self.end:
raise Exception("Used space out of bounds (right)")
newfree = []
if self.start != start:
newfree.append(FreeBlock(self.start, start))
if end != self.end:
newfree.append(FreeBlock(end, self.end))
self.start, self.end = None, None
return newfree
def get_appropriate_freespace(freespaces, size):
candidates = [c for c in freespaces if c.size >= size]
if not candidates:
raise Exception("Not enough free space")
candidates = sorted(candidates, key=lambda f: f.size)
return candidates[0]
def determine_new_freespaces(freespaces, myfs, size):
freespaces.remove(myfs)
fss = myfs.unfree(myfs.start, size)
freespaces.extend(fss)
return freespaces
#######original ultimate weapons patch. moved over to WC for now, which allows for WC equippability randomization to occur
#######prior to BC mods
####Look for:
####RegalCutlass, Crystal, Stout Spear, Mithril Claw, Kodachi, Kotetsu, Forged, Darts, Epee, Punisher, DaVinci Brsh, Boomerang
def ultimates(items: List[ItemBlock], changed_commands: Set[int]=None) -> List[ItemBlock]:
from itemrandomizer import (set_item_changed_commands, extend_item_breaks)
for i in items:
convert = False
#TERRA, LOCKE, CYAN, SHADOW 0-3
#EDGAR, SABIN, CELES, STRAGO, RELM 4-8
#SETZER, MOG, GAU, GOGO, UMARO 9-13
#specify equippability:
#i.equippable = 32769 #Terra
#i.equippable = 32770 #Locke
#i.equippable = 32772 #Cyan
#32776 #Shadow
#32784 #Edgar
#32800 #Sabin
#32832 #Celes
#32896 #Strago
#33024 #Relm
#33280 #Setzer
#33792 #Mog
#34816 #Gau
#36864 #Gogo
#40960 #Umaro
#Item name symbol:
#Dirk - knife / shadow knife 216
#MithrilBlade - sword 217
#Partisan - spear 218
#ashura - katana 219
#heal rod - rod 220
#chocobo brsh - brush 221
#morning star - special 222
#cards - cards 223
#metalknuckle - claw 224
#weapon animation
#Dirk: [39, 39, 24, 62, 54, 0, 148, 0]
#Illumina: [92, 92, 32, 1, 55, 0, 164, 0]
#Aura Lance: [93, 93, 43, 5, 51, 0, 168, 0]
#Chocobo Brsh: [22, 22, 37, 4, 55, 0, 148, 0]
#Flail: [25, 25, 25, 8, 25, 0, 218, 0]
#Ragnarok: [30, 30, 32, 0, 55, 0, 164, 0]
if i.name == "RegalCutlass":
i.features['power'] = 250
i.features['hitmdef'] = 150
i.features['speedvigor'] = 7 #+7 Str
i.features['magstam'] = 112 #+7 Mag
i.features['specialaction'] = 112 #Punisher effect, MP Crit.
evade = 2
mblock = 2
i.features['mblockevade'] = evade | (mblock << 4) #20% evade, 20% mblock
i.weapon_animation = bytes([92, 92, 32, 1, 55, 0, 164, 0]) #change animation to sword/illumina
i.name = "Apocalypse"
i.dataname[1:] = name_to_bytes(i.name, 12)
convert = True
if i.name == "Crystal":
i.features['power']=220
i.features['hitmdef']=180
i.features['speedvigor'] = 115 #+3 Str +7 Spd
i.features['magstam'] = 3 #+3 Sta
evade = 3
mblock = 2
i.features['mblockevade'] = evade | (mblock << 4) #30% evade, 20% mblock
i.features['elements'] = 16 #wind element
i.features['breakeffect'] = 29 #0 = Fire, 1 = Ice, etc. Sleep = 29
i.features["otherproperties"] = 198 #198 is the proc + swdtech/allows two hands/runic, shifted from vanilla
i.name = "ZwillXBlade"
i.weapon_animation = bytes([39, 39, 24, 62, 54, 0, 148, 0]) #change animation to dagger
i.dataname[1:] = name_to_bytes(i.name, 12)
i.dataname[0:1] = bytes([216]) #change icon to dagger
convert = True
if i.name == "Stout Spear":
i.features['power'] = 235
i.features['hitmdef'] = 150
i.features['speedvigor'] = 55 #+7 Str +3 Spd
i.features['magstam'] = 3 #+3 Sta
i.name = "Longinus"
i.dataname[1:] = name_to_bytes(i.name, 12)
convert = True
#if i.name == "Mithril Claw": #even though there is a space in the game name the space is removed in BC
if i.name == "MithrilClaw":
i.features['power'] = 245
i.features['hitmdef'] = 150
i.features['speedvigor'] = 7 #+7 Str
i.features['magstam'] = 7 #+7 Sta
evade = 3
mblock = 0
i.features['mblockevade'] = evade | (mblock << 4) #30% evade, 20% mblock
i.features['elements'] = 32 #pearl element
i.name = "Godhand"
i.dataname[1:] = name_to_bytes(i.name, 12)
convert = True
if i.name == "Kodachi":
i.features['power'] = 225
i.features['hitmdef'] = 180
i.features['speedvigor'] = 119 #+7 Str +7 Spd
evade = 5
mblock = 1
i.features['mblockevade'] = evade | (mblock << 4) #30% evade, 20% mblock
i.name = "Oborozuki"
i.dataname[1:] = name_to_bytes(i.name, 12)
convert = True
if i.name == "Kotetsu":
i.features['power'] = 245
i.features['hitmdef'] = 150
i.features['speedvigor'] = 7 #+7 Str
i.features['magstam'] = 7 #+7 Sta
evade = 3
mblock = 0
i.features['mblockevade'] = evade | (mblock << 4) #30% evade, 20% mblock
i.features['elements'] = 32 #pearl element
i.name = "Zanmato"
i.dataname[1:] = name_to_bytes(i.name, 12)
convert = True
if i.name == "Forged":
i.features['power'] = 240
i.features['hitmdef'] = 150
i.features['speedvigor'] = 64 #+4 Spd
i.features['magstam'] = 115 #+7 Mag +3 Sta
evade = 4
mblock = 4
i.features['mblockevade'] = evade | (mblock << 4) #30% evade, 20% mblock
i.name = "SaveTheQueen"
i.weapon_animation = bytes([30, 30, 32, 0, 55, 0, 164, 0]) #change animation to sword/ragnarok
i.dataname[1:] = name_to_bytes(i.name, 12)
i.dataname[0:1] = bytes([217]) #change icon to sword
convert = True
if i.name == "Darts":
i.features['power'] = 215
i.features['hitmdef'] = 230
i.features['speedvigor'] = 67 #+3 Str +4 Spd
i.features['magstam'] = 4 #+4 Sta
i.name = "Final Trump"
i.dataname[1:] = name_to_bytes(i.name, 12)
convert = True
#description is wrong when using Flail
#if i.name == "Flail":
#Epee has an empty description. See below for more empty description options
if i.name == "Epee":
i.features['power'] = 240
i.features['hitmdef'] = 150
i.features['magstam'] = 119 #+7 Mag +7 Sta
i.name = "Gungnir"
i.weapon_animation = bytes([93, 93, 43, 5, 51, 0, 168, 0]) #change animation to spear
i.dataname[1:] = name_to_bytes(i.name, 12)
i.dataname[0:1] = bytes([218]) #change icon to spear
convert = True
if i.name == "Punisher":
i.features['power'] = 180
i.features['hitmdef'] = 135
i.features['magstam'] = 116 #+7 Mag +4 Sta
#i.features['specialaction'] = 112 #Punisher effect, MP Crit. carried over. 0 this to make it not?
i.features['breakeffect'] = 19 #0 = Fire, 1 = Ice, etc. Meteor = 19
i.features["otherproperties"] = 198 #198 is the proc flag, shifted from vanilla
i.name = "Stardust Rod"
i.dataname[1:] = name_to_bytes(i.name, 12)
convert = True
#description is wrong when Using Full Moon
#if i.name == "Full Moon":
#Empty description options:
#Blossom (wind-elemental)
#Hardened (dropped by Gigantos)
#Aura
#Strato (stronger Bat Pwr than Aura)
#Davinci Brsh (+1 spd/+1 mag)
#Kaiser (pearl-elemental)
if i.name == "DaVinci Brsh":
i.features['power'] = 170
i.features['hitmdef'] = 135
i.features['speedvigor'] = 112 #+7 Spd
i.features['magstam'] = 112 #+7 Mag
i.features['breakeffect'] = 30 #0 = Fire, 1 = Ice, etc. Muddle = 30
i.features["otherproperties"] = 198 #198 is the proc flag, shifted from vanilla
i.name = "Angel Brush"
i.weapon_animation = bytes([22, 22, 37, 4, 55, 0, 148, 0]) #change animation to brush
i.dataname[1:] = name_to_bytes(i.name, 12)
i.dataname[0:1] = bytes([221]) #change icon to brush
convert = True
if i.name == "Boomerang":
i.features['power'] = 225
i.features['hitmdef'] = 150
i.features['speedvigor'] = 68 #+4 Str +4 Spd
i.features['magstam'] = 68 #+4 Mag +4 Sta
i.features['elements'] = 8 #poison element
i.features['breakeffect'] = 8 #0 = Fire, 1 = Ice, etc. Bio = 8
i.features["otherproperties"] = 230 #230 is the proc flag+backrow dmg, shifted from vanilla
i.weapon_animation = bytes([25, 25, 25, 8, 25, 0, 218, 0]) #change animation to flail
i.name = "ScorpionTail"
i.dataname[1:] = name_to_bytes(i.name, 12)
convert = True
if convert == True:
i.unrestrict()
i.write_stats(fout)
#################end section to convert certain items in FF6A ultimate weapons
#################testing randomize item stats
def manage_items(items: List[ItemBlock], changed_commands: Set[int]=None, itemstats1="n", itemstats2="n",
itembreakproc="n", itemteacher="n", itemelemental="n", itemspecial="n", itemfeature="n", itemheavy="n",
item_wild_breaks="n", item_extra_effects="n") -> List[ItemBlock]:
from itemrandomizer import (set_item_changed_commands, extend_item_breaks)
always_break = Options_.is_code_active('collateraldamage')
crazy_prices = Options_.is_code_active('madworld')
extra_effects = Options_.is_code_active('masseffect')
wild_breaks = Options_.is_code_active('electricboogaloo')
no_breaks = Options_.is_code_active('nobreaks')
unbreakable = Options_.is_code_active('unbreakable')
#set_item_changed_commands(changed_commands)
####WC - not sure what these do
#unhardcode_tintinabar(fout) #<--- this is referenced in read_stats
####WC - turn this off as 0x303000 is used by WC menus/objective system
#extend_item_breaks(fout) #<--- this is required to get procs set via
####
#features["otherproperties"] to work correctly
#in itemrandomizer.py see break_flags = self.features["breakeffect"] & 0xC0
auto_equip_relics = []
for i in items:
#WC - add variable to note ultimate weapon item (by checking the item name)
ultimate_weapon_flag = False
####Originally: RegalCutlass, Crystal, Stout Spear, Mithril Claw, Kodachi, Kotetsu, Forged, Darts, DaVinci Brsh, Punisher, Epee, Boomerang
if (i.read_in_name == "Apocalypse" or i.read_in_name == "ZwillXBlade" or
i.read_in_name == "Longinus" or i.read_in_name == "Godhand" or
i.read_in_name == "Oborozuki" or i.read_in_name == "Zanmato" or
i.read_in_name == "SaveTheQueen" or i.read_in_name == "Final Trump" or
i.read_in_name == "Gungnir" or i.read_in_name == "Stardust Rod" or
i.read_in_name == "Angel Brush" or i.read_in_name == "ScorpionTail"):
ultimate_weapon_flag = True
#currently not doing anything with this but may be used later
#WC - don't mutate if ultimate_weapon_flag is True
if ultimate_weapon_flag == False:
i.mutate(always_break=always_break, crazy_prices=crazy_prices, extra_effects=extra_effects, wild_breaks=wild_breaks, no_breaks=no_breaks, unbreakable=unbreakable,
itemstats1=itemstats1, itemstats2=itemstats2, itembreakproc=itembreakproc, itemteacher=itemteacher, itemelemental=itemelemental,
itemspecial=itemspecial, itemfeature=itemfeature, itemheavy=itemheavy,item_wild_breaks=item_wild_breaks, item_extra_effects=item_extra_effects)
i.unrestrict() #not sure what this is
i.write_stats(fout)
#Gauntlet, Merit Award, Genji Glove:
if i.features['special2'] & 0x38 and i.is_relic:
auto_equip_relics.append(i.itemid)
#write to log
if i.mutation_log != {}:
log(str(i.get_mutation_log()), section="item effects")
#print(i.name + ": " + str(i.price) + " | " + str(i.rank()))
assert(auto_equip_relics)
######CDude:if you don't use this code, the Genji/Merit/Gauntlet flags won't trigger relic-to-gear reequips in the menu if item randomization is in play.
if 0 == 1:
#Test Reequip activation, select menu commands accordingly
#C3/9EEB: 201091 JSR $9110 ; Get gear effects
#C3/9EEE: 20F293 JSR $93F2 ; Define Y
#C3/9EF1: B90000 LDA $0000,Y ; Actor
#C3/9EF4: C90D CMP #$0D ; Umaro?
#C3/9EF6: F007 BEQ $9EFF ; Branch if so
#C3/9EF8: 205C9F JSR $9F5C ; Test Reequip
#C3/9EFB: A599 LDA $99 ; Triggered it?
#C3/9EFD: D007 BNE $9F06 ; Branch if so
#C3/9EFF: A904 LDA #$04 ; C3/1A8A
#C3/9F01: 8527 STA $27 ; Queue main menu
#C3/9F03: 6426 STZ $26 ; Next: Fade-out
#C3/9F05: 60 RTS
auto_equip_sub = Substitution()
auto_equip_sub.set_location(0x39EF9)
auto_equip_sub.bytestring = bytes([0xA0, 0xF1,])
auto_equip_sub.write(fout)
#normally unused space. in WC Bank C3, 03FC18-03FFFF are available
auto_equip_sub.set_location(0x3F1A0)
auto_equip_sub.bytestring = bytes([0x20, 0xF2, 0x93,
0xB9, 0x23, 0x00,
0xC5, 0xB0,
0xD0, 0x09,
0xB9, 0x24, 0x00,
0xC5, 0xB1,
0xD0, 0x02,
0x80, 0x4C,
0x64, 0x99,
0xA5, 0xB0,
0x20, 0x21, 0x83,
0xAE, 0x34, 0x21,
0xBF, 0x0C, 0x50, 0xD8,
0x29, 0x38,
0x85, 0xFE,
0xA5, 0xB1,
0x20, 0x21, 0x83,
0xAE, 0x34, 0x21,
0xBF, 0x0C, 0x50, 0xD8,
0x29, 0x38,
0x04, 0xFE,
0xB9, 0x23, 0x00,
0x20, 0x21, 0x83,
0xAE, 0x34, 0x21,
0xBF, 0x0C, 0x50, 0xD8,
0x29, 0x38,
0x85, 0xFF,
0xB9, 0x24, 0x00,
0x20, 0x21, 0x83,
0xAE, 0x34, 0x21,
0xBF, 0x0C, 0x50, 0xD8,
0x29, 0x38,
0x04, 0xFF,
0xA5, 0xFE,
0xC5, 0xFF,
0xF0, 0x02,
0xE6, 0x99,
0x60])
auto_equip_sub.write(fout)
######not sure what this does, comment out for now
return items
#################testing randomize item stats
#################testing monster appearance
def manage_monster_appearance(monsters: List[MonsterBlock], preserve_graphics: bool = False,
monstersprites="n",monsterpalettes="n") -> List[MonsterGraphicBlock]:
mgs = [m.graphics for m in monsters]
esperptr = 0x127000 + (5 * 384)
espers = []
for j in range(32):
mg = MonsterGraphicBlock(pointer=esperptr + (5 * j), name="")
mg.read_data(sourcefile)
espers.append(mg)
mgs.append(mg)
for m in monsters:
g = m.graphics
pp = g.palette_pointer
others = [h for h in mgs if h.palette_pointer == pp + 0x10]
if others:
g.palette_data = g.palette_data[:0x10]
#print(str(m.name) + " | " + str(m.graphicname))
#input("x")
nonbosses = [m for m in monsters if not m.is_boss and not m.boss_death]
bosses = [m for m in monsters if m.is_boss or m.boss_death]
assert not set(bosses) & set(nonbosses)
nonbossgraphics = [m.graphics.graphics for m in nonbosses]
bosses = [m for m in bosses if m.graphics.graphics not in nonbossgraphics]
for i, m in enumerate(nonbosses):
if "Chupon" in m.name:
m.update_pos(6, 6)
m.update_size(8, 16)
if "Siegfried" in m.name:
m.update_pos(8, 8)
m.update_size(8, 8)
candidates = nonbosses[i:]
#print(str(m.name) + " | " + str(m.graphicname))
#all the non-bosses
#print(m.name + " | " + str(m.miny) + " | " + str(m.maxy) + " | " +
#str(m.width) + " | " + str(m.height) + " | " +str(m.graphics.graphics) +
#" | " + str(m.graphics.palette) + " | " + str(m.graphics.palette_pointer) +
#" | " + str(m.graphics.palette_data) + " | " + str(m.graphics.palette_values) +
#" | " + str(m.graphics.large))
#print(m.name + " | " + m.graphicname)
#input("X")
#WC test
#this changes the entire monster sprite
#there are still some issues with sprite placement which causes
#portions of some large sprites to appear offscreen
#formations handle the x/y positions
if monstersprites == "y":
m.mutate_graphics_swap(candidates)
#WC - don't change names
#name = randomize_enemy_name(fout, m.id)
#m.changed_name = name
done = {}
#WC D27820 isn't really "freespace", it's the beginning of the Monster Palettes
freepointer = 0x127820
for m in monsters:
mg = m.graphics
#WC - don't randomize final boss palette
# this didn't seem to work, final Kefka palette still got randomized
#if m.id != 0x12a:
if 1 == 1:
#final Kefka
if m.id == 0x12a and not preserve_graphics:
idpair = "KEFKA 1"
#from monsterrandomizer:
# Dummied Umaro, Dummied Kefka, Colossus, CzarDragon, ???, ???
#REPLACE_ENEMIES = [0x10f, 0x136, 0x137]
if m.id in REPLACE_ENEMIES + [0x172]:
mg.set_palette_pointer(freepointer)
freepointer += 0x40
continue
else:
#pair each monster with its palette_pointer from mg
idpair = (m.name, mg.palette_pointer)
if idpair not in done:
#WC - this change the palette of the monster
if monsterpalettes == "y":
mg.mutate_palette()
#check get_palette_transformer from utils
#WC - this change the palette of the monster
done[idpair] = freepointer
freepointer += len(mg.palette_data)
mg.write_data(fout, palette_pointer=done[idpair])
else:
mg.write_data(fout, palette_pointer=done[idpair],
no_palette=True)
#end of check for final boss
#WC - Esper palettes change
if monsterpalettes == "y":
for mg in espers:
mg.mutate_palette()
mg.write_data(fout, palette_pointer=freepointer)
freepointer += len(mg.palette_data)
return mgs
########################end monster appearance
######randomize animations colors
def manage_colorize_animations():
palettes = []
#D26000 D26EFF Battle Animation Palettes (8 colors each)
for i in range(240):
pointer = 0x126000 + (i * 16)
fout.seek(pointer)
palette = [read_multi(fout, length=2) for _ in range(8)]
palettes.append(palette)
for i, palette in enumerate(palettes):
transformer = get_palette_transformer(basepalette=palette)
palette = transformer(palette)
pointer = 0x126000 + (i * 16)
fout.seek(pointer)
for c in palette:
write_multi(fout, c, length=2)
######randomize animations colors
######randomize map palettes
### requires locationformations.txt and locationpaletteswaps.txt
def manage_colorize_dungeons(locations=None, freespaces=None):
locations = locations or get_locations()
get_namelocdict()
paldict = {}
for l in locations:
if l.setid in namelocdict:
name = namelocdict[l.setid]
if l.name and name != l.name:
raise Exception("Location name mismatch.")
if l.name is None:
l.name = namelocdict[l.setid]
if l.field_palette not in paldict:
paldict[l.field_palette] = set([])
if l.attacks:
formation = [f for f in get_fsets() if f.setid == l.setid][0]
if set(formation.formids) != set([0]):
paldict[l.field_palette].add(l)
l.write_data(fout)
from itertools import product
if freespaces is None:
freespaces = [FreeBlock(0x271530, 0x271650)]
done = []
for line in open(LOCATION_PALETTE_TABLE):
line = line.strip()
if line[0] == '#':
continue
line = line.split(':')
if len(line) == 2:
names, palettes = tuple(line)
names = names.split(',')
palettes = palettes.split(',')
backgrounds = []
elif len(line) == 3:
names, palettes, backgrounds = tuple(line)
names = names.split(',')
palettes = palettes.split(',')
backgrounds = backgrounds.split(',')
elif len(line) == 1:
names, palettes = [], []
backgrounds = line[0].split(',')
else:
raise Exception("Bad formatting for location palette data.")
palettes = [int(s, 0x10) for s in palettes]
backgrounds = [int(s, 0x10) for s in backgrounds]
candidates = set()
for name, palette in product(names, palettes):
if name.endswith('*'):
name = name.strip('*')
break
candidates |= {l for l in locations if l.name == name and l.field_palette == palette and l.attacks}
if not candidates and not backgrounds:
palettes, battlebgs = [], []
battlebgs = {l.battlebg for l in candidates if l.attacks}
battlebgs |= set(backgrounds)
transformer = None
battlebgs = sorted(battlebgs)
random.shuffle(battlebgs)
for bg in battlebgs:
palettenum = battlebg_palettes[bg]
pointer = 0x270150 + (palettenum * 0x60)
fout.seek(pointer)
if pointer in done:
# raise Exception("Already recolored palette %x" % pointer)
continue
raw_palette = [read_multi(fout, length=2) for i in range(0x30)]
if transformer is None:
if bg in [0x33, 0x34, 0x35, 0x36]:
transformer = get_palette_transformer(always=True)
else:
transformer = get_palette_transformer(basepalette=raw_palette, use_luma=True)
new_palette = transformer(raw_palette)
fout.seek(pointer)
for c in new_palette:
write_multi(fout, c, length=2)
done.append(pointer)
for p in palettes:
if p in done:
raise Exception("Already recolored palette %x" % p)
fout.seek(p)
raw_palette = [read_multi(fout, length=2) for i in range(0x80)]
new_palette = transformer(raw_palette)
fout.seek(p)
for c in new_palette:
write_multi(fout, c, length=2)
done.append(p)
#if Options_.random_animation_palettes or Options_.swap_sprites or Options_.is_code_active('partyparty'):
manage_colorize_wor()
manage_colorize_esper_world()
def manage_colorize_wor():
transformer = get_palette_transformer(always=True)
fout.seek(0x12ed00)
raw_palette = [read_multi(fout, length=2) for i in range(0x80)]
new_palette = transformer(raw_palette)
fout.seek(0x12ed00)
for c in new_palette:
write_multi(fout, c, length=2)
fout.seek(0x12ef40)
raw_palette = [read_multi(fout, length=2) for i in range(0x60)]
new_palette = transformer(raw_palette)
fout.seek(0x12ef40)
for c in new_palette:
write_multi(fout, c, length=2)
fout.seek(0x12ef00)
raw_palette = [read_multi(fout, length=2) for i in range(0x12)]
airship_transformer = get_palette_transformer(basepalette=raw_palette)
new_palette = airship_transformer(raw_palette)
fout.seek(0x12ef00)
for c in new_palette:
write_multi(fout, c, length=2)
for battlebg in [1, 5, 0x29, 0x2F]:
palettenum = battlebg_palettes[battlebg]
pointer = 0x270150 + (palettenum * 0x60)
fout.seek(pointer)
raw_palette = [read_multi(fout, length=2) for i in range(0x30)]
new_palette = transformer(raw_palette)
fout.seek(pointer)
for c in new_palette:
write_multi(fout, c, length=2)
for palette_index in [0x16, 0x2c, 0x2d, 0x29]:
field_palette = 0x2dc480 + (256 * palette_index)
fout.seek(field_palette)
raw_palette = [read_multi(fout, length=2) for i in range(0x80)]
new_palette = transformer(raw_palette)
fout.seek(field_palette)
for c in new_palette:
write_multi(fout, c, length=2)
def manage_colorize_esper_world():
loc = get_location(217)
chosen = random.choice([1, 22, 25, 28, 34, 38, 43])
loc.palette_index = (loc.palette_index & 0xFFFFC0) | chosen
loc.write_data(fout)
######randomize map palettes
#################testing randomize monster stats
####### monsters = manage_monsters()
####### for m in monsters:
####### m.write_stats(fout)
def manage_monsters(monsterstats1="n", monsterstats2="n", monstermisc="n", monsterautostatus="n", monsterelemental="n", monsterspecials="n",
monsters_darkworld="n", monsterscripts="n", monstercontrol="n", monsterdrops="n", monstersteals="n", monstermorphs="n",
vanillacontrol="n", monsterSketchRage="n", vanillaSketchRage="n", easybossupgrades="n") -> List[MonsterBlock]:
monsters = get_monsters(sourcefile)
#safe_solo_terra = not Options_.is_code_active("ancientcave")
#darkworld = Options_.is_code_active("darkworld")
change_skillset = None
#katn = Options_.mode.name == 'katn'
#final_bosses = (list(range(0x157, 0x160)) + list(range(0x127, 0x12b)) + [0x112, 0x11a, 0x17d])
#list of bosses
#Vargas, TunnelArmr, GhostTrain, Dadaluma, Shiva, Ifrit, Number 024, Number 128, Left Crane, Right Crane, Nerapa, Kefka (Narshe), Ultros 1, Ultros 2, Ultros 3, Right Blade, Left Blade, Ipooh, Leader, Piranha, Rizopas, Ultros 4, Air Force
easy_bosses = [259, 260, 262, 263, 264, 265, 266, 267, 269, 270, 280, 330, 300, 301, 302, 319, 320, 333, 334, 340, 341, 360, 275]
ranked_items = get_ranked_items()
exclusion_list = []
#WC - add variable to note ultimate weapon item (by checking the item name)
ultimate_weapon_flag = False
for i in ranked_items:
####Originally: RegalCutlass, Crystal, Stout Spear, Mithril Claw, Kodachi, Kotetsu, Forged, Darts, DaVinci Brsh, Punisher, Epee, Boomerang
if (i.read_in_name == "Apocalypse" or i.read_in_name == "ZwillXBlade" or
i.read_in_name == "Longinus" or i.read_in_name == "Godhand" or
i.read_in_name == "Oborozuki" or i.read_in_name == "Zanmato" or
i.read_in_name == "SaveTheQueen" or i.read_in_name == "Final Trump" or
i.read_in_name == "Gungnir" or i.read_in_name == "Stardust Rod" or
i.read_in_name == "Angel Brush" or i.read_in_name == "ScorpionTail" or
i.read_in_name == "Dueling Mask" or i.read_in_name == "Bone Wrist"):
exclusion_list.append(i.itemid)
ultimate_weapon_flag = True
###### BC on WC week 3
if (i.name == "Exp. Egg" or i.name == "Fixed Dice" or i.name == "Illumina"
or i.name == "Paladin Shld" or i.name == "Cursed Shld" or
i.name == "Ragnarok" or i.name == "ValiantKnife"):
exclusion_list.append(i.itemid)
#end of loop through items
if ultimate_weapon_flag:
exclusion_list.sort()
#print(exclusion_list)
for m in monsters:
#####WC - good example of a conditional to ignore specific monsters
#in this example Zone Eater is left untouched
if "zone eater" in m.name.lower():
continue
if not m.name.strip('_') and not m.display_name.strip('_'):
continue
if m.id in easy_bosses and easybossupgrades == "y":
#print(m.display_name)
m.mutate_easybosses()
#####commenting out boss section
if 0 == 1:
if m.id in final_bosses:
if 0x157 <= m.id < 0x160 or m.id == 0x17d:
# deep randomize three tiers, Atma
m.randomize_boost_level()
if darkworld:
m.increase_enemy_difficulty()
m.mutate(Options_=Options_, change_skillset=True, safe_solo_terra=False, katn=katn)
else:
m.mutate(Options_=Options_, change_skillset=change_skillset, safe_solo_terra=False, katn=katn)
if 0x127 <= m.id < 0x12a or m.id == 0x17d or m.id == 0x11a:
# boost statues, Atma, final kefka a second time
m.randomize_boost_level()
if darkworld:
m.increase_enemy_difficulty()
m.mutate(Options_=Options_, change_skillset=change_skillset, safe_solo_terra=False)
m.misc1 &= (0xFF ^ 0x4) # always show name
else:
if darkworld:
m.increase_enemy_difficulty()
m.mutate(Options_=Options_, change_skillset=change_skillset, safe_solo_terra=safe_solo_terra, katn=katn)
#####end of commenting out boss section
#print("Start: " + str(m.name) + " | " + str(m.controls) + " | " + str(m.sketches) + " | " + str(m.rages))
#print("Start: " + str(m.id) + " | " + str(m.aiscript))
#print(m.vanilla_aiscript)
#print(str(m.name) + " | " + str(m.stats['hp']))
#m.mutate(Options_=Options_, change_skillset=change_skillset, safe_solo_terra=True, katn=False)
m.mutate(Options_=Options_, change_skillset=True, safe_solo_terra=True, katn=False,
monsterstats1=monsterstats1, monsterstats2=monsterstats2, monstermisc=monstermisc, monsterautostatus=monsterautostatus,
monsterelemental=monsterelemental, monsterspecials=monsterspecials, monsters_darkworld=monsters_darkworld,
monsterscripts=monsterscripts, monstercontrol=monstercontrol, monsterdrops=monsterdrops,
monstersteals=monstersteals, monstermorphs=monstermorphs, vanillacontrol=vanillacontrol, monsterSketchRage=monsterSketchRage,
vanillaSketchRage=vanillaSketchRage)
#print("End: " + str(m.name) + " | " + str(m.aiscript))
#print(m.name + " | " + str(m.statuses) + " | " + str(m.immunities) + " | " +
#str(m.absorb) + " | " + str(m.null) + " | " + str(m.weakness))
#m.statuses[3] |= 0x01 #give all enemies trueknight
#m.statuses[3] |= 0x02 #give all enemies runic
#m.statuses[3] |= 0x04 #give all enemies Life3
#mutate_items is never called in mutate
m.mutate_items(katnFlag=False,monsterdrops=monsterdrops,monstersteals=monstersteals,
exclusion_list=exclusion_list)
if monstermorphs == "y":
m.mutate_metamorph()
if monsterspecials == "y":
#this changes the name/animation of the special attack
#but the actual effect appears to be the same unless otherwise
#changed in earlier mutate()
m.randomize_special_effect(fout)
#print(str(m.name) + ": " + str(m.special))
m.write_stats(fout)
#not sure about these, ignore them for now
#m.tweak_fanatics() #adjusts L. magics and Magimaster, no need
#m.relevel_specifics() #adjusts monster ID's 0x4F, 0xBD, 0xAD: Bomb, SoulDancer, Outsider
#WC - does this actually need to happen. comment out for now
#change_enemy_name(fout, 0x166, "L.255Magic")
#WC - probably don't need to do this since WC already moves stuff around if the player wants it to
#shuffle_monsters(monsters, safe_solo_terra=True)