package view.tile; import controller.tiles.interactive.InteractiveTileController; import model.Tile; import model.tiles.BackgroundTile; import model.tiles.InteractiveTileModel; import util.WorldGenerator; import view.GamePanel; import view.sound.SoundManager; import view.tile.interactive.InteractiveTileView; import view.util.RenderingManager; import java.awt.*; import java.io.*; public class TileManager implements RenderingManager { private GamePanel gamePanel; private Tile[] tile; private int[][] mapTileNum; private int mapTileOverflow = 1; private InteractiveTileController draggingTile; private int originalGridX, originalGridY; public TileManager(GamePanel gp){ this.gamePanel = gp; tile = new Tile[10]; mapTileNum = new int[GamePanel.maxWorldCol][GamePanel.maxWorldRow]; getTileImage(); } public int getTileSize(){ return gamePanel.tileSize; } public void addInteractiveTile(InteractiveTileController tile) { gamePanel.gameController.interactiveTileControllers.add(tile); } public void loadMap(String filePath){ try{ InputStream is = getClass().getResourceAsStream(filePath); BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath))); int col = 0; int row = 0; while(col < gamePanel.maxWorldCol && row < gamePanel.maxWorldRow){ String line = br.readLine(); while(col < gamePanel.maxWorldRow){ String numbers[] = line.split(" "); int num = Integer.parseInt(numbers[col]); mapTileNum[col][row] = num; col++; } if(col == gamePanel.maxWorldCol) { col = 0; row++; } } br.close(); }catch (Exception e){ System.out.println("Couldn't find world path... Creating new World..."); gamePanel.generateNewWorld(); } } public void draw(Graphics2D g2){ int worldCol = 0; int worldRow = 0; while (worldCol< gamePanel.maxWorldCol && worldRow < gamePanel.maxWorldRow){ int tileNum = mapTileNum[worldCol][worldRow]; int worldX = worldCol * gamePanel.tileSize; int worldY = worldRow * gamePanel.tileSize; double screenX = worldX - gamePanel.camera.worldX + gamePanel.camera.screenX; double screenY = worldY - gamePanel.camera.worldY + gamePanel.camera.screenY; if(worldX + gamePanel.tileSize*mapTileOverflow > gamePanel.camera.worldX - gamePanel.camera.screenX && worldX - gamePanel.tileSize*mapTileOverflow < gamePanel.camera.worldX + gamePanel.camera.screenX && worldY + gamePanel.tileSize*mapTileOverflow > gamePanel.camera.worldY - gamePanel.camera.screenY && worldY - gamePanel.tileSize*mapTileOverflow < gamePanel.camera.worldY + gamePanel.camera.screenY ) { g2.drawImage(tile[tileNum].image, (int) screenX, (int) screenY, gamePanel.tileSize, gamePanel.tileSize, null); } worldCol++; if(worldCol == gamePanel.maxWorldCol){ worldCol = 0; worldRow++; } } drawAllTiles(g2); } /** * Entry point for dragging movement: called from GameMouseListener.mouseDragged when right mouse button. * dx, dy are screen pixel deltas. */ public void handleTileShift(int dx, int dy) { if (draggingTile == null) { return; } // Smooth pixel-based move: update view's screen coordinates by delta InteractiveTileView view = draggingTile.getView(); view.setScreenCoordinates(view.getScreenX() + dx, view.getScreenY() + dy); } /** * Drawing loop: update view coords from model for non-dragged tiles; draw all. */ public void drawAllTiles(Graphics2D g2) { // Optionally draw non-dragged first, then dragged to render on top for (InteractiveTileController tile : gamePanel.gameController.interactiveTileControllers) { if (!isTileDragged(tile)) { InteractiveTileModel model = tile.getModel(); int sx = worldColToScreenX(model.getWorldGridX()); int sy = worldRowToScreenY(model.getWorldGridY()); tile.getView().setScreenCoordinates(sx, sy); tile.drawTile(g2); } } if (getDraggedTile() != null) { // Draw dragged tile on top getDraggedTile().drawTile(g2); } } public int worldColToScreenX(int worldCol) { double worldX = worldCol * gamePanel.tileSize; // Reactively use tileSize return (int) (worldX - gamePanel.camera.worldX + gamePanel.camera.screenX); } public int worldRowToScreenY(int worldRow) { double worldY = worldRow * gamePanel.tileSize; // Reactively use tileSize return (int) (worldY - gamePanel.camera.worldY + gamePanel.camera.screenY); } public void getTileImage(){ try{ setupTile(0, "grass"); setupTile(1, "wall"); setupTile(2, "water"); setupTile(3, "earth"); setupTile(4, "tree"); setupTile(5, "sand"); }catch (IOException e){ e.printStackTrace(); } } private void setupTile(int index, String path) throws IOException { tile[index] = new BackgroundTile(); tile[index].setImage("/tiles/background/" + path + ".png"); } private void setupInteractiveTile( int index, String path) throws IOException { tile[index] = new BackgroundTile(); tile[index].setImage("/tiles/" + path + ".png"); } public void generateNewWorld() { String filePath = WorldGenerator.generateNewWorld(GamePanel.maxWorldCol, GamePanel.maxWorldRow, (int) Math.floor(Math.random()*100000)); loadMap(filePath); } @Override public void drawPopup(Graphics2D g2) { for (InteractiveTileController tile : gamePanel.gameController.interactiveTileControllers) { tile.drawPopup(g2); } } @Override public void resize(int newTileSize) { for (InteractiveTileController tile : gamePanel.gameController.interactiveTileControllers) { tile.resize(newTileSize); } } public void handleTileRelease() { SoundManager.getInstance().stopLoopSound(SoundManager.SOUNDS.MOVING_BUSH); if (draggingTile == null) { return; } InteractiveTileView view = draggingTile.getView(); InteractiveTileModel model = draggingTile.getModel(); int tileSize = gamePanel.tileSize; int widthTiles = view.getScaleX(); int heightTiles = view.getScaleY(); // Compute the screen coordinates of the tile's top-left int dropScreenX = view.getScreenX(); int dropScreenY = view.getScreenY(); // Compute the center pixel position of the tile int centerScreenX = dropScreenX + (widthTiles * tileSize) / 2; int centerScreenY = dropScreenY + (heightTiles * tileSize) / 2; // Convert center to world grid coordinate (floor) int centerGridX = screenToWorldX(centerScreenX); int centerGridY = screenToWorldY(centerScreenY); // Compute top-left grid position so that tile center aligns to centerGrid int targetGridX = centerGridX - (widthTiles / 2); int targetGridY = centerGridY - (heightTiles / 2); if (isValidPosition(targetGridX, targetGridY)) { model.setWorldGridX(targetGridX); model.setWorldGridY(targetGridY); } else { // Revert to original grid position model.setWorldGridX(originalGridX); model.setWorldGridY(originalGridY); } // After updating model, reset view’s screen coords to the snapped grid position int snappedScreenX = worldColToScreenX(model.getWorldGridX()); int snappedScreenY = worldRowToScreenY(model.getWorldGridY()); view.setScreenCoordinates(snappedScreenX, snappedScreenY); // Clear dragging state draggingTile = null; } /** * Checks if draggingTile can be placed at new grid coordinates without out-of-bounds or collision. * * @param newX target grid X * @param newY target grid Y * @return true if valid position */ private boolean isValidPosition(int newX, int newY) { if (draggingTile == null) { return false; } InteractiveTileModel model = draggingTile.getModel(); InteractiveTileView tileView = draggingTile.getView(); int width = tileView.getScaleX(); int height = tileView.getScaleY(); // Check world bounds if (newX < 0 || newY < 0 || newX + width > GamePanel.maxWorldCol || newY + height > GamePanel.maxWorldRow) { return false; } // Check collisions for (int x = newX; x < newX + width; x++) { for (int y = newY; y < newY + height; y++) { InteractiveTileController other = getTileAt(x, y); if (other != null && other != draggingTile) { return false; } } } return true; } /** * Returns the InteractiveTileController at the specified grid coordinates, or null if none. * * @param worldX grid X coordinate * @param worldY grid Y coordinate * @return the tile controller at that position, or null */ public InteractiveTileController getTileAt(int worldX, int worldY) { if (gamePanel.gameController.interactiveTileControllers == null) { return null; } for (InteractiveTileController tile : gamePanel.gameController.interactiveTileControllers) { InteractiveTileModel model = tile.getModel(); int tileX = model.getWorldGridX(); int tileY = model.getWorldGridY(); int width = tile.getView().getScaleX(); int height = tile.getView().getScaleY(); boolean withinX = (worldX >= tileX && worldX < tileX + width); boolean withinY = (worldY >= tileY && worldY < tileY + height); if (withinX && withinY) { return tile; } } return null; } // screenToWorldX/Y assumed unchanged public int screenToWorldX(int screenX) { return (gamePanel.camera.worldX + screenX - gamePanel.camera.screenX) / gamePanel.tileSize; } public int screenToWorldY(int screenY) { return (gamePanel.camera.worldY + screenY - gamePanel.camera.screenY) / gamePanel.tileSize; } /** * */ public boolean isTileDragged(InteractiveTileController controller){ return draggingTile == controller; } public InteractiveTileController getDraggedTile() { return draggingTile; } public void setDraggingTile(InteractiveTileController tile) { draggingTile = tile; if (draggingTile != null) { InteractiveTileModel model = draggingTile.getModel(); originalGridX = model.getWorldGridX(); originalGridY = model.getWorldGridY(); // Optionally, ensure view's screen coords are set initially InteractiveTileView view = draggingTile.getView(); int sx = worldColToScreenX(originalGridX); int sy = worldRowToScreenY(originalGridY); view.setScreenCoordinates(sx, sy); } } }