Downloads containing olckeengrounds.j2as

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: A Pleasant Little Planet Lark Capture the flag N/A Download file

File preview

const bool MLLESetupSuccessful = MLLE::Setup(); ///@MLLE-Generated
#include "MLLE-Include-1.8.asc" ///@MLLE-Generated
#pragma require "JJ1 Tubelectric NOKA.j2t" ///@MLLE-Generated
#pragma require "olckeengrounds.j2l" ///@MLLE-Generated
#pragma require "jj2_silence.wav"
#pragma require "keen_pants.wav"

const int LOCAL_PLAYER_SLOTS = 4;
const int WEAPON_COUNT = 9;
const int SUGAR_RUSH_DURATION = 15 * 70;
const int SUGAR_RUSH_HIT_PENALTY = 5 * 70;
const int SUGAR_RUSH_HIT_COOLDOWN = 70;
const string SUGAR_RUSH_MUSIC = "keen_pants.wav";

array<bool> sugarRushActive(LOCAL_PLAYER_SLOTS, false);
array<int> sugarRushTicksRemaining(LOCAL_PLAYER_SLOTS, 0);
array<int> sugarRushHitCooldown(LOCAL_PLAYER_SLOTS, 0);
array<int> protectedHealth(LOCAL_PLAYER_SLOTS, 0);
array<bool> infiniteAmmo(LOCAL_PLAYER_SLOTS * WEAPON_COUNT, false);
array<int> savedAmmo(LOCAL_PLAYER_SLOTS * WEAPON_COUNT, 0);
bool sugarRushMusicActive = false;
string musicBeforeSugarRush;

bool supportedGameMode() {
	return jjGameMode == GAME::CTF || jjGameMode == GAME::SP;
}

bool supportedCharacter(const jjPLAYER@ player) {
	return player.charCurr == CHAR::JAZZ || player.charCurr == CHAR::SPAZ;
}

int ammoIndex(int localPlayerID, int weapon) {
	return localPlayerID * WEAPON_COUNT + weapon - 1;
}

void silenceNativeSugarRushJingle() {
	jjSampleLoad(SOUND::RUSH_RUSH, "jj2_silence.wav");
}

void startSugarRushMusic() {
	if (sugarRushMusicActive) {
		return;
	}

	musicBeforeSugarRush = jjMusicFileName;
	sugarRushMusicActive = jjMusicLoad(SUGAR_RUSH_MUSIC, true, true);
	if (!sugarRushMusicActive) {
		musicBeforeSugarRush = "";
	}
}

void restoreLevelMusic(bool force = false) {
	if (!sugarRushMusicActive) {
		return;
	}

	if (!force) {
		for (int playerID = 0; playerID < LOCAL_PLAYER_SLOTS; ++playerID) {
			if (sugarRushActive[playerID]) {
				return;
			}
		}
	}

	if (musicBeforeSugarRush.length() > 0) {
		jjMusicLoad(musicBeforeSugarRush, true);
	} else {
		jjMusicStop();
	}

	sugarRushMusicActive = false;
	musicBeforeSugarRush = "";
}

void resetSugarRushState() {
	for (int playerID = 0; playerID < LOCAL_PLAYER_SLOTS; ++playerID) {
		sugarRushActive[playerID] = false;
		sugarRushTicksRemaining[playerID] = 0;
		sugarRushHitCooldown[playerID] = 0;
		protectedHealth[playerID] = 0;

		for (int weapon = 1; weapon <= WEAPON_COUNT; ++weapon) {
			const int index = ammoIndex(playerID, weapon);
			infiniteAmmo[index] = false;
			savedAmmo[index] = 0;
		}
	}
}

void beginSugarRush(jjPLAYER@ player) {
	const int playerID = player.localPlayerID;
	player.startSugarRush(SUGAR_RUSH_DURATION);
	sugarRushActive[playerID] = true;
	sugarRushTicksRemaining[playerID] = SUGAR_RUSH_DURATION;
	sugarRushHitCooldown[playerID] = 0;
	protectedHealth[playerID] = player.health;
	startSugarRushMusic();

	for (int weapon = 1; weapon <= WEAPON_COUNT; ++weapon) {
		const int index = ammoIndex(playerID, weapon);
		const int ammo = player.ammo[weapon];
		infiniteAmmo[index] = ammo > 0;
		savedAmmo[index] = ammo;
	}
}

void shortenSugarRushAfterHit(jjPLAYER@ player) {
	const int playerID = player.localPlayerID;
	if (sugarRushHitCooldown[playerID] > 0) {
		return;
	}

	sugarRushHitCooldown[playerID] = SUGAR_RUSH_HIT_COOLDOWN;
	sugarRushTicksRemaining[playerID] -= SUGAR_RUSH_HIT_PENALTY;
	jjSamplePriority(SOUND::P2_CRUNCH);

	// One tick lets JJ2 finish the rush normally when a hit uses up its time.
	if (sugarRushTicksRemaining[playerID] < 1) {
		sugarRushTicksRemaining[playerID] = 1;
	}
	player.startSugarRush(sugarRushTicksRemaining[playerID]);
}

bool isHostileProjectile(const jjOBJ@ object, const jjPLAYER@ victim) {
	if (object.playerHandling == HANDLING::ENEMYBULLET) {
		return true;
	}

	if (object.playerHandling != HANDLING::PLAYERBULLET ||
		object.creatorType != CREATOR::PLAYER || object.creatorID >= 32 ||
		object.creatorID == victim.playerID) {
		return false;
	}

	const jjPLAYER@ attacker = jjPlayers[object.creatorID];
	return attacker.isActive && attacker.isEnemy(victim);
}

bool touchingHostileProjectile(jjPLAYER@ player) {
	for (int objectID = 1; objectID < jjObjectCount; ++objectID) {
		const jjOBJ@ object = jjObjects[objectID];
		if (!object.isActive || object.state == STATE::KILL) {
			continue;
		}

		if (isHostileProjectile(object, player) && player.doesCollide(object, true)) {
			return true;
		}
	}

	return false;
}

void maintainSugarRush(jjPLAYER@ player) {
	const int playerID = player.localPlayerID;
	player.blink = 70;

	for (int weapon = 1; weapon <= WEAPON_COUNT; ++weapon) {
		const int index = ammoIndex(playerID, weapon);
		const int ammo = player.ammo[weapon];

		// A gun collected during the rush also becomes infinite.
		if (!infiniteAmmo[index] && ammo > 0) {
			infiniteAmmo[index] = true;
			savedAmmo[index] = ammo;
		}

		if (infiniteAmmo[index]) {
			// Keep ammo pickups, but undo ammo spent by firing.
			if (ammo > savedAmmo[index]) {
				savedAmmo[index] = ammo;
			} else if (ammo < savedAmmo[index]) {
				player.ammo[weapon] = savedAmmo[index];
			}
		}
	}
}

void endSugarRush(jjPLAYER@ player) {
	const int playerID = player.localPlayerID;

	for (int weapon = 1; weapon <= WEAPON_COUNT; ++weapon) {
		const int index = ammoIndex(playerID, weapon);
		if (infiniteAmmo[index]) {
			player.ammo[weapon] = savedAmmo[index];
		}
		infiniteAmmo[index] = false;
		savedAmmo[index] = 0;
	}

	player.blink = 0;
	sugarRushActive[playerID] = false;
	sugarRushTicksRemaining[playerID] = 0;
	sugarRushHitCooldown[playerID] = 0;
	protectedHealth[playerID] = 0;
	restoreLevelMusic();
}

void onLevelLoad() {
	resetSugarRushState();
	jjObjectPresets[OBJECT::CANDY].determineCurFrame();
	silenceNativeSugarRushJingle();
}

void onLevelBegin() {
	jjSugarRushAllowed = true;
	// Reload after JJ2 has initialized its animation-set sounds.
	silenceNativeSugarRushJingle();
}

void onLevelReload() {
	restoreLevelMusic(true);
	resetSugarRushState();
	silenceNativeSugarRushJingle();
}

void onPlayer(jjPLAYER@ player) {
	if (player is null || !player.isLocal) {
		return;
	}

	const int playerID = player.localPlayerID;
	if (playerID < 0 || playerID >= LOCAL_PLAYER_SLOTS || !sugarRushActive[playerID]) {
		return;
	}

	if (sugarRushTicksRemaining[playerID] > 0) {
		--sugarRushTicksRemaining[playerID];
	}
	if (sugarRushHitCooldown[playerID] > 0) {
		--sugarRushHitCooldown[playerID];
	}

	// Restore any damage that bypasses blink, while still charging five seconds.
	if (player.health < protectedHealth[playerID]) {
		player.health = protectedHealth[playerID];
		shortenSugarRushAfterHit(player);
	} else if (player.health > protectedHealth[playerID]) {
		protectedHealth[playerID] = player.health;
	}

	// Blink prevents ordinary damage, so detect the projectile that would hit.
	if (sugarRushHitCooldown[playerID] == 0 && touchingHostileProjectile(player)) {
		shortenSugarRushAfterHit(player);
	}
}

bool onDrawAmmo(jjPLAYER@ player, jjCANVAS@ canvas) {
	if (player !is null && supportedGameMode() && supportedCharacter(player)) {
		const int right = player.subscreenX + jjSubscreenWidth - 8;
		const int aboveAmmo = player.subscreenY + jjSubscreenHeight - 52;
		const string foodMeter = "x" + formatInt(player.food % 100, "1") + "/100";
		jjTEXTAPPEARANCE foodTextStyle(STRING::RIGHTALIGN);
		const int textWidth = jjGetStringWidth(foodMeter, STRING::MEDIUM, foodTextStyle);

		canvas.drawSpriteFromCurFrame(
			right - textWidth - 12,
			aboveAmmo + 2,
			jjObjectPresets[OBJECT::CANDY].curFrame
		);
		canvas.drawString(right, aboveAmmo, foodMeter, STRING::MEDIUM, STRING::RIGHTALIGN);
	}

	// Keep JJ2's normal weapon and ammo indicator.
	return false;
}

void onPlayerDraw(jjPLAYERDRAW& draw) {
	jjPLAYER@ player = draw.player;
	if (player is null || !player.isLocal) {
		return;
	}

	const int playerID = player.localPlayerID;
	if (playerID < 0 || playerID >= LOCAL_PLAYER_SLOTS) {
		return;
	}

	if (!supportedGameMode() || !supportedCharacter(player)) {
		if (sugarRushActive[playerID]) {
			endSugarRush(player);
		}
		return;
	}

	// draw.sugarRush is JJ2+'s exposed native Sugar Rush state. It is false
	// while frozen, so preserve an already active rush until the player thaws.
	const bool rushing = draw.sugarRush || (sugarRushActive[playerID] && player.frozen > 0);

	if (rushing) {
		if (!sugarRushActive[playerID]) {
			beginSugarRush(player);
		}
		maintainSugarRush(player);
	} else if (sugarRushActive[playerID]) {
		endSugarRush(player);
	}
}