const int SPRITE_RIGHTSWORD = 91; // a right facing sword sprite, with no frames of animation
ffc script IsilmoPredictable
{
// Enemy ID is an argument of the run() function, Autoghost will handle automatically. No need to provide specific number.
void run(int enemyID)
{
// NPC pointer declaration and ghost init
npc ghost = Ghost_InitAutoGhost(this, enemyID);
// Increasing enemy size, important to use ghost variables when available. Hitbox handled automatically with tile changes.
ghost->Extend = 3;
//ghost->TileWidth = 3;
//ghost->TileHeight = 1;
Ghost_TileWidth = 3;
Ghost_TileHeight = 1;
// Various ghost flags exist, this one is set so walls don't get in the way of Gohma-like movement.
Ghost_SetFlag(GHF_IGNORE_ALL_TERRAIN);
// Set its starting position directly instead of relying on random spawn or enemy flags.
Ghost_X = 112;
Ghost_Y = 64;
// Randomly choose left or right from the start.
int dir = Choose(DIR_LEFT, DIR_RIGHT);
// Variable to keep track of left/right, so it can resume after down/up walk cycle.
int prev_dir;
// Timer for down/up walk cycle.
int timer_walkdown = 240;
// Timer for sword attack.
int timer_attack = Rand(120, 240);
// Eweapons function like other variables, they have to be declared first.
eweapon sword;
while (true)
{
// Turn around four tiles from the edge.
if (Ghost_X <= 64 && dir == DIR_LEFT)
{
dir = DIR_RIGHT;
}
// Same as going left, but enemy width is factored in since X/Y are always the enemy's top left corner.
if (Ghost_X + (Ghost_TileWidth * 16) >= 192 && dir == DIR_RIGHT)
{
dir = DIR_LEFT;
}
// Start process of going down when this timer hits 0, and reset the timer to some random value.
if (timer_walkdown <= 0)
{
prev_dir = dir;
dir = DIR_DOWN;
timer_walkdown = Rand(120, 240);
}
// Decrement timer.
if (timer_walkdown > 0 && dir != DIR_DOWN && dir != DIR_UP)
{
timer_walkdown--;
}
// Start going back up four tiles from bottom screen edge.
if (dir == DIR_DOWN && Ghost_Y + (Ghost_TileHeight * 16) >= 128)
{
dir = DIR_UP;
}
// Resume going left/right four tiles from screen top edge.
if (dir == DIR_UP && Ghost_Y <= 64)
{
dir = prev_dir;
}
// Decrement attack timer.
if (timer_attack)
{
timer_attack--;
}
// Big attack loop starts here.
if (timer_attack <= 0)
{
// Wait a full second so player has time to react.
for (int i = 0; i < 60; i++)
{
Ghost_Waitframe(this, ghost);
}
// Play a single sound effect for attack.
Game->PlaySound(SFX_SWORD);
// Using angle in for loop here, instead of generic "i" counter variable.
for (int angle = 135; angle > 45; angle -= 3)
{
// Stabbing weapons are hard. A new sword is produced every frame, each with 1 frame lifespan. This makes unfortunate double sword graphical error, potentially fixed
// by making actual weapon invisible and using DrawTile to draw sword instead.
//Screen->DrawTile(int layer, int x, int y, int tile, int blockw, int blockh, int cset, int xscale, int yscale, int rx, int ry, int rangle, int flip, bool transparency, int opacity);
// Actual sword is created here. Center of sprite - 8 will center a 16 by 16 weapon on an enemy, and the Cos and Sin factor in the rotation, at a radius of 16 pixels.
// ZScript angles start pointing right, and go clockwise from there, hence the need for right facing sword to get properly rotated weapon graphic.
// All ghost eweapon flags are entered as one argument, ORed together with the bar symbol "|".
sword = FireEWeapon(EW_SCRIPT1, (Ghost_X + (0.5 * (Ghost_TileWidth * 16)) - 8) + (16 * Cos(angle)), (Ghost_Y + (0.5 * (Ghost_TileHeight * 16)) - 8) + (16 * Sin(angle)), DegtoRad(angle), 0, ghost->WeaponDamage, SPRITE_RIGHTSWORD, 0, EWF_UNBLOCKABLE | EWF_ROTATE_360);
// Lifespan and death effects, see ghost.txt for these functions.
SetEWeaponLifespan(sword, EWL_TIMER, 1);
SetEWeaponDeathEffect(sword, EWD_VANISH, 0);
// Need a waitframe here so the attack doesn't get crammed into 1 frame. Takes 30 frames with given loop parameters.
Ghost_Waitframe(this, ghost);
}
// Set timer to attack again to a random value, between 3 and 6 seconds.
timer_attack = Rand(180, 360);
}
// Actual movement function. Needs a pixels per frame step speed argument, so ZQuest step speed is divided by 100. Enemy stops moving when this function isn't called,
// hence why enemy no longer moves during attack loop.
Ghost_Move(dir, ghost->Step * 0.01, 2);
// Ghost_Waitframe() is very important. CANNOT use normal Waitframe() in ghosted enemy scripts. Large number of variants, see ghost.txt.
Ghost_Waitframe(this, ghost);
}
}
}
ffc script IsilmoCircleMover
{
void run(int enemyID)
{
npc ghost = Ghost_InitAutoGhost(this, enemyID);
ghost->Extend = 3;
//ghost->TileWidth = 2;
//ghost->TileHeight = 2;
Ghost_TileWidth = 2;
Ghost_TileHeight = 2;
Ghost_SetFlag(GHF_IGNORE_ALL_TERRAIN);
int timer_attack = 240;
// Center and radius of circle declared here. Could be read from enemy attributes instead, if you want to make several variants of this run off a single script.
int radius = 64;
int center_x = 128;
int center_y = 88;
// Starting angle is calculated from enemy's starting position. Not super important, it could be set to constant value instead.
int angle = Angle(center_x, center_y, Ghost_X + (0.5 * (Ghost_TileWidth * 16)), Ghost_Y + (0.5 * (Ghost_TileHeight * 16)));
// Rotation speed. 3 degrees per frame works out to a full clockwise rotation in 2 seconds.
int rotation_speed = 3;
// Attack angle is where the sword will point. Calculated as needed later.
int attack_angle;
eweapon sword;
// Using a for loop instead of while. No var intialization, but still needs the semicolon.
for (; true; angle += rotation_speed)
{
// The math for positioning a thing on a circle. Ghost_X + (0.5 * (Ghost_TileWidth * 16)) is the center of the enemy's sprite, a little algebra was used
// to directly set Ghost_X.
Ghost_X = center_x + (radius * Cos(angle)) - (0.5 * (Ghost_TileWidth * 16));
Ghost_Y = center_y + (radius * Sin(angle)) - (0.5 * (Ghost_TileHeight * 16));
if (timer_attack)
{
timer_attack--;
}
if (timer_attack <= 0)
{
// Wait for half a second before attacking.
for (int i = 0; i < 30; i++)
{
Ghost_Waitframe(this, ghost, 1, true);
}
// Calculate attack angle based on where player is standing. Uses center of both sprites for consistency. Angle() is very useful for enemy script attacks in general.
attack_angle = Angle(Ghost_X + (0.5 * (Ghost_TileWidth * 16)), Ghost_Y + (0.5 * (Ghost_TileHeight * 16)), Link->X + 8, Link->Y + 8);
// Plays just one sound effect. FireEWeapon has a sound effect argument, but we don't want it to play 20 times so we play one here instead.
Game->PlaySound(SFX_SWORD);
for (int i = 0; i < 20; i++)
{
// Similar to Predictable's sword, but stabs instead of slashes. Radius of 24 is just right for positioning sword on outer edge of 2 by 2 sprite.
// Also of note: weapon angles are always in radians, so degrees have to be converted with DegtoRad(). Angle is functionally useless here other than graphic rotation,
// since weapon has 0 step speed and is unblockable by shields, but good to keep in mind.
sword = FireEWeapon(EW_SCRIPT1, (Ghost_X + (0.5 * (Ghost_TileWidth * 16)) - 8) + (24 * Cos(attack_angle)), (Ghost_Y + (0.5 * (Ghost_TileHeight * 16)) - 8) + (24 * Sin(attack_angle)), DegtoRad(attack_angle), 0, ghost->WeaponDamage, SPRITE_RIGHTSWORD, 0, EWF_UNBLOCKABLE | EWF_ROTATE_360);
SetEWeaponLifespan(sword, EWL_TIMER, 1);
SetEWeaponDeathEffect(sword, EWD_VANISH, 0);
Ghost_Waitframe(this, ghost, 1, true);
}
// Attack timer randomized between 3 and 5 seconds.
timer_attack = Rand(180, 300);
}
// Another Ghost_Waitframe() variant. This one includes an exploding death animation.
Ghost_Waitframe(this, ghost, 1, true);
}
}
}
ffc script IsilmoBreakable
{
void run(int enemyID)
{
npc ghost = Ghost_InitAutoGhost(this, enemyID);
ghost->Extend = 3;
//ghost->TileWidth = 2;
//ghost->TileHeight = 2;
Ghost_TileWidth = 2;
Ghost_TileHeight = 2;
// counter must be initialized to -1. Ghost_HaltingWalk4() uses this internally.
int counter = -1;
int timer_attack = 240;
int angle;
int attack_dir;
// Generic phase variable, to ensure armor breaking and step speed increase only happen once per phase.
int phase = 1;
// Enemies don't have a max HP variable, only current HP. So we declare our own.
int maxHP = Ghost_HP;
eweapon magic;
while (true)
{
// Main walk function. Uses ZQuest step speed, not pixels per frame, so no need to divide by 100. Other enemy properties plugged in. The final argument, 48, doesn't exist in
// enemy editor. It is halt time, how long the enemy stops moving when halted.
counter = Ghost_HaltingWalk4(counter, ghost->Step, ghost->Rate, ghost->Homing, ghost->Hunger, ghost->Haltrate, 48);
// counter is equal to remaining halt time, so with halt time of 48, checking for counter == 16 means that enemy attacks after being halted for 32 frames.
if (counter == 16)
{
// Find angle between enemy and Link.
angle = Angle(Ghost_X + (0.5 * (Ghost_TileWidth * 16)), Ghost_Y + (0.5 * (Ghost_TileHeight * 16)), Link->X + 8, Link->Y + 8);
// Wrap angle between -180 and 180. Necessary for AngleDir8() probably. AngleDir8() converts angle to one of 8 directions.
attack_dir = AngleDir8(WrapDegrees(angle));
// Fire magic in direction. Directional weapons use a different function than angular weapons. 300 step speed is the same as Link's basic arrows.
magic = FireNonAngularEWeapon(EW_MAGIC, Ghost_X + (0.5 * (Ghost_TileWidth * 16)) - 8, Ghost_Y + (0.5 * (Ghost_TileHeight * 16)) - 8, attack_dir, 300, ghost->WeaponDamage, -1, -1, 0);
// Function I made to give weapons proper Flip values, since scripted weapons don't have this set correctly by default.
EWeaponFlip(magic, magic->Dir);
}
// Checks for proper phase and HP thresholds, and increases step and changes graphic. Shifts one full tile row for each change.
if (phase == 1 && Ghost_HP <= maxHP * 0.75)
{
ghost->OriginalTile += 20;
ghost->Step += 25;
phase = 2;
}
if (phase == 2 && Ghost_HP <= maxHP * 0.50)
{
ghost->OriginalTile += 20;
ghost->Step += 25;
phase = 3;
}
if (phase == 3 && Ghost_HP <= maxHP * 0.25)
{
ghost->OriginalTile += 20;
ghost->Step += 25;
phase = 4;
}
// Explody Ghost_Waitframe() variant again.
Ghost_Waitframe(this, ghost, 1, true);
}
}
}
void EWeaponFlip(eweapon ewpn, int dir)
{
int flip;
if (dir == DIR_UP)
{
flip = 0;
}
else if (dir == DIR_DOWN)
{
flip = 3;
}
else if (dir == DIR_LEFT)
{
flip = 7;
}
else if (dir == DIR_RIGHT)
{
flip = 4;
}
else if (dir == DIR_LEFTUP)
{
flip = 0;
}
else if (dir == DIR_RIGHTUP)
{
flip = 0;
}
else if (dir == DIR_LEFTDOWN)
{
flip = 3;
}
else if (dir == DIR_RIGHTDOWN)
{
flip = 3;
}
ewpn->Flip = flip;
}