Jump to content

Photo

Ocarina songs


  • Please log in to reply
22 replies to this topic

#16 Beefster

Beefster

    Human Being

  • Members
  • Real Name:Justin
  • Location:Colorado

Posted 27 June 2009 - 08:31 PM

I honestly think it would be silly to use a system of primes for this sort of thing- it's ridiculously inefficient and won't maintain order. Plus, if you messed up on a note you have to stop playing then start again. It would be much better to use a wrap-around array. (Plus it's REALLY efficient)

You know what? I'll just post some baseline code for the song checker later.

EDIT: I haven't tested it, but here it is. I hope it works pretty well.

I don't know how to freeze all action though, which is REALLY important. I did forget a few other things, but I don't feel like adding them, so this code is in no way complete. I probably won't finish it myself, so I give everyone else permission to tweak it.
CODE
//import "std.zh"

const int I_INVIS = 200; //Placeholder. This probably already exists somewhere else in your code.

const int FFC_SONGSECRETS = 31; //Placeholder

const int OC_A = 1;
const int OC_D = 2;
const int OC_R = 3;
const int OC_L = 4;
const int OC_U = 5;

const int SFX_OC_A = 100; //Placeholder
const int SFX_OC_D = 101; //Placeholder
const int SFX_OC_R = 102; //Placeholder
const int SFX_OC_L = 103; //Placeholder
const int SFX_OC_U = 104; //Placeholder


const int SFX_SONG_EPONA        = 105; //Placeholder
const int SFXLEN_SONG_EPONA     = 300; //Placeholder

const int SFX_SONG_SARIA        = 106; //Placeholder
const int SFXLEN_SONG_SARIA     = 300; //Placeholder

const int SFX_SONG_HEALING      = 107; //Placeholder
const int SFXLEN_SONG_HEALING   = 300; //Placeholder


const int SONGLEN_EPONA         = 6;
const int SONG_EPONA_PLAYED     = 1; //Placeholder
bool KnowEponaSong = true; //This is for testing purposes. Change it to false in your quest.

const int SONGLEN_SARIA        = 6;
const int SONG_SARIA_PLAYED     = 2; //Placeholder
bool KnowSariaSong = true; //This is for testing purposes. Change it to false in your quest.

const int SONGLEN_HEALING      = 6;
const int SONG_HEALING_PLAYED   = 3; //Placeholder
bool KnowSongOfHealing = true; //This is for testing purposes. Change it to false in your quest.

//Treat these like constants!
int SONG_EPONA[6]   = {5, 4, 3, 5, 4, 3}; //Fixed that.
int SONG_SARIA[6]   = {2, 3, 4, 2, 3, 4};
int SONG_HEALING[6] = {4, 3, 2, 4, 3, 2};

//This needs to be checked in the global script.
bool PlayingOcarina = false;

item script Ocarina {
    void run() {
        PlayingOcarina = true;
    }
}

//This should be a method in your global script
void PlayOcarina() {

    int notes[8] = { 0, 0, 0, 0,  0, 0, 0, 0 }; //Remembers the last 8 notes you played.
    int ptr = 0; //last note played. (It wraps around)
    
    //Copied from Elmensajero.
    while(Link->InputB) Waitframe(); //Wait until B is depressed so it doesn't quit right away
    
    Link->Item[I_INVIS] = true; //Turn Link invisible
    
    int upleftT = Screen->ComboT[0]; //Hey, it MIGHT be important.
    
    Screen->ComboT[0] = CT_SCREENFREEZE; //Change that combo so that the screen freezes
    
    while(true) {
        
        if(Link->PressB) break;
        
        if(Link->PressA) {
            notes[ptr] = OC_A;
            ptr = (ptr + 1) % 8; //The modulus is for wrap-around
            Game->PlaySound(SFX_OC_A);
        }
        else if(Link->PressDown) {
            notes[ptr] = OC_D;
            ptr = (ptr + 1) % 8; //The modulus is for wrap-around
            Game->PlaySound(SFX_OC_D);
        }
        else if(Link->PressRight) {
            notes[ptr] = OC_R;
            ptr = (ptr + 1) % 8; //The modulus is for wrap-around
            Game->PlaySound(SFX_OC_R);
        }
        else if(Link->PressLeft) {
            notes[ptr] = OC_L;
            ptr = (ptr + 1) % 8; //The modulus is for wrap-around
            Game->PlaySound(SFX_OC_L);
        }
        else if(Link->PressUp) {
            notes[ptr] = OC_U;
            ptr = (ptr + 1) % 8; //The modulus is for wrap-around
            Game->PlaySound(SFX_OC_U);
        }
        
        //Here's the fun part. We get to check the notes against known songs! YAAAY! D:
        //Yes, you have to check each song. ZScript can't pass arrays as parameters. :(
        if(KnowEponaSong) {
            //Work through the notes BACKWARDS starting with the last note that was played.
            //If at any time, there is a mismatch, it's WRONG, so exit the loop.
            bool correct = true;
            for(int pos = 0; pos < SONGLEN_EPONA && correct; pos++) {
                correct &&= (notes[(ptr - pos) % 8] == SONG_EPONA[SONGLEN_EPONA - pos - 1]);
                if(!correct) break;
            }
            
            //If the song was right, play the song, then pass the song number onto ffc FFC_SONGSECRETS to process secrets...
            if(correct) {
                Game->PlaySound(SFX_SONG_EPONA);
                Waitframes(SFXLEN_SONG_EPONA);
                ffc activator = Screen->LoadFFC(FFC_SONGSECRETS);
                activator->X = SONG_EPONA_PLAYED;
                //Call Epona if possible.
                break;
            }
        }
        
        if(KnowSariaSong) {
            //Work through the notes BACKWARDS starting with the last note that was played.
            //If at any time, there is a mismatch, it's WRONG, so exit the loop.
            bool correct = true;
            for(int pos = 0; pos < SONGLEN_SARIA && correct; pos++) {
                correct &&= (notes[(ptr - pos) % 8] == SONG_SARIA[SONGLEN_SARIA - pos - 1]);
                if(!correct) break;
            }
            
            //If the song was right, play the song, then pass the song number onto ffc FFC_SONGSECRETS to process secrets...
            if(correct) {
                Game->PlaySound(SFX_SONG_SARIA);
                Waitframes(SFXLEN_SONG_SARIA);
                ffc activator = Screen->LoadFFC(FFC_SONGSECRETS);
                activator->X = SONG_SARIA_PLAYED;
                break;
            }
        }
        
        if(KnowSongOfHealing) {
            //Work through the notes BACKWARDS starting with the last note that was played.
            //If at any time, there is a mismatch, it's WRONG, so exit the loop.
            bool correct = true;
            for(int pos = 0; pos < SONGLEN_HEALING && correct; pos++) {
                correct &&= (notes[(ptr - pos) % 8] == SONG_HEALING[SONGLEN_HEALING - pos - 1]);
                if(!correct) break;
            }
            
            //If the song was right, play the song, then pass the song number onto ffc FFC_SONGSECRETS to process secrets...
            if(correct) {
                Game->PlaySound(SFX_SONG_HEALING);
                Waitframes(SFXLEN_SONG_HEALING);
                ffc activator = Screen->LoadFFC(FFC_SONGSECRETS);
                activator->X = SONG_HEALING_PLAYED;
                break;
            }
        }
        
        Waitframe();
    }
    
    PlayingOcarina = false;
    Screen->ComboT[0] = upleftT;
}

//Song Secrets Activator
//D0: Song number
//D1: Flag to replace
//D2: Replacement combo number
//D3: Replacement cset
//D4: Replacement flag
//D5: Replacement inherent flag
//D6: Replacement type
//D7: Replacement walkability mask
//Notes: Set Y to nonzero to indicate that secrets are permanent,
//in which case you should mark the "run script on screen init." box.
//Place this script on your reserved ffc.
ffc script SongSecrets {

    void run(int song_num, int rflag, int data, int cset, int flag, int inherent, int type, int solid) {
    
        //If the secrets are permanent, switch out the combos right now!
        if(this->Y != 0 && Screen->State[ST_SECRET]) {
            for(int i = 0; i < 176; i++) {
                //Check for the flag.
                if(Screen->ComboF[i] == rflag ||
                   Screen->ComboI[i] == rflag) {
                
                    //Change the combo information.
                    Screen->ComboD[i] = data;
                    Screen->ComboC[i] = cset;
                    Screen->ComboF[i] = flag;
                    Screen->ComboI[i] = inherent;
                    Screen->ComboT[i] = type;
                    Screen->ComboS[i] = solid;
                }
            }
            
            Quit(); //All done!
        }
    
        while(true) {
        
            if(this->X == song_num) {
                //Trigger activated! Sweep through the screen and replace the appropriate combos.
                for(int i = 0; i < 176; i++) {
                    //Check for the flag.
                    if(Screen->ComboF[i] == rflag ||
                       Screen->ComboI[i] == rflag) {
                    
                        //Change the combo information.
                        Screen->ComboD[i] = data;
                        Screen->ComboC[i] = cset;
                        Screen->ComboF[i] = flag;
                        Screen->ComboI[i] = inherent;
                        Screen->ComboT[i] = type;
                        Screen->ComboS[i] = solid;
                        
                    }
                }
                
                //"Turn off" the song that was just played so that this isn't activated again
                this->X = 0;
                
                //Set the screen state if appropriate.
                if(this->Y != 0) {
                    Screen->State[ST_SECRET] = true;
                    Quit(); //All done!
                }
            }
            
            Waitframe();
        }
    }
}
NOTE: THIS CODE IS NOT USABLE AS-IS. You WILL need to move sections around and possibly add a few lines to make this compatible with your scripts.

Edited by Beefster, 28 June 2009 - 03:44 PM.


#17 Elmensajero

Elmensajero

    Doyen(ne)

  • Members
  • Real Name:John
  • Location:Raleigh, North Carolina

Posted 27 June 2009 - 08:45 PM

Here is the code I had been working on. I set it up for use with a array of size 10, but wrote the algorithms involved to allow for any size song <=10 to be possible to be added by user. If you want to see the script in action, download Ocarina.qst, although so far it only allows you to play notes using the four directional keys and the A button when the ocarina is used. Press "B" to stop playing. I haven't really started testing to see why the note-checking part of the script doesn't work yet, once I get some more free time hopefully I will be able to finish it.

CODE

//Credits go to Joe123 for posting his original ocarina script and Gleeok for helping me
//to come up with an alternate solution to my "array problem".

import "std.zh"

// Definitions: Don't change these 6 constants or the script will not work
const int NULL = 0;
const int NOTE_UP = 1;
const int NOTE_DOWN = 2;
const int NOTE_LEFT = 3;
const int NOTE_RIGHT = 4;
const int NOTE_A = 5;

// Sound Effects
const int SFX_DLOW = 63;     //A button ocarina note
const int SFX_F = 64;        //Down button ocarina note
const int SFX_A = 60;        //Right button ocarina note
const int SFX_B = 61;        //Left button ocarina note
const int SFX_DHIGH = 62;    //Up button ocarina note
const int SFXLEN_OCRNA = 5;  //Amount of frames to play sound (holding button down will repeat loop)
const int SFX_CORRECT = 65;  //Plays when song is played correctly

// Midis
const int MIDI_SNG1 = 1;
const int MIDI_SNG2 = 2;

// Songs: Each song must have 10 numbers in it. See the definitions above to see what the
//        numbers represent. If your song has only 6 notes, put zeroes for the last four
//        numbers. If it has 3 notes, put zeroes for the last seven numbers. A song cannot
//        be longer than 10 notes.
int SONG_EX1[10] = {3,1,4,3,1,4,0,0,0,0}; //Zelda's Lullaby in this example
int SONG_EX2[10] = {5,2,4,4,3,0,0,0,0,0}; //Serenade of Water in this example

// Items
const int ITEM_INVIS = 125; //Item ID of an item with LTM that turns Link invisible
const int ITEM_SNG1 = 123; //Item ID for the song SONG_EX1, given when the song is learned
const int ITEM_SNG2 = 124; //Item ID for the song SONG_EX2, given when the song is learned

// Drawing
const int TILE_OCARINA = 1260; //Tile of Link playing ocarina

//Global variables: Do not change these or the script will not work.
int song_buffer[10];
int song[10];
int notes_played = 0;
bool use_ocarina = false;

// Put this script in slot 2 of the global scripts section.
global script Slot_2
{
    void run()
    {
        while(true)
        {
            if(use_ocarina) //if whistle item is used
            {
                useocarina(); //call function that does most of the work
                use_ocarina = false; //reset global variable
            }
            // Add any other code that needs to be run globally here. Note that this section
            // of code will not run while the ocarina is being used.
            Waitframe();
        }
    }
}

// Put this script in slot 3 of the global scripts section.
global script Slot_3
{
    void run()
    {
        Link->Item[ITEM_INVIS] = false;
        // Add any other code that needs to run when the game is quit here.
    }
}

// Method that is executed by the global script when the ocarina is used
void useocarina()
{
    //Local variables
    int i;
    int origtype = Screen->ComboT[0]; //Save combotype of top left combo on screen
    bool noteplayed = false;
    bool lastUp = false;
    bool lastDown = false;
    bool lastLeft = false;
    bool lastRight = false;
    bool lastA = false;
    
    while(Link->InputB) Waitframe(); //Wait until B is depressed so it doesn't quit right away
    Link->Item[ITEM_INVIS] = true; //Turn Link invisible
    Screen->ComboT[0] = CT_SCREENFREEZE; //Change that combo so that the screen freezes
    while(true)
    {
        if(Link->InputUp && !lastUp)
        {
            add_note(NOTE_UP); //Add "up" note to song buffer
            noteplayed = true;
            Game->PlaySound(SFX_DHIGH);
        }
        else if(Link->InputDown && !lastDown)
        {
            add_note(NOTE_DOWN); //Add "down" note to song buffer
            noteplayed = true;
            Game->PlaySound(SFX_F);
        }
        else if(Link->InputLeft && !lastLeft)
        {
            add_note(NOTE_LEFT); //Add "left" note to song buffer
            noteplayed = true;
            Game->PlaySound(SFX_B);
        }
        else if(Link->InputRight && !lastRight)
        {
            add_note(NOTE_RIGHT); //Add "right" note to song buffer
            noteplayed = true;
            Game->PlaySound(SFX_A);
        }
        else if(Link->InputA && !lastA)
        {
            add_note(NOTE_A); //Add "A" note to song buffer
            noteplayed = true;
            Game->PlaySound(SFX_DLOW);
        }
        else if(Link->InputB) break; //Stop playing if "B" is pressed
        
        if(noteplayed) //Don't allow other notes to be played for SFXLEN_OCRNA frames
        {
            for(i=0;i<SFXLEN_OCRNA;i++)
            {
                Screen->DrawTile(3,Link->X,Link->Y,TILE_OCARINA,1,1,6,1,0,0,0,0,true,128); //Draw Link playing ocarina
                Waitframe();
            }
            noteplayed = false;
        }
        
        // Load each song and check to see if the notes played match the song. If so, call the
        // playsong function and then return control to the global script.
        for(i=0;i<10;i++) song[i] = SONG_EX1[i]; //Load song SOMG_EX1 into the global song array
        if(check_song(ITEM_SNG1,6)) //Check if notes played match this song
        {
            play_song(MIDI_SNG1,660);
            break;
        }
        for(i=0;i<10;i++) song[i] = SONG_EX2[i]; //Load song SOMG_EX2 into the global song array
        if(check_song(ITEM_SNG2,5)) //Check if notes played match this song
        {
            play_song(MIDI_SNG2,780);
            break;
        }
        
        lastUp = Link->InputUp;
        lastDown = Link->InputDown;
        lastLeft = Link->InputLeft;
        lastRight = Link->InputRight;
        lastA = Link->InputA;
        Screen->DrawTile(3,Link->X,Link->Y,TILE_OCARINA,1,1,6,1,0,0,0,0,true,128); //Draw Link playing ocarina
        Waitframe();
    }
    Screen->ComboT[0] = origtype; //Unfreeze screen
    Link->Item[ITEM_INVIS] = false;
    clear_notes(); //Reset song buffer
    while(Link->InputB) Waitframe(); //Wait until B is depressed so it doesn't use the ocarina again on exit
}

// Method that adds a note to the song buffer. If buffer overflows, it shifts
// data so that the oldest entry is removed.
void add_note(int note)
{
    //Local Variable
    int i;
    
    if(notes_played == 10) for(i=0;i<10;i++) song_buffer[i] = song_buffer[i+1]; //if buffer overflowed, remove oldest entry
    else notes_played++; //otherwise, increase global variable that keeps track of how many notes have been played
    song_buffer[notes_played - 1] = note;
}

// Method that resets the song buffer and global variable that keeps track of how many notes have been played
void clear_notes()
{
    //Local Variable
    int i;
    
    for(i=0;i<10;i++) song_buffer[i] = 0; //Reset song buffer
    notes_played = 0; //Reset global variable
}

// Method that checks song in song buffer with the global song array.
bool check_song(int song, int length)
{
    //Local Variables
    int i;
    int j=length;
    int psong[10];
    
    if(!Link->Item[song]) return false; //if song has not been learned, return error
    if(notes_played < length) return false; //if not enough notes have been played for this song, return error
    for(i=notes_played;i>notes_played-length;i--) //load the most recently played notes (amount equal to "length") into an array
    {
        psong[j-1] = song_buffer[i-1];
        j--;
    }
    for(i=0;i<10;i++) if(song[i] != psong[i]) return false; //if any of the notes are different, return error
    return true; //if it reaches this point, song has been played correctly
}

// Method that plays the midi for a song and draws the notes on the screen.
void play_song(int song, int songlength)
{
    int currentmidi;
    
    Game->PlaySound(SFX_CORRECT);
    Waitframes(60);
    currentmidi = Game->GetMIDI();
    Game->PlayMIDI(song);
    // draw song on screen
    Waitframes(songlength);
    Game->PlayMIDI(currentmidi);
}

// Attach this script to the action script of the ocarina
item script ocarina
{
    void run()
    {
        use_ocarina = true;
    }
}

Edited by Elmensajero, 27 June 2009 - 08:49 PM.


#18 Beefster

Beefster

    Human Being

  • Members
  • Real Name:Justin
  • Location:Colorado

Posted 27 June 2009 - 11:01 PM

You beat me to it. Ah well. We both attacked the problem pretty differently, so I wonder which will work better.

Edited by Beefster, 27 June 2009 - 11:04 PM.


#19 Elmensajero

Elmensajero

    Doyen(ne)

  • Members
  • Real Name:John
  • Location:Raleigh, North Carolina

Posted 28 June 2009 - 12:12 AM

Yeah, it's cool seeing how two different approaches are used to solve the same problem. One thing I know won't compile in yours is the following line:
CODE
int SONG_EPONA[6]   = {OC_U, OC_L, OC_R, OC_U, OC_L, OC_R};

Arrays can't be initialized in ZScript using constants. (I did the same thing at first as well. icon_biggrin.gif) That coupled with not being able to pass arrays into functions has made working on this kinda frustrating...

#20 L33TSkillz

L33TSkillz

    Junior

  • Members

Posted 28 June 2009 - 08:59 AM

yeah, I tried both and got an error on beefsters

#21 Beefster

Beefster

    Human Being

  • Members
  • Real Name:Justin
  • Location:Colorado

Posted 28 June 2009 - 01:50 PM

FIXED! Thanks for telling me that, Elmensajero.

L33TSkillz: Try it again. For future reference, know that you need to give the full specifics of errors. Just saying that you got an error doesn't tell me anything.

Edited by Beefster, 28 June 2009 - 02:00 PM.


#22 L33TSkillz

L33TSkillz

    Junior

  • Members

Posted 28 June 2009 - 03:25 PM

Okay it was error P00 on _Saria.


It's fine now, but I can't get the ocarina to do anything at all.

Edited by L33TSkillz, 28 June 2009 - 03:33 PM.


#23 Beefster

Beefster

    Human Being

  • Members
  • Real Name:Justin
  • Location:Colorado

Posted 28 June 2009 - 03:37 PM

The new version compiles fine, now. I'm just wondering if it works right.

EDIT: Make sure you put something like
CODE
if(PlayingOcarina) PlayOcarina();

somewhere in your global script's loop.

Also, I forgot something, so transfer the script over to your file again.

Edited by Beefster, 28 June 2009 - 03:40 PM.



0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users