Jump to content

Photo

How to make a boss waiting for an intro ? (2.55 Alpha 115)

2.55 Alpha 155 NPC Script

  • Please log in to reply
14 replies to this topic

#1 Pier

Pier

    Newbie

  • Members

Posted 23 September 2023 - 05:51 AM

Hello everyone,

 

So, another thing that I pointed out for my friend quest is that the boss music is affected to a screen and not the boss itself so even if the boss is dead the music still play on the screen, which is weird.

 

However, since this is too easy and not funny enough to make a script just to change the music back to normal when the boss is dead, I tried to add some sort of intro that will work like this.

  • Link enter the room, the music stop and the controls are locked.
  • The boss make a one frame pose (like Aquamentus open its mouth and Gohma raise its arms), the game play a roar sound and the screen shakes for a little moment.
  • Play the boss music and unlocked controls.
  • When the boss is dead (maybe make an multiple explosions animation than just a "pouf" like the original) and play the donjon music.

 

The only problem is that I didn't manage to find a way to force the boss to do nothing. I can remove its weapon so it can no longer shoot projectiles but he can still walk and do its animation.

 

I check other scripts on the website and I found this one for a Ganon with an intro: https://www.purezc.n...=scripts&id=463and apparently he use WaitNoAction() (a combinaison of NoAction() that lock Link controls and WaitFrame() that make the script wait for one frame) but when I tried it the script just quit like if this is an item script. However, the Ganon script and my script are two npc scripts and I didn't see something that say that Waiframe() make npc script quit.

 

Did there is some other ways that a Waitframe() do a quit outside of an item script ? Is there is another way to make a boss waiting ?

 

Thanks in advance for your answer and I wish you a nice day.

 



#2 Jamian

Jamian

    ZC enthusiast

  • Members

Posted 23 September 2023 - 07:20 AM

Hardcoded Z1 bosses can be hard to control. How about you just draw the animation of the boss during the intro, and then spawn the boss after that?



#3 Pier

Pier

    Newbie

  • Members

Posted 29 September 2023 - 10:16 AM

Hardcoded Z1 bosses can be hard to control. How about you just draw the animation of the boss during the intro, and then spawn the boss after that?

 

Nice idea ! I was busy since I make my post but I managed to make a first semi-complete version of my script that will allow to implement this idea:

const int D_BOSS_STATE       = 2; //Screen->D[] to define room state
const int BOSS_STATE_VISITED = -1; //Room visited but boss not dead
const int BOSS_STATE_KILLED  = -2; //Room visited and dead boss

const int ID_MIDI_SILENT    = -7;
const int SCREEN_SHAKE_TIME = 120;

screendata script Boss_Screen {


    void run(int idMusic) {
        this->Pattern = PATTERN_NO_SPAWNING; //Prevent ennemies spawning

        if(this->D[D_BOSS_STATE] <= BOSS_STATE_KILLED) {
            TraceS("Boss already dead");
            return; //The boss is already dead, nothing to do here
        }

        //Memorize the music of the donjon
        int normalMidi = Game->GetMIDI();

        int waitTime = 20;
        while(waitTime > 0) {
            waitTime--;
            WaitNoAction();
        }

        //Stop music
        Game->PlayMIDI(ID_MIDI_SILENT);
        
        //Play boss animation here
        Game->PlaySound(24);
        this->Quake = SCREEN_SHAKE_TIME;
        int shakeTimeLeft = SCREEN_SHAKE_TIME;
        while(shakeTimeLeft > 0) {
            shakeTimeLeft--;
            WaitNoAction();
        }

        this->Pattern = PATTERN_STANDARD; //Allow ennemies to spawn

        //Wait ennemies spawn
        int nbEnnemis = this->NumNPCs();
        while(nbEnnemis <= 0) {
            //Trace(nbEnnemis);
            WaitNoAction();
            nbEnnemis = this->NumNPCs();
        }

        //Begining of the battle
        Game->PlayMIDI(idMusic);

        int format[] = "%s%d%s%d";
        int messageTrace[1000];
        sprintf(messageTrace, format, "Musique actu: ", normalMidi, "\nMusique boss: ", Game->GetMIDI());
        TraceS(messageTrace);

        this->D[D_BOSS_STATE] = BOSS_STATE_VISITED;

        //While an enemy is still alive
        nbEnnemis = this->NumNPCs();
        while(nbEnnemis > 0) {
            //Trace(nbEnnemis);
            Waitframe();
            nbEnnemis = this->NumNPCs();
        }

        //When everyone is dead
        TraceS("BOSS DEAD DX");
        this->D[D_BOSS_STATE] = BOSS_STATE_KILLED;
        Game->PlayMIDI(normalMidi);
    }
}

This gives this result: https://drive.google...iew?usp=sharing(video of me testing if the script work properly if Link die during or after the boss battle).

So of course there other things to do to complete the script (check is the player use enchanced music instead of MIDI, correct the broken doors graphics that depend of an slightly altered version of this script https://www.purezc.n...=scripts&id=290, adapt the roar sound to the enemies on screen, etc...) but I have another question to implement your idea: how can I know the spawning position of the screen enemies in advance (before they spawn) ? Apparently Aquamentus always spawn on the same location but I don't think this is the case for other boss (like Dodongo I guess) and I don't know if my friend wants to use custom boss so I don't know if I can rely on values directly typed on the code.
 



#4 Russ

Russ

    Caelan, the Encouraging

  • Administrators
  • Location:Washington

Posted 29 September 2023 - 04:12 PM

The easiest way for enemies that don't spawn at hard coded positions would be to decide for yourself where to spawn them and then spawn them at that location.



#5 Emily

Emily

    Scripter / Dev

  • ZC Developers

Posted 29 September 2023 - 09:23 PM

The easiest way for enemies that don't spawn at hard coded positions would be to decide for yourself where to spawn them and then spawn them at that location.

Or just use enemy placement flags?



#6 Pier

Pier

    Newbie

  • Members

Posted 01 October 2023 - 09:22 AM

Or just use enemy placement flags?

It works with ennemies that spawn at a random location like Dodongo but not with ones that spawn on a fixed location like Aquamentus.

 

I think that is something to do with the "Ennemy type":

https://drive.google...iew?usp=sharing

 

However I don't know how to access the attributs of an ennemy type, whether with a script or with the editor itself.

So I guess I have to check Screen->Ennemy[0-9] to get the ID of the ennemies and manage the animation case by case by using hard-coded values for ennemies like Aquamentus and tell my friends to use flags for ennemies like Dodongo.
 



#7 Pier

Pier

    Newbie

  • Members

Posted 01 October 2023 - 09:37 AM

It works with ennemies that spawn at a random location like Dodongo but not with ones that spawn on a fixed location like Aquamentus.

Ok nevermind, there is a flag that force the ennemy to spawn on a position defined with a flag: https://drive.google...iew?usp=sharing



#8 Pier

Pier

    Newbie

  • Members

Posted 05 October 2023 - 11:00 AM

Ok, I finally make a first version of the intro animation with Aquamentus using Screen->DrawTile:

const int D_BOSS_STATE       = 2; //Screen->D[] to define room state
const int BOSS_STATE_VISITED = -1; //Room visited but boss not dead
const int BOSS_STATE_KILLED  = -2; //Room visited and dead boss

const int ID_MIDI_SILENT    = -7;
const int SCREEN_SHAKE_TIME = 120;

const int MAX_ENNEMIES        = 10; //Max number of ennemies on a screen
const int FIRST_ENNEMY_FLAG   = 37; //ID of Flag "Enemy 0";
const int LAYER_BOSS_GRAPHICS = 6;
const int BOSS_CSET           = 14; //Color Set use for Boss

const int NB_COMBOS_PER_LINE   = 16; //In the editor there is 16 combos per line (first line = 0 à 15)
const int NB_COMBOS_PER_COLUMN = SCREEN_COMBOS/NB_COMBOS_PER_LINE; //=11
const int COMBO_WIDTH          = SCREEN_WIDTH/NB_COMBOS_PER_LINE; //=16
const int COMBO_HEIGHT         = SCREEN_HEIGHT/NB_COMBOS_PER_COLUMN; //=16

screendata script Boss_Screen {

    void run(int idBossMusic) {
        Screen->Pattern = PATTERN_NO_SPAWNING; //Prevent ennemies spawning

        if(Screen->D[D_BOSS_STATE] <= BOSS_STATE_KILLED) {
            TraceS("Boss already dead");
            return; //The boss is already dead, nothing to do here
        }

        //Place ennemies graphics
        int indexActu    = 0;
        int idEnnemyActu = Screen->Enemy[indexActu];

        int posXEnnemies[MAX_ENNEMIES];
        int posYEnnemies[MAX_ENNEMIES];
        for(int i = 0; i < MAX_ENNEMIES; i++) {
            posXEnnemies[i] = 0;
            posYEnnemies[i] = 0;
        }

        int indexComboActu = 0;
        //There can be 10 ennemies maximum and the ID 0 is "(NONE)" and no ennemies spawn after a (NONE) one
        while(indexActu < MAX_ENNEMIES && idEnnemyActu > 0) {
            //Every "Ennemy" flags ID follow each other
            indexComboActu = getFirstFlagPosition(FIRST_ENNEMY_FLAG + indexActu);
            posXEnnemies[indexActu] = (indexComboActu%NB_COMBOS_PER_LINE)*COMBO_WIDTH;
            posYEnnemies[indexActu] = (indexComboActu-(indexComboActu%NB_COMBOS_PER_LINE))/NB_COMBOS_PER_LINE*COMBO_HEIGHT;
            indexActu++;
        }
        int indexMax = indexActu-1; //Now we know the number of ennemies on the screen
        placeGraphicEnnemies(posXEnnemies, posYEnnemies, indexMax, 1);

        //Memorize the music of the donjon
        int normalMidi = Game->GetMIDI();

        int waitTime = 20;
        while(waitTime > 0) {
            waitTime--;
            WaitNoAction();
            placeGraphicEnnemies(posXEnnemies, posYEnnemies, indexMax, 1);
        }

        //Stop the music
        Game->PlayMIDI(ID_MIDI_SILENT);

        playSoundEnnemies(indexMax);

        Screen->Quake = SCREEN_SHAKE_TIME;
        int shakeTimeLeft = SCREEN_SHAKE_TIME;
        while(shakeTimeLeft > 0) {
            shakeTimeLeft--;
            WaitNoAction();
            placeGraphicEnnemies(posXEnnemies, posYEnnemies, indexMax, 2);
        }

        Screen->Pattern = PATTERN_STANDARD; //Allow ennemies to spawn

        //Wait ennemies spawn
        int nbEnnemis = Screen->NumNPCs();
        while(nbEnnemis <= 0) {
            //Trace(nbEnnemis);
            WaitNoAction();
            placeGraphicEnnemies(posXEnnemies, posYEnnemies, indexMax, 1);
            nbEnnemis = Screen->NumNPCs();
        }

        //Begining of the battle
        Game->PlayMIDI(idBossMusic);

        int format[] = "%s%d%s%d";
        int messageTrace[1000];
        sprintf(messageTrace, format, "Musique actu: ", normalMidi, "\nMusique boss: ", Game->GetMIDI());
        TraceS(messageTrace);

        Screen->D[D_BOSS_STATE] = BOSS_STATE_VISITED;

        //While an enemy is still alive
        nbEnnemis = Screen->NumNPCs();
        while(nbEnnemis > 0) {
            //Trace(nbEnnemis);
            Waitframe();
            nbEnnemis = Screen->NumNPCs();
        }

        //When everyone is dead
        TraceS("BOSS DEAD DX");
        Screen->D[D_BOSS_STATE] = BOSS_STATE_KILLED;
        Game->PlayMIDI(normalMidi);
    }

    //Return the most upper-left instance of the flag
    int getFirstFlagPosition(int idFlag) {
        for(int i = 0; i < SCREEN_COMBOS; i++) {
            if(Screen->ComboF[i] == idFlag) {
                return i;
            }
        }
        return 0;
    }

    //---------------------------------------------------------

    void placeGraphicEnnemies(int posXEnnemies, int posYEnnemies, int indexMax, int state) {
        int idActu = 0;
        for(int i = 0; i < indexMax; i++) {
            idActu = Screen->Enemy[i];
            if(idActu == 58) { //Aquamentus (Facing Left)
                placeGraphicAquamentusLeft(posXEnnemies[i], posYEnnemies[i], state);
            }
        }
    }

    void placeGraphicAquamentusLeft(int x, int y, int state) {
        if(state == 1) {
            Screen->DrawTile(LAYER_BOSS_GRAPHICS, x, y,
                7528,
                2,
                2,
                BOSS_CSET,
                -1, -1, 0, 0, 0, 0, true, OP_OPAQUE);
        } else {
            Screen->DrawTile(LAYER_BOSS_GRAPHICS, x, y,
                7568,
                2,
                2,
                BOSS_CSET,
                -1, -1, 0, 0, 0, 0, true, OP_OPAQUE);
        }
    }

    //---------------------------------------------------------

    void playSoundEnnemies(int indexMax) {
        int idActu = 0;
        for(int i = 0; i < indexMax; i++) {
            idActu = Screen->Enemy[i];
            if(idActu == 58) { //Aquamentus (Facing Left)
                Game->PlaySound(24);
            }
        }
    }
}

However, there is still a problem as you can see on this video: https://drive.google...iew?usp=sharing

The Color Set used for the intro is not the same as the boss itself. This is maybe because most of the bosses use the special CSet 14 which is not accessible with the editor except when we go try to change the sprite data of the boss (https://drive.google...iew?usp=sharing) ... Kinda... Because if we change the CSet we can no longer select it (ok... I guess :confused: ).

 

Anyway, apparently this CSet doesn't exist for ZScript and the methods that use a CSet as an argument (like DrawTile), so giving 14 will give the CSet 8 to the tiles I draw. So there is a way to acess this special CSet by ZScript or I have to recolor every boss that use CSet 14 to be able to copy them with scripts ?
 



#9 Emily

Emily

    Scripter / Dev

  • ZC Developers

Posted 05 October 2023 - 11:21 AM

Ok, I finally make a first version of the intro animation with Aquamentus using Screen->DrawTile:

const int D_BOSS_STATE       = 2; //Screen->D[] to define room state
const int BOSS_STATE_VISITED = -1; //Room visited but boss not dead
const int BOSS_STATE_KILLED  = -2; //Room visited and dead boss

const int ID_MIDI_SILENT    = -7;
const int SCREEN_SHAKE_TIME = 120;

const int MAX_ENNEMIES        = 10; //Max number of ennemies on a screen
const int FIRST_ENNEMY_FLAG   = 37; //ID of Flag "Enemy 0";
const int LAYER_BOSS_GRAPHICS = 6;
const int BOSS_CSET           = 14; //Color Set use for Boss

const int NB_COMBOS_PER_LINE   = 16; //In the editor there is 16 combos per line (first line = 0 à 15)
const int NB_COMBOS_PER_COLUMN = SCREEN_COMBOS/NB_COMBOS_PER_LINE; //=11
const int COMBO_WIDTH          = SCREEN_WIDTH/NB_COMBOS_PER_LINE; //=16
const int COMBO_HEIGHT         = SCREEN_HEIGHT/NB_COMBOS_PER_COLUMN; //=16

screendata script Boss_Screen {

    void run(int idBossMusic) {
        Screen->Pattern = PATTERN_NO_SPAWNING; //Prevent ennemies spawning

        if(Screen->D[D_BOSS_STATE] <= BOSS_STATE_KILLED) {
            TraceS("Boss already dead");
            return; //The boss is already dead, nothing to do here
        }

        //Place ennemies graphics
        int indexActu    = 0;
        int idEnnemyActu = Screen->Enemy[indexActu];

        int posXEnnemies[MAX_ENNEMIES];
        int posYEnnemies[MAX_ENNEMIES];
        for(int i = 0; i < MAX_ENNEMIES; i++) {
            posXEnnemies[i] = 0;
            posYEnnemies[i] = 0;
        }

        int indexComboActu = 0;
        //There can be 10 ennemies maximum and the ID 0 is "(NONE)" and no ennemies spawn after a (NONE) one
        while(indexActu < MAX_ENNEMIES && idEnnemyActu > 0) {
            //Every "Ennemy" flags ID follow each other
            indexComboActu = getFirstFlagPosition(FIRST_ENNEMY_FLAG + indexActu);
            posXEnnemies[indexActu] = (indexComboActu%NB_COMBOS_PER_LINE)*COMBO_WIDTH;
            posYEnnemies[indexActu] = (indexComboActu-(indexComboActu%NB_COMBOS_PER_LINE))/NB_COMBOS_PER_LINE*COMBO_HEIGHT;
            indexActu++;
        }
        int indexMax = indexActu-1; //Now we know the number of ennemies on the screen
        placeGraphicEnnemies(posXEnnemies, posYEnnemies, indexMax, 1);

        //Memorize the music of the donjon
        int normalMidi = Game->GetMIDI();

        int waitTime = 20;
        while(waitTime > 0) {
            waitTime--;
            WaitNoAction();
            placeGraphicEnnemies(posXEnnemies, posYEnnemies, indexMax, 1);
        }

        //Stop the music
        Game->PlayMIDI(ID_MIDI_SILENT);

        playSoundEnnemies(indexMax);

        Screen->Quake = SCREEN_SHAKE_TIME;
        int shakeTimeLeft = SCREEN_SHAKE_TIME;
        while(shakeTimeLeft > 0) {
            shakeTimeLeft--;
            WaitNoAction();
            placeGraphicEnnemies(posXEnnemies, posYEnnemies, indexMax, 2);
        }

        Screen->Pattern = PATTERN_STANDARD; //Allow ennemies to spawn

        //Wait ennemies spawn
        int nbEnnemis = Screen->NumNPCs();
        while(nbEnnemis <= 0) {
            //Trace(nbEnnemis);
            WaitNoAction();
            placeGraphicEnnemies(posXEnnemies, posYEnnemies, indexMax, 1);
            nbEnnemis = Screen->NumNPCs();
        }

        //Begining of the battle
        Game->PlayMIDI(idBossMusic);

        int format[] = "%s%d%s%d";
        int messageTrace[1000];
        sprintf(messageTrace, format, "Musique actu: ", normalMidi, "\nMusique boss: ", Game->GetMIDI());
        TraceS(messageTrace);

        Screen->D[D_BOSS_STATE] = BOSS_STATE_VISITED;

        //While an enemy is still alive
        nbEnnemis = Screen->NumNPCs();
        while(nbEnnemis > 0) {
            //Trace(nbEnnemis);
            Waitframe();
            nbEnnemis = Screen->NumNPCs();
        }

        //When everyone is dead
        TraceS("BOSS DEAD DX");
        Screen->D[D_BOSS_STATE] = BOSS_STATE_KILLED;
        Game->PlayMIDI(normalMidi);
    }

    //Return the most upper-left instance of the flag
    int getFirstFlagPosition(int idFlag) {
        for(int i = 0; i < SCREEN_COMBOS; i++) {
            if(Screen->ComboF[i] == idFlag) {
                return i;
            }
        }
        return 0;
    }

    //---------------------------------------------------------

    void placeGraphicEnnemies(int posXEnnemies, int posYEnnemies, int indexMax, int state) {
        int idActu = 0;
        for(int i = 0; i < indexMax; i++) {
            idActu = Screen->Enemy[i];
            if(idActu == 58) { //Aquamentus (Facing Left)
                placeGraphicAquamentusLeft(posXEnnemies[i], posYEnnemies[i], state);
            }
        }
    }

    void placeGraphicAquamentusLeft(int x, int y, int state) {
        if(state == 1) {
            Screen->DrawTile(LAYER_BOSS_GRAPHICS, x, y,
                7528,
                2,
                2,
                BOSS_CSET,
                -1, -1, 0, 0, 0, 0, true, OP_OPAQUE);
        } else {
            Screen->DrawTile(LAYER_BOSS_GRAPHICS, x, y,
                7568,
                2,
                2,
                BOSS_CSET,
                -1, -1, 0, 0, 0, 0, true, OP_OPAQUE);
        }
    }

    //---------------------------------------------------------

    void playSoundEnnemies(int indexMax) {
        int idActu = 0;
        for(int i = 0; i < indexMax; i++) {
            idActu = Screen->Enemy[i];
            if(idActu == 58) { //Aquamentus (Facing Left)
                Game->PlaySound(24);
            }
        }
    }
}

However, there is still a problem as you can see on this video: https://drive.google...iew?usp=sharing

The Color Set used for the intro is not the same as the boss itself. This is maybe because most of the bosses use the special CSet 14 which is not accessible with the editor except when we go try to change the sprite data of the boss (https://drive.google...iew?usp=sharing) ... Kinda... Because if we change the CSet we can no longer select it (ok... I guess :confused: ).

 

Anyway, apparently this CSet doesn't exist for ZScript and the methods that use a CSet as an argument (like DrawTile), so giving 14 will give the CSet 8 to the tiles I draw. So there is a way to acess this special CSet by ZScript or I have to recolor every boss that use CSet 14 to be able to copy them with scripts ?
 

You CAN access it by zscript, except, CSet 14's colors change based on the enemies onscreen, so it won't have the right colors if the enemy isn't using them...

What is the specific issue you are having with selecting it?



#10 Pier

Pier

    Newbie

  • Members

Posted 05 October 2023 - 02:20 PM

You CAN access it by zscript, except, CSet 14's colors change based on the enemies onscreen, so it won't have the right colors if the enemy isn't using them...

What is the specific issue you are having with selecting it?

Ooooook I understand now, as you can see on the script, I prevent ennemies from spawning when I do the animation, so because the real Aquamentus is not onscreen, my graphic don't use the right colors (red instead of green)... I just make a little test by redrawing the intro graphic after Aquamentus spawn and there are the right color this time.

Thanks for the information... Even if I don't see how I can have the real boss and the intro graphics at the same time without making it weird to see...  To stop the ennemy to move, I have to freeze the screen, but if I freeze the screen then the "shake screen" effect doesn't play...

 

Welp, I hope that my friend will not be annoyed if I say to him that he has to change the CSet and recolor every boss that use this CSet in order to use the intro script ^^'
 



#11 Emily

Emily

    Scripter / Dev

  • ZC Developers

Posted 06 October 2023 - 01:44 AM

To stop the ennemy to move, I have to freeze the screen

 

Or you could just have a script `Stun` the enemy? (`npc->Stun = 5;` would stun it for 5 frames) (IDK if that works on Z1 bosses though?)



#12 Pier

Pier

    Newbie

  • Members

Posted 06 October 2023 - 09:36 AM

Or you could just have a script `Stun` the enemy? (`npc->Stun = 5;` would stun it for 5 frames) (IDK if that works on Z1 bosses though?)

I have already tried that and that doesn't work with some ennemies, including bosses.

 

However I found a way to load the correct CSet 14 palette without spawning the ennemies:

  • In the editor go to "Quest" -> "Ennemies"
  • Copy-Paste one ennemy that use CSet 14
  • Remove the Weapon, change the type to one that can't do anything (like "Custom 1"), set the Item Set to "None" and check the Basic Flags "Does not draw"

 

And bang ! This function will load this false ennemy to the Screen:

npc createNPCCSet14(int indexMax) {
        int idActu = 0;
        for(int i = indexMax-1; i >= 0; i--) {
            idActu = Screen->Enemy[i];
            if(idActu == NPC_AQUAMENTUSR || idActu == NPC_AQUAMENTUSL)
                return Screen->CreateNPC(ID_LOAD_AQUA);
            else if(idActu == NPC_DIGDOGGER1 || idActu == NPC_DIGDOGGER3 || 
                    idActu == NPC_DIGKID1 || idActu == NPC_DIGKID2 || idActu == NPC_DIGKID3 || idActu == NPC_DIGKID4 ||
                    idActu == NPC_DODONGOBS)
                return Screen->CreateNPC(ID_LOAD_DIGDO);
        }
        return Screen->CreateNPC(ID_LOAD_AQUA); //Aquamentus by default
    }

(PS: It is not complete already, I have to implement Ganon (forgot that he is a special case and he only spawn with his own cutscene in "Ganon" room, so I have to see with my friend if we keep it like this or if we take a script to replace him) and every version of Gleook to support every ennemy that use CSet 14).

So, even if this NPC is not visible for the player with the "Does not draw" flag, it will change the palette of CSet 14 for the animation, then, after the animations I spawn the true ennemies and kill the invisible one by setting its HP to 0.

 

There is the result with the Aquamentus palette (shared by the 2 Aquamentus variants): https://drive.google...iew?usp=sharingand the Digdogger one (shared by every version of Digdogger and Digdokid and Dodongo BS): https://drive.google...iew?usp=sharing
 


Edited by Pier, 06 October 2023 - 10:31 AM.


#13 Pier

Pier

    Newbie

  • Members

Posted 10 October 2023 - 10:24 AM

Hello everyone,

 

So, the boss animation itself is ready (I have just to update the script to include bosses as my friend want to use them and see how he want to handle Ganon).

 

However do you remember when I said this ?

[..] However, since this is too easy and not funny enough to make a script just to change the music back to normal when the boss is dead [...]

 

Well, this is, ideed, extremely easy with using only MIDI (Game->GetMIDI() to get the ID of the normal music of the donjon and Game->PlayMIDI(idMidi) to play the boss music and revert back to the normal music). However, I want to make it compatible with enchanced music (.nsf, .mp3, etc...) so I created a file dedicated to music change:

const char32 ENCHANCHED_MUSICS_BOSS[] = {"Coromon_Battle.mp3", //ID = 1
"Triforce_Heroes_Boss.mp3", //ID = 2
"Isabelle_Z1.nsf", //ID = 3 etc...
"Isabelle_Z2.nsf"};

const int ID_MIDI_SILENT = -7;

int savedMidi = -1;
int savedEnchancedName[256];
int savedEnchancedTrack = -1;

void saveCurrentMusic() {
    savedMidi = Game->GetMIDI();
    Game->GetDMapMusicFilename(Game->GetCurDMap(), savedEnchancedName);
    savedEnchancedTrack = Game->GetDMapMusicTrack(Game->GetCurDMap());

    //Message debug
    int format[] = "%s%s%s%d%s%d";
    int messageTrace[1000];
    sprintf(messageTrace, format, "Saved enchanced: '", savedEnchancedName, "' / ", savedEnchancedTrack, 
        "\nSaved midi; ", savedMidi);
    TraceS(messageTrace);
}

void playSavedMusic() {
    playMusic(savedMidi, savedEnchancedName, savedEnchancedTrack);
}

void playChoosedMusic(int idMidi, int enchancedID, int enchancedTrack) {

    //Don't work

    /*int musicName[256];
    if(enchancedID > 0 && enchancedID <= SizeOfArray(ENCHANCHED_MUSICS_BOSS) && enchancedTrack > 0)
        musicName = ENCHANCHED_MUSICS_BOSS[enchancedID-1];
    else
        musicName = "";

    playMusic(idMidi, musicName, enchancedTrack);*/

    //Temporary
    Game->PlayMIDI(idMidi);
}

void playSilent() {
    Game->PlayMIDI(ID_MIDI_SILENT);
}

//Try to play the enchanced music first, if this fail, will play the midi instead
void playMusic(int idMidi, int enchancedName, int enchancedTrack) {
    int messageTrace[1000];
    int format[] = "%s%s%s%d%s%d";
    sprintf(messageTrace, format, "Try to play: '", enchancedName, "' / ", enchancedTrack, 
        "\nDefault midi: ", idMidi);
    TraceS(messageTrace); 

    if(Game->PlayEnhancedMusic(enchancedName, enchancedTrack))
        return;

    if(idMidi <= 0)
        idMidi = ID_MIDI_SILENT;

    Game->PlayMIDI(idMidi);
}

Save the name and the track number of the current enchanced music works, as well as playing the saved enchanced music when the battle is over.

However, I didn't managed to have a list of playlable track that the user can choose with an ID in the initD[] of the Screen script of the editor. My initial plan is to create an array of musics names in the variable ENCHANCHED_MUSICS_BOSS but strings only work as int or char32 and in both case, try to read an element of the array will instantly finish as an invalid pointer error.

Did I do something wrong ? If this is not possible to create a string array, there is a way to read a file like XML or JSON to make the musics name list ?

 

Thanks in advance for your answer and I wish you a nice day.
 



#14 Emily

Emily

    Scripter / Dev

  • ZC Developers

Posted 10 October 2023 - 11:32 AM

...snip...

const char32 ENCHANCHED_MUSICS_BOSS[] = {"Coromon_Battle.mp3", //ID = 1
"Triforce_Heroes_Boss.mp3", //ID = 2
"Isabelle_Z1.nsf", //ID = 3 etc...
"Isabelle_Z2.nsf"};
...snip...
Did I do something wrong ? If this is not possible to create a string array, there is a way to read a file like XML or JSON to make the musics name list ?
 
Thanks in advance for your answer and I wish you a nice day.

 


Aye, what you did here is store 4 string LITERALS in the array. A string literal ceases to exist immediately at the end of that line, so, the array is left invalid. The correct way to do this would be to do the arrays out separately, so:

char32 battle1[] = "Coromon_Battle.mp3";
char32 battle2[] = "Triforce_Heroes_Boss.mp3";
char32 battle3[] = "Isabelle_Z1.nsf";
char32 battle4[] = "Isabelle_Z2.nsf";
char32 ENCHANCHED_MUSICS_BOSS[] = {battle1, battle2, battle3, battle4};

Also, `const` does not prevent you from changing the contents of an array. And to answer an immediate question of "but doesn't that eat up more global arrays?" to which the answer is "it eats up 5 global arrays, which is REQUIRED to do this".

However, if you want to avoid using global arrays, there's a MUCH EASIER way to do this!
 

enum
{
	MUSIC_COROMON_BATTLE,
	MUSIC_TRIHEROES_BOSS,
	MUSIC_ISABELLE_Z1,
	MUSIC_ISABELLE_Z2,
	MAX_MUSIC
};
void getMusicName(char32 buf, int musicID)
{
	switch(musicID)
	{
		case MUSIC_COROMON_BATTLE:
			strcpy(buf, "Coromon_Battle.mp3");
			break;
		case MUSIC_TRIHEROES_BOSS:
			strcpy(buf, "Triforce_Heroes_Boss.mp3");
			break;
		case MUSIC_ISABELLE_Z1:
			strcpy(buf, "Isabelle_Z1.nsf");
			break;
		case MUSIC_ISABELLE_Z2:
			strcpy(buf, "Isabelle_Z2.nsf");
			break;
	}
}
bool playEnhancedMusic(int musicID, int track)
{
	char32 buf[2048];
	getMusicName(buf, musicID);
	return Audio->PlayEnhancedMusic(buf, track);
}
void playChosenMusic(int idMidi, int enhancedID, int enhancedTrack)
{
	unless(playEnhancedMusic(enhancedID, enhancedTrack))
		Audio->PlayMIDI(idMidi <= 0 ? 0 : idMidi);
}

Something like this would do exactly what you want.


ALSO, just a heads-up, doing this is uh... super complicated for no reason.

int messageTrace[1000];
int format[] = "%s%s%s%d%s%d";
sprintf(messageTrace, format, "Try to play: '", enchancedName, "' / ", enchancedTrack, 
	"\nDefault midi: ", idMidi);
TraceS(messageTrace); 

You can just do this instead:

printf("%s%s%s%d%s%d", "Try to play: '", enchancedName, "' / ", enchancedTrack, 
	"\nDefault midi: ", idMidi);

or even more efficiently, this:

printf("Try to play: '%s' / %d\nDefault midi: %d", enchancedName, enchancedTrack, idMidi);

  • Pier likes this

#15 Pier

Pier

    Newbie

  • Members

Posted 21 October 2023 - 05:41 AM

 

Aye, what you did here is store 4 string LITERALS in the array. A string literal ceases to exist immediately at the end of that line, so, the array is left invalid. The correct way to do this would be to do the arrays out separately, so:

char32 battle1[] = "Coromon_Battle.mp3";
char32 battle2[] = "Triforce_Heroes_Boss.mp3";
char32 battle3[] = "Isabelle_Z1.nsf";
char32 battle4[] = "Isabelle_Z2.nsf";
char32 ENCHANCHED_MUSICS_BOSS[] = {battle1, battle2, battle3, battle4};

Also, `const` does not prevent you from changing the contents of an array. And to answer an immediate question of "but doesn't that eat up more global arrays?" to which the answer is "it eats up 5 global arrays, which is REQUIRED to do this".

However, if you want to avoid using global arrays, there's a MUCH EASIER way to do this!
 

enum
{
	MUSIC_COROMON_BATTLE,
	MUSIC_TRIHEROES_BOSS,
	MUSIC_ISABELLE_Z1,
	MUSIC_ISABELLE_Z2,
	MAX_MUSIC
};
void getMusicName(char32 buf, int musicID)
{
	switch(musicID)
	{
		case MUSIC_COROMON_BATTLE:
			strcpy(buf, "Coromon_Battle.mp3");
			break;
		case MUSIC_TRIHEROES_BOSS:
			strcpy(buf, "Triforce_Heroes_Boss.mp3");
			break;
		case MUSIC_ISABELLE_Z1:
			strcpy(buf, "Isabelle_Z1.nsf");
			break;
		case MUSIC_ISABELLE_Z2:
			strcpy(buf, "Isabelle_Z2.nsf");
			break;
	}
}
bool playEnhancedMusic(int musicID, int track)
{
	char32 buf[2048];
	getMusicName(buf, musicID);
	return Audio->PlayEnhancedMusic(buf, track);
}
void playChosenMusic(int idMidi, int enhancedID, int enhancedTrack)
{
	unless(playEnhancedMusic(enhancedID, enhancedTrack))
		Audio->PlayMIDI(idMidi <= 0 ? 0 : idMidi);
}

Something like this would do exactly what you want.


ALSO, just a heads-up, doing this is uh... super complicated for no reason.

int messageTrace[1000];
int format[] = "%s%s%s%d%s%d";
sprintf(messageTrace, format, "Try to play: '", enchancedName, "' / ", enchancedTrack, 
	"\nDefault midi: ", idMidi);
TraceS(messageTrace); 

You can just do this instead:

printf("%s%s%s%d%s%d", "Try to play: '", enchancedName, "' / ", enchancedTrack, 
	"\nDefault midi: ", idMidi);

or even more efficiently, this:

printf("Try to play: '%s' / %d\nDefault midi: %d", enchancedName, enchancedTrack, idMidi);

Thanks for the solution. I didn't know these things about ZScript because this is kinda hard to find documentation and there are more about variables and functions of each object (Link, Game, Screen, FFC, etc...) than the basics like array or strings manipulation (and there are also incomplete because there are some variables/functions that they don't mention or missing code examples replaced by a bid "TODO").

 

Now the script is good and I have just to make a doc to explain how to install and use it for my friend.





Also tagged with one or more of these keywords: 2.55, Alpha 155, NPC Script

2 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users


    Bing (1)