// 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.
//