Copy to Clipboard Test

Sensor Ring Code

//start READ ME
/*
//=============\\
|| SENSOR RING ||
\\=============//
Author: Orithan
ZC Version: 2.55

This ring causes combos that hide secrets to be marked while you're nearby, inspired by the Secrets Ring in 7th Quest. This can be considered an alternative to the Stone of Agony item.

/------------\
| ZInfo Data |
\------------/
Copy paste the following strings into an unused ItemClass (ie. zz###) in Quest->ZInfo if you want to define the Sensor Rings as an itemclass in the editor.
Itemclass Name String: Sensor Rings
Itemclass Description String: Scripted. Draws sparkles over combos that hide secrets. Use CF_SENSORRINGHINT to place arbitary hints, use CF_NOSENSORRING to prevent the Sensor Rings from highlighting combos. Read the Script File for more information.


/--------------\
| INSTRUCTIONS |
\--------------/
Import the script. Adjust your settings, found under the SETTINGS section, accordingly. Then compile.
Enable the Quest Script Rule "Item Scripts Continue To Run", found under ZScript->Quest Script Settings->Script.
Setting up the item:
	Set the flag "Constant Script". This is vital to ensure the script continues to run for this item in the background.
    Set Attributes[0] to the range, in pixels, which the item will highlight secrets in.
    Set Sprites[0] to the sprite drawn to highlight the secrets.
Have fun.


/-------\
| NOTES |
\-------/
The script is not set up to detect secrets on FFCs.

*/
//end

//start Imports and typedefs
/*
/----------------------\
| IMPORTS AND TYPEDEFS |
\----------------------/
*/
//import "std.zh"


//end 



namespace SensorRing
{
	//start Settings
	/*
	/----------\
	| SETTINGS |
	\----------/
	*/

	CONFIG CF_SENSORRINGHINT = CF_SCRIPT1; //Generic flag used to arbitarily show Sensor Ring hints. Set to 0 to disable.
	CONFIG CF_NOSENSORRING = CF_SCRIPT2;   //Generic flag used to arbitarily deny Sensor Ring hints. Set to 0 to disable.

	//Lowest and highest layers to check for secret combos. This is primarily done in to minimize demands on processing.
	CONFIG SENSORRING_MINLAYER = 0; //Lowest layer. Lowest this should be is 0
	CONFIG SENSORRING_MAXLAYER = 2; //Highest layer. This should be higher than SENSORRING_MINLAYER. Highest this should be is 6.

	CONFIG SENSORRING_DRAWLAYER = 6; //Layer which the sparkles are drawn to. If drawing to Layer 2 or 3, the game will instead draw to Layers 1 and 4 respectively if those respective layers are background layers. This should be at least Layer 6 so it draws over all combos.


	//end 
	//start Scripts
	/*
	/---------\
	| SCRIPTS |
	\---------/
	*/
	//The Sensor Ring script itself
	@Author("Orithan Fox"),
	@Attribute0("Sensor Radius"),
	@AttributeHelp0("The radius, in pixels, at which the Sensor Ring will detect secrets."),
	@Attribute1("Sparkle Sprite (SET Sprites[0] INSTEAD)"),
	@AttributeHelp1("Sprites[0] under Graphics->Sprites: The sprite that gets drawn over secrets that the ring detects. Set Sprites[0] instead of this - Attributes 1 does nothing and exists purely to inform that you need to set Sprites[0] under Graphics->Sprites to the Sparkle Sprite due to a lack of annotations/metadata for them.")
	item script SensorRing
	{
		void run()
		{
			while(true)
			{
				SeekCombos(this, CenterLinkX(), CenterLinkY(), this->Attributes[0]);
				Waitframe();
			}
		}
		//Checks points in a circle in a radius around a center defined by circx and circy.
		void SeekCombos(itemdata this, int circx, int circy, int radius)
		{
			//Get the edges of the box covering the circle
			int upedge = Max(circy-radius, 0);
			int downedge = Min(circy+radius, 175);
			int leftedge = Max(circx-radius, 0);
			int rightedge = Min(circx+radius, 255);
			
			//Initialize counters
			int checkx = leftedge;
			int checky = upedge;
			int actlayers; //Flagset used to store which layers are active. Is not used if the ring does not check for layers greater than 0.
			
			if(SENSORRING_MINLAYER > 0 || SENSORRING_MAXLAYER > 0)
			{
				//Layers checked are 1 through to 6 and binary values begin from there.
				for(int layer = SENSORRING_MINLAYER; layer <= SENSORRING_MAXLAYER; layer++)
				{
					if(layer > 0 && Screen->LayerMap(layer) > -1)
						actlayers |= 1 << layer-1; //Store the data pertaining to layer activity in binary format.
				}
			}
			
			//Grab the sparkle sprite data
			spritedata sparkle = Game->LoadSpriteData(this->Sprites[0]);
			//For loop to impose a cap on the number of combo locations it can cycle through
			for(int rep = 0; rep < 176; rep ++)
			{
				int curcmb = ComboAt(checkx, checky); //Combo number to check
				CONFIG ANIMOFFSET = 3; //This is multiplied by the current combo and added to the animation frame, preventing the sparkle animation from syncing across all instances of the sparkle being drawn. Modify this if you need to for your quest
				if(Distance(ComboX(curcmb)+8, ComboY(curcmb)+8, circx, circy) <= radius)
				{
					//Check through layers
					for(int layer = SENSORRING_MINLAYER; layer <= SENSORRING_MAXLAYER; layer++)
					{
						//Layer not used, skip.
						if(layer > 0 && !(actlayers&(1<<(layer-1))))
						{
							continue;
						}
						if(SensorRing_HighlightableCombo(curcmb, layer)) //Combo is in range and can be highlighted, draw sparkles over it.
						{
							Screen->FastTile(GetDrawLayer(SENSORRING_DRAWLAYER), ComboX(curcmb), ComboY(curcmb), sparkle->Tile+(Div(Game->Time, (sparkle->Speed+1)*1L)+curcmb*ANIMOFFSET)%sparkle->Frames, sparkle->CSet, OP_OPAQUE);
						}
					}
				}

				//Bottom right corner before the checkpoints are moved this frame, return.
				if(checkx >= rightedge && checky >= downedge)
					break;

				//Move the check points.
				checky = checkx+1 < rightedge ? checky : Min(checky+16, downedge);
				checkx = checkx < rightedge ? Min(checkx+16, rightedge) : leftedge;
			}
			//NES Dungeon screen
			if((Screen->Flags[SF_ROOMTYPE]&SFR_INTERIOR) == 0 && (Game->LoadDMapData(Game->GetCurDMap())->Type == 0 || (Screen->Flags[SF_ROOMTYPE]&SFR_DUNGEON)))
			{
				//Check for Z1 Bombable and Walk Through Walls.
				int Z1DoorMap[] = {
				120, 16,	//Top door
				120, 144,	//Bottom door
				16, 80,		//Left door
				224, 80		//Right door
				};
				for(int door = 0; door < 4; door ++)
				{
					//Door in range and of the appropriate type
					if((Screen->Door[door] == D_BOMB || Screen->Door[door] == D_WALKTHRU) && Distance(circx, circy, Z1DoorMap[door*2]+8, Z1DoorMap[door*2+1]+8) <= radius)
						Screen->FastTile(GetDrawLayer(SENSORRING_DRAWLAYER), Z1DoorMap[door*2], Z1DoorMap[door*2+1], sparkle->Tile+Div(Game->Time, (sparkle->Speed+1)*1L)%sparkle->Frames, sparkle->CSet, OP_OPAQUE); //Draw the sparkle over the wall.
				}
			}
		}
	}

	//Returns true if a given combo is highlightable. Is stored outside of the script in the event the user wants to check this through another script
	bool SensorRing_HighlightableCombo(int id, int layer)
	{
		//Clamps the layer to between the two max layers
		layer = Clamp(layer, SENSORRING_MINLAYER, SENSORRING_MAXLAYER);
		int layermap = layer > 0 ? Screen->LayerMap(layer) : -1; //If an unused layer is passed, treat it as Layer 0 instead.
		int layerscreen = layermap >= 0 ? Screen->LayerScreen(layer) : -1; //Layer screen cannot exist without a layer map being valid
		//Combo flags
		for(int flagtype = 0; flagtype < 2; flagtype ++) //Checks both the placed flag and the inherent flag on the combo.
		{
			int flag = flagtype == 0 ? (layermap < 0 ? Screen->ComboF[id] : Game->GetComboFlag(layermap, layerscreen, id)) : (layermap < 0 ? Screen->ComboI[id] : Game->GetComboInherentFlag(layermap, layerscreen, id));
			switch(flag)
			{
				//These two flags will take effect regardless of which layer it is placed on (and checked).
				//The No Sensor Ring flag identified, return false.
				case CF_NOSENSORRING:
				{
					return false;
				}
				//Sensor Ring identified. Defined under settings in this script file.
				case CF_SENSORRINGHINT:
				{
					return true;
				}
				
				//Push blocks are only pushable on Layer 0 and Layers 1 or 2 if the quest rule "Push Blocks Work On Layers 1 and 2" is enabled. This goes for the block trigger flags too
				case CF_PUSHUPDOWN:		//Push Block (Vertical, Trigger), also pushable only once. Flag 1.
				case CF_PUSH4WAY:		//Push Block (4-Way, Trigger), also pushable only once. Flag 2.
				case CF_PUSHLR:			//Push Block (Horiz, Once, Trigger). Flag 47.
				case CF_PUSHUP:			//Push Block (Up, Once, Trigger). Flag 48.
				case CF_PUSHDOWN:		//Push Block (Down, Once, Trigger). Flag 49.
				case CF_PUSHLEFT:		//Push Block (Left, Once, Trigger). Flag 50.
				case CF_PUSHRIGHT:		//Push Block (Right, Once, Trigger). Flag 51.
				case CF_BLOCKTRIGGER: 	//Push Block Trigger. Flag 66.
				{
					if(layermap < 0 || (layer <= 2 && Game->FFRules[qr_PUSHBLOCK_LAYER_1_2]))
						return true;
					break;
				}
				
				//These flags will show the sensor ring sparkles if checked on layers 0-2 and no sensor ring flag is not seen by the script
				case CF_WHISTLE:		//Whistle Trigger. Flag 3.
				case CF_CANDLE1:		//Burn Trigger (Any). Flag 4.
				case CF_ARROW:			//Arrow Trigger (Any). Flag 5.
				case CF_BOMB:			//Bomb Trigger (Any). Flag 6.
				case CF_ARMOSSECRET:	//Armos -> Secret. Flag 9.
				case CF_SBOMB:			//Bomb (Super). Flag 11.
				case CF_LENSMARKER:		//Lens Marker. Flag 14.
				case CF_BRANG1:			//Boomerang Trigger (Any). Flag 68.
				case CF_BRANG2:			//Boomerang Trigger (Magic +), aka. L2 Boomerang. Flag 69.
				case CF_BRANG3:			//Boomerang Trigger (Fire), aka. L3 Boomerang. Flag 70.
				case CF_ARROW2:			//Arrow Trigger (Silver), aka. L2 Arrows. Flag 71.
				case CF_ARROW3:			//Arrow Trigger (Golden), aka. L3 Arrows. Flag 72.
				case CF_CANDLE2:		//Burn Trigger (Red Candle +), aka. L2 Candle fire. Flag 73.
				case CF_WANDFIRE:		//Burn Trigger (Wand Fire). Flag 74.
				case CF_DINSFIRE:		//Burn Trigger (Din's Fire). Flag 75.
				case CF_WANDMAGIC:		//Magic Trigger (Wand). Flag 76.
				case CF_REFMAGIC:		//Magic Trigger (Reflected). Flag 77.
				case CF_REFFIREBALL:	//Fireball Trigger (Reflected). Flag 78.
				case CF_SWORD1:			//Sword Trigger (Any). Flag 79.
				case CF_SWORD2:			//Sword Trigger (White +) aka. L2 Sword. Flag 80.
				case CF_SWORD3:			//Sword Trigger (Magic +) aka. L3 Sword. Flag 81.
				case CF_SWORD4:			//Sword Trigger (Master) aka. L4 Sword. Flag 82.
				case CF_SWORD1BEAM:		//Sword Beam Trigger (Any). Flag 83.
				case CF_SWORD2BEAM:		//Sword Beam Trigger (White +) aka. L2 Sword Beam. Flag 84.
				case CF_SWORD3BEAM:		//Sword Beam Trigger (Magic +) aka. L3 Sword Beam. Flag 85.
				case CF_SWORD4BEAM:		//Sword Beam Trigger (Master) aka. L4 Sword Beam. Flag 86.
				case CF_HOOKSHOT:		//Hookshot Trigger. Flag 87.
				case CF_WAND:			//Wand Trigger. Wand Melee. Flag 88.
				case CF_HAMMER:			//Hammer Trigger. Flag 89.
				case CF_STRIKE:			//Strike Trigger. Anything that responds to any of the previous trigger flags IIRC. Flag 90.
				{
					if(layermap < 0 || layer <= 2)
						return true;
					break;
				}
				
				//Conditional flags.
				//These are shown only if unclaimed special items are on the screen
				case CF_ARMOSITEM:		//Armos/Chest -> Item. Flag 10. This script only checks for the Armos portion - 9 times out of 10 it should be obvious where a treasure chest is on the screen and otherwise you can use CF_SENSORRINGHINT instead.
				{
					//Special item can spawn.
					if(Screen->RoomType == RT_SPECIALITEM && !Screen->State[ST_SPECIALITEM])
					{
						//Check for combo types that can spawn special items with the flag.
						switch(layermap < 0 ? Screen->ComboT[id] : Game->GetComboType(layermap, layerscreen, id))
						{
							case CT_ARMOS:
							{
								return true;
							}
							case CT_SLASH:
							case CT_SLASHNEXT:
							case CT_SLASH:
							case CT_SLASHNEXT:
							case CT_SLASHITEM:
							case CT_SLASHNEXTITEM:
							case CT_SLASHITEMC:
							case CT_SLASHNEXTITEMC:
							case CT_BUSH:
							case CT_BUSHNEXT:
							case CT_BUSHC:
							case CT_BUSHNEXTC:
							case CT_FLOWERS:
							case CT_FLOWERSC:
							case CT_TALLGRASS:
							case CT_TALLGRASSC:
							case CT_TALLGRASSNEXT:
							{
								if(layermap < 0 || (Game->FFRules[qr_BUSHESONLAYERS1AND2] && layer <= 2)) //Slashable combo is on a layer, check the status of the Quest Rule "Slash Combos Work On Layers 1 and 2" checked
									return true;
								break;
							}
						}
					}
					break;
				}
				case CF_DIVEITEM:		//Dive -> Item. Flag 13.
				{
					if(Screen->RoomType == RT_SPECIALITEM && !Screen->State[ST_SPECIALITEM] && (layermap < 0 ? Screen->ComboT[id] : Game->GetComboType(layermap, layerscreen, id)) == CT_WATER && layer == 0 && !IsSideview()) //Verify that this is a water combo that functions. Also does not have dive functionality on sideview.
						return true;
					break;
				}
			}
		}
		//Now check combo types
		switch(layermap < 0 ? Screen->ComboT[id] : Game->GetComboType(layermap, layerscreen, id))
		{
			//Most of these work only on Layer 0
			case CT_TRIGNOFLAG:			//Step->Secrets (Temporary) (021)
			case CT_TRIGFLAG:			//Step->Secrets (Permanent) (022)
			case CT_STRIGNOFLAG:		//Step->Secrets (Sensitive, Temporary) (104)
			case CT_STRIGFLAG:			//Step->Secrets (Sensitive, Permanent) (105)
			case CT_STEP:				//Step->Next (106)
			case CT_STEPSAME:			//Step->Next (Same) (107)
			case CT_STEPALL:			//Step->Next (All) (108)
			{
				if(layer == 0)
					return true;
				break;
			}
			//The Dive Warps in addition only warp outside of Sideview. Otherwise they act like regular Water combos
			case CT_DIVEWARP:			//Dive Warp [A] (019)
			case CT_DIVEWARPB:			//Dive Warp [B] (089)
			case CT_DIVEWARPC:			//Dive Warp [C] (090)
			case CT_DIVEWARPD:			//Dive Warp [D] (091)
			{
				if(layer == 0 && !IsSideview())
					return true;
				break;
			}
			case CT_SWITCH:				//Switch (167)
			case CT_LIGHT_TARGET:		//Light Trigger (172)
			{
				return true;
			}
			
			//Generic combos are not supported because they are entirely depricated.
		}
		//Combo triggers check.
		//The only thing from the triggers tab that will be automatically checked is for special items/screen secrets.
		//For the rest you can just put CF_SENSORRINGHINT flags on if they're deemed secrets or add the other flags to the if statement.
		combodata cmbdat = Game->LoadComboData(layermap < 0 ? Screen->ComboD[id] : Game->GetComboData(layermap, layerscreen, id));
		if((cmbdat->TrigFlags[TRIGFLAG_TRIG_SECRETS] && !Screen->State[ST_SECRET]) || (cmbdat->TrigFlags[TRIGFLAG_SPECIAL_ITEM] && !Screen->State[ST_SPECIALITEM]))
			return true;
			
		return false;
	}
	//end

	//start Generic functions
	/*
	/----------\
	| GENERICS |
	\----------/
	*/

	//Returns the correct layer to draw at.
	int GetDrawLayer(int layer)
	{
		if(layer == 2 && (ScreenFlag(1, 4) ^^ Game->LoadDMapData(Game->GetCurDMap())->Flagset[DMFS_LAYER2ISBACKGROUND])) //Layer -2
			return 1;
		else if(layer==3 && (ScreenFlag(1, 5) ^^ Game->LoadDMapData(Game->GetCurDMap())->Flagset[DMFS_LAYER3ISBACKGROUND])) //Layer -3
			return 4;
		return layer;
	}
	//end 
}