C-Dawg
08-31-2007, 10:48 PM
The enemy editor is great, but it doesn't give you any control over the enemy's AI or movement patterns. And you can't make then 2x2.
Well, now you can. Here's some scripts that will let you do that.
These are three scripts, all examples of a very general-purpose custom enemy script I've been using. The first enemy crawls along the ground, and hops up small obstacles. The second enemy should be familiar to people who played Adventure of Link - it's those hoppy things in the woods around Error's cabin. The third is an enemy that moves by bouncing off of the walls like the anti-fairies in LttP.
// =================================================
// crawlie Custom Enemy - This enemy crawls along the ground towards
// the player and will hop over obstacles. The ffc treats the enemy
// as four tiles large (with the ffc at the very center).
//
// INPUT:
// D0 = The number of this FFC
// D1 = hit points of this FFC, ***PLUS 100***
// There is a very good reason for the plus 100, dont forget it.
// D2 = number of the first of 4 FFCs making up this enemy.
// D4 -D4+3
// For a smaller enemy, just leave some of these FFCs blank.
// Ensure all of these FFCs are LINKED to the FFC running this script.
// The script expects each FFC to start out facing left. It will use
// the original combo of each FFC + 1 for facing right graphics.
// All higher variables
// D3 = Speed at which the ffc walks towards the player
// D4 = Height of hops
// ==================================================
ffc script crawlie_CE{
void run (int this_ffc_number, int hit_points, int body_ffc_number, int walk_speed, int hop_height){
// ---------------------------
// GENERAL ENEMY SETUP (same for all)
// ---------------------------
ffc this_ffc = Screen->LoadFFC(this_ffc_number);
npc ghosted_enemy = Screen->CreateNPC(85);
ghosted_enemy->HP = hit_points;
ffc body1 = Screen->LoadFFC(body_ffc_number);
ffc body2 = Screen->LoadFFC(body_ffc_number+1);
ffc body3 = Screen->LoadFFC(body_ffc_number+2);
ffc body4 = Screen->LoadFFC(body_ffc_number+3);
int original_CSet = body1->CSet;
int prev_health = ghosted_enemy->HP;
// The health of the ghosted enemy last frame
int invuln_counter = 0; // Enemy is invulnerable briefly after being damaged.
int state = 0; // State 0 = moving normally
// State 1 = reacting to damage
// State 2 = enemy is dead
// --------------------------
// SPECIFIC ENEMY SETUP (varies)
// --------------------------
int original_combo1 = body1->Data;
int original_combo2 = body2->Data;
int original_combo3 = body3->Data;
int original_combo4 = body4->Data;
while(true){
// --------------------------
// ENEMY DAMAGE CONTROL (same for all)
// --------------------------
if (invuln_counter <= 0){
ghosted_enemy->X = this_ffc->X;
ghosted_enemy->Y = this_ffc->Y;
body1->CSet = original_CSet;
body2->CSet = original_CSet;
body3->CSet = original_CSet;
body4->CSet = original_CSet;
}
else{
ghosted_enemy->X = -16;
ghosted_enemy->Y = -16;
invuln_counter--;
body1->CSet++;
body2->CSet++;
body3->CSet++;
body4->CSet++;
if(this_ffc->X < Link->X){this_ffc->Vx=-0.5;}
else {this_ffc->Vx=0.5;}
if(this_ffc->Y < Link->Y){this_ffc->Vy=-0.5;}
else {this_ffc->Vy=0.5;}
}
// check to see if enemy has been damaged
if (prev_health > ghosted_enemy->HP){
invuln_counter = 40;
}
// if hit points are exhausted, enemy vanishes
if (ghosted_enemy->HP < 100){
this_ffc->Vx = 300;
if ( state <= 1 ){ Game->PlaySound(10); state = 2; }
ghosted_enemy->HP = 0;
}
if ( invuln_counter <= 1){
// --------------------------
// ENEMY MOVEMENT (varies per enemy!)
// --------------------------
// This enemy responds to gravity and this ffc
// expects to be in the very middle of four combos.
if(canMove(this_ffc->X, this_ffc->Y+25)){
this_ffc->Vy = this_ffc->Vy+0.2;
if ( this_ffc->Vy >2){ this_ffc->Vy = 2;}
}
else{ this_ffc->Vy = 0;}
if(this_ffc->X < Link->X){
this_ffc->Vx=walk_speed;
body1->Data = original_combo1+1;
body2->Data = original_combo2+1;
body3->Data = original_combo3+1;
body4->Data = original_combo4+1;
if(!canMove(this_ffc->X+25, this_ffc->Y)){
this_ffc->Vy = -hop_height;
}
}
else {
this_ffc->Vx=-walk_speed;
body1->Data = original_combo1;
body2->Data = original_combo2;
body3->Data = original_combo3;
body4->Data = original_combo4;
if(!canMove(this_ffc->X-18, this_ffc->Y)){
this_ffc->Vy = -hop_height;
}
}
} // end of movement
// --------------------
// Stop enemy from leaving (unless dead)
// --------------------
if ( state != 2 ){
if ( (!canMove(this_ffc->X-18, this_ffc->Y)) && (this_ffc->Vx < 0)){this_ffc->Vx=0;}
if ( (!canMove(this_ffc->X+25, this_ffc->Y)) && (this_ffc->Vx > 0)){this_ffc->Vx=0;}
if ( (!canMove(this_ffc->X, this_ffc->Y-18)) && (this_ffc->Vy < 0)){this_ffc->Vy=0;}
if ( (!canMove(this_ffc->X, this_ffc->Y+25)) && (this_ffc->Vy > 0)){this_ffc->Vy=0;}
}
// --------------------
// Necessary cleanup
// --------------------
prev_health = ghosted_enemy->HP;
Waitframe();
}// end of while loop
} // end of void run
// ===================================
// Collision detection function
// ===================================
bool canMove(int x, int y){
// x=23, y=130
// Obviously in range...
if(x<0 || x>255 || y<0 || y>175)
return false;
int mask=1111b;
// x % 16 = 7, so
// mask = 1111 & 0011 = 0011
if(x%16<8)
mask&=0011b;
else
mask&=1100b;
// y % 16 = 2, so
// mask = 0011 & 0101 = 0001
if(y%16<8)
mask&=0101b;
else
mask&=1010b;
// All but the top-right quarter of the combo is solid, so ComboS = 1011
// mask & ComboS = 0001 & 1011 = 0001
// The result wasn't 0, so return false
return ((Screen->ComboS[ComboAt(x, y)]&mask)==0);
}// end of canMove
} // end of ffc script
// =================================================
// hopper Custom Enemy - This enemy hops along the ground
// and changes direction when it bumps into a wall. The
// ffc running this script expects the enemy to be one tile
// large for collision with wall purposes.
//
// INPUT:
// D0 = The number of this FFC
// D1 = hit points of this FFC, ***PLUS 100***
// There is a very good reason for the plus 100, dont forget it.
// D2 = number of the first of 4 FFCs making up this enemy.
// D4 -D4+3
// For a smaller enemy, just leave some of these FFCs blank.
// Ensure all of these FFCs are LINKED to the FFC running this script.
// The script expects each FFC to start out facing left. It will use
// the original combo of each FFC + 1 for facing right graphics.
// D3 = hopper's horizontal speed
// D4 = hopper's jump height
// D5 = hopper's initial direction (-1 left, 1 right)
// ==================================================
ffc script hopper_CE{
void run (int this_ffc_number, int hit_points, int body_ffc_number, int walk_speed, int hop_height, int dir){
// ---------------------------
// GENERAL ENEMY SETUP (same for all)
// ---------------------------
ffc this_ffc = Screen->LoadFFC(this_ffc_number);
npc ghosted_enemy = Screen->CreateNPC(85);
ghosted_enemy->HP = hit_points;
ffc body1 = Screen->LoadFFC(body_ffc_number);
ffc body2 = Screen->LoadFFC(body_ffc_number+1);
ffc body3 = Screen->LoadFFC(body_ffc_number+2);
ffc body4 = Screen->LoadFFC(body_ffc_number+3);
int original_CSet = body1->CSet;
int prev_health = ghosted_enemy->HP;
// The health of the ghosted enemy last frame
int invuln_counter = 0; // Enemy is invulnerable briefly after being damaged.
int state = 0; // State 0 = moving normally
// State 1 = reacting to damage
// State 2 = enemy is dead
// --------------------------
// SPECIFIC ENEMY SETUP (varies)
// --------------------------
int original_combo1 = body1->Data;
int original_combo2 = body2->Data;
int original_combo3 = body3->Data;
int original_combo4 = body4->Data;
while(true){
// --------------------------
// ENEMY DAMAGE CONTROL (same for all)
// --------------------------
if (invuln_counter <= 0){
ghosted_enemy->X = this_ffc->X;
ghosted_enemy->Y = this_ffc->Y;
body1->CSet = original_CSet;
body2->CSet = original_CSet;
body3->CSet = original_CSet;
body4->CSet = original_CSet;
}
else{
ghosted_enemy->X = -16;
ghosted_enemy->Y = -16;
invuln_counter--;
body1->CSet++;
body2->CSet++;
body3->CSet++;
body4->CSet++;
if(this_ffc->X < Link->X){this_ffc->Vx=-0.5;}
else {this_ffc->Vx=0.5;}
if(this_ffc->Y < Link->Y){this_ffc->Vy=-0.5;}
else {this_ffc->Vy=0.5;}
}
// check to see if enemy has been damaged
if (prev_health > ghosted_enemy->HP){
invuln_counter = 40;
}
// if hit points are exhausted, enemy vanishes
if (ghosted_enemy->HP < 100){
this_ffc->Vx = 300;
if ( state <= 1 ){ Game->PlaySound(10); state = 2; }
ghosted_enemy->HP = 0;
}
if ( invuln_counter <= 1){
// --------------------------
// ENEMY MOVEMENT (varies per enemy!)
// --------------------------
// This enemy responds to gravity and this ffc
// expects to be at the BOTTOM MIDDLE of the enemy.
if(canMove(this_ffc->X, this_ffc->Y+17)){
this_ffc->Vy = this_ffc->Vy+0.2;
if ( this_ffc->Vy >2){ this_ffc->Vy = 2;}
}
else{ this_ffc->Vy = -hop_height; }
this_ffc->Vx = (dir)*(walk_speed);
if(dir == 1){
body1->Data = original_combo1+1;
body2->Data = original_combo2+1;
body3->Data = original_combo3+1;
body4->Data = original_combo4+1;
if(!canMove(this_ffc->X+18, this_ffc->Y)){
dir = -1;
}
}
else {
this_ffc->Vx=-walk_speed;
body1->Data = original_combo1;
body2->Data = original_combo2;
body3->Data = original_combo3;
body4->Data = original_combo4;
if(!canMove(this_ffc->X-2, this_ffc->Y)){
dir = 1;
}
}
} // end of movement
// --------------------
// Stop enemy from leaving (unless dead)
// --------------------
if ( state != 2 ){
if ( (!canMove(this_ffc->X-2, this_ffc->Y)) && (this_ffc->Vx < 0)){this_ffc->Vx=0;}
if ( (!canMove(this_ffc->X+18, this_ffc->Y)) && (this_ffc->Vx > 0)){this_ffc->Vx=0;}
if ( (!canMove(this_ffc->X, this_ffc->Y-2)) && (this_ffc->Vy < 0)){this_ffc->Vy=0;}
if ( (!canMove(this_ffc->X, this_ffc->Y+17)) && (this_ffc->Vy > 0)){this_ffc->Vy=0;}
}
// --------------------
// Necessary cleanup
// --------------------
prev_health = ghosted_enemy->HP;
Waitframe();
}// end of while loop
} // end of void run
// ===================================
// Collision detection function
// ===================================
bool canMove(int x, int y){
// x=23, y=130
// Obviously in range...
if(x<0 || x>255 || y<0 || y>175)
return false;
int mask=1111b;
// x % 16 = 7, so
// mask = 1111 & 0011 = 0011
if(x%16<8)
mask&=0011b;
else
mask&=1100b;
// y % 16 = 2, so
// mask = 0011 & 0101 = 0001
if(y%16<8)
mask&=0101b;
else
mask&=1010b;
// All but the top-right quarter of the combo is solid, so ComboS = 1011
// mask & ComboS = 0001 & 1011 = 0001
// The result wasn't 0, so return false
return ((Screen->ComboS[ComboAt(x, y)]&mask)==0);
}// end of canMove
} // end of ffc script
// =================================================
// Diamond Custom Enemy - This enemy moves at an angle
// and bounces off of the walls and celings. This script
// expects the enemy to be one-tile large for collision purposes.
//
// INPUT:
// D0 = The number of this FFC
// D1 = hit points of this FFC, ***PLUS 100***
// There is a very good reason for the plus 100, dont forget it.
// D2 = number of the first of 4 FFCs making up this enemy.
// D4 -D4+3
// For a smaller enemy, just leave some of these FFCs blank.
// Ensure all of these FFCs are LINKED to the FFC running this script.
// The script expects each FFC to start out facing left. It will use
// the original combo of each FFC + 1 for facing right graphics.
// D3 = Initial X velocity
// D4 = Initial Y velocity
// ==================================================
ffc script diamond_CE{
void run (int this_ffc_number, int hit_points, int body_ffc_number, int initial_X, int initial_Y){
// ---------------------------
// GENERAL ENEMY SETUP (same for all)
// ---------------------------
ffc this_ffc = Screen->LoadFFC(this_ffc_number);
npc ghosted_enemy = Screen->CreateNPC(85);
ghosted_enemy->HP = hit_points;
ffc body1 = Screen->LoadFFC(body_ffc_number);
ffc body2 = Screen->LoadFFC(body_ffc_number+1);
ffc body3 = Screen->LoadFFC(body_ffc_number+2);
ffc body4 = Screen->LoadFFC(body_ffc_number+3);
int original_CSet = body1->CSet;
int prev_health = ghosted_enemy->HP;
// The health of the ghosted enemy last frame
int invuln_counter = 0; // Enemy is invulnerable briefly after being damaged.
int state = 0; // State 0 = moving normally
// State 1 = reacting to damage
// State 2 = enemy is dead
// --------------------------
// SPECIFIC ENEMY SETUP (varies)
// --------------------------
this_ffc->Vx = initial_X;
this_ffc->Vy = initial_Y;
while(true){
// --------------------------
// ENEMY DAMAGE CONTROL (same for all)
// --------------------------
if (invuln_counter <= 0){
ghosted_enemy->X = this_ffc->X;
ghosted_enemy->Y = this_ffc->Y;
body1->CSet = original_CSet;
body2->CSet = original_CSet;
body3->CSet = original_CSet;
body4->CSet = original_CSet;
}
else{
ghosted_enemy->X = -16;
ghosted_enemy->Y = -16;
invuln_counter--;
body1->CSet++;
body2->CSet++;
body3->CSet++;
body4->CSet++;
if(this_ffc->X < Link->X){this_ffc->Vx=-0.5;}
else {this_ffc->Vx=0.5;}
if(this_ffc->Y < Link->Y){this_ffc->Vy=-0.5;}
else {this_ffc->Vy=0.5;}
}
// check to see if enemy has been damaged
if (prev_health > ghosted_enemy->HP){
invuln_counter = 40;
}
// if hit points are exhausted, enemy vanishes
if (ghosted_enemy->HP < 100){
this_ffc->Vx = 300;
if ( state <= 1 ){ Game->PlaySound(10); state = 2; }
ghosted_enemy->HP = 0;
}
if ( invuln_counter <= 1){
// --------------------------
// ENEMY MOVEMENT (varies per enemy!)
// --------------------------
// Just reverses X and Y velocity on hitting a wall.
if ( (!canMove(this_ffc->X-2, this_ffc->Y)) && (this_ffc->Vx < 0)){this_ffc->Vx=-this_ffc->Vx;}
if ( (!canMove(this_ffc->X+17, this_ffc->Y)) && (this_ffc->Vx > 0)){this_ffc->Vx=-this_ffc->Vx;}
if ( (!canMove(this_ffc->X, this_ffc->Y-2)) && (this_ffc->Vy < 0)){this_ffc->Vy=-this_ffc->Vy;}
if ( (!canMove(this_ffc->X, this_ffc->Y+17)) && (this_ffc->Vy > 0)){this_ffc->Vy=-this_ffc->Vy;}
} // end of movement
// --------------------
// Stop enemy from leaving (unless dead)
// --------------------
if ( state != 2 ){
if ( (!canMove(this_ffc->X-2, this_ffc->Y)) && (this_ffc->Vx < 0)){this_ffc->Vx=0;}
if ( (!canMove(this_ffc->X+17, this_ffc->Y)) && (this_ffc->Vx > 0)){this_ffc->Vx=0;}
if ( (!canMove(this_ffc->X, this_ffc->Y-2)) && (this_ffc->Vy < 0)){this_ffc->Vy=0;}
if ( (!canMove(this_ffc->X, this_ffc->Y+17)) && (this_ffc->Vy > 0)){this_ffc->Vy=0;}
}
// --------------------
// Necessary cleanup
// --------------------
prev_health = ghosted_enemy->HP;
Waitframe();
}// end of while loop
} // end of void run
// ===================================
// Collision detection function
// ===================================
bool canMove(int x, int y){
// x=23, y=130
// Obviously in range...
if(x<0 || x>255 || y<0 || y>175)
return false;
int mask=1111b;
// x % 16 = 7, so
// mask = 1111 & 0011 = 0011
if(x%16<8)
mask&=0011b;
else
mask&=1100b;
// y % 16 = 2, so
// mask = 0011 & 0101 = 0001
if(y%16<8)
mask&=0101b;
else
mask&=1010b;
// All but the top-right quarter of the combo is solid, so ComboS = 1011
// mask & ComboS = 0001 & 1011 = 0001
// The result wasn't 0, so return false
return ((Screen->ComboS[ComboAt(x, y)]&mask)==0);
}// end of canMove
} // end of ffc script
Notice that these three enemies vary only by how they move; the rest of how they work is taken care of by the same code.
This skeleton script will totally take care of setting up enemy health and damage in response to Link's weapons. If you want it to shoot, you can use my enemy ghosting script to stick a shooter on it. All you have to do is plug in the script that makes it move into the proper place (see comments) and turn it on.
So, here's what you do to make your own enemy:
1. Script the AI (under what conditions does it move in what direction?)
2. Decide how many tiles wide it will be, and set up your FFCs accordingly. Want a 2x2 enemy? Fine, make FFC 2 the one running this script... place it on the screen somewhere. Make FFC 3, 4, 5, and 6 the graphics for the enemy (damage combos work well). Place them around FFC 2.
This isn't a very detailed discussion of how to use this script, I know. Go download Zodiac and check out how I've used it there to get some further insight.
Well, now you can. Here's some scripts that will let you do that.
These are three scripts, all examples of a very general-purpose custom enemy script I've been using. The first enemy crawls along the ground, and hops up small obstacles. The second enemy should be familiar to people who played Adventure of Link - it's those hoppy things in the woods around Error's cabin. The third is an enemy that moves by bouncing off of the walls like the anti-fairies in LttP.
// =================================================
// crawlie Custom Enemy - This enemy crawls along the ground towards
// the player and will hop over obstacles. The ffc treats the enemy
// as four tiles large (with the ffc at the very center).
//
// INPUT:
// D0 = The number of this FFC
// D1 = hit points of this FFC, ***PLUS 100***
// There is a very good reason for the plus 100, dont forget it.
// D2 = number of the first of 4 FFCs making up this enemy.
// D4 -D4+3
// For a smaller enemy, just leave some of these FFCs blank.
// Ensure all of these FFCs are LINKED to the FFC running this script.
// The script expects each FFC to start out facing left. It will use
// the original combo of each FFC + 1 for facing right graphics.
// All higher variables
// D3 = Speed at which the ffc walks towards the player
// D4 = Height of hops
// ==================================================
ffc script crawlie_CE{
void run (int this_ffc_number, int hit_points, int body_ffc_number, int walk_speed, int hop_height){
// ---------------------------
// GENERAL ENEMY SETUP (same for all)
// ---------------------------
ffc this_ffc = Screen->LoadFFC(this_ffc_number);
npc ghosted_enemy = Screen->CreateNPC(85);
ghosted_enemy->HP = hit_points;
ffc body1 = Screen->LoadFFC(body_ffc_number);
ffc body2 = Screen->LoadFFC(body_ffc_number+1);
ffc body3 = Screen->LoadFFC(body_ffc_number+2);
ffc body4 = Screen->LoadFFC(body_ffc_number+3);
int original_CSet = body1->CSet;
int prev_health = ghosted_enemy->HP;
// The health of the ghosted enemy last frame
int invuln_counter = 0; // Enemy is invulnerable briefly after being damaged.
int state = 0; // State 0 = moving normally
// State 1 = reacting to damage
// State 2 = enemy is dead
// --------------------------
// SPECIFIC ENEMY SETUP (varies)
// --------------------------
int original_combo1 = body1->Data;
int original_combo2 = body2->Data;
int original_combo3 = body3->Data;
int original_combo4 = body4->Data;
while(true){
// --------------------------
// ENEMY DAMAGE CONTROL (same for all)
// --------------------------
if (invuln_counter <= 0){
ghosted_enemy->X = this_ffc->X;
ghosted_enemy->Y = this_ffc->Y;
body1->CSet = original_CSet;
body2->CSet = original_CSet;
body3->CSet = original_CSet;
body4->CSet = original_CSet;
}
else{
ghosted_enemy->X = -16;
ghosted_enemy->Y = -16;
invuln_counter--;
body1->CSet++;
body2->CSet++;
body3->CSet++;
body4->CSet++;
if(this_ffc->X < Link->X){this_ffc->Vx=-0.5;}
else {this_ffc->Vx=0.5;}
if(this_ffc->Y < Link->Y){this_ffc->Vy=-0.5;}
else {this_ffc->Vy=0.5;}
}
// check to see if enemy has been damaged
if (prev_health > ghosted_enemy->HP){
invuln_counter = 40;
}
// if hit points are exhausted, enemy vanishes
if (ghosted_enemy->HP < 100){
this_ffc->Vx = 300;
if ( state <= 1 ){ Game->PlaySound(10); state = 2; }
ghosted_enemy->HP = 0;
}
if ( invuln_counter <= 1){
// --------------------------
// ENEMY MOVEMENT (varies per enemy!)
// --------------------------
// This enemy responds to gravity and this ffc
// expects to be in the very middle of four combos.
if(canMove(this_ffc->X, this_ffc->Y+25)){
this_ffc->Vy = this_ffc->Vy+0.2;
if ( this_ffc->Vy >2){ this_ffc->Vy = 2;}
}
else{ this_ffc->Vy = 0;}
if(this_ffc->X < Link->X){
this_ffc->Vx=walk_speed;
body1->Data = original_combo1+1;
body2->Data = original_combo2+1;
body3->Data = original_combo3+1;
body4->Data = original_combo4+1;
if(!canMove(this_ffc->X+25, this_ffc->Y)){
this_ffc->Vy = -hop_height;
}
}
else {
this_ffc->Vx=-walk_speed;
body1->Data = original_combo1;
body2->Data = original_combo2;
body3->Data = original_combo3;
body4->Data = original_combo4;
if(!canMove(this_ffc->X-18, this_ffc->Y)){
this_ffc->Vy = -hop_height;
}
}
} // end of movement
// --------------------
// Stop enemy from leaving (unless dead)
// --------------------
if ( state != 2 ){
if ( (!canMove(this_ffc->X-18, this_ffc->Y)) && (this_ffc->Vx < 0)){this_ffc->Vx=0;}
if ( (!canMove(this_ffc->X+25, this_ffc->Y)) && (this_ffc->Vx > 0)){this_ffc->Vx=0;}
if ( (!canMove(this_ffc->X, this_ffc->Y-18)) && (this_ffc->Vy < 0)){this_ffc->Vy=0;}
if ( (!canMove(this_ffc->X, this_ffc->Y+25)) && (this_ffc->Vy > 0)){this_ffc->Vy=0;}
}
// --------------------
// Necessary cleanup
// --------------------
prev_health = ghosted_enemy->HP;
Waitframe();
}// end of while loop
} // end of void run
// ===================================
// Collision detection function
// ===================================
bool canMove(int x, int y){
// x=23, y=130
// Obviously in range...
if(x<0 || x>255 || y<0 || y>175)
return false;
int mask=1111b;
// x % 16 = 7, so
// mask = 1111 & 0011 = 0011
if(x%16<8)
mask&=0011b;
else
mask&=1100b;
// y % 16 = 2, so
// mask = 0011 & 0101 = 0001
if(y%16<8)
mask&=0101b;
else
mask&=1010b;
// All but the top-right quarter of the combo is solid, so ComboS = 1011
// mask & ComboS = 0001 & 1011 = 0001
// The result wasn't 0, so return false
return ((Screen->ComboS[ComboAt(x, y)]&mask)==0);
}// end of canMove
} // end of ffc script
// =================================================
// hopper Custom Enemy - This enemy hops along the ground
// and changes direction when it bumps into a wall. The
// ffc running this script expects the enemy to be one tile
// large for collision with wall purposes.
//
// INPUT:
// D0 = The number of this FFC
// D1 = hit points of this FFC, ***PLUS 100***
// There is a very good reason for the plus 100, dont forget it.
// D2 = number of the first of 4 FFCs making up this enemy.
// D4 -D4+3
// For a smaller enemy, just leave some of these FFCs blank.
// Ensure all of these FFCs are LINKED to the FFC running this script.
// The script expects each FFC to start out facing left. It will use
// the original combo of each FFC + 1 for facing right graphics.
// D3 = hopper's horizontal speed
// D4 = hopper's jump height
// D5 = hopper's initial direction (-1 left, 1 right)
// ==================================================
ffc script hopper_CE{
void run (int this_ffc_number, int hit_points, int body_ffc_number, int walk_speed, int hop_height, int dir){
// ---------------------------
// GENERAL ENEMY SETUP (same for all)
// ---------------------------
ffc this_ffc = Screen->LoadFFC(this_ffc_number);
npc ghosted_enemy = Screen->CreateNPC(85);
ghosted_enemy->HP = hit_points;
ffc body1 = Screen->LoadFFC(body_ffc_number);
ffc body2 = Screen->LoadFFC(body_ffc_number+1);
ffc body3 = Screen->LoadFFC(body_ffc_number+2);
ffc body4 = Screen->LoadFFC(body_ffc_number+3);
int original_CSet = body1->CSet;
int prev_health = ghosted_enemy->HP;
// The health of the ghosted enemy last frame
int invuln_counter = 0; // Enemy is invulnerable briefly after being damaged.
int state = 0; // State 0 = moving normally
// State 1 = reacting to damage
// State 2 = enemy is dead
// --------------------------
// SPECIFIC ENEMY SETUP (varies)
// --------------------------
int original_combo1 = body1->Data;
int original_combo2 = body2->Data;
int original_combo3 = body3->Data;
int original_combo4 = body4->Data;
while(true){
// --------------------------
// ENEMY DAMAGE CONTROL (same for all)
// --------------------------
if (invuln_counter <= 0){
ghosted_enemy->X = this_ffc->X;
ghosted_enemy->Y = this_ffc->Y;
body1->CSet = original_CSet;
body2->CSet = original_CSet;
body3->CSet = original_CSet;
body4->CSet = original_CSet;
}
else{
ghosted_enemy->X = -16;
ghosted_enemy->Y = -16;
invuln_counter--;
body1->CSet++;
body2->CSet++;
body3->CSet++;
body4->CSet++;
if(this_ffc->X < Link->X){this_ffc->Vx=-0.5;}
else {this_ffc->Vx=0.5;}
if(this_ffc->Y < Link->Y){this_ffc->Vy=-0.5;}
else {this_ffc->Vy=0.5;}
}
// check to see if enemy has been damaged
if (prev_health > ghosted_enemy->HP){
invuln_counter = 40;
}
// if hit points are exhausted, enemy vanishes
if (ghosted_enemy->HP < 100){
this_ffc->Vx = 300;
if ( state <= 1 ){ Game->PlaySound(10); state = 2; }
ghosted_enemy->HP = 0;
}
if ( invuln_counter <= 1){
// --------------------------
// ENEMY MOVEMENT (varies per enemy!)
// --------------------------
// This enemy responds to gravity and this ffc
// expects to be at the BOTTOM MIDDLE of the enemy.
if(canMove(this_ffc->X, this_ffc->Y+17)){
this_ffc->Vy = this_ffc->Vy+0.2;
if ( this_ffc->Vy >2){ this_ffc->Vy = 2;}
}
else{ this_ffc->Vy = -hop_height; }
this_ffc->Vx = (dir)*(walk_speed);
if(dir == 1){
body1->Data = original_combo1+1;
body2->Data = original_combo2+1;
body3->Data = original_combo3+1;
body4->Data = original_combo4+1;
if(!canMove(this_ffc->X+18, this_ffc->Y)){
dir = -1;
}
}
else {
this_ffc->Vx=-walk_speed;
body1->Data = original_combo1;
body2->Data = original_combo2;
body3->Data = original_combo3;
body4->Data = original_combo4;
if(!canMove(this_ffc->X-2, this_ffc->Y)){
dir = 1;
}
}
} // end of movement
// --------------------
// Stop enemy from leaving (unless dead)
// --------------------
if ( state != 2 ){
if ( (!canMove(this_ffc->X-2, this_ffc->Y)) && (this_ffc->Vx < 0)){this_ffc->Vx=0;}
if ( (!canMove(this_ffc->X+18, this_ffc->Y)) && (this_ffc->Vx > 0)){this_ffc->Vx=0;}
if ( (!canMove(this_ffc->X, this_ffc->Y-2)) && (this_ffc->Vy < 0)){this_ffc->Vy=0;}
if ( (!canMove(this_ffc->X, this_ffc->Y+17)) && (this_ffc->Vy > 0)){this_ffc->Vy=0;}
}
// --------------------
// Necessary cleanup
// --------------------
prev_health = ghosted_enemy->HP;
Waitframe();
}// end of while loop
} // end of void run
// ===================================
// Collision detection function
// ===================================
bool canMove(int x, int y){
// x=23, y=130
// Obviously in range...
if(x<0 || x>255 || y<0 || y>175)
return false;
int mask=1111b;
// x % 16 = 7, so
// mask = 1111 & 0011 = 0011
if(x%16<8)
mask&=0011b;
else
mask&=1100b;
// y % 16 = 2, so
// mask = 0011 & 0101 = 0001
if(y%16<8)
mask&=0101b;
else
mask&=1010b;
// All but the top-right quarter of the combo is solid, so ComboS = 1011
// mask & ComboS = 0001 & 1011 = 0001
// The result wasn't 0, so return false
return ((Screen->ComboS[ComboAt(x, y)]&mask)==0);
}// end of canMove
} // end of ffc script
// =================================================
// Diamond Custom Enemy - This enemy moves at an angle
// and bounces off of the walls and celings. This script
// expects the enemy to be one-tile large for collision purposes.
//
// INPUT:
// D0 = The number of this FFC
// D1 = hit points of this FFC, ***PLUS 100***
// There is a very good reason for the plus 100, dont forget it.
// D2 = number of the first of 4 FFCs making up this enemy.
// D4 -D4+3
// For a smaller enemy, just leave some of these FFCs blank.
// Ensure all of these FFCs are LINKED to the FFC running this script.
// The script expects each FFC to start out facing left. It will use
// the original combo of each FFC + 1 for facing right graphics.
// D3 = Initial X velocity
// D4 = Initial Y velocity
// ==================================================
ffc script diamond_CE{
void run (int this_ffc_number, int hit_points, int body_ffc_number, int initial_X, int initial_Y){
// ---------------------------
// GENERAL ENEMY SETUP (same for all)
// ---------------------------
ffc this_ffc = Screen->LoadFFC(this_ffc_number);
npc ghosted_enemy = Screen->CreateNPC(85);
ghosted_enemy->HP = hit_points;
ffc body1 = Screen->LoadFFC(body_ffc_number);
ffc body2 = Screen->LoadFFC(body_ffc_number+1);
ffc body3 = Screen->LoadFFC(body_ffc_number+2);
ffc body4 = Screen->LoadFFC(body_ffc_number+3);
int original_CSet = body1->CSet;
int prev_health = ghosted_enemy->HP;
// The health of the ghosted enemy last frame
int invuln_counter = 0; // Enemy is invulnerable briefly after being damaged.
int state = 0; // State 0 = moving normally
// State 1 = reacting to damage
// State 2 = enemy is dead
// --------------------------
// SPECIFIC ENEMY SETUP (varies)
// --------------------------
this_ffc->Vx = initial_X;
this_ffc->Vy = initial_Y;
while(true){
// --------------------------
// ENEMY DAMAGE CONTROL (same for all)
// --------------------------
if (invuln_counter <= 0){
ghosted_enemy->X = this_ffc->X;
ghosted_enemy->Y = this_ffc->Y;
body1->CSet = original_CSet;
body2->CSet = original_CSet;
body3->CSet = original_CSet;
body4->CSet = original_CSet;
}
else{
ghosted_enemy->X = -16;
ghosted_enemy->Y = -16;
invuln_counter--;
body1->CSet++;
body2->CSet++;
body3->CSet++;
body4->CSet++;
if(this_ffc->X < Link->X){this_ffc->Vx=-0.5;}
else {this_ffc->Vx=0.5;}
if(this_ffc->Y < Link->Y){this_ffc->Vy=-0.5;}
else {this_ffc->Vy=0.5;}
}
// check to see if enemy has been damaged
if (prev_health > ghosted_enemy->HP){
invuln_counter = 40;
}
// if hit points are exhausted, enemy vanishes
if (ghosted_enemy->HP < 100){
this_ffc->Vx = 300;
if ( state <= 1 ){ Game->PlaySound(10); state = 2; }
ghosted_enemy->HP = 0;
}
if ( invuln_counter <= 1){
// --------------------------
// ENEMY MOVEMENT (varies per enemy!)
// --------------------------
// Just reverses X and Y velocity on hitting a wall.
if ( (!canMove(this_ffc->X-2, this_ffc->Y)) && (this_ffc->Vx < 0)){this_ffc->Vx=-this_ffc->Vx;}
if ( (!canMove(this_ffc->X+17, this_ffc->Y)) && (this_ffc->Vx > 0)){this_ffc->Vx=-this_ffc->Vx;}
if ( (!canMove(this_ffc->X, this_ffc->Y-2)) && (this_ffc->Vy < 0)){this_ffc->Vy=-this_ffc->Vy;}
if ( (!canMove(this_ffc->X, this_ffc->Y+17)) && (this_ffc->Vy > 0)){this_ffc->Vy=-this_ffc->Vy;}
} // end of movement
// --------------------
// Stop enemy from leaving (unless dead)
// --------------------
if ( state != 2 ){
if ( (!canMove(this_ffc->X-2, this_ffc->Y)) && (this_ffc->Vx < 0)){this_ffc->Vx=0;}
if ( (!canMove(this_ffc->X+17, this_ffc->Y)) && (this_ffc->Vx > 0)){this_ffc->Vx=0;}
if ( (!canMove(this_ffc->X, this_ffc->Y-2)) && (this_ffc->Vy < 0)){this_ffc->Vy=0;}
if ( (!canMove(this_ffc->X, this_ffc->Y+17)) && (this_ffc->Vy > 0)){this_ffc->Vy=0;}
}
// --------------------
// Necessary cleanup
// --------------------
prev_health = ghosted_enemy->HP;
Waitframe();
}// end of while loop
} // end of void run
// ===================================
// Collision detection function
// ===================================
bool canMove(int x, int y){
// x=23, y=130
// Obviously in range...
if(x<0 || x>255 || y<0 || y>175)
return false;
int mask=1111b;
// x % 16 = 7, so
// mask = 1111 & 0011 = 0011
if(x%16<8)
mask&=0011b;
else
mask&=1100b;
// y % 16 = 2, so
// mask = 0011 & 0101 = 0001
if(y%16<8)
mask&=0101b;
else
mask&=1010b;
// All but the top-right quarter of the combo is solid, so ComboS = 1011
// mask & ComboS = 0001 & 1011 = 0001
// The result wasn't 0, so return false
return ((Screen->ComboS[ComboAt(x, y)]&mask)==0);
}// end of canMove
} // end of ffc script
Notice that these three enemies vary only by how they move; the rest of how they work is taken care of by the same code.
This skeleton script will totally take care of setting up enemy health and damage in response to Link's weapons. If you want it to shoot, you can use my enemy ghosting script to stick a shooter on it. All you have to do is plug in the script that makes it move into the proper place (see comments) and turn it on.
So, here's what you do to make your own enemy:
1. Script the AI (under what conditions does it move in what direction?)
2. Decide how many tiles wide it will be, and set up your FFCs accordingly. Want a 2x2 enemy? Fine, make FFC 2 the one running this script... place it on the screen somewhere. Make FFC 3, 4, 5, and 6 the graphics for the enemy (damage combos work well). Place them around FFC 2.
This isn't a very detailed discussion of how to use this script, I know. Go download Zodiac and check out how I've used it there to get some further insight.