#pragma name "Randomize all shields"
/*
	This mutator replaces every ordinary shield event in the level (e.g. Fire Shield, Laser Shield) with a randomizer shield
	monitor that shows only static. Destroying such a monitor will give you a random shield not necessarily matching the
	event as it was placed in JCS. There are basically three steps to this mutator: replacing the shield animation/s,
	doing the actual randomizing work upon shield destruction, and making sure clients don't get kicked for using
	shields that are not placed anywhere in the level. These will be explained below.
*/

/*
	Though currently this mutator is set up to randomize all shields, the shield eventIDs (stored as OBJECT::Object values) are
	actually going to be kept in two separate arrays: shieldsToRandomize is the set of events that will be replaced with
	randomizer shields, and possibleShieldsToBecome is the set of shield types that can result from destroying a randomizer shield.
	If you want to restrict the range or domain of the randomization, simply remove one or more shields from either or both
	arrays. Alternatively, if more shields are added in future releases of JJ2+ and they're given their own OBJECT::Object
	values, you'll be able to add them to these arrays seamlessly.
*/
const array<OBJECT::Object> shieldsToRandomize = {OBJECT::FIRESHIELD, OBJECT::WATERSHIELD, OBJECT::PLASMASHIELD, OBJECT::LASERSHIELD};
const array<OBJECT::Object> possibleShieldsToBecome = {OBJECT::FIRESHIELD, OBJECT::WATERSHIELD, OBJECT::PLASMASHIELD, OBJECT::LASERSHIELD};
const int shieldAnimID = jjObjectPresets[shieldsToRandomize[0]].curAnim;

void onLevelLoad() {
	jjANIMATION@ shieldAnim = jjAnimations[shieldAnimID];	//The jjAnimations array contains all the sprite animations of the game, defined essentially by their firstFrame and frameCount values. Here we access a shield animation.
	shieldAnim.firstFrame += 4;				//Each shield animation uses the same two images for its fifth and sixth frames; a completely staticky screen. These two lines make jjAnimations[shieldAnimID] use _only_ those two images.
	shieldAnim.frameCount = 2;				//Another approach would be to use the standard shield animations in full, but switch between them every frame, since they're all the same length. This approach here does a better job of showing off a simple use for the jjANIMATION class.
	
	for (uint i = 0; i < shieldsToRandomize.length; ++i) {
		jjObjectPresets[shieldsToRandomize[i]].curAnim = shieldAnimID;			//By default each shield uses a different animation; rather than edit all of the different shield animations to each be only two frames long, we're just going to make them all use the same one.
		jjObjectPresets[shieldsToRandomize[i]].determineCurFrame();			//This is mostly unneeded, since shields animate on their own, but it'll prevent the very first frame of a shield's lifetime after spawning from using the wrong curFrame value.
		jjObjectPresets[shieldsToRandomize[i]].scriptedCollisions = true;		//Since shields use HANDLING::SPECIAL for their jjOBJ::playerHandling property, this will cause player/bullet interactions with the randomizer shields to call the onObjectHit hook defined below.
		jjObjectPresets[shieldsToRandomize[i]].bulletHandling = HANDLING::DETECTBULLET;	//We're going to make bullets collide with shields twice in one tick. By default the initial bullet-shield collision will set the shield's state to STATE::KILL, which would then prevent a subsequent collision from taking place. DETECTBULLET prevents that default reaction.
	}
	for (uint i = 0; i < possibleShieldsToBecome.length; ++i)	//Having finished setting up the randomizer shield events and editing the animation they all now use, we have to make sure that clients are allowed to fire _any_ shield bullet, even if it's not placed in the level.
		jjEventSet(0, i, possibleShieldsToBecome[i]);		//JJ2+ determines which weapons/shields are or aren't allowed by searching the event map for the matching events, and it does this after onLevelLoad and before onLevelBegin. Here we prime it by placing one of each necessary shield into the event map.
}
void onLevelBegin() {
	for (int i = jjObjectCount - 1; i >= 1; --i) {
		jjOBJ@ obj = jjObjects[i];
		if (obj.curAnim == shieldAnimID && obj.creator == CREATOR::LEVEL && obj.xOrg < 32)	//Find the shield objects we placed in the event map at the end of onLevelLoad.
			obj.delete();									//Delete them. JJ2+ only checks for allowable shields once, right before onLevelBegin, so we'll never need these particular objects again. If this mutator is running, presumably there's one or more actual shield events placed in the level somewhere.
	}
}

void onObjectHit(jjOBJ@ obj, jjOBJ@ bull, jjPLAYER@ play, int force) {	//This hook is called when an object with a true jjOBJ::scriptedCollisions gets touched by a player or shot by a bullet.
	if (obj.curAnim == shieldAnimID && force != 0) { //It pays to make extra sure that onObjectHit is being called for the object/s you expect it is, in case there are some other mutators out there that gave some other objects a true jjOBJ::scriptedCollisions.
		if (play.isLocal) {
			/*
				Rather than try to guess all the different code that might run upon a player destroying a shield,
				we're just going to use the standard collision code to do it for us. This takes just two steps to set up:
			*/
			obj.scriptedCollisions = false;	//We're about to make the player (or bullet) collide with this shield a second time. If scriptedCollisions were left true, that would just call onObjectHit again, leading to an infinite loop.
			obj.eventID = possibleShieldsToBecome[jjRandom() % possibleShieldsToBecome.length()]; //Most object collision code is based primarily on jjOBJ::eventID. In particular, it determines which shield (SHIELD::FIRE, SHIELD::WATER, SHIELD::LIGHTNING, or SHIELD::LASER) to give you.
			
			if (bull is null) //Player-object collision
				play.objectHit(obj, force, obj.playerHandling); //The objectHit methods simulate a collision between the calling object/player and the target object, which is the simplest way of doing all the standard shield-destroying stuff (playing samples, creating particles, setting jjPLAYER::shieldTime, etc.).
			else { //Bullet-object collision
				obj.bulletHandling = HANDLING::HURTBYBULLET; //Back to the original bulletHandling value, so that the shield will get destroyed by the collision.
				bull.objectHit(obj, HANDLING::SPECIAL);
			}
		} else { //If this hook is called for a non-local player's shield collision, you can be sure it's because they already destroyed the shield and you're just now being informed of it.
			obj.state = STATE::KILL; //There are probably more careful ways of doing this, but the important part is just for the local player.
		}
	}
}
