import "std.zh"
// Misc. Attr. 1: lower bound for time spent underground
// Misc. Attr. 2: upper bound for time spent underground
// Misc. Attr. 3: surface time (by random chance)
// Misc. Attr. 4: surface time (by quake hammer)
// Misc. Attr. 5: detection distance for jumping out of the ground
// Misc. Attr. 6: aggression level, for attempts to get around solid tiles while chasing, 100 max
// Misc. Attr. 7: ignore solid tile (0 for obey solidity, nonzero for ignore)
// Misc. Attr. 8: tile offset for jumping tile
// Misc. Attr. 9: tile offset for burrowing tile
// Misc. Attr. 10: tile offset for halt tile
// Misc. Attr. 11: tile offset for emerge/submerge tile
npc script SeekerMole
{
bool SeekerMoleCanMoveX(npc mole, int step, int angle, int ignore_solid)
{
bool can_move_x = true;
for (int i = 2; i < 16; i++)
{
if (VectorX(step, angle) > 0)
{
if ((Screen->isSolid(mole->X + (16 * mole->TileWidth), mole->Y + i) && !ignore_solid) || !SeekerMoleAllowed(ComboAt(mole->X + (16 * mole->TileWidth), mole->Y + i)) || mole->X + (16 * mole->TileWidth) > 256)
{
can_move_x = false;
break;
}
}
else if (VectorX(step, angle) < 0)
{
if ((Screen->isSolid(mole->X - 1, mole->Y + i) && !ignore_solid) || !SeekerMoleAllowed(ComboAt(mole->X - 1, mole->Y + i)) || mole->X - 1 < 0)
{
can_move_x = false;
break;
}
}
}
return can_move_x;
}
bool SeekerMoleCanMoveY(npc mole, int step, int angle, int ignore_solid)
{
bool can_move_y = true;
for (int i = 2; i < 16; i++)
{
if (VectorY(step, angle) > 0)
{
if ((Screen->isSolid(mole->X + i, mole->Y + (16 * mole->TileHeight)) && !ignore_solid) || !SeekerMoleAllowed(ComboAt(mole->X + i, mole->Y + (16 * mole->TileHeight))) || mole->Y + (16 * mole->TileHeight) > 176)
{
can_move_y = false;
break;
}
}
else if (VectorY(step, angle) < 0)
{
if ((Screen->isSolid(mole->X + i, mole->Y - 1) && !ignore_solid) || !SeekerMoleAllowed(ComboAt(mole->X + i, mole->Y - 1)) || mole->Y - 1 < 0)
{
can_move_y = false;
break;
}
}
}
return can_move_y;
}
bool SeekerMoleMoveX(npc mole, int step, int angle, int ignore_solid)
{
float dx = VectorX(step, angle);
if (dx > 0)
{
while (dx > 0 && SeekerMoleCanMoveX(mole, step, angle, ignore_solid))
{
mole->X += Min(dx, 1);
dx -= 1;
}
return dx <= 0;
}
else if (dx < 0)
{
while (dx < 0 && SeekerMoleCanMoveX(mole, step, angle, ignore_solid))
{
mole->X += Max(dx, -1);
dx += 1;
}
return dx >= 0;
}
return true;
}
bool SeekerMoleMoveY(npc mole, int step, int angle, int ignore_solid)
{
float dy = VectorY(step, angle);
if (dy > 0)
{
while (dy > 0 && SeekerMoleCanMoveY(mole, step, angle, ignore_solid))
{
mole->Y += Min(dy, 1);
dy -= 1;
}
return dy <= 0;
}
else if (dy < 0)
{
while (dy < 0 && SeekerMoleCanMoveY(mole, step, angle, ignore_solid))
{
mole->Y += Max(dy, -1);
dy += 1;
}
return dy >= 0;
}
return true;
}
bool SeekerMoleRandomMovement(npc mole, int angle, float speed_multiplier, int ignore_solid)
{
return SeekerMoleMoveX(mole, (mole->Step / 100) * speed_multiplier, angle, ignore_solid) && SeekerMoleMoveY(mole, (mole->Step / 100) * speed_multiplier, angle, ignore_solid);
}
bool SeekerMoleChase(npc mole, int ignore_solid)
{
int angle = Angle(mole->X + (8 * mole->TileWidth), mole->Y + (8 * mole->TileHeight), Player->X + 8, Player->Y + 8);
return SeekerMoleMoveX(mole, (mole->Step / 100), angle, ignore_solid) && SeekerMoleMoveY(mole, (mole->Step / 100), angle, ignore_solid);
}
bool SeekerMoleAllowed(int combo_pos)
{
return (Screen->ComboT[combo_pos] != CT_NOENEMY && Screen->ComboT[combo_pos] != CT_NOGROUNDENEMY && Screen->ComboT[combo_pos] != CT_NOFLYZONE && Screen->ComboF[combo_pos] != CF_NOENEMY && Screen->ComboF[combo_pos] != CF_NOGROUNDENEMY);
}
int SeekerMoleFindPath(npc mole, int step, int ignore_solid)
{
int paths[4];
int path_count;
if (SeekerMoleCanMoveY(mole, step, -90, ignore_solid))
{
paths[path_count] = Rand(-110, -70);
path_count++;
}
if (SeekerMoleCanMoveY(mole, step, 90, ignore_solid))
{
paths[path_count] = Rand(70, 110);
path_count++;
}
if (SeekerMoleCanMoveY(mole, step, 180, ignore_solid))
{
paths[path_count] = Rand(160, 200);
path_count++;
}
if (SeekerMoleCanMoveY(mole, step, 0, ignore_solid))
{
paths[path_count] = Rand(-20, 20);
path_count++;
}
return paths[Rand(path_count)];
}
void run()
{
// default values
int underground_time_lower = this->Attributes[0] ? this->Attributes[0] : 120;
int underground_time_upper = this->Attributes[1] ? this->Attributes[1] : 360;
int surface_time_random = this->Attributes[2] ? this->Attributes[2] : 60;
int surface_time_quake = this->Attributes[3] ? this->Attributes[3] : 120;
int detect_distance = this->Attributes[4] ? this->Attributes[4] : 10;
int aggression = Min(100, this->Attributes[5]);
int ignore_solid = this->Attributes[6];
int tile_jump = this->Attributes[7] ? this->Attributes[7] : 20;
int tile_burrow = this->Attributes[8] ? this->Attributes[8] : 40;
int tile_halt = this->Attributes[9] ? this->Attributes[9] : 60;
int tile_emerge = this->Attributes[10] ? this->Attributes[10] : 80;
int angle = Rand(360);
int direction_change_counter = Rand(20, 80);
int no_interrupt_timer = 90;
int speed_multiplier = 2;
int surface_timer = Rand(underground_time_lower, underground_time_upper);
int original_tile = this->OriginalTile;
this->CollDetection = false;
while (true)
{
if (!SeekerMoleAllowed(ComboAt(Player->X + 8, Player->Y + 8)) || no_interrupt_timer > 0)
{
if (!SeekerMoleRandomMovement(this, angle, speed_multiplier, ignore_solid))
{
angle = Rand(360);
direction_change_counter = Rand(20, 80);
}
}
else
{
if (!SeekerMoleChase(this, ignore_solid) && Rand(120 - aggression) == 0)
{
angle = SeekerMoleFindPath(this, this->Step / 100, ignore_solid);
no_interrupt_timer = Rand(30, 50);
speed_multiplier = 2;
direction_change_counter = Rand(no_interrupt_timer, 80);
}
}
// pop up if under player
if (Abs((this->X + (8 * this->TileWidth)) - (Player->X + 8)) < detect_distance && Abs((this->Y + (8 * this->TileHeight)) - (Player->Y + 8)) < detect_distance && no_interrupt_timer <= 0)
{
Game->PlaySound(SFX_JUMP);
this->OriginalTile = original_tile + tile_jump;
this->CollDetection = true;
this->Jump = 4;
this->Z = 1;
while (this->Z > 0)
{
Waitframe();
}
this->OriginalTile = original_tile + tile_burrow;
Waitframes(20);
// submerge again
this->OriginalTile = original_tile; // underground tile
this->CollDetection = false;
Waitframes(20);
no_interrupt_timer = 60;
speed_multiplier = 2;
}
// surface if quake effect activates or surface timer expires
if (Screen->Quake > 0 || surface_timer <= 0)
{
this->OriginalTile = original_tile + tile_emerge;
Waitframes(10);
this->OriginalTile = original_tile + tile_halt;
this->CollDetection = true;
surface_timer = (Screen->Quake > 0) ? surface_time_quake : surface_time_random; // less time surfaced by random chance, more for quake
while (Screen->Quake > 0 || surface_timer > 0)
{
Waitframe();
surface_timer--;
}
// submerge again
this->CollDetection = false;
this->OriginalTile = original_tile + tile_emerge;
Waitframes(10);
this->OriginalTile = original_tile;
Waitframes(20);
no_interrupt_timer = 60;
speed_multiplier = 2;
surface_timer = Rand(underground_time_lower, underground_time_upper);
}
if (direction_change_counter > 0)
{
direction_change_counter--;
}
if (direction_change_counter <= 0)
{
direction_change_counter = Rand(20, 80);
angle = Rand(360);
}
if (no_interrupt_timer > 0)
{
no_interrupt_timer--;
}
if (no_interrupt_timer <=0 && speed_multiplier > 1)
{
speed_multiplier = 1;
}
if (no_interrupt_timer <= 0)
{
surface_timer--;
}
Waitframe();
}
}
}