////////////////////////////////////////////////////////////////
// Phantom Cloak
// by grayswandir
////////////////////////////////////////////////////////////////
// The Phantom Cloak is an item that allows you to dash forward several tiles
// (possibly through special blocks that would normally be solid), dealing
// damage along the way. You also leave a bait at your original location,
// drawn transparently.
//
// To setup, you need to examine everything in the following Integration
// section to make sure that it's compatible with your quest. The following
// two sections, Behavior and Default Arguments, have isolated some of the
// easier parts of the script to modify.
//
// Then, give an item the activation script Use_PhantomCloak. The arguments
// are in the same order and effect as in the Default Arguments section - any
// item argument left at 0 will instead be set to those values.
//
// You'll also need to call PhantomCloak_Update in your main active script
// loop.
//
// Note that this makes use of global variables, so adding it will invalidate
// any saves you have.
////////////////////////////////////////////////////////////////
// Integration
////////////////
// The code in this section needs to be modified on a per-quest basis.
// Set this to be a completely transparent tile. Cannot be 0.
const int PCLOAK_TILE_EMPTY = 4;
// Set this to be a completely transparent combo. Cannot be 0.
const int PCLOAK_COMBO_EMPTY = 41;
// This is the weapon id to be used for the cloak's attack. Set it to be
// something that won't interfere with any other scripts.
const int PCLOAK_WEAPON_ID = 31;
////////////////
// Cloak Animation: The cloak's graphics are set up as a sequence of
// animations. There's two parts - an animation to be drawn in place of link
// while they're dashing, and an animation to use as the bait. Each animation
// is PCLOAK_NUM_FRAMES long, and they are laid out in this order:
// - Dash Up
// - Dash Down
// - Dash Left
// - Dash Right
// - Bait Up
// - Bait Down
// - Bait Left
// - Bait Right
// Each cloak item may specify their own set of animations, but they must all
// be in this same format.
// The number of frames. Set this to match whatever animation you are using.
const int PCLOAK_NUM_FRAMES = 1;
// How many game frames each animation frame lasts.
const int PCLOAK_ASPEED = 8;
////////////////////////////////////////////////////////////////
// Behavior
////////////////
// The code in this section can be left unmodified, but is set up to be easily
// changed to allow for different behaviors.
// The number of frames to delay before dashing.
const int PCLOAK_DELAY = 2;
// The number of frames to delay after landing.
const int PCLOAK_RECOVERY = 8;
// Sound effect to play if the cloak can't be used because of range. Set to 0
// for none.
const int PCLOAK_SFX_ERROR = 58;
// The amount of pixels we'll move sideways to fit in a gap.
const int PCLOAK_TOLERANCE = 8;
////
// The following functions determine what sort of combos the phantom cloak can
// traverse.
// This function is used to determine if Link can land on a given pixel. You
// should modify this function if you want to change landing behavior.
bool PhantomCloak_CanLand_Pixel(int x, int y) {
return !Screen->isSolid(x, y);
}
// Return true if the phantom cloak can land link at the given
// coordinates. You shouldn't need to modify this function, just the one
// above.
bool PhantomCloak_CanLand(int x, int y) {
y += 8;
if (!PhantomCloak_CanLand_Pixel(x, y)) {return false;}
x += 15.9999;
if (!PhantomCloak_CanLand_Pixel(x, y)) {return false;}
y += 7.9999;
if (!PhantomCloak_CanLand_Pixel(x, y)) {return false;}
x -= 15.9999;
if (!PhantomCloak_CanLand_Pixel(x, y)) {return false;}
return true;
}
// Return true if the phantom cloak can pass link through the given
// coordinates. This is separated from CanLand in case you want to be able to
// go over pitfalls, or through special blocks, and such. If you only care
// about the landing point, just set this to return true directly.
bool PhantomCloak_CanPass_Pixel(int x, int y) {
return !Screen->isSolid(x, y) || ComboFI(x, y, CF_SCRIPT2);
}
// Again, you shouldn't need to modify this function, just the above one.
bool PhantomCloak_CanPass(int x, int y) {
y += 8;
if (!PhantomCloak_CanPass_Pixel(x, y)) {return false;}
x += 15.9999;
if (!PhantomCloak_CanPass_Pixel(x, y)) {return false;}
y += 7.9999;
if (!PhantomCloak_CanPass_Pixel(x, y)) {return false;}
x -= 15.9999;
if (!PhantomCloak_CanPass_Pixel(x, y)) {return false;}
return true;
}
////////////////////////////////////////////////////////////////
// Default Arguments
////////////////
// These are the default item arguments. If any of an item's arguments are 0,
// these are used in their place. You can change them if it's convenient.
// The set of tiles to use. Set to the first tile in the sequence.
const int PCLOAK_DEFAULT_TILES = 30601;
// The default cset of the tiles.
const int PCLOAK_DEFAULT_CSET = 6;
// The minimum travel distance.
const int PCLOAK_DEFAULT_MIN_DISTANCE = 20;
// The maxmium travel distance.
const int PCLOAK_DEFAULT_MAX_DISTANCE = 48;
// The dashing speed in pixels per frame. For reference, normal walking speed
// is around 1.5.
const int PCLOAK_DEFAULT_SPEED = 4;
// The bait duration.
const int PCLOAK_DEFAULT_DURATION = 150;
// The default damage for the dash.
const int PCLOAK_DEFAULT_DAMAGE = 2;
////////////////////////////////////////////////////////////////
// Main Script
// Yet again, I'm writing these two functions...
int PhantomCloak_DirX(int dir) {
if (DIR_LEFT == dir || DIR_LEFTUP == dir || DIR_LEFTDOWN == dir) {return -1;}
if (DIR_RIGHT == dir || DIR_RIGHTUP == dir || DIR_RIGHTDOWN == dir) {return 1;}
return 0;
}
int PhantomCloak_DirY(int dir) {
if (DIR_UP == dir || DIR_LEFTUP == dir || DIR_RIGHTUP == dir) {return -1;}
if (DIR_DOWN == dir || DIR_LEFTDOWN == dir || DIR_RIGHTDOWN == dir) {return 1;}
return 0;
}
// Get the script id for the main ffc script.
int PhantomCloak_ScriptId() {
int name[] = "PhantomCloak";
return Game->GetFFCScript(name);
}
int PhantomCloak_Screen = -1;
bool PhantomCloak_InUse = false;
void PhantomCloak_Update() {
int screen = Game->GetCurDMap() << 8 + Game->GetCurDMapScreen();
if (PhantomCloak_Screen != screen || Link->HP <= 0) {
PhantomCloak_Screen = screen;
if (PhantomCloak_InUse) {
PhantomCloak_InUse = false;
Link->CollDetection = true;
Link->Invisible = false;
}
}
}
bool PhantomCloak_CheckDirection(int x, int y, int dir, int min_distance, int max_distance, int dist_array) {
int dx = PhantomCloak_DirX(dir) * 8;
int dy = PhantomCloak_DirY(dir) * 8;
dist_array[0] = 0;
int land_dist = 0;
while (dist_array[0] < min_distance) {
dist_array[0] += 8;
x += dx;
y += dy;
if (!PhantomCloak_CanPass(x, y)) {return false;}
}
while (dist_array[0] <= max_distance) {
dist_array[0] += 8;
x += dx;
y += dy;
// If we can land here, mark it.
if (PhantomCloak_CanLand(x, y)) {
land_dist = dist_array[0];
}
// If we can't pass, stop looking.
else if (!PhantomCloak_CanPass(x, y)) {
break;
}
}
dist_array[0] = land_dist;
return land_dist;
}
// The activation script for the cloak. For the item arguments, see the
// "Default Arguments" section above.
item script Use_PhantomCloak {
void run(int tiles, int cset, int min_distance, int max_distance, int speed, int duration, int damage) {
if (PhantomCloak_InUse) {
// Loop through every ffc, looking for the phantom cloak ffc. If we
// don't find any, it means we messed up somewhere and it's not actually
// in use.
int script_id = PhantomCloak_ScriptId();
bool found = false;
for (int i = 1; i <= 32; ++i) {
ffc x = Screen->LoadFFC(i);
if (script_id == x->Script) {
found = true;
break;
}
}
if (found) {
return;
} else {
PhantomCloak_InUse = false;
Link->CollDetection = true;
Link->Invisible = false;
}
}
// Get diagonal possibly.
int dir = Link->Dir;
// Ignore for now, doesn't have proper collision detection.
//if (DIR_UP == dir) {
// if (Link->InputLeft) {dir = DIR_LEFTUP;}
// else if (Link->InputRight) {dir = DIR_RIGHTUP;}
//} else if (DIR_DOWN == dir) {
// if (Link->InputLeft) {dir = DIR_LEFTDOWN;}
// else if (Link->InputRight) {dir = DIR_RIGHTDOWN;}
//} else if (DIR_LEFT == dir) {
// if (Link->InputUp) {dir = DIR_LEFTUP;}
// else if (Link->InputDown) {dir = DIR_LEFTDOWN;}
//} else if (DIR_RIGHT == dir) {
// if (Link->InputUp) {dir = DIR_RIGHTUP;}
// else if (Link->InputDown) {dir = DIR_RIGHTDOWN;}
//}
if (!tiles) {tiles = PCLOAK_DEFAULT_TILES;}
if (!cset) {cset = PCLOAK_DEFAULT_CSET;}
if (!min_distance) {min_distance = PCLOAK_DEFAULT_MIN_DISTANCE;}
if (!max_distance) {max_distance = PCLOAK_DEFAULT_MAX_DISTANCE;}
if (!speed) {speed = PCLOAK_DEFAULT_SPEED;}
if (!duration) {duration = PCLOAK_DEFAULT_DURATION;}
if (!damage) {damage = PCLOAK_DEFAULT_DAMAGE;}
bool valid = false;
int distance[1] = {0};
// Check to see if we have a valid path.
valid = PhantomCloak_CheckDirection(Link->X, Link->Y, Link->Dir, min_distance, max_distance, distance);
int x = Link->X;
int y = Link->Y;
// Check tolerances.
// Up
if (!valid && (DIR_LEFT == Link->Dir || DIR_RIGHT == Link->Dir)
&& ((Link->Y % 8) > 0) && ((Link->Y % 8) < PCLOAK_TOLERANCE)) {
valid = PhantomCloak_CheckDirection(
Link->X, Link->Y & ~7, Link->Dir, min_distance, max_distance, distance);
if (valid) {y = (Link->Y & ~7);}
}
// Down
if (!valid && (DIR_LEFT == Link->Dir || DIR_RIGHT == Link->Dir)
&& (Link->Y >> 3 != ((Link->Y + PCLOAK_TOLERANCE) >> 3))) {
valid = PhantomCloak_CheckDirection(
Link->X, (Link->Y & ~7) + 8, Link->Dir, min_distance, max_distance, distance);
if (valid) {y = (Link->Y & ~7) + 8;}
}
// Left
if (!valid && (DIR_UP == Link->Dir || DIR_DOWN == Link->Dir)
&& (Link->X % 8 > 0) && (Link->X % 8 < PCLOAK_TOLERANCE)) {
valid = PhantomCloak_CheckDirection(
Link->X & ~7, Link->Y & ~7, Link->Dir, min_distance, max_distance, distance);
if (valid) {x = Link->X & ~7;}
}
// Right
if (!valid && (DIR_UP == Link->Dir || DIR_DOWN == Link->Dir)
&& (Link->X >> 3 != ((Link->X + PCLOAK_TOLERANCE) >> 3))) {
valid = PhantomCloak_CheckDirection(
(Link->X & ~7) + 8, Link->Y, Link->Dir, min_distance, max_distance, distance);
if (valid) {x = (Link->X & ~7) + 8;}
}
if (!valid) {
if (PCLOAK_SFX_ERROR) {Game->PlaySound(PCLOAK_SFX_ERROR);}
return;
}
// Setup ffc script.
for (int i = 1; i <= 32; ++i) {
ffc f = Screen->LoadFFC(i);
if (!f->Data && !f->Script) {
f->Script = PhantomCloak_ScriptId();
f->Data = PCLOAK_TILE_EMPTY;
f->InitD[0] = tiles;
f->InitD[1] = cset;
f->InitD[2] = distance[0] / speed;
f->InitD[3] = duration;
f->InitD[4] = damage;
f->InitD[5] = x + PhantomCloak_DirX(dir) * distance[0];
f->InitD[6] = y + PhantomCloak_DirY(dir) * distance[0];
f->Data = PCLOAK_COMBO_EMPTY;
break;;
}
}
}
}
// A helper script for the cloak. Created by the item script.
ffc script PhantomCloak {
void run(int tiles, int cset, int move_duration, int bait_duration, int damage, int tx, int ty) {
PhantomCloak_InUse = true;
int x = Link->X;
int y = Link->Y;
int dx = (tx - Link->X) / move_duration;
int dy = (ty - Link->Y) / move_duration;
// Delay link acting, and exit if we get hurt.
int link_timer = PCLOAK_DELAY;
while (link_timer > 0) {
--link_timer;
NoAction();
if (LA_GOTHURTLAND == Link->Action || LA_GOTHURTWATER == Link->Action) {
this->Data = 0;
PhantomCloak_InUse = false;
return;
}
Waitframe();
}
// Setup the bait.
lweapon bait = Screen->CreateLWeapon(LW_BAIT);
bait->X = Link->X;
bait->Y = Link->Y;
bait->OriginalTile = PCLOAK_TILE_EMPTY;
bait->NumFrames = 1;
bait->CollDetection = false;
bait->DeadState = WDS_ALIVE;
bait->Dir = Link->Dir;
// Setup the dash weapon.
lweapon dash = Screen->CreateLWeapon(LW_SCRIPT1);
dash->Damage = damage;
dash->OriginalTile = tiles + Link->Dir * PCLOAK_NUM_FRAMES;
dash->ASpeed = PCLOAK_ASPEED;
dash->NumFrames = PCLOAK_NUM_FRAMES;
dash->CSet = cset;
dash->Dir = Link->Dir;
dash->X = 100;
dash->Y = 100;
// Link is invincible!!!
Link->CollDetection = false;
Link->Invisible = true;
// Loop, handling the bait, the weapon, and link. It'll only exit once all
// three are done.
int bait_timer = bait_duration;
while (link_timer > 0 || dash->isValid() || bait->isValid()) {
// Insurance
if (!dash->isValid() && Link->Invisible) {
Link->CollDetection = true;
Link->Invisible = false;
}
// We're still moving.
if (move_duration > 0) {
// Kill normal movement.
Link->InputUp = false; Link->PressUp = false;
Link->InputDown = false; Link->PressDown = false;
Link->InputLeft = false; Link->PressLeft = false;
Link->InputRight = false; Link->PressRight = false;
// Move link.
x += dx;
Link->X = x;
y += dy;
Link->Y = y;
--move_duration;
// Align the dash weapon.
dash->HitXOffset = Link->X - 100;
dash->HitYOffset = Link->Y - 100;
dash->DrawXOffset = Link->X - 100;
dash->DrawYOffset = Link->Y - 100;
dash->DeadState = WDS_ALIVE;
if (move_duration <= 0) {
// Start the recovery phase.
link_timer = PCLOAK_RECOVERY;
PhantomCloak_InUse = false;
}
}
// We're recovering.
else if (link_timer >= 0) {
--link_timer;
// Get rid of the dash. We do it here because we don't want link to be
// vulnerable during the last frame of movement.
if (dash->isValid()) {
Remove(dash);
Link->CollDetection = true;
Link->Invisible = false;
}
// Don't move during recovery.
NoAction();
// Exit out of recovery if you got hurt.
if (LA_GOTHURTLAND == Link->Action || LA_GOTHURTWATER == Link->Action) {
link_timer = 0;
PhantomCloak_InUse = false;
}
}
// Countdown the bait timer and draw it.
if (bait_timer > 0) {
--bait_timer;
int tile = tiles + PCLOAK_NUM_FRAMES * (4 + bait->Dir);
tile += (((bait_duration - bait_timer) / PCLOAK_ASPEED) % PCLOAK_NUM_FRAMES) >> 0;
Screen->DrawTile(0, bait->X, bait->Y, tile, 1, 1, cset, -1, -1, 0, 0, 0, 0, true, OP_TRANS);
}
// Otherwise get rid of it.
else if (bait->isValid()) {
Remove(bait);
}
Waitframe();
}
this->Data = 0;
}
}