cakefoot/src/Enemy.cpp

554 lines
16 KiB
C++

#include "Enemy.hpp"
void Enemy::reset()
{
if (!coin_collected)
{
_coin_taken = false;
}
}
bool Enemy::collide(const sb::Box& box, const sb::Sprite& sprite, const glm::vec3& clip_lower, const glm::vec3& clip_upper) const
{
sb::Box wrapped = this->box;
wrapped.center(sb::wrap_point({this->box.center(), 0.0f}, clip_lower, clip_upper));
sb::Box wrapped_other = box;
wrapped_other.center(sb::wrap_point({box.center(), 0.0f}, clip_lower, clip_upper));
return wrapped.collide(wrapped_other);
}
bool Enemy::collide_coin(sb::Box box, const glm::vec3& clip_lower, const glm::vec3& clip_upper) const
{
/* If there is no coin, return false immediately */
if (!_coin.has_value() || coin_taken() || coin_collected)
{
return false;
}
else
{
/* Returning true defers the full check to the derived class */
return true;
}
}
void Enemy::take_coin()
{
if (_coin.has_value())
{
_coin_taken = true;
}
}
bool Enemy::coin_taken() const
{
return _coin_taken;
}
void Enemy::collect_coin()
{
if (_coin.has_value())
{
coin_collected = true;
}
}
void Enemy::draw(GLuint transformation_uniform, const glm::mat4& view, const glm::mat4& projection, GLuint texture_flag_uniform,
const sb::Color& rotating_hue, GLuint color_addition_uniform)
{
sprite.draw(transformation_uniform, view, projection, texture_flag_uniform);
if (_coin.has_value() && !coin_taken() && !coin_collected)
{
glUniform4fv(color_addition_uniform, 1, &rotating_hue.normal()[0]);
_coin->draw(transformation_uniform, view, projection, texture_flag_uniform);
glUniform4fv(color_addition_uniform, 1, &glm::vec4(0)[0]);
}
}
Slicer::Slicer(const Curve& curve, float relative, float speed, float stray) : curve(curve), relative(relative), speed(speed), stray(stray)
{
position = start();
sprite.texture("resource/slicer/slicer-1.png");
sprite.texture("resource/slicer/slicer-2.png");
sprite.frames.frame_length(0.5f);
sprite.frames.play();
/* Get size as the pixels divided by the original resolution */
float size = 12.0f / 486.0f;
/* Apply size to sprite and box objects */
sprite.scale(glm::vec2{size});
box.size(2.0f * glm::vec2{size});
};
void Slicer::coin(const sb::Sprite& sprite, float radius, float angle)
{
_coin = sprite;
coin_radius = radius;
coin_angle = angle;
}
void Slicer::update(const sb::Timer& timer)
{
sprite.update(timer);
float move = timer.delta(speed);
glm::vec3 destination;
/* Use a loop to move repeatedly between start and end when there is a long move, for example when there is major lag between frames. */
while (move > 0.0f)
{
if (toward_end)
{
destination = end();
}
else
{
destination = start();
}
float distance = glm::distance(position, destination);
if (distance < move)
{
position = destination;
move -= distance;
toward_end = !toward_end;
}
else
{
position += glm::vec3{sb::Segment(position, destination).step(move), 0.0f};
move = 0.0f;
}
}
/* Move hit box */
box.center(position);
/* Update challenge coin position */
if (_coin.has_value() && !coin_taken() && !coin_collected)
{
glm::vec3 coin_position = position + glm::vec3{sb::angle_to_vector(coin_angle, coin_radius), 0.0f};
_coin->translate(curve.get().wrap(coin_position));
}
/* Place sprite */
sprite.translate(curve.get().wrap(position));
}
glm::vec3 Slicer::center() const
{
return curve.get().relative(relative);
}
float Slicer::angle() const
{
int index = curve.get().index(relative);
int next_index = std::min(index + 1, curve.get().length() - 1);
int prev_index = std::max(index - 1, 0);
return sb::angle_between(curve.get()[prev_index], curve.get()[next_index]) + glm::half_pi<float>();
}
glm::vec3 Slicer::start() const
{
return center() + glm::vec3{sb::angle_to_vector(angle(), stray), 0.0f};
}
glm::vec3 Slicer::end() const
{
return center() + glm::vec3{sb::angle_to_vector(angle() + glm::pi<float>(), stray), 0.0f};
}
bool Slicer::collide_coin(sb::Box box, const glm::vec3& clip_lower, const glm::vec3& clip_upper) const
{
if (Enemy::collide_coin(box, clip_lower, clip_upper))
{
sb::Box coin_box = this->box;
coin_box.move(glm::vec3{sb::angle_to_vector(coin_angle, coin_radius), 0.0f});
coin_box.center(sb::wrap_point({coin_box.center(), 0.0f}, clip_lower, clip_upper));
box.center(sb::wrap_point({box.center(), 0.0f}, clip_lower, clip_upper));
return coin_box.collide(box);
}
return false;
}
Fish::Fish(const Curve& curve, float relative, float speed, float radius, float offset) :
curve(curve), relative(relative), speed(speed), radius(radius), offset(offset)
{
sprite.texture("resource/fish/fish-1.png");
sprite.texture("resource/fish/fish-2.png");
sprite.frames.frame_length(0.3f);
sprite.frames.play();
/* Set size of objects */
glm::vec2 size {12.0f / 486.0f};
sprite.scale(size);
box.size(2.0f * size);
};
void Fish::coin(const sb::Sprite& sprite, float angle)
{
_coin = sprite;
coin_angle = angle;
}
void Fish::coin(const sb::Sprite& sprite, float radius, float angle)
{
coin_radius = radius;
Fish::coin(sprite, angle);
}
void Fish::update(const sb::Timer& timer)
{
sprite.update(timer);
/* On the first update, record the timestamp of the timer, so the difference between subsequent timestamps and the first timestamp can
* be used to measure the angle position of the fish. Do this instead of adding to the angle position every frame to avoid letting
* floating point addition error accumulate differently among fish with different speeds. */
if (!first_update_time.has_value())
{
first_update_time = timer.elapsed();
}
/* The angle position is the seconds since the first update multiplied by radians per second plus the fish's given angle offset. */
angle = (timer.elapsed() - *first_update_time) * speed + offset;
/* Place the fish an amount away from its center using the angle and radius to get the vector. */
glm::vec3 position = center() + glm::vec3{sb::angle_to_vector(angle, radius), 0.0f};
/* Update challenge coin position */
if (_coin.has_value() && !coin_taken() && !coin_collected)
{
glm::vec3 coin_position;
/* Place coin either along the fish's circle or translated a vector away from the fish, depending on whether or not the radius is set. */
if (coin_radius == 0.0f)
{
coin_position = center() + glm::vec3{sb::angle_to_vector(angle + coin_angle, radius), 0.0f};
}
else
{
coin_position = position + glm::vec3{sb::angle_to_vector(coin_angle, coin_radius), 0.0f};
}
_coin->translate(curve.get().wrap(coin_position));
}
sprite.translate(curve.get().wrap(position));
box.center(position);
}
glm::vec3 Fish::center() const
{
return curve.get().relative(relative);
}
bool Fish::collide_coin(sb::Box box, const glm::vec3& clip_lower, const glm::vec3& clip_upper) const
{
if (Enemy::collide_coin(box, clip_lower, clip_upper))
{
sb::Box coin_box = this->box;
coin_box.center(sb::wrap_point(center() + glm::vec3{sb::angle_to_vector(angle + coin_angle, radius), 0.0f}, clip_lower, clip_upper));
box.center(sb::wrap_point({box.center(), 0.0f}, clip_lower, clip_upper));
return coin_box.collide(box);
}
return false;
}
Projectile::Projectile(const glm::vec3& position, const glm::vec3& target, float speed) : position(position), target(target), speed(speed)
{
/* Set angle once at construction so it will continue moving past the target once it reaches. */
angle = sb::angle_between(position, target);
/* Initialize sprite */
sprite.texture("resource/projectile/projectile-1.png");
sprite.texture("resource/projectile/projectile-2.png");
sprite.texture("resource/projectile/projectile-3.png");
sprite.texture("resource/projectile/projectile-4.png");
sprite.frames.frame_length(0.05f);
sprite.frames.play();
/* Set size of objects */
glm::vec2 size {12.0f / 486.0f};
sprite.scale(size);
box.size(2.0f * size);
/* Place objects at initial position */
sprite.translate(position);
box.center(position);
};
void Projectile::coinify(const sb::Sprite& sprite)
{
_coin = sprite;
}
void Projectile::update(const sb::Timer& timer)
{
sprite.update(timer);
/* Move */
position += glm::vec3{sb::angle_to_vector(angle, timer.delta(speed)), 0.0f};
sprite.translate(position);
/* Update challenge coin */
if (_coin.has_value())
{
_coin->translate(position);
}
box.center(position);
}
void Projectile::draw(GLuint transformation_uniform, const glm::mat4& view, const glm::mat4& projection, GLuint texture_flag_uniform,
const sb::Color& rotating_hue, GLuint color_addition_uniform)
{
if (_coin.has_value())
{
if (!coin_taken() && !coin_collected)
{
glUniform4fv(color_addition_uniform, 1, &rotating_hue.normal()[0]);
_coin->draw(transformation_uniform, view, projection, texture_flag_uniform);
glUniform4fv(color_addition_uniform, 1, &glm::vec4(0)[0]);
}
}
else
{
sprite.draw(transformation_uniform, view, projection, texture_flag_uniform);
}
}
bool Projectile::out_of_bounds() const
{
return position.x > 1.777f || position.x < -1.777f || position.y > 1.0f || position.y < -1.0f;
}
bool Projectile::coinified() const
{
return _coin.has_value();
}
Projector::Projector(const Character& character, const glm::vec3& position, float speed, float rate, float release_delay) :
character(character), position(position), speed(speed), rate(rate), release_delay(release_delay)
{
animation_charge.frame_length(rate);
sprite.texture("resource/projector/projector-1.png");
sprite.texture("resource/projector/projector-2.png");
/* Set size and position of objects */
glm::vec2 size {12.0f / 486.0f};
sprite.scale(size);
sprite.translate(position);
box.size(2.0f * size);
box.center(position);
/* Charge animation will always be playing */
animation_charge.play();
};
void Projector::reset()
{
Enemy::reset();
projectiles.clear();
animation_release.pause();
count = 0;
}
void Projector::coin(const sb::Sprite& sprite, int frequency)
{
_coin = sprite;
coin_frequency = frequency;
}
void Projector::update(const sb::Timer& timer)
{
sprite.update(timer);
/* Update the animation timers */
animation_charge.update(timer.stamp());
animation_release.update(timer.stamp());
/* Erase projectiles which have gone off screen */
projectiles.erase(
std::remove_if(
projectiles.begin(),
projectiles.end(),
[](const Projectile& projectile)
{
return projectile.out_of_bounds();
}),
projectiles.end());
/* Update projectiles */
for (Projectile& projectile : projectiles)
{
projectile.update(timer);
}
}
bool Projector::collide(const sb::Box& box, const sb::Sprite& sprite, const glm::vec3& clip_lower, const glm::vec3& clip_upper) const
{
for (const Projectile& projectile : projectiles)
{
if (!projectile.coinified() && projectile.collide(box, sprite, clip_lower, clip_upper))
{
return true;
}
}
return false;
}
void Projector::take_coin()
{
Enemy::take_coin();
/* Erase coinified projectiles */
projectiles.erase(
std::remove_if(
projectiles.begin(),
projectiles.end(),
[](const Projectile& projectile)
{
return projectile.coinified();
}),
projectiles.end());
}
bool Projector::collide_coin(sb::Box box, const glm::vec3& clip_lower, const glm::vec3& clip_upper) const
{
if (Enemy::collide_coin(box, clip_lower, clip_upper))
{
for (const Projectile& projectile : projectiles)
{
if (projectile.coinified() && projectile.collide(box, sb::Sprite(), clip_lower, clip_upper))
{
return true;
}
}
}
return false;
}
void Projector::charge()
{
sprite.texture_index(1);
animation_release.play_once(release_delay);
}
void Projector::release()
{
sprite.texture_index(0);
/* Keep a count of how many projectiles have been fired */
count++;
/* Temporarily hard code the wrapping until figuring out how to reference the current curve in this function. */
glm::vec3 point = sb::wrap_point(character.position, {-1.77777f, -1.0f, 0.0f}, {1.77777f, 1.0f, 1.0f});
projectiles.emplace_back(position, point, speed);
/* Fire a challenge coin every frequency time if frequency is non-zero and coin has not been taken */
if (coin_frequency > 0 && _coin.has_value() && !coin_taken() && count % coin_frequency == 0)
{
projectiles.back().coinify(*_coin);
}
}
void Projector::draw(GLuint transformation_uniform, const glm::mat4& view, const glm::mat4& projection, GLuint texture_flag_uniform,
const sb::Color& rotating_hue, GLuint color_addition_uniform)
{
sprite.draw(transformation_uniform, view, projection, texture_flag_uniform);
for (Projectile& projectile : projectiles)
{
projectile.draw(transformation_uniform, view, projection, texture_flag_uniform, rotating_hue, color_addition_uniform);
}
}
Flame::Flame(const sb::Box& field, const glm::vec3& position, float speed, float angle, float mirror_interval, bool camo) :
field(field), position(position), speed(speed), angle(angle), _camo(camo)
{
/* Save initial values */
initial_angle = angle;
initial_position = position;
/* Set up animation */
if (!camo)
{
sprite.texture("resource/flame/flame-1.png");
sprite.texture("resource/flame/flame-2.png");
}
else
{
sprite.texture("resource/coin/coin-0.png");
}
sprite.frames.frame_length(0.3f);
sprite.frames.play();
/* Initialize object size and position */
glm::vec2 size {12.0f / 486.0f};
sprite.scale(size);
sprite.translate(position);
box.size(2.0f * size);
box.center(position);
/* Only start the mirror effect if the interval is a positive value. */
if (mirror_interval >= 0.0f)
{
animation_mirror.frame_length(mirror_interval);
animation_mirror.play();
}
};
void Flame::coin(const sb::Sprite& sprite, float radius, float angle)
{
_coin = sprite;
coin_radius = radius;
coin_angle = angle;
}
void Flame::update(const sb::Timer& timer)
{
sprite.update(timer);
/* Move */
animation_mirror.update(timer.stamp());
position += glm::vec3{sb::angle_to_vector(angle, timer.delta(speed)), 0.0f};
/* Update challenge coin position */
if (_coin.has_value() && !coin_taken() && !coin_collected)
{
glm::vec3 coin_position = position + glm::vec3{sb::angle_to_vector(coin_angle, coin_radius), 0.0f};
_coin->translate(sb::wrap_point(coin_position, glm::vec3{field.left(), field.bottom(), -1.0f}, glm::vec3{field.right(), field.top(), 1.0f}));
}
box.center(position);
sprite.translate(sb::wrap_point(position, glm::vec3{field.left(), field.bottom(), -1.0f}, glm::vec3{field.right(), field.top(), 1.0f}));
}
void Flame::mirror()
{
/* Reset the flame position and angle at the end of a cycle so overflow in time and movement don't begin to accumulate and desync the mirror cycle. */
if (mirrored)
{
angle = initial_angle;
position = initial_position;
}
else
{
angle += glm::pi<float>();
}
/* Indicate whether flame is moving away from its original position or toward it. */
mirrored = !mirrored;
}
bool Flame::collide_coin(sb::Box box, const glm::vec3& clip_lower, const glm::vec3& clip_upper) const
{
if (Enemy::collide_coin(box, clip_lower, clip_upper))
{
sb::Box coin_box = this->box;
coin_box.move(glm::vec3{sb::angle_to_vector(coin_angle, coin_radius), 0.0f});
coin_box.center(sb::wrap_point({coin_box.center(), 0.0f}, clip_lower, clip_upper));
box.center(sb::wrap_point({box.center(), 0.0f}, clip_lower, clip_upper));
return coin_box.collide(box);
}
return false;
}