Downloads containing SBR.j2as

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: SpazBallRun -ANAS- Test N/A Download file

File preview

#pragma name "SpazBallRun"
#pragma description "Spaz becomes a curved ball. Features 15 runs."

const int ZONE_INI = 0;   // Text param 100

// Per-player state tracking
array<int>  playerZone(32, -1);
array<int>  playerRunNumber(32, 0); 
array<bool> playerIsBall(32, false);
array<bool> playerHasTouchedIni(32, false);
array<bool> playerInfiniteJumps(32, false); // For Text ID 160
array<bool> playerWon(32, false); // For Text ID 255

// Jump Tracking
array<int>  playerJumps(32, 0);
array<bool> wasJumping(32, false);

// Keeps track of which Run texts have already been shown
array<uint> playedRunsMask(32, 0); 

// Custom Zone Coordinates (Hidden from UTM)
array<int> potionX(0), potionY(0);
array<int> unlimitedX(0), unlimitedY(0);
array<int> funX(0), funY(0);

const array<string> runColors = {"\\3", "\\4", "\\5", "\\2", "\\7", "\\6", "\\9", "\\8"};

int introTimer = 420; // 6 seconds total (2s title, 3s Ready, 1s Spin)

// --- Helper Functions ---
string lowercase(string text) {
    for (int i = text.length() - 1; i >= 0; i--) {
        if (text[i] > 64 && text[i] < 91) text[i] ^= 32;
    }
    return text;
}

string shortenNick(string nick) {
    array<string> results;
    while (jjRegexMatch(nick, """(.*)\[(1?\d{1,2}|ini|win)\](\|*\*)?""", results)) {
        nick = results[1];
    }
    return nick;
}

int getZone(jjPLAYER@ play) {
    int x = int(play.xPos) >> 5;
    int y = int(play.yPos) >> 5;
    
    // Check stored custom zones first
    for (uint i = 0; i < funX.length(); i++) {
        if (x == funX[i] && y == funY[i]) return 17;
    }
    for (uint i = 0; i < unlimitedX.length(); i++) {
        if (x == unlimitedX[i] && y == unlimitedY[i]) return 60;
    }
    for (uint i = 0; i < potionX.length(); i++) {
        if (x == potionX[i] && y == potionY[i]) return 150;
    }

    // Normal zone check
    if (x >= 0 && y >= 0 && x < jjLayerWidth[4] && y < jjLayerHeight[4]) {
        if (jjEventGet(x, y) == 0) {
            int param = jjParameterGet(x, y, 0, 8) - 100;
            if (param >= 0) return param;
        }
    }
    return -1;
}

// Easy way to set jump limits for specific runs
int getJumpLimit(int run) {
    if (run == 6) return 10;
    if (run == 7) return 5;
    if (run == 8) return 15;
    if (run == 9) return 10;
    if (run == 10) return 10;
    if (run == 11) return 0; // 0 means unlimited
    if (run == 12) return 50;
    if (run == 13) return 35;
    if (run == 14) return 100;
    return 0; // 0 means unlimited
}

// --- Main Script ---
void onLevelLoad() {
    // Clear coordinate arrays
    potionX.resize(0); potionY.resize(0);
    unlimitedX.resize(0); unlimitedY.resize(0);
    funX.resize(0); funY.resize(0);

    // Scan level for custom zones
    for (int x = jjLayerWidth[4] - 1; x >= 0; --x) {
        for (int y = jjLayerHeight[4] - 1; y >= 0; --y) {
            int ev = jjEventGet(x, y);
            if (ev == AREA::TEXT || ev == 0) {
                int param = jjParameterGet(x, y, 0, 8);
                if (param == 117 || param == 160 || param == 250) {
                    // Save the coordinates to memory
                    if (param == 117) { funX.insertLast(x); funY.insertLast(y); }
                    if (param == 160) { unlimitedX.insertLast(x); unlimitedY.insertLast(y); }
                    if (param == 250) { potionX.insertLast(x); potionY.insertLast(y); }
                    
                    // Erase them from the map so UTM never sees them
                    jjEventSet(x, y, 0);
                    jjParameterSet(x, y, 0, 8, 0); 
                }
            }
        }
    }
}

void onLevelBegin() {
    introTimer = 420; 

    // Now clear the actual run events (UniTM has already scanned them)
    for (int x = jjLayerWidth[4] - 1; x >= 0; --x) {
        for (int y = jjLayerHeight[4] - 1; y >= 0; --y) {
            if (jjEventGet(x, y) == AREA::TEXT) {
                int param = jjParameterGet(x, y, 0, 8);
                if (param >= 100 && param <= 255) {
                    jjEventSet(x, y, 0);
                }
            }
        }
    }
}

void onMain() {
    if (introTimer > 0) {
        introTimer--;
    }

    // Process zone logic and physics for ALL players (local and remote)
    for (int i = 0; i < 32; i++) {
        jjPLAYER@ play = jjPlayers[i];
        if (!play.isActive) continue;

        int zone = getZone(play);

        if (zone != playerZone[i]) {
            playerZone[i] = zone;

            if (zone == ZONE_INI) {
                playerIsBall[i] = false;
                playerRunNumber[i] = 0;
                playerJumps[i] = 0;
                playedRunsMask[i] = 0; 
                playerInfiniteJumps[i] = false;
                playerWon[i] = false; 
                playerHasTouchedIni[i] = true;
                if (play.isLocal) play.spriteMode = SPRITE::PLAYER; 
            }
            // Special Fun Zone: ID 117
            else if (zone == 17) {
                playerIsBall[i] = true;
                if (play.isLocal) play.spriteMode = SPRITE::INVISIBLE;
                playerRunNumber[i] = 0; 
            }
            else if (zone >= 1 && zone <= 49) { // 101 to 149
                playerIsBall[i] = true;
                playerRunNumber[i] = zone; 
                playerJumps[i] = 0; 
                playerInfiniteJumps[i] = false; 
                if (play.isLocal) play.spriteMode = SPRITE::INVISIBLE;

                // Vanish Water on Run 13
                if (zone == 13 && play.isLocal) {
                    jjSetWaterLevel(jjLayerHeight[4] * 32 + 100, true);
                }

                if ((playedRunsMask[i] & (1 << zone)) == 0) {
                    playedRunsMask[i] |= (1 << zone); 
                    
                    if (play.isLocal) {
                        string colorCode = runColors[(zone - 1) % runColors.length()];
                        
                        if (zone == 12) {
                            play.showText(colorCode + "Run 12 : RUN!!", STRING::LARGE);
                        } else if (zone == 15) {
                            play.showText("\\7Final run : find the key and escape!", STRING::MEDIUM);
                        } else {
                            play.showText(colorCode + "Run " + zone + "!", STRING::LARGE);
                        }
                        
                        jjSamplePriority(SOUND::COMMON_PICKUP1);
                    }
                }
            }
            // 250 Area (Potion) - Resets Jumps
            else if (zone == 150) {
                if (playerRunNumber[i] > 0) {
                    if (playerJumps[i] > 0) {
                        playerJumps[i] = 0; 
                        if (play.isLocal) {
                            play.showText("\\3Jumps Reset!", STRING::SMALL);
                            if (jjGameTicks % 14 == 0) jjSample(play.xPos, play.yPos, SOUND::PICKUPS_BOING_CHECK);
                        }
                    }
                }
            }
            // 160 Area (Text ID 160 -> Zone 60) - Grants Unlimited Jumps
            else if (zone == 60) {
                if (!playerInfiniteJumps[i]) {
                    playerInfiniteJumps[i] = true;
                }
            }
            // 255 Area (Win Area) - Congratulations!
            else if (zone == 155) {
                if (play.isLocal && !playerWon[i]) {
                    string pName = shortenNick(play.nameUnformatted);
                    play.showText("\\5Congratulations! Mr. \\3" + pName + "\\5 you won! 50 million carrots!", STRING::MEDIUM);
                    jjSamplePriority(SOUND::BONUS_BONUS1);
                    playerWon[i] = true; 
                }
                playerIsBall[i] = false;
                playerRunNumber[i] = 0;
                if (play.isLocal) {
                    play.spriteMode = SPRITE::PLAYER;
                    play.jumpStrength = -10;
                }
            }
        }

        // --- PHYSICS: Unlimited Jump mechanics (Apply to ALL players for server validation) ---
        int limit = getJumpLimit(playerRunNumber[i]);
        bool isUnlimited = playerInfiniteJumps[i] || (playerRunNumber[i] >= 1 && playerRunNumber[i] <= 5);
        
        if (limit > 0 && playerJumps[i] < limit) {
            isUnlimited = true;
        }

        if (isUnlimited) {
            play.doubleJumpCount = 0; 
        } else if (playerIsBall[i]) {
            play.doubleJumpCount = 99; 
        }

        if (play.isLocal) {
            if (playerHasTouchedIni[i]) {
                play.noFire = true;
                play.currWeapon = WEAPON::BLASTER;
            }
            // Stop idle animations and sounds completely
            play.idle = 0;
        }
    }
}

// Intercept the jump and fire buttons cleanly (Local player only)
void onPlayerInput(jjPLAYER@ play) {
    int id = play.playerID;
    
    // 1. Physically block shooting forever
    if (playerHasTouchedIni[id]) {
        play.keyFire = false;
    }

    // 2. Jump Limits
    int limit = getJumpLimit(playerRunNumber[id]);
    
    if (limit > 0 && playerJumps[id] >= limit && !playerInfiniteJumps[id]) {
        play.keyJump = false; 
        wasJumping[id] = false; 
        return;
    }

    // Count jumps on fresh press
    if (play.keyJump && !wasJumping[id]) {
        wasJumping[id] = true;
        if (playerRunNumber[id] > 0) {
            playerJumps[id]++;
        }
    } else if (!play.keyJump) {
        wasJumping[id] = false;
    }
}

void onDrawLayer4(jjPLAYER@ viewer, jjCANVAS@ canvas) {
    for (int i = 0; i < 32; i++) {
        jjPLAYER@ p = jjPlayers[i];
        if (p.isActive && playerIsBall[i]) {
            jjANIMATION@ ballAnim = jjAnimations[jjAnimSets[p.setID].firstAnim + RABBIT::ROLLING];
            if (ballAnim !is null && ballAnim.frameCount > 0) {
                uint frame = ballAnim.firstFrame + ((jjGameTicks >> 2) % ballAnim.frameCount);
                canvas.drawSpriteFromCurFrame(p.xPos, p.yPos, frame, p.direction, SPRITE::PLAYER, p.playerID);
            }
        }
    }
}

bool onDrawAmmo(jjPLAYER@ play, jjCANVAS@ screen) {
    return true; // Hide ammo HUD
}

bool onDrawHealth(jjPLAYER@ play, jjCANVAS@ screen) {
    // Intro Title & Ready/Spin
    if (introTimer > 280) {
        string title = "\\5SpazBallRun"; 
        string sub   = "\\1By \\2red\\3ski\\1"; 
        string subClean = "By redski"; 
        
        int titleWidth = jjGetStringWidth(title, STRING::LARGE, STRING::NORMAL);
        int subWidth   = jjGetStringWidth(subClean, STRING::MEDIUM, STRING::NORMAL);
        
        int titleX = (jjResolutionWidth / 2) - (titleWidth / 2);
        int subX   = (jjResolutionWidth / 2) - (subWidth / 2);
        
        int titleY = (jjResolutionHeight / 2) - 40;
        int subY   = (jjResolutionHeight / 2) + 10;
        
        screen.drawString(titleX, titleY, title, STRING::LARGE, STRING::BOUNCE, 50);
        screen.drawString(subX, subY, sub, STRING::MEDIUM, STRING::NORMAL);
    } 
    else if (introTimer > 70) {
        string readyText = "\\5Ready?";
        int w = jjGetStringWidth(readyText, STRING::LARGE, STRING::NORMAL);
        screen.drawString((jjResolutionWidth / 2) - (w / 2), (jjResolutionHeight / 2) - 20, readyText, STRING::LARGE, STRING::BOUNCE, 100);
    } 
    else if (introTimer > 0) {
        string goText = "\\5Spin!";
        int w = jjGetStringWidth(goText, STRING::LARGE, STRING::NORMAL);
        screen.drawString((jjResolutionWidth / 2) - (w / 2), (jjResolutionHeight / 2) - 20, goText, STRING::LARGE, STRING::BOUNCE, 100);
        
        if (introTimer == 70) jjSamplePriority(SOUND::COMMON_PICKUP1);
    }

    int id = play.playerID;
    
    // Top-left HUD Icon (Only appears during a run)
    if (playerRunNumber[id] > 0) {
        int iconX = 10;
        int iconY = 50;
        
        // Draw Icon Box
        screen.drawRectangle(iconX - 5, iconY - 5, 55, 30, 0, SPRITE::GEM, 5);
        
        jjANIMATION@ ballAnim = jjAnimations[jjAnimSets[play.setID].firstAnim + RABBIT::ROLLING];
        if (ballAnim !is null && ballAnim.frameCount > 0) {
            uint frame = ballAnim.firstFrame + ((jjGameTicks >> 2) % ballAnim.frameCount);
            screen.drawSpriteFromCurFrame(iconX, iconY, frame, 1, SPRITE::PLAYER, id);
        }
        
        int limit = getJumpLimit(playerRunNumber[id]);
        
        if (playerInfiniteJumps[id] || (limit == 0 && playerRunNumber[id] > 0)) {
            screen.drawString(iconX + 25, iconY + 5, "\\5oo", STRING::MEDIUM, STRING::NORMAL);
        } else if (limit > 0) {
            int remaining = limit - playerJumps[id];
            if (remaining < 0) remaining = 0;
            
            // White normally, Red when 15% or less remains
            string colorCode = "\\1"; 
            if (remaining * 100 <= limit * 15) {
                colorCode = "\\2"; 
            }
            
            screen.drawString(iconX + 25, iconY + 5, colorCode + remaining, STRING::MEDIUM, STRING::NORMAL);
        }
    }

    return false; 
}