// import "std.zh" // import "string.zh" // import "ghost.zh" // Time before coming to life const int AK_INITIAL_WAIT_TIME = 180; const int AK_RUMBLE_TIME = 60; // First phase movement const int AK_PART_1_WAIT_TIME = 150; const int AK_PART_1_TOTAL_CIRCLE_TIME = 510; const int AK_PART_1_REDUCE_RADIUS_TIME = 300; const int AK_PART_1_EXPAND_RADIUS_TIME = 390; const int AK_PART_1_STEP = 1; const int AK_PART_1_ROTATE_SPEED = 1; // Second phase movement const int AK_PART_2_WAIT_TIME = 30; const int AK_PART_2_STEP = 3.5; const int AK_PART_2_RISE_DIVISOR = 4; const int AK_PART_2_FALL_STEP = 6; // ffc->Misc[] indices const int AK_IDX_LAST_ONE = 0; const int AK_IDX_PLAY_SOUND = 1; const int AK_IDX_DYING = 2; // npc->Attributes[] indices const int AK_ATTR_NUM_KNIGHTS = 0; const int AK_ATTR_SECOND_CSET = 1; const int AK_ATTR_SFX_STOMP = 2; const int AK_ATTR_SFX_JUMP = 3; const int AK_ATTR_SFX_RUMBLE = 4; const int AK_ATTR_EXPLODE = 5; ffc script ArmosKnights { void run(int enemyID, int knightID, int numKnights, int rowX) { // The placed enemy is a dummy that acts as a supervisor; // it'll set up the rest of the FFCs and give them nonzero knightIDs if(knightID==0) SupervisorRun(this, enemyID); else KnightRun(this, enemyID, knightID-1, numKnights, rowX); } // Supervisor mode ------------------------ void SupervisorRun(ffc this, int enemyID) { npc ghost; int combo; int cset; int numKnights; int numFFCs; int ffcID; int numAlive; ffc knightFFC[6]; int startX[6]; // Starting positions int startY[6]; int rowX[6]; // Initial positions when jumping downward in a row bool alive[6]; // Whether each knight is still alive bool playSound; int i; ghost=Ghost_InitAutoGhost(this, enemyID); ghost->CollDetection=false; ghost->SFX=0; combo=Ghost_Data; cset=Ghost_CSet; this->Data=0; numKnights=Ghost_GetAttribute(ghost, AK_ATTR_NUM_KNIGHTS, 1, 1, 6); // Get FFCs for knights numFFCs=0; ffcID=0; while(numFFCs<numKnights) { ffcID=FindUnusedFFC(ffcID); if(ffcID==0) break; knightFFC[numFFCs]=Screen->LoadFFC(ffcID); numFFCs++; } // Reduce the number of knights if too few FFCs were available numKnights=Min(numKnights, numFFCs); // Find where the knights go based on how many there are GetStartingPositions(numKnights, startX, startY); GetRowPositions(numKnights, rowX); // Set up the FFCs; no enemies or scripts yet for(i=0; i<numKnights; i++) { knightFFC[i]->Data=combo; knightFFC[i]->CSet=cset; knightFFC[i]->TileWidth=2; knightFFC[i]->TileHeight=2; knightFFC[i]->X=startX[i]; knightFFC[i]->Y=startY[i]; knightFFC[i]->Flags[FFCF_OVERLAY]=true; } // Let the FFCs sit there a moment, then rumble Waitframes(AK_INITIAL_WAIT_TIME); Game->PlaySound(ghost->Attributes[AK_ATTR_SFX_RUMBLE]); for(i=0; i<AK_RUMBLE_TIME; i++) { for(int j=0; j<numKnights; j++) knightFFC[j]->X=startX[j]+Rand(3)-1; // Starting position +/- 1 Waitframe(); } // Set up the knights' scripts numAlive=numKnights; for(i=0; i<numKnights; i++) { knightFFC[i]->Script=this->Script; knightFFC[i]->InitD[0]=enemyID; knightFFC[i]->InitD[1]=i+1; knightFFC[i]->InitD[2]=numKnights; knightFFC[i]->InitD[3]=rowX[i]; alive[i]=true; } // The knights will move on their own; just watch to see when they die while(numAlive>1) { Waitframe(); for(i=0; i<numKnights; i++) { if(!alive[i]) continue; // If a knight is dying, don't count it as dead unless there's at least one other; // if they all died at once here, the second phase of the fight wouldn't happen if(knightFFC[i]->Misc[AK_IDX_DYING]!=0 && numAlive>1) { alive[i]=false; numAlive--; } // Check if a landing sound should be played; // It's the supervisor that does this so the sound only plays once per jump else if(knightFFC[i]->Misc[AK_IDX_PLAY_SOUND]==1) playSound=true; } if(playSound) { Game->PlaySound(ghost->Attributes[AK_ATTR_SFX_STOMP]); playSound=false; } } // Only one is left; tell it to switch behavior for(i=0; i<numKnights; i++) { if(alive[i]) { knightFFC[i]->Misc[AK_IDX_LAST_ONE]=1; break; } } // Then just wait for it to die knightFFC[i]->Misc[AK_IDX_DYING]=0; while(knightFFC[i]->Misc[AK_IDX_DYING]==0) Waitframe(); // And kill the supervisor ghost->X=1024; ghost->Y=1024; ghost->ItemSet=0; this->Data=0; Quit(); } // Determine the initial position of each knight based on the total number void GetStartingPositions(int numKnights, int startX, int startY) { if(numKnights==1) { startX[0]=112; startY[0]=72; } else if(numKnights==2) { startX[0]=64; startY[0]=72; startX[1]=160; startY[1]=72; } else if(numKnights==3) { startX[0]=112; startY[0]=48; startX[1]=160; startY[1]=96; startX[2]=64; startY[2]=96; } else if(numKnights==4) { startX[0]=64; startY[0]=48; startX[1]=160; startY[1]=48; startX[2]=160; startY[2]=96; startX[3]=64; startY[3]=96; } else if(numKnights==5) { startX[0]=112; startY[0]=48; startX[1]=160; startY[1]=48; startX[2]=140; startY[2]=96; startX[3]=80; startY[3]=96; startX[4]=64; startY[4]=48; } else // 6 { startX[0]=112; startY[0]=48; startX[1]=160; startY[1]=48; startX[2]=160; startY[2]=96; startX[3]=112; startY[3]=96; startX[4]=64; startY[4]=96; startX[5]=64; startY[5]=48; } } // Determine each knight's X position when jumping in a row void GetRowPositions(int numKnights, int rowX) { // If there's only one, this won't be used, anyway if(numKnights==2) { rowX[0]=64; rowX[1]=160; } else if(numKnights==3) { rowX[0]=56; rowX[1]=112; rowX[2]=168; } else if(numKnights==4) { rowX[0]=40; rowX[1]=88; rowX[2]=136; rowX[3]=184; } else if(numKnights==5) { rowX[0]=32; rowX[1]=72; rowX[2]=112; rowX[3]=152; rowX[4]=192; } else // 6 { rowX[0]=32; rowX[1]=64; rowX[2]=96; rowX[3]=128; rowX[4]=160; rowX[5]=192; } } // Knight mode ------------------------ void KnightRun(ffc this, int enemyID, int position, int numKnights, int rowX) { npc ghost; int maxHP; int cset2; float radius; float targetAngle; float angleStep; float targetX; float targetY; bool endPart1; int i; ghost=Ghost_InitCreate(this, enemyID); Ghost_SetFlag(GHF_FAKE_Z); Ghost_SetFlag(GHF_KNOCKBACK_4WAY); Ghost_SetFlag(GHF_REDUCED_KNOCKBACK); maxHP=Ghost_HP; cset2=ghost->Attributes[AK_ATTR_SECOND_CSET]; // First stage - other knights still alive endPart1=false; angleStep=AK_PART_1_ROTATE_SPEED; radius=40; while(true) { // Move into position and hold it briefly targetAngle=270+(position/numKnights)*360; targetX=112+radius*Cos(targetAngle); targetY=72+radius*Sin(targetAngle); for(i=0; i<AK_PART_1_WAIT_TIME && !endPart1; i++) endPart1=AKPart1Waitframe(this, ghost, targetX, targetY); if(endPart1) break; // Jump in a circle for a while for(i=0; i<AK_PART_1_TOTAL_CIRCLE_TIME && !endPart1; i++) { // Change radius at certain points if(i==AK_PART_1_REDUCE_RADIUS_TIME) radius=24; else if(i==AK_PART_1_EXPAND_RADIUS_TIME) radius=40; // Rotate until the radius has been decreased and restored if(i<=AK_PART_1_EXPAND_RADIUS_TIME) { targetAngle+=angleStep; targetX=112+radius*Cos(targetAngle); targetY=72+radius*Sin(targetAngle); } endPart1=AKPart1Waitframe(this, ghost, targetX, targetY); } if(endPart1) break; // Line up and jump down the screen targetX=rowX; targetY=16; for(i=0; i<AK_PART_1_WAIT_TIME && !endPart1; i++) endPart1=AKPart1Waitframe(this, ghost, targetX, targetY); if(endPart1) break; targetY=112; // Moving 112-16=96 pixels, so wait exactly long enough for that for(i=96/AK_PART_1_STEP; i>0 && !endPart1; i--) endPart1=AKPart1Waitframe(this, ghost, targetX, targetY); if(endPart1) break; // Rotate the opposite direction next time angleStep*=-1; } // Part 1's ended, so this is the last knight; change color, restore HP, change behavior Ghost_CSet=cset2; Ghost_HP=maxHP; // Finish falling... while(Ghost_Z>0) AKPart2Waitframe(this, ghost, Ghost_X, Ghost_Y); // Change Z handling... Ghost_SetFlag(GHF_NO_FALL); Ghost_UnsetFlag(GHF_FAKE_Z); while(true) { // Move to directly above Link targetX=Link->X-8; targetY=Link->Y-16; Game->PlaySound(ghost->Attributes[AK_ATTR_SFX_JUMP]); while(!(Ghost_X==targetX && Ghost_Y==targetY)) AKPart2Waitframe(this, ghost, targetX, targetY); // Hold it for a moment for(i=0; i<AK_PART_2_WAIT_TIME; i++) AKPart2Waitframe(this, ghost, targetX, targetY); // Fall while(Ghost_Z>0) { Ghost_Z-=AK_PART_2_FALL_STEP; AKPart2Waitframe(this, ghost, targetX, targetY); } Game->PlaySound(ghost->Attributes[AK_ATTR_SFX_STOMP]); // And wait for a moment before repeating for(i=0; i<AK_PART_2_WAIT_TIME; i++) AKPart2Waitframe(this, ghost, targetX, targetY); } } // Waitframe used in the first phase of the fight. Returns true if this // is the last knight and it's time to start the next phase. bool AKPart1Waitframe(ffc this, npc ghost, float targetX, float targetY) { // Move toward where the knight's supposed to be if(Distance(Ghost_X, Ghost_Y, targetX, targetY)<AK_PART_1_STEP) { Ghost_X=targetX; Ghost_Y=targetY; } else { float angle=ArcTan(targetX-Ghost_X, targetY-Ghost_Y)*57.2958; Ghost_X+=AK_PART_1_STEP*Cos(angle); Ghost_Y+=AK_PART_1_STEP*Sin(angle); } // If on the ground, jump and tell the supervisor to play the bounce sound if(Ghost_Z==0) { Ghost_Jump=2; this->Misc[AK_IDX_PLAY_SOUND]=1; } else if(this->Misc[AK_IDX_PLAY_SOUND]==1) this->Misc[AK_IDX_PLAY_SOUND]=0; // Don't automatically clear and quit on death; if the last two died // at the same time, the second phase of the battle wouldn't happen if(!Ghost_Waitframe(this, ghost, false, false)) { // Wait a frame to let the supervisor check this->Misc[AK_IDX_DYING]=1; Ghost_HP=1; Ghost_Waitframe(this, ghost, true, true); // If this isn't the last knight, it can die now if(this->Misc[AK_IDX_LAST_ONE]==0) { Ghost_HP=0; Ghost_Waitframe(this, ghost, true, true); } else return true; } return this->Misc[AK_IDX_LAST_ONE]!=0; } // Waitframe used in the second phase of the fight. void AKPart2Waitframe(ffc this, npc ghost, float targetX, float targetY) { if(Ghost_X!=targetX || Ghost_Y!=targetY) { float dist=Distance(Ghost_X, Ghost_Y, targetX, targetY); if(dist<AK_PART_2_STEP) { Ghost_X=targetX; Ghost_Y=targetY; Ghost_Z+=Sqrt(dist)/AK_PART_2_RISE_DIVISOR; } else { float angle=ArcTan(targetX-Ghost_X, targetY-Ghost_Y)*57.2958; Ghost_X+=AK_PART_2_STEP*Cos(angle); Ghost_Y+=AK_PART_2_STEP*Sin(angle); Ghost_Z+=Sqrt(dist)/AK_PART_2_RISE_DIVISOR; } } if(!Ghost_Waitframe(this, ghost, ghost->Attributes[AK_ATTR_EXPLODE]==0, ghost->Attributes[AK_ATTR_EXPLODE]==0)) { this->Misc[AK_IDX_DYING]=1; Ghost_DeathAnimation(this, ghost, ghost->Attributes[AK_ATTR_EXPLODE]); Quit(); } } }