import "std.zh"
import "stdExtra.zh"
import "ffcscript.zh"
import "string.zh"
const int CR_MAXBOMBS = 7; //Max bombs on screen (Script 1)
const int I_BOMBWALK = 100; //Item to allow walking on bombs
const int I_BOMBPUSH = 102; //Item to allow pushing bombs (once each)
const int CMB_SOLID = 1; //Solid transparent combo
const int CF_BOMBERBLOCK = 98; //Flag that makes blocks bombable
const int LW_BOMBERBOMB = 31; //LWeapon type for the placed bomb (only change if used by another script)
const int LW_BOMBERBLAST = 32; //LWeapon type for the explosion (can't use bomb blast because that's bigger than one tile)
//Must not be shared by any other weapon type
const int NPC_DROPPER = 200; //Invisible enemy whose dropset has possible block drops
const int explStunTime = 60; //Time an enemy is stunned when hit
//D0 = Bomb sprite
//D1 = Bomb fuse (time till it explodes)
//D2 = Explosion sprite (Uses one sprite. Have animation frames for vertical, horizontal, and middle explosion tiles in that order)
//D3 = Explosion damage
//D4 = Time explosions remain on screen
//D5 = 0 = break one block each direction; 1 = break unlimited number of blocks
//D6 = Explosion length (in tiles)
//D7 = Whether or not the bomb is remote-activated
item script bomber{
void run ( int bombSprite, int fuse, int expSprite, int damage, int blastTime, int pierce, int length, int remote ){
int ffcScriptName[] = "bomberFFC";
int ffcScriptNum = Game->GetFFCScript(ffcScriptName);
//Not remote: Check for max bombs
//Remote: Check for one bomb
if ( ( !remote && CountFFCsRunning(ffcScriptNum) < Game->Counter[CR_MAXBOMBS] )
|| ( remote && CountFFCsRunning(ffcScriptNum) < 1 )
){
int args[8] = { bombSprite, fuse, expSprite, damage, blastTime, pierce, length, remote };
RunFFCScript(ffcScriptNum, args);
}
}
}
ffc script bomberFFC{
void run ( int bombSprite, int fuse, int expSprite, int damage, int blastTime, int pierce, int length, int remote ){
lweapon explosion[65];
bool madeSolid; //Whether the bomb's combo has been made solid yet
//Push block variables
int lastX = Link->X; //Link's last position
int lastY = Link->Y;
int pushTime = 8;
int curPushTime;
int pushDir;
bool moved;
//Place bomb constrained to grid
lweapon bomb = CreateLWeaponAt(LW_BOMBERBOMB, GridX(CenterLinkX()), GridY(CenterLinkY()));
bomb->UseSprite(bombSprite);
bomb->CollDetection = false;
//Save the combo under the bomb and make it solid
int saveCombo = Screen->ComboD[ComboAt(bomb->X, bomb->Y)];
int saveFlag = Screen->ComboF[ComboAt(bomb->X, bomb->Y)];
//Before placing the bomb, make sure there's not another bomb there
for ( int i = Screen->NumLWeapons(); i > 0; i-- ){
lweapon otherBomb = Screen->LoadLWeapon(i); //Check each other weapon
if ( otherBomb != bomb //If it's not this script's weapon
&& otherBomb->X == bomb->X //And it's on the same spot
&& otherBomb->Y == bomb->Y
){
bomb->DeadState = WDS_DEAD; //Kill the bomb
return; //And quit
}
}
//Play place SFX
Game->PlaySound(SFX_PLACE);
//Set "No enemies" flag
Screen->ComboF[ComboAt(bomb->X, bomb->Y)] = CF_NOGROUNDENEMY;
//Wait for fuse to end or remote to be signaled
Link->PressB = false; //Don't let the bomb blow up immediately
while ( (!remote && fuse-- > 0)
|| (remote && !Link->PressB)
){
//Once Bomberman gets off the bomb, make it solid unless he has the bombwalk item
if ( ComboAt(CenterLinkX(), CenterLinkY()) != ComboAt(bomb->X, bomb->Y)
&& !madeSolid
&& !Link->Item[I_BOMBWALK]
){
madeSolid = true;
Screen->ComboD[ComboAt(bomb->X, bomb->Y)] = CMB_SOLID;
}
//Draw old combo under bomb
if ( madeSolid )
Screen->FastCombo( 0, bomb->X, bomb->Y, saveCombo, Screen->ComboC[ComboAt(bomb->X, bomb->Y)], 128 );
//Push block features
//If Link has block push item and walks against it
if ( Link->Item[I_BOMBPUSH] ){
if (PushingFFC(this)){
Waitdraw();
Link->X = lastX; //Cancel Link's movement
Link->Y = lastY;
if ( !moved ){
curPushTime += 2;
pushDir = Link->Dir;
}
}
else //Otherwise reduce push time
curPushTime--;
if ( curPushTime >= pushTime ){
for ( int i = 0; i < 16; i++ ){
if ( pushDir == DIR_UP ) this->Y--;
else if ( pushDir == DIR_DOWN ) this->Y++;
else if ( pushDir == DIR_LEFT ) this->X--;
else if ( pushDir == DIR_RIGHT ) this->X++;
WaitNoAction();
}
moved = true;
}
lastX = Link->X; //Update Link's last position
lastY = Link->Y;
}
//Check for another explosion hitting the bomb
for ( int i = Screen->NumLWeapons(); i > 0; i-- ){
lweapon weap = Screen->LoadLWeapon(i);
if ( weap->ID == LW_BOMBERBLAST && Collision(weap, bomb) ){
remote = 0; //End waiting loop; detonate bomb now
fuse = 0;
break;
}
}
Waitframe();
}
//Restore old combo if made solid
if ( madeSolid )
Screen->ComboD[ComboAt(bomb->X, bomb->Y)] = saveCombo;
//And old flag
Screen->ComboF[ComboAt(bomb->X, bomb->Y)] = saveFlag;
//Play explosion SFX
Game->PlaySound(SFX_BOMB);
//For each of 4 directions
//Start with the center
explosion[0] = CreateLWeaponAt(LW_BOMBERBLAST, bomb->X, bomb->Y);
explosion[0]->Damage = damage;
explosion[0]->UseSprite(expSprite);
explosion[0]->OriginalTile += explosion[0]->NumFrames*2;
for ( int dir = 0; dir < 4; dir++ ){
for ( int dist = 32; dist < length*16; dist += 16 ){
int index = 1 + (dir * 16) + (dist / 16); //Find the number of this segment
//1: Skip the center
//Dir*16: Up to 16 segments per direction
//Dist/16: One segment per 16 pixels
//Find explosion location
int x = bomb->X + InFrontX(dir, dist);
int y = bomb->Y + InFrontY(dir, dist);
//Determine the combo's properties
bool solid = Screen->isSolid(x+8, y+8); //Combo is solid
bool destructible = ComboFI(x+8, y+8, CF_BOMBERBLOCK); //Combo can be broken
//Check if it hits a solid wall
if ( solid ){
//If combo is destructible
if ( destructible ){
Screen->ComboD[ComboAt(x+8, y+8)] = Screen->UnderCombo; //Change to undercombo
npc itemDropper = CreateNPCAt(NPC_DROPPER, x, y); //Make an NPC to possibly spawn an item
itemDropper->HP = HP_SILENT; //Kill it silently to get its item
}
if ( !pierce && !destructible){ //If piercing not enabled and not walkable
dist = SCREEN_WIDTH; //Force inner loop to end
continue; //Stop and move on to next direction
}
}
//Now make each explosion segment
explosion[index] = CreateLWeaponAt(LW_BOMBERBLAST, x, y);
explosion[index]->Dir = dir;
explosion[index]->Damage = damage;
explosion[index]->UseSprite(expSprite);
if ( dir == DIR_DOWN ) explosion[index]->Flip = 3; //Down = vertical/horizontal flip
else if ( dir == DIR_LEFT || dir == DIR_RIGHT ){
explosion[index]->OriginalTile += explosion[index]->NumFrames; //If horizontal, change tile
if ( dir == DIR_LEFT ) explosion[index]->Flip = FLIP_H;
}
}
}
//Clear the bomb
bomb->DeadState = WDS_DEAD;
//Wait for explosions to clear, keep them alive until then
for( int t = 0; t < blastTime; t++ ){
for ( int i = 0; i < 65; i++ ){
if ( explosion[i]->isValid() ){
explosion[i]->DeadState = WDS_ALIVE;
if ( SmLinkCollision(explosion[i]) ) //If bomberman touches the explosion
Link->HP = 0; //He dies!
}
}
Waitframe();
}
//And then remove all of them
for ( int i = 0; i < 65; i++ ){
if ( explosion[i]->isValid() )
explosion[i]->DeadState = WDS_DEAD;
}
}
}
//"Small" LinkCollision: Doesn't count if only 1 pixel overlap between hitboxes
//In other words, it shrinks Link's hitbox
bool SmLinkCollision(lweapon b) {
int ax = Link->X + Link->HitXOffset + 3;
int bx = b->X + b->HitXOffset+2;
int ay = Link->Y + Link->HitYOffset + 3;
int by = b->Y + b->HitYOffset+2;
return RectCollision(ax, ay, ax+Link->HitWidth-3, ay+Link->HitHeight-3, bx, by, bx+b->HitWidth-2, by+b->HitHeight-2) && (Link->Z + Link->HitZHeight >= b->Z) && (Link->Z <= b->Z + b->HitZHeight);
}
//Same for item/lweapon, only more so
bool SmCollision(item a, lweapon b) {
int ax = a->X + a->HitXOffset + 2;
int bx = b->X + b->HitXOffset;
int ay = a->Y + a->HitYOffset + 2;
int by = b->Y + b->HitYOffset;
return RectCollision(ax, ay, ax+a->HitWidth-2, ay+a->HitHeight-2, bx, by, bx+b->HitWidth, by+b->HitHeight) && (a->Z + a->HitZHeight >= b->Z) && (a->Z <= b->Z + b->HitZHeight);
}
//Stolen/modified from whomever made the FFC lockblock script
bool PushingFFC(ffc this){
int y = this->X; int x = this->X;
return (Link->Dir == DIR_UP && Link->InputUp && Link->Y == y+this->EffectHeight-8 && Abs(Link->X-x) < 8)
|| (Link->Dir == DIR_DOWN && Link->InputDown && Link->Y == y-16 && Abs(Link->X-x) < 8)
|| (Link->Dir == DIR_LEFT && Link->InputLeft && Link->X == x+this->EffectWidth && Abs(Link->Y-y) < 8)
|| (Link->Dir == DIR_RIGHT && Link->InputRight && Link->X == x-16 && Abs(Link->Y-y) < 8);
}