Name | Author | Game Mode | Rating | |||||
---|---|---|---|---|---|---|---|---|
Find It Out (Single Player) | Superjazz | Single player | 9.1 |
#include "Fio_cutscene.asc"
#include "Fio_drawing.asc"
#include "Fio_entities.asc"
#include "Fio_utils.asc"
namespace fio {
bool canBuyInvincibility() {
if (currentGameSession.invincibilities < MAX_INVINCIBILITIES) {
return true;
}
return false;
}
bool canBuyPocketCarrot() {
if (currentGameSession.pocketCarrots < MAX_POCKET_CARROTS) {
return true;
}
return false;
}
bool canConsumeInvincibility() {
// play.health > 0 to ensure that bugs related to artificial reincarnation won't happen (like music volume going down, etc)
if (currentGameSession.invincibilities > 0 && play.health > 0) {
return true;
}
return false;
}
bool canConsumePocketCarrot() {
// play.health > 0 to ensure that bugs related to artificial reincarnation won't happen (like music volume going down, etc)
if (currentGameSession.pocketCarrots > 0 && play.health < jjMaxHealth && play.health > 0) {
return true;
}
return false;
}
void completeQuest(jjPLAYER@ play) {
jjSamplePriority(SOUND::INTRO_STRING);
bonusRewardGiven = true;
play.score = play.score + questRewardPoints;
fioDraw::doShowOptionalQuest(1);
}
void completeQuestPerfect(jjPLAYER@ play) {
jjSamplePriority(SOUND::EPICLOGO_EPIC2); // EPIC!
perfectBonusRewardGiven = true;
play.score = play.score + questPerfectRewardPoints;
fioDraw::doShowOptionalQuest(2);
}
void controlArmoryInput(jjPLAYER@ play) {
if (isPlayerInArmory) {
if (fioUtils::isKeyTapped(KEY_CODE_SPACE) || fioUtils::isKeyTapped(KEY_CODE_ENTER)) {
ArmoryItem@ armoryItem = armoryItems[selectedArmoryItem];
if (armoryItem.canBuy()) {
const bool hasPlayerEnoughCoins = play.testForCoins(armoryItem.cost);
if (hasPlayerEnoughCoins) {
armoryItem.sell();
asPlay.savePlayerProperties(play);
} else {
jjAlert("|Not enough coins to buy that item");
}
} else {
jjAlert("|This item is already bought at maximum."); // For now
}
} else if (fioUtils::isKeyTapped(KEY_CODE_ARROW_DOWN) && selectedArmoryItem < armoryItems.length() - 1 && !areArmoryItemsInMotion) {
selectedArmoryItem++;
moveArmoryItems();
} else if (fioUtils::isKeyTapped(KEY_CODE_ARROW_UP) && selectedArmoryItem > 0 && !areArmoryItemsInMotion) {
selectedArmoryItem--;
moveArmoryItems();
}
}
if (armoryItemsMotionElapsed < ARMORY_ITEM_MOTION_DURATION) {
armoryItemsMotionElapsed++;
} else {
areArmoryItemsInMotion = false;
}
}
void controlPlayerInput(jjPLAYER@ play, bool isInCutscene = false, bool canUseItems = true) {
if (play.keyUp && fioDraw::elapsedTextDisplay > 0 && fioDraw::elapsedTextDisplay < fioDraw::TOOL_TIP_DISPLAY_TICKS) {
fioDraw::elapsedTextDisplay = 0;
}
if (play.keyUp && fioDraw::elapsedQuestDisplay > 0 && fioDraw::elapsedQuestDisplay < fioDraw::TOOL_TIP_DISPLAY_TICKS) {
fioDraw::elapsedQuestDisplay = 0;
}
if (fioUtils::isKeyTapped(KEY_CODE_P)
&& !isPlayerInArmory
&& !isInCutscene
&& canUseItems
&& canConsumePocketCarrot()) {
currentGameSession.pocketCarrots--;
play.health = play.health + 1; // Property accessors cannot be used in combined read/write operations, so no play.health++;
play.extendInvincibility(SECOND);
jjSamplePriority(SOUND::COMMON_EAT1);
updateArmoryItemTexts();
} else if (fioUtils::isKeyTapped(KEY_CODE_I)
&& !isPlayerInArmory
&& !isInCutscene
&& canUseItems
&& canConsumeInvincibility()) {
currentGameSession.invincibilities--;
play.extendInvincibility(INVINCIBILITY_DURATION);
jjSamplePriority(SOUND::COMMON_PICKUP1);
updateArmoryItemTexts();
}
}
void controlPressedKeys() {
for (uint i = 0; i < keys.length(); i++) {
if (@keys[i] != null) {
keys[i].isPressed = jjKey[i];
}
}
}
void controlQuest() {
if (questGemsInLevel > 0 && play.gems[GEM::PURPLE] == questGemsInLevel && !bonusRewardGiven) {
completeQuest(play);
}
if (totalGemsInLevel > 0 && play.gems[GEM::PURPLE] == totalGemsInLevel && !perfectBonusRewardGiven) {
completeQuestPerfect(play);
}
}
ANIM::Set getAnimSetForPlayer(jjPLAYER@ play) {
if (play.charOrig == CHAR::LORI) {
return ANIM::LORI;
}
if (play.charOrig == CHAR::SPAZ) {
return ANIM::SPAZ;
}
return ANIM::JAZZ;
}
jjOBJ@ getActiveObjectFromLevel(uint8 eventId) {
for (int i = 1; i < jjObjectCount; i++) {
jjOBJ@ object = jjObjects[i];
if (object.isActive && object.eventID == eventId) {
return object;
}
}
return null;
}
CharacterWithMindStone@ getCharacterWithMindStoneForPlayer(jjPLAYER@ play, bool isLeftDirection = false) {
if (play.charOrig == CHAR::LORI) {
// mindStoneX, mindStoneY, animSet, idleAnimation, idleFrame, withMindStoneAnimation, withMindStoneFrame,
// digAnimation, digFrameStart, digFrameEnd
return CharacterWithMindStone(isLeftDirection ? -22 : -6, isLeftDirection ? -48 : -44, ANIM::LORI, RABBIT::IDLE1, 0, RABBIT::LIFTLAND, 0, RABBIT::IDLE2, 27, 34);
}
if (play.charOrig == CHAR::SPAZ) {
return CharacterWithMindStone(isLeftDirection ? -22 : -2, isLeftDirection ? -52 : -48, ANIM::SPAZ, RABBIT::IDLE3, 0, RABBIT::IDLE4, 2, RABBIT::IDLE2, 12, 19);
}
return CharacterWithMindStone(isLeftDirection ? -2 : -20, -48, ANIM::JAZZ, RABBIT::IDLE3, 0, RABBIT::IDLE1, 7, RABBIT::IDLE2, 0, 7);
}
string getInvincibilityItemText() {
uint8 invincibilities = @currentGameSession !is null ? currentGameSession.invincibilities : 0;
return "|Invincibility for 10 seconds - Press I to consume@(Current amount " + invincibilities + "/" + MAX_INVINCIBILITIES + ")";
}
string getPocketCarrotItemText() {
uint8 pocketCarrots = @currentGameSession !is null ? currentGameSession.pocketCarrots : 0;
return "|Pocket Carrot - Press P to consume when below maximum health@(Current amount " + pocketCarrots + "/" + MAX_POCKET_CARROTS + ")";
}
// Don't use totalGemsInLevel here, since that one has not been calculated at the point of calling this
string getQuestText(int questGems, int totalGems, int reward) {
return "||||Optional quest:@Find " + questGems + " out of " + totalGems + " purple gems@for +" + reward + " points!";
}
string getQuestTextComplete(int reward) {
return "||||Optional quest complete!@Reward: +" + reward + " points!";
}
string getQuestTextPerfect(int reward) {
return "||||EPIC!@You receive +" + reward + " bonus points@for discovering all purple gems!";
}
bool handleCheat(string &in cheat, string nextLevelFilename) {
// The cheat string parameter value is always inserted in lower case by the game from what I see
if (cheat == "jjk") {
saveTriggerStates();
asPlay.savePlayerProperties(play);
} else if (cheat == "jjnxt" || cheat == "jjnext") {
handleLevelCycle(nextLevelFilename, false, true);
}
return false;
}
void handleLevelCycle(string nextLevel, bool warp = false, bool fast = false) {
// Makes sure the process is called only once, so that multiple calls to this method via e.g. text event functions without vanishing property set
// won't trigger this process more often than desired (which caused the issue with food count being overwritten by game session ID)
if (!hasLevelCycleBeenInitiated) {
hasLevelCycleBeenInitiated = true;
if (nextLevel == "Fio6_Score.j2l") {
currentGameSession.finishedTime = jjUnixTimeMs();
currentGameSession.isFinished = true; // A bit of a redundant field after all but w/e
}
// Pass the original food count into the currentGameSession first, then store the currentGameSession id on jjPLAYER::food property
prepareCurrentGameSessionForFileWriting();
if (!fioUtils::writeGameSessionsToFile()) {
jjAlert("|WARNING: Failed to write game stats into file! Game stats may not be carried over to the next level.");
}
// Now assign the game session ID into the play.food property to carry over the game session to the next (save game) level
play.food = currentGameSession.id;
jjNxt(nextLevel, warp, fast);
}
}
bool handleLevelReload() {
if (currentGameSession.hasPlayerDiedPreviously) {
if (play.score - PLAYER_POINT_LOSS_FOR_DEATH < 0) {
play.setScore(0);
} else {
play.setScore(play.score - PLAYER_POINT_LOSS_FOR_DEATH);
}
fioDraw::elapsedTextDisplay = 0;
} else {
currentGameSession.hasPlayerDiedPreviously = true;
fioDraw::elapsedQuestDisplay = 0;
fioDraw::textIndex = -1; // Cheap hack
fioDraw::doShowTextCustom(RESPAWN_WARNING_TEXT);
}
currentGameSession.deathCount++;
reloadGlobals();
playerPropertiesReinitialized = false;
loadTriggerStates();
Checkpoint@ checkpoint = fioUtils::getLatestReachedCheckpoint(checkpoints);
if (checkpoint !is null) {
play.yOrg = checkpoint.yOrg;
play.xOrg = checkpoint.xOrg;
return true;
}
return false;
}
void handlePlayer(jjPLAYER@ play) {
if (!playerPropertiesReinitialized) {
playerPropertiesReinitialized = true;
shouldStorePlayerEquipment = true; // Always reset upon dying, since the player is not in the boss fight anymore
asPlay.loadPlayerProperties(play);
}
// forceMoveLeftiä ei kannata edes yrittää, kun sitä ei kuitenkaan saa toimimaan kovin helpolla...
// Should be used only when a cutscene is running...
if (forceMoveRight) {
fioUtils::blockPlayerMovement(play, false);
play.keyRight = true;
play.xSpeed = fioCut::getPace() * 3;
}
if (isPlayerHiddenAndUnableToMove) {
fioUtils::blockPlayerMovement(play);
play.invisibility = true;
} else if (isPlayerUnableToMove) {
fioUtils::blockPlayerMovement(play, false);
play.invisibility = false;
}
}
bool isPlayerNotMoving() {
return play.xSpeed == 0 && play.ySpeed == 0;
}
void increaseCutscenesWatchedIfFastForwardWasNotUsed(bool wasFastForwardUsed) {
if (!wasFastForwardUsed) {
currentGameSession.cutscenesWatched++;
}
}
void killFollowingEnemies() {
for (int i = 1; i < jjObjectCount; i++) {
if ((jjObjects[i].state == STATE::FLY || jjObjects[i].state == STATE::ATTACK)
&& (jjObjects[i].eventID == OBJECT::FLOATLIZARD || jjObjects[i].eventID == OBJECT::BAT)) {
jjObjects[i].particlePixelExplosion(1); // Toaster effect
jjObjects[i].state = STATE::KILL;
}
}
}
void killPlayer() {
// onRoast() is not called upon play.kill() so need to save player properties and trigger states explicitly here
saveTriggerStates();
asPlay.savePlayerProperties(play);
play.kill();
}
void loadTriggerStates() {
for (uint i = 1; i < 32; ++i) {
jjTriggers[i] = activeTriggers[i];
}
}
void moveArmoryItems() {
armoryItemsMotionElapsed = 0;
areArmoryItemsInMotion = true;
for (uint i = 0; i < armoryItems.length(); i++) {
armoryItems[i].move(i);
}
}
void prepareCurrentGameSessionForFileWriting() {
currentGameSession.food = play.food;
currentGameSession.score = play.score;
currentGameSession.totalTime += int64(jjActiveGameTicks);
}
void preserveAmmoForBossBattle() {
asPlay.savePlayerProperties(play);
shouldStorePlayerEquipment = false; // Call after saving the player properties - Reload ammo upon respawning
if (!wasEquipmentPreserveAlertDisplayed) {
jjAlert("Equipment preserved for boss battle!", false, STRING::MEDIUM);
wasEquipmentPreserveAlertDisplayed = true;
}
}
void renderCommon(jjPLAYER@ play, jjCANVAS@ canvas) {
switch (staticPlayerRenderState) {
case STATE_STATIC_PLAYER_RENDER_IDLE:
renderPlayerIdle(play, canvas);
break;
case STATE_STATIC_PLAYER_RENDER_FOCUSED:
renderPlayerFocused(play, canvas);
break;
case STATE_STATIC_PLAYER_RENDER_FRIGHTENED:
renderPlayerFrightened(play, canvas);
break;
case STATE_STATIC_PLAYER_RENDER_FALLING:
renderPlayerFalling(play, canvas);
break;
}
}
void renderPlayerFalling(jjPLAYER@ play, jjCANVAS@ canvas) {
if (jjGameTicks % 5 == 0) {
if (playerFallingFrame > 1) {
playerFallingFrame = 0;
} else {
++playerFallingFrame;
}
}
fioDraw::drawFrameAtPlayerPos(play, canvas, getAnimSetForPlayer(play), RABBIT::FALL, playerFallingFrame, idlePlayerDirection);
}
void renderPlayerFocused(jjPLAYER@ play, jjCANVAS@ canvas) {
switch (play.charOrig) {
case CHAR::LORI:
fioDraw::drawFrameAtPlayerPos(play, canvas, ANIM::LORI, RABBIT::IDLE1, 0, idlePlayerDirection);
break;
case CHAR::SPAZ:
fioDraw::drawFrameAtPlayerPos(play, canvas, ANIM::SPAZ, RABBIT::IDLE3, 0, idlePlayerDirection);
break;
case CHAR::JAZZ:
default:
fioDraw::drawFrameAtPlayerPos(play, canvas, ANIM::JAZZ, RABBIT::IDLE3, 0, idlePlayerDirection);
break;
}
}
void renderPlayerFrightened(jjPLAYER@ play, jjCANVAS@ canvas) {
RABBIT::Anim rabbitAnim = play.charCurr == CHAR::LORI || play.charCurr == CHAR::SPAZ ? RABBIT::DIVE : RABBIT::ENDOFLEVEL; // For Jazz
fioDraw::drawFrameAtPlayerPos(play, canvas, getAnimSetForPlayer(play), rabbitAnim, 0, idlePlayerDirection);
}
void renderPlayerIdle(jjPLAYER@ play, jjCANVAS@ canvas) {
switch (play.charOrig) {
case CHAR::LORI:
fioDraw::drawFrameAtPlayerPos(play, canvas, ANIM::LORI, RABBIT::IDLE1, 7, idlePlayerDirection);
break;
case CHAR::SPAZ:
fioDraw::drawFrameAtPlayerPos(play, canvas, ANIM::SPAZ, RABBIT::IDLE2, 0, idlePlayerDirection);
break;
case CHAR::JAZZ:
default:
fioDraw::drawFrameAtPlayerPos(play, canvas, ANIM::JAZZ, RABBIT::IDLE1, 2, idlePlayerDirection);
break;
}
}
void rewardPoints(int pointReward) {
play.setScore(play.score + pointReward);
jjAlert("+" + pointReward + " points!", false, STRING::MEDIUM);
}
void saveTriggerStates() {
for (uint i = 1; i < 32; ++i) {
activeTriggers[i] = jjTriggers[i];
}
}
void sellArmoryItemBouncerPU() {
// Compound assignments with indexed property accessors are not supported, thus += etc. will not work for array items, etc.
play.ammo[WEAPON::BOUNCER] = play.ammo[WEAPON::BOUNCER] + 20; // For now 20 ammo for all powerups
play.powerup[WEAPON::BOUNCER] = true;
jjSamplePriority(SOUND::COMMON_GLASS2);
}
void sellArmoryItemFreezerPU() {
// Compound assignments with indexed property accessors are not supported, thus += etc. will not work for array items, etc.
play.ammo[WEAPON::ICE] = play.ammo[WEAPON::ICE] + 20; // For now 20 ammo for all powerups
play.powerup[WEAPON::ICE] = true;
jjSamplePriority(SOUND::COMMON_GLASS2);
}
void sellArmoryItemInvincibility() {
currentGameSession.invincibilities++;
jjSamplePriority(SOUND::INTRO_SWISH1);
updateArmoryItemTexts();
}
void sellArmoryItemPeppersprayPU() {
// Compound assignments with indexed property accessors are not supported, thus += etc. will not work for array items, etc.
play.ammo[WEAPON::GUN8] = play.ammo[WEAPON::GUN8] + 20; // For now 20 ammo for all powerups
play.powerup[WEAPON::GUN8] = true;
jjSamplePriority(SOUND::COMMON_GLASS2);
}
void sellArmoryItemPocketCarrot() {
currentGameSession.pocketCarrots++;
jjSamplePriority(SOUND::INTRO_SWISH1);
updateArmoryItemTexts();
}
void sellArmoryItemRFPU() {
// Compound assignments with indexed property accessors are not supported, thus += etc. will not work for array items, etc.
play.ammo[WEAPON::RF] = play.ammo[WEAPON::RF] + 20;
play.powerup[WEAPON::RF] = true;
jjSamplePriority(SOUND::COMMON_GLASS2);
}
void sellArmoryItemSeekerAmmo() {
// Compound assignments with indexed property accessors are not supported, thus += etc. will not work for array items, etc.
play.ammo[WEAPON::SEEKER] = play.ammo[WEAPON::SEEKER] + 15; // For now 15 for seeker ammo
jjSamplePriority(SOUND::COMMON_PICKUPW1);
}
void sellArmoryItemToasterPU() {
// Compound assignments with indexed property accessors are not supported, thus += etc. will not work for array items, etc.
play.ammo[WEAPON::TOASTER] = play.ammo[WEAPON::TOASTER] + 20; // For now 20 ammo for all powerups
play.powerup[WEAPON::TOASTER] = true;
jjSamplePriority(SOUND::COMMON_GLASS2);
}
void updateArmoryItemTexts() {
if (armoryItemIndexInvincibility >= 0) {
armoryItems[armoryItemIndexInvincibility].text = getInvincibilityItemText();
}
if (armoryItemIndexPocketCarrot >= 0) {
armoryItems[armoryItemIndexPocketCarrot].text = getPocketCarrotItemText();
}
}
}
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.