Downloads containing Fio_common.asc

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

File preview

#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();
		}
	}

}