-
Notifications
You must be signed in to change notification settings - Fork 1
/
smw-bizhawk.lua
1382 lines (1110 loc) · 54.7 KB
/
smw-bizhawk.lua
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
---------------------------------------------------------------------------
-- Super Mario World (U) Utility Script for BizHawk - 1.9.0 version or higher
-- http://tasvideos.org/BizHawk.html
--
-- Author: Rodrigo A. do Amaral (Amaraticando)
-- Git repository: https://github.com/rodamaral/smw-tas
---------------------------------------------------------------------------
--#############################################################################
-- CONFIG:
-- Display
local DISPLAY_MISC_INFO = true
local SHOW_PLAYER_INFO = true
local SHOW_PLAYER_HITBOX = true -- fix: in a way that user can change
local SHOW_INTERACTION_POINTS = true -- fix: idem
local SHOW_SPRITE_INFO = true
local SHOW_LEVEL_INFO = true
local SHOW_SPRITE_HITBOX = true
local SHOW_YOSHI_INFO = true
local SHOW_COUNTERS_INFO = true
local SHOW_DEBUG_INFO = false
-- Colours (text)
local DEFAULT_TEXT_OPACITY = 1.0
local DEFAULT_BG_OPACITY = 0.6
local TEXT_COLOR = 0xffffffff
local BACKGROUND_COLOR = 0xff000000 -- used as alert_text default background
local OUTLINE_COLOR = 0xff000060
local WEAK_COLOR = 0xb0a9a9a9
local WARNING_COLOR = 0xffff0000
local WARNING_BG = 0xff0000ff
-- Colours (hitbox and related text)
local MARIO_COLOR = 0xffff0000
local MARIO_BG = 0
local MARIO_BG_MOUNTED = 0
local INTERACTION_COLOR = 0xffffffff
local INTERACTION_BG = 0x20000000
local INTERACTION_COLOR_WITHOUT_HITBOX = 0xa0000000
local INTERACTION_BG_WITHOUT_HITBOX = 0x70000000
local SPRITES_COLOR = {0xff00ff00, 0xff0000ff, 0xffffff00, 0xffff8000, 0xffff00ff, 0xffb00040}
local SPRITES_BG = 0x500000b0
local SPRITES_INTERACTION_COLOR = 0xffffff00 -- unused yet
local YOSHI_COLOR = 0xff00ffff
local YOSHI_BG = 0x5000ffff
local YOSHI_BG_MOUNTED = 0
local TONGUE_BG = 0x60ff0000
local EXTENDED_SPRITES = 0xffffffff -- unused yet
local CAPE_COLOR = 0xffffd700
local CAPE_BG = 0x30ffd700
-- Font settings
local BIZHAWK_FONT_HEIGHT = 14
local BIZHAWK_FONT_WIDTH = 10
-- Symbols
local LEFT_ARROW = "<-"
local RIGHT_ARROW = "->"
-- END OF CONFIG < < < < < < <
--#############################################################################
-- INITIAL STATEMENTS:
-- Text/Background_max_opacity is only changed by the player using the hotkeys
-- Text/Bg_opacity must be used locally inside the functions
local Text_max_opacity = DEFAULT_TEXT_OPACITY
local Background_max_opacity = DEFAULT_BG_OPACITY
local Outline_max_opacity = 1
local Text_opacity = 1
local Bg_opacity = 1
local fmt = string.format
local gui = gui
local mainmemory = mainmemory
-- Compatibility
local u8 = mainmemory.read_u8
local s8 = mainmemory.read_s8
local u16 = mainmemory.read_u16_le
local s16 = mainmemory.read_s16_le
local u24 = mainmemory.read_u24_le
local s24 = mainmemory.read_s24_le
--#############################################################################
-- GAME AND SNES SPECIFIC MACROS:
SMW = {
-- Game Modes
game_mode_overworld = 0x0e,
game_mode_level = 0x14,
sprite_max = 12, -- maximum number of sprites
}
WRAM = {
game_mode = 0x0100,
real_frame = 0x0013,
effective_frame = 0x0014,
lag_indicator = 0x01fe,
timer_frame_counter = 0x0f30,
RNG = 0x148d,
current_level = 0x00fe, -- plus 1
sprite_memory_header = 0x1692,
lock_animation_flag = 0x009d, -- Most codes will still run if this is set, but almost nothing will move or animate.
level_mode_settings = 0x1925,
star_road_speed = 0x1df7,
star_road_timer = 0x1df8,
exit_level_byte = 0x0DD5,
--exit_level_byte.no_auto_walk = 0x0, -- 00 = do not auto-walk while on overworld,
--exit_level_byte.normal_level_beat = 0x01, -- 0x01 = beaten level, regular, for the first time
--exit_level_byte.level_beat_secret = 0x02, -- 0x02 = level beaten, for the first time, secret
--exit_level_byte.level_exit_dying_start_select = 0x80, -- 0x80 = Exit level with either start or select, or by dying
--exit_level_byte.level_beat_not_first_time = 0xE0, -- 0xE0 = Beat level, but not for the first time.
-- cheats
frozen = 0x13fb,
level_paused = 0x13d4,
level_index = 0x13bf,
room_index = 0x00ce,
level_flag_table = 0x1ea2,
level_exit_type = 0x0dd5,
midway_point = 0x13ce,
-- Camera
camera_x = 0x001a,
camera_y = 0x001c,
screens_number = 0x005d,
hscreen_number = 0x005e,
vscreen_number = 0x005f,
vertical_scroll = 0x1412, -- #$00 = Disable; #$01 = Enable; #$02 = Enable if flying/climbing/etc.
-- Sprites
sprite_status = 0x14c8,
sprite_throw = 0x1504, --
chuckHP = 0x1528, --
sprite_stun = 0x1540,
sprite_contact_mario = 0x154c,
spriteContactSprite = 0x1564, --
spriteContactoObject = 0x15dc, --
sprite_number = 0x009e,
sprite_x_high = 0x14e0,
sprite_x_low = 0x00e4,
sprite_y_high = 0x14d4,
sprite_y_low = 0x00d8,
sprite_x_sub = 0x14f8,
sprite_y_sub = 0x14ec,
sprite_x_speed = 0x00b6,
sprite_y_speed = 0x00aa,
sprite_direction = 0x157c,
sprite_x_offscreen = 0x15a0,
sprite_y_offscreen = 0x186c,
sprite_miscellaneous = 0x160e,
sprite_miscellaneous2 = 0x163e,
sprite_1_tweaker = 0x1656,
sprite_2_tweaker = 0x1662,
sprite_3_tweaker = 0x166e,
sprite_4_tweaker = 0x167a,
sprite_5_tweaker = 0x1686,
sprite_6_tweaker = 0x190f,
sprite_tongue_length = 0x151c,
sprite_tongue_timer = 0x1558,
sprite_tongue_wait = 0x14a3,
sprite_yoshi_squatting = 0x18af,
sprite_buoyancy = 0x190e,
reznor_killed_flag = 0x151c,
sprite_turn_around = 0x15ac,
-- Player
x = 0x0094,
y = 0x0096,
previous_x = 0x00d1,
previous_y = 0x00d3,
x_sub = 0x13da,
y_sub = 0x13dc,
x_speed = 0x007b,
x_subspeed = 0x007a,
y_speed = 0x007d,
direction = 0x0076,
animation_trigger = 0x0071,
is_ducking = 0x0073,
p_meter = 0x13e4,
take_off = 0x149f,
powerup = 0x0019,
cape_spin = 0x14a6,
cape_fall = 0x14a5,
cape_interaction = 0x13e8,
flight_animation = 0x1407,
diving_status = 0x1409,
player_in_air = 0x0071,
climbing_status = 0x0074,
spinjump_flag = 0x140d,
player_blocked_status = 0x0077,
player_item = 0x0dc2, --hex
cape_x = 0x13e9,
cape_y = 0x13eb,
on_ground = 0x13ef,
on_ground_delay = 0x008d,
on_air = 0x0072,
can_jump_from_water = 0x13fa,
carrying_item = 0x148f,
mario_score = 0x0f34,
player_looking_up = 0x13de,
mario_lives = 0x0DBE,
-- Yoshi
yoshi_riding_flag = 0x187a, -- #$00 = No, #$01 = Yes, #$02 = Yes, and turning around.
yoshi_tongue_height = 0x188b,
-- Timer
--keep_mode_active = 0x0db1,
score_incrementing = 0x13d6,
end_level_timer = 0x1493,
game_over_time_out_flag = 0x143B,
multicoin_block_timer = 0x186b,
gray_pow_timer = 0x14ae,
blue_pow_timer = 0x14ad,
dircoin_timer = 0x190c,
pballoon_timer = 0x1891,
star_timer = 0x1490,
animation_timer = 0x1496,--
invisibility_timer = 0x1497,
fireflower_timer = 0x149b,
yoshi_timer = 0x18e8,
swallow_timer = 0x18ac,
lakitu_timer = 0x18e0,
}
local WRAM = WRAM -- to make it slightly faster
local HITBOX_SPRITE = {
[0x00] = { left = 0, right = 16, up = 3, down = 15},
[0x01] = { left = 0, right = 16, up = 3, down = 26},
[0x02] = { left = 14, right = 34, up = -2, down = 18},
[0x03] = { left = 18, right = 30, up = 8, down = 18},
[0x04] = { left = -2, right = 50, up = -2, down = 14},
[0x05] = { left = -2, right = 82, up = -2, down = 14},
[0x06] = { left = -1, right = 17, up = 2, down = 28},
[0x07] = { left = 6, right = 50, up = 8, down = 58},
[0x08] = { left = -10, right = 26, up = -2, down = 16},
[0x09] = { left = 2, right = 14, up = 19, down = 29}, -- Yoshi, default = {]=] left = -4, right = 20, up = 8, down = 40},
[0x0a] = { left = 1, right = 6, up = 7, down = 11},
[0x0b] = { left = 4, right = 11, up = 6, down = 11},
[0x0c] = { left = -1, right = 16, up = -2, down = 22},
[0x0d] = { left = -2, right = 17, up = -4, down = 14},
[0x0e] = { left = 4, right = 28, up = 6, down = 28},
[0x0f] = { left = 0, right = 40, up = -2, down = 18},
[0x10] = { left = -2, right = 17, up = -2, down = 32},
[0x11] = { left = -26, right = 42, up = -24, down = 42},
[0x12] = { left = -6, right = 6, up = 16, down = 70},
[0x13] = { left = -6, right = 6, up = 16, down = 134},
[0x14] = { left = 2, right = 30, up = 2, down = 16},
[0x15] = { left = -2, right = 17, up = -2, down = 14},
[0x16] = { left = -6, right = 22, up = -12, down = 14},
[0x17] = { left = 0, right = 16, up = 8, down = 79},
[0x18] = { left = 0, right = 16, up = 19, down = 79},
[0x19] = { left = 0, right = 16, up = 35, down = 79},
[0x1a] = { left = 0, right = 16, up = 51, down = 79},
[0x1b] = { left = 0, right = 16, up = 67, down = 79},
[0x1c] = { left = -2, right = 12, up = 10, down = 60},
[0x1d] = { left = 0, right = 32, up = -3, down = 26},
[0x1e] = { left = 4, right = 11, up = -8, down = 26}, -- Goal tape, default = { left = -34, right = 18, up = -8, down = 26},
[0x1f] = { left = -18, right = 34, up = -4, down = 16},
[0x20] = { left = -6, right = 6, up = -24, down = 2},
[0x21] = { left = -6, right = 6, up = 16, down = 42},
[0x22] = { left = -2, right = 18, up = 0, down = 18},
[0x23] = { left = -10, right = 26, up = -24, down = 10},
[0x24] = { left = -14, right = 46, up = 32, down = 90},
[0x25] = { left = -16, right = 48, up = 4, down = 26},
[0x26] = { left = -2, right = 34, up = 88, down = 98},
[0x27] = { left = -6, right = 22, up = -4, down = 22},
[0x28] = { left = -16, right = 16, up = -24, down = 18},
[0x29] = { left = -18, right = 18, up = -4, down = 25},
[0x2a] = { left = 0, right = 16, up = -8, down = 13},
[0x2b] = { left = -2, right = 18, up = 2, down = 80},
[0x2c] = { left = -10, right = 10, up = -8, down = 10},
[0x2d] = { left = 2, right = 14, up = 4, down = 10},
[0x2e] = { left = 0, right = 32, up = -2, down = 34},
[0x2f] = { left = 0, right = 32, up = -2, down = 32},
[0x30] = { left = 6, right = 26, up = -14, down = 16},
[0x31] = { left = -2, right = 50, up = -2, down = 18},
[0x32] = { left = -2, right = 50, up = -2, down = 18},
[0x33] = { left = -2, right = 66, up = -2, down = 18},
[0x34] = { left = -6, right = 6, up = -4, down = 6},
[0x35] = { left = 1, right = 23, up = 0, down = 34},
[0x36] = { left = 6, right = 62, up = 8, down = 56},
[0x37] = { left = -2, right = 17, up = -8, down = 14},
[0x38] = { left = 6, right = 42, up = 16, down = 58},
[0x39] = { left = 2, right = 14, up = 3, down = 15},
[0x3a] = { left = -10, right = 26, up = 16, down = 34},
[0x3b] = { left = -2, right = 18, up = 0, down = 15},
[0x3c] = { left = 10, right = 17, up = 10, down = 18},
[0x3d] = { left = 10, right = 17, up = 21, down = 43},
[0x3e] = { left = 14, right = 272, up = 18, down = 36},
[0x3f] = { left = 6, right = 18, up = 8, down = 34}
}
-- Creates a set from a list
local function make_set(list)
local set = {}
for _, l in ipairs(list) do set[l] = true end
return set
end
-- from sprite number, returns oscillation flag
-- A sprite must be here iff it processes interaction with player every frame AND this bit is not working in the sprite_4_tweaker WRAM(0x167a)
local OSCILLATION_SPRITES = make_set{0x0e, 0x21, 0x29, 0x35, 0x54, 0x74, 0x75, 0x76, 0x77, 0x78, 0x81, 0x83, 0x87}
-- Sprites that have a custom hitbox drawing
local ABNORMAL_HITBOX_SPRITES = make_set{0x62, 0x63, 0x6b, 0x6c}
--#############################################################################
-- SCRIPT UTILITIES:
local Isloaded, Movie_mode, Readonly, Currentframe, Framecount, Lagcount, Rerecords, Islagged
local function bizhawk_movie_info()
Isloaded = movie.isloaded() == true and "Loaded" or "Not loaded"
Movie_mode = movie.mode() -- string
Readonly = movie.getreadonly()
Currentframe = emu.framecount()
Framecount = movie.length()
Lagcount = emu.lagcount()
Rerecords = movie.rerecordcount() -- string
Islagged = emu.islagged()
--draw_text(0, 0, fmt("%s, %s, %s", tostring(Isloaded), Movie_mode, tostring(Readonly)))
--draw_text(0, BIZHAWK_FONT_HEIGHT, fmt("(%d/%d)| %d %d %dfps", Currentframe, Framecount, Lagcount, Rerecords, Movie_fps))
end
-- Get screen values of the game and emulator areas
local Border_left, Border_right, Border_top, Border_bottom, Buffer_width, Buffer_height
local Screen_size, Screen_width, Screen_height, Pixel_rate_x, Pixel_rate_y
local function bizhawk_screen_info()
Border_left = client.borderwidth() -- Borders' dimensions
Border_right = Border_left
Border_top = client.borderheight()
Border_bottom = Border_top
Buffer_width = client.bufferwidth() -- Game area
Buffer_height = client.bufferheight()
Screen_size = client.getwindowsize() -- Emulator area
Screen_width = client.screenwidth()
Screen_height = client.screenheight()
Pixel_rate_x = Buffer_width/256
Pixel_rate_y = Buffer_height/224
end
-- draw a pixel given (x,y) with SNES' pixel sizes
local draw_pixel = gui.drawPixel
-- draws a line given (x,y) and (x',y') with SNES' pixel sizes
local draw_line = gui.drawLine
local function draw_box(x1, y1, x2, y2, ...)
--[[
if x2 < x1 then
x1, x2 = x2, x1
end
if y2 < y1 then
y1, y2 = y2, y1
end
--]]
gui.drawBox(x1, y1, x2, y2, ...)
end
local draw_box2 = draw_box -- fix
-- Extension to the "gui" function, to handle opacity
gui.opacity = function(text, bg)
Text_opacity = text or Text_opacity
Bg_opacity = bg or Bg_opacity
return Text_opacity, Bg_opacity
end
-- Changes the default behavior of gui.text
local function new_gui_text(x, y, text, text_color, outline_color)
-- Reads external variables
local game_screen_x = Border_left
local game_screen_y = Border_top
--outline_color = change_transparency(outline_color, 0.8)
--text_color = change_transparency(text_color, 0.8)
gui.text(x + game_screen_x, y + game_screen_y - 2, text, outline_color, text_color)
end
-- Changes transparency of a color: result is opaque original * transparency level (0.0 to 1.0). Acts like gui.opacity() in Snex9s.
local function change_transparency(color, transparency)
if type(color) ~= "number" then
error("Color must be numeric. Color = "..color)
end
if transparency > 1 then transparency = 1 end
if transparency < 0 then transparency = 0 end
local a = bit.rshift(color, 24)
local rgb = color - bit.lshift(a, 24)
local new_a = math.ceil(transparency * a)
local new_color = bit.lshift(new_a, 24) + rgb
return new_color
end
-- returns the (x, y) position to start the text and its length:
-- number, number, number text_position(x, y, text, font_width, font_height[[[[, always_on_client], always_on_game], ref_x], ref_y])
-- x, y: the coordinates that the refereed point of the text must have
-- text: a string, don't make it bigger than the buffer area width and don't include escape characters
-- font_width, font_height: the sizes of the font
-- always_on_client, always_on_game: boolean
-- ref_x and ref_y: refer to the relative point of the text that must occupy the origin (x,y), from 0% to 100%
-- for instance, if you want to display the middle of the text in (x, y), then use 0.5, 0.5
local function text_position(x, y, text, font_width, font_height, always_on_client, always_on_game, ref_x, ref_y)
-- Reads external variables
local border_left = Border_left
local border_right = Border_right
local border_top = Border_top
local border_bottom = Border_bottom
local buffer_width = Buffer_width
local buffer_height = Buffer_height
-- text processing
text_length = string.len(text)
text_length = text_length*font_width
-- reference point
if not ref_x then ref_x = 0 end
if not ref_y then ref_y = 0 end
-- adjustment if text is supposed to be on screen area
local x_end = x + text_length
local y_end = y + font_height
-- actual position, relative to game area origin
local x = x - text_length*ref_x
local y = y - font_height*ref_y
if always_on_game then
if x < 0 then x = 0 end
if y < 0 then y = 0 end
if x_end > buffer_width then x = buffer_width - text_length end
if y_end > buffer_height then y = buffer_height - font_height end
elseif always_on_client then
if x < -border_left then x = -border_left end
if y < -border_top then y = -border_top end
if x_end > buffer_width + border_right then x = buffer_width + border_right - text_length end
if y_end > buffer_height + border_bottom then y = buffer_height + border_bottom - font_height end
end
return x, y, text_length
end
local function draw_text(x, y, text, ...)
-- Reads external variables
local font_width = BIZHAWK_FONT_WIDTH
local font_height = BIZHAWK_FONT_HEIGHT
local full_bg = false -- BizHawk doesn't offer this option
local bg_default_color = full_bg and BACKGROUND_COLOR or OUTLINE_COLOR
local text_color, halo_color, always_on_client, always_on_game, ref_x, ref_y
local arg1, arg2, arg3, arg4, arg5, arg6 = ...
if type(arg1) == "boolean" or type(arg1) == "nil" then
text_color = TEXT_COLOR
halo_color = bg_default_color
always_on_client, always_on_game, ref_x, ref_y = arg1, arg2, arg3, arg4
elseif type(arg2) == "boolean" or type(arg2) == "nil" then
text_color = arg1
halo_color = bg_default_color
always_on_client, always_on_game, ref_x, ref_y = arg2, arg3, arg4, arg5
else
text_color, halo_color = arg1, arg2
always_on_client, always_on_game, ref_x, ref_y = arg3, arg4, arg5, arg6
end
text_color = change_transparency(text_color, Text_max_opacity * Text_opacity)
halo_color = change_transparency(halo_color, Background_max_opacity * Bg_opacity)
local x_pos, y_pos = text_position(x, y, text, font_width, font_height, always_on_client, always_on_game, ref_x, ref_y)
new_gui_text(x_pos, y_pos, text, text_color, halo_color)
end
local function alert_text(x, y, text, text_color, bg_color, outline_color, always_on_game, ref_x, ref_y)
-- Reads external variables
local font_width = BIZHAWK_FONT_WIDTH
local font_height = BIZHAWK_FONT_HEIGHT
local x_pos, y_pos, text_length = text_position(x, y, text, font_width, font_height, false, always_on_game, ref_x, ref_y)
if not bg_color then bg_color = BACKGROUND_COLOR end
draw_box(x_pos/Pixel_rate_x, y_pos/Pixel_rate_y, (x_pos + text_length)/Pixel_rate_x + 2, (y_pos + font_height)/Pixel_rate_y + 1, 0, bg_color)
new_gui_text(x_pos, y_pos, text, text_color, outline_color)
end
local function draw_over_text(x, y, base, color_base, text, color_text, color_bg, always_on_client, always_on_game, ref_x, ref_y)
draw_text(x, y, base, color_base, color_bg, always_on_client, always_on_game, ref_x, ref_y)
draw_text(x, y, text, color_text, 0, always_on_client, always_on_game, ref_x, ref_y)
end
-- Sum of the digits of a integer
local function sum_digits(number)
local sum = 0
while number > 0 do
sum = sum + number%10
number = math.floor(number*0.1)
end
return sum
end
-- Displays frame count, lag, etc
function timer()
local islagged = emu.islagged()
local isloaded = movie.isloaded()
if isloaded then
local mode = movie.mode()
if mode == "RECORD" then alert_text(Buffer_width, Buffer_height, "(REC)", WARNING_COLOR, WARNING_BG, OUTLINE_COLOR, true) end -- draws REC symbol while recording
end
if islagged then
alert_text(Buffer_width/2, BIZHAWK_FONT_HEIGHT, "LAG", WARNING_COLOR, WARNING_BG, OUTLINE_COLOR, true, 0.5, 0.0)
end
end
-- SMW FUNCTIONS:
local Real_frame, Previous_real_frame, Effective_frame, Game_mode, Level_index, Room_index
local Level_flag, Current_level, Is_paused, Lock_animation_flag
function scan_smw()
Previous_real_frame = Real_frame or u8(WRAM.real_frame)
Real_frame = u8(WRAM.real_frame)
Effective_frame = u8(WRAM.effective_frame)
Game_mode = u8(WRAM.game_mode)
Level_index = u8(WRAM.level_index)
Level_flag = u8(WRAM.level_flag_table + Level_index)
Is_paused = u8(WRAM.level_paused) == 1
Lock_animation_flag = u8(WRAM.lock_animation_flag)
Room_index = bit.lshift(u8(WRAM.room_index), 16) + bit.lshift(u8(WRAM.room_index + 1), 8) + u8(WRAM.room_index + 2)
end
-- Converts the in-game (x, y) to SNES-screen coordinates
local function screen_coordinates(x, y)
local camera_x = u16(WRAM.camera_x)
local camera_y = u16(WRAM.camera_y)
x_screen = (x - camera_x)
y_screen = (y - camera_y) - 1
return x_screen, y_screen
end
-- Returns the in-game coordinates of the mouse
local function mouse_position(x_mouse, y_mouse)
camera_x = u16(WRAM.camera_x)
camera_y = u16(WRAM.camera_y)
x_game = x_mouse + camera_x - 8
y_game = y_mouse + camera_y - 15
--draw_text(1, 210, string.format("Mouse in game %d %d", x_game, y_game))
return x_game, y_game
end
-- Displays the SNES-screen and in-game coordinates of the mouse -- EDIT
local function mouse()
mouse_table = input.getmouse()
x_mouse = mouse_table.X
y_mouse = mouse_table.Y
text = string.format("Mouse(%d, %d)", x_mouse, y_mouse)
x, y = mouse_position(x_mouse, y_mouse)
gui.drawRectangle(x, y, 15, 15, "red") -- (Pasky13's script is better for now)
end
local function show_movie_info() -- fix it / optional use, as BizHawk has a suitable movie info
-- Reads external variables
local width = BIZHAWK_FONT_WIDTH
local rec_color = Readonly and TEXT_COLOR or WARNING_COLOR --is_recording and WARNING_COLOR or TEXT_COLOR
local recording_bg = Readonly and OUTLINE_COLOR or WARNING_BG --is_recording and WARNING_BG or BACKGROUND_COLOR
--[[
local text_table = {
{Movie_mode.." ", rec_color, recording_bg},
{fmt("%d/%d", Currentframe, Framecount), TEXT_COLOR, OUTLINE_COLOR},
{fmt("|%d ", Rerecords), WEAK_COLOR, OUTLINE_COLOR},
{Lagcount, WARNING_COLOR, OUTLINE_COLOR},
}
draw_text(-Border_left, -Border_top, text_table, true)
--]]
draw_text(0, Buffer_height, fmt("%s,%s", Isloaded, Movie_mode), true, true)
--local str = frame_time(Currentframe - 1) -- Shows the latest frame emulated, not the frame being run now
--custom_text("right", "bottom", str, TEXT_COLOR, recording_bg)
--if Is_lagged then
--gui.textHV(screen_width/2 - 3*LSNES_FONT_WIDTH, 2*LSNES_FONT_HEIGHT, "Lag", WARNING_COLOR, change_transparency(WARNING_BG, Background_max_opacity))
--end
end
local function show_misc_info()
local color = TEXT_COLOR
local color_bg = OUTLINE_COLOR
local RNG = u8(WRAM.RNG)
local main_info = fmt("Frame(%02x, %02x) RNG(%04x) Mode(%02x)",
Real_frame, Effective_frame, RNG, Game_mode)
;
draw_text(Buffer_width + Border_right, -Border_top, main_info, color, color_bg, true, false, 1.0, 1.0)
end
function read_screens()
local screens_number = u8(WRAM.screens_number)
local vscreen_number = u8(WRAM.vscreen_number)
local hscreen_number = u8(WRAM.hscreen_number)
local vscreen_current = s8(WRAM.y + 1)
local hscreen_current = s8(WRAM.x + 1)
local level_mode_settings = u8(WRAM.level_mode_settings)
local level_type
if (level_mode_settings ~= 0) and (level_mode_settings == 0x3 or level_mode_settings == 0x4 or level_mode_settings == 0x7
or level_mode_settings == 0x8 or level_mode_settings == 0xa or level_mode_settings == 0xd) then
level_type = "Vertical"
;
else
level_type = "Horizontal"
end
return level_type, screens_number, hscreen_current, hscreen_number, vscreen_current, vscreen_number
end
local function level_info()
-- Font
gui.opacity(1.0, 1.0)
local sprite_memory_header = u8(WRAM.sprite_memory_header)
local sprite_buoyancy = u8(WRAM.sprite_buoyancy)/0x40
local color = TEXT_COLOR
if sprite_buoyancy == 0 then sprite_buoyancy = "" else
sprite_buoyancy = string.format(" %.2x", sprite_buoyancy)
color = WARNING_COLOR
end
local lm_level_number = Level_index
if Level_index > 0x24 then lm_level_number = Level_index + 0xdc end -- converts the level number to the Lunar Magic number; should not be used outside here
-- Number of screens within the level
local level_type, screens_number, hscreen_current, hscreen_number, vscreen_current, vscreen_number = read_screens()
draw_text(Buffer_width + Border_right, BIZHAWK_FONT_HEIGHT, fmt("%.1sLevel(%.2x, %.2x)%s", level_type, lm_level_number, sprite_memory_header, sprite_buoyancy), -- BizHawk: can't apply gap, so different Y for those info
color, true, false)
;
draw_text(Buffer_width + Border_right, 2*BIZHAWK_FONT_HEIGHT, fmt("Screens(%d):", screens_number), true)
draw_text(Buffer_width + Border_right, 3*BIZHAWK_FONT_HEIGHT, fmt("Rooms(%d):", Room_index), true)
draw_text(Buffer_width + Border_right, 4*BIZHAWK_FONT_HEIGHT, fmt("(%d/%d, %d/%d)", hscreen_current, hscreen_number,
vscreen_current, vscreen_number), true)
draw_text(Buffer_width + Border_right, 5*BIZHAWK_FONT_HEIGHT, fmt("Level Index: (%d) ", Level_index), true)
draw_text(Buffer_width + Border_right, 5*BIZHAWK_FONT_HEIGHT, fmt("Level Flag: (%d) ", Level_flag), true)
;
-- Time frame counter of the clock
local timer_frame_counter = u8(WRAM.timer_frame_counter)
draw_text(0.645*Buffer_width, 0.085*Buffer_height, fmt("%.2d", timer_frame_counter), false, false, 0.5, 0.5)
-- Score: sum of digits, useful for avoiding lag -- new
local score = u24(WRAM.mario_score)
draw_text(0.936*Buffer_width, 0.125*Buffer_height, fmt("=%d", sum_digits(score)), WEAK_COLOR, false, false, 0.0, 0.5)
end
local function player(camera_x, camera_y)
-- Font
gui.opacity(1.0, 1.0)
-- Reads WRAM
local x = s16(WRAM.x)
local y = s16(WRAM.y)
local previous_x = s16(WRAM.previous_x)
local previous_y = s16(WRAM.previous_y)
local x_sub = u8(WRAM.x_sub)
local y_sub = u8(WRAM.y_sub)
local x_speed = s8(WRAM.x_speed)
local x_subspeed = u8(WRAM.x_subspeed)
local y_speed = s8(WRAM.y_speed)
local p_meter = u8(WRAM.p_meter)
local take_off = u8(WRAM.take_off)
local powerup = u8(WRAM.powerup)
local direction = u8(WRAM.direction)
local cape_spin = u8(WRAM.cape_spin)
local cape_fall = u8(WRAM.cape_fall)
local flight_animation = u8(WRAM.flight_animation)
local diving_status = s8(WRAM.diving_status)
local player_in_air = u8(WRAM.player_in_air)
local player_blocked_status = u8(WRAM.player_blocked_status)
local player_item = u8(WRAM.player_item)
local is_ducking = u8(WRAM.is_ducking)
local on_ground = u8(WRAM.on_ground)
local spinjump_flag = u8(WRAM.spinjump_flag)
local can_jump_from_water = u8(WRAM.can_jump_from_water)
local carrying_item = u8(WRAM.carrying_item)
local yoshi_riding_flag = u8(WRAM.yoshi_riding_flag)
-- Transformations
if direction == 0 then direction = LEFT_ARROW else direction = RIGHT_ARROW end
if x_sub%0x10 == 0 then x_sub = bit.rshift(x_sub, 4) end
if y_sub%0x10 == 0 then y_sub = bit.rshift(y_sub, 4) end
local x_speed_int, x_speed_frac = math.modf(x_speed + x_subspeed/0x100)
x_speed_frac = math.abs(x_speed_frac*100)
local spin_direction = (Effective_frame)%8
if spin_direction < 4 then
spin_direction = spin_direction + 1
else
spin_direction = 3 - spin_direction
end
local is_caped = powerup == 0x2
local is_spinning = cape_spin ~= 0 or spinjump_flag ~= 0
-- Blocked status
local blocked_status = {}
local was_boosted
if bit.check(player_blocked_status, 0) then
table.insert(blocked_status, "R")
if x_speed < 0 then was_boosted = true end
else table.insert(blocked_status, " ")
end
if bit.check(player_blocked_status, 1) then
table.insert(blocked_status, "L")
if x_speed > 0 then was_boosted = true end
else table.insert(blocked_status, " ")
end
if bit.check(player_blocked_status, 2) then table.insert(blocked_status, "D") else table.insert(blocked_status, " ") end
if bit.check(player_blocked_status, 3) then
table.insert(blocked_status, "U")
if y_speed > 6 then was_boosted = true end
else table.insert(blocked_status, " ")
end
if bit.check(player_blocked_status, 4) then table.insert(blocked_status, "M") else table.insert(blocked_status, " ") end
local block_str = table.concat(blocked_status)
-- Display info
local i = 0
local delta_x = BIZHAWK_FONT_WIDTH
local delta_y = BIZHAWK_FONT_HEIGHT
local table_x = 0
local table_y = 0.16*Buffer_height -- BizHawk equivalent to 64/448
draw_text(table_x, table_y + i*delta_y, fmt("Meter (%03d, %02d) %s", p_meter, take_off, direction))
draw_text(table_x + 18*delta_x, table_y + i*delta_y, fmt(" %+d", spin_direction),
(is_spinning and TEXT_COLOR) or WEAK_COLOR)
i = i + 1
draw_text(table_x, table_y + i*delta_y, fmt("Pos (%+d.%x, %+d.%x)", x, x_sub, y, y_sub))
i = i + 1
draw_text(table_x, table_y + i*delta_y, fmt("Speed (%+d(%d.%02.0f), %+d)", x_speed, x_speed_int, x_speed_frac, y_speed))
i = i + 1
if is_caped then
draw_text(table_x, table_y + i*delta_y, fmt("Cape (%.2d, %.2d)/(%d, %d)", cape_spin, cape_fall, flight_animation, diving_status), CAPE_COLOR)
i = i + 1
end
local block_info_bg = was_boosted and WARNING_BG or nil
draw_text(table_x, table_y + i*delta_y, "Block: ", TEXT_COLOR, block_info_bg)
draw_over_text(table_x + 7*delta_x, table_y + i*delta_y, "RLDUM", WEAK_COLOR, block_str, WARNING_COLOR)
i = i + 1
draw_text(table_x, table_y + i*delta_y, fmt("Camera (%d, %d)", camera_x, camera_y))
-- shows hitbox and interaction points for player
if not (SHOW_PLAYER_HITBOX or SHOW_INTERACTION_POINTS) then return end
--------------------
-- displays player's hitbox
local function player_hitbox(x, y)
local x_screen, y_screen = screen_coordinates(x, y, camera_x, camera_y)
local yoshi_hitbox = nil
local is_small = is_ducking ~= 0 or powerup == 0
local on_yoshi = yoshi_riding_flag ~= 0
local x_points = {
center = 0x8,
left_side = 0x2 + 1,
left_foot = 0x5,
right_side = 0xe - 1,
right_foot = 0xb
}
local y_points = {}
if is_small and not on_yoshi then
y_points = {
head = 0x10,
center = 0x18,
shoulder = 0x16,
side = 0x1a,
foot = 0x20,
sprite = 0x18
}
elseif not is_small and not on_yoshi then
y_points = {
head = 0x08,
center = 0x12,
shoulder = 0x0f,
side = 0x1a,
foot = 0x20,
sprite = 0x0a
}
elseif is_small and on_yoshi then
y_points = {
head = 0x13,
center = 0x1d,
shoulder = 0x19,
side = 0x28,
foot = 0x30,
sprite = 0x28,
sprite_up = 0x1c
}
else
y_points = {
head = 0x10,
center = 0x1a,
shoulder = 0x16,
side = 0x28,
foot = 0x30,
sprite = 0x28,
sprite_up = 0x14
}
end
draw_box(x_screen + x_points.left_side, y_screen + y_points.head, x_screen + x_points.right_side, y_screen + y_points.foot,
INTERACTION_BG, INTERACTION_BG) -- background for block interaction
;
if SHOW_PLAYER_HITBOX then
-- Collision with sprites
local mario_bg = (not on_yoshi and MARIO_BG) or MARIO_BG_MOUNTED
if y_points.sprite_up then
draw_box(x_screen + x_points.left_side + 1, y_screen + y_points.sprite_up - 2,
x_screen + x_points.right_side - 1, y_screen + y_points.foot, MARIO_COLOR, mario_bg)
;
else
draw_box(x_screen + x_points.left_side + 1, y_screen + y_points.sprite - 2,
x_screen + x_points.right_side - 1, y_screen + y_points.foot, MARIO_COLOR, mario_bg)
;
end
end
-- interaction points (collision with blocks)
if SHOW_INTERACTION_POINTS then
local color = INTERACTION_COLOR
if not SHOW_PLAYER_HITBOX then
draw_box(x_screen + x_points.left_side , y_screen + y_points.head,
x_screen + x_points.right_side, y_screen + y_points.foot, INTERACTION_COLOR_WITHOUT_HITBOX, INTERACTION_BG_WITHOUT_HITBOX)
end
draw_line(x_screen + x_points.left_side, y_screen + y_points.side, x_screen + x_points.left_foot, y_screen + y_points.side, color) -- left side
draw_line(x_screen + x_points.right_side, y_screen + y_points.side, x_screen + x_points.right_foot, y_screen + y_points.side, color) -- right side
draw_line(x_screen + x_points.left_foot, y_screen + y_points.foot - 2, x_screen + x_points.left_foot, y_screen + y_points.foot, color) -- left foot bottom
draw_line(x_screen + x_points.right_foot, y_screen + y_points.foot - 2, x_screen + x_points.right_foot, y_screen + y_points.foot, color) -- right foot bottom
draw_line(x_screen + x_points.left_side, y_screen + y_points.shoulder, x_screen + x_points.left_side + 2, y_screen + y_points.shoulder, color) -- head left point
draw_line(x_screen + x_points.right_side - 2, y_screen + y_points.shoulder, x_screen + x_points.right_side, y_screen + y_points.shoulder, color) -- head right point
draw_line(x_screen + x_points.center, y_screen + y_points.head, x_screen + x_points.center, y_screen + y_points.head + 2, color) -- head point
draw_line(x_screen + x_points.center - 1, y_screen + y_points.center, x_screen + x_points.center + 1, y_screen + y_points.center, color) -- center point
draw_line(x_screen + x_points.center, y_screen + y_points.center - 1, x_screen + x_points.center, y_screen + y_points.center + 1, color) -- center point
end
-- That's the pixel that appears when Mario dies in the pit
if y_screen >= 184 then -- when should the bottom gap appear 184 out of 224
draw_pixel(x_screen, y_screen, color)
end
return x_points, y_points
end
--------------------
-- displays the hitbox of the cape while spinning
local function cape_hitbox()
local cape_interaction = u8(WRAM.cape_interaction)
if cape_interaction == 0 then return end
local cape_x = u16(WRAM.cape_x)
local cape_y = u16(WRAM.cape_y)
local cape_x_screen, cape_y_screen = screen_coordinates(cape_x, cape_y, camera_x, camera_y)
local cape_left = 0
local cape_right = 0x10
local cape_up = 0x02
local cape_down = 0x10
local cape_middle = 0x08
local block_interaction_cape = (x > cape_x and cape_left + 2) or cape_right - 2
local active_frame = Real_frame%2 == (x > cape_x and 0 or 1) -- active iff the cape can hit a block
if active_frame then bg_color = CAPE_BG else bg_color = 0 end
draw_box(cape_x_screen + cape_left, cape_y_screen + cape_up, cape_x_screen + cape_right, cape_y_screen + cape_down, CAPE_COLOR, bg_color)
if active_frame then
draw_pixel(cape_x_screen + block_interaction_cape, cape_y_screen + cape_middle, WARNING_COLOR)
else
draw_pixel(cape_x_screen + block_interaction_cape, cape_y_screen + cape_middle, TEXT_COLOR)
end
end
--------------------
cape_hitbox()
player_hitbox(x, y)
-- Shows where Mario is expected to be in the next frame, if he's not boosted or stopped (DEBUG)
if SHOW_DEBUG_INFO then player_hitbox(math.floor((256*x + x_sub + 16*x_speed)/256), math.floor((256*y + y_sub + 16*y_speed)/256)) end
end
-- Returns the id of Yoshi; if more than one, the lowest sprite slot
local function get_yoshi_id()
for i = 0, SMW.sprite_max - 1 do
id = u8(WRAM.sprite_number + i)
status = u8(WRAM.sprite_status + i)
if id == 0x35 and status ~= 0 then return i end
end
return nil
end
local function sprites(camera_x, camera_y)
-- Font
gui.opacity(1.0, 1.0)
local yoshi_riding_flag = u8(WRAM.yoshi_riding_flag)
local counter = 0
local table_position = 0.18*Buffer_height
for id = 0, SMW.sprite_max - 1 do
local sprite_status = u8(WRAM.sprite_status + id)
if sprite_status ~= 0 then
local x = bit.lshift(u8(WRAM.sprite_x_high + id), 8) + u8(WRAM.sprite_x_low + id)
local y = bit.lshift(u8(WRAM.sprite_y_high + id), 8) + u8(WRAM.sprite_y_low + id)
local x_sub = u8(WRAM.sprite_x_sub + id)
local y_sub = u8(WRAM.sprite_y_sub + id)
local number = u8(WRAM.sprite_number + id)
local stun = u8(WRAM.sprite_stun + id)
local x_speed = s8(WRAM.sprite_x_speed + id)
local y_speed = s8(WRAM.sprite_y_speed + id)
local contact_mario = u8(WRAM.sprite_contact_mario + id)
local x_offscreen = s8(WRAM.sprite_x_offscreen + id)
local y_offscreen = s8(WRAM.sprite_y_offscreen + id)