Files
letnik3koncni-prap/src/game/agame/background.cpp
2026-05-19 22:35:10 +02:00

357 lines
16 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <game/agame/background.hpp>
#include <game/agame/player.hpp>
#include <window/window.hpp>
#include <state/gamestate.hpp>
#include <object/camera.hpp>
#include <object/components/boxcollider.hpp>
#include <algorithm>
#include <chrono>
#include <ctime>
#include <fstream>
#include <iomanip>
#include <utils.hpp>
namespace {
void writeFinalScoreFile(int score) {
std::ofstream file("score.txt", std::ios::trunc);
if (!file.is_open()) {
WARN("Neuspešno odpiranje score.txt za pisanje");
return;
}
const auto now = std::chrono::system_clock::now();
const std::time_t nowTime = std::chrono::system_clock::to_time_t(now);
std::tm localTime{};
#if defined(_WIN32)
localtime_s(&localTime, &nowTime);
#else
localtime_r(&nowTime, &localTime);
#endif
std::string playerName = Game::GameManager::getSharedData<std::string>("playerName");
if (playerName.empty()) playerName = "Player";
file << "Končna statistika igre:\n";
file << "Igralec: " << playerName << "\n";
file << "Točke: " << score << "\n";
file << "Datum: " << std::put_time(&localTime, "%Y-%m-%d %H:%M:%S") << "\n";
// Replay system with form state
std::vector<Game::Object::Transform> playerHistory;
std::vector<bool> formHistory;
Game::GameManager::getPlayerPositionHistory(playerHistory);
Game::GameManager::getPlayerFormHistory(formHistory);
std::ofstream replayFile("replay.txt", std::ios::trunc);
if (!replayFile.is_open()) {
WARN("Neuspešno odpiranje replay.txt za pisanje");
return;
}
for (size_t i = 0; i < playerHistory.size(); ++i) {
const auto& transform = playerHistory[i];
bool isShipMode = (i < formHistory.size()) ? formHistory[i] : true;
int shipFlag = isShipMode ? 1 : 0;
replayFile << transform.x << " " << transform.y << " " << transform.rotation << " " << transform.scaleX << " " << transform.scaleY << " " << shipFlag << "\n";
}
LOG("Zapis končne statistike in replaya igre dokončan");
replayFile.close();
file.close();
}
}
namespace Game::AGame {
void Background::start() {
mSeaTex = std::make_shared<Game::Renderer::Texture>("../resources/l3sea.png", SDL_GetRenderer(Window::Window::getSDLWindowBackend()), "seaTex");
mEnemyTex = std::make_shared<Game::Renderer::Texture>("../resources/l3enemy.png", SDL_GetRenderer(Window::Window::getSDLWindowBackend()), "enemyTex");
mTrashTex = std::make_shared<Game::Renderer::Texture>("../resources/l3trash.png", SDL_GetRenderer(Window::Window::getSDLWindowBackend()), "trashTex");
mFriendlyTex = std::make_shared<Game::Renderer::Texture>("../resources/l3friendly.png", SDL_GetRenderer(Window::Window::getSDLWindowBackend()), "friendlyTex");
GameManager::setSharedData("enemyActiveCount", 0);
GameManager::setSharedData("trashActiveCount", 0);
GameManager::setSharedData("friendlyActiveCount", 0);
GameManager::setSharedData("gameStage", 1);
GameManager::setSharedData("gameWon", false);
GameManager::setSharedData("gameLost", false);
mZIndex = -1; // Ensure background renders behind other entities
mTex->setTiled(true); // Set the background texture to be tiled
if (mSeaTex) {
mSeaTex->setTiled(true);
}
mTiledScale = 0.5f;
// Use logical world dimensions (1280×720) not actual screen size
mW = 1280;
mH = 720;
// Land boundary: left 1/3 of map in centered coordinates
// For 1280px window: -640 (left) + 426.67 (1/3) = -213.33
mLandBoundaryX = -static_cast<float>(mW) / 2.f + static_cast<float>(mW) / 3.f;
GameManager::setSharedData("terrainLandBoundaryX", mLandBoundaryX);
GameManager::setSharedData("enemyRevealRadius", 260.f);
mTransform.scaleX *= 10.f;
mTransform.scaleY *= 10.f;
mTransform.x -= mTex->getWidth() * mTransform.adjustedScaleX() / 2.f;
mTransform.y -= mTex->getHeight() * mTransform.adjustedScaleY() / 2.f;
LOG("W: " << mW << " H: " << mH);
spawnLevel(1);
}
void Background::render(Game::Renderer::Renderer* renderer, Game::Renderer::RendererConfig config) {
if (!renderer || !mTex || !mSeaTex) {
return;
}
const float worldLeft = -static_cast<float>(mW) / 2.f;
const float worldRight = static_cast<float>(mW) / 2.f;
const float worldTop = -static_cast<float>(mH) / 2.f;
const float worldBottom = static_cast<float>(mH) / 2.f;
const float landLeft = worldLeft;
const float landRight = mLandBoundaryX;
const float seaLeft = mLandBoundaryX;
const float seaRight = worldRight;
auto drawSection = [&](const std::shared_ptr<Game::Renderer::Texture>& tex, float startX, float endX) {
if (!tex) {
return;
}
float tileW = 0.f;
float tileH = 0.f;
SDL_GetTextureSize(tex->getSDLTexture(), &tileW, &tileH);
tileW *= mTiledScale;
tileH *= mTiledScale;
if (tileW <= 0.f || tileH <= 0.f) {
return;
}
const float screenStartX = startX - config.camX + config.screenW / 2.f;
const float screenEndX = endX - config.camX + config.screenW / 2.f;
const float screenStartY = worldTop - config.camY + config.screenH / 2.f;
const float screenEndY = worldBottom - config.camY + config.screenH / 2.f;
for (float x = screenStartX; x < screenEndX; x += tileW) {
for (float y = screenStartY; y < screenEndY; y += tileH) {
SDL_FRect dst{ x, y, tileW, tileH };
SDL_RenderTexture(renderer->getSDLRenderer(), tex->getSDLTexture(), nullptr, &dst);
}
}
};
drawSection(mTex, landLeft, landRight);
drawSection(mSeaTex, seaLeft, seaRight);
}
void Background::spawnFriendly(int stage, int count) {
const float viewLeft = -mW / 2.f;
const float viewRight = mW / 2.f;
const float viewTop = -mH / 2.f;
const float viewBottom = mH / 2.f;
Object::Transform tS;
tS.x = 0.f;
tS.y = 0.f;
tS.rotation = 0.f;
tS.scaleX = 4.0f;
tS.scaleY = 4.0f;
const float halfFriendlyW = mFriendlyTex->getWidth() * tS.adjustedScaleX() / 2.f;
const float halfFriendlyH = mFriendlyTex->getHeight() * tS.adjustedScaleY() / 2.f;
// Split friendlies: most on land, a smaller number may appear on sea
// Decide how many friendlies appear on the sea.
// For stage 1 keep them on land; for later stages allow at least one on sea
int seaCount = 0;
if (stage > 1) {
seaCount = std::max(1, count / 3); // roughly one third on sea for later stages
}
int landCount = count - seaCount;
// Spawn land friendlies (left side)
for (int i = 0; i < landCount; ++i) {
tS.x = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(viewLeft + halfFriendlyW + 25.f), static_cast<int>(mLandBoundaryX - halfFriendlyW - 25.f)));
tS.y = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(viewTop + halfFriendlyH + 100.f), static_cast<int>(viewBottom - halfFriendlyH - 100.f)));
auto* friendly = State::GameState::getInstance().addEntity(std::make_unique<AGame::Friendly>("Friendly" + std::to_string(stage) + "_L" + std::to_string(i + 1), mFriendlyTex, tS));
if (friendly) {
if (auto* collider = friendly->getComponent<Object::Components::BoxCollider>()) {
collider->setScale(0.75f);
}
}
}
// Spawn a smaller number of friendlies on the sea (right side)
for (int i = 0; i < seaCount; ++i) {
tS.x = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(mLandBoundaryX + halfFriendlyW + 25.f), static_cast<int>(viewRight - halfFriendlyW - 25.f)));
tS.y = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(viewTop + halfFriendlyH + 100.f), static_cast<int>(viewBottom - halfFriendlyH - 100.f)));
auto* friendly = State::GameState::getInstance().addEntity(std::make_unique<AGame::Friendly>("Friendly" + std::to_string(stage) + "_S" + std::to_string(i + 1), mFriendlyTex, tS));
if (friendly) {
if (auto* collider = friendly->getComponent<Object::Components::BoxCollider>()) {
collider->setScale(0.75f);
}
}
}
}
void Background::spawnLevel(int stage) {
const float viewLeft = -mW / 2.f;
const float viewRight = mW / 2.f;
const float viewTop = -mH / 2.f;
const float viewBottom = mH / 2.f;
const int enemyCount = 2 + stage * 2;
const int trashCount = 4 + stage * 3;
const int friendlyCount = 1 + (stage - 1);
GameManager::setSharedData("gameStage", stage);
GameManager::setSharedData("enemyActiveCount", enemyCount);
GameManager::setSharedData("trashActiveCount", trashCount);
GameManager::setSharedData("friendlyActiveCount", friendlyCount);
if (stage > 1) {
auto* player = GameManager::getEntityByName<Player>("Player");
if (player) {
player->respawnRandomSea(mLandBoundaryX);
}
}
Object::Transform tS;
tS.rotation = 0.f;
tS.scaleX = 4.7f;
tS.scaleY = 4.7f;
const float halfEnemyW = mEnemyTex->getWidth() * tS.adjustedScaleX() / 2.f;
const float halfEnemyH = mEnemyTex->getHeight() * tS.adjustedScaleY() / 2.f;
for (int i = 0; i < enemyCount; ++i) {
tS.x = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(viewLeft + halfEnemyW + 25.f), static_cast<int>(mLandBoundaryX - halfEnemyW - 25.f)));
tS.y = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(viewTop + halfEnemyH + 100.f), static_cast<int>(viewBottom - halfEnemyH - 100.f)));
GameManager::instantiateEntity(std::make_unique<AGame::Enemy>("Enemy" + std::to_string(stage) + "_" + std::to_string(i + 1), mEnemyTex, tS));
}
tS.scaleX = 5.5f;
tS.scaleY = 5.5f;
const float halfTrashW = mTrashTex->getWidth() * tS.adjustedScaleX() / 2.f;
const float halfTrashH = mTrashTex->getHeight() * tS.adjustedScaleY() / 2.f;
for (int i = 0; i < trashCount; ++i) {
tS.x = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(mLandBoundaryX + halfTrashW + 25.f), static_cast<int>(viewRight - halfTrashW - 25.f)));
tS.y = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(viewTop + halfTrashH + 100.f), static_cast<int>(viewBottom - halfTrashH - 100.f)));
GameManager::instantiateEntity(std::make_unique<AGame::Trash>("Trash" + std::to_string(stage) + "_" + std::to_string(i + 1), mTrashTex, tS));
}
spawnFriendly(stage, friendlyCount);
}
void Background::spawnTrashAt(const Object::Transform& tS, bool seaOnly) {
// Prepare transform and ensure sea-only pop is placed on the sea side
Object::Transform t = tS;
t.rotation = 0.f;
t.scaleX = 5.5f;
t.scaleY = 5.5f;
const float halfTrashW = mTrashTex->getWidth() * t.adjustedScaleX() / 2.f;
if (seaOnly) {
const float minSeaX = mLandBoundaryX + halfTrashW + 25.f;
if (t.x < minSeaX) t.x = minSeaX;
}
// Create a unique-ish name for the auto-spawned trash
const int id = Utils::getUtils().rirng32(0, 1000000);
const std::string name = "Trash_Auto_" + std::to_string(id);
GameManager::instantiateEntity(std::make_unique<AGame::Trash>(name, mTrashTex, t));
// If requested, mark the spawned trash as sea-only
if (seaOnly) {
auto* tr = GameManager::getEntityByName<AGame::Trash>(name);
if (tr) tr->setSeaOnly(true);
}
GameManager::setSharedData("trashActiveCount", GameManager::getSharedData<int>("trashActiveCount") + 1);
}
void Background::update(float deltaTime) {
(void)deltaTime;
const int enemyCount = GameManager::getSharedData<int>("enemyActiveCount");
const int trashCount = GameManager::getSharedData<int>("trashActiveCount");
const int stage = GameManager::getSharedData<int>("gameStage");
// Periodically spawn a friendly on land or sea with a small probability
// evaluated each update using deltaTime so the average interval is respected.
const int activeFriendlies = GameManager::getSharedData<int>("friendlyActiveCount");
if (activeFriendlies < mMaxAutoFriendlies) {
// Compute chance = deltaTime / avgInterval
const float chance = deltaTime / std::max(0.0001f, mFriendlySpawnAvgInterval);
const int thresh = static_cast<int>(chance * 10000.f);
if (thresh > 0) {
const int roll = Utils::getUtils().rirng32(0, 9999);
if (roll < thresh) {
// Decide side: sea probability increases with stage
float seaProb = (stage > 1) ? 0.3f : 0.1f;
const int sideRoll = Utils::getUtils().rirng32(0, 99);
const bool spawnSea = sideRoll < static_cast<int>(seaProb * 100.f);
Object::Transform tS;
tS.x = 0.f;
tS.y = 0.f;
tS.rotation = 0.f;
tS.scaleX = 4.0f;
tS.scaleY = 4.0f;
const float viewLeft = -mW / 2.f;
const float viewRight = mW / 2.f;
const float viewTop = -mH / 2.f;
const float viewBottom = mH / 2.f;
const float halfFriendlyW = mFriendlyTex->getWidth() * tS.adjustedScaleX() / 2.f;
const float halfFriendlyH = mFriendlyTex->getHeight() * tS.adjustedScaleY() / 2.f;
if (!spawnSea) {
tS.x = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(viewLeft + halfFriendlyW + 25.f), static_cast<int>(mLandBoundaryX - halfFriendlyW - 25.f)));
} else {
tS.x = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(mLandBoundaryX + halfFriendlyW + 25.f), static_cast<int>(viewRight - halfFriendlyW - 25.f)));
}
tS.y = static_cast<float>(Utils::getUtils().rirng32(static_cast<int>(viewTop + halfFriendlyH + 100.f), static_cast<int>(viewBottom - halfFriendlyH - 100.f)));
const int id = Utils::getUtils().rirng32(0, 1000000);
const std::string name = std::string("Friendly_Auto_") + std::to_string(id);
auto* friendly = State::GameState::getInstance().addEntity(std::make_unique<AGame::Friendly>(name, mFriendlyTex, tS));
if (friendly) {
if (auto* collider = friendly->getComponent<Object::Components::BoxCollider>()) {
collider->setScale(0.75f);
}
GameManager::setSharedData("friendlyActiveCount", GameManager::getSharedData<int>("friendlyActiveCount") + 1);
}
}
}
}
if (mPendingLevelSpawn) {
if (enemyCount <= 0 && trashCount <= 0) {
GameManager::processPendingEntityRemovals();
mPendingLevelSpawn = false;
spawnLevel(mPendingLevelStage);
}
return;
}
if (enemyCount <= 0 && trashCount <= 0) {
if (stage < mMaxLevels) {
mPendingLevelSpawn = true;
mPendingLevelStage = stage + 1;
} else if (!GameManager::getSharedData<bool>("gameWon")) {
writeFinalScoreFile(GameManager::getSharedData<int>("gameScore"));
GameManager::setSharedData("gameWon", true);
LOG("Vsi nivoji so zaključeni");
}
}
}
void Background::onWindowResized(int newWidth, int newHeight) {
// Always maintain logical world dimensions (1280×720)
mW = 1280;
mH = 720;
}
}