Name | Author | Game Mode | Rating | |||||
---|---|---|---|---|---|---|---|---|
Threed Realms | Violet CLM | Single player | 9.7 |
#pragma require "Threed.asc"
#pragma require "Threed.j2a"
namespace Threed {
enum Objects {
Clown, GarbageCan, Puppet, PutridMoldyman, RingOfFire, SmellyGhost, TrickOrTrickKid, UrbanZombie, ZombieDog, ZombiePossessor
, LAST
}
array<uint> ObjectAnimSetIDs(Objects::LAST, 0);
uint GetAnimSet() {
uint newSetID;
uint8 customID = 0;
do {
newSetID = ANIM::CUSTOM[customID++];
} while (jjAnimSets[newSetID] != 0);
return newSetID;
}
uint GetAnimSet(Threed::Objects objec, bool reusableAnimSet = true) {
uint newSetID;
if (!reusableAnimSet || ObjectAnimSetIDs[objec] == 0) {
newSetID = GetAnimSet();
if (reusableAnimSet)
ObjectAnimSetIDs[objec] = newSetID;
jjAnimSets[newSetID].load(objec, "Threed.j2a");
} else {
newSetID = ObjectAnimSetIDs[objec];
}
return newSetID;
}
void MakeEnemy(jjOBJ@ preset) {
preset.playerHandling = HANDLING::ENEMY;
preset.bulletHandling = HANDLING::HURTBYBULLET;
preset.scriptedCollisions = false;
preset.isTarget = true;
preset.isBlastable = true;
preset.isFreezable = true;
preset.triggersTNT = true;
preset.state = STATE::START;
preset.frameID = 0;
preset.lightType = LIGHT::NONE;
}
void Apply(jjOBJ@ preset, Objects objec, bool reusableAnimSet = true, bool alternateBehavior = false) {
if (objec != Objects::GarbageCan)
MakeEnemy(preset);
preset.curAnim = jjAnimSets[GetAnimSet(objec, reusableAnimSet)].firstAnim; //on average this is correct
preset.curFrame = jjAnimations[preset.curAnim].firstFrame;
switch (objec) {
case Objects::Clown:
preset.behavior = Clown;
preset.energy = 100;
preset.points = 500;
preset.counter = 0;
preset.killAnim = jjAnimSets[ANIM::AMMO] + 81;
break;
case Objects::GarbageCan: //mimic barrel setup
preset.behavior = GarbageCan;
preset.playerHandling = HANDLING::SPECIAL;
preset.triggersTNT = true;
preset.causesRicochet = false;
preset.bulletHandling = HANDLING::DETECTBULLET;
preset.isFreezable = true;
preset.isBlastable = false;
preset.direction = 1;
preset.points = 100;
preset.doesHurt = preset.eventID;
preset.eventID = OBJECT::GUNBARREL; //for proper bullet treatment, including ricocheting
//leave .special up to the user
if (alternateBehavior)
preset.var[8] = 1;
break;
case Objects::Puppet:
preset.xSpeed = jjDifficulty > 0 ? 1.5 : 1;
preset.playerHandling = HANDLING::PARTICLE;
preset.points = 0;
preset.killAnim = 0;
preset.var[5] = alternateBehavior ? 1 : 0;
preset.isTarget = false;
preset.isFreezable = false;
preset.deactivates = false;
preset.behavior = Puppet(preset.eventID); //do this at the end so the above assignments have time to sink in before we start adding objects
break;
case Objects::PutridMoldyman:
if (!jjSampleIsLoaded(SOUND::TURTLE_TURN))
jjAnimSets[ANIM::TURTLE].load();
preset.behavior = PutridMoldyman;
preset.points = 1000;
preset.energy = 4;
preset.playerHandling = HANDLING::PARTICLE; //hidden
break;
case Objects::RingOfFire:
preset.behavior = RingOfFire();
preset.playerHandling = HANDLING::SPECIAL;
preset.bulletHandling = HANDLING::IGNOREBULLET;
preset.isTarget = false;
preset.scriptedCollisions = true;
preset.curAnim = jjAnimations[preset.curAnim].firstFrame;
preset.curFrame = jjAnimations[jjAnimSets[ANIM::AMMO] + 5] + 4;
break;
case Objects::SmellyGhost:
if (!jjSampleIsLoaded(SOUND::TURTLE_TURN))
jjAnimSets[ANIM::RAPIER].load();
preset.behavior = SmellyGhost;
preset.energy = 2;
preset.points = 500;
preset.counter = 0;
preset.special = 0;
preset.counterEnd = 0;
preset.lightType = LIGHT::PLAYER;
preset.light = 9;
break;
case Objects::TrickOrTrickKid:
preset.behavior = TrickOrTrickKid;
preset.energy = 2;
preset.points = 1000;
preset.counter = 0;
preset.killAnim = 0;
break;
case Objects::UrbanZombie:
preset.behavior = UrbanZombie;
preset.energy = 1;
preset.points = 200;
preset.xSpeed = 1;
preset.killAnim = jjAnimSets[ANIM::AMMO] + 6;
preset.isFreezable = false;
break;
case Objects::ZombieDog:
if (jjAnimSets[ANIM::DOG] == 0)
jjAnimSets[ANIM::DOG].load(); //for sound effects
preset.behavior = ZombieDog;
preset.energy = 3;
preset.points = 200;
preset.doesHurt = preset.eventID;
preset.playerHandling = HANDLING::SPECIAL;
preset.eventID = OBJECT::DOGGYDOGG;
preset.special = preset.curAnim; //refer back to it later
preset.curAnim += 1; //walk, not attack
preset.curFrame = jjAnimations[preset.curAnim].firstFrame;
preset.killAnim = jjAnimSets[ANIM::AMMO] + 6;
break;
case Objects::ZombiePossessor:
if (!jjSampleIsLoaded(SOUND::RAPIER_GOSTOOOH) || !jjSampleIsLoaded(SOUND::RAPIER_GOSTDIE))
jjAnimSets[ANIM::RAPIER].load();
preset.behavior = ZombiePossessor;
preset.energy = 1;
preset.points = 200;
preset.direction = 1;
preset.state = STATE::IDLE;
break;
}
if (preset.energy > 0 && jjDifficulty >= 3) //turbo
preset.energy += 1;
}
void Apply(uint8 eventID, Objects objec, bool reusableAnimSet = true, bool alternateBehavior = false) {
if (eventID != jjObjectPresets[eventID].eventID)
jjDebug("Warning: jjObjectPresets[" + eventID + "].eventID has been modified.");
Apply(jjObjectPresets[eventID], objec, reusableAnimSet, alternateBehavior);
}
const int ClownFrequency = 6000;
void Clown(jjOBJ@ obj) {
++obj.counter;
if (obj.energy < 100 && obj.bulletHandling == HANDLING::HURTBYBULLET) { //hurt!
jjOBJ@ ball = jjObjects[jjAddObject(obj.eventID, obj.xPos, obj.yPos - 4, obj.objectID, CREATOR::OBJECT, ClownBall)];
ball.xSpeed = obj.xSpeed * 3 * obj.direction;
if (ball.xSpeed == 0)
ball.behavior = BEHAVIOR::BOUNCEONCE;
ball.curAnim = obj.curAnim;
ball.determineCurFrame();
ball.ySpeed = -3;
ball.energy = 1;
ball.points = 100;
obj.bulletHandling = HANDLING::IGNOREBULLET;
obj.counter = 0;
if (obj.state == STATE::FREEZE)
obj.unfreeze(0);
obj.state = STATE::FALL;
obj.xSpeed = obj.direction * -3;
obj.xAcc = obj.direction / 24.f;
obj.ySpeed = -2;
obj.yPos -= 30;
obj.curFrame = jjAnimations[obj.curAnim += 3];
obj.creatorType = CREATOR::OBJECT; //don't come back to life anymore if deactivated
} else switch (obj.state) {
case STATE::START:
obj.putOnGround(false);
obj.var[0] = jjMaskedTopVLine(int(obj.xPos), int(obj.yPos) - 40, 60);
obj.state = STATE::ROTATE;
obj.direction = 1;
break;
case STATE::DEACTIVATE:
obj.deactivate();
break;
case STATE::FREEZE:
--obj.counter;
if (--obj.freeze == 0)
obj.state = obj.oldState;
if (obj.oldState != STATE::WAIT && obj.oldState != STATE::FALL)
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, jjAnimations[obj.curAnim], obj.direction);
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::FROZEN);
break;
case STATE::ROTATE: {
const auto sine = jjSin(obj.counter << 1);
obj.xSpeed = sine * (3 + jjDifficulty / 2.f);
const float targetX = obj.xPos + obj.xSpeed * obj.direction;
if (jjMaskedTopVLine(int(targetX), int(obj.yPos) - 40, 60) == obj.var[0] && jjEventAtLastMaskedPixel != AREA::STOPENEMY)
obj.xPos = targetX;
else {
obj.direction = -obj.direction;
jjSample(obj.xPos, obj.yPos, SOUND::Sample(SOUND::SPAZSOUNDS_HAHAHA + (jjRandom() & 1)), 0, ClownFrequency);
}
obj.xAcc += sine;
obj.frameID = int(obj.xAcc) / 4;
obj.curAnim += 1;
obj.determineCurFrame(true);
obj.curAnim -= 1;
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.determineCurFrame(false), obj.direction);
const int angle = int(sine * obj.direction * 96);
const float asin = jjSin(angle), acos = jjCos(angle);
const int xdiff = 6 * obj.direction;
jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, angle, obj.direction,1, obj.justHit == 0 ? SPRITE::NORMAL : SPRITE::SINGLECOLOR, 15);
jjDrawSpriteFromCurFrame(obj.xPos - (asin * 33 + acos * xdiff), obj.yPos - (acos * 33 - asin * xdiff), jjAnimations[obj.curAnim + 5] + ((jjGameTicks >> 2) % 12), obj.direction);
if (obj.counter == 256) {
obj.counter = 0;
obj.state = STATE::ATTACK;
}
break; }
case STATE::ATTACK:
obj.curFrame = jjAnimations[obj.curAnim + 2] + obj.counter / 10;
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, jjAnimations[obj.curAnim], obj.direction);
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction);
if (obj.counter == 20) {
jjSample(obj.xPos, obj.yPos, SOUND::SPAZSOUNDS_HIHI, 0, ClownFrequency * 3 / 5);
jjOBJ@ bomb = jjObjects[obj.fireBullet(OBJECT::BOMB)];
bomb.curAnim = obj.curAnim + 6;
bomb.xSpeed = obj.direction * 2.5;
bomb.ySpeed = -3;
bomb.state = STATE::FLY;
if (jjDifficulty > 2) {
bomb.playerHandling = HANDLING::ENEMYBULLET;
bomb.animSpeed = 1;
}
} else if (obj.counter == 49) {
obj.counter = 0;
obj.state = STATE::ROTATE;
}
break;
case STATE::FALL: {
if ((obj.xSpeed > 0) == (obj.direction == -1)) {
const float targetX = obj.xPos + (obj.xSpeed += obj.xAcc);
if (!jjMaskedVLine(int(targetX), int(obj.yPos) - 12, 24))
obj.xPos = targetX;
else
obj.xSpeed = obj.xAcc = 0;
}
const float targetY = obj.yPos + (obj.ySpeed += 0.09);
if (!jjMaskedHLine(int(obj.xPos) - 24, 24, int(targetY) + 18)) {
obj.yPos = targetY;
if (obj.counter == 20)
++obj.curFrame;
} else {
obj.curFrame = jjAnimations[obj.curAnim] + 2;
obj.state = STATE::WAIT;
obj.counter = 0;
}
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, obj.justHit == 0 ? SPRITE::TRANSLUCENT : SPRITE::TRANSLUCENTCOLOR, 15);
break; }
case STATE::WAIT:
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::TRANSLUCENT);
if (obj.counter == 35)
obj.curFrame += 1;
else if (obj.counter == 50) {
jjSample(obj.xPos, obj.yPos, SOUND::Sample(SOUND::SPAZSOUNDS_HAHAHA + (jjRandom() & 1)), 0, ClownFrequency);
obj.bulletHandling = HANDLING::HURTBYBULLET;
obj.curAnim += 1;
obj.frameID = 0;
obj.determineCurFrame();
obj.state = STATE::WALK;
obj.behavior = BEHAVIOR::WALKINGENEMY;
obj.xSpeed = 4 * obj.direction;
obj.xAcc = 0;
obj.energy = (jjDifficulty > 2) ? 4 : 3;
}
break;
}
}
void ClownBall(jjOBJ@ obj) { //minimally edited from BEHAVIOR::TURTLESHELL to avoid killing enemies
if (obj.state==STATE::START)
obj.state=STATE::FLY;
else if (obj.state==STATE::KILL || obj.state==STATE::DEACTIVATE)
obj.delete();
else if (obj.state == STATE::FREEZE) {
if (--obj.freeze == 0)
obj.state = STATE::FLY;
} else {
if (abs(obj.xSpeed) > 2) {
if (obj.yPos>jjWaterLevel)
{
obj.xSpeed=(obj.xSpeed*7)/8;
obj.ySpeed=(obj.ySpeed*7)/8;
}
else
obj.xSpeed=(obj.xSpeed*63)/64;
}
obj.xPos+=obj.xSpeed;
obj.yPos+=obj.ySpeed;
const bool nogoup=jjMaskedPixel(int(obj.xPos),int(obj.yPos)-4);
const bool nogodown=jjMaskedPixel(int(obj.xPos),int(obj.yPos)+10);
const bool nogoleft=jjMaskedPixel(int(obj.xPos)-8,int(obj.yPos));
const bool nogoright=jjMaskedPixel(int(obj.xPos)+8,int(obj.yPos));
int calc, calc2;
if ((obj.xSpeed>0) && nogoright)
{
calc=1+int(obj.xSpeed);
calc2=jjMaskedTopVLine(int(obj.xPos+8),int(obj.yPos)+10-calc,calc);
if ((calc2<=1) || (calc2>=calc))
{
obj.xPos-=obj.xSpeed;
obj.xSpeed=-obj.xSpeed;
} else
{
obj.yPos-=calc;
obj.xSpeed=(obj.xSpeed*15)/16;
};
} else
if ((obj.xSpeed<0) && nogoleft)
{
calc=1-int(obj.xSpeed);
calc2=jjMaskedTopVLine(int(obj.xPos-8),int(obj.yPos)+10-calc,calc);
if ((calc2<=1) || (calc2>=calc))
{
obj.xPos-=obj.xSpeed;
obj.xSpeed=-obj.xSpeed;
} else
{
obj.yPos-=calc;
obj.xSpeed=(obj.xSpeed*15)/16;
};
};
if (nogoup && (obj.ySpeed<0))
{
obj.yPos-=obj.ySpeed; //reset to last location!
obj.ySpeed=-obj.ySpeed/2;
} else
if (nogodown)
{
calc=jjMaskedTopVLine(int(obj.xPos),int(obj.yPos),10);
if (calc != 0)
obj.yPos-=(10-calc);
else
obj.yPos-=obj.ySpeed;
if (obj.ySpeed>1)
jjSample(obj.xPos, obj.yPos, SOUND::Sample(SOUND::COMMON_IMPACT1 + (jjRandom() & 7)));
obj.ySpeed=-(obj.ySpeed/2);
} else
{
if (obj.yPos>jjWaterLevel)
{
obj.ySpeed+=0.125/4;
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;
};
};
}
obj.direction = (obj.xSpeed >= 0) ? 1 : -1;
if (jjGameTicks & 3 == 0) {
++obj.frameID;
obj.determineCurFrame();
}
obj.draw();
}
jjVOIDFUNCOBJ@ GarbageCanCallback = null;
void GarbageCan(jjOBJ@ obj) {
if (obj.state == STATE::ACTION) {
obj.eventID = obj.doesHurt;
const int originalCurAnim = obj.curAnim;
obj.curAnim = jjAnimSets[ANIM::PICKUPS] + 3; //pretend to be a barrel
obj.behave(BEHAVIOR::CRATE, true);
bool foundLid = obj.var[7] == 1;
for (int i = jjObjectCount; --i >= 0;) {
jjOBJ@ shard = jjObjects[i];
if (shard.eventID == OBJECT::SHARD && shard.isActive && shard.xOrg == obj.xPos && shard.yOrg == obj.yPos) {
if (!foundLid) { //only one lid shard
shard.curAnim = originalCurAnim + 1;
foundLid = true;
} else //misc
shard.curAnim = originalCurAnim + 2 + (jjRandom() & 1);
}
}
if (obj.var[8] != 0 && GarbageCanCallback !is null)
GarbageCanCallback(obj);
} else {
if (obj.state == STATE::DEACTIVATE)
obj.eventID = obj.doesHurt;
obj.behave(BEHAVIOR::CRATE, true);
}
}
void SmellyGhost(jjOBJ@ obj) {
if (obj.state == STATE::START) {
if (obj.creatorType == CREATOR::LEVEL) {
obj.state = STATE::IDLE;
} else { //from a garbage can?
obj.state = STATE::ROTATE;
obj.playerHandling = HANDLING::PARTICLE;
}
obj.direction = int(jjRandom() & 1) * 2 - 1;
}
switch (obj.state) {
case STATE::IDLE:
obj.counter += 4;
obj.xPos += jjSin(obj.counter) / 12 * obj.direction;
obj.yPos += jjCos(obj.counter) / 8;
if (obj.counter > 100) {
const int playerID = obj.findNearestPlayer(160 * 160);
if (playerID >= 0) {
obj.var[0] = playerID;
obj.counter = 0;
obj.state = STATE::ATTACK;
jjSample(obj.xPos, obj.yPos, SOUND::RAPIER_GOSTOOOH, 63, 16000);
}
}
break;
case STATE::ATTACK: {
const jjPLAYER@ target = jjPlayers[obj.var[0]];
if (++obj.counter == 20) {
obj.counter = 0;
if (++obj.special == 3) {
obj.state = STATE::FIRE;
obj.var[1] = int(atan2(
target.yPos - obj.yPos,
target.xPos - obj.xPos
) * 1024 / 6.283185307179586);
}
}
obj.direction = target.xPos > obj.xPos ? 1 : -1;
break; }
case STATE::FIRE:
if (jjGameTicks & 3 == 1)
jjAddObject(OBJECT::BULLET, obj.xPos, obj.yPos - 8, obj.objectID, CREATOR::OBJECT, SmellyGhostBee);
if (++obj.counter >= jjDifficulty * 25 + 45) {
obj.state = STATE::IDLE;
obj.counter = 0;
obj.special = 0;
}
break;
case STATE::ROTATE:
if ((obj.counter += 4) >= 256) {
obj.state = STATE::IDLE;
obj.playerHandling = HANDLING::ENEMY;
}
obj.xPos = obj.xOrg - jjSin(obj.counter) * 60 * obj.direction;
obj.yPos = obj.yOrg - 110 + jjCos(obj.counter) * 110;
break;
case STATE::KILL:
jjSample(obj.xPos, obj.yPos, SOUND::RAPIER_GOSTDIE);
obj.delete();
return;
case STATE::DEACTIVATE:
obj.deactivate();
return;
case STATE::FREEZE:
if ((obj.freeze -= 1 )<= 0) {
obj.state = obj.oldState;
obj.unfreeze(0);
}
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::FROZEN);
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, jjAnimations[obj.curAnim + 1] + obj.special, obj.direction, SPRITE::FROZEN);
return;
}
if (++obj.counterEnd == 9) {
obj.frameID += 1;
obj.counterEnd = 0;
obj.determineCurFrame();
}
const SPRITE::Mode mode = obj.justHit == 0 ? SPRITE::NORMAL : SPRITE::SINGLECOLOR;
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, mode, 15);
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, jjAnimations[obj.curAnim + 1] + obj.special, obj.direction, mode, 15);
}
void SmellyGhostBee(jjOBJ@ obj) {
if (obj.state == STATE::START) {
const jjOBJ@ creator = jjObjects[obj.creatorID];
obj.playerHandling = HANDLING::ENEMYBULLET;
obj.curAnim = creator.curAnim + 2;
obj.curFrame = jjAnimations[obj.curAnim];
const int angle = creator.var[1] + int(jjRandom() & 63) - 32;
obj.xSpeed = jjCos(angle) * 2;
obj.ySpeed = jjSin(angle) * 2;
obj.xAcc = obj.yAcc = 0;
obj.direction = (obj.xSpeed >= 0) ? 1 : -1;
//obj.behavior = SmellyGhostBee;
obj.counter = 1;
obj.counterEnd = 145;
MakeEnemy(obj);
obj.state = STATE::FLY;
obj.energy = 1;
obj.points = 10;
obj.lightType = LIGHT::NONE;
}
else if (obj.counter >= int(obj.counterEnd) || jjMaskedPixel(int(obj.xPos + obj.xSpeed), int(obj.yPos + obj.ySpeed)))
obj.delete(); //avoid playing explosion sound
else
obj.behave(BEHAVIOR::BULLET);
}
const array<int> LidPeekFrames = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0};
void PutridMoldyman(jjOBJ@ obj) {
jjOBJ@ can = jjObjects[obj.special];
switch (obj.state) {
case STATE::START: {
obj.state = STATE::DELAYEDSTART;
obj.special = jjAddObject(OBJECT::GUNBARREL, obj.xOrg, obj.yOrg, obj.objectID, CREATOR::OBJECT, BEHAVIOR::INACTIVE);
@can = jjObjects[obj.special];
Apply(can, Objects::GarbageCan);
can.deactivates = false;
can.behave(BEHAVIOR::DEFAULT, false);
obj.yPos = can.yPos;
can.var[7] = 1; //don't spawn another lid shard later
can.xSpeed = 0.2;
}
case STATE::DELAYEDSTART: {
const int nearestPlayerID = can.findNearestPlayer(90 * 90);
if (nearestPlayerID >= 0) {
obj.state = STATE::IDLE;
obj.playerHandling = HANDLING::ENEMY;
jjSample(can.xPos, can.yPos, SOUND::TURTLE_TURN);
jjSample(can.xPos, can.yPos, SOUND::COMMON_SPLUT);
if (jjDifficulty > 0) {
for (int i = 4; --i >= 0;)
jjAddObject(OBJECT::SHARD, can.xPos, can.yPos - 26, obj.objectID, CREATOR::OBJECT, function(h){h.behavior = PutridSpore; h.behave();});
if (jjPlayers[nearestPlayerID].invincibility < 0) //recently stomped something
jjPlayers[nearestPlayerID].invincibility = 0; //be vulnerable to my spores
}
jjObjects[jjAddObject(OBJECT::SHARD, can.xPos, can.yPos, obj.objectID, CREATOR::OBJECT)].curAnim = jjObjects[obj.special].curAnim + 1; //lid
} else {
const int frameID = LidPeekFrames[(jjGameTicks >> 2) % LidPeekFrames.length];
if (frameID >= 0)
jjDrawSpriteFromCurFrame(can.xPos, can.yPos, jjAnimations[obj.curAnim + 1] + frameID, 1, SPRITE::NORMAL,0, 3);
}
return; }
case STATE::KILL:
jjSample(obj.xPos, obj.yPos, SOUND::COMMON_SPLUT);
obj.particlePixelExplosion(104);
if (can.behavior == GarbageCan && can.creatorID == uint(obj.objectID))
can.state = STATE::ACTION;
obj.delete();
break;
case STATE::DEACTIVATE:
if (can.behavior == GarbageCan && can.creatorID == uint(obj.objectID))
can.deactivate();
obj.deactivate();
return;
case STATE::IDLE:
if (can.behavior != GarbageCan || can.creatorID != uint(obj.objectID))
obj.delete();
else {
if (jjGameTicks & 3 == 3) {
jjPARTICLE@ bubble = jjAddParticle(PARTICLE::RAIN);
if (bubble !is null) {
bubble.xPos = obj.xPos - 16 + (jjRandom() & 31);
bubble.yPos = obj.yPos - 3;
bubble.ySpeed = 1.8;
}
}
if (jjGameTicks & 7 == 7) {
jjPARTICLE@ smoke = jjAddParticle(PARTICLE::SMOKE);
if (smoke !is null) {
smoke.xPos = obj.xPos - 16 + (jjRandom() & 31);
smoke.yPos = obj.yPos - 5 - int(jjRandom() & 31);
}
}
if (jjGameTicks & 15 == 15) {
jjAddObject(OBJECT::EXPLOSION, obj.xPos - 16 + (jjRandom() & 31), obj.yPos - 32 + (jjRandom() & 31), obj.objectID, CREATOR::OBJECT, PutridExplosion);
}
}
break;
case STATE::FREEZE:
if ((obj.freeze -= 1 )<= 0) {
obj.state = obj.oldState;
obj.unfreeze(0);
} else {
obj.xPos = can.xPos;
obj.yPos = can.yPos;
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, 0, SPRITE::FROZEN, 0, 3);
return;
}
break;
}
if (obj.special != 0) { //edge cases or something idk
obj.xPos = can.xPos;
obj.yPos = can.yPos;
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, 0, obj.justHit == 0 ? SPRITE::NORMAL : SPRITE::SINGLECOLOR, 15, 3);
} else {
obj.delete();
}
}
void PutridExplosion(jjOBJ@ obj) {
if (obj.state == STATE::START) {
obj.determineCurAnim(ANIM::PICKUPS, 86);
obj.lightType = LIGHT::NONE;
}
obj.behave(BEHAVIOR::EXPLOSION, false);
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::SINGLECOLOR, 68);
}
int SporeXSpeed = -3;
class PutridSporeClass : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
if (obj.state == STATE::START) {
obj.curFrame = jjAnimations[jjObjects[obj.creatorID].curAnim + 2];
obj.playerHandling = HANDLING::PICKUP;
obj.scriptedCollisions = true;
obj.ySpeed = -1.4 + abs(SporeXSpeed) / 14;
obj.xSpeed = SporeXSpeed / 7.f;
if ((SporeXSpeed += 2) > 3)
SporeXSpeed = -3;
obj.state = STATE::FLY;
} else if (obj.state == STATE::EXPLODE || obj.state == STATE::DEACTIVATE || jjMaskedPixel(int(obj.xPos), int(obj.yPos))) {
obj.delete();
} else {
obj.xPos += obj.xSpeed;
obj.yPos += obj.ySpeed += 0.01;
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame);
}
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@, jjPLAYER@ play, int) {
const auto realButtstomp = play.buttstomp;
play.buttstomp = 121;
if (!play.hurt(1))
play.buttstomp = realButtstomp;
obj.delete();
return true;
}
}
PutridSporeClass PutridSpore;
void ZombiePossessor(jjOBJ@ obj) {
//if (obj.state != STATE::FREEZE)
// jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::BLEND_SCREEN,160);
obj.age += 4;
switch (obj.state) {
case STATE::KILL:
if (obj.counter > 40 && jjDifficulty > 0)
jjAddObject(obj.eventID, obj.xPos, obj.yPos, obj.objectID, CREATOR::OBJECT, function(h){h.behavior = ZombiePossessorIcyHand; h.behave();});
obj.delete();
break;
case STATE::DEACTIVATE:
obj.deactivate();
break;
case STATE::FREEZE:
if (--obj.freeze == 0)
obj.state = obj.oldState;
break;
case STATE::IDLE:
obj.var[0] = obj.findNearestPlayer(200*200);
if (obj.var[0] >= 0) {
obj.state = STATE::FLY;
obj.xSpeed = 0.75 * obj.direction; //some deaccel maybe
} else {
obj.yPos = obj.yOrg + jjSin(obj.age) * 8;
if (jjMaskedVLine(int(obj.xPos + obj.direction * 12), int(obj.yPos) - 8, 16))
obj.direction = -obj.direction;
else
obj.xPos += 0.75 * obj.direction;
break;
}
case STATE::FLY: {
const jjPLAYER@ target = jjPlayers[obj.var[0]]; //once a target has been found, never abandon it (short of deactivation), even if there are multiple local players
obj.direction = (target.xPos > obj.xPos) ? 1 : -1;
const int distance = 150 + int(jjSin(obj.age) * 25);
const float targetX = target.xPos - distance * obj.direction, targetY = target.yPos - distance;
if (obj.xPos < targetX) {
if (obj.xSpeed < 0.8) obj.xSpeed += 0.075;
} else {
if (obj.xSpeed > -0.8) obj.xSpeed -= 0.075;
}
if (obj.yPos < targetY) {
if (obj.ySpeed < 0.8) obj.ySpeed += 0.075;
} else {
if (obj.ySpeed > -0.8) obj.ySpeed -= 0.075;
}
obj.ySpeed += (int(jjRandom() & 63) - 31) / 310.f;
obj.xPos += obj.xSpeed;
obj.yPos += obj.ySpeed;
if (target.frozen == 0 && ++obj.counter > 70 && abs(abs(target.xPos - obj.xPos) - (target.yPos - obj.yPos)) < 40) { //a poor woman's atan
obj.state = STATE::ATTACK;
jjAddObject(obj.eventID, obj.xPos, obj.yPos, obj.objectID, CREATOR::OBJECT, function(h){h.behavior = ZombiePossessorIcyHand;});
obj.counter = 25;
}
break; }
case STATE::ATTACK:
if (--obj.counter <= 0)
obj.state = STATE::FLY;
break;
}
if (obj.state != STATE::FREEZE) {
obj.frameID = obj.age >> 4;
obj.determineCurFrame();
obj.var[1] = jjSampleLooped(obj.xPos, obj.yPos, SOUND::RAPIER_GOSTLOOP, obj.var[1]);
}
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, (obj.state != STATE::FREEZE) ? (obj.justHit == 0) ? SPRITE::TRANSLUCENT : SPRITE::SINGLECOLOR : SPRITE::FROZEN,15);
}
class ZombiePossessorIcyHandClass : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
if (obj.state == STATE::DEACTIVATE) {
obj.delete();
return;
}
if (obj.state == STATE::IDLE) { //ersatz START
obj.curFrame = jjAnimations[obj.curAnim + 1];
const jjOBJ@ creator = jjObjects[obj.creatorID];
obj.direction = creator.direction;
obj.var[0] = creator.var[0];
obj.xSpeed = obj.direction * 3;
obj.ySpeed = 3;
obj.counterEnd = 5;
obj.state = STATE::DELAYEDSTART;
obj.animSpeed = 1;
obj.playerHandling = HANDLING::PARTICLE; //still forming
obj.bulletHandling = HANDLING::DETECTBULLET;
obj.isFreezable = false;
obj.isTarget = false;
}
if (obj.state == STATE::DELAYEDSTART) {
if ((obj.counterEnd += 10) == 255) {
obj.state = STATE::FLY;
obj.playerHandling = HANDLING::SPECIAL;
obj.scriptedCollisions = true;
} else
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::BLEND_DISSOLVE, obj.counterEnd);
}
if (obj.state == STATE::EXPLODE || (obj.yPos > jjPlayers[obj.var[0]].yPos && jjMaskedPixel(int(obj.xPos), int(obj.yPos)))) {
obj.unfreeze(1);
obj.delete();
} else if (obj.state == STATE::FLY) { //this check may not be needed
obj.xPos += obj.xSpeed;
obj.yPos += obj.ySpeed;
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction);
}
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ play, int force) {
if (bullet is null) {
if (play.shieldType != SHIELD::FIRE) {
if (play.blink == 0 && jjDifficulty != 1) { //not normal
play.freeze();
obj.state = STATE::EXPLODE;
}
if (jjDifficulty > 0) { //not easy
if (play.hurt())
obj.state = STATE::EXPLODE;
}
} else {
obj.particlePixelExplosion(1);
obj.delete();
}
} else {
if (bullet.var[6] & 2 == 2) { //fire
obj.particlePixelExplosion(1);
obj.delete();
} else
bullet.state = STATE::EXPLODE;
}
return true;
}
}
ZombiePossessorIcyHandClass ZombiePossessorIcyHand();
void ZombieDog(jjOBJ@ obj) {
switch (obj.state) {
case STATE::WALK:
obj.xSpeed = obj.direction / 2.f;
obj.curAnim = obj.special + 1;
obj.behave(BEHAVIOR::WALKINGENEMY);
if (++obj.counter > obj.var[3]) {
obj.counter = 0;
obj.var[3] = 30 + (jjRandom() & 31);
if (obj.var[3] & 1 == 1)
jjSample(obj.xPos, obj.yPos, SOUND::DOG_SNIF1, 40,0);
}
return;
case STATE::DEACTIVATE:
obj.eventID = obj.doesHurt;
break;
case STATE::ACTION:
obj.behave(BEHAVIOR::DOGGYDOGG, false);
obj.curAnim = obj.special;
obj.determineCurFrame();
obj.draw();
return;
}
obj.behave(BEHAVIOR::DOGGYDOGG);
}
void UrbanZombie(jjOBJ@ obj) {
switch (obj.state) {
case STATE::START:
if (obj.creator == CREATOR::LEVEL && jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0, 1) == 1) {
obj.state = STATE::DELAYEDSTART;
obj.playerHandling = HANDLING::PARTICLE;
obj.lightType = LIGHT::POINT;
obj.putOnGround();
obj.yPos = (int(obj.yPos) & ~31) + 42;
} else
obj.state = STATE::WALK;
return;
case STATE::DELAYEDSTART: {
const int nearestPlayerID = obj.findNearestPlayer(110 * 110);
if (nearestPlayerID >= 0) {
obj.state = STATE::JUMP;
obj.direction = jjPlayers[nearestPlayerID].xPos > obj.xPos ? 1 : -1;
obj.yPos = obj.yOrg;
jjSample(obj.xPos, obj.yPos, SOUND::DOG_AGRESSIV, 0, 14000);
} else {
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame + ((jjGameTicks / 13) % 12), SPRITE::FLIPV);
return;
}
}
case STATE::JUMP:
obj.yPos = obj.yOrg - jjSin(obj.age += 8) * 75;
obj.frameID = obj.age / 40;
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.determineCurFrame(), obj.direction, layerZ: obj.age < 256 ? 7 : 4);
if (obj.age == 256)
obj.playerHandling = HANDLING::ENEMY;
else if (obj.age == 512)
obj.state = STATE::WALK;
break; //fire
case STATE::DEACTIVATE:
obj.deactivate();
return;
case STATE::KILL:
if (jjDifficulty == 1 || jjDifficulty == 2)
for (uint objectID = jjObjectCount; --objectID != 0;) {
const jjOBJ@ bullet = jjObjects[objectID];
if (bullet.eventID == obj.eventID && int(bullet.creatorID) == obj.objectID)
jjDeleteObject(objectID);
}
default:
obj.behave(BEHAVIOR::WALKINGENEMY, true);
break; //fire
}
if (jjDifficulty > 0 && obj.state != STATE::FREEZE && obj.state != STATE::KILL && jjGameTicks & 3 == 0) {
jjAddObject(obj.eventID, obj.xPos, obj.yPos - 10, obj.objectID, CREATOR::OBJECT, function(h){h.behavior = UrbanZombieArcticColdBreath; h.behave();});
}
}
class UrbanZombieArcticColdBreathClass : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
if (obj.state == STATE::START) {
obj.doesHurt = 32 + (jjRandom() & 3);
obj.isTarget = false;
obj.playerHandling = HANDLING::SPECIAL;
obj.bulletHandling = HANDLING::IGNOREBULLET;
obj.scriptedCollisions = true;
obj.curAnim = 27;
obj.determineCurFrame();
obj.killAnim = 0;
const jjOBJ@ creator = jjObjects[obj.creatorID];
if (creator.state != STATE::JUMP) {
obj.direction = creator.direction;
obj.xSpeed = 1.5 * obj.direction;
obj.xAcc = float(jjRandom() & 63) / 1280 * obj.direction;
obj.yAcc = float(jjRandom() & 63) / 1280 - 0.025;
} else {
obj.curAnim += 4;
obj.xSpeed = 0;
obj.ySpeed = -1.5;
obj.yAcc = float(jjRandom() & 63) / -1280;
obj.xAcc = float(jjRandom() & 63) / 1280 - 0.025;
}
obj.state = STATE::FLY;
}
if (++obj.counter >= 70)
obj.delete();
else {
obj.xPos += obj.xSpeed += obj.xAcc;
obj.yPos += obj.ySpeed += obj.yAcc;
if (obj.counter >= 9)
jjDrawSprite(obj.xPos, obj.yPos, ANIM::AMMO, obj.curAnim, obj.counter >> 1, obj.direction, SPRITE::TRANSLUCENT);
}
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ play, int force) {
if (bullet is null && play.shieldType != SHIELD::FIRE)
play.frozen = 70; //one third the time of a freeze enemies pickup
return true;
}
}
UrbanZombieArcticColdBreathClass UrbanZombieArcticColdBreath();
void TrickOrTrickKid(jjOBJ@ obj) {
switch (obj.state) {
case STATE::START:
obj.direction = jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0, 1) * 2 - 1;
if (jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 1, 1) == 1) { //maybe true if generating, depending on eventID
obj.behavior = TrickOrTrickKidPumpkinBomb;
obj.behave();
return;
}
obj.putOnGround(true);
obj.state = STATE::WAIT;
obj.special = jjAddObject(obj.eventID, 0,0, obj.objectID, CREATOR::OBJECT, function(h){h.behavior = TrickOrTrickKidPumpkinBomb; h.behave();});
break;
case STATE::DEACTIVATE:
jjObjects[obj.special].delete();
obj.deactivate();
return;
case STATE::KILL: {
jjOBJ@ pumpkin = jjObjects[obj.special];
if (jjDifficulty <= 0) {
pumpkin.particlePixelExplosion(2);
pumpkin.delete();
} else {
pumpkin.state = STATE::FALL;
pumpkin.deactivates = true;
pumpkin.isTarget = false;
pumpkin.bulletHandling = HANDLING::DESTROYBULLET;
}
obj.delete();
return; }
case STATE::FREEZE:
if (--obj.freeze <= 0)
obj.state = obj.oldState;
break;
case STATE::WAIT:
obj.frameID = ++obj.counter >> 2;
obj.determineCurFrame();
if (obj.counter > 70) {
const int nearest = obj.findNearestPlayer(160 * 160);
if (nearest >= 0 && ((jjPlayers[nearest].xPos > obj.xPos) == (obj.direction == 1))) {
obj.counter = 0;
obj.frameID = 0;
obj.state = STATE::ATTACK;
obj.curAnim += 2;
}
}
break;
case STATE::ATTACK:
obj.frameID = obj.counter++ >> 2;
if (obj.frameID >= 15) {
obj.state = STATE::WAIT;
obj.counter = 0;
obj.curAnim -= 2;
break;
} else if (obj.frameID >= 8) {
obj.frameID = 15 - obj.frameID;
} else if (obj.counter == 30) {
for (int i = 2 + jjDifficulty; --i >= 0; ) {
jjOBJ@ seed = jjObjects[jjAddObject(OBJECT::BULLET, obj.xPos + 20 * obj.direction, obj.yPos - 8, obj.objectID, CREATOR::OBJECT, BEHAVIOR::BULLET)];
seed.curAnim = obj.curAnim + 1;
seed.xSpeed = (0.5 + i * 0.7) * obj.direction;
seed.ySpeed = -(1.2 + i * 0.4);
seed.xAcc = 0.01 * obj.direction;
seed.yAcc = 0.065;
seed.animSpeed = (jjDifficulty >= 2) ? 2 : 1;
seed.playerHandling = HANDLING::ENEMYBULLET;
seed.killAnim = jjAnimSets[ANIM::AMMO] + 70;
seed.counterEnd = 110;
}
}
obj.determineCurFrame();
break;
}
obj.draw();
}
class TrickOrTrickKidPumpkinBombClass : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
if (obj.state == STATE::START) {
obj.curAnim += 1;
obj.playerHandling = HANDLING::SPECIAL;
obj.scriptedCollisions = true;
obj.isFreezable = false;
obj.light = 9;
obj.lightType = LIGHT::PLAYER;
if (obj.creatorType == CREATOR::OBJECT && jjObjects[obj.creatorID].eventID == obj.eventID) {
obj.direction = jjObjects[obj.creatorID].direction;
obj.deactivates = false;
obj.state = STATE::BOUNCE;
obj.bulletHandling = HANDLING::IGNOREBULLET;
} else {
obj.state = STATE::FALL;
obj.isTarget = false;
obj.bulletHandling = HANDLING::DESTROYBULLET;
}
}
const int nearest = obj.findNearestPlayer(160 * 160);
if (nearest >= 0)
obj.frameID = ((jjPlayers[nearest].xPos > obj.xPos) == (obj.direction == 1)) ? 1 : 2;
//else
// obj.frameID = 0;
obj.curFrame = jjAnimations[obj.curAnim] + obj.frameID;
if (obj.state == STATE::BOUNCE) {
const jjOBJ@ creator = jjObjects[obj.creatorID];
obj.xPos = creator.xPos;//just in case?
if (creator.state == STATE::FREEZE) {
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::FROZEN);
} else {
obj.age += 8;
const auto bounceHeight = (obj.age & 1024 == 0) ? 0 : jjSin(obj.age) * 40;
obj.yPos = creator.yPos + jjAnimFrames[creator.curFrame].hotSpotY + 21 + (bounceHeight > 0 ? 0 : bounceHeight);
jjDrawRotatedSpriteFromCurFrame(
obj.xPos, obj.yPos,
obj.curFrame,
bounceHeight >= 0 ? int(sin(abs(jjSin(obj.age >> 1))) * 40 * obj.direction) : 0,
obj.direction,1,
creator.justHit == 0 ? SPRITE::NORMAL : SPRITE::SINGLECOLOR,
15
);
}
} else if (obj.state == STATE::DEACTIVATE) {
obj.delete();
} else if (obj.state == STATE::FALL) {
if (jjMaskedPixel(int(obj.xPos), int(obj.yPos))) {
if (obj.frameID != 0 && ++obj.counter >= 100) {
explode(obj);
return;
}
obj.ySpeed = 0;
jjDrawSprite(obj.xPos, obj.yPos - 20, ANIM::AMMO, 1, (++obj.age) >> 1, -obj.direction);
} else
obj.yPos += obj.ySpeed += 0.05;
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction);
}
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ play, int force) {
if (bullet is null) {
if (play.hurt() && obj.state == STATE::FALL)
explode(obj);
}
return true;
}
void explode(jjOBJ@ obj) const {
jjSample(obj.xPos, obj.yPos, SOUND::COMMON_SPLUT);
jjSample(obj.xPos, obj.yPos, SOUND::AMMO_BOEM1);
obj.blast(100*100, false);
obj.particlePixelExplosion(2);
obj.particlePixelExplosion(2);
obj.determineCurAnim(ANIM::AMMO, 3);
obj.behavior = BEHAVIOR::EXPLOSION;
obj.playerHandling = HANDLING::EXPLOSION;
}
}
TrickOrTrickKidPumpkinBombClass TrickOrTrickKidPumpkinBomb();
array<array<int>> PuppetStringLocationsX = {
//front hand, front foot, head, back hand, back foot
{3-29, 3-29, 0, 47-29, 47-29},
{6, 44, 29, 55, 21},
{6, 34, 23, 44, 24},
{27, 28, 25, 20, 36},
{57, 29, 35, 10, 57},
{40, 26, 30, 20, 50},
{18, 24, 26, 39, 44},
{12, 13, 26, 50, 42},
{23, 8, 34, 53, 42},
{21, 5, 28, 55, 36},
{12, 9, 25, 54, 30},
{4, 52, 33, 62, 33},
{5, 54, 34, 62, 27}
}, PuppetStringLocationsY = {
//front hand, front foot, head, back hand, back foot
{3-11, 17-11, 0, 17-11, 3-11},
{34, 54, 5, 24, 57},
{28, 54, 3, 25, 49},
{37, 51, 7, 32, 45},
{21, 50, 9, 33, 27},
{44, 58, 11, 45, 50},
{51, 62, 10, 49, 63},
{33, 55, 10, 41, 58},
{21, 20, 15, 32, 57},
{19, 44, 16, 32, 62},
{19, 60, 11, 31, 63},
{19, 54, 9, 26, 62},
{35, 47, 5, 26, 60}
};
bool PuppetLocationsAdjusted = false;
class Puppet : jjBEHAVIORINTERFACE {
array<array<jjOBJ@>> Locations;
int EventID;
Puppet(uint8 eventID) {
EventID = eventID;
if (!PuppetLocationsAdjusted) {
PuppetLocationsAdjusted = true;
auto walkingFrame = jjAnimations[jjObjectPresets[eventID].curAnim].firstFrame;
for (uint i = 1; i <= 12; ++i) {
const jjANIMFRAME@ frame = jjAnimFrames[walkingFrame++];
for (uint j = 0; j < 5; ++j) {
PuppetStringLocationsX[i][j] += frame.hotSpotX;
PuppetStringLocationsY[i][j] += frame.hotSpotY;
}
}
}
Initiate();
}
void Initiate() {
for (int x = jjLayerWidth[4]; --x >= 0;)
for (int y = jjLayerHeight[4]; --y >= 0;) {
if (jjEventGet(x,y) == EventID) {
jjParameterSet(x,y, -1,1, 1); //active
const uint groupID = jjParameterGet(x,y, 1,8);
if (Locations.length <= groupID)
Locations.resize(groupID + 1);
array<jjOBJ@>@ group = Locations[groupID];
if (group.length == 0)
group.insertLast(jjObjects[jjAddObject(EventID, 0,0, groupID,CREATOR::LEVEL, jjVOIDFUNCOBJ(PuppetHand))]);
jjOBJ@ newLocation = jjObjects[jjAddObject(EventID, x * 32 + 15, y * 32 + 15, group[0].objectID, CREATOR::OBJECT, BEHAVIOR::INACTIVE)];
newLocation.behavior = this;
newLocation.behave();
group.insertLast(newLocation);
}
}
for (uint groupID = 0; groupID < Locations.length; ++groupID) {
array<jjOBJ@>@ group = Locations[groupID];
if (group.length < 2)
continue;
float averageX = 0, averageY = 0;
const uint locationCount = group.length - 1;
for (uint i = 1; i <= locationCount; ++i) {
averageX += group[i].xOrg / locationCount;
averageY += group[i].yOrg / locationCount;
}
group[0].xPos = averageX; //hand
group[0].yPos = averageY;
}
}
void PuppetHand(jjOBJ@ obj) {
float targetX = 0, targetY;
switch (obj.state) {
case STATE::START:
obj.curAnim += 2;
obj.determineCurFrame();
obj.state = STATE::TURN;
//obj.isFreezable = true;
obj.isTarget = true;
//obj.deactivates = true;
break;
case STATE::TURN:
{
array<jjOBJ@> targets;
array<jjOBJ@>@ group = Locations[obj.creatorID];
for (uint i = 0; i < group.length; ++i)
if (group[i].state == STATE::DELAYEDSTART && cast<jjBEHAVIORINTERFACE@>(group[i].behavior) is this)
targets.insertLast(group[i]);
if (targets.length == 0) {
obj.particlePixelExplosion(72); //gray
if (obj.var[5] == 1) { //alternate behavior
obj.state = STATE::KILL;
obj.behave(BEHAVIOR::GEMSTOMP);
} else {
obj.delete();
}
return;
}
obj.special = targets[jjRandom() % targets.length].objectID;
if (targets.length == 1)
obj.points = (group.length - 1) * 200;
}
obj.state = STATE::FLY;
obj.playerHandling = HANDLING::PARTICLE;
case STATE::FLY: {
jjOBJ@ target = jjObjects[obj.special];
targetX = target.xPos;
targetY = target.yPos - 70;
if (abs(obj.xPos - targetX) < 14 && abs(obj.yPos - targetY) < 14) {
target.energy = 99;
target.state = STATE::WALK;
target.playerHandling = HANDLING::ENEMY;
target.isFreezable = true; //hmmm
//target.isTarget = true; //hmmm
obj.state = STATE::ROTATE;
obj.curFrame += 1;
obj.playerHandling = HANDLING::ENEMY;
obj.energy = 1;
}
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::TRANSLUCENT);
}
case STATE::ROTATE:
if (targetX == 0) { //didn't come here from FLY
const jjOBJ@ target = jjObjects[obj.special];
obj.xPos = targetX = target.xPos + jjSin(obj.counter * 3) * 14;
targetY = target.yPos - 70 + jjCos(obj.counter += 3) * 9;
jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.age = int(jjSin(obj.counter * 6) * 16));
//obj.draw();
}
if (obj.xPos < targetX - 5) obj.direction = 3;
else if (obj.xPos > targetX + 5) obj.direction = -3;
else obj.direction = 0;
obj.xPos += obj.direction;
if (obj.yPos < targetY - 5) obj.yPos += 3;
else if (obj.yPos > targetY + 5) { obj.yPos -= 3; obj.direction ^= 0x40; }
break;
case STATE::FREEZE:
if (--obj.freeze <= 0)
obj.state = obj.oldState;
obj.draw();
break;
case STATE::DEACTIVATE:
//nothing, ReloadPuppets will take care of everything later
break;
case STATE::KILL:
obj.energy = (jjDifficulty <= 2) ? 1 : 2;
obj.state = STATE::TURN;
obj.curFrame -= 1;
obj.playerHandling = HANDLING::PARTICLE;
jjObjects[obj.special].state = STATE::KILL;
break;
}
}
void onBehave(jjOBJ@ obj) { //body
switch (obj.state) {
case STATE::START:
{
const int color = jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0, 1);
obj.special = jjAnimations[obj.curAnim + 5] + color;
if (color == 1) {
obj.curFrame += jjAnimations[obj.curAnim].frameCount;
obj.curAnim += 1;
obj.xSpeed = 2;
}
}
obj.putOnGround(true);
obj.state = STATE::DELAYEDSTART;
case STATE::DELAYEDSTART:
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.special, obj.direction);
return;
case STATE::WALK:
obj.xPos -= abs(jjSin(jjGameTicks << 2)) * obj.direction;
case STATE::FREEZE:
obj.energy = 99;
obj.behave(BEHAVIOR::WALKINGENEMY, false);
{
const jjOBJ@ hand = jjObjects[obj.creatorID];
const array<int>@
HandX = @PuppetStringLocationsX[0],
HandY = @PuppetStringLocationsY[0],
LimbX = @PuppetStringLocationsX[obj.frameID + 1],
LimbY = @PuppetStringLocationsY[obj.frameID + 1];
for (uint i = 0; i < 5; ++i) {
const auto handX = hand.xPos + jjCos(hand.age) * HandX[i] - jjSin(hand.age) * HandY[i];
const auto handY = hand.yPos + jjCos(hand.age) * HandY[i] - jjSin(hand.age) * HandX[i];
const auto limbX = obj.xPos + LimbX[i] * obj.direction;
const auto limbY = obj.yPos + LimbY[i];
const auto dX = limbX - handX;
const auto dY = limbY - handY;
const auto length = sqrt(dX * dX + dY * dY) / 50;
if (length != 0)
jjDrawRotatedSpriteFromCurFrame(
handX, handY,
hand.curFrame + 1,
int(atan2(
dX, dY
) * 1024 / 6.283185307179586),
1, length,
SPRITE::SINGLECOLOR,64+i,
i > 1 ? 4 : 3
);
}
}
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, obj.state == STATE::FREEZE ? SPRITE::FROZEN : SPRITE::NORMAL);
break;
case STATE::KILL:
obj.state = STATE::DONE;
obj.playerHandling = HANDLING::PARTICLE;
{
const auto firstShardFrame = jjAnimations[obj.curAnim + 3].firstFrame;
for (uint i = 0; i < 11; ++i)
jjAddObject(OBJECT::SHARD, obj.xPos, obj.yPos, firstShardFrame + i, CREATOR::OBJECT, PuppetShard);
}
break;
}
}
}
void PuppetShard(jjOBJ@ obj) {
obj.behave(BEHAVIOR::SHARD, false);
jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.creatorID, obj.direction);
}
void ReloadPuppets() {
for (uint i = 32; i < 256; ++i) {
jjBEHAVIORINTERFACE@ bi = cast<jjBEHAVIORINTERFACE@>(jjObjectPresets[i].behavior);
if (bi !is null) {
Puppet@ p = cast<Puppet@>(bi);
if (p !is null) {
p.Locations.resize(0);
p.Initiate();
}
}
}
}
array<int> NextSuckerTime(jjLocalPlayerCount, 0);
array<const jjOBJ@> LastSuckerObject(jjLocalPlayerCount, null);
array<bool> PlayerOnFire(jjLocalPlayerCount, false);
class RingOfFire : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
if (obj.state == STATE::START) {
obj.age = obj.special = jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0, 3) * 128; //special: original angle
obj.state = STATE::FLY;
obj.var[2] = jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 3, 3); //style
if (jjDifficultyOrig >= 1)
for (int i = 0; i < 2; ++i)
obj.var[i] = jjAddObject(obj.eventID, 0,0, obj.objectID, CREATOR::OBJECT, function(h){h.behavior = RingOfFireHurtPoint; h.behave();});
} else if (obj.state == STATE::DEACTIVATE) {
if (jjDifficultyOrig >= 1)
for (int i = 0; i < 2; ++i)
jjObjects[obj.var[i]].delete();
obj.deactivate();
} else {
//...
switch (obj.var[2]) {
case 0: //static
break;
case 1: //flip45
obj.age = int(obj.special + jjSin(jjGameTicks << 1) * 128);
break;
case 2: //spin
obj.age = (jjGameTicks << 2) + obj.special;
break;
case 3: //point at player
{
const int playerID = obj.findNearestPlayer(400 * 400);
if (playerID >= 0) {
const jjPLAYER@ play = jjPlayers[playerID];
if (NextSuckerTime[play.localPlayerID] <= jjGameTicks || LastSuckerObject[play.localPlayerID] !is obj)
obj.age = int(atan2(
obj.yPos - play.yPos,
play.xPos - obj.xPos
) * 1024 / 6.283185307179586);
}
}
break;
}
if (jjDifficultyOrig >= 1)
for (int i = 0; i < 2; ++i) {
jjOBJ@ hurt = jjObjects[obj.var[i]];
hurt.xPos = obj.xPos + jjSin(obj.age + (i << 9)) * 32;
hurt.yPos = obj.yPos + jjCos(obj.age + (i << 9)) * 32;
}
}
}
void onDraw(jjOBJ@ obj) {
jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curAnim+1, obj.age, 1,1, SPRITE::NORMAL,0, 3);
if (jjDifficultyOrig >= 1) {
const auto cosa = jjCos(obj.age), sina = jjSin(obj.age);
for (int i = 0; i < 1024; i += !jjLowDetail ? 64 : 128) {
const auto x = jjSin(i) * 13, y = jjCos(i) * 32;
jjDrawSprite(
obj.xPos + cosa * x + sina * y,
obj.yPos + cosa * y - sina * x,
ANIM::AMMO, 13, (jjGameTicks >> 3) + i,
1,
SPRITE::NORMAL, 0,
i < 512 ? 4 : 3
);
}
}
//or anim 55?
jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curAnim, obj.age);
//obj.draw();
}
bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ play, int force) {
if (bullet is null && play.isLocal) {// && play.curAnim - jjAnimSets[play.setID] != RABBIT::HURT && play.blink == 0) {
if (abs(play.invincibility) < 20)
play.invincibility = (play.invincibility <= 0) ? -20 : 20;
if (NextSuckerTime[play.localPlayerID] <= jjGameTicks || LastSuckerObject[play.localPlayerID] !is obj) {
NextSuckerTime[play.localPlayerID] = jjGameTicks + 25;
@LastSuckerObject[play.localPlayerID] = obj;
PlayerOnFire[play.localPlayerID] = false;
play.xPos = obj.xPos;
play.yPos = obj.yPos;
play.suckerTube(int(jjCos(obj.age) * 10), int(jjSin(obj.age) * -17), false);
play.helicopter = 0;
play.alreadyDoubleJumped = false;
if (jjDifficultyOrig > 0)
jjSample(obj.xPos, obj.yPos, SOUND::AMMO_FIREGUN1A);
else
jjSample(obj.xPos, obj.yPos, SOUND::COMMON_BURN, 0, 50000); //trigsample
}
}
return true;
}
}
class RingOfFireHurtPointClass : jjBEHAVIORINTERFACE {
void onBehave(jjOBJ@ obj) {
if (obj.state == STATE::START) {
obj.curFrame -= 4;
obj.playerHandling = HANDLING::PICKUP;
obj.scriptedCollisions = true;
obj.deactivates = false;
}
obj.state = STATE::FLY; //reusable
//obj.draw();
}
bool onObjectHit(jjOBJ@, jjOBJ@, jjPLAYER@ play, int) {
if (play.shieldType != SHIELD::FIRE && play.hurt(1)) {
NextSuckerTime[play.localPlayerID] = jjGameTicks + 70;
PlayerOnFire[play.localPlayerID] = true;
}
return true;
}
}
RingOfFireHurtPointClass RingOfFireHurtPoint;
}
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.