romdevtools 0.21.0 → 0.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +15 -4
- package/CHANGELOG.md +66 -0
- package/examples/atari7800/templates/hello_sprite.c +48 -4
- package/examples/atari7800/templates/music_demo.c +47 -2
- package/examples/c64/templates/tile_engine.c +77 -27
- package/examples/gb/templates/hello_sprite.c +15 -6
- package/examples/gb/templates/music_demo.c +36 -0
- package/examples/gb/templates/platformer.c +3 -2
- package/examples/gb/templates/puzzle.c +3 -2
- package/examples/gb/templates/racing.c +3 -2
- package/examples/gb/templates/shmup.c +3 -2
- package/examples/gb/templates/sports.c +3 -2
- package/examples/gb/templates/tile_engine.c +3 -2
- package/examples/gba/templates/maxmod_demo.c +36 -2
- package/examples/gba/templates/platformer.c +3 -1
- package/examples/gba/templates/tonc_hello_sprite.c +35 -1
- package/examples/gbc/templates/hello_sprite.c +12 -3
- package/examples/gbc/templates/music_demo.c +56 -12
- package/examples/gbc/templates/platformer.c +3 -2
- package/examples/gbc/templates/puzzle.c +3 -2
- package/examples/gbc/templates/racing.c +3 -2
- package/examples/gbc/templates/shmup.c +3 -2
- package/examples/gbc/templates/sports.c +3 -2
- package/examples/gbc/templates/tile_engine.c +3 -2
- package/examples/genesis/main.s +53 -1
- package/examples/genesis/templates/hello_sprite.c +25 -3
- package/examples/genesis/templates/shmup_2p.c +31 -0
- package/examples/genesis/templates/xgm2_demo.c +20 -0
- package/examples/gg/templates/hello_sprite.c +25 -2
- package/examples/gg/templates/music_demo.c +24 -2
- package/examples/gg/templates/racing.c +7 -4
- package/examples/gg/templates/sports.c +11 -13
- package/examples/gg/templates/tile_engine.c +12 -6
- package/examples/lynx/templates/hello_sprite.c +15 -1
- package/examples/lynx/templates/music_demo.c +13 -1
- package/examples/nes/templates/hello_sprite.c +35 -0
- package/examples/nes/templates/music_demo.c +40 -0
- package/examples/pce/catch_game/main.c +22 -3
- package/examples/pce/music_sfx/main.c +28 -1
- package/examples/pce/sprite_move/main.c +7 -2
- package/examples/sms/templates/hello_sprite.c +29 -3
- package/examples/sms/templates/music_demo.c +18 -4
- package/examples/sms/templates/shmup_2p.c +24 -1
- package/examples/sms/templates/sports.c +4 -2
- package/examples/snes/main.asm +108 -17
- package/examples/snes/templates/c-hello-data.asm +23 -0
- package/examples/snes/templates/c-hello.c +18 -1
- package/examples/snes/templates/hello_sprite-data.asm +23 -0
- package/examples/snes/templates/hello_sprite.c +17 -1
- package/examples/snes/templates/music_demo-data.asm +23 -0
- package/examples/snes/templates/music_demo.c +22 -4
- package/examples/snes/templates/platformer.c +4 -1
- package/examples/snes/templates/puzzle.c +4 -1
- package/package.json +1 -1
- package/src/cheats/gamegenie.js +0 -1
- package/src/cli/smoke.js +1 -3
- package/src/host/LibretroHost.js +69 -15
- package/src/host/chafa-render.js +2 -0
- package/src/host/dsp-state.js +2 -2
- package/src/host/gpgx-state.js +4 -0
- package/src/http/routes.js +1 -1
- package/src/mcp/server.js +1 -1
- package/src/mcp/state.js +36 -0
- package/src/mcp/tools/address-to-symbol.js +0 -1
- package/src/mcp/tools/art-loaders.js +1 -1
- package/src/mcp/tools/cart-parts.js +0 -1
- package/src/mcp/tools/classify-region.js +1 -1
- package/src/mcp/tools/diff-roms.js +1 -1
- package/src/mcp/tools/disasm-rebuild.js +1 -1
- package/src/mcp/tools/disasm.js +2 -3
- package/src/mcp/tools/find-references.js +1 -2
- package/src/mcp/tools/font-map.js +1 -1
- package/src/mcp/tools/index.js +0 -49
- package/src/mcp/tools/input-layout.js +0 -1
- package/src/mcp/tools/input.js +33 -3
- package/src/mcp/tools/lifecycle.js +14 -2
- package/src/mcp/tools/lospec.js +0 -19
- package/src/mcp/tools/platform-docs.js +1 -1
- package/src/mcp/tools/platform-tools.js +4 -4
- package/src/mcp/tools/project.js +0 -2
- package/src/mcp/tools/reinject.js +0 -1
- package/src/mcp/tools/rom-id.js +2 -2
- package/src/mcp/tools/snippets.js +2 -2
- package/src/mcp/tools/sprite-pipeline.js +1 -2
- package/src/mcp/tools/tile-inspect.js +1 -1
- package/src/mcp/tools/toolchain.js +29 -9
- package/src/mcp/tools/watch-memory.js +13 -3
- package/src/platforms/_guides/ROMHACKING_PLAYBOOK.md +34 -0
- package/src/platforms/atari2600/TROUBLESHOOTING.md +6 -0
- package/src/platforms/atari7800/TROUBLESHOOTING.md +6 -0
- package/src/platforms/c64/TROUBLESHOOTING.md +6 -0
- package/src/platforms/c64/d64.js +0 -1
- package/src/platforms/c64/sid.js +0 -2
- package/src/platforms/common/metasprite-adapters.js +1 -1
- package/src/platforms/common/metasprite-codegen.js +3 -3
- package/src/platforms/common/registers.js +5 -3
- package/src/platforms/gb/TROUBLESHOOTING.md +6 -0
- package/src/platforms/gb/lib/c/gb_runtime.c +4 -4
- package/src/platforms/gba/TROUBLESHOOTING.md +6 -0
- package/src/platforms/gbc/TROUBLESHOOTING.md +6 -0
- package/src/platforms/gbc/lib/c/gb_runtime.c +4 -4
- package/src/platforms/genesis/TROUBLESHOOTING.md +6 -0
- package/src/platforms/gg/TROUBLESHOOTING.md +6 -0
- package/src/platforms/lynx/TROUBLESHOOTING.md +6 -0
- package/src/platforms/msx/TROUBLESHOOTING.md +6 -0
- package/src/platforms/nes/TROUBLESHOOTING.md +6 -0
- package/src/platforms/nes/image-to-tilemap.js +3 -0
- package/src/platforms/nes/lib/asm/famitone2.s +5 -1
- package/src/platforms/pce/TROUBLESHOOTING.md +6 -0
- package/src/platforms/sms/TROUBLESHOOTING.md +6 -0
- package/src/platforms/snes/TROUBLESHOOTING.md +6 -0
- package/src/platforms/snes/brr.js +0 -2
- package/src/playtest/playtest.js +0 -7
- package/src/toolchains/asar/asar.js +0 -9
- package/src/toolchains/assemble-snippet.js +30 -12
- package/src/toolchains/cc65/presets/nes/chr-ram-runtime.cfg +14 -1
- package/src/toolchains/common/reassemble.js +0 -1
- package/src/toolchains/common/sdk-cache.js +1 -1
- package/src/toolchains/genesis-c/genesis-c.js +5 -3
- package/src/toolchains/index.js +27 -3
- package/src/toolchains/parse-errors.js +78 -1
- package/src/toolchains/sdcc/preflight-lint.js +5 -1
- package/src/toolchains/sdcc/sdcc.js +1 -1
- package/src/toolchains/sjasm/sjasm.js +1 -1
- package/src/toolchains/snes-c/snes-c.js +2 -2
- package/src/toolchains/vasm68k/vasm68k.js +2 -4
- package/src/toolchains/wladx/wladx.js +1 -1
|
@@ -33,10 +33,13 @@ extern void gg_sat_upload(void);
|
|
|
33
33
|
#define VIS_X1 207 /* 48 + 160 - 1 */
|
|
34
34
|
#define VIS_Y1 167 /* 24 + 144 - 1 */
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
#define
|
|
36
|
+
/* Explicit (uint8_t) casts: the computed int expressions all fit in a byte,
|
|
37
|
+
* but SDCC warns (158) on the implicit int->uint8_t narrowing in the const
|
|
38
|
+
* initializers below unless the conversion is spelled out. */
|
|
39
|
+
#define LANE_LEFT_X ((uint8_t)(VIS_X0 + 28)) /* 76 */
|
|
40
|
+
#define LANE_MID_X ((uint8_t)((VIS_X0 + VIS_X1) / 2 - 4)) /* ~123 */
|
|
41
|
+
#define LANE_RIGHT_X ((uint8_t)(VIS_X1 - 36)) /* 171 */
|
|
42
|
+
#define PLAYER_Y ((uint8_t)(VIS_Y1 - 16))
|
|
40
43
|
#define MAX_OBSTACLES 4
|
|
41
44
|
|
|
42
45
|
/* GG palette = 32 entries × 2 bytes (4-4-4 BGR LE): low=(g<<4)|r, high=b.
|
|
@@ -30,8 +30,11 @@ extern void gg_sat_upload(void);
|
|
|
30
30
|
#define COURT_BOT VIS_Y1
|
|
31
31
|
#define PADDLE_H 24
|
|
32
32
|
#define BALL_SIZE 8
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
/* Explicit (uint8_t) casts: these fit a byte but SDCC warns (158) on the
|
|
34
|
+
* implicit int->uint8_t narrowing when the computed macro is passed to the
|
|
35
|
+
* uint8_t x/y args of gg_sprite_set. */
|
|
36
|
+
#define PADDLE_X1 ((uint8_t)(VIS_X0 + 8)) /* near the visible left edge */
|
|
37
|
+
#define PADDLE_X2 ((uint8_t)(VIS_X1 - 16)) /* near the visible right edge */
|
|
35
38
|
|
|
36
39
|
/* GG palette = 32 entries × 2 bytes (4-4-4 BGR LE): low=(g<<4)|r, high=b.
|
|
37
40
|
* gg_load_palette reads 64 bytes; a 32-byte array leaves the sprite palette
|
|
@@ -127,8 +130,9 @@ void main(void) {
|
|
|
127
130
|
reset_match();
|
|
128
131
|
|
|
129
132
|
do {
|
|
130
|
-
uint8_t p1
|
|
133
|
+
uint8_t p1;
|
|
131
134
|
uint8_t slot;
|
|
135
|
+
int16_t target;
|
|
132
136
|
gg_vblank_wait();
|
|
133
137
|
sfx_update();
|
|
134
138
|
|
|
@@ -145,20 +149,14 @@ void main(void) {
|
|
|
145
149
|
gg_sat_upload();
|
|
146
150
|
|
|
147
151
|
p1 = gg_joypad_read();
|
|
148
|
-
p2 = 0; /* GG has only one controller — always AI for the right paddle */
|
|
149
152
|
|
|
150
153
|
if ((p1 & JOY_UP) && p1y > COURT_TOP) p1y -= 2;
|
|
151
154
|
if ((p1 & JOY_DOWN) && p1y < COURT_BOT - PADDLE_H) p1y += 2;
|
|
152
155
|
|
|
153
|
-
/*
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
} else {
|
|
158
|
-
int16_t target = by - PADDLE_H / 2;
|
|
159
|
-
if (p2y < target && p2y < COURT_BOT - PADDLE_H) p2y += 1;
|
|
160
|
-
else if (p2y > target && p2y > COURT_TOP) p2y -= 1;
|
|
161
|
-
}
|
|
156
|
+
/* GG has only one controller — the right paddle is always AI. */
|
|
157
|
+
target = by - PADDLE_H / 2;
|
|
158
|
+
if (p2y < target && p2y < COURT_BOT - PADDLE_H) p2y += 1;
|
|
159
|
+
else if (p2y > target && p2y > COURT_TOP) p2y -= 1;
|
|
162
160
|
|
|
163
161
|
if (serve_timer > 0) {
|
|
164
162
|
serve_timer--;
|
|
@@ -46,8 +46,9 @@ extern void gg_sat_upload(void);
|
|
|
46
46
|
* (entries 16-31) reading garbage = invisible sprites. BG colour 1 = entry 1
|
|
47
47
|
* (dark grey wall); sprite colour 1 = entry 17 (white player). */
|
|
48
48
|
static const uint8_t palette[64] = {
|
|
49
|
-
/* BG 0-15:
|
|
50
|
-
|
|
49
|
+
/* BG 0-15: 0 = dark navy backdrop, 1 = grey wall, 2 = teal floor,
|
|
50
|
+
* 3 = blue floor (the two floor-dither tones). */
|
|
51
|
+
0x20,0x02, 0x66,0x06, 0xC8,0x08, 0x80,0x0C, 0,0, 0,0, 0,0, 0,0,
|
|
51
52
|
0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
52
53
|
/* SPRITE 16-31: 16=transparent, 17=white player */
|
|
53
54
|
0,0, 0xFF,0x0F, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
@@ -55,10 +56,15 @@ static const uint8_t palette[64] = {
|
|
|
55
56
|
};
|
|
56
57
|
|
|
57
58
|
static const uint8_t bg_tiles[32 * 2] = {
|
|
58
|
-
/* T_OPEN —
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
/* T_OPEN — dithered floor: plane1=0xFF (colour-2 bit always on), plane0
|
|
60
|
+
* alternates 0xAA/0x55 so pixels flip between colour 2 (teal) and colour 3
|
|
61
|
+
* (blue). The open floor now fills with TWO tones instead of the backdrop,
|
|
62
|
+
* so the screen never reads as a single flat colour. */
|
|
63
|
+
0xAA,0xFF,0x00,0x00, 0x55,0xFF,0x00,0x00,
|
|
64
|
+
0xAA,0xFF,0x00,0x00, 0x55,0xFF,0x00,0x00,
|
|
65
|
+
0xAA,0xFF,0x00,0x00, 0x55,0xFF,0x00,0x00,
|
|
66
|
+
0xAA,0xFF,0x00,0x00, 0x55,0xFF,0x00,0x00,
|
|
67
|
+
/* T_WALL — bordered block (colour 1, grey) */
|
|
62
68
|
0xFF,0x00,0x00,0x00, 0x81,0x00,0x00,0x00,
|
|
63
69
|
0x81,0x00,0x00,0x00, 0x81,0x00,0x00,0x00,
|
|
64
70
|
0x81,0x00,0x00,0x00, 0x81,0x00,0x00,0x00,
|
|
@@ -22,7 +22,21 @@ void main(void) {
|
|
|
22
22
|
sfx_tone(0, 80, 12); /* boot chime */
|
|
23
23
|
|
|
24
24
|
for (;;) {
|
|
25
|
-
|
|
25
|
+
/* CANONICAL LYNX FRAME LOOP — full redraw every frame:
|
|
26
|
+
* 1. WAIT for Suzy's blitter to finish the previous frame. Drawing
|
|
27
|
+
* while it's mid-flight loses the frame → black screen. This is
|
|
28
|
+
* the #1 "Lynx stays blank" trap (tgi_clear alone leaves the back
|
|
29
|
+
* page stale on this core, so we clear with a full-screen bar). */
|
|
30
|
+
while (tgi_busy()) { }
|
|
31
|
+
|
|
32
|
+
/* Blue field so the screen is obviously not blank... */
|
|
33
|
+
tgi_setcolor(COLOR_BLUE);
|
|
34
|
+
tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy());
|
|
35
|
+
/* ...with a green band so no single colour fills the whole screen. */
|
|
36
|
+
tgi_setcolor(COLOR_GREEN);
|
|
37
|
+
tgi_bar(0, 60, tgi_getmaxx(), 102);
|
|
38
|
+
|
|
39
|
+
/* The joystick-driven player square on top. */
|
|
26
40
|
tgi_setcolor(COLOR_YELLOW);
|
|
27
41
|
tgi_bar(x, y, x + 8, y + 8);
|
|
28
42
|
tgi_setcolor(COLOR_WHITE);
|
|
@@ -26,7 +26,19 @@ void main(void) {
|
|
|
26
26
|
lynx_snd_play(0, (unsigned char *)demo_music);
|
|
27
27
|
|
|
28
28
|
for (;;) {
|
|
29
|
-
|
|
29
|
+
/* CANONICAL LYNX FRAME LOOP — full redraw every frame: WAIT for Suzy's
|
|
30
|
+
* blitter (drawing mid-flight loses the frame → black), then clear with
|
|
31
|
+
* a full-screen bar (tgi_clear leaves the back page stale on this core)
|
|
32
|
+
* before drawing. The #1 "Lynx stays blank" trap. */
|
|
33
|
+
while (tgi_busy()) { }
|
|
34
|
+
|
|
35
|
+
/* Two colour bands so the backdrop is obviously not blank and no single
|
|
36
|
+
* colour fills the whole screen (a flat fill still reads as "blank"). */
|
|
37
|
+
tgi_setcolor(COLOR_BLUE);
|
|
38
|
+
tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy());
|
|
39
|
+
tgi_setcolor(COLOR_PURPLE);
|
|
40
|
+
tgi_bar(0, 56, tgi_getmaxx(), 102);
|
|
41
|
+
|
|
30
42
|
tgi_setcolor(COLOR_WHITE);
|
|
31
43
|
tgi_outtextxy(20, 20, "LYNX MUSIC DEMO");
|
|
32
44
|
tgi_setcolor(COLOR_YELLOW);
|
|
@@ -32,6 +32,21 @@ static const uint8_t tile_data[16] = {
|
|
|
32
32
|
0, 0, 0, 0, 0, 0, 0, 0,
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
+
/* Two BG tiles so the backdrop isn't a single flat colour (a uniform
|
|
36
|
+
* screen reads >=92% one colour and fails the blank-screen check):
|
|
37
|
+
* tile 1 — solid colour 1
|
|
38
|
+
* tile 2 — solid colour 2
|
|
39
|
+
* Checkerboarded across the nametable below. BG fetches from $1000-$1FFF
|
|
40
|
+
* under the default PPUCTRL, so these upload to the BG pattern table. */
|
|
41
|
+
static const uint8_t bg_tiles[2 * 16] = {
|
|
42
|
+
/* tile 1: solid colour 1 */
|
|
43
|
+
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
|
44
|
+
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
45
|
+
/* tile 2: solid colour 2 */
|
|
46
|
+
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
47
|
+
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
|
48
|
+
};
|
|
49
|
+
|
|
35
50
|
/* 32-byte palette: 4 BG palettes + 4 sprite palettes.
|
|
36
51
|
* BG index 0 ($3F00) is the universal backdrop.
|
|
37
52
|
* SPR index 0 ($3F10) is transparent — always; the value is written
|
|
@@ -49,6 +64,21 @@ static const uint8_t palette[32] = {
|
|
|
49
64
|
0x0F, 0x2A, 0x1A, 0x0A,
|
|
50
65
|
};
|
|
51
66
|
|
|
67
|
+
/* Fill nametable 0 ($2000) with a checkerboard of BG tiles 1 and 2 so the
|
|
68
|
+
* screen behind the sprite is visibly NOT blank. Attribute table stays at
|
|
69
|
+
* palette 0. Caller must have the PPU off. */
|
|
70
|
+
static void fill_bg(void) {
|
|
71
|
+
uint16_t addr;
|
|
72
|
+
uint8_t y, x;
|
|
73
|
+
for (y = 0; y < 30; y++) {
|
|
74
|
+
addr = (uint16_t)(0x2000 + (uint16_t)y * 32);
|
|
75
|
+
for (x = 0; x < 32; x++) {
|
|
76
|
+
vram_unsafe_set(addr, (uint8_t)(((x ^ y) & 1) + 1));
|
|
77
|
+
++addr;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
52
82
|
void main(void) {
|
|
53
83
|
uint8_t px = 124; /* mid-screen X */
|
|
54
84
|
uint8_t py = 110; /* mid-screen Y */
|
|
@@ -64,6 +94,11 @@ void main(void) {
|
|
|
64
94
|
* at $0000-$0FFF since the default PPUCTRL has sprite_pattern=0. */
|
|
65
95
|
chr_ram_upload(0x0010, tile_data, 16);
|
|
66
96
|
|
|
97
|
+
/* ── 2b. Upload BG tiles to the BG pattern table at $1000 and paint
|
|
98
|
+
* a checkerboard backdrop so the sprite isn't alone on a blank field. */
|
|
99
|
+
chr_ram_upload(0x1000, bg_tiles, sizeof(bg_tiles));
|
|
100
|
+
fill_bg();
|
|
101
|
+
|
|
67
102
|
/* ── 3. Load palette ─────────────────────────────────────────── */
|
|
68
103
|
palette_load(palette);
|
|
69
104
|
|
|
@@ -43,6 +43,37 @@ extern const unsigned char music_data[];
|
|
|
43
43
|
|
|
44
44
|
static const unsigned char bg_colors[4] = { 0x0F, 0x01, 0x21, 0x31 };
|
|
45
45
|
|
|
46
|
+
/* Two BG tiles so the backdrop isn't a single flat colour (a uniform
|
|
47
|
+
* screen reads >=92% one colour and fails the blank-screen check):
|
|
48
|
+
* tile 1 — solid colour 1
|
|
49
|
+
* tile 2 — solid colour 2
|
|
50
|
+
* We checkerboard them across the nametable below. NES BG fetches from
|
|
51
|
+
* $1000-$1FFF under the default PPUCTRL (bit 4 set), so BG tiles upload
|
|
52
|
+
* there. */
|
|
53
|
+
static const unsigned char bg_tiles[2 * 16] = {
|
|
54
|
+
/* tile 1: solid colour 1 (plane 0 all set, plane 1 clear) */
|
|
55
|
+
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
|
56
|
+
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
57
|
+
/* tile 2: solid colour 2 (plane 1 all set, plane 0 clear) */
|
|
58
|
+
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
59
|
+
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/* Fill nametable 0 ($2000) with a checkerboard of tiles 1 and 2 so the
|
|
63
|
+
* screen shows two distinct colours. Attribute table left at palette 0.
|
|
64
|
+
* Caller must have the PPU off. */
|
|
65
|
+
static void fill_bg(void) {
|
|
66
|
+
unsigned int addr;
|
|
67
|
+
unsigned char y, x;
|
|
68
|
+
for (y = 0; y < 30; y++) {
|
|
69
|
+
addr = 0x2000 + (unsigned int)y * 32;
|
|
70
|
+
for (x = 0; x < 32; x++) {
|
|
71
|
+
vram_unsafe_set(addr, (unsigned char)(((x ^ y) & 1) + 1));
|
|
72
|
+
++addr;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
46
77
|
void main(void) {
|
|
47
78
|
unsigned char palette[32];
|
|
48
79
|
unsigned char i;
|
|
@@ -50,9 +81,18 @@ void main(void) {
|
|
|
50
81
|
unsigned char frame = 0;
|
|
51
82
|
|
|
52
83
|
for (i = 0; i < 32; i++) palette[i] = bg_colors[0];
|
|
84
|
+
/* BG palette 0: backdrop black, colour 1 blue, colour 2 red — gives the
|
|
85
|
+
* checkerboard two visibly different cells. */
|
|
86
|
+
palette[0] = 0x0F; /* $3F00 backdrop */
|
|
87
|
+
palette[1] = 0x11; /* colour 1 — blue */
|
|
88
|
+
palette[2] = 0x16; /* colour 2 — red */
|
|
53
89
|
|
|
54
90
|
ppu_off();
|
|
55
91
|
palette_load(palette);
|
|
92
|
+
/* Upload the two BG tiles to the BG pattern table at $1000. */
|
|
93
|
+
chr_ram_upload(0x1000, bg_tiles, sizeof(bg_tiles));
|
|
94
|
+
/* Paint the checkerboard backdrop so the screen is visibly NOT blank. */
|
|
95
|
+
fill_bg();
|
|
56
96
|
oam_clear();
|
|
57
97
|
|
|
58
98
|
/* Start the music BEFORE rendering — FamiToneInit takes a few
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
#define BAT_VRAM 0x0000 /* 32x32 background map */
|
|
28
28
|
#define FONT_VRAM 0x1000 /* digit + glyph tiles (8x8, 16 words each) */
|
|
29
29
|
#define BLANK_VRAM 0x1000 /* tile 0 of the font = blank */
|
|
30
|
+
#define FIELD_VRAM 0x1700 /* dim "field" BG tile (16 words), drawn behind
|
|
31
|
+
* the HUD so the playfield isn't a blank void */
|
|
30
32
|
#define CATCHER_VRAM 0x1800 /* catcher sprite cell (16x16 = 64 words) */
|
|
31
33
|
#define FRUIT_VRAM 0x1840 /* fruit sprite cell (16x16 = 64 words) */
|
|
32
34
|
|
|
@@ -146,15 +148,30 @@ static void upload_sprites(void) {
|
|
|
146
148
|
load_tiles(FRUIT_VRAM, fruit_cell, 64);
|
|
147
149
|
}
|
|
148
150
|
|
|
149
|
-
/* ----
|
|
151
|
+
/* ---- build the dim "field" BG tile -------------------------------------- */
|
|
152
|
+
/* A solid 8x8 tile in BG colour index 2 (the dim field blue). Filling the BAT
|
|
153
|
+
* with this instead of blank gives the playfield a visible background — an
|
|
154
|
+
* all-blank BAT reads as a near-empty backdrop (one colour > 92% of the screen,
|
|
155
|
+
* which looks blank to a human). plane1 set = colour index 2. */
|
|
156
|
+
static void upload_field(void) {
|
|
157
|
+
u16 i;
|
|
158
|
+
for (i = 0; i < 16; ++i) font_cell[i] = 0;
|
|
159
|
+
for (i = 0; i < 8; ++i) font_cell[i] = 0xFF00; /* plane1 (hi byte) = ci 2 */
|
|
160
|
+
load_tiles(FIELD_VRAM, font_cell, 16);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* ---- fill the whole BAT with the field tile (a 2x2-cell checkerboard of
|
|
164
|
+
* field/blank), behind the HUD glyphs which are written on top afterwards ---- */
|
|
150
165
|
static void clear_bat(void) {
|
|
151
166
|
u16 r, c;
|
|
167
|
+
u16 field = BAT_ENTRY(0, FIELD_VRAM);
|
|
152
168
|
u16 blank = BAT_ENTRY(0, BLANK_VRAM);
|
|
153
169
|
for (r = 0; r < 32; ++r) {
|
|
154
170
|
vram_set_write_addr((u16)(BAT_VRAM + r * 32));
|
|
155
171
|
for (c = 0; c < 32; ++c) {
|
|
156
|
-
|
|
157
|
-
|
|
172
|
+
u16 e = (((r >> 1) ^ (c >> 1)) & 1) ? blank : field;
|
|
173
|
+
VDC_DATA_LO = (u8)(e & 0xFF);
|
|
174
|
+
VDC_DATA_HI = (u8)(e >> 8);
|
|
158
175
|
}
|
|
159
176
|
}
|
|
160
177
|
}
|
|
@@ -229,6 +246,7 @@ void main(void) {
|
|
|
229
246
|
/* palette: backdrop, BG text colours, sprite colours */
|
|
230
247
|
vce_set_color(0, PCE_RGB(0, 0, 1)); /* backdrop: near-black blue */
|
|
231
248
|
vce_set_color(1, PCE_RGB(7, 7, 7)); /* BG colour 1: white text */
|
|
249
|
+
vce_set_color(2, PCE_RGB(1, 1, 3)); /* BG colour 2: dim field blue */
|
|
232
250
|
vce_set_color(256, PCE_RGB(0, 0, 0)); /* sprite transparent */
|
|
233
251
|
vce_set_color(257, PCE_RGB(2, 5, 7)); /* sprite c1: cyan (catcher 0) */
|
|
234
252
|
vce_set_color(259, PCE_RGB(2, 5, 7)); /* sprite c3: cyan (catcher) */
|
|
@@ -238,6 +256,7 @@ void main(void) {
|
|
|
238
256
|
|
|
239
257
|
upload_font();
|
|
240
258
|
upload_sprites();
|
|
259
|
+
upload_field(); /* dim field tile for a visible playfield BG */
|
|
241
260
|
|
|
242
261
|
clear_bat();
|
|
243
262
|
draw_hud_labels();
|
|
@@ -76,6 +76,32 @@ static void draw_bar(u8 active) {
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/* Paint a decorative full-screen frame so the demo reads as a real UI panel
|
|
80
|
+
* instead of a near-empty backdrop. conio only inits a backdrop + font, so an
|
|
81
|
+
* otherwise text-only screen leaves >92% of pixels one colour, which looks
|
|
82
|
+
* blank to a human. We fill the top/bottom title bands and both side borders
|
|
83
|
+
* with a block character (the labels are drawn ON TOP afterwards). PCE conio is
|
|
84
|
+
* 32 cols x 28 rows. */
|
|
85
|
+
#define SCR_COLS 32
|
|
86
|
+
#define SCR_ROWS 28
|
|
87
|
+
static void draw_frame(void) {
|
|
88
|
+
u8 x, y;
|
|
89
|
+
/* solid top band (rows 0-1) and bottom band (rows 26-27) */
|
|
90
|
+
for (y = 0; y < SCR_ROWS; ++y) {
|
|
91
|
+
if (y < 2 || y >= SCR_ROWS - 2) {
|
|
92
|
+
for (x = 0; x < SCR_COLS; ++x) cputcxy(x, y, '#');
|
|
93
|
+
} else {
|
|
94
|
+
/* left + right vertical borders (two columns each for weight) */
|
|
95
|
+
cputcxy(0, y, '#');
|
|
96
|
+
cputcxy(1, y, '#');
|
|
97
|
+
cputcxy((u8)(SCR_COLS - 2), y, '#');
|
|
98
|
+
cputcxy((u8)(SCR_COLS - 1), y, '#');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/* a mid separator bar under the title so the panel has visible structure */
|
|
102
|
+
for (x = 2; x < SCR_COLS - 2; ++x) cputcxy(x, 5, '=');
|
|
103
|
+
}
|
|
104
|
+
|
|
79
105
|
void main(void) {
|
|
80
106
|
u8 pad, prev_pad;
|
|
81
107
|
u8 step; /* current melody step 0..7 */
|
|
@@ -86,8 +112,9 @@ void main(void) {
|
|
|
86
112
|
|
|
87
113
|
_keep[0] = 0;
|
|
88
114
|
|
|
89
|
-
/* conio: clear + enable display,
|
|
115
|
+
/* conio: clear + enable display, paint the frame, then the static labels. */
|
|
90
116
|
clrscr();
|
|
117
|
+
draw_frame(); /* visible bordered panel (not a blank backdrop) */
|
|
91
118
|
cputsxy(8, 8, "PC ENGINE MUSIC + SFX");
|
|
92
119
|
cputsxy(8, 11, "MELODY:");
|
|
93
120
|
cputsxy(8, BAR_Y - 1, "STEP:");
|
|
@@ -90,7 +90,10 @@ static void make_sprite(void) {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
/* Draw a 32x32-cell checkerboard of TILE_A / TILE_B across the BAT. The default
|
|
93
|
-
* PCE virtual screen is 32x32 cells (256x256 px), which covers the display.
|
|
93
|
+
* PCE virtual screen is 32x32 cells (256x256 px), which covers the display.
|
|
94
|
+
* A two-colour checkerboard (green + dark teal) makes the whole playfield read
|
|
95
|
+
* as a real, visible background — a SOLID single-colour fill instead looks blank
|
|
96
|
+
* to a human (one colour covers >92% of the screen), so we alternate by cell. */
|
|
94
97
|
static void fill_bat(void) {
|
|
95
98
|
u16 ea = BAT_ENTRY(TILE_A_VRAM, 0);
|
|
96
99
|
u16 eb = BAT_ENTRY(TILE_B_VRAM, 0);
|
|
@@ -98,7 +101,9 @@ static void fill_bat(void) {
|
|
|
98
101
|
for (r = 0; r < 32; ++r) {
|
|
99
102
|
vram_set_write_addr((u16)(BAT_VRAM + r * 32));
|
|
100
103
|
for (col = 0; col < 32; ++col) {
|
|
101
|
-
|
|
104
|
+
/* 2x2-cell checkerboard: alternates green/teal so the background is
|
|
105
|
+
* unmistakably present while the sprite still stands out clearly. */
|
|
106
|
+
e = (((r >> 1) ^ (col >> 1)) & 1) ? eb : ea;
|
|
102
107
|
VDC_DATA_LO = (u8)(e & 0xFF);
|
|
103
108
|
VDC_DATA_HI = (u8)(e >> 8);
|
|
104
109
|
}
|
|
@@ -28,22 +28,37 @@ extern void sms_vdp_write_reg(uint8_t reg, uint8_t value);
|
|
|
28
28
|
extern void sms_vdp_set_addr(uint16_t addr, uint8_t prefix);
|
|
29
29
|
extern void sms_load_palette(const uint8_t *palette);
|
|
30
30
|
extern void sms_load_tiles(uint16_t vram_dest, const uint8_t *src, uint16_t byte_count);
|
|
31
|
+
extern void sms_set_tilemap_cell(uint8_t row, uint8_t col, uint8_t tile_idx, uint8_t attr);
|
|
31
32
|
extern void sms_vblank_wait(void);
|
|
32
33
|
extern uint8_t sms_joypad_read(void);
|
|
33
34
|
extern void sms_sprite_init(void);
|
|
34
35
|
extern void sms_sprite_set(uint8_t slot, uint8_t x, uint8_t y, uint8_t tile);
|
|
35
36
|
extern void sms_sat_upload(void);
|
|
36
37
|
|
|
37
|
-
/* BG palette: backdrop blue
|
|
38
|
-
*
|
|
38
|
+
/* BG palette: backdrop blue, colour 1 = teal, colour 2 = navy (the two
|
|
39
|
+
* tones of the dithered BG). Sprite palette (entries 16-31) sets white at
|
|
40
|
+
* index 17 so our sprite is visible.
|
|
39
41
|
* SMS CRAM is 2-2-2 BGR: 0x00=black, 0x3F=white. */
|
|
40
42
|
static const uint8_t palette[32] = {
|
|
41
|
-
0x10,
|
|
43
|
+
0x10,0x38,0x20,0x00, 0x00,0x00,0x00,0x00,
|
|
42
44
|
0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
|
|
43
45
|
0x00,0x3F,0x00,0x00, 0x00,0x00,0x00,0x00,
|
|
44
46
|
0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
|
|
45
47
|
};
|
|
46
48
|
|
|
49
|
+
/* Two BG tiles in the BG bank at $0000. Tile 0 is a dithered checkerboard
|
|
50
|
+
* (plane0/plane1 alternate per row) so the whole BG fills with TWO colours
|
|
51
|
+
* and no single colour dominates the frame — a flat one-colour fill still
|
|
52
|
+
* reads as a blank screen. */
|
|
53
|
+
static const uint8_t bg_tiles[32 * 1] = {
|
|
54
|
+
/* T_BG — dither: plane0=0xAA→colour 1, plane1=0x55→colour 2, swapped
|
|
55
|
+
* each row so it reads as a fine checkerboard. */
|
|
56
|
+
0xAA,0x55,0x00,0x00, 0x55,0xAA,0x00,0x00,
|
|
57
|
+
0xAA,0x55,0x00,0x00, 0x55,0xAA,0x00,0x00,
|
|
58
|
+
0xAA,0x55,0x00,0x00, 0x55,0xAA,0x00,0x00,
|
|
59
|
+
0xAA,0x55,0x00,0x00, 0x55,0xAA,0x00,0x00,
|
|
60
|
+
};
|
|
61
|
+
|
|
47
62
|
/* One 8×8 sprite tile (4bpp interleaved). Filled square in color 1. */
|
|
48
63
|
static const uint8_t sprite_tile[32] = {
|
|
49
64
|
0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
|
|
@@ -52,6 +67,14 @@ static const uint8_t sprite_tile[32] = {
|
|
|
52
67
|
0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
|
|
53
68
|
};
|
|
54
69
|
|
|
70
|
+
/* Fill the whole 32×28 name table with the dithered BG tile. */
|
|
71
|
+
static void draw_bg(void) {
|
|
72
|
+
uint8_t row, col;
|
|
73
|
+
for (row = 0; row < 28; row++)
|
|
74
|
+
for (col = 0; col < 32; col++)
|
|
75
|
+
sms_set_tilemap_cell(row, col, 0, 0);
|
|
76
|
+
}
|
|
77
|
+
|
|
55
78
|
void main(void) {
|
|
56
79
|
uint8_t x = 124; /* mid-screen X */
|
|
57
80
|
uint8_t y = 88; /* mid-screen Y */
|
|
@@ -59,6 +82,9 @@ void main(void) {
|
|
|
59
82
|
|
|
60
83
|
sms_vdp_init();
|
|
61
84
|
sms_load_palette(palette);
|
|
85
|
+
/* BG dither tile → BG bank $0000, then paint the whole name table. */
|
|
86
|
+
sms_load_tiles(0x0000, bg_tiles, 32);
|
|
87
|
+
draw_bg();
|
|
62
88
|
/* Upload one sprite tile to VRAM $2000 (sprite tile area). */
|
|
63
89
|
sms_load_tiles(0x2000, sprite_tile, 32);
|
|
64
90
|
|
|
@@ -29,10 +29,11 @@ extern void sms_load_tiles(uint16_t vram_dest, const uint8_t *src, uint16_t byte
|
|
|
29
29
|
extern void sms_set_tilemap_cell(uint8_t row, uint8_t col, uint8_t tile_idx, uint8_t attr);
|
|
30
30
|
extern void sms_vblank_wait(void);
|
|
31
31
|
|
|
32
|
-
/* BG palette: backdrop dark blue, fg colour 1 = bright cyan, colour 2 = yellow
|
|
33
|
-
*
|
|
32
|
+
/* BG palette: backdrop dark blue, fg colour 1 = bright cyan, colour 2 = yellow,
|
|
33
|
+
* colour 3 = navy (the second dither tone behind the text).
|
|
34
|
+
* SMS CRAM is 2-2-2 BGR — 0x20 = blue, 0x3F = white/cyan, 0x0F = yellow. */
|
|
34
35
|
static const uint8_t palette[32] = {
|
|
35
|
-
0x20, 0x3F, 0x0F,
|
|
36
|
+
0x20, 0x3F, 0x0F, 0x28, 0x00, 0x00, 0x00, 0x00,
|
|
36
37
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
37
38
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
38
39
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
@@ -100,8 +101,19 @@ static const uint8_t font_tiles[] = {
|
|
|
100
101
|
0xC3,0x00,0x00,0x00, 0xC3,0x00,0x00,0x00,
|
|
101
102
|
0xC3,0x00,0x00,0x00, 0xC3,0x00,0x00,0x00,
|
|
102
103
|
0x66,0x00,0x00,0x00, 0x3C,0x00,0x00,0x00,
|
|
104
|
+
|
|
105
|
+
/* tile 9 — dithered BG. plane1=0xFF (colour-2 bit always on), plane0
|
|
106
|
+
* alternates 0xAA/0x55 so pixels flip between colour 2 (yellow) and
|
|
107
|
+
* colour 3 (navy). Fills the whole field with TWO tones so no single
|
|
108
|
+
* colour dominates, while the cyan (colour 1) text stays distinct. */
|
|
109
|
+
0xAA,0xFF,0x00,0x00, 0x55,0xFF,0x00,0x00,
|
|
110
|
+
0xAA,0xFF,0x00,0x00, 0x55,0xFF,0x00,0x00,
|
|
111
|
+
0xAA,0xFF,0x00,0x00, 0x55,0xFF,0x00,0x00,
|
|
112
|
+
0xAA,0xFF,0x00,0x00, 0x55,0xFF,0x00,0x00,
|
|
103
113
|
};
|
|
104
114
|
|
|
115
|
+
#define T_DITHER 9
|
|
116
|
+
|
|
105
117
|
/* Tile indices for each char in our message — 'S' 'M' 'S' ' ' 'M' 'U'
|
|
106
118
|
* 'S' 'I' 'C' ' ' 'D' 'E' 'M' 'O'. 14 cells total. */
|
|
107
119
|
static const uint8_t message[14] = {
|
|
@@ -114,9 +126,11 @@ static const uint8_t message[14] = {
|
|
|
114
126
|
static void clear_name_table(void) {
|
|
115
127
|
uint8_t row;
|
|
116
128
|
uint8_t col;
|
|
129
|
+
/* Fill with the dithered BG tile (not blank) so the whole screen reads
|
|
130
|
+
* as a two-tone field and never as a blank backdrop. */
|
|
117
131
|
for (row = 0; row < 28; row++) {
|
|
118
132
|
for (col = 0; col < 32; col++) {
|
|
119
|
-
sms_set_tilemap_cell(row, col,
|
|
133
|
+
sms_set_tilemap_cell(row, col, T_DITHER, 0);
|
|
120
134
|
}
|
|
121
135
|
}
|
|
122
136
|
}
|
|
@@ -24,6 +24,7 @@ extern void sms_vdp_init(void);
|
|
|
24
24
|
extern void sms_vdp_display_on(void);
|
|
25
25
|
extern void sms_load_palette(const uint8_t *palette);
|
|
26
26
|
extern void sms_load_tiles(uint16_t vram_dest, const uint8_t *src, uint16_t byte_count);
|
|
27
|
+
extern void sms_set_tilemap_cell(uint8_t row, uint8_t col, uint8_t tile_idx, uint8_t attr);
|
|
27
28
|
extern void sms_vblank_wait(void);
|
|
28
29
|
extern uint8_t sms_joypad_read(void);
|
|
29
30
|
extern uint8_t sms_joypad_read_p2(void);
|
|
@@ -40,13 +41,32 @@ extern void sms_sat_upload(void);
|
|
|
40
41
|
#define T_ENEMY 3
|
|
41
42
|
|
|
42
43
|
static const uint8_t palette[32] = {
|
|
43
|
-
|
|
44
|
+
/* BG palette: 0 backdrop space-blue, 1 mid-blue, 2 dark-blue (starfield dither) */
|
|
45
|
+
0x10, 0x24, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
44
46
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
45
47
|
/* Sprite palette: 1 white (P1), 2 yellow (bullet), 3 red (enemy + P2 highlight) */
|
|
46
48
|
0x00, 0x3F, 0x0F, 0x03, 0x00, 0x00, 0x00, 0x00,
|
|
47
49
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
48
50
|
};
|
|
49
51
|
|
|
52
|
+
/* One dithered BG tile (BG bank $0000): plane0=0xAA/0x55, plane1=0x55/0xAA
|
|
53
|
+
* so pixels alternate colour 1 (mid-blue) and colour 2 (dark-blue) in a fine
|
|
54
|
+
* checkerboard. Filling the name table with it gives a two-tone "space"
|
|
55
|
+
* backdrop so the screen never reads as a single flat colour. */
|
|
56
|
+
static const uint8_t bg_tiles[32] = {
|
|
57
|
+
0xAA,0x55,0x00,0x00, 0x55,0xAA,0x00,0x00,
|
|
58
|
+
0xAA,0x55,0x00,0x00, 0x55,0xAA,0x00,0x00,
|
|
59
|
+
0xAA,0x55,0x00,0x00, 0x55,0xAA,0x00,0x00,
|
|
60
|
+
0xAA,0x55,0x00,0x00, 0x55,0xAA,0x00,0x00,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
static void draw_bg(void) {
|
|
64
|
+
uint8_t row, col;
|
|
65
|
+
for (row = 0; row < 28; row++)
|
|
66
|
+
for (col = 0; col < 32; col++)
|
|
67
|
+
sms_set_tilemap_cell(row, col, 0, 0);
|
|
68
|
+
}
|
|
69
|
+
|
|
50
70
|
/* 4 sprite tiles back-to-back: P1 ship (col1), P2 ship (col3), bullet (col2), enemy (col3). */
|
|
51
71
|
static const uint8_t sprite_tiles[32 * 4] = {
|
|
52
72
|
/* P1 ship — diamond, colour 1 (plane 0) */
|
|
@@ -112,6 +132,9 @@ void main(void) {
|
|
|
112
132
|
uint8_t prev1 = 0, prev2 = 0;
|
|
113
133
|
sms_vdp_init();
|
|
114
134
|
sms_load_palette(palette);
|
|
135
|
+
/* BG dither tile → BG bank $0000, paint the whole name table. */
|
|
136
|
+
sms_load_tiles(0x0000, bg_tiles, 32);
|
|
137
|
+
draw_bg();
|
|
115
138
|
sms_load_tiles(0x2000, sprite_tiles, 32 * 4);
|
|
116
139
|
|
|
117
140
|
p1.x = 80; p1.y = 160; p1.alive = 1;
|
|
@@ -123,6 +123,7 @@ void main(void) {
|
|
|
123
123
|
do {
|
|
124
124
|
uint8_t p1, p2;
|
|
125
125
|
uint8_t slot;
|
|
126
|
+
int16_t target;
|
|
126
127
|
sms_vblank_wait();
|
|
127
128
|
sfx_update();
|
|
128
129
|
|
|
@@ -144,12 +145,13 @@ void main(void) {
|
|
|
144
145
|
if ((p1 & JOY_UP) && p1y > COURT_TOP) p1y -= 2;
|
|
145
146
|
if ((p1 & JOY_DOWN) && p1y < COURT_BOT - PADDLE_H) p1y += 2;
|
|
146
147
|
|
|
147
|
-
/* P2 input if any, otherwise AI.
|
|
148
|
+
/* P2 input if any, otherwise AI. (`target` is declared at the top of the
|
|
149
|
+
* loop body — SDCC is C89, declarations must precede statements.) */
|
|
148
150
|
if (p2 != 0) {
|
|
149
151
|
if ((p2 & JOY_UP) && p2y > COURT_TOP) p2y -= 2;
|
|
150
152
|
if ((p2 & JOY_DOWN) && p2y < COURT_BOT - PADDLE_H) p2y += 2;
|
|
151
153
|
} else {
|
|
152
|
-
|
|
154
|
+
target = by - PADDLE_H / 2;
|
|
153
155
|
if (p2y < target && p2y < COURT_BOT - PADDLE_H) p2y += 1;
|
|
154
156
|
else if (p2y > target && p2y > COURT_TOP) p2y -= 1;
|
|
155
157
|
}
|