Name | Author | Game Mode | Rating | |||||
---|---|---|---|---|---|---|---|---|
Find It Out (Single Player) | Superjazz | Single player | 9.1 |
const bool MLLESetupSuccessful = MLLE::Setup(); ///@MLLE-Generated
#include "MLLE-Include-1.6.asc" ///@MLLE-Generated
#pragma require "Fio6_Score.j2l" ///@MLLE-Generated
#include "Fio_common.asc"
#include "Fio_globals.asc"
#include "Fio_utils.asc"
#include "SEdatetime.asc"
enum RESULT_TYPE { DIFFICULTY, FOOD, PURPLE_GEMS, ENEMIES_SLAIN, DEATH_COUNT, CUTSCENES_WATCHED, TOTAL_TIME, SCORE, RANK };
class Label {
bool isHighlighted; // Value to be set later
string text;
STRING::Size size;
Label(string text, STRING::Size size) {
// Should always be the same x, so no need to store that (and it should be calculated dynamically on each tick)
// Y shall be calculated on the fly, for better scalability as well
this.text = text;
this.size = size;
}
}
class Rank {
string title;
int minDifficulty;
int pointsRequired;
Rank(string title, int minDifficulty, int pointsRequired) {
this.title = title;
this.minDifficulty = minDifficulty;
this.pointsRequired = pointsRequired;
}
}
const float FILL_DURATION = 175;
const int HEADER_END_Y = 40;
const int RANK_TABLE_HEIGHT = 352;
const int RANK_TABLE_WIDTH = 256;
const int SUB_HEADER_Y = 84;
const int64 MILLISECONDS_IN_SECOND = 1000;
const uint8 RANK_TABLE_BORDER_COLOR = 70;
const uint8 RANK_TABLE_COLOR = 64;
const uint8 RANK_TABLE_FILL_COLOR = 36;
const uint8 RANK_TABLE_HIGHLIGHT_COLOR = 32;
const string BONUS_LEVEL_FILENAME = "Fio7_Bonus_a.j2l";
const string BONUS_LEVEL_QUESTION = "Oh by the way, would you like to play a couple more bonus levels for fun? These two levels represent the original state of the bomb run level (Fio1_b) from Pre-JJ2+ era. The level was split into two parts due to object memory limitations in vanilla JJ2.@@Also, you can always revisit this level from the menu later, even if you do not wish to play these bonus levels right now. What do you say?";
const string BONUS_LEVEL_QUESTION_ALTERNATIVE = "Would you like to give the bonus levels another go?";
const string BONUS_LEVEL_ANSWER_NO = "Maybe later.";
const string BONUS_LEVEL_ANSWER_YES = "Sure!";
const string NEXT_LEVEL_FILENAME = "end";
const array<Label@> LEGENDS = {
Label("Character", STRING::MEDIUM),
Label("Difficulty", STRING::MEDIUM),
Label("|Food collected", STRING::MEDIUM),
Label("|||||Purple gems collected", STRING::MEDIUM),
Label("||||Enemies slain", STRING::MEDIUM),
Label("||Respawns", STRING::MEDIUM),
Label(fioUtils::processText("Cutscenes watched till the end without fast-forward ;)", jjSubscreenWidth / 8 * 3 + 64), STRING::SMALL),
Label("|||||||Total time", STRING::MEDIUM),
Label("||||||||Total score", STRING::MEDIUM),
Label("Rank", STRING::MEDIUM)
};
const array<Rank@> RANKS = {
Rank("|Survivor", 0, 0),
Rank("|Wayfarer", 0, 100000),
Rank("|Voyager", 0, 300000),
Rank("|Explorer", 0, 500000),
Rank("Combatant", 1, 525000),
Rank("Hunter", 1, 550000),
Rank("Fighter", 1, 575000),
Rank("Warrior", 1, 600000),
Rank("||||Mercenary", 2, 650000),
Rank("||||Veteran", 2, 700000),
Rank("||||Elite", 2, 750000),
Rank("||||Savage", 2, 800000),
Rank("||Redeemer", 3, 850000),
Rank("||Godlike", 3, 900000),
Rank("||Deity", 3, 950000),
Rank("||Immortal", 3, 1000000)
};
bool isBonusLevelModalDisplayed = false;
bool isBonusLevelSelected = true;
bool isRankTableDisplayed = false;
float fillElapsed = FILL_DURATION;
float fillPosY = 0;
int headerY = -48;
int rankTableDisplayElapsed = 0; // Maybe not needed
int resultIndex = -1;
jjPAL purplePalette;
array<Label@> results = {
Label("", STRING::MEDIUM),
Label("", STRING::MEDIUM),
Label("", STRING::MEDIUM),
Label("", STRING::MEDIUM),
Label("", STRING::MEDIUM),
Label("", STRING::MEDIUM),
Label("", STRING::MEDIUM),
Label("", STRING::MEDIUM),
Label("", STRING::MEDIUM),
Label("", STRING::MEDIUM)
};
GameSession@ latestFinishedGameSession;
void drawBonusLevelModal(jjCANVAS@ canvas) {
const string bonusLevelQuestion = latestFinishedGameSession.hasBonusLevelBeenPlayed ? BONUS_LEVEL_QUESTION_ALTERNATIVE : BONUS_LEVEL_QUESTION;
const int quarterOfScreenHeight = jjSubscreenHeight / 4;
fioDraw::drawBox(canvas, jjSubscreenWidth / 12, quarterOfScreenHeight, jjSubscreenWidth / 12 * 10, jjSubscreenHeight / 2, HUD_BAR_BACKGROUND_COLOR,
SPRITE::NORMAL, true, HUD_BAR_BORDER_COLOR);
canvas.drawString(jjSubscreenWidth / 2,
jjSubscreenHeight / 4 + (latestFinishedGameSession.hasBonusLevelBeenPlayed ? 144 : 32),
fioUtils::processText(bonusLevelQuestion, jjSubscreenWidth / 4 * 3),
STRING::SMALL, centeredText);
canvas.drawString(jjSubscreenWidth / 2, quarterOfScreenHeight + 232,
isBonusLevelSelected ? "|" + BONUS_LEVEL_ANSWER_YES : BONUS_LEVEL_ANSWER_YES,
isBonusLevelSelected ? STRING::MEDIUM : STRING::SMALL,
centeredText);
canvas.drawString(jjSubscreenWidth / 2, quarterOfScreenHeight + 276,
isBonusLevelSelected ? BONUS_LEVEL_ANSWER_NO : "|" + BONUS_LEVEL_ANSWER_NO,
isBonusLevelSelected ? STRING::SMALL : STRING::MEDIUM,
centeredText);
}
void drawRankTable(jjCANVAS@ canvas) {
const int rankTableX = jjSubscreenWidth / 2 + 54;
const int rankTableY = jjSubscreenHeight / 5;
const int ranksStartY = rankTableY + RANK_TABLE_HEIGHT - 8;
const int rankTableBottomDividerY = rankTableY + RANK_TABLE_HEIGHT;
fioDraw::drawBox(canvas, rankTableX, rankTableY, RANK_TABLE_WIDTH, RANK_TABLE_HEIGHT, RANK_TABLE_COLOR, SPRITE::NORMAL, true, RANK_TABLE_BORDER_COLOR);
fioDraw::drawBox(canvas, rankTableX, rankTableY + 31, RANK_TABLE_WIDTH, 1, RANK_TABLE_BORDER_COLOR);
fioDraw::drawBox(canvas, rankTableX + 1, rankTableBottomDividerY - int(fillPosY), RANK_TABLE_WIDTH - 1, int(fillPosY), RANK_TABLE_FILL_COLOR);
// Draw fills and highlight colors before texts in order not to drown them in the color
if (fillElapsed <= 0) {
fioDraw::drawBox(canvas, rankTableX + 1, rankTableBottomDividerY - int(fillPosY), RANK_TABLE_WIDTH - 1, 20, RANK_TABLE_HIGHLIGHT_COLOR,
SPRITE::NORMAL, true);
}
if (latestFinishedGameSession.difficulty < 3) {
fioDraw::drawBox(canvas, rankTableX, rankTableY + getRankTableDifficultyDividerY(), RANK_TABLE_WIDTH, 1, RANK_TABLE_BORDER_COLOR);
}
canvas.drawString(rankTableX + 128, rankTableY + 20, "|Rank", STRING::SMALL, centeredText);
for (uint i = 0; i < RANKS.length(); ++i) {
canvas.drawString(rankTableX + 48, ranksStartY + (int(i) * -20), "||||||||" + RANKS[i].pointsRequired, STRING::SMALL, centeredText);
canvas.drawString(rankTableX + 192, ranksStartY + (int(i) * -20), "" + RANKS[i].title, STRING::SMALL, centeredText);
}
fioDraw::drawBox(canvas, rankTableX + 1, rankTableY + 32, RANK_TABLE_WIDTH - 1, getRankTableGreyAreaLength(latestFinishedGameSession), 76,
SPRITE::TRANSLUCENT);
if (fillElapsed > 0) {
if (fillElapsed % 10 == 0) {
jjSamplePriority(SOUND::AMMO_BLUB1);
}
fillElapsed--;
float fillSpeedY = getRankTableFillLength(latestFinishedGameSession) / FILL_DURATION;
fillPosY += fillSpeedY;
}
}
void drawGemSprite(jjCANVAS@ canvas, int x, int y) {
canvas.drawResizedSprite(x, y, ANIM::PICKUPS, 35, 0, 0.5, 0.5, SPRITE::GEM, 1);
}
void drawResults(jjCANVAS@ canvas) {
const int firstThird = jjSubscreenWidth / 3;
const int lastQuarter = jjSubscreenWidth / 4 * 3;
const int legendsStartY = jjSubscreenHeight / 5 + 6;
drawGemSprite(canvas, jjSubscreenWidth / 2 - 64, 12);
canvas.drawString(jjSubscreenWidth / 2 + 16, 16, "|= New Record", STRING::SMALL, centeredText);
for (uint i = 0; i < LEGENDS.length(); ++i) {
if (resultIndex >= int(i)) {
Label@ legend = LEGENDS[i];
int y = legendsStartY + (jjSubscreenHeight / 20 * i + 2);
// Hacky offset fix for the long text ;)
if (i == 6) {
y += 6;
} else if (i == 7) {
y += 22;
} else if (i >= 8) {
y += 24;
}
canvas.drawString(firstThird, y, legend.text, legend.size, centeredText);
}
}
for (uint i = 0; i < results.length(); ++i) {
if (resultIndex >= int(i)) {
Label@ result = results[i];
int y = legendsStartY + (jjSubscreenHeight / 20 * i + 2);
// Hacky offset fix for the long text ;)
if (i == 6) {
y += 12;
} else if (i == 7) {
y += 22;
} else if (i >= 8) {
y += 24;
}
canvas.drawString(lastQuarter, y, result.text, result.size, centeredText);
if (result.isHighlighted) {
drawGemSprite(canvas, lastQuarter + 24 + jjGetStringWidth(result.text, result.size, centeredText) / 2, y + 4);
}
}
}
}
GameSession@ getLatestFinishedGameSession() {
if (gameSessions.length() > 0) {
gameSessions.sort(function(a, b) { return a.finishedTime > b.finishedTime; });
}
for (uint i = 0; i < gameSessions.length(); i++) {
if (gameSessions[i].finishedTime > 0) {
return gameSessions[i];
}
}
return null;
}
int getRankTableDifficultyDividerY() {
if (latestFinishedGameSession.difficulty == 2) {
return 112;
}
if (latestFinishedGameSession.difficulty == 1) {
return 192;
}
return 272;
}
int getRankTableFillLength(GameSession@ gameSession) {
for (int i = RANKS.length() - 1; i > -1; --i) {
if (gameSession.difficulty >= RANKS[i].minDifficulty && gameSession.score >= RANKS[i].pointsRequired) {
return i * 20 + 20;
}
}
return 20;
}
int getRankTableGreyAreaLength(GameSession@ gameSession) {
if (gameSession.difficulty <= 0) {
return 240;
}
if (gameSession.difficulty == 1) {
return 160;
}
if (gameSession.difficulty == 2) {
return 80;
}
return 0;
}
bool isContinueKeyTapped() {
return fioUtils::isKeyTapped(KEY_CODE_SPACE) || fioUtils::isKeyTapped(KEY_CODE_ENTER);
}
// Nevermind the type differences between different values, all should be comparable as int here
bool isNewRecord(RESULT_TYPE resultType, int value) {
bool foundNoPreviousRecord = true;
for (uint i = 0; i < gameSessions.length(); ++i) {
if (gameSessions[i].id != latestFinishedGameSession.id && gameSessions[i].isFinished) {
switch (resultType) {
case DIFFICULTY:
if (gameSessions[i].difficulty >= value) {
foundNoPreviousRecord = false;
}
break;
case FOOD:
if (gameSessions[i].food >= value) {
foundNoPreviousRecord = false;
}
break;
case PURPLE_GEMS:
if (int(gameSessions[i].purpleGemsCollected) >= value) {
foundNoPreviousRecord = false;
}
break;
case ENEMIES_SLAIN:
if (int(gameSessions[i].enemiesSlain) >= value) {
foundNoPreviousRecord = false;
}
break;
case DEATH_COUNT:
if (int(gameSessions[i].deathCount) <= value) {
foundNoPreviousRecord = false;
}
break;
case CUTSCENES_WATCHED:
if (int(gameSessions[i].cutscenesWatched) >= value) {
foundNoPreviousRecord = false;
}
break;
case TOTAL_TIME:
if (gameSessions[i].totalTime <= value) {
foundNoPreviousRecord = false;
}
break;
case SCORE:
if (gameSessions[i].score >= value) {
foundNoPreviousRecord = false;
}
break;
case RANK:
if (mapScoreToRankIndex(gameSessions[i].score) >= value) {
foundNoPreviousRecord = false;
}
break;
}
}
}
return foundNoPreviousRecord;
}
string mapDifficultyToString() {
if (latestFinishedGameSession.difficulty >= 3) {
return "||Turbo";
}
if (latestFinishedGameSession.difficulty == 2) {
return "||||Hard";
}
else if (latestFinishedGameSession.difficulty <= 0) {
return "|Easy";
}
return "Medium";
}
string mapPlayCharOrigToString() {
if (latestFinishedGameSession.charOrig == CHAR::LORI) {
return "||||Lori";
}
if (latestFinishedGameSession.charOrig == CHAR::SPAZ) {
return "||Spaz";
}
return "|Jazz";
}
int mapScoreToRankIndex(int score) {
for (int i = RANKS.length() - 1; i > -1; --i) {
if (score >= RANKS[i].pointsRequired) {
return i;
}
}
return 0;
}
string mapScoreToRankTitle(GameSession@ gameSession) {
for (int i = RANKS.length() - 1; i > -1; --i) {
if (gameSession.difficulty >= RANKS[i].minDifficulty && gameSession.score >= RANKS[i].pointsRequired) {
return RANKS[i].title;
}
}
return RANKS[0].title;
}
void onLevelBegin() {
play.cameraFreeze(TILE * 30, TILE * 20, true, true);
if (latestFinishedGameSession !is null) {
results[0].text = mapPlayCharOrigToString();
results[1].text = mapDifficultyToString();
results[2].text = "|" + latestFinishedGameSession.food;
results[3].text = "|||||" + latestFinishedGameSession.purpleGemsCollected;
results[4].text = "||||" + latestFinishedGameSession.enemiesSlain;
results[5].text = "||" + latestFinishedGameSession.deathCount;
results[6].text = "" + latestFinishedGameSession.cutscenesWatched;
results[7].text = "|||||||" + se::Duration(latestFinishedGameSession.totalTime / int64(SECOND) * MILLISECONDS_IN_SECOND).format("%H:%m:%s");
results[8].text = "||||||||" + latestFinishedGameSession.score;
results[9].text = "" + mapScoreToRankTitle(latestFinishedGameSession);
results[1].isHighlighted = isNewRecord(DIFFICULTY, latestFinishedGameSession.difficulty);
results[2].isHighlighted = isNewRecord(FOOD, latestFinishedGameSession.food);
results[3].isHighlighted = isNewRecord(PURPLE_GEMS, latestFinishedGameSession.purpleGemsCollected);
results[4].isHighlighted = isNewRecord(ENEMIES_SLAIN, latestFinishedGameSession.enemiesSlain);
results[5].isHighlighted = isNewRecord(DEATH_COUNT, latestFinishedGameSession.deathCount);
results[6].isHighlighted = isNewRecord(CUTSCENES_WATCHED, latestFinishedGameSession.cutscenesWatched);
results[7].isHighlighted = isNewRecord(TOTAL_TIME, latestFinishedGameSession.totalTime);
results[8].isHighlighted = isNewRecord(SCORE, latestFinishedGameSession.score);
results[9].isHighlighted = isNewRecord(RANK, mapScoreToRankIndex(latestFinishedGameSession.score));
}
}
void onLevelLoad() {
initializeGlobals(array<Checkpoint@> = {}, 0, 0, 0, false); // Required to initialize text appearances, etc.
@latestFinishedGameSession = getLatestFinishedGameSession();
}
void onLevelReload() {
onLevelLoad();
}
bool onDrawAmmo(jjPLAYER@ play, jjCANVAS@ canvas) {
return true;
}
bool onDrawHealth(jjPLAYER@ play, jjCANVAS@ canvas) {
return true;
}
bool onDrawLives(jjPLAYER@ play, jjCANVAS@ canvas) {
return true;
}
bool onDrawScore(jjPLAYER@ play, jjCANVAS@ canvas) {
canvas.drawString(jjSubscreenWidth / 2, headerY, "Your score", STRING::LARGE, centeredText);
canvas.drawString(jjSubscreenWidth / 2, jjSubscreenHeight - 8, "Score is displayed for the latest finished game in UTC timezone", STRING::SMALL, centeredText);
if (latestFinishedGameSession !is null) {
canvas.drawString(104, 16, "Started at (UTC)", STRING::SMALL, centeredText);
canvas.drawString(104, 32, "" + se::Date(se::TimePoint(latestFinishedGameSession.startedTime)).format("%y-%O-%d %H:%m:%s"), STRING::SMALL, centeredText);
canvas.drawString(jjSubscreenWidth - 104, 16, "Finished at (UTC)", STRING::SMALL, centeredText);
canvas.drawString(jjSubscreenWidth - 104, 32, "" + se::Date(se::TimePoint(latestFinishedGameSession.finishedTime)).format("%y-%O-%d %H:%m:%s"), STRING::SMALL, centeredText);
if (headerY >= HEADER_END_Y) {
canvas.drawString(jjSubscreenWidth / 2, SUB_HEADER_Y, "||||Find It Out!", STRING::MEDIUM, centeredText);
if (resultIndex >= 0) {
drawResults(canvas);
}
string spaceOrEnterAction = "continue";
if (isBonusLevelModalDisplayed) {
spaceOrEnterAction = "choose";
} else if (resultIndex >= int(results.length()) -1) {
string footerTextToDisplay = latestFinishedGameSession.difficulty < 3
? "To unlock higher ranks, play on a harder difficulty. Good luck on your next run!"
: "|||You beat the episode with the hardest difficulty! Mighty impressive!";
canvas.drawString(jjSubscreenWidth / 2, jjSubscreenHeight - 104,
fioUtils::processText(footerTextToDisplay, jjSubscreenWidth / 2),
STRING::SMALL, centeredText);
spaceOrEnterAction = "close";
}
canvas.drawString(jjSubscreenWidth / 2, jjSubscreenHeight - 46, "|Press space/enter to " + spaceOrEnterAction, STRING::SMALL, centeredText);
}
if (isRankTableDisplayed) {
drawRankTable(canvas);
}
if (isBonusLevelModalDisplayed) {
drawBonusLevelModal(canvas);
}
} else {
canvas.drawString(jjSubscreenWidth / 2, jjSubscreenHeight / 2 - 24, "||||There are no finished games yet.", STRING::MEDIUM, centeredText);
canvas.drawString(jjSubscreenWidth / 2, jjSubscreenHeight / 2 + 24, "|Go play some!", STRING::MEDIUM, centeredText);
if (headerY >= HEADER_END_Y) {
canvas.drawString(jjSubscreenWidth / 2, jjSubscreenHeight - 48, "|Press space/enter to close", STRING::SMALL, centeredText);
}
}
return true;
}
void onMain() {
fio::controlPressedKeys();
if (headerY < HEADER_END_Y) {
headerY += 2;
}
}
void onPlayerInput(jjPLAYER@ play) {
// Thanks for the plusMenu example cooba! :-)
play.ballTime = 1;
play.keyFire = false;
play.keyJump = false;
play.keyRun = false;
if (headerY >= HEADER_END_Y) {
if (isContinueKeyTapped() && latestFinishedGameSession is null) {
jjNxt(NEXT_LEVEL_FILENAME, false, true);
} else if (isContinueKeyTapped() && isRankTableDisplayed) {
isRankTableDisplayed = false;
} else if (isContinueKeyTapped() && resultIndex >= int(results.length()) - 1) {
if (isBonusLevelModalDisplayed) {
if (isBonusLevelSelected) {
latestFinishedGameSession.hasBonusLevelBeenPlayed = true;
fioUtils::writeGameSessionsToFile();
}
jjNxt(isBonusLevelSelected ? BONUS_LEVEL_FILENAME : NEXT_LEVEL_FILENAME, false, true);
} else {
isBonusLevelSelected = latestFinishedGameSession.hasBonusLevelBeenPlayed ? false : true;
isBonusLevelModalDisplayed = true;
}
} else if (isContinueKeyTapped() && resultIndex < int(results.length()) - 1) {
if (resultIndex == 8) {
isRankTableDisplayed = true;
}
++resultIndex;
jjSamplePriority(SOUND::AMMO_BOEM1);
} else if (fioUtils::isKeyTapped(KEY_CODE_ARROW_DOWN) && isBonusLevelModalDisplayed && isBonusLevelSelected) {
isBonusLevelSelected = false;
} else if (fioUtils::isKeyTapped(KEY_CODE_ARROW_UP) && isBonusLevelModalDisplayed && !isBonusLevelSelected) {
isBonusLevelSelected = true;
}
}
}
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.