Copy to Clipboard Test

Consumable Weapons Code

// only need these imports once
import "std.zh"
import "ffcscript.zh"


// Consumable items script v2.0
// Requires: std.zh, ffcscript.zh
//
// Import and give the two items scripts, and the one ffc script each a slot.
// Attach the I_ACT_Consume_Weap script to your consumable weapon.  See below for the arguments.
// Attach the I_PU_Consume_Weap script if the consumable isn't a 1-use item.  See below for the arguments.
// Potentially reset the following constants if any other script conflicts.

const int LW_MISC_CONSUME = 0;  // index to lweapon->Misc[] array.  if any other scripts use this array, change this number accordingly.
const int E_MISC_CONSUME = 0;   // index to npc->Misc[] array.  if any other scripts use this array, change this number accordingly.

// Optional: Repair item.  Create Custom Item with Custom Item Class.
// Attach the I_ACT_Repair script to it.  See below for the arguments.
// If you want the Repait item itself consumable, attach the I_PU_Consume_Weap script to it as well.
// Note: once the item is gone, the repair item won't get it back.  And it won't work on items set as one-time use.
//
// See more notes at the bottom of the script file.
//
//
// --------------------------------------------------------------------------------------
// Action Slot Arguments - I_ACT_Consume_Weap
//
// D0 = The consumable weapon's item#.  
//      Set to -1 if item should not be removed when uses run out.  This will require a global function to intercept use if counter = 0.
//	The global function doesn't exist yet.
//
// D1 = The consumable weapon's type.  Doesn't need to be set if D3 = 0
//
//      LW_SWORD = 1, LW_BEAM = 2, LW_BRANG = 3, LW_FIRE = 9, LW_CANDLE = 12, LW_WAND = 12, LW_MAGIC = 13, LW_HAMMER = 19
//	New types:  41 = BRANG Bounce - boomerang collision makes it bounce (turn all solids into a Block Flag)
//		    42 = BRANG KILL - boomerang collision = kill lweapon
//      above values have been tested, other values are found in std_constants.zh
//
// D2 = The consumable weapon's associated script counter #.  Set to -1, if its a one-time use weapon with no counter.
//	Accepts the Script#, NOT the CR_SCRIPT# value.  I.e. if using Script1, input 1.
//	Set to 0 if its an unlimited use item, and using a special value (described below)
//
//	If for whatever reason you want to use a counter (rupees, keys, etc) other than a script counter, 
//      find the // ************ in each item script below and read the comment.
//      D2 would then require the actual CR_ counter value.  Values in std_constants.zh
//
// D3 = What criteria count as a use?  
//      Not all the values will make sense for all LW types, and might produce bizarre behavior.
//  	   		  	
//      0 = Usage only
//      1 = Enemy Only Collisions
//      2 = Enemy+Solid Collisions
//      3 = Enemy+Solid+Water Collisions
//	4 = Water Only Collisions
//	5 = Solid Only Collisions
//	6 = Enemy+Water Collisions
//	7 = Solid+Water Collisions
//      
// D4 = unused for action. Can be anything.
//
//
// 	A Note on "Special" values.
//	Certain weapons create two LW_ types, I.e. a sword with beams, candle with fire.
//	This allows you to do something to both.  Using this only makes sense for certain weapons.
//	The special values will never destroy the actual item, only the regular values can do that.
//	They are exclusively for controlling the LW.
//
//	D1, D2 and D3 accept two values.  1st before the decimal place, Special after the decimal
//	They all need a special value for it to work.
//	Note D1's special value is two decimal places.  So #.01 = LW_SWORD as the second value.
//	The D2 special value is NOT a reference to an actual counter.  Just how many collisions the LW will have.
//
//	Example usage:  Sword with Enemy and Solids collision, with penetrating Beam with 2 Enemy collisions.  
//	D1 = 1.02, D2 = script#.2, D3 = 2.1
//	Example usage:  Candle with 3 uses, with fire with 1 Enemy+Solid+Water collisions.  
//	D1 = 0.09, D2 = script#.1, D3 = 0.3
//	Example usage:  Candle with 1 use, with fire with 3 Enemy collisions.  
//	D1 = 0.09, D2 = -1.3, D3 = 0.1
//	Example usage:  Candle with unlimited use, with fire with 1 water collision.  
//	D1 = 0.09, D2 = 0.1, D3 = 0.4
//
// --------------------------------------------------------------------------------------
// Pickup Slot Arguments - I_PU_Consume_Weap
// If its a 1-time use weapon you don't need this.
//
// D0 & D1 = unused for pickup.  Can be anything.
//
// D2 = The consumable weapon's associated counter.
//	Accepts the Script#, NOT the CR_SCRIPT# value.  I.e. if using Script1, input 1.
//
//	If for whatever reason you want to use a counter (rupees, keys, etc) other than a script counter, 
//      find the // ************ in each item script below and read the comment.
//      D2 would then require the actual CR_ counter value.  Values in std_constants.zh
//
// D3 = unused for pickup. Can be anything.
//
// D4 = The amount of uses the weapon has.
//
// 
// --------------------------------------------------------------------------------------
// Action Slot Arguments - I_ACT_Repair 
//
// D0 - Consumable counter the repair improves
//	Accepts the Script#, NOT the CR_SCRIPT# value.  I.e. if using Script1, input 1.
//
//	If for whatever reason you want to use a counter (rupees, keys, etc) other than a script counter, 
//      find the // ************ in each item script below and read the comment.
//      D2 would then require the actual CR_ counter value.  Values in std_constants.zh
//
// D1 - Amount to improve
//
// D2 - If the repair item itself is consumable, put its counter here.  Else leave 0.
//	Accepts the Script#, NOT the CR_SCRIPT# value.  I.e. if using Script1, input 1.
//
//	If for whatever reason you want to use a counter (rupees, keys, etc) other than a script counter, 
//      find the // ************ in each item script below and read the comment.
//      D2 would then require the actual CR_ counter value.  Values in std_constants.zh
//
// D3 - If the repair item itself is consumable, put its item# here. Else leave 0.
//
// D4 - unused for action. Can be anything.
//
//
// --------------------------------------------------------------------------------------
// Consumable Ladder 
// Needs to be a global script.  Just add ConsumeLadder(); to before your Waitframe(); in your global slot2 loop.
// Uses the standard Ladder item#.  Replace I_LADDER1 with the item# if using something different.
// Set the following constant to the an unused counter the ladder will use.

const int CR_LADDER = 29; // Currently script counter 23.

bool onLadder = false;

void ConsumeLadder(){
	if(!onLadder && Link->LadderX > 0 && Link->LadderY > 0){
		onLadder = true;
	}else if(onLadder && Link->LadderX == 0 && Link->LadderY == 0){
		onLadder = false;

		Game->Counter[CR_LADDER]--;
		if(Game->Counter[CR_LADDER] <= 0) Link->Item[I_LADDER1] = false;
	}
}


item script I_ACT_Consume_Weap{
	void run(int consume_item, float fconsume_type, float fconsume_counter, float fcwc, int notused1){
		int consume_type = Floor(fconsume_type);
		int consume_counter;

		if(fconsume_counter>=0){
			consume_counter = Floor(fconsume_counter);
		}else{
			consume_counter = Ceiling(fconsume_counter);
		}			

		int consume_with_coll = Floor(fcwc);

		int type2 = (fconsume_type - consume_type) * 100;
		int counter2 = (Abs(fconsume_counter) - Abs(consume_counter)) * 10;
		int consume_with_coll2 = (fcwc - consume_with_coll) * 10;

		// ************
		// so you don't have to input the CR_SCRIPT#, just the Script#.
		// remove this if statement, if for whatever reason you are using counters other than script counters to track item uses.

		if(consume_counter > 0){
			consume_counter += 6;
		}

		// run the 2nd type collision checking
		if(type2 != 0 && counter2 != 0 && consume_with_coll2 != 0){
			RunConsumeFFC(-1,type2,counter2+100,consume_with_coll2);
		}

		if(consume_counter == 0){
			// unlimited use item
			Quit();
		}

		if(consume_with_coll > 0){
			// collision checking for first type

			RunConsumeFFC(consume_item,consume_type,consume_counter,consume_with_coll);
		}else if(consume_with_coll == 0){
			// usage only
			if(consume_item > 0){
				itemdata itm = Game->LoadItemData(consume_item);

				// Candles and Hammers take 6 frames to spawn their LW.
				// without this check, the item would be removed and nothing happen.
				// only matters for one-time use, or multiuse with only one use left.
	
				if( (itm->Family == IC_CANDLE || itm->Family == IC_HAMMER) &&
				    (consume_counter == -1 || Game->Counter[consume_counter] == 1) ){

					if(Game->Counter[consume_counter] == 1){
						Game->Counter[consume_counter] = 0;
					}
				
					RunConsumeFFC(consume_item,-1,-1,0);
			
				}else if(consume_counter == -1){    
					// a one-time use (no collision check) item
					Link->Item[consume_item] = false;
				}else{ 				    
					// a multiuse (no collision check) item
					Game->Counter[consume_counter]--;
					if(Game->Counter[consume_counter]<=0) Link->Item[consume_item] = false;  // no uses left.
				}  
			
			}else if(consume_item == -1 && consume_type > 0 && consume_counter > 0){
				// non-destroyable item, with usage counter

				if(Game->Counter[consume_counter] > 0){
					Game->Counter[consume_counter]--;
				}			

			}  // end valid consumable item check
		} // end consume_with_coll checks
	}//end run
}//end item function


item script I_PU_Consume_Weap{
	void run(int notused1, int notused2, int counter, int notused3, int amount){

		// ************
		// so you don't have to input the CR_SCRIPT#, just the Script#.
		// remove this if statement, if for whatever reason you are using counters other than script counters to track item uses.

		if(counter > 0){
			counter += 6;
		}

		Game->Counter[counter] = amount;
	}
}


item script I_ACT_Repair{
	void run(int counter_being_repaired, int repair_amount, int repair_item_counter, int repair_item, int notused){

		// ************
		// so you don't have to input the CR_SCRIPT#, just the Script#.
		// remove these two if statements, if for whatever reason you are using counters other than script counters to track item uses.
		if(counter_being_repaired > 0){
			counter_being_repaired += 6;
		}
		if(repair_item_counter > 0){
			repair_item_counter += 6;
		}

		if(Game->Counter[counter_being_repaired]>0){

			Game->Counter[counter_being_repaired] += repair_amount;

			// check if repair item itself is consumable.

			if(repair_item_counter != 0 && repair_item != 0){
				Game->Counter[repair_item_counter]--;
				if(Game->Counter[repair_item_counter]<=0) Link->Item[repair_item] = false;
			}
		}
	}
}


void RunConsumeFFC(int i, int t, int c, int s){
	int ffcScriptName[] = "Consumable_FFC";
	int ffcScriptNum = Game->GetFFCScript(ffcScriptName);
	int args[] = {i,t,c,s};
	RunFFCScript(ffcScriptNum, args);
}


ffc script Consumable_FFC{
	void run(int consume_item, int consume_type, int consume_counter, int check_solid){
		bool lw_consume_exist = false;
		int unique_ID = 1;

		int brangspecial = 0;
		int solid_counter = 0;

		// this IF is a carryover from the item script that launched this.  It fixes a bug with Candles/Hammers.
		if(consume_type == -1 && consume_counter == -1){
			Waitframes(6);
			Link->Item[consume_item] = false;
		}

		// some lweapons take a few frames to spawn.
		if(consume_type == LW_WAND || (consume_type == LW_SWORD && !Game->Generic[GEN_CANSLASH]) ){
			Waitframes(4);
		}else if(consume_type == LW_BEAM || consume_type == LW_MAGIC){
			Waitframes(12);
		}else if(consume_type == LW_FIRE){
			Waitframe();
		}else if(consume_type == 41){
			consume_type = LW_BRANG;
			brangspecial = 1; // bounce
		}else if(consume_type == 42){
			consume_type = LW_BRANG;
			brangspecial = 2; // kill
		}


		// this finds our lweapon, and marks it.  so that it is possible for two of the same lw type to be on the screen at once.
		// if we don't find a matching lweapon, the script won't continue.
		// problem is that if we wait too long for our lweapon, we can't be sure it is ours.

		unique_ID = MarkOurLW(consume_type);

		if(unique_ID >= 1){
			lw_consume_exist = true;
		}else{
			// our lweapon doesn't exist, debug to figure out why?  

			lw_consume_exist = false; // should already be false, but we want to make sure nothing else runs

			// optional function for debugging why lweapon isn't being found
			// shows frame count on screen so you know it is writing to allegro.log
			// after a new line and 2000, it writes the LW_ type, and the frame it was found

			Debug_MissingLW(consume_type);
		}				
		
		
		while(lw_consume_exist){
			lw_consume_exist = false; // if our LW isn't found on screen, we stop looping.

			for (int j = 1; j <= Screen->NumLWeapons(); j++){
				lweapon consume_weap = Screen->LoadLWeapon(j);

				// check if this lweapon is our lweapon.
				if(consume_weap->Misc[LW_MISC_CONSUME] == unique_ID && consume_weap->ID == consume_type){

					lw_consume_exist = true;  // the LW is still onscreen

					// if collisions with solid objects or water count as a collision
					if(check_solid != 0){

						// if it available to check for solid/water collision
						if(solid_counter == 0){

							// if the lweapon is colliding with solid or water combo based on what we are looking for

							if( SolidCheck(check_solid, consume_weap, consume_type) ){

								if(consume_counter == -1){      // one-time use, so kill it

									SolidKillWeap(consume_weap);

									if(consume_item != -1){
										Link->Item[consume_item] = false;
									}

									Quit(); // collision destroyed weapon, so all done.
								}else if(consume_counter >= 100){
									// must be special type/counter (no item to destroy, just lweapon)
									consume_counter--;

									if(consume_counter <= 100){
										SolidKillWeap(consume_weap);

										Quit();
									}
								}else{  // multiuse, reduce counter
									Game->Counter[consume_counter]--;  
						
									if(Game->Counter[consume_counter]<=0){   // multiuse, no more uses.
										SolidKillWeap(consume_weap);

										if(consume_item != -1){
											Link->Item[consume_item] = false;
										}

										Quit(); // collision destroyed weapon, so all done.
									}
								}

								if(brangspecial	== 1){
									consume_weap->DeadState = WDS_BOUNCE;
								}else if(brangspecial == 2){
									consume_weap->DeadState = WDS_DEAD;
								}

								// weapon still exists, set cooldown timer
							
								solid_counter = SetCoolDown(consume_type);

							} // end of solid or water check

						}else if(solid_counter>0){
							// we have already collided with solid or water, so let's reduce cooldown counter
							solid_counter--;
						}
					}//end of Check_Solid code

					if(check_solid == 4 || check_solid == 5 || check_solid == 7){
						// not checking enemy collisions
						break;
					}

					// now lets check for collision with enemies.

					for (int i = 1; i <= Screen->NumNPCs(); i++ ){
						npc enem = Screen->LoadNPC(i);

						// certain lweapon types have ability to hit more than once after a "cooldown" period.
						if(enem->Misc[E_MISC_CONSUME] > 0){
							enem->Misc[E_MISC_CONSUME]--;
						}


						// check that enemy has not already been counted for collision, and if there is a collision.
						if(enem->Misc[E_MISC_CONSUME]==0 && Collision(consume_weap, enem) ){
							if(consume_counter == -1){  // one-time use, so kill it

								EnemyKillWeap(consume_weap, enem, unique_ID);

								if(consume_item != -1){
									Link->Item[consume_item] = false;
								}

								Quit(); // collision destroyed weapon, so all done.

							}else if(consume_counter >= 100){
								// must be special type/counter (no item to destroy, just lweapon)
								consume_counter--;

								if(consume_counter <= 100){

									EnemyKillWeap(consume_weap, enem, unique_ID);
					
									Quit();
								}

							}else{  // multiuse, reduce counter
								Game->Counter[consume_counter]--;  
							
								if(Game->Counter[consume_counter]<=0){  // multiuse, no more uses.

									EnemyKillWeap(consume_weap, enem, unique_ID);

									if(consume_item != -1){
										Link->Item[consume_item] = false;
									}

									Quit(); // collision destroyed weapon, so all done.
								}
							}

							if(brangspecial	== 1){
								consume_weap->DeadState = WDS_BOUNCE;
							}else if(brangspecial == 2){
								consume_weap->DeadState = WDS_DEAD;
							}

							// if LW still exists, set that this enemy has been counted for collision.
							// certain lweapon types will be possible to hit more than once.
							if(consume_type == LW_FIRE){
								// enemy can be hurt again by fire after this many frames
								enem->Misc[E_MISC_CONSUME] = 30;  
							}else if(consume_type == LW_BRANG){
								// enemy can be hurt again by boomerang (returning?) 	
								enem->Misc[E_MISC_CONSUME] = 7;
							}else{
								// not possible to hit more than once.
 								enem->Misc[E_MISC_CONSUME] = -1;
							}
					
						}//end of enemy collision if

					} // end of NPC forloop

					break; // we found our LW, so no need to search for more.
				} // end of its our LW if.

			} // end of Lweapon forloop

			Waitframe();
		}//end of while loop
	}//end of run
}//end of function Consumable_FFC


// ------------------------------------------------------------------
// Following functions are called by Consumable_FFC

// finds and marks our LW, returns the unique_ID if found.  -1 if LW not found.
int MarkOurLW(int consume_type){
	int our_lw = 0;
	int unique_ID = 1;

	for (int j = Screen->NumLWeapons(); j > 0; j--){
		lweapon consume_weap = Screen->LoadLWeapon(j);

		if(consume_weap->ID == consume_type){
			if( our_lw == 0 ){
				our_lw = j;
			}else{
				unique_ID += 1;
			}
		}
	}
	if(our_lw > 0){
		lweapon consume_weap = Screen->LoadLWeapon(our_lw);
		consume_weap->Misc[LW_MISC_CONSUME] = unique_ID;
		return unique_ID;
	}
	
	return -1;
}//end function


// function handles all collision checking with solids
bool SolidCheck(int check_solid, lweapon cweap, int ctype){
	if( (check_solid == 2 || check_solid == 3 || check_solid == 5 || check_solid == 7)
            && Screen->isSolid(cweap->X, cweap->Y) ){
		return true;
	}
	if( (check_solid == 3 || check_solid == 4 || check_solid == 6 || check_solid == 7)
            && IsWater(ComboAt(cweap->X, cweap->Y)) ){
		return true;
	}

	// LW_FIRE collision detection is a bit off.	
	if(cweap->ID == LW_FIRE){
		if(cweap->Dir == DIR_LEFT || cweap->Dir == DIR_RIGHT){
			if( (check_solid == 2 || check_solid == 3 || check_solid == 5 || check_solid == 7) && 
			    (Screen->isSolid(HitboxRight(cweap), cweap->Y) || Screen->isSolid(HitboxLeft(cweap), cweap->Y) ) ){
				return true;
			}
			if( (check_solid == 3 || check_solid == 4 || check_solid == 6 || check_solid == 7) && 
			    (IsWater(ComboAt(HitboxRight(cweap), cweap->Y)) || IsWater(ComboAt(HitboxLeft(cweap), cweap->Y)) ) ){
				return true;
			}
		}else if(cweap->Dir == DIR_UP || cweap->Dir == DIR_DOWN){ 
			if( (check_solid == 2 || check_solid == 3 || check_solid == 5 || check_solid == 7) &&
			    (Screen->isSolid(cweap->X,HitboxTop(cweap)) || Screen->isSolid(cweap->X,HitboxBottom(cweap)) ) ){
				return true;
			}
			if( (check_solid == 3 || check_solid == 4 || check_solid == 6 || check_solid == 7) &&
			    (IsWater(ComboAt(cweap->X,HitboxTop(cweap))) || IsWater(ComboAt(cweap->X,HitboxBottom(cweap))) ) ){
				return true;
			}
		}
	}

	// this is a random oddity.  Link's sword stab doesn't register collisions with solids when stabbing down or right.  this fixes it.
	if( (check_solid == 2 || check_solid == 3 || check_solid == 5 || check_solid == 7) 
            && (ctype == LW_SWORD && !Game->Generic[GEN_CANSLASH]) ){
		if(Link->Dir == DIR_DOWN && Screen->isSolid(cweap->X, HitboxBottom(cweap) ) ){
			return true;
		}
		if(Link->Dir == DIR_RIGHT && Screen->isSolid(HitboxRight(cweap), cweap->Y ) ){
			return true;
		}
	}

	return false;
}//end function


// special function for LW_FIRE to prevent a bug where the collision registers before
// damage is done when fire moving up or left towards enemy.
void WaitForDamageThenDeadState(int wait_hp, int unique_ID){
	while(true){
		for (int i = 1; i <= Screen->NumNPCs(); i++ ){
			npc enem = Screen->LoadNPC(i);

			// look for the marked enemy that it has registered a collision with
			// wait for the hp to drop before killing lweapon
			if(enem->Misc[E_MISC_CONSUME] == -100){
				if(enem->HP < wait_hp){
					for (int j = 1; j <= Screen->NumLWeapons(); j++){
						lweapon consume_weap = Screen->LoadLWeapon(j);

						// check if this lweapon is our lweapon.
						if(consume_weap->Misc[LW_MISC_CONSUME] == unique_ID && consume_weap->ID == LW_FIRE){
							consume_weap->DeadState = WDS_DEAD;
							Quit();
						}
					}//end lweapon for

					// our enemy hp went down, but our lweapon was already gone
					Quit();
				}//end hp check
			}//end check for marked enemy
		}//end enem for loop

		Waitframe();
	}//end while loop
}//end function

// function handles some of the unique properties of various lweapons
void EnemyKillWeap(lweapon consume_weap, npc enem, int unique_ID){

	if(consume_weap->ID == LW_HAMMER){
		Waitframes(6);
	}else if(consume_weap->ID == LW_BEAM){
		consume_weap->DeadState = WDS_BEAMSHARDS;
	}else if(consume_weap->ID == LW_FIRE && 
		(consume_weap->Dir == DIR_UP || consume_weap->Dir == DIR_LEFT) ){

		enem->Misc[E_MISC_CONSUME] = -100; // mark our enemy for the next function
		WaitForDamageThenDeadState(enem->HP, unique_ID);						
	}else{
		consume_weap->DeadState = WDS_DEAD;
	}
}//end function


// function handles some of the unique properties of various lweapons
void SolidKillWeap(lweapon consume_weap){

	if(consume_weap->ID == LW_HAMMER){
		Waitframes(6);
	}else if(consume_weap->ID == LW_BEAM){
		consume_weap->DeadState = WDS_BEAMSHARDS;
	}else{
		consume_weap->DeadState = WDS_DEAD;
	}
}//end function

// function sets cooldown timer for next solid collision
int SetCoolDown(int consume_type){
	if(consume_type == LW_FIRE){
		return 30;
	}else if(consume_type == LW_BRANG){
		return 7;
	}else if(consume_type == LW_BEAM){
		return 10; // test
	}else if(consume_type == LW_MAGIC){
		return 10; // test
	}

	return 100; // high number will prevent other lweapons from registering another solid collision
}//end function

// optional function for debugging why lweapon isn't being found
// shows frame count on screen so you know it is writing to allegro.log
// after a new line and 2000, it writes the LW_ type, and the frame it was found
void Debug_MissingLW(int consume_type){
	TraceNL();
	Trace(2000);

	for (int idebug = 0; idebug < 600; idebug++){
		Quick_Debug(idebug);
				
		for (int j = Screen->NumLWeapons(); j > 0; j--){
			lweapon consume_weap = Screen->LoadLWeapon(j);
	
			if(consume_weap->ID == consume_type){
				Trace(consume_type);
				Trace(idebug);
				Quit();
			}
		}//end for

		Waitframe();	
	}//end for
}//end function

void Quick_Debug(int num){
	Screen->DrawInteger(6, ComboX(80), ComboY(80), FONT_Z1, 0x01, 0x00, 0, 0, num, 0, OP_OPAQUE);
}

// end functions called by Consumable_FFC
// ------------------------------------------------------------------



// Notes on various curiosities with the script:
//
// Swords - Beams aren't counted as a collison when using LW_SWORD type.
//	    You can set LW_BEAMS as the 2nd type.
//          stab and slash both work.  
//          
// Bow&Arrow - if you want to have the weapon itself consumable, you need to set the Action script
//             on the Arrow item (not arrow ammunition).  Setting it on bow item doesn't do anything.
//
// Candle - probably works best as a use item
//          however collisions have been tested with LW_FIRE type. the same enemy can be hit more than once by one fire after a brief rest period. 
//          untested collisions with LW_CANDLE type, because who swings a candle?
//
// Wand - works.  If you set LW_MAGIC as the type you might not be able to trigger Wand Fire with certain setups.
// Whistle - seems to work without issue?  Only tried whirlwinds, not drying lakes and secrets.
//
// Bait - seems to work without issue?
// Hammer - seems to work without issue?
// Boomerang - seems to work without issue?
//
// Ladder - no issue?
//
// didn't test anything else.  Let me know.
//