// Necessary headers, uncomment (remove "//") if you haven't already imported these. //import "std.zh" //import "string.zh" //import "ghost.zh" const int COLOR_WIND = 0x01; const int SUCK_STRENGTH = 1.3; const int BLOW_STRENGTH = 1.3; // ENEMY EDITOR MISC ATTRIBUTES // Attr. 1: CSet for suck, -1 to disable suck // Attr. 2: CSet for blow, -1 to disable blow // Attr. 3: CSet for wind, -1 to disable wind // Attr. 4: 1 to cycle through non-disabled attacks instead of randomizing // Attr. 5: 1 to render enemy immobile // Attr. 6: 1 to turn off collision detection (enemy is invincible and unable to damage player) // Attr. 7: lower bound for random attack interval timer, defaults to 90 when Attributes 7 and 8 are both 0 // Attr. 8: upper bound for random attack interval timer, defaults to 150 when Attributes 7 and 8 are both 0 // Attr. 9: continuous attack mode, attack interval timers are ignored, no CSet flash warning, non-disabled attacks are continuously used (pair with immobile recommended) ffc script SuckToBlow { void run(int enemyID) { npc ghost = Ghost_InitAutoGhost(this, enemyID); int counter = -1; int cset_default = Ghost_CSet; int cset_index = -1; int cset1 = ghost->Attributes[0]; int cset2 = ghost->Attributes[1]; int cset3 = ghost->Attributes[2]; int csets[3] = {cset1, cset2, cset3}; int non_random = ghost->Attributes[3]; int non_moving = ghost->Attributes[4]; int no_collision = ghost->Attributes[5]; int timer_attack_lower_bound = ghost->Attributes[6]; int timer_attack_upper_bound = ghost->Attributes[7]; int continuous = ghost->Attributes[8]; int store_def[18]; Ghost_StoreDefenses(ghost, store_def); int timer_attack = 120; int direct_shot; int wind_lines[48]; float angle; float x_step; float y_step; float x_dir; float y_dir; if (non_moving) { ghost->DrawYOffset = 0; ghost->HitYOffset = 0; } if (no_collision) { ghost->CollDetection = false; } if (timer_attack_lower_bound == 0 && timer_attack_upper_bound == 0) { timer_attack_lower_bound = 90; timer_attack_upper_bound = 150; } while (true) { if (!non_moving) { counter = Ghost_ConstantWalk8(counter, ghost->Step, ghost->Rate, ghost->Homing, ghost->Hunger); } if (timer_attack <= 0) { cset_index = SuckToBlowRandomCSet(csets, cset_index, non_random); if (cset_index >= 0) { if (!continuous) { SuckToBlowFlashCSet(this, ghost, wind_lines, cset_default, csets[cset_index], 60); } else { Ghost_CSet = csets[cset_index]; } if (cset_index == 0) { Ghost_SetAllDefenses(ghost, NPCDT_BLOCK); this->Misc[0] = -90; SuckToBlow_WindLinesRandomize(this, ghost, wind_lines, true); Game->PlaySound(48); while (this->Misc[0] < 0) { SuckToBlow_Waitframe(this, ghost, wind_lines); } } else if (cset_index == 1) { this->Misc[0] = 90; SuckToBlow_WindLinesRandomize(this, ghost, wind_lines, false); Game->PlaySound(49); while (this->Misc[0] > 0) { SuckToBlow_Waitframe(this, ghost, wind_lines); } } else { SuckToBlow_Waitframes(this, ghost, wind_lines, 10); direct_shot = Rand(10); for (int i = 0; i < 10; i++) { if (i == direct_shot) { angle = Angle(Ghost_X + (0.5 * 16 * ghost->TileWidth), Ghost_Y + (0.5 * 16 * ghost->TileHeight), Link->X + 8, Link->Y + 8); } else { angle = Angle(Ghost_X + (0.5 * 16 * ghost->TileWidth), Ghost_Y + (0.5 * 16 * ghost->TileHeight), Link->X + 8, Link->Y + 8); angle += Rand(-45, 45); } SuckToBlow_Wind(angle, Rand(200, 400)); SuckToBlow_Waitframes(this, ghost, wind_lines, 6); } } if (!continuous) { SuckToBlowFlashCSet(this, ghost, wind_lines, csets[cset_index], cset_default, 30); } Ghost_SetDefenses(ghost, store_def); } if (!continuous) { timer_attack = Rand(timer_attack_lower_bound, timer_attack_upper_bound); } else { timer_attack = 0; } } if (timer_attack) { timer_attack--; } SuckToBlow_Waitframe(this, ghost, wind_lines); } } } int SuckToBlowRandomCSet(int csets, int cset_index, int non_random) { int temp[3]; int valid_csets; if (!non_random) { for (int i = 0; i < SizeOfArray(csets); i++) { if (csets[i] >= 0) { temp[valid_csets] = i; valid_csets++; } } if (valid_csets > 0) { return temp[Rand(valid_csets)]; } else { return -1; } } else { for (int i = 0; i < SizeOfArray(csets); i++) { cset_index++; cset_index % SizeOfArray(csets); if (csets[cset_index] >= 0) { return cset_index; } } return -1; } } void SuckToBlowFlashCSet(ffc this, npc ghost, int wind_lines, int cset1, int cset2, int duration) { for (int i = 0; i < duration; i++) { if (i % 2 == 0) { Ghost_CSet = cset1; } else { Ghost_CSet = cset2; } SuckToBlow_Waitframe(this, ghost, wind_lines); } Ghost_CSet = cset2; } void SuckToBlow_Wind(int angle, int step) { eweapon wind = FireEWeapon(EW_SCRIPT1, Ghost_X, Ghost_Y, DegtoRad(angle), step, 0, 36, 59, EWF_NO_COLLISION); wind->Misc[0] = 23999; SetEWeaponLifespan(wind, EWL_SLOW_TO_HALT, step / 60); SetEWeaponDeathEffect(wind, EWD_VANISH, 0); wind->CollDetection = false; } void SuckToBlow_Waitframe(ffc this, npc ghost, int wind_lines) { eweapon wind; for (int i = 1; i <= Screen->NumEWeapons(); i++) { wind = Screen->LoadEWeapon(i); if (wind->ID == EW_SCRIPT1 && wind->Misc[0] == 23999 && LinkCollisionMod(wind)) { Link->Jump = 4; this->Misc[1] = 1; this->Misc[4] = RadtoDeg(wind->Angle); } } SuckToBlow_WindPush(this, ghost, wind_lines); Ghost_Waitframe(this, ghost); } void SuckToBlow_Waitframes(ffc this, npc ghost, int wind_lines, int num) { for (int i = 0; i < num; i++) { SuckToBlow_Waitframe(this, ghost, wind_lines); } } void SuckToBlow_WindPush(ffc this, npc ghost, int wind_lines) { int strength; float angle; float x_step; float y_step; float x = this->Misc[2]; float y = this->Misc[3]; float x_dir; float y_dir; bool suck; if (this->Misc[0] != 0 || this->Misc[1] > 0) { // SUCK if (this->Misc[0] < 0 && this->Misc[1] == 0) { angle = Angle(Link->X + 8, Link->Y + 8, Ghost_X + (0.5 * 16 * ghost->TileWidth), Ghost_Y + (0.5 * 16 * ghost->TileHeight)); suck = true; strength = SUCK_STRENGTH; } // BLOW else if (this->Misc[0] > 0 && this->Misc[1] == 0) { angle = Angle(Ghost_X + (0.5 * 16 * ghost->TileWidth), Ghost_Y + (0.5 * 16 * ghost->TileHeight), Link->X + 8, Link->Y + 8); suck = false; strength = BLOW_STRENGTH; } else { angle = this->Misc[4]; strength = BLOW_STRENGTH; } x_step = VectorX(strength, angle); y_step = VectorY(strength, angle); if (x_step > 0) { x_dir = DIR_RIGHT; } else { x_dir = DIR_LEFT; } if (y_step > 0) { y_dir = DIR_DOWN; } else { y_dir = DIR_UP; } if (CanWalk(Link->X, Link->Y, x_dir, x_step, false)) { x += x_step; if (x >= 1 || x <= -1) { Link->X += x << 0; x -= x << 0; } } if (CanWalk(Link->X, Link->Y, y_dir, y_step, false)) { y += y_step; if (y >= 1 || y <= -1) { Link->Y += y << 0; y -= y << 0; } } SuckToBlow_WindLines(this, ghost, wind_lines, suck); } if (this->Misc[0] > 0) { this->Misc[0]--; } if (this->Misc[0] < 0) { this->Misc[0]++; } if (this->Misc[1] > 0 && Link->Jump <= 0 && Link->Z == 0) { this->Misc[1] = 0; } this->Misc[2] = x; this->Misc[3] = y; } void SuckToBlow_WindLines(ffc this, npc ghost, int wind_lines, bool suck) { int line_x1; int line_y1; int line_x2; int line_y2; int step = 4; float angle; float new_dist; float new_angle; if (this->Misc[1] == 0) { for (int i = 0; i < SizeOfArray(wind_lines) * 0.25; i++) { line_x1 = wind_lines[i]; line_y1 = wind_lines[i + (SizeOfArray(wind_lines) * 0.25)]; line_x2 = wind_lines[i + (SizeOfArray(wind_lines) * 0.5)]; line_y2 = wind_lines[i + (SizeOfArray(wind_lines) * 0.75)]; if (suck) { angle = Angle(wind_lines[i], wind_lines[i + (SizeOfArray(wind_lines) * 0.25)], Ghost_X + (0.5 * 16 * ghost->TileWidth), Ghost_Y + (0.5 * 16 * ghost->TileHeight)); if (Distance(Ghost_X + (0.5 * 16 * ghost->TileWidth), Ghost_Y + (0.5 * 16 * ghost->TileHeight), wind_lines[i], wind_lines[i + (SizeOfArray(wind_lines) * 0.25)]) > 10) { line_x1 += VectorX(step, angle); line_y1 += VectorY(step, angle); } else if (Distance(wind_lines[i], wind_lines[i + (SizeOfArray(wind_lines) * 0.25)], wind_lines[i + (SizeOfArray(wind_lines) * 0.5)], wind_lines[i + (SizeOfArray(wind_lines) * 0.75)]) <= step) { new_dist = Rand(96, 112); new_angle = Rand(360); line_x1 = Ghost_X + VectorX(new_dist, new_angle); line_y1 = Ghost_Y + VectorY(new_dist, new_angle); line_x2 = line_x1; line_y2 = line_y1; } if (Distance(wind_lines[i], wind_lines[i + (SizeOfArray(wind_lines) * 0.25)], wind_lines[i + (SizeOfArray(wind_lines) * 0.5)], wind_lines[i + (SizeOfArray(wind_lines) * 0.75)]) >= 32 || (Distance(Ghost_X + (0.5 * 16 * ghost->TileWidth), Ghost_Y + (0.5 * 16 * ghost->TileHeight), wind_lines[i], wind_lines[i + (SizeOfArray(wind_lines) * 0.25)]) <= 10 && Distance(wind_lines[i], wind_lines[i + (SizeOfArray(wind_lines) * 0.25)], wind_lines[i + (SizeOfArray(wind_lines) * 0.5)], wind_lines[i + (SizeOfArray(wind_lines) * 0.75)]) > step)) { line_x2 += VectorX(step, angle); line_y2 += VectorY(step, angle); } } else { angle = Angle(Ghost_X + (0.5 * 16 * ghost->TileWidth), Ghost_Y + (0.5 * 16 * ghost->TileHeight), wind_lines[i], wind_lines[i + (SizeOfArray(wind_lines) * 0.25)]); if (Distance(Ghost_X + (0.5 * 16 * ghost->TileWidth), Ghost_Y + (0.5 * 16 * ghost->TileHeight), wind_lines[i], wind_lines[i + (SizeOfArray(wind_lines) * 0.25)]) < 144) { line_x1 += VectorX(step, angle); line_y1 += VectorY(step, angle); } else if (Distance(wind_lines[i], wind_lines[i + (SizeOfArray(wind_lines) * 0.25)], wind_lines[i + (SizeOfArray(wind_lines) * 0.5)], wind_lines[i + (SizeOfArray(wind_lines) * 0.75)]) <= step) { new_dist = Rand(0, 64); new_angle = Rand(360); line_x1 = Ghost_X + VectorX(new_dist, new_angle); line_y1 = Ghost_Y + VectorY(new_dist, new_angle); line_x2 = line_x1; line_y2 = line_y1; } if (Distance(wind_lines[i], wind_lines[i + (SizeOfArray(wind_lines) * 0.25)], wind_lines[i + (SizeOfArray(wind_lines) * 0.5)], wind_lines[i + (SizeOfArray(wind_lines) * 0.75)]) >= 32 || (Distance(Ghost_X + (0.5 * 16 * ghost->TileWidth), Ghost_Y + (0.5 * 16 * ghost->TileHeight), wind_lines[i], wind_lines[i + (SizeOfArray(wind_lines) * 0.25)]) >= 144 && Distance(wind_lines[i], wind_lines[i + (SizeOfArray(wind_lines) * 0.25)], wind_lines[i + (SizeOfArray(wind_lines) * 0.5)], wind_lines[i + (SizeOfArray(wind_lines) * 0.75)]) > step)) { line_x2 += VectorX(step, angle); line_y2 += VectorY(step, angle); } } Screen->Line(6, line_x1, line_y1, line_x2, line_y2, COLOR_WIND, 1, 0, 0, 0, OP_TRANS); wind_lines[i] = line_x1; wind_lines[i + (SizeOfArray(wind_lines) * 0.25)] = line_y1; wind_lines[i + (SizeOfArray(wind_lines) * 0.5)] = line_x2; wind_lines[i + (SizeOfArray(wind_lines) * 0.75)] = line_y2; } } } void SuckToBlow_WindLinesRandomize(ffc this, npc ghost, int wind_lines, bool suck) { int new_dist; int new_angle; for (int i = 0; i < SizeOfArray(wind_lines) * 0.25; i++) { if (suck) { new_dist = Rand(96, 112); } else { new_dist = Rand(0, 64); } new_angle = Rand(360); wind_lines[i] = Ghost_X + VectorX(new_dist, new_angle); wind_lines[i + (SizeOfArray(wind_lines) * 0.25)] = Ghost_Y + VectorY(new_dist, new_angle); wind_lines[i + (SizeOfArray(wind_lines) * 0.5)] = Ghost_X + VectorX(new_dist, new_angle); wind_lines[i + (SizeOfArray(wind_lines) * 0.75)] = Ghost_Y + VectorY(new_dist, new_angle); } } bool LinkCollisionMod(eweapon b) { int ax = Link->X + Link->HitXOffset; int bx = b->X + b->HitXOffset; int ay = Link->Y + Link->HitYOffset; int by = b->Y + b->HitYOffset; return RectCollision(ax+8, ay+8, ax+Link->HitWidth-8, ay+Link->HitHeight-8, bx, by, bx+b->HitWidth, by+b->HitHeight) && (Link->Z + Link->HitZHeight >= b->Z) && (Link->Z <= b->Z + b->HitZHeight); }