/* cc2600 Multisprite Library Copyright (C) 2024 Bruno STEUX This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Contact info: bruno.steux@gmail.com */ // v0.1: Initial version // DONE: Add support for SARA chip // DONE: Test with bankswitching // DONE: Add support for single color sprites (MS_ONE_COLOR_SPRITES) // DONE: Implement bidir search in multisprite_move #ifndef __MULTISPRITE_H__ #define __MULTISPRITE_H__ #include "vcs.h" #ifndef EXTRA_RAM #define EXTRA_RAM #endif #ifndef kernel_short_macro #define kernel_short_macro #endif #ifndef kernel_medium_macro #define kernel_medium_macro #endif #ifndef kernel_long_macro #define kernel_long_macro strobe(WSYNC) #endif #ifndef MS_MAX_NB_SPRITES #define MS_MAX_NB_SPRITES 10 #endif #ifndef MS_PLAYFIELD_HEIGHT #define MS_PLAYFIELD_HEIGHT 192 #endif #ifndef MS_OFFSCREEN_BANK #define MS_OFFSCREEN_BANK bank0 #endif #ifndef MS_OFFSCREEN2_BANK #define MS_OFFSCREEN2_BANK MS_OFFSCREEN_BANK #endif #ifndef MS_KERNEL_BANK #define MS_KERNEL_BANK bank0 #endif #define MS_UNALLOCATED 255 #define MS_OFFSET 32 #define MS_END_OF_SCREEN (MS_PLAYFIELD_HEIGHT + MS_OFFSET - 2) #define MS_REFLECTED 8 #define MS_PF_COLLISION 0x40 #define MS_COLLISION 0x80 char ms_y0, ms_y1, ms_h0, ms_h1, ms_v; #ifndef MS_ONE_COLOR_SPRITES char *ms_colup0ptr, *ms_colup1ptr; #endif char *ms_grp0ptr, *ms_grp1ptr, *ms_scenery; char ms_sprite_iter; EXTRA_RAM char ms_sprite_x[MS_MAX_NB_SPRITES]; EXTRA_RAM char ms_sprite_y[MS_MAX_NB_SPRITES]; EXTRA_RAM char ms_sprite_model[MS_MAX_NB_SPRITES]; EXTRA_RAM char ms_sprite_nusiz[MS_MAX_NB_SPRITES]; EXTRA_RAM char ms_sorted_by_y[MS_MAX_NB_SPRITES]; #ifdef MS_ONE_COLOR_SPRITES EXTRA_RAM char ms_color[MS_MAX_NB_SPRITES]; #endif char ms_id_p[2]; char ms_nb_sprites; // Generated by hmgen2 17 MS_OFFSCREEN2_BANK aligned(256) const char ms_sprite_wait_offscreen[160] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11}; #ifdef MS_OFFSCREEN2_DATA MS_OFFSCREEN2_BANK { MS_OFFSCREEN2_DATA } #endif MS_OFFSCREEN2_BANK aligned(256) const char ms_sprite_hm_offscreen[160] = {0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00, 0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80, 0x00, 0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00, 0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00, 0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00, 0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00, 0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00, 0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00, 0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00, 0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00, 0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00, 0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80, 0x60}; // Generated by hmgen2 19 (compiled with -DEARLY_HMOVE) MS_KERNEL_BANK aligned(256) const char ms_sprite_wait[160] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11}; #ifdef MS_KERNEL_DATA MS_KERNEL_BANK { MS_KERNEL_DATA } #endif MS_KERNEL_BANK aligned(256) const char ms_sprite_hm[160] = {0x40, 0x30, 0x20, 0x10, 0x00, 0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80, 0x00, 0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00, 0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00, 0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00, 0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00, 0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00, 0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00, 0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00, 0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00, 0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00, 0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80, 0x60, 0x50, 0x40}; MS_OFFSCREEN_BANK void multisprite_init(char *scenery) { ms_nb_sprites = 0; ms_scenery = scenery - MS_OFFSET; } inline void multisprite_clear() { ms_nb_sprites = 0; } #ifdef MS_ONE_COLOR_SPRITES // Create a new sprite at nx, ny (model and nusiz provided) // In output, X is the rank of this sprite MS_OFFSCREEN_BANK char multisprite_new(char model, char nx, char ny, char nusiz, char color) { char i; // Look for right ny position for (X = ms_nb_sprites; X != 0; X--) { X--; i = ms_sorted_by_y[X]; X++; if (ny >= ms_sprite_y[Y = i & 0x7f]) break; ms_sorted_by_y[X] = i; } // Put new sprite data // Look for a free place for (Y = 0; Y != ms_nb_sprites; Y++) { if (ms_sprite_y[Y] == MS_UNALLOCATED) break; } ms_sorted_by_y[X] = Y; ms_sprite_x[Y] = nx; ms_sprite_y[Y] = ny; ms_sprite_model[Y] = model; ms_sprite_nusiz[Y] = nusiz; ms_color[Y] = color; // Update number of sprites ms_nb_sprites++; return Y; } #else // Create a new sprite at nx, ny (model and nusiz provided) // In output, X is the rank of this sprite MS_OFFSCREEN_BANK char multisprite_new(char model, char nx, char ny, char nusiz) { char i; // Look for right ny position for (X = ms_nb_sprites; X != 0; X--) { X--; i = ms_sorted_by_y[X]; X++; if (ny >= ms_sprite_y[Y = i & 0x7f]) break; ms_sorted_by_y[X] = i; } // Put new sprite data // Look for a free place for (Y = 0; Y != ms_nb_sprites; Y++) { if (ms_sprite_y[Y] == MS_UNALLOCATED) break; } ms_sorted_by_y[X] = Y; ms_sprite_x[Y] = nx; ms_sprite_y[Y] = ny; ms_sprite_model[Y] = model; ms_sprite_nusiz[Y] = nusiz; // Update number of sprites ms_nb_sprites++; return Y; } #endif MS_OFFSCREEN_BANK void multisprite_delete_with_rank(char i, char rank) { // Remove from ms_sorted_by_y array if (i != (ms_sorted_by_y[X = rank] & 0x7f)) { X = i; if (ms_sprite_y[X] >= MS_OFFSET + MS_PLAYFIELD_HEIGHT / 2) { for (X = ms_nb_sprites - 1; X != 0; X--) { if ((ms_sorted_by_y[X] & 0x7f) == i) break; } } else { for (X = 0; X != ms_nb_sprites; X++) { if ((ms_sorted_by_y[X] & 0x7f) == i) break; } } } Y = X; Y++; for (; X < ms_nb_sprites - 1; X++, Y++) { ms_sorted_by_y[X] = ms_sorted_by_y[Y]; } ms_sorted_by_y[X] = MS_UNALLOCATED; ms_sprite_y[X = i] = MS_UNALLOCATED; // Mark as free ms_nb_sprites--; } MS_OFFSCREEN_BANK void multisprite_delete(char i) { // Remove from ms_sorted_by_y array X = i; if (ms_sprite_y[X] >= MS_OFFSET + MS_PLAYFIELD_HEIGHT / 2) { for (X = ms_nb_sprites - 1; X != 0; X--) { if ((ms_sorted_by_y[X] & 0x7f) == i) break; } } else { for (X = 0; X != ms_nb_sprites; X++) { if ((ms_sorted_by_y[X] & 0x7f) == i) break; } } Y = X; Y++; for (; X < ms_nb_sprites - 1; X++, Y++) { ms_sorted_by_y[X] = ms_sorted_by_y[Y]; } ms_sorted_by_y[X] = MS_UNALLOCATED; ms_sprite_y[X = i] = MS_UNALLOCATED; // Mark as free ms_nb_sprites--; } // Output: Y as the new rank for this sprite MS_OFFSCREEN_BANK char multisprite_move_with_rank(char i, char nx, char ny, char rank) { char j; X = i; Y = rank; if (nx != -1) ms_sprite_x[X] = nx; if (ms_sprite_y[X] == ny) { return Y; // No vertical move, so nothing to check } if (i != (ms_sorted_by_y[Y] & 0x7f)) { if (ms_sprite_y[X] >= MS_OFFSET + MS_PLAYFIELD_HEIGHT / 2) { for (Y = ms_nb_sprites - 1; Y != 0; Y--) { if ((ms_sorted_by_y[Y] & 0x7f) == i) break; } } else { j = ms_nb_sprites - 1; for (Y = 0; Y != j; Y++) { if ((ms_sorted_by_y[Y] & 0x7f) == i) break; } } } j = ms_sorted_by_y[Y]; // Update ms_sorted_by_y if needed if (ms_sprite_y[X] < ny) { // We have gone downwards ms_sprite_y[X] = ny; i = ms_nb_sprites - 1; for (; Y != i; Y++) { Y++; X = ms_sorted_by_y[Y] & 0x7f; if (ms_sprite_y[X] < ny) { X = ms_sorted_by_y[Y]; Y--; ms_sorted_by_y[Y] = X; } else { Y--; break; } } ms_sorted_by_y[Y] = j; } else { // We have gone upwards ms_sprite_y[X] = ny; for (; Y != 0; Y--) { Y--; X = ms_sorted_by_y[Y] & 0x7f; if (ny < ms_sprite_y[X]) { X = ms_sorted_by_y[Y]; Y++; ms_sorted_by_y[Y] = X; } else { Y++; break; } } ms_sorted_by_y[Y] = j; } return Y; } MS_OFFSCREEN_BANK void multisprite_move(char i, char nx, char ny) { char j; X = i; if (nx != -1) ms_sprite_x[X] = nx; if (ms_sprite_y[X] == ny) return; // No vertical move, so nothing to check if (ms_sprite_y[X] >= MS_OFFSET + MS_PLAYFIELD_HEIGHT / 2) { for (Y = ms_nb_sprites - 1; Y != 0; Y--) { if ((ms_sorted_by_y[Y] & 0x7f) == i) break; } } else { j = ms_nb_sprites - 1; for (Y = 0; Y != j; Y++) { if ((ms_sorted_by_y[Y] & 0x7f) == i) break; } } j = ms_sorted_by_y[Y]; // Update ms_sorted_by_y if needed if (ms_sprite_y[X] < ny) { // We have gone downwards ms_sprite_y[X] = ny; i = ms_nb_sprites - 1; for (; Y != i; Y++) { Y++; X = ms_sorted_by_y[Y] & 0x7f; if (ms_sprite_y[X] < ny) { X = ms_sorted_by_y[Y]; Y--; ms_sorted_by_y[Y] = X; } else { Y--; break; } } ms_sorted_by_y[Y] = j; } else { // We have gone upwards ms_sprite_y[X] = ny; for (; Y != 0; Y--) { Y--; X = ms_sorted_by_y[Y] & 0x7f; if (ny < ms_sprite_y[X]) { X = ms_sorted_by_y[Y]; Y++; ms_sorted_by_y[Y] = X; } else { Y++; break; } } ms_sorted_by_y[Y] = j; } } // See https://stackoverflow.com/questions/1557894/non-recursive-merge-sort for reference void _ms_mergesort() { char left, rght, wid, rend; char ai, aj, yi, yj; char j, k, m; char b[MS_MAX_NB_SPRITES]; char end = ms_nb_sprites - 1; for (Y = 0; Y < end; ) { ai = ms_sorted_by_y[Y++]; yi = ms_sprite_y[X = ai & 0x7f]; aj = ms_sorted_by_y[Y++]; yj = ms_sprite_y[X = aj & 0x7f]; if (yj < yi) { ms_sorted_by_y[--Y] = ai; ms_sorted_by_y[--Y] = aj; Y++; Y++; } } for (k = 2; k < ms_nb_sprites; k <<= 1) { end = ms_nb_sprites - k; for (left = 0; left < end; left += (k << 1)) { rght = left + k; rend = rght + k; if (ms_nb_sprites < rend) rend = ms_nb_sprites; m = left; Y = left; j = rght; if (j < rend) { ai = ms_sorted_by_y[Y]; yi = ms_sprite_y[X = ai & 0x7f]; aj = ms_sorted_by_y[X = j]; yj = ms_sprite_y[X = aj & 0x7f]; X = m; do { if (yi < yj) { b[X++] = ai; Y++; if (Y >= rght) break; m = X; ai = ms_sorted_by_y[Y]; yi = ms_sprite_y[X = ai & 0x7f]; } else { b[X++] = aj; j++; m = X; aj = ms_sorted_by_y[X = j]; yj = ms_sprite_y[X = aj & 0x7f]; } X = m; } while (j < rend); } while (Y < rght) { b[X++] = ms_sorted_by_y[Y++]; } Y = j; while (Y < rend) { b[X++] = ms_sorted_by_y[Y++]; } for (X = left; X < rend; X++) { ms_sorted_by_y[X] = b[X]; } } } } #ifdef MS_SELECT_FAST MS_OFFSCREEN_BANK _ms_select_sprites() { char end = ms_nb_sprites - 1; char candidate1, candidate2; for (Y = 0; Y < end; Y++) { candidate1 = ms_sorted_by_y[Y]; X = candidate1 & 0x7f; ms_sprite_nusiz[X] &= 0x3f; // Reset collision if (!(candidate1 & 0x80)) { char candidate2 = ms_sorted_by_y[++Y]; Y--; if (candidate2 & 0x80) { // If it was not displayed at previous iteration // Let's see if this candidate overlaps with our previous candidate // Yes. It overlaps. Skip candidate1 and set it as prioritary for next time X = candidate1 | 0x80; } } ms_sorted_by_y[Y] = X; } X = ms_sorted_by_y[Y] & 0x7f; ms_sprite_nusiz[X] &= 0x3f; // Reset collision ms_sorted_by_y[Y] = X; } #else #ifdef MS_SELECT_ACCURATE MS_KERNEL_BANK _ms_select_sprites() { char end = ms_nb_sprites - 1; char candidate1, candidate2; for (Y = 0; Y < end; Y++) { candidate1 = ms_sorted_by_y[Y]; X = candidate1 & 0x7f; ms_sprite_nusiz[X] &= 0x3f; // Reset collision if (!(candidate1 & 0x80)) { char candidate2 = ms_sorted_by_y[++Y]; Y--; if (candidate2 & 0x80) { // If it was not displayed at previous iteration // Let's see if this candidate overlaps with our previous candidate char y1 = ms_sprite_y[X] + 13; char height1 = ms_height[X = ms_sprite_model[X]]; char y2 = ms_sprite_y[X = candidate2 & 0x7f]; if (y1 + height1 >= y2) { // Yes. It overlaps. Skip candidate1 and set it as prioritary for next time X = candidate1 | 0x80; } else { X = candidate1 & 0x7f; } } } ms_sorted_by_y[Y] = X; } X = ms_sorted_by_y[Y] & 0x7f; ms_sprite_nusiz[X] &= 0x3f; // Reset collision ms_sorted_by_y[Y] = X; } #else #ifndef MS_OVERLAP_MARGIN #define MS_OVERLAP_MARGIN 30 #endif MS_OFFSCREEN_BANK _ms_select_sprites() { char end = ms_nb_sprites - 1; char candidate1, candidate2; for (Y = 0; Y < end; Y++) { candidate1 = ms_sorted_by_y[Y]; X = candidate1 & 0x7f; ms_sprite_nusiz[X] &= 0x3f; // Reset collision if (!(candidate1 & 0x80)) { char candidate2 = ms_sorted_by_y[++Y]; Y--; if (candidate2 & 0x80) { // If it was not displayed at previous iteration // Let's see if this candidate overlaps with our previous candidate char y1 = ms_sprite_y[X]; char y2 = ms_sprite_y[X = candidate2 & 0x7f]; if (y1 + MS_OVERLAP_MARGIN >= y2) { // Yes. It overlaps. Skip candidate1 and set it as prioritary for next time X = candidate1 | 0x80; } else { X = candidate1 & 0x7f; } } } ms_sorted_by_y[Y] = X; } X = ms_sorted_by_y[Y] & 0x7f; ms_sprite_nusiz[X] &= 0x3f; // Reset collision ms_sorted_by_y[Y] = X; } #endif #endif MS_OFFSCREEN2_BANK char _ms_allocate_sprite_ex() { char ms_tmp; X = ms_sprite_iter; if (X != ms_nb_sprites) { ms_tmp = ms_sorted_by_y[X]; if (ms_tmp & 0x80) { // was removed X++; if (X == ms_nb_sprites) { ms_sprite_iter = X; return -1; } ms_tmp = ms_sorted_by_y[X] & 0x7f; } X++; ms_sprite_iter = X; return ms_tmp; } return -1; } inline char _ms_allocate_sprite() { char ms_tmp; X = ms_sprite_iter; if (X != ms_nb_sprites) { ms_tmp = ms_sorted_by_y[X]; if (ms_tmp & 0x80) { // was removed X++; if (X == ms_nb_sprites) { ms_sprite_iter = X; return -1; } ms_tmp = ms_sorted_by_y[X] & 0x7f; } X++; ms_sprite_iter = X; return ms_tmp; } return -1; } MS_KERNEL_BANK char _ms_mark_as_removed() { X = ms_sprite_iter; ms_sorted_by_y[--X] |= 0x80; X = ms_scenery[Y]; // 8 strobe(WSYNC); // 3 VSYNC[X] = ms_v; // 7 Y++; // 2 } MS_KERNEL_BANK char _ms_kernel_repo0() { char ms_tmp; // Entering at [46/76] ms_tmp = ms_scenery[Y]; // 9 [55/76] (variable) X = ms_id_p[0]; // 3 *NUSIZ0 = ms_sprite_nusiz[X]; // 7 [65/76] *REFP0 = *NUSIZ0; // 3 [68/76] strobe(WSYNC); // 3 X = ms_sprite_x[X]; // 6 *HMP0 = ms_sprite_hm[X]; // 7 X = ms_sprite_wait[X]; // 6 [19/76] // Critical loop. Must not cross page boundary. if (X) do { X--; } while (X); // 3 for X = 0. 1 + X * 5 cycles. strobe(RESP0); // 3. Minimum = 26 cycles strobe(WSYNC); // 3 X = ms_tmp; // 3 VSYNC[X] = ms_v; // 7 [10] X = ms_id_p[0]; // 3 ms_y0 = ms_sprite_y[X]; // 7 [20] #ifdef MS_ONE_COLOR_SPRITES *COLUP0 = ms_color[X]; // 7 csleep(7); // 7 csleep(5); // 5 csleep(2); // 2 #endif X = ms_sprite_model[X]; // 6 [26] ms_grp0ptr = ms_grptr[X] - ms_y0; // 21 [47] #ifndef MS_ONE_COLOR_SPRITES ms_colup0ptr = ms_coluptr[X] - ms_y0; // 21 [68] #endif Y++; // 2 [70] strobe(HMOVE); // Early hmove [73] //strobe(WSYNC); // 3 ms_v = ms_scenery[Y++]; // 11 return ms_height[X]; // Must leave at < [53/76] } // RTS: 6 MS_KERNEL_BANK char _ms_kernel_repo1() { char ms_tmp; // Entering at [46/76] ms_tmp = ms_scenery[Y]; // 9 [55/76] (variable) X = ms_id_p[1]; // 3 *NUSIZ1 = ms_sprite_nusiz[X]; // 7 [65/76] *REFP1 = *NUSIZ1; // 3 [68/76] strobe(WSYNC); // 3 X = ms_sprite_x[X]; // 6 *HMP1 = ms_sprite_hm[X]; // 7 X = ms_sprite_wait[X]; // 6 [19/76] // Critical loop. Must not cross page boundary. if (X) do { X--; } while (X); // 3 for X = 0. 1 + X * 5 cycles. strobe(RESP1); // 3. Minimum = 26 cycles strobe(WSYNC); // 3 X = ms_tmp; // 3 VSYNC[X] = ms_v; // 7 [10] X = ms_id_p[1]; // 3 ms_y1 = ms_sprite_y[X]; // 7 [20] #ifdef MS_ONE_COLOR_SPRITES *COLUP1 = ms_color[X]; // 7 csleep(7); // 7 csleep(5); // 5 csleep(2); // 2 #endif X = ms_sprite_model[X]; // 6 [26] ms_grp1ptr = ms_grptr[X] - ms_y1; // 21 [47] #ifndef MS_ONE_COLOR_SPRITES ms_colup1ptr = ms_coluptr[X] - ms_y1; // 21 [68] #endif Y++; // 2 [70] strobe(HMOVE); // Early hmove [73] //strobe(WSYNC); // 3 ms_v = ms_scenery[Y++]; // 11 return ms_height[X]; // Must leave at < [53/76] } // RTS: 6 MS_KERNEL_BANK void _ms_p0_kernel(char stop) { do { ms_v = ms_scenery[Y]; // 9 strobe(WSYNC); // 3 *GRP0 = ms_grp0ptr[Y]; // 9 #ifndef MS_ONE_COLOR_SPRITES *COLUP0 = ms_colup0ptr[Y]; // 9 #endif kernel_medium_macro; // Max 39 cycles Y++; // 2 X = ms_scenery[Y]; // 8 load(ms_grp0ptr[Y]); // 6 strobe(WSYNC); // 3 // [37/76] store(*GRP0); // 3 #ifndef MS_ONE_COLOR_SPRITES *COLUP0 = ms_colup0ptr[Y]; // 9 #endif VSYNC[X] = ms_v; // 7 Y++; // 2 } while (Y < stop); // 5/6 // [32/76] including RTS } MS_KERNEL_BANK void _ms_p1_kernel(char stop) { do { ms_v = ms_scenery[Y]; // 9 strobe(WSYNC); // 3 *GRP1 = ms_grp1ptr[Y]; // 9 #ifndef MS_ONE_COLOR_SPRITES *COLUP1 = ms_colup1ptr[Y]; // 9 #endif kernel_medium_macro; // Max 39 cycles Y++; // 2 X = ms_scenery[Y]; // 8 load(ms_grp1ptr[Y]); // 6 strobe(WSYNC); // 3 // [37/76] store(*GRP1); // 3 #ifndef MS_ONE_COLOR_SPRITES *COLUP1 = ms_colup1ptr[Y]; // 9 #endif VSYNC[X] = ms_v; // 7 Y++; // 2 } while (Y < stop); // 5/6 // [32/76] including RTS } MS_KERNEL_BANK void _ms_p0_p1_kernel(char stop) { #ifndef MS_ONE_COLOR_SPRITES char ms_colup0, ms_colup1; #endif // [46/76] when coming from p0/p1 multisprite_kernel ms_v = ms_scenery[Y]; // 9 *GRP0 = ms_grp0ptr[Y]; // 9 #ifndef MS_ONE_COLOR_SPRITES *COLUP1 = ms_colup1ptr[Y]; // 9 *COLUP0 = ms_colup0ptr[Y]; // 9 [82/76]. No WSYNC necessary #endif *GRP1 = ms_grp1ptr[Y]; // 9 *VDELP0 = 1; // 5 Y++; // 2 X = ms_scenery[Y]; // 8 #ifndef MS_ONE_COLOR_SPRITES ms_colup0 = ms_colup0ptr[Y]; // 9 ms_colup1 = ms_colup1ptr[Y]; // 9 #endif *GRP0 = ms_grp0ptr[Y]; // 9 load(ms_grp1ptr[Y]); // 6 strobe(WSYNC); // 3: Total (2) = 134 = 58 into the second line store(*GRP1); // 3 #ifndef MS_ONE_COLOR_SPRITES *COLUP0 = ms_colup0; // 6 *COLUP1 = ms_colup1; // 6 #endif VSYNC[X] = ms_v; // 7 Y++; // 2 if (Y < stop) { do { // [30/76] while looping ms_v = ms_scenery[Y]; // 9 #ifndef MS_ONE_COLOR_SPRITES ms_colup0 = ms_colup0ptr[Y]; // 9 ms_colup1 = ms_colup1ptr[Y]; // 9 #endif *GRP0 = ms_grp0ptr[Y]; // 9 load(ms_grp1ptr[Y]); // 6 strobe(WSYNC); // 3 ; Total (1) = 30 + 36 + 9 = 76 store(*GRP1); // 3 #ifndef MS_ONE_COLOR_SPRITES *COLUP0 = ms_colup0; // 6 *COLUP1 = ms_colup1; // 6 #endif kernel_short_macro; // Max 13 cycles Y++; // 2 X = ms_scenery[Y]; // 8 #ifndef MS_ONE_COLOR_SPRITES ms_colup0 = ms_colup0ptr[Y]; // 9 ms_colup1 = ms_colup1ptr[Y]; // 9 #endif *GRP0 = ms_grp0ptr[Y]; // 9 load(ms_grp1ptr[Y++]); // 8 strobe(WSYNC); // 3: Total (2) = 63 store(*GRP1); // 3 #ifndef MS_ONE_COLOR_SPRITES *COLUP0 = ms_colup0; // 6 *COLUP1 = ms_colup1; // 6 #endif VSYNC[X] = ms_v; // 7 } while (Y < stop); // 5/6 } *VDELP0 = 0; // 5 // [40/76] when getting out (including RTS) } MS_KERNEL_BANK void _ms_void_kernel(char stop) { // [45/76] when coming from no sprite 0 & 1 left to display strobe(WSYNC); // 3 // *GRP0 = 0; // *GRP1 = 0; ms_v = ms_scenery[Y]; // 9 Y++; // 2 X = ms_scenery[Y++]; // 10 load(ms_v); // 3 strobe(WSYNC); // 3 store(VSYNC[X]); // 4 if (Y < stop) { do { kernel_long_macro; // Macro max 76 * 2 - 39 = 113 cycles (min 76 - 19 = 57 cycles) ms_v = ms_scenery[Y]; // 9 Y++; // 2 X = ms_scenery[Y++]; // 10 load(ms_v); // 3 strobe(WSYNC); // 3 store(VSYNC[X]); // 4 } while (Y < stop); // 5/6 } } //[15/76] when getting out (including RTS) MS_OFFSCREEN2_BANK void multisprite_kernel_post() { for (X = ms_sprite_iter; X < ms_nb_sprites; X++) { ms_sorted_by_y[X] |= 0x80; } } MS_OFFSCREEN2_BANK void _ms_kernel_prep() { // Phase 1: before the multisprite_kernel actually starts, allocates and positions sprites p0 and p1. ms_sprite_iter = 0; ms_v = 0; *VDELP0 = 0; ms_y0 = MS_UNALLOCATED; ms_y1 = MS_UNALLOCATED; strobe(CXCLR); X = _ms_allocate_sprite_ex(); // Position sprite 0 if (X != -1) { ms_id_p[0] = X; *NUSIZ0 = ms_sprite_nusiz[X]; *REFP0 = *NUSIZ0; #ifdef MS_ONE_COLOR_SPRITES *COLUP0 = ms_color[X]; #endif ms_y0 = ms_sprite_y[X]; X = ms_sprite_x[X]; // 6 strobe(WSYNC); // 3 *HMP0 = ms_sprite_hm_offscreen[X]; // 7 X = ms_sprite_wait_offscreen[X]; // 6 csleep(4); // 4 [17/76] // Critical loop. Must not cross page boundary. if (X) do { X--; } while (X); // 3 for X = 0. 1 + X * 5 cycles. strobe(RESP0); // 3. Minimum = 23 cycles strobe(WSYNC); // 3 strobe(HMOVE); } X = _ms_allocate_sprite_ex(); // Position sprite 1 if (X != -1) { ms_id_p[1] = X; *NUSIZ1 = ms_sprite_nusiz[X]; *REFP1 = *NUSIZ1; #ifdef MS_ONE_COLOR_SPRITES *COLUP1 = ms_color[X]; #endif ms_y1 = ms_sprite_y[X]; X = ms_sprite_x[X]; // 6 *HMP0 = 0; // 3 strobe(WSYNC); // 3 *HMP1 = ms_sprite_hm_offscreen[X];// 7 [13/76] X = ms_sprite_wait_offscreen[X]; // 6 csleep(4); // 4 [17/76] // Critical loop. Must not cross page boundary. if (X) do { X--; } while (X); // 3 for X = 0. 1 + X * 5 cycles. strobe(RESP1); // 3. Minimum = 23 cycles strobe(WSYNC); // 3 strobe(HMOVE); } *GRP1 = 0; *GRP0 = 0; } void multisprite_kernel_prep() { _ms_select_sprites(); _ms_kernel_prep(); } MS_KERNEL_BANK _ms_check_collisions() { ms_y0 = MS_UNALLOCATED; ms_y1 = MS_UNALLOCATED; if (*CXPPMM & 0x80) { ms_sprite_nusiz[X = ms_id_p[0]] |= MS_COLLISION; ms_sprite_nusiz[X = ms_id_p[1]] |= MS_COLLISION; } else { if (*CXP0FB & 0x80) ms_sprite_nusiz[X = ms_id_p[0]] |= MS_PF_COLLISION; // 8/20 if (*CXP1FB & 0x80) ms_sprite_nusiz[X = ms_id_p[1]] |= MS_PF_COLLISION; } //strobe(WSYNC); // 3 strobe(CXCLR); } MS_KERNEL_BANK void multisprite_kernel() { char ms_tmp0, ms_tmp1; Y = MS_OFFSET; strobe(WSYNC); // 3 // Prepare for drawing ms_v = ms_scenery[Y++]; // 11 X = ms_scenery[Y++]; // 10 VSYNC[X] = ms_v; // 7 ms_v = -1; X = ms_id_p[0]; if (X != -1) { ms_v = 0; X = ms_sprite_model[X]; ms_grp0ptr = ms_grptr[X] - ms_y0; // 21 #ifndef MS_ONE_COLOR_SPRITES ms_colup0ptr = ms_coluptr[X] - ms_y0; // 21 #endif ms_h0 = ms_height[X]; ms_tmp0 = ms_y0 + ms_h0; X = ms_id_p[1]; if (X != -1) { X = ms_sprite_model[X]; ms_grp1ptr = ms_grptr[X] - ms_y1; // 21 #ifndef MS_ONE_COLOR_SPRITES ms_colup1ptr = ms_coluptr[X] - ms_y1; // 21 #endif ms_h1 = ms_height[X]; ms_tmp1 = ms_y1 + ms_h1; if (ms_y0 < MS_OFFSET && ms_y1 < MS_OFFSET) { ms_v = 1; if (ms_tmp1 >= ms_tmp0) ms_v = 2; } } } *HMP0 = 0x80; *HMP1 = 0x80; strobe(WSYNC); // 3 *VBLANK = 0; if (ms_v == 0) { goto display_sprite0; } else if (ms_v != -1) { if (ms_v == 1) { _ms_p0_p1_kernel(ms_tmp1); _ms_p0_kernel(ms_tmp0); } else { _ms_p0_p1_kernel(ms_tmp0); _ms_p1_kernel(ms_tmp1); } ms_y0 = MS_UNALLOCATED; ms_y1 = MS_UNALLOCATED; goto repo0_try_again; } repo_kernel: if (ms_y0 == MS_UNALLOCATED) { // 8. There is no sprite 0. Allocate 1 ? repo0_kernel: if (ms_y1 == MS_UNALLOCATED || Y < ms_y1 - 6) { // 22 repo0_try_again: load(ms_scenery[Y++]); // 8 strobe(WSYNC); // 3 store(ms_v); // 3 X = _ms_allocate_sprite(); // 47 [58/76] if (X != -1) { // 5/7 // Check if y position is compatible ms_id_p[0] = X; // 3 X = ms_scenery[Y]; // 8 strobe(WSYNC); // 3 VSYNC[X] = ms_v; // 7 Y++; // 2 ms_v = ms_scenery[Y++]; // 9 X = ms_id_p[0]; // 3 [29/76] if (Y < ms_sprite_y[X] - 6) { // 11/13 [40/76] ms_h0 = _ms_kernel_repo0();// 6 [46/76] } else { // This one will be skipped. Let's set it as prioritary for next time strobe(WSYNC); // 3 ms_id_p[0] = -1; _ms_mark_as_removed(); if (Y >= MS_OFFSET + MS_PLAYFIELD_HEIGHT - 10) goto display_sprites; goto repo0_kernel; } *HMP0 = 0x80; // 5 //strobe(HMCLR); // 3 } } else { strobe(WSYNC); // 3 ms_v = ms_scenery[Y++]; // 11 } } else { strobe(WSYNC); // 3 ms_v = ms_scenery[Y++]; // 11 } X = ms_scenery[Y++]; // 10 strobe(WSYNC); // 3 VSYNC[X] = ms_v; // 7 // Repo multisprite_kernel 1 if (ms_y1 == MS_UNALLOCATED) { // 7. There is no sprite 1. Allocate 1 ? repo1_kernel: if (ms_y0 == MS_UNALLOCATED || Y < ms_y0 - 6) { // 22 repo1_try_again: load(ms_scenery[Y++]); // 8 strobe(WSYNC); // 3 store(ms_v); // 3 X = _ms_allocate_sprite(); // 47 [58/76] if (X != -1) { // 4/5 // Check if y position is compatible ms_id_p[1] = X; // 3 X = ms_scenery[Y]; // 8 strobe(WSYNC); // 3 VSYNC[X] = ms_v; // 7 Y++; // 2 ms_v = ms_scenery[Y++]; // 9 X = ms_id_p[1]; // 3 [29/76] if (Y < ms_sprite_y[X] - 6) { // 11/13 [40/76] ms_h1 = _ms_kernel_repo1(); // 6 [46/76] } else { // This one will be skipped. Let's set it as prioritary for next time strobe(WSYNC); // 3 ms_id_p[1] = -1; _ms_mark_as_removed(); if (Y >= MS_OFFSET + MS_PLAYFIELD_HEIGHT - 10) goto display_sprites; goto repo1_kernel; } *HMP1 = 0x80; // 5 //strobe(HMCLR); // 3 } } else { strobe(WSYNC); // 3 ms_v = ms_scenery[Y++]; } } else { strobe(WSYNC); // 3 ms_v = ms_scenery[Y++]; } X = ms_scenery[Y++]; // 10 ms_tmp0 = ms_y0 + ms_h0; ms_tmp1 = ms_y1 + ms_h1; load(ms_v); // 3 strobe(WSYNC); // 3 store(VSYNC[X]); // 4 display_sprites: if (ms_y0 < ms_y1) { // 8/9 display_sprite0: if (Y < ms_y0) _ms_void_kernel(ms_y0); if (ms_tmp0 < ms_y1) { if (ms_tmp0 >= MS_END_OF_SCREEN) { _ms_p0_kernel(MS_OFFSET + MS_PLAYFIELD_HEIGHT); goto check_collisions_and_return; } _ms_p0_kernel(ms_tmp0); if (Y >= MS_OFFSET + MS_PLAYFIELD_HEIGHT - 6) goto finish; strobe(WSYNC); // 3 if (*CXP0FB & 0x80) ms_sprite_nusiz[X = ms_id_p[0]] |= 0x40; strobe(CXCLR); // Clear collisions ms_tmp0 = ms_y1 - 4; ms_y0 = MS_UNALLOCATED; ms_v = ms_scenery[Y]; // 9 Y++; // 2 X = ms_scenery[Y++]; // 10 load(ms_v); // 3 strobe(WSYNC); // 3 store(VSYNC[X]); // 4 if (Y < ms_tmp0) goto repo0_kernel; goto display_sprite1; } else { if (ms_tmp0 < ms_tmp1) { _ms_p0_kernel(ms_y1); if (ms_tmp0 >= MS_END_OF_SCREEN) { _ms_p0_p1_kernel(MS_OFFSET + MS_PLAYFIELD_HEIGHT); goto check_collisions_and_return; } _ms_p0_p1_kernel(ms_tmp0); if (ms_tmp1 >= MS_END_OF_SCREEN) { _ms_p1_kernel(MS_OFFSET + MS_PLAYFIELD_HEIGHT); goto check_collisions_and_return; } _ms_p1_kernel(ms_tmp1); goto repo_try_again; } else { _ms_p0_kernel(ms_y1); if (ms_tmp1 >= MS_END_OF_SCREEN) { _ms_p0_p1_kernel(MS_OFFSET + MS_PLAYFIELD_HEIGHT); goto check_collisions_and_return; } _ms_p0_p1_kernel(ms_tmp1); if (ms_tmp0 >= MS_END_OF_SCREEN) { _ms_p0_kernel(MS_OFFSET + MS_PLAYFIELD_HEIGHT); goto check_collisions_and_return; } _ms_p0_kernel(ms_tmp0); goto repo_try_again; } } } else if (ms_y1 < ms_y0) { // 8 / 9 display_sprite1: if (Y < ms_y1) _ms_void_kernel(ms_y1); if (ms_tmp1 >= ms_y0) { if (ms_tmp0 < ms_tmp1) { _ms_p1_kernel(ms_y0); if (ms_tmp0 >= MS_END_OF_SCREEN) { _ms_p0_p1_kernel(MS_OFFSET + MS_PLAYFIELD_HEIGHT); goto check_collisions_and_return; } _ms_p0_p1_kernel(ms_tmp0); if (ms_tmp1 >= MS_END_OF_SCREEN) { _ms_p1_kernel(MS_OFFSET + MS_PLAYFIELD_HEIGHT); goto check_collisions_and_return; } _ms_p1_kernel(ms_tmp1); goto repo_try_again; } else { _ms_p1_kernel(ms_y0); if (ms_tmp1 >= MS_END_OF_SCREEN) { _ms_p0_p1_kernel(MS_OFFSET + MS_PLAYFIELD_HEIGHT); goto check_collisions_and_return; } _ms_p0_p1_kernel(ms_tmp1); if (ms_tmp0 >= MS_END_OF_SCREEN) { _ms_p0_kernel(MS_OFFSET + MS_PLAYFIELD_HEIGHT); goto check_collisions_and_return; } _ms_p0_kernel(ms_tmp0); goto repo_try_again; } } else { if (ms_tmp1 >= MS_END_OF_SCREEN) { _ms_p1_kernel(MS_OFFSET + MS_PLAYFIELD_HEIGHT); goto check_collisions_and_return; } _ms_p1_kernel(ms_tmp1); if (Y >= MS_OFFSET + MS_PLAYFIELD_HEIGHT - 6) goto finish; strobe(WSYNC); // 3 if (*CXP1FB & 0x80) ms_sprite_nusiz[X = ms_id_p[1]] |= 0x40; strobe(CXCLR); // Clear collisions ms_tmp1 = ms_y0 - 4; ms_y1 = MS_UNALLOCATED; ms_v = ms_scenery[Y]; // 9 Y++; // 2 X = ms_scenery[Y++]; // 10 load(ms_v); // 3 strobe(WSYNC); // 3 store(VSYNC[X]); // 4 if (Y < ms_tmp1) goto repo_kernel; goto display_sprite0; } } else { if (ms_y0 != MS_UNALLOCATED) { _ms_void_kernel(ms_y0); if (ms_tmp0 < ms_tmp1) { // 5/6 [42/76] if (ms_tmp0 >= MS_END_OF_SCREEN) { _ms_p0_p1_kernel(MS_OFFSET + MS_PLAYFIELD_HEIGHT); goto check_collisions_and_return; } _ms_p0_p1_kernel(ms_tmp0); // 12 [54/76] if (Y < ms_tmp1) { if (ms_tmp1 >= MS_END_OF_SCREEN ) { _ms_p1_kernel(MS_OFFSET + MS_PLAYFIELD_HEIGHT); goto check_collisions_and_return; } _ms_p1_kernel(ms_tmp1); } goto repo_try_again; } else { if (ms_tmp1 >= MS_END_OF_SCREEN) { _ms_p0_p1_kernel(MS_OFFSET + MS_PLAYFIELD_HEIGHT); goto check_collisions_and_return_immediately; } _ms_p0_p1_kernel(ms_tmp1); if (Y < ms_tmp0) { if (ms_tmp0 >= MS_END_OF_SCREEN) { _ms_p0_kernel(MS_OFFSET + MS_PLAYFIELD_HEIGHT); goto check_collisions_and_return; } _ms_p0_kernel(ms_tmp0); } goto repo_try_again; } } else { // This is the end of the multisprite kernel.Fill with void. finish: if (Y < MS_OFFSET + MS_PLAYFIELD_HEIGHT) _ms_void_kernel(MS_OFFSET + MS_PLAYFIELD_HEIGHT); goto check_collisions_and_return; } } repo_try_again: _ms_check_collisions(); ms_v = ms_scenery[Y]; // 9 Y++; // 2 X = ms_scenery[Y++]; // 10 load(ms_v); // 3 strobe(WSYNC); // 3 store(VSYNC[X]); // 4 if (Y >= MS_OFFSET + MS_PLAYFIELD_HEIGHT - 6) goto finish; goto repo0_try_again; check_collisions_and_return: *GRP0 = 0; *GRP1 = 0; check_collisions_and_return_immediately: _ms_check_collisions(); } #endif // __MULTISPRITE_H__