// The constants and functions in this file are provided only for backward // compatibility. They should not be used in new scripts. const int GHI_FLAGS = 9; const int GHI_KNOCKBACK_COUNTER = 10; const int GHI_FLASH_COUNTER = 11; const int GHI_PREV_X = 12; const int GHI_PREV_Y = 13; const int GHI_PREV_HP = 14; const int GHF_GOT_HIT = 01000000000b; const int GHF_IN_USE = 10000000000b; const int GHF_INTERNAL = 11000000000b; const int GHI_IN_USE = 15; const int EWF_DEAD = 010000b; const int EWF_IS_GHZH_EWPN = 100000b; const int EWF_INTERNAL = 110000b; const int EWI_XPOS = 5; const int EWI_YPOS = 6; const int EWI_WORK = 7; const int EWI_WORK_2 = 8; const int EWI_MOVEMENT = 9; const int EWI_MOVEMENT_ARG = 10; const int EWI_LIFESPAN = 11; const int EWI_LIFESPAN_ARG = 12; const int EWI_ON_DEATH = 13; const int EWI_ON_DEATH_ARG = 14; const int EWI_FLAGS = 15; void GhostInit(ffc this, npc ghost) { if(ghost->ASpeed==256 && GH_BLANK_TILE>0) ghost->OriginalTile=GH_BLANK_TILE; ghost->Extend=3; ghost->TileWidth=this->TileWidth; ghost->TileHeight=this->TileHeight; ghost->HitWidth=16*this->TileWidth; ghost->HitHeight=16*this->TileHeight; ghost->X=this->X; ghost->Y=this->Y; ghost->CSet=this->CSet; ghost->Misc[GHI_FLASH_COUNTER]=0; ghost->Misc[GHI_PREV_HP]=ghost->HP; ghost->Misc[GHI_IN_USE]=1; } npc GhostInitCreate(ffc this, int enemyID) { npc ghost=Screen->CreateNPC(enemyID); GhostInit(this, ghost); return ghost; } npc GhostInitWait(ffc this, int enemyIndex, bool useEnemyPos) { int combo=this->Data; this->Data=0; npc ghost; for(int i=0; i<4; i++) { if(Screen->NumNPCs()>=enemyIndex) { ghost=Screen->LoadNPC(enemyIndex); ghost->Extend=3; ghost->TileWidth=this->TileWidth; ghost->TileHeight=this->TileHeight; if(ghost->ASpeed==256 && GH_BLANK_TILE>0) ghost->OriginalTile=GH_BLANK_TILE; ghost->Extend=3; ghost->TileWidth=this->TileWidth; ghost->TileHeight=this->TileHeight; ghost->HitWidth=16*this->TileWidth; ghost->HitHeight=16*this->TileHeight; ghost->CSet=this->CSet; ghost->Misc[GHI_FLASH_COUNTER]=0; ghost->Misc[GHI_PREV_HP]=ghost->HP; ghost->Misc[GHI_IN_USE]=1; this->Data=combo; if(useEnemyPos) { this->X=ghost->X; this->Y=ghost->Y; } else { ghost->X=this->X; ghost->Y=this->Y; } return ghost; } Waitframe(); } // Timed out Quit(); } npc GhostInitWait2(ffc this, int enemyID, bool useEnemyPos) { int combo=this->Data; this->Data=0; npc ghost; for(int i=0; i<4; i++) { // Cycle through enemies to find the right one for(int j=1; j<=Screen->NumNPCs(); j++) { ghost=Screen->LoadNPC(j); // Wrong ID? if(ghost->ID!=enemyID) continue; // Already in use? if((ghost->Misc[GHI_IN_USE])!=0) continue; // Found it; initialize if(ghost->ASpeed==256 && GH_BLANK_TILE>0) ghost->OriginalTile=GH_BLANK_TILE; ghost->Extend=3; ghost->TileWidth=this->TileWidth; ghost->TileHeight=this->TileHeight; ghost->HitWidth=16*this->TileWidth; ghost->HitHeight=16*this->TileHeight; ghost->CSet=this->CSet; ghost->Misc[GHI_FLASH_COUNTER]=0; ghost->Misc[GHI_PREV_HP]=ghost->HP; ghost->Misc[GHI_IN_USE]=1; this->Data=combo; if(useEnemyPos) { this->X=ghost->X; this->Y=ghost->Y; } else { ghost->X=this->X; ghost->Y=this->Y; } return ghost; } Waitframe(); } // Timed out Quit(); } npc GhostInitSpawn(ffc this, int enemyID) { npc ghost=SpawnNPC(enemyID); if(ghost->ASpeed==256 && GH_BLANK_TILE>0) ghost->OriginalTile=GH_BLANK_TILE; ghost->Extend=3; ghost->TileWidth=this->TileWidth; ghost->TileHeight=this->TileHeight; ghost->HitWidth=16*this->TileWidth; ghost->HitHeight=16*this->TileHeight; ghost->CSet=this->CSet; ghost->Misc[GHI_FLASH_COUNTER]=0; ghost->Misc[GHI_PREV_HP]=ghost->HP; ghost->Misc[GHI_IN_USE]=1; return ghost; } void GhostInit(ffc this, npc ghost, int flags) { GhostInit(this, ghost); SetFlags(this, ghost, flags); } npc GhostInitCreate(ffc this, int enemyID, int flags) { npc ghost=GhostInitCreate(this, enemyID); SetFlags(this, ghost, flags); return ghost; } npc GhostInitWait(ffc this, int enemyIndex, bool useEnemyPos, int flags) { npc ghost=GhostInitWait(this, enemyIndex, useEnemyPos); SetFlags(this, ghost, flags); return ghost; } npc GhostInitWait2(ffc this, int enemyID, bool useEnemyPos, int flags) { npc ghost=GhostInitWait2(this, enemyID, useEnemyPos); SetFlags(this, ghost, flags); return ghost; } npc GhostInitSpawn(ffc this, int enemyID, int flags) { npc ghost=GhostInitSpawn(this, enemyID); SetFlags(this, ghost, flags); return ghost; } void SetFlags(ffc this, npc ghost, int flags) { ghost->Misc[GHI_FLAGS]=flags|(ghost->Misc[GHI_FLAGS]&GHF_INTERNAL); if((flags&GHF_4WAY)!=0) this->Data=this->Data+ghost->Dir; } void DrawSpawnAnimation(ffc this, npc ghost) { int i; int j; lweapon graphic; int combo=this->Data; this->Data=0; ghost->CollDetection=false; ghost->DrawXOffset=32768; for(i=0; iTileWidth; i++) { for(j=0; jTileHeight; j++) { graphic=Screen->CreateLWeapon(LW_SCRIPT10); graphic->CollDetection=false; graphic->UseSprite(GH_SPAWN_SPRITE); graphic->X=this->X+16*i; graphic->Y=this->Y+16*j; if(graphic->NumFrames==0) graphic->NumFrames=3; if(graphic->ASpeed==0) graphic->ASpeed=4; graphic->DeadState=graphic->NumFrames*graphic->ASpeed; } } Waitframes(graphic->NumFrames*graphic->ASpeed); this->Data=combo; ghost->CollDetection=true; ghost->DrawXOffset=0; } bool GhostWaitframeM(ffc this, npc ghost, float x, float y, float z, bool clearOnDeath, bool quitOnDeath) { // Set direction if((ghost->Misc[GHI_FLAGS]&GHF_SET_DIRECTION)!=0 && (this->X!=x || (this->Y+ghost->Z)!=y)) { // Figure out the base combo, in case GHF_4WAY is set int baseCombo=this->Data-ghost->Dir; if(Abs(this->X-x)>Abs((this->Y+ghost->Z)-y)) { if(this->X>x) ghost->Dir=DIR_LEFT; else ghost->Dir=DIR_RIGHT; } else { if(this->Y+ghost->Y>y) ghost->Dir=DIR_UP; else ghost->Dir=DIR_DOWN; } // Change combo to match if((ghost->Misc[GHI_FLAGS]&GHF_4WAY)!=0) this->Data=baseCombo+ghost->Dir; } // Draw over if high enough if((ghost->Misc[GHI_FLAGS]&GHF_SET_OVERLAY)!=0) { if(z>=GH_DRAW_OVER_THRESHOLD && !this->Flags[FFCF_OVERLAY]) this->Flags[FFCF_OVERLAY]=true; else if(zFlags[FFCF_OVERLAY]) this->Flags[FFCF_OVERLAY]=false; } do { // Stop falling and set position if(ghost->isValid()) { if((ghost->Misc[GHI_FLAGS]&GHF_NO_FALL)!=0) ghost->Jump=0; ghost->X=x; ghost->Y=y; ghost->Z=z; } this->X=Max(-64, Min(256, x)); // The FFC shouldn't go too far offscreen, or it will disappear this->Y=Max(-64, Min(176, y-z)); Waitframe(); } while((__ghzhData[__GHI_GLOBAL_FLAGS]&__GHGF_SUSPEND)!=0); // Dead? if(!ghost->isValid()) { if(clearOnDeath) this->Data=0; if(quitOnDeath) Quit(); return false; } if(ghost->HP<=0) { if(clearOnDeath) { ghost->TileWidth=1; ghost->TileHeight=1; ghost->X=this->X+8*(this->TileWidth-1); ghost->Y=this->Y+8*(this->TileHeight-1); this->Data=0; } if(quitOnDeath) Quit(); return false; } // Hit? CheckHit(this, ghost); if((ghost->Misc[GHI_FLAGS]&GHF_STUN)!=0) CheckStun(this, ghost); return true; } bool GhostWaitframeN(ffc this, npc ghost, bool clearOnDeath, bool quitOnDeath) { GhostWaitframeM(this, ghost, ghost->X, ghost->Y, ghost->Z, clearOnDeath, quitOnDeath); } bool GhostWaitframeF(ffc this, npc ghost, bool clearOnDeath, bool quitOnDeath) { GhostWaitframeM(this, ghost, this->X, this->Y, 0, clearOnDeath, quitOnDeath); } bool GhostWaitframesM(ffc this, npc ghost, float x, float y, float z, bool clearOnDeath, bool quitOnDeath, int numFrames) { for(; numFrames>0; numFrames--) { if(!GhostWaitframeM(this, ghost, x, y, z, clearOnDeath, quitOnDeath)) return false; } return true; } bool GhostWaitframesN(ffc this, npc ghost, bool clearOnDeath, bool quitOnDeath, int numFrames) { for(; numFrames>0; numFrames--) { if(!GhostWaitframeM(this, ghost, ghost->X, ghost->Y, ghost->Z, clearOnDeath, quitOnDeath)) return false; } return true; } bool GhostWaitframesF(ffc this, npc ghost, bool clearOnDeath, bool quitOnDeath, int numFrames) { for(; numFrames>0; numFrames--) { if(!GhostWaitframeM(this, ghost, this->X, this->Y, 0, clearOnDeath, quitOnDeath)) return false; } return true; } void CheckHit(ffc this, npc ghost) { // Just got hit - set flash and knockback counters if(ghost->HPMisc[GHI_PREV_HP]) { ghost->Misc[GHI_FLAGS]|=GHF_GOT_HIT; ghost->Misc[GHI_PREV_HP]=ghost->HP; ghost->Misc[GHI_FLASH_COUNTER]=32; // Set knockback if((ghost->Misc[GHI_FLAGS]&GHF_KNOCKBACK)!=0) { int xDiff=Link->X-ghost->X; int yDiff=Link->Y-ghost->Y; // The direction can't be checked the proper way, so guess based on Link's position // If close, use Link's direction if(Sqrt(xDiff*xDiff+yDiff*yDiff)<32) { if((Link->Dir&10b)==(ghost->Dir&10b)) // Both horizontal or vertical ghost->Misc[GHI_KNOCKBACK_COUNTER]=Link->Dir<<5|16; } // If far, use relative positions else { if(Abs(xDiff)Dir==DIR_UP || ghost->Dir==DIR_DOWN) { if(yDiff>0) ghost->Misc[GHI_KNOCKBACK_COUNTER]=(DIR_UP<<5)|16; else ghost->Misc[GHI_KNOCKBACK_COUNTER]=(DIR_DOWN<<5)|16; } } else // Left or right { if(ghost->Dir==DIR_LEFT || ghost->Dir==DIR_RIGHT) { if(xDiff>0) ghost->Misc[GHI_KNOCKBACK_COUNTER]=(DIR_LEFT<<5)|16; else ghost->Misc[GHI_KNOCKBACK_COUNTER]=(DIR_RIGHT<<5)|16; } } } } } // Not hit; unset flag else if((ghost->Misc[GHI_FLAGS]&GHF_GOT_HIT)!=0) ghost->Misc[GHI_FLAGS]&=(~GHF_GOT_HIT); // Flash if(ghost->Misc[GHI_FLASH_COUNTER]>1) { this->CSet=9-(ghost->Misc[GHI_FLASH_COUNTER]&3); ghost->Misc[GHI_FLASH_COUNTER]--; } // Done flashing else if(ghost->Misc[GHI_FLASH_COUNTER]==1) { ghost->Misc[GHI_FLASH_COUNTER]=0; this->CSet=ghost->CSet; } // Handle knockback if(ghost->Misc[GHI_KNOCKBACK_COUNTER]!=0) { int dir=ghost->Misc[GHI_KNOCKBACK_COUNTER]>>5; int counter=ghost->Misc[GHI_KNOCKBACK_COUNTER]&31; // Get knocked back if(CanMove(this, ghost, dir, 4, 0)) { counter--; if(counter>0) ghost->Misc[GHI_KNOCKBACK_COUNTER]=(dir<<5)|counter; else ghost->Misc[GHI_KNOCKBACK_COUNTER]=0; if(dir==DIR_UP) { this->Y-=4; ghost->Y-=4; } else if(dir==DIR_DOWN) { this->Y+=4; ghost->Y+=4; } else if(dir==DIR_LEFT) { this->X-=4; ghost->X-=4; } else // Right { this->X+=4; ghost->X+=4; } } // Can't go any farther; end knockback else { ghost->Misc[GHI_KNOCKBACK_COUNTER]=0; if(dir==DIR_UP) { ghost->Y&=~7; this->Y=ghost->Y-ghost->Z; } else if(dir==DIR_DOWN) { if(ghost->Y%8!=0) { ghost->Y=(ghost->Y&~7)+8; this->Y=ghost->Y-ghost->Z; } } else if(dir==DIR_LEFT) { ghost->X&=~7; this->X=ghost->X; } else // Right { if(ghost->X%8!=0) { ghost->X=(ghost->X&~7)+8; this->X=ghost->X; } } } } } bool CheckStun(ffc this, npc ghost) { if(ghost->Stun>0) { // Stop all movement float vx=this->Vx; float vy=this->Vy; float ax=this->Ax; float ay=this->Ay; this->Vx=0; this->Vy=0; this->Ax=0; this->Ay=0; // Do nothing except get hit until recovered while(ghost->Stun>0) { Waitframe(); if(!ghost->isValid() || ghost->HP<=0) return false; CheckHit(this, ghost); // If the ghost shouldn't get knocked back, make sure it doesn't if((ghost->Misc[GHI_FLAGS]&GHF_KNOCKBACK)==0) { ghost->X=this->X; ghost->Y=this->Y+ghost->Z; } } // Restore movement this->Vx=vx; this->Vy=vy; this->Ax=ax; this->Ay=ay; } return true; } bool CanMove(ffc this, npc ghost, int dir, float step, int imprecision) { int y=this->Y+ghost->Z; if(dir==DIR_UP) { // Screen edges if((Screen->Flags[SF_ROOMTYPE]&010b)!=0 && y-step<32) return false; else if(y-step<0) return false; int y2=y-step; int flags=ghost->Misc[GHI_FLAGS]; // Check every 8 pixels for solid, pit, or water for(int i=imprecision; iTileWidth*16-imprecision; i+=8) { if(!CanMovePixel(this->X+i, y2, flags)) return false; } // One last pixel... if(!CanMovePixel(this->X+16*this->TileWidth-1-imprecision, y2, flags)) return false; return true; } else if(dir==DIR_DOWN) { if((Screen->Flags[SF_ROOMTYPE]&010b)!=0 && y+16*this->TileHeight+step>=144) return false; else if(y+16*this->TileHeight+step>=176) return false; int y2=y+16*this->TileHeight+step; int flags=ghost->Misc[GHI_FLAGS]; for(int i=imprecision; iTileWidth*16-imprecision; i+=8) { if(!CanMovePixel(this->X+i, y2, flags)) return false; } if(!CanMovePixel(this->X+16*this->TileWidth-1-imprecision, y2, flags)) return false; return true; } else if(dir==DIR_LEFT) { if((Screen->Flags[SF_ROOMTYPE]&010b)!=0 && this->X-step<32) return false; else if(this->X-step<0) return false; int x2=this->X-step; int flags=ghost->Misc[GHI_FLAGS]; for(int i=imprecision; iTileHeight*16-imprecision; i+=8) { if(!CanMovePixel(x2, y+i, flags)) return false; } if(!CanMovePixel(x2, y+16*this->TileHeight-1-imprecision, flags)) return false; return true; } else if(dir==DIR_RIGHT) { if((Screen->Flags[SF_ROOMTYPE]&010b)!=0 && this->X+16*this->TileWidth+step>=224) return false; else if(this->X+16*this->TileWidth+step>=256) return false; int x2=this->X+16*this->TileWidth+step; int flags=ghost->Misc[GHI_FLAGS]; for(int i=imprecision; iTileHeight*16-imprecision; i+=8) { if(!CanMovePixel(x2, y+i, flags)) return false; } if(!CanMovePixel(x2, y+16*this->TileHeight-1-imprecision, flags)) return false; return true; } else // Invalid direction return false; } bool CanMovePixel(int x, int y, int flags) { int combo=ComboAt(x, y); // "No enemy" flags and combos if(Screen->ComboT[combo]==CT_NOENEMY) return false; if(Screen->ComboF[combo]==CF_NOENEMY) return false; if(Screen->ComboI[combo]==CF_NOENEMY) return false; // Water and pit walkability override solidity checking if(IsWater(combo)) { if((flags&GHF_IGNORE_WATER)==0) return false; } else if(IsPit(combo)) { if((flags&GHF_IGNORE_PITS)==0) return false; } else if(Screen->isSolid(x, y)) { if((flags&GHF_IGNORE_SOLIDITY)==0) return false; } return true; } void Move(ffc this, npc ghost, float xStep, float yStep, int imprecision) { if((ghost->Misc[GHI_FLAGS]&GHF_SET_DIRECTION)!=0) { // Figure out the base combo, in case GHF_4WAY is set int baseCombo=this->Data-ghost->Dir; if(Abs(yStep)>Abs(xStep)) { if(yStep<0) ghost->Dir=DIR_UP; else ghost->Dir=DIR_DOWN; } else { if(xStep<0) ghost->Dir=DIR_LEFT; else ghost->Dir=DIR_RIGHT; } if((ghost->Misc[GHI_FLAGS]&GHF_4WAY)!=0) this->Data=baseCombo+ghost->Dir; } if(yStep<0) { if(CanMove(this, ghost, DIR_UP, -yStep, imprecision)) { this->Y+=yStep; ghost->Y+=yStep; } } else if(yStep>0) { if(CanMove(this, ghost, DIR_DOWN, yStep, imprecision)) { this->Y+=yStep; ghost->Y+=yStep; } } if(xStep<0) { if(CanMove(this, ghost, DIR_LEFT, -xStep, imprecision)) { this->X+=xStep; ghost->X+=xStep; } } else if(xStep>0) { if(CanMove(this, ghost, DIR_RIGHT, xStep, imprecision)) { this->X+=xStep; ghost->X+=xStep; } } } void MoveAtAngle(ffc this, npc ghost, float angle, float step, int imprecision) { Move(this, ghost, VectorX(step, angle), VectorY(step, angle), imprecision); } void MoveTowardLink(ffc this, npc ghost, float step, int imprecision) { float angle=Angle(this->X, this->Y, Link->X, Link->Y); Move(this, ghost, VectorX(step, angle), VectorY(step, angle), imprecision); } void Transform(ffc this, npc ghost, int combo, int cset, int tileWidth, int tileHeight) { int diff; if(combo>=0) this->Data=combo; if(cset>=0) { this->CSet=cset; ghost->CSet=cset; } if(tileWidth>0) { diff=8*(this->TileWidth-tileWidth); this->X=this->X+diff; ghost->TileWidth=this->TileWidth; this->TileWidth=tileWidth; ghost->X=ghost->X+diff; } if(tileHeight>0) { diff=8*(this->TileHeight-tileHeight); this->Y=this->Y+diff; ghost->TileHeight=this->TileHeight; this->TileHeight=tileHeight; ghost->Y=ghost->Y+diff; } SetOffsets(this, ghost, 0, 0, 0, 0); } void SwapGhost(npc oldGhost, npc newGhost, bool copyHP) { newGhost->X=oldGhost->X; newGhost->Y=oldGhost->Y; newGhost->Z=oldGhost->Z; newGhost->Jump=oldGhost->Jump; newGhost->Dir=oldGhost->Dir; newGhost->Extend=oldGhost->Extend; newGhost->TileWidth=oldGhost->TileWidth; newGhost->TileHeight=oldGhost->TileHeight; newGhost->HitWidth=oldGhost->HitWidth; newGhost->HitHeight=oldGhost->HitHeight; newGhost->HitXOffset=oldGhost->HitXOffset; newGhost->HitYOffset=oldGhost->HitYOffset; newGhost->CSet=oldGhost->CSet; newGhost->CollDetection=oldGhost->CollDetection; for(int i=0; i<16; i++) newGhost->Misc[i]=oldGhost->Misc[i]; if(copyHP) newGhost->HP=oldGhost->HP; // Move the old ghost out of the way oldGhost->CollDetection=false; oldGhost->HitXOffset=32768; } void ReplaceGhost(npc oldGhost, npc newGhost, bool copyHP) { SwapGhost(oldGhost, newGhost, copyHP); oldGhost->X=384; oldGhost->HP=-1000; } void SetOffsets(ffc this, npc ghost, float top, float bottom, float left, float right) { if(top>0 && top<1) top=Round(top*this->TileHeight*16); if(bottom>0 && bottom<1) bottom=Round(bottom*this->TileHeight*16); if(left>0 && left<1) left=Round(left*this->TileWidth*16); if(right>0 && right<1) right=Round(right*this->TileWidth*16); ghost->HitXOffset=left; ghost->HitYOffset=top; ghost->HitWidth=16*this->TileWidth-(left+right); ghost->HitHeight=16*this->TileHeight-(top+bottom); } void Set4WayCombo(ffc this, npc ghost, int newCombo) { this->Data=newCombo+ghost->Dir; } void SetHP(ffc this, npc ghost, int newHP) { ghost->HP=newHP; ghost->Misc[GHI_PREV_HP]=newHP; } void SetAllDefenses(npc ghost, int defType) { for(int i=0; i<17; i++) ghost->Defense[i]=defType; } void SetCSet(ffc this, npc ghost, int newCSet) { // Don't change the FFC's color if it's not already the normal one if(this->CSet==ghost->CSet) { ghost->CSet=newCSet; this->CSet=ghost->CSet; } else ghost->CSet=newCSet; } void RevertCSet(ffc this, npc ghost) { this->CSet=ghost->CSet; } bool GotHit(npc ghost) { return (ghost->Misc[GHI_FLAGS]&GHF_GOT_HIT)!=0; } eweapon FireEWeapon(int weaponID, int x, int y, float angle, int step, int damage, bool blockable, int sprite, bool rotate, int sound) { int flags=0; if(!blockable) flags|=EWF_UNBLOCKABLE; if(rotate) flags|=EWF_ROTATE; return FireEWeapon(weaponID, x, y, angle, step, damage, sprite, sound, flags); } eweapon FireAimedEWeapon(int weaponID, int x, int y, float angle, int step, int damage, bool blockable, int sprite, bool rotate, int sound) { int flags=0; if(!blockable) flags|=EWF_UNBLOCKABLE; if(rotate) flags|=EWF_ROTATE; return FireEWeapon(weaponID, x, y, ArcTan(Link->X-x, Link->Y-y)+angle, step, damage, sprite, sound, flags); } eweapon FireNonAngularEWeapon(int weaponID, int x, int y, int direction, int step, int damage, int sprite, bool rotate, int sound) { int flags=0; if(rotate) flags|=EWF_ROTATE; return FireNonAngularEWeapon(weaponID, x, y, direction, step, damage, sprite, sound, flags); } void SetFlags(ffc this, int flags) { SetFlags(this, Screen->LoadNPC(1), flags); } npc GhostInitWait2(ffc this, int enemyID, int which, bool useEnemyPos) { return GhostInitWait2(this, enemyID, useEnemyPos); } npc GhostInitWait2(ffc this, int enemyID, int which, bool useEnemyPos, int flags) { return GhostInitWait2(this, enemyID, useEnemyPos, flags); }