Downloads containing BossrushV6.j2as

Downloads
Name Author Game Mode Rating
JJ2 1.23 vanilla: Miscellaneous stuff Violet CLM Multiple N/A Download file

File preview

bool onDrawScore(jjPLAYER@ play, jjCANVAS@ screen) { return true; }
bool onDrawAmmo(jjPLAYER@ play, jjCANVAS@ screen) { return true; }
bool onDrawLives(jjPLAYER@ play, jjCANVAS@ screen) { return jjDifficulty <= 0; }

void onLevelLoad() {
	jjObjectPresets[OBJECT::BLASTERBULLET].behavior = BulletRepositioner;
	jjObjectPresets[OBJECT::FISH].behavior = MyFish;
	jjObjectPresets[OBJECT::FISH].doesHurt = 0;
	jjObjectPresets[OBJECT::UTERUS].behavior = MyUterus;
	jjObjectPresets[OBJECT::UTERUSSPIKEBALL].behavior = MyUterusEl;
	jjObjectPresets[OBJECT::CRAB].behavior = CrabBullet;
	jjObjectPresets[OBJECT::CRAB].determineCurAnim(ANIM::UTERUS, 1);
	jjObjectPresets[OBJECT::CRAB].doesHurt = 1;
	jjObjectPresets[OBJECT::CRAB].playerHandling = HANDLING::PARTICLE;
	jjObjectPresets[OBJECT::CRAB].xAcc = 0;
	jjObjectPresets[OBJECT::CRAB].points = 0;
	jjObjectPresets[OBJECT::UTERUSSPIKEBALL].doesHurt = 1;
	jjObjectPresets[OBJECT::UTERUSSPIKEBALL].playerHandling = HANDLING::PARTICLE;
	
	jjObjectPresets[OBJECT::UTERUS].deactivates = false;
	jjObjectPresets[OBJECT::UTERUSSPIKEBALL].deactivates = false;
	jjObjectPresets[OBJECT::CRAB].deactivates = false;
	jjObjectPresets[OBJECT::BLASTERBULLET].deactivates = false;
}
bool NeedToFreezeCameras = true;
array<jjOBJ@> PlayerCollisionObjects(jjLocalPlayerCount, null);
void onLevelBegin() {
	jjWaterLayer = 2;
	jjSetWaterLevel(17 * 32, true);
	jjLayerHasTiles[6] = false;
	jjTexturedBGTexture = TEXTURE::MUCKAMOKNIGHT;
	jjTexturedBGUsed = jjLocalPlayerCount == 1;
	jjAnimSets[ANIM::FISH].load();
}
const float MAXANGLE = 1024.f;
const int MAXANGLERANGE = 1023;
const float LEFTBOUNDARY = 16.f;
const float TOPEXTENT = (8 * 32.f);
const float LEVELCENTERX = 10*32.f + 16.f;
const float LEVELCENTERY = 25*32.f + 16.f;
const float LEVELRADIUS = 208.f;
void onPlayer(jjPLAYER@ play) {
	if (play.xPos < LEFTBOUNDARY)
		play.xPos = play.xPos + MAXANGLE;
	else if (play.xPos > LEFTBOUNDARY + MAXANGLE)
		play.xPos = play.xPos - MAXANGLE;
	if (play.health <= 0)
		NeedToFreezeCameras = true;
}
void onPlayerInput(jjPLAYER@ play) {
	if (play.yPos < TOPEXTENT) { //go through the center and you're arguably in negative space or something
		bool swap = play.keyLeft;
		play.keyLeft = play.keyRight;
		play.keyRight = swap;
	}
}

const float FISHXOFFSET = 200;
void BulletRepositioner(jjOBJ@ bull) {
	jjPLAYER@ play = jjPlayers[bull.creatorID];
	jjOBJ@ playerCollisionObject = PlayerCollisionObjects[play.localPlayerID];
	jjANIMFRAME@ frame = jjAnimFrames[play.curFrame];
	
	float baseAngle = play.xPos - LEFTBOUNDARY, xAngle = jjSin(baseAngle), yAngle = jjCos(baseAngle);
	bull.xPos = playerCollisionObject.xPos + (frame.hotSpotX - frame.gunSpotX) * playerCollisionObject.direction * yAngle + (frame.hotSpotY - frame.gunSpotY) * xAngle;
	bull.yPos = playerCollisionObject.yPos - (frame.hotSpotX - frame.gunSpotX) * playerCollisionObject.direction * xAngle + (frame.hotSpotY - frame.gunSpotY) * yAngle;
	
	if (play.yPos < TOPEXTENT) {
		bull.xSpeed = -bull.xSpeed;
		bull.xAcc = -bull.xAcc;
	}
	
	float newXSpeed = bull.xSpeed *  yAngle + bull.ySpeed * xAngle;
	bull.ySpeed =     bull.xSpeed * -xAngle + bull.ySpeed * yAngle;
	bull.xSpeed = newXSpeed;
	
	float newXAcc = bull.xAcc *  yAngle + bull.yAcc * xAngle;
	bull.yAcc =     bull.xAcc * -xAngle + bull.yAcc * yAngle;
	bull.xAcc = newXAcc;
	
	bull.var[7] = 0; //gain no xAcc from player's xSpeed
	bull.curAnim = jjObjectPresets[bull.eventID].curAnim; //instead of rotating 90 degrees if fired upwards
	bull.behavior = BEHAVIOR::BULLET;
}
void MyFish(jjOBJ@ obj) {
	if (obj.state == STATE::START) {
		obj.state = STATE::IDLE;
		obj.direction = (obj.xOrg < 0) ? 1 : -1;
	} else if (obj.xPos < -FISHXOFFSET - 10 || obj.xPos > 650 + FISHXOFFSET)
		obj.delete();
	else {
		obj.xPos = obj.xPos + obj.xSpeed * obj.direction;
		jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos + jjSin(obj.age << 2) * obj.xSpeed * 6, jjAnimations[jjAnimSets[ANIM::FISH].firstAnim + 1].firstFrame + ((++obj.age >> 2) % 6), 0, obj.xSpeed * obj.direction, obj.xSpeed, SPRITE::TRANSLUCENT, 0, (obj.xSpeed < 1.5) ? 5 : 1);
	}
}

const int UTERUSFULLSIZE = 350;
void onMain() {
	if (NeedToFreezeCameras) {
		NeedToFreezeCameras = false;
		for (int i = 0; i < jjLocalPlayerCount; ++i) {
			jjPLAYER@ play = jjLocalPlayers[i];
			play.cameraFreeze(16 + play.subscreenX, 18*32 + play.subscreenY, false, true);
			if (PlayerCollisionObjects[i] is null || PlayerCollisionObjects[i].behavior != PlayerCollision)
				@PlayerCollisionObjects[i] = jjObjects[jjAddObject(OBJECT::THING, 64, 64, play.playerID, CREATOR::PLAYER, PlayerCollision)];
		}
	}
	uint rand = jjRandom();
	if (!jjLowDetail && rand & 127 == 0) {
		jjOBJ@ newFish = jjObjects[jjAddObject(OBJECT::FISH, (rand & 128 == 128) ? -FISHXOFFSET : 640 + FISHXOFFSET, (10 + ((rand >> 8) & 15)) * 32)];
		newFish.xSpeed = ((rand >> 12) % 100) / 20.f;
		newFish.age = rand >> 20;
	}
	
	if (jjGameTicks == UTERUSFULLSIZE / 2) {
		jjSamplePriority(SOUND::COMMON_BENZIN1);
		for (int x = 8; x < 13; ++x) for (int y = 21; y < 33; ++y) {
			uint16 tileID = jjTileGet(5, x, y);
			if (tileID != 0) {
				jjTileSet(5, x, y, 0);
				if (y < 32) {
					jjAddParticleTileExplosion(x, y, tileID, false);
				}
			}
		}
	}
}

void PlayerCollision(jjOBJ@ obj) {
	jjPLAYER@ play = jjPlayers[obj.creator];
	float xExtent = play.xPos - LEFTBOUNDARY, yExtent = (play.yPos - TOPEXTENT) / TOPEXTENT * LEVELRADIUS;
	obj.xPos = LEVELCENTERX + jjSin(xExtent) * yExtent;
	obj.yPos = LEVELCENTERY + jjCos(xExtent) * yExtent;
	obj.direction = (play.yPos >= TOPEXTENT) ? play.direction : -play.direction;
	if (play.blink == 0 || (jjGameTicks >> 3) & 1 == 1)
		jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, play.curFrame, xExtent, obj.direction, 1, SPRITE::NORMAL, 0, 1);
	
	for (int objectID = jjObjectCount - 1; objectID >= 1; --objectID) {
		jjOBJ@ obj2 = jjObjects[objectID];
		if (obj2.isActive && obj2.doesHurt > 0 && obj.objectID != objectID && obj.doesCollide(obj2)) {
			if (obj2.playerHandling == HANDLING::ENEMY && (play.buttstomp < 50 || play.specialMove > 0)) {
				play.buttstomp = 50;
				play.ySpeed = -6;
				if ((obj2.energy -= 4) <= 0) {
					obj2.particlePixelExplosion(2);
					obj2.delete();
					jjSamplePriority(SOUND::COMMON_SPLAT1);
					play.extendInvincibility(-70);
				}
			} else
				play.hurt(obj2.doesHurt);
		}
	}
	
	if (jjRandom() & 15 == 0)
		jjAddObject(OBJECT::BUBBLE, obj.xPos, obj.yPos);
}

/*Uterus animations:
0: Returning to shell
1: Crab ball
2: Shell
3: Normal
4: Crab deballing
5: Coming out of shell
6: Spike ball
7: Crab
*/

float getObjectDistanceFromCenter(jjOBJ@ obj) {
	return sqrt(pow(obj.xPos - LEVELCENTERX, 2) + pow(obj.yPos - LEVELCENTERY, 2));
}

const float UTERUSINITIALANGLE = MAXANGLE * 0.75f;
void UterusAttack(jjOBJ@ obj, STATE::State newState) {
	obj.state = newState;
	obj.counter = -1;
	obj.yAcc = jjRandom() & MAXANGLERANGE;
	jjSamplePriority(SOUND::UTERUS_SCREAM1);
}
const float UTERUSBOUNCESPEED = 8;
const int UTERUSWAITCRABINTERVAL = MAXANGLE / 8;
const int UTERUSWALKCRABINTERVAL = MAXANGLE / 5;
void UterusSetSpeedsBounce(jjOBJ@ obj) {
	obj.xSpeed = jjSin(obj.xOrg - 256) * UTERUSBOUNCESPEED;
	obj.ySpeed = jjCos(obj.xOrg - 256) * UTERUSBOUNCESPEED;
}
const array<STATE::State> UterusAttackStates = {STATE::BOUNCE, STATE::WAIT, STATE::CIRCLE, STATE::ROTATE, STATE::WALK, STATE::TURN, STATE::FADEIN, STATE::FADEOUT};
array<STATE::State> NextSetOfUterusAttackStates = {STATE::WALK, STATE::ROTATE, STATE::FADEOUT}; //a reasonable introduction
void MyUterus(jjOBJ@ obj) {
	//xOrg: angle
	//yOrg: size
	switch(obj.state) {
	case STATE::START:
		obj.age = 0;
		obj.xOrg = UTERUSINITIALANGLE;
		obj.yOrg = .1;
		obj.xPos = LEVELCENTERX;
		obj.yPos = LEVELCENTERY;
		obj.state = STATE::DELAYEDSTART;
		obj.determineCurAnim(ANIM::UTERUS, 2);
		obj.scriptedCollisions = true;
		obj.bulletHandling = HANDLING::DETECTBULLET;
		obj.doesHurt = 1;
	case STATE::DELAYEDSTART:
		if (obj.age >= UTERUSFULLSIZE) {
			obj.yOrg = 1;
			obj.xAcc = 1; //rotate speed
			UterusAttack(obj, STATE::IDLE);
			jjLocalPlayers[0].boss = obj.objectID;
			jjLocalPlayers[0].activateBoss();
			jjMusicLoad("boss.j2b");
		} else
			obj.yOrg = .1 + obj.age * (.9 / UTERUSFULLSIZE);
		break;
	case STATE::IDLE:
		if (NextSetOfUterusAttackStates.isEmpty()) {
			array<STATE::State> inbetween = UterusAttackStates;
			while (!inbetween.isEmpty()) {
				uint index = jjRandom() % inbetween.length;
				NextSetOfUterusAttackStates.insertLast(inbetween[index]);
				inbetween.removeAt(index);
			}
		}
		if (obj.counter >= 120) {
			UterusAttack(obj, NextSetOfUterusAttackStates[0]);
			NextSetOfUterusAttackStates.removeAt(0);
		}
		break;
	case STATE::BOUNCE:
		if (obj.counter == 0) {
			obj.xAcc = 20;
			obj.counterEnd = 10 + (jjRandom() & 7);
		} if (obj.counter == 48) {
			obj.xAcc = 0; //first attack: no spin
		} else if (obj.counter == 50) { //get going
			UterusSetSpeedsBounce(obj);
		} else if (obj.counter > 55 && getObjectDistanceFromCenter(obj) >= LEVELRADIUS) {
			if (--obj.counterEnd <= 0) { //final bounce
				obj.state = STATE::DONE;
				obj.counter = -1;
			} else {
				obj.xOrg = obj.xOrg + MAXANGLE/2 + (jjRandom() & 63) - 32; //bounce backwards
				obj.xAcc = float(jjRandom() & 15) - 7.5;
				obj.counter = 49;
				jjSamplePriority(SOUND::COMMON_LAND);
			}
		} else if (obj.counter > 50) {
			obj.xPos = obj.xPos + obj.xSpeed;
			obj.yPos = obj.yPos + obj.ySpeed;
			UterusSetSpeedsBounce(obj);
			if (jjGameTicks & 1 == 1) {
				jjOBJ@ expl = jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos, obj.objectID)];
				expl.lightType = LIGHT::POINT2;
				expl.determineCurAnim(ANIM::AMMO, 71);
				expl.doesHurt = 2; //don't touch!
			}
		}
		break;
	case STATE::DONE: {
		bool angleRight = false;
		float currentAngle = float(int(obj.xOrg) & MAXANGLERANGE);
		
		if (obj.counter <= 0) {
			jjSamplePriority(SOUND::COMMON_STEAM);
			obj.yAcc = jjRandom() & MAXANGLERANGE; //new angle target
			if (abs(currentAngle - obj.yAcc) < abs(currentAngle - obj.yAcc/2))
				obj.xAcc = 4;
			else
				obj.xAcc = -4;
		} else if (abs(currentAngle - obj.yAcc) < 9) {
			obj.xOrg = obj.yAcc;
			obj.xAcc = 0;
			angleRight = true;
		}
		bool xCenter = false;
		if (obj.xPos > LEVELCENTERX + 2)
			obj.xPos = obj.xPos - 2;
		else if (obj.xPos < LEVELCENTERX -2)
			obj.xPos = obj.xPos + 2;
		else xCenter = true;
		if (obj.yPos > LEVELCENTERY + 2)
			obj.yPos = obj.yPos - 2;
		else if (obj.yPos < LEVELCENTERY -2)
			obj.yPos = obj.yPos + 2;
		else if (xCenter && angleRight) {
			obj.state = STATE::IDLE;
			obj.counter = 0;
		}
		break; }
	case STATE::WAIT:
		if (obj.counter == 0)
			obj.xAcc = (float(jjRandom() & 7) - 3.5) * 3;
		if (obj.counter & 31 == 0) {
			makeCrabNoise();
			for (int i = (obj.counter & 128 == 128) ? 0 : UTERUSWAITCRABINTERVAL/2; i < MAXANGLE; i += UTERUSWAITCRABINTERVAL) {
				jjAddObject(OBJECT::CRAB, obj.xPos, obj.yPos, i); //pass angle as creatorID
			}
		}
		if (obj.counter > 490) {
			obj.state = STATE::DONE; //rotate back
			obj.counter = -1;
		}
		break;
	case STATE::CIRCLE:
		if (obj.counter == 0)
			obj.xAcc = (jjRandom() & 1 == 1) ? -13 : 13;
		if (obj.counter % 5 == 0) {
			jjAddObject(OBJECT::CRAB, obj.xPos, obj.yPos, obj.xOrg + 256); //pass angle as creatorID
		}
		if (jjDifficulty >= 1 && obj.counter % 30 == 0) { //not on Easy
			makeCrabNoise();
			jjOBJ@ playerCollisionObject = PlayerCollisionObjects[0];
			jjOBJ@ crab = jjObjects[jjAddObject(OBJECT::CRAB, obj.xPos, obj.yPos, int(atan2(obj.yPos - playerCollisionObject.yPos, playerCollisionObject.xPos - obj.xPos) / 6.28 * MAXANGLE) + 256)];
			crab.lightType = LIGHT::POINT2;
			crab.xSpeed = crab.xSpeed * 2;
			crab.ySpeed = crab.ySpeed * 2;
			crab.doesHurt = 2;
		}
		if (obj.counter > 1023) {
			obj.state = STATE::DONE; //rotate back
			obj.counter = -1;
		}
		break;
	case STATE::ROTATE:
		if (obj.counter == 0) {
			obj.xAcc = (jjRandom() & 1 == 1) ? -18 : 18;
			obj.counterEnd = 0;
		}
		if (++obj.counterEnd > 14 && jjRandom() & 15 == 0) {
			obj.counterEnd = 0;
			makeCrabNoise();
			for (float i = 0; i < 10; ++i) {
				jjOBJ@ crab = jjObjects[jjAddObject(OBJECT::CRAB, obj.xPos, obj.yPos, obj.xOrg + 512)];
				crab.xSpeed = crab.xSpeed * (1 + i/3);
				crab.ySpeed = crab.ySpeed * (1 + i/3);
			}
		}
		if (obj.counter > 490) {
			obj.state = STATE::DONE; //rotate back
			obj.counter = -1;
		}
		break;
	case STATE::WALK:
		if (obj.counter == 1) for (int i = jjRandom() % UTERUSWALKCRABINTERVAL; i < MAXANGLE; i += UTERUSWALKCRABINTERVAL) {
			jjOBJ@ crab = jjObjects[jjAddObject(OBJECT::CRAB, obj.xPos, obj.yPos, i)];
			crab.special = 1; //comes out crab
			crab.energy = 1; //more of them, so make them weaker
			crab.xSpeed = crab.xSpeed * 3.5;
			crab.ySpeed = crab.ySpeed * 3.5;
		} else if (obj.counter > 210) {
			obj.state = STATE::IDLE;
			obj.counter = -80;
		}
		break;
	case STATE::TURN:
		if (obj.counter == 0)
			obj.xAcc = (jjRandom() & 1 == 1) ? -13 : 13;
		if (obj.counter & 127 == 0) {
			if (obj.counter < 385) {
				jjSamplePriority(SOUND::UTERUS_CRABCLOSE);
				float xSpeed = (obj.counter < 129) ? 2 : -2;
				float xOrg = obj.yAcc + ((obj.counter < 129) ? 0 : 256);
				for (float i = 0.1f; i < 1.2f; i += 0.1f) {
					jjOBJ@ spike = jjObjects[jjAddObject(OBJECT::UTERUSSPIKEBALL, obj.xPos, obj.yPos, obj.objectID)];
					spike.xSpeed = xSpeed;
					spike.xOrg = xOrg - i * 100;
					spike.ySpeed = 4;
					spike.yOrg = i;
					spike.state = STATE::TURN;
				}
			} else {
				obj.state = STATE::DONE;
				obj.counter = -1;
			}
		}
		break;
	case STATE::FADEIN:
		if (obj.counter == 0 || obj.counter == 128) {
			if (obj.counter == 0) {
				obj.xAcc = (jjRandom() & 1 == 1) ? -5 : 5;
				if (jjDifficulty >= 1) for (float i = 0; i < MAXANGLE; i += MAXANGLE / 4) { //not on Easy
					jjOBJ@ spike = jjObjects[jjAddObject(OBJECT::UTERUSSPIKEBALL, obj.xPos, obj.yPos, obj.objectID)];
					spike.xSpeed = 1.5f;
					spike.xOrg = obj.yAcc + i;
					spike.ySpeed = 3.5f;
					spike.yOrg = 1.f;
					spike.state = STATE::FADEIN;
					spike.lightType = LIGHT::POINT2;
					spike.doesHurt = 2;
				}
			}
			for (float i = 0; i < MAXANGLE; i += MAXANGLE / 10) {
				jjOBJ@ spike = jjObjects[jjAddObject(OBJECT::UTERUSSPIKEBALL, obj.xPos, obj.yPos, obj.objectID)];
				spike.xSpeed = 0.5f;
				spike.xOrg = obj.yAcc + i - ((obj.counter == 0) ? 0 : 0.5f * 128);
				spike.ySpeed = 2.f;
				spike.yOrg = 1.f;
				spike.state = STATE::FADEIN;
			}
		} else if (obj.counter > 612) {
			jjSamplePriority(SOUND::COMMON_SPLAT4);
			obj.state = STATE::DONE; //rotate back
			obj.counter = -1;
		}
		break;
	case STATE::FADEOUT:
		if (obj.counter == 0) {
			obj.xAcc = 8;
			for (float i = 0.1f; i < 1.2f; i += 0.1f) {
				for (float j = 256; j <= 768; j += 512) {
					jjOBJ@ spike = jjObjects[jjAddObject(OBJECT::UTERUSSPIKEBALL, obj.xPos, obj.yPos, obj.objectID)];
					spike.xSpeed = obj.xAcc;
					spike.xOrg = obj.xOrg + j + i * 100;
					spike.ySpeed = 1.3 / i;
					spike.yOrg = i;
					spike.state = STATE::FADEOUT;
					spike.lightType = (jjDifficulty >= 2) ? LIGHT::POINT2 : LIGHT::LASER;
					spike.light = 4;
					spike.special = 1; //don't unextend
					spike.doesHurt = 3;
				}
			}
			if (jjDifficulty >= 2) { //only on Hard/Turbo
				jjOBJ@ spike = jjObjects[jjAddObject(OBJECT::UTERUSSPIKEBALL, obj.xPos, obj.yPos, obj.objectID)];
				spike.xSpeed = -6; //running alternate direction but at lesser speed
				spike.xOrg = obj.xOrg + 512;
				spike.ySpeed = 4;
				spike.yOrg = 1;
				spike.state = STATE::FADEOUT;
				spike.lightType = LIGHT::LASER;
				spike.light = 1;
				spike.special = 1; //don't unextend
			}
		} else if (obj.counter > 320) {
			jjSamplePriority(SOUND::COMMON_SHLDOF3);
			obj.state = STATE::IDLE;
			obj.counter = 85;
		}
		break;
	}
	obj.xOrg = obj.xOrg + obj.xAcc;
	++obj.counter;
	obj.frameID = (++obj.age >> 3) % jjAnimations[obj.curAnim].frameCount;
	obj.determineCurFrame();
	jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.xOrg, obj.yOrg, obj.yOrg, (obj.justHit > 0) ? SPRITE::SINGLECOLOR : SPRITE::NORMAL, 15, (obj.state == STATE::DELAYEDSTART) ? 6 : 1);
	if (obj.energy <= 0) { //dead
		jjSamplePriority(SOUND::UTERUS_SCREAM1);
		jjSamplePriority(SOUND::COMMON_STEAM);
		//obj.behavior = MyUterus2; //stage 2
		obj.state = STATE::DELAYEDSTART;
		obj.energy = 4;
		jjPalette.load("Beach2.j2t");
		jjPalette.apply();
	}
	
}

array<SOUND::Sample> ricochetNoises = {SOUND::AMMO_BULFL1, SOUND::AMMO_BULFL2, SOUND::AMMO_BULFL3};
array<SOUND::Sample> crabNoises = {SOUND::UTERUS_SCISSORS1, SOUND::UTERUS_SCISSORS2, SOUND::UTERUS_SCISSORS3, SOUND::UTERUS_SCISSORS4, SOUND::UTERUS_SCISSORS5, SOUND::UTERUS_SCISSORS6, SOUND::UTERUS_SCISSORS7, SOUND::UTERUS_SCISSORS8};
void makeCrabNoise() {
	jjSamplePriority(crabNoises[jjRandom() % crabNoises.length]);
}
void onObjectHit(jjOBJ@ obj, jjOBJ@ bull, jjPLAYER@ play, int force) {
	if (bull !is null) {
		int bulletAngle = int(atan2(obj.yPos - bull.yPos, bull.xPos - obj.xPos) / 6.28 * MAXANGLE) & MAXANGLERANGE;
		int uterusAngle = int(obj.xOrg) & MAXANGLERANGE;
		int angleDifference = abs(bulletAngle - uterusAngle);
		if (angleDifference > 100) { //hits the side
			bull.ricochet();
			jjSamplePriority(ricochetNoises[jjRandom() % ricochetNoises.length]);
		} else {
			obj.energy -= force;
			obj.justHit = 5;
			bull.state = STATE::KILL;
			jjSamplePriority(SOUND::UTERUS_STEP2);
		}
	}
}


void CrabBullet(jjOBJ@ obj) {
	if (obj.state == STATE::START) {
		obj.state = STATE::FLY;
		obj.xOrg = obj.creatorID;
		obj.xSpeed = jjSin(obj.creatorID) * 1.2;
		obj.ySpeed = jjCos(obj.creatorID) * 1.2;
		if (jjDifficulty >= 2 && jjRandom() & 511 == 0) //only on Hard/Turbo
			obj.special = 1;
	}
	obj.xPos = obj.xPos + obj.xSpeed;
	obj.yPos = obj.yPos + obj.ySpeed;
	if (obj.special == 1 && getObjectDistanceFromCenter(obj) >= LEVELRADIUS) {
		obj.curAnim += 6;
		obj.behavior = CrabWalker;
		if (obj.xAcc == 0) {
			obj.direction = jjRandom() & 1 == 1 ? 1 : -1;
			obj.xAcc = obj.direction;
		}
		obj.playerHandling = HANDLING::ENEMY;
		obj.behave();
		jjSamplePriority(SOUND::UTERUS_CRABOPEN2);
		return;
	} else if (getObjectDistanceFromCenter(obj) > 340)
		obj.delete();
	else {
		obj.frameID = (++obj.age >> 3) % jjAnimations[obj.curAnim].frameCount;
		obj.determineCurFrame();
		jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, 0, SPRITE::NORMAL, 0, 1);
	}
}
void CrabWalker(jjOBJ@ obj) {
	if (obj.state == STATE::KILL) {
		obj.delete();
		return;
	}
	obj.xOrg = obj.xOrg + obj.xAcc;
	obj.xPos = LEVELCENTERX + jjSin(obj.xOrg) * LEVELRADIUS;
	obj.yPos = LEVELCENTERY + jjCos(obj.xOrg) * LEVELRADIUS;
	obj.frameID = (++obj.age >> 3) % jjAnimations[obj.curAnim].frameCount;
	obj.determineCurFrame();
	jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.xOrg, obj.direction, 1, (obj.justHit > 0) ? SPRITE::SINGLECOLOR : SPRITE::NORMAL, 15, 1);
}
void MyUterusEl(jjOBJ@ obj) {
	if (obj.state == STATE::START)
		return; //wait for another state to be assigned
	else if (jjObjects[obj.creator].state != obj.state) {
		obj.behavior = BEHAVIOR::EXPLOSION;
		obj.particlePixelExplosion(0);
		obj.curAnim = obj.killAnim;
		obj.doesHurt = 0;
		obj.lightType = LIGHT::NONE;
	} else {
		if (obj.special < 2) ++obj.age;
		float distance = obj.age * obj.ySpeed;
		if (obj.special == 1 && distance > 256) {
			obj.special = 2;
			jjSamplePriority(SOUND::AMMO_LASER);
		}
		distance = (LEVELRADIUS + 16.f) * jjSin(distance) * obj.yOrg;
		obj.xOrg = obj.xOrg + obj.xSpeed;
		obj.xPos = LEVELCENTERX + jjSin(obj.xOrg) * distance;
		obj.yPos = LEVELCENTERY + jjCos(obj.xOrg) * distance;
		jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, 0, SPRITE::NORMAL, 0, 1);
	}
	
}