GameScreen.java

package io.github.neonteam10;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputMultiplexer;
import com.badlogic.gdx.ScreenAdapter;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.maps.MapRenderer;
import com.badlogic.gdx.maps.tiled.TiledMap;
import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer;
import com.badlogic.gdx.utils.ScreenUtils;

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

/**
 * A class representing the main gameplay screen.
 */
public class GameScreen extends ScreenAdapter {
    private final AssetManager assetManager;
    public final CursorManager cursorManager;
    private final SpriteBatch batch;
    private final CameraController cameraController;
    private final GameLogic gameLogic;
    private final UiStage uiStage;
    private GameMap map;
    private GameMapInput mapInput;
    private MapRenderer mapRenderer;
    private UniSimGame uniSimGame;

    public GameScreen(UniSimGame uniSimGame, AssetManager assetManager, CursorManager cursorManager) {
        this.assetManager = assetManager;
        this.cursorManager = cursorManager;
        batch = new SpriteBatch();
        cameraController = new CameraController();
        gameLogic = new GameLogic(this);
        uiStage = new UiStage(assetManager, gameLogic);
        this.uniSimGame = uniSimGame;

        // Create an input multiplexer to chain together our input adapters.
        // Add the UI stage first, then the camera controller.
        var inputMultiplexer = new InputMultiplexer();
        inputMultiplexer.addProcessor(uiStage);
        inputMultiplexer.addProcessor(cameraController);
        Gdx.input.setInputProcessor(inputMultiplexer);
    }

    @Override
    public void resize(int width, int height) {
        // Propagate resize to other systems.
        cameraController.setViewportDimensions(width, height);
        uiStage.getViewport().update(width, height, true);
    }

    @Override
    public void render(float deltaTime) {
        // Clear the screen with a solid color (black).
        ScreenUtils.clear(0.0f, 0.0f, 0.0f, 1.0f);

        // Create map and map renderer if first run.
        if (map == null) {
            initializeMap();
        }

        // Set cursor based on camera behavior.
        updateCursorState();

        // Update camera, game logic, and UI.
        cameraController.update(deltaTime);
        gameLogic.update(deltaTime);
        uiStage.act(deltaTime);

        // Render the map.
        mapRenderer.setView(cameraController.getCamera());
        mapRenderer.render();

        // Render the building currently being placed.
        renderBuildingPlacement();

        // Render the UI last.
        uiStage.update();
        uiStage.draw();
    }

    /**
     * Initializes the map and map renderer and sets up the camera position.
     */
    private void initializeMap() {
        try {
            var tiledMap = assetManager.get("maps/map.tmx", TiledMap.class);
            map = new GameMap(tiledMap);
            AchievementController achievementController = new AchievementController(gameLogic, map, uiStage);
            uiStage.setAchievementController(achievementController);
            gameLogic.setMap(map);
            gameLogic.setAchievementController(achievementController);

            mapRenderer = new OrthogonalTiledMapRenderer(tiledMap, batch);

            // Center the camera on the map.
            cameraController.getCamera().position.set(map.getWidthPx() / 2.0f, map.getHeightPx() / 2.0f, 0.0f);
            cameraController.setMapDimensions(map.getWidthPx(), map.getHeightPx());

            gameLogic.setMap(map);

            // Add input handler for map.
            mapInput = new GameMapInput(map, gameLogic, cameraController);
            ((InputMultiplexer) Gdx.input.getInputProcessor()).addProcessor(mapInput);
        } catch (Exception e) {
            Gdx.app.error("GameScreen", "Failed to initialize the map renderer: " + e.getMessage());
        }
    }

    /**
     * Updates the cursor state based on camera actions.
     */
    private void updateCursorState() {
        if (cameraController.isZoomingIn()) {
            cursorManager.setCursor(GameCursor.ZOOM_IN);
        } else if (cameraController.isZoomingOut()) {
            cursorManager.setCursor(GameCursor.ZOOM_OUT);
        } else if (cameraController.isPanning()) {
            cursorManager.setCursor(GameCursor.HAND_OPEN);
        } else {
            cursorManager.setCursor(GameCursor.POINTER);
        }
    }

    /**
     * Renders a building on the mouse cursor if a building is currently being placed.
     */
    private void renderBuildingPlacement() {
        var prefab = gameLogic.getSelectedPrefab();
        if (prefab == null) {
            // Building not being placed.
            return;
        }

        int placementX = mapInput.getPlacementTileX();
        int placementY = mapInput.getPlacementTileY();

        // Select texture based on whether this placement is valid.
        boolean canPlace = map.canPlaceBuilding(prefab, placementX, placementY);
        var texture = canPlace ? prefab.getTransparentTexture() : prefab.getRedTexture();

        // Render texture.
        batch.begin();
        batch.draw(texture, placementX * map.getTileWidthPx(), placementY * map.getTileHeightPx());
        batch.end();
    }

    @Override
    public void dispose() {
        batch.dispose();
        uiStage.dispose();
    }

    public void restart() {
        uniSimGame.create();
    }
}