diff --git a/overcooked_simulator/counters.py b/overcooked_simulator/counters.py
index ed6bab5736ea6859db335d8cd49288baa0f85c26..3f68bae69948eba8f059f73e68bbec1554f94c90 100644
--- a/overcooked_simulator/counters.py
+++ b/overcooked_simulator/counters.py
@@ -12,11 +12,9 @@ from overcooked_simulator.game_items import (
     CuttableItem,
     Item,
     Plate,
-    Pot,
     CookingEquipment,
-    Soup,
+    Meal,
 )
-from overcooked_simulator.game_items import item_loopkup
 
 
 class Counter:
@@ -128,7 +126,7 @@ class ServingWindow(Counter):
 
     def can_score(self, item):
         if isinstance(item, Plate) and item.holds is not None:
-            if isinstance(item.holds, Soup):
+            if isinstance(item.holds, Meal):
                 return item.holds.finished
             elif item.holds.name == "Salad":
                 return True
@@ -141,79 +139,31 @@ class ServingWindow(Counter):
         pass
 
 
-class PlateReturn(Counter):
-    def __init__(self, pos):
-        super().__init__(pos)
-        self.occupied_by = [Plate()]
-
-    def pick_up(self, on_hands: bool = True):
-        """Gets called upon a player performing the pickup action. Gives back a plate (possibly with ingredient.
-
-        Returns: A plate possibly with an ingredient on it.
-
-        """
-        give_player = self.occupied_by.pop()
-        if not self.occupied_by:
-            self.occupied_by.append(Plate())
-        return give_player
-
-    def drop_off(self, item: Item) -> Item | None:
-        """Takes the ingredient dropped of by the player.
-
-        Args:
-            item: The ingredient to be placed on the counter.
-        """
-        if isinstance(item, Plate):
-            if self.occupied_by[-1].holds:
-                return item
-            self.occupied_by.append(item)
-            return None
-        if self.occupied_by[-1].can_combine(item):
-            return self.occupied_by[-1].combine(item)
-        return item
-
-    def can_drop_off(self, item: Item) -> bool:
-        """Checks whether an ingredient by the player can be dropped of.
-
-        Args:
-            item: The ingredient for which to check, if it can be placed on the counter.
-
-        Returns: True if the ingredient can be placed on the counter, False if not.
-
-        """
-        # possibility to drop off empty plate on empty plate return
-        return (
-            isinstance(self.occupied_by[-1], Plate) and isinstance(item, Plate)
-        ) or self.occupied_by[-1].can_combine(item)
-
-
 class Dispenser(Counter):
     def __init__(self, pos, dispensing):
         self.dispensing = dispensing
         super().__init__(
             pos,
-            CuttableItem(
-                name=self.dispensing,
-                finished_name=item_loopkup[self.dispensing]["finished_name"],
-            ),
+            self.dispensing.create_item(),
         )
 
     def pick_up(self, on_hands: bool = True):
-        returned = CuttableItem(
-            name=self.dispensing,
-            finished_name=item_loopkup[self.dispensing]["finished_name"],
-        )
-        print(self.occupied_by)
-        return returned
+        new_dispensing = self.dispensing.create_item()
+        if self.occupied_by != new_dispensing:
+            old_dispensing = self.occupied_by
+            self.occupied_by = new_dispensing
+            return old_dispensing
+        return new_dispensing
 
     def drop_off(self, item: Item) -> Item | None:
-        return None
+        if self.occupied_by.can_combine(item):
+            return self.occupied_by.combine(item)
 
     def can_drop_off(self, item: Item) -> bool:
-        return False
+        return self.occupied_by.can_combine(item)
 
     def __repr__(self):
-        return f"{self.dispensing}Dispenser"
+        return f"{self.dispensing.name}Dispenser"
 
 
 class Trash(Counter):
@@ -234,11 +184,6 @@ class Trash(Counter):
 
 
 class Stove(Counter):
-    def __init__(self, pos: npt.NDArray[float], occupied_by: Optional[Item] = ...):
-        if occupied_by is ...:
-            occupied_by = Pot()
-        super().__init__(pos, occupied_by)
-
     def progress(self):
         """Called by environment step function for time progression"""
         if (
diff --git a/overcooked_simulator/game_content/item_combinations.yaml b/overcooked_simulator/game_content/item_combinations.yaml
deleted file mode 100644
index dd20f8914e520dce0148565b825dc83bbfefaf6e..0000000000000000000000000000000000000000
--- a/overcooked_simulator/game_content/item_combinations.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
-Tomato:
-  type: Cuttable
-  needs: Tomato
-  finished_name: ChoppedTomato
-
-Lettuce:
-  type: Cuttable
-  needs: Lettuce
-  finished_name: ChoppedLettuce
-
-RawPatty:
-  type: Cuttable
-  needs: RawSteak
-
-Burger:
-  type: Meal
-  needs: [ Bun, ChoppedLettuce, ChoppedTomato, CookedPatty ]
-
-Salad:
-  type: Meal
-  needs: [ Lettuce, Tomato ]
-
-TomatoSoup:
-  type: Soup
-  needs: [ Tomato, Tomato, Tomato ]
-
diff --git a/overcooked_simulator/game_content/item_info.yaml b/overcooked_simulator/game_content/item_info.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..a2df260836a62fed83a32d3e35d84fd78f74a96e
--- /dev/null
+++ b/overcooked_simulator/game_content/item_info.yaml
@@ -0,0 +1,37 @@
+Tomato:
+  type: Ingredient
+  needs: Tomato
+  is_cuttable: True
+  steps_needed: 500
+
+Lettuce:
+  type: Ingredient
+  needs: Lettuce
+  is_cuttable: True
+  steps_needed: 500
+
+RawPatty:
+  type: Ingredient
+  needs: RawSteak
+
+Burger:
+  type: Meal
+  needs: [ Bun, ChoppedLettuce, ChoppedTomato, CookedPatty ]
+
+Salad:
+  type: Meal
+  needs: [ Lettuce, Tomato ]
+  equipment: Plate
+
+TomatoSoup:
+  type: Meal
+  finished_progress_name: TomatoSoup
+  steps_needed: 500
+  needs: [ Tomato, Tomato, Tomato ]
+  equipment: Pot
+
+Plate:
+  type: Equipment
+
+Pot:
+  type: Equipment
diff --git a/overcooked_simulator/game_items.py b/overcooked_simulator/game_items.py
index 822ce543bcb5a21e41d8bd7d099ff914820fd931..a0356cccbf381077270518ed5c418cc3f3b64b6c 100644
--- a/overcooked_simulator/game_items.py
+++ b/overcooked_simulator/game_items.py
@@ -1,25 +1,85 @@
 from __future__ import annotations
 
-import yaml
-
-from overcooked_simulator import ROOT_DIR
-
-with open(ROOT_DIR / "game_content/item_combinations.yaml", "r") as file:
-    item_loopkup = yaml.safe_load(file)
+import dataclasses
+from enum import Enum
+
+
+class ItemType(Enum):
+    Ingredient = "Ingredient"
+    Meal = "Meal"
+    Equipment = "Equipment"
+
+
+@dataclasses.dataclass
+class ItemInfo:
+    type: ItemType = dataclasses.field(compare=False)
+    name: str = dataclasses.field(compare=True)
+    is_cuttable: bool = dataclasses.field(compare=False, default=False)
+    steps_needed: int = dataclasses.field(compare=False, default=0)
+    finished_progress_name: str = dataclasses.field(compare=False, default="Chopped*")
+    needs: list[ItemInfo] = dataclasses.field(compare=False, default_factory=list)
+    equipment: ItemInfo | None = dataclasses.field(compare=False, default=None)
+
+    _start_items: list[ItemInfo] = dataclasses.field(
+        compare=False, default_factory=list
+    )
+
+    def __post_init__(self):
+        self.type = ItemType(self.type)
+
+    def get_finished_name(self):
+        if "*" in self.finished_progress_name:
+            return self.finished_progress_name.replace("*", self.name)
+        return self.name
+
+    def create_item(self) -> Item:
+        match self.type:
+            case ItemType.Ingredient:
+                if self.is_cuttable:
+                    return CuttableItem(
+                        name=self.name,
+                        finished=False,
+                        steps_needed=self.steps_needed,
+                        finished_name=self.get_finished_name(),
+                        item_info=self,
+                    )
+                return Item(name=self.name, item_info=self)
+            case ItemType.Equipment:
+                if self.name == "Plate":
+                    return Plate(item_info=self)
+                if self.name == "Pot":
+                    return CookingEquipment(name=self.name, item_info=self)
+            case ItemType.Meal:
+                return Meal(
+                    name=self.name,
+                    finished=False,
+                    steps_needed=self.steps_needed,
+                    finished_name=self.get_finished_name(),
+                    item_info=self,
+                )
+
+    def add_start_item_to_equipment(self, start_item: ItemInfo):
+        self._start_items.append(start_item)
+
+    def can_start_meal(self, start_item: Item):
+        # TODO check specific order / only specific start items
+        return any(
+            [start_item.name == s for meal in self._start_items for s in meal.needs]
+        )
 
-combinables = {}
-for key, item in item_loopkup.items():
-    if item["type"] in ["Meal", "Soup"]:
-        combinables[key] = item["needs"]
-print(combinables)
+    def start_meal(self, start_item: Item) -> Item:
+        for meal in self._start_items:
+            for s in meal.needs:
+                if s == start_item.name:
+                    return meal.create_item()
 
 
 class Item:
     """Base class for game items which can be held by a player."""
 
-    def __init__(self, name: str = None, *args, **kwargs):
-        super().__init__(*args, **kwargs)
+    def __init__(self, name: str, item_info: ItemInfo, *args, **kwargs):
         self.name = self.__class__.__name__ if name is None else name
+        self.item_info = item_info
 
     def can_combine(self, other):
         return False
@@ -30,14 +90,17 @@ class Item:
     def __repr__(self):
         return f"{self.name}({self.extra_repr})"
 
+    def __eq__(self, other):
+        return other and self.name == other.name
+
     @property
     def extra_repr(self):
         return ""
 
 
 class Plate(Item):
-    def __init__(self, holds: Item = None):
-        super().__init__()
+    def __init__(self, holds: Item = None, *args, **kwargs):
+        super().__init__(*args, name="Plate", **kwargs)
         self.clean = True
         self.holds = holds
 
@@ -54,6 +117,14 @@ class Plate(Item):
             return other
         self.holds = other
 
+    def __eq__(self, other):
+        return (
+            other
+            and self.name == other.name
+            and self.clean == other.clean
+            and self.holds == other.holds
+        )
+
     @property
     def extra_repr(self):
         return self.holds
@@ -106,18 +177,6 @@ class ProgressibleItem(Item):
 class CuttableItem(ProgressibleItem):
     """Class of item which can be processed by the cutting board."""
 
-    pass
-
-
-class Tomato(CuttableItem, Item):
-    """Item class representing a tomato. Can be cut on the cutting board"""
-
-    def can_combine(self, other):
-        return False
-
-    def __init__(self):
-        super().__init__(steps_needed=500)
-
 
 class CookingEquipment(Item):
     def __init__(self, content: Meal = None, *args, **kwargs):
@@ -127,13 +186,13 @@ class CookingEquipment(Item):
     def can_combine(self, other):
         if self.content is None:
             # TODO check other is start of a meal, create meal
-            return True
+            return self.item_info.can_start_meal(other)
         return self.content.can_combine(other)
 
     def combine(self, other):
         if not self.content:
             # find starting meal for other
-            self.content = Soup()
+            self.content = self.item_info.start_meal(other)
         self.content.combine(other)
 
     def can_progress(self, counter_type="Stove") -> bool:
@@ -163,46 +222,36 @@ class CookingEquipment(Item):
         return self.content
 
 
-class Pot(CookingEquipment):
-    def __init__(self, holds: Meal = None):
-        super().__init__()
-
-
-class Meal(Item):
+class Meal(ProgressibleItem):
     def __init__(self, parts=None, *args, **kwargs):
         super().__init__(*args, **kwargs)
         self.parts = [] if parts is None else parts
         # self.rules ...
 
     def can_combine(self, other) -> bool:
-        return (
-            isinstance(other, Tomato)
-            and all([isinstance(o, other.__class__) for o in self.parts])
-            and len(self.parts) < 3
-        )  # rules
+        if other and not self.finished:
+            satisfied = [False for _ in range(len(self.parts))]
+            for n in self.item_info.needs:
+                for i, p in enumerate(self.parts):
+                    if not satisfied[i] and p.name == n:
+                        satisfied[i] = True
+                        break
+                else:
+                    if n == other.name:
+                        return True
+        return False
 
     def combine(self, other):
         self.parts.append(other)
 
-    @property
-    def extra_repr(self):
-        return self.parts
-
-
-class Soup(ProgressibleItem, Meal):
-    def __init__(self):
-        super().__init__(finished_name="CookedSoup")
-
     def can_progress(self) -> bool:
-        return len(self.parts) == 3
-
-
-class Pan(CookingEquipment):
-    def __init__(self):
-        super().__init__(steps_needed=500)
+        return self.item_info.steps_needed and len(self.item_info.needs) == len(
+            self.parts
+        )
 
-    def can_combine(self, other):
-        return False
+    def finished_call(self):
+        super().finished_call()
 
-    def combine(self, other):
-        pass
+    @property
+    def extra_repr(self):
+        return self.parts
diff --git a/overcooked_simulator/main.py b/overcooked_simulator/main.py
index 9524338b1a38d513a42ddfd60d82ef74d9093a67..05d6f0c0c62a9c2ba0df540c5a8b68c15d185a11 100644
--- a/overcooked_simulator/main.py
+++ b/overcooked_simulator/main.py
@@ -1,5 +1,4 @@
 import sys
-from pathlib import Path
 
 import numpy as np
 import pygame
@@ -11,7 +10,7 @@ from overcooked_simulator.simulation_runner import Simulator
 
 
 def main():
-    simulator = Simulator(Path(ROOT_DIR, "game_content/layouts", "basic.layout"), 600)
+    simulator = Simulator(ROOT_DIR / "game_content" / "layouts" / "basic.layout", 600)
     player_one_name = "p1"
     player_two_name = "p2"
     simulator.register_player(Player(player_one_name, np.array([350.0, 200.0])))
diff --git a/overcooked_simulator/overcooked_environment.py b/overcooked_simulator/overcooked_environment.py
index 4161e1d47e3559f2c265d22ddeb5a34c1b75be61..23ecb41865b64bc16b88061e13f47abf028912c1 100644
--- a/overcooked_simulator/overcooked_environment.py
+++ b/overcooked_simulator/overcooked_environment.py
@@ -2,6 +2,10 @@ from __future__ import annotations
 
 from typing import TYPE_CHECKING
 
+import yaml
+
+from overcooked_simulator.game_items import ItemInfo
+
 if TYPE_CHECKING:
     from overcooked_simulator.player import Player
 from pathlib import Path
@@ -14,7 +18,6 @@ from overcooked_simulator.counters import (
     Trash,
     Dispenser,
     ServingWindow,
-    PlateReturn,
     Stove,
 )
 
@@ -55,10 +58,12 @@ class Environment:
     # TODO Abstract base class for different environments
     """
 
-    def __init__(self, layout_path):
+    def __init__(self, layout_path, item_info_path):
         self.players: dict[str, Player] = {}
         self.counter_side_length: int = 40
         self.layout_path: Path = layout_path
+        self.item_info_path: Path = item_info_path
+        self.item_info = self.load_item_info()
 
         self.game_score = GameScore()
         self.SYMBOL_TO_CHARACTER_MAP = {
@@ -66,11 +71,14 @@ class Environment:
             "B": CuttingBoard,
             "X": Trash,
             "W": lambda pos: ServingWindow(pos, self.game_score),
-            "T": lambda pos: Dispenser(pos, "Tomato"),
-            "L": lambda pos: Dispenser(pos, "Lettuce"),
-            "P": PlateReturn,
+            "T": lambda pos: Dispenser(pos, self.item_info["Tomato"]),
+            "L": lambda pos: Dispenser(pos, self.item_info["Lettuce"]),
+            "P": lambda pos: Dispenser(pos, self.item_info["Plate"]),
             "E": None,
-            "U": Stove,  # Stove with pot: U because it looks like a pot
+            "U": lambda pos: Stove(
+                pos,
+                self.item_info["Pot"].create_item(),
+            ),  # Stove with pot: U because it looks like a pot
         }
 
         self.counters: list[Counter] = self.create_counters(self.layout_path)
@@ -78,6 +86,18 @@ class Environment:
         self.world_width: int = 800
         self.world_height: int = 600
 
+    def load_item_info(self) -> dict[str, ItemInfo]:
+        with open(self.item_info_path, "r") as file:
+            item_lookup = yaml.safe_load(file)
+        for item_name in item_lookup:
+            item_lookup[item_name] = ItemInfo(name=item_name, **item_lookup[item_name])
+
+        for item_name, item_info in item_lookup.items():
+            if item_info.equipment:
+                item_info.equipment = item_lookup[item_info.equipment]
+                item_info.equipment.add_start_item_to_equipment(item_info)
+        return item_lookup
+
     def create_counters(self, layout_file: Path):
         """Creates layout of kitchen counters in the environment based on layout file.
         Counters are arranged in a fixed size grid starting at [0,0]. The center of the first counter is at
diff --git a/overcooked_simulator/pygame_gui/pygame_gui.py b/overcooked_simulator/pygame_gui/pygame_gui.py
index a086961c2a3a3cb0e131ed4d23799386dd6b5315..1540d625229a6b262baa886f9c20ccda11d34397 100644
--- a/overcooked_simulator/pygame_gui/pygame_gui.py
+++ b/overcooked_simulator/pygame_gui/pygame_gui.py
@@ -1,5 +1,4 @@
 import math
-from pathlib import Path
 
 import numpy as np
 import numpy.typing as npt
@@ -13,7 +12,6 @@ from overcooked_simulator.game_items import (
     Item,
     CookingEquipment,
     Meal,
-    Soup,
 )
 from overcooked_simulator.overcooked_environment import Action
 from overcooked_simulator.simulation_runner import Simulator
@@ -84,10 +82,10 @@ class PyGameGUI:
             for player_name, keys in zip(self.player_names, self.player_keys)
         ]
 
-        with open(ROOT_DIR / "pygame_gui/visualization.yaml", "r") as file:
+        with open(ROOT_DIR / "pygame_gui" / "visualization.yaml", "r") as file:
             self.visualization_config = yaml.safe_load(file)
 
-        self.images_path = Path(ROOT_DIR, "pygame_gui", "images")
+        self.images_path = ROOT_DIR / "pygame_gui" / "images"
 
     def send_action(self, action: Action):
         """Sends an action to the game environment.
@@ -179,7 +177,7 @@ class PyGameGUI:
             else:
                 img_path = self.visualization_config["Cook"]["parts"][0]["path"]
                 image = pygame.image.load(
-                    ROOT_DIR / Path("pygame_gui") / img_path
+                    ROOT_DIR / "pygame_gui" / img_path
                 ).convert_alpha()
                 rel_x, rel_y = player.facing_direction
                 angle = -np.rad2deg(math.atan2(rel_y, rel_x)) + 90
@@ -206,7 +204,7 @@ class PyGameGUI:
             part_type = part["type"]
             if part_type == "image":
                 image = pygame.image.load(
-                    ROOT_DIR / Path("pygame_gui") / parts[0]["path"]
+                    ROOT_DIR / "pygame_gui" / parts[0]["path"]
                 ).convert_alpha()
 
                 size = parts[0]["size"]
@@ -267,25 +265,13 @@ class PyGameGUI:
         if isinstance(item, CookingEquipment) and item.content:
             self.draw_item(pos, item.content)
 
-        if isinstance(item, Meal) and item.parts and not isinstance(item, Soup):
-            for i, o in enumerate(item.parts):
-                self.draw_item(np.abs([pos[0], pos[1] - (i * 5)]), o)
-
-        if isinstance(item, Soup) and item.parts:
-            if item.parts:
-                if not item.finished:
-                    match len(item.parts):
-                        case 1:
-                            pygame.draw.circle(self.screen, RED, pos, 4)
-                        case 2:
-                            pygame.draw.circle(self.screen, RED, pos, 8)
-                        case 3:
-                            pygame.draw.circle(self.screen, RED, pos, 12)
-                else:
+        if isinstance(item, Meal):
+            if "Soup" in item.name:
+                if item.finished:
                     if item.name in self.visualization_config:
                         image = pygame.image.load(
                             ROOT_DIR
-                            / Path("pygame_gui")
+                            / "pygame_gui"
                             / self.visualization_config[item.name]["parts"][0]["path"]
                         ).convert_alpha()
 
@@ -295,6 +281,17 @@ class PyGameGUI:
                         rect = image.get_rect()
                         rect.center = pos
                         self.screen.blit(image, rect)
+                elif item.parts:
+                    match len(item.parts):
+                        case 1:
+                            pygame.draw.circle(self.screen, RED, pos, 4)
+                        case 2:
+                            pygame.draw.circle(self.screen, RED, pos, 8)
+                        case 3:
+                            pygame.draw.circle(self.screen, RED, pos, 12)
+            else:
+                for i, o in enumerate(item.parts):
+                    self.draw_item(np.abs([pos[0], pos[1] - (i * 5)]), o)
 
     def draw_progress_bar(self, pos, current, needed):
         """Visualize progress of progressing item as a green bar under the item."""
diff --git a/overcooked_simulator/pygame_gui/visualization.yaml b/overcooked_simulator/pygame_gui/visualization.yaml
index 758f7372a2bc73e12d5c3e88f34fc1ca70d282d5..67c6f455e8e089ab03a173898b71fa5649ebc7a9 100644
--- a/overcooked_simulator/pygame_gui/visualization.yaml
+++ b/overcooked_simulator/pygame_gui/visualization.yaml
@@ -17,7 +17,7 @@ CuttingBoard:
       center_offset: [ +6, -8 ]
       color: [ 120, 120, 120 ]
 
-PlateReturn:
+PlateDispenser:
   parts:
     - type: "rect"
       height: 38
@@ -129,7 +129,7 @@ TomatoSoup3of3:
       radius: 12
       color: [ 255, 0, 0 ]
 
-CookedSoup:
+TomatoSoup:
   parts:
     - type: "image"
       path: "images/tomato_soup.png"
diff --git a/overcooked_simulator/simulation_runner.py b/overcooked_simulator/simulation_runner.py
index 31d9d3774dd5425a6579ba8ea4ded7e1ac8dfa26..6c9cf367ca96478711ec0aa717bc16c22a88f0d9 100644
--- a/overcooked_simulator/simulation_runner.py
+++ b/overcooked_simulator/simulation_runner.py
@@ -1,6 +1,7 @@
 import time
 from threading import Thread
 
+from overcooked_simulator import ROOT_DIR
 from overcooked_simulator.overcooked_environment import Environment, Action
 from overcooked_simulator.player import Player
 
@@ -19,12 +20,17 @@ class Simulator(Thread):
     ```
     """
 
-    def __init__(self, env_layout_path, frequency: int):
+    def __init__(
+        self,
+        env_layout_path,
+        frequency: int,
+        item_info_path=ROOT_DIR / "game_content" / "item_info.yaml",
+    ):
         self.finished: bool = False
 
         self.step_frequency: int = frequency
         self.preferred_sleep_time_ns: float = 1e9 / self.step_frequency
-        self.env: Environment = Environment(env_layout_path)
+        self.env: Environment = Environment(env_layout_path, item_info_path)
 
         super().__init__()
 
diff --git a/tests/test_start.py b/tests/test_start.py
index e23635c29b84f756973474fedaf8b15309f6517b..729bd43e31fa644dea3cf52504c3e068e60035c4 100644
--- a/tests/test_start.py
+++ b/tests/test_start.py
@@ -1,5 +1,4 @@
 import time
-from pathlib import Path
 
 import numpy as np
 import pytest
@@ -11,7 +10,7 @@ from overcooked_simulator.overcooked_environment import Action
 from overcooked_simulator.player import Player
 from overcooked_simulator.simulation_runner import Simulator
 
-layouts_folder = Path(ROOT_DIR / "game_content/layouts")
+layouts_folder = ROOT_DIR / "game_content" / "layouts"
 
 
 @pytest.fixture(autouse=True)