Name | Author | Game Mode | Rating | |||||
---|---|---|---|---|---|---|---|---|
![]() |
Holiday Hare 24![]() |
PurpleJazz | Single player | 10 | ![]() |
#pragma require "HH18Smoke_HH24.asc"
#pragma require "HH24.asc"
#pragma require "SEroller.asc"
#pragma require "TrueColor v13.asc"
#pragma require "AlienPalace.j2t"
#pragma require "HH18E1.j2a"
#pragma require "SEhh24.j2a"
#pragma require "SEhh24dryfire.wav"
#pragma require "SEhh24launcher1.wav"
#pragma require "SEhh24launcher2.wav"
#pragma require "SEhh24launcherswitch.wav"
#pragma require "SEhh24secret.wav"
#pragma require "SEhh24spikeball1.wav"
#pragma require "SEhh24spikeball2.wav"
#pragma require "SEwaterbubble.png"
#pragma require "SExmas.j2a"
#include "HH18Smoke_HH24.asc"
#include "HH24.asc"
#include "SEroller.asc"
#include "TrueColor v13.asc"
///@Event 103=Frozen Alien |-|Enemy |Frozen |Alien
///@Event 104=Penguin Gentleman |-|Enemy |Pengu |Boyo
///@Event 215=Spike Ball |+|Object |Spike |Ball
///@Event 232=Bubble Launcher |+|Object |Bubble |Launch |Angle 1:3 |Angle 2:3 |Switch: 4
///@Event 254=Delet This |-|Modifier |NOT |v
namespace sound {
const SOUND::Sample roller = SOUND::INTRO_BLOW;
const SOUND::Sample spikeballHit = SOUND::INTRO_BOEM1;
const SOUND::Sample spikeballRecover = SOUND::INTRO_BOEM2;
const SOUND::Sample launcherSwitch = SOUND::INTRO_BRAKE;
const SOUND::Sample dryFire = SOUND::INTRO_END;
const SOUND::Sample secret = SOUND::INTRO_GRAB;
const SOUND::Sample launcherHit = SOUND::INTRO_GREN1;
const SOUND::Sample launcherFire = SOUND::INTRO_GREN2;
}
enum CustomEvent {
event_launcher = 232,
event_floor_spike = 252,
event_ceiling_spike = 253,
event_deleter = 254,
}
enum CustomAnim {
anim_roller,
anim_bubble,
anim_penguin,
anim_rope,
anim_launcher,
anim_spike_ball,
anim_poster,
anim_temp_penguin,
anim_temp_pickups,
}
enum BubbleVar {
bubble_size,
bubble_max_size
}
enum LauncherVar {
launcher_angle_1,
launcher_angle_2,
launcher_angle_target,
launcher_angle_alt,
launcher_angle_displayed,
launcher_angular_velocity,
launcher_state,
launcher_liquid_deficit
}
enum LauncherSwitchVar {
launcher_switch_rope_length,
launcher_switch_rope_frame
}
class Bubble : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) override {
obj.state = STATE::FLOAT;
obj.xPos += obj.xSpeed;
obj.yPos += obj.ySpeed;
float squish = 1.f + 0.075f * jjSin(obj.counter);
obj.counter += 9;
if (obj.var[bubble_size] < obj.var[bubble_max_size])
obj.var[bubble_size] = obj.var[bubble_size] + 1;
float scale = 1.f * obj.var[bubble_size] / obj.var[bubble_max_size];
if (isOnScreen(obj)) {
if (jjColorDepth != 8)
TrueColor::DrawResizedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[anim_bubble], 0, 1, scale * squish, scale / squish, 3);
else
jjDrawResizedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[anim_bubble], 0, 0, scale * squish, scale / squish, SPRITE::TRANSLUCENT, 0, 3);
}
bool pop = false;
int width = jjLayerWidth[4] << 5;
int height = jjLayerHeight[4] << 5;
float outOfBounds = 2 * obj.var[1];
pop = obj.xPos < -outOfBounds || obj.yPos < -outOfBounds || obj.xPos > width + outOfBounds || obj.yPos > height + outOfBounds;
int radius = obj.var[0] * 3 / 4;
for (int i = 0; !pop && i < 1024; i += 32) {
int x = int(obj.xPos + jjSin(i) * radius);
int y = int(obj.yPos + jjCos(i) * radius);
pop = x >= 0 && y >= 0 && x < width && y < height && jjMaskedPixel(x, y) && jjEventAtLastMaskedPixel != AREA::STOPENEMY;
}
if (pop)
destroy(obj);
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
if (bullet !is null) {
int radius = obj.var[bubble_size] * 3 / 4;
float x = bullet.xPos - obj.xPos;
float y = bullet.yPos - obj.yPos;
if (x * x + y * y < radius * radius)
destroy(obj);
}
return true;
}
void assign(jjOBJ@ obj) {
obj.behavior = this;
obj.counter = 0;
obj.var[bubble_size] = 2;
obj.var[bubble_max_size] = 64;
obj.playerHandling = HANDLING::SPECIAL;
obj.bulletHandling = HANDLING::DETECTBULLET;
obj.deactivates = false;
obj.scriptedCollisions = true;
obj.isBlastable = false;
obj.isFreezable = false;
obj.isTarget = false;
obj.causesRicochet = false;
obj.frameID = 0;
obj.determineCurAnim(ANIM::CUSTOM[anim_bubble], 0);
obj.determineCurFrame();
}
void destroy(jjOBJ@ obj) const {
int count = obj.var[bubble_size] * 4;
for (int i = 0; i < count; i++) {
auto@ part = jjAddParticle(PARTICLE::PIXEL);
if (part !is null) {
uint random = jjRandom();
int angle = random & 1023;
random >>= 10;
float z = (random & 63) / 64.f;
random >>= 6;
float r = sqrt(1.f - z * z) * float(obj.var[bubble_size]);
float xComp = jjSin(angle);
float yComp = jjCos(angle);
part.xPos = obj.xPos + xComp * r;
part.yPos = obj.yPos + yComp * r;
part.xSpeed = xComp + ((random & 15) - 7.5f) * 0.05f;
random >>= 4;
part.ySpeed = yComp + ((random & 15) - 7.5f) * 0.05f;
random >>= 4;
part.pixel.size = 1;
for (int j = 0; j < 4; j++) {
part.pixel.color[j] = (jjColorDepth != 8 ? 33 : 35) + (random & 3);
random >>= 2;
}
}
}
jjSample(obj.xPos, obj.yPos, SOUND::COMMON_PLOP2);
obj.delete();
}
bool isOnScreen(const jjOBJ@ obj) const {
float margin = obj.var[bubble_size] * 2;
float x = obj.xPos;
float y = obj.yPos;
for (int i = 0; i < jjLocalPlayerCount; i++) {
const auto@ player = jjLocalPlayers[i];
float left = player.cameraX;
float top = player.cameraY;
float right = left + jjSubscreenWidth;
float bottom = top + jjSubscreenHeight;
if (x + margin >= left && x - margin <= right && y + margin >= top && y - margin <= bottom)
return true;
}
return false;
}
}
class Launcher : jjBEHAVIORINTERFACE {
float bubbleSpeed = 2.f;
int fireTime = 64;
int rechargeTime = 120;
void onBehave(jjOBJ@ obj) override {
float xScale = 1.f;
float yScale = 1.f;
if (obj.state == STATE::START) {
obj.state = STATE::IDLE;
int x = int(obj.xPos) >> 5;
int y = int(obj.yPos) >> 5;
obj.var[launcher_angle_1] = jjParameterGet(x, y, 0, 3) << 7;
obj.var[launcher_angle_2] = jjParameterGet(x, y, 3, 3) << 7;
if (iabs(obj.var[launcher_angle_2] - obj.var[launcher_angle_1]) > 512) {
auto lesser = obj.var[launcher_angle_1] < obj.var[launcher_angle_2] ? launcher_angle_1 : launcher_angle_2;
obj.var[lesser] = obj.var[lesser] + 1024;
}
obj.var[launcher_angle_target] = obj.var[launcher_angle_1];
obj.var[launcher_angle_alt] = obj.var[launcher_angle_2];
obj.var[launcher_angle_displayed] = obj.var[launcher_angle_target];
int ropeLength = jjParameterGet(x, y, 6, 4) << 5;
if (ropeLength != 0) {
int id = jjAddObject(OBJECT::TRIGGERCRATE, obj.xPos, obj.yPos + ropeLength, obj.objectID, CREATOR::OBJECT, BEHAVIOR::INACTIVE);
if (id > 0) {
jjOBJ@ other = jjObjects[id];
launcherSwitch.assign(other);
other.var[launcher_switch_rope_length] = ropeLength;
}
}
}
if (obj.counter > 0) {
float squish = 1.f + 0.3f * jjSin(obj.counter * 1024 / fireTime);
xScale *= 1.f / squish;
yScale *= squish;
if (obj.var[launcher_liquid_deficit] < rechargeTime) {
obj.var[launcher_liquid_deficit] = obj.var[launcher_liquid_deficit] + obj.counter / 4;
if (obj.var[launcher_liquid_deficit] > rechargeTime)
obj.var[launcher_liquid_deficit] = rechargeTime;
}
if (obj.counter == fireTime * 3 / 4) {
float xOff = jjCos(obj.var[launcher_angle_target]);
float yOff = -jjSin(obj.var[launcher_angle_target]);
int id = jjAddObject(OBJECT::BUBBLE, obj.xPos + 40.f * xOff, obj.yPos + 40.f * yOff, obj.objectID, CREATOR::OBJECT, BEHAVIOR::INACTIVE);
if (id > 0) {
jjOBJ@ other = jjObjects[id];
bubble.assign(other);
other.xSpeed = xOff * bubbleSpeed;
other.ySpeed = yOff * bubbleSpeed;
jjSample(other.xPos, other.yPos, sound::launcherFire);
}
}
obj.counter++;
if (obj.counter >= fireTime)
obj.counter = 0;
} else if (obj.var[launcher_liquid_deficit] > 0) {
obj.var[launcher_liquid_deficit] = obj.var[launcher_liquid_deficit] - 1;
}
if (obj.state == STATE::ROTATE) {
int direction = isign(obj.var[launcher_angle_target] - obj.var[launcher_angle_displayed]);
obj.var[launcher_angular_velocity] = obj.var[launcher_angular_velocity] + direction;
if (obj.var[launcher_angular_velocity] < -16)
obj.var[launcher_angular_velocity] = -16;
else if (obj.var[launcher_angular_velocity] > 16)
obj.var[launcher_angular_velocity] = 16;
obj.var[launcher_angle_displayed] = obj.var[launcher_angle_displayed] + obj.var[launcher_angular_velocity];
int newDirection = isign(obj.var[launcher_angle_target] - obj.var[launcher_angle_displayed]);
if (newDirection != direction) {
obj.var[launcher_angular_velocity] = -obj.var[launcher_angular_velocity] * 5 / 8;
obj.var[launcher_angle_displayed] = obj.var[launcher_angle_target] + obj.var[launcher_angular_velocity];
if (obj.var[launcher_angular_velocity] == 0)
obj.state = STATE::IDLE;
}
}
if (isOnScreen(obj)) {
SPRITE::Mode mode = obj.justHit > 0 ? SPRITE::SINGLECOLOR : SPRITE::NORMAL;
int liquidFrame = obj.var[launcher_liquid_deficit] * jjAnimations[obj.curAnim + 3].frameCount / (rechargeTime + 1);
jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, jjAnimations[obj.curAnim + 2], obj.var[launcher_angle_displayed], xScale, yScale, mode, 15);
if (mode != SPRITE::SINGLECOLOR)
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, jjAnimations[obj.curAnim + 3] + liquidFrame, 0, SPRITE::TRANSLUCENT);
jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, jjAnimations[obj.curAnim + 1], obj.var[launcher_angle_displayed], xScale, yScale, mode, 15);
}
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
if (bullet !is null && obj.counter == 0 && obj.var[launcher_liquid_deficit] == 0) {
obj.justHit = 5;
obj.counter++;
bullet.state = STATE::EXPLODE;
jjSample(obj.xPos, obj.yPos, sound::launcherHit);
}
return true;
}
void assign(jjOBJ@ obj) {
obj.behavior = this;
obj.playerHandling = HANDLING::SPECIAL;
obj.bulletHandling = HANDLING::DETECTBULLET;
obj.deactivates = false;
obj.scriptedCollisions = true;
obj.isBlastable = false;
obj.isFreezable = false;
obj.isTarget = false;
obj.causesRicochet = false;
obj.frameID = 0;
obj.determineCurAnim(ANIM::CUSTOM[anim_launcher], 0);
obj.determineCurFrame();
obj.killAnim = jjAnimations[obj.determineCurAnim(ANIM::CUSTOM[anim_launcher], 1, false)];
}
bool isOnScreen(const jjOBJ@ obj) const {
const float margin = 60.f;
float x = obj.xPos;
float y = obj.yPos;
for (int i = 0; i < jjLocalPlayerCount; i++) {
const auto@ player = jjLocalPlayers[i];
float left = player.cameraX;
float top = player.cameraY;
float right = left + jjSubscreenWidth;
float bottom = top + jjSubscreenHeight;
if (x + margin >= left && x - margin <= right && y + margin >= top && y - margin <= bottom)
return true;
}
return false;
}
}
class LauncherSwitch : jjBEHAVIORINTERFACE {
int revolutionCount = 2;
int spinTime = 110;
float angularVelocity = (2 * revolutionCount + 1) * 1024 / float(spinTime);
float angularAcceleration = angularVelocity / spinTime;
void onBehave(jjOBJ@ obj) override {
int angle = 0;
if (obj.state == STATE::START) {
obj.direction = 1;
obj.state = STATE::IDLE;
} else if (obj.state == STATE::ROTATE) {
int oldAngle = int(0.5f * angularAcceleration * (obj.counter * obj.counter)) & 1023;
obj.counter--;
if (obj.counter <= 0) {
obj.state = STATE::IDLE;
} else {
angle = int(0.5f * angularAcceleration * (obj.counter * obj.counter)) & 1023;
float product = jjSin(angle) * jjSin(oldAngle);
if (product < 0.f || product == 0.f && jjSin(angle) != 0.f)
jjSample(obj.xPos, obj.yPos, sound::launcherSwitch, 63, 20000 + 2000 * obj.counter);
}
}
int length = obj.var[launcher_switch_rope_length];
jjDrawSwingingVineSpriteFromCurFrame(obj.xPos, obj.yPos - length, obj.var[launcher_switch_rope_frame], length, 0, SPRITE::NORMAL, 0, 5);
SPRITE::Mode mode = obj.justHit > 0 ? SPRITE::SINGLECOLOR : SPRITE::NORMAL;
float xScale = abs(jjCos(angle));
jjDrawResizedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, xScale, 1.f, mode, 15);
const auto@ creator = jjObjects[obj.creatorID];
int frame = jjAnimations[obj.curAnim + 1] + (creator.var[angle < 256 || angle >= 768 ? launcher_angle_target : launcher_angle_alt] >> 7 & 7);
jjDrawResizedSpriteFromCurFrame(obj.xPos, obj.yPos, frame, xScale, 1.f, mode, 15);
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
if (bullet !is null && obj.state == STATE::IDLE) {
obj.justHit = 5;
bullet.state = STATE::EXPLODE;
obj.state = STATE::ROTATE;
obj.counter = spinTime;
auto@ creator = jjObjects[obj.creatorID];
int prevAngle = creator.var[launcher_angle_target];
creator.var[launcher_angle_target] = creator.var[launcher_angle_alt];
creator.var[launcher_angle_alt] = prevAngle;
creator.state = STATE::ROTATE;
}
return true;
}
void assign(jjOBJ@ obj) {
obj.behavior = this;
obj.playerHandling = HANDLING::SPECIAL;
obj.bulletHandling = HANDLING::DETECTBULLET;
obj.deactivates = false;
obj.scriptedCollisions = true;
obj.isBlastable = false;
obj.isFreezable = false;
obj.isTarget = false;
obj.causesRicochet = false;
obj.frameID = 0;
obj.determineCurAnim(ANIM::CUSTOM[anim_launcher], 4);
obj.determineCurFrame();
obj.var[launcher_switch_rope_frame] = jjAnimations[jjAnimSets[ANIM::CUSTOM[anim_rope]]];
}
}
class SpikeBall : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) override {
if (obj.state == STATE::START) {
obj.state = STATE::ATTACK;
} else if (obj.state == STATE::HIDE) {
const int delay = 2;
if (obj.counter % delay == 0) {
if (obj.frameID > obj.counter / delay)
obj.frameID--;
else if (obj.frameID < int(jjAnimations[obj.curAnim].frameCount) - 1)
obj.frameID++;
}
obj.causesRicochet = true;
obj.counter--;
if (obj.counter <= 0) {
jjSample(obj.xPos, obj.yPos, sound::spikeballRecover);
obj.state = STATE::ATTACK;
obj.frameID = 0;
obj.causesRicochet = false;
}
}
obj.determineCurFrame();
obj.draw();
if (obj.justHit == 0) {
const auto@ anim = jjAnimations[obj.curAnim + 1];
int frameCount = anim.frameCount;
int timerFrame = obj.counter * frameCount / (getMaxCounter() - 5);
if (timerFrame >= frameCount)
timerFrame = frameCount - 1;
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, anim + timerFrame);
}
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
if (bullet !is null) {
if (obj.state == STATE::ATTACK)
jjSample(obj.xPos, obj.yPos, sound::spikeballHit);
if (!obj.causesRicochet)
bullet.state = STATE::EXPLODE;
obj.justHit = 5;
obj.state = STATE::HIDE;
obj.counter = getMaxCounter();
} else if (obj.state == STATE::ATTACK) {
player.hurt();
}
return true;
}
int getMaxCounter() const {
return jjDifficulty < 3 ? 420 - jjDifficulty * 100 : 160;
}
void assign(jjOBJ@ obj) {
obj.behavior = this;
obj.playerHandling = HANDLING::SPECIAL;
obj.bulletHandling = HANDLING::DETECTBULLET;
obj.deactivates = false;
obj.scriptedCollisions = true;
obj.isBlastable = false;
obj.isFreezable = false;
obj.isTarget = false;
obj.causesRicochet = false;
obj.frameID = 0;
obj.determineCurAnim(ANIM::CUSTOM[anim_spike_ball], 0);
obj.determineCurFrame();
}
}
class Poster : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) override {
jjDrawResizedSpriteFromCurFrame(obj.xPos, obj.yPos + 2.f, obj.curFrame, 1.05f, 1.1f, SPRITE::SHADOW);
obj.draw();
}
void assign(jjOBJ@ obj) {
obj.behavior = this;
obj.playerHandling = HANDLING::PARTICLE;
obj.bulletHandling = HANDLING::IGNOREBULLET;
obj.deactivates = true;
obj.scriptedCollisions = false;
obj.isBlastable = false;
obj.isFreezable = false;
obj.isTarget = false;
obj.causesRicochet = false;
obj.frameID = 0;
obj.determineCurAnim(ANIM::CUSTOM[anim_poster], 0);
obj.determineCurFrame();
}
}
class AlienPalaceSpikeImporter {
private int tilesetEnd;
private int width;
private int height;
void initialize() {
const auto@ layer = jjLayers[4];
tilesetEnd = jjTileCount;
width = layer.width;
height = layer.height;
}
array<uint8>@ makeMapping() const {
array<uint8> mapping(256);
mapping[144] = 15;
for (int i = 0; i < 8; i++) {
mapping[145 + i] = 32 + i;
}
return mapping;
}
void squarify(jjPIXELMAP& image) const {
int ia = image[0, 0] == 0 || image[31, 0] == 0 ? 0 : 24;
int iz = image[0, 31] == 0 || image[31, 31] == 0 ? 32 : 8;
int ja = image[0, 0] == 0 || image[0, 31] == 0 ? 0 : 24;
int jz = image[31, 0] == 0 || image[31, 31] == 0 ? 32 : 8;
for (int i = ia; i < iz; i++) {
int im = i < 16 ? 7 : 24;
for (int j = ja; j < jz; j++) {
int jm = j < 16 ? 7 : 24;
image[j, i] = iabs(i - im) < iabs(j - jm) ? image[j, im] : image[jm, i];
}
}
}
void generateCaveSpikes() const {
jjPIXELMAP cave(132);
for (int i = 0; i < 2; i++) {
int src = tilesetEnd + (i == 0 ? 7 : 27);
int dest = src - 3;
jjPIXELMAP image(src);
for (int j = 0; j < 32; j++) {
for (int k = 0; k < 32; k++) {
if (image[k, j] == 0)
image[k, j] = cave[k, j];
}
}
image.save(dest);
jjMASKMAP(src).save(dest);
}
}
void generateCorners() const {
jjMASKMAP masked(true);
for (int i = 0; i < 5; i++) {
jjPIXELMAP image(51 + i);
squarify(image);
image.save(tilesetEnd + 11 + i);
masked.save(tilesetEnd + 11 + i);
}
}
void placeSpikes() const {
bool needsGenerate = true;
for (int i = 1; i < height - 1; i++) {
for (int j = 0; j < width; j++) {
if (j & 3 == 0)
needsGenerate = true;
int tile = jjTileGet(4, j, i);
int replacement = -1;
switch (tile) {
case 59: {
int below = jjTileGet(4, j, i + 1);
if (below != 132)
below = 0;
replacement = jjEventGet(j, i) == AREA::HURT ? tilesetEnd + (below != 132 ? 7 : 4) : below;
break;
}
case 69: {
int above = jjTileGet(4, j, i - 1);
if (above != 132)
above = 0;
replacement = jjEventGet(j, i) == AREA::HURT ? tilesetEnd + (above != 132 ? 27 : 24) : above;
break;
}
}
if (replacement >= 0) {
if (needsGenerate) {
jjGenerateSettableTileArea(4, j, i, 1, 1);
needsGenerate = false;
}
jjTileSet(4, j, i, replacement);
}
}
}
}
void placeCorners() const {
bool needsGenerate = true;
for (int i = 1; i < height - 1; i++) {
for (int j = 0; j < width; j++) {
if (j & 3 == 0)
needsGenerate = true;
int tile = jjTileGet(4, j, i);
int replacement = -1;
switch (tile) {
case 51: case 53: case 180: case 181: {
int below = jjTileGet(4, j, i + 1);
if (below == tilesetEnd + 4 || below == tilesetEnd + 7)
replacement = (tile & 127) + tilesetEnd - (tile != 180 ? 40 : 41);
break;
}
case 54: case 55: case 182: case 183: {
int above = jjTileGet(4, j, i - 1);
if (above == tilesetEnd + 24 || above == tilesetEnd + 27)
replacement = (tile & 127) + tilesetEnd - 40;
break;
}
}
if (replacement >= 0) {
if (needsGenerate) {
jjGenerateSettableTileArea(4, j, i, 1, 1);
needsGenerate = false;
}
jjTileSet(4, j, i, replacement);
}
}
}
}
void opCall() {
initialize();
bool success = jjTilesFromTileset("AlienPalace.j2t", 70, 30, makeMapping());
if (!success) {
jjAlert("|Failed to load AlienPalace.j2t");
jjAlert("|The level will not work correctly.");
return;
}
generateCaveSpikes();
generateCorners();
placeSpikes();
placeCorners();
}
}
Bubble bubble;
Launcher launcher;
LauncherSwitch launcherSwitch;
SpikeBall spikeBall;
Poster poster;
se::DefaultWeaponHook weaponHook;
array<int> gunJammedTimer(jjLocalPlayerCount);
jjPAL palette8;
jjPAL palette16;
int colorDepth = 16;
bool finished = false;
int iabs(int x) {
return x < 0 ? -x : x;
}
int isign(int x) {
return x < 0 ? -1 : x > 0 ? 1 : 0;
}
jjPIXELMAP@ makeRopeSprite() {
jjPIXELMAP tile(5);
jjPIXELMAP result(5, 8);
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 5; j++) {
result[j, i] = tile[i, 13 + j];
}
}
return result;
}
void pingPongAnimation(const jjANIMATION@ src, jjANIMATION@ dest) {
for (uint i = 0; i < src.frameCount; i++) {
jjAnimFrames[dest + i] = jjAnimFrames[src + i];
}
for (uint i = 1; i < src.frameCount - 1; i++) {
jjAnimFrames[dest + dest.frameCount - i] = jjAnimFrames[src + i];
}
}
jjPIXELMAP@ flipXY(jjPIXELMAP@ image) {
int width = image.width;
int height = image.height;
for (int i = 0; i < height; i++) {
for (int j = i + 1; j < width; j++) {
auto t = image[j, i];
image[j, i] = image[i, j];
image[i, j] = t;
}
}
return image;
}
jjMASKMAP@ flipXY(jjMASKMAP@ mask) {
for (int i = 0; i < 32; i++) {
for (int j = i + 1; j < 32; j++) {
auto t = mask[j, i];
mask[j, i] = mask[i, j];
mask[i, j] = t;
}
}
return mask;
}
int colorDistanceSqr(jjPALCOLOR first, jjPALCOLOR second) {
int rs = first.red + second.red;
int r = first.red - second.red;
int g = first.green - second.green;
int b = first.blue - second.blue;
return (1024 + rs) * r * r + 2048 * g * g + (1534 - rs) * b * b;
}
int nearestColor(const jjPAL& palette, jjPALCOLOR color, int begin, int end) {
int result = begin;
int bestDist = 1e9f;
for (int i = begin; i < end; i++) {
int dist = colorDistanceSqr(palette.color[i], color);
if (dist < bestDist) {
result = i;
bestDist = dist;
}
}
return result;
}
void loadBubbleSprite() {
auto@ animSet = jjAnimSets[ANIM::CUSTOM[anim_bubble]];
animSet.allocate(array<uint> = {1 + TrueColor::NumberOfFramesPerImage});
auto@ animation = jjAnimations[animSet];
auto@ frame = jjAnimFrames[animation];
TrueColor::Bitmap image("SEwaterbubble.png");
int width = image.width;
int height = image.height;
frame.hotSpotX = -width / 2;
frame.hotSpotY = -height / 2;
image.saveToAnimFrames(animation + 1, TrueColor::Coordinates(0, 0, width, height, frame.hotSpotX, frame.hotSpotY));
jjPIXELMAP reduced(width, height);
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
const auto@ color = image.pixels[j][i];
if (color.alpha > 0) {
jjPALCOLOR c(color.red * color.alpha / 255, color.green * color.alpha / 255, color.blue * color.alpha / 255);
reduced[j, i] = nearestColor(palette8, c, 144, 176);
}
}
}
reduced.save(frame);
}
jjPALCOLOR darken(jjPALCOLOR color) {
color.red = int(color.red * 0.8f);
color.green = int(color.green * 0.85f);
color.blue = int(color.blue * 0.9f);
return color;
}
void modifyPalette() {
palette16 = jjBackupPalette;
palette16.gradient(jjPALCOLOR(0, 0, 0), jjPALCOLOR(255, 84, 209), 176, 16);
palette16.gradient(jjPALCOLOR(255, 84, 209), jjPALCOLOR(0, 0, 0), 176 + 16, 16);
palette16.gradient(jjPALCOLOR(0, 0, 0), jjPALCOLOR(4, 28, 20), 144, 8);
palette16.gradient(jjPALCOLOR(4, 28, 20), jjPALCOLOR(71, 255, 191), 144 + 8, 24);
palette16.color[208] = jjPALCOLOR(0, 0, 0);
palette16.color[209] = jjPALCOLOR(0, 12, 20);
palette16.color[210] = jjPALCOLOR(0, 20, 33);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 8; j++) {
palette16.color[96 + i * 8 + j] = darken(jjBackupPalette.color[32 + i * 16 + j]);
}
}
palette16.color[128] = darken(jjBackupPalette.color[15]);
palette16.color[129] = darken(jjBackupPalette.color[112]);
palette8 = palette16;
palette8.gradient(jjPALCOLOR(45, 152, 210), jjPALCOLOR(8, 71, 106), 144, 8);
palette8.gradient(jjPALCOLOR(8, 71, 106), jjPALCOLOR(0, 0, 0), 152, 12);
palette8.gradient(jjPALCOLOR(255, 255, 255), jjPALCOLOR(78, 126, 153), 164, 12);
for (int i = 0; i < 32; i++) {
palette8.color[176 + i] = palette16.color[175 - i];
}
palette16.apply();
TrueColor::ProcessPalette();
}
void modifyTiles() {
flipXY(jjMASKMAP(92)).save(92);
flipXY(jjPIXELMAP(105)).save(115);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 6; j++) {
int id = 164 + i * 10 + j;
jjPIXELMAP tile(id);
for (int k = 0; k < 32; k++) {
for (int m = 0; m < 32; m++) {
if (tile[m, k] != 0)
tile[m, k] += 172;
}
}
tile.save(id, true);
}
}
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
int id = 202 + i * 10 + j;
jjPIXELMAP tile(id);
for (int k = 0; k < 32; k++) {
for (int m = 0; m < 32; m++) {
tile[m, k] -= 32;
}
}
tile.save(id);
}
}
array<int> layer4Tiles;
int width = jjLayerWidth[4];
int height = jjLayerHeight[4];
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int tile = jjTileGet(4, j, i);
int index = layer4Tiles.find(tile);
if (index < 0)
layer4Tiles.insertLast(tile);
}
}
int count = layer4Tiles.length();
for (int i = 0; i < count; i++) {
int tile = layer4Tiles[i];
jjPIXELMAP image(tile);
int w = image.width;
int h = image.height;
for (int j = 0; j < h; j++) {
for (int k = 0; k < w; k++) {
int color = image[k, j];
if (color == 0)
continue;
int recolor = color;
if (color == 15) {
recolor = 128;
} else if (color == 112) {
recolor = 129;
} else if (color >= 32 && color < 96 && color & 8 == 0) {
recolor = 96 + (color - 32) / 16 * 8 + color % 8;
}
image[k, j] = recolor;
}
}
image.save(tile);
}
}
void modifyLayers() {
jjLayers[2].spriteMode = SPRITE::BLEND_DODGE;
jjLayers[2].spriteParam = 224;
jjLayers[3].spriteParam = 255;
jjLayers[8].textureSurface = SURFACE::FULLSCREEN;
for (int i = 5; i <= 7; i++) {
jjLayers[i].xSpeedModel = LAYERSPEEDMODEL::FROMSTART;
jjLayers[i].ySpeedModel = LAYERSPEEDMODEL::FROMSTART;
}
jjLAYER aurora(jjLayers[8]);
aurora.texture = TEXTURE::MEZ02;
aurora.spriteMode = SPRITE::BLEND_DODGE;
aurora.spriteParam = 64;
aurora.xAutoSpeed *= -0.89f;
aurora.yAutoSpeed *= -0.86f;
aurora.warpHorizon.stars = false;
aurora.warpHorizon.fadePositionY = 0.7f;
jjLAYER ocean(8, 8);
ocean.texture = TEXTURE::DIAMONDUSBETA;
ocean.textureSurface = SURFACE::FULLSCREEN;
ocean.textureStyle = TEXTURE::REFLECTION;
ocean.reflection.distortion = 96;
ocean.reflection.top = 0.7f;
ocean.reflection.tintColor = 210;
ocean.reflection.tintOpacity = 64;
ocean.xAutoSpeed = 0.07f;
ocean.yAutoSpeed = 0.02f;
auto@ order = jjLayerOrderGet();
order.removeAt(1);
order.insertAt(2, jjLayers[2]);
order.insertAt(order.length() - 2, aurora);
order.insertAt(order.length() - 4, ocean);
jjLayerOrderSet(order);
jjUseLayer8Speeds = true;
jjWaterLayer = 0;
}
void loadResources() {
se::roller.loadAnims(jjAnimSets[ANIM::CUSTOM[anim_roller]]);
se::roller.loadSamples(array<SOUND::Sample> = {sound::roller});
se::roller.setAsWeapon(9, weaponHook);
int src = jjAnimSets[ANIM::CUSTOM[anim_temp_pickups]].load(0, "SExmas.j2a");
int dest = jjAnimSets[ANIM::PICKUPS];
for (int i = 0; i < 95; i++) {
const jjANIMATION@ anim = jjAnimations[src + i];
if (anim.frameCount != 0)
jjAnimations[dest + i] = anim;
}
loadBubbleSprite();
jjAnimSets[ANIM::CUSTOM[anim_temp_penguin]].load(11, "HH18E1.j2a");
jjAnimSets[ANIM::CUSTOM[anim_rope]].allocate(array<uint> = {1});
jjAnimSets[ANIM::CUSTOM[anim_launcher]].load(0, "SEhh24.j2a");
jjAnimSets[ANIM::CUSTOM[anim_spike_ball]].load(1, "SEhh24.j2a");
jjAnimSets[ANIM::CUSTOM[anim_poster]].load(2, "SEhh24.j2a");
jjAnimSets[ANIM::CUSTOM[anim_penguin]].allocate(array<uint> = {jjAnimations[jjAnimSets[ANIM::CUSTOM[anim_temp_penguin]]].frameCount * 2 - 2});
pingPongAnimation(jjAnimations[jjAnimSets[ANIM::CUSTOM[anim_temp_penguin]]], jjAnimations[jjAnimSets[ANIM::CUSTOM[anim_penguin]]]);
auto@ ropeAnimFrame = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::CUSTOM[anim_rope]]]];
makeRopeSprite().save(ropeAnimFrame);
ropeAnimFrame.hotSpotX = -ropeAnimFrame.width / 2;
jjSampleLoad(sound::spikeballHit, "SEhh24spikeball1.wav");
jjSampleLoad(sound::spikeballRecover, "SEhh24spikeball2.wav");
jjSampleLoad(sound::launcherSwitch, "SEhh24launcherswitch.wav");
jjSampleLoad(sound::dryFire, "SEhh24dryfire.wav");
jjSampleLoad(sound::secret, "SEhh24secret.wav");
jjSampleLoad(sound::launcherHit, "SEhh24launcher1.wav");
jjSampleLoad(sound::launcherFire, "SEhh24launcher2.wav");
}
void setBehaviors() {
jjObjectPresets[event_deleter].behavior = BEHAVIOR::INACTIVE;
jjObjectPresets[event_floor_spike].behavior = BEHAVIOR::INACTIVE;
jjObjectPresets[event_ceiling_spike].behavior = BEHAVIOR::INACTIVE;
jjObjectPresets[OBJECT::BUBBLE].behavior = BEHAVIOR::INACTIVE;
jjObjectPresets[OBJECT::LIZARD].determineCurAnim(ANIM::CUSTOM[anim_penguin], 0);
launcher.assign(jjObjectPresets[event_launcher]);
spikeBall.assign(jjObjectPresets[OBJECT::SPIKEBOLL]);
poster.assign(jjObjectPresets[OBJECT::CHESHIRE1]);
SMOKE::FROZENALIEN(OBJECT::DRAGON, 3);
int id = jjAddObject(0, 0.f, 0.f);
if (id > 0) {
auto@ obj = jjObjects[id];
obj.deactivates = false;
obj.behavior = function(obj) {
jjSetWaterLevel(jjLayerHeight[4] * 32 + 128.f, true);
};
}
}
void deleteUnwantedEvents() {
int width = jjLayerWidth[4];
int height = jjLayerHeight[4] - 1;
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int event = jjEventGet(j, i);
if (event == event_deleter)
jjParameterSet(j, i + 1, -12, 32, 0);
}
}
}
void overwriteSpikes() {
int width = jjLayerWidth[4];
int height = jjLayerHeight[4];
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
if (jjEventGet(j, i) == AREA::HURT)
jjEventSet(j, i, jjParameterGet(j, i, 1, 1) == 0 ? event_floor_spike : event_ceiling_spike);
}
}
}
void updateVideoSettings() {
for (int i = 1; i <= 3; i++) {
jjLayers[8 - i].yOffset = -i * (jjSubscreenHeight - 450) / 4;
}
if (jjColorDepth != colorDepth) {
colorDepth = jjColorDepth;
auto@ order = jjLayerOrderGet();
order[order.length() - 1].texture = colorDepth == 8 ? TEXTURE::MEZ02 : TEXTURE::FROMTILES;
order[order.length() - 3].spriteMode = colorDepth == 8 ? SPRITE::TRANSLUCENT : SPRITE::BLEND_DODGE;
order[order.length() - 5].reflection.tintOpacity = colorDepth == 8 ? 128 : 64;
(jjColorDepth == 8 ? palette8 : palette16).apply();
}
}
bool touchesSpikes(jjPLAYER@ player) {
if (player.noclipMode)
return false;
for (int i = 0; i < 2; i++) {
if (i == 0 ? player.ySpeed < 0.f : player.ySpeed > 0.f)
continue;
int x = int(player.xPos) - 12;
int y = int(player.yPos) + (i == 0 ? 21 : -12);
int w = 24;
int event = i == 0 ? event_floor_spike : event_ceiling_spike;
if (jjMaskedHLine(x, w, y)) {
if (int(jjEventAtLastMaskedPixel) == event)
return true;
if (x & 31 > 8) {
w -= ~x & 31;
x += ~x & 31;
if (jjMaskedHLine(x, w, y) && int(jjEventAtLastMaskedPixel) == event)
return true;
}
}
}
return false;
}
jjOBJ@ getBubble(const jjPLAYER@ player) {
for (int i = 0; i < jjObjectCount; i++) {
const auto@ obj = jjObjects[i];
if (cast<const jjBEHAVIORINTERFACE>(obj.behavior) is bubble) {
float x = obj.xPos - player.xPos;
float y = obj.yPos - player.yPos;
if (x * x + y * y < obj.var[bubble_size] * obj.var[bubble_size])
return obj;
}
}
return null;
}
bool onDrawAmmo(jjPLAYER@ player, jjCANVAS@ canvas) {
return weaponHook.drawAmmo(player, canvas);
}
bool onDrawScore(jjPLAYER@ player, jjCANVAS@ canvas) {
HH24::score(player, canvas, false);
return false;
}
void onLevelLoad() {
deleteUnwantedEvents();
AlienPalaceSpikeImporter()();
modifyPalette();
modifyTiles();
modifyLayers();
loadResources();
setBehaviors();
overwriteSpikes();
updateVideoSettings();
HH24::levelLoad();
}
void onLevelReload() {
(jjColorDepth == 8 ? palette8 : palette16).apply();
HH24::levelReload();
}
void onMain() {
updateVideoSettings();
HH24::main();
const auto@ frontLayer = jjLayers[3];
const jjPLAYER@ hiddenPlayer = null;
for (int i = 0; i < jjLocalPlayerCount; i++) {
auto@ player = jjLocalPlayers[i];
int x = int(player.xPos) / 32;
int y = int(player.yPos) / 32;
if (x >= 0 && x < frontLayer.width && y >= 0 && y < frontLayer.height && frontLayer.tileGet(x, y) != 0) {
@hiddenPlayer = player;
break;
}
}
if (hiddenPlayer !is null) {
frontLayer.spriteMode = SPRITE::BLEND_NORMAL;
if (frontLayer.spriteParam == 255)
jjSamplePriority(sound::secret);
if (frontLayer.spriteParam > 45)
frontLayer.spriteParam -= 10;
} else {
if (frontLayer.spriteParam < 255)
frontLayer.spriteParam += 10;
else
frontLayer.spriteMode = SPRITE::NORMAL;
}
array<jjOBJ@> bubbles;
array<jjOBJ@> spikeBalls;
for (int i = 0; i < jjObjectCount; i++) {
auto@ obj = jjObjects[i];
if (obj.isActive) {
const auto@ behavior = cast<const jjBEHAVIORINTERFACE>(obj.behavior);
if (behavior is bubble)
bubbles.insertLast(@obj);
else if (behavior is spikeBall && obj.state == STATE::ATTACK)
spikeBalls.insertLast(@obj);
}
}
for (uint i = 0; i < bubbles.length(); i++) {
auto@ obj = bubbles[i];
int radius = obj.var[bubble_size];
for (uint j = 0; j < spikeBalls.length(); j++) {
auto@ ball = spikeBalls[j];
float x = ball.xPos - obj.xPos;
float y = ball.yPos - obj.yPos;
if (x * x + y * y < radius * radius)
bubble.destroy(obj);
}
}
}
void onPlayer(jjPLAYER@ player) {
HH24::player(player);
if (touchesSpikes(player))
player.hurt();
const auto@ bubble = getBubble(player);
jjSetWaterLevel(bubble !is null ? 0.f : jjLayerHeight[4] * 32 + 128.f, true);
auto event = jjEventGet(int(player.xPos) / 32, int(player.yPos) / 32);
if (event == AREA::EOL) {
finished = true;
HH24::gem::saveGemData();
}
if (finished && bubble !is null) {
const float speed = 0.5f;
player.xSpeed = 0.f;
player.ySpeed = 0.f;
player.xPos += bubble.xSpeed;
player.yPos += bubble.ySpeed;
float dx = bubble.xPos - player.xPos;
float dy = bubble.yPos - player.yPos;
float d = sqrt(dx * dx + dy * dy);
if (d > speed) {
dx *= speed / d;
dy *= speed / d;
}
player.xPos += dx;
player.yPos += dy;
if (jjEventGet(int(bubble.xPos) / 32, int(bubble.yPos) / 32) == AREA::FLYOFF) {
bubble.xSpeed = 0.f;
bubble.ySpeed = 0.f;
}
}
}
void onPlayerInput(jjPLAYER@ player) {
if (!player.keyFire || player.frozen > 0) {
gunJammedTimer[player.localPlayerID] = 0;
return;
}
if (getBubble(player) !is null) {
if (gunJammedTimer[player.localPlayerID] == 0) {
jjSamplePriority(sound::dryFire);
const auto@ frame = jjAnimFrames[player.curFrame];
float x = player.xPos + (player.direction < 0 ? -1 : 1) * (frame.hotSpotX - frame.gunSpotX);
float y = player.yPos + frame.hotSpotY - frame.gunSpotY;
int id = jjAddObject(OBJECT::EXPLOSION, x, y);
if (id > 0)
jjObjects[id].determineCurAnim(ANIM::AMMO, 6);
}
player.keyFire = false;
gunJammedTimer[player.localPlayerID] = 30;
return;
}
if (gunJammedTimer[player.localPlayerID] > 0) {
player.keyFire = false;
gunJammedTimer[player.localPlayerID]--;
return;
}
gunJammedTimer[player.localPlayerID] = -1;
}
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.