4 Achegas a1f4ef9200 ... badd71dcf6

Autor SHA1 Mensaxe Data
  Jan badd71dcf6 Shop ist funktionsfähig hai 5 meses
  Jan cbfed6b228 Shop wird korrekt angezeigt, vorerst ohne Aktionen hai 6 meses
  Jan 9226ffcaa7 Economy is getting loaded from the file hai 6 meses
  Jan 39a7fda42c Mini Refactoring: Tile Dragging in GameController -> Tilemanager hai 6 meses

+ 29 - 178
src/main/java/controller/GameController.java

@@ -7,6 +7,7 @@ import controller.factories.InteractiveTileFactory;
 import controller.input.KeyHandler;
 import controller.tiles.interactive.*;
 import controller.tiles.interactive.upgradeable.*;
+import controller.ui.ShopController;
 import model.GameModel;
 import model.GameSaver;
 import model.Inventory;
@@ -18,9 +19,6 @@ import model.tiles.InteractiveTileModel;
 import model.tiles.InteractiveTileType;
 import util.GAMESTATE;
 import view.GamePanel;
-import view.tile.interactive.InteractiveTileView;
-
-import java.awt.*;
 import java.io.Serializable;
 import java.util.ArrayList;
 
@@ -38,10 +36,9 @@ public class GameController implements Runnable, Serializable {
     private transient Thread gameThread;
     private transient RessourceManager ressourceManager;
     private transient GamePanel view;
+    private transient ShopController shopController;
     public transient ArrayList<EntityController> entityControllers;
     public transient ArrayList<InteractiveTileController> interactiveTileControllers;
-    private transient InteractiveTileController draggingTile;
-    private transient int residualShiftX, residualShiftY;
     private transient boolean gameActive;
 
     /**
@@ -56,9 +53,17 @@ public class GameController implements Runnable, Serializable {
         initializeInteractiveTiles();
         initializeEntities();
         initResourceManager();
+        initShopController();
         view.loadMap(worldPath);
     }
 
+    /**
+     * Creates a new ShopController
+     */
+    private void initShopController() {
+        shopController = new ShopController(this);
+    }
+
     /**
      * Creates a new ResourceManager
      */
@@ -145,7 +150,7 @@ public class GameController implements Runnable, Serializable {
      *
      * @param tileController the interactive tile controller to add
      */
-    private void addInteractiveTileController(InteractiveTileController tileController) {
+    public void addInteractiveTileController(InteractiveTileController tileController) {
         interactiveTileControllers.add(tileController);
     }
 
@@ -198,6 +203,17 @@ public class GameController implements Runnable, Serializable {
         getModel().getInventory().select(slot);
     }
 
+    /**
+     * Handles a click on the shop UI.
+     *  @param x screen x-coordinate of click
+     * @param y screen y-coordinate of click
+     * @return
+     */
+    public void handleShopClick(int x, int y) {
+        InteractiveTileController controller = view.getShopView().getClickedOffer(x, y);
+        shopController.setDraggingOffer(controller);
+    }
+
     /**
      * Starts the main game loop and resource manager thread.
      */
@@ -327,9 +343,8 @@ public class GameController implements Runnable, Serializable {
         if (view.gameState == GAMESTATE.INVENTORY) {
             view.gameState = GAMESTATE.PLAY;
             getModel().getInventory().deselectAll();
-        } else if (view.gameState == GAMESTATE.PLAY) {
+        } else
             view.gameState = GAMESTATE.INVENTORY;
-        }
     }
 
     /**
@@ -385,45 +400,12 @@ public class GameController implements Runnable, Serializable {
         getView().generateNewWorld();
     }
 
-    private int originalGridX, originalGridY;
-
-    /**
-     * Called from GameMouseListener.mousePressed when starting a drag on right-click.
-     */
-    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);
-        }
-    }
-
-    /**
-     * Called from GameMouseListener.mousePressed to clear any residual state.
-     * Retained for compatibility; residual shifting no longer used for mouse drag.
-     */
-    public void resetResidualShift() {
-        // No-op or clear any previous state related to grid-shift dragging
-        // Previously, residualShiftX/Y were used; now smooth drag uses pixel offsets via handleTileShift
-    }
-
     /**
      * 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);
+        getView().tileManager.handleTileShift(dx, dy);
     }
 
     /**
@@ -431,130 +413,10 @@ public class GameController implements Runnable, Serializable {
      * Snaps the tile to grid, validates position, reverts if invalid.
      */
     public void handleTileRelease(int mouseX, int mouseY) {
-        if (draggingTile == null) {
-            return;
-        }
-        InteractiveTileView view = draggingTile.getView();
-        InteractiveTileModel model = draggingTile.getModel();
-
-        GamePanel gp = getView();
-        int tileSize = gp.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 (interactiveTileControllers == null) {
-            return null;
-        }
-        for (InteractiveTileController tile : 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;
-    }
-
-
-    // Ensure you have utility methods:
-    private int worldColToScreenX(int worldCol) {
-        GamePanel gp = getView();
-        return gp.camera.screenX + worldCol * gp.tileSize - gp.camera.worldX;
-    }
+        getView().tileManager.handleTileRelease(mouseX, mouseY);
 
-    private int worldRowToScreenY(int worldRow) {
-        GamePanel gp = getView();
-        return gp.camera.screenY + worldRow * gp.tileSize - gp.camera.worldY;
     }
 
-    // screenToWorldX/Y assumed unchanged
-    public int screenToWorldX(int screenX) {
-        GamePanel gp = getView();
-        return (gp.camera.worldX + screenX - gp.camera.screenX) / gp.tileSize;
-    }
-
-    public int screenToWorldY(int screenY) {
-        GamePanel gp = getView();
-        return (gp.camera.worldY + screenY - gp.camera.screenY) / gp.tileSize;
-    }
-
-
     /**
      * Loads interactive tiles and entities from the saved GameModel.
      */
@@ -562,6 +424,7 @@ public class GameController implements Runnable, Serializable {
         loadInteractiveTilesFromSave();
         loadEntitiesFromSave();
         initResourceManager();
+        initShopController();
     }
 
     /**
@@ -594,19 +457,6 @@ public class GameController implements Runnable, Serializable {
         }
     }
 
-    /**
-     *
-     */
-    public boolean isTileDragged(InteractiveTileController controller){
-        return draggingTile == controller;
-    }
-    /**
-     *  Initializes the Main Menu for changing worlds etc.
-     */
-    public void loadMainMenu() {
-
-    }
-
     public boolean isRunning() {
         return running;
     }
@@ -615,8 +465,9 @@ public class GameController implements Runnable, Serializable {
         getModel().getInventory().addToInventory(collected);
     }
 
-    public InteractiveTileController getDraggedTile() {
-        return draggingTile;
+
+    public void toggleShop() {
+        getView().toggleShop();
     }
 }
 

+ 11 - 7
src/main/java/controller/input/GameMouseListener.java

@@ -4,6 +4,7 @@ import controller.GameController;
 import util.GAMESTATE;
 import view.Camera;
 import view.GamePanel;
+import view.tile.TileManager;
 
 import javax.swing.*;
 import java.awt.*;
@@ -61,19 +62,22 @@ public class GameMouseListener implements MouseListener, MouseMotionListener {
         }else if(gamePanel.gameState == GAMESTATE.MAIN_MENU){
             gamePanel.handleMainMenuClick(e.getX(), e.getY());
         }
-
     }
 
     @Override
     public void mousePressed(MouseEvent e) {
         if (SwingUtilities.isRightMouseButton(e)) {
-            // Compute world coords of initial press:
-            int worldX = controller.screenToWorldX(e.getX());
-            int worldY = controller.screenToWorldY(e.getY());
-            controller.setDraggingTile(controller.getTileAt(worldX, worldY));
-            // Reset residuals:
-            controller.resetResidualShift();
+            TileManager tm = controller.getView().tileManager;
+
+            int worldX = tm.screenToWorldX(e.getX());
+            int worldY = tm.screenToWorldY(e.getY());
+            tm.setDraggingTile(tm.getTileAt(worldX, worldY));
         }
+
+        if (gamePanel.gameState == GAMESTATE.SHOP && SwingUtilities.isRightMouseButton(e)) {
+            controller.handleShopClick(e.getX(), e.getY());
+        }
+
         lastY = e.getX();
         lastX = e.getY();
     }

+ 1 - 0
src/main/java/controller/input/KeyHandler.java

@@ -31,6 +31,7 @@ public class KeyHandler implements KeyListener {
             case KeyEvent.VK_DOWN -> controller.zoomInOut(-1);
             case KeyEvent.VK_ESCAPE -> controller.togglePauseOrExitInventory();
             case KeyEvent.VK_E -> controller.toggleInventory();
+            case KeyEvent.VK_R -> controller.toggleShop();
         }
     }
 

+ 44 - 0
src/main/java/controller/ui/ShopController.java

@@ -1,4 +1,48 @@
 package controller.ui;
 
+import controller.GameController;
+import controller.factories.InteractiveTileFactory;
+import controller.tiles.interactive.InteractiveTileController;
+import model.Inventory;
+import model.items.ITEM_NAME;
+import model.tiles.InteractiveTileModel;
+import model.tiles.InteractiveTileType;
+import util.Translator;
+import util.economy.EconomyData;
+import util.economy.EconomyInfo;
+import view.tile.TileManager;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
 public class ShopController {
+    private GameController gameController;
+    public ShopController(GameController gameController){
+        this.gameController = gameController;
+    }
+
+    public void setDraggingOffer(InteractiveTileController controller) {
+        EconomyData data = EconomyInfo.infoOf(controller.getModel().getTileType());
+        List<Map.Entry<ITEM_NAME, Integer>> entries = new ArrayList<>(data.getCosts().get(1).entrySet());
+
+        for(Map.Entry<ITEM_NAME, Integer> entry : entries){
+            Inventory inventory = gameController.getModel().getInventory();
+
+            if(!inventory.hasEnough(entry.getKey(), entry.getValue())){
+                return;
+            }else{
+                inventory.spend(entry.getKey(), entry.getValue());
+            }
+        }
+        if(controller != null){
+
+            gameController.addInteractiveTileController(controller);
+            gameController.getView().tileManager.setDraggingTile(controller);
+            gameController.getView().toggleShop();
+            //gameController.createInteractiveTileModel(1,1, InteractiveTileType.SAWMILL);
+            System.out.println(controller.getModel().getWorldGridX());
+        }
+    }
 }

+ 2 - 0
src/main/java/main/Main.java

@@ -3,6 +3,7 @@ package main;
 import controller.GameController;
 import model.GameSaver;
 import util.Translator;
+import util.economy.EconomyInfo;
 import view.GamePanel;
 
 import javax.swing.*;
@@ -13,6 +14,7 @@ public class Main {
     public static void main(String[] args) throws IOException {
 
         Translator.load("lang/de_de.json");
+        EconomyInfo.load();
 
         GameController gameController = GameSaver.loadGame();
 

+ 8 - 0
src/main/java/model/Inventory.java

@@ -56,4 +56,12 @@ public class Inventory implements Serializable {
             item.setSelected(false);
         });
     }
+
+    public boolean hasEnough(ITEM_NAME key, int comparedTo) {
+        return itemHashMap.get(key).getCount() >= comparedTo;
+    }
+
+    public void spend(ITEM_NAME key, int value) {
+        itemHashMap.get(key).takeItems(value);
+    }
 }

+ 1 - 1
src/main/java/util/GAMESTATE.java

@@ -5,5 +5,5 @@ public enum GAMESTATE {
     PAUSED,
     INVENTORY,
     QUIT,
-    MAIN_MENU, SAVE
+    MAIN_MENU, SHOP, SAVE
 }

+ 39 - 0
src/main/java/util/economy/EconomyData.java

@@ -0,0 +1,39 @@
+package util.economy;
+
+import model.items.ITEM_NAME;
+
+import java.util.Map;
+
+public class EconomyData {
+    private final Map<Integer, Map<ITEM_NAME, Integer>> levelCosts;
+    private final int maxLevel;
+    private final Map<Integer, Integer> lighthouseLevels;
+    private final Map<Integer, Integer> productionTimePerLevel;
+
+    public EconomyData(Map<Integer, Map<ITEM_NAME, Integer>> costs, int maxLevel, Map<Integer, Integer> lighthouseLevels, Map<Integer, Integer> productionTimePerLevel) {
+        this.levelCosts = costs;
+        this.maxLevel = maxLevel;
+        this.lighthouseLevels = lighthouseLevels;
+        this.productionTimePerLevel = productionTimePerLevel;
+    }
+
+    public Map<Integer, Map<ITEM_NAME, Integer>> getCosts() {
+        return levelCosts;
+    }
+
+    public int getMaxLevel() {
+        return maxLevel;
+    }
+
+    public Map<Integer, Integer> getRequiredLighthouseLevels() {
+        return lighthouseLevels;
+    }
+
+    public Map<Integer, Integer> getProductionTimePerLevel() {
+        return productionTimePerLevel;
+    }
+
+    public int getProductionTime(int level) {
+        return productionTimePerLevel.getOrDefault(level, -1);
+    }
+}

+ 226 - 0
src/main/java/util/economy/EconomyInfo.java

@@ -0,0 +1,226 @@
+package util.economy;
+
+import model.items.ITEM_NAME;
+import model.tiles.InteractiveTileType;
+
+import java.io.*;
+import java.util.*;
+
+public class EconomyInfo {
+    private static final String FILE_PATH = "/economy/economy.json";
+    private static final Map<InteractiveTileType, EconomyData> economyHashmap = new HashMap<>();
+
+    private EconomyInfo(){};
+
+    public static void load() {
+        economyHashmap.clear();
+        try (InputStream is = EconomyInfo.class.getResourceAsStream(FILE_PATH);
+             BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
+
+            StringBuilder jsonBuilder = new StringBuilder();
+            String line;
+            while ((line = reader.readLine()) != null) {
+                jsonBuilder.append(line);
+            }
+
+            String json = jsonBuilder.toString().trim();
+            if (json.startsWith("{") && json.endsWith("}")) {
+                json = json.substring(1, json.length() - 1);
+            }
+
+            parseTopLevel(json);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+    public static List<InteractiveTileType> getOffers(){
+        return economyHashmap.keySet().stream().toList();
+    }
+
+    private static void parseTopLevel(String json) {
+        int index = 0;
+        while (index < json.length()) {
+            int keyStart = json.indexOf('"', index);
+            if (keyStart == -1) break;
+
+            int keyEnd = json.indexOf('"', keyStart + 1);
+            String typeName = json.substring(keyStart + 1, keyEnd);
+
+            int dataStart = json.indexOf('{', keyEnd);
+            if (dataStart == -1) break;
+
+            int depth = 1;
+            int dataEnd = dataStart + 1;
+            while (dataEnd < json.length() && depth > 0) {
+                if (json.charAt(dataEnd) == '{') depth++;
+                else if (json.charAt(dataEnd) == '}') depth--;
+                dataEnd++;
+            }
+
+            if (depth != 0) break;
+
+            String dataJson = json.substring(dataStart, dataEnd);
+            parseTileData(InteractiveTileType.valueOf(typeName), dataJson);
+
+            index = dataEnd;
+        }
+    }
+
+    private static void parseTileData(InteractiveTileType tileType, String json) {
+        int maxLevel = extractIntValue(json, "maxLevel");
+        Map<Integer, Map<ITEM_NAME, Integer>> costs = extractNestedMap(json, "costs");
+        Map<Integer, Integer> lighthouseLevels = extractFlatMap(json, "lighthouseLevels");
+        Map<Integer, Integer> productionTimes = extractFlatMap(json, "productionTimeInSec");
+
+        economyHashmap.put(tileType, new EconomyData(
+                costs,
+                maxLevel,
+                lighthouseLevels,
+                productionTimes
+        ));
+    }
+
+    private static int extractIntValue(String json, String key) {
+        int keyIndex = json.indexOf("\"" + key + "\":");
+        if (keyIndex == -1) return -1;
+
+        int valueStart = json.indexOf(':', keyIndex) + 1;
+        int valueEnd = findValueEnd(json, valueStart);
+        String value = json.substring(valueStart, valueEnd).trim();
+
+        try {
+            return Integer.parseInt(value);
+        } catch (NumberFormatException e) {
+            return -1;
+        }
+    }
+
+    private static Map<Integer, Map<ITEM_NAME, Integer>> extractNestedMap(String json, String key) {
+        Map<Integer, Map<ITEM_NAME, Integer>> result = new HashMap<>();
+        String mapJson = extractSubJson(json, key);
+        if (mapJson == null) return result;
+
+        int index = 0;
+        while (index < mapJson.length()) {
+            int levelStart = mapJson.indexOf('"', index);
+            if (levelStart == -1) break;
+
+            int levelEnd = mapJson.indexOf('"', levelStart + 1);
+            int level = Integer.parseInt(mapJson.substring(levelStart + 1, levelEnd));
+
+            int innerStart = mapJson.indexOf('{', levelEnd);
+            if (innerStart == -1) break;
+
+            int innerDepth = 1;
+            int innerEnd = innerStart + 1;
+            while (innerEnd < mapJson.length() && innerDepth > 0) {
+                if (mapJson.charAt(innerEnd) == '{') innerDepth++;
+                else if (mapJson.charAt(innerEnd) == '}') innerDepth--;
+                innerEnd++;
+            }
+
+            if (innerDepth != 0) break;
+
+            String innerJson = mapJson.substring(innerStart + 1, innerEnd - 1);
+            Map<ITEM_NAME, Integer> innerMap = parseInnerMap(innerJson);
+            result.put(level, innerMap);
+
+            index = innerEnd;
+        }
+
+        return result;
+    }
+
+    private static Map<ITEM_NAME, Integer> parseInnerMap(String innerJson) {
+        Map<ITEM_NAME, Integer> map = new HashMap<>();
+        int index = 0;
+
+        while (index < innerJson.length()) {
+            int keyStart = innerJson.indexOf('"', index);
+            if (keyStart == -1) break;
+
+            int keyEnd = innerJson.indexOf('"', keyStart + 1);
+            String itemName = innerJson.substring(keyStart + 1, keyEnd);
+
+            int valueStart = innerJson.indexOf(':', keyEnd) + 1;
+            int valueEnd = findValueEnd(innerJson, valueStart);
+            String valueStr = innerJson.substring(valueStart, valueEnd).trim();
+
+            try {
+                ITEM_NAME item = ITEM_NAME.valueOf(itemName);
+                int amount = Integer.parseInt(valueStr);
+                map.put(item, amount);
+            } catch (IllegalArgumentException ignored) {
+            }
+
+            index = valueEnd + 1;
+        }
+
+        return map;
+    }
+
+    private static Map<Integer, Integer> extractFlatMap(String json, String key) {
+        Map<Integer, Integer> result = new HashMap<>();
+        String mapJson = extractSubJson(json, key);
+        if (mapJson == null) return result;
+
+        int index = 0;
+        while (index < mapJson.length()) {
+            int keyStart = mapJson.indexOf('"', index);
+            if (keyStart == -1) break;
+
+            int keyEnd = mapJson.indexOf('"', keyStart + 1);
+            int level = Integer.parseInt(mapJson.substring(keyStart + 1, keyEnd));
+
+            int valueStart = mapJson.indexOf(':', keyEnd) + 1;
+            int valueEnd = findValueEnd(mapJson, valueStart);
+            String valueStr = mapJson.substring(valueStart, valueEnd).trim();
+
+            try {
+                int value = Integer.parseInt(valueStr);
+                result.put(level, value);
+            } catch (NumberFormatException ignored) {
+            }
+
+            index = valueEnd + 1;
+        }
+
+        return result;
+    }
+
+    private static String extractSubJson(String json, String key) {
+        String searchKey = "\"" + key + "\":";
+        int keyIndex = json.indexOf(searchKey);
+        if (keyIndex == -1) return null;
+
+        int start = json.indexOf('{', keyIndex + searchKey.length());
+        if (start == -1) return null;
+
+        int depth = 1;
+        int end = start + 1;
+        while (end < json.length() && depth > 0) {
+            if (json.charAt(end) == '{') depth++;
+            else if (json.charAt(end) == '}') depth--;
+            end++;
+        }
+
+        if (depth != 0) return null;
+        return json.substring(start, end);
+    }
+
+    private static int findValueEnd(String json, int start) {
+        int end = start;
+        while (end < json.length()) {
+            char c = json.charAt(end);
+            if (c == ',' || c == '}' || c == ']') break;
+            end++;
+        }
+        return end;
+    }
+
+    public static EconomyData infoOf(InteractiveTileType type) {
+        return economyHashmap.get(type);
+    }
+}
+

+ 19 - 1
src/main/java/view/GamePanel.java

@@ -10,6 +10,7 @@ import view.popUpMenu.PopupManager;
 import view.tile.TileManager;
 import util.GAMESTATE;
 import view.ui.InventoryView;
+import view.ui.ShopView;
 import view.ui.UI;
 
 import javax.swing.*;
@@ -48,6 +49,7 @@ public class GamePanel extends JPanel{
     public EntityManager entityManager;
     private PopupManager popupManager;
     private InventoryView inventoryView;
+    private ShopView shopView;
     public Camera camera;
     private MainMenu mainMenu;
 
@@ -61,6 +63,8 @@ public class GamePanel extends JPanel{
         this.entityManager = new EntityManager(this);
         this.popupManager = new PopupManager(this, new ArrayList<>(Arrays.asList(tileManager, entityManager)));
         this.mainMenu = new MainMenu(this);
+        this.shopView = new ShopView(this);
+
         Inventory inventory = gameController.getModel().getInventory();
         this.inventoryView = new InventoryView(inventory);
 
@@ -137,7 +141,9 @@ public class GamePanel extends JPanel{
         entityManager.draw(g2);
         popupManager.drawPopups(g2);
 
-
+        if(gameState == GAMESTATE.SHOP){
+            shopView.drawOverlay(g2);
+        }
         if (gameState == GAMESTATE.INVENTORY) {
             inventoryView.drawInventoryOverlay(g2);
         }
@@ -165,4 +171,16 @@ public class GamePanel extends JPanel{
     public void handleMainMenuClick(int x, int y) {
         mainMenu.handleClick(x,y);
     }
+
+    public void toggleShop() {
+        closeMenus();
+        if(gameState == GAMESTATE.SHOP){
+            gameState = GAMESTATE.PLAY;
+        }else gameState = GAMESTATE.SHOP;
+
+    }
+
+    public ShopView getShopView() {
+        return shopView;
+    }
 }

+ 156 - 9
src/main/java/view/tile/TileManager.java

@@ -1,13 +1,12 @@
 package view.tile;
 
-import controller.entity.EntityController;
 import controller.tiles.interactive.InteractiveTileController;
 import model.Tile;
-import model.entity.EntityModel;
 import model.tiles.BackgroundTile;
 import model.tiles.InteractiveTileModel;
 import util.WorldGenerator;
 import view.GamePanel;
+import view.tile.interactive.InteractiveTileView;
 import view.util.RenderingManager;
 
 import java.awt.*;
@@ -15,10 +14,12 @@ import java.io.*;
 
 public class TileManager implements RenderingManager {
 
-    GamePanel gamePanel;
-    public Tile[] tile;
-    public int[][] mapTileNum;
-    public int mapTileOverflow = 1;
+    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;
@@ -99,6 +100,18 @@ public class TileManager implements RenderingManager {
         }
         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.
@@ -106,7 +119,7 @@ public class TileManager implements RenderingManager {
     public void drawAllTiles(Graphics2D g2) {
         // Optionally draw non-dragged first, then dragged to render on top
         for (InteractiveTileController tile : gamePanel.gameController.interactiveTileControllers) {
-            if (!gamePanel.gameController.isTileDragged(tile)) {
+            if (!isTileDragged(tile)) {
                 InteractiveTileModel model = tile.getModel();
                 int sx = worldColToScreenX(model.getWorldGridX());
                 int sy = worldRowToScreenY(model.getWorldGridY());
@@ -114,9 +127,9 @@ public class TileManager implements RenderingManager {
                 tile.drawTile(g2);
             }
         }
-        if (gamePanel.gameController.getDraggedTile() != null) {
+        if (getDraggedTile() != null) {
             // Draw dragged tile on top
-            gamePanel.gameController.getDraggedTile().drawTile(g2);
+            getDraggedTile().drawTile(g2);
         }
     }
 
@@ -129,6 +142,7 @@ public class TileManager implements RenderingManager {
         double worldY = worldRow * gamePanel.tileSize; // Reactively use tileSize
         return (int) (worldY - gamePanel.camera.worldY + gamePanel.camera.screenY);
     }
+
     public void getTileImage(){
         try{
             setupTile(0, "grass");
@@ -172,5 +186,138 @@ public class TileManager implements RenderingManager {
             tile.resize(newTileSize);
         }
     }
+
+    public void handleTileRelease(int mouseX, int mouseY) {
+        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);
+        }
+    }
 }
 

+ 1 - 1
src/main/java/view/tile/interactive/AnimalEnclosureView.java

@@ -7,6 +7,6 @@ public class AnimalEnclosureView extends InteractiveTileView {
 
     public AnimalEnclosureView(GameController gc, InteractiveTileController controller) {
         super( 1, 1, controller, gc.getView().tileManager);
-        setImage("/tiles/animalEnclosure/animalEnclosureTest.png");
+        setImage("/tiles/animal_enclosure/animal_enclosureTest.png");
     }
 }

+ 1 - 1
src/main/java/view/tile/interactive/IronMineView.java

@@ -7,6 +7,6 @@ public class IronMineView extends InteractiveTileView {
 
     public IronMineView(GameController gc, InteractiveTileController controller) {
         super( 1,1, controller, gc.getView().tileManager);
-        setImage("/tiles/mineIron/mineIronTest.png");
+        setImage("/tiles/iron_mine/iron_mineTest.png");
     }
 }

+ 1 - 0
src/main/java/view/ui/InventoryView.java

@@ -20,6 +20,7 @@ public class InventoryView {
     public InventoryView(Inventory inventory){
         this.inventory = inventory;
     }
+
     public void drawInventoryOverlay(Graphics2D g2) {
         slotCount = inventory.getUniqueItemsCount();
 

+ 149 - 0
src/main/java/view/ui/ShopView.java

@@ -1,4 +1,153 @@
 package view.ui;
 
+import controller.factories.InteractiveTileFactory;
+import controller.tiles.interactive.InteractiveTileController;
+import model.Inventory;
+import model.items.ITEM_NAME;
+import model.tiles.InteractiveTileModel;
+import model.tiles.InteractiveTileType;
+import util.Translator;
+import util.economy.EconomyData;
+import util.economy.EconomyInfo;
+import view.GamePanel;
+import view.tile.TileManager;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
 public class ShopView {
+    private GamePanel gamePanel;
+    private List<InteractiveTileType> offers;
+
+    int overlayY = 80;
+    int slotSize = 128;
+    int spacing = 8;
+    int slotCount = 0;
+    int offsetX, offsetY;
+    public ShopView(GamePanel gamePanel){
+        this.gamePanel = gamePanel;
+        offers = EconomyInfo.getOffers();
+    }
+
+    public void drawOverlay(Graphics2D g2) {
+        drawShopOverlay(g2, Translator.translate("menu.shop"));
+
+        drawInventoryOverlay(g2);
+    }
+
+    private void drawInventoryOverlay(Graphics2D g2) {
+        gamePanel.getInventoryView().drawInventoryOverlay(g2);
+    }
+
+    public void drawShopOverlay(Graphics2D g2, String title){
+
+        slotCount = offers.size();
+
+        int overlayWidth = (slotSize + spacing) * slotCount + spacing;
+        int overlayHeight = slotSize + 2* spacing;
+
+        offsetX = gamePanel.getWidth()/2 - overlayWidth/2;
+        offsetY = overlayY + spacing;
+        g2.setColor(new Color(0, 0, 0, 160));
+        g2.fillRoundRect(offsetX, offsetY, overlayWidth, overlayHeight, 25,25);
+        drawTitle(g2, title);
+
+        offsetX += spacing;
+        offsetY += spacing;
+
+        for(InteractiveTileType type : offers){
+            drawOffer(g2, type);
+        }
+    }
+
+    public InteractiveTileController getClickedOffer(int x, int y) {
+        int offerCount = offers.size();
+
+        int overlayWidth = (slotSize + spacing) * offerCount + spacing;
+
+        int baseOffsetX = gamePanel.getWidth() / 2 - overlayWidth / 2;
+        int baseOffsetY = overlayY + spacing;
+
+        int offerX = baseOffsetX + spacing;
+        int offerY = baseOffsetY + spacing;
+
+        for (int i = 0; i < offerCount; i++) {
+            int currentX = offerX + i * (slotSize + spacing);
+            int currentY = offerY;
+
+            if (x >= currentX && x <= currentX + slotSize &&
+                    y >= currentY && y <= currentY + slotSize) {
+                TileManager tm = gamePanel.tileManager;
+
+                return InteractiveTileFactory.createTile(gamePanel.gameController, new InteractiveTileModel(tm.screenToWorldX(x), tm.screenToWorldY(y), offers.get(i)));
+            }
+        }
+        return null; // No offer clicked
+    }
+
+    private void drawOffer(Graphics2D g2, InteractiveTileType offer){
+        g2.setColor(Color.LIGHT_GRAY);
+        g2.fillRoundRect(offsetX, offsetY, slotSize, slotSize, 25, 25);
+
+        g2.setColor(Color.BLACK);
+        BufferedImage image = getImage("/tiles/" + offer.toString().toLowerCase() + "/" + offer.toString().toLowerCase() + "Test.png");
+        int imageSize = slotSize/2;
+        g2.drawImage(image, offsetX + (slotSize/2 - imageSize/2), offsetY, imageSize, imageSize,null);
+
+        EconomyData data = EconomyInfo.infoOf(offer);
+
+        int stringOffset = slotSize + imageSize;
+        List<Map.Entry<ITEM_NAME, Integer>> entries = new ArrayList<>(data.getCosts().get(1).entrySet());
+
+        for(Map.Entry<ITEM_NAME, Integer> entry : entries){
+            String needed = Translator.translate("item."+entry.getKey()) + ": " + entry.getValue();
+
+            int strWidth = g2.getFontMetrics().stringWidth(needed);
+            Inventory inventory = gamePanel.gameController.getModel().getInventory();
+
+            if(inventory.hasEnough(entry.getKey(), entry.getValue())){
+                g2.setColor(Color.BLACK);
+            }else{
+                g2.setColor(Color.RED);
+            }
+            g2.drawString(needed, offsetX + (slotSize/2 - strWidth/2), stringOffset);
+            stringOffset += g2.getFontMetrics().getHeight();
+        }
+        offsetX += slotSize + spacing;
+
+        if(offsetX >= gamePanel.getWidth() - slotSize*2){
+            offsetY += slotSize + spacing;
+            offsetX = slotSize + slotSize/2;
+        }
+    }
+
+
+    private void drawTitle(Graphics2D g2, String title){
+        Font defaultF = g2.getFont();
+        g2.setFont(new Font("Arial", Font.BOLD, 50));
+
+        FontMetrics fm = g2.getFontMetrics();
+        int msgWidth = fm.stringWidth(title);
+        g2.setColor(new Color(0, 0, 0, 100));
+        int offset = 100;
+
+        g2.fillRoundRect(gamePanel.screenWidth /2 - msgWidth /2 - offset/2, 40 - fm.getHeight()/2, msgWidth + offset, 50, 25, 25);
+        g2.setColor(Color.WHITE);
+        g2.drawString(title, gamePanel.screenWidth / 2 - msgWidth / 2, 50);
+        g2.setFont(defaultF);
+    }
+
+    private BufferedImage getImage(String imagePath){
+        try {
+            return ImageIO.read(getClass().getResourceAsStream(imagePath));
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
 }

+ 3 - 0
src/main/java/view/ui/UI.java

@@ -133,5 +133,8 @@ public class UI {
         for (InteractiveTileController tile : gp.gameController.interactiveTileControllers) {
             tile.unClick();
         }
+        for(EntityController e : gp.gameController.entityControllers){
+            e.unClick();
+        }
     }
 }

+ 114 - 0
src/main/resources/economy/economy.json

@@ -0,0 +1,114 @@
+{
+  "IRON_MINE": {
+    "maxLevel": 10,
+    "costs": {
+      "1": { "stone": 40, "wood": 20 },
+      "2": { "stone": 5, "wood": 10 },
+      "3": { "stone": 50, "wood": 50 },
+      "4": { "stone": 500, "wood": 100 }
+    },
+    "lighthouseLevels": {
+      "1": 1,
+      "2": 4
+    },
+    "maxAmountPerLighthouseLevel": {
+      "1": 1,
+      "2": 2,
+      "3": 4
+    },
+    "productionTimeInSec": {
+      "1": 30,
+      "2": 28,
+      "3": 25,
+      "4": 20
+    }
+  },
+  "SAWMILL": {
+    "maxLevel": 5,
+    "costs": {
+      "1": { "wood": 1 },
+      "2": { "stone": 54, "wood": 40 }
+    },
+    "lighthouseLevels": {
+      "1": 1,
+      "2": 3
+    },
+    "maxAmountPerLighthouseLevel": {
+      "1": 1,
+      "2": 2,
+      "3": 4
+    },
+    "productionTimeInSec": {
+      "1": 30,
+      "2": 28,
+      "3": 25,
+      "4": 20
+    }
+  },
+  "BARRACKS": {
+    "maxLevel": 5,
+    "costs": {
+      "1": { "wood": 1 },
+      "2": { "stone": 54, "wood": 40 }
+    },
+    "lighthouseLevels": {
+      "1": 1,
+      "2": 3
+    },
+    "maxAmountPerLighthouseLevel": {
+      "1": 1,
+      "2": 2,
+      "3": 4
+    },
+    "productionTimeInSec": {
+      "1": 30,
+      "2": 28,
+      "3": 25,
+      "4": 20
+    }
+  },
+  "ANIMAL_ENCLOSURE": {
+    "maxLevel": 5,
+    "costs": {
+      "1": { "wood": 1 },
+      "2": { "stone": 54, "wood": 40 }
+    },
+    "lighthouseLevels": {
+      "1": 1,
+      "2": 3
+    },
+    "maxAmountPerLighthouseLevel": {
+      "1": 1,
+      "2": 2,
+      "3": 4
+    },
+    "productionTimeInSec": {
+      "1": 30,
+      "2": 28,
+      "3": 25,
+      "4": 20
+    }
+  },
+  "MINE": {
+    "maxLevel": 5,
+    "costs": {
+      "1": { "wood": 1 },
+      "2": { "stone": 54, "wood": 40 }
+    },
+    "lighthouseLevels": {
+      "1": 1,
+      "2": 3
+    },
+    "maxAmountPerLighthouseLevel": {
+      "1": 1,
+      "2": 2,
+      "3": 4
+    },
+    "productionTimeInSec": {
+      "1": 30,
+      "2": 28,
+      "3": 25,
+      "4": 20
+    }
+  }
+}

+ 1 - 0
src/main/resources/lang/de_de.json

@@ -6,6 +6,7 @@
   "menu.quit": "Beenden",
   "menu.save": "Speichern",
   "menu.main_menu": "Hauptmenu",
+  "menu.shop": "Laden",
   "popup.collect": "Sammeln",
   "popup.upgrade": "Verbessern",
   "item.wood": "Holz",

+ 1 - 0
src/main/resources/lang/en_us.json

@@ -6,6 +6,7 @@
   "menu.quit": "Quit",
   "menu.save": "Save",
   "menu.main_menu": "Main Menu",
+  "menu.shop": "Shop",
   "popup.collect": "Collect",
   "popup.upgrade": "Upgrade",
   "item.wood": "Wood",

+ 0 - 0
src/main/resources/tiles/animalEnclosure/animalEnclosureTest.png → src/main/resources/tiles/animal_enclosure/animal_enclosureTest.png


+ 0 - 0
src/main/resources/tiles/mineIron/mineIronTest.png → src/main/resources/tiles/iron_mine/iron_mineTest.png


+ 44 - 0
src/test/java/EconomyInfoTest.java

@@ -0,0 +1,44 @@
+import model.items.ITEM_NAME;
+import model.tiles.InteractiveTileType;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import util.economy.EconomyData;
+import util.economy.EconomyInfo;
+
+import java.util.Map;
+
+
+public class EconomyInfoTest {
+    @Test
+    public void isReadingCorrectly(){
+        EconomyInfo.load();
+
+        EconomyData data = EconomyInfo.infoOf(InteractiveTileType.IRON_MINE);
+
+        Map<Integer, Map<ITEM_NAME, Integer>> costs = data.getCosts();
+
+        Map<ITEM_NAME, Integer> levelCosts2 = costs.get(1);
+
+        int max = data.getMaxLevel();
+
+        Integer stoneAmount = levelCosts2.get(ITEM_NAME.stone);
+        Integer woodAmount = levelCosts2.get(ITEM_NAME.wood);
+
+        Integer maxAmountLvl2 = data.getRequiredLighthouseLevels().get(1);
+        Integer maxAmountLvl3 = data.getRequiredLighthouseLevels().get(2);
+
+        Assertions.assertEquals(30, data.getProductionTime(1));
+        Assertions.assertEquals(28, data.getProductionTime(2));
+        Assertions.assertEquals(25, data.getProductionTime(3));
+        Assertions.assertEquals(20, data.getProductionTime(4));
+
+        Assertions.assertEquals(40, stoneAmount);
+        Assertions.assertEquals(20, woodAmount);
+
+        Assertions.assertEquals(10, max);
+
+        Assertions.assertEquals(1, maxAmountLvl2);
+
+        Assertions.assertEquals(4, maxAmountLvl3);
+    }
+}

+ 114 - 0
src/test/resources/economy/economy.json

@@ -0,0 +1,114 @@
+{
+  "IRON_MINE": {
+    "maxLevel": 10,
+    "costs": {
+      "1": { "stone": 40, "wood": 20 },
+      "2": { "stone": 5, "wood": 10 },
+      "3": { "stone": 50, "wood": 50 },
+      "4": { "stone": 500, "wood": 100 }
+    },
+    "lighthouseLevels": {
+      "1": 1,
+      "2": 4
+    },
+    "maxAmountPerLighthouseLevel": {
+      "1": 1,
+      "2": 2,
+      "3": 4
+    },
+    "productionTimeInSec": {
+      "1": 30,
+      "2": 28,
+      "3": 25,
+      "4": 20
+    }
+  },
+  "SAWMILL": {
+    "maxLevel": 5,
+    "costs": {
+      "1": { "wood": 1 },
+      "2": { "stone": 54, "wood": 40 }
+    },
+    "lighthouseLevels": {
+      "1": 1,
+      "2": 3
+    },
+    "maxAmountPerLighthouseLevel": {
+      "1": 1,
+      "2": 2,
+      "3": 4
+    },
+    "productionTimeInSec": {
+      "1": 30,
+      "2": 28,
+      "3": 25,
+      "4": 20
+    }
+  },
+  "BARRACKS": {
+    "maxLevel": 5,
+    "costs": {
+      "1": { "wood": 1 },
+      "2": { "stone": 54, "wood": 40 }
+    },
+    "lighthouseLevels": {
+      "1": 1,
+      "2": 3
+    },
+    "maxAmountPerLighthouseLevel": {
+      "1": 1,
+      "2": 2,
+      "3": 4
+    },
+    "productionTimeInSec": {
+      "1": 30,
+      "2": 28,
+      "3": 25,
+      "4": 20
+    }
+  },
+  "ANIMAL_ENCLOSURE": {
+    "maxLevel": 5,
+    "costs": {
+      "1": { "wood": 1 },
+      "2": { "stone": 54, "wood": 40 }
+    },
+    "lighthouseLevels": {
+      "1": 1,
+      "2": 3
+    },
+    "maxAmountPerLighthouseLevel": {
+      "1": 1,
+      "2": 2,
+      "3": 4
+    },
+    "productionTimeInSec": {
+      "1": 30,
+      "2": 28,
+      "3": 25,
+      "4": 20
+    }
+  },
+  "MINE": {
+    "maxLevel": 5,
+    "costs": {
+      "1": { "wood": 1 },
+      "2": { "stone": 54, "wood": 40 }
+    },
+    "lighthouseLevels": {
+      "1": 1,
+      "2": 3
+    },
+    "maxAmountPerLighthouseLevel": {
+      "1": 1,
+      "2": 2,
+      "3": 4
+    },
+    "productionTimeInSec": {
+      "1": 30,
+      "2": 28,
+      "3": 25,
+      "4": 20
+    }
+  }
+}