import "std.zh" // only need this once
import "ffcscript.zh" // only need this once
///////////////////////////////////////////////////////////////////////////////////////////////////
// Bait weapons
//
// Setup:
// 1. Set the following constants.
// 1a. Potentially remove the common isSolid function at the bottom of the script, if you already have it in your script file.
// 2. Import the script file.
// 3. Slot all the item scripts you are using, and all the corresponding ffc scripts.
// If using a regular bait item in your quest as well, you do also need the I_ACT_BaitNormal to prevent a bug where the other baits will just modify an already placed normal bait.
// ie. if using only the poison bait and normal bait in your quest, you will need to slot the I_ACT_BaitPoison, FFC_BaitPoison, and the I_ACT_BaitNormal.
//
// 4. Setup your new bait items to your liking. Attach the I_ACT scripts to your bait items. Set the script arguments to your liking.
// Under Quest->Graphics->Sprites->Weapons/Misc, create new bait sprites for when they are placed, and set that as the Bait sprite on the item action tab.
// Remember the normal bait needs the BaitNormal attached as well.
// For the baitchu item. The sprite is intended to be directional, and can be animated. Set up your tiles - up, down, left, right. Select the first up tile in the sprite editor.
//
// 5. You don't need to place the ffcs on the screen, they are created by the item scripts.
//
// Bait - Poison arguments (I_ACT_BaitPoison)
//
// D0 = poison damage - HP amount it deals to affected enemy every D1 frames
// D1 = poison rate - how often the D0 damage is dealt to affected enemy
// D2 = poison duration - number of times the D0 damage will be inflicted.
// D3 = hunger threshold - only affects enemies with a hunger number >= this. Range from 0 - 4.
// Bait - Sleep arguments (I_ACT_BaitSleep)
// D0 = sleep delay - number of frames before the enemy is affected by the sleep
// D2 = sleep duration - number of frames the enemy will be asleep
// D3 = hunger threshold - only affects enemies with a hunger number >= this. Range from 0 - 4.
// Bait - Mine arguments (I_ACT_BaitMine)
//
// D0 = explosion damage - HP amount the bait mine does when it explodes
// D1 = explosion delay - how many frames after being triggered it takes for the explosion to happen
// D2 = hunger threshold - only triggered by enemies with a hunger number >= this. Range from 0 - 4.
// D3 = number of enemies to trigger - number of enemies required to activate the mine. leaving at 0 is the same as 1 enemy.
// D4 = display countdown to explosion text. only displays is countdown>0 and delay>0. the countdown is displayed as D1/D4 rounded down.
// Baitchu arguments (I_ACT_Baitchu)
//
// D0 = speed - baitchu will travel away from Link in the direction he was facing when he placed it at this speed. number of pixels per frame. decimal accepted.
// Constants to set
const int COMBO_POISON = 900; // combo# of the graphic to display over poisoned enemy. leave at 0 if not using. can be animated.
const int COMBO_POISON_CSET = 8; // cset# of above combo
const int COMBO_POISON_YOFFSET = 0; // yoffset. to change the position of the poison graphic. negative numbers will be above the enemy.
const int COMBO_SLEEP = 0; // as above, for enemies affected by bait sleep
const int COMBO_SLEEP_CSET = 0;
const int COMBO_SLEEP_YOFFSET = 0;
// if using bait->mine delay, can show a countdown over the activated bait.
const int MINECOUNTDOWN_FONT = 0; // FONT_Z1, other font values found in std_contants.zh
const int MINECOUNTDOWN_FONTCOLOR = 0x01; // white in classic set
const int MINECOUNTDOWN_FONTBG = 0x00; // black in classic set, set to -1 for transparent
const int MINECOUNTDOWN_YOFFSET = -8; // yoffset for countdown over bait sprite, negative numbers will be above bait
// index to npc->Misc arrays, use value that are unused by other scripts. if using both poison and sleep baits, these do have to be different values.
const int E_MISC_POISON = 0;
const int E_MISC_SLEEP = 1;
const int LW_MISC_BAITTYPE = 0; // index to lweapon->Misc[] array. use a value unused by other scripts for the LW_BAIT type. the scripts need to track baits on screen to prevent bugs.
// Item scripts
item script I_ACT_BaitPoison
{
void run(int damage, int rate, int duration, int hunger)
{
if( NumLWeaponsOf(LW_BAIT) != 1) Quit();
lweapon bait = LoadLWeaponOf(LW_BAIT);
if(bait->Misc[LW_MISC_BAITTYPE] == BT_UNMARKED) bait->Misc[LW_MISC_BAITTYPE] = BT_POISON;
else Quit();
int ffcScriptName[] = "FFC_BaitPoison";
int ffcScriptNum = Game->GetFFCScript(ffcScriptName);
int args[] = {damage, rate, duration, hunger};
RunFFCScript(ffcScriptNum, args);
}
}
item script I_ACT_BaitSleep
{
void run(int delay, int duration, int hunger)
{
if( NumLWeaponsOf(LW_BAIT) != 1) Quit();
lweapon bait = LoadLWeaponOf(LW_BAIT);
if(bait->Misc[LW_MISC_BAITTYPE] == BT_UNMARKED) bait->Misc[LW_MISC_BAITTYPE] = BT_SLEEP;
else Quit();
int ffcScriptName[] = "FFC_BaitSleep";
int ffcScriptNum = Game->GetFFCScript(ffcScriptName);
int args[] = {delay, duration, hunger};
RunFFCScript(ffcScriptNum, args);
}
}
item script I_ACT_BaitMine
{
void run(int damage, int delay, int hunger, int numenemies, int countdown)
{
if( NumLWeaponsOf(LW_BAIT) != 1) Quit();
lweapon bait = LoadLWeaponOf(LW_BAIT);
if(bait->Misc[LW_MISC_BAITTYPE] == BT_UNMARKED) bait->Misc[LW_MISC_BAITTYPE] = BT_MINE;
else Quit();
int ffcScriptName[] = "FFC_BaitMine";
int ffcScriptNum = Game->GetFFCScript(ffcScriptName);
int args[] = {damage, delay, hunger, numenemies, countdown};
RunFFCScript(ffcScriptNum, args);
}
}
item script I_ACT_Baitchu
{
void run(int speed)
{
if( NumLWeaponsOf(LW_BAIT) != 1) Quit();
lweapon bait = LoadLWeaponOf(LW_BAIT);
if(bait->Misc[LW_MISC_BAITTYPE] == BT_UNMARKED) bait->Misc[LW_MISC_BAITTYPE] = BT_BCHU;
else Quit();
int ffcScriptName[] = "FFC_Baitchu";
int ffcScriptNum = Game->GetFFCScript(ffcScriptName);
int args[] = {speed};
RunFFCScript(ffcScriptNum, args);
}
}
item script I_ACT_BaitNormal
{
void run()
{
if( NumLWeaponsOf(LW_BAIT) != 1) Quit();
lweapon bait = LoadLWeaponOf(LW_BAIT);
if(bait->Misc[LW_MISC_BAITTYPE] == BT_UNMARKED) bait->Misc[LW_MISC_BAITTYPE] = BT_NORMAL;
}
}
// FFC scripts, called by the item scripts.
ffc script FFC_BaitPoison
{
void run(int damage, int rate, int duration, int hunger)
{
bool poison;
int counter = 0;
lweapon bait;
npc e;
while(true)
{
bait = LoadLWeaponOf(LW_BAIT);
if (!bait->isValid() && !poison) Quit();
poison = false;
for (int i = Screen->NumNPCs(); i > 0; i--)
{
e = Screen->LoadNPC(i);
if (e->Hunger < hunger) continue;
if(bait->isValid())
{
if (Collision(e,bait))
{
e->Misc[E_MISC_POISON] = duration;
e->Hunger = 0;
}
}
if (e->Misc[E_MISC_POISON] > 0)
{
poison = true;
if(COMBO_POISON > 0) Screen->FastCombo(6,e->X,e->Y+COMBO_POISON_YOFFSET,COMBO_POISON,COMBO_POISON_CSET,OP_OPAQUE);
if(counter == 0)
{
e->Misc[E_MISC_POISON]--;
e->HP -= damage;
}
}
}
counter = (counter +1) % rate;
Waitframe();
}
}
}
ffc script FFC_BaitSleep
{
void run(int delay, int duration, int hunger)
{
bool sleep;
lweapon bait;
npc e;
while(true)
{
bait = LoadLWeaponOf(LW_BAIT);
if (!bait->isValid() && !sleep) Quit();
sleep = false;
for (int i = Screen->NumNPCs(); i > 0; i--)
{
e = Screen->LoadNPC(i);
if (e->Hunger < hunger) continue;
if(e->Misc[E_MISC_SLEEP] == -1)
{
if(e->Stun > 0)
{
Screen->FastCombo(6,e->X,e->Y+COMBO_SLEEP_YOFFSET,COMBO_SLEEP,COMBO_SLEEP_CSET,OP_OPAQUE);
sleep = true;
continue;
}
else e->Misc[E_MISC_SLEEP] = 0;
}
if (e->Misc[E_MISC_SLEEP] > 0)
{
e->Misc[E_MISC_SLEEP]--;
if (e->Misc[E_MISC_SLEEP] == 0)
{
e->Stun = duration;
if(COMBO_SLEEP > 0) e->Misc[E_MISC_SLEEP] = -1;
}
sleep = true;
continue;
}
if (Collision(e,bait) && e->Stun == 0)
{
if(delay == 0) e->Stun = duration;
else
{
e->Misc[E_MISC_SLEEP] = Max(1,delay);
sleep = true;
}
}
}
Waitframe();
}
}
}
ffc script FFC_BaitMine
{
void run(int damage, int delay, int hunger, int numenemies, int countdown)
{
lweapon bait;
npc e;
int delaycount = -1;
int ecount;
while(true)
{
bait = LoadLWeaponOf(LW_BAIT);
if (!bait->isValid()) Quit();
ecount = 0;
for (int i = Screen->NumNPCs(); i > 0; i--)
{
if (delaycount >= 0) break;
e = Screen->LoadNPC(i);
if (e->Hunger < hunger) continue;
if (Collision(e,bait))
{
if (numenemies > 0) ecount++;
if (numenemies == ecount)
{
delaycount = delay;
break;
}
}
}
if (delaycount>0)
{
delaycount--;
if(countdown>0) Screen->DrawInteger(6, bait->X, bait->Y+MINECOUNTDOWN_YOFFSET,
MINECOUNTDOWN_FONT, MINECOUNTDOWN_FONTCOLOR, MINECOUNTDOWN_FONTBG, 0, 0, Floor(delaycount/countdown), 0, OP_OPAQUE);
}
else if (delaycount == 0)
{
lweapon blast = CreateLWeaponAt(LW_BOMBBLAST,bait->X,bait->Y);
blast->Damage = damage;
bait->DeadState = WDS_DEAD;
Quit();
}
Waitframe();
}
}
}
ffc script FFC_Baitchu
{
void run(int speed)
{
lweapon bait = LoadLWeaponOf(LW_BAIT);
if (!bait->isValid()) Quit();
bait->Dir = Link->Dir;
if (bait->Dir == DIR_DOWN) bait->OriginalTile += bait->NumFrames;
else if (bait->Dir == DIR_LEFT) bait->OriginalTile += bait->NumFrames*2;
else if (bait->Dir == DIR_RIGHT) bait->OriginalTile += bait->NumFrames*3;
bool stopped;
float bx = bait->X;
float by = bait->Y;
while(true)
{
bait = LoadLWeaponOf(LW_BAIT);
if (!bait->isValid()) Quit();
if(!stopped)
{
int top = HitboxTop(bait);
int bottom = HitboxBottom(bait);
int left = HitboxLeft(bait);
int right = HitboxRight(bait);
if (bait->Dir == DIR_UP)
{
top -= speed;
int loc = ComboAt(CenterX(bait),top);
if(top < 0 || isSolid(left,top) || isSolid(right,top) || IsWater(loc) ) stopped = true;
else by -= speed;
if( Floor(by) != bait->Y) bait->Y = by;
}
else if (bait->Dir == DIR_DOWN)
{
bottom += speed;
int loc = ComboAt(CenterX(bait),bottom);
if(bottom > 176 || isSolid(left,bottom) || isSolid(right,bottom) || IsWater(loc) ) stopped = true;
else by += speed;
if( Floor(by) != bait->Y) bait->Y = by;
}
else if (bait->Dir == DIR_LEFT)
{
left -= speed;
int loc = ComboAt(left,CenterY(bait));
if(left < 0 || isSolid(left,top) || isSolid(left,bottom) || IsWater(loc) ) stopped = true;
else bx -= speed;
if( Floor(bx) != bait->X) bait->X = bx;
}
else if (bait->Dir == DIR_RIGHT)
{
right += speed;
int loc = ComboAt(right,CenterY(bait));
if(right > 256 || isSolid(right,top) || isSolid(right,bottom) || IsWater(loc) ) stopped = true;
else bx += speed;
if( Floor(bx) != bait->X) bait->X = bx;
}
}
Waitframe();
}
}
}
// !!!!!!!!! This function is used by a lot of scripts. Only need it once.
//returns true if the combo is solid at x/y
bool isSolid(int x, int y){
if(x<0 || x>255 || y<0 || y>175) return false;
int mask=1111b;
if(x%16<8) mask&=0011b;
else mask&=1100b;
if(y%16<8) mask&=0101b;
else mask&=1010b;
return (!(Screen->ComboS[ComboAt(x, y)]&mask)==0);
}
// don't touch these contants. they are the values of the lw->Misc[LW_MISC_BAITTYPE]
const int BT_UNMARKED = 0;
const int BT_NORMAL = 1;
const int BT_POISON = 2;
const int BT_SLEEP = 3;
const int BT_MINE = 4;
const int BT_BCHU = 5;