Copy to Clipboard Test

[2.55] Enemy Sword Code

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();
	}
}