Copy to Clipboard Test

Armos Knights Code

// 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();
		}
	}
}