| Name | Author | Game Mode | Rating | |||||
|---|---|---|---|---|---|---|---|---|
| Castle + Damn Ultimate | DoubleGJ | Tileset conversion | N/A | |||||
const bool MLLESetupSuccessful = MLLE::Setup(); ///@MLLE-Generated
#include "MLLE-Include-1.8.asc" ///@MLLE-Generated
#pragma require "CastUltEx2-MLLE-Data-1.j2l" ///@MLLE-Generated
#pragma require "CastUlt-BDay.j2t" ///@MLLE-Generated
#pragma require "DamnUlt-Normal.j2t" ///@MLLE-Generated
#pragma require "CastUltEx2.j2l" ///@MLLE-Generated
#pragma require "CastUlt-BNight.j2t"
#pragma require "CastUlt-Cracco.j2t"
#include "DD-Order.asc"
#include "DD-Rank.asc"
#pragma offer "CastleBreadMix.mp3"
#pragma offer "DD-CastleFast.it"
#pragma offer "DD-BossGBA.it"
#pragma offer "CastleCitizenMix.m4a"
#pragma offer "BEER.it"
#pragma require "DamnUlt-Cave.png"
#pragma require "DamnUlt-CaveUpscaled.png"
#pragma require "aslime2.wav"
#pragma require "chain-rattle1.wav"
#pragma require "chain-rattle2.wav"
#pragma require "squidward-walkL.wav"
#pragma require "squidward-walkR.wav"
#pragma require "blink.wav"
#pragma require "stnloop4.wav"
#pragma require "Stower4b.wav"
#pragma require "Stower51.wav"
#pragma require "bats.wav"
#pragma require "wind30.wav"
#pragma require "Bat_takeoff.ogg"
#pragma require "Bat_loop.ogg"
#pragma require "Bat_idle1.ogg"
#pragma require "Bat_idle2.ogg"
#pragma require "Bat_idle3.ogg"
#pragma require "Bat_idle4.ogg"
#pragma require "floppy-read.wav"
#pragma require "snuff-out1.wav"
#pragma require "snuff-out2.wav"
#pragma require "empty.wav"
#pragma require "common-gemsmsh1.wav"
#pragma require "Dn_battlelord_action_or_roar.wav"
#pragma require "slamD2.wav"
#pragma require "bad-landing.ogg"
#pragma require "falling-whistle-swish.ogg"
#pragma require "sword.ogg"
#pragma require "jjgba-slip.ogg"
const int bestTime = 960;
const float maxScore = 160000;
const int easyTimer = 0;
const int normalTimer = 0;
const int hardTimer = 105000;
const int turboTimer = 84000;
array<uint> fastFeetMode(4, 0);
bool noFast = true;
array<uint8> defaultModTempo = {135, 113}; // index 0 = beer, index 1 = boss
uint8 fadeParam = 255;
string storeMusic = "CastleBreadMix.mp3";
bool computerOn, craccoMode, fadeWall, NPCnear = false;
int farPlayerID, nearPlayerID, batChannel, crate1, crate2, block1, block2 = 0;
int startHP = 5;
array<uint16> spiderTileX(256, 0), spiderTileY(256, 0);
const uint8 CornerEventID = OBJECT::GUN8AMMO3; //whatever
//dragon stuff (see rest in onLevelLoad, onPlayer, and functions from dragonBoss to dragonEdit)
array<bool> dragonSpawned;
array<float> dragonCeiling;
bool dragonReveal, foughtDragon, defeatedDragon = false;
bool inScene = false;
int sceneCounter = 0;
uint8 scenePhase, saberFade, shakeStr = 0;
enum PlayerState { Normal, Loop, Ropeway };
enum LoopAngle { Floor, WallOnLeft, Ceiling, WallOnRight };
class PlayerX {
PlayerX(){}
PlayerState State = PlayerState::Normal;
LoopAngle CurrentAngle = LoopAngle::Floor, NextAngle;
float CornerXOrg, CornerYOrg, CornerSpeed, CornerElapsed = -1,/* CornerLength,*/ LastX, LastY;
//int LastLoopEventParameter = -1;
int LastTile;
int CornerAngleChange, CornerRepositioningMultiplier, CornerAngleMultiplier;
int LastHealth = -1;
}
array<PlayerX> PlayerXs(jjLocalPlayerCount);
class CornerEventResult {
LoopAngle Angle;
int XOrg, YOrg;
CornerEventResult(LoopAngle a, int x, int y) { Angle = a; XOrg = x; YOrg = y; }
}
const array<const CornerEventResult@> NextLoopAngles = {
null, CornerEventResult(WallOnRight, 0, -1), //floor, turn left, loop
null, CornerEventResult(Floor, 2, 0), //wallonleft, turn left, loop
null, CornerEventResult(WallOnLeft, 1, 2), //ceiling, turn left, loop
null, CornerEventResult(Ceiling, -1, 1), //wallonright, turn left, loop
CornerEventResult(WallOnLeft, 1, -1), null, //floor, turn right, loop
CornerEventResult(Ceiling, 2, 1), null, //wallonleft, turn right, loop
CornerEventResult(WallOnRight, 0, 2), null, //ceiling, turn right, loop
CornerEventResult(Floor, -1, 0), null, //wallonright, turn right, loop
CornerEventResult(WallOnRight, 1, 3), null, //floor, turn left, corner
CornerEventResult(Floor, -2, 1), null, //wallonleft, turn left, corner
CornerEventResult(WallOnLeft, 0, -2), null, //ceiling, turn left, corner
CornerEventResult(Ceiling, 3, 0), null, //wallonright, turn left, corner
null, CornerEventResult(WallOnLeft, 0, 3), //floor, turn right, corner
null, CornerEventResult(Ceiling, -2, 0), //wallonleft, turn right, corner
null, CornerEventResult(WallOnRight, 1, -2), //ceiling, turn right, corner
null, CornerEventResult(Floor, 3, 1) //wallonright, turn right, corner
};
void onLevelLoad() {
initiateDialogue();
initiateRanking();
initFade();
jjAnimSets[ANIM::DOG].load(); // for ambient sfx
jjAnimSets[ANIM::BONUS].load();
jjAnimSets[ANIM::CUSTOM[14]].load(14, "DD-Anims.j2a");
jjAnimSets[ANIM::CUSTOM[37]].load(37, "DD-Anims.j2a");
jjAnimSets[ANIM::ROBOT].load();
jjAnimSets[ANIM::PLUS_SCENERY].load();
jjSampleLoad(SOUND::SCIENCE_PLOPKAOS, "stnloop4.wav");
jjSampleLoad(SOUND::DOG_SNIF1, "Stower4b.wav");
jjSampleLoad(SOUND::DOG_WAF1, "Stower51.wav");
jjSampleLoad(SOUND::DOG_WAF2, "bats.wav");
jjSampleLoad(SOUND::DOG_WAF3, "wind30.wav");
jjAnimSets[ANIM::ROCK].load(4, "DD-Anims.j2a");
///@Event 39=Corner Object |+|Modifier |Corner
jjObjectPresets[CornerEventID].behavior = BEHAVIOR::INACTIVE; //zonify
///@Event 40=Loop Object |+|Modifier |Loop
jjObjectPresets[OBJECT::GUN9AMMO3].behavior = LoopObject;
jjObjectPresets[OBJECT::GUN9AMMO3].playerHandling = HANDLING::PARTICLE;
AddCornerEvents();
///@Event 41=Bomb Demon (armed) |+|Object |Demon |Bomb
///@Event 117=Bomb Demon |-|Enemy |Bomb |Demon |Pinkie:c1
jjSampleLoad(SOUND::TURTLE_SPK1TURT, "squidward-walkL.wav");
jjSampleLoad(SOUND::TURTLE_SPK2TURT, "squidward-walkR.wav");
jjSampleLoad(SOUND::TURTLE_IDLE1, "blink.wav");
jjAnimSets[ANIM::TURTLE].load(29, "DD-Anims.j2a");
jjObjectPresets[OBJECT::NORMTURTLE].behavior = bombDemon();
jjObjectPresets[OBJECT::NORMTURTLE].scriptedCollisions = true;
jjObjectPresets[OBJECT::NORMTURTLE].playerHandling = HANDLING::SPECIAL;
demonBomb(jjObjectPresets[OBJECT::TURTLESHELL]);
jjPlayers[31].furSet(88, 48, 32, 72);
jjObjectPresets[OBJECT::FIVEHUNDREDBUMP].behavior = bumpFix();
jjObjectPresets[OBJECT::FIVEHUNDREDBUMP].scriptedCollisions = true;
///@Event 186=Destruct Scenery (Bomb) |+|Trigger |Bomb |Scen |Special:2
jjObjectPresets[OBJECT::DESTRUCTSCENERYBOMB].behavior = bombScenEdit;
jjObjectPresets[OBJECT::BOMB].behavior = bombEdit;
///@Event 120=Normal Demon |-|Enemy |Norm |Demon |Pinkie:c1
jjObjectPresets[OBJECT::DEMON].behavior = demonEdit();
///@Event 125=Tuff Demon |-|Enemy |Tuff |Demon
jjAnimSets[ANIM::CUSTOM[27]].load(27, "DD-Anims.j2a");
jjObjectPresets[OBJECT::FATCHICK].energy = 5;
jjObjectPresets[OBJECT::FATCHICK].points = 500;
jjObjectPresets[OBJECT::FATCHICK].behavior = tuffDemon();
///@Event 237=Pentagram Portal |-|Enemy |Penta|-gram |Spawn:{Bomb Demon,Pink Bomb Demon,Tuff Demon,Normal Demon,Pink Demon}3 |RandomDir:c1
jjAnimSets[ANIM::CUSTOM[30]].load(30, "DD-Anims.j2a");
jjSampleLoad(SOUND::INTRO_HITSPAZ, "snuff-out1.wav");
jjSampleLoad(SOUND::INTRO_HITTURT, "snuff-out2.wav");
pentagram(jjObjectPresets[OBJECT::BEEBOY]);
///@Event 127=Imp |-|Enemy|Imp| |X Range:3|Y Range:3
jjAnimSets[ANIM::CUSTOM[26]].load(26, "DD-Anims.j2a");
jjObjectPresets[OBJECT::FISH].behavior = imp();
jjObjectPresets[OBJECT::FISH].determineCurAnim(ANIM::CUSTOM[26], 0);
jjObjectPresets[OBJECT::FISH].points = 50;
///@Event 96=Decorative Vine|+|Scenery|Decor|Vine|Length:3|Foreground:c1|XOffset:3|YOffset:3
jjSampleLoad(SOUND::INTRO_UP1, "chain-rattle1.wav");
jjSampleLoad(SOUND::INTRO_UP2, "chain-rattle2.wav");
const uint vineFrameID = jjAnimations[jjAnimSets[ANIM::CUSTOM[13]].allocate(array<uint> = {8})];
for (uint i = 0; i < 8; ++i) {
jjANIMFRAME@ vineFrame = jjAnimFrames[vineFrameID + i];
jjPIXELMAP vineImage(i * 32 + 8, 5*32, 16, 52 + i*16, 4);
for (uint x = 0; x < vineImage.width; ++x)
for (uint y = 0; y < vineImage.height; ++y)
if (vineImage[x,y] == 0)
vineImage[x,y] = 128;
vineImage.save(vineFrame);
vineFrame.hotSpotX = -8;
}
jjOBJ@ preset = jjObjectPresets[96];
preset.behavior = DecorativeVine;
preset.curFrame = vineFrameID;
preset.playerHandling = HANDLING::PARTICLE;
///@Event 126=Lava Block |+|Scenery |Lava |Block |Adjust Y:5 |Deep:c1
jjAnimSets[ANIM::CUSTOM[9]].load(9, "DD-Anims.j2a"); // visual fx
jjSampleLoad(SOUND::WIND_WIND2A, "aslime2.wav");
jjOBJ@ lavaBlockPreset = jjObjectPresets[OBJECT::FENCER]; // not expecting anyone to put fencers in a level with lava in it
lavaBlockPreset.behavior = lavaBlock();
lavaBlockPreset.determineCurAnim(ANIM::DESTSCEN, 4);
lavaBlockPreset.playerHandling = HANDLING::SPECIAL;
lavaBlockPreset.scriptedCollisions = true;
lavaBlockPreset.bulletHandling = HANDLING::IGNOREBULLET;
lavaBlockPreset.isTarget = false;
lavaBlockPreset.isFreezable = false;
lavaBlockPreset.triggersTNT = false;
lavaBlockPreset.deactivates = true;
lavaBlockPreset.energy = 50; // just in case
///@Event 111=Dripper |+|Scenery |Drip | |Speed:2 |Lava:c1
jjAnimSets[ANIM::CUSTOM[28]].load(28, "DD-Anims.j2a");
jjObjectPresets[OBJECT::CHESHIRE1].behavior = dripper();
///@Event 112=Lori Block |+|Trigger |Lori |Scen
jjOBJ@ myDestructSceneryVariant = jjObjectPresets[OBJECT::CHESHIRE2];
myDestructSceneryVariant.points = 50;
myDestructSceneryVariant.playerHandling = HANDLING::SELFCOLLISION;
myDestructSceneryVariant.behavior = loriBlock;
///@Event 38=Little Bat |+|Scenery |Batty |Batt
jjObjectPresets[OBJECT::TNTAMMO3].behavior = littleBat();
jjObjectPresets[OBJECT::TNTAMMO3].playerHandling = HANDLING::PARTICLE;
jjSampleLoad(SOUND::P2_THROW, "Bat_takeoff.ogg");
jjSampleLoad(SOUND::P2_FART, "Bat_loop.ogg");
jjSampleLoad(SOUND::P2_FOEW1, "Bat_idle1.ogg");
jjSampleLoad(SOUND::P2_FOEW4, "Bat_idle2.ogg");
jjSampleLoad(SOUND::P2_FOEW5, "Bat_idle3.ogg");
jjSampleLoad(SOUND::P2_POEP, "Bat_idle4.ogg");
jjAnimSets[ANIM::CUSTOM[3]].load(3, "DD-Anims.j2a"); // DD pickups
jjAnimSets[ANIM::CUSTOM[5]].load(5, "DD-Anims.j2a");
jjAnimSets[ANIM::CUSTOM[11]].load(11, "DD-Anims.j2a");
jjAnimSets[ANIM::CUSTOM[12]].load(12, "DD-Anims.j2a");
jjSampleLoad(SOUND::ENDING_OHTHANK, "floppy-read.wav");
jjObjectPresets[OBJECT::PURPLEGEM].behavior = floppy();
jjObjectPresets[OBJECT::PURPLEGEM].determineCurAnim(ANIM::CUSTOM[11], 0);
///@Event 184=Computer |+|Object |PC
jjObjectPresets[OBJECT::STANDMONKEY].behavior = computer();
jjObjectPresets[OBJECT::STANDMONKEY].determineCurAnim(ANIM::CUSTOM[11], 1);
jjObjectPresets[OBJECT::STANDMONKEY].bulletHandling = HANDLING::IGNOREBULLET;
jjObjectPresets[OBJECT::STANDMONKEY].playerHandling = HANDLING::SPECIAL;
///@Event 141=Peppermint |+|Food |Pepr-|mint
jjObjectPresets[OBJECT::APPLE].determineCurAnim(ANIM::CUSTOM[3], 55);
///@Event 145=Caramel |+|Food |Cara |-mel
jjObjectPresets[OBJECT::PEAR].determineCurAnim(ANIM::CUSTOM[3], 56);
///@Event 154=Parsley Max Energy +1 |+|Goodies |Parsley
jjObjectPresets[OBJECT::LEMON].determineCurAnim(ANIM::CUSTOM[3], 2);
jjObjectPresets[OBJECT::LEMON].scriptedCollisions = true;
jjObjectPresets[OBJECT::LEMON].behavior = parsley();
startHP = jjMaxHealth;
///@Event 159=Cookie|+|Food |Cookie
jjObjectPresets[OBJECT::GRAPES].determineCurAnim(ANIM::CUSTOM[3], 0);
///@Event 160=Bread (white)|+|Food |Bread
jjObjectPresets[OBJECT::LETTUCE].determineCurAnim(ANIM::CUSTOM[3], 68);
///@Event 156=Bread (wholegrain)|+|Food |Whole |Bread
jjObjectPresets[OBJECT::THING].determineCurAnim(ANIM::CUSTOM[3], 69);
///@Event 157=Bun |+|Food |Bun
jjObjectPresets[OBJECT::WATERMELON].determineCurAnim(ANIM::CUSTOM[3], 70);
///@Event 161=Kaiser Roll |+|Food |Kaiser
jjObjectPresets[OBJECT::EGGPLANT].determineCurAnim(ANIM::CUSTOM[3], 71);
///@Event 162=Croissant |+|Food |Croi- |ssant
jjObjectPresets[OBJECT::CUCUMBER].determineCurAnim(ANIM::CUSTOM[3], 72);
///@Event 158=Swiss Roll |+|Food |Swiss |Roll
jjObjectPresets[OBJECT::PEACH].determineCurAnim(ANIM::CUSTOM[3], 51);
///@Event 142=Canned Beans |+|Food |Can |Bean
jjObjectPresets[OBJECT::BANANA].determineCurAnim(ANIM::CUSTOM[3], 74);
///@Event 143=Eclair |+|Food |Eclair
jjObjectPresets[OBJECT::CHERRY].determineCurAnim(ANIM::CUSTOM[3], 76);
///@Event 144=Apple Pie |+|Food |Apple |Pie
jjObjectPresets[OBJECT::ORANGE].determineCurAnim(ANIM::CUSTOM[3], 52);
///@Event 147=Toast |+|Food |Toast
jjObjectPresets[OBJECT::STRAWBERRY].determineCurAnim(ANIM::CUSTOM[3], 75);
///@Event 155=Muffin |+|Food |Muffin
jjObjectPresets[OBJECT::LIME].determineCurAnim(ANIM::CUSTOM[3], 77);
///@Event 170=White Chocolate |+|Food |White |Choc
jjObjectPresets[OBJECT::CHIPS].determineCurAnim(ANIM::CUSTOM[3], 48);
///@Event 176=Fudge |+|Food |Fudge
jjObjectPresets[OBJECT::FRIES].determineCurAnim(ANIM::CUSTOM[3], 62);
///@Event 164=Beer |+|Food |Beer
jjObjectPresets[OBJECT::PEPSI].determineCurAnim(ANIM::CUSTOM[3], 78);
///@Event 67=Super Item |+|Goodies |Super |Item|Type:{Red Gem,Green Gem,Blue Gem,Purple Gem,Watermelon,Jackfruit,Cheese Wheel}3
jjObjectPresets[OBJECT::SUPERGEM].behavior = SuperGemWrapper;
jjObjectPresets[OBJECT::FLICKERGEM].behavior = flickerWrapper;
jjSampleLoad(SOUND::COMMON_GEMSMSH1, "empty.wav");
jjSampleLoad(SOUND::INTRO_GRAB, "common-gemsmsh1.wav");
jjObjectPresets[OBJECT::FASTFEET].points = 100;
jjObjectPresets[OBJECT::FASTFEET].scriptedCollisions = true;
jjObjectPresets[OBJECT::FASTFEET].behavior = FastFeet();
///@Event 107=Spider |-|Enemy |Spider | |Web:{None,Center,Left,Right}2
jjAnimSets[ANIM::VINE].load();
jjAnimSets[ANIM::CUSTOM[31]].load(31, "DD-Anims.j2a");
jjObjectPresets[OBJECT::SPARK].behavior = function(obj) {
obj.behavior = spider();
obj.behave();
};
///@Event 42=Swinging Web |+|Object |Swing |Web
jjObjectPresets[OBJECT::SWINGINGVINE].behavior = swingingWeb();
jjPlayers[30].furSet(64, 64, 64, 64); // lol fuck it
///@Event 151=Dragon Boss |-|Boss |Dragon |Boss |Rotate:c1 |DrawBody:c1
jjAnimSets[ANIM::CUSTOM[33]].load(33, "DD-Anims.j2a"); // dragon boss sprites
jjAnimSets[ANIM::CUSTOM[34]].load(34, "DD-Anims.j2a"); // BL18 egg (for dragon boss)
jjSampleLoad(SOUND::INTRO_MONSTER2, "Dn_battlelord_action_or_roar.wav");
jjSampleLoad(SOUND::INTRO_HITSPAZ, "slamD2.wav");
jjAnimSets[ANIM::DEVILDEVAN].load(); // need this for one damn sample...
dragonBoss(jjObjectPresets[OBJECT::QUEEN]);
///@Event 201=
///@Event 202=
///@Event 225=
///@Event 241=
dragonArm(jjObjectPresets[OBJECT::ROBOT]);
jjObjectPresets[OBJECT::DEVANROBOT].behavior = dragonWings();
jjObjectPresets[OBJECT::TWEEDLEBOSS].behavior = dragonTail();
jjObjectPresets[OBJECT::TWEEDLEBOSS].playerHandling = HANDLING::PARTICLE;
jjObjectPresets[OBJECT::TWEEDLEBOSS].bulletHandling = HANDLING::IGNOREBULLET;
jjObjectPresets[OBJECT::BEES].behavior = egg();
jjObjectPresets[OBJECT::DRAGON].behavior = dragonEdit;
jjObjectPresets[OBJECT::BAT].behavior = batEdit; // not necessary for your level possibly
///@Event 226=Ropeway |+|Object |Rope |Way |Direction:{Left,Right}1|Spins:c1
jjAnimSets[ANIM::CUSTOM[32]].load(32, "DD-Anims.j2a");
jjObjectPresets[OBJECT::COPTER].behavior = Ropeway();
jjObjectPresets[OBJECT::COPTER].scriptedCollisions = true;
jjObjectPresets[OBJECT::COPTER].curFrame = jjAnimations[jjAnimSets[ANIM::CUSTOM[32]]];
///@Event 217=NPC |+|Object |NPC | |Type:{Jill,Yoman,Chief,Savage (male),Savage (female),Guard}3 |Order ID:5
jjAnimSets[ANIM::CUSTOM[16]].load(16, "DD-Anims.j2a"); // NPC sprites
jjAnimSets[ANIM::CUSTOM[5]].load(5, "DD-Anims.j2a"); // dialogue prompt sprite
jjObjectPresets[OBJECT::EVA].behavior = NPC();
///@Event 134=
///@Event 135=
jjSampleLoad(SOUND::INTRO_SKI, "falling-whistle-swish.ogg");
jjSampleLoad(SOUND::INTRO_INSECT, "bad-landing.ogg");
jjSampleLoad(SOUND::INTRO_KATROL, "jjgba-slip.ogg");
jjSampleLoad(SOUND::INTRO_SWISH1, "sword.ogg");
jjAnimSets[ANIM::CUSTOM[35]].load(35, "DD-Anims.j2a");
jjAnimSets[ANIM::CUSTOM[36]].load(36, "DD-Anims.j2a");
jjObjectPresets[OBJECT::SEEKERPOWERUP].behavior = admael;
jjObjectPresets[OBJECT::SEEKERPOWERUP].bulletHandling = HANDLING::IGNOREBULLET;
jjObjectPresets[OBJECT::SEEKERPOWERUP].playerHandling = HANDLING::PARTICLE;
jjObjectPresets[OBJECT::RFPOWERUP].behavior = eva;
jjObjectPresets[OBJECT::RFPOWERUP].bulletHandling = HANDLING::IGNOREBULLET;
jjObjectPresets[OBJECT::RFPOWERUP].playerHandling = HANDLING::PARTICLE;
///@Event 244=Falling Brick |+|Object |Brick |Trap |BGWall:c1
jjObjectPresets[OBJECT::CTFBASE].behavior = brickTrap();
///@Event 129=Takeoff |+|Modifier |Take |Off |Left:c1
jjObjectPresets[OBJECT::STEAM].behavior = BEHAVIOR::INACTIVE; //zonify
///@Event 254=Delet This |-|Modifier |NOT |v
deleteUnwantedEvents();
if (jjDifficulty > 0) jjObjectPresets[OBJECT::SPIKEBOLL].bulletHandling = HANDLING::IGNOREBULLET;
jjAnimSets[ANIM::HELMUT].load(); // for pixelmaps
// cavern pixelmap:
jjPIXELMAP cavernup("DamnUlt-CaveUpscaled.png");
jjANIMFRAME@ helmut1 = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::HELMUT].firstAnim].firstFrame];
cavernup.save(helmut1);
helmut1.hotSpotX = -helmut1.width/2;
helmut1.hotSpotY = -helmut1.height/2;
jjPIXELMAP cavern("DamnUlt-Cave.png");
jjANIMFRAME@ helmut2 = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::HELMUT].firstAnim].firstFrame + 1];
cavern.save(helmut2);
helmut2.hotSpotX = -helmut2.width/2;
helmut2.hotSpotY = -helmut2.height/2;
// bones pixelmap:
jjPIXELMAP bones("DamnUlt-Bones.png");
jjANIMFRAME@ helmut3 = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::HELMUT].firstAnim].firstFrame + 2];
bones.save(helmut3);
helmut3.hotSpotX = 0;
helmut3.hotSpotY = 0;
jjLAYER@ coverLayer = jjLayers[3];
coverLayer.generateSettableTileArea(607, 2, 20, 7);
jjLayerHasTiles[6] = jjLayerHasTiles[7] = false;
}
void onLevelBegin() {
setTimer();
int i = 0;
for (int xTile = jjLayerWidth[4]; --xTile >= 0;) {
for (int yTile = jjLayerHeight[4]; --yTile >= 0;) {
if (jjEventGet(xTile,yTile) == OBJECT::SPARK && jjParameterGet(xTile, yTile, 0, 2) != 0) {
spiderTileX[i] = xTile;
spiderTileY[i] = yTile;
i++;
}
}
}
}
void onLevelReload() {
scoreSeconds = storeScoreSeconds;
if (!jjLayers[2].hasTiles) jjTriggers[4] = true;
if (craccoMode) {
jjPAL craccoPal;
craccoPal.load("CastUlt-Cracco.j2t");
apply(craccoPal);
}
if (storeMusic == "DD-BossGBA.it") {
if (craccoMode) storeMusic = "BEER.it";
else storeMusic = "CastleBreadMix.mp3";
}
}
void onMain() {
if (jjDifficulty == 0) oneUpsIntoBlueGems();
if ((jjGameTicks % 70) == 0 && !levelOver && CurrentPopup is null) scoreSeconds++;
rankingSetup();
if (levelOver) updateFade();
if (fadeWall) {
jjLayers[2].spriteMode = SPRITE::BLEND_DISSOLVE;
jjLayers[2].spriteParam = fadeParam;
if (fadeParam > 0) fadeParam--;
else jjLayers[2].hasTiles = false; // clean up mem
}
if (inScene) {
if (sceneCounter == 240 && fadeWall) {
continueTalk(jjLocalPlayers[0], 0);
inScene = false;
}
if (defeatedDragon && CurrentPopup is null) {
switch (sceneCounter) {
case 0:
jjSamplePriority(SOUND::INTRO_SKI);
break;
case 50:
jjSample(jjLocalPlayers[0].xPos, jjLocalPlayers[0].yPos, SOUND::COMMON_BLUB1);
break;
case 120:
dropGirls();
break;
case 290:
jjMusicResume();
storeMusic = "CastleCitizenMix.m4a";
inScene = false;
continueTalk(jjLocalPlayers[0], 2);
break;
case 330:
scenePhase = 2;
break;
case 400:
inScene = false;
continueTalk(jjLocalPlayers[0], 3);
break;
}
}
sceneCounter++;
}
for (int i = 0; i <= jjLocalPlayerCount; i++) {
if (fastFeetMode[i] != 0) {
noFast = false;
break;
} else noFast = true;
}
if (noFast && !levelOver) {
jjMusicLoad(storeMusic);
if ((storeMusic == "BEER.it") && uint8(jjGetModTempo()) != defaultModTempo[0]) jjSetModTempo(defaultModTempo[0]);
else if (storeMusic == "DD-BossGBA.it" && uint8(jjGetModTempo()) != defaultModTempo[1]) jjSetModTempo(defaultModTempo[1]);
}
}
void onPlayer(jjPLAYER@ play) {
dialogueInterface(play);
rankingInterface(play);
if (inScene) {
play.timerPause();
play.invincibility = -15;
play.keyLeft = play.keyRight = play.keyDown = play.keyUp = play.keyRun = play.keyFire = play.keyJump = play.keySelect = false;
play.idle = 0;
if (defeatedDragon && CurrentPopup is null) {
if (sceneCounter > 50 && sceneCounter <= 150) jjDrawSprite(play.xPos + 40, play.yPos - 40, ANIM::CUSTOM[5], 1, 1, 0, SPRITE::NORMAL, 0, 3);
if (sceneCounter == 140) {
if (play.xPos >= 691 * 32) play.direction = -1;
else play.direction = 1;
}
}
}
if (!levelOver) {
if (fastFeetMode[play.playerID] > 0) {
// if (playX.State != PlayerState::Loop) {
if (play.xAcc > 0) {
play.xAcc = play.xAcc + 1;
play.xSpeed = play.xSpeed + 2;
}
if (play.xAcc < 0) {
play.xAcc = play.xAcc - 1;
play.xSpeed = play.xSpeed - 2;
}
play.keyRun = true;
// }
fastFeetMode[play.playerID]--;
if (play.xPos > 232 * 32 && play.xPos < 239 * 32 && play.yPos > 24 * 32 && play.yPos < 34 * 32 && play.xSpeed > 16.0) {
for (int x = int(play.xPos) + 16; x < play.xPos + play.xSpeed; ++x) {
if (jjMaskedVLine(x, int(play.yPos) - 24, 48)) {
play.xSpeed = 16.0;
break;
}
}
}
}
// DRAGON BOSS STUFF
int maxBossDistance = 567; // push distance; adjust for your boss arena (this whole part is to prevent getting stuck in a wall)
if (jjGameTicks > 10) { // maybe let's let stuff initiate fully first...
for (int i = 1; i < jjObjectCount; i++) {
jjOBJ@ other = jjObjects[i];
if (other.eventID == 151) {
if (other.direction == -1) {
if (play.bossActivated && other.state == STATE::PUSH && !jjMaskedPixel(int(play.xPos + play.xSpeed - 4), int(play.yPos))) play.xPos = play.xPos - 2;
if (play.bossActivated && play.xPos < other.xPos - maxBossDistance) play.xPos = other.xPos - maxBossDistance;
if (other.counterEnd != 3 && play.xPos > other.xPos - 24) play.xPos = other.xPos - 24;
} else {
if (play.bossActivated && other.state == STATE::PUSH && !jjMaskedPixel(int(play.xPos + play.xSpeed + 4), int(play.yPos))) play.xPos = play.xPos + 2;
if (play.bossActivated && play.xPos > other.xPos + maxBossDistance) play.xPos = other.xPos + maxBossDistance;
if (other.counterEnd != 3 && play.xPos < other.xPos + 24) play.xPos = other.xPos + 24;
}
}
}
}
PlayerX@ playX = PlayerXs[play.localPlayerID];
if (playX.LastHealth != play.health) {
if (playX.LastHealth > play.health) { //injured
playX.State = PlayerState::Normal;
playX.CurrentAngle = LoopAngle::Floor;
playX.LastTile = -1;
play.invisibility = false;
play.cameraUnfreeze();
}
playX.LastHealth = play.health;
return;
}
if (playX.State == PlayerState::Ropeway) {
play.keyLeft = play.keyRight = play.keyRun = play.keyDown = false;
play.xSpeed = play.ySpeed = 0;
//play.invincibility = -2;
return;
}
const uint xTile = uint(play.xPos) >> 5, yTile = uint(play.yPos) >> 5;
const uint8 currEvent = jjEventGet(xTile,yTile);
if (currEvent == CornerEventID) {
const int loopEventParameter = jjParameterGet(xTile, yTile, 0, 4);
if ((loopEventParameter >> 2) == playX.CurrentAngle) {
const auto curAnim = play.curAnim - jjAnimSets[play.setID];
if (play.currTile != playX.LastTile && (playX.State == PlayerState::Loop || (curAnim >= RABBIT::RUN1 && curAnim <= RABBIT::RUN3))) { //running
//if (loopEventParameter != playX.LastLoopEventParameter) {
const int circumstance = ((loopEventParameter & 3) << 3) | (playX.CurrentAngle << 1) | (play.direction == 1 ? 1 : 0);
const CornerEventResult@ result = NextLoopAngles[circumstance];
if (result !is null) {
playX.LastTile = play.currTile;
//playX.LastLoopEventParameter = loopEventParameter;
//jjAlert(result.Angle + "," + result.XOrg + "," + result.YOrg);
playX.NextAngle = result.Angle;
playX.CornerXOrg = (int(xTile) + result.XOrg) * 32;
playX.CornerYOrg = (int(yTile) + result.YOrg) * 32;
const bool isLoop = loopEventParameter & 2 == 0;
//playX.CornerLength = isLoop ? 256 : 256; //todo
playX.CornerElapsed = 0; //todo?
playX.CornerRepositioningMultiplier = isLoop ? 50 : 84; //40:120
playX.CornerAngleMultiplier = isLoop ? 1 : -1;
playX.CornerSpeed = isLoop ? 30 : 18;
playX.CornerAngleChange = int(playX.NextAngle - playX.CurrentAngle);
if (abs(playX.CornerAngleChange) == 3)
playX.CornerAngleChange /= -3;
playX.State = PlayerState::Loop;
play.invisibility = true;
//play.noclipMode = true;
play.xSpeed = play.ySpeed = 0;
if (jjParameterGet(xTile, yTile, -2, 1) == 1)
play.cameraFreeze(play.cameraX, play.cameraY, false, true);
}
//}
}
}
} else if (currEvent == AREA::FLYOFF) { //this feels like the sensible choice
if (playX.State == PlayerState::Loop) play.keyJump = true;
play.ballTime = 0;
} else if (currEvent == AREA::PATH) { // not renaming the event and its parameters cuz the original functionality is retained, this is just an add-on; refer to param values below
float entryX;
if (abs(play.xSpeed) >= 16.0) entryX = play.xSpeed;
else entryX = play.xSpeed * 1.05;
if (play.currTile != playX.LastTile && jjParameterGet(xTile, yTile, 9, 1) == 0) { // visited tile for the first time AND is entry/exit point?
play.ballTime = 120;
play.xPos = xTile * 32 + 16;
play.yPos = yTile * 32 + 16;
playX.LastTile = play.currTile;
play.xSpeed = entryX;
switch (jjParameterGet(xTile, yTile, 6, 3)) { // entry/exit type
case 0: play.ySpeed = entryX / 2.1; break; // left edge spiral top
case 1: play.ySpeed = entryX / -2.1; break; // right edge spiral top
case 2: if (entryX < 0) play.ySpeed = entryX / 2; else play.ySpeed = 0; break; // left edge spiral bottom
case 3: if (entryX > 0) play.ySpeed = entryX / -2; else play.ySpeed = 0; break; // right edge spiral bottom
case 4: if (entryX < 0) play.ySpeed = entryX / 2; else play.ySpeed = entryX; break; // left edge spiral bottom into slope
case 5: if (entryX > 0) play.ySpeed = entryX / -2; else play.ySpeed = entryX * -1; break; // right edge spiral bottom into slope
}
play.noclipMode = !play.noclipMode; // if already noclip then not noclip, else do noclip.
} else if (play.currTile != playX.LastTile && jjParameterGet(xTile, yTile, 9, 1) == 1 && play.noclipMode) { // inner spiral node, must be noclip
play.xPos = xTile * 32 + 16;
play.yPos = yTile * 32 + 16;
playX.LastTile = play.currTile;
play.xSpeed = entryX * -1; // bounce to the other side
play.ySpeed = play.ySpeed * 1.05; // adjust for increasing xSpeed
}
} else if (currEvent == OBJECT::STEAM) {
if (play.currTile != playX.LastTile && ((play.xSpeed >= 4.0 && jjParameterGet(xTile, yTile, 0, 1) == 0) || (play.xSpeed <= -4.0 && jjParameterGet(xTile, yTile, 0, 1) == 1))) {
if (play.xSpeed > 16.0) play.xSpeed = 16.0;
else if (play.xSpeed < -16.0) play.xSpeed = -16.0;
play.spring(0, abs(play.xSpeed) * -1, true, false);
playX.LastTile = play.currTile;
}
} else {
playX.LastTile = -1;
if (!inScene && CurrentPopup is null && !levelOver) {
if (shakeStr > 0) {
if (jjGameTicks % 3 == 0) {
if ((jjGameTicks % 6) == 0) play.cameraFreeze(play.xPos, play.yPos - shakeStr, true, false);
else play.cameraFreeze(play.xPos, play.yPos + shakeStr, true, false);
}
shakeStr -= uint(jjSubscreenHeight / 150);
} else play.cameraUnfreeze();
}
}
int angle = playX.CurrentAngle << 8;
const int speedDivider = play.keyRun ? 1 : 4;
if (playX.CornerElapsed >= 0) {
playX.CornerElapsed += playX.CornerSpeed / speedDivider;
if (playX.CornerElapsed > 256)
playX.CornerElapsed = 256;
angle += int(playX.CornerElapsed * playX.CornerAngleChange);
//play.xPos = playX.LastX = playX.CornerXOrg + jjSin(angle) * playX.CornerRepositioningMultiplier * -playX.CornerAngleMultiplier;
//play.yPos = playX.LastY = playX.CornerYOrg - jjCos(angle) * playX.CornerRepositioningMultiplier * -playX.CornerAngleMultiplier;
playX.LastX = playX.CornerXOrg + jjSin(angle) * playX.CornerRepositioningMultiplier * -playX.CornerAngleMultiplier;
playX.LastY = playX.CornerYOrg - jjCos(angle) * playX.CornerRepositioningMultiplier * -playX.CornerAngleMultiplier;
play.xSpeed = playX.LastX - play.xPos;
play.ySpeed = playX.LastY - play.yPos;
if (playX.CornerElapsed == 256) {
playX.CornerElapsed = -1;
playX.CurrentAngle = playX.NextAngle;
}
} else if (playX.State == PlayerState::Loop) {
const int speedMultiplier = 16 / speedDivider * play.direction;
//play.xPos = playX.LastX += jjCos(angle) * speedMultiplier;
//play.yPos = playX.LastY += jjSin(angle) * speedMultiplier;
playX.LastX += jjCos(angle) * speedMultiplier;
playX.LastY += jjSin(angle) * speedMultiplier;
play.xSpeed = playX.LastX - play.xPos;
play.ySpeed = playX.LastY - play.yPos;
}
if (playX.State == PlayerState::Loop) {
if (playX.CornerAngleMultiplier == 1 && playX.CornerElapsed >= 0) //quarter pipe
play.keyJump = false;
if ((playX.CornerElapsed < 0 && playX.CurrentAngle == LoopAngle::Floor) || play.keyJump || (play.direction == 1 && !play.keyRight) || (play.direction == -1 && !play.keyLeft) || jjMaskedPixel(int(playX.LastX), int(playX.LastY))) {
playX.State = PlayerState::Normal;
playX.CurrentAngle = LoopAngle::Floor;
playX.CornerElapsed = -1;
play.invisibility = false;
play.xSpeed = play.direction * jjCos(angle) * 16 / speedDivider;
play.ySpeed = play.direction * jjSin(angle) * 16 / speedDivider;
play.cameraUnfreeze();
} else {
play.keyLeft = play.keyRight = play.keyFire = false;
jjDrawRotatedSprite(play.xPos, play.yPos, play.setID, play.keyRun ? RABBIT::RUN3 : RABBIT::RUN1, jjGameTicks >> 2, -angle, play.direction,1, SPRITE::PLAYER, play.playerID);
}
}
if ((jjTriggers[0] && play.xPos >=89*32 && play.xPos < 90*32 && play.yPos >= 57*32 && play.yPos < 62*32+6)
||
(play.xPos >=253*32 && play.xPos < 254*32 && play.yPos >= 17*32 && play.yPos < 24*32+6))
play.xPos = play.xPos + 16;
}
}
bool onCheat(string &in cheat) { // made for testing but do go off
if (cheat == "jjcracco") {
if (!craccoMode) { getFunky(jjLocalPlayers[0]); }
else { stopFunky(jjLocalPlayers[0]); }
jjAlert("jjcracco", size: STRING::MEDIUM);
return true;
}
if (cheat == "jjfast") {
fastFeetMode[0] = 1400; // can only cheat in SP mode anyway
if (storeMusic == "BEER.it") jjSetModTempo(uint8(defaultModTempo[0] * 1.33));
else if (storeMusic == "DD-BossGBA.it") jjSetModTempo(uint8(defaultModTempo[1] * 1.33));
else if (jjMusicFileName != "DD-CastleFast.it") {
storeMusic = jjMusicFileName;
jjMusicLoad("DD-CastleFast.it");
}
jjAlert("jjfast", size: STRING::MEDIUM);
return true;
}
if (levelOver || inScene || CurrentPopup !is null) return true;
else return false;
}
bool onDrawScore(jjPLAYER@ player, jjCANVAS@ canvas) {
if (computerOn) {
canvas.drawSprite((jjResolutionWidth / 2) - 16, jjResolutionHeight - (jjResolutionHeight / 32), ANIM::CUSTOM[11], 0, (jjGameTicks/5) & 15);
canvas.drawString(jjResolutionWidth / 2, jjResolutionHeight - (jjResolutionHeight / 32), "x" + player.gems[GEM::PURPLE], STRING::MEDIUM);
}
if (fastFeetMode[player.playerID] > 210 || (fastFeetMode[player.playerID] > 0 && jjGameTicks % 2 == 0)) canvas.drawString((jjSubscreenWidth / 2) - (jjGetStringWidth("GOTTA GO FAST 00", STRING::MEDIUM, STRING::BOUNCE) / 2), 50, "GOTTA GO FAST " + uint(fastFeetMode[player.playerID] / 70), STRING::MEDIUM, STRING::BOUNCE, 16);
if (CurrentPopup !is null) {
CurrentPopup.Draw(canvas);
return !CurrentPopup.DrawHUD();
}
if (levelOver) {
drawFade(player, canvas);
if (rankLine < 6) rankingDisplay(player, canvas);
}
if (rankLine >= 6 || inScene) return true;
else return false;
}
bool onDrawAmmo(jjPLAYER@ player, jjCANVAS@ canvas) {
if (jjColorDepth < 16) {
canvas.drawRectangle(0, 0, jjSubscreenWidth, jjSubscreenHeight, 79);
canvas.drawString((jjSubscreenWidth / 16), (jjSubscreenHeight / 2) - (jjSubscreenHeight / 8), "This level does not", smallScreen ? STRING::SMALL : STRING::MEDIUM);
canvas.drawString((jjSubscreenWidth / 16), (jjSubscreenHeight / 2) - (jjSubscreenHeight / 16), "support 8-bit color depth.", smallScreen ? STRING::SMALL : STRING::MEDIUM);
canvas.drawString((jjSubscreenWidth / 16), (jjSubscreenHeight / 2) + (jjSubscreenHeight / 16), "Please go to options menu", smallScreen ? STRING::SMALL : STRING::MEDIUM);
canvas.drawString((jjSubscreenWidth / 16), (jjSubscreenHeight / 2) + (jjSubscreenHeight / 8), "and switch to 16-bit color.", smallScreen ? STRING::SMALL : STRING::MEDIUM);
}
if (rankLine >= 6 || inScene) return true;
else return CurrentPopup !is null && !CurrentPopup.DrawHUD();
}
bool onDrawHealth(jjPLAYER@ player, jjCANVAS@ canvas) {
if (rankLine >= 6 || inScene) return true;
else return CurrentPopup !is null && !CurrentPopup.DrawHUD();
}
bool onDrawLives(jjPLAYER@ player, jjCANVAS@ canvas) {
if (jjDifficulty == 0 && CurrentPopup is null) {
return true;
}
if (rankLine >= 6 || inScene) return true;
else return CurrentPopup !is null && !CurrentPopup.DrawHUD();
}
bool onDrawPlayerTimer(jjPLAYER@ play, jjCANVAS@ canvas) {
if (inScene) return true;
else return CurrentPopup !is null && !CurrentPopup.DrawHUD();
}
//applying bones to layer:
void onDrawLayer1(jjPLAYER@ player, jjCANVAS@ canvas) {
for (int i = 0; i < 50; i++) canvas.drawSprite(224 * i, 800, ANIM::HELMUT, 0, 2);
}
void onDrawLayer4(jjPLAYER@ player, jjCANVAS@ canvas) {
for (uint i = 0; i < spiderTileX.length; i++) {
if (spiderTileX[i] != 0 || spiderTileY[i] != 0) {
canvas.drawSprite(spiderTileX[i] * 32 + 16, spiderTileY[i] * 32 + 16, ANIM::CUSTOM[31], 1, jjParameterGet(spiderTileX[i], spiderTileY[i], 0, 2) - 1);
}
}
canvas.drawSprite((559 * 32) + 16, (34 * 32) + 22, ANIM::BONUS, 0, (jjGameTicks/8) & 9, 1);
canvas.drawSprite((559 * 32) + 16, (40 * 32) + 22, ANIM::BONUS, 0, (jjGameTicks/8) & 9, 1);
jjTEXTAPPEARANCE appearance(STRING::BOUNCE);
appearance.spacing = 1;
canvas.drawString((559 * 32) + 3, (34 * 32) + 7, "50", STRING::SMALL, appearance, 6, SPRITE::PALSHIFT, 208);
canvas.drawString((559 * 32) + 3, (40 * 32) + 7, "30", STRING::SMALL, appearance, 6, SPRITE::PALSHIFT, 208);
if (!jjTriggers[7]) canvas.drawSprite((688 * 32) - 1, 48 * 32, ANIM::CUSTOM[36], 7, 0, 1, SPRITE::BLEND_NORMAL, saberFade);
}
void onDrawLayer5(jjPLAYER@ player, jjCANVAS@ canvas) { // optimize this later
canvas.drawSprite((688 * 32) + 16, 44 * 32, ANIM::CUSTOM[34], 0, 0, 64, SPRITE::TINTED, 79);
canvas.drawSprite((694 * 32) + 16, 40 * 32, ANIM::CUSTOM[34], 0, 0, 64, SPRITE::TINTED, 79);
canvas.drawSprite((695 * 32) + 16, (40 * 32) + 16, ANIM::CUSTOM[34], 0, 0, 64, SPRITE::TINTED, 79);
canvas.drawSprite((696 * 32) + 16, 41 * 32, ANIM::CUSTOM[34], 0, 0, 64, SPRITE::TINTED, 79);
canvas.drawSprite((697 * 32) + 16, 43 * 32, ANIM::CUSTOM[34], 0, 0, 64, SPRITE::TINTED, 79);
canvas.drawSprite((699 * 32) + 16, 44 * 32, ANIM::CUSTOM[34], 0, 0, 64, SPRITE::TINTED, 79);
canvas.drawSprite((700 * 32) + 16, 45 * 32, ANIM::CUSTOM[34], 0, 0, 64, SPRITE::TINTED, 79);
canvas.drawSprite((701 * 32) + 16, 46 * 32, ANIM::CUSTOM[34], 0, 0, 64, SPRITE::TINTED, 79);
canvas.drawSprite((687 * 32) + 16, 50 * 32, ANIM::CUSTOM[34], 0, 0, 0, SPRITE::TINTED, 79);
canvas.drawSprite((689 * 32) + 16, 50 * 32, ANIM::CUSTOM[34], 0, 0, 0, SPRITE::TINTED, 79);
canvas.drawSprite((695 * 32) + 16, 50 * 32, ANIM::CUSTOM[34], 0, 0, 0, SPRITE::TINTED, 79);
canvas.drawSprite((696 * 32) + 16, 50 * 32, ANIM::CUSTOM[34], 0, 0, 0, SPRITE::TINTED, 79);
canvas.drawSprite((706 * 32) + 16, 50 * 32, ANIM::CUSTOM[34], 0, 0, 0, SPRITE::TINTED, 79);
canvas.drawSprite((707 * 32) + 16, 50 * 32, ANIM::CUSTOM[34], 0, 0, 0, SPRITE::TINTED, 79);
}
void onDrawLayer8(jjPLAYER@ play, jjCANVAS@ canvas) {
canvas.drawSprite(jjSubscreenWidth, int(jjSubscreenHeight * 0.8), ANIM::HELMUT, 0, smallScreen ? 1 : 0);
if (smallScreen) {
for (int i = 1; i < 4; i++)
canvas.drawSprite(jjSubscreenWidth + (480 * i), int(jjSubscreenHeight * 0.8), ANIM::HELMUT, 0, 1);
}
else canvas.drawSprite(jjSubscreenWidth + 1920, int(jjSubscreenHeight * 0.8), ANIM::HELMUT, 0, 0);
}
void deleteUnwantedEvents() {
int width = jjLayerWidth[4];
int height = jjLayerHeight[4] - 1;
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int event = jjEventGet(j, i);
if (event == 254)
jjParameterSet(j, i + 1, -12, 32, 0);
}
}
}
void LoopObject(jjOBJ@ obj) {
if (obj.state == STATE::START) {
obj.state = STATE::ROTATE;
obj.var[0] = jjParameterGet(uint(obj.xOrg) >> 5,uint(obj.yOrg) >> 5, 0,1);
obj.var[1] = jjParameterGet(uint(obj.xOrg) >> 5,uint(obj.yOrg) >> 5, 1,4);
obj.doesHurt = 0; //starts at 0, so (LoopsUnmaskedOnLeft != leftSideUnmasked) below will always be true the first time
} if (obj.state == STATE::DEACTIVATE)
obj.deactivate();
else {
const auto nearestPlayerID = obj.findNearestPlayer(960*960);
if (nearestPlayerID >= 0) {
const jjPLAYER@ play = jjPlayers[nearestPlayerID];
const uint spacing = obj.var[1];
uint8 leftSideUnmasked;
if (play.xPos < obj.xOrg - 32)
leftSideUnmasked = 2;
else if (play.xPos > obj.xOrg + spacing*32 + 128)
leftSideUnmasked = 1;
else if (PlayerXs[play.localPlayerID].CurrentAngle == LoopAngle::Ceiling)
leftSideUnmasked = play.direction == -1 ? 2 : 1;
else
return;
if (obj.doesHurt != leftSideUnmasked) {
obj.doesHurt = leftSideUnmasked;
const uint xTile = uint(obj.xOrg) >> 5, yTile = uint(obj.yOrg) >> 5;
const bool grassOnRight = obj.var[0] == 1;
if (leftSideUnmasked == 2) {
jjTileSet(4, xTile + 0, yTile, 0);
jjTileSet(4, xTile + 1, yTile, 0);
jjTileSet(4, xTile + spacing + 2, yTile, grassOnRight ? 323 : 306);
jjTileSet(4, xTile + spacing + 3, yTile, grassOnRight ? 324 : 307);
jjTileSet(4, xTile + 0, yTile - 1, 0);
jjTileSet(4, xTile + spacing + 3, yTile - 1, 297);
jjEventSet(xTile + 1, yTile, 0);
jjEventSet(xTile + spacing + 2, yTile, CornerEventID);
} else {
jjTileSet(4, xTile + 0, yTile, grassOnRight ? 304 : 325);
jjTileSet(4, xTile + 1, yTile, grassOnRight ? 305 : 326);
jjTileSet(4, xTile + spacing + 2, yTile, 0);
jjTileSet(4, xTile + spacing + 3, yTile, 0);
jjTileSet(4, xTile + 0, yTile - 1, 294);
jjTileSet(4, xTile + spacing + 3, yTile - 1, 0);
jjEventSet(xTile + 1, yTile, CornerEventID);
jjEventSet(xTile + spacing + 2, yTile, 0);
}
}
}
}
}
void AddCornerEvents() {
for (int xTile = jjLayerWidth[4]; --xTile >= 0;)
for (int yTile = jjLayerHeight[4]; --yTile >= 0;) {
LoopAngle inputAngle = LoopAngle::Floor;
bool turnRight = false;
bool isLoop = false;
int xOffset = 0, yOffset = 0;
switch (jjTileGet(4, xTile, yTile)) {
case 677:
case 855:
case 2284:
case 2352:
yOffset = -1;
break;
case 686:
case 864:
case 2293:
case 2361:
xOffset = -1;
turnRight = true;
inputAngle = LoopAngle::WallOnRight;
break;
case 685:
case 819:
case 2262:
case 2362:
xOffset = 1;
turnRight = true;
inputAngle = LoopAngle::WallOnLeft;
break;
case 694:
case 828:
case 2271:
case 2371:
yOffset = 1;
inputAngle = LoopAngle::Ceiling;
break;
case 670:
case 852:
case 2280:
case 2353:
yOffset = -1;
turnRight = true;
break;
case 681:
case 863:
case 2291:
case 2364:
xOffset = 1;
inputAngle = LoopAngle::WallOnLeft;
break;
case 682:
case 838:
case 2292:
case 2363:
xOffset = -1;
inputAngle = LoopAngle::WallOnRight;
break;
case 693:
case 849:
case 2303:
case 2374:
yOffset = 1;
turnRight = true;
inputAngle = LoopAngle::Ceiling;
break;
case 956:
yOffset = 1;
isLoop = true;
turnRight = true;
inputAngle = LoopAngle::Ceiling;
break;
case 696:
case 866:
case 2369:
xOffset = -1;
isLoop = true;
turnRight = true;
inputAngle = LoopAngle::WallOnRight;
break;
case 714:
case 874:
case 2387:
yOffset = -1;
isLoop = true;
break;
case 713:
case 873:
case 2377:
yOffset = -1;
isLoop = true;
turnRight = true;
break;
case 691:
case 861:
case 2355:
xOffset = 1;
isLoop = true;
inputAngle = LoopAngle::WallOnLeft;
break;
case 3600:
xOffset = 1;
isLoop = true;
turnRight = true;
inputAngle = LoopAngle::WallOnLeft;
break;
case 3601:
yOffset = 1;
isLoop = true;
inputAngle = LoopAngle::Ceiling;
break;
case 3602:
yOffset = 1;
isLoop = true;
turnRight = true;
inputAngle = LoopAngle::Ceiling;
break;
case 3603:
xOffset = -1;
isLoop = true;
inputAngle = LoopAngle::WallOnRight;
break;
default:
continue;
}
if (jjEventGet(xTile + xOffset, yTile + yOffset) == 0) {
jjEventSet(xTile + xOffset, yTile + yOffset, CornerEventID);
jjParameterSet(xTile + xOffset, yTile + yOffset, 0,4, (turnRight ? 1 : 0) | (isLoop ? 0 : 2) | (inputAngle << 2));
}
}
}
class Ropeway : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
jjPLAYER@ play = jjPlayers[obj.var[0]];
bool isRealPlayer = obj.var[0] >= 0 && PlayerXs[play.localPlayerID].State == PlayerState::Ropeway;
if (obj.state == STATE::START) {
obj.state = STATE::WAIT;
obj.var[0] = -1; //no player yet
const uint xTile = uint(obj.xOrg) >> 5, yTile = uint(obj.yOrg) >> 5;
obj.direction = jjParameterGet(xTile,yTile, 0,1) * 2 - 1;
obj.special = jjParameterGet(xTile,yTile, 1,1); //rotates
} else if (obj.state == STATE::DEACTIVATE) {
reset(obj);
} else if (obj.state == STATE::ROTATE) {
obj.special += int(obj.xAcc * 3 * obj.direction);
const float sin = jjSin(obj.special), cos = jjCos(obj.special);
jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos - 24, obj.curFrame, obj.special);
if (isRealPlayer) {
jjDrawRotatedSprite(play.xPos = (obj.xPos + sin * 30), play.yPos = (obj.yPos + cos * 30 - 24), play.setID, RABBIT::HANGIDLE2, jjGameTicks>>3, obj.special, play.direction = obj.direction,1, SPRITE::PLAYER, obj.var[0]);
if (play.keyJump) {
release(obj);
obj.counter = 200;
}
obj.counterEnd = jjSampleLooped(obj.xPos, obj.yPos, SOUND::COMMON_FOEW5, obj.counterEnd, int(abs(cos) * 46) + 15);
} else
release(obj);
} else {
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos - 24, obj.curFrame);
if (obj.state == STATE::ACTION) {
if (isRealPlayer) {
jjDrawSprite(play.xPos = obj.xPos, (play.yPos = obj.yPos) + 6, play.setID, play.keyFire ? RABBIT::HANGINGFIRERIGHT : RABBIT::HANGIDLE2, jjGameTicks>>3, play.direction = obj.direction, SPRITE::PLAYER, obj.var[0]);
if (play.keyJump && obj.xAcc > 3.f) {
release(obj);
play.ySpeed = -5;
obj.state = STATE::ACTION;
}
}
if (obj.xAcc < 8.f)
obj.xAcc += 0.1f;
if (jjGameTicks & 1 == 0) {
jjPARTICLE@ part = jjAddParticle(PARTICLE::SPARK);
if (part !is null) {
part.xPos = obj.xPos - obj.direction*7;
part.yPos = obj.yPos - 21;
part.xSpeed = -obj.direction;
part.ySpeed = 0.5;
}
}
obj.xPos += obj.xAcc * obj.direction;
obj.yPos += obj.xAcc / 2;
obj.counterEnd = jjSampleLooped(obj.xPos, obj.yPos, SOUND::COMMON_FOEW5, obj.counterEnd);
const auto newTileID = jjTileGet(4, uint(obj.xPos)>>5, uint(obj.yPos)>>5);
if (newTileID == 1500 || newTileID == 1510 || newTileID == 1520) { //bg pillar
obj.xPos += obj.xAcc * obj.direction * 2;
obj.yPos += obj.xAcc;
if (obj.special == 1 && isRealPlayer)
obj.state = STATE::ROTATE;
else
release(obj);
}
} else if (obj.state == STATE::DONE) {
if (++obj.counter > 210) {
obj.particlePixelExplosion(0);
reset(obj);
}
}
}
}
void reset(jjOBJ@ obj) const {
obj.state = STATE::START;
obj.xPos = obj.xOrg;
obj.yPos = obj.yOrg;
obj.playerHandling = HANDLING::PICKUP;
obj.counter = 0;
}
void release(jjOBJ@ obj) const {
if (obj.var[0] >= 0) {
jjPLAYER@ play = jjPlayers[obj.var[0]];
PlayerX@ playX = PlayerXs[play.localPlayerID];
if (playX.State == PlayerState::Ropeway) {
playX.State = PlayerState::Normal;
const float xThrust = obj.xAcc * obj.direction * 2;
play.xAcc = jjCos(obj.special) * xThrust;
play.ySpeed = -jjSin(obj.special) * xThrust;
play.direction = obj.direction;
play.invisibility = false;
}
}
obj.state = STATE::DONE;
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ play, int force) {
PlayerX@ playX = PlayerXs[play.localPlayerID];
if (obj.var[0] == -1 && playX.State == PlayerState::Normal && force != -1) {
obj.var[0] = play.playerID;
obj.state = STATE::ACTION;
obj.playerHandling = HANDLING::PARTICLE; //no further collisions
obj.xAcc = 0;
playX.State = PlayerState::Ropeway;
play.invisibility = true;
}
return true;
}
}
void loriBlock(jjOBJ@ obj) {
obj.behave(BEHAVIOR::DESTRUCTSCENERY);
int nearPlayerID = obj.findNearestPlayer(256);
if (obj.state != STATE::KILL && obj.state != STATE::DONE) {
if (jjLocalPlayers[nearPlayerID].charCurr == CHAR::LORI && jjLocalPlayers[obj.findNearestPlayer(256)].doesCollide(obj) && jjLocalPlayers[nearPlayerID].specialMove > 0) {
givePlayerPointsForObject(jjLocalPlayers[nearPlayerID], obj);
obj.state = STATE::KILL;
}
}
}
void bombScenEdit(jjOBJ@ obj) {
const array<float> crateX = {347*32+16,350*32+16};
const array<float> crateY = {18*32+16,28*32+16};
obj.behave(BEHAVIOR::DESTRUCTSCENERY);
if (obj.isActive) obj.var[5] = jjParameterGet(int(obj.xOrg / 32), int(obj.yOrg / 32), 0, 2);
if (obj.var[5] != 0) { // block flagged for tracking puzzle progress
if (obj.isActive && obj.age == 0) {
jjOBJ@ spawn;
if (obj.var[6] == 0) {
@spawn = jjObjects[jjAddObject(OBJECT::EXPLOSION, crateX[obj.var[5] - 1], crateY[obj.var[5] - 1])];
spawn.determineCurAnim(ANIM::PICKUPS, 86); // twinkle twinkle little star
}
if (obj.var[6] == 40) {
// Create new crate
jjOBJ@ crate;
@crate = jjObjects[jjAddObject(OBJECT::BOMBCRATE, crateX[obj.var[5] - 1], crateY[obj.var[5] - 1])];
crate.deactivates = false;
if (obj.var[7] == 1) crate.points = 0; // already got points for it once, now worthless (like generator obj)
obj.age = crate.objectID; // Store the ID
obj.counter = 0;
} else obj.var[6] = obj.var[6] + 1;
}
// Check if crate still exists by looking up the stored ID
if (obj.state == STATE::SLEEP && obj.age != 0) {
jjOBJ@ crate = jjObjects[obj.age];
if (!crate.isActive) {
if (obj.counter == 70) {
// Crate was destroyed - reset so we can create a new one
obj.var[6] = 0;
obj.age = 0;
} else obj.counter++;
} else if (crate.state == STATE::KILL) obj.var[7] = 1;
}
}
obj.deactivates = false; // only run age == 0 once
if (obj.state == STATE::KILL) {
jjOBJ@ crate = jjObjects[obj.age];
crate.deactivates = true; // no longer need to keep it around
}
}
bool givePlayerPointsForObject(jjPLAYER@ player, jjOBJ@ obj) {
if (player is null)
return false;
if (obj.points != 0 && (jjGameMode == GAME::SP || jjGameMode == GAME::COOP)) {
player.score += obj.points;
jjPARTICLE@ particle = jjAddParticle(PARTICLE::STRING);
if (particle !is null) {
particle.xPos = obj.xPos;
particle.yPos = obj.yPos;
particle.xSpeed = (-32768 - int(jjRandom() & 0x3FFF)) / 65536.f;
particle.ySpeed = (-65536 - int(jjRandom() & 0x7FFF)) / 65536.f;
particle.string.text = formatInt(obj.points);
}
obj.points = 0;
return true;
}
return false;
}
class parsley : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
obj.behave(BEHAVIOR::PICKUP);
obj.points = 500;
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@, jjPLAYER@ player, int) {
increaseMaxHP(player);
player.invincibility = 70;
player.blink = 0;
obj.behavior = BEHAVIOR::EXPLOSION2;
obj.scriptedCollisions = false;
obj.frameID = 0;
return true;
}
}
void increaseMaxHP(jjPLAYER@ player) {
int HPmem = player.health; // store HP so it doesn't increase
jjChat("/smhealth " + (jjMaxHealth + 1));
for (int i = 0; i < 8; ++i) {
jjAlert("");
}
player.health = HPmem;
}
class FastFeet : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
obj.behave(BEHAVIOR::PICKUP);
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@, jjPLAYER@ player, int) {
fastFeetMode[player.playerID] = 1400;
if (storeMusic == "BEER.it") jjSetModTempo(uint8(defaultModTempo[0] * 1.33));
else if (storeMusic == "DD-BossGBA.it") jjSetModTempo(uint8(defaultModTempo[1] * 1.33));
else if (jjMusicFileName != "DD-CastleFast.it" && noFast) {
storeMusic = jjMusicFileName;
jjMusicLoad("DD-CastleFast.it");
}
obj.behavior = BEHAVIOR::EXPLOSION2;
obj.scriptedCollisions = false;
obj.frameID = 0;
return true;
}
}
class bombDemon : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
jjANIMFRAME@ frame = jjAnimFrames[obj.curFrame];
int nearPlayerID = obj.findNearestPlayer(0x2000);
bool onPlatform = false;
float platformOffset = 0.f;
float py = frame.hotSpotY - frame.coldSpotY;
if (obj.isActive && obj.state != STATE::KILL) {
for (int i = 1; i < jjObjectCount; i++) {
jjOBJ@ other = jjObjects[i];
if ((other.eventID == 231 || other.eventID == 232) && other.yPos > obj.yPos + 50 && other.yPos < obj.yPos + 58 && abs(other.xPos - obj.xPos) <= 30) {
onPlatform = true;
platformOffset = abs(other.xPos - obj.xPos);
obj.ySpeed = 0;
obj.xPos = obj.xPos + other.xSpeed;
break;
}
if ((other.eventID == 231 || other.eventID == 232) && obj.state == STATE::WALK && abs(other.yPos - obj.yPos) <= 24 && int(other.xPos) == int(obj.xPos + (36 * obj.direction))) {
obj.counter = 0;
obj.frameID = 0;
obj.determineCurAnim(ANIM::TURTLE, 2);
jjSample(obj.xPos, obj.yPos, SOUND::FROG_FROG5);
obj.xPos -= 2 * obj.direction;
obj.state = STATE::TURN;
}
if ((other.eventID == 85 || other.eventID == 86 || other.eventID == 87) && obj.doesCollide(other)) {
obj.counter = 0;
obj.ySpeed = 81.0 - float(other.eventID);
obj.determineCurAnim(ANIM::TURTLE, 5);
obj.state = STATE::FALL;
other.state = STATE::SPRING;
jjSample(other.xPos, other.yPos, SOUND::SPRING_SPRING1);
}
}
}
//jjPrint(""+obj.state + " " + onPlatform + " " + platformOffset + " " + obj.direction);
switch (obj.state) {
case STATE::START:
if (obj.creatorType == CREATOR::LEVEL) obj.var[7] = jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0,1);
obj.var[5] = 0; // idle counter
obj.counter = 0;
obj.counterEnd = 64;
obj.putOnGround();
if (jjRandom()%2 == 0) obj.direction = -1;
else obj.direction = 1;
obj.xSpeed = obj.direction * 1;
obj.ySpeed = 0;
obj.yAcc = 0.1;
obj.determineCurAnim(ANIM::TURTLE, 7);
obj.state = STATE::WALK;
case STATE::WALK:
obj.frameID = ((obj.counter / 5) % jjAnimations[obj.curAnim].frameCount);
obj.counter++;
obj.var[5] = obj.var[5] + 1;
if (obj.counterEnd > 0) obj.counterEnd--;
obj.determineCurFrame();
if (obj.counter % 5 == 0) {
if (obj.frameID == 5) jjSample(obj.xPos, obj.yPos, SOUND::TURTLE_SPK1TURT);
else if (obj.frameID == 10) jjSample(obj.xPos, obj.yPos, SOUND::TURTLE_SPK2TURT);
}
if (nearPlayerID > -1 && obj.counter > 20 - (jjDifficulty * 5)) { // attack trigger
if (((obj.direction < 0 && jjLocalPlayers[nearPlayerID].xPos < obj.xPos) ||
(obj.direction > 0 && jjLocalPlayers[nearPlayerID].xPos > obj.xPos)) &&
abs(jjLocalPlayers[nearPlayerID].yPos - obj.yPos) < 64 &&
obj.counterEnd == 0) {
obj.determineCurAnim(ANIM::TURTLE, 0);
obj.counter = 0;
obj.frameID = 0;
jjSample(obj.xPos, obj.yPos, SOUND::TURTLE_NECK);
obj.state = STATE::ATTACK;
}
}
if (obj.var[5] > 224 + (jjDifficulty * 16) + (jjRandom() % 32)) { // idle trigger
obj.counter = 0;
obj.frameID = 0;
obj.determineCurAnim(ANIM::TURTLE, 1);
obj.state = STATE::IDLE;
}
if (((!jjMaskedPixel(int(obj.xPos + obj.xSpeed), int(obj.yPos + py)) && obj.var[7] == 1) || !jjMaskedPixel(int(obj.xPos - (obj.direction * 2)), int(obj.yPos + py))) && !onPlatform) { // fall or turn trigger
obj.counter = 0;
obj.determineCurAnim(ANIM::TURTLE, 5);
obj.state = STATE::FALL;
}
else if (
(!onPlatform && (!jjMaskedVLine(int(obj.xPos + obj.xSpeed), int(obj.yPos + py), 2)
||
jjMaskedVLine(int(obj.xPos + (obj.xSpeed * (frame.width / 2))), int(obj.yPos - (frame.height / 4)), int(frame.height / 2))
||
jjEventGet(uint16(obj.xPos / 32), uint16(obj.yPos / 32)) == 14))
^^
(onPlatform && obj.var[7] == 0 && platformOffset >= 27.f) // don't go *too* close to 30.f
) {
obj.counter = 0;
obj.frameID = 0;
obj.determineCurAnim(ANIM::TURTLE, 2);
jjSample(obj.xPos, obj.yPos, SOUND::FROG_FROG5);
obj.xPos -= 2 * obj.direction;
obj.state = STATE::TURN;
}
else obj.xPos = obj.xPos + obj.xSpeed;
break;
case STATE::IDLE:
if (!onPlatform && !jjMaskedVLine(int(obj.xPos), int(obj.yPos + py), 2)) { // GROUND NOT IS THERE (GONE) (STOLEM)
obj.counter = 0;
obj.determineCurAnim(ANIM::TURTLE, 5);
obj.state = STATE::FALL;
}
if (nearPlayerID > -1 && obj.counter > 20 - (jjDifficulty * 5)) { // unlike turtle can still bite...
if (((obj.direction < 0 && jjLocalPlayers[nearPlayerID].xPos < obj.xPos) ||
(obj.direction > 0 && jjLocalPlayers[nearPlayerID].xPos > obj.xPos)) &&
abs(jjLocalPlayers[nearPlayerID].yPos - obj.yPos) < 64 &&
obj.counterEnd == 0) {
obj.determineCurAnim(ANIM::TURTLE, 0);
obj.counter = 0;
obj.frameID = 0;
obj.state = STATE::ATTACK;
}
}
if (obj.counterEnd > 0) obj.counterEnd--;
obj.counter++;
if (obj.counter % 7 == 0) {
obj.frameID++;
if (obj.frameID == 6) jjSample(obj.xPos, obj.yPos, SOUND::TURTLE_IDLE1, 63, jjRandom()%2 == 0 ? 48000 : 38097);
if (uint(obj.frameID) > jjAnimations[obj.curAnim].frameCount) {
obj.frameID = 0;
obj.counter = 0;
obj.var[5] = 0;
obj.determineCurAnim(ANIM::TURTLE, 7);
obj.state = STATE::WALK;
}
}
obj.determineCurFrame();
break;
case STATE::TURN:
if (!jjMaskedPixel(int(obj.xPos + obj.xSpeed), int(obj.yPos + py)) && obj.var[7] == 1) obj.state == STATE::FALL;
obj.counter++;
if (obj.counter % 7 == 0) obj.frameID++;
if (uint(obj.frameID) == jjAnimations[obj.curAnim].frameCount) {
obj.frameID = 0;
obj.counter = 0;
if (uint(obj.curAnim) == jjAnimSets[ANIM::TURTLE].firstAnim + 2) {
obj.determineCurAnim(ANIM::TURTLE, 3);
obj.direction = obj.direction * -1;
obj.xSpeed = obj.xSpeed * -1;
jjSample(obj.xPos, obj.yPos, SOUND::FROG_FROG2);
} else {
obj.determineCurAnim(ANIM::TURTLE, 7);
obj.state = STATE::WALK;
}
}
obj.determineCurFrame();
break;
case STATE::ATTACK:
obj.counter++;
if (obj.counter % 4 == 0) {
obj.frameID++;
obj.determineCurFrame();
if (obj.frameID == 3) jjSample(obj.xPos, obj.yPos, SOUND::TURTLE_BITE3);
if (uint(obj.frameID) > jjAnimations[obj.curAnim].frameCount) {
obj.determineCurAnim(ANIM::TURTLE, 7);
obj.counter = 0;
obj.counterEnd = 96;
obj.frameID = 0;
obj.state = STATE::WALK;
}
}
break;
case STATE::FALL:
obj.frameID = (jjGameTicks/7) & 1;
obj.determineCurFrame();
obj.special++; // fall timer
if (jjMaskedPixel(int(obj.xPos), int(obj.yPos - py)) && obj.ySpeed < 0) {
obj.ySpeed = 1;
}
else if (obj.ySpeed < 5) {
obj.ySpeed += obj.yAcc;
}
obj.xPos += obj.xSpeed;
obj.yPos += obj.ySpeed;
if (obj.ySpeed > 0 && (jjMaskedPixel(int(obj.xPos + obj.xSpeed), int(obj.yPos + obj.ySpeed + py)) || onPlatform)) { // landing detection
if (obj.special > 10) jjSample(obj.xPos, obj.yPos, SOUND::FROG_FROG2); // don't play sound if the drop is tiny
obj.determineCurAnim(ANIM::TURTLE, 7);
obj.ySpeed = 0;
obj.special = 0;
obj.state = STATE::WALK;
}
break;
case STATE::FREEZE:
if (obj.freeze == 0) obj.state = obj.oldState;
else obj.freeze--;
break;
case STATE::DEACTIVATE:
obj.deactivate();
break;
case STATE::KILL:
if (obj.creatorType == CREATOR::OBJECT) obj.var[8] = 0; // if I'm thinking right this should be exactly one tick
obj.delete();
break;
}
}
void onDraw(jjOBJ@ obj) {
if (obj.var[7] == 1) jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::PLAYER, jjPlayers[31].playerID);
else obj.draw();
// jjDrawString(obj.xPos, obj.yPos - 64, formatInt(obj.counter) + " " + formatInt(obj.frameID), STRING::MEDIUM);
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
int lastHitID = player.playerID;
if (bullet !is null) {
if ((bullet.var[6] & 16) == 0) { //not a fireball
bullet.state = STATE::EXPLODE;
}
if (obj.freeze != 0) {
if (force < 3)
force = 3;
obj.unfreeze(0);
}
if (bullet.objectID == 0) obj.energy = 0;
else {
obj.energy -= bullet.animSpeed;
obj.justHit = 5;
}
if (obj.energy <= 0) {
if (obj.var[9] == 0) {
jjOBJ@ bomb = jjObjects[jjAddObject(OBJECT::TURTLESHELL, obj.xPos, obj.yPos)];
obj.var[9] = 1;
bomb.ySpeed = 4;
bomb.xSpeed = bullet.xSpeed * (0.1 + (jjRandom() % 10 * 0.1));
bomb.determineCurAnim(ANIM::TURTLE, 4);
bomb.var[6] = lastHitID;
bomb.var[7] = obj.var[7];
playRandomSwish(obj.xPos, obj.yPos);
}
if (obj.creatorType == CREATOR::OBJECT) obj.var[8] = 1;
obj.state = STATE::KILL;
obj.grantPickup(jjLocalPlayers[lastHitID], bullet.animSpeed == 0 ? 10 : bullet.animSpeed * 5);
givePlayerPointsForObject(jjLocalPlayers[lastHitID], obj);
}
}
else if (force == 0) { //collision
if (obj.state != STATE::FREEZE)
player.hurt();
} else {
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) { //frozen+running
player.xAcc = 0;
player.xSpeed /= -2;
player.ySpeed = -6;
player.extendInvincibility(-10);
}
if (obj.var[9] == 0) {
jjOBJ@ bomb = jjObjects[jjAddObject(OBJECT::TURTLESHELL, obj.xPos, obj.yPos)];
obj.var[9] = 1;
bomb.ySpeed = 4;
bomb.xSpeed = (jjRandom() % 4 - 2) * 0.1;
bomb.determineCurAnim(ANIM::TURTLE, 4);
bomb.var[6] = lastHitID;
bomb.var[7] = obj.var[7];
playRandomSwish(obj.xPos, obj.yPos);
}
obj.state = STATE::KILL;
givePlayerPointsForObject(jjLocalPlayers[lastHitID], obj);
}
return true;
}
}
class demonBomb : jjBEHAVIORINTERFACE {
demonBomb(jjOBJ@ objectPreset) {
objectPreset.behavior = this;
objectPreset.playerHandling = HANDLING::SPECIAL;
objectPreset.bulletHandling = HANDLING::DETECTBULLET;
objectPreset.scriptedCollisions = true;
objectPreset.isTarget = false;
objectPreset.isFreezable = false;
objectPreset.triggersTNT = false;
objectPreset.deactivates = true;
}
void onBehave(jjOBJ@ obj) {
float dx, dy, distance;
if (obj.state == STATE::START) {
obj.counterEnd = 250;
obj.state = STATE::FLY;
}
if (obj.state == STATE::FLY) {
int calc = 0;
int calc2 = 0;
obj.counterEnd--;
if (obj.counterEnd < 125 && jjGameTicks % 10 == 0) {
obj.justHit = 5;
}
obj.xSpeed *= 0.984375;
obj.xPos += obj.xSpeed;
obj.yPos += obj.ySpeed;
bool noUp = jjMaskedPixel(int(obj.xPos), int(obj.yPos) - 4);
bool noDown = jjMaskedPixel(int(obj.xPos), int(obj.yPos) + 16);
bool noLeft = jjMaskedPixel(int(obj.xPos) - 8, int(obj.yPos));
bool noRight = jjMaskedPixel(int(obj.xPos) + 8, int(obj.yPos));
if (abs(obj.xSpeed) > 0.015625) {
for (int i = 1; i < jjObjectCount; i++) {
jjOBJ@ other = jjObjects[i];
if (other.isActive && (other.playerHandling == HANDLING::ENEMY || other.playerHandling == HANDLING::SPECIAL) && obj.doesCollide(other) && other.eventID != 237) {
if (jjPlayers[obj.var[6]].objectHit(other, -1, other.playerHandling)) {
if (obj.xSpeed > 0) noRight = true;
else if (obj.xSpeed < 0) noLeft = true;
}
}
}
}
for (int i = 1; i < jjObjectCount; i++) {
jjOBJ@ other = jjObjects[i];
if (other.eventID == OBJECT::DESTRUCTSCENERY && other.state == STATE::SLEEP && other.var[6] == 0 && obj.doesCollide(other)) {
other.state = STATE::KILL;
givePlayerPointsForObject(jjLocalPlayers[obj.var[6]], other);
}
}
if (obj.xSpeed > 0 && noRight) {
calc = 1 + int(obj.xSpeed);
calc2 = jjMaskedTopVLine(int(obj.xPos + 8), int(obj.yPos) + 16 - calc, calc);
if (calc2 <= 1 || calc2 >= calc) {
obj.xPos -= obj.xSpeed;
obj.xSpeed *= -1;
} else {
obj.yPos -= calc;
obj.xSpeed *= 0.9375;
}
} else if (obj.xSpeed < 0 && noLeft) {
calc = 1 - int(obj.xSpeed);
calc2 = jjMaskedTopVLine(int(obj.xPos - 8), int(obj.yPos) + 16 - calc, calc);
if (calc2 <= 1 || calc2 >= calc) {
obj.xPos -= obj.xSpeed;
obj.xSpeed *= -1;
} else {
obj.yPos -= calc;
obj.xSpeed *= 0.9375;
}
}
if (obj.ySpeed < 0 && noUp) {
obj.yPos -= obj.ySpeed;
obj.ySpeed = -obj.ySpeed * 0.5;
} else if (noDown) {
calc = jjMaskedTopVLine(int(obj.xPos), int(obj.yPos), 16);
if (calc != 0) {
obj.yPos -= 16 - calc;
} else {
obj.yPos -= obj.ySpeed;
}
if (obj.ySpeed > 1) {
playRandomImpact(obj.xPos, obj.yPos);
}
obj.ySpeed = -obj.ySpeed * 0.5;
} else {
if (obj.yPos > jjWaterTarget) {
obj.ySpeed += 0.03125;
if (obj.ySpeed > 4) obj.ySpeed = 4;
else if (obj.ySpeed < -4) obj.ySpeed -4;
} else {
obj.ySpeed += 0.125;
if (obj.ySpeed > 8) obj.ySpeed = 8;
}
}
}
if (obj.counterEnd <= 0 && obj.isActive) {
jjOBJ@ boom = jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos)];
boom.determineCurAnim(ANIM::AMMO, 81);
// boom.playerHandling = HANDLING::ENEMYBULLET;
jjSample(obj.xPos, obj.yPos, SOUND::COMMON_EXPL_TNT);
for (int i = 1; i < jjObjectCount; i++) {
jjOBJ@ other = jjObjects[i];
dx = abs(obj.xPos - other.xPos);
dy = abs(obj.yPos - other.yPos);
distance = sqrt(pow(dx, 2) + pow(dy, 2)); // A Perfect Circle...
if (other.eventID == OBJECT::DESTRUCTSCENERYBOMB && other.state == STATE::SLEEP && distance <= 64) {
other.state = STATE::KILL;
givePlayerPointsForObject(jjLocalPlayers[obj.var[6]], other);
} else if (other.bulletHandling == HANDLING::HURTBYBULLET && distance <= 64) {
other.energy -= 1;
if (other.energy <= 0) givePlayerPointsForObject(jjLocalPlayers[obj.var[6]], other);
}
if (obj.findNearestPlayer(64 * 64) > -1) jjLocalPlayers[obj.findNearestPlayer(64 * 64)].hurt(1);
}
obj.delete();
}
if (obj.state == STATE::DEACTIVATE) obj.delete();
}
void onDraw(jjOBJ@ obj) {
obj.lightType = LIGHT::POINT2;
obj.light = 100;
obj.frameID = ((jjGameTicks/5) % jjAnimations[obj.curAnim].frameCount);
obj.determineCurFrame();
if (obj.justHit > 0) jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, 1, SPRITE::SINGLECOLOR, 15, 1);
else if (obj.var[7] == 1) jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::PLAYER, jjPlayers[31].playerID);
else obj.draw();
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
if (bullet !is null) {
obj.xSpeed = obj.xSpeed + bullet.xSpeed;
if (obj.causesRicochet) {
if ((bullet.var[6] & 6) == 0) //not fire-based, not a laser beam
bullet.ricochet();
else if ((bullet.var[6] & 4) == 0) //not a laser beam
bullet.delete();
} else if ((bullet.var[6] & 16) == 0) //not a fireball
bullet.state = STATE::EXPLODE;
jjSample(obj.xPos, obj.yPos, SOUND::TURTLE_HITSHELL);
}
obj.var[6] = player.playerID;
return true;
}
}
void playRandomImpact(float xPos, float yPos) {
switch(jjRandom()%8) {
case 0: jjSample(xPos, yPos, SOUND::COMMON_IMPACT1); break;
case 1: jjSample(xPos, yPos, SOUND::COMMON_IMPACT2); break;
case 2: jjSample(xPos, yPos, SOUND::COMMON_IMPACT3); break;
case 3: jjSample(xPos, yPos, SOUND::COMMON_IMPACT4); break;
case 4: jjSample(xPos, yPos, SOUND::COMMON_IMPACT5); break;
case 5: jjSample(xPos, yPos, SOUND::COMMON_IMPACT6); break;
case 6: jjSample(xPos, yPos, SOUND::COMMON_IMPACT7); break;
case 7: jjSample(xPos, yPos, SOUND::COMMON_IMPACT8); break;
}
}
void playRandomSwish(float xPos, float yPos) {
switch(jjRandom()%8) {
case 0: jjSample(xPos, yPos, SOUND::COMMON_SWISH1); break;
case 1: jjSample(xPos, yPos, SOUND::COMMON_SWISH2); break;
case 2: jjSample(xPos, yPos, SOUND::COMMON_SWISH3); break;
case 3: jjSample(xPos, yPos, SOUND::COMMON_SWISH4); break;
case 4: jjSample(xPos, yPos, SOUND::COMMON_SWISH5); break;
case 5: jjSample(xPos, yPos, SOUND::COMMON_SWISH6); break;
case 6: jjSample(xPos, yPos, SOUND::COMMON_SWISH7); break;
case 7: jjSample(xPos, yPos, SOUND::COMMON_SWISH8); break;
}
}
class bumpFix : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
obj.behave(BEHAVIOR::BUMP);
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
if (force == -1 && obj.findNearestPlayer(64 * 64) < 0) return true;
else {
obj.scriptedCollisions = false;
if (bullet is null)
player.objectHit(obj, force, obj.playerHandling);
else
bullet.objectHit(obj, obj.playerHandling);
obj.scriptedCollisions = true;
return true;
}
}
}
class lavaBlock : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
if (obj.var[0] > 0) obj.var[0] = obj.var[0] - 1;
switch (obj.state) {
case STATE::START:
obj.yPos = obj.yOrg + jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0,5);
obj.var[1] = jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 5,1);
obj.state = STATE::IDLE;
case STATE::IDLE:
obj.frameID = 0;
obj.determineCurFrame();
break;
case STATE::DEACTIVATE:
obj.deactivate();
break;
}
}
void onDraw(jjOBJ@ obj) {
// don't draw
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
if (bullet is null) {
if (obj.findNearestPlayer(64 * 64) >= 0) {
if (force <= 0) {
player.hurt(2);
}
if (player.xSpeed * player.xSpeed > 0.2) {
player.xSpeed = player.xSpeed * 0.1;
}
if (player.ySpeed > 0 && obj.var[0] == 0 && obj.var[1] == 0) {
obj.var[0] = 25;
playRandomSquish(player.xPos, obj.yPos - 16);
jjOBJ@ splash = jjObjects[jjAddObject(OBJECT::EXPLOSION, player.xPos, obj.yPos - 16, obj.objectID, CREATOR::OBJECT)];
splash.determineCurAnim(ANIM::CUSTOM[9], 1);
}
}
return true;
}
return false;
}
}
void playRandomSquish(float xPos, float yPos, int volume = 63) {
switch(jjRandom()%5) {
case 0: jjSample(xPos, yPos, SOUND::FROG_FROG1, volume); break;
case 1: jjSample(xPos, yPos, SOUND::FROG_FROG2, volume); break;
case 2: jjSample(xPos, yPos, SOUND::FROG_FROG3, volume); break;
case 3: jjSample(xPos, yPos, SOUND::FROG_FROG4, volume); break;
case 4: jjSample(xPos, yPos, SOUND::FROG_FROG5, volume); break;
}
}
void DecorativeVine(jjOBJ@ obj) {
if (obj.state == STATE::START) {
const uint eventX = uint(obj.xPos) >> 5, eventY = uint(obj.yPos) >> 5;
const int length = jjParameterGet(eventX,eventY, 0, 3);
obj.age = jjRandom();
obj.curFrame += length;
obj.var[0] = 50 + length*16;
obj.var[1] = 4 - jjParameterGet(eventX,eventY, 3, 1);
obj.xPos += jjParameterGet(eventX,eventY, 4, 3) << 2;
obj.yPos += jjParameterGet(eventX,eventY, 8, 3) << 2;
obj.state = STATE::IDLE;
} else if (obj.state == STATE::DEACTIVATE) {
obj.deactivate();
} else {
if (!jjLowDetail)
jjDrawSwingingVineSpriteFromCurFrame(int(obj.xPos), int(obj.yPos), obj.curFrame, obj.var[0], int(jjSin(obj.age += 6) * 0x4000), SPRITE::NORMAL,0, obj.var[1]);
if ((jjSin(obj.age) * 0x4000) % 12000 == 0) playRandomRattle(obj.xPos, obj.yPos);
}
}
void playRandomRattle(float xPos, float yPos) {
switch(jjRandom()%2) {
case 0: jjSample(xPos, yPos, SOUND::INTRO_UP1, 63, 42000 + jjRandom()%4000); break;
case 1: jjSample(xPos, yPos, SOUND::INTRO_UP2, 63, 42000 + jjRandom()%4000); break;
}
}
class pentagram : jjBEHAVIORINTERFACE {
int lastHitID, spawnID;
pentagram(jjOBJ@ objectPreset) {
objectPreset.behavior = this;
objectPreset.playerHandling = HANDLING::SPECIAL;
objectPreset.scriptedCollisions = true;
objectPreset.bulletHandling = HANDLING::IGNOREBULLET;
objectPreset.isTarget = false;
objectPreset.isFreezable = false;
objectPreset.triggersTNT = false;
objectPreset.deactivates = false;
objectPreset.points = 300;
}
void onBehave(jjOBJ@ obj) {
switch (obj.state) {
case STATE::START:
obj.putOnGround();
obj.counterEnd = 0;
obj.counter = 245;
obj.var[1] = jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0,2);
obj.var[2] = jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 2,1);
switch (obj.var[1]) {
case 0:
case 1: spawnID = 117; break;
case 2: spawnID = 125; break;
case 3:
case 4: spawnID = 120; break;
}
obj.state = STATE::IDLE;
case STATE::IDLE:
if (jjGameTicks % 6 == 0) {
jjPARTICLE@ particle = jjAddParticle(PARTICLE::SMOKE);
particle.xPos = obj.xPos - 28 + jjRandom() % 56;
particle.yPos = obj.yPos - 5 + jjRandom() % 10;
}
if (obj.counterEnd < 3 && obj.counter == 40) {
jjOBJ@ sparkl = jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos)];
sparkl.determineCurAnim(ANIM::PICKUPS, 86);
}
if (obj.counterEnd < 3 && obj.counter <= 0) {
jjOBJ@ spawn = jjObjects[jjAddObject(spawnID, obj.xOrg - 28 + jjRandom() % 56, obj.yOrg)];
spawn.creatorID = uint(obj.objectID);
spawn.points = 0; // prevent farming
spawn.deactivates = false; // keep detecting deaths
if (obj.var[2] == 1) spawn.direction = jjRandom() % 2 - 1;
if (obj.var[1] == 1 || obj.var[1] == 4) spawn.var[7] = 1;
obj.counter = 490;
obj.creatorType = CREATOR::OBJECT;
} else if (obj.counter > 0) obj.counter--;
obj.counterEnd = 0;
for (int i = 1; i < jjObjectCount; i++) {
jjOBJ@ other = jjObjects[i];
if (other.creatorID == uint(obj.objectID) && other.isActive) obj.counterEnd += 1;
}
break;
case STATE::DEACTIVATE:
obj.deactivate();
break;
case STATE::KILL:
if (obj.isActive) {
jjOBJ@ poof = jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos)];
poof.determineCurAnim(ANIM::AMMO, 2);
}
obj.delete();
break;
}
}
void onDraw(jjOBJ@ obj) {
if (obj.state == STATE::IDLE) {
obj.determineCurAnim(ANIM::CUSTOM[30], 0);
obj.frameID = ((jjGameTicks/12) % jjAnimations[obj.curAnim].frameCount);
obj.determineCurFrame();
obj.draw();
obj.lightType = LIGHT::FLICKER;
obj.light = 100;
}
if (obj.state == STATE::KILL) {
obj.determineCurAnim(ANIM::CUSTOM[30], 1);
obj.frameID = 0;
obj.determineCurFrame();
}
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
if (player !is null && force != 0) {
lastHitID = player.playerID;
playRandomSnuff(obj.xPos, obj.yPos);
obj.state = STATE::KILL;
givePlayerPointsForObject(jjLocalPlayers[lastHitID], obj);
obj.particlePixelExplosion(2);
}
return true;
}
void playRandomSnuff(float xPos, float yPos) {
switch(jjRandom()%2) {
case 0: jjSample(xPos, yPos, SOUND::INTRO_HITSPAZ); break;
case 1: jjSample(xPos, yPos, SOUND::INTRO_HITTURT); break;
}
}
}
class littleBat : jjBEHAVIORINTERFACE {
float offset_x,offset_y,ceiling_y;
int distance_counter,rolled_angle;
void onBehave(jjOBJ@ obj) {
// jjANIMFRAME@ frame = jjAnimFrames[obj.curFrame];
//jjDebug("obj.xPos = " + formatFloat(obj.xPos, '', 8, 2));
//jjDebug("obj.yPos = " + formatFloat(obj.yPos, '', 8, 2));
switch (obj.state) {
case STATE::START:
while (!jjMaskedPixel(int(obj.xPos), int(obj.yPos) - 9)) obj.yPos = obj.yPos - 1; // fall up
ceiling_y = obj.yPos;
obj.counter = jjRandom() % 1023;
//rolled_angle = jjRandom() % 512 - 256;
//if (rolled_angle < 0) rolled_angle = 1023 - rolled_angle;
//obj.counter = rolled_angle;
obj.state = STATE::IDLE;
break;
case STATE::IDLE:
obj.counter++;
obj.determineCurAnim(ANIM::CUSTOM[12], 2);
obj.frameID = 0;
if (obj.findNearestPlayer(128 * 128) >= 0)
{
jjSample(obj.xPos, obj.yPos, SOUND::P2_THROW, 23);
obj.state = STATE::FLY;
distance_counter = 64;
offset_x = (obj.xOrg + jjSin((obj.counter + distance_counter)*4)*32 - obj.xPos)/64;
//offset_y = (obj.yOrg + jjCos((obj.counter + distance_counter)*2)*24 - obj.yPos)/64;
offset_y = (ceiling_y + jjCos((obj.counter + distance_counter)*2)*24 - obj.yPos)/64;
}
break;
case STATE::FLY:
if (offset_x < 0)
obj.direction = -1;
else
obj.direction = 1;
obj.xPos += offset_x;
obj.yPos += offset_y;
distance_counter -= 1;
if (distance_counter <= 0) obj.state = STATE::FLOAT;
obj.counter++;
obj.determineCurAnim(ANIM::CUSTOM[12], 1);
obj.frameID = obj.counter / 6 % jjAnimations[obj.curAnim].frameCount;
break;
case STATE::FLOAT:
obj.counter += 1 + (jjRandom() % 3);
obj.xPos = obj.xOrg + jjSin(obj.counter * 4) * 32 + (int(jjRandom() % 7) - 3);
batChannel = jjSampleLooped(obj.xPos, obj.yPos, SOUND::P2_FART, batChannel, 23);
if ((obj.counter % 255) < 128) {
if (obj.direction > 0)
playRandomBat(obj.xPos, obj.yPos, 42000 + jjRandom()%4000);
obj.direction = -1;
} else {
if (obj.direction < 0)
playRandomBat(obj.xPos, obj.yPos, 42000 + jjRandom()%4000);
obj.direction = +1;
}
obj.yPos = obj.yOrg + jjCos(obj.counter * 2) * 24 + (int(jjRandom() % 7) - 3);
obj.determineCurAnim(ANIM::CUSTOM[12], 1);
obj.frameID = obj.counter / 6 % jjAnimations[obj.curAnim].frameCount;
if (obj.findNearestPlayer(256 * 256) == -1)
{
obj.state = STATE::STOP;
obj.counter = 0;
distance_counter = 64;
offset_x = (obj.xOrg - obj.xPos) / 64;
offset_y = (ceiling_y - obj.yPos) / 64;
}
break;
case STATE::STOP:
if (offset_x < 0)
obj.direction = -1;
else
obj.direction = 1;
obj.xPos += offset_x;
obj.yPos += offset_y;
distance_counter -= 1;
if (distance_counter <= 0) {
obj.state = STATE::IDLE;
}
obj.counter++;
obj.determineCurAnim(ANIM::CUSTOM[12], 1);
obj.frameID = obj.counter / 6 % jjAnimations[obj.curAnim].frameCount;
break;
case STATE::FREEZE:
if (obj.freeze > 0) obj.freeze--;
if (obj.freeze == 0) {
obj.state = STATE::STOP;
obj.counter = 0;
distance_counter = 64;
offset_x = (obj.xOrg - obj.xPos) / 64;
offset_y = (ceiling_y - obj.yPos) / 64;
}
if (obj.freeze < 30) {
obj.yPos += 4 * (jjRandom() % 16383);
obj.state = obj.oldState;
}
break;
case STATE::KILL:
obj.delete();
break;
case STATE::DEACTIVATE:
//jjDebug("obj.xPos = " + formatFloat(obj.xPos, '', 8, 2));
//jjDebug("obj.yPos = " + formatFloat(obj.yPos, '', 8, 2));
obj.deactivate();
break;
}
}
void onDraw(jjOBJ@ obj) {
if (obj.isActive && obj.state != STATE::KILL) {
obj.determineCurFrame();
obj.draw();
}
}
void playRandomBat(float xPos, float yPos, int freq) {
switch(jjRandom()%4) {
case 0: jjSample(xPos, yPos, SOUND::P2_FOEW1, 47, freq); break;
case 1: jjSample(xPos, yPos, SOUND::P2_FOEW4, 47, freq); break;
case 2: jjSample(xPos, yPos, SOUND::P2_FOEW5, 47, freq); break;
case 3: jjSample(xPos, yPos, SOUND::P2_POEP, 47, freq); break;
}
}
}
void flickerWrapper(jjOBJ@ obj) {
if (jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0,3) > 3) {
obj.eventID = 157;
obj.behave(BEHAVIOR::FLICKERGEM);
obj.points = 50;
obj.var[0] = 0;
if (jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0,3) == 4) obj.determineCurAnim(ANIM::PICKUPS, 92);
if (jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0,3) == 5) obj.determineCurAnim(ANIM::CUSTOM[3], 43);
if (jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0,3) == 6) obj.determineCurAnim(ANIM::PICKUPS, 15);
}
else {
obj.behave(BEHAVIOR::FLICKERGEM);
obj.var[0] = jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0,3) + 1;
int points = jjObjectPresets[OBJECT::REDGEM + obj.var[0] - 1].points;
obj.points = points;
obj.determineCurAnim(ANIM::PICKUPS, 22);
}
}
void SuperGemWrapper(jjOBJ@ obj) {
if (obj.state == STATE::START) {
if (jjParameterGet(uint(obj.xPos) >> 5, uint(obj.yPos) >> 5, 0,3) < 4)
obj.var[0] = jjParameterGet(uint(obj.xPos) >> 5, uint(obj.yPos) >> 5, 0,3) + 1; //color
else {
obj.var[0] = 0;
obj.age = jjParameterGet(uint(obj.xPos) >> 5, uint(obj.yPos) >> 5, 0,3) - 3;
}
}
obj.behave(BEHAVIOR::SUPERGEM);
if (obj.state == STATE::SLEEP && obj.var[0] == 0) {
if (obj.age == 1) obj.determineCurAnim(ANIM::CUSTOM[3], 6);
if (obj.age == 2) obj.determineCurAnim(ANIM::CUSTOM[3], 42);
if (obj.age == 3) obj.determineCurAnim(ANIM::CUSTOM[3], 73);
}
if (obj.state == STATE::ACTION) {
if (jjParameterGet(uint(obj.xPos) >> 5, uint(obj.yPos) >> 5, 0,3) < 4) jjSample(obj.xOrg, obj.yOrg, SOUND::INTRO_GRAB);
else jjSample(obj.xOrg, obj.yOrg, SOUND::MONKEY_SPLUT);
}
}
class tuffDemon : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
obj.behave(BEHAVIOR::FATCHICK, false);
if (obj.state == STATE::WALK) {
obj.determineCurAnim(ANIM::CUSTOM[27], 0);
obj.frameID = ((obj.counter / 6) % jjAnimations[obj.curAnim].frameCount);
obj.determineCurFrame();
}
if (obj.state == STATE::ACTION) obj.determineCurAnim(ANIM::CUSTOM[27], 1);
}
void onDraw(jjOBJ@ obj) {
obj.draw();
}
}
class spider : jjBEHAVIORINTERFACE {
int webLength, storeAngle;
jjOBJ@ web;
void onBehave(jjOBJ@ obj) {
switch (obj.state) {
case STATE::START:
webLength = 1;
@web = jjObjects[jjAddObject(OBJECT::SWINGINGVINE, obj.xPos, obj.yPos)];
obj.state = STATE::IDLE;
case STATE::IDLE:
obj.determineCurAnim(ANIM::CUSTOM[31], 0);
obj.animSpeed = 12;
if (obj.findNearestPlayer(0x4000) > -1 && jjLocalPlayers[obj.findNearestPlayer(0x4000)].yPos >= obj.yOrg) obj.state = STATE::ATTACK;
obj.xPos = obj.xOrg;
obj.yPos = obj.yOrg;
break;
case STATE::ATTACK:
if (webLength < 128) {
webLength += 4;
obj.determineCurAnim(ANIM::CUSTOM[31], 2);
obj.animSpeed = 9;
} else {
obj.determineCurAnim(ANIM::CUSTOM[31], 0);
obj.animSpeed = 12;
}
if (obj.findNearestPlayer(0x10000) == -1 || jjLocalPlayers[obj.findNearestPlayer(0x10000)].yPos < obj.yOrg) obj.state = STATE::HIDE;
obj.xPos = web.xPos;
obj.yPos = web.yPos - 8;
break;
case STATE::HIDE:
obj.determineCurAnim(ANIM::CUSTOM[31], 3);
obj.animSpeed = 9;
if (webLength == 1) obj.state = STATE::IDLE;
else webLength -= 2;
obj.xPos = web.xPos;
obj.yPos = web.yPos - 16;
break;
case STATE::FREEZE:
if (obj.freeze-- <= 1) {
obj.freeze = 0;
obj.state = obj.oldState;
}
web.var[3] = storeAngle;
break;
case STATE::KILL:
web.delete();
obj.delete();
break;
case STATE::DEACTIVATE:
web.delete(); // will be created again when state is start so chill
obj.deactivate();
break;
}
web.var[1] = webLength;
}
void onDraw(jjOBJ@ obj) {
if (obj.isActive && obj.state != STATE::KILL) {
obj.frameID = ((jjGameTicks / obj.animSpeed) % jjAnimations[obj.curAnim].frameCount);
obj.determineCurFrame();
if (obj.state == STATE::ATTACK || obj.state == STATE::HIDE) jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, int(web.var[2] / 256));
else obj.draw();
}
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
if (bullet !is null && bullet.var[3] == 3) storeAngle = web.var[3];
return true;
}
}
class swingingWeb : jjBEHAVIORINTERFACE { // had to add this to obscure a black line leading the vine; unfortunately this draws the web in front of the spider but there doesn't seem to be a better solution
void onBehave(jjOBJ@ obj) {
obj.behave(BEHAVIOR::SWINGINGVINE, false);
}
void onDraw(jjOBJ@ obj) {
if (obj.var[1] > 1 && obj.var[4] == 0) {
jjDrawSwingingVineSpriteFromCurFrame(
obj.xOrg,
obj.yOrg,
jjAnimations[jjAnimSets[ANIM::VINE].firstAnim + 1].firstFrame,
obj.var[1],
obj.var[2],
SPRITE::PLAYER,
jjPlayers[30].playerID
);
}
}
}
class demonEdit : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
switch(obj.state) {
case STATE::START:
obj.determineCurAnim(ANIM::DEMON, 0);
obj.putOnGround();
obj.state = STATE::IDLE;
obj.var[7] = jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0,1);
obj.xSpeed = obj.ySpeed = obj.xAcc = obj.yAcc = 0; // all initially 0
break;
case STATE::IDLE:
if (obj.ySpeed != 0) {
obj.determineCurAnim(ANIM::DEMON, 3);
obj.frameID = 2;
obj.determineCurFrame();
} else {
obj.determineCurAnim(ANIM::DEMON, 0); // additional CPU load but unfortunately necessary
obj.frameID = (jjGameTicks / 4) % jjAnimations[obj.curAnim].frameCount;
obj.determineCurFrame();
if (jjGameTicks % 4 == 0 && obj.frameID == 5) {
jjSample(obj.xPos, obj.yPos, SOUND::COMMON_HIBELL);
}
}
obj.counter++;
if (obj.counter > 140) {
int playerID = obj.findNearestPlayer(256 * 256);
if (playerID >= 0) {
jjPLAYER@ victim = jjPlayers[playerID];
if (victim.xPos < obj.xPos) {
obj.direction = -1;
} else if (victim.xPos > obj.xPos) {
obj.direction = 1;
}
if (abs(victim.yPos - obj.yPos) < 64) {
obj.frameID = 0;
obj.counter = 0;
obj.state = STATE::ATTACK;
obj.determineCurAnim(ANIM::DEMON, 1);
jjSample(obj.xPos, obj.yPos, SOUND::COMMON_FOEW2);
}
}
}
break;
case STATE::ATTACK:
obj.counter++;
if (obj.counter > 4) {
obj.frameID++;
obj.counter = 0;
if (uint16(obj.frameID) >= jjAnimations[obj.curAnim].frameCount) {
obj.state = STATE::WALK;
obj.determineCurAnim(ANIM::DEMON, 2);
obj.counter = 0;
obj.frameID = 0;
jjSample(obj.xPos, obj.yPos, SOUND::DEMON_RUN);
}
}
obj.determineCurFrame();
break;
case STATE::WALK: // STATE::FALL also merged into this. Inconvenient, I know.
obj.xSpeed = (2 + jjSin(int(abs(obj.frameID-11))*32)) / 2.0;
obj.xSpeed *= obj.direction;
obj.age = 1;
if (jjGameTicks % 8 == 0) {
obj.frameID++;
}
obj.determineCurFrame();
obj.counter++;
if (obj.counter > 140) {
int playerID = obj.findNearestPlayer(96 * 96);
if (playerID < 0) {
// The original code makes an access to a nonexistent player, which is questionable desgin...
// I think it makes more sense to only check playerID < 0 then take action accordingly. If
// this doesn't give you desired behavior then remove the uncommented code and uncomment
// the below code block, but I'm not responsible for any weird bugs here.
/*
float dx = jjPlayers[playerID].xPos - obj.xPos;
if ((obj.direction < 0 && dx < 0) || (obj.direction > 0 && dx > 0)) {
obj.state = STATE::STOP;
obj.counter = 0;
obj.frameID = 0;
obj.determineCurAnim(ANIM::DEMON, 3);
}
*/
obj.state = STATE::STOP;
obj.counter = 0;
obj.frameID = 0;
obj.determineCurAnim(ANIM::DEMON, 3);
}
}
break;
case STATE::WAIT:
obj.frameID = (jjGameTicks / 8) % jjAnimations[obj.curAnim].frameCount;
obj.determineCurFrame();
break;
case STATE::STOP:
obj.xSpeed = (obj.xSpeed * 7) / 8;
obj.counter++;
if (obj.counter > 4) {
obj.frameID++;
obj.counter = 0;
if (uint16(obj.frameID) >= jjAnimations[obj.curAnim].frameCount) {
obj.frameID = 0;
obj.determineCurAnim(ANIM::DEMON, 0);
obj.state = STATE::IDLE;
obj.xSpeed = 0;
obj.counter = 0;
}
}
obj.determineCurFrame();
break;
// The below states have the least likely side effects with
// custom behavior that it doesn't seem worth rewriting them.
case STATE::FREEZE:
case STATE::KILL:
case STATE::DEACTIVATE:
obj.behave(BEHAVIOR::DEMON);
break;
}
// The demon will have "physics" forever from now on when they lock on a target for the first time.
// This applies to all states! That means a STATE::IDLE demon can also fall (if they were mid-air during STATE::WALK):
if (obj.age > 0 && obj.state != STATE::FREEZE) { // ok that's one state we actually don't want physics in
handleMotion(obj, false, false);
handleFall(obj);
}
}
// The original demon behavior caps xSpeed and ySpeed, but if you don't want that then pass false to any of them.
void handleMotion(jjOBJ@ obj, bool capSpeedX, bool capSpeedY) {
jjANIMFRAME@ frame = jjAnimFrames[obj.curFrame];
float px = 0, py = 0;
py = frame.hotSpotY - frame.coldSpotY;
if (obj.xSpeed < 0) {
px = -frame.hotSpotX - frame.width;
} else {
px = frame.hotSpotX + frame.width;
}
// Check for edges if not Pinkie
if (obj.var[7] == 0 && !jjMaskedPixel(int(obj.xPos + (px * obj.direction) + obj.xSpeed), int(obj.yPos + py))) obj.direction = obj.xSpeed < 0 ? -1 : 1;
// Check for walls
if (jjMaskedPixel(int(obj.xPos + px), int(obj.yPos))) {
obj.xSpeed *= -1;
obj.direction = obj.xSpeed < 0 ? -1 : 1; // Maybe this needs to be outside?
obj.xPos += obj.xSpeed;
}
// and ceilings
if (jjMaskedPixel(int(obj.xPos), int(obj.yPos - py)) && obj.ySpeed < 0) {
obj.ySpeed = 0;
}
obj.xPos += obj.xSpeed;
obj.yPos += obj.ySpeed;
if (obj.yPos > jjWaterLevel) {
obj.xSpeed += obj.xAcc / 4;
obj.ySpeed += obj.yAcc / 4;
if (capSpeedX) {
if (obj.xSpeed > 3) {
obj.xSpeed = 3;
} else if (obj.xSpeed < -3) {
obj.xSpeed = -3;
}
}
if (capSpeedY) {
if (obj.ySpeed > 2) {
obj.ySpeed = 2;
} else if (obj.ySpeed < -2) {
obj.ySpeed = -2;
}
}
} else {
obj.xSpeed += obj.xAcc;
obj.ySpeed += obj.yAcc;
if (capSpeedX) {
if (obj.xSpeed > 6) {
obj.xSpeed = 6;
} else if (obj.xSpeed < -6) {
obj.xSpeed = -6;
}
}
if (capSpeedY) {
if (obj.ySpeed > 4) {
obj.ySpeed = 4;
} else if (obj.ySpeed < -4) {
obj.ySpeed = -4;
}
}
}
}
void handleFall(jjOBJ@ obj) {
jjANIMFRAME@ frame = jjAnimFrames[obj.curFrame];
float py = frame.hotSpotY - frame.coldSpotY;
float px = 0;
if (obj.xSpeed < 0) {
px = -frame.hotSpotX - (frame.width / 2);
} else {
px = (frame.width / 2) + frame.hotSpotX;
}
if (jjMaskedPixel(int(obj.xPos + px), int(obj.yPos + py))) {
obj.yAcc = 0;
obj.ySpeed = 0;
int event = jjEventGet(int(obj.xPos) / 32, int(obj.yPos) / 32); // redundancy-efficiency trade off
if (event >= OBJECT::REDSPRING && event <= OBJECT::BLUESPRING) {
for (int i = 1; i < jjObjectCount; i++) {
jjOBJ@ spring = jjObjects[i];
if (int(spring.eventID) == event && (obj.doesCollide(spring) || (abs(obj.xPos - spring.xPos) < 4 && abs(obj.yPos - spring.yPos) < 8))) {
// This was your original speed
// obj.ySpeed = 81.5 - int(spring.eventID);
// This is the revised version, based on the actual values ...
// obj.ySpeed = -(array<float> = {16,24,32})[spring.eventID - OBJECT::REDSPRING];
obj.ySpeed = -(array<float> = {12,18,24})[spring.eventID - OBJECT::REDSPRING]; // lighter than player = less pressure on spring = less bounce!
// ... even thats too fast if ySpeed isn't capped, so you gotta scale it somehow:
obj.ySpeed /= 2.5;
spring.state = STATE::SPRING;
jjSample(spring.xPos, spring.yPos, SOUND::SPRING_SPRING1);
}
}
}
} else {
// Froducish acc (close to player acc)
// obj.yAcc = 0.125;
obj.yAcc = 0.09; // demon is lighter than player
}
}
void onDraw(jjOBJ@ obj) {
if (obj.var[7] == 1 && obj.justHit == 0 && obj.freeze == 0) jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::PLAYER, jjPlayers[31].playerID);
else obj.draw();
// jjDrawString(obj.xPos, obj.yPos - 32, formatFloat(obj.ySpeed, '', 6, 2), STRING::MEDIUM);
// jjDrawString(obj.xPos, obj.yPos - 64, formatInt(obj.oldState), STRING::MEDIUM);
}
}
void bombEdit(jjOBJ@ obj) {
float dx, dy, distance;
obj.behave(BEHAVIOR::BOMB);
if (obj.state == STATE::EXPLODE) {
for (int i = 1; i < jjObjectCount; i++) {
jjOBJ@ other = jjObjects[i];
dx = abs(obj.xPos - other.xPos);
dy = abs(obj.yPos - other.yPos);
distance = sqrt(pow(dx, 2) + pow(dy, 2)); // A Perfect Circle...
if (other.eventID == OBJECT::DESTRUCTSCENERYBOMB && other.state == STATE::SLEEP && distance <= 64) {
other.state = STATE::KILL;
// givePlayerPointsForObject(jjLocalPlayers[obj.var[6]], other); -- don't know how to determine this yet :(
}
}
}
}
class brickTrap : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
const uint xTile = uint(obj.xOrg)>>5, yTile = uint(obj.yOrg)>>5;
int animTileID = jjParameterGet(xTile, yTile, 0, 1) == 0 ? 123 : 124;
if (obj.state == STATE::START) {
jjTileSet(4, xTile, yTile, animTileID | TILE::ANIMATED);
const auto@ tile = jjAnimatedTiles[animTileID];
const auto@ Frames = tile.getFrames();
obj.var[1] = Frames[Frames.length-1];
obj.yPos = obj.yOrg + 16;
obj.state = STATE::SLEEP;
}
if (obj.state == STATE::SLEEP) {
jjPARTICLE@ particle = jjAddParticle(PARTICLE::ICETRAIL);
if (particle !is null) {
particle.xPos = obj.xPos + (jjRandom()%28 - 15);
particle.yPos = obj.yPos;
particle.icetrail.color = 72;
particle.icetrail.colorStop = 80;
}
if (jjLocalPlayers[obj.findNearestPlayer(128 * 128)].yPos >= obj.yPos && abs(jjLocalPlayers[obj.findNearestPlayer(128 * 128)].xPos - obj.xPos) <= 64.f) {
jjOBJ@ brick = jjObjects[jjAddObject(OBJECT::BOUNCEONCE, obj.xPos, obj.yPos + 6)];
brick.determineCurAnim(ANIM::QUEEN, 4);
brick.xAcc = 0;
brick.xSpeed = 0;
brick.playerHandling = HANDLING::ENEMYBULLET;
brick.animSpeed = 1;
jjSample(obj.xPos, obj.yPos + 6, SOUND::COMMON_HOLYFLUT);
jjTileSet(4, xTile, yTile, obj.var[1]);
obj.delete();
}
}
}
void onDraw (jjOBJ@ obj) {
// don't draw
}
}
class imp : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
obj.scriptedCollisions = true;
obj.playerHandling = HANDLING::SPECIAL;
if (obj.isActive && obj.freeze == 0) {
obj.var[6] = jjParameterGet(uint16(obj.xOrg / 32), uint16(obj.yOrg / 32), 0, 3);
obj.var[7] = jjParameterGet(uint16(obj.xOrg / 32), uint16(obj.yOrg / 32), 3, 3);
obj.counter += 1 + (jjRandom() & 3);
int arg = obj.counter / ((obj.var[6] + 1) + (obj.var[7] + 1) / 2);
obj.xPos = obj.xOrg + jjSin(arg * 4) * 32 * (obj.var[6] + 1);
obj.yPos = obj.yOrg + jjCos(arg * 2) * 24 * (obj.var[7] + 1);
if (arg & 255 < 128 ^^ obj.direction < 0) {
jjSample(obj.xPos, obj.yPos, SOUND::COMMON_FOEW1, 10, (obj.direction < 0 ? 30300 : 30000) + (jjRandom() & 0x7FF));
obj.direction = obj.direction < 0 ? 1 : -1;
}
obj.frameID = obj.counter / 6 % jjAnimations[obj.curAnim].frameCount;
obj.determineCurFrame();
obj.draw();
}
if (obj.state == STATE::KILL) obj.delete();
if (obj.freeze > 0) {
if (obj.freeze == 0) {
obj.unfreeze(0);
obj.state = obj.oldState;
}
else obj.freeze--;
obj.determineCurFrame();
obj.draw();
}
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
[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.