175 lines
8.1 KiB
C++
175 lines
8.1 KiB
C++
#include <game/agame/enemy.hpp>
|
||
#include <game/agame/background.hpp>
|
||
#include <object/components/boxcollider.hpp>
|
||
#include <game/agame/player.hpp>
|
||
#include <state/gamestate.hpp>
|
||
#include <algorithm>
|
||
#include <cmath>
|
||
#include <utils.hpp>
|
||
#include <game/gamemanager.hpp>
|
||
#include <window/window.hpp>
|
||
|
||
namespace Game::AGame {
|
||
void Enemy::start() {
|
||
mZIndex = 20;
|
||
addComponent<Object::Components::BoxCollider>();
|
||
LOG("Sovražnik zagnan: " << getName());
|
||
|
||
// Initialize random movement
|
||
const float angle = static_cast<float>(Utils::getUtils().rirng32(0, 360)) * 3.14159f / 180.f;
|
||
const float speed = 20.f + static_cast<float>(Utils::getUtils().rirng32(0, 30));
|
||
mMoveSpeedX = std::cos(angle) * speed;
|
||
mMoveSpeedY = std::sin(angle) * speed;
|
||
mDirectionChangeTimer = 0.f;
|
||
}
|
||
|
||
void Enemy::update(float deltaTime) {
|
||
(void)deltaTime;
|
||
|
||
auto* player = GameManager::getEntityByName<Player>("Player");
|
||
if (!player) {
|
||
mIsVisible = false;
|
||
return;
|
||
}
|
||
|
||
// Enemies are visible only within a reveal radius around the player
|
||
const float revealRadius = GameManager::getSharedData<float>("enemyRevealRadius");
|
||
const float px = player->getTransform()->x + (player->getTexture() ? player->getTexture()->getWidth() * player->getTransform()->adjustedScaleX() / 2.f : 0.f);
|
||
const float py = player->getTransform()->y + (player->getTexture() ? player->getTexture()->getHeight() * player->getTransform()->adjustedScaleY() / 2.f : 0.f);
|
||
const float ew = getTexture() ? getTexture()->getWidth() * mTransform.adjustedScaleX() : 0.f;
|
||
const float eh = getTexture() ? getTexture()->getHeight() * mTransform.adjustedScaleY() : 0.f;
|
||
const float ex = mTransform.x + ew / 2.f;
|
||
const float ey = mTransform.y + eh / 2.f;
|
||
const float dxv = px - ex;
|
||
const float dyv = py - ey;
|
||
mIsVisible = (dxv * dxv + dyv * dyv) <= (revealRadius * revealRadius);
|
||
|
||
// Check if player is on land (not in ship mode) and within follow distance
|
||
const float distanceToPlayer = std::sqrt(dxv * dxv + dyv * dyv);
|
||
const bool playerOnLand = !player->isShipMode();
|
||
const bool withinFollowRange = distanceToPlayer <= FOLLOW_DISTANCE;
|
||
|
||
if (playerOnLand && withinFollowRange) {
|
||
// Follow player: calculate direction and move at constant speed
|
||
mFollowingPlayer = true;
|
||
if (distanceToPlayer > 1.f) { // Avoid division by zero
|
||
mMoveSpeedX = (dxv / distanceToPlayer) * FOLLOW_SPEED;
|
||
mMoveSpeedY = (dyv / distanceToPlayer) * FOLLOW_SPEED;
|
||
}
|
||
} else {
|
||
// Revert to random movement when player is on sea or out of range
|
||
mFollowingPlayer = false;
|
||
|
||
// Semi-random movement with periodic direction changes
|
||
mDirectionChangeTimer += deltaTime;
|
||
if (mDirectionChangeTimer > 2.0f) {
|
||
const float angle = static_cast<float>(Utils::getUtils().rirng32(0, 360)) * 3.14159f / 180.f;
|
||
const float speed = 20.f + static_cast<float>(Utils::getUtils().rirng32(0, 30));
|
||
mMoveSpeedX = std::cos(angle) * speed;
|
||
mMoveSpeedY = std::sin(angle) * speed;
|
||
mDirectionChangeTimer = 0.f;
|
||
}
|
||
}
|
||
|
||
// Move enemy
|
||
mTransform.x += mMoveSpeedX * deltaTime;
|
||
mTransform.y += mMoveSpeedY * deltaTime;
|
||
|
||
// Decrease shoreline-spawn cooldown
|
||
if (mShoreSpawnCooldown > 0.f) mShoreSpawnCooldown = std::max(0.f, mShoreSpawnCooldown - deltaTime);
|
||
|
||
// Clamp to land section
|
||
const float landBoundaryX = GameManager::getSharedData<float>("terrainLandBoundaryX");
|
||
const float entityWidth = getTexture() ? getTexture()->getWidth() * mTransform.adjustedScaleX() : 0.f;
|
||
const float entityHeight = getTexture() ? getTexture()->getHeight() * mTransform.adjustedScaleY() : 0.f;
|
||
const float halfWidth = entityWidth / 2.f;
|
||
const float halfHeight = entityHeight / 2.f;
|
||
|
||
// Use logical world dimensions (1280×720) not actual screen size
|
||
constexpr int w = 1280;
|
||
constexpr int h = 720;
|
||
const float leftEdge = -w / 2.f + 25.f;
|
||
|
||
if (mTransform.x - halfWidth < leftEdge) {
|
||
mTransform.x = leftEdge + halfWidth;
|
||
mMoveSpeedX = std::abs(mMoveSpeedX);
|
||
}
|
||
if (mTransform.x + halfWidth > landBoundaryX - 25.f) {
|
||
mTransform.x = landBoundaryX - 25.f - halfWidth;
|
||
mMoveSpeedX = -std::abs(mMoveSpeedX);
|
||
|
||
// Enemy hit the shoreline on the right side; spawn a trash on the sea side
|
||
if (mShoreSpawnCooldown <= 0.f) {
|
||
auto* bg = GameManager::getEntityByName<AGame::Background>("BG");
|
||
if (bg) {
|
||
Object::Transform t;
|
||
t.rotation = 0.f;
|
||
t.scaleX = 5.5f;
|
||
t.scaleY = 5.5f;
|
||
// Place trash on sea side at same Y
|
||
t.y = mTransform.y;
|
||
t.x = landBoundaryX + 10.f; // will be adjusted inside spawnTrashAt
|
||
bg->spawnTrashAt(t, true);
|
||
}
|
||
mShoreSpawnCooldown = 3.0f; // 3 second cooldown per enemy
|
||
}
|
||
}
|
||
if (mTransform.y - halfHeight < -h / 2.f + 25.f) {
|
||
mTransform.y = -h / 2.f + 25.f + halfHeight;
|
||
mMoveSpeedY = std::abs(mMoveSpeedY);
|
||
}
|
||
if (mTransform.y + halfHeight > h / 2.f - 25.f) {
|
||
mTransform.y = h / 2.f - 25.f - halfHeight;
|
||
mMoveSpeedY = -std::abs(mMoveSpeedY);
|
||
}
|
||
}
|
||
|
||
bool Enemy::hasAdjacentEnemy() {
|
||
if (!getTexture()) return false;
|
||
|
||
const float detectionRadius = 40.f;
|
||
const float enemyWidth = getTexture()->getWidth() * mTransform.adjustedScaleX();
|
||
const float enemyHeight = getTexture()->getHeight() * mTransform.adjustedScaleY();
|
||
const float centerX = mTransform.x + enemyWidth / 2.f;
|
||
const float centerY = mTransform.y + enemyHeight / 2.f;
|
||
|
||
auto entities = GameManager::getEntityByName<Object::Entity>("Dummy");
|
||
if (!entities) {
|
||
auto snapshot = State::GameState::getInstance().getEntitiesSnapshot();
|
||
for (auto* other : snapshot) {
|
||
if (!other || other == this || !dynamic_cast<Enemy*>(other)) continue;
|
||
auto* otherEnemy = dynamic_cast<Enemy*>(other);
|
||
if (!otherEnemy || !otherEnemy->getTexture()) continue;
|
||
const float otherWidth = otherEnemy->getTexture()->getWidth() * otherEnemy->getTransform()->adjustedScaleX();
|
||
const float otherHeight = otherEnemy->getTexture()->getHeight() * otherEnemy->getTransform()->adjustedScaleY();
|
||
const float otherCenterX = otherEnemy->getTransform()->x + otherWidth / 2.f;
|
||
const float otherCenterY = otherEnemy->getTransform()->y + otherHeight / 2.f;
|
||
const float dx = centerX - otherCenterX;
|
||
const float dy = centerY - otherCenterY;
|
||
if (dx * dx + dy * dy <= detectionRadius * detectionRadius) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
void Enemy::onCollisionEnter(Object::Entity* other) {
|
||
auto* player = dynamic_cast<Player*>(other);
|
||
if (!player || !mIsVisible) {
|
||
return;
|
||
}
|
||
|
||
if (hasAdjacentEnemy()) {
|
||
LOG("Igralec je trčil v močno skupino onesnaževalcev; konec igre!");
|
||
GameManager::setSharedData("gameLost", true);
|
||
GameManager::destroyEntity(player);
|
||
return;
|
||
}
|
||
|
||
LOG("Sovražnik '" << getName() << "' je trčil v igralca; odstranjujem onesnaževalca in dodeljujem točke");
|
||
GameManager::setSharedData("enemyActiveCount", std::max(0, GameManager::getSharedData<int>("enemyActiveCount") - 1));
|
||
GameManager::setSharedData("gameScore", GameManager::getSharedData<int>("gameScore") + 100);
|
||
GameManager::destroyEntity(this);
|
||
}
|
||
} |