feat: Implemented farmland

This commit is contained in:
Jakub Žitník 2025-03-21 07:28:10 +01:00
parent bc33c5f531
commit 9fc72a67ec
Signed by: jzitnik
GPG Key ID: C577A802A6AF4EF3
21 changed files with 373 additions and 56 deletions

View File

@ -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;
}

View File

@ -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"));

View File

@ -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 {
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -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();
}
}
}
}
}

View File

@ -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);

View File

@ -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"));
}
}
}

View File

@ -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);
}

View File

@ -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();
}
}
}
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,5 @@
package cz.jzitnik.game.logic.services.farmable;
public class FarmableData {
}

View File

@ -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++;
}
}

View File

@ -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);
}
}
}
}
}

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -0,0 +1,25 @@
                    
                         
                       
                        
                         
                       
                         
                         
                        
                         
                         
                      
                       
                       
                       
                         
                         
                         
                         
                       
                         
                        
                        
                        
                      

View File

@ -0,0 +1,25 @@