Downloads containing Fio6_Score.j2as

Downloads
Name Author Game Mode Rating
JJ2+ Only: Find It Out (Single Player)Featured Download Superjazz Single player 8.7 Download file

File preview

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;
		}
	}
}