Name | Author | Game Mode | Rating | |||||
---|---|---|---|---|---|---|---|---|
Miscellaneous stuff | Violet CLM | Multiple | N/A |
enum CustomAnimSets {
caCLOUD, caMOUNTAINS, caHILLS, caPOPULATEDHILLS, caGEMSTONE, caGLOW, caEND
};
array<jjANIMSET@> ANIMSETS(caEND, null);
array<uint> FirstAnimFrames(caEND, 0);
void AssembleAnimSet(CustomAnimSets ca, uint frameCount) {
@ANIMSETS[ca] = jjAnimSets[ANIM::CUSTOM[ca]].allocate(array<uint>={frameCount});
FirstAnimFrames[ca] = jjAnimations[ANIMSETS[ca].firstAnim].firstFrame; //global value
}
void onLevelLoad() {
AssembleAnimSet(caCLOUD, 1);
jjANIMFRAME@ frame = jjAnimFrames[FirstAnimFrames[caCLOUD]];
{
jjPIXELMAP cloudMap(0, 0, 7*32, 4*32, 8);
for (uint y = cloudMap.height; y-- != 0; ) { //make background transparent
for (uint x = 0; x < 112; ++x)
if (cloudMap[x,y] == 180) cloudMap[x,y] = 0;
else break;
for (uint x = cloudMap.width - 1; x > 112; --x)
if (cloudMap[x,y] == 180) cloudMap[x,y] = 0;
else break;
}
cloudMap.save(frame);
}
frame.hotSpotX = -frame.width / 2; frame.hotSpotY = -frame.height / 2;
AssembleAnimSet(caMOUNTAINS, 4);
for (uint i = 0; i < 4; ++i) {
@frame = jjAnimFrames[FirstAnimFrames[caMOUNTAINS] + i];
jjPIXELMAP mountainMap(0, (64 + i*4)*32, i<3 ? 8*32 : 9*32, 4*32, 5);
mountainMap.save(frame);
frame.hotSpotX = -frame.width / 2;
frame.hotSpotY = 0;
}
AssembleAnimSet(caHILLS, 4);
for (uint i = 0; i < 4; ++i) {
@frame = jjAnimFrames[FirstAnimFrames[caHILLS] + i];
if (i < 2) {
jjPIXELMAP hillMap(8*32, (64 + i*4)*32, 11*32, 4*32, 5);
hillMap.save(frame);
} else {
jjPIXELMAP towerMap((7+i)*32, (72)*32, (i-1)*32, 6*32, 5);
jjPIXELMAP resizedTowerMap(towerMap.width >> 2, towerMap.height >> 2);
for (uint xx = 0; xx < resizedTowerMap.width; ++xx)
for (uint yy = 0; yy < resizedTowerMap.height; ++yy)
resizedTowerMap[xx,yy] = towerMap[xx<<2, yy<<2];
resizedTowerMap.save(frame);
}
frame.hotSpotX = -frame.width / 2;
frame.hotSpotY = 0;
}
jjSetDarknessColor(jjPALCOLOR(255,255,255));
jjGenerateSettableTileArea(3, 0, 0, jjLayerWidth[3], jjLayerHeight[3]);
jjIsSnowing = true;
jjIsSnowingOutdoorsOnly = true;
jjSnowingType = SNOWING::FLOWER;
jjObjectPresets[OBJECT::STEADYLIGHT].behavior = LensFlare;
jjObjectPresets[OBJECT::STEADYLIGHT].curAnim = jjObjectPresets[OBJECT::SUPERGEM].curAnim;
jjObjectPresets[OBJECT::STEADYLIGHT].lightType = LIGHT::BRIGHT;
jjObjectPresets[OBJECT::STEADYLIGHT].var[0] = jjObjectPresets[OBJECT::BLUEGEM].var[0];
AssembleAnimSet(caGEMSTONE, 1);
{
@frame = jjAnimFrames[FirstAnimFrames[caGEMSTONE]];
jjPIXELMAP gemMap(9*32, 78*32, 1*32, 2*32, 5);
gemMap.save(frame);
}
frame.hotSpotX = -frame.width / 2;
frame.hotSpotY = -frame.height / 2;
jjObjectPresets[OBJECT::RECTBLUEGEM].behavior = Gemstone;
jjObjectPresets[OBJECT::RECTBLUEGEM].curFrame = FirstAnimFrames[caGEMSTONE];
jjObjectPresets[OBJECT::RECTBLUEGEM].playerHandling = HANDLING::PARTICLE;
}
array<jjOBJ@> FlickerLights(jjLocalPlayerCount, null);
void MakeLightingFlicker(jjPLAYER@ play) {
jjOBJ@ light = @FlickerLights[play.localPlayerID];
if (light is null || light.objectID == 0 || light.eventID != OBJECT::STEADYLIGHT) {
@light = @FlickerLights[play.localPlayerID] = jjObjects[jjAddObject(OBJECT::STEADYLIGHT, 0, 0, 0, CREATOR::OBJECT, BEHAVIOR::BEES)];
light.light = 127;
light.lightType = LIGHT::FLICKER;
} else {
light.xPos = play.cameraX + jjSubscreenWidth/2;
light.yPos = play.cameraY + jjSubscreenHeight/2;
}
play.lighting = int8(jjSin(jjRenderFrame) * 25) + 77;
if (CURRENTPALETTE == palEVIL && !jjLowDetail) {
if ((jjRenderFrame & 31) == 31) {
if ((jjRenderFrame & 63) == 31)
jjSetDarknessColor(jjPALCOLOR(0,0,0));
else
jjSetDarknessColor(jjPALCOLOR(128,0,128));
}
}
}
void SetupFlickeringLighting() {
for (int i = 0; i < jjLocalPlayerCount; ++i)
jjLocalPlayers[i].lightType = LIGHT::NONE;
}
class Cloud {
float xPos, yPos, scale;
Cloud() {
xPos = jjSubscreenWidth + 128;
yPos = jjRandom() % (jjSubscreenHeight/2);
scale = (jjRandom() & 0b11110000) / 256.0f;
}
void draw(jjCANVAS@ screen) {
screen.drawResizedSpriteFromCurFrame(int(xPos), int(yPos), FirstAnimFrames[caCLOUD], scale, scale, SPRITE::TRANSLUCENT);
}
};
array<Cloud> Clouds;
void AddClouds() {
if ((jjRandom() & 31) == 0)
Clouds.insertLast(Cloud());
}
void DrawClouds(jjCANVAS@ screen) {
for (uint i = 0; i < Clouds.length; ++i) {
Cloud@ cloud = @Clouds[i];
cloud.xPos -= cloud.scale * 4; //clouds are moved in an onDraw, meaning their speed is tied to jjRenderFrame, not jjGameTicks
if (cloud.xPos < -128) {
Clouds.removeAt(i); --i;
} else {
cloud.draw(screen);
}
}
}
const float MOUNTAINSPEED = 0.12f;
const int MOUNTAINYOFFSET = 200;
const float MOUNTAINYFLOATDIVIDE = float(LEVELHEIGHT*50);
class Mountain {
int xPos, xPosTileWidth, yPos;
float yFloat;
uint curFrame;
Mountain(){}
Mountain(int x, int y) {
xPos = x; xPosTileWidth = x + 800; yPos = y + MOUNTAINYOFFSET; yFloat = y / MOUNTAINYFLOATDIVIDE;
curFrame = FirstAnimFrames[caMOUNTAINS] + (jjRandom() & 3);
}
Mountain(int x, int y, uint frameID) { //for hills
xPos = x; xPosTileWidth = x + 800; yPos = y + HILLYOFFSET; yFloat = y / HILLYFLOATDIVIDE;
//if (frameID < POPULATEDHILLCOUNT)
curFrame = FirstAnimFrames[caPOPULATEDHILLS] + (frameID % POPULATEDHILLCOUNT);
//else
// curFrame = FirstAnimFrames[caHILLS] + (jjRandom() & 1);
}
void draw(int xOffset, int yOffset, float cameraY, jjCANVAS@ screen) {
const int yDraw = yPos - yOffset - int(yFloat * cameraY);
screen.drawSpriteFromCurFrame(xPos - xOffset, yDraw, curFrame);
if (xPos < 200) //do a Tile Width effect
screen.drawSpriteFromCurFrame(xPosTileWidth - xOffset, yDraw, curFrame);
}
};
array<Mountain> Mountains, Hills;
void AddMountains() { //done only once
const array<array<int>> mountainPositions = {
{ 18, 40},
{175, 22},
{375, 40},
{570, 13},
{740, 45},
{ 75, 85},
{270, 55},
{450, 92},
{637, 75},
{ 85, 130},
{360, 130},
{195, 95},
{540, 90},
{745, 140},
{ 95, 165},
{ 20, 150},
{315, 150},
{670, 165},
{570, 185},
{185, 195},
{420, 170},
{485, 150}
};
for (uint i = mountainPositions.length; i-- != 0; ) {
const array<int>@ positions = @mountainPositions[i];
Mountains.insertLast(Mountain(positions[0], positions[1]));
}
}
void DrawMountains(jjPLAYER@ play, jjCANVAS@ screen) {
const int xOffset = int(play.cameraX * MOUNTAINSPEED);
const int yOffset = int((480 - jjSubscreenHeight)/2 + play.cameraY * MOUNTAINSPEED/2);
for (uint i = Mountains.length; i-- != 0; )
Mountains[i].draw(xOffset, yOffset, play.cameraY, screen);
}
const float HILLSPEED = 0.175f;
const int HILLYOFFSET = 336;
const float HILLYFLOATDIVIDE = float(LEVELHEIGHT*47);
const uint POPULATEDHILLCOUNT = 10;
const uint HILLTOPEMPTYSPACE = 24;
void AddHills() {
AssembleAnimSet(caPOPULATEDHILLS, POPULATEDHILLCOUNT);
jjPIXELMAP firstHill(jjAnimFrames[FirstAnimFrames[caHILLS]+0]);
jjPIXELMAP secondHill(jjAnimFrames[FirstAnimFrames[caHILLS]+1]);
const array<jjPIXELMAP@> hills = {@firstHill, @secondHill};
const jjPIXELMAP firstBuilding(jjAnimFrames[FirstAnimFrames[caHILLS]+2]);
const jjPIXELMAP secondBuilding(jjAnimFrames[FirstAnimFrames[caHILLS]+3]);
const array<const jjPIXELMAP@> buildings = {@firstBuilding, @secondBuilding};
for (uint i = 0; i < POPULATEDHILLCOUNT; ++i) {
jjPIXELMAP populatedHill(firstHill.width, firstHill.height + HILLTOPEMPTYSPACE);
for (uint j = 15 + (jjRandom() & 7); j-- != 0; ) { //add buildings
const jjPIXELMAP@ sourceBuilding = @buildings[j&1];
const uint xDraw = (jjRandom()%320); //position the building on the hill
const uint yDraw = 96 + (jjRandom() & 15) - int(jjSin(int(xDraw*1.6f))*96);
//jjAlert("" + xDraw +": " + yDraw);
const int hueShift = (int(jjRandom() & 3) - 8) * 8; //buildings in four different colors
for (uint x = 0; x < sourceBuilding.width; ++x)
for (uint y = 0; y < sourceBuilding.height; ++y) {
if (yDraw + y >= populatedHill.height)
break;
if (sourceBuilding[x,y] != 0)
populatedHill[5 + xDraw + x, yDraw + y] = sourceBuilding[x,y] + hueShift;
}
}
for (uint x = 0; x < firstHill.width; ++x)
for (uint y = 0; y < firstHill.height; ++y) {
uint8 color = hills[i&1][x,y];
if (color != 0) { //draw the hill in front of the building
if ((i&3)>1) color += 2;
populatedHill[x,y+HILLTOPEMPTYSPACE] = color;
}
}
jjANIMFRAME@ frame = jjAnimFrames[FirstAnimFrames[caPOPULATEDHILLS] + i];
populatedHill.save(frame);
frame.hotSpotX = -frame.width / 2;
}
/*for (uint i = 0; i < 2; ++i) { //now that the initial hill sprites have been harvested for the populated hills, recolor the originals to be a bit darker
for (uint x = 0; x < firstHill.width; ++x)
for (uint y = 0; y < firstHill.height; ++y) {
const uint8 color = hills[i][x,y];
if (color != 0)
hills[i][x,y] = color + 2; //palshift to dark
}
hills[i].save(jjAnimFrames[FirstAnimFrames[caHILLS]+i]);
}*/
const array<array<int>> hillPositions = {
{525, 5},
{ 82, 17},
{290, 55},
{ 50, 80},
{480, 85},
{630, 110},
{150, 145},
{280, 160},
{630, 175},
{435, 185},
{ 0, 190},
{195, 225},
{525, 250},
{ 40, 275},
{350, 290},
{560, 315},
{155, 320}
};
for (uint i = hillPositions.length; i-- != 0; ) {
const array<int>@ positions = @hillPositions[i];
Hills.insertLast(Mountain(positions[0], positions[1], i));
}
}
void DrawHills(jjPLAYER@ play, jjCANVAS@ screen) {
const int xOffset = int(play.cameraX * HILLSPEED);
const int yOffset = int((480 - jjSubscreenHeight)/2 + play.cameraY * HILLSPEED/2);
for (uint i = Hills.length; i-- != 0; ) {
Hills[i].draw(xOffset, yOffset, play.cameraY, screen);
}
}
const uint FIRSTFLOORTILE = 1;
const uint LASTFLOORTILE = 15;
const uint FIRSTWALLTILE = LASTFLOORTILE + 1;
const uint LASTWALLTILE = 39;
const uint FIRSTWALLSLOPE = 20;
array<array<bool>> TilesAreFloor(LEVELWIDTH, array<bool>(LEVELHEIGHT, false));
void DetermineWhichTilesAreFloor() {
for (uint xTile = 0; xTile < LEVELWIDTH; ++xTile)
for (uint yTile = 0; yTile < LEVELHEIGHT; ++yTile) {
const uint16 tileID = jjTileGet(5, xTile,yTile) & TILE::RAWRANGE;
TilesAreFloor[xTile][yTile] = tileID >= FIRSTFLOORTILE && tileID <= LASTFLOORTILE;
}
}
bool objectInView(int xPos, int yPos) {
for (int i = 0; i < jjLocalPlayerCount; ++i) {
jjPLAYER@ play = jjLocalPlayers[i];
if (xPos > play.cameraX - 64 && xPos < play.cameraX + jjSubscreenWidth + 64 && yPos > play.cameraY - 64 && yPos < play.cameraY + jjSubscreenHeight + 64)
return true;
}
return false;
}
void DrawReflection(int xPos, int yPos, int direction, uint curFrame, SPRITE::Mode spriteMode, uint8 spriteParam) {
if (!objectInView(xPos, yPos)) return;
const uint xTile = xPos >> 5;
if (xTile < 0 || xTile >= TilesAreFloor.length) return;
const uint yTile = yPos >> 5;
if (yTile < 0 || yTile + 1 >= TilesAreFloor[0].length) return;
if (TilesAreFloor[xTile][yTile] || TilesAreFloor[xTile][yTile+1]) {
const int distance = jjMaskedTopVLine(xPos, yPos, 70);
if (distance <= 70) //within range
jjDrawSpriteFromCurFrame(xPos, yPos + distance*2, curFrame, direction ^ 0x40, spriteMode, spriteParam);
}
}
void DrawReflections() {
for (uint i = 0; i < 32; ++i) {
jjPLAYER@ play = jjPlayers[i];
if (play.isInGame && play.blink == 0 && play.spriteMode == SPRITE::PLAYER)
DrawReflection(int(play.xPos), int(play.yPos), play.direction, play.curFrame, SPRITE::TRANSLUCENTPLAYER, i);
}
for (uint i = jjObjectCount - 1; i > 0; --i) {
jjOBJ@ obj = jjObjects[i];
if (obj.isActive && obj.curFrame != 0 && obj.behavior != Gemstone && obj.behavior != BEHAVIOR::ELECTROBULLET)
DrawReflection(int(obj.xPos), int(obj.yPos), obj.direction, obj.curFrame, SPRITE::TRANSLUCENT, 0);
}
}
uint TILESETSIZE = 150; //first (unused) tileID to be replaced by a blurred wall
bool LASTTILEFLIPPED = false;
array<jjPIXELMAP@>@ BlendTileRow(uint yTile) {
array<jjPIXELMAP@> row;
return @row;
}
const uint LEVELWIDTH = jjLayerWidth[4];
const uint LEVELHEIGHT = jjLayerHeight[4];
const uint YTILEMAX = LEVELHEIGHT - 1;
const uint GENERICFLOORTILE = 9;
const array<int> YOFFSETS = {0, -1, 1};
array<array<uint16>>@ BlendRow(uint yTile) {
array<array<uint16>> results;
for (uint xTile = 0; xTile < LEVELWIDTH; ++xTile) {
array<uint16> blendablePositions;
for (uint layerID = 5; layerID >= 3; --layerID) { //layers with 1,1 speeds
const uint16 tileID = jjTileGet(layerID, xTile, yTile);
const uint16 tileIDRAW = tileID & TILE::RAWRANGE;
if (tileIDRAW >= FIRSTWALLTILE && tileIDRAW <= LASTWALLTILE) {
blendablePositions.insertLast(tileID);
if (layerID == 4 && tileIDRAW != 0 && !blendablePositions.isEmpty() && blendablePositions[0] == GENERICFLOORTILE)
blendablePositions.insertAt(0, tileID);
} else if (tileIDRAW >= FIRSTFLOORTILE && tileIDRAW <= LASTFLOORTILE)
blendablePositions.insertLast(GENERICFLOORTILE);
}
results.insertLast(blendablePositions);
}
return results;
}
jjPIXELMAP@ MakePixelMap(array<uint16>@ blendablePositionsOther) {
if (blendablePositionsOther.isEmpty())
return null;/*
bool isWallNotFloor = false;
for (uint i = 0; i < blendablePositionsOther.length; ++i)
if (blendablePositionsOther[i] != GENERICFLOORTILE) {
isWallNotFloor = true;
break;
}
if (!isWallNotFloor)
return null;*/
jjPIXELMAP blendedTile(blendablePositionsOther[0]);
for (uint additionalTileIndex = 1; additionalTileIndex < blendablePositionsOther.length; ++additionalTileIndex) {
jjPIXELMAP additionalTile(blendablePositionsOther[additionalTileIndex]);
for (uint xx = 0; xx < additionalTile.width; ++xx)
for (uint yy = 0; yy < additionalTile.height; ++yy)
if (additionalTile[xx,yy] != 0)
blendedTile[xx,yy] = additionalTile[xx,yy];
}
return @blendedTile;
}
uint GetColor(int xx, int yy, const array<array<jjPIXELMAP@>>& tileImages) {
int xTile = 1, yTile = 1;
if (xx < 0) { xx += 32; xTile -= 1;}
else if (xx >= 32) { xx -= 32; xTile += 1;}
if (yy < 0) { yy += 32; yTile -= 1;}
else if (yy >= 32) { yy -= 32; yTile += 1;}
jjPIXELMAP@ map = tileImages[yTile][xTile];
if (map is null) return 0;
return map[xx,yy];
}
array<array<jjPIXELMAP@>@> normalBlendedTiles;
array<array<array<uint16>>@> blendableTileIDs;
void BlurWalls() {
if ((jjGameTicks & 15) != 15)
return;
const uint yTile = jjGameTicks >> 4; //for ease of reference
if (yTile == 0) {
normalBlendedTiles.insertLast(@array<jjPIXELMAP@>(LEVELWIDTH, null));
normalBlendedTiles.insertLast(@normalBlendedTiles[0]);
blendableTileIDs.insertLast(@BlendRow(0));
blendableTileIDs.insertLast(@blendableTileIDs[0]);
} else if (yTile >= LEVELHEIGHT) {
if (yTile == LEVELHEIGHT) {
normalBlendedTiles.resize(0);
blendableTileIDs.resize(0);
}
return;
}
if (yTile < YTILEMAX) {
normalBlendedTiles.insertLast(array<jjPIXELMAP@>(LEVELWIDTH, null));
blendableTileIDs.insertLast(BlendRow(yTile + 1));
} else {
normalBlendedTiles.insertLast(@normalBlendedTiles[1]);
blendableTileIDs.insertLast(@blendableTileIDs[1]);
}
for (uint xTile = 0; xTile < LEVELWIDTH; ++xTile) {
//jjAlert("" + xTile + ", " + yTile);
array<uint16>@ blendablePositionsLocal = @blendableTileIDs[1][xTile];
if (blendablePositionsLocal.length > 0) { //Blurable
bool isWallNotFloor = false;
for (uint i = 0; i < blendablePositionsLocal.length; ++i)
if (blendablePositionsLocal[i] != GENERICFLOORTILE) {
isWallNotFloor = true;
break;
}
if (!isWallNotFloor) {
//jjAlert("floor at " + xTile + ", " + yTile);
continue;
}
//jjAlert("wall at " + xTile + ", " + yTile);
for (int xOffset = -1; xOffset <= 1; ++xOffset)
for (uint yOffset = 0; yOffset < 3; ++yOffset) {
//if (xOffset != 0 && yOffset != 0) //diagonal
// continue;
int xToTest = xTile + xOffset;
if (xToTest < 0) xToTest = 0;
else if (xToTest >= int(LEVELWIDTH)) xToTest = LEVELWIDTH - 1;
const uint yToTest = 1 + YOFFSETS[yOffset];
if (normalBlendedTiles[yToTest][xToTest] is null) { //no pixel map here yet
@normalBlendedTiles[yToTest][xToTest] = MakePixelMap(@blendableTileIDs[yToTest][xToTest]);
}
}
{
int xTileLeft = xTile - 1;
if (xTileLeft < 0) xTileLeft = 0;
int xTileRight = xTile + 1;
if (xTileRight >= int(LEVELWIDTH)) xTileRight = LEVELWIDTH - 1;
const bool stackHeight = blendableTileIDs[1][xTile].length > 1;
const uint oldTileID = blendableTileIDs[1][xTile][0];
jjPIXELMAP result(oldTileID);
const array<array<jjPIXELMAP@>> tileImages = {
{@normalBlendedTiles[0][xTileLeft], @normalBlendedTiles[0][xTile], @normalBlendedTiles[0][xTileRight]},
{@normalBlendedTiles[1][xTileLeft], @normalBlendedTiles[1][xTile], @normalBlendedTiles[1][xTileRight]},
{@normalBlendedTiles[2][xTileLeft], @normalBlendedTiles[2][xTile], @normalBlendedTiles[2][xTileRight]},
};
const jjPIXELMAP@ tileLocal = @tileImages[1][1];
bool differentFromBaseTile = false;
for (uint xx = 0; xx < result.width; ++xx)
for (uint yy = 0; yy < result.height; ++yy) {
const uint originalColor = result[xx,yy];
if (originalColor != 0) {
const uint basicColor = tileLocal[xx,yy];
if (!stackHeight && xx > 3 && xx < 28 && yy > 3 && yy < 28) //inside pixel of a single-tile stack
result[xx,yy] = basicColor;
else {
uint averagePixel = 0;
uint i = 0;
for (int xOffset = -4; xOffset <= 4; xOffset += 2)
for (int yOffset = -4; yOffset <= 4; yOffset += 2) {
if (abs(xOffset) + abs(yOffset) != 4) //corners
continue;
const uint colorToAdd = GetColor(xx + xOffset, yy + yOffset, tileImages);
averagePixel += (colorToAdd != 0) ? colorToAdd : tileLocal[xx,yy];
}
averagePixel >>= 3; //mean
if (averagePixel != originalColor) {
//if (averagePixel != basicColor)
// if ((jjRandom() & 1) == 1)
// averagePixel -= 1; //noise
differentFromBaseTile = true;
}
result[xx,yy] = averagePixel;
}
}
}
if (differentFromBaseTile) {
//jjAlert("" +TILESETSIZE);
if (LASTTILEFLIPPED) {
result.save(TILESETSIZE | TILE::HFLIPPED);
jjTileSet(3, xTile, yTile, TILESETSIZE++ | TILE::HFLIPPED);
LASTTILEFLIPPED = false;
} else {
result.save(TILESETSIZE);
jjTileSet(3, xTile, yTile, TILESETSIZE);
LASTTILEFLIPPED = true;
}
} else
jjTileSet(3, xTile, yTile, oldTileID);
}
}
}
normalBlendedTiles.removeAt(0);
blendableTileIDs.removeAt(0);
}
const uint GLOWSIZE = 6;
const uint GLOWFADEMAX = 127;
const uint GLOWFADEOFF = 20;
const uint8 GLOWCOLORINDEX = 214;
const array<uint16> GlowingRawTileIDs = {55, 64, 66, 67, 68, 74, 75, 76, 85};
class Glow {
float xPos, yPos, xScale, yScale;
uint layer, curFrame;
Glow(){}
Glow(uint x, uint y, uint l, int frameID, bool h, bool v) {
curFrame = FirstAnimFrames[caGLOW] + frameID;
const jjANIMFRAME@ frame = jjAnimFrames[curFrame];
xPos = x * 32; yPos = y * 32; layer = l;
if (h) {
xScale = -1;
xPos += 32 - frame.coldSpotX;
} else {
xScale = 1;
xPos += frame.coldSpotX;
}
if (v) {
yScale = -1;
yPos += 32 - frame.coldSpotY;
} else {
yScale = 1;
yPos += frame.coldSpotY;
}
}
void draw(float scale) {
jjDrawResizedSpriteFromCurFrame(xPos, yPos, curFrame, scale * xScale, scale * yScale, SPRITE::ALPHAMAP, 15, layer);
}
};
array<Glow> Glows;
void DetermineWhichTilesGlow() {
AssembleAnimSet(caGLOW, GlowingRawTileIDs.length);
for (uint tileIndex = 0; tileIndex < GlowingRawTileIDs.length; ++tileIndex) {
const jjPIXELMAP tileImage(GlowingRawTileIDs[tileIndex]);
uint LEFT = 32;
uint TOP = 32;
uint RIGHT = 0;
uint BOTTOM = 0;
for (uint x = 0; x < 32; ++x)
for (uint y = 0; y < 32; ++y)
if (tileImage[x,y] == GLOWCOLORINDEX) {
if (LEFT > x) LEFT = x;
if (TOP > y) TOP = y;
if (RIGHT < x) RIGHT = x;
if (BOTTOM < y) BOTTOM = y;
}
const uint width = RIGHT+1-LEFT;
const uint height = BOTTOM+1-TOP;
jjPIXELMAP glow(width + GLOWSIZE*2, height + GLOWSIZE*2);
for (uint x = 0; x < width; ++x) {
bool seenColorInColumn = false;
for (uint y = 0; y <= height; ++y) { //<= because this needs to hit a transparent pixel beneath the bottommost opaque pixel
if (tileImage[LEFT+x,TOP+y] == GLOWCOLORINDEX) {
glow[GLOWSIZE+x,GLOWSIZE+y] = GLOWFADEMAX;
if (!seenColorInColumn) {
seenColorInColumn = true;
for (uint i = 1; i <= GLOWSIZE; ++i)
glow[GLOWSIZE+x,GLOWSIZE+y-i] = GLOWFADEMAX - i*GLOWFADEOFF;
}
} else if (seenColorInColumn) {
seenColorInColumn = false;
for (uint i = 0; i < GLOWSIZE; ++i)
glow[GLOWSIZE+x,GLOWSIZE+y+i] = GLOWFADEMAX - i*GLOWFADEOFF;
}
}
}
for (uint y = 0; y < height; ++y) {
bool seenColorInRow = false;
for (uint x = 0; x <= width; ++x) {
if (tileImage[LEFT+x,TOP+y] == GLOWCOLORINDEX) {
if (!seenColorInRow) {
seenColorInRow = true;
for (uint i = 1; i <= GLOWSIZE; ++i) {
const uint8 newIntensity = GLOWFADEMAX - i*GLOWFADEOFF;
const uint8 oldIntensity = glow[GLOWSIZE+x-i,GLOWSIZE+y];
if (newIntensity > oldIntensity) //maybe better to add them somehow, but I'll settle for this
glow[GLOWSIZE+x-i,GLOWSIZE+y] = newIntensity;
}
}
} else if (seenColorInRow) {
seenColorInRow = false;
for (uint i = 0; i < GLOWSIZE; ++i) {
const uint8 newIntensity = GLOWFADEMAX - i*GLOWFADEOFF;
const uint8 oldIntensity = glow[GLOWSIZE+x+i,GLOWSIZE+y];
if (newIntensity > oldIntensity)
glow[GLOWSIZE+x+i,GLOWSIZE+y] = newIntensity;
}
}
}
}
jjANIMFRAME@ frame = jjAnimFrames[FirstAnimFrames[caGLOW] + tileIndex];
glow.save(jjAnimFrames[FirstAnimFrames[caGLOW] + tileIndex]);
frame.hotSpotX = -frame.width/2;
frame.hotSpotY = -frame.height/2;
frame.coldSpotX = LEFT-GLOWSIZE-frame.hotSpotX;
frame.coldSpotY = TOP-GLOWSIZE-frame.hotSpotY;
}
for (uint xTile = 0; xTile < LEVELWIDTH; ++xTile)
for (uint yTile = 0; yTile < LEVELHEIGHT; ++yTile)
for (uint layer = 5; layer >= 2; --layer) {
const uint16 rawTileID = jjTileGet(layer, xTile, yTile);
const uint16 baseTileID = rawTileID & TILE::RAWRANGE;
const int arrayIndex = GlowingRawTileIDs.find(baseTileID);
if (arrayIndex > -1) {
Glows.insertLast(Glow(xTile, yTile, layer, arrayIndex, (rawTileID & TILE::HFLIPPED) != 0, (rawTileID & TILE::VFLIPPED) != 0));
}
}
}
void DrawGlows() {
const float scale = jjSin(jjGameTicks << 3) / 2 + 1.5f;
for (uint i = Glows.length; i-- != 0; )
Glows[i].draw(scale);
}
void ReadyFlare() {
if (jjAnimSets[ANIM::FLARE] == 0)
jjAnimSets[ANIM::FLARE].load();
for (uint i = 0; i < 6; ++i) {
jjANIMFRAME@ frame = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::FLARE]]+i];
jjPIXELMAP circle(frame);
uint borderSize = i+2; if (i >= 3) borderSize -= 1;
for (uint x = borderSize; x < circle.width-borderSize; ++x) {
uint drawStage = 0;
for (uint y = 0; y < circle.height; ++y) { //fill in FLARE circles
const uint8 color = circle[x,y];
if (drawStage == 0) {
if (color == 16)
drawStage = 1;
} else if (drawStage == 1) {
if (color == 0) {
drawStage = 2;
circle[x,y] = 16;
}
} else if (drawStage == 2) {
if (color == 0)
circle[x,y] = 16;
else break;
}
}
}
circle.save(frame);
}
}
const array<float> LensFlareDeltas = {0.80f, 0.33f, 1.75f, 1.25f, 2.00f};
void LensFlare(jjOBJ@ obj) {
if (CURRENTPALETTE == palEVIL) {
obj.light = 0;
return;
}
if (obj.state == STATE::START) {
obj.state = STATE::WAKE;
obj.xPos -= 15; //center
}
obj.light = 12;
obj.frameID = (jjGameTicks >> 4) % jjAnimations[obj.curAnim]; //rotate slowly
obj.determineCurFrame();
obj.draw(); //will automatically be drawn as a gem
//the flare is low-intensity enough that I'm comfortable not checking jjLowDetail for it
const float xPos = obj.xPos, yPos = obj.yPos;
const float halfWidth = jjSubscreenWidth/2.f;
const float halfHeight = jjSubscreenHeight/2.f;
for (int localPlayerID = 0; localPlayerID < jjLocalPlayerCount; ++localPlayerID) {
const jjPLAYER@ play = jjLocalPlayers[localPlayerID];
const float xPositionOnScreen = xPos - play.cameraX;
if (xPositionOnScreen <= -32 || xPositionOnScreen >= jjSubscreenWidth + 32)
continue;
const float yPositionOnScreen = yPos - play.cameraY;
if (yPositionOnScreen <= -32 || yPositionOnScreen >= jjSubscreenHeight + 32)
continue;
const float xDelta = halfWidth - xPositionOnScreen;
const float yDelta = halfHeight - yPositionOnScreen;
const int8 playerID = play.playerID;
for (uint i = 0; i < 5; ++i)
jjDrawSprite(xPos + xDelta*LensFlareDeltas[i], yPos + yDelta*LensFlareDeltas[i], ANIM::FLARE, 0, 1+i, 1, SPRITE::TRANSLUCENTCOLOR, 15, 1, 4, playerID);
}
}
void Gemstone(jjOBJ@ obj) {
if (obj.state == STATE::START) {
obj.state = STATE::WAKE;
const int xTile = int(obj.xOrg/32);
const int yTile = int(obj.yOrg/32);
obj.age = (jjParameterGet(xTile, yTile, 0, 1) == 1) ? 2 : 4; //layer
obj.special = jjParameterGet(xTile, yTile, 1, 2) << 3; //palshift
obj.xAcc = (jjParameterGet(xTile, yTile, 3, 2) + 1) * 0.25f; //size
obj.animSpeed = jjParameterGet(xTile, yTile, 5, 4) * 64; //angle
}
jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.animSpeed, obj.xAcc, obj.xAcc, SPRITE::PALSHIFT, obj.special, obj.age);
}
void RecolorSnow() {
for (uint i = 0; i < 1024; ++i) {
jjPARTICLE@ part = jjParticles[i];
if (part.type == PARTICLE::FLOWER)
switch(CURRENTPALETTE) {
case palEVIL:
part.flower.color = 0; //black
break;
case palNIGHT:
break;
case palEVENING:
break;
case palDAY: default:
part.flower.color = 15; //white
break;
}
}
}
enum PaletteOptions {
palDAY, palEVENING, palNIGHT, palEVIL
}
int8 CURRENTPALETTE = palDAY;
void onFunction0(jjPLAYER@ play, int8 paletteIndex) {
if (CURRENTPALETTE == paletteIndex)
return; //nothing to change, don't bother
if (paletteIndex != palDAY)
jjPalette.load("CrystalEmpireV" + (paletteIndex+1) + ".pal");
switch (CURRENTPALETTE = paletteIndex) {
case palEVIL:
jjSetDarknessColor(jjPALCOLOR(0,0,0));
break;
case palNIGHT:
break;
case palEVENING:
break;
case palDAY: default:
jjPalette.reset();
jjSetDarknessColor(jjPALCOLOR(255,255,255));
break;
}
jjPalette.apply();
jjSetFadeColors();
}
void onLevelBegin() {
AddMountains();
AddHills();
SetupFlickeringLighting();
DetermineWhichTilesAreFloor();
DetermineWhichTilesGlow();
ReadyFlare();
}
void onMain() {
BlurWalls(); //spread out over the first LEVELHEIGHT gameticks*n
RecolorSnow();
if (!jjLowDetail) {
AddClouds();
DrawReflections();
DrawGlows();
}
}
void onDrawLayer4(jjPLAYER@ play, jjCANVAS@ screen) { //called even while spectating
MakeLightingFlicker(play);
}
void onDrawLayer8(jjPLAYER@ play, jjCANVAS@ screen) { //Layer 8 is always at position 0,0, so these are screen-absolute coordinates
if (!jjLowDetail) {
DrawClouds(screen);
DrawMountains(play, screen);
DrawHills(play, screen);
//screen.drawRectangle(0, jjSubscreenHeight - 60, jjSubscreenWidth, 10, jjPalette.findNearestColor(jjPALCOLOR(90,111,130)));
//screen.drawRectangle(0, jjSubscreenHeight - 50, jjSubscreenWidth, 50, jjPalette.findNearestColor(jjPALCOLOR(194,202,230)));
}
}
//TEMP!
bool onDrawScore(jjPLAYER@ play, jjCANVAS@ screen) { return true; }
bool onDrawHealth(jjPLAYER@ play, jjCANVAS@ screen) { return true; }
bool onDrawAmmo(jjPLAYER@ play, jjCANVAS@ screen) { return true; }
bool onDrawLives(jjPLAYER@ play, jjCANVAS@ screen) { return true; }
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.