diff --git a/src/main/java/cz/jzitnik/game/annotations/PlaceOnSolid.java b/src/main/java/cz/jzitnik/game/annotations/PlaceOnSolid.java new file mode 100644 index 0000000..8fe27b8 --- /dev/null +++ b/src/main/java/cz/jzitnik/game/annotations/PlaceOnSolid.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; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface PlaceOnSolid { +} diff --git a/src/main/java/cz/jzitnik/game/annotations/ResetDataOnMine.java b/src/main/java/cz/jzitnik/game/annotations/ResetDataOnMine.java new file mode 100644 index 0000000..6765d80 --- /dev/null +++ b/src/main/java/cz/jzitnik/game/annotations/ResetDataOnMine.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; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface ResetDataOnMine { +} diff --git a/src/main/java/cz/jzitnik/game/entities/items/registry/blocks/OakSaplingBlock.java b/src/main/java/cz/jzitnik/game/entities/items/registry/blocks/OakSaplingBlock.java index 16ceb06..fb69791 100644 --- a/src/main/java/cz/jzitnik/game/entities/items/registry/blocks/OakSaplingBlock.java +++ b/src/main/java/cz/jzitnik/game/entities/items/registry/blocks/OakSaplingBlock.java @@ -2,11 +2,15 @@ package cz.jzitnik.game.entities.items.registry.blocks; import cz.jzitnik.game.SpriteLoader; import cz.jzitnik.game.annotations.BlockRegistry; +import cz.jzitnik.game.annotations.PlaceOnSolid; +import cz.jzitnik.game.annotations.ResetDataOnMine; import cz.jzitnik.game.annotations.Sapling; import cz.jzitnik.game.entities.Block; import cz.jzitnik.game.logic.services.saplings.SaplingData; @Sapling +@PlaceOnSolid +@ResetDataOnMine @BlockRegistry("oak_sapling") public class OakSaplingBlock extends Block { public OakSaplingBlock() { diff --git a/src/main/java/cz/jzitnik/game/handlers/place/CustomAnnotationHandler.java b/src/main/java/cz/jzitnik/game/handlers/place/CustomAnnotationHandler.java new file mode 100644 index 0000000..b6a5c97 --- /dev/null +++ b/src/main/java/cz/jzitnik/game/handlers/place/CustomAnnotationHandler.java @@ -0,0 +1,66 @@ +package cz.jzitnik.game.handlers.place; + +import cz.jzitnik.game.Game; +import cz.jzitnik.game.annotations.PlaceOnSolid; +import cz.jzitnik.game.annotations.ResetDataOnMine; +import cz.jzitnik.game.entities.Block; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +public class CustomAnnotationHandler implements CustomPlaceHandler { + private final Class clazz; + private final DefaultPlaceHandler defaultPlaceHandler = new DefaultPlaceHandler(); + + public CustomAnnotationHandler(Class clazz) { + this.clazz = clazz; + } + + @Override + public boolean place(Game game, int x, int y) { + if (clazz.isAnnotationPresent(PlaceOnSolid.class)) { + return placeOnSolid(game, x, y); + } + + return defaultPlaceHandler.place(game, x, y); + } + + @Override + public boolean mine(Game game, int x, int y) { + if (clazz.isAnnotationPresent(ResetDataOnMine.class)) { + resetDataOnMine(game, x, y); + } + + return defaultPlaceHandler.mine(game, x, y); + } + + + + private boolean placeOnSolid(Game game, int x, int y) { + var blocksBottom = game.getWorld()[y + 1][x]; + + if (blocksBottom.stream().allMatch(Block::isGhost)) { + return false; + } + + return defaultPlaceHandler.place(game, x, y); + } + + private void resetDataOnMine(Game game, int x, int y) { + var blocks = game.getWorld()[y][x].stream().filter(i -> !i.getBlockId().equals("air")).toList(); + + for (Block block : blocks) { + if (block.getData() == null) { + continue; + } + + try { + Constructor constructor = block.getData().getClass().getDeclaredConstructor(); + Object object = constructor.newInstance(); + block.setData(object); + } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | + InvocationTargetException _) { + } + } + } +} diff --git a/src/main/java/cz/jzitnik/game/handlers/place/PlaceHandler.java b/src/main/java/cz/jzitnik/game/handlers/place/PlaceHandler.java index ba3303d..9292e8e 100644 --- a/src/main/java/cz/jzitnik/game/handlers/place/PlaceHandler.java +++ b/src/main/java/cz/jzitnik/game/handlers/place/PlaceHandler.java @@ -3,6 +3,9 @@ package cz.jzitnik.game.handlers.place; import java.util.HashMap; import java.util.Set; +import cz.jzitnik.game.annotations.BlockRegistry; +import cz.jzitnik.game.annotations.PlaceOnSolid; +import cz.jzitnik.game.annotations.ResetDataOnMine; import org.reflections.Reflections; public class PlaceHandler { @@ -43,5 +46,21 @@ public class PlaceHandler { } } } + + Reflections reflections2 = new Reflections("cz.jzitnik.game.entities.items.registry.blocks"); + Set> blocks = reflections2 + .getTypesAnnotatedWith(BlockRegistry.class); + + for (Class clazz : blocks) { + if (clazz.isAnnotationPresent(PlaceOnSolid.class) || clazz.isAnnotationPresent(ResetDataOnMine.class)) { + try { + var annotation = clazz.getAnnotation(BlockRegistry.class); + var id = annotation.value(); + placeHandlerList.put(id, new CustomAnnotationHandler(clazz)); + } catch (Exception e) { + e.printStackTrace(); + } + } + } } } diff --git a/src/test/java/cz/jzitnik/game/handlers/place/PlaceHandlerTest.java b/src/test/java/cz/jzitnik/game/handlers/place/PlaceHandlerTest.java new file mode 100644 index 0000000..510a963 --- /dev/null +++ b/src/test/java/cz/jzitnik/game/handlers/place/PlaceHandlerTest.java @@ -0,0 +1,87 @@ +package cz.jzitnik.game.handlers.place; + +import cz.jzitnik.game.annotations.BlockRegistry; +import cz.jzitnik.game.annotations.PlaceOnSolid; +import cz.jzitnik.game.annotations.ResetDataOnMine; +import cz.jzitnik.game.entities.items.ItemBlockSupplier; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.reflections.Reflections; + +import java.util.Arrays; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +class PlaceHandlerTest { + @Test + @DisplayName("All blocks in annotation @PlaceHandler must exist") + void allBlocksMustExist() { + Reflections reflections = new Reflections("cz.jzitnik.game.handlers.place.handlers"); + Set> handlerClasses = reflections + .getTypesAnnotatedWith(cz.jzitnik.game.annotations.PlaceHandler.class); + + for (Class clazz : handlerClasses) { + cz.jzitnik.game.annotations.PlaceHandler annotation = clazz + .getAnnotation(cz.jzitnik.game.annotations.PlaceHandler.class); + var id = annotation.value(); + assertDoesNotThrow(() -> ItemBlockSupplier.getBlock(id), "Block " + id + " does not exist but is in annotation @PlaceHandler in class " + clazz.getName()); + } + } + + @Test + @DisplayName("All blocks annotated with @PlaceOnSolid must be also annotated with @BlockRegistry") + void placeOnSolidTest() { + Reflections reflections = new Reflections("cz.jzitnik.game.entities.items.registry.blocks"); + Set> handlerClasses = reflections + .getTypesAnnotatedWith(PlaceOnSolid.class); + + for (Class clazz : handlerClasses) { + boolean hasAnnotation = clazz.isAnnotationPresent(BlockRegistry.class); + assertTrue(hasAnnotation, "Class " + clazz.getName() + " is annotated with @PlaceOnSolid but does not have annotation @BlockRegistry"); + } + } + + @Test + @DisplayName("All blocks annotated with @ResetDataOnMine must be also annotated with @BlockRegistry") + void resetDataOnMineTest() { + Reflections reflections = new Reflections("cz.jzitnik.game.entities.items.registry.blocks"); + Set> handlerClasses = reflections + .getTypesAnnotatedWith(ResetDataOnMine.class); + + for (Class clazz : handlerClasses) { + boolean hasAnnotation = clazz.isAnnotationPresent(BlockRegistry.class); + assertTrue(hasAnnotation, "Class " + clazz.getName() + " is annotated with @ResetDataOnMine but does not have annotation @BlockRegistry"); + } + } + + @Test + @DisplayName("All blocks annotated with @ResetDataOnMine must have data class with default constructor") + void dataOnMineHasDataTest() { + Reflections reflections = new Reflections("cz.jzitnik.game.entities.items.registry.blocks"); + Set> handlerClasses = reflections + .getTypesAnnotatedWith(ResetDataOnMine.class); + + for (Class clazz : handlerClasses) { + var annotation = clazz.getAnnotation(BlockRegistry.class); + var block = ItemBlockSupplier.getBlock(annotation.value()); + assertNotNull(block.getData(), "Block " + clazz.getName() + " annotated with @ResetDataOnMine does not have default Data set."); + } + } + + @Test + @DisplayName("All blocks annotated with @ResetDataOnMine must have data class with default constructor") + void dataOnMineDefaultConstructorTest() { + Reflections reflections = new Reflections("cz.jzitnik.game.entities.items.registry.blocks"); + Set> handlerClasses = reflections + .getTypesAnnotatedWith(ResetDataOnMine.class); + + for (Class clazz : handlerClasses) { + var annotation = clazz.getAnnotation(BlockRegistry.class); + var block = ItemBlockSupplier.getBlock(annotation.value()); + var dataClass = block.getData().getClass(); + var hasDefaultConstructor = Arrays.stream(dataClass.getDeclaredConstructors()).anyMatch(constructor -> constructor.getParameterCount() == 0); + assertTrue(hasDefaultConstructor, "Block " + clazz.getName() + " annotated with @ResetDataOnMine does not have Data that has default constructor."); + } + } +} \ No newline at end of file