const int EW_ENEMYSWORD = EW_SCRIPT1; //Weapon type used for scripted sword weapons
//D0: Sword Sprite. Uses four tiles: Up, Down, Left, Right
//D1: How far out the sword is held
//D2: How far to offset the sword to the side when facing up and down (flipped based on direction)
//D3: How far to offset the sword up or down when facing left and right (not flipped based on direction)
@InitD0("Sword Sprite"),
@InitDHelp0("Sprite for the sword. Uses four tiles facing Up, Down, Left, and Right"),
@InitD1("Sword Distance"),
@InitDHelp1("How far from the enemy the sword is held"),
@InitD2("Sword X Distance"),
@InitDHelp2("How far the sword is held to the left or right when facing up or down."),
@InitD3("Sword Y Distance"),
@InitDHelp3("How far the sword is held below the enemy when facing left or right")
npc script EnemySword_Simple{
eweapon UpdateEnemySword(npc this, eweapon hitbox, int swordSprite, int swordExtend, int sideOffset, int downOffset){
int dir = DIR_UP;
if(this->Dir>=0)
dir = this->Dir&3;
int swordX;
int swordY;
//Get offsets for the sword
switch(dir){
case DIR_UP:
swordX += sideOffset;
swordY -= swordExtend;
break;
case DIR_DOWN:
swordX -= sideOffset;
swordY += swordExtend;
break;
case DIR_LEFT:
swordX -= swordExtend;
swordY += downOffset;
break;
case DIR_RIGHT:
swordX += swordExtend;
swordY += downOffset;
break;
}
//If the weapon isn't there recreate it
if(!hitbox->isValid()){
hitbox = Screen->CreateEWeapon(EW_ENEMYSWORD);
hitbox->Damage = this->Damage;
hitbox->UseSprite(swordSprite);
}
hitbox->Tile = hitbox->OriginalTile + dir;
hitbox->Dir = Link->Dir;
hitbox->X = this->X;
hitbox->Y = this->Y;
//We're setting the weapon's offsets instead of position as this lets it clip off the side of the screen and still draw properly
hitbox->DrawXOffset = swordX;
hitbox->DrawYOffset = swordY;
hitbox->HitXOffset = swordX;
hitbox->HitYOffset = swordY;
hitbox->DeadState = WDS_ALIVE;
hitbox->ViewportDespawnRange = 0;
return hitbox;
}
void run(int swordSprite, int swordExtend, int sideOffset, int downOffset){
//Wait for the enemy to spawn
spritedata poof = Game->LoadSpriteData(22);
Waitframes(poof->Frames*poof->Speed);
if(swordExtend==0)
swordExtend = 12;
eweapon hitbox;
this->Immortal = true;
while(true){
hitbox = UpdateEnemySword(this, hitbox, swordSprite, swordExtend, sideOffset, downOffset);
//Do cleanup of the sword when the enemy is dead
if(this->HP<=0||this->Falling){
this->Immortal = false;
if(hitbox->isValid())
hitbox->DeadState = 0;
Quit();
}
Waitframe();
}
}
}
const int SFX_SWORDCLASH = 57;
const int SPR_SWORDCLASH = 103;
const int SWORDCLASH_PUSH_SPEED = 2; //Measured in pixels per frame
const int SWORDCLASH_PUSH_TIME = 8; //Frames the enemy will be moved for on a sword clash
const int SWORDCLASH_STUN_TIME = 8; //Frames the enemy will be additionally stunned for after movement on a sword clash
//D0: Sword Sprite. Uses four tiles: Up, Down, Left, Right
//D1: How far out the sword is held
//D2: How far to offset the sword to the side when facing up and down (flipped based on direction)
//D3: How far to offset the sword up or down when facing left and right (not flipped based on direction)
//D4: How many pixels away the enemy will aggro on Link from
//D5: How many frames the enemy tries to chase for
//D6: How many frames the enemy takes before it can do another chase
@InitD0("Sword Sprite"),
@InitDHelp0("Sprite for the sword. Uses four tiles facing Up, Down, Left, and Right"),
@InitD1("Sword Distance"),
@InitDHelp1("How far from the enemy the sword is held"),
@InitD2("Sword X Distance"),
@InitDHelp2("How far the sword is held to the left or right when facing up or down."),
@InitD3("Sword Y Distance"),
@InitDHelp3("How far the sword is held below the enemy when facing left or right"),
@InitD4("Aggro Dist"),
@InitDHelp4("How many pixels from Link the enemy will aggro at"),
@InitD5("Chase Frames"),
@InitDHelp5("How many frames the enemy tries to chase for"),
@InitD6("Chase Cooldown"),
@InitDHelp6("How many frames before the enemy can do another chase")
npc script EnemySword_Complex{
using namespace GhostBasedMovement;
eweapon UpdateEnemySword(npc this, eweapon hitbox, int swordSprite, int swordExtend, int sideOffset, int downOffset, int swordDir){
int dir = DIR_UP;
if(swordDir>=0)
dir = swordDir&3;
int swordX;
int swordY;
//Get offsets for the sword
switch(dir){
case DIR_UP:
swordX += sideOffset;
swordY -= swordExtend;
break;
case DIR_DOWN:
swordX -= sideOffset;
swordY += swordExtend;
break;
case DIR_LEFT:
swordX -= swordExtend;
swordY += downOffset;
break;
case DIR_RIGHT:
swordX += swordExtend;
swordY += downOffset;
break;
}
//If the weapon isn't there recreate it
if(!hitbox->isValid()){
hitbox = Screen->CreateEWeapon(EW_ENEMYSWORD);
hitbox->Damage = this->Damage;
hitbox->UseSprite(swordSprite);
}
hitbox->Tile = hitbox->OriginalTile + dir;
hitbox->Dir = Link->Dir;
hitbox->X = this->X;
hitbox->Y = this->Y;
//We're setting the weapon's offsets instead of position as this lets it clip off the side of the screen and still draw properly
hitbox->DrawXOffset = swordX;
hitbox->DrawYOffset = swordY;
hitbox->HitXOffset = swordX;
hitbox->HitYOffset = swordY;
hitbox->DeadState = WDS_ALIVE;
hitbox->ViewportDespawnRange = 0;
return hitbox;
}
void UpdateAnimation(npc this, int[] animData, bool walking, int dir){
enum {CURFRAME, CURTIL, MAXFRAME};
if(walking){
this->ScriptTile = this->OriginalTile+animData[CURTIL]+4*dir;
}
else{
this->ScriptTile = this->OriginalTile+20+animData[CURTIL]+4*dir;
}
if(this->HP<=0||this->Falling)
this->ScriptTile = -1;
++animData[CURFRAME];
if(animData[CURFRAME]>=animData[MAXFRAME]){
animData[CURFRAME] = 0;
++animData[CURTIL];
if(animData[CURTIL]>3)
animData[CURTIL] = 0;
}
}
bool SwordCollision(lweapon linkSword, eweapon eSword, int eSwordDir){
int x1 = eSword->X+eSword->HitXOffset+2;
int y1 = eSword->Y+eSword->HitYOffset+2;
int w1 = 12;
int h1 = 12;
int x2 = linkSword->X+2;
int y2 = linkSword->Y+2;
int w2 = 12;
int h2 = 12;
if(eSwordDir==DIR_UP||eSwordDir==DIR_DOWN){
x1 += 3;
w1 -= 6;
}
else if(eSwordDir==DIR_LEFT||eSwordDir==DIR_RIGHT){
y1 += 3;
h1 -= 6;
}
int swordDir = AngleDir8(Angle(Link->X, Link->Y, linkSword->X, linkSword->Y));
if(swordDir==DIR_UP||swordDir==DIR_DOWN){
x2 += 3;
w2 -= 6;
}
else if(swordDir==DIR_LEFT||swordDir==DIR_RIGHT){
y2 += 3;
h2 -= 6;
}
return RectCollision(x1, y1, x1+w1-1, y1+h1-1, x2, y2, x2+w2-1, y2+h2-1);
}
void run(int swordSprite, int swordExtend, int sideOffset, int downOffset, int aggroRange, int chaseTime, int cooldownTime){
//Wait for the enemy to spawn
spritedata poof = Game->LoadSpriteData(22);
Waitframes(poof->Frames*poof->Speed);
//Get the enemy's frame rate off NPC data
npcdata d_this = Game->LoadNPCData(this->ID);
int animData[3];
animData[2] = Floor(d_this->ExFramerate/4);
if(swordExtend==0)
swordExtend = 12;
eweapon hitbox;
this->Immortal = true;
int drawnDir = this->Dir;
int step = this->Step/100;
//When chasing the enemy goes at 1.5x speed but will not go above 1.5 base (Link's default step speed)
int chaseStep = Min((this->Step/100)*1.5, 1.5);
int state;
int chaseClock;
int stunClock;
int coolownClock;
int knockbackClock;
int knockbackAngle;
enum {WANDERING, STUNNED, ATTACKING, REALIGNING};
int realignPos;
int realignX;
int realignY;
int counter = -1;
while(true){
this->SlideSpeed = 2;
if(this->Dir>-1)
drawnDir = this->Dir;
//The enemy can't walk into pits except in the knockback state
this->MoveFlags[NPCMV_CAN_PIT_WALK] = false;
switch(state){
//Wandering about using 4-way movement
case WANDERING:
this->Slide();
counter = ConstWalk4(this, counter);
if(coolownClock)
--coolownClock;
else if(Distance(this->X, this->Y, Link->X, Link->Y)<aggroRange){
state = ATTACKING;
chaseClock = chaseTime;
}
break;
//Was just in a sword clash
case STUNNED:
this->Slide();
this->MoveFlags[NPCMV_CAN_PIT_WALK] = true;
if(knockbackClock){
this->MoveAtAngle(knockbackAngle, SWORDCLASH_PUSH_SPEED, SPW_NONE);
--knockbackClock;
}
if(stunClock)
--stunClock;
else{
if(chaseClock){
this->MoveFlags[NPCMV_CAN_PIT_WALK] = false;
state = ATTACKING;
}
else{
this->MoveFlags[NPCMV_CAN_PIT_WALK] = false;
state = REALIGNING;
realignPos = ComboAt(this->X+8, this->Y+8);
realignX = this->X;
realignY = this->Y;
}
}
break;
//Chase Link for a period of time
case ATTACKING:
int ang = Angle(this->X, this->Y, Link->X, Link->Y);
this->Dir = AngleDir4(ang);
this->MoveAtAngle(ang, chaseStep, SPW_NONE);
this->Slide();
if(chaseClock)
--chaseClock;
else{
state = REALIGNING;
realignPos = ComboAt(this->X+8, this->Y+8);
realignX = this->X;
realignY = this->Y;
}
break;
//Use regular enemy movement for a period after chasing
case REALIGNING:
this->NoSlide = true;
this->Slide();
if(Distance(this->X, this->Y, realignX, realignY)>step){
int ang = Angle(this->X, this->Y, ComboX(realignPos), ComboY(realignPos));
realignX += VectorX(step, ang);
realignY += VectorY(step, ang);
}
else{
realignX = ComboX(realignPos);
realignY = ComboY(realignPos);
state = WANDERING;
this->NoSlide = false;
counter = -1;
}
this->X = realignX;
this->Y = realignY;
break;
}
//Scan over all weapons to find Link's sword and detect a collision with it
if(hitbox->isValid()){
for(int i=Screen->NumLWeapons; i>0; --i){
lweapon l = Screen->LoadLWeapon(i);
if(l->ID==LW_SWORD||l->Weapon==LW_SWORD){
if(SwordCollision(l, hitbox, drawnDir)&&state!=STUNNED){
state = STUNNED;
stunClock = SWORDCLASH_PUSH_TIME+SWORDCLASH_STUN_TIME;
Audio->PlaySound(SFX_SWORDCLASH);
lweapon clash = CreateLWeaponAt(LW_SPARKLE, (l->X+hitbox->X+hitbox->HitXOffset)/2, (l->Y+hitbox->Y+hitbox->HitYOffset)/2);
clash->UseSprite(SPR_SWORDCLASH);
clash->NoCollisionTimer = -1;
knockbackClock = SWORDCLASH_PUSH_TIME;
knockbackAngle = Angle(Link->X, Link->Y, this->X, this->Y);
//Create a weapon script that runs the effect that pushes Link. This way even if the enemy dies that frame, Link still gets the full push.
eweapon LinkPush = CreateEWeaponAt(EW_ENEMYSWORD, Link->X, Link->Y);
LinkPush->NoCollisionTimer = -1;
LinkPush->DrawYOffset = -1000;
LinkPush->Script = Game->GetEWeaponScript("LinkPushEffect");
LinkPush->InitD[0] = WrapDegrees(knockbackAngle+180);
LinkPush->InitD[1] = SWORDCLASH_PUSH_SPEED;
LinkPush->InitD[2] = SWORDCLASH_PUSH_TIME;
LinkPush->ViewportDespawnRange = 0;
break;
}
}
}
}
hitbox = UpdateEnemySword(this, hitbox, swordSprite, swordExtend, sideOffset, downOffset, drawnDir);
UpdateAnimation(this, animData, state!=STUNNED, drawnDir);
//Do cleanup of the sword when the enemy is dead
if(this->HP<=0||this->Falling){
this->Immortal = false;
if(hitbox->isValid())
hitbox->DeadState = 0;
Quit();
}
Waitframe();
}
}
}
eweapon script LinkPushEffect{
void run(int angle, int speed, int time){
for(int i=0; i<time; ++i){
Link->MoveXY(VectorX(speed, angle), VectorY(speed, angle));
Waitframe();
}
this->Remove();
}
}