-
Notifications
You must be signed in to change notification settings - Fork 8
/
tweaks.rb
1215 lines (1036 loc) · 57.6 KB
/
tweaks.rb
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
module Tweaks
def apply_pre_randomization_tweaks
@tiled = TMXInterface.new
game.fix_unnamed_skills()
if GAME == "dos"
# Make the drawbridge stay permanently down once the player has lowered it.
game.apply_armips_patch("dos_drawbridge_stays_down")
# And also modify the level design of the drawbridge room so you can go up and down even when the drawbridge is closed.
filename = "./dsvrandom/roomedits/dos_room_rando_00-00-15.tmx"
room = game.areas[0].sectors[0].rooms[0x15]
tiled.import_tmx_map(filename, room)
end
if GAME == "dos"
# Modify the level design of the room after Flying Armor to not require a backdash jump to go from right to left.
filename = "./dsvrandom/roomedits/dos_00-00-08.tmx"
room = game.areas[0].sectors[0].rooms[8]
tiled.import_tmx_map(filename, room)
end
if GAME == "dos"
# Remove the enemy and the entity hider hiding that enemy during the intro cutscene from the first room of the vanilla game.
# These two just cause more problems than they're worth - the hider can hide starting items, the enemy can hit you as soon as you enter the room when randomized into something besides the vanilla Yeti, etc.
entity_hider = game.entity_by_str("00-00-01_0B")
entity_hider.type = 0
entity_hider.write_to_rom()
if options[:randomize_enemies]
enemy = game.entity_by_str("00-00-01_0C")
enemy.type = 0
enemy.write_to_rom()
end
end
if GAME == "ooe"
if options[:open_world_map]
game.apply_armips_patch("ooe_nonlinear")
else
# Even if the user doesn't want the world map opened up we still make the events capable of being accessed nonlinearly.
game.apply_armips_patch("ooe_nonlinear_events")
end
end
if GAME == "ooe" && !options[:open_world_map]
# Make both Ecclesia and Wygol unlocked at the start instead of just Ecclesia.
game.apply_armips_patch("ooe_wygol_unlocked_at_start")
end
if GAME == "ooe"
# Change various things so that most of the hardcoded world map unlocks are removed and instead done via exit objects.
# (Note that this also means you don't need to talk to Barlowe to unlocks certain things anymore.)
# Make Ecclesia unlock Monastery via its exit instead of via a cutscene.
ecclesia_exit = game.entity_by_str("02-00-03_00")
ecclesia_exit.var_a = 0x12 # Unlock Monastery
ecclesia_exit.write_to_rom()
# Add new back exits to areas that would normally have the next area unlocked via a cutscenes.
# To do this the level design of the rooms needs to be modified to add a hole to them.
[
[0x12, 0, 0x14], # Monastery. (Unlocks Ruvas Forest.)
[0x11, 0, 0x08], # Skeleton Cave. (Unlocks Somnus Reef.)
[0x10, 0, 0x00], # Oblivion Ridge. (Unlocks Argila Swamp.)
].each do |area_index, sector_index, room_index|
filename = "./dsvrandom/roomedits/ooe_linear_%02X-%02X-%02X.tmx" % [area_index, sector_index, room_index]
room = game.areas[area_index].sectors[sector_index].rooms[room_index]
tiled.import_tmx_map(filename, room)
# Also update the map tile to be an entrance.
map = game.get_map(area_index, sector_index)
map_tile = map.tiles.find{|tile| tile.x_pos == room.x_pos && tile.y_pos == room.y_pos}
map_tile.is_entrance = true
map.write_to_rom()
end
# Remove the hardcoded world map unlocks from cutscene code.
# Remove the code in Ecclesia that unlocks Monastery.
game.fs.load_overlay(42)
game.fs.write(0x022C49F4, [0xE1A00000].pack("V")) # nop
# Remove the code in Monastery that unlocks Wygol.
game.fs.load_overlay(78)
game.fs.write(0x022C2AD4, [0xE1A00000].pack("V")) # nop
# Remove the code in Wygol that unlocks Ruvas, and makes Minera visible.
game.fs.load_overlay(41)
game.fs.write(0x022C2B30, [0xE1A00000].pack("V")) # nop
game.fs.write(0x022C2B3C, [0xE1A00000].pack("V")) # nop
# Remove the code in Ecclesia that unlocks Argila, and makes Mystery Manor visible.
game.fs.load_overlay(42)
game.fs.write(0x022C51EC, [0xE1A00000].pack("V")) # nop
game.fs.write(0x022C51F8, [0xE1A00000].pack("V")) # nop
# Remove the code in Wygol that unlocks Somnus, and makes Giant's Dwelling visible.
game.fs.load_overlay(41)
game.fs.write(0x022C2680, [0xE1A00000].pack("V")) # nop
game.fs.write(0x022C268C, [0xE1A00000].pack("V")) # nop
end
if GAME == "ooe" && options[:open_world_map] && !room_rando?
# Fix some broken platforms in Tristis Pass so the player cannot become permastuck.
layer = game.areas[0xB].sectors[0].rooms[2].layers.first
layer.tiles[0xD1].index_on_tileset = 0x378
layer.tiles[0xD2].index_on_tileset = 0x378
layer.write_to_rom()
layer = game.areas[0xB].sectors[0].rooms[4].layers.first
layer.tiles[0x31].index_on_tileset = 0x37C
layer.tiles[0x32].index_on_tileset = 0x37C
layer.tiles[0x121].index_on_tileset = 0x378
layer.tiles[0x122].index_on_tileset = 0x378
layer.tiles[0x1BD].index_on_tileset = 0x37C
layer.tiles[0x1BE].index_on_tileset = 0x37C
layer.tiles[0x2AD].index_on_tileset = 0x378
layer.tiles[0x2AE].index_on_tileset = 0x378
layer.write_to_rom()
end
if GAME == "ooe"
# We need to unset the prerequisite event flag for certain events manually.
# The ooe_nonlinear patch does this but we might already have those rooms cached by now in memory so the changes won't get read in correctly.
# Also, we unset these event flags even if the nonlinear option is off just in case the player sequence breaks the game somehow.
game.each_room do |room|
room.entities.each do |entity|
if entity.is_special_object? && [0x69, 0x6B, 0x6C, 0x6F, 0x71, 0x74, 0x7E, 0x85].include?(entity.subtype)
entity.var_b = 0
entity.write_to_rom()
end
end
end
end
if GAME == "dos" && room_rando?
# Remove the special code for the slide puzzle.
game.fs.write(0x0202738C, [0xE3A00000, 0xE8BD41F0, 0xE12FFF1E].pack("V*"))
# Next we remove the walls on certain rooms so the dead end rooms are accessible and fix a couple other things in the level design.
[0xE, 0xF, 0x14, 0x15, 0x19].each do |room_index|
# 5 room: Remove floor and move the item off where the floor used to be.
# 6 room: Remove left wall.
# 11 room: Remove right wall.
# 12 room: Remove ceiling.
# Empty room: Add floor so the player can't fall out of bounds.
room = game.areas[0].sectors[1].rooms[room_index]
filename = "./dsvrandom/roomedits/dos_room_rando_00-01-%02X.tmx" % room_index
tiled.import_tmx_map(filename, room)
end
# Now remove the wall entities in each room and the control panel entity.
game.each_room do |room|
room.entities.each do |entity|
if entity.is_special_object? && [0x0C, 0x0D].include?(entity.subtype)
entity.type = 0
entity.write_to_rom()
end
end
end
# Change regular Gergoth's code to act like boss rush Gergoth and not break the floor.
# (We can't just use boss rush Gergoth himself because he wakes up whenever you're in the room, even if you're in a lower part of the tower.)
game.fs.load_overlay(36)
game.fs.write(0x02303AB4, [0xE3A01000].pack("V")) # mov r1, 0h
game.fs.write(0x02306D70, [0xE3A00000].pack("V")) # mov r0, 0h
# And modify the code of the floors to not care if Gergoth's boss death flag is set, and just always be in place.
game.fs.write(0x0219EF40, [0xE3A00000].pack("V")) # mov r0, 0h
# Apply a patch to make Gergoth move to the right if the player enters from the left so he's not on top of the player.
game.apply_armips_patch("dos_gergoth_either_side")
# When starting a new game, don't unlock the Lost Village warp by default.
game.fs.write(0x021F6054, [0xE1A00000].pack("V"))
if !options[:randomize_maps]
# When map randomizer is NOT on, the total number of tiles on the map never gets updated.
# But Gergoth not breaking the floor means you can't access some vanilla tiles.
# So update the number of tiles here to prevent 100% map being impossible.
game.apply_armips_patch("dos_allow_changing_total_map_tiles")
total_num_tiles = 0x400 - 7 # 0x400 is the vanilla number of tiles, 7 Tiles are unreachable when Gergoth doesn't break the floor.
game.fs.write(0x02026B68, [total_num_tiles].pack("V"))
end
end
if GAME == "dos" && room_rando?
# Remove the warp blockade in room rando since it's pointless.
warp_blockade = game.entity_by_str("00-00-19_00")
warp_blockade.type = 0
warp_blockade.write_to_rom()
end
if GAME == "por" && room_rando?
# Modify several split doors, where there are two different gaps in the level design, to only have one gap instead.
# This is because the logic doesn't support multi-gap doors.
["07-00-07", "07-00-0A", "07-00-0B", "07-00-0D", "08-02-18", "08-02-19"].each do |room_str|
room = game.room_by_str(room_str)
filename = "./dsvrandom/roomedits/por_room_rando_#{room_str}.tmx"
tiled.import_tmx_map(filename, room)
end
end
if GAME == "ooe" && room_rando?
# Make the frozen waterfall always be unfrozen. (Only the bottom part, the part at the top will still be frozen.)
game.fs.load_overlay(57)
game.fs.write(0x022C2CAC, [0xE3E01000].pack("V"))
# The Giant Skeleton boss room will softlock the game if the player enters from the right side.
# So we get rid of the searchlights that softlock the game and modify the Giant Skeleton boss's AI to wake up like a non-boss Giant Skeleton.
searchlights = game.entity_by_str("08-00-02_03")
searchlights.type = 0
searchlights.write_to_rom()
game.fs.write(0x02277EFC, [0xE3A01000].pack("V"))
# Modify the level design of three Tymeo rooms where they have a platform at the bottom door, but only on either the left or right edge of the screen.
# If the upwards door connected to one of these downwards doors doesn't have a platform on the same side of the screen, the player won't be able to get up.
# So we place a large platform across the center of the bottom of these three rooms so the player can walk across it.
[0x8, 0xD, 0x12].each do |room_index|
room = game.areas[0xA].sectors[0].rooms[room_index]
filename = "./dsvrandom/roomedits/ooe_room_rando_0A-00-%02X.tmx" % room_index
tiled.import_tmx_map(filename, room)
end
end
case GAME
when "por", "ooe"
game.each_room do |room|
room.entities.each_with_index do |entity, entity_index|
if entity.type == 8 && entity.byte_8 == 0
# This is an entity hider that removes all remaining entities in the room.
if entity_index == room.entities.size - 1
# If the entity hider is the last entity in the room we just remove it entirely since it's not doing anything and we can't have it remove 0 entities.
entity.type = 0
entity.write_to_rom()
else
# Otherwise, we need to change it to only remove the number of entities remaining in the vanilla game.
# This way, even if we add new entities to this room later, the new entity will show up.
entity.byte_8 = room.entities.size - entity_index - 1
entity.write_to_rom()
end
end
end
end
when "dos"
game.each_room do |room|
room.entities.each_with_index do |entity, entity_index|
if entity.type == 6
# This is an entity hider. In DoS, entity hiders always remove all remaining entities in the room.
if entity_index == room.entities.size - 1
# If the entity hider is the last entity in the room we just remove it entirely since it's not doing anything and we can't have it remove 0 entities.
entity.type = 0
entity.write_to_rom()
elsif room.room_str == "00-01-20"
# Mini-Paranoia room. This hides the item inside Mini-Paranoia's mirror until Paranoia is dead.
# We remove this so the item appears always.
entity.type = 0
entity.write_to_rom()
else
# Otherwise, there's no easy way to fix this problem like in PoR/OoE, since in DoS entity hiders always hide all remaining entities.
# So just do nothing and hope this isn't an issue. It probably won't be since it's only a few event rooms and boss rooms that use these anyway.
# TODO?
end
end
end
end
end
if GAME == "dos"
# Remove the event where Yoko talks to you outside Flying Armor's room.
# This event can look weird and make the player think the game has softlocked if the player views it after killing Flying Armor.
room = game.room_by_str("00-00-0E")
[9, 0xA, 0xB].each do |entity_index| # Event, font loader, and entity hider
entity = room.entities[entity_index]
entity.type = 0
entity.write_to_rom()
end
end
if GAME == "dos"
# Remove the event with Celia, Julius and Arikado in Cursed Clock Tower.
# This event is useless, and it can softlock if the map randomizer blocks off the left wall because that prevents Arikado from leaving the room.
room = game.room_by_str("00-08-1E")
[2, 3, 4].each do |entity_index| # Event, font loader, and entity hider
entity = room.entities[entity_index]
entity.type = 0
entity.write_to_rom()
end
end
if GAME == "dos" && options[:randomize_maps]
# Remove the cutscene where Yoko gives you a magic seal in the drawbridge room for map rando.
# This cutscene puts you in the topleft door - but that door might be removed and blocked off in map rando, in which case the player will be put out of bounds.
room = game.room_by_str("00-00-15")
[9, 0xC, 0xD].each do |entity_index| # Event, font loader, and entity hider
# TODO: the entity indexes got shifted around beforehand, so this removes the wrong entity
entity = room.entities[entity_index]
entity.type = 0
entity.write_to_rom()
end
end
if GAME == "dos"
# Hellfire's iframes line was originally coded to just copy register r4 (containing 1) into register r1.
# Change this to a constant "mov r1, 1h" so the iframes value can be directly written to when skill behavior rando is on.
game.fs.write(0x022035CC, [0xE3A01001].pack("V"))
end
if GAME == "ooe" && options[:gain_extra_attribute_points]
# Make every enemy give 25x more AP than normal.
game.enemy_dnas.each do |enemy|
enemy["AP"] = 1 if enemy["AP"] < 1
enemy["AP"] *= 25
enemy["AP"] = 0xFF if enemy["AP"] > 0xFF
enemy.write_to_rom()
end
# Make absorbing a glyph give 25 AP instead of 1.
game.fs.write(0x0206D994, [25].pack("C"))
end
if GAME == "ooe" && options[:randomize_villagers]
# Add support for randomizing George, since in vanilla his rescue seen was part of a cutscene tied to Skeleton Cave.
game.apply_armips_patch("ooe_allow_moving_george")
# Replace the cutscene where you see Albus and then rescue George with the generic villager object so George can be randomized.
albus_event = game.entity_by_str("11-00-08_01")
albus_event.type = 0
albus_event.write_to_rom()
george_event = game.entity_by_str("11-00-08_02")
george_event.x_pos = 0x40
george_event.y_pos = 0xB0
george_event.subtype = 0x89 # Generic object for a villager trapped in Torpor
george_event.var_b = 0
george_event.write_to_rom()
end
if options[:randomize_enemies] && GAME == "dos"
# Remove Mothman's vanilla searchlight if enemy randomizer is on, since it won't do anything without Mothman himself.
vanilla_searchlight = game.entity_by_str("00-09-12_00")
vanilla_searchlight.type = 0
vanilla_searchlight.write_to_rom()
end
if GAME == "ooe"
# Add a warp to the Ecclesia save room. This allows easy access between warp points and the world map (and allows Ecclesia to be the starting room when starting room rando is on).
room = game.areas[2].sectors[0].rooms[5]
if !room.entities.any?{|e| e.is_warp_point?}
add_save_or_warp_to_room(room, :warp)
# And fix the map to show the warp room color.
map = game.get_map(2, 0)
tile = map.tiles.find{|t| t.x_pos == room.x_pos && t.y_pos == room.y_pos}
tile.is_warp = true
map.write_to_rom(allow_changing_num_tiles: false)
end
end
if room_rando?
center_bosses_for_room_rando()
end
# Add a free space overlay so we can add entities as much as we want.
if !game.fs.has_free_space_overlay?
game.add_new_overlay()
end
# Now apply any ASM patches that go in the free space overlay first.
if GAME == "dos"
dos_implement_magical_tickets()
end
if GAME == "por"
# Remove the requirement that you must beat Stella and talk to Wind about her locket before entering the Forest of Doom portrait.
game.fs.write(0x02079280, [0xEA000005].pack("V")) # "b 0207929Ch" Skip the forest of doom specific code
# And remove the cutscene where Charlotte stops you from entering the Forest of Doom portrait.
# This cutscene softlocks the game if it plays but the portrait also lets you go in.
forest_cutscene = game.entity_by_str("00-08-01_03")
forest_cutscene.type = 0
forest_cutscene.write_to_rom()
end
# Fix the bugs where an incorrect map tile would get revealed when going through doors in the room randomizer (or sliding puzzle in vanilla DoS).
if GAME == "dos"
game.apply_armips_patch("dos_fix_map_explore_bug")
end
if GAME == "por"
game.apply_armips_patch("por_fix_map_explore_bug")
end
# When portrait rando is on we need to be able to change the X/Y pos each return portrait places you at individually.
# This patch recodes how the X/Y dest pos work so that they can be easily changed by the pickup randomizer later.
if GAME == "por" && options[:randomize_portraits]
game.apply_armips_patch("por_distinct_return_portrait_positions")
end
if GAME == "ooe" && room_rando?
# Fix softlocks that happen when entering the Lighthouse from the wrong door.
# If entering from the bottom right there's a wall that blocks it. That is removed.
# If entering from the top there are the breakable ceilings, so the player is teleported down to the bottom in that case.
game.apply_armips_patch("ooe_fix_lighthouse_other_entrances")
end
if options[:randomize_enemy_sprites] || options[:randomize_boss_sprites]
# Add a new function to load multi-gfx sprites that has the same function signature as the one that loads single-gfx sprites.
game.apply_armips_patch("#{GAME}_custom_load_sprite_func")
end
if GAME == "dos" && options[:dos_new_style_map]
# Make DoS maps look like PoR and OoE, with a hole in the doors and different colors.
game.apply_armips_patch("dos_add_hole_to_map_doors")
colors = renderer.read_single_palette(0x02079D5C, 16)
colors[1] = ChunkyPNG::Color.rgba(0x14<<3, 8<<3, 0x10<<3, 255) # Room fill color
colors[3] = ChunkyPNG::Color.rgba(0x1B<<3, 0x1B<<3, 0x1B<<3, 255) # Door color
renderer.save_palette_by_specific_palette_pointer(0x02079D5C, colors)
end
if GAME == "dos" && options[:randomize_bosses]
game.apply_armips_patch("dos_balore_face_player")
end
if GAME == "ooe" && options[:randomize_bosses]
game.apply_armips_patch("ooe_boss_orb_reloads_room")
end
if GAME == "dos" && options[:randomize_bosses]
game.apply_armips_patch("dos_fix_bosses_not_playing_music")
end
if GAME == "por" && options[:randomize_bosses]
game.apply_armips_patch("por_fix_bosses_not_playing_music")
end
if GAME == "dos"
game.apply_armips_patch("dos_new_map_tile_color")
end
if GAME == "dos"
game.apply_armips_patch("dos_prevent_multiple_cutscenes_in_first_room")
end
if GAME == "ooe"
# Apply a patch to fix a crash that happens in vanilla when you unlock the back Kalidus entrance before ever visiting the front entrance.
# Which patch we use depends on whether world map exits are randomized or not.
if options[:randomize_world_map_exits]
# If world map exits are randomized, we need unlocking the back entrance to not automatically unlock the front entrance as well.
# So this patch allows setting var B to 3 to unlock the front entrance, or 1 to unlock the back entrance.
game.apply_armips_patch("ooe_allow_separate_kalidus_entrance_unlocks")
else
# If world map exits are not randomized, we can just have it so unlocking the back exit unlocks both the front and back entrances at the same time.
game.apply_armips_patch("ooe_fix_kalidus_back_entrance_crash")
end
end
if GAME == "por"
# Allow using warp points to go between different area maps.
game.apply_armips_patch("por_inter-area_warps")
end
if GAME == "ooe"
# Allow using warp points to go between different area maps.
game.apply_armips_patch("ooe_inter-area_warps")
end
if GAME == "por" && options[:show_map_markers_on_top_screen]
game.apply_armips_patch("por_map_markers_on_top_screen")
# The patch handles the code, but the top screen doesn't normally have the correct GFX or palette to display the markers.
# So we need to copy the appropriate parts of the GFX image and the appropriate palette from the bottom screen to the top screen.
bottom_screen_ui_palette_ptr = 0x022C1554
bottom_screen_ui_palette_index = 4
top_screen_ui_palette_ptr = 0x022C1490
top_screen_ui_palette_index = 5
marker_palette = renderer.generate_palettes(bottom_screen_ui_palette_ptr, 16)[bottom_screen_ui_palette_index]
renderer.save_palette(marker_palette, top_screen_ui_palette_ptr, top_screen_ui_palette_index, 16)
bottom_screen_ui_gfx_ptr = 0x022CDA9C
top_screen_ui_gfx_ptr = 0x022CBA90
bottom_gfx = GfxWrapper.new(bottom_screen_ui_gfx_ptr, game.fs)
top_gfx = GfxWrapper.new(top_screen_ui_gfx_ptr, game.fs)
bottom_image = renderer.render_gfx_page(bottom_gfx, marker_palette)
marker_image = bottom_image.crop(32, 0, 16, 16)
top_image = renderer.render_gfx_1_dimensional_mode(top_gfx, marker_palette)
top_image.compose!(marker_image, 112, 0)
renderer.save_gfx_page_1_dimensional_mode(top_image, top_gfx, top_screen_ui_palette_ptr, 16, top_screen_ui_palette_index)
end
if GAME == "ooe" && options[:show_map_markers_on_top_screen]
game.apply_armips_patch("ooe_map_markers_on_top_screen")
end
if GAME == "por"
game.apply_armips_patch("por_fix_waterwheel_particle_crash")
end
if GAME == "dos" && options[:randomize_bosses]
game.apply_armips_patch("dos_hide_tower_boss_until_top_floor")
end
# Then tell the free space manager that the entire file is available for free use, except for the parts we've already used with the above patches.
new_overlay_path = "/ftc/overlay9_#{NEW_OVERLAY_ID}"
new_overlay_file = game.fs.files_by_path[new_overlay_path]
new_overlay_size = new_overlay_file[:size]
game.fs.mark_space_unused(new_overlay_path, new_overlay_size, NEW_OVERLAY_FREE_SPACE_MAX_SIZE-new_overlay_size)
end
def apply_tweaks
# Adds the seed to the start a new game menu text.
game_start_text_id = case GAME
when "dos"
0x421
when "por"
0x5BC
when "ooe"
0x4C5
end
text = game.text_database.text_list[game_start_text_id]
text.decoded_string = "Starts a new game. Seed:\\n#{@seed}"
if GAME == "dos"
# Modify that one pit in the Demon Guest House so the player can't get stuck in it without double jump.
layer = game.areas[0].sectors[1].rooms[0x26].layers.first
layer.tiles[0x17].index_on_tileset = 0x33
layer.tiles[0x18].index_on_tileset = 0x33
layer.tiles[0x18].horizontal_flip = true
layer.tiles[0x27].index_on_tileset = 0x43
layer.tiles[0x28].index_on_tileset = 0x43
layer.tiles[0x28].horizontal_flip = true
layer.write_to_rom()
end
if GAME == "dos"
# Move the boss door to the left of Zephyr's room to the correct spot so it's not offscreen.
boss_door = game.areas[0].sectors[8].rooms[0xD].entities[1]
boss_door.x_pos = 0xF0
boss_door.write_to_rom()
end
if (options[:randomize_boss_souls] || options[:randomize_bosses]) && GAME == "dos"
# If the player beats Balore but doesn't own Balore's soul they will appear stuck. (Though they could always escape with suspend.)
# Also if Balore is put in a random room the blocks won't fit at all in that room.
# So get rid of the line of code Balore runs when he dies that recreates the Balore blocks in the room.
game.fs.load_overlay(23)
game.fs.write(0x02300808, [0xE1A00000].pack("V"))
end
if GAME == "dos" && options[:randomize_bosses]
# When you kill a boss, they depend on the magic seal being created properly, otherwise they softlock the game and never die.
# Death has so many GFX pages that if there are any other things in the room with him (such as Gergoth's floors) the magic seal doesn't have enough extra memory to load in.
# So when boss randomizer is on we need to make the magic seal still set 020F6E0B,1 on to indicate the magic seal was completed.
# In MakeMagicSeal, nop out the 2 lines that would return early if the magic seals's GFX couldn't be loaded.
# Even without its graphics, the magic seal still functions as normal - it's just the "completed visual" won't show up.
# Also, Death's body and scythes get sucked into the upper left corner of the room instead of the magic seal, since the magic seal doesn't exist.
game.fs.write(0x02215800, [0xE1A00000].pack("V"))
game.fs.write(0x02215804, [0xE1A00000].pack("V"))
end
if options[:randomize_enemies] && GAME == "por"
# Remove the line of code that spawns the sand to go along with Sand Worm/Poison Worm.
# This sand can cause various bugs depending on the room, such as teleporting the player out of bounds, preventing the player from picking up items, and turning the background into an animated rainbow.
game.fs.load_overlay(69)
game.fs.write(0x022DA394, [0xE3A00000].pack("V"))
end
if GAME == "dos"
# Always apply the patch to fix the first ability soul giving you multiple abilities.
game.apply_armips_patch("dos_fix_first_ability_soul")
end
if GAME == "dos" && options[:no_touch_screen]
game.apply_armips_patch("dos_skip_drawing_seals")
game.apply_armips_patch("dos_melee_balore_blocks")
game.apply_armips_patch("dos_skip_name_signing")
end
if GAME == "dos" && options[:fix_luck]
game.apply_armips_patch("dos_fix_luck")
end
if GAME == "dos" && options[:remove_slot_machines]
game.each_room do |room|
room.entities.each do |entity|
if entity.is_special_object? && entity.subtype == 0x26
entity.type = 0
entity.write_to_rom()
end
end
end
end
if GAME == "dos" && options[:unlock_boss_doors]
game.apply_armips_patch("dos_skip_boss_door_seals")
end
if GAME == "dos" && options[:always_start_with_rare_ring]
# Make the game think AoS is always in the GBA slot.
game.fs.write(0x02000D84, [0xE3A00001].pack("V"))
end
if GAME == "dos" && options[:randomize_pickups]
game.apply_armips_patch("dos_julius_start_with_tower_key")
end
if GAME == "dos" && !options[:randomize_maps]
# Update the vanilla map to show mirror rooms in orange.
# (The map rando handles doing this when it's on, so this tweak only needs to be run when the map rando is off.)
map = game.get_map(0, 0)
map.tiles.each do |tile|
next if tile.is_blank
room = game.areas[0].sectors[tile.sector_index].rooms[tile.room_index]
tile.is_entrance = room.entities.any?{|e| e.is_special_object? && e.subtype == 0xA} # Mirror
end
map.write_to_rom()
end
if GAME == "por" && options[:fix_infinite_quest_rewards]
game.apply_armips_patch("por_fix_infinite_quest_rewards")
end
if GAME == "por" && options[:skip_emblem_drawing]
game.apply_armips_patch("por_skip_emblem_drawing")
end
if GAME == "por" && options[:por_nerf_enemy_resistances]
game.apply_armips_patch("por_nerf_enemy_resistances")
end
if GAME == "por"
# Update the boss indexes corresponding to each of the portraits.
# These can be changed by the boss randomizer.
# These must be updated so Burnt Paradise and 13th Street know what boss unlocks them.
# Also so the monster overlay on the front of the portrait knows when to disappear.
@boss_id_for_each_portrait.each do |portrait_name, boss_id|
boss_index = BOSS_ID_TO_BOSS_INDEX[boss_id]
area_index = PickupRandomizer::PORTRAIT_NAME_TO_DATA[portrait_name][:area_index]
game.fs.write(0x020F4E78+area_index, [boss_index].pack("C"))
end
end
if GAME == "por"
# Update the boss death flags checked by the studio portrait.
# These can be modified by both short mode and the boss randomizer.
boss_flag_checking_code_locations = [0x02076B84, 0x02076BA4, 0x02076BC4, 0x02076BE4]
@portraits_needed_to_open_studio_portrait.each_with_index do |portrait_name, i|
boss_id = @boss_id_for_each_portrait[portrait_name]
boss_index = BOSS_ID_TO_BOSS_INDEX[boss_id]
code_location = boss_flag_checking_code_locations[i]
game.fs.replace_hardcoded_bit_constant(code_location, boss_index)
end
end
if GAME == "por" && options[:randomize_portraits]
# The 13 Street and Burnt Paradise portraits try to use the blue flame animation of object 5F when they're still locked.
# But object 5F's sprite is not loaded unless object 5F is in the room and before the portrait, so trying to use a sprite that's not loaded causes a crash on no$gba and probably real hardware.
# So we change the flames to use an animation in the common sprite, which is always loaded, so we still have a visual indicator of the portraits being locked without a crash.
game.fs.write(0x020767DC, [0xEBFEA5BA].pack("V")) # Change this call to LoadCommonSprite
game.fs.write(0x02076804, [0x21].pack("C")) # Change the animation to 21
# We also raise the z-pos of the portrait frame so that it doesn't appear behind room tiles.
game.fs.write(0x0207B9C0, [0x5380].pack("V"))
end
if GAME == "por" && (options[:randomize_portraits] || options[:por_short_mode])
game.areas.each do |area|
map = game.get_map(area.area_index, 0)
map.tiles.each do |tile|
room = game.areas[area.area_index].sectors[tile.sector_index].rooms[tile.room_index]
if room.room_str == "00-0B-00"
# The studio portrait room. Always mark this as having a portrait.
tile.is_entrance = true
next
end
tile_x_off = (tile.x_pos - room.room_xpos_on_map) * SCREEN_WIDTH_IN_PIXELS
tile_y_off = (tile.y_pos - room.room_ypos_on_map) * SCREEN_HEIGHT_IN_PIXELS
tile.is_entrance = room.entities.any? do |e|
if e.is_special_object? && [0x1A, 0x76, 0x86, 0x87].include?(e.subtype)
# Portrait.
# Clamp the portrait's X and Y within the bounds of the room so we can detect that it's on a tile even if it's slightly off the edge of the room.
x = [0, e.x_pos, room.width*SCREEN_WIDTH_IN_PIXELS-1].sort[1] # Clamp the portrait's X within the room
y = [0, e.y_pos, room.height*SCREEN_HEIGHT_IN_PIXELS-1].sort[1] # Clamp the portrait's Y within the room
# Then check if the clamped X and Y are within the current tile.
(tile_x_off..tile_x_off+SCREEN_WIDTH_IN_PIXELS-1).include?(x) && (tile_y_off..tile_y_off+SCREEN_HEIGHT_IN_PIXELS-1).include?(y)
end
end
end
map.write_to_rom()
end
end
if GAME == "por" && (options[:randomize_portraits] || options[:por_short_mode])
# Remove the cutscene where the player characters talk about the first portrait they find that leads to City of Haze.
# It bugs out the game pretty seriously if there's no portrait in the room.
first_portrait_cutscene = game.entity_by_str("00-01-00_01")
first_portrait_cutscene.type = 0
first_portrait_cutscene.write_to_rom()
end
if GAME == "por"
# If the portrait randomizer or short mode remove all 4 portraits from the studio portrait room, going into the studio portrait crashes on real hardware.
# This is because it needs part of the small square-framed portrait's sprite.
# So in this case we just add a dummy portrait out of bounds so it loads its sprite.
studio_portrait_room = game.room_by_str("00-0B-00")
square_framed_portrait = studio_portrait_room.entities.find{|e| e.is_special_object? && [0x76, 0x87].include?(e.subtype)}
if square_framed_portrait.nil?
dummy_portrait = Entity.new(studio_portrait_room, game.fs)
dummy_portrait.x_pos = 0
dummy_portrait.y_pos = -0x100
dummy_portrait.type = 2
dummy_portrait.subtype = 0x76
dummy_portrait.var_a = 0
dummy_portrait.var_b = 0
studio_portrait_room.entities << dummy_portrait
studio_portrait_room.write_entities_to_rom()
end
end
if GAME == "por" && options[:randomize_portraits]
# Portraits that return to the castle from 13th Street/Forgotten City/Burnt Paradise/Dark Academy (object 87) place the player at a different X position than other portraits.
# Those other positions aren't taken into account by the logic, so change these to use the same X pos (80) as the others.
game.fs.replace_arm_shifted_immediate_integer(0x02078EA0, 0x80)
game.fs.replace_arm_shifted_immediate_integer(0x02078E98, 0x80)
game.fs.replace_arm_shifted_immediate_integer(0x02078EA8, 0x80)
game.fs.replace_arm_shifted_immediate_integer(0x02078EB0, 0x80)
end
if GAME == "por"
# The conditions for unlocking the second tier of portraits is different in Richter/Sisters/Old Axe Armor mode compared to Jonathan mode.
# The logic only takes Jonathan mode into account, so change the second tier of portraits to always use the Jonathan mode conditions even in the other modes.
game.fs.write(0x02078F98, [0xE3A01000].pack("V"))
end
if GAME == "por"
# Fix a bug in the base game where you have a couple seconds after picking up the cog where you can use a magical ticket to skip fighting Legion.
# To do this we make Legion's horizontal boss doors turn on global flag 2 (in the middle of a boss fight, prevents magical ticket use) as soon as you enter the room, in the same line that it was turning on global flag 1.
game.fs.load_overlay(98)
game.fs.write(0x022E88E4, [3].pack("C"))
end
if GAME == "por"
# Fix a bug in the base game where skipping the cutscene after you kill Death too quickly will prevent Death's boss death flag from being set.
game.fs.load_overlay(64)
game.fs.write(0x022D8B18, [0xE1A00000].pack("V")) # nop out the line that waits 2 seconds before making Death put down his arms and set his boss death flag.
end
if GAME == "por"
# Allow Jonathan and Charlotte to use weapons with swing anims they originally couldn't use.
# Normally Jonathan cannot use books because he doesn't have any book swing anims specified.
# Copy his punch swing anims to his book swing anims so that he can.
jonathan = game.players[0]
punch_swing_anims_ptr = game.fs.read(jonathan["Swing anims ptr"] + 7*4, 4).unpack("V").first
game.fs.write(jonathan["Swing anims ptr"] + 9*4, [punch_swing_anims_ptr].pack("V*"))
# Normally Charlotte cannot use any weapons besides books and punches because she doesn't have swing anims specified for most types of weapons.
# Copy her book swing anims to all her other types so that she can.
charlotte = game.players[1]
book_swing_anims_ptr = game.fs.read(charlotte["Swing anims ptr"] + 9*4, 4).unpack("V").first
game.fs.write(charlotte["Swing anims ptr"], ([book_swing_anims_ptr]*10).pack("V*"))
end
if GAME == "por"
# Give Charlotte the ability to jumpkick and superjump so she's on par with Jonathan in terms of mobility.
charlotte = game.players[1]
charlotte["Actions"][5] = true # Can jumpkick
charlotte["Actions"][6] = true # Can superjump
# And set her state anims for both of those to be the broom animation.
# For superjumping the broom looks appropriate.
# For jumpkicking none of her animations look appropriate, so go with the broom anyway since it looks ridiculous.
state_anims = game.state_anims_for_player(1)
state_anims[0x0E] = 0x0A # Superjumping
state_anims[0x33] = 0x0A # Jumpkicking straight down
state_anims[0x34] = 0x0A # Jumpkicking diagonally down
game.save_state_anims_for_player(1, state_anims)
# Also give her the ability to use critical arts.
# To do this we must enable her "Can combo tech" bit.
charlotte["??? bitfield"][3] = true # Can combo tech
# But enabling that bit causes an issue - by default that bit means she can do *all* combo techs, but we only want her to do critical art.
# So we need to specify a list of combo techs that she can and can't do, so we can set them all to 00 to specify that she can't do them.
# Get 4 bytes of free space for Charlotte's combo techs list.
charlotte["Combo tech ptr"] = game.fs.get_free_space(4)
# Then set all four to 0.
game.fs.write(charlotte["Combo tech ptr"], [0, 0, 0, 0].pack("CCCC"))
charlotte.write_to_rom()
end
if GAME == "por" && room_rando?
# In room rando, unlock the bottom passage in the second room of the game by default to simplify the logic. (The one that usually needs you to complete the Nest of Evil quest.)
game.fs.load_overlay(78)
game.fs.write(0x022E8988, [0xE3E00000].pack("V")) # mvn r0, 0h
end
if GAME == "por" && room_rando?
balore_room = game.room_by_str("09-00-0A")
unless @unused_rooms.include?(balore_room)
right_door_out = balore_room.doors.find{|d| d.direction == :right}
if right_door_out
left_door_in = right_door_out.destination_door
# Left door that leads into Balore's room in Nest of Evil.
# If the player enters through this door they will be behind Balore, unable to get into the room, and Balore can't hit them.
# The player can still kill Balore but there's no challenge in that.
# So we make the door leave the player on the left half of the room, in front of Balore on the ground.
left_door_in.dest_x_2 = -0xC0
left_door_in.dest_y_2 = 0x30
left_door_in.write_to_rom()
end
end
gergoth_room = game.room_by_str("09-00-14")
unless @unused_rooms.include?(gergoth_room)
right_door_out = gergoth_room.doors.find{|d| d.direction == :right}
if right_door_out
left_door_in = right_door_out.destination_door
# Do the same thing for the door into Gergoth's room as we did for the one into Balore's room.
left_door_in.dest_x_2 = -0xC0
left_door_in.dest_y_2 = 0x30
left_door_in.write_to_rom()
end
end
end
if GAME == "por" && (options[:randomize_starting_room] || options[:randomize_maps])
# If the starting room (or map) is randomized, we need to lower the drawbridge by default or the player can't ever reach the first few rooms of the entrance.
# Do this by making the drawbridge think the game mode is Richter mode, since it automatically lowers itself in that case.
game.fs.load_overlay(78)
game.fs.write(0x022E8880, [0xE3A01001].pack("V")) # mov r1, 1h
end
if GAME == "por" && (options[:randomize_area_connections] || options[:randomize_maps])
# Some bosses (e.g. Stella) connect directly to a transition room.
# This means the boss door gets placed in whatever transition room gets connected to the boss by the area randomizer.
# But almost all transition room hiders have a higher Z-position than boss doors, hiding the boss door.
# We want the boss door to be visible as a warning for the boss. So change all boss doors to have a higher Z-pos.
# Boss doors normally have 0x4F80 Z-pos, we raise it to 0x65A0 (which has the side effect of putting it on top of the player too, who is at 0x5B00).
game.fs.write(0x020718CC, [0x65A0].pack("V"))
end
if GAME == "por" && options[:randomize_portraits] && options[:randomize_room_connections]
# Room rando can make it hard to know where to go to find portraits, so reveal all portrait tiles on the map by default.
game.apply_armips_patch("por_reveal_all_portraits_on_map")
end
if GAME == "por" && room_rando?
# In room rando, get rid of the left wall of the sisters boss fight and replace it with a boss door instead.
# If the player entered the room from the left they would get stuck in the wall otherwise.
entity = game.entity_by_str("00-0B-01_01")
entity.subtype = BOSS_DOOR_SUBTYPE
entity.y_pos = 0xB0
entity.var_a = 1
entity.var_b = 0xE
entity.write_to_rom()
end
if GAME == "dos" && options[:randomize_starting_room]
# If a soul candle gets placed in a starting save room, it will appear behind the save point's graphics.
# We need to raise the sould candle's Z-pos from 5200 to 5600 so it appears on top of the save point.
game.fs.write(0x021A4444, [0x56].pack("C"))
# Note that this also affects other candles besides soul candles. Hopefully it doesn't make them look weird in any rooms.
end
if GAME == "ooe" && options[:always_dowsing]
game.apply_armips_patch("ooe_always_dowsing")
end
if GAME == "ooe" && options[:summons_gain_extra_exp]
# Increase the rate that summons gain EXP.
# Normally they gain 3 EXP every time they hit an enemy, and need 0x7FFF to level up once.
# So we significantly increase that 3 per hit so they level up faster.
game.fs.write(0x0207D438, [64].pack("C"))
end
if GAME == "por" && !options[:randomize_skill_behavior]
# If the SP needed to master subweapons isn't randomized, reduce it to 1/4 vanilla so it's more attainable in a run.
(0x150..0x1A0).each do |skill_global_id|
skill = game.items[skill_global_id]
is_spell = skill["??? bitfield"][2]
next if is_spell
skill_extra_data = game.items[skill_global_id+0x6C]
skill_extra_data["SP to Master"] /= 4
skill_extra_data.write_to_rom()
end
end
if GAME == "por" && options[:allow_mastering_charlottes_skills]
game.apply_armips_patch("por_allow_mastering_spells")
if !options[:randomize_skill_behavior]
# If skill behavior rando is off, give all spells a default SP to master of 500.
(0x150..0x1A0).each do |skill_global_id|
skill = game.items[skill_global_id]
is_spell = skill["??? bitfield"][2]
next unless is_spell
next if SKILLS_THAT_CANT_GAIN_SP.include?(skill.name)
skill_extra_data = game.items[skill_global_id+0x6C]
case skill.name
when "Sanctuary", "Speed Up", "Eye for an Eye", "Summon Medusa", "Salamander", "Cocytus", "Thor's Bellow"
skill_extra_data["SP to Master"] = 150
when "Dark Rift", "Summon Ghost", "Summon Skeleton", "Summon Frog"
skill_extra_data["SP to Master"] = 300
else
skill_extra_data["SP to Master"] = 500
end
skill_extra_data.write_to_rom()
end
end
end
if GAME == "ooe" && !options[:randomize_skill_stats]
# If skill damage isn't randomized, buff familiar summons to 5x vanilla damage so they're not completely useless.
(0x47..0x4D).each do |skill_global_id|
skill = game.items[skill_global_id]
skill["DMG multiplier"] *= 5
skill.write_to_rom()
end
end
if GAME == "ooe"
# Remove the bottom left boss door in Brachyura's boss room.
# This is so the player can escape from the room even if they accidentally went in there before they could fight Brachyura.
boss_door = game.entity_by_str("09-00-05_03")
boss_door.type = 0
boss_door.write_to_rom()
end
if options[:unlock_all_modes]
game.apply_armips_patch("#{GAME}_unlock_everything")
end
if options[:reveal_breakable_walls]
game.apply_armips_patch("#{GAME}_reveal_breakable_walls")
end
if options[:reveal_bestiary]
game.apply_armips_patch("#{GAME}_reveal_bestiary")
end
if GAME == "por" && options[:always_show_drop_percentages]
game.apply_armips_patch("por_always_show_drop_percentages")
end
if GAME == "por"
# Fix common enemy creature setting boss death flag.
game.apply_armips_patch("por_fix_the_creature_boss_death_flag")
end
if GAME == "por"
# Make Speed Up's effect not run out after 60 seconds.
# Change conditional branch for the timer being above 0 into an unconditional branch.
game.fs.write(0x021EE19C, [0xEA000003].pack("V"))
end
if GAME == "dos"
# When you walk over an item you already have 9 of, the game plays a sound effect every 0.5 seconds.
# We change it to play once a second so it's less annoying.
game.fs.write(0x021E8B30, [0x3C].pack("C"))
end
if GAME == "por"
# PoR Malphas has leftover code from DoS to set the "during a boss fight" global game flag.
# We need to remove the line that sets this flag, otherwise portraits and magical tickets won't work in any room he is in.
game.fs.load_overlay(46)
game.fs.write(0x022D7A10, [0xE1A00000].pack("V")) # nop
end
if GAME == "dos" && options[:randomize_weapon_synths]
# Fix the stat difference display in the weapon synth screen.
# It assumes that all items are weapons and displays a weapon's stats by default.
# This change tells it to make no assumptions and calculate what the item type actually is.
game.fs.write(0x02032A2C, [0xE3E00000].pack("V")) # mvn r0, 0h
game.fs.write(0x02032A3C, [0xE3E00000].pack("V")) # mvn r0, 0h
end
if GAME == "dos" && options[:randomize_starting_room]
# Fix the HUD disappearing with the starting room rando.
game.fs.write(0x021D9910, [0xE3C11081].pack("V")) # This line normally disables global game flag 0x1, we change it to disable both 0x1 and 0x80 (which hides the HUD).
game.fs.write(0x021C782C, [0xE1A00000].pack("V")) # This line explicitly resets 0x80 later on, so nop it out.
game.fs.write(0x021C7518, [0xE1A00000].pack("V")) # Same as above, but this is for if the player watched the cutscene instead of skipping it.
end
if room_rando?