const int SCRIPTEDDAMAGECOMBO_ONLYFACING = 1; //If 1, scripted damage combos will only do damage when Link is facing them
const int SCRIPTEDDAMAGECOMBO_DAMAGEONHEADBONK = 1; //If 1, Link takes damage instantly when jumping into a spiked ceiling
const int DELAY_SCRIPTEDDAMAGECOMBO = 8; //Default frame delay before taking damage
const int DELAY_SCRIPTEDDAMAGECOMBO_SIDEVIEW = 8; //Frame delay for sideview screens
const int EW_SCRIPTEDDAMAGECOMBO = 40; //EWeapon used for dealing damage. Script 10 by default
//Collision points for spike hitboxes
//This tells the script which points to check offset from
//Link's hitbox for various collision checks. Unless you're using
//large Link these shouldn't need to be changed
const int SDC_SOLID_HITBOX_UD_LEFT = 7;
const int SDC_SOLID_HITBOX_UD_RIGHT = 8;
const int SDC_SOLID_HITBOX_UD_TOP = 8;
const int SDC_SOLID_HITBOX_LR_TOP = 11;
const int SDC_SOLID_HITBOX_LR_BOTTOM = 12;
const int SDC_FALLING_HITBOX_LEFT = 4;
const int SDC_FALLING_HITBOX_RIGHT = 11;
const int SDC_CEILING_HITBOX_LEFT = 4;
const int SDC_CEILING_HITBOX_RIGHT = 11;
const int SDC_NONSOLID_HITBOX_LEFT = 4;
const int SDC_NONSOLID_HITBOX_RIGHT = 11;
const int SDC_NONSOLID_HITBOX_TOP = 11;
const int SDC_NONSOLID_HITBOX_BOTTOM = 12;
const int SDC_NONSOLID_HITBOX_SIDEVIEW_LEFT = 4;
const int SDC_NONSOLID_HITBOX_SIDEVIEW_RIGHT = 11;
const int SDC_NONSOLID_HITBOX_SIDEVIEW_TOP = 4;
const int SDC_NONSOLID_HITBOX_SIDEVIEW_BOTTOM = 11;
//Change this function is you have custom rings with different damage divisors in your quest
int ScriptedDamageCombo_GetTunicMultipliers(){
//Return the divisor of the highest level ring
if(Link->Item[I_RING3])
return 8;
else if(Link->Item[I_RING2])
return 4;
else if(Link->Item[I_RING1])
return 2;
//Otherwise no ring and damage is normal
return 1;
}
//D0: The combo flag to check for scripted damage combos. If 0, the FFC itself is a hitbox
//D1: How much base damage the spikes deal. 4 points = 1 heart
//D2: If 1, the damage pierces rings
//D3: If >0 set the delay before spikes deal damage when holding up against them
//D4: If 1, the combos are treated as non solid damage combos
//D5: If 1, Link takes no knockback when hit
ffc script ScriptedDamageCombo{
void run(int flag, int damage, int pierce, int damageDelay, int notSolid, int noKnockback){
if(damageDelay==0){
damageDelay = DELAY_SCRIPTEDDAMAGECOMBO;
if(IsSideview())
damageDelay = DELAY_SCRIPTEDDAMAGECOMBO_SIDEVIEW;
}
int timer;
int xyInput[2];
while(true){
//Combo placed on the screen
if(flag>0){
int collchk = ScriptedDamageCombo_DetectLinkCollision(flag, Link->X, Link->Y, Link->Dir, notSolid, xyInput);
if(collchk){
if(notSolid){
ScriptedDamageCombo_DamageLink(damage, pierce, noKnockback);
}
else{
if(timer<damageDelay&&collchk!=2)
timer++;
else{
ScriptedDamageCombo_DamageLink(damage, pierce, noKnockback);
}
}
}
else
timer = 0;
}
//Combo is the FFC
else{
eweapon e = FireEWeapon(EW_SCRIPTEDDAMAGECOMBO, this->X, this->Y, 0, 0, damage, 0, 0, EWF_UNBLOCKABLE);
e->DrawYOffset = -1000;
e->HitWidth = this->EffectWidth;
e->HitHeight = this->EffectHeight;
SetEWeaponLifespan(e, EWL_TIMER, 0);
SetEWeaponDeathEffect(e, EWD_VANISH, 0);
}
Waitframe();
}
}
void ScriptedDamageCombo_DamageLink(int damage, bool pierce, bool noKnockback){
ScriptedDamageCombo_DamageLink(damage, pierce, noKnockback, Link->Dir);
}
void ScriptedDamageCombo_DamageLink(int damage, bool pierce, bool noKnockback, int dir){
int x = Link->X;
int y = Link->Y;
if(dir==DIR_UP)
y -= 4;
else if(dir==DIR_DOWN)
y += 4;
else if(dir==DIR_LEFT)
x -= 4;
else if(dir==DIR_RIGHT)
x += 4;
if(pierce)
damage *= ScriptedDamageCombo_GetTunicMultipliers();
eweapon e = FireEWeapon(EW_SCRIPTEDDAMAGECOMBO, x, y, 0, 0, damage, 0, 0, EWF_UNBLOCKABLE);
e->DrawYOffset = -1000;
SetEWeaponLifespan(e, EWL_TIMER, 0);
SetEWeaponDeathEffect(e, EWD_VANISH, 0);
if(noKnockback)
Link->HitDir = -1;
}
void ScriptedDamageCombo_UpdateInput(int xyInput){
if(xyInput[1]==0){ //If no Y axis pressed
if(Link->PressUp&&Link->PressDown) //Default to up when buttons pressed simultaneously
xyInput[1] = -1;
else if(Link->PressUp||Link->InputUp) //Set axis based on which button what pressed
xyInput[1] = -1;
else if(Link->PressDown||Link->InputDown)
xyInput[1] = 1;
}
else{ //If Y axis pressed
if(!Link->InputUp&&!Link->InputDown) //Release Y axis if neither button pressed
xyInput[1] = 0;
else if(xyInput[1]==-1&&!Link->InputUp) //Reverse Y axis if opposite direction held and button released
xyInput[1] = 1;
else if(xyInput[1]==1&&!Link->InputDown)
xyInput[1] = -1;
}
if(xyInput[0]==0){ //If no X axis pressed
if(Link->PressLeft&&Link->PressRight) //Default to left when buttons pressed simultaneously
xyInput[0] = -1;
else if(Link->PressLeft||Link->InputLeft) //Set axis based on which button what pressed
xyInput[0] = -1;
else if(Link->PressRight||Link->InputRight)
xyInput[0] = 1;
}
else{ //If Y axis pressed
if(!Link->InputLeft&&!Link->InputRight) //Release Y axis if neither button pressed
xyInput[0] = 0;
else if(xyInput[0]==-1&&!Link->InputLeft) //Reverse Y axis if opposite direction held and button released
xyInput[0] = 1;
else if(xyInput[0]==1&&!Link->InputRight)
xyInput[0] = -1;
}
}
int ScriptedDamageCombo_DetectLinkCollision(int flag, int x, int y, int dir, bool notSolid, int xyInput){
ScriptedDamageCombo_UpdateInput(xyInput);
int x2; int y2;
//Check for collisions against solid combos
if(!notSolid){
//Check for sideview gravity
if(IsSideview()){
bool onSpikes;
bool onSafeSolid;
for(int i=SDC_FALLING_HITBOX_LEFT;i<=SDC_FALLING_HITBOX_RIGHT; i=Min(i+8, SDC_FALLING_HITBOX_RIGHT)){
x2 = x+i;
y2 = y+16;
//Spikes mark Link as getting hurt
if(ComboFI(x2, y2, flag)){
if(Screen->isSolid(x2, y2)){
onSpikes = true;
}
}
//But standing on adjacent land overrides this
else{
if(Screen->isSolid(x2, y2)){
onSafeSolid = true;
}
}
if(i==SDC_FALLING_HITBOX_RIGHT)
break;
}
if(onSpikes&&!onSafeSolid){
return 2; //Special return value for sideview spikes, which overrides the damage cooldown
}
if(SCRIPTEDDAMAGECOMBO_DAMAGEONHEADBONK){
if(Link->Jump>0){
onSpikes = false;
onSafeSolid = false;
for(int i=SDC_CEILING_HITBOX_LEFT;i<=SDC_CEILING_HITBOX_RIGHT; i=Min(i+8, SDC_CEILING_HITBOX_RIGHT)){
x2 = x+i;
y2 = y-Max(Ceiling(Link->Jump), 1);
//Spikes mark Link as getting hurt
if(ComboFI(x2, y2, flag)){
if(Screen->isSolid(x2, y2)){
onSpikes = true;
}
}
//But standing on adjacent land overrides this
else{
if(Screen->isSolid(x2, y2)){
onSafeSolid = true;
}
}
if(i==SDC_CEILING_HITBOX_RIGHT)
break;
}
if(onSpikes&&!onSafeSolid){
return 2; //Special return value for sideview spikes, which overrides the damage cooldown
}
}
}
}
//Check collisions walking up
if(xyInput[1]==-1){
for(int i=SDC_SOLID_HITBOX_UD_LEFT; i<=SDC_SOLID_HITBOX_UD_RIGHT; i=Min(i+8, SDC_SOLID_HITBOX_UD_RIGHT)){
x2 = x+i;
y2 = y+SDC_SOLID_HITBOX_UD_TOP-1;
if(ComboFI(x2, y2, flag)){
if(Screen->isSolid(x2, y2)){
if(!SCRIPTEDDAMAGECOMBO_ONLYFACING||Link->Dir==DIR_UP)
return 1;
}
}
if(i==SDC_SOLID_HITBOX_UD_RIGHT)
break;
}
}
//Check collisions walking down
else if(xyInput[1]==1){
for(int i=SDC_SOLID_HITBOX_UD_LEFT; i<=SDC_SOLID_HITBOX_UD_RIGHT; i=Min(i+8, SDC_SOLID_HITBOX_UD_RIGHT)){
x2 = x+i;
y2 = y+16;
if(ComboFI(x2, y2, flag)){
if(Screen->isSolid(x2, y2)){
if(!SCRIPTEDDAMAGECOMBO_ONLYFACING||Link->Dir==DIR_DOWN)
return 1;
}
}
if(i==SDC_SOLID_HITBOX_UD_RIGHT)
break;
}
}
//Check collisions walking left
else if(xyInput[0]==-1){
for(int i=SDC_SOLID_HITBOX_LR_TOP; i<=SDC_SOLID_HITBOX_LR_BOTTOM; i=Min(i+8, SDC_SOLID_HITBOX_LR_BOTTOM)){
x2 = x-1;
y2 = y+i;
if(ComboFI(x2, y2, flag)){
if(Screen->isSolid(x2, y2)){
if(!SCRIPTEDDAMAGECOMBO_ONLYFACING||Link->Dir==DIR_LEFT)
return 1;
}
}
if(i==SDC_SOLID_HITBOX_LR_BOTTOM)
break;
}
}
//Check collisions walking right
else if(xyInput[0]==1){
for(int i=SDC_SOLID_HITBOX_LR_TOP; i<=SDC_SOLID_HITBOX_LR_BOTTOM; i=Min(i+8, SDC_SOLID_HITBOX_LR_BOTTOM)){
x2 = x+16;
y2 = y+i;
if(ComboFI(x2, y2, flag)){
if(Screen->isSolid(x2, y2)){
if(!SCRIPTEDDAMAGECOMBO_ONLYFACING||Link->Dir==DIR_RIGHT)
return 1;
}
}
if(i==SDC_SOLID_HITBOX_LR_BOTTOM)
break;
}
}
}
//Check for collisions against nonsolid combos
else{
int hit[4] = {SDC_NONSOLID_HITBOX_LEFT, SDC_NONSOLID_HITBOX_RIGHT, SDC_NONSOLID_HITBOX_TOP, SDC_NONSOLID_HITBOX_BOTTOM};
if(IsSideview()){
hit[0] = SDC_NONSOLID_HITBOX_SIDEVIEW_LEFT;
hit[1] = SDC_NONSOLID_HITBOX_SIDEVIEW_RIGHT;
hit[2] = SDC_NONSOLID_HITBOX_SIDEVIEW_TOP;
hit[3] = SDC_NONSOLID_HITBOX_SIDEVIEW_BOTTOM;
}
for(int xi=hit[0]; xi<=hit[1]; xi=Min(xi+8, hit[1])){
for(int yi=hit[2]; yi<=hit[3]; yi=Min(yi+8, hit[3])){
x2 = x+xi;
y2 = y+yi;
if(ComboFI(x2, y2, flag)){
return 1;
}
if(yi==hit[3])
break;
}
if(xi==hit[1])
break;
}
}
return 0;
}
}