Commit 136fa6bc authored by louiz’'s avatar louiz’

Implement attack animations

And fix a few related things in Tasks and Works
parent 440cb7bc
......@@ -353,6 +353,7 @@ void GameClient::on_entity_task_changed(const Entity* entity, const Task* task)
this->sounds_handler.play(SoundType::Dash, false, 100.f);
break;
}
this->camera.on_entity_task_changed(entity, task);
}
void GameClient::on_entity_deleted(const Entity* entity)
......
......@@ -9,6 +9,7 @@
#include <world/layer.hpp>
#include <world/entity.hpp>
#include <world/team.hpp>
#include <world/task.hpp>
#include <gui/utils.hpp>
#include <game/game_client.hpp>
#include <climits>
......@@ -727,6 +728,43 @@ void Camera::draw_energy_bar(sf::Vector2f screen_position, const EnergyBar& bar_
}
}
void Camera::draw_vertical_bar(sf::Vector2f screen_position, const EnergyBar& bar_specs,
const std::size_t max_val, int current_val)
{
sf::RectangleShape rect;
rect.setSize(bar_specs.size);
rect.setOutlineColor(sf::Color::Black);
rect.setOutlineThickness(1);
rect.setFillColor({25, 25, 25, 200});
screen_position.x -= bar_specs.size.x/2;
screen_position.y -= bar_specs.size.y/2;
rect.setPosition(screen_position);
float grad_width = bar_specs.size.x / (max_val / bar_specs.little_graduation);
this->draw(rect);
rect.setOutlineThickness(1);
float grad_height = bar_specs.size.y / (max_val / bar_specs.little_graduation);
sf::Color color = mix(bar_specs.min_color, bar_specs.max_color,
static_cast<float>(current_val) / max_val);
rect.setSize({bar_specs.size.x, grad_height});
while (current_val > 0)
{
if (current_val >= bar_specs.little_graduation)
rect.setFillColor(color);
else
rect.setFillColor(color * sf::Color(255, 255, 255, 100));
rect.setPosition(screen_position);
this->draw(rect);
current_val -= bar_specs.little_graduation;
screen_position.y += grad_height;
}
}
void Camera::on_new_entity(const Entity* entity)
{
if (entity->get_type() == 0)
......@@ -756,6 +794,19 @@ void Camera::on_entity_deleted(const Entity* entity)
}
}
void Camera::on_entity_task_changed(const Entity* entity, const Task* task)
{
// Look for an EntitySprite using this entity pointer
auto it = std::find_if(this->sprites.begin(),
this->sprites.end(),
[entity](const auto& s)
{
return s->get_entity() == entity;
});
assert(it != this->sprites.end());
(*it)->set_task(task);
}
const sf::Vector2u Camera::get_win_size() const
{
return this->win().getSize();
......
......@@ -36,6 +36,7 @@ class WorldSprite;
class GameClient;
class Entity;
class EntitySprite;
class Task;
class Camera: public ScreenElement
{
......@@ -58,6 +59,9 @@ public:
*/
void draw_energy_bar(sf::Vector2f screen_position, const EnergyBar& bar,
const std::size_t max_val, int current_val);
void draw_vertical_bar(sf::Vector2f screen_position, const EnergyBar& bar_specs,
const std::size_t max_val, int current_val);
/**
* Draw an indicator, centered around `center`, the width is the diameter.
*/
......@@ -150,6 +154,7 @@ public:
void draw(const sf::Drawable&, const sf::RenderStates& states = sf::RenderStates::Default);
void on_new_entity(const Entity*);
void on_entity_deleted(const Entity*);
void on_entity_task_changed(const Entity* entity, const Task* task);
void graphical_tick();
const sf::Vector2u get_win_size() const;
......
#ifndef ANIMATIONS_HPP_INCLUDED
#define ANIMATIONS_HPP_INCLUDED
#include <gui/sprites/energy_bar.hpp>
struct AttackAnimation
{
/**
* The total duration of the frontswing
*/
std::size_t fs;
/**
* The total duration of the backswing
*/
std::size_t bs;
};
#endif /* ANIMATIONS_HPP_INCLUDED */
#include <gui/sprites/entity_sprite.hpp>
#include <world/entity.hpp>
#include <world/task.hpp>
#include <world/location.hpp>
#include <logging/logging.hpp>
sf::Texture EntitySprite::shadow_texture;
bool EntitySprite::init = false;
const std::vector<sf::Color> EntitySprite::team_colors = {sf::Color::White,
......@@ -10,7 +13,8 @@ const std::vector<sf::Color> EntitySprite::team_colors = {sf::Color::White,
sf::Color::Blue};
EntitySprite::EntitySprite(const Entity* const entity):
entity(entity)
entity(entity),
task_type(TaskType::None)
{
if (EntitySprite::init == false)
{
......@@ -64,3 +68,11 @@ bool EntitySprite::is_mouse_over(const Camera* camera) const
mouse_pos.y > ent_pos.y - 80 &&
mouse_pos.y < ent_pos.y + 20);
}
void EntitySprite::set_task(const Task* task)
{
log_debug("EntitySprite::set_task" << static_cast<int>(task->get_type()));
this->task_type = task->get_type();
this->on_task_changed(task);
}
......@@ -6,6 +6,8 @@
#include <vector>
enum class TaskType;
class Task;
class Entity;
class EntitySprite: public WorldSprite
......@@ -16,12 +18,20 @@ public:
const Entity* get_entity() const;
Position get_world_pos() const override final;
bool is_mouse_over(const Camera* camera) const;
/**
* Set our current animation based on the new task the entity is doing.
* For example if our new task is TaskType::Move, the current animation we
* draw (until the task is changed) is the move animation, if any.
*/
void set_task(const Task* task);
protected:
const Entity* const entity;
void draw_shadow(Camera& camera, const sf::Color color) const;
virtual void on_task_changed(const Task*) {}
static const std::vector<sf::Color> team_colors;
TaskType task_type;
private:
static bool init;
......
......@@ -6,10 +6,12 @@
#include <world/manapool.hpp>
#include <world/team.hpp>
#include <world/location.hpp>
#include <world/tasks/attack_task.hpp>
PicpicSprite::PicpicSprite(const Entity* const entity):
EntitySprite(entity),
float_direction(-0.0002)
float_direction(-0.0002),
animation(PicpicSprite::Animation::Idle)
{
if (PicpicSprite::init == false)
{
......@@ -50,7 +52,7 @@ void PicpicSprite::draw(GameClient* game) const
EnergyBar bar = this->standard_health_bar;
Health* entity_health = entity->get<Health>();
Health* entity_health = this->entity->get<Health>();
if (entity_health)
{
game->get_debug_hud().add_debug_line("Entity health: " + std::to_string(entity_health->get()) + "/" +std::to_string(entity_health->get_max()));
......@@ -64,6 +66,16 @@ void PicpicSprite::draw(GameClient* game) const
game->get_camera().draw_energy_bar({x, y - 80}, mana_bar, entity_mana->get_max().to_int(), entity_mana->get().to_int());
}
switch (this->animation)
{
case Animation::Idle:
return;
case Animation::Move:
// TODO
break;
case Animation::Attack:
this->draw_attack_animation(this->entity->get_task<AttackTask>(), game, {x - 80, y});
}
}
void PicpicSprite::tick()
......@@ -73,6 +85,62 @@ void PicpicSprite::tick()
this->height += this->float_direction;
}
void PicpicSprite::on_task_changed(const Task* task)
{
log_debug("on task change (init): " << static_cast<int>(task->get_type()));
switch (task->get_type())
{
case TaskType::None:
this->animation = PicpicSprite::Animation::Idle;
break;
case TaskType::Attack:
this->animation = PicpicSprite::Animation::Attack;
this->init_attack_animation(static_cast<const AttackTask*>(task));
break;
case TaskType::Follow:
case TaskType::Path:
this->animation = PicpicSprite::Animation::Move;
break;
}
}
void PicpicSprite::init_attack_animation(const AttackTask* task)
{
log_debug("Init task anim with: fs=" << task->get_remaining_frontswing_duration() <<
" and bs=" << task->get_remaining_backswing_duration());
this->attack_animation = {task->get_remaining_frontswing_duration(),
task->get_remaining_backswing_duration()};
}
void PicpicSprite::draw_attack_animation(const AttackTask* task, GameClient* game, sf::Vector2f pos) const
{
std::size_t current_fs;
std::size_t current_bs;
if (task)
{
current_fs = task->get_remaining_frontswing_duration();
current_bs = task->get_remaining_backswing_duration();
}
else
{
current_fs = 0;
current_bs = this->attack_animation.bs;
}
auto current_value = (current_fs > 0 ? this->attack_animation.fs - current_fs: current_bs);
int max = (current_fs > 0 ? this->attack_animation.fs : this->attack_animation.bs);
EnergyBar bar = {
sf::Color::Red,
sf::Color::Red,
{8, 100},
2,
0
};
game->get_camera().draw_vertical_bar(pos, bar, max, current_value);
}
bool PicpicSprite::init = false;
sf::Texture PicpicSprite::body_texture;
sf::Texture PicpicSprite::eye_texture;
......@@ -2,9 +2,20 @@
# define __PICPIC_SPRITE_HPP__
#include <gui/sprites/entity_sprite.hpp>
#include <gui/sprites/animations.hpp>
class AttackTask;
class PicpicSprite: public EntitySprite
{
enum class Animation
{
Idle,
Move,
Attack,
count
};
public:
PicpicSprite(const Entity* const);
......@@ -15,6 +26,13 @@ public:
private:
float height;
float float_direction;
Animation animation;
AttackAnimation attack_animation;
void on_task_changed(const Task*) override final;
void init_attack_animation(const AttackTask*);
void draw_attack_animation(const AttackTask* task, GameClient* game, sf::Vector2f pos) const;
PicpicSprite(const PicpicSprite&);
PicpicSprite& operator=(const PicpicSprite&);
......
......@@ -21,16 +21,6 @@ void Abilities::add(const std::size_t index,
this->abilities[index] = std::move(ability);
}
Ability* Abilities::find(const AbilityType& type) const
{
for (auto& ability: this->abilities)
{
if (ability->get_type() == type)
return ability.get();
}
return nullptr;
}
template <typename T>
T* get_ability(Entity* entity)
{
......
......@@ -19,9 +19,9 @@ public:
static const ComponentType component_type = ComponentType::Abilities;
Abilities(const std::size_t size,
const std::size_t fs, const std::size_t bs):
abilities(size),
cast_frontswing(fs),
cast_backswing(bs)
cast_backswing(bs),
abilities(size)
{}
~Abilities() = default;
void tick(Entity* entity, World* world) override final
......@@ -39,7 +39,16 @@ public:
/**
* Look for an Ability with that type.
*/
Ability* find(const AbilityType& type) const;
template <typename Type = Ability>
Type* find(const AbilityType& type) const
{
for (auto& ability: this->abilities)
{
if (ability->get_type() == type)
return static_cast<Type*>(ability.get());
}
return nullptr;
}
/**
* The duration, in ticks, of the two phases of a casted ability
......
......@@ -20,20 +20,20 @@ Attack::Attack(const utils::Duration fs_duration, const utils::Duration bs_durat
{
}
void Attack::cast(Entity* entity, World*, const Position& pos, const bool queue)
void Attack::cast(Entity* entity, World* world, const Position& pos, const bool queue)
{
log_debug("Attacking with entity " << entity->get_id() << " until position " << pos);
auto work = std::make_unique<AttackWork>(entity, pos, this->range);
auto work = std::make_unique<AttackWork>(world, entity, pos, this->range);
if (queue)
entity->queue_work(std::move(work));
else
entity->set_work(std::move(work));
}
void Attack::cast(Entity* entity, World *, const std::shared_ptr<Entity>& target, const bool queue)
void Attack::cast(Entity* entity, World* world, const std::shared_ptr<Entity>& target, const bool queue)
{
log_debug("Attacking with entity " << entity->get_id() << " the target " << target->get_id());
auto work = std::make_unique<AttackWork>(entity, target, this->range);
auto work = std::make_unique<AttackWork>(world, entity, target, this->range);
if (queue)
entity->queue_work(std::move(work));
else
......@@ -49,3 +49,8 @@ std::size_t Attack::get_backswing_duration() const
{
return this->backswing_duration;
}
Fix16 Attack::get_range() const
{
return this->range;
}
......@@ -16,6 +16,8 @@ public:
std::size_t get_frontswing_duration() const;
std::size_t get_backswing_duration() const;
Fix16 get_range() const;
private:
std::size_t frontswing_duration;
std::size_t backswing_duration;
......
......@@ -17,11 +17,11 @@ Blink::Blink():
{
}
void Blink::cast(Entity* entity, World*, const Position& position, const bool queue)
void Blink::cast(Entity* entity, World* world, const Position& position, const bool queue)
{
// Check mana, cooldown etc etc
log_debug("CASTING blink for entity" << entity->get_id() << " to pos " << position);
auto work = std::make_unique<BlinkWork>(entity, position);
auto work = std::make_unique<BlinkWork>(world, entity, position);
if (queue)
entity->queue_work(std::move(work));
else
......
......@@ -19,6 +19,6 @@ void Concentrate::cast(Entity* entity, World* world, const bool queue)
{
log_debug("Concentrate::cast. Starting concentrate task");
// TODO queue, or disable queue alltogether
auto work = std::make_unique<ConcentrateWork>(entity, world);
auto work = std::make_unique<ConcentrateWork>(world, entity);
entity->set_work(std::move(work));
}
......@@ -57,7 +57,7 @@ void Dash::cast(Entity* entity, World* world, const Position& pos, const bool qu
world->callbacks->impact(entity, impacted_entity);
health->add(-concentrate_value);
};
auto work = std::make_unique<DashWork>(entity, world, pos, 50, this->max_distance, 50,
auto work = std::make_unique<DashWork>(world, entity, pos, 50, this->max_distance, 50,
on_impact, nullptr);
entity->set_work(std::move(work));
}
......
......@@ -26,6 +26,6 @@ void Emp::cast(Entity* entity, World* world, const Position& position, const boo
return;
auto emp = world->do_new_entity(2, position, team->get());
emp->set_work(std::make_unique<EmpWork>(emp, world));
emp->set_work(std::make_unique<EmpWork>(world, emp));
world->callbacks->ability_casted(entity, AbilityType::Emp, nullptr, position);
}
......@@ -4,17 +4,11 @@
#include <world/entity.hpp>
#include <world/world.hpp>
#include <world/work.hpp>
#include <world/tasks/idle_task.hpp>
#include <world/world_callbacks.hpp>
EntityId Entity::current_id = 0;
Entity::Entity(const EntityType& type):
id(++Entity::current_id),
type(type),
to_be_deleted(false),
manipulable(false)
{
}
Entity::~Entity()
{
this->clear_works();
......@@ -51,6 +45,13 @@ Work* Entity::get_current_work()
return this->works.front().get();
}
const Work* Entity::get_current_work() const
{
if (this->works.empty())
return nullptr;
return this->works.front().get();
}
void Entity::tick(World* world)
{
for (const auto& stat: this->status)
......@@ -66,8 +67,10 @@ void Entity::tick(World* world)
if (this->works.empty())
return ;
auto& work = this->works.front();
if (work->tick(world) == true)
if (work->tick() == true)
this->works.pop_front();
if (works.empty())
world->callbacks->task_changed(this, &IdleTask::that);
}
void Entity::kill()
......
......@@ -3,7 +3,10 @@
#include <world/components.hpp>
#include <world/status.hpp>
#include <world/brain.hpp>
#include <world/work.hpp>
#include <cassert>
#include <cstdint>
#include <memory>
#include <list>
......@@ -22,10 +25,14 @@ class Entity
friend class World;
public:
/**
* This constructor is used when creating a model.
*/
Entity(const EntityType& type);
Entity(const EntityType& type):
id(++Entity::current_id),
type(type),
to_be_deleted(false),
manipulable(false),
brain(std::make_unique<Brain>())
{ }
~Entity();
EntityId get_id() const { return this->id; }
......@@ -41,6 +48,7 @@ public:
void queue_work(std::unique_ptr<Work>);
void interrupt();
Work* get_current_work();
const Work* get_current_work() const;
template <typename ComponentClass>
ComponentClass* get() const
......@@ -67,6 +75,27 @@ public:
status->apply();
this->status.push_back(std::move(status));
}
template <typename BrainType, typename... ArgsType>
void set_brain(World* world, ArgsType&&... args)
{
this->brain = std::make_unique<BrainType>(this, world,
std::forward<ArgsType>(args)...);
}
template <typename T>
const T* get_task() const
{
auto work = this->get_current_work();
if (!work)
return nullptr;
auto task = work->get_task();
if (!task)
return nullptr;
auto res = dynamic_cast<const T*>(task);
assert(res);
// TODO in non-debug build, do not use dynamic cast, use this instead:
// return static_cast<const T*>(task);
return res;
}
/**
* Mark this entity to be removed from the world.
*/
......
......@@ -57,11 +57,20 @@ void AttackTask::do_attack(World* world)
if (this->ranged)
{
Entity* projectile = world->do_new_entity(1, this->location->position(), 1);
projectile->set_work(
std::make_unique<ProjectileWork>(projectile,
projectile->set_work(std::make_unique<ProjectileWork>(world, projectile,
this->target,
std::move(cb)));
}
else
cb(this->target.lock().get());
}
std::size_t AttackTask::get_remaining_frontswing_duration() const
{
return this->frontswing;
}
std::size_t AttackTask::get_remaining_backswing_duration() const
{
return this->backswing;
}
......@@ -16,6 +16,11 @@ public:
bool tick(World* world) override final;
TaskType get_type() const override final
{ return TaskType::Attack; }
/**
* in ticks
*/
std::size_t get_remaining_frontswing_duration() const;
std::size_t get_remaining_backswing_duration() const;
private:
void do_attack(World* world);
......
......@@ -16,8 +16,8 @@ DashTask::DashTask(Entity* entity, const Position& goal, Fix16 speed,
impact_callback(impact_cb),
goal_callback(goal_cb),
impacted_entities{},
mobility(entity->get<Mobility>()),
location(entity->get<Location>())
location(entity->get<Location>()),
mobility(entity->get<Mobility>())
{
assert(this->mobility);
assert(this->location);
......
#include <world/tasks/idle_task.hpp>
IdleTask IdleTask::that;
#ifndef IDLE_TASK_HPP_INCLUDED
#define IDLE_TASK_HPP_INCLUDED
#include <world/task.hpp>