| Name | Author | Game Mode | Rating | |||||
|---|---|---|---|---|---|---|---|---|
| Original Levels - Stone... | ShaddowBlack0 | Single player | 7 | |||||
#pragma require "kangaroo.j2a"
//#pragma require "Anims2.j2a"
#pragma require "BL18.asc"
#pragma require "HH17.asc"
#pragma require "hh17_icicle.j2a"
#pragma require "Resize v10.asc"
#include "Resize v10.asc"
#pragma require "BL18.j2a"
#pragma require "BL18.bmp"
#pragma require "HPYeahoo.wav"
#pragma require "HPCrystal.wav"
#pragma require "HPSwitch.wav"
#pragma require "HPTreasure.wav"
#pragma require "HPTrigger.wav"
#pragma require "HPWarp.wav"
#pragma require "ESJM1.wav"
#pragma require "ESJM2.wav"
#pragma require "ESJM3.wav"
#pragma require "ESJF1.wav"
#pragma require "ESJF2.wav"
#pragma require "ESJF3.wav"
/*
API (all expected to be called in onLevelLoad):
jjOBJ@ Kangaroo::MakeEventJoey(uint8 eventID, int minX = 0, int maxX = 4, int minY = 6, int maxY = 12, int jumpDelay = 35, int minDistance = 224)
Assign a specific event slot to the Joey enemy, e.g. replacing another enemy type or a food object or something. If you assign multiple event slots, you can get Joey enemies with different parameters.
jjOBJ@ Kangaroo::MakeEventJill(uint8 eventID, uint8 spawn = 0, bool secondStage = false, int textID = -1)
Assign a specific event slot to the Jill boss.
"spawn", if non-zero, is an eventID that the boss will occasionally create from her pouch. This is assumed to be a Joey enemy but may be other objects as well.
The "secondStage" bool causes Jill to turn red and jump faster after she has lost three-quarters of her health.
If "textID" is 0-16, defeating Jill will display that text ID.
void Kangaroo::OnJillDefeat(JILLCALLBACKFUNC@ callback = null)
Three seconds after defeating Jill, this function will be called. The JILLCALLBACKFUNC pattern is the same as the behavior pattern: a void-returning function taking a jjOBJ@ as its only argument. If "callback" is left null, or if OnJillDefeat is never called, defeating a Jill will simply end the level.
void Kangaroo::Joey(jjOBJ@ obj)
The behavior function for the Joey enemy
void Kangaroo::Jill(jjOBJ@ obj)
The behavior function for the Jill boss
*/
namespace Kangaroo {
class Point {
float x; float y;
Point(){}
Point(float xx, float yy) {
x = xx;
y = yy;
}
const Point& opAssign(const Point &in other) {
x = other.x;
y = other.y;
return this;
}
bool opEquals(const Point &in other) const {
return x == other.x && y == other.y;
}
}
enum CollectibleTypes {
Coin, Pink,Turquoise,Red,Silver,Blue,Purple,Gold,Green
};
class CollectibleLocation {
Point Location;
bool Found = false;
CollectibleTypes Type;
CollectibleLocation(Point l, CollectibleTypes t) { Location = l; Type = t; }
CollectibleLocation(){}
}
class SortableDistance {
float distance;
uint index;
SortableDistance(){}
SortableDistance(float d, uint i) { distance = d; index = i; }
int opCmp(const SortableDistance &in other) const {
return int(distance - other.distance);
}
}
Point CoinWarpLocation;
bool CoinWarpFound = false;
int ArrowTimer = 0;
const uint ArrowSprite = jjAnimations[jjAnimSets[ANIM::FLAG]];
array<CollectibleLocation> CollectibleLocations;
void ShowArrow() {
if (!CoinWarpFound) {
uint coinsTotal = 0;
uint coinsFound = 0;
uint collectiblesFound = 0;
int keyX = 56;
const int keyY = jjSubscreenHeight - 19;
for (uint i = 0; i < CollectibleLocations.length; ++i) {
const auto@ location = CollectibleLocations[i];
const bool found = location.Found;
/*if (found)
collectiblesFound += 1;
if (location.Type == CollectibleTypes::Coin) {
if (found)
coinsFound += 1;
coinsTotal += 1;
} else {
canvas.drawSpriteFromCurFrame(keyX += 20, keyY, KeyFrame, 1, found ? SPRITE::SINGLEHUE : SPRITE::SHADOW, KeyColors[location.Type - 1]);
}*/
}
}
if (CoinWarpFound)
return;
if (ArrowTimer > jjGameTicks + 70*4)
ArrowTimer = jjGameTicks;
else if (ArrowTimer < jjGameTicks) {
if (ArrowTimer + 64 >= jjGameTicks) {
const Point playerLocation(Player.xPos, Player.yPos);
array<SortableDistance> distances;
for (uint i = 0; i < CollectibleLocations.length; ++i) {
if (CollectibleLocations[i].Found)
continue;
const float deltaX = abs(playerLocation.x - CollectibleLocations[i].Location.x);
const float deltaY = abs(playerLocation.y - CollectibleLocations[i].Location.y);
distances.insertLast(SortableDistance(deltaX * deltaX + deltaY * deltaY, i));
}
const Point@ target = null;
if (distances.length == 0) {
@target = CoinWarpLocation;
} else {
distances.sortAsc();
if (distances[0].distance > 300*300)
@target = CollectibleLocations[distances[0].index].Location;
}
if (target !is null) {
const int angle = GetAngle(target.x - playerLocation.x, playerLocation.y - target.y);
const float scale = (112 - (jjSin((jjGameTicks - ArrowTimer) << 3) * -64)) / 128.f;
jjDrawRotatedSpriteFromCurFrame(
Player.xPos + jjCos(angle) * 32, Player.yPos + jjSin(angle) * 32,
ArrowSprite, 970 - angle, scale, scale, SPRITE::SINGLEHUE, 72, -9
);
}
} else {
ArrowTimer = jjGameTicks + 70*3;
}
}
}
namespace Private {
void jillDefeatedDefaultAction(jjOBJ@) {
jjNxt();
}
JILLCALLBACKFUNC@ jillCallback = jillDefeatedDefaultAction;
bool animsLoaded = false;
uint customAnimID = 0;
void loadAnims() {
if (!animsLoaded) {
animsLoaded = true;
while (jjAnimSets[ANIM::CUSTOM[customAnimID]] != 0)
++customAnimID;
customAnimID = ANIM::CUSTOM[customAnimID];
jjAnimSets[customAnimID].load(0, "kangaroo.j2a");
if (!jjSampleIsLoaded(SOUND::BUBBA_BUBBABOUNCE1))
jjAnimSets[ANIM::BUBBA].load();
}
}
void applyGenericEnemySettingsToPreset(jjOBJ@ preset) {
preset.playerHandling = HANDLING::ENEMY;
preset.bulletHandling = HANDLING::HURTBYBULLET;
preset.causesRicochet = false;
preset.isBlastable = false;
preset.triggersTNT = true;
preset.isFreezable = true;
preset.isTarget = true;
preset.scriptedCollisions = false;
preset.direction = 1;
preset.freeze = 0;
}
void putKangarooOnGround(jjOBJ@ obj, int width, int height) {
while (!jjMaskedHLine(int(obj.xPos) - width/2, width, int(obj.yPos) + height/2))
obj.yPos += 1;
}
uint firstGloveAnimationFrame;
const jjANIMFRAME@ roundExplosionFrame = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::AMMO] + 5] + 2];;
void doGloveAt(int x, int y) {
for (int i = 0; i < jjLocalPlayerCount; ++i) {
jjPLAYER@ localPlayer = jjLocalPlayers[i];
if (localPlayer.blink == 0 && roundExplosionFrame.doesCollide(x, y, 0, jjAnimFrames[localPlayer.curFrame], int(localPlayer.xPos), int(localPlayer.yPos), localPlayer.direction))
localPlayer.hurt();
}
for (int i = jjObjectCount - 1; i > 0; --i) {
jjOBJ@ obj = jjObjects[i];
if (obj.playerHandling == HANDLING::PLAYERBULLET && obj.state != STATE::EXPLODE && roundExplosionFrame.doesCollide(x, y, 0, jjAnimFrames[obj.curFrame], int(obj.xPos), int(obj.yPos), obj.direction)) {
obj.ricochet();
//obj.playerHandling = HANDLING::ENEMYBULLET;
}
}
}
}
enum KangarooVariables {
kvWIDTH = 0, kvHEIGHT, kvMINX, kvMAXX, kvMINY, kvMAXY, kvJUMPDELAY, kvMINDISTANCE, kvGLOVE1FRAME, kvGLOVE2FRAME, kvSECONDSTAGE
}
jjOBJ@ MakeEventJoey(uint8 eventID, int minX = 0, int maxX = 4, int minY = 6, int maxY = 12, int jumpDelay = 35, int minDistance = 224) {
Kangaroo::Private::loadAnims();
jjOBJ@ preset = jjObjectPresets[eventID];
preset.behavior = Joey;
preset.determineCurAnim(Kangaroo::Private::customAnimID, 0);
preset.frameID = 0; preset.determineCurFrame();
Kangaroo::Private::applyGenericEnemySettingsToPreset(preset);
preset.deactivates = true;
preset.energy = 1;
preset.points = 200;
preset.yAcc = 0.33f;
preset.counter = 0;
preset.var[kvWIDTH] = 12;
preset.var[kvHEIGHT] = 28;
preset.var[kvMINX] = minX;
preset.var[kvMAXX] = maxX;
preset.var[kvMINY] = minY;
preset.var[kvMAXY] = maxY;
preset.var[kvJUMPDELAY] = jumpDelay;
preset.var[kvMINDISTANCE] = minDistance;
return preset;
}
funcdef void JILLCALLBACKFUNC(jjOBJ@);
jjOBJ@ MakeEventJill(uint8 eventID, uint8 spawn = 0, bool secondStage = false, int textID = -1) {
Kangaroo::Private::loadAnims();
if (jjAnimSets[ANIM::GLOVE] == 0)
jjAnimSets[ANIM::GLOVE].load();
Kangaroo::Private::firstGloveAnimationFrame = jjAnimations[jjAnimSets[ANIM::GLOVE] + 3];
jjOBJ@ preset = jjObjectPresets[eventID];
preset.behavior = Jill;
preset.determineCurAnim(Kangaroo::Private::customAnimID, 1);
preset.frameID = 0; preset.determineCurFrame();
Kangaroo::Private::applyGenericEnemySettingsToPreset(preset);
preset.doesHurt = spawn;
preset.yAcc = 0.16f;
preset.energy = 100;
preset.points = 5000;
preset.counterEnd = 210; //death wait
preset.special = textID;
preset.playerHandling = HANDLING::DYING; //no initial collision damage
preset.var[kvWIDTH] = 32;
preset.var[kvHEIGHT] = 98;
preset.var[kvMINX] = 2;
preset.var[kvMAXX] = 4;
preset.var[kvMINY] = 5;
preset.var[kvMAXY] = 10;
preset.var[kvJUMPDELAY] = 140;
preset.var[kvMINDISTANCE] = 400;
preset.var[kvGLOVE1FRAME] = 0;
preset.var[kvGLOVE2FRAME] = 0;
preset.var[kvSECONDSTAGE] = secondStage ? 1 : 0;
return preset;
}
void OnJillDefeat(JILLCALLBACKFUNC@ callback = null) {
if (callback !is null)
@Kangaroo::Private::jillCallback = callback;
}
void Joey(jjOBJ@ obj) {
const int width = obj.var[kvWIDTH];
const int height = obj.var[kvHEIGHT];
switch (obj.state) {
case STATE::START:
obj.state = STATE::IDLE;
Kangaroo::Private::putKangarooOnGround(obj, width, height);
case STATE::IDLE:
if (obj.counter == 0 || --obj.counter == 0) {
const int nearestPlayerID = obj.findNearestPlayer(int(pow(obj.var[kvMINDISTANCE], 2)));
if (nearestPlayerID >= 0) {
jjPLAYER@ nearestPlayer = jjPlayers[nearestPlayerID];
obj.xSpeed = (nearestPlayer.xPos - obj.xPos) / 20.0f;
obj.direction = (obj.xSpeed >= 0) ? 1 : -1;
float xSpeed = abs(obj.xSpeed);
if (xSpeed > obj.var[kvMAXX]) xSpeed = obj.var[kvMAXX];
else if (xSpeed < obj.var[kvMINX]) xSpeed = obj.var[kvMINX];
obj.xSpeed = xSpeed * obj.direction;
float ySpeed = abs((nearestPlayer.yPos - obj.yPos) / 20.0f);
if (ySpeed > obj.var[kvMAXY]) ySpeed = obj.var[kvMAXY];
else if (ySpeed < obj.var[kvMINY]) ySpeed = obj.var[kvMINY];
obj.ySpeed = -ySpeed;
obj.state = STATE::JUMP;
obj.counter = obj.var[kvJUMPDELAY];
jjSample(obj.xPos, obj.yPos, ((jjRandom() & 1) == 0) ? SOUND::BUBBA_BUBBABOUNCE1 : SOUND::BUBBA_BUBBABOUNCE2);
} else
obj.direction = (obj.xPos > jjLocalPlayers[0].xPos) ? -1 : 1;
}
break;
case STATE::FREEZE:
if (obj.freeze > 0)
--obj.freeze;
if (obj.freeze <= 0) {
obj.state = obj.oldState;
obj.unfreeze(0);
}
break;
case STATE::JUMP:{
obj.yPos += (obj.ySpeed += obj.yAcc);
const int newXPos = int(obj.xPos + obj.xSpeed) + (width * obj.direction)/2;
if ((newXPos < 0) || (newXPos > jjLayerWidth[4]*32) || jjMaskedVLine(newXPos, int(obj.yPos - height/2), height)) {
obj.xSpeed = -obj.xSpeed;
obj.direction = -obj.direction;
}
obj.xPos += obj.xSpeed;
int newYPos = int(obj.yPos + obj.ySpeed);
if (obj.ySpeed < 0) {
if ((newYPos < 0) || jjMaskedHLine(int(obj.xPos) - width/2, width, newYPos - height/2)) {
obj.ySpeed = obj.yAcc;
obj.frameID = 2;
} else obj.frameID = 1;
}
if (obj.ySpeed > 0) {
if ((newYPos > jjLayerHeight[4]*32) || jjMaskedHLine(int(obj.xPos) - width/2, width, newYPos + height/2)) {
obj.state = STATE::IDLE;
obj.ySpeed = 0;
obj.frameID = 0;
Kangaroo::Private::putKangarooOnGround(obj, width, height);
} else obj.frameID = 2;
}
obj.determineCurFrame();
break;
} case STATE::DEACTIVATE:
obj.deactivate();
return;
case STATE::KILL:
obj.delete();
return;
}
obj.draw();
}
void Jill(jjOBJ@ obj) {
switch (obj.state) {
case STATE::START:
obj.state = STATE::DELAYEDSTART;
case STATE::DELAYEDSTART:
for (int i = 0; i < jjLocalPlayerCount; ++i) {
jjPLAYER@ localPlayer = jjLocalPlayers[i];
if (localPlayer.bossActivated) {
localPlayer.boss = obj.objectID;
obj.state = STATE::START;
}
}
if (obj.state == STATE::START) {
obj.playerHandling = HANDLING::ENEMY;
break;
}
return;
case STATE::KILL:
if (obj.special >= 0) //textID
jjLocalPlayers[0].showText(obj.special, 0);
obj.playerHandling = HANDLING::DYING;
obj.state = STATE::DONE;
case STATE::DONE:
if (--obj.counterEnd == 0) {
obj.delete();
Kangaroo::Private::jillCallback(obj);
}
return;
default:
break;
}
int oldState = obj.state;
obj.behave(Joey, false);
obj.frameID = 0;
obj.determineCurFrame();
const int direction = obj.direction;
const bool secondStage = (obj.var[kvSECONDSTAGE] != 0 && obj.energy < 25);
if (secondStage) {
if (obj.var[kvSECONDSTAGE] == 1) {
obj.var[kvSECONDSTAGE] = 2;
obj.var[kvMINX] = 3;
obj.var[kvMAXX] = 5;
obj.var[kvJUMPDELAY] = 50;
obj.var[kvMINDISTANCE] = 600;
}
}
if (obj.doesHurt != 0 && (jjRandom() & 255) == 0) {
jjOBJ@ spawn = jjObjects[jjAddObject(obj.doesHurt, obj.xPos, obj.yPos + 11, obj.objectID, CREATOR::OBJECT, BEHAVIOR::INACTIVE)];
jjBEHAVIOR originalBehavior = jjObjectPresets[obj.doesHurt].behavior;
if (originalBehavior == Joey) {
spawn.direction = obj.direction;
spawn.xSpeed = spawn.direction * (1 + (jjRandom() & 3));
spawn.ySpeed = -6;
spawn.state = STATE::JUMP;
}
spawn.behavior = originalBehavior;
}
SPRITE::Mode mode = SPRITE::NORMAL;
uint8 param = 15;
SPRITE::Mode modeDark = SPRITE::BRIGHTNESS;
uint8 paramDark = 96;
if (obj.justHit != 0) {
mode = modeDark = SPRITE::SINGLECOLOR;
paramDark = param;
const int nearestPlayerID = obj.findNearestPlayer(64);
for (int i = 0; i < jjLocalPlayerCount; ++i) {
jjPLAYER@ localPlayer = jjLocalPlayers[i];
if (localPlayer.specialMove != 0) {
localPlayer.specialMove = 0;
localPlayer.ySpeed -= 1;
localPlayer.extendInvincibility(-35);
}
}
} else if (obj.state == STATE::FREEZE) {
mode = modeDark = SPRITE::FROZEN;
} else if (secondStage) {
mode = modeDark = SPRITE::TINTED;
param = 25;
paramDark = 29;
}
int armAngle = obj.age;
if (obj.state != STATE::FREEZE) {
const int nearestPlayerID = obj.findNearestPlayer(1000000);
if (nearestPlayerID >= 0) {
jjPLAYER@ nearestPlayer = jjPlayers[nearestPlayerID];
const float deltaX = nearestPlayer.xPos - obj.xPos;
const float deltaY = nearestPlayer.yPos - obj.yPos;
armAngle = int(atan2(
(direction == 1) ? deltaY : -deltaY,
abs(deltaX)
) * -512.0 * 0.318309886142228);
}
obj.age = armAngle;
}
const float armSin = jjSin(armAngle);
const float armCos = jjCos(armAngle);
int tailAngle = int(obj.ySpeed*-16)*direction;
const int tailX = int(obj.xPos) - 32 * direction;
const int tailY = int(obj.yPos) + 23;
const int gloveLength = (12 + 29 + 29) * obj.direction;
const int legAngle = int(obj.ySpeed*8)*direction;
const int arm1X = int(obj.xPos) - 2 * direction;
const int arm1Y = int(obj.yPos) - 7;
const int arm2X = int(obj.xPos) + 4 * direction;
const int arm2Y = int(obj.yPos - 11);
//if (tailAngle > 0) tailAngle = 0;
if (obj.ySpeed < 0) {
obj.animSpeed = jjSampleLooped(obj.xPos, obj.yPos, SOUND::COMMON_FLAMER, obj.animSpeed);
if (obj.state != STATE::FREEZE && (jjRandom() & 1) == 0) {
jjPARTICLE@ part = jjAddParticle(PARTICLE::FIRE);
if (part !is null) {
part.xSpeed = jjSin(tailAngle) * 2;
part.ySpeed = jjCos(tailAngle) * 2;
part.xPos = tailX + part.xSpeed * 8;
part.yPos = tailY + part.ySpeed * 8;
part.xSpeed += abs(obj.xSpeed) * -obj.direction;
}
}
if (oldState == STATE::IDLE) {
obj.var[kvGLOVE1FRAME] = 1;
jjSample(obj.xPos, obj.yPos, SOUND::COMMON_PISTOL1);
obj.var[kvGLOVE2FRAME] = 0;
}
}
{
int oldGloveFrame = obj.var[kvGLOVE1FRAME];
if (oldGloveFrame > 0 && oldGloveFrame < 12 && (jjGameTicks & 3) == 1)
obj.var[kvGLOVE1FRAME] = oldGloveFrame + 1;
oldGloveFrame = obj.var[kvGLOVE2FRAME];
if (obj.state == STATE::IDLE && oldGloveFrame == 0) {
obj.var[kvGLOVE2FRAME] = 1;
jjSample(obj.xPos, obj.yPos, SOUND::COMMON_PISTOL1);
}
if (oldGloveFrame > 0 && oldGloveFrame < 12 && (jjGameTicks & 3) == 1)
obj.var[kvGLOVE2FRAME] = oldGloveFrame + 1;
}
const int glove2FrameID = Kangaroo::Private::firstGloveAnimationFrame + (obj.var[kvGLOVE2FRAME] + 3) % 12;
jjDrawRotatedSpriteFromCurFrame(
arm2X + 12*armSin + gloveLength*armCos,
arm2Y + 12*armCos - gloveLength*armSin,
glove2FrameID,
armAngle - 256 * obj.direction,
direction, 2, modeDark, paramDark
); //glove
if (obj.state != STATE::FREEZE) {
const int glove2Length = gloveLength + (jjAnimFrames[glove2FrameID].height - 30) * 2 * obj.direction;
Kangaroo::Private::doGloveAt(
int(arm2X + 12*armSin + glove2Length*armCos),
int(arm2Y + 12*armCos - glove2Length*armSin)
);
}
jjDrawRotatedSpriteFromCurFrame(arm2X, arm2Y, obj.curFrame + 1, armAngle, direction, 1, modeDark, paramDark); //back arm
jjDrawRotatedSpriteFromCurFrame(obj.xPos - 24 * direction, obj.yPos + 24, obj.curFrame + 2, legAngle, direction, 1, modeDark, paramDark); //back leg
jjDrawRotatedSpriteFromCurFrame(tailX, tailY, obj.curFrame + 3, tailAngle, direction, 1, mode, param); //tail
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, direction, mode, param); //body
jjDrawRotatedSpriteFromCurFrame(obj.xPos - 30 * direction, obj.yPos + 28, obj.curFrame + 2, legAngle, direction, 1, mode, param); //leg
jjDrawRotatedSpriteFromCurFrame(arm1X, arm1Y, obj.curFrame + 1, armAngle, direction, 1, mode, param); //arm
const int glove1FrameID = Kangaroo::Private::firstGloveAnimationFrame + (obj.var[kvGLOVE1FRAME] + 3) % 12;
jjDrawRotatedSpriteFromCurFrame(
arm1X + 12*armSin + gloveLength*armCos,
arm1Y + 12*armCos - gloveLength*armSin,
glove1FrameID,
armAngle - 256 * obj.direction,
direction, 2, mode, param
); //glove
if (obj.state != STATE::FREEZE) {
const int glove1Length = gloveLength + (jjAnimFrames[glove1FrameID].height - 30) * 2 * obj.direction;
Kangaroo::Private::doGloveAt(
int(arm2X + 12*armSin + glove1Length*armCos),
int(arm2Y + 12*armCos - glove1Length*armSin)
);
}
}
//}
//namespace EBL18{
bool IsMurderer = false;
bool allowSpecialMoves = true;
bool monsterRemain = true;
jjPLAYER@ Player = jjLocalPlayers[0];
void mRemain(bool remain){
monsterRemain = remain;
}
void makeRed(jjOBJ@ o){
//o.curFrame = (jjAnimations[o.curAnim] = jjAnimations[jjAnimSets[ANIM::CUSTOM[AnimSets::Misc]] + 8]).firstFrame;
//const uint KeyFrame = jjAnimations[jjAnimSets[ANIM::CUSTOM[AnimSets::Misc]] + 8];
//jjAnimSets[ANIM::SUCKER] + 4
const uint KeyFrame = jjAnimations[jjAnimSets[ANIM::CUSTOM[9]] + 4];
o.curFrame = KeyFrame;//(jjAnimations[jjAnimSets[ANIM::CUSTOM[AnimSets::Misc]] + 8]).firstFrame;
//Recolor(jjAnimations[jjAnimSets[ANIM::SUCKER] + 4], array<uint8>={24}, RecolorReplace(40)); //red to orange
//Recolor(jjAnimations[jjAnimSets[ANIM::SUCKER] + 4], array<uint8>={80}, RecolorReplace(24)); //green to red
}
array<uint> GemTargets(4, 0), GemCounts(4, 0);
int CountGems(){
int countGem = GemTargets[0]+GemTargets[1]+GemTargets[2]+GemTargets[3];
return countGem;
}
void Setup(){
jjAnimSets[ANIM::CUSTOM[AnimSets::Misc]].load(AnimSets::Misc, "BL18.j2a");
jjAnimSets[ANIM::CUSTOM[9]].load(99, "Anims.j2a"); //was Anims2.j2a
//const uint firstMiscMiscFrame = jjAnimations[jjAnimSets[ANIM::CUSTOM[AnimSets::Misc]] + 1];
//KeyFrame = firstMiscMiscFrame + 3;
array<bool> EventsFound(256, false);
/*for (int l = 1; l < jjObjectCount; l++) {
jjOBJ@ o = jjObjects[l];
if(o.eventID == OBJECT::FLOATSUCKER) {
o.behavior = WhiteSucker();
}
}*/
for (int i = 0; i < jjLayerHeight[4]; i++) {
for (int j = 0; j < jjLayerWidth[4]; j++) {
uint8 eventID = jjEventGet(j, i);
//detect events hidden inside walls
if (eventID == OBJECT::BANANA) //HocusWall
eventID = jjParameterGet(j, i, 5, 8);
else if (eventID == OBJECT::CHERRY) // HocusDestructScenery
eventID = jjParameterGet(j, i, 0, 8);
if (eventID == OBJECT::REDGEM || eventID == OBJECT::GREENGEM || eventID == OBJECT::BLUEGEM || eventID == OBJECT::PURPLEGEM) {
GemTargets[eventID - OBJECT::REDGEM] += 1;
}
if (eventID > 32 && !EventsFound[eventID]) {
EventsFound[eventID] = true;
jjOBJ@ preset = jjObjectPresets[eventID];
/*if (jjEventGet(j, i) == OBJECT::FATCHICK) {
FatChick(preset);
}else if (jjEventGet(j, i) == OBJECT::FENCER) {
Fencer(preset);
}else if (jjEventGet(j, i) == OBJECT::TUBETURTLE) {
TubeTurle(preset);
}else if (jjEventGet(j, i) == OBJECT::FLOATLIZARD) {
//FloatLizard(preset);
}else if (jjEventGet(j, i) == OBJECT::BAT) {
RedBat(preset);
}else if (jjEventGet(j, i) == OBJECT::MONKEY || jjEventGet(j, i) == OBJECT::STANDMONKEY) {
Monkey(preset);
}else if (jjEventGet(j, i) == OBJECT::DOGGYDOGG) {
Muttshroom(preset);
}else if (jjEventGet(j, i) == OBJECT::WATERMELON) {
Trogdor(preset);
}*/
switch (eventID) {
//case OBJECT::SILVERCOIN:
// EventsFound[eventID] = false;
// CollectibleLocations.insertLast(CollectibleLocation(Point(i * 32 + 15, j * 32 + 15), CollectibleTypes(jjParameterGet(i, j, 0, 4))));
// break;
case OBJECT::FATCHICK:
FatChick(preset);
break;
case OBJECT::FENCER:
Fencer(preset);
break;
case OBJECT::LIZARD:
//FloatLizard(preset);
break;
case OBJECT::TUBETURTLE:
TubeTurle(preset);
break;
case OBJECT::FISH:
//Fish(preset);
break;
case OBJECT::BAT:
//RedBat(preset);
break;
case OBJECT::MONKEY:
case OBJECT::STANDMONKEY:
Monkey(preset);
break;
case OBJECT::DOGGYDOGG:
Muttshroom(preset);
break;
case OBJECT::WATERMELON:
Trogdor(preset);
break;
case OBJECT::BILSY:
//MiniBilsy(preset);
break;
case OBJECT::PEACH:
//FrogFly(jjObjectPresets[OBJECT::DRAGONFLY]);
//Frog(preset);
break;
case OBJECT::RAVEN:
Phoenix(preset);
break;
case OBJECT::DEMON:
Demon(preset);
break;
case OBJECT::SPARK:
//Duet(preset);
break;
case OBJECT::HATTER:
Hatter(preset);
break;
case OBJECT::FLOATSUCKER:
//FloatSucker(preset);
//jjObjectPresets[OBJECT::FLOATSUCKER].behavior = WhiteSucker();
Recolor(jjAnimations[jjAnimSets[ANIM::SUCKER] + 4], array<uint8>={24}, RecolorReplace(40)); //red to orange
Recolor(jjAnimations[jjAnimSets[ANIM::SUCKER] + 4], array<uint8>={80}, RecolorReplace(24)); //green to red
jjSampleLoad(SOUND::P2_FART, "HH17_balloon.wav");
break;
case OBJECT::SUCKER:
Sucker(preset);
jjObjectPresets[OBJECT::FLOATSUCKER].behavior = WhiteSucker();
break;
case OBJECT::DRAGON:
//jjObjectPresets[OBJECT::DRAGON].behavior = IceDragon();
break;
case OBJECT::SKELETON:
jjObjectPresets[OBJECT::SKELETON].behavior = BoneChucker();
break;
case OBJECT::CRAB:
jjObjectPresets[OBJECT::CRAB].behavior = YetiCrab();
jjAnimSets[ANIM::UTERUS].load();
break;
case OBJECT::DRAGONFLY:
jjObjectPresets[OBJECT::DRAGONFLY].behavior = DragonFly();
break;
}
/*case OBJECT::SILVERCOIN:
EventsFound[eventID] = false;
CollectibleLocations.insertLast(CollectibleLocation(Point(x * 32 + 15, y * 32 + 15), CollectibleTypes(jjParameterGet(x, y, 0, 4))));
break;
case OBJECT::WARP:
if (jjParameterGet(x, y, 17, 1) == 0) { //ShowAnim=0: just an area, not a warp object
EventsFound[eventID] = false;
break;
}
CoinWarpLocation = Point(x * 32, y * 32);
preset.behavior = CoinWarp;
break;
case OBJECT::CARROT:
preset.behavior = RestrictedCarrot;
break;
case OBJECT::BOUNCERAMMO3:
case OBJECT::ICEAMMO3:
case OBJECT::SEEKERAMMO3:
case OBJECT::RFAMMO3:
case OBJECT::TOASTERAMMO3:
case OBJECT::TNTAMMO3:
case OBJECT::GUN8AMMO3:
case OBJECT::GUN9AMMO3:
preset.behavior = NotOnHardPickup;
break;
case OBJECT::APPLE:
preset.scriptedCollisions = true;
preset.curFrame = KeyFrame + 1;
preset.behavior = Lock();
break;
case OBJECT::BANANA:
preset.behavior = HocusWall;
preset.playerHandling = HANDLING::PARTICLE;
break;
case OBJECT::CHERRY:
preset.behavior = HocusDestructScenery();
preset.playerHandling = HANDLING::SPECIAL;
preset.bulletHandling = HANDLING::DETECTBULLET;
preset.scriptedCollisions = true;
preset.isFreezable = false;
preset.triggersTNT = true;
preset.determineCurAnim(ANIM::DESTSCEN, 2);
preset.determineCurFrame();
break;
case OBJECT::REDGEM:
HocusGem(preset, 0, 0, 10, 100);
break;
case OBJECT::PURPLEGEM:
HocusGem(preset, 0, 3, 4, 250);
break;
case OBJECT::GREENGEM:
HocusGem(preset, 1, 1, 5, 500);
break;
case OBJECT::BLUEGEM:
HocusGem(preset, 2, 2, 5, 1000);
break;
case OBJECT::ORANGE: {
preset.behavior = WarpPotion();
preset.scriptedCollisions = true;
Recolor(jjAnimations[preset.determineCurAnim(ANIM::PICKUPS, 78)], array<uint8>={88}, RecolorReplace(32));
break; }
case OBJECT::PEAR:
preset.behavior = Switch();
preset.playerHandling = HANDLING::PARTICLE;
preset.curFrame = firstMiscMiscFrame + 5;
break;
case OBJECT::PRETZEL:
preset.behavior = Friend();
preset.playerHandling = HANDLING::PARTICLE;
preset.determineCurAnim(ANIM::MENU,0,false);
preset.curFrame = firstMiscMiscFrame + 5;
break;
case OBJECT::STRAWBERRY:
preset.behavior = Furryball;
preset.playerHandling = HANDLING::ENEMYBULLET;
preset.animSpeed = jjDifficulty > 0 ? 2 : 1;
preset.curFrame = firstMiscMiscFrame + 9;
preset.killAnim = jjAnimSets[ANIM::AMMO] + 5;
break;
case OBJECT::LEMON:
preset.behavior = Elevator;
preset.isBlastable = false;
preset.playerHandling = HANDLING::SPECIAL;
preset.bulletHandling = HANDLING::DESTROYBULLET;
preset.curFrame = skullFrame + TrueColor::NumberOfFramesPerImage;
preset.special = firstMiscMiscFrame + 10; //8-bit
preset.deactivates = false;
break;
case OBJECT::LIME:
preset.behavior = Spikes;
preset.playerHandling = HANDLING::ENEMYBULLET;
preset.animSpeed = 1;
preset.curFrame = firstMiscMiscFrame + 12;
break;
case 122: //lava
LevelContainsLava = true;
preset.behavior = function(obj) { obj.behavior = Lava(); };
preset.playerHandling = HANDLING::PARTICLE;
preset.determineCurAnim(ANIM::CUSTOM[AnimSets::Misc], 6);
preset.special = firstMiscMiscFrame + 14;
break;
case OBJECT::BRIDGE:
jjAnimSets[ANIM::BRIDGE].load(AnimSets::Bridge, "BL18.j2a");
break;
case OBJECT::SAVEPOST:
preset.deactivates = false;
preset.behavior = CheckpointWrapper;
preset.curFrame = (jjAnimations[preset.curAnim] = jjAnimations[jjAnimSets[ANIM::CUSTOM[AnimSets::Misc]] + 8]).firstFrame;
break;
case OBJECT::BIGROCK:
preset.behavior = CompanionRock;
jjAnimFrames[preset.curFrame].hotSpotX -= 4; //to be better able to avoid landing on elevators
break;
case OBJECT::GRAPES:
preset.behavior = Balloon();
preset.scriptedCollisions = true;
preset.curFrame = firstMiscMiscFrame + 18;
break;
case OBJECT::FLYCARROT:
preset.behavior = TemporaryFlyCarrot(); //this is going to be limited to the PJ level, but it's similar enough code to some of this other stuff I find it more convenient to have it in here
preset.scriptedCollisions = true;
break;
case OBJECT::LETTUCE:
if (jjDifficulty > 0) { //no smart bombs on easy
preset.behavior = SmartBomb();
preset.playerHandling = HANDLING::SPECIAL;
preset.scriptedCollisions = true;
preset.curAnim = jjAnimSets[TrueColor::FindCustomAnim()].load(AnimSets::SmartBomb, "BL18.j2a");
preset.killAnim = jjAnimSets[ANIM::AMMO] + 77;
preset.lightType = LIGHT::BRIGHT;
preset.light = 9;
} else {
preset.behavior = BEHAVIOR::INACTIVE;
}
break;
case OBJECT::BURGER:
preset.behavior = Gear;
preset.playerHandling = HANDLING::PARTICLE;
preset.curAnim = jjAnimSets[TrueColor::FindCustomAnim()].load(AnimSets::Gear, "BL18.j2a");
preset.determineCurFrame();
break;
}*/
}
}
}
}
class IceDragon : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
obj.behave(BEHAVIOR::DRAGON, true);
//obj.behave(BEHAVIOR::DRAGON, obj.var[1] == 0? true:false);
if (obj.var[1] == 1) jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos + 2, obj.curFrame, obj.direction, SPRITE::NORMAL, 3, 3, 3);
obj.freeze = 0;
for (int i = 1; i < jjObjectCount; i++) {
if (jjObjects[i].behavior == BEHAVIOR::PLATFORM) {
float dx = jjObjects[i].xOrg - obj.xOrg, dy = jjObjects[i].yOrg - obj.yOrg;
if (dx * dx + dy * dy < 128 * 128) {
obj.xPos = jjObjects[i].xPos;
obj.yPos = jjObjects[i].yPos - 10;
obj.var[1] = 1;
}
}
}
const float wantx = Player.xPos;
const float wanty = Player.yPos - 96;
if (jjGameTicks % 70 == 1) {
IceBullet temp;
jjOBJ@ icebullet = jjObjects[jjAddObject(OBJECT::ICEBULLETPU, int(obj.xPos), int(obj.yPos), obj.objectID, CREATOR::OBJECT, jjVOIDFUNCOBJ(temp.onBehave))];
jjSample(icebullet.xPos, icebullet.yPos, SOUND::AMMO_ICEPU3);
icebullet.xPos = obj.xOrg + (8 * obj.direction);
icebullet.counterEnd = 30;
icebullet.state = STATE::FLY;
icebullet.playerHandling = HANDLING::ENEMYBULLET;
}
}
}
class NormalSucker : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
obj.behave(BEHAVIOR::FLOATSUCKER, false);
//obj.behave(BEHAVIOR::FLOATSUCKER, obj.state == STATE::KILL? true:false);
if (obj.state == STATE::FLOAT) makeRed(obj);
//jjDrawResizedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, (1 + (obj.age/20)) * obj.direction, (1 + (obj.age/20)), obj.freeze > 0? SPRITE::FROZEN : SPRITE::NORMAL);
jjDrawResizedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, 1, 1, obj.freeze > 0? SPRITE::FROZEN : SPRITE::NORMAL);
int playerID = obj.findNearestPlayer(28000);
/*if (playerID > -1 && obj.state == STATE::FLOAT) {
if (obj.age < 100 && obj.freeze == 0) {
obj.age += 5;
}
} else {
if (obj.age > 0) obj.age -= 5;
}
if (obj.age == 5 && obj.var[1] == 1) {
obj.var[0] = 1;
}
if (obj.age == 10) {
obj.var[1] = 0;
}
if (obj.age == 0) {
obj.var[0] = 0;
obj.var[1] = 1;
}*/
if (obj.state != STATE::DONE) {
//obj.behave(BEHAVIOR::FLOATSUCKER, false);
if (obj.freeze == 0) {
if (--obj.age == -175) {
obj.age = (false ? 75 : 200) + (jjRandom() & 31);
obj.lightType = LIGHT::NONE;
} else if (obj.age < 0) {
}
}
}else{
}
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bull, jjPLAYER@ play, int force) {
if (bull !is null) {
if (bull.playerHandling == HANDLING::PLAYERBULLET) {
obj.energy -= bull.animSpeed;
obj.justHit = 5;
if (bull.var[3] == 3) obj.special = 350;
}
//obj.behavior = BEHAVIOR::SUCKER;
//obj.state = STATE::EXPLODE;
//obj.behavior = BEHAVIOR::FLOATSUCKER;
}
return true;
}
}
/*
class WhiteSucker : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
obj.behave(BEHAVIOR::FLOATSUCKER, obj.state == STATE::KILL? true:false);
if (obj.state != STATE::KILL) jjDrawResizedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, (1 + (obj.age/20)) * obj.direction, (1 + (obj.age/20)), obj.freeze > 0? SPRITE::FROZEN : SPRITE::NORMAL);
int playerID = obj.findNearestPlayer(28000);
if (playerID > -1 && obj.state == STATE::FLOAT) {
if (obj.age < 100 && obj.freeze == 0) {
obj.age += 5;
}
} else {
if (obj.age > 0) obj.age -= 5;
}
if (obj.age == 5 && obj.var[1] == 1) {
obj.var[0] = 1;
}
if (obj.age == 10) {
obj.var[1] = 0;
}
if (obj.age == 0) {
obj.var[0] = 0;
obj.var[1] = 1;
}
float dx = jjPlayers[playerID].xPos - obj.xPos, dy = jjPlayers[playerID].yPos - obj.yPos;
if (obj.freeze == 0 && dx * dx + dy * dy < obj.age * obj.age) {
jjPlayers[playerID].hurt(1, false);
if (jjPlayers[playerID].xPos > int(obj.xPos + 8)) jjPlayers[playerID].xSpeed = 4;
else if (jjPlayers[playerID].xPos < int(obj.xPos - 8)) jjPlayers[playerID].xSpeed = -4;
if (jjPlayers[playerID].xPos < int(obj.xPos + 8) && jjPlayers[playerID].xPos > int(obj.xPos - 8)) {
jjPlayers[playerID].ySpeed = jjPlayers[playerID].yPos > obj.yPos? 3:-3;
jjPlayers[playerID].xSpeed = 4 * obj.direction;
} else jjPlayers[playerID].ySpeed = jjPlayers[playerID].yPos > obj.yPos? 1:-1;
}
if (obj.age == 5 && obj.var[1] == 1 && obj.freeze == 0) jjSample(obj.xPos, obj.yPos, SOUND::P2_FART, 48, 0);
for (int i = 0; i < jjObjectCount; i++) {
jjOBJ@ bull = jjObjects[i];
if (bull.playerHandling == HANDLING::PLAYERBULLET && obj.state == STATE::FLOAT) {
float bdx = bull.xPos - obj.xPos, bdy = bull.yPos - obj.yPos;
if (bdx * bdx + bdy * bdy < 200 * 200) {
if (obj.age < 100 && obj.freeze == 0) obj.age += 5;
}
if (bdx * bdx + bdy * bdy < obj.age * obj.age) {
if (bull.var[3] == 3) {
jjOBJ@ freeze = jjObjects[jjAddObject(OBJECT::ICEBULLET, obj.xPos, obj.yPos, obj.creatorID, CREATOR::PLAYER)];
freeze.counterEnd = 1;
freeze.state = STATE::FLY;
freeze.xSpeed = 1;
freeze.playerHandling = HANDLING::PLAYERBULLET;
bull.delete();
} else {
bull.ricochet();
}
}
}
}
if (obj.state == STATE::FREEZE) {
obj.special--;
if (obj.special == 0) {
obj.unfreeze(1);
obj.state = obj.oldState;
}
}
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bull, jjPLAYER@ play, int force) {
if (bull !is null) {
if (bull.playerHandling == HANDLING::PLAYERBULLET) {
obj.energy -= bull.animSpeed;
obj.justHit = 5;
if (bull.var[3] == 3) obj.special = 350;
}
}
return true;
}
}*/
class WhiteSucker : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
obj.behave(BEHAVIOR::FLOATSUCKER, false);
jjDrawResizedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, 1, 1, obj.freeze > 0? SPRITE::FROZEN : SPRITE::NORMAL);
int playerID = obj.findNearestPlayer(28000);
if (obj.state != STATE::DONE) {
//obj.behave(BEHAVIOR::FLOATSUCKER, false);
if (obj.freeze == 0) {
if (--obj.age == -175) {
obj.age = (false ? 75 : 200) + (jjRandom() & 31);
obj.lightType = LIGHT::NONE;
} else if (obj.age < 0) {
//obj.behave(BEHAVIOR::FLOATSUCKER, false);
//makeSparks(obj, true);
bool reducedY = true;
obj.lightType = LIGHT::BRIGHT;
obj.doesHurt = jjSampleLooped(obj.xPos,obj.yPos,SOUND::COMMON_SHIELD_ELEC,obj.doesHurt);
if (jjGameTicks & 3 == 0) {
jjOBJ@ spark = jjObjects[jjAddObject(OBJECT::SHARD, obj.xPos, obj.yPos, obj.objectID)];
spark.determineCurAnim(ANIM::PICKUPS, 85);
spark.animSpeed = 1;
spark.playerHandling = HANDLING::ENEMYBULLET;
spark.xSpeed /= 7;
if (reducedY)
spark.ySpeed /= 2;
else
spark.ySpeed = -2.5;
spark.lightType = LIGHT::POINT2;
}
}
}
}
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bull, jjPLAYER@ play, int force) {
if (bull !is null) {
if (bull.playerHandling == HANDLING::PLAYERBULLET) {
obj.energy -= bull.animSpeed;
obj.justHit = 5;
if (bull.var[3] == 3) obj.special = 350;
}
//obj.behavior = BEHAVIOR::SUCKER;
//obj.state = STATE::EXPLODE;
//obj.behavior = BEHAVIOR::FLOATSUCKER;
}
return true;
}
}
const uint heartFrame = jjAnimations[jjAnimSets[ANIM::PICKUPS] + 41];
enum AnimSets {
Basketball, Bridge, Canopy, Cow, Duet, Egg, FatChick, Font, Fooruman, Gear, LavaLamp, Misc, Monk, Muttshroom, PurpleDragon, SmartBomb, Trogdor, TubeTurle,
_LAST
};
enum Gender { Male, Female, Random };
class Enemy : jjBEHAVIORINTERFACE {
ANIM::Set animSetID;
Gender gender = Gender::Random;
Enemy(jjOBJ@ preset, Kangaroo::AnimSets asi = Kangaroo::AnimSets::_LAST) {
preset.behavior = this;
if (asi != AnimSets::_LAST)
jjAnimSets[animSetID = TrueColor::FindCustomAnim()].load(asi, "BL18.j2a");
preset.playerHandling = HANDLING::SPECIAL; //here special
preset.scriptedCollisions = true;
preset.bulletHandling = HANDLING::DETECTBULLET; //here special
preset.isTarget = true;
preset.triggersTNT = true;
preset.isFreezable = true;
preset.isBlastable = false; //not bothering with that nonsense
}
void die(jjOBJ@ obj, bool diedToBullet, bool diedWhileFrozen) const {
Player.food += 1;
obj.particlePixelExplosion(diedToBullet ? 0 : 2);
/*~ for (uint i = 0; i < 30; ++i) {
if (i < 10)
jjAddObject(OBJECT::SHARD, obj.xPos, obj.yPos, i, CREATOR::OBJECT, BloodSpatter);
jjObjects[jjAddObject(OBJECT::SHARD, obj.xPos, obj.yPos - (jjRandom() & 15), i, CREATOR::OBJECT, BoneWrapper)].freeze = diedWhileFrozen ? 255 : 0;
}*/
/*if(obj.eventID == OBJECT::FLOATSUCKER){
obj.state = STATE::KILL;
removeFromEventMap(obj);
obj.delete();
jjOBJ@ newObj = jjObjects[jjAddObject(OBJECT::FLOATSUCKER, obj.xPos, obj.yPos, OBJECT::FLOATSUCKER)];//, CREATOR::OBJECT, WhiteSucker);
//newObj.behavior = WhiteSucker();
//newObj.energy = 5;
//newObj.behavior = BEHAVIOR::FLOATSUCKER;
//jjSampleLoad(SOUND::P2_FART, "HH17_balloon.wav");
//jjObjects[jjAddObject(OBJECT::SUCKER, obj.xPos, obj.yPos)].behavior = BEHAVIOR::SUCKER;
//newObj.state = STATE::FALL;
//Sucker(newObj);
//obj.energy = 0;
//jjSamplePriority(jjRandom() & 3 < 3 ? SOUND::BAT_BATFLY1 : SOUND::BAT_BATFLY1);
//jjSample(obj.xPos, obj.yPos, SOUND::SPAZSOUNDS_BURP, 63, 0); //SUCKER_FART actually
}else{*/
obj.energy = 0;
obj.state = STATE::KILL;
removeFromEventMap(obj);
if(allowSpecialMoves == false)
jjSamplePriority(jjRandom() & 3 < 3 ? SOUND::INTRO_BOEM1 : SOUND::INTRO_BOEM2);
obj.delete();
//}
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
if (obj.state == STATE::START || obj.state == STATE::DELAYEDSTART)
return true;
const bool isProbablyBoss = jjObjectPresets[obj.eventID].energy >= 20;
const bool objfreeze = obj.freeze != 0;
if (bullet is null) {
if (force != 0) {
if (objfreeze) {
obj.state = obj.oldState;
obj.unfreeze(1);
}
if ((obj.energy -= 4) <= 0) {
obj.energy = 0;
if (!IsMurderer) {
if(allowSpecialMoves == false){
player.showText("#||||~@@MURDERER", STRING::LARGE);
die(obj, false, objfreeze);
}
}
} else {
obj.justHit = 5;
}
if (!IsMurderer) {
if(allowSpecialMoves == false){
IsMurderer = true;
jjPalette.fill(jjPALCOLOR(), 0.15);
jjPalette.apply();
if (!jjMusicLoad("DARKSIDE.MOD")) jjMusicStop();
}
}
//~ jjSample(obj.xPos, obj.yPos, ButtstompSounds[jjRandom() % ButtstompSounds.length]);
if (force > 0) { //buttstomp or sugar rush
player.buttstomp = 50; //landing
player.ySpeed = player.ySpeed / -2 - 8;
player.yAcc = 0;
player.extendInvincibility(-70);
} else if (force == -101) { //running into frozen enemy
player.xAcc = 0;
player.xSpeed /= -2;
player.ySpeed = -6;
player.extendInvincibility(-10);
}
if (jjObjectPresets[obj.eventID].energy > 20)
player.specialMove = 0;
if (!IsMurderer && allowSpecialMoves && obj.eventID != OBJECT::BAT && obj.eventID != OBJECT::FLOATSUCKER && monsterRemain) {
obj.oldState = obj.state;
obj.state = STATE::DONE;
obj.playerHandling = HANDLING::SPECIALDONE;
obj.deactivates = false;
obj.triggersTNT = false;
obj.isTarget = false;
obj.lightType = LIGHT::NONE;
//~ if (!jjLowDetail)
//~ jjAddObject(OBJECT::SHARD, obj.xPos, obj.yPos, 0, CREATOR::OBJECT, Orgasm);
const uint rand = jjRandom();
//~ jjSample(obj.xPos, obj.yPos, SOUND::Sample(((gender == Gender::Female || (gender == Gender::Random && (rand & 128) == 128)) ? ESJF : ESJM) + (rand % 3)));
//~ Player.lives += 1;
removeFromEventMap(obj);
}else if(obj.eventID == OBJECT::BAT || obj.eventID == OBJECT::FLOATSUCKER || !monsterRemain){
die(obj, true, objfreeze);
}
} else if (!objfreeze)
player.hurt();
} else {
if (objfreeze) {
obj.state = obj.oldState;
obj.unfreeze(0);
force += 1;
}
if ((bullet.var[6] & 16) == 0) { //fireball
bullet.state = STATE::EXPLODE;
} else if (isProbablyBoss) {
bullet.state = STATE::EXPLODE;
force *= 2;
}
if ((obj.energy -= force) <= 0) {
if (!IsMurderer && obj.eventID != OBJECT::BAT && obj.eventID != OBJECT::FLOATSUCKER && monsterRemain) {
obj.oldState = obj.state;
obj.state = STATE::DONE;
obj.playerHandling = HANDLING::SPECIALDONE;
obj.deactivates = false;
obj.triggersTNT = false;
obj.isTarget = false;
obj.lightType = LIGHT::NONE;
//~ if (!jjLowDetail)
//~ jjAddObject(OBJECT::SHARD, obj.xPos, obj.yPos, 0, CREATOR::OBJECT, Orgasm);
const uint rand = jjRandom();
//~ jjSample(obj.xPos, obj.yPos, SOUND::Sample(((gender == Gender::Female || (gender == Gender::Random && (rand & 128) == 128)) ? ESJF : ESJM) + (rand % 3)));
//~ Player.lives += 1;
removeFromEventMap(obj);
} else
if(allowSpecialMoves == false || obj.eventID == OBJECT::BAT || obj.eventID == OBJECT::FLOATSUCKER || !monsterRemain){
die(obj, true, objfreeze);
}
} else {
obj.justHit = 5;
}
}
if (obj.energy <= 0 && !isProbablyBoss) {
const auto rand = jjRandom();
if (rand & 15 == 0) {
jjOBJ@ pickup = jjObjects[jjAddObject(rand & 16 == 16 ? OBJECT::FASTFIRE : OBJECT::CARROT, obj.xPos, obj.yPos, obj.objectID,CREATOR::OBJECT, BEHAVIOR::FLICKERGEM)];
pickup.playerHandling = HANDLING::DELAYEDPICKUP;
pickup.var[2] = 20;
pickup.counter = 400 - (jjDifficulty * 50);
pickup.light = 1;
pickup.lightType = LIGHT::LASER;
}
}
return true;
}
void onBehave(jjOBJ@ obj) {}
uint8 Layer = 4;
void drawBodySprite(const jjOBJ@ obj, float xPos, float yPos, int curFrame, int angle = 0, float scale = 1) const {
const SPRITE::Mode mode = obj.state == STATE::DONE ? SPRITE::TINTED : obj.state == STATE::FREEZE ? SPRITE::FROZEN : obj.justHit == 0 ? SPRITE::NORMAL : SPRITE::SINGLECOLOR;
if (angle == 0)
jjDrawSpriteFromCurFrame(xPos, yPos, curFrame, obj.direction, mode, 48, Layer);
else
jjDrawRotatedSpriteFromCurFrame(xPos, yPos, curFrame, angle, obj.direction * scale, scale, mode, 48, Layer);
}
void drawHearts(const jjOBJ@ obj, int curFrame = -1) const {
if (obj.state == STATE::DONE) {
if (curFrame < 0) curFrame = obj.curFrame;
const float heartsY = obj.yPos + jjAnimFrames[obj.curFrame].hotSpotY - 8;
for (int i = 0; i < 1024; i += 205) {
const int heartAngle = (jjGameTicks << 3) + i;
jjDrawResizedSpriteFromCurFrame(obj.xPos + jjSin(heartAngle) * 13, heartsY + jjCos(heartAngle) * 6, heartFrame, 0.5, 0.5, SPRITE::TINTED, 48, Layer);
}
}
}
void drawMainSprite(const jjOBJ@ obj, int curFrame = -1, int angle = 0) const {
if (curFrame < 0) curFrame = obj.curFrame;
drawHearts(obj, curFrame);
drawBodySprite(obj, obj.xPos, obj.yPos, curFrame, angle);
}
void onDraw(jjOBJ@ obj) { drawMainSprite(obj); }
}
void removeFromEventMap(jjOBJ@ obj) {
obj.eventID = 0; //prevent STATE::DEACTIVATE snafus
jjEventSet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0);
}
class FatChick : Enemy {
FatChick(jjOBJ@ preset) {
super(preset, AnimSets::FatChick);
gender = Gender::Female;
}
void onBehave(jjOBJ@ obj) override {
if (obj.counter >= 70*2 - 1 && obj.state == STATE::WALK) {
if (obj.counter++ == 70*2 - 1) {
jjObjects[jjAddObject(OBJECT::BULLET, obj.xPos + 5*obj.direction, obj.yPos - 14, obj.objectID, CREATOR::OBJECT, FatHead)].curFrame = jjAnimations[jjAnimSets[animSetID]];
} else if (obj.counter == 70*3)
obj.counter = 0;
} else {
obj.behave(BEHAVIOR::FATCHICK, false);
}
}
void onDraw(jjOBJ@ obj) override {
drawMainSprite(obj, (obj.counter >= 70*2) ? jjAnimations[jjAnimSets[ANIM::FATCHK]] + 10 + ((obj.counter >> 2) & 1) : -1);
}
}
int GetAngle(float xSpeed, float ySpeed) {
return int(atan2(
ySpeed,
xSpeed
) * -512.0 * 0.318309886142228);
}
void FatHead(jjOBJ@ obj) {
//loop this and select the closest local player
//but be careful to hurt all
if (obj.state == STATE::START) {
obj.direction = jjObjects[obj.creatorID].direction;
obj.special = GetAngle(Player.yPos - obj.yPos, obj.xPos - Player.xPos);
obj.doesHurt = (jjRandom() & 3) + 6;
obj.state = STATE::FLY;
obj.playerHandling = HANDLING::ENEMYBULLET;
obj.animSpeed = 1;
} else if (obj.state == STATE::EXPLODE || ++obj.counter >= (!IsMurderer ? 120 + jjDifficulty * 40 : 255)) {
obj.delete();
} else {
int angle = obj.special;
float xPos = obj.xPos, yPos = obj.yPos;
const jjANIMFRAME@ myFrame = jjAnimFrames[obj.curFrame];
for (int i = 0; i < obj.counter; ++i) {
jjDrawSpriteFromCurFrame(xPos, yPos, obj.curFrame, obj.direction, SPRITE::NORMAL,0, 3);
if (Player.blink == 0 && myFrame.doesCollide(int(xPos), int(yPos), obj.direction, jjAnimFrames[Player.curFrame], int(Player.xPos), int(Player.yPos), Player.direction)) {
Player.objectHit(obj, 0, HANDLING::ENEMYBULLET);
//break;
}
xPos += jjSin(angle) * 2;
yPos += jjCos(angle) * 2;
angle += ((((((i >> 5) + 1) >> 1) & 1) << 1) - 1) * obj.doesHurt * obj.direction;
}
}
}
void Recolor(jjANIMFRAME@ frame, const array<uint8> &in pattern, const array<uint8> &in replace) {
jjPIXELMAP image(frame);
for (int x = image.width - 1; x >= 0; --x)
for (int y = image.height - 1; y >= 0; --y) {
const uint8 color = image[x,y];
if (pattern.find( color & ~7) >= 0)
image[x,y] = replace[color & 7];
}
image.save(frame);
}
void Recolor(const jjANIMATION@ anim, const array<uint8> &in pattern, const array<uint8> &in replace) {
for (uint frameID = 0; frameID < anim.frameCount; ++frameID)
Recolor(jjAnimFrames[anim + frameID], pattern, replace);
}
array<uint8>@ RecolorReplace(uint8 hue) {
return array<uint8> = {hue,hue+1,hue+2,hue+3,hue+4,hue+5,hue+6,hue+7};
}
int getParameterAtOrigin(const jjOBJ@ obj, int offset, int length) /*const*/ {
return jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, offset, length);
}
class BoneChucker : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
obj.behave(BEHAVIOR::SKELETON);
int playerID = obj.findNearestPlayer(150000);
int fireRate = jjDifficulty >= 3? 12:(35 * (3 / (jjDifficulty + 1)));
if (playerID > -1 && obj.freeze == 0) {
if (facingPlayer(obj, playerID) && jjGameTicks % fireRate == 0) {
jjOBJ@ bone = jjObjects[jjAddObject(OBJECT::SHARD, int(obj.xPos - (8 * obj.direction)), int(obj.yPos + 2), obj.objectID, CREATOR::OBJECT)];
bone.behavior = BEHAVIOR::BOUNCERBULLET;
bone.determineCurAnim(ANIM::SKELETON, 0);
bone.playerHandling = HANDLING::ENEMYBULLET;
bone.animSpeed = 1;
bone.direction = obj.direction;
bone.xSpeed = 6 * bone.direction;
bone.ySpeed = -3;
bone.state = STATE::FLY;
bone.lightType = LIGHT::POINT;
bone.counterEnd = 105;
if (bone.counter == 0) randomBoneSample(bone);
bone.killAnim = jjObjectPresets[OBJECT::BOUNCERBULLET].killAnim;
}
}
}
}
void randomBoneSample(jjOBJ@ obj) {
switch(jjRandom()%4) {
case 0: jjSample(obj.xPos, obj.yPos, SOUND::SKELETON_BONE1, 0, 0); break;
case 1: jjSample(obj.xPos, obj.yPos, SOUND::SKELETON_BONE2, 0, 0); break;
case 2: jjSample(obj.xPos, obj.yPos, SOUND::SKELETON_BONE3, 0, 0); break;
case 3: jjSample(obj.xPos, obj.yPos, SOUND::SKELETON_BONE5, 0, 0); break;
}
}
bool facingPlayer(jjOBJ@ obj, int playerID) {
if (((obj.xPos < (jjPlayers[playerID].xPos - 32) && obj.direction == 1) || (obj.xPos > (jjPlayers[playerID].xPos + 32) && obj.direction == -1)) && obj.yPos <= int(jjPlayers[playerID].yPos + 208) && obj.yPos >= int(jjPlayers[playerID].yPos - 208)) return true;
return false;
}
class Fencer : Enemy {
Fencer(jjOBJ@ preset) {
super(preset);
gender = Gender::Male;
for (uint animID = 0; animID < 2; ++animID) {
Recolor(jjAnimations[jjAnimSets[ANIM::FENCER] + animID], array<uint8>={24,48}, array<uint8>={38,39,79,79,79,79,79,79}); //red or pink to black
}
}
void onBehave(jjOBJ@ obj) override {
if (obj.state == STATE::START) {
obj.doesHurt = getParameterAtOrigin(obj, 0, 2);
switch (obj.doesHurt) {
case 0: //floor
obj.behave(BEHAVIOR::FENCER, false);
break;
case 1: {
int newYPos = int(obj.yPos) - 22;
while (newYPos > 0 && !jjMaskedPixel(int(obj.xPos), newYPos))
newYPos -= 2;
obj.yPos = newYPos + 22;
obj.state = STATE::DELAYEDSTART;
break;
}
case 2:
case 3: {
int newXPos = int(obj.xPos);
obj.direction = ((int(obj.doesHurt) - 2) << 1) - 1;
while (!jjMaskedPixel(newXPos, int(obj.yPos)))
newXPos += (obj.direction << 1);
obj.xPos = newXPos - (obj.direction << 4);
obj.state = STATE::STILL;
}
}
} else if (obj.doesHurt == 1) { //hiding at ceiling
if (Player.yPos > obj.yPos && abs(Player.xPos - obj.xPos) < 64) {
obj.state = STATE::JUMP;
obj.doesHurt = 0;
obj.curAnim -= 1;
obj.counter = obj.frameID = 0;
jjSample(obj.xPos, obj.yPos, SOUND::COMMON_UP, 52);
obj.direction = Player.direction;
obj.xSpeed = -1 * obj.direction;
obj.ySpeed = 1;
}
} else {
if (obj.state == STATE::JUMP && obj.doesHurt != 0) {
obj.xSpeed = (obj.doesHurt == 2) ? 4 : -4;
obj.doesHurt = 0;
}
obj.behave(BEHAVIOR::FENCER, false);
if (IsMurderer)// || (jjGameTicks & 1) == 1)
obj.behave(BEHAVIOR::FENCER, false);
}
}
void onDraw(jjOBJ@ obj) override {
if (obj.state == STATE::DELAYEDSTART) //hiding in the ceiling
return;
else if (obj.doesHurt != 0)
drawMainSprite(obj, -1, obj.doesHurt == 2 ? 0x300 : 0x100);
else
Enemy::onDraw(obj);
}
}
//3 Tube Turtle
class TubeTurle : Enemy {
TubeTurle(jjOBJ@ preset) {
super(preset, AnimSets::TubeTurle);
preset.energy = 5;
}
void onBehave(jjOBJ@ obj) override {
++obj.counter;
if (obj.state == STATE::WAIT) {
obj.curFrame = jjAnimations[obj.curAnim] + ((jjGameTicks >> 3) & 3);
const float distanceFromPlayer = abs(Player.xPos - obj.xPos);
if (distanceFromPlayer < 290)
obj.direction = (obj.xPos > Player.xPos) ? -1 : 1;
if ((distanceFromPlayer < 180 || IsMurderer) && obj.counter > 70) {
obj.counter = 0;
obj.state = STATE::ATTACK;
}
} else if (obj.state == STATE::ATTACK) {
int frameID = obj.counter / 6;
if (frameID > 12) {
obj.state = STATE::WAIT;
obj.counter = 0;
} else if (frameID > 8)
frameID = 12 - frameID;
else if (frameID > 3) {
frameID = 3;
if (obj.counter % 6 == 5) {
jjObjects[jjAddObject(OBJECT::BULLET, obj.xPos + 20*obj.direction, obj.yPos + 12, obj.objectID, CREATOR::OBJECT, TurleTube)].curAnim = jjAnimSets[animSetID];
jjSample(obj.xPos, obj.yPos, SOUND::Sample(SOUND::TURTLE_SPK1TURT + (jjRandom() & 3)));
}
}
obj.curFrame = jjAnimations[obj.curAnim + 1] + frameID;
} else {
obj.behave(BEHAVIOR::TUBETURTLE, false);
return;
}
const jjANIMFRAME@ frame = jjAnimFrames[obj.curFrame];
if (!jjMaskedPixel(int(obj.xPos), int(obj.yPos) + frame.hotSpotY - frame.coldSpotY))
obj.yPos += 1;
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
if (bullet !is null && obj.state == STATE::FLOAT) {
bullet.state = STATE::EXPLODE;
obj.state = STATE::WAIT;
obj.energy = 4;
obj.curAnim = jjAnimSets[animSetID] + 1;
obj.counter = 45;
obj.yPos -= 20;
jjSample(obj.xPos, obj.yPos, SOUND::TURTLE_TURN);
return true;
}
return Enemy::onObjectHit(obj, bullet, player, force);
}
}
void TurleTube(jjOBJ@ obj) {
if (obj.state == STATE::START) {
obj.xSpeed = jjObjects[obj.creatorID].direction * 5;
obj.ySpeed = int(jjRandom() & 7) * -0.5;
obj.state = STATE::FLY;
obj.playerHandling = HANDLING::ENEMYBULLET;
obj.animSpeed = 1;
obj.killAnim = jjAnimSets[ANIM::AMMO] + 7;
} else if (obj.state == STATE::EXPLODE) {
obj.curAnim = obj.killAnim;
obj.frameID = 0;
obj.behavior = BEHAVIOR::EXPLOSION;
obj.playerHandling = HANDLING::EXPLOSION;
} else {
if (jjMaskedPixel(int(obj.xPos), int(obj.yPos)))
obj.state = STATE::EXPLODE;
else {
obj.xPos += obj.xSpeed;
obj.yPos += obj.ySpeed += 0.1;
obj.frameID = jjGameTicks / 3;
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.determineCurFrame());
}
}
}
//4 Float Lizard
class FloatLizard : Enemy {
Egg egg;
FloatLizard(jjOBJ@ preset) {
super(preset, AnimSets::Egg);
egg.firstAnim = jjAnimSets[animSetID];
preset.behavior = jjVOIDFUNCOBJ(behaviorAssigner);
preset.energy = 1;
gender = Male;
Layer = 2;
for (uint animID = 0; animID < 5; ++animID) {
Recolor(jjAnimations[jjAnimSets[ANIM::LIZARD] + animID], array<uint8>={40,56}, array<uint8>={38,39,39,39,39,79,79,79}); //orange or yellow to black
}
}
void behaviorAssigner(jjOBJ@ obj) {
if (obj.creator != CREATOR::LEVEL || getParameterAtOrigin(obj, 1, 1) == 0) obj.behavior = this; else obj.behavior = egg;
}
void onBehave(jjOBJ@ obj) override {
//if (obj.state == STATE::START) {
// obj.state = getParameterAtOrigin(obj, 0, 1) == 0 ? STATE::SLEEP : STATE::FALL;
//}
//const int xDiff = 96 * obj.direction;
//Player.yPos -= 96; //target ABOVE me
//Player.xPos += xDiff;
//obj.behave(BEHAVIOR::FLOATLIZARD, true);
//Player.yPos += 96;
//Player.xPos -= xDiff;
obj.behave(BEHAVIOR::FLOATLIZARD, true);
obj.state = STATE::WALK;
if ( 0<=abs((int(Player.xPos)>>>5) - (int(obj.xPos)>>>5)) && abs((int(Player.xPos)>>>5) - (int(obj.xPos)>>>5)) <=5 && 0<=abs((int(Player.yPos)>>>5) - (int(obj.yPos)>>>5)) && abs((int(Player.yPos)>>>5) - (int(obj.yPos)>>>5)) <=5) {
//jjDebug((Player.xPos>>5)+" "+(Player.yPos>>5)+" "+(obj.xPos>>5)+" "+(obj.yPos>>5)+" "+abs((Player.xPos>>5) - (obj.xPos>>5))+" "+abs((Player.yPos>>5) - (obj.yPos>>5)));
jjObjects[jjAddObject(OBJECT::FLOATLIZARD, obj.xPos, obj.yPos, obj.creatorID, CREATOR::LEVEL, BEHAVIOR::FLOATLIZARD)];
obj.delete();
}
/*if (obj.state == STATE::WALK) {
obj.var[1] = 0; //counter to entering STATE::ATTACK
if (obj.yPos < Player.yPos && obj.yPos > Player.yPos - 192 && abs(obj.xPos - Player.xPos) < 24 && !jjMaskedVLine(int(obj.xPos), int(obj.yPos) - 5, 30)) {
//obj.state = STATE::FLY;
//obj.behavior = BEHAVIOR::FLOATLIZARD;
jjObjects[jjAddObject(OBJECT::FLOATLIZARD, obj.xPos, obj.yPos, obj.creatorID, CREATOR::LEVEL, BEHAVIOR::FLOATLIZARD)];
//obj.counter = 0;
//obj.frameID = 0;
//obj.direction = (obj.xPos > Player.xPos) ? -6 : 6;
obj.delete();
}
} else if ((obj.state == STATE::WALK || obj.state == STATE::WAIT) && ++obj.counter > 70*4) {
obj.state = STATE::SLEEP; //return to the air
obj.ySpeed = -8;
obj.xSpeed *= 2;
} else if (obj.state == STATE::SLEEP) { //just got off the ground
obj.xPos += (obj.xSpeed *= 0.9) + jjSin(jjGameTicks << 3) / 2;
obj.yPos += (obj.ySpeed *= 0.9) + jjCos(jjGameTicks << 3);
}*/
}
/*void onDraw(jjOBJ@ obj) override {
if (obj.state == STATE::FLY || obj.state == STATE::SLEEP || (obj.state == STATE::DONE && (obj.oldState == STATE::FLY || obj.oldState == STATE::SLEEP)))
jjDrawSprite(obj.xPos, obj.yPos + 4, ANIM::LIZARD, 3, jjGameTicks/3, obj.direction, SPRITE::NORMAL,0, Layer);
Enemy::onDraw(obj);
}*/
}
class Egg : jjBEHAVIORINTERFACE {
uint firstAnim;
void onBehave(jjOBJ@ obj) {
if (obj.state == STATE::START) {
obj.curFrame = jjAnimations[firstAnim];
obj.xPos += int(jjRandom() & 31) - 15;
obj.putOnGround();
obj.yPos += jjRandom() & 3;
obj.isFreezable = false; //let's just not bother
obj.isTarget = false;
if (jjParameterGet(uint(obj.xOrg)>>5, uint(obj.yOrg)>>5, 0, 1) == 0) { //real egg
obj.playerHandling = HANDLING::SPECIAL;
obj.scriptedCollisions = true;
obj.bulletHandling = HANDLING::DETECTBULLET;
obj.counterEnd = 4;
obj.energy = 30;
} else { //false egg
obj.playerHandling = HANDLING::PARTICLE;
obj.counterEnd = (jjRandom() & 2) + 3;
}
obj.state = STATE::WAIT;
} else if (obj.state == STATE::DEACTIVATE) {
obj.deactivate();
} else if (obj.counterEnd == 4) { //real egg, needing to hatch
if (obj.counter > 0) {
if (++obj.counter >= 210)
hatch(obj);
else
obj.curFrame = jjAnimations[firstAnim] + obj.counter / 70;
}
}
}
void onDraw(jjOBJ@ obj) {
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, 1, obj.counterEnd != 4 ? SPRITE::TINTED : SPRITE::NORMAL, 79, obj.counterEnd);
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
if (bullet !is null) {
obj.counter += force*70;
bullet.state = STATE::EXPLODE;
} else {
if (obj.counter == 0) obj.counter = 1;
}
return true;
}
void hatch(jjOBJ@ obj) const {
jjSample(obj.xPos, obj.yPos, SOUND::COMMON_CANSPS);
jjParameterSet(uint(obj.xOrg)>>5, uint(obj.yOrg)>>5, 1, 1, 0); //only raptors now
jjOBJ@ blacky = jjObjects[jjAddObject(obj.eventID, obj.xOrg, obj.yOrg, 0, CREATOR::LEVEL)];
blacky.xPos = obj.xPos;
blacky.yPos = obj.yPos - 24;
for (uint i = 0; i < 6; ++i) {
jjOBJ@ shard = jjObjects[jjAddObject(OBJECT::SHARD, obj.xPos, obj.yPos - 24, obj.creatorID, CREATOR::OBJECT, BEHAVIOR::INACTIVE)];
shard.curAnim = firstAnim + 1 + i;
shard.behavior = BEHAVIOR::SHARD;
}
obj.delete();
}
}
class Fish : Enemy {
Fish(jjOBJ@ preset) {
super(preset);
preset.energy = 1;
preset.isFreezable = false;
preset.isTarget = false;
for (uint animID = 0; animID < 2; ++animID) {
Recolor(jjAnimations[jjAnimSets[ANIM::FISH] + animID], array<uint8>={32}, RecolorReplace(80));
Recolor(jjAnimations[jjAnimSets[ANIM::FISH] + animID], array<uint8>={48}, RecolorReplace(64));
}
}
void onBehave(jjOBJ@ obj) override {
if (obj.state == STATE::START)
obj.state = STATE::DELAYEDSTART;
else if (obj.state == STATE::DEACTIVATE)
obj.deactivate();
else if (obj.state == STATE::FREEZE)
obj.behave(BEHAVIOR::FISH, false); //as good as any other for unfreezing
else {
if (obj.state == STATE::DELAYEDSTART) {
if (obj.yPos > jjWaterLevel) {
obj.state = STATE::FLY;
obj.isFreezable = true;
obj.isTarget = true;
}
} else if (obj.state != STATE::DONE && obj.state != STATE::KILL) {
if (int(obj.xSpeed) == 0 && int(obj.ySpeed) == 0) {
const int divide = 1;//IsMurderer ? 1 : 2;
const float targetX = obj.xOrg + ((jjRandom() & 255) - 127.5) / divide;
const float targetY = obj.yOrg + ((jjRandom() & 255) - 127.5) / divide;
obj.xSpeed = (targetX - obj.xPos) / 16 / divide;
obj.ySpeed = (targetY - obj.yPos) / 16 / divide;
obj.direction = obj.xSpeed >= 0 ? 1 : -1;
} else {
if (jjMaskedPixel(int(obj.xPos + obj.xSpeed), int(obj.yPos)))
obj.xSpeed = -obj.xSpeed;
else
obj.xPos += obj.xSpeed;
if (jjMaskedPixel(int(obj.xPos), int(obj.yPos + obj.ySpeed)))
obj.ySpeed = -obj.ySpeed;
else
obj.yPos += obj.ySpeed;
obj.xSpeed *= 0.94;
obj.ySpeed *= 0.94;
}
if (obj.yPos < jjWaterLevel)
obj.yPos = jjWaterLevel;
}
obj.frameID = jjGameTicks >> 3;
obj.determineCurFrame();
}
}
void onDraw(jjOBJ@ obj) override {
if (obj.state == STATE::DELAYEDSTART) //yet to be activated by the water
return;
else
Enemy::onDraw(obj);
}
}
class YetiCrab : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
obj.behave(BEHAVIOR::CRAB);
if (obj.xSpeed > 0 && obj.direction == 1) obj.xPos += 1;
else if (obj.xSpeed < 0 && obj.direction == -1) obj.xPos -= 1;
if (jjGameTicks % 70 == 1) {
IceBullet temp;
jjOBJ@ icebullet = jjObjects[jjAddObject(OBJECT::LIGHTNINGSHIELDBULLET, int(obj.xPos), int(obj.yPos), obj.objectID, CREATOR::OBJECT)];//, jjVOIDFUNCOBJ(temp.onBehave))];
icebullet.behavior = BEHAVIOR::PEPPERBULLET;
jjSample(icebullet.xPos, icebullet.yPos, SOUND::AMMO_ICEPU3);
icebullet.xPos = obj.xOrg + (8 * obj.direction);
icebullet.counterEnd = 30;
icebullet.state = STATE::FLY;
icebullet.playerHandling = HANDLING::ENEMYBULLET;
}
}
}
class DragonFly : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
obj.behave(BEHAVIOR::DRAGONFLY);
if (jjGameTicks % 70 == 1) {
IceBullet temp;
jjOBJ@ icebullet = jjObjects[jjAddObject(OBJECT::RFBULLETPU, int(obj.xPos), int(obj.yPos), obj.objectID, CREATOR::OBJECT)];//, jjVOIDFUNCOBJ(temp.onBehave))];
icebullet.behavior = BEHAVIOR::RFBULLET;
//jjSample(icebullet.xPos, icebullet.yPos, SOUND::AMMO_ICEPU3);
//icebullet.xPos = obj.xOrg + (8 * obj.direction);
//icebullet.counterEnd = 30;
//icebullet.state = STATE::FLY;
icebullet.playerHandling = HANDLING::ENEMYBULLET;
}
}
}
//5 Red Bat
class RedBat : Enemy {
RedBat(jjOBJ@ preset) {
super(preset);
preset.energy = 2;
preset.curAnim -= 1;
gender = Gender::Female;
//for (uint animID = 0; animID < 1; ++animID) { //lul
// Recolor(jjAnimations[jjAnimSets[ANIM::BAT] + animID], array<uint8>={88}, RecolorReplace(24));
//}
}
void onBehave(jjOBJ@ obj) override {
if (obj.state != STATE::FLY) obj.behave(BEHAVIOR::BAT);
if (obj.state == STATE::START){
// obj.state = STATE::FLY; //don't stick to the ceiling like regular bats
// obj.behave(BEHAVIOR::BAT, true);
}else if (obj.state == STATE::DEACTIVATE || obj.state == STATE::FREEZE){
obj.behave(BEHAVIOR::BAT, false); //as good as any other for unfreezing
}else if (obj.state == STATE::FLY) { //I feel bad about not making any call to jjOBJ::behave in here but there's not a lot of bat code to begin with
//obj.behave(BEHAVIOR::BAT, false);
obj.doesHurt = jjSampleLooped(obj.xPos,obj.yPos,SOUND::BAT_BATFLY1,obj.doesHurt,43,0);
const float wantx = Player.xPos;
const float wanty = Player.yPos - 96;
//cut out some code here about making the bat return to its place of origin if the player is too far away
const float xDiff = int(jjRandom() & 32767) / 10923.6667f;
const float xTarget = obj.xPos + (wantx<obj.xPos ? -xDiff : xDiff);
if (!jjMaskedPixel(int(xTarget), int(obj.yPos)))
obj.xPos = xTarget;
const float yTarget = obj.yPos + obj.ySpeed;
/*if (yTarget <= jjWaterLevel && !jjMaskedPixel(int(obj.xPos), int(yTarget)))
obj.yPos = yTarget;
*/
if (++obj.counter > 7) {
if (wantx<obj.xPos-1)
obj.direction=-1;
else
if (wantx>obj.xPos+1)
obj.direction=1;
//if (player.yPos > obj.yPos && obj.yPos < (obj.yOrg + 96)) obj.yPos += 1;
if (wanty < obj.yPos)
obj.yPos -= 5;
else
obj.yPos += 5;
/*if (wanty < obj.yPos)
obj.ySpeed = int(jjRandom() & 32767) / -16384.f;
else
obj.ySpeed = int(jjRandom() & 32767) / 16384.f;
*/
if (++obj.counterEnd > 14) {
obj.counterEnd = 0;
jjOBJ@ bullet = jjObjects[jjAddObject(OBJECT::ELECTROBULLET, obj.xPos, obj.yPos, 0, CREATOR::OBJECT)];
bullet.playerHandling = HANDLING::ENEMYBULLET;
bullet.xSpeed = (wantx - obj.xPos) / 32.f;//(IsMurderer ? 32.f : 128.f);
bullet.ySpeed = 3;
jjSample(obj.xPos, obj.yPos, SOUND::Sample(SOUND::AMMO_LASER2 + (jjRandom()&1)));
}
obj.counter = 0;
++obj.frameID;
obj.determineCurFrame();
}
}
}
}
//6 Monkeys
bool LevelContainsMonkeys, LevelContainsLava;
class Monkey : Enemy {
Monkey(jjOBJ@ preset) {
super(preset);
gender = Gender::Male;
if (!LevelContainsMonkeys) {
LevelContainsMonkeys = true;
for (uint animID = 0; animID < 7; ++animID) {
const jjANIMATION@ anim = jjAnimations[jjAnimSets[ANIM::MONKEY] + animID];
if (animID >= 2) {
Recolor(anim, array<uint8>={40}, RecolorReplace(24)); //orange to red
Recolor(anim, array<uint8>={64}, RecolorReplace(36)); //brown to orange
} else {
Resize::Resize(anim, 1.75, Resize::Method::Scale2x);
}
}
}
}
void onBehave(jjOBJ@ obj) override {
for (uint i = (IsMurderer && jjGameTicks&1==1) ? 2 : 1; i > 0; --i)
obj.behave(BEHAVIOR::MONKEY, false); //both MONKEY and STANDMONKEY use the same behavior... not very efficient, arjan
}
}
void DoObjectLoop() {
if (LevelContainsMonkeys && (jjDifficulty > 0 || IsMurderer)) {
for (uint i = jjObjectCount; --i > 0;) {
jjOBJ@ obj = jjObjects[i];
if (obj.behavior == BEHAVIOR::MONKEYBULLET) {
if (obj.state == STATE::EXPLODE) {
obj.behavior = BEHAVIOR::RFBULLET;
obj.playerHandling = HANDLING::ENEMYBULLET;
obj.eventID = OBJECT::RFBULLETPU;
obj.curAnim = obj.killAnim;
obj.frameID = 0;
obj.blast(96*96, false);
} else {
obj.lightType = LIGHT::BRIGHT;
obj.light = 6;
}
}
}
}
}
class IceBullet : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
obj.behave(BEHAVIOR::BULLET, true);
obj.direction = obj.xSpeed < 1? -1:1;
int playerID = obj.findNearestPlayer(30000);
if (obj.state == STATE::FLY && obj.doesCollide(jjPlayers[playerID], true)) {
jjPlayers[playerID].freeze(true);
jjPlayers[playerID].xSpeed -= (obj.xSpeed / (obj.direction == -1? 8:-8)) * obj.direction;
}
}
}
void handleEnemyProjectiles() {
for (int i = 0; i < jjObjectCount; i++) {
jjOBJ@ obj = jjObjects[i];
if (obj.behavior == BEHAVIOR::TOASTERBULLET && obj.creatorType != CREATOR::PLAYER && jjGameTicks % 1 == 0 ) {
IceBullet temp;
jjOBJ@ icebullet = jjObjects[jjAddObject(OBJECT::ICEBULLETPU, int(obj.xPos), int(obj.yPos), obj.objectID, CREATOR::OBJECT, jjVOIDFUNCOBJ(temp.onBehave))];
jjSample(icebullet.xPos, icebullet.yPos, SOUND::AMMO_ICEPU3);
icebullet.xPos = obj.xOrg + (8 * obj.direction);
icebullet.counterEnd = 30;
icebullet.state = STATE::FLY;
icebullet.playerHandling = HANDLING::ENEMYBULLET;
obj.delete();
}
/*
if (obj.behavior == BEHAVIOR::MONKEYBULLET) {
Snowball temp;
jjOBJ@ snowball = jjObjects[jjAddObject(OBJECT::BOUNCERBULLET, int(obj.xPos), int(obj.yPos), obj.objectID, CREATOR::OBJECT, jjVOIDFUNCOBJ(temp.onBehave))];
snowball.playerHandling = HANDLING::ENEMYBULLET;
jjSample(snowball.xOrg, snowball.yOrg, SOUND::COMMON_FOEW2, 0, 0);
snowball.xAcc = snowball.yAcc = 0;
snowball.xSpeed = 8 * obj.direction;
snowball.yPos = snowball.yOrg;
int playerID = snowball.findNearestPlayer(80000);
if (jjPlayers[playerID].yPos > int(snowball.yPos)) snowball.ySpeed = (jjPlayers[playerID].yPos > int(snowball.yPos) + 64)? 2:0;
else if (jjPlayers[playerID].yPos < int(snowball.yPos)) snowball.ySpeed = (jjPlayers[playerID].yPos < int(snowball.yPos) - 64)? -4:-2;
snowball.direction = obj.direction;
snowball.counterEnd = 140;
snowball.animSpeed = 1;
snowball.light = 8;
snowball.state = STATE::FLY;
snowball.killAnim = jjObjectPresets[OBJECT::ICEBULLET].killAnim;
obj.delete();
}*/
}
}
//7 Muttshroom
class Muttshroom : Enemy {
uint firstMushroomFrame;
Muttshroom(jjOBJ@ preset) {
super(preset, AnimSets::Muttshroom);
firstMushroomFrame = jjAnimations[jjAnimSets[animSetID]];
for (uint animID = 0; animID < 2; ++animID) {
const jjANIMATION@ anim = jjAnimations[jjAnimSets[ANIM::DOG] + animID];
Recolor(anim, array<uint8>={64}, RecolorReplace(24)); //brown to red
Recolor(anim, array<uint8>={40}, RecolorReplace(62)); //orange to brown
}
if (true) {
for (uint i = 0; i < 5; ++i) { //copter
jjANIMFRAME@ frame = jjAnimFrames[firstMushroomFrame + 5 + i];
jjPIXELMAP image(frame);
for (int x = image.width - 1; x >= 0; --x)
for (int y = image.height - 1; y >= 0; --y)
if (image[x,y] != 0)
image[x,y] += 16;
image.save(frame);
}
}
}
void onBehave(jjOBJ@ obj) override {
switch (obj.state) {
case STATE::ROTATE:
obj.counter += 4;
obj.xPos = obj.xOrg + jjSin(obj.counter) * 48;
obj.yPos = obj.yOrg + jjCos(obj.counter) * 48;
obj.direction = (obj.counter - 256) & 1023 < 512 ? 1 : -1;
if (Player.blink == 0 && jjAnimFrames[Player.curFrame].doesCollide(int(Player.xPos), int(Player.yPos), Player.direction, jjAnimFrames[firstMushroomFrame+5], int(obj.xPos), int(obj.yPos), 1)) //player collides with copter
Player.hurt(2);
if (obj.counterEnd == 0)
obj.counterEnd = jjRandom() & 63;
else if ((++obj.counterEnd > 200 || IsMurderer) && jjGameTicks & 1 == 1)
jjAddObject(obj.eventID, obj.xPos, obj.yPos+20, obj.creatorID); //spores
break;
case STATE::FLY: //copter part
if ((obj.counterEnd -= 3) < 30)
obj.delete();
else {
obj.xPos += obj.xSpeed *= 0.97;
obj.yPos += obj.ySpeed *= 0.97;
}
break;
case STATE::START:
if (obj.creatorType == CREATOR::LEVEL) {
if (getParameterAtOrigin(obj, 0, 1) == 1) {
obj.state = STATE::ROTATE;
obj.curFrame = firstMushroomFrame + 3;
obj.energy = 3;
break;
}
obj.energy = 2;
} else {
obj.state = STATE::FLY;
obj.curFrame = firstMushroomFrame + 4;
obj.counterEnd = 255;
const auto angle = jjRandom();
obj.xSpeed = jjSin(angle) * 4;
obj.ySpeed = jjCos(angle) * 4;
obj.isTarget = false;
obj.isFreezable = false;
break;
}
default:
obj.behave(BEHAVIOR::DOGGYDOGG, false);
if (obj.state == STATE::WALK && (jjRandom() & 127) == 0) {
jjSample(obj.xPos, obj.yPos, SOUND::DOG_AGRESSIV);
obj.var[0] = -1;
obj.var[1] = 255;
obj.counter=jjGameTicks&7; //aligning anim
obj.state = STATE::ACTION;
}
}
}
void onDraw(jjOBJ@ obj) override {
if (obj.state == STATE::FLY) {
const float scale = obj.counterEnd / 255.f;
jjDrawResizedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, scale,scale, SPRITE::ALPHAMAP, 42);
} else {
if (obj.curFrame > firstMushroomFrame) {
const auto realDirection = obj.direction;
obj.direction = 1;
drawMainSprite(obj, firstMushroomFrame + 5 + ((obj.counter >> 3) % 5));
obj.direction = realDirection;
}
Enemy::onDraw(obj);
if (obj.curFrame < firstMushroomFrame) {
const uint frameID = (jjGameTicks >> 2) & 3;
const uint mushroomFrame = firstMushroomFrame + (obj.state == STATE::DONE ? 0 : (frameID < 3 ? frameID : 1));
drawBodySprite(obj, obj.xPos + obj.direction * 13, obj.yPos + (obj.curAnim == jjObjectPresets[obj.eventID].curAnim ? 2 : -5), mushroomFrame);
drawBodySprite(obj, obj.xPos - obj.direction * 18, obj.yPos - 6, mushroomFrame, 128 * obj.direction, 0.5f);
}
}
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
if (obj.state != STATE::FLY || (bullet is null && obj.counterEnd > 50)) {
if (jjColorDepth >= 16 && player.shieldTime <= 0) {
if (Player.stoned <= 0)
jjSample(Player.xPos, Player.yPos, SOUND::CATERPIL_RIDOE);
if (Player.stoned < 15)
Player.stoned = IsMurderer ? 140 : 55;
}
}
if (obj.state != STATE::FLY) {
const bool isFlier = obj.state == STATE::ROTATE;
Enemy::onObjectHit(obj,bullet,player,force);
if (!obj.isActive && isFlier) { //murdered
jjObjects[jjAddObject(OBJECT::SHARD, obj.xPos, obj.yPos)].curAnim = jjAnimSets[animSetID] + 1; //lose copter as particle
}
} else if (bullet is null)
obj.delete();
return true;
}
}
//8 Trogdor
class Trogdor : Enemy {
Trogdor(jjOBJ@ preset) {
super(preset, AnimSets::Trogdor);
preset.energy = 3;
preset.curAnim = jjAnimSets[animSetID];
preset.determineCurFrame();
preset.xSpeed = 0.7;
gender = Gender::Male;
}
void onBehave(jjOBJ@ obj) override {
obj.behave(BEHAVIOR::WALKINGENEMY, false);
if (obj.state != STATE::FREEZE && obj.state != STATE::DONE && (jjGameTicks & 3) == 3 && (IsMurderer || (obj.frameID % 7) == 3)) {
jjSample(obj.xPos, obj.yPos, SOUND::AMMO_FIREGUN2A);
jjOBJ@ bullet = jjObjects[obj.fireBullet(OBJECT::TOASTERBULLET)];
bullet.xPos += 40 * obj.direction;
bullet.yPos += 20;
bullet.killAnim = bullet.determineCurAnim(ANIM::AMMO, 0, false);
bullet.playerHandling = HANDLING::ENEMYBULLET;
bullet.state = STATE::FLY;
bullet.animSpeed = 1;
bullet.xSpeed /= 4;
bullet.xAcc /= 3;
bullet.yAcc = (int(jjRandom() & 3) - 1) / 64.f; //replicates normal spreading behavior for player-fired toaster
}
}
}
//9 Frogs
const uint FrogDistanceFromGround = 18;
class Frog : Enemy {
Frog(jjOBJ@ preset) {
super(preset);
preset.energy = 2;
preset.determineCurAnim(ANIM::FROG, 2);
preset.determineCurFrame();
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
Enemy::onObjectHit(obj,bullet,player,force);
if (obj.energy <= 0) {
for (uint i = jjObjectCount; --i > 0;) {
jjOBJ@ obj2 = jjObjects[i];
if (obj2.eventID == OBJECT::DRAGONFLY && obj2.isActive && obj2.state != STATE::DONE && obj2.creatorID == uint(obj.objectID))
obj2.state = STATE::ATTACK;
}
}
return true;
}
void onBehave(jjOBJ@ obj) override {
if (obj.state == STATE::START) {
obj.state = STATE::IDLE;
obj.yPos += jjMaskedTopVLine(int(obj.xPos), int(obj.yPos + FrogDistanceFromGround), 200);
for (uint i = 0; i < 3; ++i)
jjAddObject(OBJECT::DRAGONFLY, obj.xPos, obj.yPos, obj.objectID);
} else if (obj.state == STATE::DEACTIVATE) {
for (uint i = jjObjectCount; --i > 0;) {
jjOBJ@ obj2 = jjObjects[i];
if (obj2.eventID == OBJECT::DRAGONFLY && obj2.isActive && obj2.state != STATE::DONE && obj2.creatorID == uint(obj.objectID)) {
obj2.delete();
}
}
if (obj.var[0] == 0)
obj.deactivate();
else {
obj.delete();
removeFromEventMap(obj);
}
} else if (obj.state == STATE::FREEZE)
obj.behave(BEHAVIOR::WALKINGENEMY, false);
else if (obj.state != STATE::DONE) {
const float delta = abs(obj.xPos - Player.xPos);
if (delta > 16)
obj.direction = (obj.xPos > Player.xPos) ? -1 : 1;
if (obj.state != STATE::JUMP && obj.state != STATE::FALL && !jjMaskedHLine(int(obj.xPos) - 12, 24, int(obj.yPos) + FrogDistanceFromGround)) {
obj.determineCurAnim(ANIM::FROG, 0);
obj.state = STATE::FALL;
obj.ySpeed = 0;
} else if (obj.state == STATE::FIRE) {
if (++obj.counter >= 14*5) {
obj.determineCurAnim(ANIM::FROG, 2);
obj.state = STATE::IDLE;
obj.counter = 0;
}
} else if (obj.state == STATE::IDLE) {
if (++obj.counter > 80) {
if (delta > 32 && delta < 256 && abs(obj.yPos - Player.yPos) < 192) {
if (Player.direction == obj.direction) { //looking away from me
obj.determineCurAnim(ANIM::FROG, jjIsTSF ? 12 : 11);
obj.state = STATE::WALK;
obj.xSpeed = 1.5;
} else { //looking at me
obj.determineCurAnim(ANIM::FROG, 5);
obj.state = STATE::JUMP;
obj.ySpeed = -3.75;
obj.xSpeed = -2.3;
jjSample(obj.xPos, obj.yPos, SOUND::COMMON_JUMP);
}
} else if (obj.counter >= 100) {
obj.counter = 0;
obj.state = STATE::FIRE;
obj.determineCurAnim(ANIM::FROG, (jjIsTSF ? 8 : 7) + (jjRandom() % 3));
jjSample(obj.xPos, obj.yPos, SOUND::FROG_TONG);
}
}
} else {
if (!jjMaskedVLine(int(obj.xPos) + (obj.xSpeed >= 0 ? 19 : -19) * obj.direction, int(obj.yPos) - 10, 4 + FrogDistanceFromGround))
obj.xPos += obj.xSpeed * obj.direction;
else if (obj.state == STATE::WALK) {
obj.determineCurAnim(ANIM::FROG, 2);
obj.state = STATE::IDLE;
obj.counter = 0;
}
if (obj.state != STATE::WALK && obj.state != STATE::IDLE) {
if ((obj.ySpeed += 0.25) >= 0 && obj.state == STATE::JUMP) {
obj.determineCurAnim(ANIM::FROG, 0);
obj.state = STATE::FALL;
} else if (jjMaskedHLine(int(obj.xPos) - 11, 22, int(obj.yPos += obj.ySpeed) + FrogDistanceFromGround)) {
if (obj.xSpeed != 1.5) {
if (obj.state == STATE::FALL) {
obj.determineCurAnim(ANIM::FROG, 2);
obj.counter = 30;
obj.yPos -= FrogDistanceFromGround * 2;
obj.yPos += jjMaskedTopVLine(int(obj.xPos), int(obj.yPos + FrogDistanceFromGround), 200);
obj.state = STATE::IDLE;
}
} else {
obj.determineCurAnim(ANIM::FROG, jjIsTSF ? 12 : 11);
obj.state = STATE::WALK;
}
}
} else if (delta <= 16) {
obj.determineCurAnim(ANIM::FROG, 2);
obj.state = STATE::IDLE;
obj.counter = 0;
obj.xSpeed = 0;
}
}
obj.frameID = (obj.state != STATE::FIRE ? jjGameTicks : obj.counter) / 5 ;
obj.determineCurFrame();
}
}
}
class FrogFly : Enemy {
FrogFly(jjOBJ@ preset) {
super(preset);
preset.energy = 1;
preset.deactivates = false;
preset.determineCurAnim(ANIM::DRAGFLY, 0);
}
void onBehave(jjOBJ@ obj) override {
switch (obj.state) {
case STATE::START:
if (obj.creatorType == CREATOR::OBJECT) {
obj.state = STATE::CIRCLE;
obj.counter = jjRandom();
} else {
jjDebug("something went wrong with a frogfly :(");
obj.delete();
}
break;
case STATE::DEACTIVATE:
case STATE::FREEZE:
obj.behave(BEHAVIOR::DRAGONFLY, false);
return;
case STATE::CIRCLE: {
obj.counter += 5 + (jjRandom() & 7);
const float targetX = jjObjects[obj.creatorID].xPos + jjSin(obj.counter) * 40, targetY = jjObjects[obj.creatorID].yPos - 45 + jjCos(obj.counter) * 15;
if (obj.xPos > targetX + 3) {
obj.xPos -= 1;
obj.direction = -1;
} else if (obj.xPos < targetX - 3) {
obj.xPos += 1;
obj.direction = 1;
}
if (obj.yPos > targetY + 3) {
obj.yPos -= 1;
} else if (obj.yPos < targetY - 3) {
obj.yPos += 1;
}
break; }
case STATE::ATTACK:
obj.counter += 14;
if ((obj.counterEnd += 2) == 20) {
obj.special = GetAngle(Player.yPos - obj.yPos, obj.xPos - Player.xPos);
obj.deactivates = true;
obj.direction = jjSin(obj.special) >= 0 ? 1 : -1;
} else if (obj.counterEnd > 70) {
obj.xPos += jjSin(obj.special) * 6;
obj.yPos += jjCos(obj.special) * 6;
}
break;
default:
return;
}
obj.frameID = obj.counter >> 4;
obj.determineCurFrame();
//obj.doesHurt = jjSampleLooped(obj.xPos,obj.yPos,SOUND::DRAGFLY_BEELOOP,obj.doesHurt, obj.state == STATE::ATTACK ? 63 : 20);
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
Enemy::onObjectHit(obj,bullet,player,force);
if (obj.energy <= 0) {
jjObjects[obj.creatorID].var[0] = 1; //has lost a fly; should be deleted instead of deactivated, to avoid letting the player kill more enemies than exist.
}
return true;
}
}
//10 Bilsy
class MiniBilsy : Enemy {
MiniBilsy(jjOBJ@ preset) {
super(preset);
preset.energy = 4;
preset.isFreezable = false;
preset.isTarget = false;
gender = Gender::Male;
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
if (obj.state == STATE::DELAYEDSTART) {
if (bullet is null) {
obj.isFreezable = true;
obj.isTarget = true;
obj.state = STATE::FADEIN;
obj.determineCurAnim(ANIM::BILSBOSS, 1);
obj.determineCurFrame();
obj.putOnGround(true);
obj.direction = player.direction;
obj.xPos -= obj.direction * 96;
jjSample(obj.xPos, obj.yPos, SOUND::BILSBOSS_THUNDER);
}
} else
Enemy::onObjectHit(obj,bullet,player,force);
return true;
}
void onBehave(jjOBJ@ obj) override {
switch (obj.state) {
case STATE::START:
obj.state = STATE::DELAYEDSTART;
break;
case STATE::FREEZE:
case STATE::DEACTIVATE:
obj.behave(BEHAVIOR::TUBETURTLE);
break;
case STATE::IDLE:
obj.frameID = (jjGameTicks >> 2) & 7;
obj.determineCurFrame();
switch (jjRandom() & 127) {
case 0:
obj.state = STATE::ATTACK;
obj.determineCurAnim(ANIM::BILSBOSS, 0);
obj.counter = 0;
break;
case 1: //teleport
case 2:
case 3:
obj.state = STATE::FADEOUT;
obj.determineCurAnim(ANIM::BILSBOSS, 2);
obj.counter = 0;
if (IsMurderer) { //invincible while transportalizing
obj.playerHandling = HANDLING::PARTICLE;
} {
obj.xAcc = Player.xPos;
obj.yAcc = Player.yPos;
}
break;
}
break;
case STATE::FADEOUT:
if (++obj.counter < 68) {
obj.frameID = obj.counter >> 2;
obj.determineCurFrame();
} else {
obj.frameID = obj.counter = 0;
obj.state = STATE::FADEIN;
jjSample(obj.xPos, obj.yPos, SOUND::BILSBOSS_BILLAPPEAR);
obj.determineCurAnim(ANIM::BILSBOSS, 1);
obj.xPos = obj.xAcc;
obj.yPos = obj.yAcc - 32;
obj.direction = (Player.xPos < obj.xPos) ? -1 : 1;
obj.determineCurFrame();
obj.putOnGround(true);
return;
}
break;
case STATE::FADEIN:
if (++obj.counter < 40) {
obj.frameID = obj.counter >> 1;
obj.determineCurFrame();
} else {
obj.frameID = obj.counter = 0;
obj.determineCurAnim(ANIM::BILSBOSS, 4);
obj.state = STATE::IDLE;
obj.playerHandling = HANDLING::SPECIAL;
}
break;
case STATE::ATTACK:
if (++obj.counter < 36) {
obj.frameID = obj.counter >> 1;
obj.determineCurFrame();
if (obj.counter == 32) {
jjOBJ@ bullet = jjObjects[obj.fireBullet(OBJECT::FIREBALLBULLET)];
bullet.direction = obj.direction;
bullet.xAcc = abs(bullet.xAcc) * bullet.direction;
bullet.xSpeed = abs(bullet.xSpeed) * bullet.direction / 3;
bullet.state = STATE::FLY;
bullet.playerHandling = HANDLING::ENEMYBULLET;
bullet.animSpeed = IsMurderer ? 2 : 1;
bullet.determineCurAnim(ANIM::BILSBOSS, 3);
}
} else {
obj.frameID = obj.counter = 0;
obj.determineCurAnim(ANIM::BILSBOSS, 4);
obj.state = STATE::IDLE;
}
break;
}
}
void onDraw(jjOBJ@ obj) override {
if (obj.state != STATE::DELAYEDSTART)
Enemy::onDraw(obj);
//else //just for testing
// jjDrawSpriteFromCurFrame(obj.xPos,obj.yPos,obj.curFrame,obj.direction,SPRITE::TRANSLUCENT);
}
}
//11 Phoenix
class Phoenix : Enemy {
Phoenix(jjOBJ@ preset) {
super(preset);
preset.energy = 3;
gender = Gender::Male;
for (uint i = 0; i < 3; ++i) {
Recolor(jjAnimations[jjAnimSets[ANIM::RAVEN] + i], array<uint8>={24}, RecolorReplace(88));
Recolor(jjAnimations[jjAnimSets[ANIM::RAVEN] + i], array<uint8>={72}, array<uint8>={0,0,0,0,46,44,42,41});
}
}
void onBehave(jjOBJ@ obj) override {
if (obj.state != STATE::DONE) {
const auto oldDirection = obj.direction;
for (uint i = (obj.state == STATE::ATTACK) ? 3 : IsMurderer ? 2 : 1; i > 0; --i)
obj.behave(BEHAVIOR::RAVEN, false);
if (oldDirection != obj.direction) {
jjSample(obj.xPos, obj.yPos, SOUND::AMMO_FIREGUN2A);
obj.direction *= -1;
for (int i = 0; i < 3; ++i) {
jjOBJ@ bullet = jjObjects[jjAddObject(OBJECT::TOASTERBULLET, obj.xPos + i * 15 * oldDirection, obj.yPos + (i-1) * 10)];
bullet.playerHandling = HANDLING::ENEMYBULLET;
bullet.state = STATE::FLY;
bullet.animSpeed = 1;
bullet.xSpeed /= 3;
bullet.xAcc /= 2;
bullet.yAcc = (int(jjRandom() & 3) - 1) / 64.f; //replicates normal spreading behavior for player-fired toaster
}
obj.direction *= -1;
}
if (obj.state == STATE::STOP)
obj.state = STATE::IDLE; //never return to origin place
}
}
void onDraw(jjOBJ@ obj) override {
if (obj.state != STATE::ATTACK)
Enemy::onDraw(obj);
else
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::SINGLEHUE, 40);
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) override {
if (obj.state == STATE::ATTACK) {
if (bullet is null)
player.hurt(IsMurderer ? 2 : 1);
return true;
}
return Enemy::onObjectHit(obj,bullet,player,force);
}
}
class Demon : Enemy {
Demon(jjOBJ@ preset) {
super(preset);
for (uint i = 0; i < 4; ++i) {
Recolor(jjAnimations[jjAnimSets[ANIM::DEMON] + i], array<uint8>={24}, RecolorReplace(48));
Recolor(jjAnimations[jjAnimSets[ANIM::DEMON] + i], array<uint8>={64,80}, RecolorReplace(72));
}
}
void onBehave(jjOBJ@ obj) override {
obj.special = (obj.state == STATE::IDLE) ? obj.counter : 0;
if (obj.state != STATE::DONE) {
if (obj.state == STATE::START) {
obj.isBlastable = getParameterAtOrigin(obj, -2, 1) == 1;
obj.lightType = LIGHT::NONE;
}
if (obj.special > 140) obj.special = 140;
else if (obj.state == STATE::WALK && abs(obj.direction) == 1)
obj.direction *= 2;
if (!obj.isBlastable) { //rightside up
obj.behave(BEHAVIOR::DEMON, false);
} else { //upside down
obj.yPos -= 64;
Player.yPos -= 64;
obj.behave(BEHAVIOR::DEMON, false);
obj.yPos += 64;
Player.yPos += 64;
}
if (obj.special != 0 && obj.state == STATE::ATTACK) {
FireLoop(obj, true);
}
}
}
void onDraw(jjOBJ@ obj) override {
if (obj.isBlastable) obj.direction ^= SPRITE::FLIPV;
Enemy::onDraw(obj);
if (obj.isBlastable) obj.direction ^= SPRITE::FLIPV;
FireLoop(obj, false);
}
void FireLoop(const jjOBJ@ obj, bool attack) const {
const int fireFrame = jjAnimations[jjAnimSets[ANIM::AMMO] + 13] + ((jjGameTicks >> 2) % 7);
for (int i = 0; i + 16 < obj.special; i += (IsMurderer ? 16 : 32)) {
const int angle = (i + jjGameTicks) << 3;
const int distance = (200 - obj.special) / 2;
const float xPos = obj.xPos + jjSin(angle) * distance;
const float yPos = obj.yPos + jjCos(angle) * distance;
if (attack) {
jjOBJ@ spark = jjObjects[jjAddObject(OBJECT::SHARD, xPos, yPos, obj.objectID)];
spark.determineCurAnim(ANIM::AMMO, 13);
spark.animSpeed = 1;
spark.playerHandling = HANDLING::ENEMYBULLET;
spark.xSpeed /= 2;
spark.lightType = LIGHT::RING2;
spark.light = 2;
} else {
jjDrawSpriteFromCurFrame(xPos, yPos, fireFrame);
if (jjAnimFrames[Player.curFrame].doesCollide(int(Player.xPos), int(Player.yPos), Player.direction, jjAnimFrames[fireFrame], int(xPos), int(yPos), 1))
Player.hurt(IsMurderer ? 2 : 1);
for (uint j = jjObjectCount; --j != 0;) {
jjOBJ@ bullet = jjObjects[j];
if (bullet.playerHandling == HANDLING::PLAYERBULLET && bullet.freeze == 0 && bullet.isActive && jjAnimFrames[bullet.curFrame].doesCollide(int(bullet.xPos), int(bullet.yPos), bullet.direction, jjAnimFrames[fireFrame], int(xPos), int(yPos), 1)) {
bullet.particlePixelExplosion(1);
bullet.delete();
}
}
}
}
}
}
class Duet : Enemy {
Duet(jjOBJ@ preset) {
super(preset, AnimSets::Duet);
Layer = 3;
preset.isFreezable = false; //splitness
preset.special = jjAnimations[jjAnimSets[animSetID]];
preset.lightType = LIGHT::BRIGHT;
preset.light = 16;
preset.direction = 1;
}
void onBehave(jjOBJ@ obj) override {
if (obj.state == STATE::DONE)
return;
if (obj.state == STATE::START || obj.state == STATE::FREEZE || obj.state == STATE::DEACTIVATE) {
if (obj.state == STATE::START)
obj.xAcc = float(jjRandom() & 1023);
obj.behave(BEHAVIOR::SPARK, false);
return;
} //else...
obj.behave(BEHAVIOR::SPARK, false);
if (!obj.isFreezable) { //not yet split
obj.doesHurt = jjSampleLooped(obj.xPos,obj.yPos,SOUND::COMMON_SHIELD_ELEC,obj.doesHurt);
obj.xAcc += (obj.xSpeed) * (IsMurderer ? 7 : 5) + obj.direction; //angle
const int angle = int(obj.xAcc);
for (int i = 0; i < 1024; i += 512) {
const float xPos = obj.xPos + jjSin(angle+i) * 50, yPos = obj.yPos + jjCos(angle+i) * 50;
const jjANIMFRAME@ frame = jjAnimFrames[obj.curFrame];
if (jjAnimFrames[Player.curFrame].doesCollide(int(Player.xPos), int(Player.yPos), Player.direction, frame, int(xPos), int(yPos), 1)) {
const auto force = Player.getObjectHitForce();
if (force == 0)
Player.hurt(1);
else
split(obj, xPos, yPos, null, force);
}
for (uint j = jjObjectCount; --j != 0;) {
jjOBJ@ bullet = jjObjects[j];
if (bullet.playerHandling == HANDLING::PLAYERBULLET && bullet.freeze == 0 && bullet.isActive && jjAnimFrames[bullet.curFrame].doesCollide(int(bullet.xPos), int(bullet.yPos), bullet.direction, frame, int(xPos), int(yPos), 1)) {
split(obj, xPos, yPos, bullet, 0);
}
}
if (obj.isFreezable) { //got split
i ^= 512;
obj.xPos += jjSin(angle+i) * 50;
obj.yPos += jjCos(angle+i) * 50;
obj.lightType = LIGHT::NONE;
break;
}
}
[preview ends here]
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.