JazzJackrabbit Community Forums

JazzJackrabbit Community Forums (http://www.jazz2online.com/jcf/index.php)
-   JCS & Scripting (http://www.jazz2online.com/jcf/forumdisplay.php?f=5)
-   -   AngelScript Requests & Help (http://www.jazz2online.com/jcf/showthread.php?t=19503)

PurpleJazz Jan 6, 2018 09:23 AM

Quote:

Originally Posted by AvalancheMaster (Post 492341)
Question time!

I want to combine the Slide event in my level with Steady Light. I'm using Mez03, and I want the neon-tube-like animated platforms to light up in the dark. So far I have no idea how to accomplish that.

Also, while on the same topic, is there a way to change the "Illuminate Surroundings" parameter to act as a steady light, rather than Flickering?

I assume this is the only time you're using the Slide event? If that's the case, try the following script:

Code:

void onLevelBegin() {
    for (int x = 0; x < jjLayerWidth[4]; x++) {
        for (int y = 0; y < jjLayerHeight[4]; y++) {
            if (jjEventGet(x,y) == AREA::SLIDE) {
                jjOBJ@ o = jjObjects[jjAddObject(OBJECT::STEADYLIGHT, (x*32)+16, (y*32)+16)]; //lights go in the middle of tiles
                o.light = 20;
                o.lightType = 3;
            }
        }
    }
}

Again, assuming there's consistency in the objects you're using illuminate surroundings on, you can set their light parameter to 20 and lightType to 3 to achieve the same result as above.

AvalancheMaster Jan 6, 2018 10:12 AM

That doesn't seem to do the job. Also, doesn't lightType use names?

Violet CLM Jan 6, 2018 10:33 AM

I'm guessing yours is a single player level and PJ is writing code for a multiplayer level (or at least testing it in a single screen). Try o.deactivates = false;?

AvalancheMaster Jan 6, 2018 10:41 AM

Single Player indeed. I included that line, but it still doesn't seem to work.

PurpleJazz Jan 6, 2018 11:38 AM

Quote:

Originally Posted by AvalancheMaster (Post 492346)
Single Player indeed. I included that line, but it still doesn't seem to work.

My bad; I overlooked the fact that your code was probably intended for SP and forgot to accommodate it accordingly.

The following script has been tested and works perfectly for me, so if this still doesn't work it means that there may be an error elsewhere in your script.

Code:

void onLevelBegin() {
    for (int x = 0; x < jjLayerWidth[4]; x++) {
        for (int y = 0; y < jjLayerHeight[4]; y++) {
            if (jjEventGet(x,y) == AREA::SLIDE) {
                jjOBJ@ o = jjObjects[jjAddObject(OBJECT::STEADYLIGHT, (x*32)+16, (y*32)+16)]; //add in middle of tiles
                o.light = 20;
                o.lightType = 3;
                o.deactivates = false;
            }
        }
    }
}

void onLevelReload() {
    onLevelBegin(); //add lights again after death
}


AvalancheMaster Jan 7, 2018 01:46 PM

Thanks, that worked!

I'm also trying to make those areas one-way, as well as slide. Is there any way to do this?

So far any attempt to achieve this has resulted either in Slide-only, or One-way-only tiles, depending on the ordering of the code.
Code:

void onLevelBegin() {
    for (int x = 0; x < jjLayerWidth[4]; x++) {
        for (int y = 0; y < jjLayerHeight[4]; y++) {
            if (jjEventGet(x,y) == AREA::SLIDE) {
                jjOBJ@ xenonlight = jjObjects[jjAddObject(OBJECT::STEADYLIGHT, (x*32)+16, (y*32)+16)]; //add in middle of tiles
                xenonlight.light = 20;
                xenonlight.lightType = 3;
                xenonlight.deactivates = false;
                jjEventSet(x, y, AREA::ONEWAY);
                jjEventSet(x, y, AREA::SLIDE);
            }
        }
    }

Also, I am wondering if there is a way to force an animation on a specific frame, depending on player's location. Because my idea is somewhat complicated, let me try to provide an example:

If you have a 10-frame animation, with frames a[1], a[2], a[3], a[4], a[5] ... a[9], a[10], instead of using speed to control the animation, you are using player's position. As the player moves in a positive direction (x and y get bigger), the frames move in chronological order. As the player moves in a negative direction (x and y get smaller), the animation runs in reverse.

When the player stops moving, the animation also stops.

This should apply to all layers, so an animation is the only feasible way I can imagine implementing this.

I want to try to implement faux reflections in a tileset, that's the whole idea behind my question.

happygreenfrog Jan 22, 2018 08:11 AM

So, I'm trying to code a custom boss that summons a bunch of enemies, including summoning low HP bosses as enemies, but I've run into an issue: I'm having a hard time making things immune to (or at least not 1HKO'd by) Spaz's kick and Jazz's uppercut. Setting things to use playerHandling mode HANDLING::SPECIAL "works"... but also removes non-bullet collision detection for most enemies and the main boss (the latter would actually be a good thing if it was consistent, since I don't want it to be hurt by anything other than time passing anyways, but it's too inconsistent to be useful). It also has an issue where setting the bosses to have that collision mode means they beat the level when killed, when you're only supposed to win the level by letting the main boss run out of HP. Basically, it's supposed to be a survival challenge.

Here's the full script for the level as of now, including an attempt at a work-around for the issue where killing bosses clears the level (the work-around works, but only about half the time for some reason):
Code:

#include "MLLE-Include-1.4.asc"
const bool MLLESetupSuccessful = MLLE::Setup();
#pragma require "Tube.j2t"
#pragma require "Medivo.j2t"
#pragma require "hgfCow-MLLE-Data-1.j2l"
#pragma require "Carrot1.j2t"
#pragma require "hgfCow.j2l"

//a special thanks to Sir Ementaler and Violet CLM for their assistance with writing the script!

void onLevelLoad() {
 jjObjectPresets[OBJECT::DEVILDEVAN].behavior = cowBoss;
 jjObjectPresets[OBJECT::DEVILDEVAN].isFreezable = false;
 jjObjectPresets[OBJECT::DEVILDEVAN].playerHandling = HANDLING::SPECIAL;
 jjObjectPresets[OBJECT::DEVILDEVAN].scriptedCollisions = true;
 jjObjectPresets[OBJECT::DEVILDEVAN].energy = 50;
 jjObjectPresets[OBJECT::DEVILDEVAN].bulletHandling = HANDLING::DESTROYBULLET;
 
 jjObjectPresets[OBJECT::BILSY].bulletHandling = HANDLING::HURTBYBULLET;
 jjObjectPresets[OBJECT::BILSY].playerHandling = HANDLING::ENEMY;
 jjObjectPresets[OBJECT::BILSY].energy = 14;
 jjObjectPresets[OBJECT::BUBBA].bulletHandling = HANDLING::HURTBYBULLET;
 jjObjectPresets[OBJECT::BUBBA].playerHandling = HANDLING::ENEMY;
 jjObjectPresets[OBJECT::BUBBA].energy = 5+(jjDifficulty*4);
 jjObjectPresets[OBJECT::TUFBOSS].bulletHandling = HANDLING::HURTBYBULLET;
 jjObjectPresets[OBJECT::TUFBOSS].playerHandling = HANDLING::ENEMY;
 jjObjectPresets[OBJECT::TUFBOSS].energy = 7+(jjDifficulty*4);
 jjObjectPresets[OBJECT::BOLLY].bulletHandling = HANDLING::HURTBYBULLET;
 jjObjectPresets[OBJECT::BOLLY].playerHandling = HANDLING::SPECIAL;
 jjObjectPresets[OBJECT::BOLLY].energy = 10;
 jjObjectPresets[OBJECT::UTERUS].bulletHandling = HANDLING::HURTBYBULLET;
 jjObjectPresets[OBJECT::UTERUS].playerHandling = HANDLING::ENEMY;
 jjObjectPresets[OBJECT::UTERUS].energy = 10;
 jjObjectPresets[OBJECT::ROBOT].bulletHandling = HANDLING::HURTBYBULLET;
 jjObjectPresets[OBJECT::ROBOT].playerHandling = HANDLING::ENEMY;
 jjObjectPresets[OBJECT::ROBOT].energy = 30;
 
 //just a thing I borrowed from plusPixelMapEx
 jjANIMATION@ anim = jjAnimations[jjObjectPresets[OBJECT::DEVILDEVAN].curAnim];
 anim.frameCount = 1;
 jjANIMFRAME@ frame = jjAnimFrames[anim.firstFrame];
 jjPIXELMAP kingCow(459*32, 3*32, 6*32, 7*32, 4);
 kingCow.save(frame);
 frame.hotSpotX = -frame.width / 2;
 frame.hotSpotY = -frame.height / 2;
}

void onLevelBegin(){
//make sure the main song reloads after encountering the boss
jjMusicLoad("Meatball Parade.ogg");
}

//int rPart(float x,float y) { // render a part of Devan Force
// jjDrawSprite(x, y, ANIM::BIGROCK, 0, 0, 0);
 //void jjDrawSprite(x, y, uint8 (ANIM::BIGROCK), uint8 (0), uint8 (0), int direction = 0, SPRITE::Mode mode = SPRITE::NORMAL, int param = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)
// return 0;
//}

void cowBoss(jjOBJ@ obj) {
 switch (obj.state) {
  case STATE::START:
  //don't start boss until player has reached boss activation point
  obj.state = STATE::DELAYEDSTART;
  case STATE::DELAYEDSTART:
    //loop players
    for (int i = 0; i < jjLocalPlayerCount; ++i) {
    jjPLAYER@ localPlayer = jjLocalPlayers[i];
    if (localPlayer.bossActivated) {
      localPlayer.boss = obj.objectID;
      obj.state = STATE::START;
    }
    }
    if (obj.state == STATE::START) {
        //set object to be handled in a more normal way, since we're using Devil Devan as the "base"
    obj.playerHandling = HANDLING::SPECIAL;
        //load song
        jjMusicLoad("dang.j2b");
        //start boss
        obj.state = STATE::ATTACK;
        //initialize some important variables
        obj.age = 60; //boss attack cooldown
        obj.special = 0; //next attack the boss is going to use
        obj.var[0] = 0; //move direction
        obj.var[1] = 0; //reticle height
        obj.var[2] = 10; //carrot delay
    break;
    }
    return;
  case STATE::KILL:
  //start next stage
  jjNxt(true, false);
  case STATE::DEACTIVATE:
  //reset boss
  jjMusicLoad("Meatball Parade.ogg");
  obj.deactivate();
  break;
  case STATE::ATTACK:
  //draw boss
  jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::NORMAL, 24);
  //set cow as boss
  for (int i = 0; i < jjLocalPlayerCount; ++i) {
    jjPLAYER@ localPlayer = jjLocalPlayers[i];
    if (localPlayer.bossActivated) {
      localPlayer.boss = obj.objectID;
    }
        }
       
        //just some old code I didn't completely remove
        //for (int i = 1; i < jjObjectCount; i++){
        // jjOBJ@ o = jjObjects[i];
        // if (o.eventID == OBJECT::BOLLY){obj.var[0] == 1;}
        // if (o.eventID == OBJECT::BUBBA){obj.var[0] == 1;}
        // if (o.eventID == OBJECT::BILSY){obj.var[0] == 1;}
        // if (o.eventID == OBJECT::TUFBOSS){obj.var[0] == 1;}
        //}
        //if (obj.var[0] == 0){}
       
        //constantly cool down attack
        obj.age -= 1;
        //prepare summon
        if (obj.age <= 0) {
        //just some more old code I didn't completely remove
        //if (obj.special == 1){jjAddObject(OBJECT::TUFBOSS, obj.xPos, obj.yPos+96);}
        //if (obj.special == 2){jjAddObject(OBJECT::BUBBA, obj.xPos, obj.yPos);}
        //if (obj.special == 2){jjAddObject(OBJECT::BOLLY, obj.xPos, obj.yPos);}
        //if (obj.special == 2){jjAddObject(OBJECT::UTERUS, obj.xPos, obj.yPos);}
        //if (obj.special == 3){jjAddObject(OBJECT::BILSY, obj.xPos, obj.yPos);}
       
        //summon an enemy based on next attack chosen
        if (obj.special == 0){jjObjects[jjAddObject(OBJECT::LIZARD, obj.xPos, obj.yPos)].playerHandling = HANDLING::SPECIAL;}
        if (obj.special == 1){jjObjects[jjAddObject(OBJECT::BAT, obj.xPos, obj.yPos+120)].playerHandling = HANDLING::SPECIAL;}
        if (obj.special == 2){jjObjects[jjAddObject(OBJECT::RAVEN, obj.xPos, obj.yPos+120)].playerHandling = HANDLING::SPECIAL;}
        if (obj.special == 3){jjObjects[jjAddObject(OBJECT::FENCER, obj.xPos, obj.yPos)].playerHandling = HANDLING::SPECIAL;}
        if (obj.special == 4){jjObjects[jjAddObject(OBJECT::BEE, obj.xPos, obj.yPos+120)].playerHandling = HANDLING::SPECIAL;}
        if (obj.special == 5){jjObjects[jjAddObject(OBJECT::TUFBOSS, obj.xPos, obj.yPos+120)].playerHandling = HANDLING::SPECIAL;}
        //if (obj.special == 6){jjObjects[jjAddObject(OBJECT::BEE, obj.xPos, obj.yPos+120)].playerHandling = HANDLING::SPECIAL;
        // if(jjDifficulty >= 2){jjObjects[jjAddObject(OBJECT::BEE, obj.xPos+10, obj.yPos+120)].playerHandling = HANDLING::SPECIAL;
        // jjObjects[jjAddObject(OBJECT::BEE, obj.xPos-10, obj.yPos+120)].playerHandling = HANDLING::SPECIAL;}}
        if (obj.special == 6){jjObjects[jjAddObject(OBJECT::BEE, obj.xPos, obj.yPos+120)].playerHandling = HANDLING::SPECIAL;
          if(jjDifficulty >= 2){jjObjects[jjAddObject(OBJECT::BEE, obj.xPos+10, obj.yPos+120)].playerHandling = HANDLING::SPECIAL;}}
        if (obj.special == 7){jjObjects[jjAddObject(OBJECT::BUBBA, obj.xPos, obj.yPos)].playerHandling = HANDLING::SPECIAL;}
        if (obj.special == 8){jjObjects[jjAddObject(OBJECT::LIZARD, obj.xPos, obj.yPos)].playerHandling = HANDLING::SPECIAL;
          if(jjDifficulty >= 2){jjObjects[jjAddObject(OBJECT::LIZARD, obj.xPos+10, obj.yPos)].playerHandling = HANDLING::SPECIAL;
          jjObjects[jjAddObject(OBJECT::LIZARD, obj.xPos-10, obj.yPos)].playerHandling = HANDLING::SPECIAL;}}
        if (obj.special == 9 && jjDifficulty >= 2){jjObjects[jjAddObject(OBJECT::BILSY, obj.xPos, obj.yPos)].playerHandling = HANDLING::SPECIAL;}
        if (obj.special == 9 && jjDifficulty == 1){jjObjects[jjAddObject(OBJECT::BAT, obj.xPos, obj.yPos+120)].playerHandling = HANDLING::SPECIAL;}
        if (obj.special == 9 && jjDifficulty == 0){jjAddObject(OBJECT::CARROT, obj.xPos, obj.yPos+140);}
        //if (obj.special == 10){jjObjects[jjAddObject(OBJECT::FENCER, obj.xPos, obj.yPos)]; if(jjDifficulty >= 2){jjObjects[jjAddObject(OBJECT::FENCER, obj.xPos+10, obj.yPos)]; jjObjects[jjAddObject(OBJECT::FENCER, obj.xPos-10, obj.yPos)];}}
        if (obj.special == 10){jjObjects[jjAddObject(OBJECT::FENCER, obj.xPos, obj.yPos)].playerHandling = HANDLING::SPECIAL;
          if(jjDifficulty >= 2){jjObjects[jjAddObject(OBJECT::FENCER, obj.xPos+10, obj.yPos)].playerHandling = HANDLING::SPECIAL;}}
        //reset cooldown
        obj.age = 180;
        if (jjDifficulty == 2){obj.age = 150;}
        if (jjDifficulty >= 3){obj.age = 120;}
        //select next move
        obj.special += 1;
        //progress towards next carrot
        obj.var[2] = obj.var[2]-1;
        //hurt boss
        obj.energy -= 1;
        }
        //loop attack pattern
        if (obj.special >= 11){
        obj.special = 5;
        }
        if (obj.var[2] <= 0){
        if (jjDifficulty <= 2){jjAddObject(OBJECT::CARROT, obj.xPos, obj.yPos+140);}
        if (jjDifficulty >= 3){jjAddObject(OBJECT::SEEKERAMMO3, obj.xPos, obj.yPos+140);}
        obj.var[2] = 10;
        }
        //end boss
        if (obj.energy <= 0){obj.bulletHandling = HANDLING::DESTROYBULLET; obj.state = STATE::KILL;}
        //turn around
    if (obj.xPos <= 495*32){obj.var[0]=1;}
        if (obj.xPos >= 506*32){obj.var[0]=0;}
        //move
        if (obj.var[0] == 0){obj.xPos -=1;}
        if (obj.var[0] == 1){obj.xPos +=1;}
       
        //set reticle height
        if (obj.special == 0 || obj.special == 3 || obj.special == 5 || obj.special == 7 || obj.special == 8 && obj.special != 9){obj.var[1]=230;}
        if (obj.special == 1 || obj.special == 2 || obj.special == 4 || obj.special == 6){obj.var[1]=120;}
        if (obj.special == 9 && jjDifficulty >= 2){obj.var[1]=230;}
        if (obj.special == 9 && jjDifficulty == 1){obj.var[1]=120;}
        if (obj.special == 9 && jjDifficulty == 0){obj.var[1]=9001;}
        //display reticle
        if (obj.age < 45 && obj.age % 6 <= 2){jjDrawSprite(obj.xPos, obj.yPos+obj.var[1], ANIM::PLUS_RETICLES, 2, 0, 0);}
        //attempted work-around for killing bosses, this works about half the time
        for (int i = 1; i < jjObjectCount; i++){
        jjOBJ@ o = jjObjects[i];
        if (o.state == STATE::DONE){jjDeleteObject(o.objectID);}
        }
  default:
  //empty
 }
}

Anybody have any ideas on how to fix the problems I've run into?

EDIT: Upon thinking about it further, I'm starting to think the easiest solution would be to disable Jazz's uppercut and Spaz's kick during the boss battle. Any easy way to go about doing that?

EDIT 2: I managed to figure it out on my own, never mind.

DennisKainz Jan 31, 2019 01:24 PM

I was going to upload a small mutator that allows players to grab ledges and lift themselves on them (as in Earthworm Jim and Commander Keen). However, there are a few problems...
Here's the script:
Code:

array hang = {0, 0, 0, 0};
array hangLeft = {false, false, false, false};
array hangRight = {false, false, false, false};

void onMain() {
        for (uint i = 0; i < 3; i++) {
                jjPLAYER@ player = jjLocalPlayers[i];
                if (player.isLocal) {
                        if (player.ySpeed > 0 && hangLeft[i] == false && hangRight[i] == false)
                                {
                                if (!jjMaskedPixel(player.xPos - 16, player.yPos - 16) && jjMaskedVLine(player.xPos - 16, player.yPos - 14, 8) && player.direction < 0)
                                        {
                                        hang[i] = 1;
                                        hangLeft[i] = true;
                                        }
                                if (!jjMaskedPixel(player.xPos + 16, player.yPos - 16) && jjMaskedVLine(player.xPos + 16, player.yPos - 14, 8) && player.direction > 0)
                                        {
                                        hang[i] = 1;
                                        hangRight[i] = true;
                                        }
                                }
                        if (hang[i] > 0)
                                {
                                player.invisibility = true;
                                hang[i] = hang[i] + 1;
                                if (hangLeft[i] == true)
                                        {
                                        player.xPos = player.xPos - 0.875;
                                        jjDrawSprite(player.xPos, player.yPos, ANIM::JAZZ, 20, 0 + (hang[i] / 3), -1, SPRITE::NORMAL, 0);
                                        }
                                if (hangRight[i] == true)
                                        {
                                        player.xPos = player.xPos + 0.875;
                                        jjDrawSprite(player.xPos, player.yPos, ANIM::JAZZ, 20, 0 + (hang[i] / 3), 1, SPRITE::NORMAL, 0);
                                        }
                                player.yPos = player.yPos - 1;
                                player.xSpeed = 0;
                                player.ySpeed = 0;
                                }
                        if (hang[i] > 30 && !jjMaskedPixel(player.xPos, player.yPos + 22))
                                {
                                player.invisibility = false;
                                hang[i] = 0;
                                hangLeft[i] = false;
                                hangRight[i] = false;
                                }
                        }
                }
        }

For now, I only made it for Jazz.
I can't find a way to determine the animation of the player itself, so I had to make the player invisible and draw the sprite in front of him instead. This gives the player the default skin color while he's hanging, which is wrong. I need a way to determine the player's current animation, rather than drawing upon him.
Oh, right! There also is this problem with the RABBIT::Anim where I simply can't find the one corresponding to Jazz grabbing the ledge. I had to use the numeral value, which probably differs between 1.23 and 1.24.

Violet CLM Jan 31, 2019 09:20 PM

That's a neat idea for a mutator!
Quote:

Originally Posted by DennisKainz (Post 493594)
I need a way to determine the player's current animation, rather than drawing upon him.

You can't do this yet, because the rabbit animation code is sufficiently complicated that we haven't come up with a good API for messing with it. But you should be able to solve your problem in the short-term by using SPRITE::PLAYER, player.playerID instead of SPRITE::NORMAL, 0.
Quote:

Oh, right! There also is this problem with the RABBIT::Anim where I simply can't find the one corresponding to Jazz grabbing the ledge. I had to use the numeral value, which probably differs between 1.23 and 1.24.
The ledge-climbing animations were removed in 1.24, which is why they don't have a RABBIT::Anim entry (and why Lori doesn't have those animations at all).

headshot2018 Feb 7, 2019 01:10 PM

hey, quick question.

is it possible to draw a custom scoreboard HUD in non-coop gametypes like CTF or battle?
i've been trying to define "bool onDrawScore()", but that seems to only alter the co-op score. is it possible?

thank you in advance.

Seren Feb 7, 2019 03:30 PM

At present, the score display in game modes other than SP and coop may not be altered, aside from drastic measures such as overwriting font sprites, which certainly wouldn't be limited in scope to said display and would probably quite negatively affect the game. Remaining HUD functions such as onDrawAmmo may still be used to display other information beside the usual.

headshot2018 Feb 7, 2019 05:01 PM

oh... that's a bummer.

oh well, i'm gonna hold off on my project until it's possible to draw over CTF score, i guess.
thank you again.

chandie Jan 28, 2020 01:47 AM

I'd like to use a regular enemy as a boss. I don't intend to change his behavior. I only change his sprites with tiles or replace them with JJ1Enemies sometimes.

I tried creating a behavior for the enemy and implement the if code below but it doesn't work.

if ( boss.energy==0&&++boss.counter>2300)
jjEventSet(play.xPos / 32, play.yPos / 32, AREA::EOL);

Is there a practical way of putting a health bar and an automatic EOL event when his energy is 0?

I am pretty new at scripting btw. Treat me as a noobie :)

Seren Jan 28, 2020 07:25 AM

Quote:

Originally Posted by chandie (Post 494057)
Is there a practical way of putting a health bar and an automatic EOL event when his energy is 0?

Hi! Awesome that you're learning JJ2+ scripting. The scripting documentation describes several entities that should help you.

A boss health bar is drawn for players if their bossActivated is true and boss refers to an object by its ID. There's also an accompanying activateBoss method, or you could use the traditional Activate Boss event. See especially the documentation for boss, as it describes how the health bar's size is determined.

Most JJ2 boss behaviors work the following way: the boss starts out inactive and its behavior has a loop over all players that checks if any active player's bossActivated is true. If it is, the boss changes its state to something else, indicating that it's now active. Then, whenever the boss is active, it runs a loop that sets all players' boss to itself. This results in the health bar being drawn.

When the boss is defeated and its energy is 0, the game doesn't despawn it, and instead (much like you, it seems) uses its behavior as a countdown to end the level. First, when the counter is 0, it might display some message much like showText. Then it waits between 4 and 6.5 seconds before it proceeds to the next level, depending on whether the player is firing bullets, for some weird reason (maybe under the assumption that if they're firing, they're getting impatient? Either way it's currently a speedrun strat to keep shooting after defeating a boss). Your magic number of 2300 seems pretty big as, at 70 ticks per second, that corresponds to over half a minute. Your strategy of spawning an EOL event seems valid, but it's generally a better idea to call jjNxt with default parameters.

To sum up the order of events in a typical boss fight:
  1. The boss is inactive.
  2. The player touches an Activate Boss event. This sets their bossActivated to true.
  3. The dormant boss detects that there's a player with an active boss fight so it wakes up.
  4. The boss sets all players' boss property to itself. This lets the health bar know what size it should have.
  5. Players defeat the boss. The boss behavior may display a text message.
  6. After a few seconds, the boss behavior ends the current level.

Violet CLM Jan 28, 2020 08:57 AM

One possible additional consideration is your boss.energy==0 condition. Several weapons do more than 1 damage, so it's easy for an enemy to end up with a negative energy number instead of exactly 0. (Some standard enemies/bosses check for this and immediately set energy to 0, others set it to -1, and probably others don't do anything at all.)

chandie Jan 29, 2020 04:41 AM

JJNxt for the end boss
 
Quote:

Originally Posted by Seren (Post 479773)
A boss health bar is drawn for players if their bossActivated is true and boss refers to an object by its ID. There's also an accompanying activateBoss method, or you could use the traditional Activate Boss event. See especially the documentation for boss, as it describes how the health bar's size is determined.

Quote:

Originally Posted by Violet CLM (Post 494059)
One possible additional consideration is your boss.energy==0 condition. Several weapons do more than 1 damage, so it's easy for an enemy to end up with a negative energy number instead of exactly 0. (Some standard enemies/bosses check for this and immediately set energy to 0, others set it to -1, and probably others don't do anything at all.)

Thank you for the advices. :) I've managed to make the boss work. However the script stops working when I try to end the level automatically. I think what I've been doing wrong is not to figure where to implement the jjNXT. Here's my code. I've tried to create a JJ1 Turtle Goon Boss, like the one in Diamondus Secret.

Code:

#include "Jazz1Enemies v05.asc"
#include "Resize v11.asc"
#include "TrueColor v13.asc"
bool Boss=false;

void onLevelLoad() {
        Jazz1::MakeEnemy(OBJECT::NORMTURTLE, Jazz1::Enemies::Diamondus_TurtleGoon, true);
        jjObjectPresets[OBJECT::NORMTURTLE].behavior = NORMTURTBOSS;
        jjObjectPresets[OBJECT::NORMTURTLE].energy = 100;
        jjObjectPresets[OBJECT::NORMTURTLE].points = 200;
        }

void onFunction0(jjPLAYER@ play) {
                Boss=true;
play.boss=jjAddObject(OBJECT::NORMTURTLE, 37*32, 54*32);
        }

void NORMTURTBOSS(jjOBJ@ boss) {
                jjPLAYER@ play = jjLocalPlayers[0];
        if (boss.energy==0&&++boss.counter>2300) 
                        jjNxt(bool warp = false, bool fast = false);
        }


Violet CLM Jan 29, 2020 05:42 PM

The main problem you're experiencing is this line: jjNxt(bool warp = false, bool fast = false); Those bits inside the parentheses are supposed to indicate the default values for those arguments, meaning that if you just type jjNxt(); it'll be interpreted the same as if you had typed jjNxt(false, false);. Typing out the parameter names (and types) doesn't help.

The next thing that's going to bite you is when you write jjObjectPresets[OBJECT::NORMTURTLE].behavior = NORMTURTBOSS; you are getting rid of the previous behavior, which included code for moving the object, drawing the object, and handling player collision and bullet collision.

chandie Jan 30, 2020 05:21 AM

OK. Solved the problem. But sadly it doesn't work with JJ1 Enemies function. Because when I remove the JJ1Enemies function it works fine. It's main code probably considers it as a normal enemy and the counter does not start. So the level does not end. I tried removing the counter problem by trying to end the level when his energy=1 but it didn't work either. :(

Violet CLM Feb 11, 2020 12:18 AM

Very delayed response, but I don't think I can tell exactly what you're doing from your description. Troubleshooting is generally easiest in response to actual code, like in your previous post, preferably the minimum amount of code that demonstrates the problem in question. What does "works fine" look like in code? What does not removing the JJ1Enemies function look like in code?

chandie Mar 6, 2020 07:33 AM

Turtle Boss
 
And another delayed response from me :D
It's been a while and I didn't keep the code. However I've tried using Normal Turtle animations and a new behavior in order to make it work and it helped. Here's the thing:

Code:

bool boss=false;


                        void onLevelLoad()  {
       
        jjObjectPresets[OBJECT::PURPLEGEM].points = 2000;               
        jjObjectPresets[OBJECT::NORMTURTLE].behavior = TURTBOSS;
        jjObjectPresets[OBJECT::NORMTURTLE].points = 5000;
        jjObjectPresets[OBJECT::NORMTURTLE].energy = 50;
        jjObjectPresets[OBJECT::NORMTURTLE].bulletHandling = HANDLING::HURTBYBULLET;
        jjObjectPresets[OBJECT::NORMTURTLE].state = STATE::WALK;

        }
       
       

        void onFunction0(jjPLAYER@ play) {
        play.activateBoss();
        boss=true;
        {play.boss=jjAddObject(OBJECT::NORMTURTLE, 38*32, 55*32);}
                jjMusicLoad("boss.s3m");
                play.activateBoss(true);

        }       

       
                void TURTBOSS(jjOBJ@ boss) {

                jjPLAYER@ play = jjLocalPlayers[0];
       
                       
        if ( boss.energy==0)
        {       
                boss.counter==0;
                boss.counter += 0;
                boss.determineCurAnim(ANIM::TURTLE, 7);
                boss.determineCurFrame();
                boss.frameID = boss.counter/56;
                jjDrawSpriteFromCurFrame(boss.xPos, boss.yPos, boss.curFrame, boss.direction, SPRITE::NORMAL);
                ++boss.counter;
       
        if ( boss.energy==0&&++boss.counter>20) 
                        jjEventSet(play.xPos / 32, play.yPos / 32, AREA::EOL);
        }
       
        if ((boss.xPos > play.xPos+3 || boss.xPos < play.xPos-3)&& boss.state != STATE::KILL)
        {boss.direction = (play.xPos < boss.xPos) ? +1 : +1;}
        else
                                {
                                boss.direction==1;}
        if (boss.yPos < 54*32)
        {boss.yPos = boss.yPos;}
        if (boss.xPos > 24*32)
        {boss.xPos = boss.xPos;}
                switch (boss.state) {
               
        case STATE::WALK:
        boss.behave(BEHAVIOR::WALKINGENEMY);
        {
                if (boss.counter >= 180)
                {boss.counter = 0; }
 
                boss.counter++;
                if (u==0||u==1)
                {if (++boss.counter < 180){
                        boss.xSpeed= (1+1)*boss.direction;
                        boss.determineCurAnim(ANIM::TURTLE, 7);
                        if (boss.counter == 10){
                        jjSample(boss.xPos, boss.yPos, SOUND::INTRO_GREN3);}
                        if (boss.justHit == 0)
                        {jjDrawSpriteFromCurFrame(boss.xPos, boss.yPos, boss.curFrame, boss.direction, SPRITE::NORMAL);}
                        boss.determineCurFrame();
                        //boss.frameID = boss.counter/10;
                                                }
                       
                                break;               
                                               
                                               
         
                        } }
                                                case STATE::FREEZE:
                        if (boss.freeze > 0) {
                                boss.draw();
                                boss.freeze -= 4;
                        }
                        if (boss.freeze < 4) {
                                boss.unfreeze(0);
                                boss.state = boss.oldState;
                        }
                       
                        break;
        case STATE::KILL:
        case STATE::DEACTIVATE:
                boss.deactivate();
                break;

               
                                                                               
                                                }
                                        //if (boss.justHit == 0)
                //jjDrawSpriteFromCurFrame(boss.xPos, boss.yPos, boss.curFrame, boss.direction, SPRITE::NORMAL);
                //else jjDrawSpriteFromCurFrame(boss.xPos, boss.yPos, boss.curFrame, boss.direction, SPRITE::SINGLECOLOR,15);                       
                                                }


chandie Mar 7, 2020 11:40 PM

Have another question though. Is there a way to combine water level event with destruct scenery? I mean a shootable water level event like the ones in dreempipes.

Violet CLM Mar 9, 2020 11:55 PM

Quote:

Originally Posted by chandie (Post 494084)
However I've tried using Normal Turtle animations and a new behavior in order to make it work and it helped.

Broadly speaking, it seems to be working, at least once I get rid of the if (u==0||u==1) line which I assume refers to a variable you didn't include in that snippet. You are running into an issue where killing it using a physical attack fails to cause the level to end, but jObjectPresets[OBJECT::NORMTURTLE].playerHandling = HANDLING::ENEMY; should fix that. Also you probably don't want STATE::KILL to call boss.deactivate().
Quote:

I mean a shootable water level event like the ones in dreempipes.
Sure... jjSetWaterLevel exists. You may need to figure out jjBEHAVIORINTERFACE and its onObjectHit method, though, since I'm guessing you want the object to react only to being shot by bullets, not to being buttstomped.

chandie Mar 10, 2020 08:09 AM

Walking Enemy
 
Quote:

Originally Posted by Violet CLM (Post 494097)
You are running into an issue where killing it using a physical attack fails to cause the level to end, but jObjectPresets[OBJECT::NORMTURTLE].playerHandling = HANDLING::ENEMY; should fix that. Also you probably don't want STATE::KILL to call boss.deactivate().

Thanks for the tips Violet. Yeah with playerHandling command EOL works with physical attack.


Quote:

Originally Posted by Violet CLM (Post 494097)
Sure... jjSetWaterLevel exists. You may need to figure out jjBEHAVIORINTERFACE and its onObjectHit method, though, since I'm guessing you want the object to react only to being shot by bullets, not to being buttstomped.

I solved jjSetWaterLevel with switchtrigger. so it works fine with a trigger zone. but objecthit with bullet seems a little more complicated but I'll keep working on it. Yeah it shouldn't work by buttstomping.


And I have a one last question. I am trying to create a walking enemy from Frog animations. It works but the animation plays under the ground sprite. Here's the code:

Code:


void onLevelLoad() {

        jjObjectPresets[OBJECT::LIZARD].behavior = Frog;
        jjObjectPresets[OBJECT::LIZARD].bulletHandling = HANDLING::HURTBYBULLET;
        jjObjectPresets[OBJECT::LIZARD].playerHandling = HANDLING::ENEMY;

}




void Frog(jjOBJ@ obj) {
        obj.behave(BEHAVIOR::WALKINGENEMY, false);
        obj.determineCurAnim(ANIM::FROG, 12);
        obj.determineCurFrame();
        obj.putOnGround(true);


jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::NORMAL, 0);


}

I've tried some other things like setting WALKINENEMY value to true but it didn't change anything. I've also try adding some negative values to obj.yPos in jjDrawSpriteFromCurFrame. It helps visually. But functionally the enemy is still under the ground. Any suggestions ar highly appreciated. Thank you.

Violet CLM Mar 12, 2020 05:55 PM

First off, you could simplify all that like this:
Code:

void onLevelLoad() {
        jjObjectPresets[OBJECT::LIZARD].determineCurAnim(ANIM::FROG, jjIsTSF ? 12 : 11);
}

You're running into the problem that BEHAVIOR::WALKINGENEMY is one of the only parts of the game that cares about jjANIMFRAME::coldSpotY, but the frog sprites are one of the only land-based animations that don't have that property defined. Walking enemies try to align their hotSpotY point to the surface of the ground they're on, but that number defaults to 0, which is the top of the sprite. Just move it upwards until it looks better:
Code:

        jjANIMATION@ frogWalkAnim = jjAnimations[jjObjectPresets[OBJECT::LIZARD].curAnim];
        for (uint i = 0; i < frogWalkAnim.frameCount; ++i)
                jjAnimFrames[frogWalkAnim.firstFrame + i].coldSpotY = -25;


chandie Mar 13, 2020 12:04 AM

Solved
 
Quote:

Originally Posted by Violet CLM (Post 494101)
First off, you could simplify all that like this:
Code:

void onLevelLoad() {
        jjObjectPresets[OBJECT::LIZARD].determineCurAnim(ANIM::FROG, jjIsTSF ? 12 : 11);
}

You're running into the problem that BEHAVIOR::WALKINGENEMY is one of the only parts of the game that cares about jjANIMFRAME::coldSpotY, but the frog sprites are one of the only land-based animations that don't have that property defined. Walking enemies try to align their hotSpotY point to the surface of the ground they're on, but that number defaults to 0, which is the top of the sprite. Just move it upwards until it looks better:
Code:

        jjANIMATION@ frogWalkAnim = jjAnimations[jjObjectPresets[OBJECT::LIZARD].curAnim];
        for (uint i = 0; i < frogWalkAnim.frameCount; ++i)
                jjAnimFrames[frogWalkAnim.firstFrame + i].coldSpotY = -25;


Solved. thanks a lot :)

chandie Mar 19, 2020 03:00 AM

I am trying to increase/decrease the player's x speed like this:
if(p.keyRight)
p.xSpeed = 20;

It works with void onPlayer but not in bool onObjectHit or void onFunction Any suggestions?

Violet CLM Mar 21, 2020 09:40 AM

There are a couple things you might be running into. One is that JJ2 is of course always changing the player's xSpeed on its own, so if you make a single short-term change, in response to something like one of the onFunction# hooks, then that change is not going to last very long before JJ2's normal speed handling takes over. But onPlayer works constantly instead of as a one-time thing, so naturally it's going to have more visible impact. The other is that p is deprecated and may not (should not) work in all situations, and you should use the actual jjPLAYER@ argument available to onObjectHit and onFunction# instead.

chandie Mar 22, 2020 11:11 PM

So even if I try with jjPLAYER I can’t make a constant speed change.

I could change the speed like this though, again with onPlayer.

if(play.xPos>27 && play.yPos>43)
if(play.keyRight) {
play.xSpeed = 10;
}
if(play.keyLeft) {
play.xSpeed = -10;
}
play.keyRun = true;
}

But that’s not the exact effect I want. I’d like to make a constant speed up (at least for ten seconds or so) regardless of the player pos

Violet CLM Mar 23, 2020 07:41 AM

Ah, okay, I see what you mean. Try something like this:
Code:

int SpeedUpUntil = 0;
void onPlayer(jjPLAYER@ play) {
  if (SpeedUpUntil > jjGameTicks) {
    //adjust player's xspeed and such in here
  }
}

Then whenever you want to turn on the speed up effect, in an onObjectHit or whatever, set SpeedUpUntil = jjGameTicks + 10 * 70; where 10 is the number of seconds you want the effect to last.

(if you want to support cooperative, SpeedUpUntil should be an array<int> instead of a int.)

chandie Mar 24, 2020 04:20 AM

Worked like a charm. Thank you :)


All times are GMT -8. The time now is 12:25 AM.

Powered by vBulletin® Copyright ©2000 - 2026, Jelsoft Enterprises Ltd.
JCF © 2001-Infinity Jazz2Online