Log in

View Full Version : All-Purpose Custom Enemy Script



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.

CJC
05-23-2008, 09:46 PM
Sounds pretty cool, especially the expanded enemy graphics aspect.

I don't mean to sound lazy, but I'd like to ask you a few things about this script.
I'm not particularly good at telling enemies what to do, and I may want some of these to use weapons (Instead of damage combos). Are the following possible?:


Drawing tiles over the enemy but NOT grabbing control of it. As in, the enemy controls the FFCs and not the reverse. (This is to fix the grave graphical eyesore that is small enemys near Big Link. Also, then I can use it to draw non-hostile NPCs).
Enemy flag detection (I have a gentle slope flag that slowly forces Link's Y-location up as he moves in a particular direction. I think it can work on enemies too, and such detection may be necessary if I don't grab control of the enemy with the FFC).
(Just a question in general): Drawing an enemy weapon that isn't a projectile?


Any help at all is appreciated. Thank you.

(Sorry to dig up an old thread, but this script still looks useful and I needed to ask some followup questions.)

bigjoe
05-24-2008, 08:42 AM
I'm not saying that this script is obsolete, but as new alphas come out, there will be a way to resize regular enemies without having to rely on the FFC to display the graphics or handle flashing.

In fact, there already is, it's just that using it now will result in having to update your scripts to the new names.

The_Amaster
05-24-2008, 09:30 AM
In fact, there already is, it's just that using it now will result in having to update your scripts to the new names.
...? *intrigued*

Oh, wait, do you mean Jman's one feature? Because I need a way to resize enemies through the editor, not via scripting.

bigjoe
05-24-2008, 09:56 AM
Well, that's not currently possible, but the scripts needed to resize enemies can be fairly small and not complex.

C-Dawg
05-24-2008, 10:06 AM
The editor lets you resize enemies now, but don't be fooled - you still don't have the ability to script the enemy's behavior or attacks. Plus, if you want the enemy to be an unusual shape, or its hitbox to change when it attacks, you still need scripting.

Anyway.

Yes, you can easily have the FFC follow an enemy. You just make the enemy with the movement pattern you want instead of the fire enemy currently used. Then, in the movement portion of your script, tell the FFCS just to draw on top of the enemy. The enemy will be free to move.

You could make "dashing" enemies this way. Say, a slow octorock that occasionally leaps across the screen at great speed.

As for enemy weapons that are not projectiles, sure. What I usually do is have spare FFC or three (depending on the size of the enemy weapon). I stash it off the screen during most of the enemy's movement, and move them onto the screen in the proper place when the enemy attacks. So, for instance, you move two FFCs that look like a sword, and are damage combos, onto the screen when the enemy stabs.

bigjoe
05-24-2008, 10:15 AM
The editor lets you resize enemies now, but don't be fooled - you still don't have the ability to script the enemy's behavior or attacks. Plus, if you want the enemy to be an unusual shape, or its hitbox to change when it attacks, you still need scripting.


Is that what those W: and H: boxes do? I had seen them and thought that they had something to do with how the tiles are organized.