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 "Fio5_c-MLLE-Data-1.j2l" ///@MLLE-Generated
#pragma require "Inferno1.j2t" ///@MLLE-Generated
#pragma require "Fio5_c.j2l" ///@MLLE-Generated
#pragma require "Boss2.j2b"
#include "Fio_common.asc"
#include "Fio_cutscene.asc"
#include "Fio_drawing.asc"
#include "Fio_entities.asc"
#include "Fio_globals.asc"
#include "Fio_utils.asc"
enum Cutscene { CUTSCENE_NONE, CUTSCENE_INTERLUDE, CUTSCENE_BOSS };
class Lift : jjBEHAVIORINTERFACE {
// Stored obj handle for state management outside the class
jjOBJ@ obj;
float renderOffsetY;
bool isLeftWallClosed = false;
Lift(jjOBJ@ obj) {
@this.obj = obj;
obj.behavior = this;
obj.deactivates = false;
obj.isFreezable = false;
obj.state = STATE::WAIT;
isLeftWallClosed = false;
renderOffsetY = 0;
}
void onBehave(jjOBJ@ obj) override {
// NOTE: MAKE SURE TO CALL THE bePlatform WITH THE OLD VALUES INSTEAD OF MODIFYING xPos AND yPos "on the fly"
// Otherwise the player will fall through the platform easily, etc.
float xOld = obj.xPos;
float yOld = obj.yPos;
switch (obj.state) {
case STATE::FADEIN:
{
obj.yPos += fioCut::getPaceNormalized();
}
break;
case STATE::FADEOUT:
{
if (obj.yPos < INTERLUDE_END_Y) {
obj.yPos += fioCut::getPaceNormalized() * 8;
} else {
fio::increaseCutscenesWatchedIfFastForwardWasNotUsed(fioCut::wasFastForwardUsed);
endCutsceneInterlude();
obj.state = STATE::KILL;
return;
}
}
break;
case STATE::KILL:
return;
}
obj.bePlatform(xOld, yOld, TILE * 7, TILE);
}
void onDraw(jjOBJ@ obj) {
// Draw the platform graphics starting from the bottom (where the platform part is)
if (obj.state == STATE::FADEIN) {
renderOffsetY = fioCut::getPaceNormalized() * -1 - 2;
} else if (obj.state == STATE::FADEOUT) {
renderOffsetY = fioCut::getPaceNormalized() * -8 - 2;
} else {
renderOffsetY = -2;
}
// LAYER 4
jjDrawTile(obj.xPos, obj.yPos + renderOffsetY, 2);
jjDrawTile(obj.xPos, obj.yPos - TILE * 6 + renderOffsetY, 2);
jjDrawTile(obj.xPos + TILE * 6, obj.yPos - TILE * 6 + renderOffsetY, 2); // Top right corner
for (uint i = 1; i < 7; i++) {
jjDrawTile(obj.xPos + TILE * i, obj.yPos + renderOffsetY, i % 3 == 0 ? 2 : 4);
if (isLeftWallClosed && i < 6) {
jjDrawTile(obj.xPos, obj.yPos - TILE * i + renderOffsetY, i % 3 == 0 ? 2 : 4);
}
}
for (uint i = 1; i < 6; i++) {
jjDrawTile(obj.xPos + TILE * 6 - TILE * i, obj.yPos - TILE * 6 + renderOffsetY, i == 3 ? 2 : 4);
jjDrawTile(obj.xPos + TILE * 6, obj.yPos - TILE * 6 + TILE * i + renderOffsetY, i == 3 ? 2 : 4);
}
// LAYER 3
jjDrawTile(obj.xPos, obj.yPos + renderOffsetY, 91, TILE::ALLQUADRANTS, 3);
jjDrawTile(obj.xPos, obj.yPos - TILE * 6 + renderOffsetY, 91, TILE::ALLQUADRANTS, 3);
jjDrawTile(obj.xPos + TILE * 6, obj.yPos - TILE * 6 + renderOffsetY, 91, TILE::ALLQUADRANTS, 3); // Top right corner
for (uint i = 1; i < 7; i++) {
jjDrawTile(obj.xPos + TILE * i, obj.yPos + renderOffsetY, i % 3 == 0 ? 91 : 90, TILE::ALLQUADRANTS, 3);
jjDrawTile(obj.xPos, obj.yPos - TILE * i + renderOffsetY, i % 3 == 0 ? 91 : 81, TILE::ALLQUADRANTS, 3);
}
for (uint i = 1; i < 6; i++) {
jjDrawTile(obj.xPos + TILE * 6 - TILE * i, obj.yPos - TILE * 6 + renderOffsetY, i == 3 ? 91 : 90, TILE::ALLQUADRANTS, 3);
jjDrawTile(obj.xPos + TILE * 6, obj.yPos - TILE * 6 + TILE * i + renderOffsetY, i == 3 ? 91 : 81, TILE::ALLQUADRANTS, 3);
}
// SPRITE LAYER CHAINS
for (uint i = 1; i < 10; i++) {
for (uint j = 0; j < 7; j++) {
if (j % 3 == 0) {
jjDrawTile(obj.xPos + TILE * j, obj.yPos - TILE * 16 + TILE * i + renderOffsetY, 110);
}
}
}
}
void addParticleTileExplosions() {
// Convert pos to tile with bit shift to the right by 5
uint16 xTile = uint16(obj.xPos) >> 5;
uint16 yTile = uint16(obj.yPos) >> 5;
jjAddParticleTileExplosion(xTile, yTile, 2, true);
jjAddParticleTileExplosion(xTile, yTile - 6, 2, true);
jjAddParticleTileExplosion(xTile + 6, yTile - 6, 2, true); // Top right corner
for (uint i = 1; i < 7; i++) {
jjAddParticleTileExplosion(xTile + i, yTile, i % 3 == 0 ? 2 : 4, true);
jjAddParticleTileExplosion(xTile, yTile - i, i % 3 == 0 ? 2 : 4, true);
}
for (uint i = 1; i < 6; i++) {
jjAddParticleTileExplosion(xTile + 6 - i, yTile - 6, i == 3 ? 2 : 4, true);
jjAddParticleTileExplosion(xTile + 6, yTile - 6 + i, i == 3 ? 2 : 4, true);
}
// SPRITE LAYER CHAINS
for (uint i = 1; i < 10; i++) {
for (uint j = 0; j < 7; j++) {
if (j % 3 == 0) {
jjAddParticleTileExplosion(xTile + j, yTile - 16 + i, 110, true);
}
}
}
}
}
class SummoningFire {
int x;
int y;
int elapsed;
SummoningFire(int x, int y) {
this.x = x;
this.y = y;
elapsed = 0;
}
bool process(jjPLAYER@ play) {
// Offset player location by 16 pixels
if (abs(play.xPos - float(x + 16)) < 32 && abs(play.yPos - float(y)) < 32) {
play.hurt(1);
}
if (elapsed < SUMMONING_FIRE_DURATION) {
elapsed++;
return false;
}
return true;
}
void draw(jjCANVAS@ canvas) {
canvas.drawTile(x, y, 71 + TILE::ANIMATED);
}
}
class SummoningPickup {
int x;
int y;
OBJECT::Object pickupId;
SummoningPickup(int x, int y, OBJECT::Object pickupId) {
this.x = x;
this.y = y;
this.pickupId = pickupId;
}
}
class SummoningRock {
int x;
int y;
SummoningRock(int x, int y) {
this.x = x + SUMMONING_ROCK_OFFSET;
this.y = y + SUMMONING_ROCK_OFFSET;
}
bool process() {
if (!jjMaskedPixel(x, y + SUMMONING_ROCK_OFFSET)) {
y += SUMMONING_ROCK_SPEED_Y;
return false;
}
int frame = int(jjAnimations[jjAnimSets[ANIM::ROCK].firstAnim].firstFrame);
jjAddParticlePixelExplosion(float(this.x), float(this.y), frame, 0, 0);
jjSample(float(this.x), float(this.y), SOUND::DEVILDEVAN_DRAGONFIRE);
return true;
}
void draw(jjCANVAS@ canvas) {
canvas.drawRotatedSprite(x, y, ANIM::ROCK, 0, 0, jjGameTicks % 1024 * 24, 0.5, 0.5, SPRITE::TINTED, 24);
}
}
const float BOSS_ARENA_START_X = TILE * 120.25;
const float BOSS_ARENA_START_Y = TILE * 226;
const float DURATION_FADE_TOTAL = CUTSCENE_SECOND * 1;
const float DURATION_FADE_BLACKOUT = CUTSCENE_SECOND * 0.5;
const float FRAME_RATE_INTERLUDE_RABBIT = 2;
const float INTERLUDE_END_X = TILE * 39.5;
const float INTERLUDE_END_Y = TILE * 198;
const float PLATFORM_OFFSET_X = -7;
const float PLATFORM_OFFSET_Y = 17;
const float RABBIT_FRIGHTENED_DURATION = CUTSCENE_SECOND * 18;
const float RABBIT_STILL_DURATION = CUTSCENE_SECOND * 15 - 15;
const int BOSS_ARENA_WALL_MOTION_END = 221;
const int LIFT_WALL_MOTION_END = 44;
const int FLOOR_MOTION_START_X = 63;
const int OUTRO_WALL_MOTION_END = 221;
const int SUMMONING_FIRE_DURATION = 140;
const int SUMMONING_FLOOR_FIRST_Y = TILE * 229;
const int SUMMONING_RAIN_INTERVAL = 700;
const int SUMMONING_PICKUPS_INTERVAL = 1050;
const int SUMMONING_ROCK_SPEED_Y = 8;
const int SUMMONING_ROCK_OFFSET = 16;
const int WALL_MOTION_INTERVAL = 5;
const uint SUMMONING_RANGE_MAX_Y = 203;
const uint SUMMONING_RANGE_MIN_Y = 187;
const uint8 MAX_ENEMIES_ON_SUMMONING_FLOOR = 30; // Can go a bit over this too, when there are multiple enemies summoned at once
const uint8 MAX_PICKUPS_ON_SUMMONING_FLOOR = 30; // Can go a bit over this too, when there are multiple pickups summoned at once
array<uint> SUMMONING_RANGES_X = { 116, 123, 129, 135, 142 };
const string NEXT_LEVEL_FILENAME = "Fio6_a.j2l";
const array<Checkpoint@> FIO5_C_CHECKPOINTS = {
Checkpoint(0, INTERLUDE_END_X, INTERLUDE_END_Y),
Checkpoint(1, TILE * 111, TILE * 220)
};
const array<OBJECT::Object> SUMMONING_ENEMIES = {
OBJECT::DEMON,
OBJECT::DOGGYDOGG,
OBJECT::DRAGON,
OBJECT::HELMUT,
OBJECT::SKELETON,
OBJECT::SUCKER
};
const array<OBJECT::Object> SUMMONING_PICKUPS = {
OBJECT::BOUNCERAMMO3,
OBJECT::SEEKERAMMO3,
OBJECT::RFAMMO3,
OBJECT::TOASTERAMMO3,
OBJECT::GUN8AMMO3,
OBJECT::FASTFIRE
};
bool hasBossArenaBeenReached = false;
bool hasLiftMiddleXBeenReached = false;
bool hasFallingPickupsTextBeenShown = false;
bool hasPlatformTextBeenShown = false;
bool isBaronOfHellRendered = true;
bool isBossArenaWallMotionActive = false;
bool isFallingPickupsTextDelayed = false;
bool isLiftCollapseDelaying = false;
bool isLiftExploding = false;
bool isLiftWallMotionActive = false;
bool isOutroActive = false;
bool isOutroWallMotionActive = false;
bool isPlayerWaitingToBeWarpedAway = false;
bool wasCutsceneWatchedTillTheEndBoss = false;
int bossArenaWallMotionOffsetY = 210;
int bossBattleElapsed = 0;
int fallingPickupsElapsed = 0;
int liftCollapseDelayElapsed = 0;
int liftExplosionElapsed = 0;
int liftWallMotionOffsetY = 39;
int outroElapsed = 0;
int outroWallMotionOffsetY = 226;
int playerWarpDelayElapsed = 0;
uint8 activeCutscene = uint8(CUTSCENE_NONE);
uint8 liftExplosionFrame = 0;
ANIM::Set playerAnimSet;
BossFromHell@ baronOfHell;
CharacterWithMindStone@ character;
Lift@ lift;
array<string> cutsceneTextsBoss = {
"||Congratulations on passing the trial! For now you are worthy enough to burn with me!",
"|Look, I am just searching for a lost soul and we want to get out of here as quickly as possible. Just let me pass through and we'll be long gone before you know it.",
"||Hah! Unfortunately it does not work that way! No one leaves this place without battling me first! And no one has made it out alive yet!",
"||Prepare to face the wrath of me and my minions!!!"
};
array<string> cutsceneTextsInterlude = {
"|||Free me! Quickly before they sacrifice my soul!",
"|Woah! Now that was the sound of Nicholas in my head again for sure! Can you hear me Nicholas?",
"|...",
"||So you made it this far...",
"|And now it's that creepy voice again...This is getting really scary! What's next?",
"|AAAAAAAAAAAAA..."
};
array<string> texts = {
"|Duh! No one around. Guess I'm just becoming insane in this heck of a place.",
"|Some kind of an...elevator?",
"|Ouch my back! That was the roughest elevator landing ever!",
"|What? A huge arsenal like this? Waiting just for me? And where are all the bad guys now?",
"|||I'm down here! You must defeat him at all costs! I will support you with my last powers!", // 4th
"|I wonder what awaits me down there. I guess my only choice is to find it out! I'm coming Nicholas!",
"|These pickups must be Nicholas'es doing. I gotta make them count!",
"||WAAAAAAAAAAAAAAAAAAAARRRRRRGGGGGH!",
"|Nicholas? Is that really you in there?",
"|||Yes! And now it's time for us to get the hell out of here! Finally I can feel my powers increasing! Here we go!"
};
array<jjOBJ@> summonedEnemies;
array<Platform@> platforms;
array<SummoningFire@> summoningFires;
array<SummoningRock@> summoningRocks;
void clearSummonedEnemies() {
for (uint i = 0; i < summonedEnemies.length(); i++) {
if (summonedEnemies[i].isActive && summonedEnemies[i].state != STATE::KILL) {
jjOBJ@ enemy = summonedEnemies[i];
enemy.particlePixelExplosion(1); // 1 = fire explosion
enemy.state = STATE::KILL;
}
}
summonedEnemies = array<jjOBJ@>(0);
}
void clearSummoningFires() {
summoningFires = array<SummoningFire@>(0);
}
void clearSummoningRocks() {
for (uint i = 0; i < summoningRocks.length(); i++) {
SummoningRock@ summoningRock = summoningRocks[i];
int frame = int(jjAnimations[jjAnimSets[ANIM::ROCK].firstAnim].firstFrame);
jjAddParticlePixelExplosion(float(summoningRock.x), float(summoningRock.y), frame, 0, 0);
}
summoningRocks = array<SummoningRock@>(0);
}
void controlSky(jjPLAYER@ play) {
if (bossBattleElapsed % SUMMONING_RAIN_INTERVAL == 0 && !isSummoningFloorFullFromEnemies()) {
summonEnemiesFromTheSky();
}
if (bossBattleElapsed % SUMMONING_PICKUPS_INTERVAL == 0 && !isSummoningFloorFullFromPickups()) {
if (!hasFallingPickupsTextBeenShown && !isFallingPickupsTextDelayed) {
fallingPickupsElapsed = 0;
isFallingPickupsTextDelayed = true;
}
summonPickupsFromTheSky();
}
}
void drawSummoningFires(jjCANVAS@ canvas) {
for (uint i = 0; i < summoningFires.length(); i++) {
summoningFires[i].draw(canvas);
}
}
void drawSummoningRocks(jjCANVAS@ canvas) {
for (uint i = 0; i < summoningRocks.length(); i++) {
summoningRocks[i].draw(canvas);
}
}
void endCutsceneBoss() {
fioCut::endCutscene(BOSS_ARENA_START_X, BOSS_ARENA_START_Y);
activeCutscene = CUTSCENE_NONE;
staticPlayerRenderState = STATE_STATIC_PLAYER_RENDER_OFF;
fioCut::clearAnimationChains();
fioUtils::releasePlayer();
@baronOfHell = BossFromHell(fio::getActiveObjectFromLevel(OBJECT::BILSY), getBossHealthByDifficulty());
baronOfHell.obj.state = STATE::DELAYEDSTART;
isCustomBossActivated = true;
isBaronOfHellRendered = false;
jjMusicLoad(BOSS_THEME_FILENAME, false, true);
fioDraw::textIndex = -1;
bossBattleElapsed = 0;
jjGenerateSettableTileArea(4, 115, 227, 2, 1);
jjTileSet(4, 115, 227, 0);
jjTileSet(4, 116, 227, 0);
fio::preserveAmmoForBossBattle();
}
void endCutsceneInterlude() {
// Make sure to call clearPlatform() before trying to delete the lift object and move player away
if (lift.obj.yPos < INTERLUDE_END_Y) {
lift.obj.yPos = INTERLUDE_END_Y;
}
fioCut::endCutscene(INTERLUDE_END_X, INTERLUDE_END_Y);
play.lighting = LIGHTING_TWILIGHT;
activeCutscene = CUTSCENE_NONE;
fioUtils::releasePlayer();
fioDraw::doShowText(2);
staticPlayerRenderState = STATE_STATIC_PLAYER_RENDER_OFF;
liftCollapseDelayElapsed = 0;
isLiftCollapseDelaying = true;
liftExplosionElapsed = 0;
liftExplosionFrame = 0;
isLiftExploding = true;
jjSample(INTERLUDE_END_X - TILE, INTERLUDE_END_Y - TILE, SOUND::COMMON_BENZIN1);
jjSample(INTERLUDE_END_X + TILE, INTERLUDE_END_Y - TILE, SOUND::COMMON_BENZIN1);
jjSample(INTERLUDE_END_X + TILE, INTERLUDE_END_Y + TILE, SOUND::COMMON_BENZIN1);
jjSample(INTERLUDE_END_X - TILE, INTERLUDE_END_Y + TILE, SOUND::COMMON_BENZIN1);
jjTriggers[1] = true;
play.hurt(0, true);
}
int getBossHealthByDifficulty() {
if (jjDifficulty >= 3) return 1337; // PrupelJazz L33t mode xP
if (jjDifficulty == 2) return 999;
if (jjDifficulty == 1) return 666; // GRMBL
return 300;
}
int getPointRewardByDifficulty() {
if (jjDifficulty >= 3) return 66600;
if (jjDifficulty == 2) return 6660;
if (jjDifficulty == 1) return 666; // Ezoons Anno Domini
return 666;
}
RABBIT::Anim getRabbitAnimForFrightenedState() {
if (play.charCurr == CHAR::LORI || play.charCurr == CHAR::SPAZ) {
return RABBIT::DIVE;
}
// Default Jazz
return RABBIT::ENDOFLEVEL;
}
int getRandomUnusedLocationFromRange(uint range, array<uint> usedLocations) {
uint random = jjRandom() % range;
while (usedLocations.find(random) >= 0) {
random = jjRandom() % range;
}
return random;
}
OBJECT::Object getRandomUnusedPickup(array<OBJECT::Object> usedPickups) {
// For safety
if (usedPickups.length() >= SUMMONING_PICKUPS.length()) {
return SUMMONING_PICKUPS[jjRandom() % SUMMONING_PICKUPS.length()];
}
uint random = jjRandom() % SUMMONING_PICKUPS.length();
while (usedPickups.find(SUMMONING_PICKUPS[random]) >= 0) {
random = jjRandom() % SUMMONING_PICKUPS.length();
}
return SUMMONING_PICKUPS[random];
}
void initiatePlatformMovement() {
for (uint i = 0; i < platforms.length(); i++) {
platforms[i].obj.state = STATE::FADEIN;
}
}
void initializeBaronOfHellAnimationChain() {
// 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@> animationsInterludeRabbit = {
fioCut::Animation(15,
TILE * 126.5, TILE * 226,
TILE * 126.5, TILE * 226,
0, 1, 1, ANIM::BILSBOSS, 1, 15, 0, 1, 1, true)
};
fioCut::createAnimationChain(animationsInterludeRabbit);
}
void initializeLift() {
@lift = Lift(jjObjects[jjAddObject(OBJECT::SONICPLATFORM, TILE * 36, TILE * 44 + 2)]);
}
void initializePlatforms() {
platforms = array<Platform@>(0);
// The x argument should represent the location of the left edge of the platform
// and the y argument should represent the top part of the platform
array<array<Node@>> nodeSets = {
{
Node(TILE * 20 + PLATFORM_OFFSET_X, TILE * 66 + PLATFORM_OFFSET_Y),
Node(TILE * 33 + PLATFORM_OFFSET_X, TILE * 66 + PLATFORM_OFFSET_Y),
Node(TILE * 33 + PLATFORM_OFFSET_X, TILE * 120 + PLATFORM_OFFSET_Y),
Node(TILE * 20 + PLATFORM_OFFSET_X, TILE * 120 + PLATFORM_OFFSET_Y)
},
{
Node(TILE * 33 + PLATFORM_OFFSET_X, TILE * 120 + PLATFORM_OFFSET_Y),
Node(TILE * 20 + PLATFORM_OFFSET_X, TILE * 120 + PLATFORM_OFFSET_Y),
Node(TILE * 20 + PLATFORM_OFFSET_X, TILE * 66 + PLATFORM_OFFSET_Y),
Node(TILE * 33 + PLATFORM_OFFSET_X, TILE * 66 + PLATFORM_OFFSET_Y)
},
{
Node(TILE * 38 + PLATFORM_OFFSET_X, TILE * 122 + PLATFORM_OFFSET_Y),
Node(TILE * 51 + PLATFORM_OFFSET_X, TILE * 122 + PLATFORM_OFFSET_Y)
},
{
Node(TILE * 84 + PLATFORM_OFFSET_X, TILE * 123 + PLATFORM_OFFSET_Y),
Node(TILE * 97 + PLATFORM_OFFSET_X, TILE * 123 + PLATFORM_OFFSET_Y)
},
{
Node(TILE * 101 + PLATFORM_OFFSET_X, TILE * 123 + PLATFORM_OFFSET_Y),
Node(TILE * 105 + PLATFORM_OFFSET_X, TILE * 126 + PLATFORM_OFFSET_Y),
Node(TILE * 109 + PLATFORM_OFFSET_X, TILE * 123 + PLATFORM_OFFSET_Y),
Node(TILE * 104 + PLATFORM_OFFSET_X, TILE * 126 + PLATFORM_OFFSET_Y)
},
{
Node(TILE * 117 + PLATFORM_OFFSET_X, TILE * 115 + PLATFORM_OFFSET_Y),
Node(TILE * 114 + PLATFORM_OFFSET_X, TILE * 119 + PLATFORM_OFFSET_Y),
Node(TILE * 109 + PLATFORM_OFFSET_X, TILE * 123 + PLATFORM_OFFSET_Y),
Node(TILE * 113 + PLATFORM_OFFSET_X, TILE * 119 + PLATFORM_OFFSET_Y)
},
{
Node(TILE * 121 + PLATFORM_OFFSET_X, TILE * 115 + PLATFORM_OFFSET_Y),
Node(TILE * 141 + PLATFORM_OFFSET_X, TILE * 115 + PLATFORM_OFFSET_Y)
},
{
Node(TILE * 145 + PLATFORM_OFFSET_X, TILE * 115 + PLATFORM_OFFSET_Y),
Node(TILE * 161 + PLATFORM_OFFSET_X, TILE * 113 + PLATFORM_OFFSET_Y),
Node(TILE * 162 + PLATFORM_OFFSET_X, TILE * 115 + PLATFORM_OFFSET_Y),
Node(TILE * 147 + PLATFORM_OFFSET_X, TILE * 117 + PLATFORM_OFFSET_Y)
}
};
array<float> platformSpeeds = {
2.5,
2.5,
2.5,
2.5,
2.5,
2.5,
3.0,
3.0
};
array<uint16> tileIds = {
10, 13
};
}
bool isBaronOfHellDead() {
return @baronOfHell !is null && baronOfHell.energy <= 0;
}
bool isObjOfTypeSummoningEnemy(jjOBJ@ obj) {
return SUMMONING_ENEMIES.find(OBJECT::Object(obj.eventID)) >= 0;
}
bool isObjOfTypeSummoningPickup(jjOBJ@ obj) {
return SUMMONING_PICKUPS.find(OBJECT::Object(obj.eventID)) >= 0;
}
bool isSummoningFloorFullFromEnemies() {
uint8 enemyCountOnSummoningFloor = 0;
for (int i = 1; i < jjObjectCount; ++i) {
jjOBJ@ obj = jjObjects[i];
if (isObjOfTypeSummoningEnemy(obj)
&& obj.xPos > TILE * 114
&& obj.xPos < TILE * 144
&& obj.yPos > TILE * 225
&& obj.yPos < TILE * 230) {
++enemyCountOnSummoningFloor;
}
}
return enemyCountOnSummoningFloor >= MAX_ENEMIES_ON_SUMMONING_FLOOR;
}
bool isSummoningFloorFullFromPickups() {
uint8 pickupCountOnSummoningFloor = 0;
for (int i = 1; i < jjObjectCount; ++i) {
jjOBJ@ obj = jjObjects[i];
if (isObjOfTypeSummoningPickup(obj)
&& obj.xPos > TILE * 114
&& obj.xPos < TILE * 144
&& obj.yPos > TILE * 225
&& obj.yPos < TILE * 230) {
++pickupCountOnSummoningFloor;
}
}
return pickupCountOnSummoningFloor >= MAX_PICKUPS_ON_SUMMONING_FLOOR;
}
void nullifyPointBonusesFromEnemiesAndObjects() {
jjObjectPresets[OBJECT::DEMON].points = 0;
jjObjectPresets[OBJECT::DOGGYDOGG].points = 0;
jjObjectPresets[OBJECT::DRAGON].points = 0;
jjObjectPresets[OBJECT::HELMUT].points = 0;
jjObjectPresets[OBJECT::SKELETON].points = 0;
jjObjectPresets[OBJECT::SUCKER].points = 0;
jjObjectPresets[OBJECT::CARROT].points = 0;
jjObjectPresets[OBJECT::BOUNCERAMMO3].points = 0;
jjObjectPresets[OBJECT::FASTFIRE].points = 0;
jjObjectPresets[OBJECT::GUN8AMMO3].points = 0;
jjObjectPresets[OBJECT::ICEAMMO3].points = 0;
jjObjectPresets[OBJECT::RFAMMO3].points = 0;
jjObjectPresets[OBJECT::SEEKERAMMO3].points = 0;
jjObjectPresets[OBJECT::TOASTERAMMO3].points = 0;
}
// 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);
}
return activeCutscene != CUTSCENE_NONE;
}
void onDrawLayer4(jjPLAYER@ play, jjCANVAS@ canvas) {
if (activeCutscene != CUTSCENE_NONE) {
fioCut::renderAnimations(canvas);
}
if (isBaronOfHellRendered) {
canvas.drawSprite(TILE * 126.5, TILE * 226, ANIM::BILSBOSS, 1, 16);
}
if (isLiftExploding) {
if (liftExplosionElapsed < 114) {
canvas.drawResizedSprite(int(INTERLUDE_END_X), int(INTERLUDE_END_Y), ANIM::AMMO, 5, liftExplosionFrame, 6, 6);
++liftExplosionElapsed;
if (liftExplosionElapsed % 10 == 0) {
++liftExplosionFrame;
}
} else {
isLiftExploding = false;
}
}
fio::renderCommon(play, canvas);
drawSummoningFires(canvas);
drawSummoningRocks(canvas);
jjDrawResizedSprite(TILE * 159.5, TILE * 224.5, ANIM::AMMO, 10, 0, 2, 2);
}
bool onDrawLives(jjPLAYER@ play, jjCANVAS@ canvas) {
return true;
}
bool onDrawScore(jjPLAYER@ play, jjCANVAS@ canvas) {
return activeCutscene != CUTSCENE_NONE;
}
void onFunction0() {
play.lighting = LIGHTING_TWILIGHT;
}
void onFunction1() {
fioDraw::doShowText(0);
}
void onFunction2() {
fioDraw::doShowText(1);
}
void onFunction3() {
forceMoveRight = true;
if (!checkpoints[0].isReached()) {
checkpoints[0].setReached();
}
}
void onFunction4() {
fioDraw::doShowText(3);
}
void onFunction5() {
fioDraw::doShowText(4);
}
void onFunction6() {
fioDraw::doShowText(5);
}
void onFunction7() {
play.lighting = LIGHTING_TWILIGHT;
if (!checkpoints[1].isReached()) {
checkpoints[1].setReached();
nullifyPointBonusesFromEnemiesAndObjects();
}
}
void onFunction8() {
bossArenaWallMotionOffsetY = 210;
forceMoveRight = true;
fioCut::pace = fioCut::NORMAL_PACE; // Dirty hack for forceMoveRight when a cutscene may not be initialized, if the cutscene was watched till the end
}
void onFunction9() {
forceMoveRight = true;
fioDraw::doShowText(8);
}
void onFunction10() {
forceMoveRight = false;
isPlayerHiddenAndUnableToMove = true;
staticPlayerRenderState = STATE_STATIC_PLAYER_RENDER_FOCUSED;
fioDraw::doShowText(9);
outroElapsed = 0;
isOutroActive = true;
}
// Required for each level
void onLevelBegin() {
@character = fio::getCharacterWithMindStoneForPlayer(play);
initializeLift();
initializePlatforms();
initiatePlatformMovement();
}
// Required for each level
void onLevelLoad() {
// NOTE TO SELF: Make sure to initialize globals (custom object preset behaviors) in onLevelLoad instead of onLevelBegin, since apparently
// some events/objects may already be placed in the level prior to running that hook, but initializing things here should ensure that all objects get
// the custom behavior presets set
initializeGlobals(FIO5_C_CHECKPOINTS);
fioDraw::initializeDrawing(texts, array<string>(0), false);
playerAnimSet = fio::getAnimSetForPlayer(jjLocalPlayers[0]);
jjGenerateSettableTileArea(4, 36, 38, 7, 7);
jjGenerateSettableTileArea(4, 114, 210, 1, 11);
jjGenerateSettableTileArea(4, 144, 222, 1, 5);
jjAnimSets[ANIM::ROCK].load();
jjObjectPresets[OBJECT::CARROT].deactivates = false;
for (uint i = 0; i < SUMMONING_PICKUPS.length(); i++) {
jjObjectPresets[SUMMONING_PICKUPS[i]].deactivates = false;
}
}
// Required for each level
void onLevelReload() {
MLLE::ReapplyPalette();
reloadGlobals();
fioDraw::initializeDrawing(texts, array<string>(0), false);
initializePlatforms();
initiatePlatformMovement();
fioUtils::releasePlayer();
bossArenaWallMotionOffsetY = 210;
bossBattleElapsed = 0;
fallingPickupsElapsed = 0;
outroWallMotionOffsetY = 226;
hasBossArenaBeenReached = false;
isBaronOfHellRendered = true;
isFallingPickupsTextDelayed = false;
isCustomBossActivated = false;
summonedEnemies = array<jjOBJ@>(0);
summoningFires = array<SummoningFire@>(0);
summoningRocks = array<SummoningRock@>(0);
for (;bossArenaWallMotionOffsetY < BOSS_ARENA_WALL_MOTION_END; ++bossArenaWallMotionOffsetY) {
jjTileSet(4, 114, bossArenaWallMotionOffsetY, 0);
}
// Not resetting outro wall, since otherwise player won't be able to re-trigger it anymore :-)
jjGenerateSettableTileArea(4, 114, 210, 1, 11);
jjGenerateSettableTileArea(4, 144, 222, 1, 5);
jjGenerateSettableTileArea(4, 115, 227, 2, 1);
jjTileSet(4, 115, 227, 4);
jjTileSet(4, 116, 227, 4);
if (!fio::handleLevelReload()) {
jjGenerateSettableTileArea(4, 36, 39, 7, 7);
for (int liftWallMotionOffsetY = 39; liftWallMotionOffsetY < LIFT_WALL_MOTION_END; ++liftWallMotionOffsetY) {
jjTileSet(4, 36, liftWallMotionOffsetY, liftWallMotionOffsetY == 41 ? 2 : 4);
}
}
}
// Required for each level
void onMain() {
fio::controlPressedKeys();
fioDraw::controlHud();
processSummonedEnemies();
processSummoningFires();
processSummoningRocks();
if (isBossArenaWallMotionActive) {
if (jjGameTicks % WALL_MOTION_INTERVAL == 0 && bossArenaWallMotionOffsetY < BOSS_ARENA_WALL_MOTION_END) {
jjTileSet(4, 114, bossArenaWallMotionOffsetY, bossArenaWallMotionOffsetY == 215 ? 2 : 4);
++bossArenaWallMotionOffsetY;
} else if (bossArenaWallMotionOffsetY >= BOSS_ARENA_WALL_MOTION_END) {
isBossArenaWallMotionActive = false;
}
}
if (isOutroWallMotionActive) {
if (jjGameTicks % WALL_MOTION_INTERVAL == 0 && outroWallMotionOffsetY > OUTRO_WALL_MOTION_END) {
jjTileSet(4, 144, outroWallMotionOffsetY, 0);
--outroWallMotionOffsetY;
} else if (outroWallMotionOffsetY <= OUTRO_WALL_MOTION_END) {
isOutroWallMotionActive = false;
}
}
// An artificial delay to ensure that the player is properly relocated into the place where the particle tile explosions are bound to happen
// Otherwise they won't render to the screen
if (isLiftCollapseDelaying) {
if (liftCollapseDelayElapsed < 10) {
++liftCollapseDelayElapsed;
} else {
isLiftCollapseDelaying = false;
lift.addParticleTileExplosions();
lift.obj.clearPlatform();
lift.obj.delete();
@lift = null;
}
}
if (isLiftWallMotionActive) {
if (jjGameTicks % WALL_MOTION_INTERVAL == 0 && liftWallMotionOffsetY < LIFT_WALL_MOTION_END) {
jjTileSet(4, 36, liftWallMotionOffsetY, liftWallMotionOffsetY == 41 ? 2 : 4);
liftWallMotionOffsetY++;
} else if (liftWallMotionOffsetY >= LIFT_WALL_MOTION_END) {
isLiftWallMotionActive = false;
lift.isLeftWallClosed = true;
// Clear the solid wall again as it is now a part of the lift graphics...
for (int liftWallMotionOffsetY = 39; liftWallMotionOffsetY < LIFT_WALL_MOTION_END; ++liftWallMotionOffsetY) {
jjTileSet(4, 36, liftWallMotionOffsetY, 0);
}
}
}
if (isPlayerWaitingToBeWarpedAway) {
if (playerWarpDelayElapsed < 105) {
playerWarpDelayElapsed++;
} else {
isPlayerWaitingToBeWarpedAway = false;
play.warpToTile(63, 49);
}
}
if (!hasLiftMiddleXBeenReached && play.xPos >= TILE * 39.5) {
hasLiftMiddleXBeenReached = true;
forceMoveRight = false;
isLiftWallMotionActive = true;
isPlayerHiddenAndUnableToMove = true;
fioCut::initializeCutscene(@processTickEvents, cutsceneTextsInterlude);
activeCutscene = CUTSCENE_INTERLUDE;
staticPlayerRenderState = STATE_STATIC_PLAYER_RENDER_IDLE;
}
if (!hasBossArenaBeenReached && play.xPos >= BOSS_ARENA_START_X) {
hasBossArenaBeenReached = true;
forceMoveRight = false;
isBossArenaWallMotionActive = true;
if (!wasCutsceneWatchedTillTheEndBoss) {
isPlayerHiddenAndUnableToMove = true;
fioCut::initializeCutscene(@processTickEvents, cutsceneTextsBoss);
activeCutscene = CUTSCENE_BOSS;
staticPlayerRenderState = STATE_STATIC_PLAYER_RENDER_IDLE;
} else {
endCutsceneBoss();
}
}
if (isCustomBossActivated && isBaronOfHellDead()) {
baronOfHell.obj.particlePixelExplosion(1);
baronOfHell.obj.state = STATE::KILL;
baronOfHell.obj.delete();
fio::rewardPoints(getPointRewardByDifficulty());
isCustomBossActivated = false;
play.invincibility = 140;
clearSummoningFires();
clearSummoningRocks();
clearSummonedEnemies();
outroWallMotionOffsetY = 226;
isOutroWallMotionActive = true;
fioDraw::doShowText(7);
} else if (isCustomBossActivated && !isBaronOfHellDead()) {
++bossBattleElapsed; // Incrementing before controlSky will ensure that modulo conditions won't trigger at bossBattleElapsed = 0
controlSky(play);
}
if (isFallingPickupsTextDelayed && fallingPickupsElapsed < 280) {
++fallingPickupsElapsed;
} else if (isFallingPickupsTextDelayed) {
fioDraw::doShowText(6);
isFallingPickupsTextDelayed = false;
hasFallingPickupsTextBeenShown = true;
}
if (isOutroActive) {
if (outroElapsed == 420) {
play.lighting = 255;
isPlayerHiddenAndUnableToMove = false;
staticPlayerRenderState = STATE_STATIC_PLAYER_RENDER_OFF;
isPlayerUnableToMove = true;
fioUtils::releasePlayer();
}
if (outroElapsed < 490) {
++outroElapsed;
} else {
isOutroActive = false;
fio::handleLevelCycle(NEXT_LEVEL_FILENAME, true);
}
}
}
// Required for each level
void onPlayer(jjPLAYER@ play) {
fio::handlePlayer(play);
if (activeCutscene != CUTSCENE_NONE) {
fioCut::run();
if (!fioCut::isTickEventsProcessed()) {
processTickEvents(play);
fioCut::setTickEventsProcessed(true);
}
}
}
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_BOSS) {
endCutsceneBoss();
} else if (activeCutscene == CUTSCENE_INTERLUDE) {
endCutsceneInterlude();
}
}
}
}
void onRoast(jjPLAYER@ victim, jjPLAYER@ killer) {
fio::saveTriggerStates();
asPlay.savePlayerProperties(play);
}
void processCutsceneBoss(jjPLAYER@ play) {
switch(uint(fioCut::getElapsedCutscene())) {
case CUTSCENE_SECOND:
fioCut::startTextSliding();
break;
case CUTSCENE_SECOND * 28:
isBaronOfHellRendered = false;
initializeBaronOfHellAnimationChain();
jjSample(TILE * 126.5, TILE * 226, SOUND::BILSBOSS_ZIP);
break;
case CUTSCENE_SECOND * 28 + 20:
fio::increaseCutscenesWatchedIfFastForwardWasNotUsed(fioCut::wasFastForwardUsed);
wasCutsceneWatchedTillTheEndBoss = true;
endCutsceneBoss();
break;
}
}
void processCutsceneInterlude(jjPLAYER@ play) {
switch(uint(fioCut::getElapsedCutscene())) {
case CUTSCENE_SECOND:
fioCut::startTextSliding();
break;
case CUTSCENE_SECOND * 2:
lift.obj.state = STATE::FADEIN;
break;
case CUTSCENE_SECOND * 6:
staticPlayerRenderState = STATE_STATIC_PLAYER_RENDER_FOCUSED;
break;
case CUTSCENE_SECOND * 18:
staticPlayerRenderState = STATE_STATIC_PLAYER_RENDER_FRIGHTENED;
break;
case CUTSCENE_SECOND * 24:
lift.obj.state = STATE::FADEOUT;
staticPlayerRenderState = STATE_STATIC_PLAYER_RENDER_FALLING;
jjSamplePriority(SOUND::BILSBOSS_SCARY3);
jjSamplePriority(SOUND::INTRO_BOEM1);
break;
}
}
void processTickEvents(jjPLAYER@ play) {
switch (activeCutscene) {
case CUTSCENE_BOSS:
processCutsceneBoss(play);
break;
case CUTSCENE_INTERLUDE:
processCutsceneInterlude(play);
break;
}
}
void processSummonedEnemies() {
uint j = 0;
uint summonedEnemiesLength = summonedEnemies.length();
for (uint i = 0; i < summonedEnemiesLength; i++) {
if (summonedEnemies[i].isActive && summonedEnemies[i].state != STATE::KILL) {
@summonedEnemies[j] = summonedEnemies[i];
j++;
}
}
summonedEnemies.removeRange(j, summonedEnemiesLength - j);
}
void processSummoningFires() {
uint j = 0;
uint summoningFiresLength = summoningFires.length();
for (uint i = 0; i < summoningFiresLength; i++) {
if (!summoningFires[i].process(play)) {
@summoningFires[j] = summoningFires[i];
j++;
}
}
summoningFires.removeRange(j, summoningFiresLength - j);
}
void processSummoningRocks() {
uint j = 0;
uint summoningRocksLength = summoningRocks.length();
for (uint i = 0; i < summoningRocksLength; i++) {
if (!summoningRocks[i].process()) {
@summoningRocks[j] = summoningRocks[i];
j++;
} else {
summoningFires.insertLast(
SummoningFire(
summoningRocks[i].x - SUMMONING_ROCK_OFFSET,
summoningRocks[i].y - SUMMONING_ROCK_OFFSET
)
);
jjOBJ@ enemyObj = jjObjects[
jjAddObject(
SUMMONING_ENEMIES[jjRandom() % SUMMONING_ENEMIES.length()],
float(summoningRocks[i].x),
float(summoningRocks[i].y)
)
];
summonedEnemies.insertLast(enemyObj);
enemyObj.direction = play.xPos < enemyObj.xPos ? -1 : 1;
// Dragons cannot walk :-)
if (enemyObj.eventID != OBJECT::DRAGON) {
enemyObj.state = STATE::WALK;
}
}
}
summoningRocks.removeRange(j, summoningRocksLength - j);
}
void summonEnemiesFromTheSky() {
array<uint> usedYLocations = array<uint>(0);
// Hacky hack :)
for (uint i = 0; i < SUMMONING_RANGES_X.length() - 1; i++) {
uint amount = jjDifficulty * 1 + 1;
array<uint> usedXLocations = array<uint>(0);
for (uint j = 0; j < amount; j++) {
uint randomX = getRandomUnusedLocationFromRange(SUMMONING_RANGES_X[i + 1] - SUMMONING_RANGES_X[i], usedXLocations);
uint randomY = getRandomUnusedLocationFromRange(SUMMONING_RANGE_MAX_Y - SUMMONING_RANGE_MIN_Y, usedYLocations);
usedXLocations.insertLast(randomX);
usedYLocations.insertLast(randomY);
summoningRocks.insertLast(
SummoningRock(TILE * (SUMMONING_RANGES_X[i] + randomX), TILE * (SUMMONING_RANGE_MIN_Y + randomY))
);
}
}
}
void summonPickupsFromTheSky() {
array<uint> usedYLocations = array<uint>(0);
array<OBJECT::Object> usedPickupIds;
array<SummoningPickup@> pickupsToBeSummoned;
// Hacky hack :)
for (uint i = 0; i < SUMMONING_RANGES_X.length() - 1; i++) {
uint amount = 4 - jjDifficulty;
array<uint> usedXLocations = array<uint>(0);
for (uint j = 0; j < amount; j++) {
uint randomX = getRandomUnusedLocationFromRange(SUMMONING_RANGES_X[i + 1] - SUMMONING_RANGES_X[i], usedXLocations);
uint randomY = getRandomUnusedLocationFromRange(SUMMONING_RANGE_MAX_Y - SUMMONING_RANGE_MIN_Y, usedYLocations);
usedXLocations.insertLast(randomX);
usedYLocations.insertLast(randomY);
OBJECT::Object pickupId = getRandomUnusedPickup(usedPickupIds);
usedPickupIds.insertLast(pickupId);
pickupsToBeSummoned.insertLast(SummoningPickup(
TILE * (SUMMONING_RANGES_X[i] + randomX),
TILE * (SUMMONING_RANGE_MIN_Y + randomY),
getRandomUnusedPickup(usedPickupIds))
);
}
}
pickupsToBeSummoned[jjRandom() % pickupsToBeSummoned.length()].pickupId = OBJECT::CARROT;
for (uint i = 0; i < pickupsToBeSummoned.length(); i++) {
SummoningPickup@ pickup = pickupsToBeSummoned[i];
jjOBJ@ obj = jjObjects[jjAddObject(pickup.pickupId, pickup.x, pickup.y)];
obj.state = STATE::FLOATFALL; // MAKE SURE TO SET THE STATE TO FLOATFALL
}
}
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.