Downloads containing kangaroo.asc

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: Holiday Hare '17Featured Download ShadowGPW Single player 8.8 Download file
JJ2+ Only: Foo Single Player 2/14:...Featured Download Violet CLM Single player 10 Download file
JJ2+ Only: KangarooFeatured Download Violet CLM Single player 9.1 Download file

File preview

#pragma require "kangaroo.j2a"
/*
API (all expected to be called in onLevelLoad):
	jjOBJ@ Kangaroo::MakeEventJoey(uint8 eventID, int minX = 0, int maxX = 4, int minY = 6, int maxY = 12, int jumpDelay = 35, int minDistance = 224)
		Assign a specific event slot to the Joey enemy, e.g. replacing another enemy type or a food object or something. If you assign multiple event slots, you can get Joey enemies with different parameters.
	jjOBJ@ Kangaroo::MakeEventJill(uint8 eventID, uint8 spawn = 0, bool secondStage = false, int textID = -1)
		Assign a specific event slot to the Jill boss.
			"spawn", if non-zero, is an eventID that the boss will occasionally create from her pouch. This is assumed to be a Joey enemy but may be other objects as well.
			The "secondStage" bool causes Jill to turn red and jump faster after she has lost three-quarters of her health.
			If "textID" is 0-16, defeating Jill will display that text ID.
	void Kangaroo::OnJillDefeat(JILLCALLBACKFUNC@ callback = null)
		Three seconds after defeating Jill, this function will be called. The JILLCALLBACKFUNC pattern is the same as the behavior pattern: a void-returning function taking a jjOBJ@ as its only argument. If "callback" is left null, or if OnJillDefeat is never called, defeating a Jill will simply end the level.
	void Kangaroo::Joey(jjOBJ@ obj)
		The behavior function for the Joey enemy
	void Kangaroo::Jill(jjOBJ@ obj)
		The behavior function for the Jill boss
*/


namespace Kangaroo {
	namespace Private {
		void jillDefeatedDefaultAction(jjOBJ@) {
			jjNxt();
		}
		JILLCALLBACKFUNC@ jillCallback = jillDefeatedDefaultAction;
		
		bool animsLoaded = false;
		uint customAnimID = 0;
		void loadAnims() {
			if (!animsLoaded) {
				animsLoaded = true;
				while (jjAnimSets[ANIM::CUSTOM[customAnimID]] != 0)
					++customAnimID;
				customAnimID = ANIM::CUSTOM[customAnimID];
				jjAnimSets[customAnimID].load(0, "kangaroo.j2a");
				if (!jjSampleIsLoaded(SOUND::BUBBA_BUBBABOUNCE1))
					jjAnimSets[ANIM::BUBBA].load();
			}
		}
		
		void applyGenericEnemySettingsToPreset(jjOBJ@ preset) {
			preset.playerHandling = HANDLING::ENEMY;
			preset.bulletHandling = HANDLING::HURTBYBULLET;
			preset.causesRicochet = false;
			preset.isBlastable = false;
			preset.triggersTNT = true;
			preset.isFreezable = true;
			preset.isTarget = true;
			preset.scriptedCollisions = false;
			preset.direction = 1;
			preset.freeze = 0;
		}
		
		void putKangarooOnGround(jjOBJ@ obj, int width, int height) {
			while (!jjMaskedHLine(int(obj.xPos) - width/2, width, int(obj.yPos) + height/2))
				obj.yPos += 1;
		}
		
		uint firstGloveAnimationFrame;
		const jjANIMFRAME@ roundExplosionFrame = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::AMMO] + 5] + 2];;
		
		void doGloveAt(int x, int y) {
			for (int i = 0; i < jjLocalPlayerCount; ++i) {
				jjPLAYER@ localPlayer = jjLocalPlayers[i];
				if (localPlayer.blink == 0 && roundExplosionFrame.doesCollide(x, y, 0, jjAnimFrames[localPlayer.curFrame], int(localPlayer.xPos), int(localPlayer.yPos), localPlayer.direction))
					localPlayer.hurt();
			}
			for (int i = jjObjectCount - 1; i > 0; --i) {
				jjOBJ@ obj = jjObjects[i];
				if (obj.playerHandling == HANDLING::PLAYERBULLET && obj.state != STATE::EXPLODE && roundExplosionFrame.doesCollide(x, y, 0, jjAnimFrames[obj.curFrame], int(obj.xPos), int(obj.yPos), obj.direction)) {
					obj.ricochet();
					//obj.playerHandling = HANDLING::ENEMYBULLET;
				}
			}
		}
	}
	enum KangarooVariables {
		kvWIDTH = 0, kvHEIGHT, kvMINX, kvMAXX, kvMINY, kvMAXY, kvJUMPDELAY, kvMINDISTANCE, kvGLOVE1FRAME, kvGLOVE2FRAME, kvSECONDSTAGE
	}
	jjOBJ@ MakeEventJoey(uint8 eventID, int minX = 0, int maxX = 4, int minY = 6, int maxY = 12, int jumpDelay = 35, int minDistance = 224) {
		Kangaroo::Private::loadAnims();
		
		jjOBJ@ preset = jjObjectPresets[eventID];
		preset.behavior = Joey;
		preset.determineCurAnim(Kangaroo::Private::customAnimID, 0);
		preset.frameID = 0; preset.determineCurFrame();
		
		Kangaroo::Private::applyGenericEnemySettingsToPreset(preset);
		
		preset.deactivates = true;
		preset.energy = 1;
		preset.points = 200;
		preset.yAcc = 0.33f;
		preset.counter = 0;
		preset.var[kvWIDTH] = 12;
		preset.var[kvHEIGHT] = 28;
		preset.var[kvMINX] = minX;
		preset.var[kvMAXX] = maxX;
		preset.var[kvMINY] = minY;
		preset.var[kvMAXY] = maxY;
		preset.var[kvJUMPDELAY] = jumpDelay;
		preset.var[kvMINDISTANCE] = minDistance;
		
		return preset;
	}
	
	funcdef void JILLCALLBACKFUNC(jjOBJ@);
	jjOBJ@ MakeEventJill(uint8 eventID, uint8 spawn = 0, bool secondStage = false, int textID = -1) {
		Kangaroo::Private::loadAnims();
		if (jjAnimSets[ANIM::GLOVE] == 0)
			jjAnimSets[ANIM::GLOVE].load();
		Kangaroo::Private::firstGloveAnimationFrame = jjAnimations[jjAnimSets[ANIM::GLOVE] + 3];
		
		jjOBJ@ preset = jjObjectPresets[eventID];
		preset.behavior = Jill;
		preset.determineCurAnim(Kangaroo::Private::customAnimID, 1);
		preset.frameID = 0; preset.determineCurFrame();
		
		Kangaroo::Private::applyGenericEnemySettingsToPreset(preset);
		
		preset.doesHurt = spawn;
		preset.yAcc = 0.16f;
		preset.energy = 100;
		preset.points = 5000;
		preset.counterEnd = 210; //death wait
		preset.special = textID;
		preset.playerHandling = HANDLING::DYING; //no initial collision damage
		preset.var[kvWIDTH] = 32;
		preset.var[kvHEIGHT] = 98;
		preset.var[kvMINX] = 2;
		preset.var[kvMAXX] = 4;
		preset.var[kvMINY] = 5;
		preset.var[kvMAXY] = 10;
		preset.var[kvJUMPDELAY] = 140;
		preset.var[kvMINDISTANCE] = 400;
		preset.var[kvGLOVE1FRAME] = 0;
		preset.var[kvGLOVE2FRAME] = 0;
		preset.var[kvSECONDSTAGE] = secondStage ? 1 : 0;
		
		return preset;
	}
	void OnJillDefeat(JILLCALLBACKFUNC@ callback = null) {
		if (callback !is null)
			@Kangaroo::Private::jillCallback = callback;
	}
	
	
	void Joey(jjOBJ@ obj) {
		const int width = obj.var[kvWIDTH];
		const int height = obj.var[kvHEIGHT];
		switch (obj.state) {
			case STATE::START:
				obj.state = STATE::IDLE;
				Kangaroo::Private::putKangarooOnGround(obj, width, height);
			case STATE::IDLE:
				if (obj.counter == 0 || --obj.counter == 0) {
					const int nearestPlayerID = obj.findNearestPlayer(int(pow(obj.var[kvMINDISTANCE], 2)));
					if (nearestPlayerID >= 0) {
						jjPLAYER@ nearestPlayer = jjPlayers[nearestPlayerID];
						obj.xSpeed = (nearestPlayer.xPos - obj.xPos) / 20.0f;
						obj.direction = (obj.xSpeed >= 0) ? 1 : -1;
						
						float xSpeed = abs(obj.xSpeed);
						if (xSpeed > obj.var[kvMAXX]) xSpeed = obj.var[kvMAXX];
						else if (xSpeed < obj.var[kvMINX]) xSpeed = obj.var[kvMINX];
						obj.xSpeed = xSpeed * obj.direction;
						
						float ySpeed = abs((nearestPlayer.yPos - obj.yPos) / 20.0f);
						if (ySpeed > obj.var[kvMAXY]) ySpeed = obj.var[kvMAXY];
						else if (ySpeed < obj.var[kvMINY]) ySpeed = obj.var[kvMINY];
						obj.ySpeed = -ySpeed;
						
						obj.state = STATE::JUMP;
						obj.counter = obj.var[kvJUMPDELAY];
						jjSample(obj.xPos, obj.yPos, ((jjRandom() & 1) == 0) ? SOUND::BUBBA_BUBBABOUNCE1 : SOUND::BUBBA_BUBBABOUNCE2);
					} else
						obj.direction = (obj.xPos > jjLocalPlayers[0].xPos) ? -1 : 1;
				}
				break;
			case STATE::FREEZE:
				if (obj.freeze > 0)
					--obj.freeze;
				if (obj.freeze <= 0) {
					obj.state = obj.oldState;
					obj.unfreeze(0);
				}
				break;
			case STATE::JUMP:{
				obj.yPos += (obj.ySpeed += obj.yAcc);
				const int newXPos = int(obj.xPos + obj.xSpeed) + (width * obj.direction)/2;
				if ((newXPos < 0) || (newXPos > jjLayerWidth[4]*32) || jjMaskedVLine(newXPos, int(obj.yPos - height/2), height)) {
					obj.xSpeed = -obj.xSpeed;
					obj.direction = -obj.direction;
				} 
				obj.xPos += obj.xSpeed;
				int newYPos = int(obj.yPos + obj.ySpeed);
				if (obj.ySpeed < 0) {
					if ((newYPos < 0) || jjMaskedHLine(int(obj.xPos) - width/2, width, newYPos - height/2)) {
						obj.ySpeed = obj.yAcc;
						obj.frameID = 2;
					} else obj.frameID = 1;
				}
				if (obj.ySpeed > 0) {
					if ((newYPos > jjLayerHeight[4]*32) || jjMaskedHLine(int(obj.xPos) - width/2, width, newYPos + height/2)) {
						obj.state = STATE::IDLE;
						obj.ySpeed = 0;
						obj.frameID = 0;
						Kangaroo::Private::putKangarooOnGround(obj, width, height);
					} else obj.frameID = 2;
				}
				obj.determineCurFrame();
				break;
			} case STATE::DEACTIVATE:
				obj.deactivate();
				return;
			case STATE::KILL:
				obj.delete();
				return;
		}
		obj.draw();
	}
	void Jill(jjOBJ@ obj) {
		switch (obj.state) {
			case STATE::START:
				obj.state = STATE::DELAYEDSTART;
			case STATE::DELAYEDSTART:
				for (int i = 0; i < jjLocalPlayerCount; ++i) {
					jjPLAYER@ localPlayer = jjLocalPlayers[i];
					if (localPlayer.bossActivated) {
						localPlayer.boss = obj.objectID;
						obj.state = STATE::START;
					}
				}
				if (obj.state == STATE::START) {
					obj.playerHandling = HANDLING::ENEMY;
					break;
				}
				return;
			case STATE::KILL:
				if (obj.special >= 0) //textID
					jjLocalPlayers[0].showText(obj.special, 0);
				obj.playerHandling = HANDLING::DYING;
				obj.state = STATE::DONE;
			case STATE::DONE:
				if (--obj.counterEnd == 0) {
					obj.delete();
					Kangaroo::Private::jillCallback(obj);
				}
				return;
			default:
				break;
		}
		
		int oldState = obj.state;
		obj.behave(Joey, false);
		obj.frameID = 0;
		obj.determineCurFrame();
		
		const int direction = obj.direction;
		const bool secondStage = (obj.var[kvSECONDSTAGE] != 0 && obj.energy < 25);
		if (secondStage) {
			if (obj.var[kvSECONDSTAGE] == 1) {
				obj.var[kvSECONDSTAGE] = 2;
				obj.var[kvMINX] = 3;
				obj.var[kvMAXX] = 5;
				obj.var[kvJUMPDELAY] = 50;
				obj.var[kvMINDISTANCE] = 600;
			}
		}
		if (obj.doesHurt != 0 && (jjRandom() & 255) == 0) {
			jjOBJ@ spawn = jjObjects[jjAddObject(obj.doesHurt, obj.xPos, obj.yPos + 11, obj.objectID, CREATOR::OBJECT, BEHAVIOR::INACTIVE)];
			jjBEHAVIOR originalBehavior = jjObjectPresets[obj.doesHurt].behavior;
			if (originalBehavior == Joey) {
				spawn.direction = obj.direction;
				spawn.xSpeed = spawn.direction * (1 + (jjRandom() & 3));
				spawn.ySpeed = -6;
				spawn.state = STATE::JUMP;
			}
			spawn.behavior = originalBehavior;
		}
		
		SPRITE::Mode mode = SPRITE::NORMAL;
		uint8 param = 15;
		SPRITE::Mode modeDark = SPRITE::BRIGHTNESS;
		uint8 paramDark = 96;
		if (obj.justHit != 0) {
			mode = modeDark = SPRITE::SINGLECOLOR;
			paramDark = param;
			const int nearestPlayerID = obj.findNearestPlayer(64);
			for (int i = 0; i < jjLocalPlayerCount; ++i) {
				jjPLAYER@ localPlayer = jjLocalPlayers[i];
				if (localPlayer.specialMove != 0) {
					localPlayer.specialMove = 0;
					localPlayer.ySpeed -= 1;
					localPlayer.extendInvincibility(-35);
				}
			}
			
		} else if (obj.state == STATE::FREEZE) {
			mode = modeDark = SPRITE::FROZEN;
		} else if (secondStage) {
			mode = modeDark = SPRITE::TINTED;
			param = 25;
			paramDark = 29;
		}
		
		int armAngle = obj.age;
		if (obj.state != STATE::FREEZE) {
			const int nearestPlayerID = obj.findNearestPlayer(1000000);
			if (nearestPlayerID >= 0) {
				jjPLAYER@ nearestPlayer = jjPlayers[nearestPlayerID];
				const float deltaX = nearestPlayer.xPos - obj.xPos;
				const float deltaY = nearestPlayer.yPos - obj.yPos;
				armAngle = int(atan2(
				    (direction == 1) ? deltaY : -deltaY,
				    abs(deltaX)
				) * -512.0 * 0.318309886142228);
			}
			obj.age = armAngle;
		}
		const float armSin = jjSin(armAngle);
		const float armCos = jjCos(armAngle);
		int tailAngle = int(obj.ySpeed*-16)*direction;
		const int tailX = int(obj.xPos) - 32 * direction;
		const int tailY = int(obj.yPos) + 23;
		const int gloveLength = (12 + 29 + 29) * obj.direction;
		const int legAngle = int(obj.ySpeed*8)*direction;
		const int arm1X = int(obj.xPos) - 2 * direction;
		const int arm1Y = int(obj.yPos) - 7;
		const int arm2X = int(obj.xPos) + 4 * direction;
		const int arm2Y = int(obj.yPos - 11);
		//if (tailAngle > 0) tailAngle = 0;
		
		if (obj.ySpeed < 0) {
			obj.animSpeed = jjSampleLooped(obj.xPos, obj.yPos, SOUND::COMMON_FLAMER, obj.animSpeed);
			if (obj.state != STATE::FREEZE && (jjRandom() & 1) == 0) {
				jjPARTICLE@ part = jjAddParticle(PARTICLE::FIRE);
				if (part !is null) {
					part.xSpeed = jjSin(tailAngle) * 2;
					part.ySpeed = jjCos(tailAngle) * 2;
					part.xPos = tailX + part.xSpeed * 8;
					part.yPos = tailY + part.ySpeed * 8;
					part.xSpeed += abs(obj.xSpeed) * -obj.direction;
				}
			}
			if (oldState == STATE::IDLE) {
				obj.var[kvGLOVE1FRAME] = 1;
				jjSample(obj.xPos, obj.yPos, SOUND::COMMON_PISTOL1);
				obj.var[kvGLOVE2FRAME] = 0;
			}
		}
		{
			int oldGloveFrame = obj.var[kvGLOVE1FRAME];
			if (oldGloveFrame > 0 && oldGloveFrame < 12 && (jjGameTicks & 3) == 1)
				obj.var[kvGLOVE1FRAME] = oldGloveFrame + 1;
			
			oldGloveFrame = obj.var[kvGLOVE2FRAME];
			if (obj.state == STATE::IDLE && oldGloveFrame == 0) {
				obj.var[kvGLOVE2FRAME] = 1;
				jjSample(obj.xPos, obj.yPos, SOUND::COMMON_PISTOL1);
			}
			if (oldGloveFrame > 0 && oldGloveFrame < 12 && (jjGameTicks & 3) == 1)
				obj.var[kvGLOVE2FRAME] = oldGloveFrame + 1;
		}
		
		const int glove2FrameID = Kangaroo::Private::firstGloveAnimationFrame + (obj.var[kvGLOVE2FRAME] + 3) % 12;
		jjDrawRotatedSpriteFromCurFrame(
			arm2X + 12*armSin + gloveLength*armCos,
			arm2Y + 12*armCos - gloveLength*armSin,
			 glove2FrameID,
			armAngle - 256 * obj.direction,
			direction, 2, modeDark, paramDark
		); //glove
		if (obj.state != STATE::FREEZE) {
			const int glove2Length = gloveLength + (jjAnimFrames[glove2FrameID].height - 30) * 2 * obj.direction;
			Kangaroo::Private::doGloveAt(
				int(arm2X + 12*armSin + glove2Length*armCos),
				int(arm2Y + 12*armCos - glove2Length*armSin)
			);
		}
		jjDrawRotatedSpriteFromCurFrame(arm2X, arm2Y, obj.curFrame + 1, armAngle, direction, 1, modeDark, paramDark); //back arm
		
		jjDrawRotatedSpriteFromCurFrame(obj.xPos - 24 * direction, obj.yPos + 24, obj.curFrame + 2, legAngle, direction, 1, modeDark, paramDark); //back leg
		jjDrawRotatedSpriteFromCurFrame(tailX, tailY, obj.curFrame + 3, tailAngle, direction, 1, mode, param); //tail
		jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, direction, mode, param); //body
		jjDrawRotatedSpriteFromCurFrame(obj.xPos - 30 * direction, obj.yPos + 28, obj.curFrame + 2, legAngle, direction, 1, mode, param); //leg
		
		jjDrawRotatedSpriteFromCurFrame(arm1X, arm1Y, obj.curFrame + 1, armAngle, direction, 1, mode, param); //arm
		const int glove1FrameID = Kangaroo::Private::firstGloveAnimationFrame + (obj.var[kvGLOVE1FRAME] + 3) % 12;
		jjDrawRotatedSpriteFromCurFrame(
			arm1X + 12*armSin + gloveLength*armCos,
			arm1Y + 12*armCos - gloveLength*armSin,
			 glove1FrameID,
			armAngle - 256 * obj.direction,
			direction, 2, mode, param
		); //glove
		if (obj.state != STATE::FREEZE) {
			const int glove1Length = gloveLength + (jjAnimFrames[glove1FrameID].height - 30) * 2 * obj.direction;
			Kangaroo::Private::doGloveAt(
				int(arm2X + 12*armSin + glove1Length*armCos),
				int(arm2Y + 12*armCos - glove1Length*armSin)
			);
		}
	}
}