From 9fc72a67ec2af1dcacf6bbd84fac72e57138909b Mon Sep 17 00:00:00 2001
From: jzitnik-dev <email@jzitnik.dev>
Date: Fri, 21 Mar 2025 07:28:10 +0100
Subject: [PATCH] feat: Implemented farmland

---
 src/main/java/cz/jzitnik/game/Game.java       | 10 ++-
 .../java/cz/jzitnik/game/SpriteLoader.java    |  2 +
 .../cz/jzitnik/game/annotations/Farmable.java | 11 +++
 .../cz/jzitnik/game/annotations/ToolUse.java  | 15 ++++
 .../jzitnik/game/entities/Dependencies.java   |  2 +
 .../registry/blocks/blocks/FarmlandBlock.java | 19 +++++
 .../registry/blocks/grassy/WheatBlock.java    | 19 +++++
 .../jzitnik/game/generation/Generation.java   |  6 +-
 .../handlers/BreakBlockActionProvider.java    | 49 ------------
 .../handlers/events/EventHandlerProvider.java |  2 +-
 .../handlers/place/FarmlandPlaceHandler.java  | 23 ++++++
 .../game/handlers/tooluse/ToolUseHandler.java |  7 ++
 .../handlers/tooluse/ToolUseProvider.java     | 47 +++++++++++
 .../handlers/tooluse/handlers/HoeUse.java     | 33 ++++++++
 .../logic/services/farmable/FarmableData.java |  5 ++
 .../logic/services/farmland/FarmlandData.java | 18 +++++
 .../services/farmland/FarmlandLogic.java      | 80 +++++++++++++++++++
 .../services/grass/GrassGrowingLogic.java     |  1 -
 .../cz/jzitnik/game/sprites/Farmland.java     | 30 +++++++
 src/main/resources/textures/farmland.ans      | 25 ++++++
 src/main/resources/textures/farmland_wet.ans  | 25 ++++++
 21 files changed, 373 insertions(+), 56 deletions(-)
 create mode 100644 src/main/java/cz/jzitnik/game/annotations/Farmable.java
 create mode 100644 src/main/java/cz/jzitnik/game/annotations/ToolUse.java
 create mode 100644 src/main/java/cz/jzitnik/game/entities/items/registry/blocks/blocks/FarmlandBlock.java
 create mode 100644 src/main/java/cz/jzitnik/game/entities/items/registry/blocks/grassy/WheatBlock.java
 delete mode 100644 src/main/java/cz/jzitnik/game/handlers/BreakBlockActionProvider.java
 create mode 100644 src/main/java/cz/jzitnik/game/handlers/events/handlers/place/FarmlandPlaceHandler.java
 create mode 100644 src/main/java/cz/jzitnik/game/handlers/tooluse/ToolUseHandler.java
 create mode 100644 src/main/java/cz/jzitnik/game/handlers/tooluse/ToolUseProvider.java
 create mode 100644 src/main/java/cz/jzitnik/game/handlers/tooluse/handlers/HoeUse.java
 create mode 100644 src/main/java/cz/jzitnik/game/logic/services/farmable/FarmableData.java
 create mode 100644 src/main/java/cz/jzitnik/game/logic/services/farmland/FarmlandData.java
 create mode 100644 src/main/java/cz/jzitnik/game/logic/services/farmland/FarmlandLogic.java
 create mode 100644 src/main/java/cz/jzitnik/game/sprites/Farmland.java
 create mode 100644 src/main/resources/textures/farmland.ans
 create mode 100644 src/main/resources/textures/farmland_wet.ans

diff --git a/src/main/java/cz/jzitnik/game/Game.java b/src/main/java/cz/jzitnik/game/Game.java
index 048b458..10145c3 100644
--- a/src/main/java/cz/jzitnik/game/Game.java
+++ b/src/main/java/cz/jzitnik/game/Game.java
@@ -375,7 +375,15 @@ public class Game extends AutoTransientSupport {
 
         if (!blocks.stream().allMatch(block -> block.getBlockId().equals("air") || block.isFlowing()
                 || block.getClass().isAnnotationPresent(BreaksByPlace.class))) {
-            RightClickHandlerProvider.handle(x, y, this, screenRenderer);
+            boolean toolUsed = false;
+            if (inventory.getItemInHand().isPresent()) {
+                var item = inventory.getItemInHand().get();
+                toolUsed = gameStates.dependencies.toolUseProvider.handle(item.getType(), this, x, y);
+            }
+
+            if (!toolUsed) {
+                RightClickHandlerProvider.handle(x, y, this, screenRenderer);
+            }
             screenRenderer.render(this);
             return;
         }
diff --git a/src/main/java/cz/jzitnik/game/SpriteLoader.java b/src/main/java/cz/jzitnik/game/SpriteLoader.java
index 1faca99..234da0d 100644
--- a/src/main/java/cz/jzitnik/game/SpriteLoader.java
+++ b/src/main/java/cz/jzitnik/game/SpriteLoader.java
@@ -15,6 +15,7 @@ public class SpriteLoader {
         WATER,
         LAVA,
         DIRT,
+        FARMLAND,
         GRASS,
         STONE,
         BEDROCK,
@@ -180,6 +181,7 @@ public class SpriteLoader {
         SPRITES_MAP.put(SPRITES.WATER, new Water());
         SPRITES_MAP.put(SPRITES.LAVA, new Lava());
         SPRITES_MAP.put(SPRITES.DIRT, new SimpleSprite("dirt.ans"));
+        SPRITES_MAP.put(SPRITES.FARMLAND, new Farmland());
         SPRITES_MAP.put(SPRITES.GRASS, new SimpleSprite("grass.ans"));
         SPRITES_MAP.put(SPRITES.STONE, new SimpleSprite("stone.ans"));
         SPRITES_MAP.put(SPRITES.BEDROCK, new SimpleSprite("bedrock.ans"));
diff --git a/src/main/java/cz/jzitnik/game/annotations/Farmable.java b/src/main/java/cz/jzitnik/game/annotations/Farmable.java
new file mode 100644
index 0000000..ed571ed
--- /dev/null
+++ b/src/main/java/cz/jzitnik/game/annotations/Farmable.java
@@ -0,0 +1,11 @@
+package cz.jzitnik.game.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.annotation.ElementType;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface Farmable {
+}
diff --git a/src/main/java/cz/jzitnik/game/annotations/ToolUse.java b/src/main/java/cz/jzitnik/game/annotations/ToolUse.java
new file mode 100644
index 0000000..30e0da8
--- /dev/null
+++ b/src/main/java/cz/jzitnik/game/annotations/ToolUse.java
@@ -0,0 +1,15 @@
+package cz.jzitnik.game.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import cz.jzitnik.game.entities.items.ItemType;
+
+import java.lang.annotation.ElementType;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface ToolUse {
+    ItemType value();
+}
diff --git a/src/main/java/cz/jzitnik/game/entities/Dependencies.java b/src/main/java/cz/jzitnik/game/entities/Dependencies.java
index c5248fc..4391c66 100644
--- a/src/main/java/cz/jzitnik/game/entities/Dependencies.java
+++ b/src/main/java/cz/jzitnik/game/entities/Dependencies.java
@@ -4,6 +4,7 @@ import cz.jzitnik.game.GameSaver;
 import cz.jzitnik.game.handlers.events.EventHandlerProvider;
 import cz.jzitnik.game.handlers.pickup.PickupHandlerProvider;
 import cz.jzitnik.game.handlers.place.PlaceHandler;
+import cz.jzitnik.game.handlers.tooluse.ToolUseProvider;
 import cz.jzitnik.game.mobs.EntityHurtAnimation;
 import cz.jzitnik.game.mobs.EntityKill;
 import cz.jzitnik.game.smelting.Smelting;
@@ -16,4 +17,5 @@ public class Dependencies {
     public GameSaver gameSaver = new GameSaver();
     public EventHandlerProvider eventHandlerProvider = new EventHandlerProvider();
     public Smelting smelting = new Smelting();
+    public ToolUseProvider toolUseProvider = new ToolUseProvider();
 }
diff --git a/src/main/java/cz/jzitnik/game/entities/items/registry/blocks/blocks/FarmlandBlock.java b/src/main/java/cz/jzitnik/game/entities/items/registry/blocks/blocks/FarmlandBlock.java
new file mode 100644
index 0000000..50b9778
--- /dev/null
+++ b/src/main/java/cz/jzitnik/game/entities/items/registry/blocks/blocks/FarmlandBlock.java
@@ -0,0 +1,19 @@
+package cz.jzitnik.game.entities.items.registry.blocks.blocks;
+
+import cz.jzitnik.game.SpriteLoader;
+import cz.jzitnik.game.annotations.BlockRegistry;
+import cz.jzitnik.game.annotations.ResetDataOnMine;
+import cz.jzitnik.game.entities.Block;
+import cz.jzitnik.game.entities.items.ItemType;
+import cz.jzitnik.game.logic.services.farmland.FarmlandData;
+
+import java.util.ArrayList;
+
+@ResetDataOnMine
+@BlockRegistry(value = "farmland", drops = "dirt")
+public class FarmlandBlock extends Block {
+    public FarmlandBlock() {
+        super("farmland", SpriteLoader.SPRITES.FARMLAND, 1, ItemType.SHOVEL, new ArrayList<>());
+        setData(new FarmlandData());
+    }
+}
diff --git a/src/main/java/cz/jzitnik/game/entities/items/registry/blocks/grassy/WheatBlock.java b/src/main/java/cz/jzitnik/game/entities/items/registry/blocks/grassy/WheatBlock.java
new file mode 100644
index 0000000..9305611
--- /dev/null
+++ b/src/main/java/cz/jzitnik/game/entities/items/registry/blocks/grassy/WheatBlock.java
@@ -0,0 +1,19 @@
+package cz.jzitnik.game.entities.items.registry.blocks.grassy;
+
+import cz.jzitnik.game.SpriteLoader;
+import cz.jzitnik.game.annotations.BlockRegistry;
+import cz.jzitnik.game.annotations.Farmable;
+import cz.jzitnik.game.annotations.ResetDataOnMine;
+import cz.jzitnik.game.entities.Block;
+import cz.jzitnik.game.logic.services.farmable.FarmableData;
+
+@ResetDataOnMine
+@Farmable
+@BlockRegistry("wheat")
+public class WheatBlock extends Block {
+    public WheatBlock() {
+        super("weat", SpriteLoader.SPRITES.OAK_SAPLING, 0);
+        setData(new FarmableData());
+        setGhost(true);
+    }
+}
diff --git a/src/main/java/cz/jzitnik/game/generation/Generation.java b/src/main/java/cz/jzitnik/game/generation/Generation.java
index 4eb591c..ef547f5 100644
--- a/src/main/java/cz/jzitnik/game/generation/Generation.java
+++ b/src/main/java/cz/jzitnik/game/generation/Generation.java
@@ -34,10 +34,8 @@ public class Generation {
         world[terrainHeight[256] - 1][256].add(steveBlock2);
         world[terrainHeight[256] - 2][256].add(steveBlock);
 
-        game.getInventory().addItem(ItemBlockSupplier.getItem("furnace"));
-        game.getInventory().addItem(ItemBlockSupplier.getItem("coal"));
-        game.getInventory().addItem(ItemBlockSupplier.getItem("oak_log"));
-        game.getInventory().addItem(ItemBlockSupplier.getItem("diamond_pickaxe"));
+        game.getInventory().addItem(ItemBlockSupplier.getItem("wooden_hoe"));
+        game.getInventory().addItem(ItemBlockSupplier.getItem("water_bucket"));
     }
 
     private static void initializeWorld(List<Block>[][] world) {
diff --git a/src/main/java/cz/jzitnik/game/handlers/BreakBlockActionProvider.java b/src/main/java/cz/jzitnik/game/handlers/BreakBlockActionProvider.java
deleted file mode 100644
index 3a12be1..0000000
--- a/src/main/java/cz/jzitnik/game/handlers/BreakBlockActionProvider.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package cz.jzitnik.game.handlers;
-
-import java.lang.reflect.Method;
-import java.util.HashMap;
-import java.util.Set;
-import java.util.function.Function;
-
-import org.reflections.Reflections;
-import org.reflections.scanners.Scanners;
-import org.reflections.util.ConfigurationBuilder;
-
-import cz.jzitnik.game.Game;
-import cz.jzitnik.game.annotations.MineEventHandler;
-
-public class BreakBlockActionProvider {
-    private HashMap<String, Function<Game, Void>> actions = new HashMap<>();
-
-    public BreakBlockActionProvider() {
-        register();
-    }
-
-    private void register() {
-        Reflections reflections = new Reflections(
-                new ConfigurationBuilder()
-                        .forPackage("cz.jzitnik.game.handlers.events.handlers.mine")
-                        .addScanners(Scanners.MethodsAnnotated)
-        );
-        Set<Method> mineHandlers = reflections.getMethodsAnnotatedWith(MineEventHandler.class);
-
-        for (Method method : mineHandlers) {
-            if (method.getParameterCount() == 1 &&
-                    method.getParameterTypes()[0] == Game.class)
-                try {
-                    Object instance = method.getDeclaringClass().getDeclaredConstructor().newInstance();
-                    Function<ScreenRenderer, Game, Integer, Integer> handler = (screenRenderer, game, x, y) -> {
-                        try {
-                            method.invoke(instance, screenRenderer, game, x, y);
-                        } catch (Exception e) {
-                            e.printStackTrace();
-                        }
-                    };
-                    this.mineHandlers.add(handler);
-                } catch (Exception e) {
-                    e.printStackTrace();
-                }
-            }
-        }
-    }
-}
diff --git a/src/main/java/cz/jzitnik/game/handlers/events/EventHandlerProvider.java b/src/main/java/cz/jzitnik/game/handlers/events/EventHandlerProvider.java
index 1b894ba..c939408 100644
--- a/src/main/java/cz/jzitnik/game/handlers/events/EventHandlerProvider.java
+++ b/src/main/java/cz/jzitnik/game/handlers/events/EventHandlerProvider.java
@@ -41,7 +41,7 @@ public class EventHandlerProvider {
     private void registerHandlers() {
         Reflections reflections = new Reflections(
                 new ConfigurationBuilder()
-                        .forPackage("cz.jzitnik.game.handlers.events.handlers.mine")
+                        .forPackage("cz.jzitnik.game.handlers.events.handlers")
                         .addScanners(Scanners.MethodsAnnotated)
         );
         Set<Method> mineHandlers = reflections.getMethodsAnnotatedWith(MineEventHandler.class);
diff --git a/src/main/java/cz/jzitnik/game/handlers/events/handlers/place/FarmlandPlaceHandler.java b/src/main/java/cz/jzitnik/game/handlers/events/handlers/place/FarmlandPlaceHandler.java
new file mode 100644
index 0000000..b1829c5
--- /dev/null
+++ b/src/main/java/cz/jzitnik/game/handlers/events/handlers/place/FarmlandPlaceHandler.java
@@ -0,0 +1,23 @@
+package cz.jzitnik.game.handlers.events.handlers.place;
+
+import cz.jzitnik.game.Game;
+import cz.jzitnik.game.annotations.PlaceEventHandler;
+import cz.jzitnik.game.entities.items.ItemBlockSupplier;
+import cz.jzitnik.game.handlers.tooluse.handlers.HoeUse;
+import cz.jzitnik.tui.ScreenRenderer;
+
+public class FarmlandPlaceHandler {
+    @PlaceEventHandler
+    public void handle(ScreenRenderer screenRenderer, Game game, int x, int y) {
+        var world = game.getWorld();
+
+        if (world[y][x].stream().noneMatch(HoeUse::isBlock)) {
+            return;
+        }
+
+        if (world[y + 1][x].stream().anyMatch(block -> block.getBlockId().equals("farmland"))) {
+            world[y + 1][x].add(ItemBlockSupplier.getBlock("dirt"));
+            world[y + 1][x].removeIf(block -> block.getBlockId().equals("farmland"));
+        }
+    }
+}
diff --git a/src/main/java/cz/jzitnik/game/handlers/tooluse/ToolUseHandler.java b/src/main/java/cz/jzitnik/game/handlers/tooluse/ToolUseHandler.java
new file mode 100644
index 0000000..b3468fd
--- /dev/null
+++ b/src/main/java/cz/jzitnik/game/handlers/tooluse/ToolUseHandler.java
@@ -0,0 +1,7 @@
+package cz.jzitnik.game.handlers.tooluse;
+
+import cz.jzitnik.game.Game;
+
+public interface ToolUseHandler {
+    void handle(Game game, int x, int y);
+}
diff --git a/src/main/java/cz/jzitnik/game/handlers/tooluse/ToolUseProvider.java b/src/main/java/cz/jzitnik/game/handlers/tooluse/ToolUseProvider.java
new file mode 100644
index 0000000..4ceb11d
--- /dev/null
+++ b/src/main/java/cz/jzitnik/game/handlers/tooluse/ToolUseProvider.java
@@ -0,0 +1,47 @@
+package cz.jzitnik.game.handlers.tooluse;
+
+import java.util.HashMap;
+import java.util.Set;
+
+import cz.jzitnik.game.Game;
+import cz.jzitnik.game.annotations.ToolUse;
+import cz.jzitnik.game.entities.items.ItemType;
+
+import org.reflections.Reflections;
+
+public class ToolUseProvider {
+    public final HashMap<ItemType, ToolUseHandler> handler = new HashMap<>();
+
+    public ToolUseProvider() {
+        registerHandlers();
+    }
+
+    public boolean handle(ItemType itemType, Game game, int x, int y) {
+        if (!handler.containsKey(itemType)) {
+            return false;
+        }
+
+        handler.get(itemType).handle(game, x, y);
+
+        return true;
+    }
+
+    private void registerHandlers() {
+        Reflections reflections = new Reflections("cz.jzitnik.game.handlers.tooluse.handlers");
+        Set<Class<?>> handlerClasses = reflections.getTypesAnnotatedWith(ToolUse.class);
+
+        for (Class<?> clazz : handlerClasses) {
+            if (ToolUseHandler.class.isAssignableFrom(clazz)) {
+                try {
+                    ToolUseHandler handlerInstance = (ToolUseHandler) clazz.getDeclaredConstructor()
+                            .newInstance();
+                    ToolUse annotation = clazz.getAnnotation(ToolUse.class);
+                    ItemType key = annotation.value();
+                    handler.put(key, handlerInstance);
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/cz/jzitnik/game/handlers/tooluse/handlers/HoeUse.java b/src/main/java/cz/jzitnik/game/handlers/tooluse/handlers/HoeUse.java
new file mode 100644
index 0000000..c01ee03
--- /dev/null
+++ b/src/main/java/cz/jzitnik/game/handlers/tooluse/handlers/HoeUse.java
@@ -0,0 +1,33 @@
+package cz.jzitnik.game.handlers.tooluse.handlers;
+
+import cz.jzitnik.game.Game;
+import cz.jzitnik.game.annotations.Farmable;
+import cz.jzitnik.game.annotations.ToolUse;
+import cz.jzitnik.game.entities.Block;
+import cz.jzitnik.game.entities.items.ItemBlockSupplier;
+import cz.jzitnik.game.entities.items.ItemType;
+import cz.jzitnik.game.handlers.tooluse.ToolUseHandler;
+
+@ToolUse(ItemType.HOE)
+public class HoeUse implements ToolUseHandler{
+
+	@Override
+	public void handle(Game game, int x, int y) {
+        var blocks = game.getWorld()[y][x];
+
+        if (!blocks.stream().anyMatch(block -> block.getBlockId().equals("dirt") || block.getBlockId().equals("grass"))) {
+            return;
+        }
+
+        if (game.getWorld()[y-1][x].stream().anyMatch(block -> isBlock(block))) {
+            return;
+        }
+
+        blocks.removeIf(block -> block.getBlockId().equals("dirt") || block.getBlockId().equals("grass"));
+        blocks.add(ItemBlockSupplier.getBlock("farmland"));
+	}
+    
+    public static boolean isBlock(Block block) {
+        return !block.getBlockId().equals("air") && !block.isMob() && !block.getClass().isAnnotationPresent(Farmable.class);
+    }
+}
diff --git a/src/main/java/cz/jzitnik/game/logic/services/farmable/FarmableData.java b/src/main/java/cz/jzitnik/game/logic/services/farmable/FarmableData.java
new file mode 100644
index 0000000..d69b24c
--- /dev/null
+++ b/src/main/java/cz/jzitnik/game/logic/services/farmable/FarmableData.java
@@ -0,0 +1,5 @@
+package cz.jzitnik.game.logic.services.farmable;
+
+public class FarmableData {
+
+}
diff --git a/src/main/java/cz/jzitnik/game/logic/services/farmland/FarmlandData.java b/src/main/java/cz/jzitnik/game/logic/services/farmland/FarmlandData.java
new file mode 100644
index 0000000..e1f9336
--- /dev/null
+++ b/src/main/java/cz/jzitnik/game/logic/services/farmland/FarmlandData.java
@@ -0,0 +1,18 @@
+package cz.jzitnik.game.logic.services.farmland;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+
+@Getter
+@Setter
+public class FarmlandData implements Serializable {
+    private int age = 0;
+    private int dryAge = 0;
+    private boolean watered = false;
+
+    public void increaseAge() {
+        age++;
+    }
+}
diff --git a/src/main/java/cz/jzitnik/game/logic/services/farmland/FarmlandLogic.java b/src/main/java/cz/jzitnik/game/logic/services/farmland/FarmlandLogic.java
new file mode 100644
index 0000000..be20e92
--- /dev/null
+++ b/src/main/java/cz/jzitnik/game/logic/services/farmland/FarmlandLogic.java
@@ -0,0 +1,80 @@
+package cz.jzitnik.game.logic.services.farmland;
+
+import cz.jzitnik.game.Game;
+import cz.jzitnik.game.annotations.CustomLogic;
+import cz.jzitnik.game.entities.items.ItemBlockSupplier;
+import cz.jzitnik.game.logic.CustomLogicInterface;
+import cz.jzitnik.game.sprites.Farmland.FarmlandState;
+
+@CustomLogic
+public class FarmlandLogic implements CustomLogicInterface {
+    private static final int RADIUS = 50;
+    private static final int WATER_DISTANCE = 4;
+    private static final int DRY_TO_DIRT_THRESHOLD = 11;
+    private static final int AGE_THRESHOLD = 5;
+
+    @Override
+    public void nextIteration(Game game) {
+        int[] data = game.getPlayerCords();
+        var world = game.getWorld();
+        int playerX = data[0];
+        int playerY = data[1];
+
+        int startX = Math.max(0, playerX - RADIUS);
+        int startY = Math.max(0, playerY - RADIUS);
+        int endX = Math.min(world[0].length - 1, playerX + RADIUS);
+        int endY = Math.min(world.length - 1, playerY + RADIUS);
+
+        for (int x = startX; x <= endX; x++) {
+            for (int y = startY; y <= endY; y++) {
+                var blocks = world[y][x];
+                var farmlandOptional = blocks.stream()
+                        .filter(block -> block.getBlockId().equals("farmland"))
+                        .findFirst();
+
+                if (farmlandOptional.isEmpty()) continue;
+
+                var farmland = farmlandOptional.get();
+                var farmlandData = (FarmlandData) farmland.getData();
+
+                boolean waterNearby = false;
+
+                for (int dx = -WATER_DISTANCE; dx <= WATER_DISTANCE && !waterNearby; dx++) {
+                    for (int dy = -WATER_DISTANCE; dy <= WATER_DISTANCE && !waterNearby; dy++) {
+                        int checkX = x + dx;
+                        int checkY = y + dy;
+
+                        if (checkX < 0 || checkX >= world[0].length || checkY < 0 || checkY >= world.length) continue;
+
+                        var nearbyBlocks = world[checkY][checkX];
+                        if (nearbyBlocks.stream().anyMatch(b -> b.getBlockId().equals("water"))) {
+                            waterNearby = true;
+                        }
+                    }
+                }
+
+                if (farmlandData.isWatered() != waterNearby) {
+                    farmlandData.setAge(farmlandData.getAge() + 1);
+                    if (farmlandData.getAge() >= AGE_THRESHOLD) {
+                        farmlandData.setWatered(waterNearby);
+                        farmland.setSpriteState(waterNearby ? FarmlandState.WET : FarmlandState.NORMAL);
+                        farmlandData.setAge(0);
+                    }
+                } else {
+                    farmlandData.setAge(0);
+                }
+
+                if (!farmlandData.isWatered()) {
+                    farmlandData.setDryAge(farmlandData.getDryAge() + 1);
+                    if (farmlandData.getDryAge() >= DRY_TO_DIRT_THRESHOLD) {
+                        blocks.remove(farmland);
+                        blocks.add(ItemBlockSupplier.getBlock("dirt"));
+                        farmlandData.setDryAge(0);
+                    }
+                } else {
+                    farmlandData.setDryAge(0);
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/cz/jzitnik/game/logic/services/grass/GrassGrowingLogic.java b/src/main/java/cz/jzitnik/game/logic/services/grass/GrassGrowingLogic.java
index fc257d8..08b5347 100644
--- a/src/main/java/cz/jzitnik/game/logic/services/grass/GrassGrowingLogic.java
+++ b/src/main/java/cz/jzitnik/game/logic/services/grass/GrassGrowingLogic.java
@@ -11,7 +11,6 @@ import java.util.*;
 @CustomLogic
 public class GrassGrowingLogic implements CustomLogicInterface {
     private static final int RADIUS = 35;
-    private final Random random = new Random();
 
     @Override
     public void nextIteration(Game game) {
diff --git a/src/main/java/cz/jzitnik/game/sprites/Farmland.java b/src/main/java/cz/jzitnik/game/sprites/Farmland.java
new file mode 100644
index 0000000..8504fe4
--- /dev/null
+++ b/src/main/java/cz/jzitnik/game/sprites/Farmland.java
@@ -0,0 +1,30 @@
+package cz.jzitnik.game.sprites;
+
+import cz.jzitnik.tui.ResourceLoader;
+import cz.jzitnik.tui.Sprite;
+
+import java.util.Optional;
+
+public class Farmland extends Sprite {
+    public enum FarmlandState {
+        NORMAL,
+        WET
+    }
+
+    public String getSprite() {
+        return getSprite(FarmlandState.NORMAL);
+    }
+
+    public String getSprite(Enum e) {
+        return ResourceLoader.loadResource(switch (e) {
+            case FarmlandState.NORMAL -> "farmland.ans";
+            case FarmlandState.WET -> "farmland_wet.ans";
+            default -> throw new IllegalStateException("Unexpected value: " + e);
+        });
+    }
+
+    @Override
+    public Optional<Class<FarmlandState>> getStates() {
+        return Optional.of(FarmlandState.class);
+    }
+}
diff --git a/src/main/resources/textures/farmland.ans b/src/main/resources/textures/farmland.ans
new file mode 100644
index 0000000..ee4901c
--- /dev/null
+++ b/src/main/resources/textures/farmland.ans
@@ -0,0 +1,25 @@
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
diff --git a/src/main/resources/textures/farmland_wet.ans b/src/main/resources/textures/farmland_wet.ans
new file mode 100644
index 0000000..b68ee91
--- /dev/null
+++ b/src/main/resources/textures/farmland_wet.ans
@@ -0,0 +1,25 @@
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+                                                  
+