| Name | Author | Game Mode | Rating | |||||
|---|---|---|---|---|---|---|---|---|
| SpazBallRun | -ANAS- | Test | N/A | |||||
#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;
}
Jazz2Online © 1999-INFINITY (Site Credits). We have a Privacy Policy. Jazz Jackrabbit, Jazz Jackrabbit 2, Jazz Jackrabbit Advance and all related trademarks and media are ™ and © Epic Games. Lori Jackrabbit is © Dean Dodrill. J2O development powered by Loops of Fury and Chemical Beats.
Eat your lima beans, Johnny.