cakefoot/src/Character.cpp

282 lines
8.0 KiB
C++

#include "Character.hpp"
Character::Character(const Configuration& configuration, std::map<std::string, sb::audio::Chunk>& audio) :
configuration(configuration), audio(audio)
{
walk.frame_length(0.1f);
walk.play();
}
void Character::profile(const std::string& name)
{
/* Store the name of the profile so Character::profile() will access the profile with this name */
_profile = name;
/* Reload the texture */
_sprite.clear_textures();
for (const std::string& path : profile().at("animation frames"))
{
_sprite.texture(path);
}
_sprite.scale(size);
}
const nlohmann::json& Character::profile() const
{
for (const nlohmann::json& profile : configuration("character", "profile"))
{
if (profile["name"] == _profile)
{
return profile;
}
}
/* Raise if the profile does not exist */
std::stringstream message;
message << "The profile " << _profile << " was not found in the configuration";
throw std::runtime_error(message.str());
}
void Character::box_size(float size)
{
_box.size(size * 2.0f * this->size);
}
void Character::load()
{
_sprite.load();
}
const Box& Character::box() const
{
return _box;
}
const sb::Sprite& Character::sprite() const
{
return _sprite;
}
void Character::beginning(const Curve& curve)
{
checkpoint(0.0f);
spawn(curve);
}
void Character::spawn(const Curve& curve)
{
next_point_index = curve.index(checkpoint());
position = curve[next_point_index];
speed = 0.0f;
accelerating = false;
_resting = true;
audio.at("walk").stop();
audio.at("reverse").stop();
}
void Character::checkpoint(float checkpoint)
{
_checkpoint = checkpoint;
}
float Character::checkpoint() const
{
return _checkpoint;
}
bool Character::at_end(const Curve& curve) const
{
return next_point_index > curve.length() - 1;
}
bool Character::resting() const
{
return _resting;
}
float Character::relative(const Curve& curve) const
{
return float(next_point_index) / curve.length();
}
void Character::update(const Curve& curve, const sb::Timer& timer, bool muted)
{
if (timer.frame() > 0.0f)
{
/* Adjust speed based on acceleration state and character profile. */
if (accelerating)
{
_resting = false;
/* Apply delta time to the speed increase. */
speed += timer.delta(profile()["speed increment"].get<float>()) + glm::abs(speed) * profile()["increment mod"].get<float>();
}
else
{
/* Apply delta time to the speed decrease. */
speed -= timer.delta(profile()["speed decrement"].get<float>()) + glm::abs(speed) * profile()["decrement mod"].get<float>();
}
/* Clamp speed, applying delta time to the limits */
float max_speed = timer.delta(profile()["max speed"].get<float>());
float min_speed = timer.delta(profile()["min speed"].get<float>());
speed = std::clamp(speed, min_speed, max_speed);
/* Calculate volume based on speed relative to max speed */
int volume;
if (speed >= 0.0f)
{
/* Only play walking forward effect. */
audio.at("reverse").stop();
if (!audio.at("walk").playing())
{
audio.at("walk").play(0.0f, walk_channel);
}
if (!muted)
{
/* Get louder closer to max speed using an exponential scale */
volume = std::round(std::pow(speed / max_speed, 3.0f) * static_cast<float>(MIX_MAX_VOLUME));
audio.at("walk").channel_volume(volume);
}
else
{
audio.at("walk").stop();
}
}
else
{
/* Only play walking backward effect. */
audio.at("walk").stop();
if (!audio.at("reverse").playing())
{
audio.at("reverse").play(0.0f, reverse_channel);
}
if (!muted)
{
/* Get louder closer to min speed using an exponential scale */
volume = std::round(std::pow(speed / min_speed, 3.0f) * static_cast<float>(MIX_MAX_VOLUME));
audio.at("reverse").channel_volume(volume);
}
else
{
audio.at("reverse").stop();
}
}
/* Set the walk frame animation based on speed */
if (walk.update(timer.stamp()))
{
if (_resting)
{
_sprite.texture_index(0);
}
else
{
if (speed > 0.0f)
{
if (speed == max_speed)
{
walk.frame_length(0.1f);
}
else if (speed > max_speed / 2)
{
walk.frame_length(0.07f);
}
else
{
walk.frame_length(0.04f);
}
_sprite.texture_increment(1);
}
else
{
if (speed == min_speed)
{
walk.frame_length(0.1f);
}
else if (speed < max_speed / 2)
{
walk.frame_length(0.07f);
}
else
{
walk.frame_length(0.04f);
}
_sprite.texture_increment(-1);
}
}
}
/* Move along unwrapped curve vertices */
float distance_remaining = std::abs(speed), distance = 0.0f;
glm::vec3 next_point, step;
while (distance_remaining)
{
if (speed < 0.0f && (relative(curve) <= 0.0f || (resting() && relative(curve) <= checkpoint())))
{
_resting = true;
audio.at("walk").stop();
audio.at("reverse").stop();
speed = 0.0f;
break;
}
else if (speed > 0.0f && next_point_index > curve.length() - 1)
{
speed = 0.0f;
break;
}
if (speed > 0.0f)
{
next_point = curve[next_point_index];
}
else
{
next_point = curve[next_point_index - 1];
}
distance = glm::distance(position, next_point);
if (distance < distance_remaining)
{
distance_remaining -= distance;
position = next_point;
next_point_index += speed < 0.0f ? -1 : 1;
}
else
{
step = glm::vec3{sb::Segment(position, next_point).step(distance_remaining), 0.0f};
position += step;
distance_remaining = 0;
}
}
/* Update collision box location. */
_box.south(position);
}
}
void Character::draw(const Curve& curve, GLuint transformation_uniform, const glm::mat4& view, const glm::mat4& projection, GLuint texture_flag_uniform)
{
/* Position along the curve */
glm::vec2 translation = sb::wrap_point(glm::vec3{_box.center(), 0.0f}, {-curve.aspect, -1.0f, -1.0f}, {curve.aspect, 1.0f, 1.0f});
_sprite.translate(glm::vec3{translation, 0.0f});
/* Reflect if walking left */
if (curve[next_point_index].x < position.x)
{
_sprite.transform(glm::scale(glm::vec3{-1.0f, 1.0f, 1.0f}));
}
_sprite.draw(transformation_uniform, view, projection, texture_flag_uniform);
/* Draw hitbox if requested */
if (configuration("display", "hitbox"))
{
sb::Sprite hitbox;
hitbox.scale(glm::vec3{_box.w / 2.0f});
hitbox.translate(glm::vec3{translation, 0.0f});
glUniform1i(texture_flag_uniform, false);
hitbox.draw(transformation_uniform, view, projection, texture_flag_uniform);
}
}