Downloads containing Fio3_b.j2as

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

File preview

const bool MLLESetupSuccessful = MLLE::Setup(); ///@MLLE-Generated
#include "MLLE-Include-1.6.asc" ///@MLLE-Generated
#pragma require "Fio3_b-MLLE-Data-1.j2l" ///@MLLE-Generated
#pragma require "wbrg.j2t" ///@MLLE-Generated
#pragma require "Oasis.j2t" ///@MLLE-Generated
#pragma require "Fio3_b.j2l" ///@MLLE-Generated
#include "Fio_common.asc"
#include "Fio_cutscene.asc"
#include "Fio_drawing.asc"
#include "Fio_entities.asc"
#include "Fio_globals.asc"

enum Cutscene { CUTSCENE_NONE, CUTSCENE_INTRO, CUTSCENE_OUTRO };

class Door {
	
	Node@ node;
	int warpID;
	
	Door(Node@ node, int warpID) {
		@this.node = node;
		this.warpID = warpID;
	}
}

class MoreVisibleRapier : RemovableEnemy {
	
	MoreVisibleRapier(jjOBJ@ preset) {
		super(preset);
	}
	
	void onDraw(jjOBJ@ obj) override {
		if (obj.justHit > 0) {
			jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::SINGLECOLOR, 15);
		} else {
			jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::TINTED, 15);
		}
	}
}

const float DURATION_FADE_TOTAL = CUTSCENE_SECOND * 4;
const float DURATION_FADE_BLACKOUT = CUTSCENE_SECOND * 2;
const float RABBIT_HURT_DURATION = CUTSCENE_SECOND * 2 + 10;
const float RABBIT_RUN_1_END_X = TILE * 486;
const float RABBIT_RUN_1_END_Y = TILE * 75;
const float RABBIT_RUN_1_START_X = TILE * 449;
const float RABBIT_RUN_1_START_Y = TILE * 75;
const float RABBIT_RUN_1_DURATION = CUTSCENE_SECOND * 6;
const float RABBIT_RUN_2_END_X = TILE * 490.5;
const float RABBIT_RUN_2_END_Y = TILE * 77;
const float RABBIT_RUN_2_DURATION = CUTSCENE_SECOND;
const float RABBIT_RUN_3_END_X = TILE * 492.5;
const float RABBIT_RUN_3_END_Y = TILE * 79;
const float RABBIT_RUN_3_DURATION = CUTSCENE_SECOND;
const float RABBIT_RUN_4_END_X = TILE * 496;
const float RABBIT_RUN_4_END_Y = TILE * 81;
const float RABBIT_RUN_4_DURATION = CUTSCENE_SECOND;
const float RABBIT_RUN_5_END_X = TILE * 508;
const float RABBIT_RUN_5_END_Y = TILE * 81;
const float RABBIT_RUN_5_DURATION = CUTSCENE_SECOND * 2;
const float RABBIT_RUN_6_END_X = TILE * 512;
const float RABBIT_RUN_6_END_Y = TILE * 79;
const float RABBIT_RUN_6_DURATION = CUTSCENE_SECOND;
const float RABBIT_RUN_7_END_X = TILE * 525;
const float RABBIT_RUN_7_END_Y = TILE * 79;
const float RABBIT_RUN_7_DURATION = CUTSCENE_SECOND * 3;
const float RABBIT_RUN_8_END_X = TILE * 540;
const float RABBIT_RUN_8_END_Y = TILE * 79;
const float RABBIT_RUN_8_DURATION = CUTSCENE_SECOND * 4;
const float RABBIT_RUN_9_START_X = TILE * 2;
const float RABBIT_RUN_9_START_Y = TILE * 79.25;
const float RABBIT_RUN_9_END_X = TILE * 15;
const float RABBIT_RUN_9_END_Y = TILE * 79.25;
const float RABBIT_RUN_9_DURATION = CUTSCENE_SECOND * 4;
const float RABBIT_STILL_DURATION = CUTSCENE_SECOND * 6 - 10;
const float OUTRO_X = TILE * 393;
const float OUTRO_Y = TILE * 34 +16;

const string NEXT_LEVEL_FILENAME = "Fio3_y.j2l";

const array<ArmoryItem@> ARMORY_ITEMS = {
	ArmoryItem(0, "||||Bouncer Power up@+ 20 ammo", 20, 52, ANIM::PICKUPS, 61, 0, @fio::sellArmoryItemBouncerPU),
	ArmoryItem(1, "||||Toaster Power up@+20 ammo", 15, 48, ANIM::PICKUPS, 65, 0, @fio::sellArmoryItemToasterPU),
	ArmoryItem(2, "||||Pepperspray Power up@+20 ammo", 25, 48, ANIM::PICKUPS, 66, 0, @fio::sellArmoryItemPeppersprayPU),
	ArmoryItem(3, "||||+15 Seeker ammo", 25, 48, ANIM::AMMO, 37, 1, @fio::sellArmoryItemSeekerAmmo),
	ArmoryItem(4, "", 15, 44, ANIM::PICKUPS, 72, 5, @fio::sellArmoryItemInvincibility, @fio::canBuyInvincibility), // Text updated later when currentGameSession has been loaded
	ArmoryItem(5, "", 10, 56, ANIM::PICKUPS, 21, 0, @fio::sellArmoryItemPocketCarrot, @fio::canBuyPocketCarrot) // Text updated later when currentGameSession has been loaded
};

const array<Door@> DOORS = {
	Door(Node(TILE * 176, TILE * 76.5), 0),
	Door(Node(TILE * 22, TILE * 15), 1),
	Door(Node(TILE * 156, TILE * 19), 2),
	Door(Node(TILE * 241, TILE * 73.5), 3),
	Door(Node(TILE * 267, TILE * 74.5), 4),
	Door(Node(TILE * 177, TILE * 20), 5),
	Door(Node(TILE * 279, TILE * 74.5), 6),
	Door(Node(TILE * 191, TILE * 20), 7),
	Door(Node(TILE * 361, TILE * 5.5), 8),
	Door(Node(TILE * 316, TILE * 5), 9),
	Door(Node(TILE * 369, TILE * 5.5), 10),
	Door(Node(TILE * 328, TILE * 5), 11),
	Door(Node(TILE * 410, TILE * 30.5), 12),
	Door(Node(TILE * 195, TILE * 34), 13),
	Door(Node(TILE * 410, TILE * 39), 14),
	Door(Node(TILE * 195, TILE * 42), 15)
};

uint8 activeCutscene = uint8(CUTSCENE_NONE);

bool canFireFirstTnt = false;
bool canFireSecondTnt = false;
bool isHoldingKeyUp = false;

ANIM::Set playerAnimSet;

array<Checkpoint@> fio3bCheckpoints = {
	Checkpoint(0, TILE * 15, TILE * 79),
	Checkpoint(1, TILE * 21, TILE * 15),
	Checkpoint(2, TILE * 241, TILE * 73),
	Checkpoint(3, TILE * 341, TILE * 41)
};

array<string> questTexts = {
	fio::getQuestText(10, 15, 10000),
	fio::getQuestTextComplete(10000),
	fio::getQuestTextPerfect(5000)
};

array<string> texts = {
	"|There seems to be a passage behind the last door to the right. With any luck I could get to the other side of the city.",
	"|Damned demons or whoever have put spike bolls as traps for bypassers. I guess my weapons are no use against them either.  I've got to be careful now...",
	"|I guess the statue lies on the higher level. I need something to get up there. Maybe there is a switch.",
	"Temple of Victoria@For the mighty!",
	"|I'm gonna need some heavy tools to get through this.",
	"|I fear these explosives are the only ones to be found nearby. I have to rely on that chance and use them wisely!", // 5
	"|Let's place an explosive here. One should be enough...",
	"|Great! That must be the statue of curses! Now just how can I get close enough to blast it off?",
	"|Alright. Here goes my last boom.",
	"|The spirits in this land are more shiny than I expected. At least that makes them easier to spot from far."
};

array<string> cutsceneTextsIntro = {
	"||||After around twelve hours of tiresome walking through the dry and hot desert...",
	"||||...you finally reach the outskirts of the city of Rine.",
	"||||Weary from the travel, you decide to hide in to the bushes nearby to talk to Nicholas peacefully.",
	"||||You touch your mindstone and hear the voice of Nicholas in your head once again...",
	"|||So, you've reached the ancient city of Rine I told you about. Perfect!",
	"|||The statue of Curses is not going down easily. I hope you have equipped yourself well in order to do that.",
	"|||...Or at least the city should be big enough to find the right tools for it.",
	"|||I wish you good luck! You're going to need it.",
	"|I won't let you down.",
	"||||You no longer hear Nicholas, and decide to move on."
};

array<string> cutsceneTextsOutro = {
	"|Now I should be close enough. Let's get this done!",
	"|Alright. Here goes my last boom. Three...two...one...",
	"|AAARGGH! WHAT IS HAPPENING NOW?"
};

// Call this before setting isHoldingKeyUp to true in the calling method (onPlayerInput)
void controlDoorInput(jjPLAYER@ play) {
	for (uint i = 0; i < DOORS.length(); ++i) {
		Door@ door = DOORS[i];
		
		if (!isHoldingKeyUp
				&& play.keyUp
				&& abs((play.xPos / TILE) - (door.node.x / TILE)) <= 1.f
				&& abs((play.yPos / TILE) - (door.node.y / TILE)) <= 1.f) {
			play.warpToID(door.warpID, true);
			return; // To ensure no extra warps may happen when the player lands on the opposing door side
		}
	}
}

void drawDoorArrows(jjCANVAS@ canvas) {
	for (uint i = 0; i < DOORS.length(); ++i) {
		Door@ door = DOORS[i];
		fioDraw::drawArrowUp(canvas, int(door.node.x), int(door.node.y) + int(jjSin(jjGameTicks % 128 * 8) * 8));
	}
}

int getPointRewardByDifficulty() {
	if (jjDifficulty >= 3) return 30000;
	if (jjDifficulty == 2) return 10000;
	if (jjDifficulty == 1) return 1000;
	return 500;
}

void initializeIntro() {
	fioCut::initializeCutscene(@processTickEvents, cutsceneTextsIntro);
	fioCut::createEventFade(DURATION_FADE_TOTAL, DURATION_FADE_BLACKOUT, false, true);
	fioCut::setTickEventsProcessed(true);
	play.noFire = true;
	play.cameraFreeze(RABBIT_RUN_1_START_X, RABBIT_RUN_1_START_Y, true, true);
	activeCutscene = CUTSCENE_INTRO;
}

void initializeIntroAnimationChain() {
	// Duration, xOrigin, yOrigin, xDestination, yDestination, angle, scaleX, scaleY, animSet, animationId,
	// startingFrame, finalFrame, frameRate, repetitions (optional)
	// REMINDER: DON'T FORGET TO REMOVE THE TRAILING COMMA, SINCE OTHERWISE AS WILL INSERT A NULL HANDLE AFTER THE LAST ACTUAL OBJECT ELEMENT
	
	const array<fioCut::Animation@> animationsIntroRabbit = {
		fioCut::Animation(RABBIT_RUN_1_DURATION,
				RABBIT_RUN_1_START_X, RABBIT_RUN_1_START_Y,
				RABBIT_RUN_1_END_X, RABBIT_RUN_1_END_Y,
				0, 1, 1, playerAnimSet, RABBIT::RUN1, 0, 7, FRAME_RATE_INTRO_RABBIT),
		fioCut::Animation(RABBIT_RUN_2_DURATION,
				RABBIT_RUN_1_END_X, RABBIT_RUN_1_END_Y,
				RABBIT_RUN_2_END_X, RABBIT_RUN_2_END_Y,
				0, 1, 1, playerAnimSet, RABBIT::RUN1, 0, 7, FRAME_RATE_INTRO_RABBIT),
		fioCut::Animation(RABBIT_RUN_3_DURATION,
				RABBIT_RUN_2_END_X, RABBIT_RUN_2_END_Y,
				RABBIT_RUN_3_END_X, RABBIT_RUN_3_END_Y,
				0, 1, 1, playerAnimSet, RABBIT::RUN1, 0, 7, FRAME_RATE_INTRO_RABBIT),
		fioCut::Animation(RABBIT_RUN_4_DURATION,
				RABBIT_RUN_3_END_X, RABBIT_RUN_3_END_Y,
				RABBIT_RUN_4_END_X, RABBIT_RUN_4_END_Y,
				0, 1, 1, playerAnimSet, RABBIT::RUN1, 0, 7, FRAME_RATE_INTRO_RABBIT),
		fioCut::Animation(RABBIT_RUN_5_DURATION,
				RABBIT_RUN_4_END_X, RABBIT_RUN_4_END_Y,
				RABBIT_RUN_5_END_X, RABBIT_RUN_5_END_Y,
				0, 1, 1, playerAnimSet, RABBIT::RUN1, 0, 7, FRAME_RATE_INTRO_RABBIT),
		fioCut::Animation(RABBIT_RUN_6_DURATION,
				RABBIT_RUN_5_END_X, RABBIT_RUN_5_END_Y,
				RABBIT_RUN_6_END_X, RABBIT_RUN_6_END_Y,
				0, 1, 1, playerAnimSet, RABBIT::RUN1, 0, 7, FRAME_RATE_INTRO_RABBIT),
		fioCut::Animation(RABBIT_RUN_7_DURATION,
				RABBIT_RUN_6_END_X, RABBIT_RUN_6_END_Y,
				RABBIT_RUN_7_END_X, RABBIT_RUN_7_END_Y,
				0, 1, 1, playerAnimSet, RABBIT::RUN1, 0, 7, FRAME_RATE_INTRO_RABBIT)
	};
	fioCut::createAnimationChain(animationsIntroRabbit);
}

void initializeOutro() {
	isPlayerUnableToMove = true;
	fioCut::initializeCutscene(@processTickEvents, cutsceneTextsOutro);
	fioCut::setTickEventsProcessed(true);
	fioCut::startTextSliding();
	play.noFire = true;
	play.cameraFreeze(OUTRO_X, OUTRO_Y, true, true);
	activeCutscene = CUTSCENE_OUTRO;
}

void initializeOutroAnimationChain() {
	// Duration, xOrigin, yOrigin, xDestination, yDestination, angle, scaleX, scaleY, animSet, animationId,
	// startingFrame, finalFrame, frameRate, repetitions (optional)
	// REMINDER: DON'T FORGET TO REMOVE THE TRAILING COMMA, SINCE OTHERWISE AS WILL INSERT A NULL HANDLE AFTER THE LAST ACTUAL OBJECT ELEMENT
	
	const array<fioCut::Animation@> animationsOutroRabbit = {
		fioCut::Animation(RABBIT_STILL_DURATION,
				OUTRO_X, OUTRO_Y,
				OUTRO_X, OUTRO_Y,
				0, 1, 1, playerAnimSet, RABBIT::IDLE2, 0, 0, 1, 0, false, -1),
		fioCut::Animation(RABBIT_HURT_DURATION,
				OUTRO_X, OUTRO_Y,
				OUTRO_X, OUTRO_Y,
				0, 1, 1, playerAnimSet, RABBIT::HURT, 0, 0, 2, 1, false, -1)
	};
	fioCut::createAnimationChain(animationsOutroRabbit);
}

// Required for each level
bool onCheat(string &in cheat) {
	return fio::handleCheat(cheat, NEXT_LEVEL_FILENAME);
}

// Required for each level
bool onDrawHealth(jjPLAYER@ play, jjCANVAS@ canvas) {
	fioDraw::animateHud();
	fioDraw::drawHud(play, canvas, activeCutscene != CUTSCENE_NONE);
	
	if (activeCutscene != CUTSCENE_NONE) {
		fioCut::drawCutscene(canvas, centeredText);
	}
	
	if (isPlayerInArmory) {
		fioDraw::drawArmoryInterface(canvas, 38, true);
	}
	
	return activeCutscene != CUTSCENE_NONE;
}

void onDrawLayer4(jjPLAYER@ play, jjCANVAS@ canvas) {
	if (activeCutscene != CUTSCENE_NONE) {
		fioCut::renderAnimations(canvas);
	}
	fio::renderCommon(play, canvas);
	fioDraw::drawArmoryAtPos(canvas, TILE * 414.5, TILE * 39.75, 35); // Offset with +0.5 xTiles and +0.75 yTiles + color 33 for this level
	drawDoorArrows(canvas);
}

bool onDrawLives(jjPLAYER@ play, jjCANVAS@ canvas) {
	return true;
}

bool onDrawScore(jjPLAYER@ play, jjCANVAS@ canvas) {
	return activeCutscene != CUTSCENE_NONE;
}

void onFunction0() {
	if (!checkpoints[0].isReached()) {
		checkpoints[0].setReached();
		fioDraw::doShowOptionalQuest(0);
	}
	play.lighting = LIGHTING_TWILIGHT;
}

void onFunction1() {
	fioDraw::doShowText(0);
}

void onFunction2() {
	fioDraw::doShowText(1);
}

// Replaced obsolete function
void onFunction3() {
	fioDraw::doShowText(9);
}

void onFunction4() {
	if (!checkpoints[2].isReached()) {
		checkpoints[2].setReached();
	}
	play.lighting = LIGHTING_TWILIGHT;
}

void onFunction5() {
	if (!jjTriggers[3]) {
		fioDraw::doShowText(2);
	}
}

void onFunction6() {
	fioDraw::doShowText(3);
}

void onFunction7() {
	if (play.ammo[7] >= 3) {
		play.currWeapon = WEAPON::TNT;
		canFireFirstTnt = true;
		fioDraw::doShowText(6);
	} else {
		fioDraw::doShowText(4);
	}
}

void onFunction8() {
	if (play.currWeapon == WEAPON::TNT) {
		play.currWeapon = WEAPON::BLASTER;
	}
	canFireFirstTnt = false;
}

void onFunction9() {
	fioDraw::doShowText(5);
}

void onFunction10() {
	if (!checkpoints[3].isReached()) {
		checkpoints[3].setReached();
	}
	if (play.currWeapon == WEAPON::TNT) {
		play.currWeapon = WEAPON::BLASTER;
	}
	play.lighting = LIGHTING_TWILIGHT;
	
	// Just in case
	canFireSecondTnt = false;
}

void onFunction11() {
	fioDraw::doShowText(7);
}

void onFunction12() {
	play.ammo[7] = 0;
	fio::rewardPoints(getPointRewardByDifficulty());
	initializeOutro();
}

void onFunction13() {
	if (play.ammo[7] >= 2) {
		play.currWeapon = WEAPON::TNT;
		canFireSecondTnt = true;
		fioDraw::doShowText(6);
	} else {
		fioDraw::doShowText(4);
	}
}

void onFunction14() {
	if (play.currWeapon == WEAPON::TNT) {
		play.currWeapon = WEAPON::BLASTER;
	}
	canFireSecondTnt = false;
}

void onFunction15(bool show) {
	if (show) {
		play.noFire = true;
		isPlayerInArmory = true;
	} else {
		play.noFire = false;
		isPlayerInArmory = false;
	}
}

void onFunction16() {
	if (!checkpoints[1].isReached()) {
		checkpoints[1].setReached();
	}
	play.lighting = LIGHTING_DARK;
}

void onLevelBegin() {
	initializeIntro();
}

// Required for each level
void onLevelLoad() {
	initializeGlobals(fio3bCheckpoints, 10, 10000, 5000);
	fioDraw::initializeDrawing(texts, questTexts, true);
	playerAnimSet = fio::getAnimSetForPlayer(jjLocalPlayers[0]);
	
	// Re-assignment via static declaration
	armoryItems = ARMORY_ITEMS;
	
	// Global indice
	armoryItemIndexInvincibility = 4;
	armoryItemIndexPocketCarrot = 5;
	
	armoryItems[armoryItemIndexInvincibility].text = fio::getInvincibilityItemText();
	armoryItems[armoryItemIndexPocketCarrot].text = fio::getPocketCarrotItemText();
	
	mindstoneCommunicationTileIds = array<uint16>(5);
	mindstoneCommunicationTileIds[0] = 1953;
	mindstoneCommunicationTileIds[1] = 1962;
	mindstoneCommunicationTileIds[2] = 1963;
	mindstoneCommunicationTileIds[3] = 1972;
	mindstoneCommunicationTileIds[4] = 1973;
	
	MoreVisibleRapier(jjObjectPresets[OBJECT::RAPIER]); // Override the default RemovableEnemy from Fio_entities.asc
}

// Required for each level
void onLevelReload() {
	
	reloadGlobals();
	fioDraw::initializeDrawing(texts, questTexts, true);
	
	if (fio::handleLevelReload()) {
		activeCutscene = CUTSCENE_NONE;
	} else {
		initializeIntro();
	}
	
	if (play.ammo[7] == 2 || play.ammo[7] == 1) {
		// Spaghetti code here we come!
		jjTileSet(4, 396, 74, 0);
		jjTileSet(4, 396, 75, 0);
		jjTileSet(4, 396, 76, 0);
		jjTileSet(4, 396, 77, 0);
		jjTileSet(4, 396, 78, 0);
		jjTileSet(4, 396, 79, 0);
		jjTileSet(4, 396, 80, 0);
	}
	
	if (play.ammo[7] == 1) {
		jjTileSet(4, 338, 31, 0);
		jjTileSet(4, 338, 32, 0);
		jjTileSet(4, 338, 33, 0);
		jjTileSet(4, 338, 34, 0);
		jjTileSet(4, 338, 35, 0);
		jjTileSet(4, 338, 36, 0);
		jjTileSet(4, 338, 37, 0);
		jjTileSet(4, 338, 38, 0);
		jjTileSet(4, 338, 39, 0);
		jjTileSet(4, 338, 40, 0);
		jjTileSet(4, 338, 41, 0);
		jjTileSet(4, 339, 31, 0);
		jjTileSet(4, 339, 32, 0);
		jjTileSet(4, 339, 33, 0);
		jjTileSet(4, 339, 34, 0);
		jjTileSet(4, 339, 35, 0);
		jjTileSet(4, 339, 36, 0);
		jjTileSet(4, 339, 37, 0);
		jjTileSet(4, 339, 38, 0);
		jjTileSet(4, 339, 39, 0);
		jjTileSet(4, 339, 40, 0);
		jjTileSet(4, 339, 41, 0);
	}
}

// Required for each level
void onMain() {
	fio::controlPressedKeys();
	fioDraw::controlHud();
}

// Required for each level
void onPlayer(jjPLAYER@ play) {
	fio::handlePlayer(play);
	fio::controlQuest();
	
	if (activeCutscene != CUTSCENE_NONE) {
		fioCut::run();
		if (!fioCut::isTickEventsProcessed()) {
			processTickEvents(play);
			fioCut::setTickEventsProcessed(true);
		}
	}
	if (play.ammo[7] == 2) {
		canFireFirstTnt = false;
		jjEnabledASFunctions[7] = false;
	} else if (play.ammo[7] == 1) {
		canFireSecondTnt = false;
		jjEnabledASFunctions[13] = false;
	}
	
	if (play.currWeapon == WEAPON::TNT && (canFireFirstTnt || canFireSecondTnt)) {
		play.noFire = false;
	} else if (play.currWeapon == WEAPON::TNT) {
		play.noFire = true;
	} else {
		play.noFire = false;
	}
}

void onPlayerInput(jjPLAYER@ play) {
	fio::controlArmoryInput(play);
	fio::controlPlayerInput(play, activeCutscene != CUTSCENE_NONE);
	if (activeCutscene != CUTSCENE_NONE) {
		fioCut::controlPlayerInput(play);
		if (fioCut::isCutsceneSkipInitiatedAfterDelay(play)) {
			fioCut::setCutsceneSkipInitiated();
			if (activeCutscene == CUTSCENE_INTRO) {
				fioCut::endCutscene(RABBIT_RUN_9_END_X, RABBIT_RUN_9_END_Y);
				play.lighting = LIGHTING_TWILIGHT;
				activeCutscene = CUTSCENE_NONE;
				fioUtils::releasePlayer();
			} else if (activeCutscene == CUTSCENE_OUTRO) {
				fio::handleLevelCycle(NEXT_LEVEL_FILENAME, true, true);
				// Don't set activeCutscene to CUTSCENE_NONE so that the cutscene engine can process the level cycle event properly
			}
		}
	}
	controlDoorInput(play);
	
	// To ensure no further warps may happen when the player lands on the opposing door side
	// This should be only a level-specific feature so no need to move it to the common library
	if (play.keyUp) {
		isHoldingKeyUp = true;
	} else {
		isHoldingKeyUp = false;
	}
}

// Required for each level
void onRoast(jjPLAYER@ victim, jjPLAYER@ killer) {
	fio::saveTriggerStates();
	asPlay.savePlayerProperties(play);
}

void processCutsceneIntro(jjPLAYER@ play) {
	switch(uint(fioCut::getElapsedCutscene())) {
		case CUTSCENE_SECOND * 1:
			fioCut::startTextSliding();
			play.lighting = LIGHTING_TWILIGHT;
			break;
		case CUTSCENE_SECOND * 2:
			initializeIntroAnimationChain();
			fioCut::createEventCameraScroll(RABBIT_RUN_1_DURATION, RABBIT_RUN_1_START_X, RABBIT_RUN_1_START_Y,
					RABBIT_RUN_1_END_X, RABBIT_RUN_1_END_Y);
			break;
		case CUTSCENE_SECOND * 8:
			fioCut::createEventCameraScroll(RABBIT_RUN_2_DURATION, RABBIT_RUN_1_END_X, RABBIT_RUN_1_END_Y,
					RABBIT_RUN_2_END_X, RABBIT_RUN_2_END_Y);
			break;
		case CUTSCENE_SECOND * 9:
			fioCut::createEventCameraScroll(RABBIT_RUN_3_DURATION, RABBIT_RUN_2_END_X, RABBIT_RUN_2_END_Y,
					RABBIT_RUN_3_END_X, RABBIT_RUN_3_END_Y);
			break;
		case CUTSCENE_SECOND * 10:
			fioCut::createEventCameraScroll(RABBIT_RUN_4_DURATION, RABBIT_RUN_3_END_X, RABBIT_RUN_3_END_Y,
					RABBIT_RUN_4_END_X, RABBIT_RUN_4_END_Y);
			break;
		case CUTSCENE_SECOND * 11:
			fioCut::createEventCameraScroll(RABBIT_RUN_5_DURATION, RABBIT_RUN_4_END_X, RABBIT_RUN_4_END_Y,
					RABBIT_RUN_5_END_X, RABBIT_RUN_5_END_Y);
			break;
		case CUTSCENE_SECOND * 13:
			fioCut::createEventCameraScroll(RABBIT_RUN_6_DURATION, RABBIT_RUN_5_END_X, RABBIT_RUN_5_END_Y,
					RABBIT_RUN_6_END_X, RABBIT_RUN_6_END_Y);
			break;
		case CUTSCENE_SECOND * 14:
			fioCut::createEventCameraScroll(RABBIT_RUN_7_DURATION, RABBIT_RUN_6_END_X, RABBIT_RUN_6_END_Y,
					RABBIT_RUN_7_END_X, RABBIT_RUN_7_END_Y);
			break;
		case CUTSCENE_SECOND * 22:
			fioCut::isMindstoneCommunicationRendered = true;
			break;
		case CUTSCENE_SECOND * 51:
			fioCut::isMindstoneCommunicationRendered = false;
			break;
		case CUTSCENE_SECOND * 52:
			{
				const array<fioCut::Animation@> furtherAnimationsRabbit = {
					fioCut::Animation(RABBIT_RUN_8_DURATION,
							RABBIT_RUN_7_END_X, RABBIT_RUN_7_END_Y,
							RABBIT_RUN_8_END_X, RABBIT_RUN_8_END_Y,
							0, 1, 1, playerAnimSet, RABBIT::RUN1, 0, 7, FRAME_RATE_INTRO_RABBIT),
					fioCut::Animation(RABBIT_RUN_9_DURATION,
							RABBIT_RUN_9_START_X, RABBIT_RUN_9_START_Y,
							RABBIT_RUN_9_END_X, RABBIT_RUN_9_END_Y,
							0, 1, 1, playerAnimSet, RABBIT::RUN1, 0, 7, FRAME_RATE_INTRO_RABBIT)
				};
				fioCut::createAnimationChain(furtherAnimationsRabbit);
				fioCut::createEventCameraScroll(RABBIT_RUN_8_DURATION, RABBIT_RUN_7_END_X, RABBIT_RUN_7_END_Y,
					RABBIT_RUN_8_END_X, RABBIT_RUN_8_END_Y);
			}
			break;
		case CUTSCENE_SECOND * 54:
			fioCut::createEventFade(DURATION_FADE_TOTAL, DURATION_FADE_BLACKOUT, true, true);
			break;
		case CUTSCENE_SECOND * 56:
			isPlayerHiddenAndUnableToMove = true;
			play.xPos = TILE * 15;
			play.yPos = TILE * 82;
			fioCut::createEventCameraScroll(RABBIT_RUN_9_DURATION, RABBIT_RUN_9_START_X, RABBIT_RUN_9_START_Y,
					RABBIT_RUN_9_END_X, RABBIT_RUN_9_END_Y);
			break;
		case CUTSCENE_SECOND * 60:
			play.xPos = TILE * 15;
			play.yPos = TILE * 79;
			fioUtils::releasePlayer();
			fio::increaseCutscenesWatchedIfFastForwardWasNotUsed(fioCut::wasFastForwardUsed);
			fioCut::endCutscene(RABBIT_RUN_9_END_X, RABBIT_RUN_9_END_Y);
			activeCutscene = CUTSCENE_NONE;
			
			// play.direction = 1 doesn't seem to do anything, so use play.keyRight once instead
			play.keyRight = true;
			break;
	}
}

void processCutsceneOutro(jjPLAYER@ play) {
	switch(uint(fioCut::getElapsedCutscene())) {
		case CUTSCENE_SECOND * 2:
			fioCut::createEventFade(DURATION_FADE_TOTAL, DURATION_FADE_BLACKOUT, true, true);
			break;
		case CUTSCENE_SECOND * 4:
			isPlayerUnableToMove = false;
			isPlayerHiddenAndUnableToMove = true;
			initializeOutroAnimationChain();
			break;
		case CUTSCENE_SECOND * 9:
			jjAddObject(OBJECT::TNT, TILE * 388, TILE * 31);
			break;
		case CUTSCENE_SECOND * 12:
			play.xPos = OUTRO_X;
			play.yPos = OUTRO_Y;
			play.keyLeft = true;
			fioUtils::releasePlayer();
			fio::increaseCutscenesWatchedIfFastForwardWasNotUsed(fioCut::wasFastForwardUsed);
			fio::handleLevelCycle(NEXT_LEVEL_FILENAME, true);
			break;
	}
}

void processTickEvents(jjPLAYER@ play) {
	switch (activeCutscene) {
		case CUTSCENE_INTRO:
			processCutsceneIntro(play);
			break;
		case CUTSCENE_OUTRO:
			processCutsceneOutro(play);
			break;
	}
}