// == Combos ==
// 5 total
// Head (2x2)
// Left hand (1x1) idle
// Followed by left hand punch
// Right hand (1x1) idle
// Followed by right hand punch
// == Enemies ==
// Head
// Place on screen
// Uses gohdan_head script (can put @gohdan_head at end of name)
// Type: Other
// Step Speed and Halt Rate are used
// Misc Attributes
// 1. Min time between attacks (in frames, default 60)
// 2. Max time between attacks (in frames, default 180)
// 3. Energy ball speed (default 400)
// 4. Energy ball count (default 4)
// 5. Energy ball time between shots (default 20)
// 9. Left hand enemy ID
// 10. Right hand enemy ID
// 11. Head combo
// Left/Right Hands (Separate enemies; same attributes)
// Spawned by script
// Uses gohdan_hand script (can put @gohdan_hand at end of name)
// Type: Other
// Step Speed is used
// Misc Attributes
// 1. 0 for left hand; 1 for right
// 2. Head enemy ID
// 3. Min time between attacks (in frames, default 40)
// 4. Max time between attacks (in frames, default 120)
// 5. % chance that attack will be a beam (1-99, default 30)
// 6. Beam speed (default 300)
// 11. Idle combo
import "std.zh"
import "string.zh"
import "ffcscript.zh"
import "ghost.zh"
import "stdExtra.zh"
const int SFX_ENERGYBALL = 32;
const int SFX_GOHDANBEAM = 2;
const int WPS_ENERGYBALL = 83; // Sprite for head's energy ball attack
const int WPS_BEAM = 39; // Sprite for hands' beam attack
ffc script gohdan_head{
void run(int enemyID){
npc ghost = Ghost_InitAutoGhost(this, enemyID);
Ghost_SetFlag(GHF_4WAY);
Ghost_Transform(this, ghost, -1, -1, 2, 2);
Ghost_X = SCREEN_WIDTH/2 - 16;
Ghost_Y = 36;
int attackFrequencyMin = Ghost_GetAttribute(ghost, 0, 120);
int attackFrequencyMax = Ghost_GetAttribute(ghost, 0, 280);
int shotSpeed = Ghost_GetAttribute(ghost, 0, 400);
int shotCount = Ghost_GetAttribute(ghost, 0, 4);
int shotFreq = Ghost_GetAttribute(ghost, 0, 20);
int leftID = Ghost_GetAttribute(ghost, 8, 0);
int rightID = Ghost_GetAttribute(ghost, 9, 0);
int attackTimer = attackFrequencyMax * 2;
int turnTimer = ghost->Rate * 10;
bool vulnerable = false;
int defenses[18];
Ghost_StoreDefenses(ghost, defenses);
Ghost_SetAllDefenses(ghost, NPCDT_BLOCK);
npc left = CreateNPCAt(leftID, Ghost_X-32, Ghost_Y+8);
npc right = CreateNPCAt(rightID, Ghost_X+64, Ghost_Y+8);
while(Ghost_HP > 0){
if(attackTimer <= 0){
attackTimer = Rand(attackFrequencyMin, attackFrequencyMax);
for(int i = 0; i < shotCount; i++){
FireAimedEWeapon(EW_FIREBALL, Ghost_X + 8, Ghost_Y + 16, 0, shotSpeed, ghost->WeaponDamage, WPS_ENERGYBALL, SFX_ENERGYBALL, 0);
Ghost_Waitframes(this, ghost, false, false, shotFreq);
}
}
attackTimer--;
// Make head vulnerable when both hands dead
if(!vulnerable && !left->isValid() && !right->isValid()){
vulnerable = true;
Ghost_SetDefenses(ghost, defenses);
}
// Move
turnTimer = Ghost_HaltingWalk4(turnTimer, ghost->Step, ghost->Rate*10, ghost->Homing, ghost->Hunger, ghost->Haltrate, 40);
// Only move left/right
if(Ghost_Dir == DIR_UP || Ghost_Dir == DIR_DOWN){
if(Rand(2))
Ghost_Dir = DIR_LEFT;
else
Ghost_Dir = DIR_RIGHT;
}
Ghost_Waitframe(this, ghost, false, false);
}
Ghost_DeathAnimation(this, ghost, 1);
}
}
ffc script gohdan_hand{
void run(int enemyID){
npc ghost = Ghost_InitAutoGhost(this, enemyID);
Ghost_SetFlag(GHF_8WAY);
Ghost_SetFlag(GHF_REDUCED_KNOCKBACK);
Ghost_SetFlag(GHF_IGNORE_ALL_TERRAIN);
Ghost_SetFlag(GHF_MOVE_OFFSCREEN);
int side = Ghost_GetAttribute(ghost, 0, 0);
int headID = Ghost_GetAttribute(ghost, 1, 0);
int attackFrequencyMin = Ghost_GetAttribute(ghost, 2, 70);
int attackFrequencyMax = Ghost_GetAttribute(ghost, 3, 180);
int beamChance = Ghost_GetAttribute(ghost, 4, 50);
int beamSpeed = Ghost_GetAttribute(ghost, 5, 300);
int normalCombo = Ghost_Data;
int attackCombo = Ghost_Data+1;
int attackTimer = Rand(attackFrequencyMin, attackFrequencyMax) * 1.5;
float step = ghost->Step / 100;
// Desired offset from boss
int xOffset = -32;
int yOffset = 8;
if(side == 1){
xOffset = 64;
}
// Find the head (assume it exists)
npc head;
for(int i = 1; i <= Screen->NumNPCs(); i++){
npc enem = Screen->LoadNPC(i);
if(enem->ID == headID){
head = enem;
break;
}
}
while(Ghost_HP > 0){
if(attackTimer <= 0){
attackTimer = Rand(attackFrequencyMin, attackFrequencyMax);
if(Rand(100) <= beamChance){
gohdan_hand_beam(this, ghost, step, beamSpeed, normalCombo, attackCombo);
}
else{
gohdan_hand_punch(this, ghost, step, normalCombo, attackCombo);
}
}
attackTimer--;
// Move next to boss
float angle = Angle(Ghost_X + 8, Ghost_Y + 8, head->X + xOffset, head->Y + yOffset);
Ghost_MoveXY(VectorX(step, angle), VectorY(step, angle), 8);
Ghost_Waitframe(this, ghost, false, false);
}
Ghost_DeathAnimation(this, ghost, 1);
}
void gohdan_hand_beam(ffc this, npc ghost, float step, int beamSpeed, int normalCombo, int attackCombo){
// Move in front of Link (far)
int destX = Link->X+8;
int destY = Link->Y - 34;
while(Distance(Ghost_X+8, Ghost_Y+8, destX, destY) > 8){
float angle = Angle(Ghost_X + 8, Ghost_Y + 8, destX, destY);
Ghost_MoveXY(VectorX(step*4, angle), VectorY(step*4, angle), 8);
Ghost_Waitframe(this, ghost, false, false);
if(Ghost_HP <= 0)
return;
}
// Change combo and shoot
Ghost_Data = attackCombo;
Ghost_Waitframes(this, ghost, false, false, 10);
if(Ghost_HP <= 0)
return;
FireNonAngularEWeapon(EW_SCRIPT1, Ghost_X, Ghost_Y, DIR_DOWN, beamSpeed, ghost->WeaponDamage, WPS_BEAM, SFX_GOHDANBEAM, 0);
Ghost_Waitframes(this, ghost, false, false, 10);
Ghost_Data = normalCombo;
}
void gohdan_hand_punch(ffc this, npc ghost, float step, int normalCombo, int attackCombo){
// Move in front of Link (close)
int destX = Link->X + 8;
int destY = Link->Y - 16;
while(Distance(Ghost_X+8, Ghost_Y+8, destX, destY) > 8){
float angle = Angle(Ghost_X + 8, Ghost_Y + 8, destX, destY);
Ghost_MoveXY(VectorX(step*4, angle), VectorY(step*4, angle), 8);
Ghost_Waitframe(this, ghost, false, false);
if(Ghost_HP <= 0)
return;
}
// Pull back before punching
for(int i = 0; i < 20; i++){
Ghost_Move(DIR_UP, step, 8);
Ghost_Waitframe(this, ghost, false, false);
if(Ghost_HP <= 0)
return;
}
Ghost_Waitframes(this, ghost, false, false, 15);
// PUNCH for 20 frames
Ghost_Data = attackCombo;
for(int i = 0; i < 20; i++){
Ghost_Move(DIR_DOWN, step * 5, 8);
Ghost_Waitframe(this, ghost, false, false);
if(Ghost_HP <= 0)
return;
}
Ghost_Data = normalCombo;
}
}