Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ add_executable(${MAIN_EXECUTABLE_NAME}
src/Pickup.cpp
src/Pickup.h
src/ResourceHolder.h
src/EnemyStrategies.cpp
src/EnemyStrategies.h
)

# NOTE: Add all defined targets (e.g. executables, libraries, etc. )
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ The current objective of the game is to accumulate the highest possible Bounty b
## Tema 3

#### Cerințe
- [ ] 2 șabloane de proiectare (design patterns)
- [ ] o clasă șablon cu sens; minim **2 instanțieri**
- [ ] preferabil și o funcție șablon (template) cu sens; minim 2 instanțieri
- [x] 2 șabloane de proiectare (design patterns)
- [x] o clasă șablon cu sens; minim **2 instanțieri**
- [x] preferabil și o funcție șablon (template) cu sens; minim 2 instanțieri
- [x] minim 85% din codul propriu să fie C++
<!-- - [ ] o specializare pe funcție/clasă șablon -->
- [ ] tag de `git` pe commit cu **toate bifele**: de exemplu `v0.3` sau `v1.0`
Expand Down
206 changes: 90 additions & 116 deletions src/Enemy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "GameExceptions.h"
#include "Game.h"
#include "Map.h"
#include "Utils.h"
#include <SFML/Audio.hpp>
#include <cmath>

Expand All @@ -17,15 +18,8 @@ Enemy::Enemy(const std::string& n, const int damage_, const int bountyScore_, co
bountyScore{bountyScore_},
target{target_},
isMoving{false},
canDealDamage{false},
state{EnemyState::Patrolling},
strategy{std::make_unique<PatrolStrategy>()},
detectionRange{400.f},
attackCooldown{0.8f},
currentAttackTimer{attackCooldown},
aggroTimer{0.f},
patrolTimer{0.f},
patrolDuration{3.f},
patrolDirection{1.f},
currentFrame{0},
animationTimer{0.f},
frameDuration{0.1f},
Expand Down Expand Up @@ -66,68 +60,74 @@ Enemy::Enemy(const std::string& n, const int damage_, const int bountyScore_, co
exclamationSprite.setScale({0.f, 0.f});
}

void Enemy::updateAI(const float deltaTime) {
updateAttack(deltaTime);
if (aggroTimer > 0.0f) aggroTimer -= deltaTime;

if (!target || !target->isAlive()) {
state = EnemyState::Patrolling;
}

const float distToPlayer = getDistanceToTarget();
const bool isTouching = isTouchingTarget();

switch (state) {
case EnemyState::Patrolling:
processPatrolState(deltaTime, distToPlayer);
break;

case EnemyState::Chasing:
processChaseState(deltaTime, distToPlayer, isTouching);
break;

case EnemyState::Attacking:
processAttackState(isTouching);
break;
Enemy::Enemy(const Enemy &other)
: Entity(other),
damage(other.damage),
bountyScore(other.bountyScore),
target(other.target),
isMoving(other.isMoving),
detectionRange(other.detectionRange),
frameSize(other.frameSize),
currentFrame(other.currentFrame),
animationTimer(other.animationTimer),
frameDuration(other.frameDuration),
animationFrameCount(other.animationFrameCount),
alertTexture(other.alertTexture),
exclamationSprite(other.exclamationSprite),
alertFrameSize(other.alertFrameSize),
alertAnimTimer(other.alertAnimTimer),
alertActive(other.alertActive),
activeSounds(other.activeSounds),
hitSound(other.hitSound),
deathSound(other.deathSound) {

if (other.strategy) {
strategy = other.strategy->clone();
} else {
strategy = std::make_unique<PatrolStrategy>();
}

activeEnemyCount++;
}

void Enemy::processPatrolState(const float deltaTime, const float distToPlayer) {
if (distToPlayer < detectionRange) {
state = EnemyState::Chasing;
return;
}
updatePatrol(deltaTime);
void swap(Enemy &lhs, Enemy &rhs) noexcept {
using std::swap;
swap(static_cast<Entity &>(lhs), static_cast<Entity &>(rhs));
swap(lhs.damage, rhs.damage);
swap(lhs.bountyScore, rhs.bountyScore);
swap(lhs.target, rhs.target);
swap(lhs.isMoving, rhs.isMoving);
swap(lhs.strategy, rhs.strategy);
swap(lhs.detectionRange, rhs.detectionRange);
swap(lhs.frameSize, rhs.frameSize);
swap(lhs.currentFrame, rhs.currentFrame);
swap(lhs.animationTimer, rhs.animationTimer);
swap(lhs.frameDuration, rhs.frameDuration);
swap(lhs.animationFrameCount, rhs.animationFrameCount);
swap(lhs.alertTexture, rhs.alertTexture);
swap(lhs.exclamationSprite, rhs.exclamationSprite);
swap(lhs.alertFrameSize, rhs.alertFrameSize);
swap(lhs.alertAnimTimer, rhs.alertAnimTimer);
swap(lhs.alertActive, rhs.alertActive);
swap(lhs.activeSounds, rhs.activeSounds);
swap(lhs.hitSound, rhs.hitSound);
swap(lhs.deathSound, rhs.deathSound);
}

void Enemy::processChaseState(const float deltaTime, const float distToPlayer, const bool isTouching) {
if (distToPlayer > detectionRange * 1.5f && aggroTimer <= 0.0f) {
state = EnemyState::Patrolling;
return;
}

if (isTouching) {
startAttackSequence();
return;
void Enemy::updateAI(const float deltaTime) {
if (!target || !target->isAlive()) {
if (!strategy || strategy->isChasing() || strategy->isAttacking()) {
setStrategy(std::make_unique<PatrolStrategy>());
}
}

updateChase(deltaTime);
}

void Enemy::processAttackState(const bool isTouching) {
if (!isTouching) {
state = EnemyState::Chasing;

if (strategy) {
strategy->update(*this, deltaTime);
}
}

void Enemy::startAttackSequence() {
if (state != EnemyState::Attacking) {
if (currentAttackTimer <= 0.f) {
currentAttackTimer = 0.35f;
canDealDamage = false;
}
state = EnemyState::Attacking;
}
void Enemy::setStrategy(std::unique_ptr<EnemyStrategy> newStrategy) {
strategy = std::move(newStrategy);
}

float Enemy::getDistanceToTarget() const {
Expand All @@ -140,44 +140,19 @@ bool Enemy::isTouchingTarget() const {
return this->getBounds().findIntersection(target->getBounds()).has_value();
}

void Enemy::updatePatrol(const float deltaTime) {
patrolTimer += deltaTime;
isMoving = true;

if (patrolTimer >= patrolDuration) {
patrolTimer = 0.0f;
patrolDirection *= -1.0f;
}

posX += speed * 0.5f * patrolDirection * deltaTime;

if (patrolDirection > 0) sprite.setScale({1.f, 1.f});
else sprite.setScale({-1.f, 1.f});
sf::Vector2f Enemy::getTargetPos() const {
if (target) return target->getPos();
return {posX, posY};
}

void Enemy::updateChase(const float deltaTime) {
if (const float diffX = target->getPos().x - posX; std::abs(diffX) > 5.0f) {
isMoving = true;

if (diffX < 0) {
posX -= speed * deltaTime;
sprite.setScale({-1.f, 1.f});
} else {
posX += speed * deltaTime;
sprite.setScale({1.f, 1.f});
void Enemy::tryDealDamageToTarget(const int dmgAmount) const {
if (target && isTouchingTarget()) {
if (auto* playerPtr = dynamic_cast<Player*>(target)) {
playerPtr->tryHit(dmgAmount);
}
}
}

void Enemy::updateAttack(const float deltaTime) {
if (currentAttackTimer > 0.0f) {
currentAttackTimer -= deltaTime;
canDealDamage = false;
}
else {
canDealDamage = true;
}
}

void Enemy::grantReward(Player& player) const {
player.processKill(this->bountyScore);
Expand All @@ -196,7 +171,7 @@ void Enemy::onDeath(Map& map) {

map.onEnemyKilled();

if (Game::generateRandomInt(1, 100) <= 25) {
if (Utils::getRandom<int>(1, 100) <= 25) {
try {
constexpr float groundOffsetY = 40;
auto drop = PickupFactory::create(
Expand All @@ -216,20 +191,21 @@ void Enemy::onDeath(Map& map) {
}
}

void Enemy::onCollision(Player &player) {
if (this->canDealDamage && this->state == EnemyState::Attacking) {
player.tryHit(this->damage);
this->resetAttackTimer();
}
}
// void Enemy::onCollision(Player &player) {
// if (this->canDealDamage && this->state == EnemyState::Attacking) {
// player.tryHit(this->damage);
// this->resetAttackTimer();
// }
// }
// i can use this later on if implementing a knockback

sf::FloatRect Enemy::doGetBounds() const{
return hitbox;
}

void Enemy::takeDamage(const int damageAmount) {
state = EnemyState::Chasing;
aggroTimer = 5.0f;
setStrategy(std::make_unique<ChaseStrategy>(5.0f));

const bool wasAlive = this->isAlive();
health -= damageAmount;
checkDeath();
Expand Down Expand Up @@ -260,10 +236,10 @@ void Enemy::doBehavior(const float deltaTime, const Map& map) {
void Enemy::updatePhysics(const float deltaTime, const Map &map) {
updateHitbox();
if (map.isWall(hitbox, true)) {
if (state == EnemyState::Patrolling) {
patrolDirection *= -1.f;
patrolTimer = 0.0f;
if (strategy) {
strategy->onWallCollision(*this);
}

const float dir = sprite.getScale().x > 0.f ? 1.f : -1.f;
posX -= speed * dir * deltaTime;
updateHitbox();
Expand All @@ -281,7 +257,10 @@ void Enemy::updatePhysics(const float deltaTime, const Map &map) {
}

void Enemy::updateAnimation(const float deltaTime) {
if (state == EnemyState::Attacking) {
const bool attacking = strategy && strategy->isAttacking();
const bool chasing = strategy && strategy->isChasing();

if (attacking) {
currentFrame = animationFrameCount - 1;
animationTimer = 0.0f;
const int rectLeft = currentFrame * frameSize.x;
Expand Down Expand Up @@ -310,13 +289,13 @@ void Enemy::updateAnimation(const float deltaTime) {
constexpr float padding = 5.f;

float yCorrection = 0.f;
if (state == EnemyState::Attacking) {
if (attacking) {
yCorrection = -5.f;
}

exclamationSprite.setPosition({posX, headTopY - padding - halfAlertHeight + yCorrection});

bool shouldShowAlert = (state == EnemyState::Chasing || state == EnemyState::Attacking);
bool shouldShowAlert = (chasing || attacking);

if (shouldShowAlert) {
if (!alertActive) {
Expand All @@ -333,11 +312,11 @@ void Enemy::updateAnimation(const float deltaTime) {

exclamationSprite.setScale({scale, scale});

if (state == EnemyState::Chasing) {
if (chasing) {
exclamationSprite.setTextureRect(sf::IntRect({0, 0},
{alertFrameSize.x, alertFrameSize.y}));
}
else if (state == EnemyState::Attacking) {
else if (attacking) {
exclamationSprite.setTextureRect(sf::IntRect({0, alertFrameSize.y},
{alertFrameSize.x, alertFrameSize.y}));
}
Expand All @@ -349,16 +328,11 @@ void Enemy::updateAnimation(const float deltaTime) {
}
}

void Enemy::resetAttackTimer() {
canDealDamage = false;
currentAttackTimer = attackCooldown;
}

void Enemy::draw(sf::RenderWindow& window) const {
if (!alive) return;
window.draw(sprite);

if (state == EnemyState::Chasing || state == EnemyState::Attacking) {
if (strategy && (strategy->isChasing() || strategy->isAttacking())) {
window.draw(exclamationSprite);
}
}
Expand Down
Loading