GameLogic.java

package io.github.neonteam10;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.math.MathUtils;

import io.github.neonteam10.Achievements.AchievementController;
import io.github.neonteam10.map.BuildingPrefab;
import io.github.neonteam10.map.GameMap;
import io.github.neonteam10.ui.UiStage;

/**
 * A class which manages the gameplay logic, including the remaining game time, placing buildings, and calculating
 * satisfaction and score.
 */
public class GameLogic {
    private static final float TOTAL_GAME_TIME = 5f * 60.0f;
    private static final float BUILDING_TIME = 20.0f;

    private GameMap gameMap;
    private int maximumAllowedBuildings;
    private int selectedPrefabIndex = -1;

    // Timers.
    private float remainingTime;
    private float nextBuildingTime;
    private boolean gameOver;
    private boolean paused;
    private boolean started;

    // Satisfaction.
    private float satisfaction;
    private float newBuildingSatisfaction;
    private int previousBuildingCount;
    private int previousStudentCount;
    private int studentCount;

    GameScreen screen;

    // Events.
    private GameEvent currentEvent;
    private float nextEventProbability;
    private float checkEventTimer;
    private float eventDurationTimer;
    AchievementController achievementController;

    public GameLogic(GameScreen screen) {
        this.screen = screen;
        remainingTime = TOTAL_GAME_TIME;
        nextBuildingTime = 0.0f;
        currentEvent = GameEvent.NONE;
        paused = true;
        started = false;
        studentCount = 0;
        maximumAllowedBuildings = 1;
    }

    public void setMap(GameMap gameMap) {
        this.gameMap = gameMap;
    }

    /**
     * Finds a building prefab given its name.
     *
     * @param name the prefab name
     * @return a {@link BuildingPrefab}
     */
    public BuildingPrefab findPrefab(String name) {
        for (var prefab : gameMap.getAvailablePrefabs()) {
            if (prefab.getName().equals(name)) {
                return prefab;
            }
        }
        return null;
    }

    /**
     * Continuously updates the student satisfaction.
     *
     * @param deltaTime the time between the last call of this method
     */
    private void updateSatisfaction(float deltaTime) {
        // Get all building types.
        var accommodationPrefab = findPrefab("Accommodation");
        var canteenPrefab = findPrefab("Canteen");
        var studyPrefab = findPrefab("Study");

        // Work out the number of students based on how many accommodation buildings there are.
        studentCount = gameMap.getBuildingCount(accommodationPrefab) * 25;

        // Store satisfaction to add for new buildings. 50 score per new building
        int newBuildingCount = gameMap.getTotalBuildingCount() - previousBuildingCount;
        if (newBuildingCount > 0) {
            newBuildingSatisfaction = 50.0f * newBuildingCount;
        } else {
            newBuildingSatisfaction = 0.0f;
        }
        previousBuildingCount = gameMap.getTotalBuildingCount();
        satisfaction += newBuildingSatisfaction;

        // Store satisfaction to add for new students. 10 score per new student
        int newStudentCount = studentCount - previousStudentCount;
        float newStudentSatisfaction = 0.0f;
        if (newStudentCount > 0) {
            newStudentSatisfaction = 10.0f * newStudentCount;
        }
        previousStudentCount = studentCount;
        satisfaction += newStudentSatisfaction;

        // Decrease satisfaction if there isn't enough canteen or study buildings for all the students. Each canteen
        // can support 100 students and each study building can support 75 students. Use exponential formulas so a
        // deficit can not just be offset by placing lots of recreation buildings.
        var canteenDeficit = studentCount - gameMap.getBuildingCount(canteenPrefab) * 100;
        var studyDeficit = studentCount - gameMap.getBuildingCount(studyPrefab) * 75;
        if (canteenDeficit > 0) {
            satisfaction -= ((float) Math.pow(1.3f, canteenDeficit/10)/10);
        }
        if (studyDeficit > 0) {
            float factor = currentEvent == GameEvent.STRIKE ? 2.0f : 1f;
            satisfaction -= ((float) Math.pow(1.3f, studyDeficit/10)/10) * factor;
        }

        // Decay satisfaction based on a rate determined by the amount of recreation buildings.
        //float gainRate = getCurrentGainRate();

        float Canteenbonus = gameMap.buildingGraph.getBonus("Canteen");
        float Accommodationbonus = gameMap.buildingGraph.getBonus("Accommodation");

        float totalBonus = (Canteenbonus+Accommodationbonus)/100;
        satisfaction += totalBonus;

        // Handle rain and roses events.
        if (currentEvent == GameEvent.RAIN) {
            satisfaction -= 2f * deltaTime;
        } else if (currentEvent == GameEvent.ROSES) {
            satisfaction += 2f * deltaTime;
        }

        // Clamp satisfaction above 0.
        satisfaction = MathUtils.clamp(satisfaction, 0.0f, Integer.MAX_VALUE);
    }

    /**
     * Updates the game logic.
     *
     * @param deltaTime the delta time between the last call of update
     */
    public void update(float deltaTime) {
        if (gameOver) {
            return;
        }

        //skip time to end
        if (Gdx.input.isKeyJustPressed(Input.Keys.E)) {
            remainingTime = 0;
        }


        // Update timers.
        if (!paused) {
            remainingTime -= deltaTime;
        }
        if (remainingTime < 0.0f) {
            gameOver = true;
        }
        if (started) {
            nextBuildingTime -= deltaTime;
        }
        if (paused) {
            nextBuildingTime += deltaTime;
        }
        if (nextBuildingTime < 0.0f) {
            // User can place another building.
            maximumAllowedBuildings++;
            nextBuildingTime = BUILDING_TIME;
        }

        // Update satisfaction.
        if (!paused && !gameOver) {
            updateSatisfaction(deltaTime);
        }

        //tick achievements
        if (!paused) {
            if (achievementController != null && gameMap != null) {
                achievementController.setGameMap(gameMap);
                achievementController.AchievementsUnlocked();
            }
        }

        if (!paused) {
            // Tick event duration timer.
            if (currentEvent != GameEvent.NONE) {
                eventDurationTimer -= deltaTime;
            }
            if (eventDurationTimer < 0.0f) {
                currentEvent = GameEvent.NONE;
            }
            if (currentEvent != GameEvent.NONE) {
                return;
            }


            // Generate a random number every 2 seconds to see if we should start an event. Bias the random number slightly
            // to prevent events from happening to close to each other.
            nextEventProbability += deltaTime * 0.01f;
            checkEventTimer += deltaTime;
            if (checkEventTimer > 2.0f) {
                checkEventTimer = 0.0f;
                if (Math.min(MathUtils.random() + 0.1f, 1.0f) < nextEventProbability) {
                    nextEventProbability = 0;
                    currentEvent = GameEvent.values()[MathUtils.random(GameEvent.values().length - 1)];
                    eventDurationTimer = MathUtils.random(15.0f, 45.0f);
                }
            }
        }


    }

    /**
     * Sets the selected building placement prefab to the given index.
     *
     * @param prefabIndex the prefab index
     */
    public void setSelectedPrefabIndex(int prefabIndex) {
        if (gameMap != null && prefabIndex < gameMap.getAvailablePrefabs().size()) {
            selectedPrefabIndex = prefabIndex;
        } else {
            selectedPrefabIndex = -1;
        }
    }

    /**
     * @return true if the player is allowed to place another building
     */
    public boolean canPlaceBuilding() {
        return !gameOver && gameMap.getTotalBuildingCount() < maximumAllowedBuildings;
    }

    /**
     * @return the selected prefab if there is one, otherwise null
     */
    public BuildingPrefab getSelectedPrefab() {
        if (gameMap == null || selectedPrefabIndex < 0) {
            return null;
        }
        return gameMap.getAvailablePrefabs().get(selectedPrefabIndex);
    }

    public GameMap getGameMap() {
        return gameMap;
    }

    public int getSelectedPrefabIndex() {
        return selectedPrefabIndex;
    }

    public float getRemainingTime() {
        return remainingTime;
    }

    public float getNextBuildingTime() {
        return nextBuildingTime;
    }

    public float getSatisfaction() {
        return satisfaction;
    }

    public GameEvent getCurrentEvent() {
        return currentEvent;
    }

    public float getEventDurationTimer() {
        return eventDurationTimer;
    }

    public boolean setPaused(boolean paused) {
        this.paused = paused;
        if (!(started) && !(paused)){
            started = true;
        }
        return this.paused;
    }

    public boolean getPaused() {
        return this.paused;
    }

    public void restart() {
        screen.restart();
    }

    public boolean getGameOver() {
        return gameOver;
    }

    public void setAchievementController(AchievementController achievementController) {
        this.achievementController = achievementController;
    }

}