feat: Twoblock block

Added new Sprite for creating two block tall blocks. This sprite takes 2
sprite files (top, bottom). Also created new annotation that handles
breaking and placing two block tall blocks and also manages the sprite
state.
This commit is contained in:
Jakub Žitník 2025-03-19 13:04:36 +01:00
parent a610bcf3ca
commit bc33c5f531
Signed by: jzitnik
GPG Key ID: C577A802A6AF4EF3
13 changed files with 282 additions and 5 deletions

View File

@ -1,7 +1,6 @@
package cz.jzitnik.game;
import cz.jzitnik.game.sprites.*;
import cz.jzitnik.game.sprites.SimpleSprite;
import cz.jzitnik.tui.Sprite;
import cz.jzitnik.tui.SpriteList;
@ -42,6 +41,7 @@ public class SpriteLoader {
OXEYE_DAISY,
AZURE_BLUET,
LILY_OF_THE_VALLEY,
LILAC,
// Ores
COAL_ORE,
@ -94,6 +94,9 @@ public class SpriteLoader {
ITEM_OBSIDIAN,
ITEM_SAND,
// Tall Flowers
ITEM_LILAC,
// Ore Items
ITEM_COAL_ORE,
ITEM_IRON_ORE,
@ -224,6 +227,7 @@ public class SpriteLoader {
SPRITES_MAP.put(SPRITES.BLUE_ORCHID, new SimpleSprite("blue_orchid.ans"));
SPRITES_MAP.put(SPRITES.OXEYE_DAISY, new SimpleSprite("oxeye_daisy.ans"));
SPRITES_MAP.put(SPRITES.AZURE_BLUET, new SimpleSprite("azure_bluet.ans"));
SPRITES_MAP.put(SPRITES.LILAC, new TwoBlockSprite("lilac_top.ans", "lilac_bottom.ans"));
// Grass
SPRITES_MAP.put(SPRITES.GRASS_BUSH, new SimpleSprite("grass_bush.ans"));
@ -262,6 +266,9 @@ public class SpriteLoader {
SPRITES_MAP.put(SPRITES.ITEM_OBSIDIAN, new SimpleSprite("items/obsidian.ans"));
SPRITES_MAP.put(SPRITES.ITEM_SAND, new SimpleSprite("items/sand.ans"));
// Tall flowers
SPRITES_MAP.put(SPRITES.ITEM_LILAC, new SimpleSprite("items/lilac.ans"));
// Ore Items
SPRITES_MAP.put(SPRITES.ITEM_COAL_ORE, new SimpleSprite("items/coal_ore.ans"));
SPRITES_MAP.put(SPRITES.ITEM_IRON_ORE, new SimpleSprite("items/iron_ore.ans"));

View File

@ -0,0 +1,12 @@
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.METHOD)
public @interface BlockBreakAction {
String value();
}

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

View File

@ -1,5 +1,6 @@
package cz.jzitnik.game.blocks;
import cz.jzitnik.game.annotations.BlockBreakAction;
import cz.jzitnik.game.annotations.RightClickLogic;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.Game;
@ -251,6 +252,7 @@ public class Furnace implements RightClickHandler, Serializable {
thread2.start();
}
@BlockBreakAction("furnace")
public void breakBlock(Game game) {
for (var i = 0; i < items.length; i++) {
if (items[i] == null) {

View File

@ -0,0 +1,21 @@
package cz.jzitnik.game.entities.items.registry.blocks.grassy.flowers;
import cz.jzitnik.game.SpriteLoader;
import cz.jzitnik.game.annotations.BlockRegistry;
import cz.jzitnik.game.annotations.BreakableByWater;
import cz.jzitnik.game.annotations.BreaksFalling;
import cz.jzitnik.game.annotations.PlaceOnSolid;
import cz.jzitnik.game.annotations.TwoblockBlock;
import cz.jzitnik.game.entities.Block;
@PlaceOnSolid
@BreakableByWater
@BreaksFalling
@TwoblockBlock
@BlockRegistry(value = "lilac")
public class LilacBlock extends Block {
public LilacBlock() {
super("lilac", SpriteLoader.SPRITES.LILAC, 0);
setGhost(true);
}
}

View File

@ -0,0 +1,13 @@
package cz.jzitnik.game.entities.items.registry.items.grassy.flowers;
import cz.jzitnik.game.SpriteLoader;
import cz.jzitnik.game.annotations.ItemRegistry;
import cz.jzitnik.game.entities.items.Item;
import cz.jzitnik.game.entities.items.ItemType;
@ItemRegistry("lilac")
public class LilacItem extends Item {
public LilacItem() {
super("lilac", "Lilac", ItemType.BLOCK, SpriteLoader.SPRITES.ITEM_LILAC);
}
}

View File

@ -34,7 +34,10 @@ public class Generation {
world[terrainHeight[256] - 1][256].add(steveBlock2);
world[terrainHeight[256] - 2][256].add(steveBlock);
game.getInventory().addItem(ItemBlockSupplier.getItem("lava_bucket"));
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"));
}
private static void initializeWorld(List<Block>[][] world) {

View File

@ -0,0 +1,49 @@
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

@ -4,6 +4,7 @@ import cz.jzitnik.game.Game;
import cz.jzitnik.game.annotations.*;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.entities.items.ItemBlockSupplier;
import cz.jzitnik.game.sprites.TwoBlockSprite;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
@ -22,11 +23,47 @@ public class CustomAnnotationHandler implements CustomPlaceHandler {
@Override
public boolean place(Game game, int x, int y) {
boolean place = true;
boolean customPlace = false;
if (clazz.isAnnotationPresent(PlaceOnSolid.class)) {
return placeOnSolid(game, x, y);
place = placeOnSolid(game, x, y);
}
if (place && clazz.isAnnotationPresent(TwoblockBlock.class)) {
var blocksTop = game.getWorld()[y - 1][x];
if (!blocksTop.stream().allMatch(block -> block.getBlockId().equals("air"))) {
place = false;
}
}
return defaultPlaceHandler.place(game, x, y);
if (place && clazz.isAnnotationPresent(TwoblockBlock.class)) {
var inventory = game.getInventory();
var blocks = game.getWorld()[y][x];
var blocksTop = game.getWorld()[y - 1][x];
Block block = inventory.getItemInHand().get().getBlock().get();
block.setSpriteState(TwoBlockSprite.TwoBlockSpriteState.BOTTOM);
blocks.add(block);
blocks.removeAll(blocks.stream().filter(Block::isFlowing).toList());
Block block2 = ItemBlockSupplier.getBlock(block.getBlockId());
block2.setSpriteState(TwoBlockSprite.TwoBlockSpriteState.TOP);
blocksTop.add(block2);
blocksTop.removeAll(blocksTop.stream().filter(Block::isFlowing).toList());
inventory.decreaseItemInHand();
customPlace = true;
return true;
}
if (!customPlace && place) {
return defaultPlaceHandler.place(game, x, y);
}
return false;
}
@Override
@ -63,6 +100,16 @@ public class CustomAnnotationHandler implements CustomPlaceHandler {
dropDefault = blockDropPercentage(game, x, y);
}
if (clazz.isAnnotationPresent(TwoblockBlock.class)) {
var blocksTop = game.getWorld()[y - 1][x];
if (blocksTop.stream().anyMatch(i -> !i.getBlockId().equals("air") && !i.isMob())) {
blocksTop.removeAll(blocksTop.stream().filter(i -> !i.getBlockId().equals("air") && !i.isMob()).toList());
} else {
var blocksBottom = game.getWorld()[y + 1][x];
blocksBottom.removeAll(blocksBottom.stream().filter(i -> !i.getBlockId().equals("air") && !i.isMob()).toList());
}
}
defaultPlaceHandler.mine(game, x, y);
return dropDefault;
@ -83,7 +130,7 @@ public class CustomAnnotationHandler implements CustomPlaceHandler {
return false;
}
return defaultPlaceHandler.place(game, x, y);
return true;
}
private void resetDataOnMine(Game game, int x, int y) {

View File

@ -0,0 +1,37 @@
package cz.jzitnik.game.sprites;
import cz.jzitnik.tui.ResourceLoader;
import cz.jzitnik.tui.Sprite;
import java.util.Optional;
public class TwoBlockSprite extends Sprite {
public enum TwoBlockSpriteState {
TOP, BOTTOM
}
private final String top;
private final String bottom;
public TwoBlockSprite(String top, String bottom) {
this.top = top;
this.bottom = bottom;
}
public String getSprite() {
return getSprite(TwoBlockSpriteState.TOP);
}
public String getSprite(Enum key) {
return ResourceLoader.loadResource(switch (key) {
case TwoBlockSpriteState.TOP -> top;
case TwoBlockSpriteState.BOTTOM -> bottom;
default -> throw new RuntimeException("Invalid state!");
});
}
@Override
public Optional<Class<Enum>> getStates() {
return Optional.empty();
}
}

View File

@ -0,0 +1,25 @@
                                                  
                                                  
                                                 
                                      
                                      
                                      
                                         
                                         
                                          
                                 
                                  
                                    
                                    
                                   
                                      
                                      
                                         
                                          
                                            
                                     
                                      
                                        
                                       
                                          
                                          

View File

@ -0,0 +1,25 @@
                                 
                                  
                                     
                                 
                                   
                                          
                                            
                                            
                                     
                                      
                                   
                                 
                                   
                                   
                                  
                                  
                                      
                                      
                                        
                                  
                                         
                                         
                                               
                                               
                                               

View File

@ -0,0 +1,25 @@