Name | Author | Game Mode | Rating | |||||
---|---|---|---|---|---|---|---|---|
Holiday Hare '17 | ShadowGPW | Single player | 8.8 | |||||
Foo Single Player 2/14:... | Violet CLM | Single player | 10 | |||||
Kangaroo | Violet CLM | Single player | 9.1 |
#pragma require "kangaroo.j2a"
/*
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 {
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)
);
}
}
}
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.