| Name | Author | Game Mode | Rating | |||||
|---|---|---|---|---|---|---|---|---|
| Commander Keen | Lark | Mutator | N/A | |||||
#pragma name "Commander Keen"
#pragma description "Replaces Jazz and Spaz with Commander Keen while preserving their gameplay."
// Source sheet: Commander Keen Sprite Sheet by Yorpy, supplied by the user.
#pragma require "KeenSprites.png"
namespace KeenJazz {
const string SPRITE_FILE = "KeenSprites.png";
const float SIZE_MULTIPLIER = 1.0f;
const uint CELL_SIZE = 128;
const uint ROW_COUNT = 26;
const float FOOT_Y = 48.0f; // atlas baseline 112 minus cell center 64
const uint SPIN_ANGLE_STEP = 64; // one full turn every 16 ticks
const uint RUN_FRAME_TICKS = 5; // hold each running pose briefly
enum Pose {
STAND, RUN, CROUCH, SHOOT, SHOOT_FLASH, AIM_UP, JUMP, FALL,
HURT, DIE, CORPSE, SKID, IDLE_SHRUG, IDLE_BOOK, DIZZY,
POLE, HOVER, AIRBOARD, HANG_RIGHT, HANG_LEFT, SWIM_RIGHT,
SWIM_LEFT, SPIN, POGO, BLASTER, BLASTER_FADE
}
uint firstAnim = 0;
int customSlot = -1;
float sizeScale = 1.0f;
float jazzFoot = 0.0f;
float spazFoot = 0.0f;
bool attemptedLoad = false;
void load() {
if (attemptedLoad) return;
attemptedLoad = true;
jjCOLORMAP sheet(SPRITE_FILE);
if (sheet.width != CELL_SIZE * 12 || sheet.height != CELL_SIZE * ROW_COUNT) {
jjPrint("Commander Keen: missing or incorrect KeenSprites.png. Copy it beside Jazz2.exe.");
return;
}
int slot = -1;
for (int i = 0; i < 256; ++i) {
if (jjAnimSets[ANIM::CUSTOM[i]].firstAnim == 0) {
slot = i;
break;
}
}
if (slot < 0) {
jjPrint("Commander Keen: an unused custom animation slot is required.");
return;
}
if (jjAnimSets[ANIM::JAZZ].firstAnim == 0)
jjAnimSets[ANIM::JAZZ].load();
if (jjAnimSets[ANIM::SPAZ].firstAnim == 0)
jjAnimSets[ANIM::SPAZ].load();
jjAnimSets[ANIM::CUSTOM[slot]].load(sheet, CELL_SIZE, CELL_SIZE);
firstAnim = jjAnimSets[ANIM::CUSTOM[slot]].firstAnim;
if (firstAnim == 0) return;
customSlot = slot;
const uint jazzStand = jjAnimations[
jjAnimSets[ANIM::JAZZ].firstAnim + RABBIT::STAND
].firstFrame;
const uint spazStand = jjAnimations[
jjAnimSets[ANIM::SPAZ].firstAnim + RABBIT::STAND
].firstFrame;
const uint keenStand = jjAnimations[firstAnim + STAND].firstFrame;
sizeScale = float(jjAnimFrames[jazzStand].height)
/ float(jjAnimFrames[keenStand].height);
jazzFoot = float(int(jjAnimFrames[jazzStand].hotSpotY)
- int(jjAnimFrames[jazzStand].coldSpotY));
spazFoot = float(int(jjAnimFrames[spazStand].hotSpotY)
- int(jjAnimFrames[spazStand].coldSpotY));
for (uint animationID = 0; animationID < ROW_COUNT; ++animationID) {
jjANIMATION@ animation = jjAnimations[firstAnim + animationID];
for (uint frameID = 0; frameID < animation.frameCount; ++frameID) {
jjANIMFRAME@ frame = jjAnimFrames[animation.firstFrame + frameID];
frame.coldSpotY = int16(int(frame.hotSpotY) - int(FOOT_Y));
}
}
}
void reload() {
if (customSlot >= 0 && firstAnim != 0
&& jjAnimSets[ANIM::CUSTOM[customSlot]].firstAnim == firstAnim) return;
firstAnim = 0;
customSlot = -1;
attemptedLoad = false;
load();
}
class BlasterVisual : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
obj.behave(BEHAVIOR::BULLET, false);
if (!obj.isActive) return;
if (obj.state == STATE::EXPLODE) {
obj.draw();
return;
}
const jjANIMATION@ animation = jjAnimations[firstAnim + BLASTER];
const uint frame = (uint(jjGameTicks) / 4) % animation.frameCount;
const uint originalFrame = obj.curFrame;
obj.curFrame = animation.firstFrame + frame;
obj.draw();
obj.curFrame = originalFrame;
}
}
void setupBlaster() {
if (firstAnim == 0) return;
jjObjectPresets[OBJECT::BLASTERBULLET].killAnim = firstAnim + BLASTER_FADE;
jjObjectPresets[OBJECT::BLASTERBULLETPU].killAnim = firstAnim + BLASTER_FADE;
jjObjectPresets[OBJECT::BLASTERBULLET].behavior = BlasterVisual();
jjObjectPresets[OBJECT::BLASTERBULLETPU].behavior = BlasterVisual();
}
uint choose(int rabbitAnim) {
switch (rabbitAnim) {
case RABBIT::STAND:
return STAND;
case RABBIT::PUSH:
case RABBIT::REV1:
case RABBIT::REV2:
case RABBIT::REV3:
case RABBIT::RUN1:
case RABBIT::RUN2:
case RABBIT::RUN3:
return RUN;
case RABBIT::SKID1:
case RABBIT::SKID2:
case RABBIT::SKID3:
return SKID;
case RABBIT::DIVE:
case RABBIT::DIVEFIREQUIT:
case RABBIT::DIVEFIRERIGHT:
case RABBIT::DIVEUP:
case RABBIT::BUTTSTOMPLAND:
case RABBIT::FALLLAND:
case RABBIT::LIFTLAND:
return CROUCH;
case RABBIT::FIRE:
case RABBIT::QUIT:
case RABBIT::JUMPFIRERIGHT:
return SHOOT;
case RABBIT::FIREUP:
case RABBIT::FIREUPQUIT:
case RABBIT::JUMPFIREQUIT:
case RABBIT::LOOKUP:
case RABBIT::LIFT:
return AIM_UP;
case RABBIT::JUMPING1:
case RABBIT::JUMPING2:
case RABBIT::JUMPING3:
case RABBIT::RIGHTJUMP:
case RABBIT::RIGHTFALL:
case RABBIT::ROLLING:
case RABBIT::LIFTJUMP:
return JUMP;
case RABBIT::STATIONARYJUMPSTART:
case RABBIT::STATIONARYJUMP:
case RABBIT::STATIONARYJUMPEND:
return POGO;
case RABBIT::FALLBUTTSTOMP:
return POGO;
case RABBIT::SPRING:
return POGO;
case RABBIT::FALL:
return FALL;
case RABBIT::AIRBOARD:
case RABBIT::AIRBOARDTURN:
return AIRBOARD;
case RABBIT::EARBRACHIATE:
case RABBIT::HANGFIREQUIT:
case RABBIT::HANGFIREREST:
case RABBIT::HANGFIREUP:
case RABBIT::HANGIDLE1:
case RABBIT::HANGIDLE2:
case RABBIT::HANGINGFIREQUIT:
case RABBIT::HANGINGFIRERIGHT:
case RABBIT::SWINGINGVINE:
return HANG_RIGHT;
case RABBIT::SWIMDOWN:
case RABBIT::SWIMRIGHT:
case RABBIT::SWIMTURN1:
case RABBIT::SWIMTURN2:
case RABBIT::SWIMUP:
return SWIM_RIGHT;
case RABBIT::HPOLE:
case RABBIT::VPOLE:
return POLE;
case RABBIT::HURT:
return HURT;
case RABBIT::DIE:
return DIE;
case RABBIT::CORPSE:
return CORPSE;
case RABBIT::LOOPY:
case RABBIT::STONED:
return DIZZY;
case RABBIT::HELICOPTER:
case RABBIT::HELICOPTERFIREQUIT:
case RABBIT::HELICOPTERFIRERIGHT:
case RABBIT::FROG:
return HOVER;
case RABBIT::TELEPORT:
case RABBIT::TELEPORTFALL:
case RABBIT::TELEPORTFALLING:
case RABBIT::TELEPORTFALLTELEPORT:
case RABBIT::TELEPORTSTAND:
return IDLE_BOOK;
case RABBIT::ENDOFLEVEL:
case RABBIT::IDLE1:
case RABBIT::IDLE3:
return IDLE_SHRUG;
case RABBIT::LEDGEWIGGLE:
return STAND;
case RABBIT::IDLE2:
case RABBIT::IDLE4:
case RABBIT::IDLE5:
return IDLE_BOOK;
}
return STAND;
}
void draw(jjPLAYERDRAW &inout drawing) {
jjPLAYER@ player = drawing.player;
if (player is null) return;
if (player.charCurr != CHAR::JAZZ && player.charCurr != CHAR::SPAZ) return;
load();
if (firstAnim == 0) return;
const uint rabbitBase = jjAnimSets[player.setID].firstAnim;
if (rabbitBase == 0 || player.curAnim < rabbitBase) return;
const int rabbitAnim = int(player.curAnim) - int(rabbitBase);
uint pose = choose(rabbitAnim);
if (pose == SHOOT && drawing.gunFlash)
pose = SHOOT_FLASH;
bool faceLeft = player.direction < 0;
if (player.xSpeed < -0.25f)
faceLeft = true;
else if (player.xSpeed > 0.25f)
faceLeft = false;
if (pose == HANG_RIGHT) {
if (player.xSpeed < 0.0f)
faceLeft = true;
else if (player.xSpeed > 0.0f)
faceLeft = false;
if (faceLeft)
pose = HANG_LEFT;
}
if (pose == SWIM_RIGHT) {
if (player.xSpeed < 0.0f)
faceLeft = true;
else if (player.xSpeed > 0.0f)
faceLeft = false;
if (faceLeft)
pose = SWIM_LEFT;
}
jjANIMATION@ target = jjAnimations[firstAnim + pose];
if (target.frameCount == 0) return;
const uint sourceCount = jjAnimations[player.curAnim].frameCount;
uint sourceFrame = player.frameID;
uint frame = 0;
if (sourceCount > 1) {
if (sourceFrame >= sourceCount) sourceFrame = sourceCount - 1;
if (pose == DIE || pose == SKID)
frame = sourceFrame * (target.frameCount - 1) / (sourceCount - 1);
else
frame = sourceFrame * target.frameCount / sourceCount;
}
if (pose == RUN)
frame = (uint(jjGameTicks) / RUN_FRAME_TICKS) % target.frameCount;
const float scale = sizeScale * SIZE_MULTIPLIER;
const float stableFoot = player.charCurr == CHAR::SPAZ ? spazFoot : jazzFoot;
const bool spinning = pose == SPIN;
if (spinning)
drawing.angle = int((uint(jjGameTicks) * SPIN_ANGLE_STEP) & 1023);
if (!spinning && drawing.angle == 0)
drawing.yOffset += (stableFoot - FOOT_Y * scale) * drawing.yScale;
drawing.curFrame = target.firstFrame + frame;
float xMagnitude = drawing.xScale;
if (xMagnitude < 0) xMagnitude = -xMagnitude;
drawing.xScale = (faceLeft ? -xMagnitude : xMagnitude) * scale;
drawing.yScale *= scale;
if (drawing.spriteMode == SPRITE::PLAYER) {
drawing.spriteMode = SPRITE::NORMAL;
drawing.spriteParam = 0;
} else if (drawing.spriteMode == SPRITE::TRANSLUCENTPLAYER) {
drawing.spriteMode = SPRITE::TRANSLUCENT;
drawing.spriteParam = 0;
}
drawing.gunFlash = false;
drawing.trail = false;
}
}
void onLevelLoad() {
KeenJazz::load();
KeenJazz::setupBlaster();
}
void onLevelReload() {
KeenJazz::reload();
KeenJazz::setupBlaster();
}
void onPlayerDraw(jjPLAYERDRAW &inout drawing) {
KeenJazz::draw(drawing);
}
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.