diff --git a/doc/notes.txt b/doc/notes.txt index 05b7aab6..c71129d8 100644 --- a/doc/notes.txt +++ b/doc/notes.txt @@ -174,6 +174,7 @@ TODO: - list of stuff for policy - popup - convert binary to hex stmts +- "suggestions" (vblank overrun, variable # scanlines, etc) WEB WORKER FORMAT @@ -319,3 +320,15 @@ Converting from NESASM to DASM - .db to .byte, .dw to .word - use NES_HEADER macros - no .bank + +Cross platform NES/SMS/GG library +- use CHR RAM + - create flipped tiles/sprites + - create alternate palette tiles +- metatiles +- cross platform music/sound +- scrolling + - row/column mask + - no nametable mirroring in SMS + - 256x240 vs 256x192 + diff --git a/presets/sms-sms-libcv/PSGlib.c b/presets/sms-sms-libcv/PSGlib.c new file mode 100644 index 00000000..2ad902a3 --- /dev/null +++ b/presets/sms-sms-libcv/PSGlib.c @@ -0,0 +1,556 @@ +/* ************************************************** + PSGlib - C programming library for the SEGA PSG + ( part of devkitSMS - github.com/sverx/devkitSMS ) + ************************************************** */ + +// http://www.smspower.org/forums/16925-PSGToolAVGMToPSGConvertor +// https://github.com/sverx/PSGlib/blob/master/tools/src/vgm2psg.c +// http://steveproxna.blogspot.com/2017/11/devkitsms-programming-sample.html +// http://battleofthebits.org/lyceum/View/Vortex+Tracker+II/ + +#include "PSGlib.h" + +#define PSGDataPort #0x7f + +#define PSGLatch #0x80 +#define PSGData #0x40 + +#define PSGChannel0 #0b00000000 +#define PSGChannel1 #0b00100000 +#define PSGChannel2 #0b01000000 +#define PSGChannel3 #0b01100000 +#define PSGVolumeData #0b00010000 + +#define PSGWait #0x38 +#define PSGSubString #0x08 +#define PSGLoop #0x01 +#define PSGEnd #0x00 + +/* define PSGPort (SDCC z80 syntax) */ +__sfr __at 0x7F PSGPort; + +// fundamental vars +unsigned char PSGMusicStatus; // are we playing a background music? +void *PSGMusicStart; // the pointer to the beginning of music +void *PSGMusicPointer; // the pointer to the current +void *PSGMusicLoopPoint; // the pointer to the loop begin +unsigned char PSGMusicSkipFrames; // the frames we need to skip +unsigned char PSGLoopFlag; // the tune should loop or not (flag) +unsigned char PSGMusicLastLatch; // the last PSG music latch +unsigned char PSGMusicVolumeAttenuation; // the volume attenuation applied to the tune (0-15) + +// decompression vars +unsigned char PSGMusicSubstringLen; // lenght of the substring we are playing +void *PSGMusicSubstringRetAddr; // return to this address when substring is over + +// volume/frequence buffering +unsigned char PSGChan0Volume; // the volume for channel 0 +unsigned char PSGChan1Volume; // the volume for channel 1 +unsigned char PSGChan2Volume; // the volume for channel 2 +unsigned char PSGChan3Volume; // the volume for channel 3 +unsigned char PSGChan2LowTone; // the low tone bits for channels 2 +unsigned char PSGChan2HighTone; // the high tone bits for channel 2 +unsigned char PSGChan3LowTone; // the tone bits for channels 3 + +// flags for channels 2-3 access +unsigned char PSGChannel2SFX; // !0 means channel 2 is allocated to SFX +unsigned char PSGChannel3SFX; // !0 means channel 3 is allocated to SFX + +// volume/frequence buffering for SFX +unsigned char PSGSFXChan2Volume; // the volume for SFX channel 2 +unsigned char PSGSFXChan3Volume; // the volume for SFX channel 3 + +// fundamental vars for SFX +unsigned char PSGSFXStatus; // are we playing a SFX? +void *PSGSFXStart; // the pointer to the beginning of SFX +void *PSGSFXPointer; // the pointer to the current address +void *PSGSFXLoopPoint; // the pointer to the loop begin +unsigned char PSGSFXSkipFrames; // the frames we need to skip +unsigned char PSGSFXLoopFlag; // the SFX should loop or not (flag) + +// decompression vars for SFX +unsigned char PSGSFXSubstringLen; // lenght of the substring we are playing +void *PSGSFXSubstringRetAddr; // return to this address when substring is over + +void PSGStop (void) { +/* ********************************************************************* + stops the music (leaving the SFX on, if it's playing) +*/ + if (PSGMusicStatus) { + PSGPort=PSGLatch|PSGChannel0|PSGVolumeData|0x0F; // latch channel 0, volume=0xF (silent) + PSGPort=PSGLatch|PSGChannel1|PSGVolumeData|0x0F; // latch channel 1, volume=0xF (silent) + if (!PSGChannel2SFX) + PSGPort=PSGLatch|PSGChannel2|PSGVolumeData|0x0F; // latch channel 2, volume=0xF (silent) + if (!PSGChannel3SFX) + PSGPort=PSGLatch|PSGChannel3|PSGVolumeData|0x0F; // latch channel 3, volume=0xF (silent) + PSGMusicStatus=PSG_STOPPED; + } +} + +void PSGResume (void) { +/* ********************************************************************* + resume the previously playing music +*/ + if (!PSGMusicStatus) { + PSGPort=PSGLatch|PSGChannel0|PSGVolumeData|PSGChan0Volume; // restore channel 0 volume + PSGPort=PSGLatch|PSGChannel1|PSGVolumeData|PSGChan1Volume; // restore channel 1 volume + if (!PSGChannel2SFX) { + PSGPort=PSGLatch|PSGChannel2|(PSGChan2LowTone&0x0F); // restore channel 2 frequency + PSGPort=PSGChan2HighTone&0x3F; + PSGPort=PSGLatch|PSGChannel2|PSGVolumeData|PSGChan2Volume; // restore channel 2 volume + } + if (!PSGChannel3SFX) { + PSGPort=PSGLatch|PSGChannel3|(PSGChan3LowTone&0x0F); // restore channel 3 frequency + PSGPort=PSGLatch|PSGChannel3|PSGVolumeData|PSGChan3Volume; // restore channel 3 volume + } + PSGMusicStatus=PSG_PLAYING; + } +} + +void PSGPlay (void *song) { +/* ********************************************************************* + receives the address of the PSG to start playing (continuously) +*/ + PSGStop(); + PSGLoopFlag=1; + PSGMusicStart=song; // store the begin point of music + PSGMusicPointer=song; // set music pointer to begin of music + PSGMusicLoopPoint=song; // looppointer points to begin too + + PSGMusicSkipFrames=0; // reset the skip frames + PSGMusicSubstringLen=0; // reset the substring len (for compression) + PSGMusicLastLatch=PSGLatch|PSGChannel0|PSGVolumeData|0x0F; // latch channel 0, volume=0xF (silent) + PSGMusicStatus=PSG_PLAYING; +} + +void PSGCancelLoop (void) { +/* ********************************************************************* + sets the currently looping music to no more loops after the current +*/ + PSGLoopFlag=0; +} + +void PSGPlayNoRepeat (void *song) { +/* ********************************************************************* + receives the address of the PSG to start playing (once) +*/ + PSGPlay(song); + PSGLoopFlag=0; +} + +unsigned char PSGGetStatus (void) { +/* ********************************************************************* + returns the current status of music +*/ + return(PSGMusicStatus); +} + +void PSGSilenceChannels (void) { +/* ********************************************************************* + silence all the PSG channels +*/ + PSGPort=PSGLatch|PSGChannel0|PSGVolumeData|0x0F; + PSGPort=PSGLatch|PSGChannel1|PSGVolumeData|0x0F; + PSGPort=PSGLatch|PSGChannel2|PSGVolumeData|0x0F; + PSGPort=PSGLatch|PSGChannel3|PSGVolumeData|0x0F; +} + +void PSGRestoreVolumes (void) { +/* ********************************************************************* + restore the PSG channels volumes (if a tune or an SFX uses them!) +*/ + if (PSGMusicStatus) { + PSGPort=PSGLatch|PSGChannel0|PSGVolumeData|((PSGChan0Volume+PSGMusicVolumeAttenuation>15)?15:PSGChan0Volume+PSGMusicVolumeAttenuation); + PSGPort=PSGLatch|PSGChannel1|PSGVolumeData|((PSGChan1Volume+PSGMusicVolumeAttenuation>15)?15:PSGChan1Volume+PSGMusicVolumeAttenuation); + } + if (PSGChannel2SFX) + PSGPort=PSGLatch|PSGChannel2|PSGVolumeData|PSGSFXChan2Volume; + else if (PSGMusicStatus) + PSGPort=PSGLatch|PSGChannel2|PSGVolumeData|((PSGChan2Volume+PSGMusicVolumeAttenuation>15)?15:PSGChan2Volume+PSGMusicVolumeAttenuation); + if (PSGChannel3SFX) + PSGPort=PSGLatch|PSGChannel3|PSGVolumeData|PSGSFXChan3Volume; + else if (PSGMusicStatus) + PSGPort=PSGLatch|PSGChannel3|PSGVolumeData|((PSGChan3Volume+PSGMusicVolumeAttenuation>15)?15:PSGChan3Volume+PSGMusicVolumeAttenuation); +} + +void PSGSetMusicVolumeAttenuation (unsigned char attenuation) { +/* ********************************************************************* + sets the volume attenuation for the music (0-15) +*/ + PSGMusicVolumeAttenuation=attenuation; + if (PSGMusicStatus) { + PSGPort=PSGLatch|PSGChannel0|PSGVolumeData|((PSGChan0Volume+PSGMusicVolumeAttenuation>15)?15:PSGChan0Volume+PSGMusicVolumeAttenuation); + PSGPort=PSGLatch|PSGChannel1|PSGVolumeData|((PSGChan1Volume+PSGMusicVolumeAttenuation>15)?15:PSGChan1Volume+PSGMusicVolumeAttenuation); + if (!PSGChannel2SFX) + PSGPort=PSGLatch|PSGChannel2|PSGVolumeData|((PSGChan2Volume+PSGMusicVolumeAttenuation>15)?15:PSGChan2Volume+PSGMusicVolumeAttenuation); + if (!PSGChannel3SFX) + PSGPort=PSGLatch|PSGChannel3|PSGVolumeData|((PSGChan3Volume+PSGMusicVolumeAttenuation>15)?15:PSGChan3Volume+PSGMusicVolumeAttenuation); + } +} + +void PSGSFXStop (void) { +/* ********************************************************************* + stops the SFX (leaving the music on, if it's playing) +*/ + if (PSGSFXStatus) { + if (PSGChannel2SFX) { + if (PSGMusicStatus) { + PSGPort=PSGLatch|PSGChannel2|(PSGChan2LowTone&0x0F); + PSGPort=PSGChan2HighTone&0x3F; + PSGPort=PSGLatch|PSGChannel2|PSGVolumeData|(((PSGChan2Volume&0x0F)+PSGMusicVolumeAttenuation>15)?15:(PSGChan2Volume&0x0F)+PSGMusicVolumeAttenuation); + } else { + PSGPort=PSGLatch|PSGChannel2|PSGVolumeData|0x0F; + } + PSGChannel2SFX=PSG_STOPPED; + } + + if (PSGChannel3SFX) { + if (PSGMusicStatus) { + PSGPort=PSGLatch|PSGChannel3|(PSGChan3LowTone&0x0F); + PSGPort=PSGLatch|PSGChannel3|PSGVolumeData|(((PSGChan3Volume&0x0F)+PSGMusicVolumeAttenuation>15)?15:(PSGChan3Volume&0x0F)+PSGMusicVolumeAttenuation); + } else { + PSGPort=PSGLatch|PSGChannel3|PSGVolumeData|0x0F; + } + PSGChannel3SFX=PSG_STOPPED; + } + PSGSFXStatus=PSG_STOPPED; + } +} + +void PSGSFXPlay (void *sfx, unsigned char channels) { +/* ********************************************************************* + receives the address of the SFX to start and the mask that indicates + which channel(s) the SFX will use +*/ + PSGSFXStop(); + PSGSFXLoopFlag=0; + PSGSFXStart=sfx; // store begin of SFX + PSGSFXPointer=sfx; // set the pointer to begin of SFX + PSGSFXLoopPoint=sfx; // looppointer points to begin too + PSGSFXSkipFrames=0; // reset the skip frames + PSGSFXSubstringLen=0; // reset the substring len + PSGChannel2SFX=(channels&SFX_CHANNEL2)?PSG_PLAYING:PSG_STOPPED; + PSGChannel3SFX=(channels&SFX_CHANNEL3)?PSG_PLAYING:PSG_STOPPED; + PSGSFXStatus=PSG_PLAYING; +} + +void PSGSFXCancelLoop (void) { +/* ********************************************************************* + sets the currently looping SFX to no more loops after the current +*/ + PSGSFXLoopFlag=0; +} + +unsigned char PSGSFXGetStatus (void) { +/* ********************************************************************* + returns the current SFX status +*/ + return(PSGSFXStatus); +} + +void PSGSFXPlayLoop (void *sfx, unsigned char channels) { +/* ********************************************************************* + receives the address of the SFX to start continuously and the mask + that indicates which channel(s) the SFX will use +*/ + PSGSFXPlay(sfx, channels); + PSGSFXLoopFlag=1; +} + +void PSGFrame (void) { +/* ********************************************************************* + processes a music frame +*/ +__asm + ld a,(_PSGMusicStatus) ; check if we have got to play a tune + or a + ret z + + ld a,(_PSGMusicSkipFrames) ; check if we have got to skip frames + or a + jp nz,_skipFrame + + ld hl,(_PSGMusicPointer) ; read current address + +_intLoop: + ld b,(hl) ; load PSG byte (in B) + inc hl ; point to next byte + ld a,(_PSGMusicSubstringLen) ; read substring len + or a + jr z,_continue ; check if it is 0 (we are not in a substring) + dec a ; decrease len + ld (_PSGMusicSubstringLen),a ; save len + jr nz,_continue + ld hl,(_PSGMusicSubstringRetAddr) ; substring is over, retrieve return address + +_continue: + ld a,b ; copy PSG byte into A + cp PSGLatch ; is it a latch? + jr c,_noLatch ; if < $80 then it is NOT a latch + ld (_PSGMusicLastLatch),a ; it is a latch - save it in "LastLatch" + + ; we have got the latch PSG byte both in A and in B + ; and we have to check if the value should pass to PSG or not + bit 4,a ; test if it is a volume + jr nz,_latch_Volume ; jump if volume data + bit 6,a ; test if the latch it is for channels 0-1 or for 2-3 + jp z,_send2PSG_A ; send data to PSG if it is for channels 0-1 + + ; we have got the latch (tone, chn 2 or 3) PSG byte both in A and in B + ; and we have to check if the value should be passed to PSG or not + bit 5,a ; test if tone it is for channel 2 or 3 + jr z,_ifchn2 ; jump if channel 2 + ld (_PSGChan3LowTone),a ; save tone LOW data + ld a,(_PSGChannel3SFX) ; channel 3 free? + or a + jp nz,_intLoop + ld a,(_PSGChan3LowTone) + and #3 ; test if channel 3 is set to use the frequency of channel 2 + cp #3 + jr nz,_send2PSG_B ; if channel 3 does not use frequency of channel 2 jump + ld a,(_PSGSFXStatus) ; test if an SFX is playing + or a + jr z,_send2PSG_B ; if no SFX is playing jump + ld (_PSGChannel3SFX),a ; otherwise mark channel 3 as occupied + ld a,PSGLatch|PSGChannel3|PSGVolumeData|#0x0F ; and silence channel 3 + out (PSGDataPort),a + jp _intLoop +_ifchn2: + ld (_PSGChan2LowTone),a ; save tone LOW data + ld a,(_PSGChannel2SFX) ; channel 2 free? + or a + jr z,_send2PSG_B + jp _intLoop + +_latch_Volume: + bit 6,a ; test if the latch it is for channels 0-1 or for 2-3 + jr nz,_latch_Volume_23 ; volume is for channel 2 or 3 + bit 5,a ; test if volume it is for channel 0 or 1 + jr z,_chn0 ; jump for channel 0 + ld (_PSGChan1Volume),a ; save volume data + jp _sendVolume2PSG_A +_chn0: + ld (_PSGChan0Volume),a ; save volume data + jp _sendVolume2PSG_A + +_latch_Volume_23: + bit 5,a ; test if volume it is for channel 2 or 3 + jr z,_chn2 ; jump for channel 2 + ld (_PSGChan3Volume),a ; save volume data + ld a,(_PSGChannel3SFX) ; channel 3 free? + or a + jr z,_sendVolume2PSG_B + jp _intLoop +_chn2: + ld (_PSGChan2Volume),a ; save volume data + ld a,(_PSGChannel2SFX) ; channel 2 free? + or a + jr z,_sendVolume2PSG_B + jp _intLoop + +_skipFrame: + dec a + ld (_PSGMusicSkipFrames),a + ret + +_noLatch: + cp PSGData + jr c,_command ; if < $40 then it is a command + ; it is a data + ld a,(_PSGMusicLastLatch) ; retrieve last latch + jp _output_NoLatch + +_command: + cp PSGWait + jr z,_done ; no additional frames + jr c,_otherCommands ; other commands? + and #0x07 ; take only the last 3 bits for skip frames + ld (_PSGMusicSkipFrames),a ; we got additional frames +_done: + ld (_PSGMusicPointer),hl ; save current address + ret ; frame done + +_otherCommands: + cp PSGSubString + jr nc,_substring + cp PSGEnd + jr z,_musicLoop + cp PSGLoop + jr z,_setLoopPoint + + ; *************************************************************************** + ; we should never get here! + ; if we do, it means the PSG file is probably corrupted, so we just RET + ; *************************************************************************** + + ret + +_send2PSG_B: + ld a,b +_send2PSG_A: + out (PSGDataPort),a ; output the byte + jp _intLoop + +_sendVolume2PSG_B: + ld a,b +_sendVolume2PSG_A: + ld c,a ; save the PSG command byte + and #0x0F ; keep lower nibble + ld b,a ; save value + ld a,(_PSGMusicVolumeAttenuation) ; load volume attenuation + add a,b ; add value + cp #0x0F ; check overflow + jr c,_no_overflow ; if it is <=15 then ok + ld a,#0x0F ; else, reset to 15 +_no_overflow: + ld b,a ; save new attenuated volume value + ld a,c ; retrieve PSG command + and #0xF0 ; keep upper nibble + or b ; set attenuated volume + out (PSGDataPort),a ; output the byte + jp _intLoop + +_output_NoLatch: + ; we got the last latch in A and the PSG data in B + ; and we have to check if the value should pass to PSG or not + ; note that non-latch commands can be only contain frequencies (no volumes) + ; for channels 0,1,2 only (no noise) + bit 6,a ; test if the latch it is for channels 0-1 or for chn 2 + jr nz,_high_part_Tone ; it is tone data for channel 2 + jp _send2PSG_B ; otherwise, it is for chn 0 or 1 so we have done! + +_setLoopPoint: + ld (_PSGMusicLoopPoint),hl + jp _intLoop + +_musicLoop: + ld a,(_PSGLoopFlag) ; looping requested? + or a + jp z,_PSGStop ; No:stop it! (tail call optimization) + ld hl,(_PSGMusicLoopPoint) + jp _intLoop + +_substring: + sub PSGSubString-4 ; len is value - $08 + 4 + ld (_PSGMusicSubstringLen),a ; save len + ld c,(hl) ; load substring address (offset) + inc hl + ld b,(hl) + inc hl + ld (_PSGMusicSubstringRetAddr),hl ; save return address + ld hl,(_PSGMusicStart) + add hl,bc ; make substring current + jp _intLoop + +_high_part_Tone: + ; we got the last latch in A and the PSG data in B + ; and we have to check if the value should pass to PSG or not + ; PSG data can only be for channel 2, here + ld a,b ; move PSG data in A + ld (_PSGChan2HighTone),a ; save channel 2 tone HIGH data + ld a,(_PSGChannel2SFX) ; channel 2 free? + or a + jr z,_send2PSG_B + jp _intLoop +__endasm; +} + +void PSGSFXFrame (void) { +/* ******************************************************************** + processes a SFX frame +*/ +__asm + ld a,(_PSGSFXStatus) ; check if we have got to play SFX + or a + ret z + + ld a,(_PSGSFXSkipFrames) ; check if we have got to skip frames + or a + jp nz,_skipSFXFrame + + ld hl,(_PSGSFXPointer) ; read current SFX address + +_intSFXLoop: + ld b,(hl) ; load a byte in B, temporary + inc hl ; point to next byte + ld a,(_PSGSFXSubstringLen) ; read substring len + or a ; check if it is 0 (we are not in a substring) + jr z,_SFXcontinue + dec a ; decrease len + ld (_PSGSFXSubstringLen),a ; save len + jr nz,_SFXcontinue + ld hl,(_PSGSFXSubstringRetAddr) ; substring over, retrieve return address + +_SFXcontinue: + ld a,b ; restore byte + cp PSGData + jp c,_SFXcommand ; if less than $40 then it is a command + bit 4,a ; check if it is a volume byte + jr z,_SFXoutbyte ; if not, output it + bit 5,a ; check if it is volume for channel 2 or channel 3 + jr nz,_SFXvolumechn3 + ld (_PSGSFXChan2Volume),a + jr _SFXoutbyte + +_SFXvolumechn3: + ld (_PSGSFXChan3Volume),a + +_SFXoutbyte: + out (PSGDataPort),a ; output the byte + jp _intSFXLoop + +_skipSFXFrame: + dec a + ld (_PSGSFXSkipFrames),a + ret + +_SFXcommand: + cp PSGWait + jr z,_SFXdone ; no additional frames + jr c,_SFXotherCommands ; other commands? + and #0x07 ; take only the last 3 bits for skip frames + ld (_PSGSFXSkipFrames),a ; we got additional frames to skip +_SFXdone: + ld (_PSGSFXPointer),hl ; save current address + ret ; frame done + +_SFXotherCommands: + cp PSGSubString + jr nc,_SFXsubstring + cp PSGEnd + jr z,_sfxLoop + cp PSGLoop + jr z,_SFXsetLoopPoint + + ; *************************************************************************** + ; we should never get here! + ; if we do, it means the PSG SFX file is probably corrupted, so we just RET + ; *************************************************************************** + + ret + +_SFXsetLoopPoint: + ld (_PSGSFXLoopPoint),hl + jp _intSFXLoop + +_sfxLoop: + ld a,(_PSGSFXLoopFlag) ; is it a looping SFX? + or a + jp z,_PSGSFXStop ; No:stop it! (tail call optimization) + ld hl,(_PSGSFXLoopPoint) + ld (_PSGSFXPointer),hl + jp _intSFXLoop + +_SFXsubstring: + sub PSGSubString-4 ; len is value - $08 + 4 + ld (_PSGSFXSubstringLen),a ; save len + ld c,(hl) ; load substring address (offset) + inc hl + ld b,(hl) + inc hl + ld (_PSGSFXSubstringRetAddr),hl ; save return address + ld hl,(_PSGSFXStart) + add hl,bc ; make substring current + jp _intSFXLoop +__endasm; +} diff --git a/presets/sms-sms-libcv/PSGlib.h b/presets/sms-sms-libcv/PSGlib.h new file mode 100644 index 00000000..2bf7b705 --- /dev/null +++ b/presets/sms-sms-libcv/PSGlib.h @@ -0,0 +1,31 @@ +/* ************************************************** + PSGlib - C programming library for the SEGA PSG + ( part of devkitSMS - github.com/sverx/devkitSMS ) + ************************************************** */ + +#define PSG_STOPPED 0 +#define PSG_PLAYING 1 + +#define SFX_CHANNEL2 #0x01 +#define SFX_CHANNEL3 #0x02 +#define SFX_CHANNELS2AND3 SFX_CHANNEL2|SFX_CHANNEL3 + +void PSGPlay (void *song); +void PSGCancelLoop (void); +void PSGPlayNoRepeat (void *song); +void PSGStop (void); +void PSGResume (void); +unsigned char PSGGetStatus (void); +void PSGSetMusicVolumeAttenuation (unsigned char attenuation); + +void PSGSFXPlay (void *sfx, unsigned char channels); +void PSGSFXPlayLoop (void *sfx, unsigned char channels); +void PSGSFXCancelLoop (void); +void PSGSFXStop (void); +unsigned char PSGSFXGetStatus (void); + +void PSGSilenceChannels (void); +void PSGRestoreVolumes (void); + +void PSGFrame (void); +void PSGSFXFrame (void); diff --git a/presets/sms-sms-libcv/chr_generic.c b/presets/sms-sms-libcv/chr_generic.c index ba7690f3..b1bde3a7 100644 --- a/presets/sms-sms-libcv/chr_generic.c +++ b/presets/sms-sms-libcv/chr_generic.c @@ -1,3 +1,4 @@ +/*{w:8,h:8,bpp:1,count:256,brev:1,np:4,pofs:1,sl:4}*/ const unsigned char CHR_GENERIC[8192] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, diff --git a/presets/sms-sms-libcv/climber.c b/presets/sms-sms-libcv/climber.c index 04b20cbf..cfa83cef 100644 --- a/presets/sms-sms-libcv/climber.c +++ b/presets/sms-sms-libcv/climber.c @@ -10,16 +10,67 @@ //#link "chr_generic.c" extern const unsigned char CHR_GENERIC[8192]; -#define XOFS -16 // sprite horiz. offset -#define LADDER_DELTA_X 8 - -#define BGCOL CV_COLOR_BLUE - +// constants for various tiles #define CH_BORDER 0x8f #define CH_FLOOR 0xf4 #define CH_LADDER 0xd4 -#define CH_ITEM 0xc8 +#define CH_ITEM 0xc4 #define CH_PLAYER 0xd8 +#define CH_BLANK 0x20 +#define CH_BASEMENT 0x30 + +///// DEFINES + +#define ROW_OFFSET 0 +#define UPDATE_ROW_DELTA -1 +#define MESSAGE_ROW_DELTA 10 + +#define MAX_FLOORS 20 // total # of floors in a stage +#define GAPSIZE 4 // gap size in tiles +#define BOTTOM_FLOOR_Y 2 // offset for bottommost floor + +#define MAX_ACTORS 5 // max # of moving actors +#define SCREEN_Y_BOTTOM 176 // bottom of screen in pixels +#define ACTOR_MIN_X 16 // leftmost position of actor +#define ACTOR_MAX_X 228 // rightmost position of actor +#define ACTOR_SCROLL_UP_Y 100 // min Y position to scroll up +#define ACTOR_SCROLL_DOWN_Y 120 // max Y position to scroll down +#define JUMP_VELOCITY 18 // Y velocity when jumping + +// indices of sound effects (0..3) +typedef enum { SND_START, SND_HIT, SND_COIN, SND_JUMP } SFXIndex; + +///// GLOBALS + +// vertical scroll amount in pixels +static int scroll_pixel_yy = 0; + +// vertical scroll amount in tiles (scroll_pixel_yy / 8) +static byte scroll_tile_y = 0; + +// last screen Y position of player sprite +static byte player_screen_y = 0; + +// score (BCD) +static byte score = 0; + +// screen flash animation (virtual bright) +static byte vbright = 4; + +// random byte between (a ... b-1) +// use rand() because rand8() has a cycle of 255 +byte rndint(byte a, byte b) { + return (rand() % (b-a)) + a; +} +//TODO +void sfx_play(byte index, byte channel) { + index; channel; +} +void music_play(byte index) { + index; +} +void music_stop() { +} /*{pal:222,n:16}*/ const char PALETTE0[16] = { @@ -37,92 +88,196 @@ const char PALETTE1[16] = { 0x30, 0x38, 0x3E, 0x3F, }; -#define NUM_SPRITE_PATTERNS 5 -#define NUM_SPRITE_STATES 4 +/// METASPRITES -/// +// define a 2x2 metasprite +#define DEF_METASPRITE_2x2(name,code,pal)\ +const unsigned char name[]={\ + 0, 0, (code)+0, pal, \ + 8, 0, (code)+2, pal, \ + 128} -typedef struct Level { - byte ypos; - byte height; // TODO: why does bitmask not work? - byte gap:4; - byte ladder1:4; - byte ladder2:4; - byte objtype:4; - byte objpos:4; -} Level; +// define a 2x2 metasprite, flipped horizontally +#define DEF_METASPRITE_2x2_FLIP(name,code,pal)\ +const unsigned char name[]={\ + 8, 0, (code)+0, (pal)|0, \ + 0, 0, (code)+2, (pal)|0, \ + 128} -#define MAX_LEVELS 32 -#define GAPSIZE 3 +// right-facing +DEF_METASPRITE_2x2(playerRStand, 0xd8, 0); +DEF_METASPRITE_2x2(playerRRun1, 0xdc, 0); +DEF_METASPRITE_2x2(playerRRun2, 0xe0, 0); +DEF_METASPRITE_2x2(playerRRun3, 0xe4, 0); +DEF_METASPRITE_2x2(playerRJump, 0xe8, 0); +DEF_METASPRITE_2x2(playerRClimb, 0xec, 0); +DEF_METASPRITE_2x2(playerRSad, 0xf0, 0); -Level levels[MAX_LEVELS]; +// left-facing +DEF_METASPRITE_2x2_FLIP(playerLStand, 0xd8-0x50, 0); +DEF_METASPRITE_2x2_FLIP(playerLRun1, 0xdc-0x50, 0); +DEF_METASPRITE_2x2_FLIP(playerLRun2, 0xe0-0x50, 0); +DEF_METASPRITE_2x2_FLIP(playerLRun3, 0xe4-0x50, 0); +DEF_METASPRITE_2x2_FLIP(playerLJump, 0xe8-0x50, 0); +DEF_METASPRITE_2x2_FLIP(playerLClimb, 0xec-0x50, 0); +DEF_METASPRITE_2x2_FLIP(playerLSad, 0xf0-0x50, 0); +// rescuee at top of building +const unsigned char personToSave[]={ + 0, -8, (0xba)+0, 3, + 0, 1, (0xba)+2, 0, + 8, -8, (0xba)+1, 3, + 8, 1, (0xba)+3, 0, + 128}; + +// player run sequence +const unsigned char* const playerRunSeq[16] = { + playerLRun1, playerLRun2, playerLRun3, + playerLRun1, playerLRun2, playerLRun3, + playerLRun1, playerLRun2, + playerRRun1, playerRRun2, playerRRun3, + playerRRun1, playerRRun2, playerRRun3, + playerRRun1, playerRRun2, +}; + +byte oam_off = 0; + +void oam_meta_spr_pal(byte x, byte y, byte pal, const sbyte* meta) { + struct cvu_sprite4 spr; + while (*meta != -128) { + spr.x = x + *meta++; + spr.y = y + *meta++; + spr.name = *meta++; + meta++; pal; + cvu_set_sprite4(SPRITES, oam_off, &spr); + oam_off++; + } +} + +void oam_hide_rest() { + if (oam_off < 64) { + cvu_vmemset(SPRITES+oam_off, 240, 64-oam_off); + } +} + +cv_vmemp getntaddr(byte x, byte y) { + return IMAGE + y*64 + x*2; +} + +///// GAME LOGIC + +// struct definition for a single floor +typedef struct Floor { + byte ypos; // # of tiles from ground + int height:4; // # of tiles to next floor + int gap:4; // X position of gap + int ladder1:4; // X position of first ladder + int ladder2:4; // X position of second ladder + int objtype:4; // item type (FloorItem) + int objpos:4; // X position of object +} Floor; + +// various items the player can pick up +typedef enum FloorItem { ITEM_NONE, ITEM_MINE, ITEM_HEART, ITEM_POWER }; + +// array of floors +Floor floors[MAX_FLOORS]; + +// is this x (pixel) position within the gap ? bool is_in_gap(byte x, byte gap) { if (gap) { - byte x1 = gap*16 + 26 - XOFS; + byte x1 = gap*16 + 4; return (x > x1 && x < x1+GAPSIZE*8-4); } else { return false; } } +// is this ladder at (tile) position x within the gap? bool ladder_in_gap(byte x, byte gap) { return gap && x >= gap && x < gap+GAPSIZE*2; } -void make_levels() { +// create floors at start of game +void make_floors() { byte i; - byte y=0; - Level* prevlev = &levels[0]; - for (i=0; iheight = rndint(4,7); - lev->ladder1 = rndint(1,14); - lev->ladder2 = rndint(1,14); + byte y = BOTTOM_FLOOR_Y; + Floor* prevlev = &floors[0]; + for (i=0; iheight = rndint(2,5)*2; do { - lev->gap = i>0 ? rndint(0,13) : 0; + // only have gaps in higher floors + lev->gap = i>=5 ? rndint(0,13) : 0; } while (ladder_in_gap(prevlev->ladder1, lev->gap) || ladder_in_gap(prevlev->ladder2, lev->gap)); - lev->objtype = rndint(1,2); - lev->objpos = rndint(1,14); + do { + lev->ladder1 = rndint(1,14); + lev->ladder2 = rndint(1,14); + } while (ladder_in_gap(lev->ladder1, lev->gap) || + ladder_in_gap(lev->ladder2, lev->gap)); + if (i > 0) { + lev->objtype = rndint(1,4); + do { + lev->objpos = rndint(1,14); + } while (ladder_in_gap(lev->objpos, lev->gap)); + } lev->ypos = y; y += lev->height; prevlev = lev; } - // top level is special - levels[MAX_LEVELS-1].height = 15; - levels[MAX_LEVELS-1].gap = 0; - levels[MAX_LEVELS-1].ladder1 = 0; - levels[MAX_LEVELS-1].ladder2 = 0; - levels[MAX_LEVELS-1].objtype = 0; + // top floor is special + floors[MAX_FLOORS-1].height = 15; + floors[MAX_FLOORS-1].gap = 0; + floors[MAX_FLOORS-1].ladder1 = 0; + floors[MAX_FLOORS-1].ladder2 = 0; + floors[MAX_FLOORS-1].objtype = 0; } -static byte scroll_y = 0; +// creete actors on floor_index, if slot is empty +void create_actors_on_floor(byte floor_index); -void create_actors_on_level(byte i); +// row image buffer +char buf[COLS*2]; -void draw_level_line(byte screen_y) { - char buf[COLS*2]; - byte i; - byte y = screen_y + scroll_y; - for (i=0; iypos; - // is this level visible on-screen? - if (dy < lev->height) { - if (dy == 0) { - // draw floor - memset(buf, CH_FLOOR, sizeof(buf)); - // draw the gap +// draw a nametable line into the frame buffer at +// 0 == bottom of stage +void draw_floor_line(byte screen_y) { + byte floor, i; + byte rowy, dy; + cv_vmemp addr; + // iterate through all floors + for (floor=0; floorypos; + // if below BOTTOM_Y_FLOOR + if (dy >= 255 - BOTTOM_FLOOR_Y) { + memset(buf, CH_BASEMENT, sizeof(buf)); + break; + } + // is this floor visible on-screen? + else if (dy < lev->height) { + if (dy <= 1) { + // iterate through all 32 columns + for (i=0; igap) - memset(buf+lev->gap*2, 0, GAPSIZE*2); + memset(buf+lev->gap*4, 0, GAPSIZE*2); } else { - // draw empty space + // clear buffer memset(buf, 0, sizeof(buf)); // draw walls - if (i < MAX_LEVELS-1) { - buf[0] = CH_FLOOR; - buf[COLS*2-1] = CH_FLOOR; + if (floor < MAX_FLOORS-1) { + buf[0] = CH_FLOOR+1; // left side + buf[COLS*2-4] = CH_FLOOR; // right side } // draw ladders if (lev->ladder1) { @@ -137,285 +292,389 @@ void draw_level_line(byte screen_y) { // draw object, if it exists if (lev->objtype) { byte ch = lev->objtype*4 + CH_ITEM; - if (dy == 1) { - buf[lev->objpos*2] = ch+1; - buf[lev->objpos*2+2] = ch+3; + if (dy == 2) { + buf[lev->objpos*4] = ch+1; // bottom-left + buf[lev->objpos*4+2] = ch+3; // bottom-right } - else if (dy == 2) { - buf[lev->objpos*2] = ch; - buf[lev->objpos*2+2] = ch+2; + else if (dy == 3) { + buf[lev->objpos*4] = ch+0; // top-left + buf[lev->objpos*4+2] = ch+2; // top-right } } - // copy line to screen buffer - cvu_memtovmemcpy(IMAGE + COLS*2*(ROWS-1) - COLS*2*screen_y, buf, sizeof(buf)); - // create actors on this level, if needed - // (only when drawing top and bottom of screen) - if (screen_y == 0 || screen_y == ROWS-1) - create_actors_on_level(i); break; } } -} - -void draw_screen() { - byte y; - for (y=0; y= 2)) { + create_actors_on_floor(floor); } } -word get_floor_yy(byte level) { - return levels[level].ypos * 8 + 8; +// draw entire stage at current scroll position +// filling up entire name table +void draw_entire_stage() { + byte y; + for (y=0; y scroll_pixel_yy) + draw_floor_line(scroll_tile_y + ROWS + UPDATE_ROW_DELTA); + else if (yy > -UPDATE_ROW_DELTA) + draw_floor_line(scroll_tile_y + UPDATE_ROW_DELTA); + } + // set scroll variables + scroll_pixel_yy = yy; + scroll_tile_y = yy >> 3; // divide by 8 + // set scroll registers (TODO: const) + cv_set_vscroll(223 - ((yy + 192) % 224)); +} + +// redraw a floor when object picked up +void refresh_floor(byte floor) { + byte y = floors[floor].ypos; + draw_floor_line(y+2); + draw_floor_line(y+3); +} + +///// ACTORS typedef enum ActorState { - INACTIVE, WALKING, CLIMBING, JUMPING, FALLING + INACTIVE, STANDING, WALKING, CLIMBING, JUMPING, FALLING, PACING +}; + +typedef enum ActorType { + ACTOR_PLAYER, ACTOR_ENEMY, ACTOR_RESCUE }; typedef struct Actor { - word yy; - byte x; - byte name; - byte color1:4; - byte color2:4; - byte level; - byte state:4; - byte dir:1; - byte onscreen:1; - union { - struct { - sbyte yvel; - sbyte xvel; - } jumping; - } u; + word yy; // Y position in pixels (16 bit) + byte x; // X position in pixels (8 bit) + byte floor; // floor index + byte state; // ActorState + int name:2; // ActorType (2 bits) + int pal:2; // palette color (2 bits) + int dir:1; // direction (0=right, 1=left) + int onscreen:1; // is actor onscreen? + sbyte yvel; // Y velocity (when jumping) + sbyte xvel; // X velocity (when jumping) } Actor; -Actor actors[MAX_ACTORS]; +Actor actors[MAX_ACTORS]; // all actors -void create_actors_on_level(byte level_index) { - byte actor_index = (level_index % (MAX_ACTORS-1)) + 1; +// creete actors on floor_index, if slot is empty +void create_actors_on_floor(byte floor_index) { + byte actor_index = (floor_index % (MAX_ACTORS-1)) + 1; struct Actor* a = &actors[actor_index]; if (!a->onscreen) { - Level *level = &levels[level_index]; - a->state = WALKING; - a->color1 = level->ladder1; - a->color2 = level->ladder2; - a->name = CH_PLAYER; - a->x = level->ladder1 ^ (level->ladder2<<3) ^ (level->gap<<6); - a->yy = get_floor_yy(level_index); - a->level = level_index; + Floor *floor = &floors[floor_index]; + a->state = STANDING; + a->name = ACTOR_ENEMY; + a->x = rand(); + a->yy = get_floor_yy(floor_index); + a->floor = floor_index; + a->onscreen = 1; + // rescue person on top of the building + if (floor_index == MAX_FLOORS-1) { + a->name = ACTOR_RESCUE; + a->state = PACING; + a->x = 0; + a->pal = 1; + } } } void draw_actor(byte i) { struct Actor* a = &actors[i]; - struct cvu_sprite4 sprite; - int screen_y = 168 - a->yy + scroll_y*8; + bool dir; + const unsigned char* meta; + byte x,y; // sprite variables + // get screen Y position of actor + int screen_y = SCREEN_Y_BOTTOM - a->yy + scroll_pixel_yy; + // is it offscreen? if (screen_y > 192+8 || screen_y < -18) { a->onscreen = 0; return; // offscreen vertically } - sprite.name = a->name; + dir = a->dir; switch (a->state) { + default: case INACTIVE: a->onscreen = 0; return; // inactive, offscreen + case STANDING: + meta = dir ? playerLStand : playerRStand; + break; case WALKING: - sprite.name += (a->x & 4) ? 12 : 16; + meta = playerRunSeq[((a->x >> 1) & 7) + (dir?0:8)]; break; case JUMPING: - sprite.name += 4; + meta = dir ? playerLJump : playerRJump; break; case FALLING: - sprite.name += 24; + meta = dir ? playerLSad : playerRSad; break; case CLIMBING: - // TODO: animation - sprite.name += 20; + meta = (a->yy & 4) ? playerLClimb : playerRClimb; + break; + case PACING: + meta = personToSave; break; } - sprite.x = a->x; - sprite.y = screen_y; - //sprite.tag = a->color1 | 0x80; - sprite.x += XOFS; - cvu_set_sprite4(SPRITES, i*2, &sprite); - //sprite.tag ^= a->color1 ^ a->color2; - sprite.x += 8; - sprite.name += 2; - cvu_set_sprite4(SPRITES, i*2+1, &sprite); - a->onscreen = 1; + // set sprite values, draw sprite + x = a->x; + y = screen_y; + oam_meta_spr_pal(x, y, a->pal, meta); + // is this actor 0? (player sprite) + if (i == 0) { + player_screen_y = y; // save last screen Y position + } + a->onscreen = 1; // if we drew the actor, consider it onscreen + return; } -void refresh_actors() { +// draw the scoreboard, right now just two digits +void draw_scoreboard() { + /* + oam_off = oam_spr(24+0, 24, '0'+(score >> 4), 2, oam_off); + oam_off = oam_spr(24+8, 24, '0'+(score & 0xf), 2, oam_off); + */ +} + +// draw all sprites +void refresh_sprites() { byte i; + // reset sprite index to 0 + oam_off = 0; + // draw all actors for (i=0; i= MAX_LEVELS) return 0; - x = is_ladder_close(player_x, level->ladder1); + if (floor_index >= MAX_FLOORS) return 0; + x = is_ladder_close(player_x, floor->ladder1); if (x) return x; - x = is_ladder_close(player_x, level->ladder2); + x = is_ladder_close(player_x, floor->ladder2); if (x) return x; return 0; } -byte mount_ladder(Actor* player, signed char level_adjust) { - byte x = get_closest_ladder(player->x, player->level + level_adjust); +// put the player on the ladder, and move up or down (floor_adjust) +byte mount_ladder(Actor* player, signed char floor_adjust) { + byte x = get_closest_ladder(player->x, player->floor + floor_adjust); if (x) { player->x = x + 8; player->state = CLIMBING; - player->level += level_adjust; + player->floor += floor_adjust; return 1; } else return 0; } +// should we scroll the screen upward? void check_scroll_up() { - byte player_screen_y = cvu_vinb(SPRITES + 0); // sprite Y pos - if (player_screen_y < 192/2-4) { - scroll_y++; - refresh_screen(); - check_scroll_up(); + if (player_screen_y < ACTOR_SCROLL_UP_Y) { + set_scroll_pixel_yy(scroll_pixel_yy + 1); } } +// should we scroll the screen downward? void check_scroll_down() { - byte player_screen_y = cvu_vinb(SPRITES + 0); // sprite Y pos - if (player_screen_y > 192/2+4 && scroll_y > 0) { - scroll_y--; - refresh_screen(); - check_scroll_down(); + if (player_screen_y > ACTOR_SCROLL_DOWN_Y && scroll_pixel_yy > 0) { + set_scroll_pixel_yy(scroll_pixel_yy - 1); } } +// actor falls down a floor void fall_down(struct Actor* actor) { - actor->level--; + actor->floor--; actor->state = FALLING; - actor->u.jumping.xvel = 0; - actor->u.jumping.yvel = 0; + actor->xvel = 0; + actor->yvel = 0; } +// move an actor (player or enemies) +// joystick - game controller mask +// scroll - if true, we should scroll screen (is player) void move_actor(struct Actor* actor, byte joystick, bool scroll) { switch (actor->state) { + case STANDING: case WALKING: // left/right has priority over climbing if (joystick & CV_FIRE_0) { actor->state = JUMPING; - actor->u.jumping.xvel = 0; - actor->u.jumping.yvel = 15; - if (joystick & CV_LEFT) actor->u.jumping.xvel = -1; - if (joystick & CV_RIGHT) actor->u.jumping.xvel = 1; + actor->xvel = 0; + actor->yvel = JUMP_VELOCITY; + if (joystick & CV_LEFT) actor->xvel = -1; + if (joystick & CV_RIGHT) actor->xvel = 1; + // play sound for player + if (scroll) sfx_play(SND_JUMP,0); } else if (joystick & CV_LEFT) { actor->x--; actor->dir = 1; + actor->state = WALKING; } else if (joystick & CV_RIGHT) { actor->x++; actor->dir = 0; + actor->state = WALKING; } else if (joystick & CV_UP) { mount_ladder(actor, 0); // state -> CLIMBING - if (scroll) check_scroll_up(); } else if (joystick & CV_DOWN) { - mount_ladder(actor, -1); // state -> CLIMBING, level -= 1 - if (scroll) check_scroll_down(); + mount_ladder(actor, -1); // state -> CLIMBING, floor -= 1 + } else { + actor->state = STANDING; + } + if (scroll) { + check_scroll_up(); + check_scroll_down(); } break; case CLIMBING: if (joystick & CV_UP) { - if (actor->yy >= get_ceiling_yy(actor->level)) { - actor->level++; - actor->state = WALKING; - if (scroll) check_scroll_up(); + if (actor->yy >= get_ceiling_yy(actor->floor)) { + actor->floor++; + actor->state = STANDING; } else { actor->yy++; } } else if (joystick & CV_DOWN) { - if (actor->yy <= get_floor_yy(actor->level)) { - actor->state = WALKING; - if (scroll) check_scroll_down(); + if (actor->yy <= get_floor_yy(actor->floor)) { + actor->state = STANDING; } else { actor->yy--; } } + if (scroll) { + check_scroll_up(); + check_scroll_down(); + } break; - case JUMPING: case FALLING: - actor->x += actor->u.jumping.xvel; - actor->yy += actor->u.jumping.yvel/4; - actor->u.jumping.yvel -= 1; - if (actor->yy <= get_floor_yy(actor->level)) { - actor->yy = get_floor_yy(actor->level); - actor->state = WALKING; - if (scroll) check_scroll_down(); + if (scroll) { + check_scroll_up(); + check_scroll_down(); + } + case JUMPING: + actor->x += actor->xvel; + actor->yy += actor->yvel/4; + actor->yvel -= 1; + if (actor->yy <= get_floor_yy(actor->floor)) { + actor->yy = get_floor_yy(actor->floor); + actor->state = STANDING; } break; } // don't allow player to travel past left/right edges of screen - if (actor->x == 0) actor->x = 255; // we wrapped around right edge - if (actor->x < 24) actor->x = 24; + if (actor->x > ACTOR_MAX_X) actor->x = ACTOR_MAX_X; // we wrapped around right edge + if (actor->x < ACTOR_MIN_X) actor->x = ACTOR_MIN_X; // if player lands in a gap, they fall (switch to JUMPING state) - if (actor->state == WALKING && - is_in_gap(actor->x, levels[actor->level].gap)) { + if (actor->state <= WALKING && + is_in_gap(actor->x, floors[actor->floor].gap)) { fall_down(actor); } } +// should we pickup an object? only player does this void pickup_object(Actor* actor) { - Level* level = &levels[actor->level]; - byte objtype = level->objtype; - if (objtype && actor->state == WALKING) { - byte objx = level->objpos * 16 + 24 - XOFS; + Floor* floor = &floors[actor->floor]; + byte objtype = floor->objtype; + // only pick up if there's an object, and if we're walking or standing + if (objtype && actor->state <= WALKING) { + byte objx = floor->objpos * 16; + // is the actor close to the object? if (actor->x >= objx && actor->x < objx+16) { - level->objtype = 0; - refresh_screen(); + // clear the item from the floor and redraw + floor->objtype = 0; + refresh_floor(actor->floor); + // did we hit a mine? + if (objtype == ITEM_MINE) { + // we hit a mine, fall down + fall_down(actor); + sfx_play(SND_HIT,0); + vbright = 8; // flash + } else { + // we picked up an object, add to score + score = bcd_add(score, 1); + sfx_play(SND_COIN,0); + } } } } +// read joystick 0 and move the player void move_player() { - struct cv_controller_state ctrl; - cv_get_controller_state(&ctrl, 0); - move_actor(&actors[0], ctrl.joystick, true); + struct cv_controller_state state; + cv_get_controller_state(&state, 0); + move_actor(&actors[0], state.joystick, true); pickup_object(&actors[0]); } -inline byte iabs(int x) { +// returns absolute value of x +byte iabs(int x) { return x >= 0 ? x : -x; } +// check to see if actor collides with any non-player actor bool check_collision(Actor* a) { byte i; + byte afloor = a->floor; + // can't fall through basement + if (afloor == 0) return false; + // can't fall if already falling + if (a->state == FALLING) return false; + // iterate through entire list of actors for (i=1; ilevel == b->level && + // actors must be on same floor and within 8 pixels + if (b->onscreen && + afloor == b->floor && iabs(a->yy - b->yy) < 8 && iabs(a->x - b->x) < 8) { return true; @@ -426,96 +685,135 @@ bool check_collision(Actor* a) { /// -void preview_stage() { - scroll_y = levels[MAX_LEVELS-1].ypos; - while (scroll_y > 0) { - wait_vsync(); - refresh_screen(); - refresh_actors(); - scroll_y--; +const char* RESCUE_TEXT = + "Is this a rescue?\n" + "I am just hanging out\n" + "on top of this building.\n" + "Get lost!!!"; + +// draw a message on the screen +void type_message(const char* charptr) { + char ch; + byte x,y; + x = 2; + // compute message y position relative to scroll + y = ROWS*8 + MESSAGE_ROW_DELTA - scroll_tile_y; + // repeat until end of string (0) is read + while ((ch = *charptr++)) { + while (y >= ROWS) y -= ROWS; // compute (y % ROWS) + // newline character? go to start of next line + if (ch == '\n') { + x = 2; + y++; + } else { + // put character into nametable + cvu_voutb(ch, getntaddr(x, y)); + x++; + } + // typewriter sound + sfx_play(SND_HIT,0); + // flush buffer and wait a few frames + delay(5); } } -void draw_blimp(struct cvu_sprite4* sprite) { - sprite->name = 48; - wait_vsync(); - cvu_set_sprite4(SPRITES, 28, sprite); - sprite->name += 4; - sprite->x += 16; - cvu_set_sprite4(SPRITES, 29, sprite); - sprite->name += 4; - sprite->x += 16; - cvu_set_sprite4(SPRITES, 30, sprite); - sprite->name += 4; - sprite->x += 16; - cvu_set_sprite4(SPRITES, 31, sprite); - refresh_actors(); +// reward scene when player reaches roof +void rescue_scene() { + // make player face to the left + actors[0].dir = 1; + actors[0].state = STANDING; + refresh_sprites(); + music_stop(); + type_message(RESCUE_TEXT); + // wait 2 seconds + delay(100); } -void blimp_pickup_scene() { - struct cvu_sprite4 sprite; - byte player_screen_y = cvu_vinb(SPRITES + 0); // sprite Y pos - sprite.x = actors[0].x-14; - sprite.y = 240; - //sprite.tag = 0x8f; - while (sprite.y != player_screen_y-16) { - draw_blimp(&sprite); - sprite.x -= 48; - sprite.y++; - } - while (sprite.y != 240) { - draw_blimp(&sprite); - sprite.x -= 48; - sprite.y--; - actors[0].yy++; +const byte PALBR_OR[9] = { + 0x00, 0x00, 0x00, 0x00, 0x00, + 0b010101, + 0b010101, + 0b101010, + 0b111111, +}; +const byte PALBR_AND[9] = { + 0b000000, + 0b010101, + 0b010101, + 0b101010, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f +}; + +void pal_bright(byte level) { + byte pal[16]; + byte i; + byte or = PALBR_OR[level]; + byte and = PALBR_AND[level]; + for (i=0; i<16; i++) { + pal[i] = PALETTE0[i] & and | or; } + cvu_memtocmemcpy(0xc000, pal, 16); } +// game loop void play_scene() { byte i; - + // initialize actors array memset(actors, 0, sizeof(actors)); - actors[0].state = WALKING; - actors[0].color1 = 0xf; - actors[0].color2 = 0xb; - actors[0].name = CH_PLAYER; + actors[0].state = STANDING; + actors[0].name = ACTOR_PLAYER; + actors[0].pal = 3; actors[0].x = 64; - actors[0].yy = 8; - actors[0].level = 0; - - create_actors_on_level(2); - refresh_screen(); - - while (actors[0].level != MAX_LEVELS-1) { + actors[0].floor = 0; + actors[0].yy = get_floor_yy(0); + // put actor at bottom + set_scroll_pixel_yy(0); + // draw initial view of level into nametable + draw_entire_stage(); + // repeat until player reaches the roof + while (actors[0].floor != MAX_FLOORS-1) { + // flush VRAM buffer (waits next frame) wait_vsync(); - refresh_actors(); + refresh_sprites(); move_player(); // move all the actors for (i=1; i 0 && check_collision(&actors[0])) { - fall_down(&actors[0]); - } + if (check_collision(&actors[0])) { + fall_down(&actors[0]); + sfx_play(SND_HIT,0); + vbright = 8; // flash + } + // flash effect + if (vbright > 4) { + pal_bright(--vbright); } } - - blimp_pickup_scene(); + // player reached goal; reward scene + rescue_scene(); } +///// + void setup_graphics() { cvu_memtovmemcpy(PATTERN, CHR_GENERIC, sizeof(CHR_GENERIC)); cvu_memtocmemcpy(0xc000, PALETTE0, 16); cvu_memtocmemcpy(0xc010, PALETTE1, 16); + flip_sprite_patterns(PATTERN+0x80*32, CHR_GENERIC+0xd0*32, 0x30*32); + cv_set_left_column_blank(true); } void main() { vdp_setup(); - setup_graphics(); - cv_set_screen_active(true); - cv_set_vint_handler(&vint_handler); - make_levels(); - play_scene(); + while (1) { + setup_graphics(); + cv_set_screen_active(true); + cv_set_vint_handler(&vint_handler); + sfx_play(SND_START,0); // play starting sound + make_floors(); // make random level + music_play(0); // start the music + play_scene(); // play the level + } } diff --git a/presets/sms-sms-libcv/common.c b/presets/sms-sms-libcv/common.c index d1af692f..50e3ee01 100644 --- a/presets/sms-sms-libcv/common.c +++ b/presets/sms-sms-libcv/common.c @@ -22,16 +22,16 @@ byte reverse_bits(byte n) { void flip_sprite_patterns(word dest, const byte* patterns, word len) { word i; for (i=0; i