cakefoot/src/Enemy.hpp

430 lines
14 KiB
C++

#pragma once
#include <optional>
#include <functional>
#include "glm/glm.hpp"
#include "Timer.hpp"
#include "Box.hpp"
#include "Animation.hpp"
#include "Curve.hpp"
#include "Sprite.hpp"
#include "Character.hpp"
class Enemy
{
protected:
sb::Sprite sprite;
sb::Box box;
/* Enemies have an optional challenge coin defined by different parameters depending on the enemy type. */
std::optional<sb::Sprite> _coin;
bool _coin_taken = false, coin_collected = false;
/*!
* Default destructor, defined as virtual so that the auto generated derived destructors will be called.
*/
virtual ~Enemy() = default;
public:
/*!
* Reset enemy to original state. Does nothing unless the derived class implements a reset method.
*/
virtual void reset();
/*!
* Set the object's coin taken state to true, so the coin will be able to be collected if the checkpoint or end
* is reached.
*/
virtual void take_coin();
/*!
* @return coin taken state
*/
bool coin_taken() const;
/*!
* Set object's coin collected state to true, so even if the enemy is reset, the coin will remain taken and collected.
*/
virtual void collect_coin();
/*!
* Set the position of the enemy's coin. If the enemy object has no coin, do nothing.
*
* @param translation coin location
*/
void coin_translation(const glm::vec3& translation);
/*!
* @param timer timer that has been updating every unpaused frame to be used to measure distance to move this frame
*/
virtual void update(const sb::Timer& timer) = 0;
/*!
* Perform GL drawing operations using the given uniform locations and transformation matrices.
*
* @param transformation_uniform transformation uniform location in the shader program
* @param view view transformation matrix
* @param projection projection transformation matrix
* @param texture_flag_uniform uniform location in the shader program of the boolean that turns texture drawing on or off
* @param rotating_hue color of the hue rotation effect
* @param color_addition_uniform uniform location in the shader program of the RGBA value for color addition effect
*/
virtual void 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);
/*!
* Check whether the enemy collides with the given ::Sprite positioned at the given ::Box. The sprite object may be used to perform
* pixel collision, so alpha pixels that collide will not be counted as a collision, but this is not implemented yet.
*
* The enemy and character are tracked without wrapping their coordinates, so the clip for wrapping must be given to this function
* in order to determine if they collide.
*
* @param box position in world coordinates to check for a collision with the enemy's own box object
* @param sprite graphics to use for per pixel collision
* @param clip_lower clip area lower bounds to wrap the position to
* @param clip_upper clip area upper bounds to wrap the position to
* @return true if the given box collides with this enemy's box
*/
virtual bool collide(const sb::Box& box, const sb::Sprite& sprite, const glm::vec3& clip_lower, const glm::vec3& clip_upper) const;
/*!
* Check whether the given box coordinates collide with the enemy's challenge coin. If the enemy doesn't have a challenge coin, this
* returns false.
*
* The enemy and character are tracked without wrapping their coordinates, so the clip for wrapping must be given to this function
* in order to determine if they collide.
*
* @param box position in world coordinates to check for a collision with the enemy's challenge coin
* @param clip_lower clip area lower bounds to wrap the position to
* @param clip_upper clip area upper bounds to wrap the position to
* @return true if the given box collides with the enemy's challenge coin
*/
virtual bool collide_coin(sb::Box box, const glm::vec3& clip_lower, const glm::vec3& clip_upper) const;
};
/*!
* Create an enemy which moves back and forth, cutting the curve perpendicular to the angle of the curve at a given point.
*/
class Slicer : public Enemy
{
private:
std::reference_wrapper<const Curve> curve;
float relative = 0.0f, speed = 0.0f, stray = 0.0f, coin_radius = 0.0f, coin_angle = 0.0f;
bool toward_end = true;
/*!
*/
glm::vec3 center() const;
/*!
*/
float angle() const;
/*!
*/
glm::vec3 start() const;
/*!
*/
glm::vec3 end() const;
public:
glm::vec3 position = {0.0f, 0.0f, 0.0f};
/*!
*/
Slicer(const Curve& curve, float relative, float speed = 0.51440329f, float stray = 0.24691358f);
/*!
* Add a challenge coin to this slicer enemy. The sprite will be owned, moved, and drawn by this object.
*
* @param sprite representation of the coin
* @param radius distance from the center of the slicer object
* @param angle angle position around the slicer object
*/
void coin(const sb::Sprite& sprite, float radius, float angle);
/*!
* If the slicer has a challenge coin, check if it collides with the given box.
*
* @see Enemy::collide_coin(const sb::Box&, const glm::vec3&, const glm::vec3&)
*/
bool collide_coin(sb::Box box, const glm::vec3& clip_lower, const glm::vec3& clip_upper) const;
/*!
*/
void update(const sb::Timer& timer);
};
/*!
* Create an enemy which rotates around a point on the curve at a given radius and speed.
*/
class Fish : public Enemy
{
private:
std::reference_wrapper<const Curve> curve;
float relative = 0.0f, speed = 0.0f, radius = 0.0f, angle = 0.0f, offset = 0.0f, coin_angle = 0.0f, coin_radius = 0.0f;
std::optional<float> first_update_time;
/*!
* @return vertex in world coordinates the fish is rotating around, always lies on the curve
*/
glm::vec3 center() const;
public:
/*!
* Create a Fish object from relative position on a given curve, speed, and radius. Optionally, an offset angle can be added to start
* the fish that amount away on its circle.
*
* The speed is in radians to rotate per second.
*
* @param curve curve to associate this enemy with
* @param relative the position on the curve from 0 to 1
* @param speed radians to rotate per second
* @param radius distance from the center to rotate around
* @param offset angle amount in radians to offset start point
*/
Fish(const Curve& curve, float relative, float speed = 0.01010029f, float radius = 0.12345679f, float offset = 0.0f);
/*!
* Add a challenge coin to this fish enemy. The coin will rotate on the same circle as the fish, offset by the given angle. The sprite will
* be owned, moved, and drawn by this object.
*
* @param sprite representation of the coin
* @param angle amount in radians to offset from the fish
*/
void coin(const sb::Sprite& sprite, float angle);
/*!
* Add a challenge coin to this fish enemy. The coin will be positioned at the given distance and angle away from the fish. The sprite will
* be owned, moved, and drawn by this object.
*
* @param sprite representation of the coin
* @param radius distance of the coin from the center of the fish
* @param angle angle around the fish's center to position the coin
*/
void coin(const sb::Sprite& sprite, float radius, float angle);
/*!
* If the fish has a challenge coin, check if it collides with the given box.
*
* @see Enemy::collide_coin(const sb::Box&, const glm::vec3&, const glm::vec3&)
*/
bool collide_coin(sb::Box box, const glm::vec3& clip_lower, const glm::vec3& clip_upper) const;
/*!
*/
void update(const sb::Timer& timer);
};
/*!
* Create an enemy which is a projectile that has been fired toward a location by a ::Projector object. It continues travelling in
* the same direction even after reaching the point, and goes off screen.
*/
class Projectile : public Enemy
{
private:
glm::vec3 position = glm::vec3{0.0f}, target = glm::vec3{0.0f};
float speed = 0.0f, angle = 0.0f;
public:
/*!
* Create a ::Projectile object, an ::Enemy directed toward a destination, given the destination, start, and speed.
*
* The speed is in world coordinates per second.
*
* @param position
* @param target
* @param speed distance in world coordinates the projectiles will travel per second
*/
Projectile(const glm::vec3& position, const glm::vec3& target, float speed = 0.51440329f);
/*!
* Turn the projectile into a coin.
*
* @param sprite sprite representing the coin, will be copied into the object, will be moved and drawn by the object
*/
void coinify(const sb::Sprite& sprite);
/*!
*/
void update(const sb::Timer& timer);
/*!
*/
void 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);
/*!
* @return true if the projectile has left the clip space
*/
bool out_of_bounds() const;
/*!
* @return return if the projectile was transformed into a challenge coin
*/
bool coinified() const;
};
/*!
* Create an enemy which fires projectiles toward the player's position.
*/
class Projector : public Enemy
{
private:
const Character& character;
glm::vec3 position = glm::vec3{0.0f};
float speed = 0.0f;
float release_delay;
sb::Animation animation_charge = sb::Animation(std::bind(&Projector::charge, this)),
animation_release = sb::Animation(std::bind(&Projector::release, this));
std::vector<Projectile> projectiles;
int coin_frequency = 0, count = 0;
void charge();
void release();
public:
/*!
* Create a Projector object at a given position. Set the speed and rate of fire.
*
* The speed is in world coordinates per second, and the frequency is number of seconds between each projectile launch.
*
* @param position location to fire from in world coordinates
* @param speed distance in world coordinates the projectiles will travel per second
* @param rate amount of seconds between each projectile
* @param release_delay amount in seconds between charge and release
*/
Projector(const Character& character, const glm::vec3& position, float speed = 0.51440329f, float rate = 3.0f, float release_delay = 0.3f);
/*!
*/
void reset();
/*!
* Add a challenge coin to this projector enemy. The projector will transform every Nth projectile into a coin.
*
* @param sprite sprite to represent the coin, will be copied to the Nth projectile
* @param frequency every Nth projectile will be a challenge coin
*/
void coin(const sb::Sprite& sprite, int frequency);
/*!
* If the projector has a challenge coin, check if it collides with the given box.
*
* @see Enemy::collide_coin(const sb::Box&, const glm::vec3&, const glm::vec3&)
*/
bool collide_coin(sb::Box box, const glm::vec3& clip_lower, const glm::vec3& clip_upper) const;
/*!
* Remove all projectiles that are coinified.
*/
void take_coin();
/*!
*/
void update(const sb::Timer& timer);
/*!
*/
void 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);
/*!
* Check if projectiles collide.
*
* @see Enemy::collide(const sb::Box&, const Sprite&, const glm::vec3&, const glm::vec3&)
*/
bool collide(const sb::Box& box, const sb::Sprite& sprite, const glm::vec3& clip_lower, const glm::vec3& clip_upper) const;
};
/*!
* Create an enemy which is a flame that constantly scrolls with a given angle direction and speed from its initial position, optionally
* mirroring to scroll the opposite direction at a given interval. The enemy wraps at the edges of the screen, so it is always on screen.
*/
class Flame : public Enemy
{
private:
sb::Box field;
glm::vec3 position = glm::vec3{0.0f}, initial_position = glm::vec3{0.0f};
float speed = 0.0f, angle = 0.0f, coin_angle = 0.0f, coin_radius = 0.0f, initial_angle = 0.0f;
sb::Animation animation_mirror = sb::Animation(std::bind(&Flame::mirror, this));
bool mirrored = false, _camo = false;
/*!
* Rotate direction 180 degrees.
*/
void mirror();
public:
/*!
* Create a ::Flame object, a scrolling ::Enemy moving away from the given start position with the given angle and speed. If
* mirror interval is given, the flame object will switch to the opposite direction every interval.
*
* The speed is in world coordinates per second.
*
* @param field
* @param position starting position
* @param speed distance in world coordinates the flame will travel per second
* @param angle direction the flame will travel
* @param mirror_interval optional interval in seconds the direction should mirror, negative values turn off mirroring
* @param camo disguise the flame as a coin
*/
Flame(const sb::Box& field, const glm::vec3& position, float speed, float angle, float mirror_interval = -1.0f, bool camo = false);
/*!
* Add a challenge coin to this flame enemy. The sprite will be owned, moved, and drawn by this object.
*
* @param sprite representation of the coin
* @param radius distance from the center of the flame object
* @param angle angle position around the slicer object
*/
void coin(const sb::Sprite& sprite, float radius, float angle);
/*!
* If the flame has a challenge coin, check if it collides with the given box.
*
* @see Enemy::collide_coin(const sb::Box&, const glm::vec3&, const glm::vec3&)
*/
bool collide_coin(sb::Box box, const glm::vec3& clip_lower, const glm::vec3& clip_upper) const;
inline void camo(bool state)
{
_camo = state;
}
bool camo() const
{
return _camo;
}
/*!
*/
void update(const sb::Timer& timer);
};