#include #include #include #include #include #include #include #include #include #include namespace Game::AGame { void Enemy::start() { mZIndex = 20; addComponent(); LOG("Sovražnik zagnan: " << getName()); // Initialize random movement const float angle = static_cast(Utils::getUtils().rirng32(0, 360)) * 3.14159f / 180.f; const float speed = 20.f + static_cast(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"); if (!player) { mIsVisible = false; return; } // Enemies are visible only within a reveal radius around the player const float revealRadius = GameManager::getSharedData("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(Utils::getUtils().rirng32(0, 360)) * 3.14159f / 180.f; const float speed = 20.f + static_cast(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("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("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("Dummy"); if (!entities) { auto snapshot = State::GameState::getInstance().getEntitiesSnapshot(); for (auto* other : snapshot) { if (!other || other == this || !dynamic_cast(other)) continue; auto* otherEnemy = dynamic_cast(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(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("enemyActiveCount") - 1)); GameManager::setSharedData("gameScore", GameManager::getSharedData("gameScore") + 100); GameManager::destroyEntity(this); } }